Build Automation with buildtool

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. The build tool is part of base MATLAB, but GNU Octave 11.3.0 has none of it: a check in the Octave 11.3.0 container found that buildtool, buildplan, runtests, assertSuccess and codeIssues do not exist there. So no example on this page was run. The build files, commands and the build output quoted from the documentation are checked line by line against the R2026b documentation and labelled "not run here". The releases in which each feature appeared are the ones stated on the documentation pages.

A project that is built by hand ("run the analyzer, run the tests, zip the folder, then remember to do all of it again before the release") drifts out of sync with the code. The MATLAB build tool (introduced in R2022b) is MATLAB’s answer: you write the steps once, as tasks in a file named buildfile.m in the project root folder, you say which task needs which, and one command, buildtool, runs them in dependency order, on your desktop or on a CI server (see Continuous Integration). The tasks that most projects need already exist as classes: find code issues, run tests with coverage, clean outputs, create P-code, compile MEX files and build a package. The build tool is the same idea as make, Gradle or cargo, written in MATLAB.

This page is the practical reference for the build tool. The checks that the check task runs are on Code Analyzer and Code Quality, the tests that the test task runs on Unit Testing with matlab.unittest, and the project layout on Namespaces and Class Folders. Which products add capabilities to the build tool is on Toolboxes and Add-On Products.

The build file and the plan

A build file is a function file named buildfile.m. Its main function takes no input and returns a plan, an object of the class matlab.buildtool.Plan. A plan is a collection of tasks (objects of matlab.buildtool.Task or a subclass) addressed by name, plus the list of default tasks. The buildplan function creates the plan. Here is the starter file that buildtool -init generates (R2024a), as shown on the overview page:

function plan = buildfile
import matlab.buildtool.tasks.*

plan = buildplan(localfunctions);

plan("clean") = CleanTask;
plan("check") = CodeIssuesTask;
plan("test") = TestTask;

plan.DefaultTasks = ["check" "test"];
end

Not run here (Octave 11.3.0 has no build tool). The file does four things:

  1. import matlab.buildtool.tasks.* makes the built-in task classes available by their short names. The namespace is matlab.buildtool.tasks.

  2. plan = buildplan(localfunctions) creates a plan and gives it one task for every task function found among the local functions of the file (explained next). localfunctions returns handles to all the local functions in the file, which buildplan inspects. plan = buildplan with no input creates an empty plan.

  3. plan("name") = task adds a task under a name. The name may contain letters, digits, underscores, dashes and periods and must not start with a dash. A name with a colon creates a task group (see below).

  4. plan.DefaultTasks lists the tasks that run when you call buildtool with no task name.

The documentation describes what the three tasks do: clean deletes the outputs and traces of the other tasks, check identifies code issues in the current folder and its subfolders and fails the build if there are errors, and test runs the tests in the current folder and its subfolders and fails the build if any test fails. The build tool looks for the file starting in the current folder and going up through its parent folders (since R2023b you can call buildtool from any subfolder of the project), and -buildFile path (R2024b) names another build file or the folder that holds it.

Task functions

When no built-in class fits, a task can be a task function: a local function of the build file whose name ends with Task (case insensitive). The task is named after the function without the suffix, so archiveTask becomes the task archive. The first line of the help text of the function (the H1 line) becomes the task description shown by buildtool -tasks. The first input must be a matlab.buildtool.TaskContext object, which the build tool creates and which carries information about the plan and the running task; a function that does not need it writes ~. The documentation’s example creates a ZIP archive of the folder:

function archiveTask(~)
% Create ZIP file
filename = "source_" + string(datetime("now",Format="yyyyMMdd'T'HHmmss"));
zip(filename,"*")
end

Not run here. A task function signals failure by throwing an error (for example assertSuccess(results) after runtests, or error(...)); the build then stops, unless -continueOnFailure was given. See Errors, Warnings and Exceptions and Local, Nested and Private Functions.

Dependencies and the default tasks

A task lists the tasks it needs in its Dependencies property. Before the build tool runs a task it runs all of its dependencies; the build tool treats the list as unordered, so the order of the names does not decide anything. A task runs at most once per build even if several tasks depend on it.

plan.DefaultTasks = "archive";
plan("archive").Dependencies = ["check" "test"];

Not run here (statements from the documentation example). There are two kinds of dependency:

  • Explicit dependencies, set in Dependencies, as above. Built-in task classes also accept Dependencies= as a name-value argument when you create them.

  • Inferred dependencies. When the Inputs of one task are the Outputs of another (see incremental builds below), the first task depends on the second without you saying so. plot(plan) draws the graph and shows these edges too.

A useful habit is to keep the graph shallow and give every task a one-line Description (set the property, or write the H1 line of a task function), because buildtool -tasks is the table of contents of the project: it prints each task name with its description.

The next figure is the task graph of the heatflow project built later on this page. An arrow points from a task to a task that needs it.

flowchart LR clean["clean (CleanTask)"] check["check (CodeIssuesTask)"] test["test (TestTask)"] pkg["package (PackageTask)"] check -->|runs before| test test -->|runs before| pkg clean -.->|removes outputs and traces of| check clean -.-> test clean -.-> pkg

The figure is an original diagram of the build file below, not a MATLAB plot; the documented way to draw the real graph of a plan is plot(buildfile). The dotted lines show that clean is not a dependency of anything: it only deletes what the other tasks produced.

Built-in tasks

The classes in the matlab.buildtool.tasks namespace cover the common jobs. Each is a subclass of matlab.buildtool.Task, so each has the inherited properties Name, Description, Dependencies, Actions, Inputs, Outputs and DisableIncremental. For a built-in task you can set only Description and Dependencies at creation, plus the properties of its own class. The documentation also says that you cannot remove or overwrite the action of a built-in task, but you can append a further action, plan("myTask").Actions(end+1) = @myAction;.

Class Since What it does

CodeIssuesTask

R2023b

Runs the Code Analyzer on files and folders (and their subfolders) and fails when the error or warning thresholds are exceeded. It always runs: it does not support incremental builds. See Code Analyzer and Code Quality for the properties (SourceFiles, ErrorThreshold, WarningThreshold, Configuration, Results).

TestTask

R2023b

Runs a test suite with the MATLAB unit testing framework and fails the build if a test fails. Properties are listed below.

CleanTask

R2023b

Deletes the outputs and the traces of other tasks (see below). It runs on the MATLAB client, not on a parallel worker.

PcodeTask

documentation page: matlab.buildtool.tasks.PcodeTask

Creates P-code files from source files, with inputs and outputs so that it takes part in incremental builds.

MexTask

documentation page: matlab.buildtool.tasks.MexTask

Compiles a C or C++ source file into a MEX file (needs a supported compiler). MexTask.forEachFile creates one task per source file (R2025a).

PackageTask

R2026b

Builds a package (.mltbx) from the matlab.toml project definition of a TOML project. It supports incremental builds.

(The documentation pages for PcodeTask and MexTask were not opened for this page, so their release is not stated here. Check the version history on each class page.)

TestTask

task = TestTask(tests, Name=Value) creates a task that runs the tests in the given files and folders (with no argument, the tests in the current folder and its subfolders, since IncludeSubfolders is true by default). The properties that matter in practice are:

Property Since Meaning

Tests

Test files and folders to run.

SourceFiles

The source code under test. Setting it to a nonempty value turns on incremental builds for the task, and the files it names are the code that coverage is collected for. With it empty, the task runs on every build.

SupportingFiles

R2025a

Test data and helper files that tests depend on. Not included in coverage.

IncludeSubfolders, Tag

Whether subfolders are searched (default true), and a filter that runs only tests with at least one of the given tags.

Selector

R2025a

A matlab.unittest.selectors.Selector object to filter the suite.

TestResults

R2024a

Output files for the test results. The extension chooses the format: .html (single-file report), .pdf, .xml (JUnit-style XML), .mat (a TestResult array), .mldatx (Simulink Test results, needs Simulink Test).

CodeCoverageResults

R2024a

Output files for coverage of the SourceFiles code: .html (interactive report), .xml (Cobertura XML) or .mat.

OutputDetail, LoggingLevel, Strict

The same ideas as in runtests: how much of the run is displayed (default: failures at the Detailed level and run progress at the Terse level), which logged diagnostics are included, and whether a warning in a test is a qualification failure.

RunOnlyImpactedTests, BaseRevision

R2025a, R2026a

Test impact analysis: rerun only the tests affected by changes (needs MATLAB Test; without that license, or without SourceFiles, all tests run). BaseRevision (Git only) measures changes since a commit such as HEAD or origin/main.

ExternalParameters

R2026b

Parameters that replace the ones defined in parameterized tests.

There is also an addCodeCoverage method that gives more control over coverage than the property, and with MATLAB Test and Simulink Test additional coverage kinds (decision, condition, MC/DC, model and generated-code coverage) are available. The overview page lists these as use cases that other products add.

PackageTask and the project

PackageTask needs a TOML project. A task created with PackageTask or PackageTask(packageName) reads the [[package]] table of matlab.toml; the name is optional when the file defines a single package and required when it defines several. The package-root entry of the table says which folder is bundled, so a project that bundles P-code makes the package task depend on the pcode task:

plan("pcode")   = PcodeTask("src","derived");
plan("package") = PackageTask(Dependencies="pcode");

Not run here (from the PackageTask reference page, where src and derived are the folders of the documentation example). The OutputFile property of the task gives access to the .mltbx file, so another task can take it as an input, for example plan("deploy").Inputs = plan("package").OutputFile;, which also creates an inferred dependency. The format of matlab.toml is on the reference page MATLAB Projects TOML Format (.toml), and the TOML project itself is described on Projects and Source Control.

Running a build: the buildtool command

buildtool is both a MATLAB command and the entry point on a CI server (matlab -batch buildtool, see Continuous Integration). The syntaxes are:

Syntax Effect

buildtool

Runs the default tasks and all the tasks they depend on.

buildtool task1 task2

Runs the named tasks, in the order given, each at most once, plus their dependencies.

buildtool -tasks

Lists the tasks with their descriptions. buildtool -tasks all (R2024b) also lists the tasks inside task groups.

buildtool -init

Creates a starter buildfile.m in the current folder if there is none (R2024a).

buildtool task(arg1,Name=Value)

Runs a task that accepts arguments (see below). Quote the argument in parentheses after the task name.

The documentation shows this output for the default tasks of the starter file (abridged; a real run differs with the folder contents and times, and is not run here):

** Starting check
Analysis Summary:
    Total Files: 3
         Errors: 0 (Threshold: 0)
** Finished check
** Starting test
...
Test Summary:
    Total Tests: 3
         Passed: 3
** Finished test
Build Successful:
    2 Tasks: 0 Failed, 0 Skipped

The lines ** Starting, ** Finished and ** Skipped mark the progress of each task, and the last block summarizes the build. The build tool changes the current folder to the plan root folder (the folder of the build file) while it runs and restores it afterwards (and, since R2023b, after each task), so relative paths in tasks are relative to the project root.

The build options, in any order, and their releases:

Option Since Meaning

-continueOnFailure

R2023b

Keep running the remaining tasks after a failure. By default the build stops at the first failure. Useful on CI, to see all failing tasks at once.

-skip task

R2023b

Skip the named task; repeat the option for several tasks.

-buildFile path

R2024b

Use another build file, or the folder containing buildfile.m.

-verbosity level

R2024b

None, Terse, Concise (default), Detailed or Verbose, the members of matlab.automation.Verbosity. From R2025a, Detailed and above also explain why each task ran (for example "Outputs modified").

-parallel

R2024a

Run tasks in parallel (needs Parallel Computing Toolbox); see below.

-ui

R2026a

Show the results of TestTask tests in the Test Browser, if you opted in from the Run Build section of the Toolstrip.

-outputCache

R2026b

Use the task output cache; see below.

-report path

R2026b

Write an HTML build report to the folder, with the main file index.html.

The build can also be run from the MATLAB Toolstrip (R2025a, "Run Build from Toolstrip"), and programmatically: plan = buildfile; run(plan) runs the default tasks, and run(plan,"archive") runs one. If the build file is inside a MATLAB project that is not open, the command opens the project first (since R2025a for any subfolder of the project; R2023b for the project root).

Task arguments

Since R2023a a task can accept arguments, so one task can be run in different ways without editing the build file. A task function adds inputs after the context and declares them with an arguments block (see Argument Validation):

function testTask(~,tests,options)
% Run unit tests
arguments
    ~
    tests string = pwd
    options.OutputDetail (1,1) string = "terse"
end
results = runtests(tests, IncludeSubfolders=true, OutputDetail=options.OutputDetail);
assertSuccess(results);
end

Not run here (adapted from the documentation; ~ is allowed as the first name in the arguments block). The task is then run with its arguments in parentheses, positional first and name-value after: buildtool test("myTests",OutputDetail="concise"). The run method takes the arguments in a cell array with one entry per task: run(plan,"archive",{"source2.zip"}), and an empty cell {} for a task without arguments when several tasks are named. Task arguments are part of the "up to date" decision: changing them reruns the task. Custom task classes can accept arguments by giving their TaskAction method extra inputs (R2025a). The built-in CleanTask takes task names as arguments, so buildtool clean("test") deletes only the outputs of test; TestTask accepts task arguments too, such as RunOnlyImpactedTests and BaseRevision.

Incremental builds

Since R2023a the build tool skips a task that is up to date: its inputs, outputs, actions and arguments are unchanged since the last time it ran successfully. For built-in tasks the class defines the inputs and outputs itself (R2023b); for a task function you declare them with the Inputs and Outputs properties, which accept strings with wildcards that become matlab.buildtool.io.FileCollection objects:

function plan = buildfile
plan = buildplan(localfunctions);
plan("pcode").Inputs  = "source/**/*.m";
plan("pcode").Outputs = plan("pcode").Inputs.replace(".m",".p");
plan("archive").Inputs  = plan("pcode").Outputs;
plan("archive").Outputs = "source.zip";
end

function pcodeTask(context)
% Create P-code files
filePaths = context.Task.Inputs.paths;
pcode(filePaths{:},"-inplace")
end

function archiveTask(context)
% Create ZIP file
task = context.Task;
zip(task.Outputs.paths,task.Inputs.paths)
end

Not run here (from the documentation example). Because the inputs of archive are the outputs of pcode, archive has an inferred dependency on pcode. The documentation shows the behaviour as follows: the first buildtool archive runs both tasks; the second prints ** Skipped pcode (up-to-date) and ** Skipped archive (up-to-date); adding a file to source reruns both; deleting only source.zip reruns only archive, and with -verbosity Detailed the output says why: "Outputs modified" and "Files deleted". The build tool keeps a trace of each task (what it consumed and produced), and that trace is what CleanTask deletes: after buildtool clean, no task is considered up to date.

A task opts out with DisableIncremental=true (R2024a), which forces it to rerun. Tasks that cannot be skipped, such as CodeIssuesTask, ignore the property. TestTask takes part only if SourceFiles is nonempty, as described above, because the build tool must know which source code the tests exercise before it can decide that a rerun is unnecessary.

Task groups

Since R2024b tasks can be grouped. Add tasks whose names start with a group name and a colon; the group is created implicitly:

plan("mex:arrayProduct") = MexTask(fullfile("source","arrayProduct.c"),"output");
plan("mex:yprime")       = MexTask(fullfile("source","yprime.c"),"output");
plan("mex").Description  = "Build MEX files";
plan("test").Dependencies = "mex";

Not run here (from the documentation, which needs a C compiler and the two example source files). A group is a matlab.buildtool.TaskGroup object. Giving a group a dependency gives it to all its tasks; naming a group on the command line (buildtool mex) runs all its tasks; naming one task (buildtool mex:yprime) runs only that one. buildtool -tasks all lists group members, and plot(plan,ShowAllTasks=true) draws them (by default a group is a single node). A group is the right tool for "build every MEX file" in a project with a C++ kernel (see C and C++ Libraries and MEX).

Custom task classes

Since R2025a you can write a reusable task as a class derived from matlab.buildtool.Task, which is better than a local function when several projects need the same step. Three attributes describe it: properties marked TaskInput and TaskOutput declare the inputs and outputs (outputs must be FileCollection or a subclass such as File), and one method marked TaskAction is the action, which receives the task and a TaskContext:

classdef ArchiveTask < matlab.buildtool.Task
    properties (TaskInput)
        Files (1,:) matlab.buildtool.io.FileCollection
    end
    properties (TaskOutput)
        Archive matlab.buildtool.io.File {mustBeScalarOrEmpty}
    end
    methods
        function task = ArchiveTask(files,archive)
            task.Files = files;
            task.Archive = archive;
            task.Description = "Create ZIP file";
        end
    end
    methods (TaskAction)
        function createArchive(task,~)
            zip(task.Archive.Path,task.Files.paths)
        end
    end
end

Not run here (the class of the documentation example). The build file then writes plan("archive") = ArchiveTask("**/*.m","source.zip");. Because the class declares its inputs and outputs, the task supports incremental builds without any Inputs or Outputs assignment in the build file. The class is an ordinary classdef (see classdef Basics) and can be shared in a package or on the path.

Parallel builds

buildtool -parallel (R2024a) uses Parallel Computing Toolbox. Before R2026a the option affected only TestTask instances, whose tests ran in parallel while the tasks themselves ran in series. Since R2026a the build tool schedules the tasks on workers of the current process-based parallel pool, starting a task only after its dependencies finish; the documentation’s demonstration uses five empty tasks, where t1 depends on t2, t3 and t5, and t3 depends on t4, and prints lines such as Scheduled t2 (Worker 1) and Completed t2 (Worker 1). Two caveats from the page: parallel builds pay off for large graphs with many tasks relative to the number of dependencies (a task that many others wait for is a bottleneck), and some built-in tasks always run on the client, among them TestTask (which has its own parallelism) and CleanTask. Parallel Computing Toolbox is a separate product (see Toolboxes and Add-On Products). The same toolbox supports running tests in parallel, covered on Background and Asynchronous Execution.

Output cache and the build report (R2026b)

Two options are new in R2026b.

Task output caching. buildtool -outputCache stores the outputs of tasks in the outputCache subfolder of a .buildtool folder in the project, and skips a task whose expected outputs are already cached, provided that its inputs, actions and arguments are unchanged. The documentation lists the conditions: the task must support incremental builds, write its outputs into the plan root folder or below it, and have DisableOutputCache set to false. Entries unused for more than seven days are removed. In the documented demonstration, after buildtool clean deleted the outputs, a new buildtool -outputCache printed ** Skipped test (from cache) and restored the files, while check ran again because CodeIssuesTask does not support incremental builds and therefore has nothing in the cache. CleanTask does not touch cached outputs. The cache is meant for CI: the MathWorks run-build action can cache the .buildtool folder between workflow runs (see Continuous Integration).

HTML report. buildtool -report myBuildReport writes an HTML report with the build environment, summary, per-task results and build output, which can be filtered by task status. The generateHTMLReport method of the build result gives more control, such as the file name and the title.

R2026b also made task dependency analysis faster; the release note quotes about 1.5 times faster for a plan with two dependent groups of one hundred tasks each (a MathWorks measurement on their test system, not reproduced here).

The heatflow build file

The heatflow project (see the scenario) keeps its code in the namespace folder +heatflow inside src and its tests in tests, the layout of Projects and Source Control and Packages and the MATLAB Package Manager. A build file that checks the code, runs the tests with reports and coverage, and then packages the result looks like this:

function plan = buildfile
import matlab.buildtool.tasks.CleanTask
import matlab.buildtool.tasks.CodeIssuesTask
import matlab.buildtool.tasks.TestTask
import matlab.buildtool.tasks.PackageTask

plan = buildplan(localfunctions);

plan("clean")   = CleanTask;
plan("check")   = CodeIssuesTask("src", WarningThreshold=0, ...
    Results="build/code-issues.sarif");
plan("test")    = TestTask("tests", SourceFiles="src", ...
    TestResults="build/test-results.xml", ...
    CodeCoverageResults="build/coverage.xml");
plan("package") = PackageTask;

plan("test").Dependencies    = "check";
plan("package").Dependencies = "test";
plan.DefaultTasks = "package";
end

Not run here. The properties come from the reference pages cited above. PackageTask needs the package table of matlab.toml described in PackageTask and the project, with package-root set to src; the definition shown on the projects page has only [folders], [dependencies] and [project.shortcuts], so that table has to be added before the package task can run (its full syntax is on the MATLAB Projects TOML Format reference page and is not reproduced here). How the pieces fit:

  • check fails on any warning (WarningThreshold=0) as well as errors and writes a SARIF file that code hosting tools can read; it always runs.

  • test lists src (which holds +heatflow) as its SourceFiles, which both enables incremental builds and defines what coverage is measured on, and writes JUnit XML and Cobertura XML files that any CI system understands (see Continuous Integration).

  • package depends on test, so a package is never built from code with failing tests. If the package bundled P-code, a pcode task would be added and listed as a further dependency, as in the PackageTask reference.

  • DefaultTasks = "package" makes a bare buildtool run the whole chain check, test, package, in that order, because each task’s dependencies run first. buildtool test runs only check and test; buildtool -skip check leaves out the analysis; buildtool clean removes the SARIF, XML and package files.

A local run of the same chain with -outputCache or -report needs R2026b; on older releases leave those options out. Everything above TestTask needs only base MATLAB; the package step additionally needs a TOML project, which is an R2026b feature of the project system.

Pitfalls

  • A task function that does not end in Task is not picked up by buildplan(localfunctions). Name it archiveTask, not archive.

  • TestTask without SourceFiles always reruns, and coverage cannot be requested without it. Set SourceFiles as soon as the build gets slow.

  • The build tool changes the current folder to the plan root, so a task that depends on the original folder, or a test that reads data through a relative path, behaves differently from an interactive run. Use fullfile with a path derived from the project, as in Search Path, Startup and Files.

  • Source code has to be on the search path when the task runs the tests and collects coverage. The addpath("source") line in the cache-demo build file of the documentation exists for this reason.

  • The first failure stops the build. On CI, consider -continueOnFailure so one run reports both a test failure and a code issue. The overall result is still a failure.

  • An empty Dependencies order is not an order. The list is unordered; express "A before B" by making B depend on A, never by the position of a name.

  • Releases matter in a shared build file. A build file that uses -outputCache, PackageTask or a parallel -parallel build fails on a runner with an older MATLAB. On a release before R2022b, buildtool itself is unknown and stops with "Unrecognized function or variable 'buildtool'.", identifier MATLAB:UndefinedFunction. Pin the release in the CI setup and keep the build file at the lowest release that you support.

  • Do not put secrets in the build file. It is committed. Read credentials from the environment, as described on Secrets and Protecting Code.

Where older books differ

Books written before 2022, such as the eighth edition of the MATLAB Primer (written for R2010a), have no build system in MATLAB. Their way of automating work is a driver script or a shell script that calls publish, pcode, mex and the test function by hand, or an external make file. The build tool (R2022b) replaces that with a plan of tasks, dependency order, incremental builds and the same file for desktop and CI. The built-in tasks arrived in R2023b (CodeIssuesTask, TestTask, CleanTask); older descriptions of the unit testing framework (script-, function- and class-based tests run with runtests or run) are still correct, but the "run everything" step that those books leave to the reader is now a TestTask. Books also predate the Code Analyzer functions codeIssues (R2022b) and the matlab -batch startup option for noninteractive runs, which replaces the older pattern of starting MATLAB with the -r option and the -nodisplay and -nosplash flags and ending the statement with exit. The current documentation page for -batch was not checked for its release; see Continuous Integration. The Pérez López book on mathematics and graphics does not discuss builds at all.