compi 0.5.1

A build system written in Rust.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# Compi

![GitHub License](https://img.shields.io/github/license/allyedge/compi)

![Crates.io Total Downloads](https://img.shields.io/crates/d/compi)

A build system written in Rust.

## Features

- **Clean TOML structure**: Compi uses a clean TOML structure to define tasks.
- **Safety**: Compi uses dependencies, inputs and outputs defined for the tasks to warn you of potential issues.
- **Dependencies**: By using dependencies, Compi allows for a clean representation of very complex command chains.
- **Parallel execution**: Compi runs independent tasks concurrently for faster execution.

## Installation

### From crates.io

```bash
cargo install compi
```

### From GitHub

```bash
cargo install --git https://github.com/allyedge/compi
```

### From Source

```bash
git clone https://github.com/allyedge/compi
cd compi
cargo install --path .
```

### From GitHub Releases

Download the latest binary for your platform from the [GitHub Releases](https://github.com/allyedge/compi/releases) page.

#### Linux

```bash
wget https://github.com/allyedge/compi/releases/latest/download/compi-linux
chmod +x compi-linux
sudo mv compi-linux /usr/local/bin/compi
```

#### macOS

```bash
wget https://github.com/allyedge/compi/releases/latest/download/compi-macos
chmod +x compi-macos
sudo mv compi-macos /usr/local/bin/compi
```

#### Windows

Download the executable from the releases page and add it to your PATH.

## CLI Usage

```bash
# Run default task
compi

# Run specific task
compi build

# Use a different config file
compi -f my-config.toml

# Verbose output
compi -v build

# Remove outputs after successful execution
compi --rm build

# Run with specific number of workers
compi -j 8 build

# Set task timeout and dry run
compi -t 30s --dry-run test

# Continue on failure with verbose output
compi --continue-on-failure -v all

# Combine flags
compi --rm -v -j 2 test

# Show help
compi --help
```

### Command Options

- `-f, --file <FILE>`: Configuration file (default: `compi.toml`)
- `-v, --verbose`: Enable verbose output
- `--rm`: Remove outputs after successful task execution
- `-j, --workers <N>`: Number of parallel workers (default: CPU cores)
- `-t, --timeout <DURATION>`: Default timeout for tasks (e.g., "30s", "5m")
- `--output <MODE>`: Output mode (`stream` or `group`, default: `group`)
- `--dry-run`: Show what would be executed without running tasks
- `--continue-on-failure`: Continue running independent tasks when others fail
- `TASK`: Task to run

### Output modes

By default, Compi forwards task output to your terminal. The default output mode is **group**, so parallel tasks do not interleave output.

- **`--output group` (default)**: Captures stdout/stderr per task and prints it as a single block after the task finishes (stdout and stderr are separated).
- **`--output stream`**: Streams each task’s stdout/stderr live as the task runs.

Examples:

```bash
# Default (grouped)
compi --output group

# Live output (may interleave when tasks run in parallel)
compi --output stream
```

## Configuration Format

Create a `compi.toml` file in your project root:

### Basic Structure

```toml
[config]
default = "build"           # Default task to run
cache_dir = "cache"         # Cache directory
workers = 4                 # Number of parallel workers (default: CPU cores)
default_timeout = "5m"      # Default timeout for tasks
output = "group"            # Output mode: "stream" or "group" (default: "group")

[task.task_name]
command = "shell command"   # Command to execute
dependencies = ["dep1"]     # Tasks that must run before this one
inputs = ["src/*.rs"]       # Input files
outputs = ["target/app"]    # Output files
auto_remove = false         # Automatically remove outputs after successful execution
always_run = false          # Always run this task
timeout = "10m"             # Task-specific timeout (overrides default)
```

### Example Configuration

```toml
[config]
default = "build"
cache_dir = ".build-cache"

[variables]
TARGET_DIR = "target"
APP_NAME = "myapp"
SOURCE_PATTERN = "src/**/*.rs"
TEST_PATTERN = "tests/**/*.rs"

[task.prepare]
id = "prep"
command = "mkdir -p ${TARGET_DIR}"
outputs = ["${TARGET_DIR}/"]

[task.build]
command = "cargo build"
dependencies = ["prep"]
inputs = [
    "${SOURCE_PATTERN}",
    "Cargo.toml"
]
outputs = ["${TARGET_DIR}/debug/${APP_NAME}"]

[task.test]
command = "cargo test"
dependencies = ["build"]
inputs = ["${SOURCE_PATTERN}", "${TEST_PATTERN}"]

[task.clean]
command = "rm -rf ${TARGET_DIR}/"
```

## Variables

Compi supports variables for reducing duplication and making configurations more maintainable.

### Variable Definition

Define variables in the `[variables]` section:

```toml
[variables]
TARGET_DIR = "target"
BUILD_TYPE = "debug"
BINARY_NAME = "myapp"
SOURCE_PATTERN = "src/**/*.rs"
COMPILE_FLAGS = "--release --target x86_64-unknown-linux-gnu"
```

### Variable Usage

Use variables anywhere in your configuration with `${VAR_NAME}` or `$VAR_NAME` syntax:

```toml
[task.build]
command = "cargo build ${COMPILE_FLAGS}"
inputs = ["${SOURCE_PATTERN}", "Cargo.toml"]
outputs = ["${TARGET_DIR}/${BUILD_TYPE}/${BINARY_NAME}"]
```

### Built-in Variables

Compi provides several built-in variables:

- `$PWD`: Current working directory
- `$ENV_*`: All environment variables prefixed with `ENV_` (e.g., `$ENV_HOME`, `$ENV_USER`)

### Environment Variables

Access environment variables using the `ENV_` prefix:

```toml
[variables]
HOME_DIR = "${ENV_HOME}"
USER_NAME = "${ENV_USER}"

[task.deploy]
command = "scp app ${ENV_USER}@server:/home/${ENV_USER}/bin/"
```

## Task Fields

### Required Fields

- `command`: Shell command to execute

### Optional Fields

- `id`: Override the task name (defaults to `[task.name]`)
- `dependencies`: Array of task names that must run first
- `inputs`: Array of input files/patterns (supports globs)
- `outputs`: Array of output files this task produces
- `auto_remove`: Automatically remove outputs after successful execution (default: `false`)
- `always_run`: Always run this task (default: `false`).
- `timeout`: Task timeout duration (e.g., "30s", "5m", "1h") - overrides default

## Build Logic

Compi uses a 4-tier system to determine if a task should run:

1. **No inputs**: Always run (e.g., cleanup tasks)
2. **Missing outputs**: Must run if any output file doesn't exist
3. **Outdated outputs**: Run if any input is newer than any output
4. **Content changed**: Run if file content hash changed since last run

Notes:

- Tasks with **inputs** may be **skipped** on subsequent runs if inputs haven't changed. For "run-only" tasks, prefer `always_run = true` (or omit `inputs`).
- Output globs like `*.o` are treated as required outputs: if the pattern matches nothing, the task is considered out-of-date and will run.

## Glob Patterns

Input files support standard glob patterns:

- `*.rs`: All Rust files in current directory
- `src/**/*.rs`: All Rust files in src and subdirectories
- `test/*.{rs,toml}`: Rust and TOML files in test directory

## Dependency Management

- Tasks run in topological order based on dependencies
- Independent tasks can execute in parallel using configurable worker pools
- Circular dependencies are detected and reported as errors
- Missing dependencies cause build failure

## Output Cleanup

Compi provides two ways to automatically clean up outputs after successful task execution:

### CLI Flag

Use the `--rm` flag to remove outputs after running a task:

```bash
# Remove outputs after building
compi --rm build

# Remove outputs with verbose logging
compi --rm -v test
```

### Auto-Remove Field

Set `auto_remove = true` in task configuration for automatic cleanup:

```toml
[task.temp_build]
command = "gcc -o temp_app *.c"
outputs = ["temp_app", "*.o"]
auto_remove = true  # Always clean up after successful execution

[task.generate_docs]
command = "doxygen"
outputs = ["docs/"]
# Only removed if --rm flag is used
```

### Behavior

- **Only on success**: Outputs are only removed if the task exits with code 0
- **Glob expansion**: Patterns like `*.o` are expanded to actual files before removal
- **Safe deletion**: Only the files/directories explicitly listed in outputs are deleted

## Cache System

- Stores file content hashes to detect changes
- Configurable location via `cache_dir` in config
- Cache location is relative to the config file

## Error Handling

- **Errors**: Stop execution (missing tasks, circular deps, command failures)
- **Warnings**: Continue execution (missing files, glob errors)
- **Info**: Verbose mode only (dependency relationships)

## Examples

### Simple Build Pipeline

```toml
[config]
default = "deploy"

[task.compile]
command = "gcc *.c -o app"
inputs = ["*.c", "*.h"]
outputs = ["app"]

[task.test]
command = "./app --test"
dependencies = ["compile"]
inputs = ["app"]

[task.deploy]
command = "scp app server:/"
dependencies = ["test"]
inputs = ["app"]
```

### Build + Run

For "run" tasks, you usually want the command to execute every time (even if nothing changed).
Use `always_run = true` and keep a dependency on `build`:

```toml
[task.build]
command = "gcc *.c -o app"
inputs = ["*.c", "*.h"]
outputs = ["app", "*.o"]

[task.run]
command = "./app"
dependencies = ["build"]
always_run = true
```

### Multi-Language Project

```toml
[config]
default = "all"

[task.js]
command = "npm run build"
inputs = ["src/**/*.js", "package.json"]
outputs = ["dist/app.js"]

[task.css]
command = "sass src/style.scss dist/style.css"
inputs = ["src/**/*.scss"]
outputs = ["dist/style.css"]

[task.all]
dependencies = ["js", "css"]
command = "echo 'Build complete'"
```

### Build with Cleanup

```toml
[config]
default = "package"

[task.compile]
command = "gcc *.c -o app"
inputs = ["*.c", "*.h"]
outputs = ["app", "*.o"]

[task.test]
command = "./app --test > test.log"
dependencies = ["compile"]
inputs = ["app"]
outputs = ["test.log"]
auto_remove = true  # Always clean up test logs

[task.package]
command = "tar -czf app.tar.gz app"
dependencies = ["test"]
inputs = ["app"]
outputs = ["app.tar.gz"]

# Usage:
# compi --rm compile  # Removes app and *.o files after compilation
# compi test          # Automatically removes test.log (auto_remove = true)
# compi package       # Keeps app.tar.gz (no cleanup)
```

## License

[MIT](./LICENSE)