diff --git a/.github/workflows/cxx-python.yml b/.github/workflows/cxx-python.yml index ef578413d..5f5f6b8c4 100644 --- a/.github/workflows/cxx-python.yml +++ b/.github/workflows/cxx-python.yml @@ -14,12 +14,12 @@ env: jobs: cxx-build-workflow: # itk-wasm branch - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea + uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@l@6868c9879405def5d7532e0437b57e78cbe0b6ea with: itk-module-deps: 'MeshToPolyData@v0.11.0' ctest-options: '-E itkPipelineTest' - # release-5.4 2025-07-02 - itk-git-tag: '276a52a5c0b1a5df67c9a2b0fdcd0bc75504e011' + # release-5.4 2024-11-21 + itk-git-tag: '49413c3a9e8ecf0f912534e7c13f4c7bc3799d60' #python-build-workflow: ## itk-wasm branch diff --git a/packages/core/typescript/create-itk-wasm/README.md b/packages/core/typescript/create-itk-wasm/README.md index 48f1ae7ea..71f934572 100644 --- a/packages/core/typescript/create-itk-wasm/README.md +++ b/packages/core/typescript/create-itk-wasm/README.md @@ -10,6 +10,12 @@ CLI to create a new [ITK-Wasm](https://wasm.itk.org) project or add a pipeline t npm install -g pnpm ``` +and [pixi](https://pixi.sh/latest/), e.g. + +```sh +curl -fsSL https://pixi.sh/install.sh | bash +``` + Then, ```sh @@ -19,16 +25,15 @@ cd my-project pnpm create itk-wasm # Answers the questions -pnpm install -pnpm build -pnpm test +pixi run build +pixi run test # Add your C++ logic code to the *.cxx files -pnpm build -pnpm test +pixi run build +pixi run test # For more granular targets, see the output of -pnpm run +pixi task list ``` For more information, see the [ITK-Wasm documentation](https://wasm.itk.org). diff --git a/packages/core/typescript/create-itk-wasm/package.json b/packages/core/typescript/create-itk-wasm/package.json index e2478f688..15187b71b 100644 --- a/packages/core/typescript/create-itk-wasm/package.json +++ b/packages/core/typescript/create-itk-wasm/package.json @@ -18,7 +18,7 @@ "lint": "prettier --check .", "lint:fix": "prettier --write .", "test": "pnpm test:help && pnpm test:defaultPipeline && pnpm test:imagePipeline && pnpm test:meshPipeline && pnpm test:polyDataPipeline", - "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 --build-and-test", + "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", "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", "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", "test:polyDataPipeline": "shx rm -rf test/poly-data && node dist/create-itk-wasm.js -o test/poly-data -n \"poly-data-pipeline\" -a \"Test Monkey\" -d \"PolyData pipeline description\" --pipeline-name poly-data-pipeline --pipeline-description \"PolyData pipeline description\" -r \"http://test.repo\" --pipeline-inputs \"input-poly-data:PolyData:An input poly-data\" --pipeline-parameters \"a-float-param:float:A float param\" --pipeline-outputs \"output-poly-data:PolyData:An output poly-data\" --pipeline-dispatch PolyData --no-input --build-and-test", diff --git a/packages/core/typescript/create-itk-wasm/src/generate/environment-yml.ts b/packages/core/typescript/create-itk-wasm/src/generate/environment-yml.ts deleted file mode 100644 index 6fd21b40c..000000000 --- a/packages/core/typescript/create-itk-wasm/src/generate/environment-yml.ts +++ /dev/null @@ -1,25 +0,0 @@ -import fs from 'fs' -import path from 'path' - -import ProjectSpec from '../project-spec.js' - -function generateEnvironmentYml(project: ProjectSpec) { - const environmentYmlPath = path.join(project.directory, 'environment.yml') - if (fs.existsSync(environmentYmlPath)) { - return - } - - const content = `name: ${project.name} -channels: - - conda-forge -dependencies: - - pytest - - python=3.11 - - pip - - pip: - - hatch -` - fs.writeFileSync(environmentYmlPath, content) -} - -export default generateEnvironmentYml diff --git a/packages/core/typescript/create-itk-wasm/src/generate/itk-wasm-env-bash.ts b/packages/core/typescript/create-itk-wasm/src/generate/itk-wasm-env-bash.ts new file mode 100644 index 000000000..173a73189 --- /dev/null +++ b/packages/core/typescript/create-itk-wasm/src/generate/itk-wasm-env-bash.ts @@ -0,0 +1,42 @@ +import fs from 'fs' +import path from 'path' + +import ProjectSpec from '../project-spec.js' + +function generateItkWasmEnvBash(project: ProjectSpec) { + const itkWasmEnvBashPath = path.join(project.directory, 'itk_wasm_env.bash') + if (fs.existsSync(itkWasmEnvBashPath)) { + return + } + + const content = `#!/usr/bin/env bash + +function die() { + echo "$1" + exit 1 +} + +export ITK_WASM_TEST_DATA_HASH=\${ITK_WASM_TEST_DATA_HASH:-$(cat package.json | jq -e -r '."itk-wasm"."test-data-hash"')} +export ITK_WASM_TEST_DATA_URLS=\${ITK_WASM_TEST_DATA_URLS:-$(cat package.json | jq -e -r '."itk-wasm"."test-data-urls" | join(" ")')} + +export ITK_WASM_ITK_REPOSITORY=\${ITK_WASM_ITK_REPOSITORY:-"https://github.com/thewtex/ITK"} +export ITK_WASM_ITK_BRANCH=\${ITK_WASM_ITK_BRANCH:-"itkwasm-2024-05-20-5db055d7ad3b-4"} + +export ITK_WASM_NATIVE_WORKSPACE=\${ITK_WASM_NATIVE_WORKSPACE:-$(pwd)/native} + +export ITK_WASM_ITK_SOURCE_DIR=\${ITK_WASM_ITK_SOURCE_DIR:-\${ITK_WASM_NATIVE_WORKSPACE}/ITK} +export ITK_WASM_ITK_BUILD_DIR=\${ITK_WASM_ITK_BUILD_DIR:-\${ITK_WASM_NATIVE_WORKSPACE}/ITK-build} +mkdir -p \${ITK_WASM_ITK_BUILD_DIR} || die "Could not create ITK build directory" + +export ITK_WASM_WEBASSEMBLY_INTERFACE_REPOSITORY=\${ITK_WASM_WEBASSEMBLY_INTERFACE_REPOSITORY:-"https://github.com/InsightSoftwareConsortium/ITK-Wasm"} +export ITK_WASM_WEBASSEMBLY_INTERFACE_BRANCH=\${ITK_WASM_WEBASSEMBLY_INTERFACE_BRANCH:-"main"} + +export ITK_WASM_WEBASSEMBLY_INTERFACE_SOURCE_DIR=\${ITK_WASM_WEBASSEMBLY_INTERFACE_SOURCE_DIR:-\${ITK_WASM_NATIVE_WORKSPACE}/ITK-Wasm} +export ITK_WASM_WEBASSEMBLY_INTERFACE_BUILD_DIR=\${ITK_WASM_WEBASSEMBLY_INTERFACE_BUILD_DIR:-\${ITK_WASM_NATIVE_WORKSPACE}/ITK-Wasm-build} +mkdir -p \${ITK_WASM_WEBASSEMBLY_INTERFACE_BUILD_DIR} || die "Could not create ITK-Wasm build directory" +` + + fs.writeFileSync(itkWasmEnvBashPath, content) +} + +export default generateItkWasmEnvBash diff --git a/packages/core/typescript/create-itk-wasm/src/generate/package-json.ts b/packages/core/typescript/create-itk-wasm/src/generate/package-json.ts index 9c8576acd..51350d415 100644 --- a/packages/core/typescript/create-itk-wasm/src/generate/package-json.ts +++ b/packages/core/typescript/create-itk-wasm/src/generate/package-json.ts @@ -13,9 +13,16 @@ function generatePackageJson(project: ProjectSpec) { } const itkWasm = { + 'test-data-hash': + 'bafkreidnoz54py66bn56uq6itwkfgngflaqilflfvwkxlps4ycmygstzja', + 'test-data-urls': [ + 'https://github.com/InsightSoftwareConsortium/ITK-Wasm/releases/download/itk-wasm-v1.0.0-b.179/sample-data.tar.gz', + 'https://bafybeidxatrsrrphfmntdyze6ec3jbiak527wj3kalwjptv4bimpcnzxdq.ipfs.w3s.link/ipfs/bafybeidxatrsrrphfmntdyze6ec3jbiak527wj3kalwjptv4bimpcnzxdq/sample-data.tar.gz' + ], 'package-description': project.packageDescription, 'typescript-package-name': project.typescriptPackageName, - 'python-package-name': project.pythonPackageName + 'python-package-name': project.pythonPackageName, + repository: project.repositoryUrl } if (project.repositoryUrl) { // @ts-ignore @@ -25,7 +32,7 @@ function generatePackageJson(project: ProjectSpec) { name: `${project.name}-build`, version: '0.1.0', private: true, - description: `Scripts to generate ${project.name} itk-wasm artifacts.`, + description: `Scripts to generate ${project.name} ITK-Wasm artifacts.`, type: 'module', 'itk-wasm': itkWasm, license: project.license, @@ -35,29 +42,18 @@ function generatePackageJson(project: ProjectSpec) { 'build:emscripten:debug': 'itk-wasm pnpm-script build:emscripten:debug', 'build:wasi': 'itk-wasm pnpm-script build:wasi', 'build:wasi:debug': 'itk-wasm pnpm-script build:wasi:debug', - 'build:python:wasi': 'itk-wasm pnpm-script build:python:wasi', + 'build:python:wasi': + "echo 'No build:python:wasi script required with pixi'", 'bindgen:typescript': 'itk-wasm pnpm-script bindgen:typescript', 'bindgen:python': 'itk-wasm pnpm-script bindgen:python', 'build:gen:typescript': 'itk-wasm pnpm-script build:gen:typescript', 'build:gen:python': 'itk-wasm pnpm-script build:gen:python', - 'build:micromamba': 'itk-wasm pnpm-script build:micromamba', - 'build:python:versionSync': - 'itk-wasm pnpm-script build:python:versionSync', - 'publish:python': 'itk-wasm pnpm-script publish:python', - test: 'pnpm test:data:download && pnpm build:gen:python && pnpm test:python', - 'test:data:download': - 'dam download test/data test/data.tar.gz bafkreigpkk3pqcoqzjzcauogw6dml52yig3ksmcrobau5pkoictymizzri https://github.com/InsightSoftwareConsortium/ITK-Wasm/releases/download/itk-wasm-v1.0.0-b.163/create-itk-wasm-test-data.tar.gz https://bafybeiczuxeuma5cjuli5mtapqnjqypeaum5ikd45zcmfhtt2emp365tca.ipfs.w3s.link/ipfs/bafybeiczuxeuma5cjuli5mtapqnjqypeaum5ikd45zcmfhtt2emp365tca/create-itk-wasm-test-data.tar.gz https://ipfs.filebase.io/ipfs/QmcxyvUKnaoTTwUqEPXwp1sdcbrFh3XnnwckLKVRpctJx9', - 'test:data:pack': 'dam pack test/data test/data.tar.gz', - 'test:python:wasi': 'itk-wasm pnpm-script test:python:wasi', - 'test:python:emscripten': 'itk-wasm pnpm-script test:python:emscripten', - 'test:python:dispatch': 'itk-wasm pnpm-script test:python:emscripten', - 'test:python': 'itk-wasm pnpm-script test:python', + test: 'pixi run download-test-data && pnpm build:gen:python', 'test:wasi': 'itk-wasm pnpm-script test:wasi' }, devDependencies: { '@itk-wasm/dam': '^1.1.1', - '@thewtex/setup-micromamba': '^1.9.7', - 'itk-wasm': '1.0.0-b.178' + 'itk-wasm': '1.0.0-b.182' } } if (project.author) { diff --git a/packages/core/typescript/create-itk-wasm/src/generate/pixi-toml.ts b/packages/core/typescript/create-itk-wasm/src/generate/pixi-toml.ts new file mode 100644 index 000000000..312c71c4d --- /dev/null +++ b/packages/core/typescript/create-itk-wasm/src/generate/pixi-toml.ts @@ -0,0 +1,286 @@ +import fs from 'fs' +import path from 'path' + +import ProjectSpec from '../project-spec.js' + +function generatePixiToml(project: ProjectSpec) { + const pixiTomlPath = path.join(project.directory, 'pixi.toml') + if (fs.existsSync(pixiTomlPath)) { + return + } + + const content = `[project] +authors = ["${project.author}"] +channels = ["conda-forge"] +description = "${project.packageDescription}" +name = "${project.name}" +platforms = ["win-64", "linux-64", "linux-aarch64", "osx-arm64"] +version = "0.1.0" + +[environments] +native = ["native"] +python = ["python"] + +[dependencies] +python = "3.12.*" +pnpm = ">=9.12.1,<10" +hatch = ">=1.13.0,<2" +pip = ">=24.2,<25" + +[target.win-64.dependencies] +m2w64-jq = ">=1.6.0,<2" + +[target.unix.dependencies] +jq = ">=1.7.1,<2" + +[activation] +scripts = ["itk_wasm_env.bash"] + +[tasks.build] +depends-on = ["build-native", "build-typescript", "build-python"] +description = "Build the project" + +[tasks.test] +cmd = "pnpm run test" +depends-on = ["download-test-data", "test-native", "test-python"] +description = "Run tests" + +[tasks.publish] +depends-on = ["publish-typescript", "publish-python-wasi", "publish-python-emscripten", "publish-python-dispatch"] +description = "Synchronize package versions" + +[tasks.version-sync] +depends-on = ["version-sync-typescript", "version-sync-python-wasi", "version-sync-python-emscripten", "version-sync-python-dispatch"] +description = "Synchronize package versions" + +[tasks.pack-test-data] +cmd = "npx dam pack test/data test/data.tar.gz" +depends-on = ["install-typescript"] +outputs = ["test/data.tar.gz"] +description = "Pack the data into a tarball for upload and print CID for package.json" + +[tasks.download-test-data] +cmd = "npx dam download test/data test/data.tar.gz $ITK_WASM_TEST_DATA_HASH $ITK_WASM_TEST_DATA_URLS" +depends-on = ["install-typescript"] +outputs = ["test/data.tar.gz"] +description = "Download test data" + +[tasks.install-typescript] +cmd = "pnpm install" +description = "Install typescript dependencies" + +[tasks.build-typescript] +cmd = "pnpm run build:gen:typescript" +depends-on = ["install-typescript"] +description = "Build typescript components" + +[tasks.test-typescript] +cmd = "pnpm test" +depends-on = ["download-test-data"] +cwd = "typescript" +description = "Test typescript components" + +[tasks.version-sync-typescript] +cmd = '''version=$(cat package.json | jq .version) && + jq ".version = $version" typescript/package.json > typescript/package.json.tmp && + mv typescript/package.json.tmp typescript/package.json''' + +[tasks.publish-typescript] +cmd = "pnpm publish --filter \\"{typescript}\\"" + +[feature.python.dependencies] +pytest = ">=8.3.3,<9" + +[feature.python.pypi-dependencies] +pyodide-py = ">=0.26.3, <0.27" +pytest-pyodide = ">=0.58.3, <0.59" +itkwasm = ">=1.0b180, <2" + +[tasks.build-python] +cmd = "pnpm run build:gen:python" +depends-on = ["install-typescript"] +description = "Build python components" + +[feature.python.tasks.download-pyodide] +cmd = '''curl -L https://github.com/pyodide/pyodide/releases/download/0.26.3/pyodide-0.26.3.tar.bz2 -o pyodide.tar.bz2 && + tar xjf pyodide.tar.bz2 && + rm pyodide.tar.bz2''' +outputs = ["pyodide"] +description = "Download Pyodide" + +[feature.python.tasks.test-wasi] +cmd = "pytest" +cwd = "python/${project.pythonPackageName}-wasi" +description = "Run tests for ${project.pythonPackageName}-wasi" + +[feature.python.tasks.serve-emscripten] +cmd = '''mkdir -p dist/pyodide && + cp -r ../../pyodide dist/ && + hatch build -t wheel ./dist/pyodide/ && + echo \\"\\nVisit http://localhost:8877/console.html\\n\\" && + python -m http.server --directory=./dist/pyodide 8877''' +cwd = "python/${project.pythonPackageName}-emscripten" +depends-on = ["download-pyodide"] +description = "Serve ${project.pythonPackageName}-emscripten for development" + +[feature.python.tasks.test-emscripten] +cmd = '''mkdir -p dist/pyodide && + cp -r ../../pyodide dist/ && + hatch build -t wheel ./dist/pyodide/ && + pytest --dist-dir=./dist/pyodide --rt=chrome''' +cwd = "python/${project.pythonPackageName}-emscripten" +depends-on = ["download-pyodide"] +description = "Run tests for ${project.pythonPackageName}-emscripten" + +[feature.python.tasks.serve-dispatch] +cmd = '''mkdir -p dist/pyodide && + cp -r ../../pyodide dist/ && + hatch build -t wheel ./dist/pyodide/ && + echo \\"\\nVisit http://localhost:8877/console.html\\n\\" && + python -m http.server --directory=./dist/pyodide 8877''' +cwd = "python/${project.pythonPackageName}" +depends-on = ["download-pyodide"] +description = "Serve ${project.pythonPackageName} for development" + +[feature.python.tasks.test-dispatch] +cmd = '''mkdir -p dist/pyodide && + cp -r ../../pyodide dist/ && + hatch build -t wheel ./dist/pyodide/ && + cd ../${project.pythonPackageName}-emscripten && + hatch build -t wheel ./dist/pyodide/ && + cd ../${project.pythonPackageName} && + cp ../${project.pythonPackageName}-emscripten/dist/pyodide/*_emscripten*.whl ./dist/pyodide/ && + pytest --dist-dir=./dist/pyodide --rt=chrome''' +cwd = "python/${project.pythonPackageName}" +depends-on = ["download-pyodide"] +description = "Run python tests for ${project.pythonPackageName}" + +[feature.python.tasks.test-python] +depends-on = ["test-wasi", "test-emscripten", "test-dispatch"] +description = "Run tests for all Python packages" + +[tasks.version-sync-python-wasi] +cmd = '''version=$(cat ../../package.json | jq -r .version) && + echo "version is $version" && + hatch version $version''' +cwd = "python/${project.pythonPackageName}-wasi" + +[tasks.version-sync-python-emscripten] +cmd = '''version=$(cat ../../package.json | jq -r .version) && + hatch version $version''' +cwd = "python/${project.pythonPackageName}-emscripten" + +[tasks.version-sync-python-dispatch] +cmd = '''version=$(cat ../../package.json | jq -r .version) && + hatch version $version''' +cwd = "python/${project.pythonPackageName}" + +[tasks.publish-python-user-check] +cmd = "if [ -n \\"$HATCH_INDEX_USER\\"]; then echo \\"HATCH_INDEX_USER is set\\"; else echo \\"HATCH_INDEX_USER is not set\\"; exit 1; fi" + +[tasks.publish-python-wasi] +cmd = '''hatch build && + hatch publish''' +cwd = "python/${project.pythonPackageName}-wasi" + +[tasks.publish-python-emscripten] +cmd = '''hatch build && + hatch publish''' +cwd = "python/${project.pythonPackageName}-emscripten" + +[tasks.publish-python-dispatch] +cmd = '''hatch build && + hatch publish''' +cwd = "python/${project.pythonPackageName}" +[feature.native.tasks.clone-itk] +cmd = ["stat", "$ITK_WASM_ITK_SOURCE_DIR", ">/dev/null", "||", + "git", "clone", + "--depth=10", + "--branch=$ITK_WASM_ITK_BRANCH", + "$ITK_WASM_ITK_REPOSITORY", + "$ITK_WASM_ITK_SOURCE_DIR"] +# Note: pixi does not seem to reliably support activation environmental variables in task inputs / outputs +outputs = ["native/ITK/LICENSE"] +description = "Fetch ITK's source code" + +[feature.native.tasks.configure-itk] +cmd = '''cmake -B$ITK_WASM_ITK_BUILD_DIR -S$ITK_WASM_ITK_SOURCE_DIR -GNinja + -DCMAKE_CXX_STANDARD:STRING=20 + -DCMAKE_BUILD_TYPE:STRING=Debug + -DBUILD_EXAMPLES:BOOL=OFF + -DBUILD_TESTING:BOOL=OFF + -DBUILD_SHARED_LIBS:BOOL=OFF + -DBUILD_STATIC_LIBS:BOOL=ON + -DITK_LEGACY_REMOVE:BOOL=ON + -DITK_BUILD_DEFAULT_MODULES:BOOL=ON + -DITKGroup_IO:BOOL=ON + -DH5_HAVE_GETPWUID:BOOL=OFF + -DModule_MeshToPolyData:BOOL=ON + -DDO_NOT_BUILD_ITK_TEST_DRIVER:BOOL=ON + -DOPJ_USE_THREAD:BOOL=OFF + -DNO_FLOAT_EXCEPTIONS:BOOL=ON + -DITK_MSVC_STATIC_RUNTIME_LIBRARY=ON''' +depends-on = ["clone-itk"] +# Note: pixi does not seem to reliably support activation environmental variables in task inputs / outputs +# outputs = ["$ITK_WASM_ITK_BUILD_DIR/CMakeFiles/"] +outputs = ["native/ITK-build/CMakeFiles/**"] +description = "Configure ITK" + +[feature.native.tasks.build-itk] +cmd = "cmake --build $ITK_WASM_ITK_BUILD_DIR" +depends-on = ["configure-itk"] +outputs = ["native/ITK-build/**"] +description = "Build ITK" + +[feature.native.tasks.clone-itk-wasm] +cmd = ["stat", "$ITK_WASM_WEBASSEMBLY_INTERFACE_SOURCE_DIR", ">/dev/null", "||", + "git", "clone", + "--depth=10", + "--branch=$ITK_WASM_WEBASSEMBLY_INTERFACE_BRANCH", + "$ITK_WASM_WEBASSEMBLY_INTERFACE_REPOSITORY", + "$ITK_WASM_WEBASSEMBLY_INTERFACE_SOURCE_DIR"] +# Note: pixi does not seem to reliably support activation environmental variables in task inputs / outputs +outputs = ["native/ITK-Wasm/LICENSE"] +description = "Fetch ITK's source code" + +[feature.native.tasks.configure-itk-wasm] +cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm-build + -S$ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm + -GNinja + -DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR + -DBUILD_TESTING:BOOL=OFF + -DCMAKE_CXX_STANDARD:STRING=20 + -DCMAKE_BUILD_TYPE:STRING=Debug''' +depends-on = ["build-itk", "clone-itk-wasm"] +outputs = ["native/ITK-Wasm-build/CMakeFiles/"] +description = "Configure ITK-Wasm" + +[feature.native.tasks.build-itk-wasm] +cmd = "cmake --build $ITK_WASM_NATIVE_WORKSPACE/ITK-Wasm-build" +depends-on = ["configure-itk-wasm"] +description = "Build ITK-Wasm" + +[feature.native.tasks.configure-native] +cmd = '''cmake -B$ITK_WASM_NATIVE_WORKSPACE/${project.name}-build -S. -GNinja + -DITK_DIR:PATH=$ITK_WASM_ITK_BUILD_DIR + -DBUILD_TESTING:BOOL=ON + -DCMAKE_CXX_STANDARD:STRING=20 + -DCMAKE_BUILD_TYPE:STRING=Debug''' +depends-on = ["build-itk-wasm"] +description = "Configure native build" + +[feature.native.tasks.build-native] +cmd = "cmake --build $ITK_WASM_NATIVE_WORKSPACE/${project.name}-build" +depends-on = ["configure-native"] +description = "Build native binaries" + +[feature.native.tasks.test-native] +cmd = "ctest --test-dir $ITK_WASM_NATIVE_WORKSPACE/${project.name}-build" +depends-on = ["build-native"] +description = "Test native binaries" + +` + fs.writeFileSync(pixiTomlPath, content) +} + +export default generatePixiToml diff --git a/packages/core/typescript/create-itk-wasm/src/generate/project.ts b/packages/core/typescript/create-itk-wasm/src/generate/project.ts index 6d228a0a3..dfb6d6dfa 100644 --- a/packages/core/typescript/create-itk-wasm/src/generate/project.ts +++ b/packages/core/typescript/create-itk-wasm/src/generate/project.ts @@ -10,7 +10,8 @@ import die from '../die.js' import generateReadme from './readme.js' import generatePackageJson from './package-json.js' -import generateEnvironmentYml from './environment-yml.js' +import generatePixiToml from './pixi-toml.js' +import generateItkWasmEnvBash from './itk-wasm-env-bash.js' import generateCMakelists from './cmakelists.js' import generatePipeline from './pipeline.js' import generatePnpmWorkspace from './pnpm-workspace.js' @@ -43,7 +44,8 @@ function generateProject( generateReadme(project) generatePackageJson(project) - generateEnvironmentYml(project) + generatePixiToml(project) + generateItkWasmEnvBash(project) generateCMakelists(project) generatePnpmWorkspace(project) @@ -62,9 +64,8 @@ function generateProject( ) console.log(chalk.magentaBright(`\nšŸš€ Next steps:`)) console.log(chalk.green(`\ncd ${project.directory}`)) - console.log(chalk.green(`pnpm install`)) - console.log(chalk.green(`pnpm build`)) - console.log(chalk.green(`pnpm test\n`)) + console.log(chalk.green(`pixi run build`)) + console.log(chalk.green(`pixi run test\n`)) } } diff --git a/packages/core/typescript/itk-wasm/package.json b/packages/core/typescript/itk-wasm/package.json index d14c0d9ac..566225643 100644 --- a/packages/core/typescript/itk-wasm/package.json +++ b/packages/core/typescript/itk-wasm/package.json @@ -1,6 +1,6 @@ { "name": "itk-wasm", - "version": "1.0.0-b.179", + "version": "1.0.0-b.182", "description": "High-performance spatial analysis in a web browser, Node.js, and reproducible execution across programming languages and hardware architectures.", "type": "module", "module": "./dist/index.js", diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-package.js b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-package.js index 9026da07d..d68780bc3 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-package.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-package.js @@ -1,17 +1,19 @@ import path from 'path' -import mkdirP from "../mkdir-p.js" +import mkdirP from '../mkdir-p.js' -import snakeCase from "../snake-case.js" +import snakeCase from '../snake-case.js' -import dispatchPackageReadme from "./dispatch-package-readme.js" -import dispatchFunctionModule from "./dispatch-function-module.js" +import dispatchPackageReadme from './dispatch-package-readme.js' +import dispatchFunctionModule from './dispatch-function-module.js' import packagePyProjectToml from './package-py-project-toml.js' -import packageDocs from "./package-docs.js" -import packageDunderInit from "./package-dunder-init.js" -import packageVersion from "./package-version.js" +import packageDocs from './package-docs.js' +import packageDunderInit from './package-dunder-init.js' +import dispatchTestModule from './dispatch-test-module.js' +import dispatchPipelineTest from './dispatch-pipeline-test.js' +import packageVersion from './package-version.js' -import wasmBinaryInterfaceJson from "../wasm-binary-interface-json.js" +import wasmBinaryInterfaceJson from '../wasm-binary-interface-json.js' function dispatchPackage(outputDir, buildDir, wasmBinaries, options) { const packageName = options.packageName @@ -27,13 +29,33 @@ function dispatchPackage(outputDir, buildDir, wasmBinaries, options) { packageVersion(packageDir, pypackage, options) const async = true const sync = true - packageDunderInit(outputDir, buildDir, wasmBinaries, packageName, options.packageDescription, packageDir, pypackage, async, sync) + packageDunderInit( + outputDir, + buildDir, + wasmBinaries, + packageName, + options.packageDescription, + packageDir, + pypackage, + async, + sync + ) packageDocs(packageName, packageDir, pypackage, options) + dispatchTestModule(packageDir, pypackage) wasmBinaries.forEach((wasmBinaryName) => { - const { interfaceJson } = wasmBinaryInterfaceJson(outputDir, buildDir, wasmBinaryName) + const { interfaceJson } = wasmBinaryInterfaceJson( + outputDir, + buildDir, + wasmBinaryName + ) const functionName = snakeCase(interfaceJson.name) - dispatchFunctionModule(interfaceJson, pypackage, path.join(packageDir, pypackage, `${functionName}.py`)) + dispatchFunctionModule( + interfaceJson, + pypackage, + path.join(packageDir, pypackage, `${functionName}.py`) + ) + dispatchPipelineTest(packageDir, pypackage, functionName) }) } diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-pipeline-test.js b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-pipeline-test.js new file mode 100644 index 000000000..787a5ebcc --- /dev/null +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-pipeline-test.js @@ -0,0 +1,34 @@ +import fs from 'fs-extra' +import path from 'path' + +function dispatchPipelineTest(packageDir, pypackage, functionName) { + const testDir = path.join(packageDir, 'tests') + const testModulePath = path.join(testDir, `test_${functionName}.py`) + + let moduleContent = `import pytest +import sys + +if sys.version_info < (3,10): + pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True) + +from pytest_pyodide import run_in_pyodide + +from .fixtures import emscripten_package_wheel, package_wheel, input_data + +@run_in_pyodide(packages=['micropip']) +async def test_${functionName}(selenium, package_wheel, emscripten_package_wheel): + import micropip + await micropip.install(emscripten_package_wheel) + await micropip.install(package_wheel) + + from ${pypackage} import ${functionName} + + # Write your test code here +` + + if (!fs.existsSync(testModulePath)) { + fs.writeFileSync(testModulePath, moduleContent) + } +} + +export default dispatchPipelineTest diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-test-module.js b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-test-module.js new file mode 100644 index 000000000..714b8deb9 --- /dev/null +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/dispatch-test-module.js @@ -0,0 +1,51 @@ +import fs from 'fs-extra' +import path from 'path' + +import mkdirP from '../mkdir-p.js' + +function dispatchTestModule(packageDir, pypackage) { + const testDir = path.join(packageDir, 'tests') + mkdirP(testDir) + + const initPath = path.join(testDir, '__init__.py') + if (!fs.existsSync(initPath)) { + fs.writeFileSync(initPath, '') + } + + const fixturesContent = `import pytest +import sys +import glob + +if sys.version_info < (3,10): + pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True) + +from pytest_pyodide import run_in_pyodide + +from ${pypackage} import __version__ as test_package_version + +@pytest.fixture +def package_wheel(): + return f"${pypackage}-{test_package_version}-py3-none-any.whl" + +@pytest.fixture +def emscripten_package_wheel(): + return f"${pypackage}_emscripten-{test_package_version}-py3-none-any.whl" + +@pytest.fixture +def input_data(): + from pathlib import Path + input_base_path = Path(__file__).parent.parent / 'test' / 'data' + test_files = list(input_base_path.glob('*')) + data = {} + for test_file in test_files: + with open(test_file, 'rb') as f: + data[test_file.name] = f.read() + return data +` + const fixturesPath = path.join(testDir, 'fixtures.py') + if (!fs.existsSync(fixturesPath)) { + fs.writeFileSync(fixturesPath, fixturesContent) + } +} + +export default dispatchTestModule diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-package.js b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-package.js index 925fed383..3e8d92c4f 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-package.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-package.js @@ -10,24 +10,36 @@ import packageVersion from '../package-version.js' import packageDunderInit from '../package-dunder-init.js' import emscriptenPyodideModule from './emscripten-pyodide-module.js' import emscriptenTestModule from './emscripten-test-module.js' +import emscriptenPipelineTest from './emscripten-pipeline-test.js' import emscriptenFunctionModule from './emscripten-function-module.js' import wasmBinaryInterfaceJson from '../../wasm-binary-interface-json.js' function emscriptenPackage(outputDir, buildDir, wasmBinaries, options) { - const defaultJsModulePath = path.join(outputDir, '..', 'typescript', 'dist', 'bundle', 'index-worker-embedded.min.js') + const defaultJsModulePath = path.join( + outputDir, + '..', + 'typescript', + 'dist', + 'bundle', + 'index-worker-embedded.min.js' + ) const moduleUrl = options.jsModulePath ?? defaultJsModulePath if (!fs.existsSync(moduleUrl)) { - console.warn(`Could not find ${moduleUrl}: skipping python emscripten package`) + console.warn( + `Could not find ${moduleUrl}: skipping python emscripten package` + ) return } - const jsModuleContent = btoa(fs.readFileSync(moduleUrl, { encoding: 'utf8', flag: 'r' })) + const jsModuleContent = btoa( + fs.readFileSync(moduleUrl, { encoding: 'utf8', flag: 'r' }) + ) const packageName = `${options.packageName}-emscripten` const packageDir = path.join(outputDir, packageName) const packageDescription = `${options.packageDescription} Emscripten implementation.` mkdirP(packageDir) - const pypackage = snakeCase(packageName) + const pypackage = snakeCase(packageName) const bindgenPyPackage = pypackage mkdirP(path.join(packageDir, pypackage)) @@ -36,16 +48,35 @@ function emscriptenPackage(outputDir, buildDir, wasmBinaries, options) { packageVersion(packageDir, pypackage, options) const async = true const sync = false - packageDunderInit(outputDir, buildDir, wasmBinaries, packageName, packageDescription, packageDir, pypackage, async, sync) + packageDunderInit( + outputDir, + buildDir, + wasmBinaries, + packageName, + packageDescription, + packageDir, + pypackage, + async, + sync + ) emscriptenPyodideModule(jsModuleContent, packageDir, pypackage) emscriptenTestModule(packageDir, pypackage) const wasmModulesDir = path.join(packageDir, pypackage, 'wasm_modules') mkdirP(wasmModulesDir) wasmBinaries.forEach((wasmBinaryName) => { - const { interfaceJson, parsedPath } = wasmBinaryInterfaceJson(outputDir, buildDir, wasmBinaryName) + const { interfaceJson, parsedPath } = wasmBinaryInterfaceJson( + outputDir, + buildDir, + wasmBinaryName + ) const functionName = snakeCase(interfaceJson.name) + '_async' - emscriptenFunctionModule(interfaceJson, pypackage, path.join(packageDir, pypackage, `${functionName}.py`)) + emscriptenFunctionModule( + interfaceJson, + pypackage, + path.join(packageDir, pypackage, `${functionName}.py`) + ) + emscriptenPipelineTest(packageDir, pypackage, functionName) }) } diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-pipeline-test.js b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-pipeline-test.js new file mode 100644 index 000000000..c633897d2 --- /dev/null +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-pipeline-test.js @@ -0,0 +1,33 @@ +import fs from 'fs-extra' +import path from 'path' + +function emscriptenPipelineTest(packageDir, pypackage, functionName) { + const testDir = path.join(packageDir, 'tests') + const testModulePath = path.join(testDir, `test_${functionName}.py`) + + let moduleContent = `import pytest +import sys + +if sys.version_info < (3,10): + pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True) + +from pytest_pyodide import run_in_pyodide + +from .fixtures import package_wheel, input_data + +@run_in_pyodide(packages=['micropip']) +async def test_${functionName}(selenium, package_wheel): + import micropip + await micropip.install(package_wheel) + + from ${pypackage} import ${functionName} + + # Write your test code here +` + + if (!fs.existsSync(testModulePath)) { + fs.writeFileSync(testModulePath, moduleContent) + } +} + +export default emscriptenPipelineTest diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-test-module.js b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-test-module.js index e0fec60f9..2b32d0b26 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-test-module.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/python/emscripten/emscripten-test-module.js @@ -4,10 +4,17 @@ import path from 'path' import mkdirP from '../../mkdir-p.js' function emscriptenTestModule(packageDir, pypackage) { - mkdirP(path.join(packageDir, 'test')) + const testDir = path.join(packageDir, 'tests') + mkdirP(testDir) - let moduleContent = `import pytest + const initPath = path.join(testDir, '__init__.py') + if (!fs.existsSync(initPath)) { + fs.writeFileSync(initPath, '') + } + + const fixturesContent = `import pytest import sys +import glob if sys.version_info < (3,10): pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True) @@ -20,21 +27,20 @@ from ${pypackage} import __version__ as test_package_version def package_wheel(): return f"${pypackage}-{test_package_version}-py3-none-any.whl" -@run_in_pyodide(packages=['micropip']) -async def test_example(selenium, package_wheel): - import micropip - await micropip.install(package_wheel) - - # Write your test code here +@pytest.fixture +def input_data(): + from pathlib import Path + input_base_path = Path(__file__).parent.parent / 'test' / 'data' + test_files = list(input_base_path.glob('*')) + data = {} + for test_file in test_files: + with open(test_file, 'rb') as f: + data[test_file.name] = f.read() + return data ` - - const modulePath = path.join(packageDir, 'test', `test_${pypackage.replace('_emscripten', '')}.py`) - if (!fs.existsSync(modulePath)) { - fs.writeFileSync(modulePath, moduleContent) - } - const initPath = path.join(packageDir, 'test', '__init__.py') - if (!fs.existsSync(initPath)) { - fs.writeFileSync(initPath, '') + const fixturesPath = path.join(testDir, 'fixtures.py') + if (!fs.existsSync(fixturesPath)) { + fs.writeFileSync(fixturesPath, fixturesContent) } } diff --git a/packages/core/typescript/itk-wasm/src/cli/pnpm-script.js b/packages/core/typescript/itk-wasm/src/cli/pnpm-script.js index 4a92a26d3..ce51068f7 100644 --- a/packages/core/typescript/itk-wasm/src/cli/pnpm-script.js +++ b/packages/core/typescript/itk-wasm/src/cli/pnpm-script.js @@ -326,10 +326,13 @@ async function pnpmScript(name, extraArgs, options) { 'build:wasi', '&&', 'pnpm', - 'bindgen:python', - '&&', + 'bindgen:python' + ]) + if (environmentFileContents) { + pnpmCommand = pnpmCommand.concat(['&&', 'pnpm', 'build:micromamba']) + } + pnpmCommand = pnpmCommand.concat([ 'pnpm', - 'build:micromamba', '&&', 'pnpm', 'build:python:wasi' diff --git a/packages/core/typescript/itk-wasm/src/version.ts b/packages/core/typescript/itk-wasm/src/version.ts index cb68b9d7e..94f7e5868 100644 --- a/packages/core/typescript/itk-wasm/src/version.ts +++ b/packages/core/typescript/itk-wasm/src/version.ts @@ -1,3 +1,3 @@ -const version = '1.0.0-b.179' +const version = '1.0.0-b.182' export default version