Table of Contents
Support for the software model is currently incubating. Please be aware that the DSL, APIs and other configuration may change in later Gradle versions.
One of the strengths of Gradle has always been its extensibility, and its adaptability to new domains. The software model takes this extensibility to a new level, enabling the deep modeling of specific domains via richly typed DSLs. The following chapter describes how the model and the corresponding DSLs can be extended to support domains like Java, Play Framework or native software development. Before reading this you should be familiar with the Gradle software model rule based configuration and concepts.
The following build script is an example of using a custom software model for building Markdown based documentation:
Example 73.1. an example of using a custom software model
build.gradle
import sample.documentation.DocumentationComponent import sample.documentation.TextSourceSet import sample.markdown.MarkdownSourceSet apply plugin:sample.documentation.DocumentationPlugin apply plugin:sample.markdown.MarkdownPlugin model { components { docs(DocumentationComponent) { sources { reference(TextSourceSet) userguide(MarkdownSourceSet) { generateIndex = true smartQuotes = true } } } } }
Note: The code for this example can be found at samples/customModel/languageType/
in the ‘-all’ distribution of Gradle.
The rest of this chapter is dedicated to explaining what is going on behind this build script.
A custom software model type has a public type, a base interface and internal views. Multiple such types then collaborate to define a custom software model.
Extended types declare a public type that extends a base interface:
ComponentSpec
base interface
BinarySpec
base interface
LanguageSourceSet
base interface
The public type is exposed to build logic.
Adding internal views to your model type, you can make some data visible to build logic via a public type, while hiding the rest of the data behind the internal view types. This is covered in a dedicated section below.
Components are composed of other components. A source set is just a special kind of component representing sources. It might be that the sources are provided, or generated. Similarily, some components are composed of different binaries, which are built by tasks. All buildable components are built by tasks. In the software model, you will write rules to generate both binaries from components and tasks from binaries.
To declare a custom component type one must extend
ComponentSpec
, or one of the following, depending on the use case:
SourceComponentSpec
represents a component which has sourcesVariantComponentSpec
represents a component which generates different binaries based on context (target platforms, build flavors, ...). Such a component generally produces multiple binaries.GeneralComponentSpec
is a convenient base interface for components that are built from sources and variant-aware. This is the typical case for a lot of software components, and therefore it should be in most of the cases the base type to be extended.
The core software model includes more types that can be used as base for extension. For example:
LibrarySpec
and
ApplicationSpec
can also be extended in this manner.
Theses are no-op extensions of GeneralComponentSpec
used to describe a software
model better by distinguishing libraries and applications components.
TestSuiteSpec
should be used for all components that describe a test suite.
Example 73.2. Declare a custom component
DocumentationComponent.groovy
@Managed interface DocumentationComponent extends GeneralComponentSpec {}
Types extending ComponentSpec
are registered via a rule
annotated with ComponentType
:
Example 73.3. Register a custom component
DocumentationPlugin.groovy
class DocumentationPlugin extends RuleSource { @ComponentType void registerComponent(TypeBuilder<DocumentationComponent> builder) {} }
To declare a custom binary type one must extend
BinarySpec
.
Example 73.4. Declare a custom binary
DocumentationBinary.groovy
@Managed interface DocumentationBinary extends BinarySpec { File getOutputDir() void setOutputDir(File outputDir) }
Types extending BinarySpec
are registered via a rule
annotated with ComponentType
:
Example 73.5. Register a custom binary
DocumentationPlugin.groovy
class DocumentationPlugin extends RuleSource { @ComponentType void registerBinary(TypeBuilder<DocumentationBinary> builder) {} }
To declare a custom source set type one must extend
LanguageSourceSet
.
Example 73.6. Declare a custom source set
MarkdownSourceSet.groovy
@Managed interface MarkdownSourceSet extends LanguageSourceSet { boolean isGenerateIndex() void setGenerateIndex(boolean generateIndex) boolean isSmartQuotes() void setSmartQuotes(boolean smartQuotes) }
Types extending LanguageSourceSet
are registered via a rule
annotated with ComponentType
:
Example 73.7. Register a custom source set
MarkdownPlugin.groovy
class MarkdownPlugin extends RuleSource { @ComponentType void registerMarkdownLanguage(TypeBuilder<MarkdownSourceSet> builder) {} }
Setting the language name is mandatory.
Binaries generation from components is done via rules annotated with
ComponentBinaries
.
This rule generates a DocumentationBinary
named exploded
for each DocumentationComponent
and sets its outputDir
property:
Example 73.8. Generates documentation binaries
DocumentationPlugin.groovy
class DocumentationPlugin extends RuleSource { @ComponentBinaries void generateDocBinaries(ModelMap<DocumentationBinary> binaries, VariantComponentSpec component, @Path("buildDir") File buildDir) { binaries.create("exploded") { binary -> outputDir = new File(buildDir, "${component.name}/${binary.name}") } } }
Tasks generation from binaries is done via rules annotated with
BinaryTasks
.
This rule generates a Copy
task
for each TextSourceSet
of each DocumentationBinary
:
Example 73.9. Generates tasks for text source sets
DocumentationPlugin.groovy
class DocumentationPlugin extends RuleSource { @BinaryTasks void generateTextTasks(ModelMap<Task> tasks, final DocumentationBinary binary) { binary.inputs.withType(TextSourceSet) { textSourceSet -> def taskName = binary.tasks.taskName("compile", textSourceSet.name) def outputDir = new File(binary.outputDir, textSourceSet.name) tasks.create(taskName, Copy) { from textSourceSet.source destinationDir = outputDir } } } }
This rule generates a MarkdownCompileTask
task
for each MarkdownSourceSet
of each DocumentationBinary
:
Example 73.10. Register a custom source set
MarkdownPlugin.groovy
class MarkdownPlugin extends RuleSource { @BinaryTasks void processMarkdownDocumentation(ModelMap<Task> tasks, final DocumentationBinary binary) { binary.inputs.withType(MarkdownSourceSet) { markdownSourceSet -> def taskName = binary.tasks.taskName("compile", markdownSourceSet.name) def outputDir = new File(binary.outputDir, markdownSourceSet.name) tasks.create(taskName, MarkdownHtmlCompile) { compileTask -> compileTask.source = markdownSourceSet.source compileTask.destinationDir = outputDir compileTask.smartQuotes = markdownSourceSet.smartQuotes compileTask.generateIndex = markdownSourceSet.generateIndex } } } }
See the sample source for more on the MarkdownCompileTask
task.
This build script demonstrate usage of the custom model defined in the sections above:
Example 73.11. an example of using a custom software model
build.gradle
import sample.documentation.DocumentationComponent import sample.documentation.TextSourceSet import sample.markdown.MarkdownSourceSet apply plugin:sample.documentation.DocumentationPlugin apply plugin:sample.markdown.MarkdownPlugin model { components { docs(DocumentationComponent) { sources { reference(TextSourceSet) userguide(MarkdownSourceSet) { generateIndex = true smartQuotes = true } } } } }
Note: The code for this example can be found at samples/customModel/languageType/
in the ‘-all’ distribution of Gradle.
And in the components reports for such a build script we can see our model types properly registered:
Example 73.12. foo bar
Output of gradle -q components
> gradle -q components ------------------------------------------------------------ Root project ------------------------------------------------------------ DocumentationComponent 'docs' ----------------------------- Source sets Markdown source 'docs:userguide' srcDir: src/docs/userguide Text source 'docs:reference' srcDir: src/docs/reference Binaries DocumentationBinary 'docs:exploded' build using task: :docsExploded Note: currently not all plugins register their components, so some components may not be visible here.
Internal views can be added to an already registered type or to a new custom type.
In other words, using internal views, you can attach extra properties to already registered components,
binaries and source sets types like JvmLibrarySpec
, JarBinarySpec
or JavaSourceSet
and to the custom types you write.
Let's start with a simple component public type and its internal view declarations:
Example 73.13. public type and internal view declaration
build.gradle
@Managed interface MyComponent extends ComponentSpec { String getPublicData() void setPublicData(String data) } @Managed interface MyComponentInternal extends MyComponent { String getInternalData() void setInternalData(String internal) }
The type registration is as follows:
Example 73.14. type registration
build.gradle
class MyPlugin extends RuleSource { @ComponentType void registerMyComponent(TypeBuilder<MyComponent> builder) { builder.internalView(MyComponentInternal) } }
The internalView(type)
method of the type builder can be called several times.
This is how you would add several internal views to a type.
Now, let's mutate both public and internal data using some rule:
Example 73.15. public and internal data mutation
build.gradle
class MyPlugin extends RuleSource { @Mutate void mutateMyComponents(ModelMap<MyComponentInternal> components) { components.all { component -> component.publicData = "Some PUBLIC data" component.internalData = "Some INTERNAL data" } } }
Our internalData
property should not be exposed to build logic.
Let's check this using the model
task on the following build file:
Example 73.16. example build script and model report output
build.gradle
apply plugin: MyPlugin model { components { my(MyComponent) } }
Output of gradle -q model
> gradle -q model ------------------------------------------------------------ Root project ------------------------------------------------------------ + components | Type: org.gradle.platform.base.ComponentSpecContainer | Creator: ComponentBasePlugin.PluginRules#components(ComponentSpecContainer) | Rules: ⤷ components { ... } @ build.gradle line 42, column 5 ⤷ MyPlugin#mutateMyComponents(ModelMap<MyComponentInternal>) + my | Type: MyComponent | Creator: components { ... } @ build.gradle line 42, column 5 > create(my) | Rules: ⤷ MyPlugin#mutateMyComponents(ModelMap<MyComponentInternal>) > all() + publicData | Type: java.lang.String | Value: Some PUBLIC data | Creator: components { ... } @ build.gradle line 42, column 5 > create(my) + tasks | Type: org.gradle.model.ModelMap<org.gradle.api.Task> | Creator: Project.<init>.tasks() + assemble | Type: org.gradle.api.DefaultTask | Value: task ':assemble' | Creator: tasks.addPlaceholderAction(assemble) | Rules: ⤷ copyToTaskContainer + build | Type: org.gradle.api.DefaultTask | Value: task ':build' | Creator: tasks.addPlaceholderAction(build) | Rules: ⤷ copyToTaskContainer + buildEnvironment | Type: org.gradle.api.tasks.diagnostics.BuildEnvironmentReportTask | Value: task ':buildEnvironment' | Creator: tasks.addPlaceholderAction(buildEnvironment) | Rules: ⤷ copyToTaskContainer + check | Type: org.gradle.api.DefaultTask | Value: task ':check' | Creator: tasks.addPlaceholderAction(check) | Rules: ⤷ copyToTaskContainer + clean | Type: org.gradle.api.tasks.Delete | Value: task ':clean' | Creator: tasks.addPlaceholderAction(clean) | Rules: ⤷ copyToTaskContainer + components | Type: org.gradle.api.reporting.components.ComponentReport | Value: task ':components' | Creator: tasks.addPlaceholderAction(components) | Rules: ⤷ copyToTaskContainer + dependencies | Type: org.gradle.api.tasks.diagnostics.DependencyReportTask | Value: task ':dependencies' | Creator: tasks.addPlaceholderAction(dependencies) | Rules: ⤷ copyToTaskContainer + dependencyInsight | Type: org.gradle.api.tasks.diagnostics.DependencyInsightReportTask | Value: task ':dependencyInsight' | Creator: tasks.addPlaceholderAction(dependencyInsight) | Rules: ⤷ HelpTasksPlugin.Rules#addDefaultDependenciesReportConfiguration(DependencyInsightReportTask, ServiceRegistry) ⤷ copyToTaskContainer + help | Type: org.gradle.configuration.Help | Value: task ':help' | Creator: tasks.addPlaceholderAction(help) | Rules: ⤷ copyToTaskContainer + init | Type: org.gradle.buildinit.tasks.InitBuild | Value: task ':init' | Creator: tasks.addPlaceholderAction(init) | Rules: ⤷ copyToTaskContainer + model | Type: org.gradle.api.reporting.model.ModelReport | Value: task ':model' | Creator: tasks.addPlaceholderAction(model) | Rules: ⤷ copyToTaskContainer + projects | Type: org.gradle.api.tasks.diagnostics.ProjectReportTask | Value: task ':projects' | Creator: tasks.addPlaceholderAction(projects) | Rules: ⤷ copyToTaskContainer + properties | Type: org.gradle.api.tasks.diagnostics.PropertyReportTask | Value: task ':properties' | Creator: tasks.addPlaceholderAction(properties) | Rules: ⤷ copyToTaskContainer + tasks | Type: org.gradle.api.tasks.diagnostics.TaskReportTask | Value: task ':tasks' | Creator: tasks.addPlaceholderAction(tasks) | Rules: ⤷ copyToTaskContainer + wrapper | Type: org.gradle.api.tasks.wrapper.Wrapper | Value: task ':wrapper' | Creator: tasks.addPlaceholderAction(wrapper) | Rules: ⤷ copyToTaskContainer
We can see in this report that publicData
is present and that
internalData
is not.