Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).
- Added `WIREIT_CACHE` environment variable, which controls caching behavior.
Can be `local` or `none` to disable.

- Added `if-file-deleted` option to the `clean` settings. In this mode,
`output` files are deleted if any of the input files have been deleted since
the last run.

### Changed

- In watch mode, the terminal is now cleared at the start of each run, making it
Expand Down
103 changes: 89 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
- [Cleaning output](#cleaning-output)
- [Watch mode](#watch-mode)
- [Package locks](#package-locks)
- [Recipes](#recipes)
- [TypeScript](#typescript)
- [Reference](#reference)
- [Configuration](#configuration)
- [Dependency syntax](#dependency-syntax)
Expand Down Expand Up @@ -291,13 +293,15 @@ a script. This is helpful for ensuring that every build is clean and free from
outdated files created in previous runs from source files that have since been
removed.

To enable output cleaning, configure the output files for each script by
specifying the `wireit.<script>.output` array (see [caching](#caching) for
example).
Cleaning is enabled by default as long as the `output` array is declared (see
[caching](#caching) for an example). To change this behavior, set the
`wireit.<script>.clean` property to one of these values:

To disable this behavior, set `<script>.clean` to `false`. You should only
disable cleaning if you are certain that the script itself already takes care of
removing outdated files from previous runs.
| Setting | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `true` | Clean before every run (the default). |
| `"if-file-deleted"` | Clean only if an input file has been deleted since the last run.<br><br>Use this option for tools that have incremental build support, but do not clean up outdated output when a source file has been deleted, such as `tsc --build` (see [TypeScript](#typescript) for more on this example.) |
| `false` | Do not clean.<br><br>Only use this option if you are certain that the script command itself already takes care of removing outdated files from previous runs. |

## Watch mode

Expand Down Expand Up @@ -357,21 +361,92 @@ If you're sure that a script isn't affected by dependencies at all, you can turn
off this behavior entirely to improve your cache hit rate by setting
`wireit.<script>.packageLocks` to `[]`.

## Recipes

This section contains advice about integrating specific build tools with Wireit.

### TypeScript

#### Use incremental build

Set [`"incremental": true`](https://www.typescriptlang.org/tsconfig#incremental)
in your `tsconfig.json`, and use the
[`--build`](https://www.typescriptlang.org/docs/handbook/project-references.html#build-mode-for-typescript)
(or `-b`) flag in your `tsc` command. This enables TypeScript's incremental
compilation mode, which significantly reduces compile times.

#### Use clean if-file-deleted

The [`"clean": "if-file-deleted"`](#cleaning-output) setting provides the best
balance between fast and correct output, giving you an incremental build when a
`.ts` source file is added or modified, and a clean build when a `.ts` source
file is deleted.

`"clean": true` (the default) is not a good option, because it either eliminates
the benefits of incremental compilation, or causes your `.tsbuildinfo` to get
out of sync, depending on whether you include your `.tsbuildinfo` file in the
`output` array.

`"clean": false` is also not a good option, because it causes stale outputs to
accumulate. This is because when you delete or rename a `.ts` source file, `tsc`
itself does not automatically delete the corresponding `.js` file emitted by
previous compiles.

#### Include .tsbuildinfo in output.

Include your
[`.tsbuildinfo`](https://www.typescriptlang.org/tsconfig#tsBuildInfoFile) file
in your `output` array. Otherwise, when Wireit performs a clean build, the
`.tsbuildinfo` file will get out-of-sync with the output, and `tsc` will wrongly
skip emit because it believes the output is already up-to-date.

#### Include tsconfig.json in files.

Include your `tsconfig.json` file in your `files` array so that Wireit knows to
re-run when you change a setting that affects compilation.

#### Use the --pretty flag

By default, `tsc` only shows colorful stylized output when it detects that it is
attached to an interactive (TTY) terminal. The processes spawned by Wireit do
not perceive themselves to be attached to an interactive terminal, because of
the way Wireit captures `stdout` and `stderr` for replays. The
[`--pretty`](https://www.typescriptlang.org/tsconfig#pretty) flag forces `tsc`
to emit colorful stylized output even on non-interactive terminals.

#### Example

```json
{
"scripts": {
"ts": "wireit"
},
"wireit": {
"ts": {
"command": "tsc --build --pretty",
"clean": "if-file-deleted",
"files": ["src/**/*.ts", "tsconfig.json"],
"output": ["lib/**", ".tsbuildinfo"]
}
}
Comment thread
AndrewJakubowicz marked this conversation as resolved.
}
```

## Reference

### Configuration

The following properties can be set inside `wireit.<script>` objects in
`package.json` files:

| Property | Type | Default | Description |
| -------------- | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
| `command` | `string` | `undefined` | The shell command to run. |
| `dependencies` | `string[]` | `undefined` | [Scripts that must run before this one](#dependencies). |
| `files` | `string[]` | `undefined` | Input file [glob patterns](#glob-patterns), used to determine the [cache key](#cache-key). |
| `output` | `string[]` | `undefined` | Output file [glob patterns](#glob-patterns), used for [caching](#caching) and [cleaning](#cleaning-output). |
| `clean` | `boolean` | `true` | [Delete output files before running](#cleaning-output). |
| `packageLocks` | `string[]` | `['package-lock.json']` | [Names of package lock files](#package-locks). |
| Property | Type | Default | Description |
| -------------- | ------------------------------ | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
| `command` | `string` | `undefined` | The shell command to run. |
| `dependencies` | `string[]` | `undefined` | [Scripts that must run before this one](#dependencies). |
| `files` | `string[]` | `undefined` | Input file [glob patterns](#glob-patterns), used to determine the [cache key](#cache-key). |
| `output` | `string[]` | `undefined` | Output file [glob patterns](#glob-patterns), used for [caching](#caching) and [cleaning](#cleaning-output). |
| `clean` | `boolean \| "if-file-deleted"` | `true` | [Delete output files before running](#cleaning-output). |
| `packageLocks` | `string[]` | `['package-lock.json']` | [Names of package lock files](#package-locks). |

### Dependency syntax

Expand Down
32 changes: 12 additions & 20 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,18 @@ export class Analyzer {
}
}

if (wireitConfig?.clean !== undefined) {
assertBoolean(placeholder, wireitConfig.clean, 'clean');
if (
wireitConfig?.clean !== undefined &&
wireitConfig.clean !== true &&
wireitConfig.clean !== false &&
wireitConfig.clean !== 'if-file-deleted'
Comment on lines +239 to +241

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if actionable, wondering if this should all be the same type? All strings instead of a bool and string combo.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered "always" and "never" instead of true and false. Somehow "never" feels stronger than false, and the potentially confusing thing is that we actually always do a clean build when restoring from cache. I'm not sure.

@augustjk augustjk Apr 8, 2022

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"on", "off"? or "enabled", "disabled"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"on", "off"? or "enabled", "disabled"?

Those seem semantically equivalent to true and false, but I feel like it might be harder to remember those strings over true and false.

) {
throw new WireitError({
script: placeholder,
type: 'failure',
reason: 'invalid-config-syntax',
message: `clean must be true, false, or "if-file-deleted"`,
});
}

if (wireitConfig?.packageLocks !== undefined) {
Expand Down Expand Up @@ -426,24 +436,6 @@ const assertString = (
}
};

/**
* Throw an error if the given value is not a boolean.
*/
const assertBoolean = (
script: ScriptReference,
value: unknown,
name: string
) => {
if (typeof value !== 'boolean') {
throw new WireitError({
type: 'failure',
reason: 'invalid-config-syntax',
script,
message: `${name} is not a boolean`,
});
}
};

/**
* Throw an error if the given value is not an Array.
*/
Expand Down
6 changes: 3 additions & 3 deletions src/caching/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type {CacheKeyString, ScriptReference} from '../script.js';
import type {ScriptStateString, ScriptReference} from '../script.js';

/**
* Saves and restores output files to some cache store (e.g. local disk or
Expand All @@ -23,7 +23,7 @@ export interface Cache {
*/
get(
script: ScriptReference,
cacheKey: CacheKeyString
cacheKey: ScriptStateString
): Promise<CacheHit | undefined>;

/**
Expand All @@ -37,7 +37,7 @@ export interface Cache {
*/
set(
script: ScriptReference,
cacheKey: CacheKeyString,
cacheKey: ScriptStateString,
relativeFilePaths: string[]
): Promise<void>;
}
Expand Down
8 changes: 4 additions & 4 deletions src/caching/local-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {getScriptDataDir} from '../util/script-data-dir.js';
import {optimizeCopies, optimizeMkdirs} from '../util/optimize-fs-ops.js';

import type {Cache, CacheHit} from './cache.js';
import type {ScriptReference, CacheKeyString} from '../script.js';
import type {ScriptReference, ScriptStateString} from '../script.js';

/**
* Caches script output to each package's
Expand All @@ -20,7 +20,7 @@ import type {ScriptReference, CacheKeyString} from '../script.js';
export class LocalCache implements Cache {
async get(
script: ScriptReference,
cacheKey: CacheKeyString
cacheKey: ScriptStateString
): Promise<CacheHit | undefined> {
const cacheDir = this.#getCacheDir(script, cacheKey);
try {
Expand All @@ -36,7 +36,7 @@ export class LocalCache implements Cache {

async set(
script: ScriptReference,
cacheKey: CacheKeyString,
cacheKey: ScriptStateString,
relativeFiles: string[]
): Promise<void> {
// TODO(aomarks) A script's cache directory currently just grows forever.
Expand Down Expand Up @@ -86,7 +86,7 @@ export class LocalCache implements Cache {
);
}

#getCacheDir(script: ScriptReference, cacheKey: CacheKeyString): string {
#getCacheDir(script: ScriptReference, cacheKey: ScriptStateString): string {
return pathlib.join(
getScriptDataDir(script),
'cache',
Expand Down
Loading