Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gradle Rules Based Model Configuration Workshop

Mark Vieira
September 22, 2015

Gradle Rules Based Model Configuration Workshop

Mark Vieira

September 22, 2015
Tweet

Transcript

  1. © Gradle Inc. 2015 Workshop Format • 2 x 90

    minute sessions • Hands-on labs • Pairing is encouraged • Ask questions anytime 2
  2. © Gradle Inc. 2015 Overview • Motivation • Configuration rules

    • Creating RuleSource plugins • The managed model • Using the model DSL • Software Model 3
  3. © Gradle Inc. 2015 Why do we need rules? •

    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
  4. © Gradle Inc. 2015 Why do we need rules? •

    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
  5. © Gradle Inc. 2015 Managed Model: Graph of objects that

    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
  6. © Gradle Inc. 2015 Rule Subject: The object that the

    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
  7. © Gradle Inc. 2015 Configuration Rules • Rules generally either

    create new model elements, or act on an existing element in the model. 
 • These rules are referred to as creation and mutation rules, respectively. 8
  8. © Gradle Inc. 2015 Creation Rules • Adds a new

    top-level element to the model • Method annotated with @Model • Should return the model element • All method arguments are considered inputs 9
  9. © Gradle Inc. 2015 Creating a model element 10 class

    Person {
 String firstName
 String lastName
 }
 
 @Model
 public Person person() {
 Person p = new Person();
 p.setFirstName("John");
 p.setLastName("Smith");
 
 return p;
 }
  10. © Gradle Inc. 2015 Creating a model element By default,

    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();
 }
  11. © Gradle Inc. 2015 Mutation Rules • Methods annotated with

    @Mutate, @Defaults or @Finalize • Must return void • Must define at least one argument (subject) • May define 0 or more additional arguments (inputs) 12
  12. © Gradle Inc. 2015 Reference Binding By Type: Default method

    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
  13. © Gradle Inc. 2015 Binding by Type Simplest mutation rule

    accepts a single argument, which is the rule subject, bound by the subject type. 14 @Mutate
 public void updatePersonName(Person person) {
 person.setFirstName("Bob");
 }
  14. © Gradle Inc. 2015 Binding by Path 15 @Model
 public

    Person first() {
 return new Person();
 }
 
 @Model
 public Person second() {
 return new Person();
 }
 
 @Mutate
 public void updateSecondPerson(@Path("second") Person person) {
 person.setFirstName("Alice");
 }
  15. © Gradle Inc. 2015 Binding by Path Failing to specify

    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())
  16. © Gradle Inc. 2015 Binding Generic Types Type binding also

    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
 }
  17. © Gradle Inc. 2015 Binding Generic Types Generic type arguments

    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)
  18. © Gradle Inc. 2015 What can I bind to? •

    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
  19. © Gradle Inc. 2015 Rule Inputs Arguments [1..n] are rule

    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"
 }
 }
 }
  20. © Gradle Inc. 2015 Rule Inputs The rule subject depends

    on its inputs. If a subject is itself an input to another rule, these dependencies are transitive. 21 Subject Input Input Input
  21. © Gradle Inc. 2015 Rule Inputs What’s the problem here?

    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) {}
  22. © Gradle Inc. 2015 Cycle Detection Remember, the relationship between

    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
  23. © Gradle Inc. 2015 RuleSource Plugins • Rules are typically

    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
  24. © Gradle Inc. 2015 RuleSource Plugins • Stateless collection of

    rule methods • Cannot declare constructor • Must extend directly from RuleSource • Cannot define instance variables • Methods cannot be overloaded • Cannot define generic type arguments 25
  25. © Gradle Inc. 2015 RuleSource Plugins 26 class Rules extends

    RuleSource {
 @Model
 public Person person() {
 return new Person();
 }
 
 @Mutate
 public void updatePerson(Person person) {
 person.setFirstName("John");
 person.setLastName("Smith");
 }
 }
 
 apply plugin: Rules
  26. © Gradle Inc. 2015 “Hybrid” Plugins You can continue to

    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
  27. © Gradle Inc. 2015 Rule Ordering 29 @Defaults @Mutate @Finalize

    @Validate Subject  Mutable yes yes yes no
  28. © Gradle Inc. 2015 Rule Ordering • All rules are

    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
  29. © Gradle Inc. 2015 Managed Types • Types whose implementations

    are provided by Gradle. • Allows for greater safety, control and diagnostics. • Improved performance. • Defined using the @Managed annotation 32
  30. © Gradle Inc. 2015 Managed Types • Must be an

    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
  31. © Gradle Inc. 2015 Managed Types • Allowed property types

    are: 34 ▪ String ▪ Boolean ▪ Character ▪ Integer ▪ Long ▪ Double ▪ BigInteger ▪ BigDecimal ▪ File
  32. © Gradle Inc. 2015 Managed Types • Instances of managed

    types are created using @Model rules • Rules for managed types return void • Managed type must be first argument • Other arguments are inputs 35
  33. © Gradle Inc. 2015 Managed Types 36 @Managed
 interface Person

    {
 String getName();
 void setName(String name);
 }
 
 @Model
 public void person(Person person) {
 person.setName("John")
 }
  34. © Gradle Inc. 2015 Managed Types Properties can be declared

    as unmanaged by adding @Unmanaged annotation to getter method. 37 @Managed
 interface Person {
 String getName();
 void setName(String name);
 @Unmanaged
 Date getBirthday();
 void setBirthday(Date birthday);
 }
  35. © Gradle Inc. 2015 Model Collections • Two new collection

    types ▪  ModelSet ▪  ModelMap
 • Behave  like  Set  and  Map
 • Collections  are  “model-­‐aware”  meaning   configuration  is  done  lazily  via  rules 39
  36. © Gradle Inc. 2015 Model Collections You can create model

    elements of type ModelMap or ModelSet. Collection type must be a managed type. 41 @Model
 public void persons(ModelMap<Person> persons) {
 persons.create(“John”);
 }
  37. © Gradle Inc. 2015 Model Collections Managed types can have

    collection properties. Only a getter may be declared. 42 @Managed
 interface Person {
 
 }
 
 @Managed
 interface Organization {
 ModelMap<Person> getMembers();
 }
  38. © Gradle Inc. 2015 Model Collections Use @Path to bind

    to individual managed type properties or collection elements. 43 @Model
 public void orgs(ModelMap<Organization> orgs) {
 orgs.create('gradle')
 }
 
 @Mutate
 public void updateGradle(@Path("orgs.gradle.members") ModelMap<Person> members) {
 members.create('alice')
 members.create('bob')
 }
  39. © Gradle Inc. 2015 Model DSL • Configuration rules can

    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
  40. © Gradle Inc. 2015 Model DSL 46 @Managed
 interface Greeting

    {
 String getMessage()
 void setMessage(String message)
 }
 
 @Model
 public void greeting(Greeting greeting) {}
 
 @Mutate
 public void createTask(ModelMap<Task> tasks, Greeting greeting) {
 tasks.create('printGreeting') {
 doLast { println greeting.message }
 }
 }
  41. © Gradle Inc. 2015 Model DSL (Mutation Rules) 47 model

    {
 greeting {
 message = 'Hello World!'
 }
 } $ gradle pG :printGreeting Hello World! BUILD SUCCESSFUL
  42. © Gradle Inc. 2015 Model DSL • DSL rules are

    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
  43. © Gradle Inc. 2015 Model DSL (Creation Rules) Note: Usage

    of $() requires passing –Dorg.gradle.model.dsl=true 49 model {
 greeting(Greeting) {
 message = 'Hello World!'
 }
 
 tasks {
 printGreeting(Task) {
 doLast {
 println $('greeting.message')
 }
 }
 }
 }
  44. © Gradle Inc. 2015 Software Model • Managed  model  is

     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
  45. © Gradle Inc. 2015 Binary Software Model 52 BinarySpec BinarySpec

    ComponentSpec LanguageSourceSet LanguageSourceSet Task Task Task Task Binary LanguageSourceSet
  46. © Gradle Inc. 2015 Software Model • Software  model  is

     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
  47. © Gradle Inc. 2015 Registering a custom language type 54

    interface CustomLanguageSourceSet extends LanguageSourceSet {} 
 class DefaultCustomLanguageSourceSet extends BaseLanguageSourceSet implements CustomLanguageSourceSet {}
 
 apply plugin: MySamplePlugin
 
 class MySamplePlugin extends RuleSource {
 @LanguageType
 void declareCustomLanguage( LanguageTypeBuilder<CustomLanguageSourceSet> builder) { 
 builder.setLanguageName("custom")
 builder.defaultImplementation(DefaultCustomLanguageSourceSet)
 }
 }
  48. © Gradle Inc. 2015 Registering a custom component type 55

    interface SampleComponent extends ComponentSpec {} 
 class DefaultSampleComponent extends BaseComponentSpec implements SampleComponent {}
 
 apply plugin: MySamplePlugin
 
 class MySamplePlugin extends RuleSource {
 @ComponentType
 void register(ComponentTypeBuilder<SampleComponent> builder) {
 builder.defaultImplementation(DefaultSampleComponent)
 }
 }
  49. © Gradle Inc. 2015 Registering a custom binary type 56

    interface SampleBinary extends BinarySpec {} 
 class DefaultSampleBinary extends BaseBinarySpec implements SampleBinary {}
 
 apply plugin: MySamplePlugin
 
 class MySamplePlugin extends RuleSource {
 @BinaryType
 void defineBinaryType(BinaryTypeBuilder<SampleBinary> builder) {
 builder.defaultImplementation(DefaultSampleBinary)
 }
 }
  50. © Gradle Inc. 2015 Mapping components to binaries @ComponentBinaries rules

     are  used  to   create  binaries  for  each  component. 57 @ComponentBinaries
 void createBinariesForSampleLibrary(ModelMap<SampleBinary> binaries, SampleComponent component) { 
 binaries.create("${component.name}Binary", SampleBinary)
 }
  51. © Gradle Inc. 2015 Creating tasks for building binaries @BinaryTasks

    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"
 }
 }
  52. © Gradle Inc. 2015 Software Model Components can then be

    configured via the model DSL. 59 model {
 components {
 foo(SampleComponent)
 bar(SampleComponent)
 }
 }
  53. © Gradle Inc. 2015 Software Model Tasks Lifecycle tasks are

    automatically created for each binary. 60 Build tasks ----------- assemble - Assembles the outputs of this project. [barBinary, fooBinary] barBinary - Assembles DefaultSampleBinary 'barBinary'. barBinaryTask1 barBinaryTask2 build - Assembles and tests this project. [assemble, check] fooBinary - Assembles DefaultSampleBinary 'fooBinary'. fooBinaryTask1 fooBinaryTask2
  54. © Gradle Inc. 2015 Model Report • Display a textual

    report of managed model • Evaluates the entire model graph • Work in progress 61
  55. © Gradle Inc. 2015 Limitations and Future Work • Bridge

    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