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

Gradle Rules Based Model Configuration Workshop

Avatar for Mark Vieira Mark Vieira
September 22, 2015

Gradle Rules Based Model Configuration Workshop

Avatar for Mark Vieira

Mark Vieira

September 22, 2015
Tweet

Other Decks in Programming

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