@@ -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
1112on :
1213 pull_request :
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
3553env :
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
4162permissions :
4263 contents : read
4364
4465jobs :
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