tasktree 0.0.5__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tasktree-0.0.5 → tasktree-0.0.7}/PKG-INFO +33 -47
- {tasktree-0.0.5 → tasktree-0.0.7}/README.md +31 -46
- {tasktree-0.0.5 → tasktree-0.0.7}/pyproject.toml +2 -1
- tasktree-0.0.7/requirements/bug-report-dependency-triggering.md +222 -0
- tasktree-0.0.7/schema/README.md +118 -0
- tasktree-0.0.7/schema/tasktree-schema.json +195 -0
- tasktree-0.0.7/schema/vscode-settings-snippet.json +10 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/cli.py +66 -31
- tasktree-0.0.7/src/tasktree/docker.py +413 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/executor.py +268 -31
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/graph.py +30 -1
- tasktree-0.0.7/src/tasktree/hasher.py +54 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/parser.py +118 -15
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/state.py +1 -1
- {tasktree-0.0.5 → tasktree-0.0.7}/tasktree.yaml +3 -1
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_cli_options.py +222 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_dependency_execution.py +160 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_working_directory.py +41 -0
- tasktree-0.0.7/tests/unit/test_docker.py +277 -0
- tasktree-0.0.7/tests/unit/test_environment_tracking.py +356 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_parser.py +50 -4
- {tasktree-0.0.5 → tasktree-0.0.7}/uv.lock +11 -0
- tasktree-0.0.5/src/tasktree/hasher.py +0 -27
- {tasktree-0.0.5 → tasktree-0.0.7}/.github/workflows/release.yml +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/.github/workflows/test.yml +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/.gitignore +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/.python-version +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/CLAUDE.md +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/example/source.txt +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/example/tasktree.yaml +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/requirements/future/docker-task-environments.md +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/requirements/implemented/shell-environment-requirements.md +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/__init__.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/__init__.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/tasks.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/src/tasktree/types.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_clean_state.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_end_to_end.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_input_detection.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_missing_outputs.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_nested_imports.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/integration/test_state_persistence.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_cli.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_executor.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_graph.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_hasher.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_state.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_tasks.py +0 -0
- {tasktree-0.0.5 → tasktree-0.0.7}/tests/unit/test_types.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasktree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A task automation tool with incremental execution
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: click>=8.1.0
|
|
7
7
|
Requires-Dist: colorama>=0.4.6
|
|
8
|
+
Requires-Dist: pathspec>=0.11.0
|
|
8
9
|
Requires-Dist: pyyaml>=6.0
|
|
9
10
|
Requires-Dist: rich>=13.0.0
|
|
10
11
|
Requires-Dist: typer>=0.9.0
|
|
@@ -108,52 +109,6 @@ Boom! Done. `build` will always run, because there's no sensible way to know wha
|
|
|
108
109
|
|
|
109
110
|
This is a toy example, but you can image how it plays out on a more complex project.
|
|
110
111
|
|
|
111
|
-
## Migrating from v1.x to v2.0
|
|
112
|
-
|
|
113
|
-
Version 2.0 requires all task definitions to be under a top-level `tasks:` key.
|
|
114
|
-
|
|
115
|
-
### Quick Migration
|
|
116
|
-
|
|
117
|
-
Wrap your existing tasks in a `tasks:` block:
|
|
118
|
-
|
|
119
|
-
```yaml
|
|
120
|
-
# Before (v1.x)
|
|
121
|
-
build:
|
|
122
|
-
cmd: cargo build
|
|
123
|
-
|
|
124
|
-
# After (v2.0)
|
|
125
|
-
tasks:
|
|
126
|
-
build:
|
|
127
|
-
cmd: cargo build
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Why This Change?
|
|
131
|
-
|
|
132
|
-
1. **Clearer structure**: Explicit separation of tasks from configuration
|
|
133
|
-
2. **No naming conflicts**: You can now create tasks named "imports" or "environments"
|
|
134
|
-
3. **Better error messages**: More helpful validation errors
|
|
135
|
-
4. **Consistency**: All recipe files use the same format
|
|
136
|
-
|
|
137
|
-
### Error Messages
|
|
138
|
-
|
|
139
|
-
If you forget to update, you'll see a clear error:
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
Invalid recipe format in tasktree.yaml
|
|
143
|
-
|
|
144
|
-
Task definitions must be under a top-level "tasks:" key.
|
|
145
|
-
|
|
146
|
-
Found these keys at root level: build, test
|
|
147
|
-
|
|
148
|
-
Did you mean:
|
|
149
|
-
|
|
150
|
-
tasks:
|
|
151
|
-
build:
|
|
152
|
-
cmd: ...
|
|
153
|
-
test:
|
|
154
|
-
cmd: ...
|
|
155
|
-
```
|
|
156
|
-
|
|
157
112
|
## Installation
|
|
158
113
|
|
|
159
114
|
### From PyPI (Recommended)
|
|
@@ -188,6 +143,37 @@ cd tasktree
|
|
|
188
143
|
pipx install .
|
|
189
144
|
```
|
|
190
145
|
|
|
146
|
+
## Editor Support
|
|
147
|
+
|
|
148
|
+
Task Tree includes a [JSON Schema](schema/tasktree-schema.json) that provides autocomplete, validation, and documentation in modern editors.
|
|
149
|
+
|
|
150
|
+
### VS Code
|
|
151
|
+
|
|
152
|
+
Install the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml), then add to your workspace `.vscode/settings.json`:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"yaml.schemas": {
|
|
157
|
+
"https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json": [
|
|
158
|
+
"tasktree.yaml",
|
|
159
|
+
"tt.yaml"
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Or add a comment at the top of your `tasktree.yaml`:
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json
|
|
169
|
+
|
|
170
|
+
tasks:
|
|
171
|
+
build:
|
|
172
|
+
cmd: cargo build
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
See [schema/README.md](schema/README.md) for IntelliJ/PyCharm and command-line validation.
|
|
176
|
+
|
|
191
177
|
## Quick Start
|
|
192
178
|
|
|
193
179
|
Create a `tasktree.yaml` (or `tt.yaml`) in your project:
|
|
@@ -94,52 +94,6 @@ Boom! Done. `build` will always run, because there's no sensible way to know wha
|
|
|
94
94
|
|
|
95
95
|
This is a toy example, but you can image how it plays out on a more complex project.
|
|
96
96
|
|
|
97
|
-
## Migrating from v1.x to v2.0
|
|
98
|
-
|
|
99
|
-
Version 2.0 requires all task definitions to be under a top-level `tasks:` key.
|
|
100
|
-
|
|
101
|
-
### Quick Migration
|
|
102
|
-
|
|
103
|
-
Wrap your existing tasks in a `tasks:` block:
|
|
104
|
-
|
|
105
|
-
```yaml
|
|
106
|
-
# Before (v1.x)
|
|
107
|
-
build:
|
|
108
|
-
cmd: cargo build
|
|
109
|
-
|
|
110
|
-
# After (v2.0)
|
|
111
|
-
tasks:
|
|
112
|
-
build:
|
|
113
|
-
cmd: cargo build
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Why This Change?
|
|
117
|
-
|
|
118
|
-
1. **Clearer structure**: Explicit separation of tasks from configuration
|
|
119
|
-
2. **No naming conflicts**: You can now create tasks named "imports" or "environments"
|
|
120
|
-
3. **Better error messages**: More helpful validation errors
|
|
121
|
-
4. **Consistency**: All recipe files use the same format
|
|
122
|
-
|
|
123
|
-
### Error Messages
|
|
124
|
-
|
|
125
|
-
If you forget to update, you'll see a clear error:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
Invalid recipe format in tasktree.yaml
|
|
129
|
-
|
|
130
|
-
Task definitions must be under a top-level "tasks:" key.
|
|
131
|
-
|
|
132
|
-
Found these keys at root level: build, test
|
|
133
|
-
|
|
134
|
-
Did you mean:
|
|
135
|
-
|
|
136
|
-
tasks:
|
|
137
|
-
build:
|
|
138
|
-
cmd: ...
|
|
139
|
-
test:
|
|
140
|
-
cmd: ...
|
|
141
|
-
```
|
|
142
|
-
|
|
143
97
|
## Installation
|
|
144
98
|
|
|
145
99
|
### From PyPI (Recommended)
|
|
@@ -174,6 +128,37 @@ cd tasktree
|
|
|
174
128
|
pipx install .
|
|
175
129
|
```
|
|
176
130
|
|
|
131
|
+
## Editor Support
|
|
132
|
+
|
|
133
|
+
Task Tree includes a [JSON Schema](schema/tasktree-schema.json) that provides autocomplete, validation, and documentation in modern editors.
|
|
134
|
+
|
|
135
|
+
### VS Code
|
|
136
|
+
|
|
137
|
+
Install the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml), then add to your workspace `.vscode/settings.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"yaml.schemas": {
|
|
142
|
+
"https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json": [
|
|
143
|
+
"tasktree.yaml",
|
|
144
|
+
"tt.yaml"
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Or add a comment at the top of your `tasktree.yaml`:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json
|
|
154
|
+
|
|
155
|
+
tasks:
|
|
156
|
+
build:
|
|
157
|
+
cmd: cargo build
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
See [schema/README.md](schema/README.md) for IntelliJ/PyCharm and command-line validation.
|
|
161
|
+
|
|
177
162
|
## Quick Start
|
|
178
163
|
|
|
179
164
|
Create a `tasktree.yaml` (or `tt.yaml`) in your project:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tasktree"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.7"
|
|
4
4
|
description = "A task automation tool with incremental execution"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
|
10
10
|
"click>=8.1.0",
|
|
11
11
|
"rich>=13.0.0",
|
|
12
12
|
"colorama>=0.4.6",
|
|
13
|
+
"pathspec>=0.11.0",
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Bug Report: False Dependency Triggering in Incremental Execution
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Tasks are incorrectly triggered to run when their dependencies execute, even when those dependencies produce no actual changes to their outputs. This violates the principle of incremental execution and causes unnecessary rebuilds.
|
|
6
|
+
|
|
7
|
+
## Current Behaviour
|
|
8
|
+
|
|
9
|
+
The executor performs static analysis of the dependency graph before execution:
|
|
10
|
+
|
|
11
|
+
1. Topologically sorts all tasks
|
|
12
|
+
2. For each task in order, checks if it should run by examining:
|
|
13
|
+
- Whether any dependency has `will_run=True` (static check)
|
|
14
|
+
- Whether inputs have changed (dynamic check via mtime)
|
|
15
|
+
- Other conditions (never run, no outputs, etc.)
|
|
16
|
+
3. Executes all tasks marked with `will_run=True`
|
|
17
|
+
|
|
18
|
+
The problem occurs at step 2: if a dependency is marked `will_run=True`, the current task is immediately marked to run, regardless of whether that dependency actually modified any files.
|
|
19
|
+
|
|
20
|
+
## Expected Behaviour
|
|
21
|
+
|
|
22
|
+
Tasks should run based solely on the runtime state of their inputs:
|
|
23
|
+
|
|
24
|
+
1. Topologically sort all tasks
|
|
25
|
+
2. For each task in execution order:
|
|
26
|
+
- Check if any of its inputs (explicit or implicit) have changed
|
|
27
|
+
- If inputs changed (or other valid conditions met), execute the task
|
|
28
|
+
- After execution, downstream tasks check their inputs against actual file mtimes
|
|
29
|
+
3. A dependency that runs but produces no changes should NOT trigger downstream tasks
|
|
30
|
+
|
|
31
|
+
This matches Make's behaviour: it checks whether dependency output files are newer than the target, not whether the dependency command ran.
|
|
32
|
+
|
|
33
|
+
## Reproduction Test Case
|
|
34
|
+
|
|
35
|
+
The following test demonstrates the bug:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
def test_dependency_runs_but_produces_no_changes():
|
|
39
|
+
"""
|
|
40
|
+
Test that a task whose dependency runs but produces no output changes
|
|
41
|
+
does NOT trigger re-execution.
|
|
42
|
+
|
|
43
|
+
Scenario:
|
|
44
|
+
- Task 'build' has no inputs, declares outputs (always runs, like cargo/make)
|
|
45
|
+
- Task 'build' runs but creates no new files (simulated by not touching)
|
|
46
|
+
- Task 'package' depends on 'build' (implicitly gets build outputs as inputs)
|
|
47
|
+
- Expected: 'package' should NOT run because build's outputs didn't change
|
|
48
|
+
- Actual (bug): 'package' runs because 'build' has will_run=True
|
|
49
|
+
"""
|
|
50
|
+
import tempfile
|
|
51
|
+
import os
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
from unittest.mock import patch
|
|
54
|
+
import yaml
|
|
55
|
+
|
|
56
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
57
|
+
project_root = Path(tmpdir)
|
|
58
|
+
|
|
59
|
+
# Create recipe file
|
|
60
|
+
recipe = {
|
|
61
|
+
'build': {
|
|
62
|
+
'desc': 'Simulate build tool (cargo/make) with internal dep resolution\nNo inputs - tool does its own dependency checking\nHas outputs so package can implicitly depend on them',
|
|
63
|
+
'outputs': ['build-artifact.txt'],
|
|
64
|
+
'cmd': 'touch build-artifact.txt',
|
|
65
|
+
},
|
|
66
|
+
'package': {
|
|
67
|
+
'desc': 'No explicit inputs - should implicitly inherit build-artifact.txt from build',
|
|
68
|
+
'deps': ['build'],
|
|
69
|
+
'outputs': ['package.tar.gz'],
|
|
70
|
+
'cmd': 'touch package.tar.gz',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
recipe_path = project_root / 'tasktree.yaml'
|
|
75
|
+
recipe_path.write_text(yaml.dump(recipe))
|
|
76
|
+
|
|
77
|
+
# First run: establish baseline
|
|
78
|
+
# This creates build-artifact.txt and package.tar.gz
|
|
79
|
+
from tasktree.parser import parse_recipe
|
|
80
|
+
from tasktree.state import StateManager
|
|
81
|
+
from tasktree.executor import Executor
|
|
82
|
+
|
|
83
|
+
parsed_recipe = parse_recipe(recipe_path)
|
|
84
|
+
state_manager = StateManager(project_root / '.tasktree-state')
|
|
85
|
+
executor = Executor(parsed_recipe, state_manager)
|
|
86
|
+
|
|
87
|
+
statuses = executor.execute_task('package')
|
|
88
|
+
|
|
89
|
+
assert statuses['build'].will_run # First run, no state
|
|
90
|
+
assert statuses['package'].will_run # First run, no state
|
|
91
|
+
|
|
92
|
+
# Verify files exist
|
|
93
|
+
assert (project_root / 'build-artifact.txt').exists()
|
|
94
|
+
assert (project_root / 'package.tar.gz').exists()
|
|
95
|
+
|
|
96
|
+
# Get actual mtime of build artifact
|
|
97
|
+
build_artifact_path = project_root / 'build-artifact.txt'
|
|
98
|
+
original_mtime = build_artifact_path.stat().st_mtime
|
|
99
|
+
|
|
100
|
+
# Second run: build task runs (no inputs) but produces no changes
|
|
101
|
+
# Simulate this by having build command do nothing
|
|
102
|
+
recipe['build']['cmd'] = 'echo "checking dependencies, nothing to do"'
|
|
103
|
+
recipe_path.write_text(yaml.dump(recipe))
|
|
104
|
+
|
|
105
|
+
parsed_recipe = parse_recipe(recipe_path)
|
|
106
|
+
executor = Executor(parsed_recipe, state_manager)
|
|
107
|
+
|
|
108
|
+
# Patch os.stat to return the original mtime for build-artifact.txt
|
|
109
|
+
# This simulates build running but not modifying its outputs
|
|
110
|
+
original_stat = os.stat
|
|
111
|
+
def patched_stat(path, *args, **kwargs):
|
|
112
|
+
result = original_stat(path, *args, **kwargs)
|
|
113
|
+
if Path(path) == build_artifact_path:
|
|
114
|
+
# Return stat result with original mtime
|
|
115
|
+
import os
|
|
116
|
+
stat_result = os.stat_result((
|
|
117
|
+
result.st_mode,
|
|
118
|
+
result.st_ino,
|
|
119
|
+
result.st_dev,
|
|
120
|
+
result.st_nlink,
|
|
121
|
+
result.st_uid,
|
|
122
|
+
result.st_gid,
|
|
123
|
+
result.st_size,
|
|
124
|
+
result.st_atime,
|
|
125
|
+
original_mtime, # Keep original mtime
|
|
126
|
+
result.st_ctime,
|
|
127
|
+
))
|
|
128
|
+
return stat_result
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
with patch('os.stat', side_effect=patched_stat):
|
|
132
|
+
with patch('pathlib.Path.stat', side_effect=lambda self: patched_stat(self)):
|
|
133
|
+
statuses = executor.execute_task('package')
|
|
134
|
+
|
|
135
|
+
# Build task should run (has no inputs)
|
|
136
|
+
assert statuses['build'].will_run
|
|
137
|
+
assert statuses['build'].reason == 'no_outputs'
|
|
138
|
+
|
|
139
|
+
# BUG: Package task runs because build ran, even though build's output unchanged
|
|
140
|
+
# EXPECTED: package task should NOT run (implicit inputs haven't changed)
|
|
141
|
+
assert statuses['package'].will_run == False, \
|
|
142
|
+
f"Package should not run when dependency produces no changes, " \
|
|
143
|
+
f"but will_run={statuses['package'].will_run}, reason={statuses['package'].reason}"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Root Cause Analysis
|
|
147
|
+
|
|
148
|
+
The issue is in the execution planning phase. The code attempts to determine which tasks will run before any tasks execute. This creates a dependency on static analysis of the dependency graph rather than dynamic checking of actual file states.
|
|
149
|
+
|
|
150
|
+
The specific problematic logic:
|
|
151
|
+
- Checking `if any(status.will_run for status in dep_statuses.values())`
|
|
152
|
+
- This happens before the dependency actually runs
|
|
153
|
+
- Therefore, it cannot know what the dependency will actually do to files
|
|
154
|
+
|
|
155
|
+
## Algorithmic Changes Required
|
|
156
|
+
|
|
157
|
+
The execution algorithm needs to shift from "plan then execute" to "check and execute incrementally":
|
|
158
|
+
|
|
159
|
+
**Current approach:**
|
|
160
|
+
```
|
|
161
|
+
for each task in topo order:
|
|
162
|
+
if dependency.will_run:
|
|
163
|
+
mark this task to run
|
|
164
|
+
elif inputs changed:
|
|
165
|
+
mark this task to run
|
|
166
|
+
|
|
167
|
+
for each task marked to run:
|
|
168
|
+
execute task
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Required approach:**
|
|
172
|
+
```
|
|
173
|
+
for each task in topo order:
|
|
174
|
+
if inputs changed (checking actual file mtimes):
|
|
175
|
+
execute task
|
|
176
|
+
update state
|
|
177
|
+
else:
|
|
178
|
+
skip task
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The key differences:
|
|
182
|
+
1. No separate planning phase that checks `dep_statuses`
|
|
183
|
+
2. Each task checks only the current runtime state of its inputs
|
|
184
|
+
3. Execution happens immediately when needed, not in a separate loop
|
|
185
|
+
4. State updates happen immediately after execution
|
|
186
|
+
|
|
187
|
+
## Implementation Considerations
|
|
188
|
+
|
|
189
|
+
- The `TaskStatus` dataclass and planning phase may need significant restructuring
|
|
190
|
+
- The `check_task_status` method should not receive or examine `dep_statuses`
|
|
191
|
+
- Input checking should always use current file system state, never cached planning decisions
|
|
192
|
+
- The execution order still requires topological sorting (dependencies run before dependents)
|
|
193
|
+
- Edge cases to preserve:
|
|
194
|
+
- Tasks with no inputs/outputs still run every time
|
|
195
|
+
- `--force` flag behaviour
|
|
196
|
+
- Missing outputs trigger re-execution
|
|
197
|
+
- First-run (no cached state) behaviour
|
|
198
|
+
|
|
199
|
+
## Test Validation
|
|
200
|
+
|
|
201
|
+
The test case should pass after the fix:
|
|
202
|
+
- First run: both tasks execute (establishing baseline)
|
|
203
|
+
- Second run: build runs (no inputs/outputs), package skips (inputs unchanged)
|
|
204
|
+
- The test explicitly verifies build-artifact.txt mtime is unchanged
|
|
205
|
+
- The test asserts package task does NOT run
|
|
206
|
+
|
|
207
|
+
## Related Code Locations
|
|
208
|
+
|
|
209
|
+
- `src/tasktree/executor.py`: `Executor.execute_task()` method
|
|
210
|
+
- `src/tasktree/executor.py`: `Executor.check_task_status()` method
|
|
211
|
+
- Focus on the dependency triggering logic and execution flow
|
|
212
|
+
|
|
213
|
+
## Additional Context
|
|
214
|
+
|
|
215
|
+
This bug fundamentally conflicts with the tool's purpose: intelligent incremental execution. Build tools like cargo, make, and cmake have sophisticated internal dependency tracking. When tt wraps these tools, it should respect their decisions about whether work is needed, not override them with static graph analysis.
|
|
216
|
+
|
|
217
|
+
The current implementation would cause spurious rebuilds in common workflows:
|
|
218
|
+
- `cargo build` (checks Rust dependencies, may do nothing)
|
|
219
|
+
- `make` (checks C/C++ timestamps, may do nothing)
|
|
220
|
+
- `cmake --build` (checks build graph, may do nothing)
|
|
221
|
+
|
|
222
|
+
These tools are designed to be incremental. Forcing downstream tasks to run when these tools decide nothing changed defeats the purpose of incremental builds.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Task Tree YAML Schema
|
|
2
|
+
|
|
3
|
+
This directory contains the JSON Schema for Task Tree recipe files (`tasktree.yaml` or `tt.yaml`).
|
|
4
|
+
|
|
5
|
+
## What is a YAML Schema?
|
|
6
|
+
|
|
7
|
+
The JSON Schema provides:
|
|
8
|
+
- **Autocomplete**: Get suggestions for task fields as you type
|
|
9
|
+
- **Validation**: Immediate feedback on syntax errors
|
|
10
|
+
- **Documentation**: Hover over fields to see descriptions
|
|
11
|
+
- **Type checking**: Ensure values match expected types
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### VS Code
|
|
16
|
+
|
|
17
|
+
For your project, copy the settings from `schema/vscode-settings-snippet.json` to your `.vscode/settings.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"yaml.schemas": {
|
|
22
|
+
"https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json": [
|
|
23
|
+
"tasktree.yaml",
|
|
24
|
+
"tt.yaml"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or add a comment at the top of your `tasktree.yaml`:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/kevinchannon/tasktree/main/schema/tasktree-schema.json
|
|
34
|
+
|
|
35
|
+
tasks:
|
|
36
|
+
build:
|
|
37
|
+
cmd: cargo build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### IntelliJ / PyCharm
|
|
41
|
+
|
|
42
|
+
1. Go to **Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings**
|
|
43
|
+
2. Add new mapping:
|
|
44
|
+
- **Name**: Task Tree
|
|
45
|
+
- **Schema file**: Point to `schema/tasktree-schema.json`
|
|
46
|
+
- **Schema version**: JSON Schema version 7
|
|
47
|
+
- **File path pattern**: `tasktree.yaml` or `tt.yaml`
|
|
48
|
+
|
|
49
|
+
### Command Line Validation
|
|
50
|
+
|
|
51
|
+
You can validate your recipe files using tools like `check-jsonschema`:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Install
|
|
55
|
+
pip install check-jsonschema
|
|
56
|
+
|
|
57
|
+
# Validate
|
|
58
|
+
check-jsonschema --schemafile schema/tasktree-schema.json tasktree.yaml
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Schema Features
|
|
62
|
+
|
|
63
|
+
The schema validates:
|
|
64
|
+
|
|
65
|
+
- **Top-level structure**: Only `imports`, `environments`, and `tasks` are allowed at root
|
|
66
|
+
- **Required fields**: Tasks must have a `cmd` field
|
|
67
|
+
- **Field types**: Ensures strings, arrays, and objects are used correctly
|
|
68
|
+
- **Naming patterns**: Task names and namespaces must match `^[a-zA-Z][a-zA-Z0-9_-]*$`
|
|
69
|
+
- **Environment requirements**: Environments must specify a `shell`
|
|
70
|
+
|
|
71
|
+
## Example
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
imports:
|
|
75
|
+
- file: common/base.yaml
|
|
76
|
+
as: base
|
|
77
|
+
|
|
78
|
+
environments:
|
|
79
|
+
default: bash-strict
|
|
80
|
+
bash-strict:
|
|
81
|
+
shell: /bin/bash
|
|
82
|
+
args: ['-e', '-u', '-o', 'pipefail']
|
|
83
|
+
|
|
84
|
+
tasks:
|
|
85
|
+
build:
|
|
86
|
+
desc: Build the application
|
|
87
|
+
deps: [base.setup]
|
|
88
|
+
inputs: ["src/**/*.rs"]
|
|
89
|
+
outputs: [target/release/bin]
|
|
90
|
+
cmd: cargo build --release
|
|
91
|
+
|
|
92
|
+
test:
|
|
93
|
+
desc: Run tests
|
|
94
|
+
deps: [build]
|
|
95
|
+
cmd: cargo test
|
|
96
|
+
|
|
97
|
+
deploy:
|
|
98
|
+
desc: Deploy to environment
|
|
99
|
+
deps: [build]
|
|
100
|
+
args: [environment, region=us-west-1]
|
|
101
|
+
cmd: |
|
|
102
|
+
echo "Deploying to {{environment}} in {{region}}"
|
|
103
|
+
./deploy.sh {{environment}} {{region}}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Contributing
|
|
107
|
+
|
|
108
|
+
If you find issues with the schema or want to improve it, please:
|
|
109
|
+
|
|
110
|
+
1. Update `tasktree-schema.json`
|
|
111
|
+
2. Test with your editor
|
|
112
|
+
3. Submit a pull request
|
|
113
|
+
|
|
114
|
+
## References
|
|
115
|
+
|
|
116
|
+
- [JSON Schema Specification](https://json-schema.org/)
|
|
117
|
+
- [VS Code YAML Extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml)
|
|
118
|
+
- [YAML Language Server](https://github.com/redhat-developer/yaml-language-server)
|