Skip to content

Commit a91a9f7

Browse files
thewtexclaude
andcommitted
fix(pixi): use line continuations in multi-line task commands
Recent pixi (>= 0.70, deno_task_shell) treats a bare newline in a multi-line `cmd` string as a command terminator. The `configure-itk*` cmake tasks split their flags across lines without a continuation token, so only `cmake ... -GNinja` ran and each subsequent `-D...` line was executed as its own command, failing the Native C++ CI with "command not found" (exit code 127). Append a trailing `\` to each continued line so all flags are passed to a single cmake invocation, in both the root workspace manifest and the create-itk-wasm project generator template (which emitted the same broken pattern for every scaffolded project). Also: - Add a create-itk-wasm regression test (test:pixiToml) that generates a pixi.toml with the real generator and asserts every multi-line `cmd` line ends with a shell continuation token. - Upgrade pixi.lock from format v6 to v7 (re-solved from locked content; identical package set) to clear the pixi install warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8a91bef commit a91a9f7

5 files changed

Lines changed: 5447 additions & 5339 deletions

File tree

packages/core/typescript/create-itk-wasm/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"build:watch": "tsc --pretty --watch",
1818
"lint": "prettier --check .",
1919
"lint:fix": "prettier --write .",
20-
"test": "pnpm test:help && pnpm test:defaultPipeline && pnpm test:imagePipeline && pnpm test:meshPipeline && pnpm test:polyDataPipeline",
20+
"test": "pnpm test:pixiToml && pnpm test:help && pnpm test:defaultPipeline && pnpm test:imagePipeline && pnpm test:meshPipeline && pnpm test:polyDataPipeline",
21+
"test:pixiToml": "node dist/test-pixi-toml.js",
2122
"test:defaultPipeline": "shx rm -rf test/default && node dist/create-itk-wasm.js -o test/default -n \"default-pipeline\" -a \"Test Monkey\" -d \"Default pipeline description\" --pipeline-name default-pipeline --pipeline-description \"Default pipeline description\" -r \"http://test.repo\" --pipeline-inputs \"default-input:string:A default input\" --pipeline-parameters \"a-double-param:double:A double param\" --pipeline-outputs \"a-json-output:JsonCompatible:A JSON compatible output\" --no-input && cd test/default && pixi run build && pixi run test",
2223
"test:imagePipeline": "shx rm -rf test/image && node dist/create-itk-wasm.js -o test/image -n \"image-pipeline\" -a \"Test Monkey\" -d \"Image pipeline description\" --pipeline-name image-pipeline --pipeline-description \"Image pipeline description\" -r \"http://test.repo\" --pipeline-inputs \"input-image:Image:An input image\" --pipeline-parameters \"a-float-param:float:A float param\" --pipeline-outputs \"output-image:Image:An output image\" --pipeline-dispatch Image --no-input --build-and-test",
2324
"test:meshPipeline": "shx rm -rf test/mesh && node dist/create-itk-wasm.js -o test/mesh -n \"mesh-pipeline\" -a \"Test Monkey\" -d \"Mesh pipeline description\" --pipeline-name mesh-pipeline --pipeline-description \"Mesh pipeline description\" -r \"http://test.repo\" --pipeline-inputs \"input-mesh:Mesh:An input mesh\" --pipeline-parameters \"a-float-param:float:A float param\" --pipeline-outputs \"output-mesh:Mesh:An output mesh\" --pipeline-dispatch Mesh --no-input --build-and-test",

packages/core/typescript/create-itk-wasm/src/generate/pixi-toml.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,21 @@ outputs = ["native/ITK/LICENSE"]
204204
description = "Fetch ITK's source code"
205205
206206
[feature.native.tasks.configure-itk]
207-
cmd = '''cmake -B$ITK_WASM_ITK_BUILD_DIR -S$ITK_WASM_ITK_SOURCE_DIR -GNinja
208-
-DCMAKE_CXX_STANDARD:STRING=20
209-
-DCMAKE_BUILD_TYPE:STRING=Debug
210-
-DBUILD_EXAMPLES:BOOL=OFF
211-
-DBUILD_TESTING:BOOL=OFF
212-
-DBUILD_SHARED_LIBS:BOOL=OFF
213-
-DBUILD_STATIC_LIBS:BOOL=ON
214-
-DITK_LEGACY_REMOVE:BOOL=ON
215-
-DITK_BUILD_DEFAULT_MODULES:BOOL=ON
216-
-DITKGroup_IO:BOOL=ON
217-
-DH5_HAVE_GETPWUID:BOOL=OFF
218-
-DModule_MeshToPolyData:BOOL=ON
219-
-DDO_NOT_BUILD_ITK_TEST_DRIVER:BOOL=ON
220-
-DOPJ_USE_THREAD:BOOL=OFF
221-
-DNO_FLOAT_EXCEPTIONS:BOOL=ON
207+
cmd = '''cmake -B$ITK_WASM_ITK_BUILD_DIR -S$ITK_WASM_ITK_SOURCE_DIR -GNinja \\
208+
-DCMAKE_CXX_STANDARD:STRING=20 \\
209+
-DCMAKE_BUILD_TYPE:STRING=Debug \\
210+
-DBUILD_EXAMPLES:BOOL=OFF \\
211+
-DBUILD_TESTING:BOOL=OFF \\
212+
-DBUILD_SHARED_LIBS:BOOL=OFF \\
213+
-DBUILD_STATIC_LIBS:BOOL=ON \\
214+
-DITK_LEGACY_REMOVE:BOOL=ON \\
215+
-DITK_BUILD_DEFAULT_MODULES:BOOL=ON \\
216+
-DITKGroup_IO:BOOL=ON \\
217+
-DH5_HAVE_GETPWUID:BOOL=OFF \\
218+
-DModule_MeshToPolyData:BOOL=ON \\
219+
-DDO_NOT_BUILD_ITK_TEST_DRIVER:BOOL=ON \\
220+
-DOPJ_USE_THREAD:BOOL=OFF \\
221+
-DNO_FLOAT_EXCEPTIONS:BOOL=ON \\
222222
-DITK_MSVC_STATIC_RUNTIME_LIBRARY=ON'''
223223
depends-on = ["clone-itk"]
224224
# Note: pixi does not seem to reliably support activation environmental variables in task inputs / outputs
@@ -244,12 +244,12 @@ outputs = ["native/ITK-Wasm/LICENSE"]
244244
description = "Fetch ITK's source code"
245245
246246
[feature.native.tasks.configure-itk-wasm]
247-
cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm-build
248-
-S$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm
249-
-GNinja
250-
-DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR
251-
-DBUILD_TESTING:BOOL=OFF
252-
-DCMAKE_CXX_STANDARD:STRING=20
247+
cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm-build \\
248+
-S$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm \\
249+
-GNinja \\
250+
-DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR \\
251+
-DBUILD_TESTING:BOOL=OFF \\
252+
-DCMAKE_CXX_STANDARD:STRING=20 \\
253253
-DCMAKE_BUILD_TYPE:STRING=Debug'''
254254
depends-on = ["build-itk", "clone-itk-wasm"]
255255
outputs = ["native/ITK-Wasm-build/CMakeFiles/"]
@@ -261,10 +261,10 @@ depends-on = ["configure-itk-wasm"]
261261
description = "Build ITK-Wasm"
262262
263263
[feature.native.tasks.configure-native]
264-
cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/${project.name}-build -S. -GNinja
265-
-DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR
266-
-DBUILD_TESTING:BOOL=ON
267-
-DCMAKE_CXX_STANDARD:STRING=20
264+
cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/${project.name}-build -S. -GNinja \\
265+
-DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR \\
266+
-DBUILD_TESTING:BOOL=ON \\
267+
-DCMAKE_CXX_STANDARD:STRING=20 \\
268268
-DCMAKE_BUILD_TYPE:STRING=Debug'''
269269
depends-on = ["build-itk-wasm"]
270270
description = "Configure native build"
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Regression test for the generated pixi.toml task commands.
2+
//
3+
// Recent pixi (>= 0.70) parses multi-line `cmd` strings with deno_task_shell,
4+
// where a bare newline terminates a command. A multi-line `cmd` must therefore
5+
// continue each line explicitly (e.g. with a trailing `\` or `&&`); otherwise
6+
// the trailing lines run as separate, bogus commands and the task fails with
7+
// "command not found" (exit code 127). See the `configure-itk*` cmake tasks.
8+
//
9+
// This test generates a pixi.toml with the real generator and asserts that no
10+
// multi-line command relies on a bare newline as a separator.
11+
12+
import assert from 'node:assert/strict'
13+
import fs from 'node:fs'
14+
import os from 'node:os'
15+
import path from 'node:path'
16+
17+
import generatePixiToml from './generate/pixi-toml.js'
18+
import type ProjectSpec from './project-spec.js'
19+
20+
// Shell tokens that legitimately continue a command onto the next line.
21+
const CONTINUATIONS = ['\\', '&&', '||', '|', '&']
22+
23+
function endsWithContinuation(line: string): boolean {
24+
const trimmed = line.trimEnd()
25+
return CONTINUATIONS.some((token) => trimmed.endsWith(token))
26+
}
27+
28+
function generateSampleToml(): string {
29+
const directory = fs.mkdtempSync(
30+
path.join(os.tmpdir(), 'create-itk-wasm-test-')
31+
)
32+
try {
33+
const project: ProjectSpec = {
34+
name: 'test-pipeline',
35+
directory,
36+
packageDescription: 'A test pipeline',
37+
author: 'Test Author',
38+
repositoryUrl: 'https://example.com/repo'
39+
}
40+
generatePixiToml(project)
41+
return fs.readFileSync(path.join(directory, 'pixi.toml'), 'utf8')
42+
} finally {
43+
fs.rmSync(directory, { recursive: true, force: true })
44+
}
45+
}
46+
47+
function run(): void {
48+
const toml = generateSampleToml()
49+
50+
// Every triple-quoted (multi-line) command in the manifest.
51+
const multilineCmd = /cmd = '''([\s\S]*?)'''/g
52+
const blocks = [...toml.matchAll(multilineCmd)].map((match) => match[1])
53+
54+
assert.ok(
55+
blocks.length > 0,
56+
'Expected the generated pixi.toml to contain multi-line commands; the test fixture may be stale.'
57+
)
58+
59+
const offenders: string[] = []
60+
for (const block of blocks) {
61+
const lines = block.split('\n')
62+
// Every line except the last must continue onto the next one.
63+
lines.slice(0, -1).forEach((line) => {
64+
if (line.trim() !== '' && !endsWithContinuation(line)) {
65+
offenders.push(line.trim())
66+
}
67+
})
68+
}
69+
70+
assert.deepEqual(
71+
offenders,
72+
[],
73+
`Generated pixi.toml has multi-line cmd line(s) without a continuation token ` +
74+
`(${CONTINUATIONS.join(' ')}); pixi >= 0.70 would run these as separate commands:\n` +
75+
offenders.map((line) => ` - ${line}`).join('\n')
76+
)
77+
78+
// Sanity check: the cmake configure tasks that originally regressed are
79+
// present and use backslash continuation on their opening line.
80+
for (const task of [
81+
'configure-itk',
82+
'configure-itk-wasm',
83+
'configure-native'
84+
]) {
85+
const header = `[feature.native.tasks.${task}]`
86+
assert.ok(
87+
toml.includes(header),
88+
`Expected generated pixi.toml to define ${header}`
89+
)
90+
}
91+
assert.match(
92+
toml,
93+
/cmd = '''cmake -B\$ITK_WASM_ITK_BUILD_DIR -S\$ITK_WASM_ITK_SOURCE_DIR -GNinja \\\n/,
94+
'Expected configure-itk to continue its first line with a trailing backslash'
95+
)
96+
97+
console.log(
98+
`pixi.toml generation test passed (${blocks.length} multi-line commands checked).`
99+
)
100+
}
101+
102+
run()

0 commit comments

Comments
 (0)