diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5cffebc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,132 @@ +name: Build & Publish Release + +on: + workflow_dispatch: + inputs: + version: + description: "Release version (must be 0.x.y, e.g. 0.1.0)" + required: true + default: "0.1.0" + type: string + +permissions: + contents: write + +jobs: + validate: + name: Validate Actor, Branch & 0.x.y Version + runs-on: ubuntu-latest + if: github.actor == 'lucasoskorep' && github.ref == 'refs/heads/master' + outputs: + version: ${{ steps.check.outputs.version }} + tag: ${{ steps.check.outputs.tag }} + steps: + - name: Validate release trigger + id: check + run: | + if [ "${{ github.actor }}" != "lucasoskorep" ]; then + echo "Error: Only lucasoskorep is authorized to run this workflow." >&2 + exit 1 + fi + if [ "${{ github.ref }}" != "refs/heads/master" ]; then + echo "Error: Releases may only be built from the master branch (got ${{ github.ref }})." >&2 + exit 1 + fi + RAW_VER="${{ inputs.version }}" + VER="${RAW_VER#v}" + if ! [[ "$VER" =~ ^0\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be 0.x.y (got '$RAW_VER')." >&2 + exit 1 + fi + echo "version=${VER}" >> "$GITHUB_OUTPUT" + echo "tag=v${VER}" >> "$GITHUB_OUTPUT" + + build: + name: Build (${{ matrix.label }}) + needs: validate + if: github.actor == 'lucasoskorep' && github.ref == 'refs/heads/master' + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + include: + - label: linux-x86_64 + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - label: macos-arm64 + os: macos-14 + target: aarch64-apple-darwin + - label: macos-x86_64 + os: macos-14 + target: x86_64-apple-darwin + - label: windows-x86_64 + os: windows-latest + target: x86_64-pc-windows-msvc + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Linux GUI system libraries + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + pkg-config \ + libxkbcommon-dev \ + libwayland-dev \ + libgtk-3-dev \ + libxcb-render0-dev \ + libxcb-shape0-dev \ + libxcb-xfixes0-dev \ + libgl1-mesa-dev + + - name: Setup mise & toolchain + uses: jdx/mise-action@v2 + + - name: Run workspace tests via mise + if: matrix.target != 'x86_64-apple-darwin' + shell: bash + run: mise run test + + - name: Build & package release archive via mise + shell: bash + run: mise run package-release "${{ matrix.target }}" "${{ needs.validate.outputs.version }}" + + - name: Upload release artifact + uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.label }} + path: dist/* + if-no-files-found: error + + publish: + name: Publish GitHub Release + needs: [validate, build] + if: github.actor == 'lucasoskorep' && github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all platform artifacts + uses: actions/download-artifact@v4 + with: + path: release-assets + merge-multiple: true + + - name: Generate SHA256SUMS.txt + run: | + cd release-assets + sha256sum * > SHA256SUMS.txt + cat SHA256SUMS.txt + + - name: Create GitHub Tag & Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.validate.outputs.tag }} + target_commitish: ${{ github.sha }} + name: ${{ needs.validate.outputs.tag }} + generate_release_notes: true + files: | + release-assets/* diff --git a/.gitignore b/.gitignore index 33abbf1..5835eca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target/ +/dist/ .DS_Store *.log *.swp diff --git a/Cargo.lock b/Cargo.lock index 041fc9c..c1baad2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1831,7 +1831,7 @@ dependencies = [ [[package]] name = "insta360-link-cli" -version = "0.1.0" +version = "0.0.0" dependencies = [ "clap", "insta360-link-protocol", @@ -1843,7 +1843,7 @@ dependencies = [ [[package]] name = "insta360-link-gui" -version = "0.1.0" +version = "0.0.0" dependencies = [ "eframe", "egui", @@ -1857,7 +1857,7 @@ dependencies = [ [[package]] name = "insta360-link-protocol" -version = "0.1.0" +version = "0.0.0" dependencies = [ "bitflags 2.13.2", "libc", @@ -1870,7 +1870,7 @@ dependencies = [ [[package]] name = "insta360-link-server" -version = "0.1.0" +version = "0.0.0" dependencies = [ "axum", "clap", diff --git a/Cargo.toml b/Cargo.toml index dde097d..438c5b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,11 @@ members = [ ] [workspace.package] -version = "0.1.0" +version = "0.0.0" edition = "2021" license = "MIT OR Apache-2.0" -authors = ["Insta360 Link Open Controller Contributors"] -repository = "https://github.com/example/insta360-link-controller" +authors = ["Lucas Oskorep "] +repository = "https://github.com/lucasoskorep/insta360-link-controller" [workspace.dependencies] thiserror = "2.0" diff --git a/mise.toml b/mise.toml index 2952d5d..7450a32 100644 --- a/mise.toml +++ b/mise.toml @@ -32,3 +32,87 @@ run = "INSTA360_GUI_SELF_TEST=1 cargo run --release -p insta360-link-gui" [tasks.udev-install] description = "Install Linux udev rules for non-root USB access to Insta360 Link webcams" run = "sudo cp udev/99-insta360-link.rules /etc/udev/rules.d/99-insta360-link.rules && sudo udevadm control --reload-rules && sudo udevadm trigger" + +[tasks.package-release] +description = "Build and package release binaries for a target (usage: mise run package-release [target] [version])" +run = ''' +#!/usr/bin/env bash +set -euo pipefail +TARGET="${1:-$(rustc -vV | sed -n 's|host: ||p')}" +VERSION="${2:-0.0.0}" +VERSION="${VERSION#v}" + +case "$VERSION" in + 0.*) ;; + *) + echo "Error: version must start with 0. (got: $VERSION)" >&2 + exit 1 + ;; +esac + +python3 -c ' +import pathlib, re, sys +ver = sys.argv[1] +p = pathlib.Path("Cargo.toml") +txt = p.read_text() +txt = re.sub(r"(?m)^version\s*=\s*\"[^\"]+\"", f"version = \"{ver}\"", txt, count=1) +p.write_text(txt) +' "$VERSION" + +rustup target add "$TARGET" 2>/dev/null || true +cargo build --release --workspace --target "$TARGET" + +case "$TARGET" in + x86_64-unknown-linux-gnu) ASSET_SUFFIX="linux-x86_64"; EXT="tar.gz" ;; + aarch64-unknown-linux-gnu) ASSET_SUFFIX="linux-arm64"; EXT="tar.gz" ;; + aarch64-apple-darwin) ASSET_SUFFIX="macos-arm64"; EXT="tar.gz" ;; + x86_64-apple-darwin) ASSET_SUFFIX="macos-x86_64"; EXT="tar.gz" ;; + x86_64-pc-windows-msvc|x86_64-pc-windows-gnu) ASSET_SUFFIX="windows-x86_64"; EXT="zip" ;; + *) ASSET_SUFFIX="$TARGET"; EXT="tar.gz" ;; +esac + +STAGE_NAME="insta360-link-controller-v${VERSION}-${ASSET_SUFFIX}" +STAGE_DIR="dist/${STAGE_NAME}" +rm -rf "$STAGE_DIR" +mkdir -p "$STAGE_DIR" + +if [ "$EXT" = "zip" ]; then + cp "target/${TARGET}/release/insta360-link-gui.exe" "$STAGE_DIR/" + cp "target/${TARGET}/release/insta360-link-ctl.exe" "$STAGE_DIR/" + cp "target/${TARGET}/release/insta360-link-server.exe" "$STAGE_DIR/" + cp README.md "$STAGE_DIR/" + python3 -c ' +import pathlib, sys, zipfile +stage_dir = pathlib.Path(sys.argv[1]) +out_zip = pathlib.Path(sys.argv[2]) +with zipfile.ZipFile(out_zip, "w", zipfile.ZIP_DEFLATED) as zf: + for f in stage_dir.rglob("*"): + if f.is_file(): + zf.write(f, f.relative_to(stage_dir.parent)) +' "$STAGE_DIR" "dist/${STAGE_NAME}.zip" + echo "Packaged dist/${STAGE_NAME}.zip" +else + cp "target/${TARGET}/release/insta360-link-gui" "$STAGE_DIR/" + cp "target/${TARGET}/release/insta360-link-ctl" "$STAGE_DIR/" + cp "target/${TARGET}/release/insta360-link-server" "$STAGE_DIR/" + cp README.md "$STAGE_DIR/" + cp -r udev "$STAGE_DIR/" + tar -C dist -czf "dist/${STAGE_NAME}.tar.gz" "$STAGE_NAME" + echo "Packaged dist/${STAGE_NAME}.tar.gz" +fi +rm -rf "$STAGE_DIR" +''' + +[tasks.trigger-release] +description = "Trigger the GitHub Actions release pipeline from master (usage: mise run trigger-release [0.1.0])" +run = ''' +#!/usr/bin/env bash +set -euo pipefail +VER="${1:-0.1.0}" +VER="${VER#v}" +env -u GITHUB_TOKEN -u GH_TOKEN -u GITHUB_AUTH_TOKEN gh workflow run release.yml \ + --repo lucasoskorep/insta360-link-controller \ + --ref master \ + -f version="$VER" +echo "Triggered release workflow on master for v${VER}" +'''