Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 14 additions & 22 deletions .github/workflows/build-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,15 @@ jobs:
- name: Build frontend
run: npm run build --workspace @bandscope/desktop
- name: Build native shell
run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target $env:BANDSCOPE_TARGET_TRIPLE
working-directory: apps/desktop
run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE
- name: Package Windows amd64 artifact
run: python scripts/release/package_desktop_artifact.py
- name: Upload Windows amd64 artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bandscope-windows-amd64-${{ github.sha }}
path: |
artifacts/*.zip
artifacts/*.sha256
artifacts/*.manifest.txt
path: artifacts/*

build-windows-arm64:
name: build / windows / arm64
Expand Down Expand Up @@ -168,17 +166,15 @@ jobs:
- name: Build frontend
run: npm run build --workspace @bandscope/desktop
- name: Build native shell
run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target $env:BANDSCOPE_TARGET_TRIPLE
working-directory: apps/desktop
run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE
- name: Package Windows arm64 artifact
run: python scripts/release/package_desktop_artifact.py
- name: Upload Windows arm64 artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bandscope-windows-arm64-${{ github.sha }}
path: |
artifacts/*.zip
artifacts/*.sha256
artifacts/*.manifest.txt
path: artifacts/*

gate-windows:
name: gate / build / windows
Expand Down Expand Up @@ -226,17 +222,15 @@ jobs:
- name: Build frontend
run: npm run build --workspace @bandscope/desktop
- name: Build native shell
run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target "$BANDSCOPE_TARGET_TRIPLE"
working-directory: apps/desktop
run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE"
- name: Package macOS amd64 artifact
run: python3 scripts/release/package_desktop_artifact.py
- name: Upload macOS amd64 artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bandscope-macos-amd64-${{ github.sha }}
path: |
artifacts/*.zip
artifacts/*.sha256
artifacts/*.manifest.txt
path: artifacts/*

build-macos-arm64:
name: build / macos / arm64
Expand Down Expand Up @@ -274,17 +268,15 @@ jobs:
- name: Build frontend
run: npm run build --workspace @bandscope/desktop
- name: Build native shell
run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target "$BANDSCOPE_TARGET_TRIPLE"
working-directory: apps/desktop
run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE"
- name: Package macOS arm64 artifact
run: python3 scripts/release/package_desktop_artifact.py
- name: Upload macOS arm64 artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bandscope-macos-arm64-${{ github.sha }}
path: |
artifacts/*.zip
artifacts/*.sha256
artifacts/*.manifest.txt
path: artifacts/*

gate-macos:
name: gate / build / macos
Expand Down Expand Up @@ -315,7 +307,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: gh release upload "$RELEASE_TAG" artifacts/*.zip artifacts/*.sha256 artifacts/*.manifest.txt --clobber --repo ${{ github.repository }}
run: gh release upload "$RELEASE_TAG" artifacts/* --clobber --repo ${{ github.repository }}

attach-macos-release-artifact:
name: release-artifact / macos
Expand All @@ -336,4 +328,4 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: gh release upload "$RELEASE_TAG" artifacts/*.zip artifacts/*.sha256 artifacts/*.manifest.txt --clobber --repo ${{ github.repository }}
run: gh release upload "$RELEASE_TAG" artifacts/* --clobber --repo ${{ github.repository }}
123 changes: 57 additions & 66 deletions scripts/release/package_desktop_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import hashlib
import os
import platform
import zipfile
import shutil
from pathlib import Path


Expand Down Expand Up @@ -61,94 +61,85 @@ def resolved_artifact_target() -> tuple[str, str]:
return normalized_platform(), normalized_architecture()


def artifact_identity() -> dict[str, str]:
def artifact_identity(filename: str) -> dict[str, str]:
"""Build the archive and manifest names for the current artifact target."""
git_sha = os.environ.get("GITHUB_SHA", "local")[:12]
target_platform, target_arch = resolved_artifact_target()
suffix = f"bandscope-{target_platform}-{target_arch}-{git_sha}"
ext = Path(filename).suffix
return {
"platform": target_platform,
"arch": target_arch,
"archive_name": f"{suffix}.zip",
"manifest_name": f"{suffix}.manifest.txt",
"archive_name": f"{suffix}{ext}",
"manifest_name": f"{suffix}{ext}.manifest.txt",
}


def expected_binary_path(repo_root: Path) -> Path:
"""Return the expected desktop binary path for the selected target triple."""
def find_installer_packages(repo_root: Path) -> list[Path]:
"""Find built Tauri installers (DMG, EXE, MSI)."""
target_triple = os.environ.get("BANDSCOPE_TARGET_TRIPLE")
if target_triple and "windows" in target_triple:
system = "windows"
elif target_triple and "apple-darwin" in target_triple:
system = "macos"
else:
system = normalized_platform()
binary_name = "bandscope-desktop.exe" if system == "windows" else "bandscope-desktop"
target_root = repo_root / "apps" / "desktop" / "src-tauri" / "target"
if target_triple:
target_root = target_root / target_triple
return target_root / "release" / binary_name

bundle_dir = target_root / "release" / "bundle"
installers = []

if bundle_dir.exists():
# macOS DMG
installers.extend(bundle_dir.glob("dmg/*.dmg"))
# Windows EXE/MSI
installers.extend(bundle_dir.glob("nsis/*.exe"))
installers.extend(bundle_dir.glob("msi/*.msi"))

return installers


def main() -> int:
"""Package the desktop binary, frontend assets, and metadata into a zip archive."""
"""Find the built installer packages, rename them, and calculate checksums."""
repo_root = Path(__file__).resolve().parents[2]
binary_path = expected_binary_path(repo_root)
frontend_dist = repo_root / "apps" / "desktop" / "dist"
output_dir = repo_root / "artifacts"
output_dir.mkdir(parents=True, exist_ok=True)

if not binary_path.exists():
raise FileNotFoundError(f"Missing built binary: {binary_path}")
if not frontend_dist.exists():
raise FileNotFoundError(f"Missing frontend dist directory: {frontend_dist}")

metadata_paths = [
repo_root / "services" / "analysis-engine" / "uv.lock",
repo_root / "package-lock.json",
repo_root / "apps" / "desktop" / "src-tauri" / "Cargo.lock",
repo_root / "supply-chain" / "supplemental-component-inventory.json",
]
missing_metadata = [str(path) for path in metadata_paths if not path.exists()]
if missing_metadata:
missing_list = ", ".join(missing_metadata)
raise FileNotFoundError(f"Missing release metadata files: {missing_list}")

identity = artifact_identity()
archive_name = identity["archive_name"]
archive_path = output_dir / archive_name

with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
archive.write(binary_path, arcname=f"bin/{binary_path.name}")
for path in frontend_dist.rglob("*"):
if path.is_file():
archive.write(
path,
arcname=str(Path("frontend") / path.relative_to(frontend_dist)),
)
for extra_path in metadata_paths:
archive.write(extra_path, arcname=str(Path("metadata") / extra_path.name))

checksum_path = output_dir / f"{archive_name}.sha256"
checksum_path.write_text(f"{sha256_file(archive_path)} {archive_name}\n", encoding="utf-8")

manifest_path = output_dir / identity["manifest_name"]
manifest_path.write_text(
"\n".join(
[
f"platform={identity['platform']}",
f"arch={identity['arch']}",
f"target_triple={os.environ.get('BANDSCOPE_TARGET_TRIPLE', 'native')}",
f"binary={binary_path.name}",
f"archive={archive_name}",
f"checksum={checksum_path.name}",
]
installers = find_installer_packages(repo_root)
if not installers:
raise FileNotFoundError("Could not find any built installers (DMG/EXE) in target/release/bundle/")
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# For safety, ensure we only pick one installer per run, or handle multiple
# Typically we might have both EXE and MSI for Windows.
for installer_path in installers:
identity = artifact_identity(installer_path.name)
archive_name = identity["archive_name"]

# If there are multiple (e.g. EXE and MSI), add original extension to avoid overwrite
if len(installers) > 1:
ext = installer_path.suffix
archive_name = archive_name.replace(ext, f"{ext}")
Comment thread
coderabbitai[bot] marked this conversation as resolved.

archive_path = output_dir / archive_name
shutil.copy2(installer_path, archive_path)

checksum_path = output_dir / f"{archive_name}.sha256"
checksum_path.write_text(f"{sha256_file(archive_path)} {archive_name}\n", encoding="utf-8")

manifest_path = output_dir / identity["manifest_name"]
manifest_path.write_text(
"\n".join(
[
f"platform={identity['platform']}",
f"arch={identity['arch']}",
f"target_triple={os.environ.get('BANDSCOPE_TARGET_TRIPLE', 'native')}",
f"original_file={installer_path.name}",
f"archive={archive_name}",
f"checksum={checksum_path.name}",
]
)
+ "\n",
encoding="utf-8",
)
+ "\n",
encoding="utf-8",
)

print(str(archive_path.relative_to(repo_root)))
print(f"Packaged {installer_path.name} to artifacts/{archive_name}")

return 0


Expand Down