Clue builds C and C++ projects. It is written in Go, and projects describe their targets in CUE, whose schema rejects a misspelled field or a value of the wrong type before anything compiles. A conventional project needs no configuration at all.
Install from source:
go install github.com/loov/clue@latestEach tagged release has Linux, macOS and Windows archives with SHA-256 checksums on the GitHub releases page.
Or build locally for development:
go build -o clue .A conventional project builds without a configuration file. clue build makes a target of each
directory with C, C++ or .s/.S assembly sources. A directory with a main.c or main.cpp
becomes an executable, and the others become static libraries. Directories with project headers
become include roots, and includes between directories become dependencies between their targets.
Clue uses the first complete Clang, GCC or MSVC toolchain it finds on the host.
Clue skips generated, dependency and hidden directories. Add a clue.cue when the conventions
can't express the targets or their dependencies. Clue then uses it and infers nothing:
name: "hello"
version: "1.0.0"
toolchain: {
compiler: "clang"
cxxStd: "c++17"
}
targets: {
hello: {
name: "hello"
type: "executable"
sources: ["main.cpp"]
}
}Validate and build:
clue validate # Check configuration
clue build # Build projectclue.cue may declare a CUE package and split the configuration across other .cue files in
its directory. Every .cue file next to clue.cue belongs to the configuration, so keep
dependency descriptions in a subdirectory, as a package that clue.cue imports. A target's
name defaults to its key. sources and headers accept globs such as src/*.cpp, where **
matches any number of directories, and exclude removes matches again, as in
exclude: ["**/win32/*"]. _target.os and _target.arch hold the platform being built, and
_project.dir the absolute project directory:
if _target.os == "windows" {
targets.app.defines: ["WINDOWS_BUILD"]
}To run the compiler, linker, archiver and build commands in a container, name an image that has the toolchain installed:
toolchain: {
compiler: "clang"
cx
10BC8
xStd: "c++23"
container: {
runtime: "podman" // optional
image: "project-toolchain:20"
workdir: "/workspace"
}
}Clue starts a new container for each command, with the project directory mounted at workdir.
Compilers and pkg-config run inside it, so the image needs the headers and libraries the build
uses. Clue mounts nothing outside the project directory. Without runtime, clue uses the first of
Docker, Podman, Apple container and nerdctl that it finds; set runtime to use another
Docker-compatible command. Clue doesn't pull image, so it must already exist in that runtime.
Instead of image, a containerfile lets clue build and cache the image, with the project
directory as the build context:
toolchain: container: {
containerfile: "toolchain/Containerfile"
platform: "linux/amd64" // optional image platform
workdir: "/workspace"
}Set exactly one of image and containerfile.
A cross build can name its tools. Clue refuses a cross target that would otherwise fall back to the host compiler:
toolchain: {
compiler: "clang"
cc: "clang"
cxx: "clang++"
ar: "llvm-ar"
targetTriple: "aarch64-linux-gnu"
sysroot: "/opt/aarch64-sysroot"
}On a Mac, Clang builds for macOS against the SDK that xcrun --show-sdk-path reports (set
SDKROOT or sysroot to use another), and --target darwin-amd64 or darwin-arm64 builds for the
other architecture. Programs run on the build machine's macOS version and later, unless
macos.deploymentTarget names an older one:
toolchain: macos: deploymentTarget: "13.0" // -mmacosx-version-min=13.0launcher runs each compile through a compiler cache such as ccache or sccache, which serves a
file again from its cache after an edit is reverted. Links run without it, and
compile_commands.json names the compiler itself:
toolchain: launcher: ["ccache"]The zig toolchain cross-compiles without a sysroot, as Zig ships libc and libc++ for
linux-amd64, linux-arm64, windows-amd64, windows-arm64 and wasi-wasm32. Pick the
platform with --target, as in clue build --target wasi-wasm32. Each platform gets its own build
directory, <buildDir>/<os>-<arch>, and Ninja file, build.<os>-<arch>.ninja. _target in
clue.cue, and clue.target in packages that import "loov.dev/clue", is the platform being
built. emulator runs cross-built programs for clue test and clue run:
toolchain: {
compiler: [if _target.os == "darwin" {"clang"}, "zig"][0]
if _target.os == "wasi" {emulator: ["wasmtime", "run", "--dir=/"]}
if _target.os == "linux" {
emulator: ["podman", "run", "--rm", "--platform", "linux/\(_target.arch)",
"-v", "\(_project.dir):\(_project.dir)", "-w", _project.dir, "debian:stable-slim"]
}
}Mark an executable that runs during the build, such as a code generator or a validator, with
host: true. Clue builds it for the machine running clue, even with --target. The host build
uses the configuration that clue.cue gives for the host and the host's build directory, and it
builds the targets the executable depends on for the host too. {output:name} refers to that
build. clue run and clue test run it without the emulator, clue install skips it, and a
cross Ninja file builds it by running clue build:
targets: gen: {type: "executable", host: true, sources: ["tools/gen.cpp"]}On WASI, executables and bundles are .wasm files, and a bundle or shared library is a reactor
module with the listed exports. Zig's libc++ for WebAssembly has no exception support, so clue
compiles WASI code with -fno-exceptions.
clue validatechecks the configuration and the dependencies.clue format [path...]formats CUE files in place, in the current directory by default.clue buildbuilds all targets.-variant releasegives an optimized build.clue cleanremoves build artifacts, of all variants with-all.clue graphdraws the target dependency graph in the terminal, or as SVG with-format svg.-format dotwrites it for Graphviz, and-format tgfin the Trivial Graph Format.clue run <target|task> [args...]builds and runs an executable target, or runs a task.clue test [name|label...]builds and runs the configured tests.clue install [target...]builds and installs artifacts and public headers, but not bundles.clue deps <list|fetch|build|clean|update|tidy>manages external dependencies.clue generate <ninja|compile-commands|all>writes build files for editors and other tools.clue generate schemawrites theloov.dev/clueschema into the CUE module, forcueand editors.clue exec <file>runs the command in a JSON list of arguments. Ninja files generated on Windows use it for arguments with newlines, which no Windows command line can hold.clue help [command]andclue version.
clue format walks directories recursively. It skips cue.mod, hidden directories and
directories starting with _, unless you name them. It prints the paths of the files it changed,
which --quiet hides. It formats files even when the configuration doesn't evaluate.
Long GCC, Clang and MSVC compile and link commands use response files, in clue build and in
generated Ninja files.
Ninja files build into <buildDir>/ninja, apart from clue build, and run the clue that
generated them to fetch dependencies. Ninja regenerates its file when the configuration's CUE
files, dependency description files or patches change.
clue.lock records the Git commits and tarball checksums that clue fetched. Commit it, so every
build uses the same dependency revisions, and run clue deps update to resolve the Git refs
again. Builds keep the entries of dependencies they don't use, because the configuration for
another target can declare them. clue deps tidy removes the entries that the configuration of
the selected target doesn't declare.
clue watch builds once, then watches the project for changes to sources, headers, modules,
assembly and CUE files. It waits for 300 ms without changes before building. A new change cancels
a build in progress, and a CUE change reloads the configuration. It ignores .git, .deps and
.build.
Watching relies on the operating system's file notifications, so use a local checkout. NFS, SMB and other network filesystems may not report changes, and a very large tree can exceed the operating system's limit on watches.
clue build -profile -v lists the translation units that took longest to compile, and -top N
sets how many. -profile -save-profile also writes a Chrome Trace file to
.build/<variant>/profile.json, which Perfetto and other trace viewers open.
To profile clue itself, CLUE_CPUPROFILE=cpu.prof clue build writes a CPU profile of the run for
go tool pprof. It works with every command.
-variant debug|releaseselects the variant. The default isdefaultVariant, else debug.-j Nsets the number of parallel jobs. The default, 0, runs one job per CPU core.-vprints each build step.-quietprints errors only.-rebuild-allrebuilds every file.-keep-goingcontinues building after errors.-target <platform>cross-compiles, for example for linux-arm64, darwin-amd64 or windows-amd64.-prefix <path>sets the installation prefix.-destdir <path>stages an installation for packaging.-profilerecords compile times, and-vthen prints the slowest files.-save-profilewrites the recorded times as Chrome Trace JSON.-top Nsets how many slow files profiling prints. The default is 10.
name: "calculator"
version: "1.0.0"
toolchain: {
compiler: "clang"
cxxStd: "c++17"
}
targets: {
mathlib: {
name: "mathlib"
type: "static_library"
sources: ["lib/math.cpp"]
headers: ["lib/arithmetic.h"]
public: {
includes: ["lib"]
}
}
app: {
name: "app"
type: "executable"
sources: ["src/main.cpp"]
depends: ["mathlib"]
}
}Clang, GCC and MSVC builds support named modules, interface and internal partitions, and imports
across targets. A target that imports a module of another target must list that target in
depends. List header units explicitly, so clue knows which headers need a BMI:
toolchain: {compiler: "clang", cxxStd: "c++20"}
targets: {
math: {
name: "math"
type: "static_library"
sources: ["math.cppm", "math-detail.cpp"]
}
app: {
name: "app"
type: "executable"
sources: ["main.cpp"]
depends: ["math"]
headerUnits: [
{name: "vector", system: true},
{name: "project/config.hpp", path: "include/project/config.hpp"},
]
}
}Sources then import those headers with import <vector>; and import "project/config.hpp";.
GCC builds use a generated module mapper, MSVC builds IFC references and Clang builds PCM
references. Which system headers can become header units still depends on the compiler and its
standard library.
stdModule: true builds the standard library modules std and std.compat into every C++
target, so its sources and the headers they include can import std;:
toolchain: {compiler: "clang", cxxStd: "c++23", stdModule: true}Clue finds the module sources from the manifest the compiler reports, such as libc++'s
libc++.modules.json. Where there is none, as with Apple's clang or Zig, set stdModule to the
path of the manifest or of std.cppm from the same standard library version. Each target builds
its own std, because Clang rejects one built with other language options such as
-fno-exceptions. For the same reason the C++ sources of a target must agree on the language
options in their sourceFlags; warnings, macros, include paths, optimization and debug flags may
differ. Container toolchains copy the module sources from the image into the build directory.
defaultVariant: "release" // used without --variant or CLUE_VARIANT; default "debug"
variants: {
debug: {
optimization: "none"
debug_info: true
}
release: {
optimization: "aggressive"
debug_info: false
}
}Clue reads the variables declared under env from the environment, or uses their default.
when_true adds defines and flags to every target when the value is 1, true, yes or on.
_env holds the values as strings, for conditions in clue.cue. Undeclared variables aren't in
_env, so the environment changes the build only where clue.cue says so:
env: {
TRACE: {name: "TRACE", default: false, when_true: defines: ["TRACE=1"]}
VALIDATE: {name: "VALIDATE", default: false}
}
if _env.VALIDATE == "1" {
targets: validator: {type: "executable", sources: ["tools/validator/*.cpp"]}
}VALIDATE=1 clue build builds the validator too, and a plain clue build doesn't.
defaults holds settings for every compiled target, which is every target except custom targets
and interface libraries. Its lists come before each target's own entries, and its single values
apply where a target sets none:
defaults: {
warnings: "strict"
warningsAsErrors: false
visibility: "hidden" // -fvisibility=hidden
flags: compiler: ["-fno-exceptions", "-fno-rtti"]
}
targets: legacy: {
type: "static_library"
sources: ["legacy/*.cpp"]
warnings: "off"
// flags for some sources only, after the target's; these stay out of unity batches
sourceFlags: "legacy/rtti.cpp": ["-frtti"]
}Every flag is a separate string, so -framework Security is two entries. CUE has no + on lists;
join lists with list.Concat, and flatten lists that a comprehension builds with list.FlattenN:
import "list"
_frameworks: ["Security", "CoreFoundation", "Network"]
targets: app: {
type: "executable"
sources: ["main.c"]
flags: linker: list.Concat([
["-lz"],
list.FlattenN([for f in _frameworks {["-framework", f]}], 1),
])
}A unity build compiles sources in batches, so the compiler starts less often and parses shared headers once per batch:
targets.app: {
name: "app"
type: "executable"
sources: ["src/*.cpp"]
unity: {
batchSize: 8
exclude: ["src/legacy.cpp", "src/generated.cpp"]
}
}batchSize defaults to 8. C and C++ sources go in separate batches, and assembly and C++ module
sources stay out of batches. exclude keeps out files whose macros, anonymous namespaces or other
file-local state clash when combined. It takes the same globs as sources and must select
sources of that target.
A test block makes an executable a test and configures how it runs:
targets: unit_tests: {
name: "unit_tests"
type: "executable"
sources: ["tests/unit.cpp"]
test: {
args: ["--reporter", "console"]
env: TEST_DATA: "tests/data"
labels: ["unit", "fast"]
}
}clue test runs every test. Its arguments select tests by target name or label, and -j sets how
many run at once. args may name another target's artifact as {output:name}, which expands to
an absolute path, and clue test builds that target first.
clue install copies executables to bin, libraries to lib, and declared headers to
include, keeping their paths below public.includes. The prefix defaults to /usr/local.
-prefix changes it, and -destdir stages a package:
clue install -variant release -prefix /usr -destdir ./pkgUse a custom target when a tool produces sources or headers that a target compiles:
targets: {
generate: {
name: "generate"
type: "custom"
command: ["protoc", "--cpp_out=generated", "schema.proto"]
inputs: ["schema.proto"]
outputs: ["generated/schema.pb.cc", "generated/schema.pb.h"]
}
app: {
name: "app"
type: "executable"
sources: ["main.cpp", "generated/schema.pb.cc"]
includes: ["generated"]
depends: ["generate"]
}
}A custom target can run in a workingDirectory, where its placeholders expand to absolute paths.
It can also write its standard output to a stdout file, which counts as an output, so a
generator needs no shell:
targets: version_header: {
type: "custom"
command: ["git", "describe", "--always"]
stdout: "{buildDir}/gen/version.txt"
}A custom target's command, inputs and outputs may use these placeholders:
{variant}is the variant name.{buildDir}is the variant's build directory, such as.build/release.{output:name}is the artifact of targetname. For a bundle target that's the bundle a host loads: the.clapor.vst3directory, or the module file where the layout has no directory.{output:name:module}is a bundle's linked module, and{output:name:bundle}the bundle.
Clue adds a target named in command or inputs to depends. A custom target with placeholders
runs once per variant, so its outputs must differ per variant too. Include paths and compiler and
linker flags may use {variant} and {buildDir} to find generated files, such as headers or a
version script:
targets: bundle: {
name: "bundle"
type: "custom"
depends: ["plugin"]
command: ["sh", "-c", "mkdir -p \"$(dirname \"$2\")\" && cp \"$1\" \"$2\"", "bundle",
"{output:plugin}", "dist/{variant}/Plugin.clap/Contents/MacOS/Plugin"]
inputs: ["{output:plugin}"]
outputs: ["dist/{variant}/Plugin.clap/Contents/MacOS/Plugin"]
}Clue keeps a custom target's build fingerprint in the build directory, not next to its outputs.
A target that depends on a custom target builds after it but doesn't link what the command
reads: the custom target's own depends are inputs to its command. With link: true, dependents
link the custom target's outputs instead, in depends order, so a renamed copy of a library
replaces the original:
targets: thirdRenamed: {
type: "custom"
link: true
command: ["objcopy", "--redefine-syms=redef.txt", "{output:third}", "{buildDir}/redef/libthird.a"]
inputs: ["redef.txt"]
outputs: ["{buildDir}/redef/libthird.a"]
}
targets: plugin: {type: "shared_library", sources: ["plugin.cpp"], depends: ["thirdRenamed"]}The system libraries of the original aren't linked either; list those in the consumer's sysLibs.
With linkWhole: true as well, dependents link every member of the output archives, as they would
object files, and put them right after their own objects, before other libraries. A target that depends on a custom target without link relinks when its outputs
change, so a generated version script is picked up.
{clue} is clue itself. clue configure <template> <output> [name=value]... writes output from
template, replacing each @name@ with its value. A value name=@file inserts the file's
contents without the final line ending. Clue doesn't touch the inserted text, nor @name@ for
names it isn't given. That lets a custom target generate a file without a shell:
targets: license: {
type: "custom"
command: ["{clue}", "configure", "src/license.h.in", "{buildDir}/gen/license.h",
"VERSION=1.2", "LICENSE=@LICENSE.txt"]
inputs: ["src/license.h.in", "LICENSE.txt"]
outputs: ["{buildDir}/gen/license.h"]
}With const char* license = R"(@LICENSE@)"; in the template, the header holds the license text
without any escaping.
A task runs its command on clue run <task> [args...], every time, and never during
clue build. Clue appends the arguments to the command, and the command's exit status becomes
clue's. Put -- before arguments that start with -. Before running the command, clue builds the
targets in depends and those the command names with {output:name}, so a task always checks
fresh artifacts:
targets: validate: {
type: "task"
command: ["{output:validator}", "{output:plugin}"]
}Tasks take the same placeholders as custom targets, and a workingDirectory. No target can
depend on a task, and generated Ninja files leave tasks out.
A target that only tasks or tests use, such as a validator, can be optional: true. A plain
clue build then builds it only when a target that it builds depends on it, clue install leaves
it out, and so does the default target of generated Ninja files. clue build <name>, clue run
and clue test still build it when they need it:
targets: validator: {type: "executable", optional: true, sources: ["tools/validator/*.cpp"]}Declare programs that commands run, but that the project doesn't build, under tools. Custom
targets, tasks and test args refer to them as {tool:name}. When the variable that env names
is set, clue runs the program it gives. Otherwise clue tries each entry of find, as a name in
PATH or as a path, and uses the first it finds. When none exists, the error includes install:
tools: clangFormat: {
find: ["clang-format", "/opt/homebrew/opt/llvm/bin/clang-format"]
env: "CLANG_FORMAT"
install: "brew install clang-format"
}
targets: fmt: {type: "task", command: ["{tool:clangFormat}", "-i", "{git-files:src/**/*.{cpp,hpp}}"]}An argument {git-files:pattern} expands to the project's files that match the pattern and that
git doesn't ignore, tracked or not. The pattern is a glob with ** and {a,b} alternatives.
Clue leaves out the build directory and .deps, and a pattern that matches nothing is an error.
It works in the command and inputs of custom targets and tasks, and in test args.
A bundle target links a loadable module. On macOS clue links it with -bundle, puts it at
<dir>/<name>.<extension>/Contents/MacOS/<name> with Info.plist and PkgInfo, and signs the
bundle. Elsewhere the module is <dir>/<name>.<extension>. With layout: "vst3" it goes in the
VST3 bundle folder instead, as <dir>/<name>.vst3/Contents/<arch>-linux/<name>.so, or
<arch>-win/<name>.vst3 on Windows. exports keeps the listed C symbols even when only static
libraries define them, and exports only those:
targets: plugin: {
type: "bundle"
sources: ["plugin/entry.cpp"]
depends: ["dsp"]
exports: ["clap_entry"]
bundle: {
extension: "clap"
name: "My Plugin" // default: the target name
dir: "dist/{variant}" // default: {buildDir}
identifier: "com.example.my-plugin" // generates Info.plist; or infoPlist: "path"
// sign: "Developer ID Application: ..." // default ad hoc; false to skip
}
}exports works on shared libraries and executables too. Consumers of a static library with
linkWhole: true link all of it, right after their own objects. A shared library or bundle may consist of its dependencies
alone, without sources of its own.
A header-only Git, tarball or vendored dependency needs only its include directory:
dependencies: json: {
type: "vendored"
path: "vendor/json"
build: {
targetType: "header_only"
includes: ["include"]
}
}A header-only library in the project is an interface_library target. Its consumers inherit its
public requirements, transitively:
targets: headers: {
name: "headers"
type: "interface_library"
public: {
includes: ["include"]
systemIncludes: ["vendor/include"]
cxxStd: "c++20"
}
}For an already-built library, point at the exact artifact instead:
dependencies: sdk: {
type: "vendored"
path: "vendor/sdk"
build: {
targetType: "prebuilt_static" // or "prebuilt_shared"
library: "lib/libsdk.a"
includes: ["include"]
}
}An SDK of several archives lists them all in libraries, which consumers link in that order:
build: {
targetType: "prebuilt_static"
libraries: ["lib/libsdk-s3.a", "lib/libsdk-core.a", "lib/libsdk-crt.a"]
includes: ["include"]
}A pkg_config dependency takes a system package's compiler and linker flags from pkg-config:
dependencies: ssl: {
type: "pkg_config"
package: "openssl" // defaults to the dependency name
static: false // use pkg-config --static when true
}toolchain.pkgConfigPath lists the directories to find .pc files in, such as a vcpkg
installation's. It replaces pkg-config's search path, so a package missing from them isn't found
elsewhere on the system:
toolchain: pkgConfigPath: ["vcpkg_installed/arm64-osx/lib/pkgconfig"]Include directories from every dependency are system include directories (-isystem), so
warnings in its headers don't fail the build. pkg-config's -W warning options are dropped, and
include directories and defines that several dependencies list are passed once. A link passes
each library once, however many dependencies and target settings name it.
clue validate runs pkg-config for each pkg_config dependency, unless the toolchain is a
container.
A dependency built by CMake, Meson or another tool runs a list of commands and names the library they produce:
dependencies: foo: {
type: "vendored"
path: "vendor/foo"
build: {
targetType: "external_static" // or "external_shared"
commands: [
["cmake", "-S", ".", "-B", "build"],
["cmake", "--build", "build", "--target", "foo"],
]
library: "build/libfoo.a"
includes: ["include"]
}
}A dependency without a clue.cue can carry its build description: defaults and targets,
written as in a clue.cue of the dependency, with paths relative to its checkout. target picks
the library that the dependency's name refers to, and "<dependency>:<target>" in depends
refers to another target of the same checkout. A dependency may also declare dependencies of
its own. Those join the project's, recursively, and clue fetches them, locks them in the
project's clue.lock and builds them like the project's own.
Descriptions fit in a CUE package of the project, which clue.cue imports. A project belongs to
the nearest cue.mod at or above it, as with the cue command. A project in no module becomes
the module clue.local at clue's CUE language version, so it imports deps/ as
"clue.local/deps" with no setup.
Clue's schema is importable as "loov.dev/clue", which needs language version v0.15.0 or later.
clue generate schema writes it into the module's cue.mod/gen, so the cue command and
editors find it too. clue.#Git, clue.#Tarball, clue.#Vendored and clue.#PkgConfig check a
description in a file of its own and fill in its type. A dependency with a name can be listed
without repeating the name. It also gets lib, with a reference to each of its libraries, such
as lib.vst3 == "clap-wrapper:vst3", and CUE checks those references where they're used:
// deps/vst3sdk.cue
package deps
import "loov.dev/clue"
vst3sdk: clue.#Git & {
name: "vst3sdk"
repo: "https://github.com/steinbergmedia/vst3sdk"
ref: "v3.8.0_build_66"
submodules: ["base", "public.sdk", "pluginterfaces"] // all submodules when omitted
defaults: flags: compiler: ["-fvisibility=hidden"]
targets: vst3sdk: {
type: "static_library"
warnings: "off"
sources: ["base/**/*.cpp", "public.sdk/source/main/*.cpp"]
exclude: ["**/dllmain.cpp", "**/linuxmain.cpp"]
public: {includes: [".", "public.sdk", "pluginterfaces"], defines: ["RELEASE=1"]}
}
}// deps/clap-wrapper.cue
package deps
import "loov.dev/clue"
clapWrapper: clue.#Git & {
name: "clap-wrapper"
repo: "https://github.com/free-audio/clap-wrapper"
ref: "v0.16.0"
target: "shared"
dependencies: [vst3sdk] // brought in for the project
targets: {
shared: {type: "static_library", sources: ["src/clap_proxy.cpp"]}
// inside a description, its libraries are named with strings
vst3: {type: "static_library", sources: ["src/wrapasvst3*.cpp"], depends: ["clap-wrapper", "vst3sdk"]}
}
}// clue.cue
import "clue.local/deps"
dependencies: [deps.clapWrapper]
targets: plugin_vst3: {type: "bundle", depends: [deps.clapWrapper.lib.vst3], bundle: extension: "vst3"}A description package can also offer templates to its consumers, as CMake modules do for CMake
projects. With CUE's experimental functions, a template is a function with declared parameters,
required ones marked !, optional ones ?, and defaults. Clue uses CUE v0.18.0-alpha.2, and
projects get its language version:
@experiment(functions)
package deps
// Plugin makes the bundle targets of a CLAP plugin.
Plugin: func(dir: string = "dist/{variant}", name!: string, sources!: [...string]) -> {...}: {
// Bind parameters first: inside {dir: dir}, dir would be the field itself.
let Sources = sources
let Dir = dir
targets: "\(name)_clap": {type: "bundle", sources: Sources, bundle: {extension: "clap", dir: Dir}}
}@experiment(functions)
import "clue.local/deps"
targets: deps.Plugin(name: "synth", sources: ["plugin.c"]).targetsWithout the experiment, a definition that the project unifies with its values does the same.
Descriptions can be shared as CUE modules. Clue loads the configuration with the registry
settings of the cue command, which are $CUE_REGISTRY or else the central registry. A project
whose cue.mod depends on a published module of descriptions imports them like its own
packages, and cue mod get and cue mod tidy manage that dependency. Clue evaluates a
dependency's own clue.cue as the package of its directory, as it does a project's, so it can
import clue's schema and packages of its repository, and use _target.
A description can instead be a separate file named by file, which clue reads on its own, not
as part of a package. A dependency's own clue.cue may declare dependencies too, keyed or
listed. Clue finds those after fetching the dependency, and keeps fetching until no new ones
appear. Paths in a fetched clue.cue are relative to its checkout.
Dependencies share one namespace. Every declaration of a name must name the same source: the same
repository, ref and submodules, the same URL and checksum, or the same path. Clue reports a source
that dependencies declare under two names. The project settles conflicts. Its own declaration of
a name wins and takes the description that another declaration gives, and overrides changes the
source of a dependency wherever it's declared:
overrides: vst3sdk: ref: "v3.8.1_build_12"Inline build blocks accept flags and warnings too, and pass every include directory on to
consumers.
Project targets can compile files of a dependency checkout with {dep:name}, for example
sources: ["{dep:clap-wrapper}/src/wrapasauv2.cpp"]. Clue expands globs there once the
dependency is fetched. Dependencies build in parallel, alongside the targets that don't need
them, and each target waits only for the dependencies it uses.
Clue clones Git dependencies at depth 1, also when clue.lock pins a commit, and checks out
their submodules shallowly too. Objective-C (.m) and Objective-C++ (.mm) sources compile like
C and C++ sources.
Git and tarball dependencies can list unified diffs in patches, which clue applies in order
after fetching. A relative path is relative to the CUE file that lists it, so deps/visage.cue
below finds deps/patches/metal.patch. That holds for a file description and a fetched
dependency's own clue.cue too:
// deps/visage.cue
visage: clue.#Git & {
name: "visage"
repo: "https://github.com/VitalAudio/visage"
ref: "v1.0.0"
patches: ["patches/metal.patch"]
}The fetched checkout or archive stays unpatched. Clue applies the patches to a copy,
.deps/git/visage-v1.0.0.patched-<hash>, and builds, Ninja files and {dep:visage} use the
copy. The hash covers the contents of the patches, so editing a patch makes a new copy and
rebuilds the dependency, without fetching the sources again. Clue makes a copy completely or not
at all. When a patch fails, clue reports the dependency, the patch file, the file in it and the
hunk, and leaves no copy. clue deps list shows the patches of each dependency.
Clue applies patches itself, without git or patch. Git diffs from git diff or
git format-patch may create, delete and rename files. For other unified diffs, such as those
from diff -u, clue strips the first component of their paths, like patch -p1. Hunks must
match exactly where they say, with no offset or fuzz, since the sources they apply to are pinned.
Vendored dependencies are part of the project, so edit them in place instead.
Patches belong to a dependency's source, so every declaration of a name must list the same
patches. In overrides, patches replaces the dependency's patches wherever it's declared, and
[] removes them. extraPatches apply after them:
overrides: vst3sdk: extraPatches: ["patches/vst3sdk-warnings.patch"]