Add multi-platform build and release workflow
This commit is contained in:
parent
e9e72b3fa5
commit
18f56ecd02
20
.github/actions/setup-flutter-sdk/action.yml
vendored
Normal file
20
.github/actions/setup-flutter-sdk/action.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Setup Flutter SDK
|
||||
description: Install the pinned Flutter SDK on Linux, macOS, or Windows runners
|
||||
|
||||
inputs:
|
||||
flutter-version:
|
||||
description: Flutter SDK version to install
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install Flutter SDK on Linux/macOS
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/install_flutter_sdk.sh "${{ inputs.flutter-version }}"
|
||||
|
||||
- name: Install Flutter SDK on Windows
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: pwsh
|
||||
run: pwsh -File ./scripts/ci/install_flutter_sdk.ps1 -FlutterVersion "${{ inputs.flutter-version }}"
|
||||
194
.github/workflows/build-and-release.yml
vendored
Normal file
194
.github/workflows/build-and-release.yml
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
name: Build and Release XWorkmate Packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "lib/**"
|
||||
- "assets/**"
|
||||
- "android/**"
|
||||
- "ios/**"
|
||||
- "macos/**"
|
||||
- "linux/**"
|
||||
- "windows/**"
|
||||
- "rust/**"
|
||||
- "scripts/**"
|
||||
- "pubspec.*"
|
||||
- "Makefile"
|
||||
- ".github/actions/setup-flutter-sdk/action.yml"
|
||||
- ".github/workflows/build-and-release.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: build-and-release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: 3.41.4
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
should_release: ${{ steps.flags.outputs.should_release }}
|
||||
release_tag: ${{ steps.meta.outputs.release_tag }}
|
||||
release_title: ${{ steps.meta.outputs.release_title }}
|
||||
release_notes: ${{ steps.meta.outputs.release_notes }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine release mode
|
||||
id: flags
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${GITHUB_REF:-}" == refs/tags/v* || "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then
|
||||
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Compute release metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/compute_release_metadata.sh
|
||||
|
||||
verify:
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Flutter SDK
|
||||
uses: ./.github/actions/setup-flutter-sdk
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Linux dependencies
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/setup_platform_deps.sh linux
|
||||
|
||||
- name: Run analysis and tests
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/run_code_analysis.sh
|
||||
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux
|
||||
arch: amd64
|
||||
runs_on: ubuntu-22.04
|
||||
- platform: windows
|
||||
arch: amd64
|
||||
runs_on: windows-2022
|
||||
- platform: macos
|
||||
arch: arm64
|
||||
runs_on: macos-14
|
||||
- platform: ios
|
||||
arch: arm64
|
||||
runs_on: macos-14
|
||||
- platform: android
|
||||
arch: arm64
|
||||
runs_on: ubuntu-22.04
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
needs:
|
||||
- prepare
|
||||
- verify
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
SHOULD_RELEASE: ${{ needs.prepare.outputs.should_release }}
|
||||
XWORKMATE_SIGN_IDENTITY: ${{ secrets.XWORKMATE_SIGN_IDENTITY }}
|
||||
WINDOWS_PFX_BASE64: ${{ secrets.WINDOWS_PFX_BASE64 }}
|
||||
WINDOWS_PFX_PASSWORD: ${{ secrets.WINDOWS_PFX_PASSWORD }}
|
||||
WINDOWS_CODESIGN_SUBJECT: ${{ secrets.WINDOWS_CODESIGN_SUBJECT }}
|
||||
APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
|
||||
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
|
||||
APPLE_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISION_PROFILE_BASE64 }}
|
||||
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
APPLE_EXPORT_METHOD: ${{ secrets.APPLE_EXPORT_METHOD }}
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Flutter SDK
|
||||
uses: ./.github/actions/setup-flutter-sdk
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Go
|
||||
if: ${{ matrix.platform == 'macos' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.1"
|
||||
|
||||
- name: Install platform dependencies
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/setup_platform_deps.sh "$PLATFORM"
|
||||
|
||||
- name: Build platform artifacts
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "$SHOULD_RELEASE"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: |
|
||||
dist/linux/*.deb
|
||||
dist/linux/*.rpm
|
||||
dist/macos/*.dmg
|
||||
dist/windows/*.msi
|
||||
dist/windows/*.zip
|
||||
dist/ios/*.ipa
|
||||
dist/ios/*.zip
|
||||
dist/android/*.apk
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: ${{ needs.prepare.outputs.should_release == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
|
||||
- name: Upload assets to GitHub Release
|
||||
shell: bash
|
||||
run: bash ./scripts/ci/github_release_upload.sh release-artifacts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
||||
RELEASE_TITLE: ${{ needs.prepare.outputs.release_title }}
|
||||
RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -46,6 +46,10 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
/android/key.properties
|
||||
/*.p12
|
||||
/*.mobileprovision
|
||||
/*.keystore
|
||||
|
||||
# Rust / FFI artifacts
|
||||
/rust/target/
|
||||
|
||||
@ -5,6 +5,14 @@ plugins {
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
import java.util.Properties
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) }
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "plus.svc.xworkmate"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@ -30,11 +38,25 @@ android {
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val storeFilePath = keystoreProperties.getProperty("storeFile")
|
||||
if (!storeFilePath.isNullOrBlank()) {
|
||||
storeFile = file(storeFilePath)
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
signingConfig = if (keystorePropertiesFile.exists()) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
ios/ExportOptions.plist
Normal file
20
ios/ExportOptions.plist
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>compileBitcode</key>
|
||||
<false/>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>method</key>
|
||||
<string>${EXPORT_METHOD}</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>teamID</key>
|
||||
<string>N3G9T67W78</string>
|
||||
<key>thinning</key>
|
||||
<string><none></string>
|
||||
</dict>
|
||||
</plist>
|
||||
51
packaging/windows/main.wxs
Normal file
51
packaging/windows/main.wxs
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Package
|
||||
Name="XWorkmate"
|
||||
Manufacturer="plus.svc"
|
||||
Version="$(var.ProductVersion)"
|
||||
UpgradeCode="B5BDB376-78C8-4988-B1F2-D3F7B4E0A4D7"
|
||||
Language="1033">
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of XWorkmate is already installed." />
|
||||
<StandardDirectory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="XWorkmate" />
|
||||
</StandardDirectory>
|
||||
|
||||
<Feature Id="MainFeature" Title="XWorkmate" Level="1">
|
||||
<ComponentGroupRef Id="ApplicationFiles" />
|
||||
</Feature>
|
||||
|
||||
<Icon Id="AppIcon.ico" SourceFile="$(var.SourceDir)\resources\app_icon.ico" />
|
||||
<Property Id="ARPPRODUCTICON" Value="AppIcon.ico" />
|
||||
</Package>
|
||||
|
||||
<Fragment>
|
||||
<StandardDirectory Id="ProgramMenuFolder">
|
||||
<Directory Id="ProgramMenuDir" Name="XWorkmate" />
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="DesktopFolder" />
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="ApplicationFiles" Directory="INSTALLFOLDER">
|
||||
<Files Include="$(var.SourceDir)\**" />
|
||||
<Component Id="AppShortcutComponent" Guid="D7AA0C43-123D-4749-A160-5169FA62CB44">
|
||||
<Shortcut
|
||||
Id="StartMenuShortcut"
|
||||
Directory="ProgramMenuDir"
|
||||
Name="XWorkmate"
|
||||
Target="[INSTALLFOLDER]xworkmate.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<Shortcut
|
||||
Id="DesktopShortcut"
|
||||
Directory="DesktopFolder"
|
||||
Name="XWorkmate"
|
||||
Target="[INSTALLFOLDER]xworkmate.exe"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="RemoveProgramMenuDir" Directory="ProgramMenuDir" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\plus.svc\XWorkmate" Name="installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
45
scripts/ci/build_matrix_artifacts.sh
Executable file
45
scripts/ci/build_matrix_artifacts.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
platform="${1:?platform is required}"
|
||||
arch="${2:?arch is required}"
|
||||
should_release="${3:-false}"
|
||||
|
||||
flutter pub get
|
||||
|
||||
case "$platform" in
|
||||
linux)
|
||||
bash ./scripts/package-linux.sh
|
||||
;;
|
||||
macos)
|
||||
bash ./scripts/package-flutter-mac-app.sh
|
||||
mkdir -p dist/macos
|
||||
find dist -maxdepth 1 -name '*.dmg' -exec mv {} dist/macos/ \;
|
||||
;;
|
||||
windows)
|
||||
flutter build windows --release
|
||||
pwsh -File ./scripts/package-windows-msi.ps1 -Arch "$arch"
|
||||
;;
|
||||
ios)
|
||||
if [[ "$should_release" == "true" ]]; then
|
||||
bash ./scripts/package-ios-ipa.sh
|
||||
else
|
||||
echo "Release secrets not required for non-release runs; building unsigned iOS app bundle."
|
||||
flutter build ios --release --no-codesign
|
||||
mkdir -p dist/ios
|
||||
(
|
||||
cd build/ios/iphoneos
|
||||
rm -f XWorkmate.app.zip
|
||||
zip -qry XWorkmate.app.zip Runner.app
|
||||
mv XWorkmate.app.zip ../../../dist/ios/
|
||||
)
|
||||
fi
|
||||
;;
|
||||
android)
|
||||
bash ./scripts/package-android-apk.sh
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported platform: $platform" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
27
scripts/ci/compute_release_metadata.sh
Executable file
27
scripts/ci/compute_release_metadata.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GITHUB_OUTPUT:-}" ]]; then
|
||||
echo "GITHUB_OUTPUT is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
|
||||
release_tag="${GITHUB_REF_NAME}"
|
||||
release_title="Release ${GITHUB_REF_NAME}"
|
||||
release_notes="Automated release for ${GITHUB_REF_NAME}"
|
||||
elif [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" ]]; then
|
||||
release_tag="manual-${GITHUB_RUN_NUMBER:-0}"
|
||||
release_title="Manual Build ${GITHUB_RUN_NUMBER:-0}"
|
||||
release_notes="Automated manual build from ${GITHUB_SHA:-unknown}"
|
||||
else
|
||||
release_tag="main-${GITHUB_RUN_NUMBER:-0}"
|
||||
release_title="Main Build ${GITHUB_RUN_NUMBER:-0}"
|
||||
release_notes="Automated build from ${GITHUB_SHA:-unknown}"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "release_tag=${release_tag}"
|
||||
echo "release_title=${release_title}"
|
||||
echo "release_notes=${release_notes}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
29
scripts/ci/github_release_upload.sh
Executable file
29
scripts/ci/github_release_upload.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
artifact_dir="${1:-release-artifacts}"
|
||||
tag="${RELEASE_TAG:-manual-${GITHUB_RUN_NUMBER:-0}}"
|
||||
title="${RELEASE_TITLE:-Manual Build ${GITHUB_RUN_NUMBER:-0}}"
|
||||
notes="${RELEASE_NOTES:-Automated build}"
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
echo "GitHub CLI is required to upload release artifacts." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! gh release view "$tag" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
|
||||
gh release create "$tag" --repo "${GITHUB_REPOSITORY}" --title "$title" --notes "$notes"
|
||||
else
|
||||
gh release edit "$tag" --repo "${GITHUB_REPOSITORY}" --title "$title" --notes "$notes"
|
||||
fi
|
||||
|
||||
mapfile -d '' files < <(find "$artifact_dir" -type f -print0)
|
||||
|
||||
if [[ "${#files[@]}" -eq 0 ]]; then
|
||||
echo "No release artifacts found in $artifact_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
gh release upload "$tag" "$file" --repo "${GITHUB_REPOSITORY}" --clobber
|
||||
done
|
||||
27
scripts/ci/install_flutter_sdk.ps1
Normal file
27
scripts/ci/install_flutter_sdk.ps1
Normal file
@ -0,0 +1,27 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$FlutterVersion
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$installRoot = Join-Path ($env:RUNNER_TEMP ?? $env:TEMP) "flutter-sdk"
|
||||
$flutterRoot = Join-Path $installRoot "flutter"
|
||||
$archiveName = "flutter_windows_${FlutterVersion}-stable.zip"
|
||||
$archivePath = Join-Path ($env:RUNNER_TEMP ?? $env:TEMP) $archiveName
|
||||
$downloadUrl = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/$archiveName"
|
||||
|
||||
New-Item -ItemType Directory -Path $installRoot -Force | Out-Null
|
||||
|
||||
if (-not (Test-Path (Join-Path $flutterRoot "bin/flutter.bat"))) {
|
||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath
|
||||
if (Test-Path $flutterRoot) {
|
||||
Remove-Item -Recurse -Force $flutterRoot
|
||||
}
|
||||
Expand-Archive -Path $archivePath -DestinationPath $installRoot -Force
|
||||
}
|
||||
|
||||
"$flutterRoot\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
& "$flutterRoot\bin\flutter.bat" --disable-analytics
|
||||
& "$flutterRoot\bin\flutter.bat" config --no-analytics
|
||||
& "$flutterRoot\bin\flutter.bat" --version
|
||||
47
scripts/ci/install_flutter_sdk.sh
Executable file
47
scripts/ci/install_flutter_sdk.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
version="${1:?flutter version is required}"
|
||||
runner_temp="${RUNNER_TEMP:-/tmp}"
|
||||
install_root="$runner_temp/flutter-sdk"
|
||||
archive_name="flutter_linux_${version}-stable.tar.xz"
|
||||
|
||||
case "${RUNNER_OS:-$(uname -s)}" in
|
||||
Linux)
|
||||
archive_name="flutter_linux_${version}-stable.tar.xz"
|
||||
download_url="https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/${archive_name}"
|
||||
;;
|
||||
macOS|Darwin)
|
||||
if [[ "$(uname -m)" == "arm64" ]]; then
|
||||
archive_name="flutter_macos_arm64_${version}-stable.zip"
|
||||
else
|
||||
archive_name="flutter_macos_${version}-stable.zip"
|
||||
fi
|
||||
download_url="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/${archive_name}"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS for Flutter install: ${RUNNER_OS:-$(uname -s)}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$install_root"
|
||||
|
||||
if [[ ! -x "$install_root/flutter/bin/flutter" ]]; then
|
||||
archive_path="$runner_temp/$archive_name"
|
||||
curl -fsSL "$download_url" -o "$archive_path"
|
||||
rm -rf "$install_root/flutter"
|
||||
case "$archive_name" in
|
||||
*.tar.xz)
|
||||
tar -xJf "$archive_path" -C "$install_root"
|
||||
;;
|
||||
*.zip)
|
||||
unzip -q "$archive_path" -d "$install_root"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "$install_root/flutter/bin" >> "$GITHUB_PATH"
|
||||
"$install_root/flutter/bin/flutter" --disable-analytics
|
||||
"$install_root/flutter/bin/flutter" config --no-analytics
|
||||
"$install_root/flutter/bin/flutter" --version
|
||||
6
scripts/ci/run_code_analysis.sh
Executable file
6
scripts/ci/run_code_analysis.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
flutter pub get
|
||||
flutter analyze
|
||||
flutter test
|
||||
81
scripts/ci/setup_platform_deps.sh
Executable file
81
scripts/ci/setup_platform_deps.sh
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
platform="${1:?platform is required}"
|
||||
|
||||
case "$platform" in
|
||||
linux)
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
clang \
|
||||
cmake \
|
||||
ninja-build \
|
||||
libgtk-3-dev \
|
||||
pkg-config \
|
||||
libx11-dev \
|
||||
libgl1-mesa-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
dpkg-dev \
|
||||
rpm \
|
||||
imagemagick
|
||||
;;
|
||||
android)
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang cmake ninja-build libgtk-3-dev pkg-config libx11-dev libgl1-mesa-dev
|
||||
|
||||
android_sdk_root="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}"
|
||||
export ANDROID_SDK_ROOT="$android_sdk_root"
|
||||
export ANDROID_HOME="$android_sdk_root"
|
||||
|
||||
{
|
||||
echo "ANDROID_SDK_ROOT=$android_sdk_root"
|
||||
echo "ANDROID_HOME=$android_sdk_root"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
for candidate in \
|
||||
"$android_sdk_root/cmdline-tools/latest/bin/sdkmanager" \
|
||||
"$android_sdk_root/cmdline-tools/bin/sdkmanager" \
|
||||
"$android_sdk_root/tools/bin/sdkmanager"; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
sdkmanager="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "${sdkmanager:-}" ]]; then
|
||||
echo "Android sdkmanager not found under $android_sdk_root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
yes | "$sdkmanager" --licenses >/dev/null 2>&1 || true
|
||||
"$sdkmanager" "platform-tools" "platforms;android-35" "build-tools;35.0.0" "ndk;27.1.12297006"
|
||||
|
||||
flutter_bin="$(command -v flutter)"
|
||||
flutter_root="$(cd "$(dirname "$flutter_bin")/.." && pwd)"
|
||||
app_version="$(sed -n 's/^version:[[:space:]]*//p' pubspec.yaml | head -n 1)"
|
||||
app_version="${app_version%%+*}"
|
||||
version_code="${GITHUB_RUN_NUMBER:-1}"
|
||||
|
||||
cat > android/local.properties <<EOF
|
||||
sdk.dir=$android_sdk_root
|
||||
flutter.sdk=$flutter_root
|
||||
flutter.buildMode=release
|
||||
flutter.versionName=$app_version
|
||||
flutter.versionCode=$version_code
|
||||
EOF
|
||||
;;
|
||||
macos)
|
||||
brew install cocoapods create-dmg
|
||||
;;
|
||||
ios)
|
||||
brew install cocoapods
|
||||
;;
|
||||
windows)
|
||||
dotnet tool install --global wix --version 4.0.4
|
||||
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported platform: $platform" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
30
scripts/package-android-apk.sh
Executable file
30
scripts/package-android-apk.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
dist_dir="$root_dir/dist/android"
|
||||
key_properties="$root_dir/android/key.properties"
|
||||
keystore_path="$root_dir/android/upload-keystore.jks"
|
||||
|
||||
mkdir -p "$dist_dir"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$key_properties" "$keystore_path"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ -n "${ANDROID_KEYSTORE_BASE64:-}" && -n "${ANDROID_KEYSTORE_PASSWORD:-}" && -n "${ANDROID_KEY_ALIAS:-}" && -n "${ANDROID_KEY_PASSWORD:-}" ]]; then
|
||||
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$keystore_path"
|
||||
cat > "$key_properties" <<EOF
|
||||
storePassword=$ANDROID_KEYSTORE_PASSWORD
|
||||
keyPassword=$ANDROID_KEY_PASSWORD
|
||||
keyAlias=$ANDROID_KEY_ALIAS
|
||||
storeFile=$keystore_path
|
||||
EOF
|
||||
else
|
||||
echo "Android signing secrets are not fully set; using debug signing fallback for non-release builds."
|
||||
fi
|
||||
|
||||
flutter pub get
|
||||
flutter build apk --release
|
||||
cp "$root_dir/build/app/outputs/flutter-apk/app-release.apk" "$dist_dir/xworkmate-android-arm64.apk"
|
||||
73
scripts/package-ios-ipa.sh
Executable file
73
scripts/package-ios-ipa.sh
Executable file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
dist_dir="$root_dir/dist/ios"
|
||||
export_method="${APPLE_EXPORT_METHOD:-ad-hoc}"
|
||||
|
||||
mkdir -p "$dist_dir"
|
||||
|
||||
decode_base64() {
|
||||
if base64 --help 2>&1 | grep -q -- '--decode'; then
|
||||
base64 --decode
|
||||
else
|
||||
base64 -D
|
||||
fi
|
||||
}
|
||||
|
||||
required_vars=(
|
||||
APPLE_CERT_P12_BASE64
|
||||
APPLE_CERT_PASSWORD
|
||||
APPLE_PROVISION_PROFILE_BASE64
|
||||
APPLE_KEYCHAIN_PASSWORD
|
||||
)
|
||||
|
||||
missing=()
|
||||
for var_name in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var_name:-}" ]]; then
|
||||
missing+=("$var_name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${#missing[@]}" -gt 0 ]]; then
|
||||
echo "Missing iOS signing secrets: ${missing[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/xworkmate-ios.XXXXXX")"
|
||||
keychain_name="xworkmate-build.keychain-db"
|
||||
keychain_path="$HOME/Library/Keychains/$keychain_name"
|
||||
cert_path="$tmp_dir/dist-cert.p12"
|
||||
profile_path="$tmp_dir/profile.mobileprovision"
|
||||
export_options_path="$tmp_dir/ExportOptions.plist"
|
||||
|
||||
cleanup() {
|
||||
security delete-keychain "$keychain_path" >/dev/null 2>&1 || true
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
printf '%s' "$APPLE_CERT_P12_BASE64" | decode_base64 > "$cert_path"
|
||||
printf '%s' "$APPLE_PROVISION_PROFILE_BASE64" | decode_base64 > "$profile_path"
|
||||
|
||||
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_name"
|
||||
security set-keychain-settings -lut 21600 "$keychain_path"
|
||||
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path"
|
||||
security import "$cert_path" -P "$APPLE_CERT_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path"
|
||||
security list-keychains -d user -s "$keychain_path"
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path"
|
||||
|
||||
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
cp "$profile_path" "$HOME/Library/MobileDevice/Provisioning Profiles/xworkmate.mobileprovision"
|
||||
|
||||
sed "s|\${EXPORT_METHOD}|$export_method|g" "$root_dir/ios/ExportOptions.plist" > "$export_options_path"
|
||||
|
||||
flutter pub get
|
||||
flutter build ipa --release --export-options-plist="$export_options_path"
|
||||
|
||||
find "$root_dir/build/ios/ipa" -maxdepth 1 -name '*.ipa' -exec cp {} "$dist_dir/" \;
|
||||
|
||||
if ! compgen -G "$dist_dir/*.ipa" >/dev/null; then
|
||||
echo "No IPA was produced under $dist_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
60
scripts/package-windows-msi.ps1
Normal file
60
scripts/package-windows-msi.ps1
Normal file
@ -0,0 +1,60 @@
|
||||
param(
|
||||
[string]$Arch = "amd64"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$root = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$sourceDir = Join-Path $root "build\windows\x64\runner\Release"
|
||||
$distDir = Join-Path $root "dist\windows"
|
||||
$wxsPath = Join-Path $root "packaging\windows\main.wxs"
|
||||
$versionLine = (Get-Content (Join-Path $root "pubspec.yaml") | Select-String '^version:\s*').ToString()
|
||||
$versionValue = ($versionLine -replace '^version:\s*', '').Split('+')[0]
|
||||
$msiPath = Join-Path $distDir "xworkmate-$versionValue-$Arch.msi"
|
||||
$zipPath = Join-Path $distDir "xworkmate-windows-$Arch.zip"
|
||||
|
||||
if (-not (Test-Path $sourceDir)) {
|
||||
throw "Expected Windows release bundle not found: $sourceDir"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $distDir -Force | Out-Null
|
||||
|
||||
if (Test-Path $zipPath) {
|
||||
Remove-Item -Force $zipPath
|
||||
}
|
||||
Compress-Archive -Path (Join-Path $sourceDir '*') -DestinationPath $zipPath
|
||||
|
||||
$wixVersion = $versionValue
|
||||
if ($wixVersion -notmatch '^\d+\.\d+\.\d+$') {
|
||||
$wixVersion = "0.0.0"
|
||||
}
|
||||
|
||||
& wix build $wxsPath `
|
||||
-arch x64 `
|
||||
-d SourceDir=$sourceDir `
|
||||
-d ProductVersion=$wixVersion `
|
||||
-o $msiPath
|
||||
|
||||
if ($env:WINDOWS_PFX_BASE64 -and $env:WINDOWS_PFX_PASSWORD) {
|
||||
$certDir = Join-Path $env:RUNNER_TEMP "windows-signing"
|
||||
$pfxPath = Join-Path $certDir "codesign.pfx"
|
||||
New-Item -ItemType Directory -Path $certDir -Force | Out-Null
|
||||
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_PFX_BASE64))
|
||||
|
||||
$signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
|
||||
Sort-Object FullName -Descending |
|
||||
Select-Object -First 1
|
||||
if (-not $signtool) {
|
||||
throw "signtool.exe not found after Windows SDK discovery."
|
||||
}
|
||||
|
||||
$subjectArgs = @()
|
||||
if ($env:WINDOWS_CODESIGN_SUBJECT) {
|
||||
$subjectArgs = @("/n", $env:WINDOWS_CODESIGN_SUBJECT)
|
||||
}
|
||||
|
||||
& $signtool.FullName sign /fd SHA256 /f $pfxPath /p $env:WINDOWS_PFX_PASSWORD /tr http://timestamp.digicert.com /td SHA256 @subjectArgs $msiPath
|
||||
}
|
||||
|
||||
Write-Host "MSI: $msiPath"
|
||||
Write-Host "ZIP: $zipPath"
|
||||
Loading…
Reference in New Issue
Block a user