From 9f8ec7ed9f50d9a6b2e5125ccbb7861ce834f7b0 Mon Sep 17 00:00:00 2001 From: yasuo Date: Mon, 23 Mar 2026 21:29:37 +0900 Subject: [PATCH] bundle newsynth binary --- .github/workflows/release.yml | 106 ++++++++++++++++++++++++++-------- .github/workflows/test.yml | 10 ++++ qret_cli_bundle/__init__.py | 50 ++++++++++++++-- tests/test_qret_cli_bundle.py | 4 ++ 4 files changed, 141 insertions(+), 29 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d095ec6..a50d4ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,7 @@ name: qret-ci on: push: + branches: [main] paths: - .github/workflows/release.yml - quration @@ -21,16 +22,12 @@ jobs: include: - os: macos-15-intel asset_suffix: macos-15-intel - archive_ext: tar.gz - os: macos-latest asset_suffix: macos-latest - archive_ext: tar.gz - os: ubuntu-latest asset_suffix: ubuntu-latest - archive_ext: tar.gz - os: windows-latest asset_suffix: windows-latest - archive_ext: zip steps: - name: Checkout @@ -38,6 +35,59 @@ jobs: with: submodules: recursive + - name: Restore newsynth cache + id: newsynth-cache + uses: actions/cache@v4 + with: + path: | + .newsynth/bin + .newsynth/license + key: newsynth-${{ matrix.os }}-v1 + + - name: Setup Haskell (GHC/Cabal) + if: steps.newsynth-cache.outputs.cache-hit != 'true' + uses: haskell-actions/setup@v2 + with: + ghc-version: "9.6.6" + cabal-version: "3.12.1.0" + + - name: Build newsynth binaries from Hackage + if: steps.newsynth-cache.outputs.cache-hit != 'true' + shell: bash + run: | + mkdir -p "$PWD/.newsynth/bin" + cabal update + cabal install newsynth --installdir "$PWD/.newsynth/bin" --install-method=copy --overwrite-policy=always + if [ "$RUNNER_OS" != "Windows" ]; then + chmod +x "$PWD/.newsynth/bin/gridsynth" + fi + ls -la "$PWD/.newsynth/bin" + + - name: Download newsynth LICENSE files + if: steps.newsynth-cache.outputs.cache-hit != 'true' + shell: bash + run: | + rm -rf "$PWD/.newsynth/src" + rm -rf "$PWD/.newsynth/license" + mkdir -p "$PWD/.newsynth/src" + mkdir -p "$PWD/.newsynth/license" + cabal get newsynth --destdir "$PWD/.newsynth/src" --pristine + newsynth_src_dir=$(find "$PWD/.newsynth/src" -maxdepth 1 -type d -name "newsynth-*" | head -n 1) + if [ -n "$newsynth_src_dir" ]; then + find "$newsynth_src_dir" -maxdepth 2 -type f \ + \( -iname "LICENSE*" -o -iname "COPYING*" -o -iname "README*" -o -iname "GPL*" \) \ + -exec cp {} "$PWD/.newsynth/license/" \; + fi + ls -la "$PWD/.newsynth/license" + + - name: Fix newsynth permissions (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + if [ -d "$PWD/.newsynth/bin" ]; then + chmod +x "$PWD"/.newsynth/bin/* || true + fi + - name: Setup MSVC (Windows) if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 @@ -99,32 +149,42 @@ jobs: - name: Install to stage run: cmake --install build --config Release --prefix stage - - name: Package artifact (Unix) - if: runner.os != 'Windows' + - name: Prepare release stub contents shell: bash run: | - mkdir -p dist package + mkdir -p dist root="qret-${{ matrix.asset_suffix }}" - mkdir -p "package/$root" - cp -R stage/. "package/$root/" - tar -C package -czf "dist/$root.${{ matrix.archive_ext }}" "$root" - - - name: Package artifact (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - New-Item -ItemType Directory -Force -Path dist | Out-Null - New-Item -ItemType Directory -Force -Path package | Out-Null - $root = "qret-${{ matrix.asset_suffix }}" - New-Item -ItemType Directory -Force -Path "package/$root" | Out-Null - Copy-Item -Recurse -Force stage\* "package/$root/" - Compress-Archive -Path "package/$root" -DestinationPath "dist/$root.${{ matrix.archive_ext }}" -Force + package_root="dist/$root" + mkdir -p "$package_root" + cp -R stage/. "$package_root/" + rm -rf "$package_root/include" + rm -rf "$package_root/lib/cmake" + + mkdir -p "$package_root/bin" + mkdir -p "$package_root/licenses/qret" + mkdir -p "$package_root/licenses/newsynth" + + for file in "$PWD"/.newsynth/bin/*; do + [ -f "$file" ] || continue + base_name="$(basename "$file")" + case "$base_name" in + README*|readme*) + continue + ;; + esac + cp "$file" "$package_root/bin/" + done + + find "$PWD/quration" -maxdepth 2 -type f \ + \( -iname "LICENSE*" -o -iname "COPYING*" -o -iname "README*" -o -iname "NOTICE*" \) \ + -exec cp {} "$package_root/licenses/qret/" \; + find "$PWD/.newsynth/license" -mindepth 1 -maxdepth 1 -type f -exec cp {} "$package_root/licenses/newsynth/" \; - name: Upload qret artifact uses: actions/upload-artifact@v4 with: name: qret-${{ matrix.asset_suffix }} - path: dist/qret-${{ matrix.asset_suffix }}.${{ matrix.archive_ext }} + path: dist/qret-${{ matrix.asset_suffix }} release: if: github.event_name == 'release' @@ -143,4 +203,4 @@ jobs: - name: Upload artifacts to release uses: softprops/action-gh-release@v2 with: - files: dist/* + files: dist/**/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 800738e..367cad8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,7 @@ name: test on: push: + branches: [main] paths: - src/** - tests/** @@ -47,7 +48,12 @@ jobs: #!/usr/bin/env sh echo "qret 0.0.0-ci" EOF + cat > .ci-bin/gridsynth <<'EOF' + #!/usr/bin/env sh + echo "gridsynth 0.0.0-ci" + EOF chmod +x .ci-bin/qret + chmod +x .ci-bin/gridsynth echo "$GITHUB_WORKSPACE/.ci-bin" >> "$GITHUB_PATH" - name: Prepare qret stub for test (Windows) @@ -59,6 +65,10 @@ jobs: @echo off echo qret 0.0.0-ci '@ | Set-Content -Path .ci-bin/qret.cmd + @' + @echo off + echo gridsynth 0.0.0-ci + '@ | Set-Content -Path .ci-bin/gridsynth.cmd "$env:GITHUB_WORKSPACE/.ci-bin" | Out-File -FilePath $env:GITHUB_PATH -Append - name: Run pytest diff --git a/qret_cli_bundle/__init__.py b/qret_cli_bundle/__init__.py index f21e6b0..15bdd53 100644 --- a/qret_cli_bundle/__init__.py +++ b/qret_cli_bundle/__init__.py @@ -76,21 +76,57 @@ def _find_qret_binary(root: Path) -> Path: raise QretBundleError(f"Could not find {exe_name} under {root}") +def _gridsynth_names() -> tuple[str, ...]: + if platform.system() == "Windows": + return ("gridsynth.exe", "gridsynth.cmd", "newsynth.exe", "newsynth.cmd") + return ("gridsynth", "newsynth") + + +def _find_gridsynth_binary(root: Path) -> Path | None: + for exe_name in _gridsynth_names(): + for path in root.rglob(exe_name): + if path.is_file(): + if platform.system() != "Windows": + mode = path.stat().st_mode + path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + return path + return None + + def _append_env_path(var_name: str, entry: str) -> None: current = os.environ.get(var_name, "") os.environ[var_name] = current + os.pathsep + entry if current else entry def _find_existing_qret_in_path() -> Path | None: - exe_name = "qret.exe" if platform.system() == "Windows" else "qret" - qret_path = shutil.which(exe_name) - if qret_path: - candidate = Path(qret_path).resolve() - if candidate.is_file(): - return candidate + exe_names = ("qret.exe", "qret.cmd") if platform.system() == "Windows" else ("qret",) + for exe_name in exe_names: + qret_path = shutil.which(exe_name) + if qret_path: + candidate = Path(qret_path).resolve() + if candidate.is_file(): + return candidate return None +def _find_existing_gridsynth_in_path() -> Path | None: + for exe_name in _gridsynth_names(): + gridsynth_path = shutil.which(exe_name) + if gridsynth_path: + candidate = Path(gridsynth_path).resolve() + if candidate.is_file(): + return candidate + return None + + +def _set_gridsynth_path(bin_dir: Path) -> None: + gridsynth = _find_gridsynth_binary(bin_dir) + if gridsynth is None: + gridsynth = _find_existing_gridsynth_in_path() + if gridsynth is not None: + os.environ["GRIDSYNTH_PATH"] = str(gridsynth) + + def ensure_qret_on_path() -> Path: """Ensure qret is downloaded and available in PATH, returning its full path.""" existing_qret = _find_existing_qret_in_path() @@ -100,6 +136,7 @@ def ensure_qret_on_path() -> Path: lib_dir = qret.parent.parent / "lib" if bin_dir.exists(): _append_env_path("PATH", str(bin_dir)) + _set_gridsynth_path(bin_dir) if platform.system() == "Linux" and lib_dir.exists(): _append_env_path("LD_LIBRARY_PATH", str(lib_dir)) return qret @@ -137,6 +174,7 @@ def ensure_qret_on_path() -> Path: lib_dir = qret.parent.parent / "lib" if bin_dir.exists(): _append_env_path("PATH", str(bin_dir)) + _set_gridsynth_path(bin_dir) if platform.system() == "Linux" and lib_dir.exists(): _append_env_path("LD_LIBRARY_PATH", str(lib_dir)) return qret diff --git a/tests/test_qret_cli_bundle.py b/tests/test_qret_cli_bundle.py index 0f9c596..00de35b 100644 --- a/tests/test_qret_cli_bundle.py +++ b/tests/test_qret_cli_bundle.py @@ -1,6 +1,7 @@ from __future__ import annotations import importlib.util +import os import subprocess import shutil from pathlib import Path @@ -20,3 +21,6 @@ def test_import_and_qret_version() -> None: ) assert result.returncode == 0, result.stderr assert result.stdout.strip() or result.stderr.strip() + + gridsynth_path = os.environ.get("GRIDSYNTH_PATH") + assert gridsynth_path is not None