From 6c59086ed80d6d8cafc6e6533da086ec4ffa2e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 11 Jun 2026 11:37:33 +0200 Subject: [PATCH 1/2] Return Gradle cache env from restore flow --- .../src/builders/__tests__/android.test.ts | 32 +++++++++++++++++ packages/build-tools/src/builders/android.ts | 11 ++++-- .../src/steps/functions/restoreBuildCache.ts | 36 +++++++++++++------ 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/packages/build-tools/src/builders/__tests__/android.test.ts b/packages/build-tools/src/builders/__tests__/android.test.ts index b6c706d601..b7f83173dd 100644 --- a/packages/build-tools/src/builders/__tests__/android.test.ts +++ b/packages/build-tools/src/builders/__tests__/android.test.ts @@ -13,6 +13,10 @@ import { injectConfigureVersionGradleConfig, injectCredentialsGradleConfig, } from '../../steps/utils/android/gradleConfig'; +import { + logGradleCacheEnv, + restoreGradleCacheAsync, +} from '../../steps/functions/restoreBuildCache'; jest.mock('../common', () => ({ runBuilderWithHooksAsync: jest.fn(async (ctx, buildFn) => { @@ -41,10 +45,13 @@ jest.mock('../../common/setup', () => ({ })); jest.mock('../../steps/functions/restoreBuildCache', () => ({ cacheStatsAsync: jest.fn(), + logGradleCacheEnv: jest.fn(), restoreCcacheAsync: jest.fn(), + restoreGradleCacheAsync: jest.fn(async () => ({ env: {} })), })); jest.mock('../../steps/functions/saveBuildCache', () => ({ saveCcacheAsync: jest.fn(), + saveGradleCacheAsync: jest.fn(), })); jest.mock('../../steps/utils/android/gradleConfig', () => ({ ...jest.requireActual('../../steps/utils/android/gradleConfig'), @@ -130,6 +137,7 @@ describe(androidBuilder, () => { logBuffer: { getLogs: () => [], getPhaseLogs: () => [] }, logger: createMockLogger(), env: { + EAS_BUILD_RUNNER: 'eas-build', __API_SERVER_URL: 'http://api.expo.test', }, uploadArtifact: jest.fn(), @@ -145,6 +153,30 @@ describe(androidBuilder, () => { expect(injectConfigureVersionGradleConfig).not.toHaveBeenCalled(); }); + it('logs Gradle cache environment variables returned by restoreGradleCacheAsync', async () => { + jest.mocked(restoreGradleCacheAsync).mockResolvedValueOnce({ + env: { + 'ORG_GRADLE_PROJECT_org.gradle.caching': 'true', + }, + }); + const ctx = new BuildContext(createTestAndroidJob(), { + workingdir: '/workingdir', + logBuffer: { getLogs: () => [], getPhaseLogs: () => [] }, + logger: createMockLogger(), + env: { + EAS_BUILD_RUNNER: 'eas-build', + __API_SERVER_URL: 'http://api.expo.test', + }, + uploadArtifact: jest.fn(), + }); + + await androidBuilder(ctx); + + expect(logGradleCacheEnv).toHaveBeenCalledWith(expect.anything(), { + 'ORG_GRADLE_PROJECT_org.gradle.caching': 'true', + }); + }); + it('marks the configure Android version phase as warning for legacy eas-build.gradle', async () => { const job: Android.Job = { ...createTestAndroidJob(), diff --git a/packages/build-tools/src/builders/android.ts b/packages/build-tools/src/builders/android.ts index 6ccff6ca09..d2ea784f5f 100644 --- a/packages/build-tools/src/builders/android.ts +++ b/packages/build-tools/src/builders/android.ts @@ -1,4 +1,4 @@ -import { Android, BuildMode, BuildPhase, Workflow } from '@expo/eas-build-job'; +import { Android, BuildMode, BuildPhase, BuildTrigger, Workflow } from '@expo/eas-build-job'; import nullthrows from 'nullthrows'; import path from 'path'; @@ -18,6 +18,7 @@ import { setupAsync } from '../common/setup'; import { Artifacts, BuildContext, SkipNativeBuildError } from '../context'; import { cacheStatsAsync, + logGradleCacheEnv, restoreCcacheAsync, restoreGradleCacheAsync, } from '../steps/functions/restoreBuildCache'; @@ -88,12 +89,18 @@ async function buildAsync(ctx: BuildContext): Promise { env: ctx.env, secrets: ctx.job.secrets, }); - await restoreGradleCacheAsync({ + const { env } = await restoreGradleCacheAsync({ logger: ctx.logger, workingDirectory, env: ctx.env, secrets: ctx.job.secrets, }); + if (Object.keys(env).length > 0) { + if (ctx.job.triggeredBy === BuildTrigger.GIT_BASED_INTEGRATION) { + ctx.updateEnv(env); + } + logGradleCacheEnv(ctx.logger, env); + } }); await ctx.runBuildPhase(BuildPhase.POST_INSTALL_HOOK, async () => { diff --git a/packages/build-tools/src/steps/functions/restoreBuildCache.ts b/packages/build-tools/src/steps/functions/restoreBuildCache.ts index de9826b0ef..2f3462cc10 100644 --- a/packages/build-tools/src/steps/functions/restoreBuildCache.ts +++ b/packages/build-tools/src/steps/functions/restoreBuildCache.ts @@ -57,12 +57,19 @@ export function createRestoreBuildCacheFunction(): BuildFunction { }); if (platform === Platform.ANDROID) { - await restoreGradleCacheAsync({ + const { env: gradleCacheEnv } = await restoreGradleCacheAsync({ logger, workingDirectory, env, secrets: stepCtx.global.staticContext.job.secrets, }); + if (Object.keys(gradleCacheEnv).length > 0) { + stepCtx.global.updateEnv({ + ...stepCtx.global.env, + ...gradleCacheEnv, + }); + logGradleCacheEnv(logger, gradleCacheEnv); + } } }, }); @@ -200,19 +207,16 @@ export async function restoreGradleCacheAsync({ workingDirectory: string; env: Record; secrets?: { robotAccessToken?: string }; -}): Promise { +}): Promise<{ env: Record }> { if (env.EAS_GRADLE_CACHE !== '1') { - return; + return { env: {} }; } - try { - const gradlePropertiesPath = path.join(workingDirectory, 'android', 'gradle.properties'); - const gradlePropertiesContent = await fs.promises.readFile(gradlePropertiesPath, 'utf-8'); - await fs.promises.writeFile( - gradlePropertiesPath, - `${gradlePropertiesContent}\n\norg.gradle.caching=true\n` - ); + const gradleCacheEnv = { + 'ORG_GRADLE_PROJECT_org.gradle.caching': 'true', + }; + try { // Configure cache cleanup via init script (works with both Gradle 8 and 9, // org.gradle.cache.cleanup property was removed in Gradle 9) const initScriptDir = path.join(os.homedir(), '.gradle', 'init.d'); @@ -294,6 +298,18 @@ export async function restoreGradleCacheAsync({ logger.warn('Failed to restore Gradle cache: ', err); } } + + return { env: gradleCacheEnv }; +} + +export function logGradleCacheEnv(logger: bunyan, env: Record): void { + logger.info( + `Enabling Gradle cache. Running Gradle with additional environment variables.\n${Object.entries( + env + ) + .map(([key, value]) => `${key}=${value}`) + .join('\n')}` + ); } export async function cacheStatsAsync({ From 4f5bdcf06f1a955497995e9bd8d612cb864e1fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Thu, 11 Jun 2026 13:33:08 +0200 Subject: [PATCH 2/2] Apply Gradle cache env in Android builder --- packages/build-tools/src/builders/__tests__/android.test.ts | 1 - packages/build-tools/src/builders/android.ts | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/build-tools/src/builders/__tests__/android.test.ts b/packages/build-tools/src/builders/__tests__/android.test.ts index b7f83173dd..611467c293 100644 --- a/packages/build-tools/src/builders/__tests__/android.test.ts +++ b/packages/build-tools/src/builders/__tests__/android.test.ts @@ -137,7 +137,6 @@ describe(androidBuilder, () => { logBuffer: { getLogs: () => [], getPhaseLogs: () => [] }, logger: createMockLogger(), env: { - EAS_BUILD_RUNNER: 'eas-build', __API_SERVER_URL: 'http://api.expo.test', }, uploadArtifact: jest.fn(), diff --git a/packages/build-tools/src/builders/android.ts b/packages/build-tools/src/builders/android.ts index d2ea784f5f..bf91763486 100644 --- a/packages/build-tools/src/builders/android.ts +++ b/packages/build-tools/src/builders/android.ts @@ -1,4 +1,4 @@ -import { Android, BuildMode, BuildPhase, BuildTrigger, Workflow } from '@expo/eas-build-job'; +import { Android, BuildMode, BuildPhase, Workflow } from '@expo/eas-build-job'; import nullthrows from 'nullthrows'; import path from 'path'; @@ -96,9 +96,7 @@ async function buildAsync(ctx: BuildContext): Promise { secrets: ctx.job.secrets, }); if (Object.keys(env).length > 0) { - if (ctx.job.triggeredBy === BuildTrigger.GIT_BASED_INTEGRATION) { - ctx.updateEnv(env); - } + Object.assign(ctx.env, env); logGradleCacheEnv(ctx.logger, env); } });