Monorepo: caching deps per target package in GH actions (#49020)

In this PR, we are implementing per-package build and caching, which optimizes build and fetching cached dependencies times across our workflows.
This commit is contained in:
Vladimir Reznichenko 2024-07-10 14:39:49 +02:00 committed by GitHub
parent f1399c9d77
commit 4311640de2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 80 additions and 35 deletions

View File

@ -16,6 +16,9 @@ inputs:
pull-playwright-cache: pull-playwright-cache:
description: 'Given a boolean value, invokes Playwright dependencies caching.' description: 'Given a boolean value, invokes Playwright dependencies caching.'
default: false default: false
pull-package-deps:
description: 'Given a string value, will pull the package specific dependencies cache.'
default: false
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
@ -31,27 +34,39 @@ runs:
uses: 'actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65' uses: 'actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65'
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
# We only want to use the cache if something is being installed. # The built-in caching is not fit to per-package caching we are aiming.
cache: ${{ inputs.install != 'false' && 'pnpm' || '' }} cache: ''
- name: 'Setup PHP' - name: 'Setup PHP'
if: ${{ inputs.php-version != 'false' }} if: ${{ inputs.php-version != 'false' }}
uses: 'shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0' uses: 'shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0'
with: with:
php-version: '${{ inputs.php-version }}' php-version: '${{ inputs.php-version }}'
coverage: 'none' coverage: 'none'
- name: 'Cache: identify pnpm caching directory'
if: ${{ inputs.pull-package-deps != 'false' }}
shell: 'bash'
run: |
echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: 'Cache: pnpm downloads'
if: ${{ inputs.pull-package-deps != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with:
path: "${{ env.PNPM_STORE_PATH }}"
key: "${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
restore-keys: '${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-'
- name: 'Cache Composer Dependencies' - name: 'Cache Composer Dependencies'
if: ${{ inputs.build == 'false' }} if: ${{ inputs.pull-package-deps != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319' uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with: with:
path: '~/.cache/composer/files' path: '~/.cache/composer/files'
key: "${{ runner.os }}-composer-${{ hashFiles( '**/composer.lock' ) }}" key: "${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-${{ hashFiles( 'packages/*/*/composer.lock', 'plugins/*/composer.lock' ) }}"
restore-keys: '${{ runner.os }}-composer-' restore-keys: '${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-'
- name: 'Cache: playwright downloads' - name: 'Cache: playwright downloads'
if: ${{ inputs.pull-playwright-cache != 'false' }} if: ${{ inputs.pull-playwright-cache != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319' uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with: with:
path: '~/.cache/ms-playwright/' path: '~/.cache/ms-playwright/'
key: "${{ runner.os }}-playwright-${{ hashFiles( '**/pnpm-lock.yaml' ) }}" key: "${{ runner.os }}-playwright-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
restore-keys: '${{ runner.os }}-playwright-' restore-keys: '${{ runner.os }}-playwright-'
- name: 'Parse Project Filters' - name: 'Parse Project Filters'
id: 'project-filters' id: 'project-filters'
@ -63,9 +78,8 @@ runs:
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter. # Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
if: ${{ inputs.install == 'true' || steps.project-filters.outputs.install != '' }} if: ${{ inputs.install == 'true' || steps.project-filters.outputs.install != '' }}
shell: 'bash' shell: 'bash'
run: 'pnpm install' # The installation command is a bit odd as it's a workaround for know bug - https://github.com/pnpm/pnpm/issues/6300.
# `pnpm install` filtering is broken: https://github.com/pnpm/pnpm/issues/6300 run: "pnpm install ${{ steps.project-filters.outputs.install }} --frozen-lockfile ${{ steps.project-filters.outputs.install != '' && '--config.dedupe-peer-dependents=false' || '' }}"
# run: 'pnpm install ${{ steps.project-filters.outputs.install }}'
# We want to include an option to build projects using this action so that we can make # We want to include an option to build projects using this action so that we can make
# sure that the build cache is always used when building projects. # sure that the build cache is always used when building projects.
- name: 'Cache Build Output' - name: 'Cache Build Output'
@ -75,6 +89,8 @@ runs:
- name: 'Build' - name: 'Build'
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter. # Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }} if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }}
env:
BROWSERSLIST_IGNORE_OLD_DATA: true
shell: 'bash' shell: 'bash'
run: | run: |
if [[ '${{ inputs.build-type }}' == 'backend' ]]; then if [[ '${{ inputs.build-type }}' == 'backend' ]]; then

View File

@ -30,12 +30,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
- name: Cache PNPM Dependencies
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65
with: with:
node-version-file: .nvmrc pull-package-deps: '@woocommerce/plugin-woocommerce'
cache: pnpm
- name: Prepare plugin zips - name: Prepare plugin zips
id: prepare id: prepare

View File

@ -20,6 +20,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip - name: Build zip
working-directory: plugins/woocommerce working-directory: plugins/woocommerce

View File

@ -153,6 +153,7 @@ jobs:
id: 'setup-monorepo' id: 'setup-monorepo'
with: with:
install: '${{ matrix.projectName }}...' install: '${{ matrix.projectName }}...'
pull-package-deps: '${{ matrix.projectName }}'
- name: 'Lint' - name: 'Lint'
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}' run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
@ -186,6 +187,7 @@ jobs:
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }} build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }} build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }}
pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }} pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }}
pull-package-deps: '${{ matrix.projectName }}'
- name: 'Update wp-env config' - name: 'Update wp-env config'
if: ${{ github.ref_type == 'tag' }} if: ${{ github.ref_type == 'tag' }}

View File

@ -52,6 +52,7 @@ jobs:
install: '@woocommerce/plugin-woocommerce...' install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce' build: '@woocommerce/plugin-woocommerce'
pull-playwright-cache: true pull-playwright-cache: true
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Install Playwright dependencies - name: Install Playwright dependencies
run: pnpm exec playwright install chromium --with-deps run: pnpm exec playwright install chromium --with-deps

View File

@ -18,6 +18,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip - name: Build zip
working-directory: plugins/woocommerce working-directory: plugins/woocommerce

View File

@ -24,6 +24,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip - name: Build zip
working-directory: plugins/woocommerce working-directory: plugins/woocommerce

View File

@ -27,6 +27,7 @@ jobs:
with: with:
install: true install: true
build: './tools/package-release' build: './tools/package-release'
pull-package-deps: 'tools/package-release'
- name: Clean working directory - name: Clean working directory
run: git checkout pnpm-lock.yaml # in case for whatever reason the lockfile is out of sync, there won't be interference with npm publish. run: git checkout pnpm-lock.yaml # in case for whatever reason the lockfile is out of sync, there won't be interference with npm publish.

View File

@ -39,12 +39,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
- name: Cache PNPM Dependencies
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65
with: with:
node-version-file: .nvmrc pull-package-deps: '@woocommerce/plugin-woocommerce'
cache: pnpm
- name: Prepare plugin zips - name: Prepare plugin zips
id: prepare id: prepare

View File

@ -17,6 +17,8 @@ jobs:
with: with:
install: 'code-analyzer...' install: 'code-analyzer...'
build: 'code-analyzer' build: 'code-analyzer'
pull-package-deps: 'code-analyzer'
- name: 'Analyze' - name: 'Analyze'
id: 'analyze' id: 'analyze'
working-directory: 'tools/code-analyzer' working-directory: 'tools/code-analyzer'

View File

@ -26,6 +26,7 @@ jobs:
with: with:
install: true install: true
build: './tools/package-release' build: './tools/package-release'
pull-package-deps: 'tools/package-release'
- name: Execute script - name: Execute script
run: ./tools/package-release/bin/dev prepare ${{ github.event.inputs.packages }} run: ./tools/package-release/bin/dev prepare ${{ github.event.inputs.packages }}

View File

@ -191,6 +191,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip - name: Build zip
working-directory: plugins/woocommerce working-directory: plugins/woocommerce
@ -219,6 +221,8 @@ jobs:
- name: Setup WooCommerce Monorepo - name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip - name: Build zip
working-directory: plugins/woocommerce working-directory: plugins/woocommerce

View File

@ -22,6 +22,7 @@ jobs:
with: with:
install: '@woocommerce/plugin-woocommerce' install: '@woocommerce/plugin-woocommerce'
build: '@woocommerce/plugin-woocommerce' build: '@woocommerce/plugin-woocommerce'
pull-package-deps: '@woocommerce/plugin-woocommerce-beta-tester'
- name: Lint - name: Lint
working-directory: plugins/woocommerce-beta-tester working-directory: plugins/woocommerce-beta-tester

View File

@ -39,10 +39,7 @@ describe( 'Shipping methods API tests', () => {
expect( body.method_id ).toEqual( methodId ); expect( body.method_id ).toEqual( methodId );
expect( body.method_title ).toEqual( methodTitle ); expect( body.method_title ).toEqual( methodTitle );
expect( body.enabled ).toEqual( true ); expect( body.enabled ).toEqual( true );
expect( body.settings.cost.value || '' ).toEqual( cost || '' );
if ( [ 'flat_rate', 'local_pickup' ].includes( methodId ) ) {
expect( body.settings.cost.value ).toEqual( cost );
}
// Cleanup: Delete the shipping method // Cleanup: Delete the shipping method
await shippingMethodsApi.delete.shippingMethod( await shippingMethodsApi.delete.shippingMethod(

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
CI: added a missing dev-dependency for passing tests in updated CI environment.

View File

@ -171,6 +171,7 @@
"jest-cli": "~27.5.1", "jest-cli": "~27.5.1",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"postcss-loader": "^4.3.0", "postcss-loader": "^4.3.0",
"qrcode.react": "^3.1.0",
"react": "^17.0.2", "react": "^17.0.2",
"rimraf": "5.0.5", "rimraf": "5.0.5",
"sass-loader": "^10.5.0", "sass-loader": "^10.5.0",

View File

@ -87,7 +87,7 @@ describe( 'Search', () => {
userEvent.type( getByRole( 'combobox' ), 'A' ); userEvent.type( getByRole( 'combobox' ), 'A' );
// Wait for async options processing. // Wait for async options processing.
await waitFor( () => { await waitFor( () => {
expect( optionsSpy ).toBeCalledWith( 'A' ); expect( optionsSpy ).toHaveBeenCalledWith( 'A' );
} ); } );
await waitFor( () => { await waitFor( () => {
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 ); expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );
@ -119,7 +119,7 @@ describe( 'Search', () => {
userEvent.type( getByRole( 'combobox' ), 'A' ); userEvent.type( getByRole( 'combobox' ), 'A' );
// Wait for async options processing. // Wait for async options processing.
await waitFor( () => { await waitFor( () => {
expect( optionsSpy ).toBeCalledWith( 'A' ); expect( optionsSpy ).toHaveBeenCalledWith( 'A' );
} ); } );
await waitFor( () => { await waitFor( () => {
expect( queryAllByRole( 'option' ) ).toHaveLength( 3 ); expect( queryAllByRole( 'option' ) ).toHaveLength( 3 );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
CI: code style fixes to pass linting in updated CI environment.

View File

@ -50,7 +50,7 @@ describe( 'fetchExperimentAssignment', () => {
experimentName: '123', experimentName: '123',
anonId: null, anonId: null,
} ); } );
await expect( fetchPromise ).rejects.toThrowError(); await expect( fetchPromise ).rejects.toThrow();
} ); } );
it( 'should throw error when experiment_name is empty', async () => { it( 'should throw error when experiment_name is empty', async () => {
@ -58,7 +58,7 @@ describe( 'fetchExperimentAssignment', () => {
experimentName: '', experimentName: '',
anonId: null, anonId: null,
} ); } );
await expect( fetchPromise ).rejects.toThrowError(); await expect( fetchPromise ).rejects.toThrow();
} ); } );
it( 'should throw error when experiment_name is invalid', async () => { it( 'should throw error when experiment_name is invalid', async () => {
@ -66,7 +66,7 @@ describe( 'fetchExperimentAssignment', () => {
experimentName: '', experimentName: '',
anonId: null, anonId: null,
} ); } );
await expect( fetchPromise ).rejects.toThrowError(); await expect( fetchPromise ).rejects.toThrow();
} ); } );
it( 'should return .json response', async () => { it( 'should return .json response', async () => {

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
CI: code style fixes to pass linting in updated CI environment.

View File

@ -5,10 +5,6 @@ import { setLocaleData } from '@wordpress/i18n';
import { registerStore } from '@wordpress/data'; import { registerStore } from '@wordpress/data';
import 'regenerator-runtime/runtime'; import 'regenerator-runtime/runtime';
// Mock the config module to avoid errors like:
// Core Error: Could not find config value for key ${ key }. Please make sure that if you need it then it has a default value assigned in config/_shared.json.
jest.mock( '@automattic/calypso-config' );
// Due to the dependency @wordpress/compose which introduces the use of // Due to the dependency @wordpress/compose which introduces the use of
// ResizeObserver this global mock is required for some tests to work. // ResizeObserver this global mock is required for some tests to work.
global.ResizeObserver = require( 'resize-observer-polyfill' ); global.ResizeObserver = require( 'resize-observer-polyfill' );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
CI: code style fixes to pass linting in updated CI environment.

View File

@ -335,7 +335,7 @@ describe( 'navigateTo', () => {
const resultUrl = new URL( window.location.href ); const resultUrl = new URL( window.location.href );
expect( getHistory().push ).not.toBeCalled(); expect( getHistory().push ).not.toHaveBeenCalled();
expect( resultUrl.search ).toBe( expect( resultUrl.search ).toBe(
'?page=wc-admin&path=%2Fsetup-wizard' '?page=wc-admin&path=%2Fsetup-wizard'
); );
@ -353,7 +353,7 @@ describe( 'navigateTo', () => {
const resultUrl = new URL( window.location.href ); const resultUrl = new URL( window.location.href );
expect( getHistory().push ).not.toBeCalled(); expect( getHistory().push ).not.toHaveBeenCalled();
expect( resultUrl.toString() ).toBe( expect( resultUrl.toString() ).toBe(
'https://vagrant.local/wp/wp-admin/orders.php' 'https://vagrant.local/wp/wp-admin/orders.php'
); );
@ -385,7 +385,7 @@ describe( 'navigateTo', () => {
const resultUrl = new URL( window.location.href ); const resultUrl = new URL( window.location.href );
expect( getHistory().push ).not.toBeCalled(); expect( getHistory().push ).not.toHaveBeenCalled();
expect( resultUrl.toString() ).toBe( expect( resultUrl.toString() ).toBe(
'https://vagrant.local/wp/wp-admin/admin.php?page=wc-admin&path=%2Fsetup-wizard' 'https://vagrant.local/wp/wp-admin/admin.php?page=wc-admin&path=%2Fsetup-wizard'
); );

View File

@ -14,6 +14,7 @@ class AddSplitChunkDependencies {
apply( compiler ) { apply( compiler ) {
compiler.hooks.thisCompilation.tap( compiler.hooks.thisCompilation.tap(
'AddStableChunksToAssets', 'AddStableChunksToAssets',
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- not used.
( compilation, callback ) => { ( compilation, callback ) => {
compilation.hooks.processAssets.tap( compilation.hooks.processAssets.tap(
{ {

View File

@ -330,6 +330,7 @@ const getFrontConfig = ( options = {} ) => {
// translations which we must avoid. // translations which we must avoid.
// @see https://github.com/Automattic/jetpack/pull/20926 // @see https://github.com/Automattic/jetpack/pull/20926
chunkFilename: `[name]-frontend${ fileSuffix }.js?ver=[contenthash]`, chunkFilename: `[name]-frontend${ fileSuffix }.js?ver=[contenthash]`,
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- not used.
filename: ( pathData ) => { filename: ( pathData ) => {
return `[name]-frontend${ fileSuffix }.js`; return `[name]-frontend${ fileSuffix }.js`;
}, },

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
CI: code style fixes to pass linting in updated CI environment.

View File

@ -905,6 +905,9 @@ importers:
postcss-loader: postcss-loader:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0(postcss@8.4.32)(webpack@5.89.0(webpack-cli@3.3.12)) version: 4.3.0(postcss@8.4.32)(webpack@5.89.0(webpack-cli@3.3.12))
qrcode.react:
specifier: ^3.1.0
version: 3.1.0(react@17.0.2)
react: react:
specifier: ^17.0.2 specifier: ^17.0.2
version: 17.0.2 version: 17.0.2