feat(ci): build only 3 split APKs, upload individual artifacts, and publish unzipped APKs to Gitea Releases
This commit is contained in:
@@ -18,6 +18,8 @@ jobs:
|
|||||||
build-android:
|
build-android:
|
||||||
name: Build Release APKs
|
name: Build Release APKs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/cirruslabs/flutter:3.19.3
|
image: ghcr.io/cirruslabs/flutter:3.19.3
|
||||||
options: --privileged
|
options: --privileged
|
||||||
@@ -100,14 +102,7 @@ jobs:
|
|||||||
flutter analyze
|
flutter analyze
|
||||||
flutter test
|
flutter test
|
||||||
|
|
||||||
- name: Build Universal Release APK
|
- name: Build Split-per-ABI Release APKs (3 APKs)
|
||||||
run: |
|
|
||||||
flutter build apk --release || {
|
|
||||||
echo "Checking if APK was built despite path mismatch..."
|
|
||||||
find build android -type f -name "*release*.apk" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Build Split-per-ABI Release APKs
|
|
||||||
run: |
|
run: |
|
||||||
flutter build apk --release --split-per-abi || {
|
flutter build apk --release --split-per-abi || {
|
||||||
echo "Checking if split APKs were built despite path mismatch..."
|
echo "Checking if split APKs were built despite path mismatch..."
|
||||||
@@ -117,13 +112,6 @@ jobs:
|
|||||||
- name: Organize Artifacts
|
- name: Organize Artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p release-apks
|
mkdir -p release-apks
|
||||||
# Copy universal APK
|
|
||||||
if [ -f build/app/outputs/flutter-apk/app-release.apk ]; then
|
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk release-apks/oggauer-gemeindetrommler-universal-release.apk
|
|
||||||
elif [ -f android/app/build/outputs/apk/release/app-release.apk ]; then
|
|
||||||
cp android/app/build/outputs/apk/release/app-release.apk release-apks/oggauer-gemeindetrommler-universal-release.apk
|
|
||||||
fi
|
|
||||||
# Copy split APKs
|
|
||||||
for apk in build/app/outputs/flutter-apk/*.apk android/app/build/outputs/apk/release/*.apk; do
|
for apk in build/app/outputs/flutter-apk/*.apk android/app/build/outputs/apk/release/*.apk; do
|
||||||
[ -f "$apk" ] || continue
|
[ -f "$apk" ] || continue
|
||||||
case "$(basename "$apk")" in
|
case "$(basename "$apk")" in
|
||||||
@@ -132,12 +120,36 @@ jobs:
|
|||||||
*x86_64*) cp "$apk" release-apks/oggauer-gemeindetrommler-x86_64.apk ;;
|
*x86_64*) cp "$apk" release-apks/oggauer-gemeindetrommler-x86_64.apk ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
echo "=== Generated APK Artifacts ==="
|
echo "=== Generated 3 Split APK Artifacts ==="
|
||||||
ls -lah release-apks/
|
ls -lah release-apks/
|
||||||
|
|
||||||
- name: Upload APKs to Gitea Artifacts
|
- name: Upload ARM64 APK
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: oggauer-gemeindetrommler-apks
|
name: oggauer-gemeindetrommler-arm64-v8a
|
||||||
path: release-apks/*.apk
|
path: release-apks/oggauer-gemeindetrommler-arm64-v8a.apk
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload ARMv7 APK
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: oggauer-gemeindetrommler-armeabi-v7a
|
||||||
|
path: release-apks/oggauer-gemeindetrommler-armeabi-v7a.apk
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload x86_64 APK
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: oggauer-gemeindetrommler-x86_64
|
||||||
|
path: release-apks/oggauer-gemeindetrommler-x86_64.apk
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Publish Direct APKs to Gitea Releases
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITEA_API: ${{ github.api_url }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
RELEASE_TAG: latest
|
||||||
|
run: |
|
||||||
|
node scripts/ci/publish_release.js
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ jobs:
|
|||||||
build-android:
|
build-android:
|
||||||
name: Build Release APKs
|
name: Build Release APKs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/cirruslabs/flutter:3.19.3
|
image: ghcr.io/cirruslabs/flutter:3.19.3
|
||||||
options: --privileged
|
options: --privileged
|
||||||
@@ -100,14 +102,7 @@ jobs:
|
|||||||
flutter analyze
|
flutter analyze
|
||||||
flutter test
|
flutter test
|
||||||
|
|
||||||
- name: Build Universal Release APK
|
- name: Build Split-per-ABI Release APKs (3 APKs)
|
||||||
run: |
|
|
||||||
flutter build apk --release || {
|
|
||||||
echo "Checking if APK was built despite path mismatch..."
|
|
||||||
find build android -type f -name "*release*.apk" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Build Split-per-ABI Release APKs
|
|
||||||
run: |
|
run: |
|
||||||
flutter build apk --release --split-per-abi || {
|
flutter build apk --release --split-per-abi || {
|
||||||
echo "Checking if split APKs were built despite path mismatch..."
|
echo "Checking if split APKs were built despite path mismatch..."
|
||||||
@@ -117,13 +112,6 @@ jobs:
|
|||||||
- name: Organize Artifacts
|
- name: Organize Artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p release-apks
|
mkdir -p release-apks
|
||||||
# Copy universal APK
|
|
||||||
if [ -f build/app/outputs/flutter-apk/app-release.apk ]; then
|
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk release-apks/oggauer-gemeindetrommler-universal-release.apk
|
|
||||||
elif [ -f android/app/build/outputs/apk/release/app-release.apk ]; then
|
|
||||||
cp android/app/build/outputs/apk/release/app-release.apk release-apks/oggauer-gemeindetrommler-universal-release.apk
|
|
||||||
fi
|
|
||||||
# Copy split APKs
|
|
||||||
for apk in build/app/outputs/flutter-apk/*.apk android/app/build/outputs/apk/release/*.apk; do
|
for apk in build/app/outputs/flutter-apk/*.apk android/app/build/outputs/apk/release/*.apk; do
|
||||||
[ -f "$apk" ] || continue
|
[ -f "$apk" ] || continue
|
||||||
case "$(basename "$apk")" in
|
case "$(basename "$apk")" in
|
||||||
@@ -132,12 +120,26 @@ jobs:
|
|||||||
*x86_64*) cp "$apk" release-apks/oggauer-gemeindetrommler-x86_64.apk ;;
|
*x86_64*) cp "$apk" release-apks/oggauer-gemeindetrommler-x86_64.apk ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
echo "=== Generated APK Artifacts ==="
|
echo "=== Generated 3 Split APK Artifacts ==="
|
||||||
ls -lah release-apks/
|
ls -lah release-apks/
|
||||||
|
|
||||||
- name: Upload APKs to Gitea Artifacts
|
- name: Upload ARM64 APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: oggauer-gemeindetrommler-apks
|
name: oggauer-gemeindetrommler-arm64-v8a
|
||||||
path: release-apks/*.apk
|
path: release-apks/oggauer-gemeindetrommler-arm64-v8a.apk
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload ARMv7 APK
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: oggauer-gemeindetrommler-armeabi-v7a
|
||||||
|
path: release-apks/oggauer-gemeindetrommler-armeabi-v7a.apk
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload x86_64 APK
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: oggauer-gemeindetrommler-x86_64
|
||||||
|
path: release-apks/oggauer-gemeindetrommler-x86_64.apk
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const token = process.env.GITEA_TOKEN;
|
||||||
|
const repo = process.env.REPO;
|
||||||
|
let apiUrl = (process.env.GITEA_API || 'https://git.djnizz.at/api/v1').replace(/\/$/, '');
|
||||||
|
if (!apiUrl.includes('/api/v1')) {
|
||||||
|
apiUrl += '/api/v1';
|
||||||
|
}
|
||||||
|
const tag = process.env.RELEASE_TAG || 'latest';
|
||||||
|
const apksDir = path.resolve('release-apks');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.log('No GITEA_TOKEN provided, skipping direct release publishing.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(apksDir)) {
|
||||||
|
console.log('No release-apks directory found, skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apkFiles = fs.readdirSync(apksDir).filter(f => f.endsWith('.apk'));
|
||||||
|
if (apkFiles.length === 0) {
|
||||||
|
console.log('No APK files found in release-apks, skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Publishing ${apkFiles.length} APK(s) to Gitea release tag '${tag}'...`);
|
||||||
|
|
||||||
|
// 1. Get or create release
|
||||||
|
let release = null;
|
||||||
|
const tagRes = await fetch(`${apiUrl}/repos/${repo}/releases/tags/${tag}`, {
|
||||||
|
headers: { 'Authorization': `token ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tagRes.ok) {
|
||||||
|
release = await tagRes.json();
|
||||||
|
console.log(`Found existing release (ID: ${release.id}) for tag '${tag}'.`);
|
||||||
|
} else {
|
||||||
|
console.log(`Release for tag '${tag}' does not exist, creating new release...`);
|
||||||
|
const createRes = await fetch(`${apiUrl}/repos/${repo}/releases`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
tag_name: tag,
|
||||||
|
name: `Oggauer Gemeindetrommler - ${tag.toUpperCase()}`,
|
||||||
|
body: `Automatische Builds der Android APKs (Split per ABI).\nDirekte APK-Downloads ohne ZIP.`,
|
||||||
|
draft: false,
|
||||||
|
prerelease: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!createRes.ok) {
|
||||||
|
console.warn(`Could not create release: ${createRes.status} ${await createRes.text()}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
release = await createRes.json();
|
||||||
|
console.log(`Created release with ID ${release.id}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Query existing assets to delete duplicates
|
||||||
|
const assetsRes = await fetch(`${apiUrl}/repos/${repo}/releases/${release.id}/assets`, {
|
||||||
|
headers: { 'Authorization': `token ${token}` }
|
||||||
|
});
|
||||||
|
const existingAssets = assetsRes.ok ? await assetsRes.json() : [];
|
||||||
|
|
||||||
|
// 3. Upload each APK
|
||||||
|
for (const fileName of apkFiles) {
|
||||||
|
const filePath = path.join(apksDir, fileName);
|
||||||
|
const existing = existingAssets.find(a => a.name === fileName);
|
||||||
|
if (existing) {
|
||||||
|
console.log(`Deleting old asset ${fileName} (ID: ${existing.id})...`);
|
||||||
|
await fetch(`${apiUrl}/repos/${repo}/releases/${release.id}/assets/${existing.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Authorization': `token ${token}` }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Uploading ${fileName} (${(fs.statSync(filePath).size / (1024 * 1024)).toFixed(1)} MB)...`);
|
||||||
|
const fileBytes = fs.readFileSync(filePath);
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('attachment', new Blob([fileBytes]), fileName);
|
||||||
|
|
||||||
|
const uploadRes = await fetch(`${apiUrl}/repos/${repo}/releases/${release.id}/assets?name=${encodeURIComponent(fileName)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `token ${token}`
|
||||||
|
},
|
||||||
|
body: form
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uploadRes.ok) {
|
||||||
|
const asset = await uploadRes.json();
|
||||||
|
console.log(`✓ Uploaded ${fileName}: ${asset.browser_download_url || fileName}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`Failed to upload ${fileName}: ${uploadRes.status} ${await uploadRes.text()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Finished publishing APKs to Gitea Releases!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('Error in publish_release.js:', err);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user