From 37aba5d2f0ae3e696394cde13011e6901b08decd Mon Sep 17 00:00:00 2001 From: ArthurErlich Date: Sun, 4 Oct 2026 00:29:17 +0200 Subject: [PATCH] ci: cache the Shopware project, drop duplicate install, reuse the tested zip in release - phpunit: cache the prepared project (vendor + phpunit) monthly via cache restore/save; drop system:install/plugin:install (TestBootstrapper installs into its own test DB, so every migration ran twice, ~5.5 min each); relax InnoDB durability on the CI database - package: save the tested zip keyed by the commit's tree hash; release restores it and only re-packages on a miss, but always validates and asserts the zip - cancel superseded PR runs (concurrency) Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/plugin-checks.yml | 69 +++++++++++++++++++++--------- .gitea/workflows/release.yml | 28 +++++++++--- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/.gitea/workflows/plugin-checks.yml b/.gitea/workflows/plugin-checks.yml index 5eac50e..2f8c8f4 100644 --- a/.gitea/workflows/plugin-checks.yml +++ b/.gitea/workflows/plugin-checks.yml @@ -11,6 +11,11 @@ on: permissions: contents: read +# A new push to the PR cancels the superseded run. +concurrency: + group: plugin-checks-${{ github.ref }} + cancel-in-progress: true + jobs: # Packages the extension (builds admin/storefront assets) and validates the resulting zip, # which is the artifact that ships. A separate directory validate would only repeat these checks. @@ -35,6 +40,16 @@ jobs: run: shopware-cli extension validate .build/package.zip - name: Assert built JS is in the zip run: scripts/ci/assert-built-assets.sh .build/package.zip + # The release workflow reuses this tested zip when main ends up with the same tree + # (it falls back to packaging again otherwise). + - name: Tree hash of the packaged commit + id: tree + run: echo "hash=$(git rev-parse "${GITHUB_SHA}^{tree}")" >> "$GITHUB_OUTPUT" + - name: Save tested zip for the release + uses: actions/cache/save@v4 + with: + path: .build/package.zip + key: plugin-zip-${{ steps.tree.outputs.hash }} phpunit: runs-on: ubuntu-latest @@ -51,21 +66,36 @@ jobs: DATABASE_URL: mysql://root:root@database:3306/shopware APP_ENV: test APP_URL: http://localhost + APP_SECRET: ci-secret-not-for-production + INSTANCE_ID: ci COMPOSER_MEMORY_LIMIT: '-1' COMPOSER_ALLOW_SUPERUSER: '1' steps: - uses: actions/checkout@v4 with: path: plugin-src - - uses: actions/cache@v4 + - name: Month stamp (refreshes the cached project monthly) + id: stamp + run: echo "month=$(date +%Y-%m)" >> "$GITHUB_OUTPUT" + # The project (incl. vendor/ and phpunit) does not depend on the plugin, so it is cached on its own. + - name: Restore prepared Shopware project + id: project + uses: actions/cache/restore@v4 with: - path: | - ~/.cache/composer - ~/.composer/cache - key: composer-${{ runner.os }}-shopware-6.6-${{ hashFiles('plugin-src/composer.json') }} - restore-keys: composer-${{ runner.os }}-shopware-6.6- - - name: Toolchain - run: php -v && composer --version && php -m | tr '\n' ' ' + path: shopware + key: shopware-6.6-prepared-${{ steps.stamp.outputs.month }} + - name: Create Shopware 6.6 project + if: steps.project.outputs.cache-hit != 'true' + run: | + set -e + composer create-project shopware/production:6.6.* shopware --no-interaction --no-scripts + composer require --dev phpunit/phpunit:^10 --no-interaction --no-scripts -d shopware + - name: Save prepared Shopware project + if: steps.project.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: shopware + key: shopware-6.6-prepared-${{ steps.stamp.outputs.month }} - name: Wait for the database run: | for i in $(seq 60); do @@ -73,22 +103,19 @@ jobs: sleep 2 done echo "::error::database service did not come up"; exit 1 - - name: Resolve plugin name + - name: Relax database durability (throwaway CI database, speeds up the migrations) + run: | + php -r '$p = new PDO("mysql:host=database;port=3306", "root", "root"); $p->exec("SET GLOBAL innodb_flush_log_at_trx_commit=0"); $p->exec("SET GLOBAL sync_binlog=0");' + - name: Resolve plugin name and install the plugin sources run: | # plugin directory name = short class name of extra.shopware-plugin-class CLASS=$(php -r 'echo json_decode(file_get_contents("plugin-src/composer.json"), true)["extra"]["shopware-plugin-class"];') - echo "PLUGIN_NAME=${CLASS##*\\}" >> "$GITHUB_ENV" - - name: Create Shopware 6.6 project - run: composer create-project shopware/production:6.6.* shopware --no-interaction --no-scripts - - name: Install Shopware and the plugin - working-directory: shopware - run: | - set -e - composer require --dev phpunit/phpunit:^10 --no-interaction - bin/console system:install --basic-setup --create-database --force --no-interaction - cp -r ../plugin-src "custom/plugins/${PLUGIN_NAME}" - bin/console plugin:refresh - bin/console plugin:install --activate "${PLUGIN_NAME}" + PLUGIN_NAME="${CLASS##*\\}" + echo "PLUGIN_NAME=${PLUGIN_NAME}" >> "$GITHUB_ENV" + rm -rf "shopware/custom/plugins/${PLUGIN_NAME}" + cp -r plugin-src "shopware/custom/plugins/${PLUGIN_NAME}" + # No system:install / plugin:install: Shopware's TestBootstrapper builds its own test database and + # installs the plugin (tests/TestBootstrap.php), so doing it here ran every migration twice. - name: Run phpunit working-directory: shopware run: vendor/bin/phpunit -c "custom/plugins/${PLUGIN_NAME}/phpunit.xml" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index c7f1001..5719fa5 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -49,8 +49,22 @@ jobs: echo "skip=false" >> "$GITHUB_OUTPUT" fi - - uses: actions/cache@v4 + # Reuse the zip the PR workflow already built and tested for this exact tree. + - name: Tree hash of this commit if: steps.meta.outputs.skip == 'false' + id: tree + run: echo "hash=$(git rev-parse "${GITHUB_SHA}^{tree}")" >> "$GITHUB_OUTPUT" + + - name: Restore the tested zip from the PR run + if: steps.meta.outputs.skip == 'false' + id: prebuilt + uses: actions/cache/restore@v4 + with: + path: .build/package.zip + key: plugin-zip-${{ steps.tree.outputs.hash }} + + - uses: actions/cache@v4 + if: steps.meta.outputs.skip == 'false' && steps.prebuilt.outputs.cache-hit != 'true' with: path: | ~/.npm @@ -58,12 +72,16 @@ jobs: key: swcli-${{ runner.os }}-${{ hashFiles('composer.json', 'src/Resources/app/**/package*.json') }} restore-keys: swcli-${{ runner.os }}- - - name: Package extension (prebuilt admin/storefront assets) - if: steps.meta.outputs.skip == 'false' + - name: Package extension (only if the tested zip is not available) + if: steps.meta.outputs.skip == 'false' && steps.prebuilt.outputs.cache-hit != 'true' run: | - shopware-cli extension package . --output-directory .build \ - --filename "${{ steps.meta.outputs.zip }}" --git-commit "$GITHUB_SHA" + shopware-cli extension package . --output-directory .build --filename package.zip --git-commit "$GITHUB_SHA" + - name: Name the zip + if: steps.meta.outputs.skip == 'false' + run: mv .build/package.zip ".build/${{ steps.meta.outputs.zip }}" + + # Cheap, and guards against a bad cache entry: always validate what ships. - name: Validate packaged zip if: steps.meta.outputs.skip == 'false' run: shopware-cli extension validate ".build/${{ steps.meta.outputs.zip }}"