How It Works

Despite its name, MonoBuild does not perform any builds. It attempts to work out from the most recent Git commit, on the current branch, if a build should occur. It decides this by building a list of dependent directories of the build directory and seeing if any of the files changes in those directories would affect the build output of the current directory.

Information Retrieval

At the start of the process, two sets of information are gathered: files changed and project structure.

Files changes

The list of files considered for a given commit. This can be seen by:

git diff --name-only head head~1

Project structure

  • The project (csproj, fsproj) file, .monobuild.deps and .monobuild.ingore files read, for the target directory are read.
  • For each dependant directory found in a project or deps file, the process of loading dependencies and ignore files is repeated until a complete tree of all the build dependencies is created.

When reading project files, only the directory name is considered so for a reference ../Site_A/Site_A.csproj, the directory ../Site_A would be checked for a project file, a .monobuild.deps file and a .monobuild.ingore file.

Custom Dependencies

The directory of any project referenced in the csproj/fsproj file is considered a dependant directory, but if you want a build to be triggered by a transitive dependency which is not described by the project files, you can add manual dependency files. Each dependent directory referenced in a project file can have a .monobuild.deps. The format of the file is one directory per line, the following is a valid .monobuild.deps file, which has a comment and points to two dependant files:

# Line comments are introduced by hash sign
../src/ServiceA
../src/ServiceB

Ignore Files

We don’t necessarily want a build to be triggered for a change on every file type or location. We can ignore files using a file glob pattern. Ignores can be relative referencing locations in another dependency, or local to the current directory. Ignore files have the name .monobuild.ignore. One ignore pattern should be per line. If you understand .gitignore you understand .monobuild.ignore files.

Dependency Resolution

Files are removed from the list of changed files if they do not match any dependency, if any files are left in the change list after we have finished then a build is required. Files are removed by the following process:

  1. Files not directly in a build directory.
  2. Files matched by a local exclusion.
  3. Files excluded by the build directories relative ignore list.
  4. Files in a directory where all of its parents have ignored it.

Step 1: Files not directly in a build directory

If we are building Service_A and all the changes happen in Service_B which is not a dependency of Service_A then all the files can be removed from the list and no build is required. If we are building Site, a change in any file in Service_A would trigger a build.

graph TD; S[Site]-->B; S-->A; A[Service_A]; B[Service_B]-.->C; C[Files Changed]

Step 2: Files matched by a local exclusion

A build directory or a dependant directory contain a file named .monobuild.ignore which will be processed for ignore globs. In the diagram below, we have two ignore files containing one ignore glob each:

graph TD; S[Site]-->B; S-->A; S-. .monobuild.ignore .->E A[Service_A]; B[Service_B]-. .monobuild.ignore .->C C{{**/*.md }} E{{**/*.SASS }}

If the list of changes is [Site/reame.md, Site/Service_A/style.SASS, Site/Service_A/readme.md] and Site is the build target:

  • Site/.monobuild.ignore: ignores all files ending in md at any directory level so will remove both Site/readme.md and Site/Service_A/readme.md
  • Site/Service_A/.monobuild.ignore: ignores all files ending in sass in so will remove Site/Service_A/style.sass

See file globbing in .net for details on globbing patterns.

Note that the patterns are file globbing patterns, not project globbing patterns. They do not understand project structure. So if the dependency tree is as above but the file structure is as below then given the following list of changes [src/Site/reame.md, src/Service_A/style.SASS, src/Service_A/readme.md], the readme.md file in Service_A would not be removed from the list of changes for the command monobuild -t src/Site. This would cause the result to be <YES> a build is required.

graph TD; R[src]-->S S[Site]-. .monobuild.ignore .->E R-->A R-->B S A[Service_A]; B[Service_B]-. .monobuild.ignore .->C C{{**/*.md }} E{{**/*.SASS }}

Step 3: Files excluded by the build directories relative ignore list

We have seen that build target directories and their dependencies can have locally applied ignores but we can also ignore files relative to the directory. Relative ignore files start with ../ to navigate out of the current directory. In the below diagram, the Site is ignoring all xml files located in the Service_A/PaymentSchema folder (not shown). If we are testing if a build is required for Site and the changed files are [src/Service_A/BankSchema/payments.xml, src/Service_A/BankSchema/PaymentContracts.cs] file src/Service_A/BankSchema/payments.xml would be removed leaving just PaymentContracts.cs which would trigger a build.

graph TD; R[src]-->S S[Site]-. .monobuild.ignore .->E R-->A R-->B S A[Service_A]; B[Service_B]-. .monobuild.ignore .->C C{{**/*.md }} E{{../Service_A/BankSchema/*.xml }}

Step 4: Files in a directory where all of its parents have ignored it.

Target directories can have relative ignores and so can the targets dependencies, but all of the target dependencies must ignore the file. If we have a project with the folder structure of:

graph TD; R[src]-->SA R-->SB R-->SU R-->ST ST[Site] SB[Service_B] SB-. .monobuild.ignore .-> SBI SA[Service_A] SU[Utilites] SBI{{../Utlities/collections/**/*.cs}}

And a dependency structure of:

graph TD; ST[Site]-->SB ST-->SA SB[Service_B]-->SU SA[Service_A]-->SU SU[Utilites]

Testing to see if Site should build with a changed file [src/Utilities/Collections/DictionaryExtensions.cs].

  • The changed file will not have been removed by any of the previous steps.
  • Site has a dependency on both Service_A and Service_B, which have a dependency on Utilities.
  • As Service_A does not have an ignore which would remove the file, it will mean that the test would return <YES> a change to that file would trigger a build on Site.

It is as if we tested if a build was required for Service_A and for Service_B, as Service_A does not ignore the file in Utilites it requires a build, which means that Site requires a build.

If Service_A had an ignore file with the relative path ../Utlities/collections/**/*.cs as well as Service_B then both of Site dependencies which are dependent on utilities would have ignored the file and the test for build required would have been <NO>.

We have now finished all the steps which would stop a changed file from triggering a build, if any have not been nullified by the above tests then a build is required for the current target directory.