Skip to content

Commit 3b3e374

Browse files
feat: support local cache for build:* commands (#446)
* feat: support local cache for build:* commands * fixup tests * save cache after build * feat: warn about ignored files affecting fingerprint (#450) * feat: warn about ignored files affecting fingerprint * changeset * Update packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts Co-authored-by: Szymon Rybczak <[email protected]> * fix: add ios/tmp.xcconfig from react-native-config to fingerprint ignores (#449) * fix: add ios/tmp.xcconfig from react-native-config to fingerprint ignores * changeset * changeset --------- Co-authored-by: Szymon Rybczak <[email protected]>
1 parent c3c3dec commit 3b3e374

File tree

14 files changed

+200
-90
lines changed

14 files changed

+200
-90
lines changed

.changeset/honest-brooms-type.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@rnef/platform-apple-helpers': patch
3+
'@rnef/plugin-brownfield-ios': patch
4+
'@rnef/platform-android': patch
5+
'@rnef/platform-ios': patch
6+
'@rnef/tools': patch
7+
---
8+
9+
feat: support local cache for build:\* commands

packages/platform-android/src/lib/commands/buildAndroid/__tests__/buildAndroid.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const args: BuildFlags = {
1414
variant: 'debug',
1515
activeArchOnly: false,
1616
extraParams: undefined,
17+
local: true,
1718
};
1819
const androidProject: AndroidProjectConfig = {
1920
appName: 'app',
@@ -73,6 +74,7 @@ test('buildAndroid runs gradle build with correct configuration for debug and ou
7374
androidProject,
7475
{ ...args, aab: true },
7576
'/root',
77+
null,
7678
fingerprintOptions
7779
);
7880

@@ -86,7 +88,7 @@ test('buildAndroid fails gracefully when gradle errors', async () => {
8688
vi.mocked(spawn).mockRejectedValueOnce({ stderr: 'gradle error' });
8789

8890
await expect(
89-
buildAndroid(androidProject, args, '/root', fingerprintOptions)
91+
buildAndroid(androidProject, args, '/root', null, fingerprintOptions)
9092
).rejects.toThrowErrorMatchingInlineSnapshot(
9193
`[RnefError: Failed to build the app. See the error above for details from Gradle.]`
9294
);

packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { AndroidProjectConfig } from '@react-native-community/cli-types';
2+
import type { RemoteBuildCache } from '@rnef/tools';
23
import {
34
colorLink,
45
type FingerprintSources,
56
formatArtifactName,
7+
getBinaryPath,
68
logger,
79
outro,
810
parseArgs,
@@ -18,12 +20,14 @@ export interface BuildFlags {
1820
activeArchOnly?: boolean;
1921
tasks?: Array<string>;
2022
extraParams?: Array<string>;
23+
local?: boolean;
2124
}
2225

2326
export async function buildAndroid(
2427
androidProject: AndroidProjectConfig,
2528
args: BuildFlags,
2629
projectRoot: string,
30+
remoteCacheProvider: null | (() => RemoteBuildCache) | undefined,
2731
fingerprintOptions: FingerprintSources
2832
) {
2933
normalizeArgs(args);
@@ -36,9 +40,19 @@ export async function buildAndroid(
3640
root: projectRoot,
3741
fingerprintOptions,
3842
});
39-
await runGradle({ tasks, androidProject, args, artifactName });
43+
const binaryPath = await getBinaryPath({
44+
artifactName,
45+
localFlag: args.local,
46+
remoteCacheProvider,
47+
fingerprintOptions,
48+
sourceDir: androidProject.sourceDir,
49+
});
50+
if (!binaryPath) {
51+
await runGradle({ tasks, androidProject, args, artifactName });
52+
}
4053

41-
const outputFilePath = await findOutputFile(androidProject, tasks);
54+
const outputFilePath =
55+
binaryPath ?? (await findOutputFile(androidProject, tasks));
4256

4357
if (outputFilePath) {
4458
logger.log(
@@ -85,4 +99,8 @@ export const options = [
8599
description: 'Custom params passed to gradle build command',
86100
parse: parseArgs,
87101
},
102+
{
103+
name: '--local',
104+
description: 'Force local build with Gradle wrapper.',
105+
},
88106
];

packages/platform-android/src/lib/commands/buildAndroid/command.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function registerBuildCommand(
2020
androidConfig,
2121
args as BuildFlags,
2222
api.getProjectRoot(),
23+
await api.getRemoteCacheProvider(),
2324
api.getFingerprintOptions()
2425
);
2526
},

packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import type {
77
import type { FingerprintSources, RemoteBuildCache } from '@rnef/tools';
88
import {
99
color,
10-
fetchCachedBuild,
1110
formatArtifactName,
12-
getLocalBuildCacheBinaryPath,
1311
intro,
1412
isInteractive,
1513
logger,
@@ -18,6 +16,7 @@ import {
1816
RnefError,
1917
spinner,
2018
} from '@rnef/tools';
19+
import { getBinaryPath } from '@rnef/tools';
2120
import type { BuildFlags } from '../buildAndroid/buildAndroid.js';
2221
import { options } from '../buildAndroid/buildAndroid.js';
2322
import { runGradle } from '../runGradle.js';
@@ -68,35 +67,14 @@ export async function runAndroid(
6867
root: projectRoot,
6968
fingerprintOptions,
7069
});
71-
// 1. First check if the binary path is provided
72-
let binaryPath = args.binaryPath;
73-
74-
// 2. If not, check if the local build is requested
75-
if (!binaryPath && !args.local) {
76-
binaryPath = getLocalBuildCacheBinaryPath(artifactName);
77-
}
78-
79-
// 3. If not, check if the remote cache is requested
80-
if (!binaryPath && !args.local) {
81-
try {
82-
const cachedBuild = await fetchCachedBuild({
83-
artifactName,
84-
remoteCacheProvider,
85-
});
86-
if (cachedBuild) {
87-
binaryPath = cachedBuild.binaryPath;
88-
}
89-
} catch (error) {
90-
const message = (error as RnefError).message;
91-
const cause = (error as RnefError).cause;
92-
logger.warn(
93-
`Failed to fetch cached build for ${artifactName}: \n${message}`,
94-
cause ? `\nCause: ${cause.toString()}` : ''
95-
);
96-
logger.debug('Remote cache failure error:', error);
97-
logger.info('Continuing with local build');
98-
}
99-
}
70+
const binaryPath = await getBinaryPath({
71+
artifactName,
72+
binaryPathFlag: args.binaryPath,
73+
localFlag: args.local,
74+
remoteCacheProvider,
75+
fingerprintOptions,
76+
sourceDir: androidProject.sourceDir,
77+
});
10078

10179
if (device) {
10280
if (!(await getDevices()).find((d) => d === device.deviceId)) {
@@ -298,8 +276,4 @@ export const runOptions = [
298276
name: '--user <number>',
299277
description: 'Id of the User Profile you want to install the app on.',
300278
},
301-
{
302-
name: '--local',
303-
description: 'Force local build with Gradle wrapper.',
304-
},
305279
];

packages/platform-apple-helpers/src/lib/commands/build/buildOptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type BuildFlags = {
1515
archive?: boolean;
1616
installPods: boolean;
1717
newArch: boolean;
18+
local?: boolean;
1819
};
1920

2021
export const getBuildOptions = ({ platformName }: BuilderCommand) => {
@@ -79,5 +80,9 @@ export const getBuildOptions = ({ platformName }: BuilderCommand) => {
7980
name: '--no-new-arch',
8081
description: 'Run React Native in legacy async architecture.',
8182
},
83+
{
84+
name: '--local',
85+
description: 'Force local build with xcodebuild.',
86+
},
8287
];
8388
};

packages/platform-apple-helpers/src/lib/commands/build/createBuild.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import fs from 'node:fs';
12
import path from 'node:path';
2-
import type { FingerprintSources } from '@rnef/tools';
3+
import type { FingerprintSources, RemoteBuildCache } from '@rnef/tools';
34
import {
45
colorLink,
56
formatArtifactName,
7+
getBinaryPath,
68
isInteractive,
79
logger,
810
promptSelect,
@@ -28,6 +30,7 @@ export const createBuild = async ({
2830
reactNativePath,
2931
fingerprintOptions,
3032
brownfield,
33+
remoteCacheProvider,
3134
}: {
3235
platformName: BuilderCommand['platformName'];
3336
projectConfig: ProjectConfig;
@@ -36,12 +39,13 @@ export const createBuild = async ({
3639
reactNativePath: string;
3740
fingerprintOptions: FingerprintSources;
3841
brownfield?: boolean;
42+
remoteCacheProvider: null | (() => RemoteBuildCache) | undefined;
3943
}) => {
4044
await validateArgs(args);
4145

4246
let xcodeProject: XcodeProjectInfo;
4347
let sourceDir: string;
44-
let scheme: string;
48+
let scheme: string | undefined;
4549
const deviceOrSimulator = args.destination
4650
? // there can be multiple destinations, so we'll pick the first one
4751
args.destination[0].match(/simulator/i)
@@ -54,6 +58,29 @@ export const createBuild = async ({
5458
root: projectRoot,
5559
fingerprintOptions,
5660
});
61+
const binaryPath = await getBinaryPath({
62+
artifactName,
63+
localFlag: args.local,
64+
remoteCacheProvider,
65+
fingerprintOptions,
66+
sourceDir: projectConfig.sourceDir,
67+
});
68+
69+
if (binaryPath) {
70+
logger.log(`Build available at: ${colorLink(relativeToCwd(binaryPath))}`);
71+
72+
if (args.archive) {
73+
const { exportDir } = getBuildPaths(platformName);
74+
if (fs.statSync(exportDir).isDirectory()) {
75+
logger.log(
76+
`Archives available at: ${colorLink(relativeToCwd(exportDir))}`
77+
);
78+
}
79+
}
80+
81+
return { scheme };
82+
}
83+
5784
try {
5885
const { appPath, ...buildAppResult } = await buildApp({
5986
projectRoot,
@@ -67,9 +94,8 @@ export const createBuild = async ({
6794

6895
xcodeProject = buildAppResult.xcodeProject;
6996
sourceDir = buildAppResult.sourceDir;
70-
// @ts-expect-error - scheme is not set when binaryPath is provided,
71-
// which is not supported for build command (but is used by run command)
7297
scheme = buildAppResult.scheme;
98+
saveLocalBuildCache(artifactName, appPath);
7399
} catch (error) {
74100
const message = `Failed to create ${args.archive ? 'archive' : 'build'}`;
75101
throw new RnefError(message, { cause: error });

packages/platform-apple-helpers/src/lib/commands/run/createRun.ts

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import path from 'node:path';
33
import type { FingerprintSources, RemoteBuildCache } from '@rnef/tools';
44
import {
55
color,
6-
fetchCachedBuild,
76
formatArtifactName,
8-
getLocalBuildCacheBinaryPath,
7+
getBinaryPath,
98
isInteractive,
109
logger,
1110
promptSelect,
@@ -59,35 +58,14 @@ export const createRun = async ({
5958
root: projectRoot,
6059
fingerprintOptions,
6160
});
62-
// 1. First check if the binary path is provided
63-
let binaryPath = args.binaryPath;
64-
65-
// 2. If not, check if the local build is requested
66-
if (!binaryPath && !args.local) {
67-
binaryPath = getLocalBuildCacheBinaryPath(artifactName);
68-
}
69-
70-
// 3. If not, check if the remote cache is requested
71-
if (!binaryPath && !args.local) {
72-
try {
73-
const cachedBuild = await fetchCachedBuild({
74-
artifactName,
75-
remoteCacheProvider,
76-
});
77-
if (cachedBuild) {
78-
binaryPath = cachedBuild.binaryPath;
79-
}
80-
} catch (error) {
81-
const message = (error as RnefError).message;
82-
const cause = (error as RnefError).cause;
83-
logger.warn(
84-
`Failed to fetch cached build for ${artifactName}: \n${message}`,
85-
cause ? `\nCause: ${cause.toString()}` : ''
86-
);
87-
logger.debug('Remote cache failure error:', error);
88-
logger.info('Continuing with local build');
89-
}
90-
}
61+
const binaryPath = await getBinaryPath({
62+
artifactName,
63+
binaryPathFlag: args.binaryPath,
64+
localFlag: args.local,
65+
remoteCacheProvider,
66+
fingerprintOptions,
67+
sourceDir: projectConfig.sourceDir,
68+
});
9169

9270
// Check if the device argument looks like a UDID
9371
// (assuming UDIDs are alphanumeric and have specific length)

packages/platform-apple-helpers/src/lib/commands/run/runOptions.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ export const getRunOptions = ({ platformName }: BuilderCommand) => {
2121
description:
2222
'Path relative to project root where pre-built .app binary lives.',
2323
},
24-
{
25-
name: '--local',
26-
description: 'Force local build with xcodebuild.',
27-
},
2824
{
2925
name: '--device <string>',
3026
description:

packages/platform-ios/src/lib/platformIOS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const platformIOS =
3535
projectRoot,
3636
reactNativePath: api.getReactNativePath(),
3737
fingerprintOptions: api.getFingerprintOptions(),
38+
remoteCacheProvider: await api.getRemoteCacheProvider(),
3839
});
3940
outro('Success 🎉.');
4041
},

0 commit comments

Comments
 (0)