AI-Studio/.github/workflows/build-and-release.yml
2024-06-16 19:48:40 +02:00

513 lines
21 KiB
YAML

name: Build and Release
on:
push
env:
RETENTION_DAYS: 30
jobs:
build_main:
name: Build app (${{ matrix.dotnet_runtime }})
strategy:
fail-fast: true
matrix:
include:
- platform: 'macos-latest' # for ARM-based macOS (M1 and above)
rust_target: 'aarch64-apple-darwin'
update_platform: 'darwin-aarch64'
dotnet_runtime: 'osx-arm64'
dotnet_name_postfix: '-aarch64-apple-darwin'
tauri_bundle: 'dmg updater'
- platform: 'macos-latest' # for Intel-based macOS
rust_target: 'x86_64-apple-darwin'
update_platform: 'darwin-x86_64'
dotnet_runtime: 'osx-x64'
dotnet_name_postfix: '-x86_64-apple-darwin'
tauri_bundle: 'dmg updater'
- platform: 'ubuntu-22.04' # for x86-based Linux
rust_target: 'x86_64-unknown-linux-gnu'
update_platform: 'linux-x86_64'
dotnet_runtime: 'linux-x64'
dotnet_name_postfix: '-x86_64-unknown-linux-gnu'
tauri_bundle: 'appimage deb updater'
- platform: 'windows-latest' # for x86-based Windows
rust_target: 'x86_64-pc-windows-msvc'
update_platform: 'windows-x86_64'
dotnet_runtime: 'win-x64'
dotnet_name_postfix: '-x86_64-pc-windows-msvc.exe'
tauri_bundle: 'nsis updater'
- platform: 'windows-latest' # for ARM-based Windows
rust_target: 'aarch64-pc-windows-msvc'
update_platform: 'windows-aarch64'
dotnet_runtime: 'win-arm64'
dotnet_name_postfix: '-aarch64-pc-windows-msvc.exe'
tauri_bundle: 'nsis updater'
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: false
- name: Store update platform to .updates directory (Unix)
if: matrix.platform != 'windows-latest'
run: echo ${{ matrix.update_platform }} > .updates/platform
- name: Store update platform to .updates directory (Windows)
if: matrix.platform == 'windows-latest'
run: Write-Output "${{ matrix.update_platform }}" | Out-File -FilePath ".updates/platform" -Encoding utf8 -NoNewline
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8'
dotnet-quality: 'ga'
cache: true
cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json'
- name: Build .NET project
run: |
cd "app/MindWork AI Studio"
dotnet publish --configuration release --runtime ${{ matrix.dotnet_runtime }} --disable-build-servers --force --output ../../publish/dotnet
- name: Move & rename .NET artifact (Unix)
if: matrix.platform != 'windows-latest'
run: |
mkdir -p "app/MindWork AI Studio/bin/dist"
cd publish/dotnet
mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}"
- name: Move & rename .NET artifact (Windows)
if: matrix.platform == 'windows-latest'
run: |
New-Item -ItemType Directory -Path "app\MindWork AI Studio\bin\dist" -Force
cd publish/dotnet
mv mindworkAIStudio.exe "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}"
- name: Create parts for the Rust cache key (Unix)
if: matrix.platform != 'windows-latest'
run: |
cd runtime
echo "RUST_VERSION=$(rustc --version | sed 's/rustc \([0-9.]*\).*/\1/')" >> $GITHUB_ENV
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_ENV
- name: Create parts for the Rust cache key (Windows)
if: matrix.platform == 'windows-latest'
run: |
cd runtime
echo "RUST_VERSION=$(rustc --version | ForEach-Object { $_ -replace 'rustc (\d+\.\d+\.\d+).*', '$1' })" >> $env:GITHUB_ENV
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $env:GITHUB_ENV
- name: Cache Rust
uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~/.cargo/git/db/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.rustup/toolchains
runtime/target
# When the entire key matches, Rust might just create the bundles using the current .NET build:
key: target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
# - 1st key: the Rust runtime dependencies changed. Anyway, we might be able to re-use many previously built packages.
#
# - No match: alright, a new Rust version was released. Sadly, we cannot re-use anything now. Why?
# The updated Rust compiler might mitigate some bugs or vulnerabilities. In order to apply
# these changes to our app, we have to re-compile everything. That's the reason why it makes
# no sense to use more parts for the cache key, like Tauri or Tauri build versions.
restore-keys: |
target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}-dependencies-
- name: Setup Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
- name: Setup dependencies (Ubuntu-specific, x86)
if: matrix.platform == 'ubuntu-22.04' && contains(matrix.rust_target, 'x86_64')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Setup Tauri (Unix)
if: matrix.platform != 'windows-latest'
run: |
if ! cargo tauri --version > /dev/null 2>&1; then
cargo install tauri-cli
else
echo "Tauri is already installed"
fi
- name: Setup Tauri (Windows)
if: matrix.platform == 'windows-latest'
run: |
if (-not (cargo tauri --version 2>$null)) {
cargo install tauri-cli
} else {
Write-Output "Tauri is already installed"
}
- name: Build Tauri project (Unix)
if: matrix.platform != 'windows-latest'
env:
PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }}
PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }}
run: |
cd runtime
export TAURI_PRIVATE_KEY="$PRIVATE_PUBLISH_KEY"
export TAURI_KEY_PASSWORD="$PRIVATE_PUBLISH_KEY_PASSWORD"
cargo tauri build --target ${{ matrix.rust_target }} --bundles ${{ matrix.tauri_bundle }}
- name: Build Tauri project (Windows)
if: matrix.platform == 'windows-latest'
env:
PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }}
PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }}
run: |
cd runtime
$env:TAURI_PRIVATE_KEY="$env:PRIVATE_PUBLISH_KEY"
$env:TAURI_KEY_PASSWORD="$env:PRIVATE_PUBLISH_KEY_PASSWORD"
cargo tauri build --target ${{ matrix.rust_target }} --bundles ${{ matrix.tauri_bundle }}
- name: Upload artifact (macOS)
if: startsWith(matrix.platform, 'macos')
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (macOS ${{ matrix.dotnet_runtime }})
path: |
runtime/target/${{ matrix.rust_target }}/release/bundle/dmg/MindWork AI Studio_*.dmg
runtime/target/${{ matrix.rust_target }}/release/bundle/macos/MindWork AI Studio.app.tar.gz*
.updates/platform
if-no-files-found: error
retention-days: ${{ env.RETENTION_DAYS }}
- name: Upload artifact (Windows - MSI)
if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'msi')
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (Windows - MSI ${{ matrix.dotnet_runtime }})
path: |
runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio_*.msi
runtime/target/${{ matrix.rust_target }}/release/bundle/msi/MindWork AI Studio*msi.zip*
.updates/platform
if-no-files-found: error
retention-days: ${{ env.RETENTION_DAYS }}
- name: Upload artifact (Windows - NSIS)
if: startsWith(matrix.platform, 'windows') && contains(matrix.tauri_bundle, 'nsis')
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (Windows - NSIS ${{ matrix.dotnet_runtime }})
path: |
runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio_*.exe
runtime/target/${{ matrix.rust_target }}/release/bundle/nsis/MindWork AI Studio*nsis.zip*
.updates/platform
if-no-files-found: error
retention-days: ${{ env.RETENTION_DAYS }}
- name: Upload artifact (Linux - Debian Package)
if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'deb')
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (Linux - deb ${{ matrix.dotnet_runtime }})
path: |
runtime/target/${{ matrix.rust_target }}/release/bundle/deb/mind-work-ai-studio_*.deb
.updates/platform
if-no-files-found: error
retention-days: ${{ env.RETENTION_DAYS }}
- name: Upload artifact (Linux - AppImage)
if: startsWith(matrix.platform, 'ubuntu') && contains(matrix.tauri_bundle, 'appimage')
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (Linux - AppImage ${{ matrix.dotnet_runtime }})
path: |
runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/mind-work-ai-studio_*.AppImage
runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/mind-work-ai-studio*AppImage.tar.gz*
.updates/platform
if-no-files-found: error
retention-days: ${{ env.RETENTION_DAYS }}
build_linux_arm64:
name: Build app (linux-arm64)
runs-on: ubuntu-latest
if: false # disable this job for now
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: false
- name: Store update platform to .updates directory
run: echo "linux-aarch64" > .updates/platform
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8'
dotnet-quality: 'ga'
cache: true
cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json'
- name: Build .NET project
run: |
cd "app/MindWork AI Studio"
dotnet publish --configuration release --runtime linux-arm64 --disable-build-servers --force --output ../../publish/dotnet
- name: Move & rename the .NET artifact
run: |
mkdir -p "app/MindWork AI Studio/bin/dist"
cd publish/dotnet
mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer-aarch64-unknown-linux-gnu"
- name: Create parts for the Rust cache key
run: |
cd runtime
echo "RUST_VERSION=$(rustc --version | sed 's/rustc \([0-9.]*\).*/\1/')" >> $GITHUB_ENV
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_ENV
- name: Cache linux arm64 runner image
uses: actions/cache@v4
id: linux_arm_cache
with:
path: $RUNNER_TEMP/linux_arm_qemu_cache.img
# When the entire key matches, Rust might just create the bundles using the current .NET build:
key: target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
# - 1st key: the Rust runtime dependencies changed. Anyway, we might be able to re-use many previously built packages.
#
# - No match: alright, a new Rust version was released. Sadly, we cannot re-use anything now. Why?
# The updated Rust compiler might mitigate some bugs or vulnerabilities. In order to apply
# these changes to our app, we have to re-compile everything. That's the reason why it makes
# no sense to use more parts for the cache key, like Tauri or Tauri build versions.
restore-keys: |
target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-
- name: Build linux arm runner image
uses: pguyot/arm-runner-action@v2.6.5
id: build-linux-arm-runner
if: steps.linux_arm_cache.outputs.cache-hit != 'true'
with:
base_image: dietpi:rpi_armv8_bullseye
cpu: cortex-a53
image_additional_mb: 6000 # ~ 6GB
optimize_image: false
shell: /bin/bash
commands: |
# Rust complains (rightly) that $HOME doesn't match eid home:
export HOME=/root
# Workaround to CI worker being stuck on Updating crates.io index:
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
# Update and upgrade the system:
apt-get update --yes --allow-releaseinfo-change
apt-get upgrade --yes
apt-get autoremove --yes
apt-get install curl wget --yes
# Install Rust:
curl https://sh.rustup.rs -sSf | sh -s -- -y
source "$HOME/.cargo/env"
# Install build tools and tauri-cli requirements:
apt-get install --yes libwebkit2gtk-4.0-dev build-essential libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
# Setup Tauri:
cargo install tauri-cli
- name: Add the built runner image to the cache
if: steps.linux_arm_cache.outputs.cache-hit != 'true'
run: |
mv ${{ steps.build-linux-arm-runner.outputs.image }} $RUNNER_TEMP/linux_arm_qemu_cache.img
- name: Compiling the Rust runtime
uses: pguyot/arm-runner-action@v2.6.5
id: build-linux-arm
with:
base_image: file://$RUNNER_TEMP/linux_arm_qemu_cache.img
cpu: cortex-a53
optimize_image: false
copy_artifact_path: runtime
copy_artifact_dest: result
#
# We do not need to set the PRIVATE_PUBLISH_KEY and PRIVATE_PUBLISH_KEY_PASSWORD here,
# because we cannot produce the AppImage on arm64. Only the AppImage supports the automatic
# update feature. The Debian package does not support this feature.
#
#PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }}
#PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }}
#
shell: /bin/bash
commands: |
export HOME=/root
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
source "$HOME/.cargo/env"
cd runtime
cargo tauri build --target aarch64-unknown-linux-gnu --bundles deb
- name: Debug
run: |
echo "Current directory: $(pwd)"
ls -lhat
echo "Searching for linux_arm_qemu_cache.img"
find $RUNNER_TEMP -name 'linux_arm_qemu_cache.img' -print 2>/dev/null
echo "Searching for mind-work-ai-studio_*.deb"
find . -name 'mind-work-ai-studio_*.deb' -print 2>/dev/null
- name: Update the runner image to cache the Rust runtime build
run: |
mv ${{ steps.build-linux-arm.outputs.image }} $RUNNER_TEMP/linux_arm_qemu_cache.img
- name: Upload artifact (Linux - Debian Package)
uses: actions/upload-artifact@v4
with:
name: MindWork AI Studio (Linux - deb linux-arm64)
path: |
result/target/aarch64-unknown-linux-gnu/release/bundle/deb/mind-work-ai-studio_*.deb
.updates/platform
if-no-files-found: warn
retention-days: ${{ env.RETENTION_DAYS }}
read_metadata:
name: Read metadata
runs-on: ubuntu-latest
outputs:
formatted_version: ${{ steps.format_metadata.outputs.formatted_version }}
formatted_build_time: ${{ steps.format_metadata.outputs.formatted_build_time }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Read and format metadata
id: format_metadata
run: |
# Read the first two lines of the metadata file:
version=$(sed -n '1p' metadata.txt)
build_time=$(sed -n '2p' metadata.txt)
# Format the version:
formatted_version="v${version}"
# Format the build time according to RFC 3339:
formatted_build_time=$(date -d "${build_time}" --utc +'%Y-%m-%dT%H:%M:%SZ')
# Log the formatted metadata:
echo "Formatted version: '${formatted_version}'"
echo "Formatted build time: '${formatted_build_time}'"
# Set the outputs:
echo "formatted_version=${formatted_version}" >> "$GITHUB_OUTPUT"
echo "formatted_build_time=${formatted_build_time}" >> "$GITHUB_OUTPUT"
create_update_file:
name: Create Tauri update file
runs-on: ubuntu-latest
needs: [build_main, read_metadata] # TODO: build_linux_arm64
steps:
- name: Create artifact directory
run: mkdir -p $GITHUB_WORKSPACE/artifacts
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ${{ github.workspace }}/artifacts
merge-multiple: false
- name: Display structure of downloaded files
run: ls -Rlhat $GITHUB_WORKSPACE/artifacts
- name: Create .update directory
run: mkdir -p $GITHUB_WORKSPACE/.updates
- name: Build platform JSON
env:
FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
run: |
# Here, we create the JSON object:
platforms_json=$(jq -n '{}')
# Iterate over all signature files:
while IFS= read -r -d '' sig_file; do
echo "Processing signature file '$sig_file':"
# Extract the platform directory. First, we start at the location of the signature file:
platform_dir=$(dirname "$sig_file")
# Iterate up the directory tree until we find the platform file.
# When we reach the artifacts directory, we stop.
while [[ "$platform_dir" != "$GITHUB_WORKSPACE/artifacts" ]]; do
if [[ -f "$platform_dir/.updates/platform" ]]; then
echo " Found platform file in '$platform_dir/.updates/platform' for signature file '$sig_file'."
break
fi
# Go up one directory level:
platform_dir=$(dirname "$platform_dir")
echo " Going up to '$platform_dir'."
done
# Ensure that we found the platform file:
if [[ -f "$platform_dir/.updates/platform" ]]; then
# Read the platform and signature:
platform=$(cat "$platform_dir/.updates/platform")
signature=$(cat "$sig_file")
# Extract the artifact name and create the URL:
artifact_name=$(basename "$sig_file" .sig)
url="https://github.com/MindWorkAI/AI-Studio/releases/download/$FORMATTED_VERSION/$artifact_name"
# Build the JSON object:
platforms_json=$(echo "$platforms_json" | jq --arg platform "$platform" --arg signature "$signature" --arg url "$url" '.[$platform] = {"signature": $signature, "url": $url}')
else
echo " Error: Could not find the platform file for the signature file '$sig_file'."
fi
done < <(find $GITHUB_WORKSPACE/artifacts -type f -name '*.sig' -print0)
# Write the JSON object to a temporary file:
echo "$platforms_json" > $GITHUB_WORKSPACE/.updates/platforms.json
- name: Create latest.json
env:
FORMATTED_VERSION: ${{ needs.read_metadata.outputs.formatted_version }}
FORMATTED_BUILD_TIME: ${{ needs.read_metadata.outputs.formatted_build_time }}
run: |
# Remove the latest.json file if it exists:
rm -f $GITHUB_WORKSPACE/.updates/latest.json
# Read the platforms JSON, which was created in the previous step:
platforms=$(cat $GITHUB_WORKSPACE/.updates/platforms.json)
# Create the latest.json file:
cat <<EOF > $GITHUB_WORKSPACE/.updates/latest.json
{
"version": "$FORMATTED_VERSION",
"notes": "Update to version $FORMATTED_VERSION.",
"pub_date": "$FORMATTED_BUILD_TIME",
"platforms": $platforms
}
EOF
- name: Display the content of latest.json
run: cat $GITHUB_WORKSPACE/.updates/latest.json