From 4a9f48c97d2ba931a65578445e8880e04d75e48d Mon Sep 17 00:00:00 2001 From: Chawye Hsu Date: Tue, 19 Nov 2024 02:37:27 +0800 Subject: [PATCH] Add rattler-build recipe for Zig-built conda-launchers (#7) Co-authored-by: jaimergp --- .github/workflows/test.yml | 44 ++++++- .gitignore | 6 + pixi.lock | 126 +++++++++++++++++++ pixi.toml | 17 +++ recipe-zig/build.bat | 50 ++++++++ recipe-zig/recipe.yaml | 157 ++++++++++++++++++++++++ recipe/build.sh | 2 +- src/build.zig | 78 ++++++++++++ src/{manifest.xml => launcher.manifest} | 0 9 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 pixi.lock create mode 100644 pixi.toml create mode 100644 recipe-zig/build.bat create mode 100644 recipe-zig/recipe.yaml create mode 100644 src/build.zig rename src/{manifest.xml => launcher.manifest} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 154829f..8e5531d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ concurrency: cancel-in-progress: true jobs: - build_recipe: + conda_build_recipe: name: Build conda recipe (${{ matrix.subdir }}) runs-on: ${{ matrix.os }} strategy: @@ -80,3 +80,45 @@ jobs: echo "Use this command to try out the build:" echo " conda install -c ${ANACONDA_ORG_CHANNEL}/label/${ANACONDA_ORG_LABEL} conda-launchers" + + rattler_build_recipe: + name: Rattler build recipe + runs-on: windows-latest + steps: + - name: Checkout Source + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Pixi + uses: prefix-dev/setup-pixi@v0.8.1 + with: + cache: true + + - name: Run Build + shell: pwsh + run: pixi run build + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: conda-launchers-zig-builds + path: | + ./output/noarch/conda-launchers*.conda + + - name: Upload to Anaconda + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + env: + ANACONDA_OWNER: conda-canary + ANACONDA_CHANNEL: conda-launchers-zig_dev + ANACONDA_API_KEY: ${{ secrets.ANACONDA_ORG_CONDA_CANARY_TOKEN }} + shell: pwsh + run: | + if (-not $env:ANACONDA_API_KEY -or $env:ANACONDA_API_KEY -eq '') { + Write-Error "ANACONDA_API_KEY is not set." + exit 1 + } + foreach ($file in Get-ChildItem -Path "./output/noarch/conda-launchers*.conda") { + Write-Host "Uploading $($file.Name)" + pixi run upload "$file" + } diff --git a/.gitignore b/.gitignore index 82f9275..1bf6380 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,9 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +!launcher.manifest +# pixi environment +.pixi/ +# rattler-build output +output/ diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..99aee03 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,126 @@ +version: 5 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/m2-conda-epoch-20230914-0_x86_64.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/m2-msys2-runtime-3.4.9.1-hd8ed1ab_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/m2-patch-2.7.6.2-hd8ed1ab_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/rattler-build-0.29.0-h49672d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda +packages: +- kind: conda + name: m2-conda-epoch + version: '20230914' + build: 0_x86_64 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/m2-conda-epoch-20230914-0_x86_64.conda + sha256: 5514efb349d06a8dfe7966b64a3076efad461934e35da9e84c0693a36097fe77 + md5: e2a0da44f380c05e8d1f897ed3ec3ce0 + constrains: + - msys2-conda-epoch <0.0a0 + license: BSD-3-Clause + license_family: BSD + size: 6961 + timestamp: 1696161055254 +- kind: conda + name: m2-msys2-runtime + version: 3.4.9.1 + build: hd8ed1ab_4 + build_number: 4 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/m2-msys2-runtime-3.4.9.1-hd8ed1ab_4.conda + sha256: e505bf056171089c761b600d21dee062ad1c962b892ca8a7bc852211e3fd3273 + md5: 77d17c947f9014b2b97a267c5e95cbf2 + depends: + - m2-conda-epoch 20230914 *_x86_64 + - m2-conda-epoch 20230914.* + license: GPL + size: 1918370 + timestamp: 1719980096293 +- kind: conda + name: m2-patch + version: 2.7.6.2 + build: hd8ed1ab_4 + build_number: 4 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/m2-patch-2.7.6.2-hd8ed1ab_4.conda + sha256: c53d091882a43cfc49f74be7c3d74d41856eac9a2cd62424d31c78b3ae5d313f + md5: ace92cb3c819c9baa7f90a9e58435ba8 + depends: + - m2-conda-epoch 20230914 *_x86_64 + - m2-conda-epoch 20230914.* + - m2-msys2-runtime + license: GPL + size: 132160 + timestamp: 1719980487208 +- kind: conda + name: rattler-build + version: 0.29.0 + build: h49672d7_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/rattler-build-0.29.0-h49672d7_1.conda + sha256: 90ef9b3a51bb8fef0ef481b4a710c3c5042d3dd3240e62613dc7a35bf5c4712b + md5: 20da1ed142dd2c94bacb123e7559384b + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.40.33810 + license: BSD-3-Clause + size: 8155169 + timestamp: 1731020976885 +- kind: conda + name: ucrt + version: 10.0.22621.0 + build: h57928b3_1 + build_number: 1 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda + sha256: db8dead3dd30fb1a032737554ce91e2819b43496a0db09927edf01c32b577450 + md5: 6797b005cd0f439c4c5c9ac565783700 + constrains: + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + size: 559710 + timestamp: 1728377334097 +- kind: conda + name: vc + version: '14.3' + build: ha32ba9b_22 + build_number: 22 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-ha32ba9b_22.conda + sha256: 2a47c5bd8bec045959afada7063feacd074ad66b170c1ea92dd139b389fcf8fd + md5: 311c9ba1dfdd2895a8cb08346ff26259 + depends: + - vc14_runtime >=14.38.33135 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + size: 17447 + timestamp: 1728400826998 +- kind: conda + name: vc14_runtime + version: 14.40.33810 + build: hcc2c482_22 + build_number: 22 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.40.33810-hcc2c482_22.conda + sha256: 4c669c65007f88a7cdd560192f7e6d5679d191ac71610db724e18b2410964d64 + md5: ce23a4b980ee0556a118ed96550ff3f3 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.40.33810.* *_22 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + size: 750719 + timestamp: 1728401055788 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..13f7f1b --- /dev/null +++ b/pixi.toml @@ -0,0 +1,17 @@ +[project] +name = "conda-launchers" +version = "0.0.0" +description = "Conda launchers for Windows" +channels = ["conda-forge"] +platforms = ["win-64"] +license = "BSD-3-Clause" + +[tasks] +build = "rattler-build build --recipe recipe-zig --log-style plain --skip-existing all -c conda-forge" +upload = "rattler-build upload anaconda --log-style plain" + +[dependencies] +rattler-build = ">=0.29.0,<0.30" + +[target.win-64.dependencies] +m2-patch = ">=2.7.6.2,<3" diff --git a/recipe-zig/build.bat b/recipe-zig/build.bat new file mode 100644 index 0000000..5fc5e8f --- /dev/null +++ b/recipe-zig/build.bat @@ -0,0 +1,50 @@ +setlocal EnableDelayedExpansion + +@rem no-op for conda-launchers metapackage +if %PKG_NAME% == conda-launchers (exit 0) + +@rem rename patched source file to be used for building +move /Y launcher.c.orig launcher.c + +if %PKG_NAME% == conda-launchers_win-32 ( + set ZIG_TARGET=x86-windows-gnu + set EXE_TARGET=32 +) + +if %PKG_NAME% == conda-launchers_win-64 ( + set ZIG_TARGET=x86_64-windows-gnu + set EXE_TARGET=64 +) + +if %PKG_NAME% == conda-launchers_win-arm64 ( + set ZIG_TARGET=aarch64-windows-gnu + set EXE_TARGET=arm64 +) + +@rem build cli launcher +zig build -Doptimize=ReleaseSmall -Dtarget=%ZIG_TARGET% -Dgui=false --prefix-exe-dir "%PREFIX%\Scripts" +if %ERRORLEVEL% neq 0 exit 1 + +@rem build gui launcher +zig build -Doptimize=ReleaseSmall -Dtarget=%ZIG_TARGET% -Dgui=true --prefix-exe-dir "%PREFIX%\Scripts" +if %ERRORLEVEL% neq 0 exit 1 + +@rem install launcher scripts +cd "%PREFIX%\Scripts" +( +echo print^("cli-%EXE_TARGET%.exe successfully launched the accompanying Python script"^) +)> "cli-%EXE_TARGET%-script.py" + +if %ERRORLEVEL% neq 0 exit 1 + +( +echo import tkinter as tk +echo root = tk.Tk^(^) +echo text = tk.Label^(root, text ="Hello and Bye!"^) +echo text.pack^(^) +echo root.geometry^("150x100"^) +echo root.after^(1000, lambda: root.destroy^(^)^) +echo root.mainloop^(^) +)> "gui-%EXE_TARGET%-script.pyw" + +if %ERRORLEVEL% neq 0 exit 1 diff --git a/recipe-zig/recipe.yaml b/recipe-zig/recipe.yaml new file mode 100644 index 0000000..cfe011c --- /dev/null +++ b/recipe-zig/recipe.yaml @@ -0,0 +1,157 @@ +context: + name: conda-launchers + version: "24.7.1" + build_number: 0 + +recipe: + name: ${{ name|lower }} + version: ${{ version }} + +source: + - url: https://raw.githubusercontent.com/python/cpython/3.7/PC/launcher.c + sha256: a3167e5908cefa942f63e0de5c6ec7a929e8ca82a382537ab8292e5aaa82ca2e + file_name: launcher.c.orig + patches: + - ../src/cpython-launcher-c-mods-for-setuptools.3.7.patch + - url: https://raw.githubusercontent.com/python/cpython/3.7/LICENSE + sha256: 96e4f59524cde5af4a2ea837ef5e52b65e51f1f999825fd8a9ec3b444cb82aea + file_name: cpython-LICENSE + - path: ../src + +build: + number: ${{ build_number }} + skip: + - unix + - win and arm64 # this ensures builds only trigger on win-64 + +outputs: + - package: + name: ${{ name }} + + build: + noarch: generic + + requirements: + run: + - ${{ name }}_win-32 ${{ version }} + - ${{ name }}_win-64 ${{ version }} + - ${{ name }}_win-arm64 ${{ version }} + + tests: + - requirements: + run: + - ${{ name }}_win-32 ${{ version }} + - ${{ name }}_win-64 ${{ version }} + - ${{ name }}_win-arm64 ${{ version }} + script: + # cli + - if not exist %PREFIX%\Scripts\cli-32.exe exit 1 + - if not exist %PREFIX%\Scripts\cli-32-script.py exit 1 + - call "%PREFIX%\Scripts\cli-32.exe" + - if not exist %PREFIX%\Scripts\cli-64.exe exit 1 + - if not exist %PREFIX%\Scripts\cli-64-script.py exit 1 + - call "%PREFIX%\Scripts\cli-64.exe" + - if not exist %PREFIX%\Scripts\cli-arm64.exe exit 1 + - if not exist %PREFIX%\Scripts\cli-arm64-script.py exit 1 + - if: win and arm64 + then: call "%PREFIX%\Scripts\cli-arm64.exe" + # gui + - if not exist %PREFIX%\Scripts\gui-32.exe exit 1 + - if not exist %PREFIX%\Scripts\gui-32-script.pyw exit 1 + - call "%PREFIX%\Scripts\gui-32.exe" + - if not exist %PREFIX%\Scripts\gui-64.exe exit 1 + - if not exist %PREFIX%\Scripts\gui-64-script.pyw exit 1 + - call "%PREFIX%\Scripts\gui-64.exe" + - if not exist %PREFIX%\Scripts\gui-arm64.exe exit 1 + - if not exist %PREFIX%\Scripts\gui-arm64-script.pyw exit 1 + - if: win and arm64 + then: call "%PREFIX%\Scripts\gui-arm64.exe" + + - package: + name: ${{ name }}_win-32 + + build: + noarch: generic + + requirements: + build: + - zig >=0.13.0 + + run: + - python + - __win + + tests: + - package_contents: + files: + # cli + - Scripts/cli-32.exe + - Scripts/cli-32-script.py + # gui + - Scripts/gui-32.exe + - Scripts/gui-32-script.pyw + + - package: + name: ${{ name }}_win-64 + + build: + noarch: generic + + requirements: + build: + - zig >=0.13.0 + + run: + - python + - __win + + tests: + - package_contents: + files: + # cli + - Scripts/cli-64.exe + - Scripts/cli-64-script.py + # gui + - Scripts/gui-64.exe + - Scripts/gui-64-script.pyw + + - package: + name: ${{ name }}_win-arm64 + + build: + noarch: generic + + requirements: + build: + - zig >=0.13.0 + + run: + - python + - __win + + tests: + - package_contents: + files: + # cli + - Scripts/cli-arm64.exe + - Scripts/cli-arm64-script.py + # gui + - Scripts/gui-arm64.exe + - Scripts/gui-arm64-script.pyw + +about: + homepage: https://github.com/conda/conda-launchers + summary: Conda's Windows launchers for Python entry points + description: | + Windows launchers for Python entry points used in the conda ecosystem + + In order to launch Python entrypoints properly on Windows, a corresponding + executable is used to proxy the call because shebangs do not work on Windows. + This is achieved by the launchers (or called shims). + license: Python-2.0 AND BSD-3-Clause + license_file: cpython-LICENSE + repository: https://github.com/conda/conda-launchers + +extra: + recipe-maintainers: + - chawyehsu diff --git a/recipe/build.sh b/recipe/build.sh index 64eb34f..0488837 100644 --- a/recipe/build.sh +++ b/recipe/build.sh @@ -15,7 +15,7 @@ fi # Build resources file test -f resources.rc && rm -f resources.rc echo "#include \"winuser.h\"" > resources.rc -echo "1 RT_MANIFEST manifest.xml" >> resources.rc +echo "1 RT_MANIFEST launcher.manifest" >> resources.rc test -f resources-${_ARCH}.res && rm -f resources-${_ARCH}.res if [[ "${c_compiler}" == "gcc" ]]; then diff --git a/src/build.zig b/src/build.zig new file mode 100644 index 0000000..2807035 --- /dev/null +++ b/src/build.zig @@ -0,0 +1,78 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const CrossTarget = std.Target.Query; + +// Usage: +// zig build -Doptimize= -Dtarget= -Dgui= +// Supported targets: +// x86-windows-gnu +// x86-windows-msvc +// x86_64-windows-gnu +// x86_64-windows-msvc +// aarch64-windows-gnu +// aarch64-windows-msvc + +const required_version = std.SemanticVersion.parse("0.13.0") catch unreachable; +const compatible = builtin.zig_version.order(required_version) != .lt; + +pub fn build(b: *std.Build) void { + if (!compatible) { + std.log.err("Unsupported Zig compiler version", .{}); + return; + } + + const options = b.addOptions(); + const gui = b.option(bool, "gui", "Build a GUI launcher") orelse false; + options.addOption(bool, "gui", gui); + + const optimize = b.standardOptimizeOption(.{}); + const target = b.standardTargetOptions(.{ .default_target = CrossTarget{ + .os_tag = .windows, + .abi = .gnu, + } }); + + if (target.result.os.tag != .windows) { + std.log.err("Non-Windows target is not supported", .{}); + return; + } + + const exe_type = if (gui) "gui" else "cli"; + const name = switch (target.result.cpu.arch) { + .x86 => exe_type ++ "-32", + .x86_64 => exe_type ++ "-64", + .aarch64 => exe_type ++ "-arm64", + else => { + std.log.err("Unsupported CPU architecture", .{}); + return; + }, + }; + + const exe = b.addExecutable(.{ + .name = name, + .target = target, + .optimize = optimize, + .win32_manifest = b.path("./launcher.manifest"), + }); + + exe.addCSourceFile(.{ .file = b.path("./launcher.c") }); + exe.defineCMacro("SCRIPT_WRAPPER", null); + exe.linkLibC(); + exe.subsystem = if (gui) .Windows else .Console; + + if (gui) { + exe.defineCMacro("_WINDOWS", null); + } + + if (target.result.abi == .gnu) { + // NOTE: This requires Zig version 0.12.0-dev.3493+3661133f9 or later + exe.mingw_unicode_entry_point = true; + } else { + exe.linkSystemLibrary("advapi32"); + exe.linkSystemLibrary("shell32"); + if (gui) { + exe.linkSystemLibrary("user32"); + } + } + + b.installArtifact(exe); +} diff --git a/src/manifest.xml b/src/launcher.manifest similarity index 100% rename from src/manifest.xml rename to src/launcher.manifest