Fix release: use bash array for asset filenames with spaces #6
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| jobs: | |
| # ── CLI executables (PyInstaller --onedir, for command-line users) ────────── | |
| build: | |
| name: Build CLI on ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| artifact: yleaf-linux.tar.gz | |
| - os: macos-latest | |
| artifact: yleaf-macos.tar.gz | |
| - os: windows-latest | |
| artifact: yleaf-windows.zip | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # ── Linux / macOS ───────────────────────────────────────────────────── | |
| - name: Set up Miniconda (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| channels: conda-forge,bioconda,defaults | |
| auto-activate: true | |
| activate-environment: base | |
| miniforge-version: latest | |
| - name: Install tools (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: mamba install -y --strict-channel-priority samtools minimap2 bcftools | |
| - name: Install Yleaf + PyInstaller (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: pip install -e . && pip install pyinstaller | |
| - name: Build (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: python -m PyInstaller --distpath dist --workpath build --noconfirm pyinstaller_yleaf.spec | |
| - name: Package (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: tar czf ${{ matrix.artifact }} -C dist yleaf | |
| # ── Windows ─────────────────────────────────────────────────────────── | |
| - name: Install samtools + bcftools via MSYS2 (Windows) | |
| if: runner.os == 'Windows' | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| msystem: MINGW64 | |
| update: false | |
| install: >- | |
| mingw-w64-x86_64-samtools | |
| mingw-w64-x86_64-bcftools | |
| - name: Stage tools and DLLs (Windows) | |
| if: runner.os == 'Windows' | |
| shell: msys2 {0} | |
| run: | | |
| mkdir -p /c/yleaf-tools | |
| for tool in samtools bcftools; do | |
| cp /mingw64/bin/${tool}.exe /c/yleaf-tools/ | |
| ldd /mingw64/bin/${tool}.exe \ | |
| | grep '/mingw64' | awk '{print $3}' \ | |
| | while read f; do cp "$f" /c/yleaf-tools/ 2>/dev/null || true; done | |
| done | |
| echo "Staged $(ls /c/yleaf-tools | wc -l) files" | |
| - name: Download minimap2 Windows binary (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $releases = gh release list --repo lh3/minimap2 --json tagName --limit 10 | ConvertFrom-Json | |
| foreach ($rel in $releases) { | |
| $assets = gh release view $rel.tagName --repo lh3/minimap2 --json assets | ConvertFrom-Json | |
| $winAsset = $assets.assets | Where-Object { $_.name -match 'win' } | Select-Object -First 1 | |
| if ($winAsset) { | |
| Write-Host "Found Windows binary in $($rel.tagName): $($winAsset.name)" | |
| Invoke-WebRequest $winAsset.browserDownloadUrl -OutFile mm2-win.zip | |
| Expand-Archive mm2-win.zip -DestinationPath C:/mm2-tmp | |
| $exe = Get-ChildItem -Recurse C:/mm2-tmp -Filter "minimap2*.exe" | Select-Object -First 1 | |
| if ($exe) { | |
| Copy-Item $exe.FullName "C:/yleaf-tools/minimap2.exe" | |
| Write-Host "minimap2.exe staged" | |
| } | |
| break | |
| } | |
| } | |
| if (-not (Test-Path "C:/yleaf-tools/minimap2.exe")) { | |
| Write-Host "No Windows minimap2 binary found — FASTQ mode unavailable on Windows" | |
| } | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set YLEAF_BUNDLED_TOOLS (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: echo "YLEAF_BUNDLED_TOOLS=C:/yleaf-tools" >> $env:GITHUB_ENV | |
| - name: Set up Python (Windows) | |
| if: runner.os == 'Windows' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Yleaf + PyInstaller (Windows) | |
| if: runner.os == 'Windows' | |
| run: pip install -e . && pip install pyinstaller | |
| - name: Build (Windows) | |
| if: runner.os == 'Windows' | |
| run: python -m PyInstaller --distpath dist --workpath build --noconfirm pyinstaller_yleaf.spec | |
| - name: Package (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: Compress-Archive -Path dist\yleaf -DestinationPath ${{ matrix.artifact }} | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: ${{ matrix.artifact }} | |
| # ── Dashboard (Tauri GUI, the default interface for end users) ────────────── | |
| tauri-build: | |
| name: Build Dashboard on ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| tauri_target: x86_64-unknown-linux-gnu | |
| - os: macos-latest | |
| tauri_target: aarch64-apple-darwin | |
| - os: windows-latest | |
| tauri_target: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # ── Linux system deps (required by Tauri / WebKitGTK) ───────────────── | |
| - name: Install Linux system dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf | |
| # ── Install bioinformatics tools ───────────────────────────────────── | |
| - name: Set up Miniconda (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| channels: conda-forge,bioconda,defaults | |
| auto-activate: true | |
| activate-environment: base | |
| miniforge-version: latest | |
| - name: Install tools (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: mamba install -y --strict-channel-priority samtools minimap2 bcftools | |
| - name: Install Yleaf + PyInstaller (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: pip install -e . && pip install pyinstaller | |
| - name: Install samtools + bcftools via MSYS2 (Windows) | |
| if: runner.os == 'Windows' | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| msystem: MINGW64 | |
| update: false | |
| install: >- | |
| mingw-w64-x86_64-samtools | |
| mingw-w64-x86_64-bcftools | |
| - name: Stage tools and DLLs (Windows) | |
| if: runner.os == 'Windows' | |
| shell: msys2 {0} | |
| run: | | |
| mkdir -p /c/yleaf-tools | |
| for tool in samtools bcftools; do | |
| cp /mingw64/bin/${tool}.exe /c/yleaf-tools/ | |
| ldd /mingw64/bin/${tool}.exe \ | |
| | grep '/mingw64' | awk '{print $3}' \ | |
| | while read f; do cp "$f" /c/yleaf-tools/ 2>/dev/null || true; done | |
| done | |
| echo "Staged $(ls /c/yleaf-tools | wc -l) files" | |
| - name: Download minimap2 Windows binary (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $releases = gh release list --repo lh3/minimap2 --json tagName --limit 10 | ConvertFrom-Json | |
| foreach ($rel in $releases) { | |
| $assets = gh release view $rel.tagName --repo lh3/minimap2 --json assets | ConvertFrom-Json | |
| $winAsset = $assets.assets | Where-Object { $_.name -match 'win' } | Select-Object -First 1 | |
| if ($winAsset) { | |
| Invoke-WebRequest $winAsset.browserDownloadUrl -OutFile mm2-win.zip | |
| Expand-Archive mm2-win.zip -DestinationPath C:/mm2-tmp | |
| $exe = Get-ChildItem -Recurse C:/mm2-tmp -Filter "minimap2*.exe" | Select-Object -First 1 | |
| if ($exe) { Copy-Item $exe.FullName "C:/yleaf-tools/minimap2.exe" } | |
| break | |
| } | |
| } | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set YLEAF_BUNDLED_TOOLS (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: echo "YLEAF_BUNDLED_TOOLS=C:/yleaf-tools" >> $env:GITHUB_ENV | |
| - name: Set up Python (Windows) | |
| if: runner.os == 'Windows' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Yleaf + PyInstaller (Windows) | |
| if: runner.os == 'Windows' | |
| run: pip install -e . && pip install pyinstaller | |
| # ── Build the PyInstaller sidecar ───────────────────────────────────── | |
| - name: Build sidecar (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| shell: bash -el {0} | |
| run: | | |
| python -m PyInstaller \ | |
| --distpath /tmp/sidecar-dist \ | |
| --workpath /tmp/sidecar-build \ | |
| --noconfirm \ | |
| dashboard/pyinstaller_yleaf.spec | |
| mkdir -p dashboard/src-tauri/binaries | |
| cp /tmp/sidecar-dist/yleaf dashboard/src-tauri/binaries/yleaf-${{ matrix.tauri_target }} | |
| chmod +x dashboard/src-tauri/binaries/yleaf-${{ matrix.tauri_target }} | |
| - name: Build sidecar (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| python -m PyInstaller ` | |
| --distpath C:\sidecar-dist ` | |
| --workpath C:\sidecar-build ` | |
| --noconfirm ` | |
| dashboard\pyinstaller_yleaf.spec | |
| New-Item -ItemType Directory -Force -Path dashboard\src-tauri\binaries | |
| Copy-Item C:\sidecar-dist\yleaf.exe ` | |
| dashboard\src-tauri\binaries\yleaf-${{ matrix.tauri_target }}.exe | |
| # ── Install Node.js + Rust, build Tauri app ─────────────────────────── | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install npm dependencies | |
| working-directory: dashboard | |
| run: npm ci | |
| - name: Build Tauri app (Linux/macOS) | |
| if: runner.os != 'Windows' | |
| working-directory: dashboard | |
| shell: bash -el {0} | |
| run: npm run tauri build | |
| - name: Build Tauri app (Windows) | |
| if: runner.os == 'Windows' | |
| working-directory: dashboard | |
| run: npm run tauri build | |
| # ── Collect and upload installer artifacts ──────────────────────────── | |
| - name: Upload Linux installers | |
| if: runner.os == 'Linux' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dashboard-linux | |
| path: | | |
| dashboard/src-tauri/target/release/bundle/appimage/*.AppImage | |
| dashboard/src-tauri/target/release/bundle/deb/*.deb | |
| - name: Upload macOS installer | |
| if: runner.os == 'macOS' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dashboard-macos | |
| path: dashboard/src-tauri/target/release/bundle/dmg/*.dmg | |
| - name: Upload Windows installers | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dashboard-windows | |
| path: | | |
| dashboard/src-tauri/target/release/bundle/nsis/*.exe | |
| dashboard/src-tauri/target/release/bundle/msi/*.msi | |
| # ── Create GitHub Release with all artifacts ─────────────────────────────── | |
| release: | |
| name: Create GitHub Release | |
| needs: [build, tauri-build] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| merge-multiple: true | |
| - name: Generate changelog | |
| run: | | |
| PREV=$(git tag --sort=-v:refname | sed -n '2p') | |
| RANGE="${PREV:+${PREV}..}HEAD" | |
| git log $RANGE --pretty=format:"- %s" \ | |
| | grep -v "Co-Authored-By" > release_notes.txt | |
| cat release_notes.txt | |
| - name: Publish release | |
| run: | | |
| ASSETS=() | |
| # CLI executables | |
| [ -f yleaf-linux.tar.gz ] && ASSETS+=("yleaf-linux.tar.gz") | |
| [ -f yleaf-macos.tar.gz ] && ASSETS+=("yleaf-macos.tar.gz") | |
| [ -f yleaf-windows.zip ] && ASSETS+=("yleaf-windows.zip") | |
| # Dashboard installers (filenames may contain spaces from productName) | |
| for f in *.AppImage *.deb *.dmg *.exe *.msi; do | |
| [ -f "$f" ] && ASSETS+=("$f") | |
| done | |
| gh release create ${{ github.ref_name }} \ | |
| --title "Yleaf ${{ github.ref_name }}" \ | |
| --notes-file release_notes.txt \ | |
| "${ASSETS[@]}" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |