Configuring build steps
Out-of-the-box behavior without explicit configuration
The Moderne CLI detects build tools, produces a list of build “steps”, and executes each of those steps to produce LSTs. Any file parsed by a previous build step is skipped by its successors.
In the default configuration, the CLI first looks for Gradle build files, then Bazel, and then Maven. These external build tools are followed by a resource parsing step that scoops up any files that weren't parsed by the external build tool steps, because not every file in a repository is part of a source set under management by a build tool. For example, the top level README.md in a repository is generally parsed by the resource parsing step because it isn't located in a place that it would be part of a source set defined by an external build tool.
Build step types
The CLI supports the following build step types:
- External build tool steps:
maven,gradle, andbazel. These shell out to the respective build tool to compile and parse source code with full type attribution. - Language-specific steps:
python,javascript,dotnet, andmainframe. These use dedicated parsers to handle their respective language ecosystems. - Resource step:
resource. A catch-all step that parses files not handled by other steps (YAML, XML, JSON, Terraform, properties, etc.).
In the default configuration, only the external build tool steps and the resource step run automatically. Language-specific steps must be explicitly configured in your moderne.yml file.
External build tool steps
Each external build tool step scans the repository for the root build files of its type — that is, the build files that represent independent projects rather than submodules — and invokes the build tool on each one.
For most projects, an external build tool step will result in one execution of the external build tool. For example, even a multi-module Gradle project with the following directory structure results in one execution of the Moderne Gradle plugin to parse all the source sets of all projects in the multi-module project:
payments/
core/
src/main/java
io/moderne/payments/core/
Payments.java
build.gradle # a subproject of the root Gradle build
server/
src/main/java
io/moderne/payments/server/
PaymentsServer.java
build.gradle # a subproject of the root Gradle build
build.gradle # the top level gradle file
Sometimes monorepo(ish) repositories are structured in such a way that there are multiple top-level Gradle projects in subdirectories of the root repository directory. While they are stored in the same repository in VCS, they effectively possess disconnected build processes in the sense that no one Gradle command could operate on these disparate parts of the codebase. For example in the following structure the payments, rating, and underwriting functions of this policy administration repository are not related. In this case, the Gradle build step would execute three distinct Gradle tasks to parse LSTs using the Moderne Gradle plugin -- one for each business functional unit.
insurance-policy-administration/
payments/
core/
build.gradle
server/
build.gradle
build.gradle
rating/
build.gradle
underwriting/
build.gradle
Language-specific steps
Language-specific steps use dedicated parsers via RPC to handle their respective ecosystems. Unlike external build tool steps, these do not run in the default pipeline and must be explicitly configured.
python- Parses Python projects. Detects projects viapyproject.toml,setup.py, or.pyfiles. Requires Python 3.9+. See Python configuration for setup details.javascript- Parses JavaScript and TypeScript projects. Detects projects viapackage.jsonfiles. Automatically discovers the appropriate package manager (npm, yarn, pnpm, or bun) and Node.js version. See JavaScript configuration for setup details.dotnet- Parses C# projects. Detects projects via.sln,.slnx, or.csprojfiles. Requires .NET SDK 10.0+.mainframe- Parses COBOL, JCL, and Control-M code.
Resource step
The resource build step parses resource files using OpenRewrite parsers in situations where there is no source set with binary dependency list necessary to type-attribute the resulting LSTs. These include YAML, XML, Terraform, properties, JSON, some Groovy DSLs, etc.
In the default build steps, the resource build step runs after all external build tool steps, and serves as a vacuum that picks up the rest of the source files in a repository not explicitly managed by those build tools.
Configuring build steps explicitly
Build steps can be configured explicitly in Moderne CLI configuration. The out-of-the-box default behavior described above can also be explicitly defined in the .moderne/cli/moderne.yml file:
specs: specs.moderne.ai/v1/cli
build:
steps:
- type: gradle
- type: bazel
- type: maven
- type: resource
inclusion: |-
**/*
The order of the steps is important, as any file parsed by one step will be skipped by a subsequent step. In this way, the steps drive the order of precedence of build tools.
To add language-specific parsing, include the corresponding step types. For example, to parse a repository that contains both a Gradle project and Python code:
specs: specs.moderne.ai/v1/cli
build:
steps:
- type: gradle
- type: python
- type: resource
inclusion: |-
**/*
The available step types are: maven, gradle, bazel, python, javascript, dotnet, mainframe, and resource.
Be careful if you remove a build step type as that will make it so that build type will never be used. For instance, if you removed the bazel build step, Bazel will never be used to build any files -- even if there were Bazel files present. Instead, Bazel files would be parsed with the resource parser.
Add a resource step to cause files/folders to be skipped by external build tools
In some cases, we have found that the CLI's recursive file walking of the repository to discover top level external build tool files will discover build tool files (e.g., build.gradle) that we do not desire to parse as a Gradle project.
As an example, one Moderne customer organizes its microservice repositories to have a top level folder called /deploy in every repository, which in turn contains a build.gradle which they are fine being parsed as plain Groovy but do not wish to be interpreted as a Gradle file at parsing time because it contains references to properties that are only available while in the act of deploying (i.e. the Gradle project fails to configure in its at-rest state in the codebase). The following explicit build step configuration would categorically work for all of this customer's microservice repositories to skip deploy/build.gradle as a Gradle project:
specs: specs.moderne.ai/v1/cli
build:
steps:
- type: resource
inclusion: |-
deploy/*
- type: gradle
- type: resource
inclusion: |-
**/*
When configuring resource steps with inclusion patterns, it's important to understand how glob patterns work:
dir/subdir/*- Matches only files directly withindir/subdir/, but not in its subdirectoriesdir/subdir/**- Matches all files and folders recursively underdir/subdir/
For example, if you have a repository structure containing multiple independent Gradle projects under a directory and want them all to be parsed as resource files instead of Gradle projects:
repo/
dir/
subdir/
project1/
settings.gradle
build.gradle
src/
project2/
settings.gradle
build.gradle
src/
project3/
settings.gradle
build.gradle
src/
Using dir/subdir/* would only match files directly in subdir/ and would not include the Gradle projects in project1/, project2/, and project3/. To include all files in those subdirectories as resources, use dir/subdir/**:
Example configuration:
specs: specs.moderne.ai/v1/cli
build:
steps:
- type: resource
inclusion: |-
dir/subdir/**
- type: gradle
- type: resource
inclusion: |-
**/*