target()
The target() function defines a target in the build system.
Parameters
-
name: stringA unique identifier for the target. Responsible for invoking, distinguishing and caching the target. If the target is an artifact, it should be a file path (string).
-
deps?: DependenciesDependencies of this target. If a dependency is an artifact, it should be a file path (string).
-
virtual?: booleanIf the target is virtual (i.e. no artifact, analogous to PHONY in Makefile).
-
doc?: stringThe documentation of the target.
-
rebuild?: 'always' | 'never' | 'onChanged'The rebuild mode of the target.
always: always rebuild the target.never: never rebuild the target.onChanged: rebuild the target if any of its dependencies has been changed. When the dependencies are not specified, the target will always be rebuilt.onChangedwill not track the changes of directory contents.
-
logError?: booleanWhether to log errors when building the target fails (default to true).
-
build: (arg: { deps: InferTargets, target: string }) => void | Promise<void>The build logic of the target.
Reference source code.
interface TargetParams
{
/**
* A unique identifier for the target.
*
* Responsible for invoking, distinguishing and caching the target.
*/
name: string,
/**
* Dependencies of the target.
*
* If a dependency is an artifact, it should be a file path (string).
*/
deps?: Deps,
/**
* If the target is virtual (i.e. no artifact, analogous to PHONY in Makefile).
*/
virtual?: boolean,
/**
* The documentation of the target.
*/
doc?: string,
/**
* The build logic of the target.
*/
build: (arg: { deps: InferencedDeps, target: string }) => void | Promise<void>,
/**
*
* The rebuild mode of the target.
*
* - 'always': always rebuild the target.
*
* - 'never': never rebuild the target.
*
* - 'onChanged': rebuild the target if any of its dependencies has been changed.
*
* !!! PS1: when the dependencies are not specified, the target will always be rebuilt.
*
* !!! PS2: 'onChanged' will not track the changes of directory contents
*/
rebuild?: 'always' | 'never' | 'onChanged',
/**
*
* Whether to log errors when building the target fails (default to true).
*/
logError?: boolean
}
Retrieving the Computed Target Name
In practice, the name parameter of the build function may be a computed one. Hence, we might reference the target name in the build logic using the target parameter.
NM.target(
{
name: 'output.txt',
async build( { target } )
{
console.log(target) // 'output.txt'
}
})
Type-safe Retrieval of Dependencies
The deps parameter of target() have multiple forms that ease the retrieval of dependencies in the build logic using a type-safe manner.
A dependency can be the following cases:
string- the return of
target()(type:Target) - an array of the above types or an async generator of the above types.
- a record with the above types as values.
The source code defines the following types including BuildDependency:
export type BuildDependency =
| string
| Target
export type BuildDependencySugar =
| AsyncGenerator<BuildDependency, any, any>
| BuildDependency[]
| BuildDependency
// the type of `deps` in `target()`
export type BuildDependencies =
| BuildDependencySugar
| { [key: string]: BuildDependencySugar }
When retrieving dependencies in the build logic, the deps parameter gives you an "inferenced" result of your dependencies:
const list_targets: NM.Target[] = [NM.target({ name: 'b.txt', ... })]
async function* async_targets()
{
yield 'c.txt'
for await (...) yield ...
}
NM.target(
{
name: 'build',
virtual: true, // like phony in Makefile
deps: { a: 'a.txt', b: list_targets, c: async_targets },
async build({ deps })
{
console.log(deps.a) // 'a.txt'
console.log(deps.b) // [ 'b.txt' ]
console.log(deps.c) // [ 'c.txt', ... ]
}
})
The mapping of dependencies to the deps parameter is as follows:
| Dependencies | Inferenced Dependencies |
|---|---|
BuildDependency |
string |
BuildDependency[] |
string[] |
AsyncGenerator<BuildDependency> |
string[] |
{ a: BuildDependency } |
{ a: string } |
{ a: BuildDependency[] } |
{ a: string[] } |
{ a: AsyncGenerator<BuildDependency> } |
{ a: string[] } |
You might be refer to the Dependency definition.
Grouping Dependencies
You can group dependencies using Record-style composition, which helps your organize your dependencies.
import * as NM from 'https://github.com/thautwarm/nomake/raw/v0.1.12/mod.ts'
function glob(options: { root: string, suffix: string, excludeParts?: string[] })
{
const { root, suffix, excludeParts } = options
return new NM.Path(root).fwalk(
(p) => p.endsWith(suffix) && !excludeParts?.some(part => p.parts.includes(part)),
{ includeDir: false, recursive: true }
)
}
NM.target(
name: './linux-x64/app',
deps:
{
cppSources: glob({ root: 'src', suffix: '.cpp', excludeParts: ['generated'] }),
libraries: ['lib1.so', 'lib2.so'],
async build( { target, deps: { cppSources, libraries } } )
{
// you might do something like:
// gcc $cppSources -l:$libraries -o $target
}
})
Virtual Targets
A virtual target is a target that does not produce an artifact. It is analogous to the PHONY target in Makefile.
import * as NM from 'https://github.com/thautwarm/nomake/raw/v0.1.12/mod.ts'
NM.target(
{
name: 'clean',
virtual: true,
async build()
{
await new NM.Path('build').rm(
{
// no worries as all the parameters have
// 1. **intellisense support**
// 2. **exhaustive type checking**
// 3. **documentations**
onError: 'ignore',
recursive: true
}
)
}
})
Caching & Rebuilding
Similar to Makefile, NoMake provides a mechanism to automatically rebuild targets based on the changes in their dependencies.
However, flexibility are left to the user to specify the rebuild mode of the target.
The rebuild parameter of target() specifies the rebuild mode of the target. It can be one of the following:
always: always rebuild the target.never: never rebuild the target.onChanged: rebuild the target if any of its dependencies has been changed.
Note
The default behavior when rebuild is not specified:
- When no
depsare specified,rebuildis set toalways - When any
depsare specified,rebuildis set toonChanged
The following code demonstrates the default behavior of the rebuild parameter:
import * as NM from 'https://github.com/thautwarm/nomake/raw/v0.1.12/mod.ts'
NM.target(
{
name: 'clean',
virtual: true,
async build()
{
// this build logic will be always executed
}
})
NM.target(
{
name: 'output.txt',
deps: ['input.txt'],
async build({ deps, target })
{
// this build logic will be executed only when 'input.txt' is changed
}
})
Adding Documentations
The doc parameter of target() specifies the documentation of the target.
So far, only virtual targets have been documented. The documentation of the target can be accessed via the help option.
import * as NM from 'https://github.com/thautwarm/nomake/raw/v0.1.12/mod.ts'
NM.option(
'legacy',
{
callback: ({ value }) => { /* do stuff with value */ },
doc: 'Enable legacy mode.'
})
// see parseOptions() for more details
NM.parseOptions()
NM.target(
{
name: 'build',
virtual: true,
doc: 'This target generates the output file.',
async build()
{
// build logic
}
})
// trigger the build process
await NM.makefile()
You can trigger the documentations by running the following command:
sh> deno run -A build.ts help
Targets:
[build] This target generates the output file.
Options:
[-Dlegacy=value] Enable legacy mode.