From 3f75306996a124c04e28105a0ec0e0c5f0998913 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:06:32 +0800 Subject: [PATCH 01/16] chore: bump ecos-studio version to 0.1.0-alpha.3 Signed-off-by: Emin --- MODULE.bazel | 4 ++-- ecos/gui/default.nix | 2 +- ecos/gui/package.json | 2 +- ecos/gui/src-tauri/Cargo.lock | 2 +- ecos/gui/src-tauri/Cargo.toml | 3 +-- ecos/gui/src-tauri/tauri.conf.json | 2 +- ecos/server/default.nix | 2 +- ecos/server/ecos_server/main.py | 11 +++++++++-- ecos/server/pyproject.toml | 2 +- ecos/server/uv.lock | 2 +- 10 files changed, 19 insertions(+), 13 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 40bded2..2502db5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,6 @@ module( name = "ecos_studio", - version = "0.1.0", + version = "0.1.0-alpha.3", ) bazel_dep(name = "rules_shell", version = "0.6.1") @@ -16,4 +16,4 @@ appimage_tool = use_extension("@ecos-bazel//rules:appimage-tool.bzl", "appimage_ use_repo(appimage_tool, "appimagetool_x86_64_linux") oss_cad_suite = use_extension("@ecos-bazel//rules:oss_cad_suite.bzl", "oss_cad_suite") -use_repo(oss_cad_suite, "oss_cad_suite", "oss_cad_suite_pruned") \ No newline at end of file +use_repo(oss_cad_suite, "oss_cad_suite", "oss_cad_suite_pruned") diff --git a/ecos/gui/default.nix b/ecos/gui/default.nix index a27e9be..82b8540 100644 --- a/ecos/gui/default.nix +++ b/ecos/gui/default.nix @@ -18,7 +18,7 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "ecos-studio"; - version = "0.1.0-alpha"; + version = "0.1.0-alpha.3"; src = with lib.fileset; diff --git a/ecos/gui/package.json b/ecos/gui/package.json index bfb3731..0d55e53 100644 --- a/ecos/gui/package.json +++ b/ecos/gui/package.json @@ -1,6 +1,6 @@ { "name": "ecos-studio-gui", - "version": "0.1.0", + "version": "0.1.0-alpha.3", "private": true, "type": "module", "pnpm": { diff --git a/ecos/gui/src-tauri/Cargo.lock b/ecos/gui/src-tauri/Cargo.lock index 74df8fb..808ec5b 100644 --- a/ecos/gui/src-tauri/Cargo.lock +++ b/ecos/gui/src-tauri/Cargo.lock @@ -751,7 +751,7 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecos-studio" -version = "0.1.0" +version = "0.1.0-alpha.3" dependencies = [ "chrono", "env_logger", diff --git a/ecos/gui/src-tauri/Cargo.toml b/ecos/gui/src-tauri/Cargo.toml index 1038546..48e29af 100644 --- a/ecos/gui/src-tauri/Cargo.toml +++ b/ecos/gui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecos-studio" -version = "0.1.0" +version = "0.1.0-alpha.3" description = "ECOS-Studio" authors = ["Ekko"] license = "" @@ -50,4 +50,3 @@ overflow-checks = false opt-level = 3 [profile.release.package.wry] opt-level = 3 - diff --git a/ecos/gui/src-tauri/tauri.conf.json b/ecos/gui/src-tauri/tauri.conf.json index bfb57b7..e43c1d1 100644 --- a/ecos/gui/src-tauri/tauri.conf.json +++ b/ecos/gui/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "ECOS-Studio", - "version": "0.1.0", + "version": "0.1.0-alpha.3", "identifier": "com.ecos.studio", "build": { "beforeDevCommand": "pnpm run dev", diff --git a/ecos/server/default.nix b/ecos/server/default.nix index 9af6a0d..856ade9 100644 --- a/ecos/server/default.nix +++ b/ecos/server/default.nix @@ -6,7 +6,7 @@ python3Packages.buildPythonPackage { pname = "ecos-server"; - version = "0.1.0"; + version = "0.1.0-alpha.3"; pyproject = true; src = diff --git a/ecos/server/ecos_server/main.py b/ecos/server/ecos_server/main.py index 82f1676..14a9563 100644 --- a/ecos/server/ecos_server/main.py +++ b/ecos/server/ecos_server/main.py @@ -8,7 +8,9 @@ from .ecc import sse_router, workspace_router # Create FastAPI application -app = FastAPI(title="ECOS Studio API", description="Backend API for ECOS Studio", version="0.1.0") +app = FastAPI( + title="ECOS Studio API", description="Backend API for ECOS Studio", version="0.1.0-alpha.3" +) # Configure CORS for frontend access app.add_middleware( @@ -35,7 +37,12 @@ @app.get("/") async def root(): """Root endpoint""" - return {"name": "ECOS Studio API", "version": "0.1.0", "status": "running", "tools": ["ecc"]} + return { + "name": "ECOS Studio API", + "version": "0.1.0-alpha.3", + "status": "running", + "tools": ["ecc"], + } @app.get("/health") diff --git a/ecos/server/pyproject.toml b/ecos/server/pyproject.toml index 7c6ce74..3170569 100644 --- a/ecos/server/pyproject.toml +++ b/ecos/server/pyproject.toml @@ -4,7 +4,7 @@ requires = ["hatchling"] [project] name = "ecos-server" -version = "0.1.0" +version = "0.1.0-alpha.3" requires-python = ">=3.11" dependencies = [ "ecc==0.1.0a1", diff --git a/ecos/server/uv.lock b/ecos/server/uv.lock index ac0c9f0..1c7bde0 100644 --- a/ecos/server/uv.lock +++ b/ecos/server/uv.lock @@ -320,7 +320,7 @@ wheels = [ [[package]] name = "ecos-server" -version = "0.1.0" +version = "0.1.0-alpha.3" source = { editable = "." } dependencies = [ { name = "ecc", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, From a6b7f03f95ae75696ca8694a4810ccd54b3b02a7 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:06:43 +0800 Subject: [PATCH 02/16] chore: bump ecc Signed-off-by: Emin --- ecc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecc b/ecc index ec4b4e3..183dc25 160000 --- a/ecc +++ b/ecc @@ -1 +1 @@ -Subproject commit ec4b4e3a1d83c4b00b7eb63a921c2fd3fcd60662 +Subproject commit 183dc252db9ba8280e827d570f0e4376bb427a76 From e5284b9ff2f160d7f59bb412ee1a38ae2c8314cd Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:07:05 +0800 Subject: [PATCH 03/16] feat(ci): add release ci and build check Signed-off-by: Emin --- .github/actions/check-version/action.yml | 144 ++++++++++++++++ .github/workflows/auto-tag.yml | 54 ++++++ .github/workflows/release.yml | 201 +++++++++++++++++++++++ 3 files changed, 399 insertions(+) create mode 100644 .github/actions/check-version/action.yml create mode 100644 .github/workflows/auto-tag.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/actions/check-version/action.yml b/.github/actions/check-version/action.yml new file mode 100644 index 0000000..ef78ea8 --- /dev/null +++ b/.github/actions/check-version/action.yml @@ -0,0 +1,144 @@ +name: Check ECOS Studio Version Consistency +description: Verify release version fields across MODULE.bazel, ecos-server, and ecos-studio. Optionally verify an expected git tag. + +inputs: + expected_tag: + description: Expected tag (for example, v0.1.0-alpha.3). If set, also verify the tag matches the detected version. + required: false + default: "" + +outputs: + version: + description: The repository release version detected from MODULE.bazel. + value: ${{ steps.verify.outputs.version }} + tag: + description: The git tag corresponding to the detected version. + value: ${{ steps.verify.outputs.tag }} + +runs: + using: composite + steps: + - name: Verify version consistency + id: verify + shell: bash + run: | + python3 <<'PY' + import json + import os + import re + import sys + import tomllib + from pathlib import Path + + expected_tag = os.environ.get("EXPECTED_TAG", "").strip() + + def read(path: str) -> str: + return Path(path).read_text(encoding="utf-8") + + def parse_regex(path: str, pattern: str, *, flags: int = 0, label: str | None = None) -> str: + text = read(path) + match = re.search(pattern, text, flags) + if not match: + raise SystemExit(f"ERROR: failed to parse {label or path}") + return match.group(1) + + versions: list[tuple[str, str]] = [] + + module_version = parse_regex( + "MODULE.bazel", + r'(?m)^\s*version\s*=\s*"([^"]+)"', + label="MODULE.bazel version", + ) + versions.append(("MODULE.bazel", module_version)) + + server_pyproject = tomllib.loads(read("ecos/server/pyproject.toml"))["project"]["version"] + versions.append(("ecos/server/pyproject.toml", server_pyproject)) + + server_default_nix = parse_regex( + "ecos/server/default.nix", + r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', + label="ecos/server/default.nix version", + ) + versions.append(("ecos/server/default.nix", server_default_nix)) + + server_main_fastapi = parse_regex( + "ecos/server/ecos_server/main.py", + r'FastAPI\(.*?version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/server/ecos_server/main.py FastAPI version", + ) + versions.append(("ecos/server/ecos_server/main.py (FastAPI)", server_main_fastapi)) + + server_main_root = parse_regex( + "ecos/server/ecos_server/main.py", + r'"version"\s*:\s*"([^"]+)"', + label="ecos/server/ecos_server/main.py root endpoint version", + ) + versions.append(("ecos/server/ecos_server/main.py (root endpoint)", server_main_root)) + + server_uv_lock = parse_regex( + "ecos/server/uv.lock", + r'\[\[package\]\]\s+name\s*=\s*"ecos-server"\s+version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/server/uv.lock root package version", + ) + versions.append(("ecos/server/uv.lock", server_uv_lock)) + + gui_package = json.loads(read("ecos/gui/package.json"))["version"] + versions.append(("ecos/gui/package.json", gui_package)) + + gui_default_nix = parse_regex( + "ecos/gui/default.nix", + r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', + label="ecos/gui/default.nix version", + ) + versions.append(("ecos/gui/default.nix", gui_default_nix)) + + gui_cargo_toml = tomllib.loads(read("ecos/gui/src-tauri/Cargo.toml"))["package"]["version"] + versions.append(("ecos/gui/src-tauri/Cargo.toml", gui_cargo_toml)) + + gui_cargo_lock = parse_regex( + "ecos/gui/src-tauri/Cargo.lock", + r'\[\[package\]\]\s+name\s*=\s*"ecos-studio"\s+version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/gui/src-tauri/Cargo.lock root package version", + ) + versions.append(("ecos/gui/src-tauri/Cargo.lock", gui_cargo_lock)) + + gui_tauri_conf = json.loads(read("ecos/gui/src-tauri/tauri.conf.json"))["version"] + versions.append(("ecos/gui/src-tauri/tauri.conf.json", gui_tauri_conf)) + + gui_version_json = json.loads(read("ecos/gui/src-tauri/ecos-version.json")) + versions.append(("ecos/gui/src-tauri/ecos-version.json (gui)", gui_version_json["gui"])) + versions.append(("ecos/gui/src-tauri/ecos-version.json (server)", gui_version_json["server"])) + + print("Detected versions:") + for name, value in versions: + print(f" {name}: {value}") + + mismatches = [(name, value) for name, value in versions if value != module_version] + if mismatches: + print("") + print(f"ERROR: version mismatch detected. Expected all files to match MODULE.bazel ({module_version}).", file=sys.stderr) + for name, value in mismatches: + print(f" {name}: {value}", file=sys.stderr) + sys.exit(1) + + tag = f"v{module_version}" + if expected_tag and expected_tag != tag: + print( + f"ERROR: tag mismatch. expected {tag} from version files, got {expected_tag}.", + file=sys.stderr, + ) + sys.exit(1) + + github_output = os.environ["GITHUB_OUTPUT"] + with open(github_output, "a", encoding="utf-8") as fh: + fh.write(f"version={module_version}\n") + fh.write(f"tag={tag}\n") + + print("") + print(f"Version check passed: {module_version}") + PY + env: + EXPECTED_TAG: ${{ inputs.expected_tag }} diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml new file mode 100644 index 0000000..5bcd79d --- /dev/null +++ b/.github/workflows/auto-tag.yml @@ -0,0 +1,54 @@ +name: Auto Tag + +on: + push: + branches: [main] + paths: + - "MODULE.bazel" + - "ecos/server/pyproject.toml" + - "ecos/server/default.nix" + - "ecos/server/ecos_server/main.py" + - "ecos/server/uv.lock" + - "ecos/gui/default.nix" + - "ecos/gui/package.json" + - "ecos/gui/src-tauri/Cargo.toml" + - "ecos/gui/src-tauri/Cargo.lock" + - "ecos/gui/src-tauri/tauri.conf.json" + - "ecos/gui/src-tauri/ecos-version.json" + +permissions: + contents: write + +jobs: + auto-tag: + name: Create version tag + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check version consistency + id: version + uses: ./.github/actions/check-version + + - name: Check if tag exists + id: check + run: | + if git ls-remote --tags origin "refs/tags/${{ steps.version.outputs.tag }}" | grep -q .; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "Tag ${{ steps.version.outputs.tag }} already exists, skipping." + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "Tag ${{ steps.version.outputs.tag }} does not exist, will create." + fi + + - name: Create and push tag + if: steps.check.outputs.exists == 'false' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${{ steps.version.outputs.tag }}" + git push origin "${{ steps.version.outputs.tag }}" + echo "Created and pushed tag: ${{ steps.version.outputs.tag }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4f73b68 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,201 @@ +name: Release + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + tag_name: + description: Tag to release (for example, v0.1.0-alpha.3) + required: true + +permissions: + contents: write + +jobs: + check-version: + name: Check Version Consistency + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag_name || github.ref }} + fetch-depth: 0 + + - name: Check version + id: version + uses: ./.github/actions/check-version + with: + expected_tag: ${{ github.event.inputs.tag_name || github.ref_name }} + + build: + name: Build AppImage + needs: check-version + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag_name || github.ref }} + submodules: recursive + fetch-depth: 0 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing \ + git curl ca-certificates build-essential pkg-config \ + python3 python3-venv python3-pip python3-dev \ + libgtk-3-dev libgtk-3-bin libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev \ + libglib2.0-dev libglib2.0-bin librsvg2-dev \ + cmake ninja-build tcl-dev \ + libgflags-dev libgoogle-glog-dev libboost-all-dev libgtest-dev \ + flex libeigen3-dev libunwind-dev libmetis-dev libgmp-dev bison \ + libhwloc-dev libcurl4-openssl-dev libtbb-dev \ + patchelf jq wget + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ecos/gui/.nvmrc + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@v2 + with: + workspaces: ecos/gui/src-tauri + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: latest + enable-cache: true + + - name: Setup Bazel + run: | + mkdir -p "$HOME/.local/bin" + curl -fsSL https://github.com/bazelbuild/bazel/releases/download/8.5.0/bazel-8.5.0-linux-x86_64 \ + -o "$HOME/.local/bin/bazel" + chmod +x "$HOME/.local/bin/bazel" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Sync server environment + working-directory: ecos/server + run: uv sync --frozen --all-groups --all-extras --python 3.11 + + - name: Build release bundle + run: | + PATH="$PWD/ecos/server/.venv/bin:$PATH" bazel build //:ecos_studio_bundle + + - name: Extract AppImage + run: | + BUNDLE_TAR="bazel-bin/ecos/ecos_studio_bundle/ecos_studio_bundle.tar" + + rm -rf dist/release + mkdir -p dist/release + + tar -tf "$BUNDLE_TAR" | grep -E '(^|/)[^/]+\.AppImage$' > /tmp/ecos-release-files.txt || true + if [ ! -s /tmp/ecos-release-files.txt ]; then + echo "ERROR: AppImage artifact not found in $BUNDLE_TAR" >&2 + echo "Bundle contents:" >&2 + tar -tf "$BUNDLE_TAR" | sed -n '1,200p' >&2 + exit 1 + fi + + tar -xf "$BUNDLE_TAR" -C dist/release -T /tmp/ecos-release-files.txt + + ( + cd dist/release + find . -type f -name '*.AppImage' -print0 \ + | sort -z \ + | xargs -0 sha256sum > SHA256SUMS + ) + + echo "Built AppImage artifact:" + find dist/release -type f \( -name '*.AppImage' -o -name 'SHA256SUMS' \) | sort + echo "" + cat dist/release/SHA256SUMS + + - name: Upload AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: ecos-studio-release + if-no-files-found: error + path: | + dist/release/**/*.AppImage + dist/release/SHA256SUMS + + release: + name: Create Release + needs: [check-version, build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.tag_name || github.ref }} + fetch-depth: 0 + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + name: ecos-studio-release + path: dist/release + + - name: Determine tag version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + FULL_TAG="${{ github.event.inputs.tag_name }}" + else + FULL_TAG="${GITHUB_REF_NAME}" + fi + TAG_VERSION="${FULL_TAG#v}" + echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" + echo "full_tag=$FULL_TAG" >> "$GITHUB_OUTPUT" + + - name: Generate release notes + run: | + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + { + if [[ -n "$PREV_TAG" ]]; then + echo "## Changes" + echo "" + git log --oneline --no-merges "${PREV_TAG}..HEAD" | sed 's/^/- /' + echo "" + fi + echo "## Checksums" + echo "" + echo '```' + cat dist/release/SHA256SUMS + echo '```' + } > release-notes.md + cat release-notes.md + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + mapfile -t ASSETS < <(find dist/release -type f \( -name '*.AppImage' -o -name 'SHA256SUMS' \) | sort) + gh release create "${{ steps.version.outputs.full_tag }}" \ + --title "ECOS Studio ${{ steps.version.outputs.full_tag }}" \ + --notes-file release-notes.md \ + "${ASSETS[@]}" From fc66f9b3dad1001332faf329322e99343e09fb13 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:15:25 +0800 Subject: [PATCH 04/16] ci: consolidate checks into ci workflow --- .github/workflows/ci.yml | 192 +++++++++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 59 ------------ 2 files changed, 192 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f18f213 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,192 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + check-version: + name: Check Version Consistency + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check version + uses: ./.github/actions/check-version + + ruff: + name: Ruff + runs-on: ubuntu-latest + defaults: + run: + working-directory: ecos/server + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Check formatting + run: uvx ruff format --check + + - name: Lint Python + run: uvx ruff check + + clippy: + name: Clippy + runs-on: ubuntu-latest + defaults: + run: + working-directory: ecos/gui/src-tauri + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --fix-missing \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@v2 + with: + workspaces: ecos/gui/src-tauri + + - name: Stub generated artifacts + run: | + mkdir -p binaries + touch binaries/api-server-x86_64-unknown-linux-gnu + chmod +x binaries/api-server-x86_64-unknown-linux-gnu + mkdir -p resources/oss-cad-suite + touch resources/oss-cad-suite/README + mkdir -p ../dist + + - name: Lint Rust + run: cargo clippy -- -D warnings + + vue-tsc: + name: Vue Typecheck + runs-on: ubuntu-latest + defaults: + run: + working-directory: ecos/gui + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ecos/gui/.nvmrc + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck Vue + run: pnpm exec vue-tsc --noEmit + + build-appimage: + name: Build AppImage + needs: [check-version, ruff, clippy, vue-tsc] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing \ + git curl ca-certificates build-essential pkg-config \ + python3 python3-venv python3-pip python3-dev \ + libgtk-3-dev libgtk-3-bin libwebkit2gtk-4.1-dev \ + libayatana-appindicator3-dev \ + libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev \ + libglib2.0-dev libglib2.0-bin librsvg2-dev \ + cmake ninja-build tcl-dev \ + libgflags-dev libgoogle-glog-dev libboost-all-dev libgtest-dev \ + flex libeigen3-dev libunwind-dev libmetis-dev libgmp-dev bison \ + libhwloc-dev libcurl4-openssl-dev libtbb-dev \ + patchelf jq wget + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ecos/gui/.nvmrc + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@v2 + with: + workspaces: ecos/gui/src-tauri + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + with: + version: latest + enable-cache: true + + - name: Setup Bazel + run: | + mkdir -p "$HOME/.local/bin" + curl -fsSL https://github.com/bazelbuild/bazel/releases/download/8.5.0/bazel-8.5.0-linux-x86_64 \ + -o "$HOME/.local/bin/bazel" + chmod +x "$HOME/.local/bin/bazel" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Sync server environment + working-directory: ecos/server + run: uv sync --frozen --all-groups --all-extras --python 3.11 + + - name: Build release bundle + run: | + PATH="$PWD/ecos/server/.venv/bin:$PATH" bazel build //:ecos_studio_bundle + + - name: Verify AppImage artifact + run: | + BUNDLE_TAR="bazel-bin/ecos/ecos_studio_bundle/ecos_studio_bundle.tar" + tar -tf "$BUNDLE_TAR" | grep -E '(^|/)[^/]+\.AppImage$' > /tmp/ecos-appimage-files.txt || true + + if [ ! -s /tmp/ecos-appimage-files.txt ]; then + echo "ERROR: AppImage artifact not found in $BUNDLE_TAR" >&2 + echo "Bundle contents:" >&2 + tar -tf "$BUNDLE_TAR" | sed -n '1,200p' >&2 + exit 1 + fi + + echo "Found AppImage artifact:" + cat /tmp/ecos-appimage-files.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 1340aad..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: lint - -on: - push: - branches: [main] - pull_request: - -jobs: - ruff: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ecos/server - steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 - - run: uvx ruff format --check - - run: uvx ruff check - - clippy: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ecos/gui/src-tauri - steps: - - uses: actions/checkout@v4 - - run: sudo apt-get update && sudo apt-get install -y --fix-missing libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - workspaces: ecos/gui/src-tauri - # Stub artifacts absent in CI: - # - api-server: Bazel/PyInstaller sidecar, checked by tauri-build build.rs - # - oss-cad-suite: not committed to git, checked by tauri-build build.rs - # - ../dist: Vite output, checked by tauri::generate_context!() proc macro - - run: | - mkdir -p binaries - touch binaries/api-server-x86_64-unknown-linux-gnu - chmod +x binaries/api-server-x86_64-unknown-linux-gnu - mkdir -p resources/oss-cad-suite - touch resources/oss-cad-suite/README - mkdir -p ../dist - - run: cargo clippy -- -D warnings - - vue-tsc: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ecos/gui - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: ecos/gui/.nvmrc - - uses: pnpm/action-setup@v4 - with: - version: latest - - run: pnpm install --frozen-lockfile - - run: pnpm exec vue-tsc --noEmit From 4551ee682445c3fcf569123acf158fa22b719333 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:30:14 +0800 Subject: [PATCH 05/16] feat(ci): split setup steps into actions Signed-off-by: Emin --- .../install-tauri-linux-deps/action.yml | 29 +++++++ .github/actions/setup-node-pnpm/action.yml | 25 ++++++ .github/actions/setup-rust-cache/action.yml | 19 +++++ .github/workflows/ci.yml | 78 +++++-------------- .github/workflows/release.yml | 3 + 5 files changed, 97 insertions(+), 57 deletions(-) create mode 100644 .github/actions/install-tauri-linux-deps/action.yml create mode 100644 .github/actions/setup-node-pnpm/action.yml create mode 100644 .github/actions/setup-rust-cache/action.yml diff --git a/.github/actions/install-tauri-linux-deps/action.yml b/.github/actions/install-tauri-linux-deps/action.yml new file mode 100644 index 0000000..877e3bf --- /dev/null +++ b/.github/actions/install-tauri-linux-deps/action.yml @@ -0,0 +1,29 @@ +name: Install Tauri Linux Dependencies +description: Install Linux packages required by Tauri GUI checks and builds. + +inputs: + extra-packages: + description: Additional apt packages to install. + required: false + default: "" + +runs: + using: composite + steps: + - name: Install packages + shell: bash + run: | + packages=( + libwebkit2gtk-4.1-dev + libgtk-3-dev + libayatana-appindicator3-dev + librsvg2-dev + ) + + if [[ -n "${{ inputs.extra-packages }}" ]]; then + read -r -a extra_packages <<< "${{ inputs.extra-packages }}" + packages+=("${extra_packages[@]}") + fi + + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing "${packages[@]}" diff --git a/.github/actions/setup-node-pnpm/action.yml b/.github/actions/setup-node-pnpm/action.yml new file mode 100644 index 0000000..7ae3ea1 --- /dev/null +++ b/.github/actions/setup-node-pnpm/action.yml @@ -0,0 +1,25 @@ +name: Setup Node.js And pnpm +description: Setup the Node.js and pnpm versions used by the GUI. + +inputs: + node-version-file: + description: Path to the Node.js version file. + required: false + default: ecos/gui/.nvmrc + pnpm-version: + description: pnpm version to install. + required: false + default: latest + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ${{ inputs.node-version-file }} + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: ${{ inputs.pnpm-version }} diff --git a/.github/actions/setup-rust-cache/action.yml b/.github/actions/setup-rust-cache/action.yml new file mode 100644 index 0000000..2f93975 --- /dev/null +++ b/.github/actions/setup-rust-cache/action.yml @@ -0,0 +1,19 @@ +name: Setup Rust Cache +description: Setup stable Rust and cache Cargo artifacts. + +inputs: + workspaces: + description: Rust workspace paths for Swatinem/rust-cache. + required: false + default: ecos/gui/src-tauri + +runs: + using: composite + steps: + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ inputs.workspaces }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f18f213..55b65e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,32 +50,14 @@ jobs: uses: actions/checkout@v4 - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y --fix-missing \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev + uses: ./.github/actions/install-tauri-linux-deps - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: ecos/gui/src-tauri - - - name: Stub generated artifacts - run: | - mkdir -p binaries - touch binaries/api-server-x86_64-unknown-linux-gnu - chmod +x binaries/api-server-x86_64-unknown-linux-gnu - mkdir -p resources/oss-cad-suite - touch resources/oss-cad-suite/README - mkdir -p ../dist + uses: ./.github/actions/setup-rust-cache - name: Lint Rust + env: + TAURI_CONFIG: '{"build":{"frontendDist":null},"bundle":{"resources":null,"externalBin":null}}' run: cargo clippy -- -D warnings vue-tsc: @@ -89,14 +71,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: ecos/gui/.nvmrc - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: latest + uses: ./.github/actions/setup-node-pnpm - name: Install dependencies run: pnpm install --frozen-lockfile @@ -116,38 +91,24 @@ jobs: fetch-depth: 0 - name: Install system dependencies - run: | - sudo apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing \ - git curl ca-certificates build-essential pkg-config \ - python3 python3-venv python3-pip python3-dev \ - libgtk-3-dev libgtk-3-bin libwebkit2gtk-4.1-dev \ - libayatana-appindicator3-dev \ - libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev \ - libglib2.0-dev libglib2.0-bin librsvg2-dev \ - cmake ninja-build tcl-dev \ - libgflags-dev libgoogle-glog-dev libboost-all-dev libgtest-dev \ - flex libeigen3-dev libunwind-dev libmetis-dev libgmp-dev bison \ - libhwloc-dev libcurl4-openssl-dev libtbb-dev \ + uses: ./.github/actions/install-tauri-linux-deps + with: + extra-packages: >- + git curl ca-certificates build-essential pkg-config + python3 python3-venv python3-pip python3-dev + libgtk-3-bin libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev + libglib2.0-dev libglib2.0-bin + cmake ninja-build tcl-dev + libgflags-dev libgoogle-glog-dev libboost-all-dev libgtest-dev + flex libeigen3-dev libunwind-dev libmetis-dev libgmp-dev bison + libhwloc-dev libcurl4-openssl-dev libtbb-dev patchelf jq wget - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: ecos/gui/.nvmrc - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: latest + uses: ./.github/actions/setup-node-pnpm - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - - - name: Cache Rust build artifacts - uses: Swatinem/rust-cache@v2 - with: - workspaces: ecos/gui/src-tauri + uses: ./.github/actions/setup-rust-cache - name: Setup Python uses: actions/setup-python@v5 @@ -173,6 +134,9 @@ jobs: run: uv sync --frozen --all-groups --all-extras --python 3.11 - name: Build release bundle + env: + ENABLE_OSS_CAD_SUITE: "true" + TAURI_BUNDLES: appimage run: | PATH="$PWD/ecos/server/.venv/bin:$PATH" bazel build //:ecos_studio_bundle diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f73b68..c66aabe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,6 +102,9 @@ jobs: run: uv sync --frozen --all-groups --all-extras --python 3.11 - name: Build release bundle + env: + ENABLE_OSS_CAD_SUITE: "true" + TAURI_BUNDLES: appimage run: | PATH="$PWD/ecos/server/.venv/bin:$PATH" bazel build //:ecos_studio_bundle From ea2c502ea6ea225deb8c2fb760b76beaf299a9c2 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:32:52 +0800 Subject: [PATCH 06/16] fix(ci): remove ecos-version.json check Signed-off-by: Emin --- .github/actions/check-version/action.yml | 4 ---- .github/workflows/auto-tag.yml | 1 - 2 files changed, 5 deletions(-) diff --git a/.github/actions/check-version/action.yml b/.github/actions/check-version/action.yml index ef78ea8..c317746 100644 --- a/.github/actions/check-version/action.yml +++ b/.github/actions/check-version/action.yml @@ -108,10 +108,6 @@ runs: gui_tauri_conf = json.loads(read("ecos/gui/src-tauri/tauri.conf.json"))["version"] versions.append(("ecos/gui/src-tauri/tauri.conf.json", gui_tauri_conf)) - gui_version_json = json.loads(read("ecos/gui/src-tauri/ecos-version.json")) - versions.append(("ecos/gui/src-tauri/ecos-version.json (gui)", gui_version_json["gui"])) - versions.append(("ecos/gui/src-tauri/ecos-version.json (server)", gui_version_json["server"])) - print("Detected versions:") for name, value in versions: print(f" {name}: {value}") diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 5bcd79d..81cbd5c 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -14,7 +14,6 @@ on: - "ecos/gui/src-tauri/Cargo.toml" - "ecos/gui/src-tauri/Cargo.lock" - "ecos/gui/src-tauri/tauri.conf.json" - - "ecos/gui/src-tauri/ecos-version.json" permissions: contents: write From d96286121766c6a09c9f87682028931949b05457 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:57:05 +0800 Subject: [PATCH 07/16] feat(ci): add warning message for release notes Signed-off-by: Emin --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c66aabe..54ebba1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,6 +179,10 @@ jobs: run: | PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") { + echo "> [!WARNING]" + echo "> **ECOS Studio** is still under rapid development. You can download the latest version from the [release page](https://github.com/openecos-projects/ecos-studio/releases) to try it out (The current release builds are produced on **Ubuntu 22.04**, so using an older Linux system may lead to compatibility issues. Additionally, ECOS Studio currently only supports the **x86_64** Linux platform.)." + echo ">" + echo "" if [[ -n "$PREV_TAG" ]]; then echo "## Changes" echo "" From 568551be3e24ce63c641baa3a5c8de85c2b03ba2 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 16:57:18 +0800 Subject: [PATCH 08/16] feat(ci): add appimage upload for ci check Signed-off-by: Emin --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55b65e8..399b6f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,6 +143,9 @@ jobs: - name: Verify AppImage artifact run: | BUNDLE_TAR="bazel-bin/ecos/ecos_studio_bundle/ecos_studio_bundle.tar" + rm -rf dist/ci-appimage + mkdir -p dist/ci-appimage + tar -tf "$BUNDLE_TAR" | grep -E '(^|/)[^/]+\.AppImage$' > /tmp/ecos-appimage-files.txt || true if [ ! -s /tmp/ecos-appimage-files.txt ]; then @@ -154,3 +157,26 @@ jobs: echo "Found AppImage artifact:" cat /tmp/ecos-appimage-files.txt + + tar -xf "$BUNDLE_TAR" -C dist/ci-appimage -T /tmp/ecos-appimage-files.txt + ( + cd dist/ci-appimage + find . -type f -name '*.AppImage' -print0 \ + | sort -z \ + | xargs -0 sha256sum > SHA256SUMS + ) + + echo "Prepared CI AppImage artifact:" + find dist/ci-appimage -type f \( -name '*.AppImage' -o -name 'SHA256SUMS' \) | sort + echo "" + cat dist/ci-appimage/SHA256SUMS + + - name: Upload CI AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: ecos-studio-appimage-${{ github.sha }} + if-no-files-found: error + retention-days: 14 + path: | + dist/ci-appimage/**/*.AppImage + dist/ci-appimage/SHA256SUMS From 372ad3f138206d8036064bca604b2c062c9aa46a Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:01:26 +0800 Subject: [PATCH 09/16] refactor(ci): split check-version scripts into single file Signed-off-by: Emin --- .github/actions/check-version/action.yml | 116 +------------------- .github/scripts/check-version.py | 129 +++++++++++++++++++++++ 2 files changed, 130 insertions(+), 115 deletions(-) create mode 100644 .github/scripts/check-version.py diff --git a/.github/actions/check-version/action.yml b/.github/actions/check-version/action.yml index c317746..f159561 100644 --- a/.github/actions/check-version/action.yml +++ b/.github/actions/check-version/action.yml @@ -21,120 +21,6 @@ runs: - name: Verify version consistency id: verify shell: bash - run: | - python3 <<'PY' - import json - import os - import re - import sys - import tomllib - from pathlib import Path - - expected_tag = os.environ.get("EXPECTED_TAG", "").strip() - - def read(path: str) -> str: - return Path(path).read_text(encoding="utf-8") - - def parse_regex(path: str, pattern: str, *, flags: int = 0, label: str | None = None) -> str: - text = read(path) - match = re.search(pattern, text, flags) - if not match: - raise SystemExit(f"ERROR: failed to parse {label or path}") - return match.group(1) - - versions: list[tuple[str, str]] = [] - - module_version = parse_regex( - "MODULE.bazel", - r'(?m)^\s*version\s*=\s*"([^"]+)"', - label="MODULE.bazel version", - ) - versions.append(("MODULE.bazel", module_version)) - - server_pyproject = tomllib.loads(read("ecos/server/pyproject.toml"))["project"]["version"] - versions.append(("ecos/server/pyproject.toml", server_pyproject)) - - server_default_nix = parse_regex( - "ecos/server/default.nix", - r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', - label="ecos/server/default.nix version", - ) - versions.append(("ecos/server/default.nix", server_default_nix)) - - server_main_fastapi = parse_regex( - "ecos/server/ecos_server/main.py", - r'FastAPI\(.*?version\s*=\s*"([^"]+)"', - flags=re.S, - label="ecos/server/ecos_server/main.py FastAPI version", - ) - versions.append(("ecos/server/ecos_server/main.py (FastAPI)", server_main_fastapi)) - - server_main_root = parse_regex( - "ecos/server/ecos_server/main.py", - r'"version"\s*:\s*"([^"]+)"', - label="ecos/server/ecos_server/main.py root endpoint version", - ) - versions.append(("ecos/server/ecos_server/main.py (root endpoint)", server_main_root)) - - server_uv_lock = parse_regex( - "ecos/server/uv.lock", - r'\[\[package\]\]\s+name\s*=\s*"ecos-server"\s+version\s*=\s*"([^"]+)"', - flags=re.S, - label="ecos/server/uv.lock root package version", - ) - versions.append(("ecos/server/uv.lock", server_uv_lock)) - - gui_package = json.loads(read("ecos/gui/package.json"))["version"] - versions.append(("ecos/gui/package.json", gui_package)) - - gui_default_nix = parse_regex( - "ecos/gui/default.nix", - r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', - label="ecos/gui/default.nix version", - ) - versions.append(("ecos/gui/default.nix", gui_default_nix)) - - gui_cargo_toml = tomllib.loads(read("ecos/gui/src-tauri/Cargo.toml"))["package"]["version"] - versions.append(("ecos/gui/src-tauri/Cargo.toml", gui_cargo_toml)) - - gui_cargo_lock = parse_regex( - "ecos/gui/src-tauri/Cargo.lock", - r'\[\[package\]\]\s+name\s*=\s*"ecos-studio"\s+version\s*=\s*"([^"]+)"', - flags=re.S, - label="ecos/gui/src-tauri/Cargo.lock root package version", - ) - versions.append(("ecos/gui/src-tauri/Cargo.lock", gui_cargo_lock)) - - gui_tauri_conf = json.loads(read("ecos/gui/src-tauri/tauri.conf.json"))["version"] - versions.append(("ecos/gui/src-tauri/tauri.conf.json", gui_tauri_conf)) - - print("Detected versions:") - for name, value in versions: - print(f" {name}: {value}") - - mismatches = [(name, value) for name, value in versions if value != module_version] - if mismatches: - print("") - print(f"ERROR: version mismatch detected. Expected all files to match MODULE.bazel ({module_version}).", file=sys.stderr) - for name, value in mismatches: - print(f" {name}: {value}", file=sys.stderr) - sys.exit(1) - - tag = f"v{module_version}" - if expected_tag and expected_tag != tag: - print( - f"ERROR: tag mismatch. expected {tag} from version files, got {expected_tag}.", - file=sys.stderr, - ) - sys.exit(1) - - github_output = os.environ["GITHUB_OUTPUT"] - with open(github_output, "a", encoding="utf-8") as fh: - fh.write(f"version={module_version}\n") - fh.write(f"tag={tag}\n") - - print("") - print(f"Version check passed: {module_version}") - PY + run: python3 .github/scripts/check-version.py env: EXPECTED_TAG: ${{ inputs.expected_tag }} diff --git a/.github/scripts/check-version.py b/.github/scripts/check-version.py new file mode 100644 index 0000000..c991762 --- /dev/null +++ b/.github/scripts/check-version.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +import json +import os +import re +import sys +import tomllib +from pathlib import Path + + +expected_tag = os.environ.get("EXPECTED_TAG", "").strip() + + +def read(path: str) -> str: + return Path(path).read_text(encoding="utf-8") + + +def parse_regex( + path: str, + pattern: str, + *, + flags: int = 0, + label: str | None = None, +) -> str: + text = read(path) + match = re.search(pattern, text, flags) + if not match: + raise SystemExit(f"ERROR: failed to parse {label or path}") + return match.group(1) + + +versions: list[tuple[str, str]] = [] + +module_version = parse_regex( + "MODULE.bazel", + r'(?m)^\s*version\s*=\s*"([^"]+)"', + label="MODULE.bazel version", +) +versions.append(("MODULE.bazel", module_version)) + +server_pyproject = tomllib.loads(read("ecos/server/pyproject.toml"))["project"]["version"] +versions.append(("ecos/server/pyproject.toml", server_pyproject)) + +server_default_nix = parse_regex( + "ecos/server/default.nix", + r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', + label="ecos/server/default.nix version", +) +versions.append(("ecos/server/default.nix", server_default_nix)) + +server_main_fastapi = parse_regex( + "ecos/server/ecos_server/main.py", + r'FastAPI\(.*?version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/server/ecos_server/main.py FastAPI version", +) +versions.append(("ecos/server/ecos_server/main.py (FastAPI)", server_main_fastapi)) + +server_main_root = parse_regex( + "ecos/server/ecos_server/main.py", + r'"version"\s*:\s*"([^"]+)"', + label="ecos/server/ecos_server/main.py root endpoint version", +) +versions.append(("ecos/server/ecos_server/main.py (root endpoint)", server_main_root)) + +server_uv_lock = parse_regex( + "ecos/server/uv.lock", + r'\[\[package\]\]\s+name\s*=\s*"ecos-server"\s+version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/server/uv.lock root package version", +) +versions.append(("ecos/server/uv.lock", server_uv_lock)) + +gui_package = json.loads(read("ecos/gui/package.json"))["version"] +versions.append(("ecos/gui/package.json", gui_package)) + +gui_default_nix = parse_regex( + "ecos/gui/default.nix", + r'(?m)^\s*version\s*=\s*"([^"]+)"\s*;', + label="ecos/gui/default.nix version", +) +versions.append(("ecos/gui/default.nix", gui_default_nix)) + +gui_cargo_toml = tomllib.loads(read("ecos/gui/src-tauri/Cargo.toml"))["package"][ + "version" +] +versions.append(("ecos/gui/src-tauri/Cargo.toml", gui_cargo_toml)) + +gui_cargo_lock = parse_regex( + "ecos/gui/src-tauri/Cargo.lock", + r'\[\[package\]\]\s+name\s*=\s*"ecos-studio"\s+version\s*=\s*"([^"]+)"', + flags=re.S, + label="ecos/gui/src-tauri/Cargo.lock root package version", +) +versions.append(("ecos/gui/src-tauri/Cargo.lock", gui_cargo_lock)) + +gui_tauri_conf = json.loads(read("ecos/gui/src-tauri/tauri.conf.json"))["version"] +versions.append(("ecos/gui/src-tauri/tauri.conf.json", gui_tauri_conf)) + +print("Detected versions:") +for name, value in versions: + print(f" {name}: {value}") + +mismatches = [(name, value) for name, value in versions if value != module_version] +if mismatches: + print("") + print( + "ERROR: version mismatch detected. " + f"Expected all files to match MODULE.bazel ({module_version}).", + file=sys.stderr, + ) + for name, value in mismatches: + print(f" {name}: {value}", file=sys.stderr) + sys.exit(1) + +tag = f"v{module_version}" +if expected_tag and expected_tag != tag: + print( + f"ERROR: tag mismatch. expected {tag} from version files, got {expected_tag}.", + file=sys.stderr, + ) + sys.exit(1) + +github_output = os.environ["GITHUB_OUTPUT"] +with open(github_output, "a", encoding="utf-8") as fh: + fh.write(f"version={module_version}\n") + fh.write(f"tag={tag}\n") + +print("") +print(f"Version check passed: {module_version}") From 246e9ba8eebe141dfb48362d5464fef08db5c007 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:05:05 +0800 Subject: [PATCH 10/16] refactor(ci): pin ubuntu version to ubuntu-22.04 Signed-off-by: Emin --- .github/workflows/auto-tag.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- .github/workflows/release.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 81cbd5c..373f7b1 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -21,7 +21,7 @@ permissions: jobs: auto-tag: name: Create version tag - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 399b6f8..1079ee9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ permissions: jobs: check-version: name: Check Version Consistency - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -22,7 +22,7 @@ jobs: ruff: name: Ruff - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: working-directory: ecos/server @@ -41,7 +41,7 @@ jobs: clippy: name: Clippy - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: working-directory: ecos/gui/src-tauri @@ -62,7 +62,7 @@ jobs: vue-tsc: name: Vue Typecheck - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 defaults: run: working-directory: ecos/gui @@ -82,7 +82,7 @@ jobs: build-appimage: name: Build AppImage needs: [check-version, ruff, clippy, vue-tsc] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54ebba1..759ddd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ permissions: jobs: check-version: name: Check Version Consistency - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: version: ${{ steps.version.outputs.version }} tag: ${{ steps.version.outputs.tag }} @@ -35,7 +35,7 @@ jobs: build: name: Build AppImage needs: check-version - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -149,7 +149,7 @@ jobs: release: name: Create Release needs: [check-version, build] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 From 6caab0d03a59d04a3444466485e42428f9fcd0e5 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:10:11 +0800 Subject: [PATCH 11/16] fix(ci): disable cache for target Signed-off-by: Emin --- .github/actions/setup-rust-cache/action.yml | 5 +++++ .github/workflows/ci.yml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/.github/actions/setup-rust-cache/action.yml b/.github/actions/setup-rust-cache/action.yml index 2f93975..35d0a34 100644 --- a/.github/actions/setup-rust-cache/action.yml +++ b/.github/actions/setup-rust-cache/action.yml @@ -6,6 +6,10 @@ inputs: description: Rust workspace paths for Swatinem/rust-cache. required: false default: ecos/gui/src-tauri + cache-targets: + description: Whether to cache target directories. + required: false + default: "true" runs: using: composite @@ -17,3 +21,4 @@ runs: uses: Swatinem/rust-cache@v2 with: workspaces: ${{ inputs.workspaces }} + cache-targets: ${{ inputs.cache-targets }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1079ee9..0db685b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,8 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust-cache + with: + cache-targets: "false" - name: Lint Rust env: From cd94fdb93891986e6753b4ded03a67047a83a88d Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:10:31 +0800 Subject: [PATCH 12/16] fix(ci): add python setup for check-version Signed-off-by: Emin --- .github/actions/check-version/action.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/actions/check-version/action.yml b/.github/actions/check-version/action.yml index f159561..fb58d63 100644 --- a/.github/actions/check-version/action.yml +++ b/.github/actions/check-version/action.yml @@ -18,6 +18,11 @@ outputs: runs: using: composite steps: + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Verify version consistency id: verify shell: bash From e60ad0e610d0422d7c7b44e19719dfc03a575308 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:11:01 +0800 Subject: [PATCH 13/16] feat(ci): add concurrency setup Signed-off-by: Emin --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0db685b..5ec335e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check-version: name: Check Version Consistency From 76ccaf9a398c6e271737acb4c796d27cb2649a96 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 17:34:00 +0800 Subject: [PATCH 14/16] refactor(ci): switch sha to run_number Signed-off-by: Emin --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ec335e..6b5587a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,7 +180,7 @@ jobs: - name: Upload CI AppImage artifact uses: actions/upload-artifact@v4 with: - name: ecos-studio-appimage-${{ github.sha }} + name: ecos-studio-appimage-${{ github.run_number }} if-no-files-found: error retention-days: 14 path: | From 73eca2bf19d42142c6828162d427369da3f9c270 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 18:33:59 +0800 Subject: [PATCH 15/16] chore: bump ecc-dreamplace to v0.1.0-alpha.2 Signed-off-by: Emin --- ecos/server/pyproject.toml | 8 ++++++-- ecos/server/uv.lock | 13 ++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ecos/server/pyproject.toml b/ecos/server/pyproject.toml index 3170569..ed9cc51 100644 --- a/ecos/server/pyproject.toml +++ b/ecos/server/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0-alpha.3" requires-python = ">=3.11" dependencies = [ "ecc==0.1.0a1", - "ecc-dreamplace==0.1.0a1", + "ecc-dreamplace==0.1.0a2", "ecc-tools==0.1.0a1", "fastapi>=0.109", "torch>=1.6.0", @@ -41,10 +41,14 @@ explicit = true environments = [ "sys_platform == 'linux' and platform_machine == 'x86_64'", ] +override-dependencies = [ +# Remove this after ecc bump its ecc-dreamplace dependency to 0.1.0a2 or later, which is the version that this server depends on. + "ecc-dreamplace==0.1.0a2", +] [tool.uv.sources] ecc = { url = "https://github.com/openecos-projects/ecc/releases/download/v0.1.0-alpha.1/ecc-0.1.0a1-py3-none-any.whl" } -ecc-dreamplace = { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.1/ecc_dreamplace-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl" } +ecc-dreamplace = { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.2/ecc_dreamplace-0.1.0a2-py3-none-manylinux_2_34_x86_64.whl" } ecc-tools = { url = "https://github.com/openecos-projects/ecc-tools/releases/download/v0.1.0-alpha.1/ecc_tools-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl" } torch = { index = "pytorch-cpu" } diff --git a/ecos/server/uv.lock b/ecos/server/uv.lock index 1c7bde0..bd294f8 100644 --- a/ecos/server/uv.lock +++ b/ecos/server/uv.lock @@ -9,6 +9,9 @@ supported-markers = [ "platform_machine == 'x86_64' and sys_platform == 'linux'", ] +[manifest] +overrides = [{ name = "ecc-dreamplace", url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.2/ecc_dreamplace-0.1.0a2-py3-none-manylinux_2_34_x86_64.whl" }] + [[package]] name = "altgraph" version = "0.17.5" @@ -264,8 +267,8 @@ requires-dist = [ [[package]] name = "ecc-dreamplace" -version = "0.1.0a1" -source = { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.1/ecc_dreamplace-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl" } +version = "0.1.0a2" +source = { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.2/ecc_dreamplace-0.1.0a2-py3-none-manylinux_2_34_x86_64.whl" } dependencies = [ { name = "cairocffi", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "configspace", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -286,7 +289,7 @@ dependencies = [ { name = "xgboost", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.1/ecc_dreamplace-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl", hash = "sha256:212139c43f825498968eda10309959b99cd93aec0744d182a17bb5d34b53145e" }, + { url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.2/ecc_dreamplace-0.1.0a2-py3-none-manylinux_2_34_x86_64.whl", hash = "sha256:fad0e489bfba62f79c193e2e0ec5051a492768e2a3d6099aa5e604c08abb191f" }, ] [package.metadata] @@ -320,7 +323,7 @@ wheels = [ [[package]] name = "ecos-server" -version = "0.1.0-alpha.3" +version = "0.1.0a3" source = { editable = "." } dependencies = [ { name = "ecc", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -348,7 +351,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "ecc", url = "https://github.com/openecos-projects/ecc/releases/download/v0.1.0-alpha.1/ecc-0.1.0a1-py3-none-any.whl" }, - { name = "ecc-dreamplace", url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.1/ecc_dreamplace-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl" }, + { name = "ecc-dreamplace", url = "https://github.com/openecos-projects/ecc-dreamplace/releases/download/v0.1.0-alpha.2/ecc_dreamplace-0.1.0a2-py3-none-manylinux_2_34_x86_64.whl" }, { name = "ecc-tools", url = "https://github.com/openecos-projects/ecc-tools/releases/download/v0.1.0-alpha.1/ecc_tools-0.1.0a1-py3-none-manylinux_2_34_x86_64.whl" }, { name = "fastapi", specifier = ">=0.109" }, { name = "torch", specifier = ">=1.6.0", index = "https://download.pytorch.org/whl/cpu" }, From 7770bb6a5e20cc4b9518030092f8fae55a9fddd2 Mon Sep 17 00:00:00 2001 From: Emin Date: Thu, 23 Apr 2026 19:34:33 +0800 Subject: [PATCH 16/16] fix(ci): normalize semver prerelease tags in version check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uv.lock canonicalizes versions to PEP 440 (e.g. 0.1.0-alpha.3 → 0.1.0a3), which caused the version check script to fail with a false mismatch. Add normalize_version() to convert semver prerelease tags before comparison. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/scripts/check-version.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/scripts/check-version.py b/.github/scripts/check-version.py index c991762..00754ac 100644 --- a/.github/scripts/check-version.py +++ b/.github/scripts/check-version.py @@ -10,6 +10,12 @@ expected_tag = os.environ.get("EXPECTED_TAG", "").strip() +def normalize_version(v: str) -> str: + """Normalize semver prerelease tags (e.g. 0.1.0-alpha.3) to PEP 440 (e.g. 0.1.0a3) + so they can be compared with uv.lock / packaging canonical forms.""" + return re.sub(r"-(alpha|beta|rc)\.?(\d+)", lambda m: m.group(1)[0] + m.group(2), v) + + def read(path: str) -> str: return Path(path).read_text(encoding="utf-8") @@ -100,7 +106,12 @@ def parse_regex( for name, value in versions: print(f" {name}: {value}") -mismatches = [(name, value) for name, value in versions if value != module_version] +normalized_module = normalize_version(module_version) +mismatches = [ + (name, value) + for name, value in versions + if normalize_version(value) != normalized_module +] if mismatches: print("") print(