Skip to content

Commit 7516d74

Browse files
kittenmeta-codesync[bot]
authored andcommitted
Update generate-artifacts-executor scripts to skip write/cp for unchanged outputs (facebook#55532)
Summary: > Side-note: I've been maintaining branches to test these changes for 3 months since around 0.83/0.84, but was waiting for other changes in `scripts/codegen` to stabilise and for 0.84 to land to not interfere with other changes, and to instead target 0.85. Currently, the codegen scripts seem to interfere with Xcode's caching. This can be validated against any app by closing and re-opening Xcode and rebuilding an app that's already been built. Many source files and libraries affected by codegen will start re-building from scratch, skipping the cache. The changes in facebook@c029032 skip writing to a temporary path and let the codegen script directly write to the output paths, since `moveOutputs` calling `cp` is skipped. However, the codegen scripts still write and copy files to the output directory, even if they exist and haven't changed. This change will replace `writeFileSync` and `cpSync` calls with wrappers that skip writing if the target exists and is identical. ## Changelog: [IOS] [INTERNAL] - Skip writing codegen outputs when outputs are unchanged Pull Request resolved: facebook#55532 Test Plan: I've been mainly testing this in [`expo/expo`'s `notification-tester`](https://github.com/expo/expo/tree/main/apps/notification-tester) since it's a small app with few dependencies and builds relatively quickly for testing. - Build: etc, `pod install`, `xed ios`, build in Xcode - Rebuild (ground-truth): close and re-open Xcode, build again - Copy new scripts into `node_modules/react-native/scripts/codegen/generate-artifacts-executor` - Rebuild: close and re-open Xcode, build again - Rebuild (2): close and re-open Xcode, build again The re-build timings and target files can then be compared between "Rebuild (ground-truth)" and "Rebuild (2)". The latter will have a very high cache-hit rate and the build completes in about ~10s (instead of ~20s) skipping almost all compilation. I've verified that running `pod install` with the new scripts still works. The re-built files in that case are identical between the old and new scripts. Reviewed By: cortinico Differential Revision: D93099065 Pulled By: cipolleschi fbshipit-source-id: c3da043935d7056990b11204868aaa62ce6b0f7d
1 parent 9bc7d38 commit 7516d74

File tree

9 files changed

+70
-21
lines changed

9 files changed

+70
-21
lines changed

packages/react-native/scripts/codegen/generate-artifacts-executor/generateAppDependencyProvider.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
'use strict';
1212
const {TEMPLATES_FOLDER_PATH, packageJson} = require('./constants');
13-
const {codegenLog} = require('./utils');
13+
const {codegenLog, writeFileSyncIfChanged} = require('./utils');
1414
const fs = require('fs');
1515
const path = require('path');
1616

@@ -38,15 +38,15 @@ function generateAppDependencyProvider(outputDir /*: string */) {
3838
'utf8',
3939
);
4040
const finalPathH = path.join(outputDir, 'RCTAppDependencyProvider.h');
41-
fs.writeFileSync(finalPathH, templateH);
41+
writeFileSyncIfChanged(finalPathH, templateH);
4242
codegenLog(`Generated artifact: ${finalPathH}`);
4343

4444
const templateMM = fs.readFileSync(
4545
APP_DEPENDENCY_PROVIDER_MM_TEMPLATE_PATH,
4646
'utf8',
4747
);
4848
const finalPathMM = path.join(outputDir, 'RCTAppDependencyProvider.mm');
49-
fs.writeFileSync(finalPathMM, templateMM);
49+
writeFileSyncIfChanged(finalPathMM, templateMM);
5050
codegenLog(`Generated artifact: ${finalPathMM}`);
5151

5252
// Generate the podspec file
@@ -58,7 +58,7 @@ function generateAppDependencyProvider(outputDir /*: string */) {
5858
outputDir,
5959
'ReactAppDependencyProvider.podspec',
6060
);
61-
fs.writeFileSync(finalPathPodspec, templatePodspec);
61+
writeFileSyncIfChanged(finalPathPodspec, templatePodspec);
6262
codegenLog(`Generated podspec: ${finalPathPodspec}`);
6363
}
6464

packages/react-native/scripts/codegen/generate-artifacts-executor/generateCustomURLHandlers.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
'use strict';
1212

1313
const {TEMPLATES_FOLDER_PATH} = require('./constants');
14-
const {parseiOSAnnotations} = require('./utils');
14+
const {parseiOSAnnotations, writeFileSyncIfChanged} = require('./utils');
1515
const fs = require('fs');
1616
const path = require('path');
1717

@@ -105,13 +105,13 @@ function generateCustomURLHandlers(
105105

106106
fs.mkdirSync(outputDir, {recursive: true});
107107

108-
fs.writeFileSync(
108+
writeFileSyncIfChanged(
109109
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.mm'),
110110
finalMMFile,
111111
);
112112

113113
const templateH = fs.readFileSync(MODULES_PROTOCOLS_H_TEMPLATE_PATH, 'utf8');
114-
fs.writeFileSync(
114+
writeFileSyncIfChanged(
115115
path.join(outputDir, 'RCTModulesConformingToProtocolsProvider.h'),
116116
templateH,
117117
);

packages/react-native/scripts/codegen/generate-artifacts-executor/generateNativeCode.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
const generateSpecsCLIExecutor = require('../generate-specs-cli-executor');
1414
const {CORE_LIBRARIES_WITH_OUTPUT_FOLDER} = require('./constants');
15-
const {codegenLog} = require('./utils');
15+
const {codegenLog, cpSyncRecursiveIfChanged} = require('./utils');
1616
const fs = require('fs');
1717
const os = require('os');
1818
const path = require('path');
@@ -63,8 +63,7 @@ function generateCode(
6363
const outputDir =
6464
reactNativeCoreLibraryOutputPath(libraryName, platform) ?? outputPath;
6565
fs.mkdirSync(outputDir, {recursive: true});
66-
// $FlowFixMe[prop-missing] - `fs.cpSync` is missing in Flow libdefs.
67-
fs.cpSync(tmpOutputDir, outputDir, {recursive: true});
66+
cpSyncRecursiveIfChanged(tmpOutputDir, outputDir);
6867
codegenLog(`Generated artifacts: ${outputDir}`);
6968
}
7069

packages/react-native/scripts/codegen/generate-artifacts-executor/generatePackageSwift.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
'use strict';
1212
const {TEMPLATES_FOLDER_PATH} = require('./constants');
13-
const {codegenLog} = require('./utils');
13+
const {codegenLog, writeFileSyncIfChanged} = require('./utils');
1414
const fs = require('fs');
1515
const path = require('path');
1616

@@ -35,7 +35,7 @@ function generatePackageSwift(
3535
path.relative(fullOutputPath, reactNativePath),
3636
);
3737
const finalPathH = path.join(outputDir, 'Package.swift');
38-
fs.writeFileSync(finalPathH, templateH);
38+
writeFileSyncIfChanged(finalPathH, templateH);
3939
codegenLog(`Generated artifact: ${finalPathH}`);
4040
}
4141

packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTModuleProviders.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
codegenLog,
1515
isReactNativeCoreLibrary,
1616
parseiOSAnnotations,
17+
writeFileSyncIfChanged,
1718
} = require('./utils');
1819
const fs = require('fs');
1920
const path = require('path');
@@ -39,7 +40,7 @@ function generateRCTModuleProviders(
3940
codegenLog('Generating RCTModulesProvider.h');
4041
const templateH = fs.readFileSync(MODULE_PROVIDERS_H_TEMPLATE_PATH, 'utf8');
4142
const finalPathH = path.join(outputDir, 'RCTModuleProviders.h');
42-
fs.writeFileSync(finalPathH, templateH);
43+
writeFileSyncIfChanged(finalPathH, templateH);
4344
codegenLog(`Generated artifact: ${finalPathH}`);
4445

4546
codegenLog('Generating RCTModuleProviders.mm');
@@ -112,7 +113,7 @@ function generateRCTModuleProviders(
112113
.readFileSync(MODULE_PROVIDERS_MM_TEMPLATE_PATH, 'utf8')
113114
.replace(/{moduleMapping}/, modulesMapping);
114115
const finalPathMM = path.join(outputDir, 'RCTModuleProviders.mm');
115-
fs.writeFileSync(finalPathMM, templateMM);
116+
writeFileSyncIfChanged(finalPathMM, templateMM);
116117
codegenLog(`Generated artifact: ${finalPathMM}`);
117118
}
118119

packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
codegenLog,
1616
isReactNativeCoreLibrary,
1717
parseiOSAnnotations,
18+
writeFileSyncIfChanged,
1819
} = require('./utils');
1920
const fs = require('fs');
2021
const path = require('path');
@@ -41,7 +42,7 @@ function generateRCTThirdPartyComponents(
4142
'utf8',
4243
);
4344
const finalPathH = path.join(outputDir, 'RCTThirdPartyComponentsProvider.h');
44-
fs.writeFileSync(finalPathH, templateH);
45+
writeFileSyncIfChanged(finalPathH, templateH);
4546
codegenLog(`Generated artifact: ${finalPathH}`);
4647

4748
codegenLog('Generating RCTThirdPartyComponentsProvider.mm');
@@ -150,7 +151,7 @@ function generateRCTThirdPartyComponents(
150151
outputDir,
151152
'RCTThirdPartyComponentsProvider.mm',
152153
);
153-
fs.writeFileSync(finalPathMM, templateMM);
154+
writeFileSyncIfChanged(finalPathMM, templateMM);
154155
codegenLog(`Generated artifact: ${finalPathMM}`);
155156
}
156157

packages/react-native/scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const {
1515
TEMPLATES_FOLDER_PATH,
1616
packageJson,
1717
} = require('./constants');
18-
const {codegenLog} = require('./utils');
18+
const {codegenLog, writeFileSyncIfChanged} = require('./utils');
1919
const {execSync} = require('child_process');
2020
const fs = require('fs');
2121
const path = require('path');
@@ -39,7 +39,7 @@ function generateReactCodegenPodspec(
3939
.replace(/{input-files}/, inputFiles)
4040
.replace(/{codegen-script}/, codegenScript);
4141
const finalPathPodspec = path.join(outputPath, 'ReactCodegen.podspec');
42-
fs.writeFileSync(finalPathPodspec, finalPodspec);
42+
writeFileSyncIfChanged(finalPathPodspec, finalPodspec);
4343
codegenLog(`Generated podspec: ${finalPathPodspec}`);
4444
}
4545

packages/react-native/scripts/codegen/generate-artifacts-executor/generateUnstableModulesRequiringMainQueueSetupProvider.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
'use strict';
1212

1313
const {TEMPLATES_FOLDER_PATH} = require('./constants');
14-
const {parseiOSAnnotations} = require('./utils');
14+
const {parseiOSAnnotations, writeFileSyncIfChanged} = require('./utils');
1515
const fs = require('fs');
1616
const path = require('path');
1717

@@ -70,7 +70,7 @@ function generateUnstableModulesRequiringMainQueueSetupProvider(
7070

7171
fs.mkdirSync(outputDir, {recursive: true});
7272

73-
fs.writeFileSync(
73+
writeFileSyncIfChanged(
7474
path.join(
7575
outputDir,
7676
'RCTUnstableModulesRequiringMainQueueSetupProvider.mm',
@@ -82,7 +82,7 @@ function generateUnstableModulesRequiringMainQueueSetupProvider(
8282
UNSTABLE_MODULES_REQUIRING_MAIN_QUEUE_SETUP_PROVIDER_H_TEMPLATE_PATH,
8383
'utf8',
8484
);
85-
fs.writeFileSync(
85+
writeFileSyncIfChanged(
8686
path.join(outputDir, 'RCTUnstableModulesRequiringMainQueueSetupProvider.h'),
8787
templateH,
8888
);

packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,52 @@ function findReactNativeRootPath(projectRoot /* : string */) /* : string */ {
485485
return path.dirname(reactNativePackageJsonPath);
486486
}
487487

488+
function writeFileSyncIfChanged(
489+
targetPath /*: string */,
490+
contents /*: string */,
491+
) {
492+
try {
493+
const oldContents = fs.readFileSync(targetPath, 'utf8');
494+
if (oldContents === contents) {
495+
return;
496+
}
497+
} catch (error) {
498+
if (error.code !== 'ENOENT') {
499+
throw error;
500+
}
501+
}
502+
fs.writeFileSync(targetPath, contents);
503+
}
504+
505+
function cpSyncRecursiveIfChanged(
506+
sourcePath /*: string */,
507+
targetPath /*: string */,
508+
) {
509+
fs.cpSync(sourcePath, targetPath, {
510+
recursive: true,
511+
force: true,
512+
preserveTimestamps: true,
513+
filter(src /*: string */, dest /*: string */) {
514+
try {
515+
const stat = fs.statSync(src);
516+
if (!stat.isFile()) {
517+
return true;
518+
} else {
519+
const oldContents = fs.readFileSync(dest, 'utf8');
520+
const newContents = fs.readFileSync(src, 'utf8');
521+
return oldContents !== newContents;
522+
}
523+
} catch (error) {
524+
if (error.code !== 'ENOENT' && error.code !== 'EISDIR') {
525+
throw error;
526+
} else {
527+
return true;
528+
}
529+
}
530+
},
531+
});
532+
}
533+
488534
module.exports = {
489535
buildCodegenIfNeeded,
490536
pkgJsonIncludesGeneratedCode,
@@ -499,4 +545,6 @@ module.exports = {
499545
readReactNativeConfig,
500546
findDisabledLibrariesByPlatform,
501547
findReactNativeRootPath,
548+
writeFileSyncIfChanged,
549+
cpSyncRecursiveIfChanged,
502550
};

0 commit comments

Comments
 (0)