Skip to content

Commit 13b745d

Browse files
IONOS(ci): introduce prepare-matrix + build-apps parallel jobs
Adds the cached parallel matrix infrastructure to hidrive-next-build.yml as two new jobs. The legacy monolithic hidrive-next-build job that builds everything inline is preserved unchanged in this commit — the next commit rewires it to consume the per-app artifacts produced here. prepare-matrix - Computes effective_cache_version from CACHE_VERSION + optional workflow_dispatch input cache_version_suffix - Generates the build matrix by invoking make -f IONOS/Makefile generate_apps_matrix_json (a 7-entry sorted JSON, one per IONOS app) - Probes each app's per-SHA cache via .github/scripts/detect-app-cache.sh (JFrog first, GitHub Actions cache as fallback) - Emits apps_to_build / apps_to_restore / apps_matrix outputs build-apps - Parallel matrix (max-parallel: 7) keyed on apps_to_build entries - Setup PHP + Node per-app, run make -f IONOS/Makefile <app>-target>, tar the result and upload to JFrog under \${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/\${VERSION}/..., and save to GitHub Actions cache as a fallback restore source Adds CACHE_VERSION env (v1.2) and three workflow_dispatch inputs (force_rebuild, cache_version_suffix, apps_to_rebuild) needed by the new jobs. At this commit the new jobs run alongside the inline build and upload their artifacts but do not yet feed the final image — both paths produce the same outputs in parallel until the next commit retires the inline path. Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
1 parent 57623e3 commit 13b745d

1 file changed

Lines changed: 264 additions & 3 deletions

File tree

.github/workflows/hidrive-next-build.yml

Lines changed: 264 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ name: HiDrive Next Build
44
# SPDX-FileCopyrightText: 2024 STRATO AG
55
# SPDX-License-Identifier: AGPL-3.0-or-later
66

7-
# The HiDrive Next source is packaged as a container image.
8-
# This is a workaround because releases can not be created without tags
9-
# and we want to be able to create snapshots from branches.
7+
# Cached parallel matrix build pipeline:
8+
# Stage 1: prepare-matrix — compute app build matrix from IONOS/Makefile and probe per-app caches
9+
# Stage 2: build-apps — parallel matrix per app, per-app SHA caching in JFrog + GitHub Actions cache
10+
# Final: hidrive-next-build — restore all per-app artifacts and build Nextcloud core
1011

1112
on:
1213
pull_request:
@@ -31,17 +32,277 @@ on:
3132
branches:
3233
- ionos-dev
3334
- ionos-stable
35+
workflow_dispatch:
36+
inputs:
37+
force_rebuild:
38+
description: 'Force rebuild all apps and dependencies (bypass ALL caches)'
39+
required: false
40+
type: boolean
41+
default: false
42+
cache_version_suffix:
43+
description: 'Optional cache version suffix (e.g., "test", "debug") - creates separate cache namespace'
44+
required: false
45+
type: string
46+
default: ''
47+
apps_to_rebuild:
48+
description: 'Comma-separated list of specific apps to rebuild (e.g., "simplesettings,viewer")'
49+
required: false
50+
type: string
51+
default: ''
3452

3553
env:
3654
TARGET_PACKAGE_NAME: hidrive-next.zip
3755
REGISTRY: ghcr.io
3856
IMAGE_NAME: ${{ github.repository }}
3957
ARTIFACTORY_REPOSITORY_SNAPSHOT: ionos-productivity-hdnext-snapshot
58+
# Cache version - increment to invalidate all caches when build tooling changes
59+
# Format: v<major>.<minor> (e.g., v1.0, v1.1, v2.0)
60+
CACHE_VERSION: v1.2
4061

4162
permissions:
4263
contents: read
4364

4465
jobs:
66+
prepare-matrix:
67+
runs-on: ubuntu-latest
68+
outputs:
69+
apps_to_build: ${{ steps.detect.outputs.apps_to_build }}
70+
apps_to_restore: ${{ steps.detect.outputs.apps_to_restore }}
71+
apps_matrix: ${{ steps.set_matrix.outputs.matrix }}
72+
apps_sha_map: ${{ steps.detect.outputs.apps_sha_map }}
73+
has_apps_to_build: ${{ steps.detect.outputs.has_apps_to_build }}
74+
has_apps_to_restore: ${{ steps.detect.outputs.has_apps_to_restore }}
75+
effective_cache_version: ${{ steps.compute_cache_version.outputs.effective_cache_version }}
76+
77+
permissions:
78+
contents: read
79+
actions: read
80+
81+
steps:
82+
- name: Compute effective cache version
83+
id: compute_cache_version
84+
run: |
85+
EFFECTIVE_VERSION="${{ env.CACHE_VERSION }}${{ github.event.inputs.cache_version_suffix && format('-{0}', github.event.inputs.cache_version_suffix) || '' }}"
86+
echo "effective_cache_version=$EFFECTIVE_VERSION" >> "$GITHUB_OUTPUT"
87+
echo "Effective cache version: $EFFECTIVE_VERSION"
88+
89+
- name: Checkout repository
90+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
91+
with:
92+
submodules: true
93+
fetch-depth: 1
94+
95+
- name: Install dependencies
96+
run: sudo apt-get update && sudo apt-get install -y make jq
97+
98+
- name: Check JFrog credentials
99+
id: jfrog-available
100+
if: github.event.inputs.force_rebuild != 'true'
101+
env:
102+
JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }}
103+
JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }}
104+
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
105+
run: |
106+
if [ -n "$JF_URL" ] && [ -n "$JF_USER" ] && [ -n "$JF_ACCESS_TOKEN" ]; then
107+
echo "available=true" >> $GITHUB_OUTPUT
108+
else
109+
echo "available=false" >> $GITHUB_OUTPUT
110+
echo "⚠ Full JFrog credential set not available — skipping JFrog checks"
111+
fi
112+
113+
- name: Setup JFrog CLI
114+
if: steps.jfrog-available.outputs.available == 'true'
115+
uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.4.1
116+
env:
117+
JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }}
118+
JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }}
119+
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
120+
121+
- name: Generate apps matrix from Makefile
122+
id: set_matrix
123+
run: |
124+
echo "Generating apps matrix from Makefile..."
125+
matrix_output=$(make -f IONOS/Makefile generate_apps_matrix_json 2>&1)
126+
if echo "$matrix_output" | grep -q '^\[i\]'; then
127+
matrix=$(echo "$matrix_output" | grep -v '^\[i\]')
128+
else
129+
matrix="$matrix_output"
130+
fi
131+
if ! echo "$matrix" | jq empty 2>/dev/null; then
132+
echo "Error: Generated matrix is not valid JSON"
133+
echo "Output: $matrix_output"
134+
exit 1
135+
fi
136+
echo "matrix=$(echo "$matrix" | jq -c '.')" >> $GITHUB_OUTPUT
137+
echo "Matrix generated with $(echo "$matrix" | jq 'length') apps"
138+
139+
- name: Collect apps SHA and check cache status
140+
id: detect
141+
env:
142+
GH_TOKEN: ${{ github.token }}
143+
CACHE_VERSION: ${{ steps.compute_cache_version.outputs.effective_cache_version }}
144+
FORCE_REBUILD: ${{ github.event.inputs.force_rebuild || 'false' }}
145+
APPS_TO_REBUILD: ${{ github.event.inputs.apps_to_rebuild || '' }}
146+
JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }}
147+
JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }}
148+
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
149+
ARTIFACTORY_REPOSITORY_SNAPSHOT: ${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}
150+
GITHUB_REF: ${{ github.ref }}
151+
run: |
152+
bash .github/scripts/detect-app-cache.sh '${{ steps.set_matrix.outputs.matrix }}'
153+
154+
build-apps:
155+
runs-on: ubuntu-latest
156+
needs: prepare-matrix
157+
if: |
158+
always() &&
159+
needs.prepare-matrix.result == 'success' &&
160+
needs.prepare-matrix.outputs.has_apps_to_build == 'true'
161+
162+
permissions:
163+
contents: read
164+
actions: write
165+
166+
name: build-apps
167+
strategy:
168+
max-parallel: 7
169+
matrix:
170+
app_info: ${{ fromJson(needs.prepare-matrix.outputs.apps_to_build) }}
171+
172+
steps:
173+
- name: Get app configuration from full matrix
174+
id: app-config
175+
run: |
176+
FULL_MATRIX='${{ needs.prepare-matrix.outputs.apps_matrix }}'
177+
APP_NAME='${{ matrix.app_info.name }}'
178+
179+
APP_CONFIG=$(echo "$FULL_MATRIX" | jq -c --arg name "$APP_NAME" '.[] | select(.name == $name)')
180+
if [ -z "$APP_CONFIG" ]; then
181+
echo "ERROR: Could not find configuration for $APP_NAME in matrix"
182+
exit 1
183+
fi
184+
185+
echo "path=$(echo "$APP_CONFIG" | jq -r '.path')" >> $GITHUB_OUTPUT
186+
echo "has-npm=$(echo "$APP_CONFIG" | jq -r '.has_npm')" >> $GITHUB_OUTPUT
187+
echo "has-composer=$(echo "$APP_CONFIG" | jq -r '.has_composer')" >> $GITHUB_OUTPUT
188+
echo "npm-lock-path=$(echo "$APP_CONFIG" | jq -r '.npm_lock_path')" >> $GITHUB_OUTPUT
189+
echo "makefile-target=$(echo "$APP_CONFIG" | jq -r '.makefile_target')" >> $GITHUB_OUTPUT
190+
echo "Building $APP_NAME (SHA: ${{ matrix.app_info.sha }})"
191+
192+
- name: Checkout repository
193+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
194+
with:
195+
submodules: true
196+
fetch-depth: 1
197+
198+
- name: Check JFrog credentials
199+
id: jfrog-creds
200+
env:
201+
JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }}
202+
JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }}
203+
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
204+
run: |
205+
if [ -n "$JF_URL" ] && [ -n "$JF_USER" ] && [ -n "$JF_ACCESS_TOKEN" ]; then
206+
echo "available=true" >> $GITHUB_OUTPUT
207+
else
208+
echo "available=false" >> $GITHUB_OUTPUT
209+
echo "⚠ Full JFrog credential set not available — using GitHub cache path where possible"
210+
fi
211+
212+
- name: Setup JFrog CLI
213+
if: steps.jfrog-creds.outputs.available == 'true'
214+
uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.4.1
215+
env:
216+
JF_URL: ${{ secrets.JF_ARTIFACTORY_URL }}
217+
JF_USER: ${{ secrets.JF_ARTIFACTORY_USER }}
218+
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
219+
220+
- name: Set up node
221+
if: steps.app-config.outputs.has-npm == 'true'
222+
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
223+
with:
224+
node-version-file: "package.json"
225+
cache: 'npm'
226+
cache-dependency-path: ${{ steps.app-config.outputs.npm-lock-path }}
227+
228+
- name: Setup PHP
229+
if: steps.app-config.outputs.has-composer == 'true'
230+
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 #v2.31.1
231+
with:
232+
tools: composer:v2
233+
extensions: gd, zip, curl, xml, xmlrpc, mbstring, sqlite, xdebug, pgsql, intl, imagick, gmp, apcu, bcmath, redis, soap, imap, opcache
234+
env:
235+
runner: ubuntu-latest
236+
237+
- name: Cache Composer dependencies for ${{ matrix.app_info.name }}
238+
if: steps.app-config.outputs.has-composer == 'true' && github.event.inputs.force_rebuild != 'true'
239+
uses: actions/cache@v4
240+
with:
241+
path: ${{ steps.app-config.outputs.path }}/vendor
242+
key: ${{ runner.os }}-composer-${{ matrix.app_info.name }}-${{ hashFiles(format('{0}/composer.lock', steps.app-config.outputs.path)) }}
243+
restore-keys: |
244+
${{ runner.os }}-composer-${{ matrix.app_info.name }}-
245+
246+
- name: Build ${{ matrix.app_info.name }}
247+
env:
248+
CYPRESS_INSTALL_BINARY: 0
249+
PUPPETEER_SKIP_DOWNLOAD: true
250+
run: make -f IONOS/Makefile ${{ steps.app-config.outputs.makefile-target }}
251+
252+
- name: Compute app cache key
253+
id: app-cache-key
254+
run: |
255+
APP_SHA="${{ matrix.app_info.sha }}"
256+
APP_NAME="${{ matrix.app_info.name }}"
257+
EFFECTIVE_VERSION="${{ needs.prepare-matrix.outputs.effective_cache_version }}"
258+
SHORT_SHA="${APP_SHA:0:8}"
259+
260+
echo "cache_key=${EFFECTIVE_VERSION}-app-build-${APP_NAME}-${SHORT_SHA}" >> $GITHUB_OUTPUT
261+
echo "archive_name=${APP_NAME}-${SHORT_SHA}.tar.gz" >> $GITHUB_OUTPUT
262+
263+
- name: Upload ${{ matrix.app_info.name }} to JFrog
264+
if: steps.jfrog-creds.outputs.available == 'true'
265+
run: |
266+
APP_NAME="${{ matrix.app_info.name }}"
267+
APP_SHA="${{ matrix.app_info.sha }}"
268+
APP_PATH="${{ steps.app-config.outputs.path }}"
269+
EFFECTIVE_VERSION="${{ needs.prepare-matrix.outputs.effective_cache_version }}"
270+
ARCHIVE_NAME="${{ steps.app-cache-key.outputs.archive_name }}"
271+
JFROG_PATH="${{ env.ARTIFACTORY_REPOSITORY_SNAPSHOT }}/apps/${EFFECTIVE_VERSION}/${APP_NAME}/${ARCHIVE_NAME}"
272+
273+
echo "Packaging $APP_NAME..."
274+
tar -czf "$ARCHIVE_NAME" \
275+
--exclude="node_modules" \
276+
--exclude=".git" \
277+
--exclude="*.log" \
278+
-C "$(dirname "$APP_PATH")" \
279+
"$(basename "$APP_PATH")"
280+
281+
echo "Archive size: $(ls -lh "$ARCHIVE_NAME" | awk '{print $5}')"
282+
echo "Uploading to: $JFROG_PATH"
283+
284+
if jf rt upload "$ARCHIVE_NAME" "$JFROG_PATH" \
285+
--target-props "app.name=${APP_NAME};app.sha=${APP_SHA};vcs.branch=${{ github.ref_name }};vcs.revision=${{ github.sha }}"; then
286+
echo "✅ Uploaded $APP_NAME to JFrog"
287+
else
288+
echo "❌ JFrog upload failed"
289+
exit 1
290+
fi
291+
rm -f "$ARCHIVE_NAME"
292+
293+
- name: Save build to GitHub Actions cache
294+
uses: actions/cache/save@v4
295+
with:
296+
path: ${{ steps.app-config.outputs.path }}
297+
key: ${{ steps.app-cache-key.outputs.cache_key }}
298+
299+
- name: Show changes on failure
300+
if: failure()
301+
run: |
302+
git status
303+
git --no-pager diff
304+
exit 1
305+
45306
hidrive-next-build:
46307
runs-on: ubuntu-latest
47308

0 commit comments

Comments
 (0)