Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 31 additions & 0 deletions .github/actions/check-version/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Verify version consistency
id: verify
shell: bash
run: python3 .github/scripts/check-version.py
env:
EXPECTED_TAG: ${{ inputs.expected_tag }}
29 changes: 29 additions & 0 deletions .github/actions/install-tauri-linux-deps/action.yml
Original file line number Diff line number Diff line change
@@ -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[@]}"
25 changes: 25 additions & 0 deletions .github/actions/setup-node-pnpm/action.yml
Original file line number Diff line number Diff line change
@@ -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 }}
24 changes: 24 additions & 0 deletions .github/actions/setup-rust-cache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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
cache-targets:
description: Whether to cache target directories.
required: false
default: "true"

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 }}
cache-targets: ${{ inputs.cache-targets }}
140 changes: 140 additions & 0 deletions .github/scripts/check-version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/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 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")


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}")

normalized_module = normalize_version(module_version)
mismatches = [
(name, value)
for name, value in versions
if normalize_version(value) != normalized_module
]
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}")
53 changes: 53 additions & 0 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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"

permissions:
contents: write

jobs:
auto-tag:
name: Create version tag
runs-on: ubuntu-22.04
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 }}"
Loading
Loading