# Extending the software model

version 6.5-milestone-1

| Rule based configuration will be deprecated. New plugins should not use this concept. Instead, use the standard approach described in the Writing Custom Plugins chapter.

## Introduction

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 different domains. 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: 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
                    }
                }
            }
        }
    }

The rest of this chapter is dedicated to explaining what is going on behind this build script.

## Concepts

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.

### Public type and base interfaces

Extended types declare a public type that extends a base interface:

- Components extend the ComponentSpec base interface

- Binaries extend the BinarySpec base interface

- Source sets extend the LanguageSourceSet base interface

The public type is exposed to build logic.

### Internal views

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 all the way down

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. Similarly, 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.

## Components

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 sources

- VariantComponentSpec 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: Declare a custom component

DocumentationComponent.groovy

    @Managed
    interface DocumentationComponent extends GeneralComponentSpec {}

Types extending `ComponentSpec` are registered via a rule annotated with ComponentType:

### Example: Register a custom component

DocumentationPlugin.groovy

    class DocumentationPlugin extends RuleSource {
        @ComponentType
        void registerComponent(TypeBuilder<DocumentationComponent> builder) {}
    }

## Binaries

To declare a custom binary type one must extend BinarySpec.

### Example: 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: Register a custom binary

DocumentationPlugin.groovy

    class DocumentationPlugin extends RuleSource {
        @ComponentType
        void registerBinary(TypeBuilder<DocumentationBinary> builder) {}
    }

## Source sets

To declare a custom source set type one must extend LanguageSourceSet.

### Example: 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: Register a custom source set

MarkdownPlugin.groovy

    class MarkdownPlugin extends RuleSource {
        @ComponentType
        void registerMarkdownLanguage(TypeBuilder<MarkdownSourceSet> builder) {}
    }

Setting the language name is mandatory.

## Putting it all together

### Generating binaries from components

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: 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}")
            }
        }
    }

### Generating tasks from binaries

Tasks generation from binaries is done via rules annotated with BinaryTasks. This rule generates a `Copy` task for each `TextSourceSet` of each `DocumentationBinary`:

### Example: 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: 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.

### Using your custom model

This build script demonstrate usage of the custom model defined in the sections above:

### Example: 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
                    }
                }
            }
        }
    }

And in the components reports for such a build script we can see our model types properly registered:

### Example: components report

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.

## About internal views

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: 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: 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: 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: 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 53, column 5
             ⤷ MyPlugin#mutateMyComponents(ModelMap<MyComponentInternal>)
        + my
              | Type:   	MyComponent
              | Creator: 	components { ... } @ build.gradle line 53, column 5 > create(my)
              | Rules:
                 ⤷ MyPlugin#mutateMyComponents(ModelMap<MyComponentInternal>) > all()
            + publicData
                  | Type:   	java.lang.String
                  | Value:  	Some PUBLIC data
                  | Creator: 	components { ... } @ build.gradle line 53, 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: 	Project.<init>.tasks.assemble()
              | Rules:
                 ⤷ copyToTaskContainer
        + build
              | Type:   	org.gradle.api.DefaultTask
              | Value:  	task ':build'
              | Creator: 	Project.<init>.tasks.build()
              | Rules:
                 ⤷ copyToTaskContainer
        + buildEnvironment
              | Type:   	org.gradle.api.tasks.diagnostics.BuildEnvironmentReportTask
              | Value:  	task ':buildEnvironment'
              | Creator: 	Project.<init>.tasks.buildEnvironment()
              | Rules:
                 ⤷ copyToTaskContainer
        + check
              | Type:   	org.gradle.api.DefaultTask
              | Value:  	task ':check'
              | Creator: 	Project.<init>.tasks.check()
              | Rules:
                 ⤷ copyToTaskContainer
        + clean
              | Type:   	org.gradle.api.tasks.Delete
              | Value:  	task ':clean'
              | Creator: 	Project.<init>.tasks.clean()
              | Rules:
                 ⤷ copyToTaskContainer
        + components
              | Type:   	org.gradle.api.reporting.components.ComponentReport
              | Value:  	task ':components'
              | Creator: 	Project.<init>.tasks.components()
              | Rules:
                 ⤷ copyToTaskContainer
        + dependencies
              | Type:   	org.gradle.api.tasks.diagnostics.DependencyReportTask
              | Value:  	task ':dependencies'
              | Creator: 	Project.<init>.tasks.dependencies()
              | Rules:
                 ⤷ copyToTaskContainer
        + dependencyInsight
              | Type:   	org.gradle.api.tasks.diagnostics.DependencyInsightReportTask
              | Value:  	task ':dependencyInsight'
              | Creator: 	Project.<init>.tasks.dependencyInsight()
              | Rules:
                 ⤷ copyToTaskContainer
        + dependentComponents
              | Type:   	org.gradle.api.reporting.dependents.DependentComponentsReport
              | Value:  	task ':dependentComponents'
              | Creator: 	Project.<init>.tasks.dependentComponents()
              | Rules:
                 ⤷ copyToTaskContainer
        + help
              | Type:   	org.gradle.configuration.Help
              | Value:  	task ':help'
              | Creator: 	Project.<init>.tasks.help()
              | Rules:
                 ⤷ copyToTaskContainer
        + init
              | Type:   	org.gradle.buildinit.tasks.InitBuild
              | Value:  	task ':init'
              | Creator: 	Project.<init>.tasks.init()
              | Rules:
                 ⤷ copyToTaskContainer
        + model
              | Type:   	org.gradle.api.reporting.model.ModelReport
              | Value:  	task ':model'
              | Creator: 	Project.<init>.tasks.model()
              | Rules:
                 ⤷ copyToTaskContainer
        + outgoingVariants
              | Type:   	org.gradle.api.tasks.diagnostics.OutgoingVariantsReportTask
              | Value:  	task ':outgoingVariants'
              | Creator: 	Project.<init>.tasks.outgoingVariants()
              | Rules:
                 ⤷ copyToTaskContainer
        + prepareKotlinBuildScriptModel
              | Type:   	org.gradle.api.DefaultTask
              | Value:  	task ':prepareKotlinBuildScriptModel'
              | Creator: 	Project.<init>.tasks.prepareKotlinBuildScriptModel()
              | Rules:
                 ⤷ copyToTaskContainer
        + projects
              | Type:   	org.gradle.api.tasks.diagnostics.ProjectReportTask
              | Value:  	task ':projects'
              | Creator: 	Project.<init>.tasks.projects()
              | Rules:
                 ⤷ copyToTaskContainer
        + properties
              | Type:   	org.gradle.api.tasks.diagnostics.PropertyReportTask
              | Value:  	task ':properties'
              | Creator: 	Project.<init>.tasks.properties()
              | Rules:
                 ⤷ copyToTaskContainer
        + tasks
              | Type:   	org.gradle.api.tasks.diagnostics.TaskReportTask
              | Value:  	task ':tasks'
              | Creator: 	Project.<init>.tasks.tasks()
              | Rules:
                 ⤷ copyToTaskContainer
        + wrapper
              | Type:   	org.gradle.api.tasks.wrapper.Wrapper
              | Value:  	task ':wrapper'
              | Creator: 	Project.<init>.tasks.wrapper()
              | Rules:
                 ⤷ copyToTaskContainer

We can see in this report that `publicData` is present and that `internalData` is not.