Skip to content

Commit 9ac7165

Browse files
authored
ci: distinguish pipeline pnpm store caches (#27577)
Use lockfile paths as literal strings to avoid unintended cross-pollination when there is a cache miss. Recently a less than 2GB cache was bumped to 37GB when there was a miss for Kusto upload job and the E2E test cache was used as fallback. Avoid secondary pnpm install for client jobs to avoid downgrading (or upgrading) pnpm versions. - In perf-benchmarks, swap the install order; so, that FF client pnpm install is first. Note: This change will cause all pipelines to reset their caches. (Which can be good time to time anyway.)
1 parent a9d74ac commit 9ac7165

8 files changed

Lines changed: 116 additions & 28 deletions

tools/pipelines/templates/build-npm-client-package.yml

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,17 @@ extends:
331331

332332
- template: /tools/pipelines/templates/include-install.yml@self
333333
parameters:
334-
packageManager: '${{ parameters.packageManager }}'
335334
buildDirectory: '${{ parameters.buildDirectory }}'
335+
installPnpm: ${{ eq(parameters.packageManager, 'pnpm') }}
336336
packageManagerInstallCommand: '${{ parameters.packageManagerInstallCommand }}'
337+
# The compat-workspaces lockfile is committed to the FluidFramework repo and changes
338+
# whenever legacy package versions are added or removed. Including it in the pnpm store
339+
# cache key ensures a cache miss whenever the set of compat packages changes.
340+
${{ if and(eq(parameters.isBundleSizeArtifactsPipeline, false), eq(parameters.buildToolsVersionToInstall, 'repo')) }}:
341+
# Currently repo build-tools also installs sharing the store - ideally that is removed by #73628 and the `| ...` can be removed.
342+
additionalLockFilePaths: '${{ parameters.buildDirectory }}/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml | ${{ parameters.buildDirectory }}/build-tools/pnpm-lock.yaml'
343+
${{ else }}:
344+
additionalLockFilePaths: '${{ parameters.buildDirectory }}/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml'
337345

338346
# The bundle-size-artifacts pipeline runs a client build but doesn't publish packages,
339347
# so we skip version setting for it.
@@ -343,6 +351,11 @@ extends:
343351
buildDirectory: '${{ parameters.buildDirectory }}'
344352
buildNumberInPatch: ${{ parameters.buildNumberInPatch }}
345353
buildToolsVersionToInstall: '${{ parameters.buildToolsVersionToInstall }}'
354+
# Note that skipping pnpm install means client pnpm version must suffice for build-tools.
355+
# Ideally, #73628 removes include-install-build-tools template use in include-set-package-version
356+
# making it a non-issue. For now, second pnpm install is skipped to preserve the client version
357+
# for all remaining steps.
358+
installPnpmForRepoBuildTools: ${{ ne(parameters.packageManager, 'pnpm') }}
346359
tagName: '${{ parameters.tagName }}'
347360
interdependencyRange: '${{ parameters.interdependencyRange }}'
348361
packageTypesOverride: '${{ parameters.packageTypesOverride }}'
@@ -600,15 +613,25 @@ extends:
600613

601614
- template: /tools/pipelines/templates/include-install.yml@self
602615
parameters:
603-
packageManager: '${{ parameters.packageManager }}'
604616
buildDirectory: '${{ parameters.buildDirectory }}'
617+
installPnpm: ${{ eq(parameters.packageManager, 'pnpm') }}
605618
packageManagerInstallCommand: '${{ parameters.packageManagerInstallCommand }}'
619+
# The compat-workspaces lockfile is committed to the FluidFramework repo and changes
620+
# whenever legacy package versions are added or removed. Including it in the pnpm store
621+
# cache key ensures a cache miss whenever the set of compat packages changes.
622+
# Currently repo build-tools also installs sharing the store - ideally that is removed by #73628 and the `| ...` can be removed.
623+
additionalLockFilePaths: '${{ parameters.buildDirectory }}/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml | ${{ parameters.buildDirectory }}/build-tools/pnpm-lock.yaml'
606624

607625
# We need it in order to run flub where the code coverage comparison logic calls for it
608626
- template: /tools/pipelines/templates/include-install-build-tools.yml@self
609627
parameters:
610628
buildDirectory: ${{ parameters.buildDirectory }}
611629
buildToolsVersionToInstall: repo
630+
# Note that skipping pnpm install means client pnpm version must suffice for build-tools.
631+
# Ideally, #73628 removes this whole template use making it a non-issue. For now, second
632+
# pnpm install is skipped to preserve the client version for any remaining steps.
633+
installPnpmForRepoBuildTools: ${{ ne(parameters.packageManager, 'pnpm') }}
634+
# This shares the store with the client installs (above).
612635
pnpmStorePath: $(Pipeline.Workspace)/.pnpm-store
613636

614637
- task: DownloadPipelineArtifact@2
@@ -764,9 +787,13 @@ extends:
764787

765788
- template: /tools/pipelines/templates/include-install.yml@self
766789
parameters:
767-
packageManager: '${{ parameters.packageManager }}'
768790
buildDirectory: '${{ parameters.buildDirectory }}'
791+
installPnpm: ${{ eq(parameters.packageManager, 'pnpm') }}
769792
packageManagerInstallCommand: '${{ parameters.packageManagerInstallCommand }}'
793+
# The compat-workspaces lockfile is committed to the FluidFramework repo and changes
794+
# whenever legacy package versions are added or removed. Including it in the pnpm store
795+
# cache key ensures a cache miss whenever the set of compat packages changes.
796+
additionalLockFilePaths: '${{ parameters.buildDirectory }}/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml'
770797

771798
- task: DownloadPipelineArtifact@2
772799
inputs:

tools/pipelines/templates/build-npm-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ extends:
314314

315315
- template: /tools/pipelines/templates/include-install.yml@self
316316
parameters:
317-
packageManager: '${{ parameters.packageManager }}'
318317
buildDirectory: '${{ parameters.buildDirectory }}'
318+
installPnpm: ${{ eq(parameters.packageManager, 'pnpm') }}
319319
packageManagerInstallCommand: '${{ parameters.packageManagerInstallCommand }}'
320320

321321
# The bundle-size-artifacts pipeline runs a client build but doesn't publish packages,

tools/pipelines/templates/include-install-build-tools.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ parameters:
2323
type: string
2424
default: repo
2525

26+
- name: installPnpmForRepoBuildTools
27+
type: boolean
28+
default: true
29+
2630
# The path to the pnpm store.
2731
- name: pnpmStorePath
2832
type: string
@@ -32,11 +36,12 @@ steps:
3236
# These steps should ONLY run if we're using the repo version of the build tools. These steps are mutually exclusive
3337
# with the next group of steps.
3438
- ${{ if eq(parameters.buildToolsVersionToInstall, 'repo') }}:
35-
- template: /tools/pipelines/templates/include-install-pnpm.yml@self
36-
parameters:
37-
buildDirectory: $(FluidFrameworkDirectory)/build-tools
38-
pnpmStorePath: ${{ parameters.pnpmStorePath }}
39-
enableCache: false
39+
- ${{ if parameters.installPnpmForRepoBuildTools }}:
40+
- template: /tools/pipelines/templates/include-install-pnpm.yml@self
41+
parameters:
42+
buildDirectory: $(FluidFrameworkDirectory)/build-tools
43+
pnpmStorePath: ${{ parameters.pnpmStorePath }}
44+
enableCache: false
4045

4146
- task: Bash@3
4247
name: InstallBuildTools

tools/pipelines/templates/include-install-pnpm.yml

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
parameters:
99
# The path containing the project(s) to build.
10+
# A package.json is expected under this folder and will be used to determine
11+
# the version of pnpm to install.
1012
- name: buildDirectory
1113
type: string
1214

@@ -16,6 +18,8 @@ parameters:
1618
default: true
1719

1820
# The path to the pnpm store. The contents here will be cached and restored when using pnpm in a pipeline.
21+
# If a job uses this template multiple times with enableCache=true, then each pnpmStorePath should be
22+
# unique AND all use of pnpm to install should be done before another use of this template.
1923
- name: pnpmStorePath
2024
type: string
2125
default: $(Pipeline.Workspace)/.pnpm-store
@@ -34,33 +38,50 @@ parameters:
3438
# An optional extra segment appended to the pnpm store cache key (separated by |).
3539
# Note that Azure pipelines interpret unquoted cache keys as files rather than literal strings. E.g. the following specification:
3640
#
37-
# additionalCacheKey: 'foo | "bar"'
41+
# additionalOptionalCacheKey: 'foo | "bar"'
3842
#
3943
# will use the hashed contents of the file "foo" (hashing nothing if that file does not exist) as well as the string "bar".
4044
# Use this parameter if the set of dependencies to be installed may be be better distinguished using files other than the root pnpm-lock.
41-
- name: additionalCacheKey
45+
# And specify quoted files paths of any files to requiredCacheKey.
46+
- name: additionalOptionalCacheKey
4247
type: string
4348
default: ''
4449

50+
# An optional extra segment appended to the pnpm store cache key (separated by |) and restore key.
51+
# Note that Azure pipelines interpret unquoted cache keys as files rather than literal strings. E.g. the following specification:
52+
#
53+
# requiredCacheKey: 'foo | "bar"'
54+
#
55+
# will use the hashed contents of the file "foo" (hashing nothing if that file does not exist) as well as the string "bar".
56+
# Unquoted files are *not recommended* as requiredCacheKey as it limits restore possibilities. However the quoted file path
57+
# of any files specified in additionalOptionalCacheKey is recommended to avoid cross-pollination from caches of other jobs.
58+
# There is a "default" default value to avoid unintended restore contamination. That can be overridden by setting empty string.
59+
- name: requiredCacheKey
60+
type: string
61+
default: '"default"'
62+
4563
steps:
4664
- ${{ if eq(parameters.enableCache, true) }}:
4765
- task: Cache@2
4866
displayName: Cache pnpm store
4967
# The timeout applies to both the pre-job restore and post-job save operations.
5068
# 3 minutes was insufficient for the post-job cache upload of the pnpm store, causing
5169
# intermittent "The task has timed out" errors that mark builds as Partially Succeeded.
70+
# In 2026-06, 5 minutes has been insufficient for pre-job as the pnpm store has grown
71+
# with additional past versions of FF client packages for compatibility testing. But
72+
# it may also just be polluted. This will either remain at 5 or be adjusted after
73+
# caches are reset.
5274
timeoutInMinutes: 5
5375
continueOnError: true
5476
inputs:
55-
# Caches are already scoped to individual pipelines, so no need to include the release group name or tag
56-
# in the cache key
57-
${{ if ne(parameters.additionalCacheKey, '') }}:
58-
key: '"pnpm-store" | "$(Agent.OS)" | $(Pipeline.Workspace)/${{ parameters.buildDirectory }}/pnpm-lock.yaml | ${{ parameters.additionalCacheKey }}'
59-
${{ else }}:
60-
key: '"pnpm-store" | "$(Agent.OS)" | $(Pipeline.Workspace)/${{ parameters.buildDirectory }}/pnpm-lock.yaml'
6177
path: ${{ parameters.pnpmStorePath }}
78+
# Caches are already scoped to individual pipelines, so no need to include the release group name or tag
79+
# in the cache key.
80+
# The pnpm lockfile path is kept as an explicit key to avoid job cross-pollination
81+
# when using multiple workspaces in a pipeline.
82+
key: '"pnpm-store" | "$(Agent.OS)" | "${{ parameters.buildDirectory }}/pnpm-lock.yaml" | ${{ parameters.requiredCacheKey }} | $(Pipeline.Workspace)/${{ parameters.buildDirectory }}/pnpm-lock.yaml | ${{ parameters.additionalOptionalCacheKey }}'
6283
restoreKeys: |
63-
"pnpm-store" | "$(Agent.OS)"
84+
"pnpm-store" | "$(Agent.OS)" | "${{ parameters.buildDirectory }}/pnpm-lock.yaml" | ${{ parameters.requiredCacheKey }}
6485
6586
# Seed the userconfig .npmrc with the primary registry. This propagates to subsequent tasks so
6687
# that npmAuthenticate (below) and any later pnpm/npm invocation in the job reuse the same file.

tools/pipelines/templates/include-install.yml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
# include-install template for the install step in client build and test stability pipeline
55

66
parameters:
7-
- name: packageManager
7+
- name: buildDirectory
88
type: string
9-
default: pnpm
109

11-
- name: buildDirectory
10+
- name: installDependenciesDescription
1211
type: string
12+
default: Install dependencies
13+
14+
- name: installPnpm
15+
type: boolean
16+
default: true
1317

1418
- name: packageManagerInstallCommand
1519
type: string
@@ -23,16 +27,24 @@ parameters:
2327
type: string
2428
default: $(Agent.TempDirectory)/.npmrc
2529

30+
# string with ` | ` separated list of lockfiles (note that the spacing is meaningful)
31+
- name: additionalLockFilePaths
32+
type: string
33+
default: ''
34+
2635
steps:
27-
- ${{ if eq(parameters.packageManager, 'pnpm') }}:
36+
- ${{ if parameters.installPnpm }}:
2837
- template: /tools/pipelines/templates/include-install-pnpm.yml@self
2938
parameters:
3039
buildDirectory: ${{ parameters.buildDirectory }}
3140
primaryRegistry: ${{ parameters.primaryRegistry }}
3241
userNpmrcPath: ${{ parameters.userNpmrcPath }}
42+
${{ if ne(parameters.additionalLockFilePaths, '') }}:
43+
additionalOptionalCacheKey: '$(Pipeline.Workspace)/${{ replace(parameters.additionalLockFilePaths, '' | '', '' | $(Pipeline.Workspace)/'') }}'
44+
requiredCacheKey: '"${{ replace(parameters.additionalLockFilePaths, '' | '', ''" | "'') }}"'
3345

3446
- task: Bash@3
35-
displayName: Install dependencies
47+
displayName: ${{ parameters.installDependenciesDescription }}
3648
retryCountOnTaskFailure: 4
3749
inputs:
3850
targetType: 'inline'

tools/pipelines/templates/include-set-package-version.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ parameters:
1515
- name: buildToolsVersionToInstall
1616
type: string
1717
default: repo
18+
- name: installPnpmForRepoBuildTools
19+
type: boolean
20+
default: true
1821

1922
# The path to the pnpm store.
2023
- name: pnpmStorePath
@@ -53,6 +56,7 @@ steps:
5356
parameters:
5457
buildDirectory: ${{ parameters.buildDirectory }}
5558
buildToolsVersionToInstall: ${{ parameters.buildToolsVersionToInstall }}
59+
installPnpmForRepoBuildTools: ${{ parameters.installPnpmForRepoBuildTools }}
5660
pnpmStorePath: ${{ parameters.pnpmStorePath }}
5761

5862
- task: Bash@3

tools/pipelines/templates/include-test-real-service.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,18 @@ stages:
246246

247247
- template: /tools/pipelines/templates/include-install-pnpm.yml@self
248248
parameters:
249+
# Install pnpm based on the FF repo settings as it has an engine requirement whereas
250+
# ff_pipeline_host does not yet. So long a FF repo's pnpm version is compatible with
251+
# ff_pipeline_host this single install and ordering of steps will suffice. If the
252+
# needs diverge, then additional coordination may be required.
249253
buildDirectory: $(FluidFrameworkDirectory)
250254
# The compat-workspaces lockfile is committed to the FluidFramework repo and changes
251255
# whenever legacy package versions are added or removed. Including it in the pnpm store
252256
# cache key ensures a cache miss whenever the set of compat packages changes.
253257
# The ff_pipeline_host lockfile is included so the cache key also invalidates when the
254258
# internal tooling dependencies change.
255-
additionalCacheKey: '$(Pipeline.Workspace)/$(FluidFrameworkDirectory)/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml | $(Pipeline.Workspace)/$(FFPipelineHostDirectory)/pnpm-lock.yaml'
259+
additionalOptionalCacheKey: '$(Pipeline.Workspace)/$(FluidFrameworkDirectory)/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml | $(Pipeline.Workspace)/$(FFPipelineHostDirectory)/pnpm-lock.yaml'
260+
requiredCacheKey: '"$(Pipeline.Workspace)/$(FluidFrameworkDirectory)/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml" | "$(Pipeline.Workspace)/$(FFPipelineHostDirectory)/pnpm-lock.yaml"'
256261

257262
- template: /tools/pipelines/templates/include-setup-npmrc-for-download.yml@self
258263

tools/pipelines/test-perf-benchmarks.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,30 @@ stages:
142142
143143
- template: /tools/pipelines/templates/include-use-node-version.yml@self
144144

145-
# Install ff_pipeline_host dependencies (provides telemetry-generator and trips-setup/cleanup)
145+
# Install FluidFramework dependencies
146146
- template: /tools/pipelines/templates/include-install.yml@self
147147
parameters:
148-
buildDirectory: $(FFPipelineHostDirectory)
148+
buildDirectory: $(FluidFrameworkDirectory)
149+
installDependenciesDescription: Install FluidFramework dependencies
150+
installPnpm: true
149151
primaryRegistry: $(ado-feeds-ff-download-only)
152+
# The compat-workspaces lockfile is committed to the FluidFramework repo and changes
153+
# whenever legacy package versions are added or removed. Including it in the pnpm store
154+
# cache key ensures a cache miss whenever the set of compat packages changes.
155+
# The ff_pipeline_host lockfile is included so the cache key also invalidates when the
156+
# internal tooling dependencies change.
157+
additionalLockFilePaths: '$(FluidFrameworkDirectory)/packages/test/test-version-utils/compat-workspaces/full/pnpm-lock.yaml | $(FFPipelineHostDirectory)/pnpm-lock.yaml'
150158

151-
# Install FluidFramework dependencies
159+
# Install ff_pipeline_host dependencies (provides telemetry-generator and trips-setup/cleanup)
152160
- template: /tools/pipelines/templates/include-install.yml@self
153161
parameters:
154-
buildDirectory: $(FluidFrameworkDirectory)
162+
buildDirectory: $(FFPipelineHostDirectory)
163+
installDependenciesDescription: Install ff_pipeline_host dependencies
164+
# Note that skipping pnpm install means client pnpm version must suffice for ff_pipeline_host.
165+
# TODO #74732: consider separating pnpm stores and caches if later ff_pipeline_host
166+
# steps can remain version agnostic and then this install can be restored to take
167+
# place before FluidFramework install.
168+
installPnpm: false
155169
primaryRegistry: $(ado-feeds-ff-download-only)
156170

157171
# Build the workspace so compiled test code is available for benchmarks

0 commit comments

Comments
 (0)