manifest.yaml
# Copyright 2026 The MathWorks, Inc.
schema-version: 1
license: https://www.mathworks.com/content/dam/mathworks/license/pmrl/license.md
scope: cross-product
version: "1.0"
human-description: Generate a buildfile.m for preparing and packaging a toolbox.
matlab-release: ">=R2026a"
required-tools:
- evaluate_matlab_code
- check_matlab_code
required-skills:
- matlab-create-project
required-products: []
toolkit: matlab-agentic-toolkit
references/buildfile-variants.md
# Buildfile Variants
## Variant: No MEX (most projects)
If no C/C++/Fortran source files are detected, omit the `mex` task entirely and remove `"mex"` from the test task's dependencies:
```matlab
plan("test") = TestTask("tests", ...
SourceFiles="toolbox", ...
Dependencies="check", ...
TestResults="results/test-results.xml") ...
.addCodeCoverage(["results/coverage.xml" "results/coverage.mat"]);
```
## Variant: Multiple MEX Files
When the project has multiple MEX source files, use `MexTask.forEachFile` to build one MEX per source file:
```matlab
% Builds one MEX per .c file in mex/, outputs to toolbox/
plan("mex") = MexTask.forEachFile("mex/*.c", "toolbox");
```
With common helper files shared across all MEX builds:
```matlab
plan("mex") = MexTask.forEachFile("mex/*.c", "toolbox", ...
CommonSourceFiles="mex/common/utils.c", ...
Options="-O");
```
## Variant: Single MEX with Multiple Sources
When multiple source files compile into a single MEX binary:
```matlab
plan("mex") = MexTask(["mex/main.c" "mex/helper.c"], "toolbox", ...
Filename="myMex", ...
Options=["-O" "-DNDEBUG"]);
```
## Variant: MEX with Debug Build
Add a separate debug MEX task for development:
```matlab
plan("mex") = MexTask("mex/compute.c", "toolbox", ...
Options="-O");
plan("mex-debug") = MexTask("mex/compute.c", "toolbox-debug", ...
Options="-g", ...
Description="Build MEX with debug symbols");
```
## Variant: No Coverage Task
If the user does not want coverage reporting at all (or the project has no meaningful coverage target yet), remove the `coverage` task and have `package` depend directly on `test`:
```matlab
plan("test") = TestTask("tests", ...
SourceFiles="toolbox", ...
Dependencies="check", ...
TestResults="results/test-results.xml") ...
.addCodeCoverage("results/coverage.xml");
plan("package").Dependencies = "test";
plan.DefaultTasks = ["check" "test"];
```
This still produces Cobertura XML coverage for CI visibility but skips the per-file summary and threshold warning. The `.mat` output can be omitted since nothing inspects it programmatically.
## Variant: No toolboxPackaging.prj
If no PRJ exists (early development, or user prefers fully programmatic packaging). **Never hardcode the version** — read it from `toolboxSpecification.m` so there's a single source of truth that `matlab-publish-toolbox` updates:
```matlab
function packageTask(context)
% Package toolbox into .mltbx
% Version is read from toolboxSpecification.m — never hardcode it here.
toolboxFolder = fullfile(context.Plan.RootFolder, "toolbox");
uuid = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"; % Generated once, committed
% Read version from spec (single source of truth)
oldPath = addpath(fullfile(context.Plan.RootFolder, "buildUtilities"));
raii = onCleanup(@()(path(oldPath)));
spec = toolboxSpecification();
opts = matlab.addons.toolbox.ToolboxOptions(toolboxFolder, uuid);
opts.ToolboxName = spec.toolbox.name;
opts.ToolboxVersion = spec.toolbox.version;
opts.Summary = spec.toolbox.summary;
opts.OutputFile = fullfile("release", strrep(spec.toolbox.name, " ", "_") + ".mltbx");
if ~isfolder("release"), mkdir("release"); end
matlab.addons.toolbox.packageToolbox(opts);
context.assertTrue(isfile(opts.OutputFile), "Package was not created");
context.log(sprintf("Package: %s v%s", opts.OutputFile, opts.ToolboxVersion));
end
```
Generate the UUID once with `matlab.lang.internal.uuid` and commit it — it must remain stable across builds (it identifies the toolbox for update detection).
## Variant: Coverage Report from Cobertura XML (existing test task)
When integrating with an existing buildfile whose test task produces Cobertura XML (not `.mat`), parse the `line-rate` attribute directly from the XML root element:
```matlab
function coverageTask(context)
% Report coverage from Cobertura XML produced by existing test task
coverageFile = fullfile(context.Plan.RootFolder, "reports", "codecoverage.xml");
if ~isfile(coverageFile)
context.log("No coverage data found — skipping.");
return
end
doc = xmlread(coverageFile);
root = doc.getDocumentElement();
lineRate = str2double(string(root.getAttribute("line-rate")));
context.log(sprintf("Coverage: %.1f%%", lineRate * 100));
threshold = 0.80;
if lineRate < threshold
context.log(sprintf("WARNING: Coverage %.1f%% is below %.0f%% threshold", ...
lineRate * 100, threshold * 100));
end
end
```
Use this variant when the test task writes Cobertura XML via `CoberturaFormat` but does NOT produce a `.mat` file. Adjust the path (`reports/codecoverage.xml`) to match whatever the existing test task actually writes.
## Variant: Coverage with Decision-Level Metrics
For projects that need deeper coverage analysis (requires MATLAB Test toolbox):
```matlab
plan("test") = TestTask("tests", ...
SourceFiles="toolbox", ...
Dependencies="check", ...
TestResults="results/test-results.xml") ...
.addCodeCoverage(["results/coverage.xml" "results/coverage.mat"], ...
MetricLevel="decision");
```
Then in the `coverageTask`, use `"decision"` instead of `"statement"`:
```matlab
[summary, desc] = coverageSummary(covResult, "decision");
```
----
Copyright 2026 The MathWorks, Inc.
----
scripts/buildfile-template.m
function plan = buildfile
%BUILDFILE Build automation for the toolbox.
import matlab.buildtool.tasks.*
plan = buildplan(localfunctions);
% CleanTask (built-in) — deletes files declared in .Outputs of other tasks
% and clears the .buildtool/ incremental-build cache.
plan("clean") = CleanTask;
% CodeIssuesTask (built-in) — runs MATLAB Code Analyzer. Produces SARIF for
% CI integration (GitHub Code Scanning, VS Code). WarningThreshold=0 means
% the build fails on any warning — set to Inf to allow warnings through.
plan("check") = CodeIssuesTask("toolbox", ...
WarningThreshold=0, ...
Results="results/code-issues.sarif");
% MexTask (built-in) — compiles C/C++/Fortran source into MEX binaries.
% Output goes into the toolbox folder so MEX files ship with the package.
% Uses forEachFile to build one MEX per source file automatically.
% REMOVE THIS BLOCK if the project has no MEX source files.
plan("mex") = MexTask("mex/mymex.c", "toolbox");
% TestTask (built-in) — runs tests and produces coverage reports.
% The built-in task supports incremental builds: it skips when source and
% test files are unchanged since the last successful run.
% .addCodeCoverage produces both Cobertura XML (for CI tools) and .mat
% (for the coverage report task to inspect programmatically).
% To disable coverage reporting, remove the .addCodeCoverage() call.
plan("test") = TestTask("tests", ...
SourceFiles="toolbox", ...
Dependencies=["check" "mex"], ...
TestResults="results/test-results.xml") ...
.addCodeCoverage(["results/coverage.xml" "results/coverage.mat"]);
% coverageTask is CUSTOM because the built-in TestTask produces coverage
% reports but does not summarize or check them. This task loads the .mat
% results, logs per-file coverage, and warns if below the threshold.
% It does NOT fail the build — coverage is advisory. To make it a hard
% gate, replace context.log with context.assertTrue in the function below.
plan("coverage").Dependencies = "test";
% packageTask is CUSTOM because there is no built-in packaging task.
% It loads ToolboxOptions from toolboxPackaging.prj and produces the .mltbx.
plan("package").Dependencies = "coverage";
% Declaring .Outputs lets CleanTask know what to delete, and enables
% incremental build support (task skips if output already exists and
% inputs haven't changed).
plan("package").Outputs = "release/My_Toolbox.mltbx";
% DefaultTasks run on bare "buildtool" with no arguments.
% We default to quality checks + coverage report — packaging is an explicit
% action via "buildtool package".
plan.DefaultTasks = ["check" "test" "coverage"];
end
function coverageTask(context)
% Report code coverage and warn if below threshold (does not fail the build)
%
% Why custom instead of built-in:
% - TestTask produces coverage reports but has no summary/threshold parameter
% - This function loads the .mat coverage data from TestTask's output,
% logs a per-file breakdown, and warns if below threshold
% - Separating reporting from test execution means TestTask retains
% incremental build support (skips when nothing changed)
%
% The context argument (TaskContext) provides:
% - context.log() — structured logging (respects buildtool verbosity)
% - context.assertTrue() — fails the task with a diagnostic message
% - context.Plan.RootFolder — absolute path to project root
% Always use these instead of disp()/fprintf()/assert().
coverageFile = fullfile("results", "coverage.mat");
if ~isfile(coverageFile)
context.log("Coverage data not found — skipping. Run the test task first.");
return
end
data = load(coverageFile);
covResult = data.Result;
% coverageSummary (R2023b+) returns Nx2 matrix: [executed, total] per file.
[summary, desc] = coverageSummary(covResult, "statement");
lineRate = sum(summary(:,1)) / sum(summary(:,2));
context.log(sprintf("Coverage: %.1f%%", lineRate * 100));
% Per-file breakdown so the user can see where coverage is low.
for i = 1:size(summary, 1)
[~, name, ext] = fileparts(desc(i).statement(1).Filename);
context.log(sprintf(" %s%s: %d/%d statements", ...
name, ext, summary(i,1), summary(i,2)));
end
threshold = 0.80;
if lineRate < threshold
context.log(sprintf("WARNING: Coverage %.1f%% is below %.0f%% threshold", ...
lineRate * 100, threshold * 100));
end
end
function packageTask(context)
% Package toolbox into .mltbx
%
% Version is read from toolboxSpecification.m (single source of truth for the pipeline).
% If toolboxSpecification doesn't exist, falls back to the version in toolboxPackaging.prj.
%
% The UUID identifies the toolbox for update detection — it must remain
% stable across versions. Generate once with matlab.lang.internal.uuid.
% Read version from toolboxSpecification if available (single source of truth)
specFile = fullfile(context.Plan.RootFolder, "buildUtilities", "toolboxSpecification.m");
if isfile(specFile)
oldPath = addpath(fullfile(context.Plan.RootFolder, "buildUtilities"));
raii = onCleanup(@()(path(oldPath)));
spec = toolboxSpecification();
version = spec.toolbox.version;
else
version = "";
end
opts = matlab.addons.toolbox.ToolboxOptions("toolboxPackaging.prj");
% Apply version from spec (overrides PRJ value)
if version ~= ""
opts.ToolboxVersion = version;
end
% Output to release/ (not source-controlled). Spaces replaced with
% underscores for cross-platform filename compatibility.
releaseFolderName = "release";
mltbxFileName = strrep(opts.ToolboxName, " ", "_") + ".mltbx";
opts.OutputFile = fullfile(releaseFolderName, mltbxFileName);
if ~isfolder(releaseFolderName), mkdir(releaseFolderName); end
matlab.addons.toolbox.packageToolbox(opts);
context.assertTrue(isfile(opts.OutputFile), "Package was not created");
info = dir(opts.OutputFile);
context.log(sprintf("Package: %s v%s (%.1f KB)", opts.OutputFile, opts.ToolboxVersion, info.bytes / 1024));
end
% Copyright 2026 The MathWorks, Inc.
SKILL.md
---
name: matlab-create-buildfile
description: "Generate a MATLAB buildfile.m with tasks for static analysis, testing, coverage reporting, and packaging. Use after matlab-create-project when the project structure is in place and you need repeatable build automation."
license: https://www.mathworks.com/content/dam/mathworks/license/pmrl/license.md
metadata:
author: MathWorks
version: "1.0"
---
# matlab-create-buildfile — Build Plan Generator
You generate a `buildfile.m` that defines the repeatable build/test/package pipeline using MATLAB's `matlab.buildtool` framework.
## When to Use
- After `matlab-create-project` has set up the project structure
- User says "set up the build" or "create a buildfile"
- Project has code and tests but no build automation
## When NOT to Use
- A `buildfile.m` already exists and works — use `matlab-build-toolbox` to execute it
- User wants to run the build, not create it — use `matlab-build-toolbox`
- No MATLAB project exists yet — use `matlab-create-project` first
## Inputs
- **project_root**: Path to the project (default: current directory)
- **coverage_threshold** (optional): Line-coverage percentage to warn below (default: 80)
- **warning_threshold** (optional): Max warnings before check fails (default: 0 = strict)
## Workflow
### Step 1 — Assess What Exists
Scan the project for:
- Source folder — one of (in priority order):
1. `toolbox/` — the standard toolbox-design-guidelines layout (everything that ships)
2. `+packagename/` — namespace-package layout (from matlab-create-project)
3. `source/` or `src/` — generic source folder
- `tests/` — test files to run
- MEX source files — C/C++/Fortran files (`.c`, `.cpp`, `.cxx`, `.F`, `.f90`) in folders like `mex/`, `src/mex/`, `c_src/`, or at the project root. Presence indicates the project needs a `MexTask`.
- `toolboxPackaging.prj` — packaging configuration (produced by Toolbox Packaging Tool)
- Existing `buildfile.m` — update rather than replace
- `buildUtilities/toolboxSpecification.m` — interface spec (for context on what the toolbox exposes)
Record the detected structure — the generated buildfile must reference actual paths.
### Step 2 — Generate `buildfile.m`
Use built-in task types (`CodeIssuesTask`, `CleanTask`, `TestTask`, `MexTask`) where they exist, and custom function-based tasks only where built-in tasks lack needed behavior (coverage reporting, packaging).
**Task strategy:**
- **`clean`** — built-in `CleanTask`
- **`check`** — built-in `CodeIssuesTask` (SARIF output, threshold enforcement)
- **`mex`** — built-in `MexTask` (only if MEX source files detected in Step 1). Use `MexTask.forEachFile` when multiple MEX sources exist. Output folder is `toolbox/` (or source folder) so MEX files ship with the toolbox.
- **`test`** — built-in `TestTask` with `.addCodeCoverage()`. Produces JUnit XML test results AND a `.mat` coverage file for programmatic inspection by the coverage task. The built-in task supports incremental builds — it skips when source/tests are unchanged.
- **`coverage`** — custom function-based task that loads the `.mat` coverage results from the test task, logs per-file coverage, and warns if below the threshold. It does NOT fail the build — coverage is advisory, not a gate.
- **`package`** — custom function-based task (no built-in equivalent for toolbox packaging).
**Include comments in the generated buildfile** that explain design choices — particularly why a task is custom vs. built-in, what tradeoffs that creates, and how the user could switch approaches.
Use `scripts/buildfile-template.m` as the base template. Apply these adaptation rules:
- Replace `"toolbox"` with the actual source folder detected in Step 1
- Replace `"tests"` if tests live elsewhere
- Replace `0.80` with the user's coverage threshold (as a decimal)
- Replace `0` in `WarningThreshold` with the user's warning threshold
- If MEX source files were detected, add a `MexTask` with appropriate source paths and output folder. Set `plan("test").Dependencies` to include `"mex"` so tests run after MEX compilation.
- If no MEX source files exist, omit the `mex` task entirely (don't generate dead code).
- If no `toolboxPackaging.prj` exists, use the programmatic variant from `references/buildfile-variants.md`
- Set `plan("package").Outputs` to match the actual output path
### Step 3 — Present the Plan
```
## Build Plan — [Toolbox Name]
| Task | Type | Description | Dependencies | Fail condition |
|------|------|-------------|--------------|----------------|
| clean | CleanTask | Remove derived artifacts | — | — |
| check | CodeIssuesTask | Static analysis (SARIF output) | — | Any error; any warning (strict) |
| mex | MexTask | Compile MEX files (if detected) | — | MEX compilation fails |
| test | TestTask | Run tests + produce coverage | check, mex (if present) | Any test failure |
| coverage | Custom | Report coverage, warn if below threshold | test | — (advisory only) |
| package | Custom | Build .mltbx from toolboxPackaging.prj | coverage | Package file not produced |
Default: `buildtool` → runs check + test + coverage
Full pipeline: `buildtool package` → check → [mex] → test → coverage → package
List tasks: `buildtool -tasks`
CI invocation: `matlab -batch "buildtool check test coverage package"`
### Artifacts Produced
| File | Format | Consumer |
|------|--------|----------|
| results/code-issues.sarif | SARIF v2.1.0 | GitHub Code Scanning, VS Code |
| results/test-results.xml | JUnit XML | CI test reporting |
| results/coverage.xml | Cobertura XML | CI coverage tools |
| results/coverage.mat | MAT-file | Coverage report task (programmatic) |
| release/My_Toolbox.mltbx | Toolbox installer | End users |
How would you like to proceed?
> A) **Approve** — write the buildfile as shown
> B) **Adjust** — modify tasks, thresholds, or dependencies
> C) **Skip** — don't create a buildfile now
```
### Step 4 — Persist
**If `buildfile.m` does NOT exist:** Write it to the project root. Add `results/` and `release/` to `.gitignore` if it exists.
**If `buildfile.m` already exists:** Do NOT edit it directly. Instead:
1. **Read the existing test task** to determine where coverage data is produced (path and format). The existing test task may write Cobertura XML, `.mat`, or both — and may use a different output directory (e.g., `reports/` vs. `results/`). The coverage report task MUST reference the actual output path and format produced by the test task.
2. Show a diff or code block of the proposed additions/modifications (new tasks, updated dependencies, new local functions).
3. Explain what each change does and why.
4. **Wait for explicit user approval** ("yes", "go ahead", "looks good") before applying any edits.
5. Only after the user confirms, apply the changes to the existing `buildfile.m`.
This approval gate prevents surprising edits to working build automation that the user may have customized.
## Output
- `buildfile.m` — the complete build plan
## Checkpoint
**Yes** — user reviews the task chain before it's written. They can adjust order, thresholds, and which tasks are included.
## Key Rules
- **Comment design decisions in the generated code.** Every task should have a comment explaining whether it's built-in or custom and WHY. For custom tasks, explain what the built-in alternative lacks and what tradeoff the custom approach introduces. Include a commented-out snippet showing how to switch to the simpler alternative. The buildfile is a teaching artifact — the user must be able to understand and maintain it without re-running this skill.
- **Use built-in tasks where they exist.** `CodeIssuesTask`, `CleanTask`, `TestTask`, and `MexTask` are battle-tested — don't reimplement them as function tasks.
- **TestTask handles testing AND coverage production.** Use the built-in `TestTask` with `.addCodeCoverage()` to produce both Cobertura XML (for CI) and `.mat` (for programmatic threshold checking). This gives incremental build support — the task skips when source/tests are unchanged.
- **Coverage reporting is a separate custom task.** The `coverageTask` loads coverage data, logs per-file results, and warns if below threshold — but does NOT fail the build. Coverage is advisory. To make it a hard gate, the user can replace the warning `context.log` with `context.assertTrue`.
- **Coverage task must match actual test output.** When adding a coverage task to an existing buildfile, read the test task (or its helper) to determine the actual coverage output path and format. If the test task produces Cobertura XML (e.g., `reports/codecoverage.xml`), parse the `line-rate` attribute from the XML root. If it produces `.mat` (from `TestTask.addCodeCoverage`), use `coverageSummary`. Never hardcode `results/coverage.mat` without verifying that the test task actually writes it.
- **MexTask for MEX compilation.** When MEX source files are detected, use the built-in `MexTask` (or `MexTask.forEachFile` for multiple sources). Place output in the source/toolbox folder so compiled MEX files ship with the toolbox. Tests must depend on the mex task.
- **Custom tasks use `context`.** Always accept the `context` argument and use `context.log()` for output, `context.assertTrue()` for failure conditions. NEVER use `disp()`, `fprintf()`, or `warning()` for status output in task functions — always `context.log()`. NEVER use bare `assert()` for failures — always `context.assertTrue()`.
- **Single test run.** The built-in `TestTask` with `.addCodeCoverage()` instruments coverage in the same run that checks pass/fail — never run tests twice.
- **Package from PRJ.** Load `ToolboxOptions` from `toolboxPackaging.prj` — this is the single source of truth for toolbox identity, files, and metadata. Only fall back to programmatic construction if no PRJ exists.
- **Never hardcode the version in packageTask.** The version must be read from `buildUtilities/toolboxSpecification.m` (if it exists) or from the PRJ file — never written as a literal string in `buildfile.m`. Hardcoded versions create drift: `matlab-publish-toolbox` updates `toolboxSpecification.m` before packaging, but a hardcoded `opts.ToolboxVersion = "1.0.0"` silently overrides it. The spec is the single source of truth for version.
- **Output to `release/`.** The `.mltbx` goes in `release/` (not source-controlled). Replace spaces with underscores in the filename for cross-platform compatibility.
- **Produce CI artifacts.** Always emit SARIF (code issues), JUnit XML (test results), Cobertura XML (coverage), and `.mat` (for coverage reporting) — these are the standard formats consumed by GitHub Actions, Azure DevOps, Jenkins, and the coverage task.
- **Declare outputs on package task.** Setting `.Outputs` lets `CleanTask` know what to delete and enables incremental build support.
- **`DefaultTasks = ["check" "test" "coverage"]`.** Running bare `buildtool` should validate code quality including coverage. Packaging is an explicit action (`buildtool package`).
- **Update, don't replace.** If `buildfile.m` already exists, add missing tasks rather than overwriting existing customization. Always propose changes as a plan and wait for user approval before editing.
- **Detect structure, don't assume.** The source folder varies (`toolbox/`, `+pkg/`, `source/`). Always verify what exists before generating.
- **Omit MEX task if no MEX sources.** Don't generate a mex task with placeholder paths — only include it when C/C++/Fortran source files are actually detected.
## Next Steps
- `/matlab-assess-toolbox` — validate readiness across all checks before building
- `/matlab-build-toolbox` — execute the build plan and produce the `.mltbx` artifact
----
Copyright 2026 The MathWorks, Inc.
----