Ensuring proper configuration evaluation order in plugins is a pain and unreliable • Existing workarounds are: – Use Project.afterEvaluate() – Use ConventionMapping – Wrap configuration in a closure – Lazy GString 4
Existing plugins are a black box • Rules allow for Gradle to better understand what your plugin is doing – Proper configuration ordering – Lazy configuration evaluation 5
are managed by Gradle. Includes the elements themselves, their relationships and the rules that act on them. Configuration Rule: Discrete unit of code that adds to, updates, or reads from the model. 6
rule acts on. Typically this means modifying the object in some way. Rule Inputs: The object(s) that the rule reads from. These are immutable and fully configured by all other rules that declared them as a subject. 7
Person { String firstName String lastName } @Model public Person person() { Person p = new Person(); p.setFirstName("John"); p.setLastName("Smith"); return p; }
rule method name is used as model element name. Override this by supplying a value to @Model annotation. 11 @Model("person") public Person createPersonModel() { return new Person(); }
@Mutate, @Defaults or @Finalize • Must return void • Must define at least one argument (subject) • May define 0 or more additional arguments (inputs) 12
of binding. Searches the root of the current scope for model element of given type. By Path: Use @Path annotation to explicitly bind to a particular node in the model. 13
accepts a single argument, which is the rule subject, bound by the subject type. 14 @Mutate public void updatePersonName(Person person) { person.setFirstName("Bob"); }
Person first() { return new Person(); } @Model public Person second() { return new Person(); } @Mutate public void updateSecondPerson(@Path("second") Person person) { person.setFirstName("Alice"); }
path for input type where multiple binding candidates exist will result in ambiguity error. 16 A problem occurred evaluating root project 'gradle'. > Failed to apply plugin [class 'Rules'] > There is a problem with model rule Rules#updateSecondPerson(Person). > Type-only model reference of type Person (parameter 1) is ambiguous as multiple model elements are available for this type: - first (created by: Rules#first()) - second (created by: Rules#second())
works with generic types. 17 @Model public GenericType<String> first() { return new GenericType<String>(); } @Model public GenericType<Boolean> second() { return new GenericType<Boolean>(); } @Mutate public void updateStringTypeParam(GenericType<String> s) { // mutate subject }
are required for rule subjects and inputs. Omitting type arguments will result in binding error. 18 A problem occurred evaluating root project 'gradle'. > Failed to apply plugin [class 'Rules'] > Rules#updateStringTypeParam(GenericType) is not a valid model rule method: raw type GenericType used for parameter 1 (all type parameters must be specified of parameterized type)
Essentially, anything in the model: – Model elements added by plugins – Elements added via the model DSL – Implicit elements, such as tasks – Anything that shows up in model report 19
inputs. These should be treated as effectively immutable. This rule creates a model dependency between the subject and inputs. 20 @Mutate public void createTask(ModelMap<Task> tasks, Person person) { tasks.create('printName') { doLast { println "$person.lastName, $person.firstName" } } }
22 @Model public Person first() { return new Person(); } @Model public Person second() { return new Person(); } @Mutate public void firstRule(@Path("first") Person first, @Path("second") Person second) {} @Mutate public void secondRule(@Path("second") Person second, @Path("first") Person first) {}
a subject and its inputs forms a dependency. Gradle will detect any cycles and report the error. 23 > A cycle has been detected in model rule dependencies. References forming the cycle: first \- Rules#firstRule(Person, Person) \- second \- Rules#secondRule(Person, Person) \- first
applied to a project in the form of a plugin. • RuleSource plugins are applied exactly the same as other plugins. • RuleSource plugins should extend RuleSource rather than implement Plugin<T>. 24
use a combination of rules and imperative code. 27 class MyPlugin implements Plugin<Project> { void apply(Project project) { project.getPluginManager().apply(JavaPlugin) } static class Rules extends RuleSource { @Model("person") public Person createPersonModel() { return new Person(); } } } apply plugin: MyPlugin
evaluated before a model element is used as an input • Ordering rules otherwise behave identically to @Mutate • Only a single annotation should be used • Model DSL rules are considered @Mutate 30
interface or abstract class • May implement Named • May inherit from other @Managed types • Can provide implementations for read-only derived properties • Define properties via JavaBean conventions (getters/setters) 33
elements of type ModelMap or ModelSet. Collection type must be a managed type. 41 @Model public void persons(ModelMap<Person> persons) { persons.create(“John”); }
collection properties. Only a getter may be declared. 42 @Managed interface Person { } @Managed interface Organization { ModelMap<Person> getMembers(); }
be defined via DSL inside a top-level model{} block. • Both creation and mutation rules supported • Reference rule inputs via $() syntax • Highly likely to change 45
treated identically to method rules. • All mutation rules, to include those defined via the DSL, are evaluated before a model element is used as an input to a rule. • No more afterEvaluate! 48
domain agnostic. • Need a more structured model targeting the software build domain. • Software is generally comprised of several components that produce binaries. • In Gradle, tasks are then used to create the binaries 51
designed to be extensible and adaptable to new domains. • Underlying model behind Gradle native and Play support • API includes specialty rules for registering custom language, component and binary types. 53
are used to create binaries for each component. 57 @ComponentBinaries void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleComponent component) { binaries.create("${component.name}Binary", SampleBinary) }
rules are used to create tasks for each binary. 58 @BinaryTasks void createBinaryTasks(ModelMap<Task> tasks, SampleBinary binary) { tasks.create("${binary.name}Task1") tasks.create("${binary.name}Task2") { dependsOn "${binary.name}Task1" } }
the gap between managed and non-managed model types • Expose project model to rules • Model DSL improvements • Configuration rules and software model still currently under active development 63