Skip to content

Commit 79c4ec1

Browse files
cipolleschiRiccardo Cipolleschi
and
Riccardo Cipolleschi
authored
[LOCAL] Port CircleCI Artifact downloads to speed up release testing to 0.72 (#38553)
Co-authored-by: Riccardo Cipolleschi <[email protected]> resolved: #37971
1 parent ff37ddb commit 79c4ec1

File tree

7 files changed

+651
-139
lines changed

7 files changed

+651
-139
lines changed

scripts/circle-ci-artifacts-utils.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Copyright (c) Meta Platforms, Inc. and affiliates.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
const {exec} = require('shelljs');
14+
15+
const util = require('util');
16+
const asyncRequest = require('request');
17+
const request = util.promisify(asyncRequest);
18+
19+
let circleCIHeaders;
20+
let jobs;
21+
let baseTemporaryPath;
22+
23+
async function initialize(circleCIToken, baseTempPath, branchName) {
24+
console.info('Getting CircleCI information');
25+
circleCIHeaders = {'Circle-Token': circleCIToken};
26+
baseTemporaryPath = baseTempPath;
27+
exec(`mkdir -p ${baseTemporaryPath}`);
28+
const pipeline = await _getLastCircleCIPipelineID(branchName);
29+
const packageAndReleaseWorkflow = await _getPackageAndReleaseWorkflow(
30+
pipeline.id,
31+
);
32+
const testsWorkflow = await _getTestsWorkflow(pipeline.id);
33+
const jobsPromises = [
34+
_getCircleCIJobs(packageAndReleaseWorkflow.id),
35+
_getCircleCIJobs(testsWorkflow.id),
36+
];
37+
38+
const jobsResults = await Promise.all(jobsPromises);
39+
40+
jobs = jobsResults.flatMap(j => j);
41+
}
42+
43+
function baseTmpPath() {
44+
return baseTemporaryPath;
45+
}
46+
47+
async function _getLastCircleCIPipelineID(branchName) {
48+
const options = {
49+
method: 'GET',
50+
url: 'https://circleci.com/api/v2/project/gh/facebook/react-native/pipeline',
51+
qs: {
52+
branch: branchName,
53+
},
54+
headers: circleCIHeaders,
55+
};
56+
57+
const response = await request(options);
58+
if (response.error) {
59+
throw new Error(error);
60+
}
61+
62+
const items = JSON.parse(response.body).items;
63+
64+
if (!items || items.length === 0) {
65+
throw new Error(
66+
'No pipelines found on this branch. Make sure that the CI has run at least once, successfully',
67+
);
68+
}
69+
70+
const lastPipeline = items[0];
71+
return {id: lastPipeline.id, number: lastPipeline.number};
72+
}
73+
74+
async function _getSpecificWorkflow(pipelineId, workflowName) {
75+
const options = {
76+
method: 'GET',
77+
url: `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`,
78+
headers: circleCIHeaders,
79+
};
80+
const response = await request(options);
81+
if (response.error) {
82+
throw new Error(error);
83+
}
84+
85+
const body = JSON.parse(response.body);
86+
let workflow = body.items.find(w => w.name === workflowName);
87+
_throwIfWorkflowNotFound(workflow, workflowName);
88+
return workflow;
89+
}
90+
91+
function _throwIfWorkflowNotFound(workflow, name) {
92+
if (!workflow) {
93+
throw new Error(
94+
`Can't find a workflow named ${name}. Please check whether that workflow has started.`,
95+
);
96+
}
97+
}
98+
99+
async function _getPackageAndReleaseWorkflow(pipelineId) {
100+
return _getSpecificWorkflow(pipelineId, 'package_and_publish_release_dryrun');
101+
}
102+
103+
async function _getTestsWorkflow(pipelineId) {
104+
return _getSpecificWorkflow(pipelineId, 'tests');
105+
}
106+
107+
async function _getCircleCIJobs(workflowId) {
108+
const options = {
109+
method: 'GET',
110+
url: `https://circleci.com/api/v2/workflow/${workflowId}/job`,
111+
headers: circleCIHeaders,
112+
};
113+
const response = await request(options);
114+
if (response.error) {
115+
throw new Error(error);
116+
}
117+
118+
const body = JSON.parse(response.body);
119+
return body.items;
120+
}
121+
122+
async function _getJobsArtifacts(jobNumber) {
123+
const options = {
124+
method: 'GET',
125+
url: `https://circleci.com/api/v2/project/gh/facebook/react-native/${jobNumber}/artifacts`,
126+
headers: circleCIHeaders,
127+
};
128+
const response = await request(options);
129+
if (response.error) {
130+
throw new Error(error);
131+
}
132+
133+
const body = JSON.parse(response.body);
134+
return body.items;
135+
}
136+
137+
async function _findUrlForJob(jobName, artifactPath) {
138+
const job = jobs.find(j => j.name === jobName);
139+
_throwIfJobIsNull(job);
140+
_throwIfJobIsUnsuccessful(job);
141+
142+
const artifacts = await _getJobsArtifacts(job.job_number);
143+
return artifacts.find(artifact => artifact.path.indexOf(artifactPath) > -1)
144+
.url;
145+
}
146+
147+
function _throwIfJobIsNull(job) {
148+
if (!job) {
149+
throw new Error(
150+
`Can't find a job with name ${job.name}. Please verify that it has been executed and that all its dependencies completed successfully.`,
151+
);
152+
}
153+
}
154+
155+
function _throwIfJobIsUnsuccessful(job) {
156+
if (job.status !== 'success') {
157+
throw new Error(
158+
`The job ${job.name} status is ${job.status}. We need a 'success' status to proceed with the testing.`,
159+
);
160+
}
161+
}
162+
163+
async function artifactURLHermesDebug() {
164+
return _findUrlForJob('build_hermes_macos-Debug', 'hermes-ios-debug.tar.gz');
165+
}
166+
167+
async function artifactURLForMavenLocal() {
168+
return _findUrlForJob('build_and_publish_npm_package-2', 'maven-local.zip');
169+
}
170+
171+
async function artifactURLForHermesRNTesterAPK(emulatorArch) {
172+
return _findUrlForJob(
173+
'test_android',
174+
`rntester-apk/hermes/debug/app-hermes-${emulatorArch}-debug.apk`,
175+
);
176+
}
177+
178+
async function artifactURLForJSCRNTesterAPK(emulatorArch) {
179+
return _findUrlForJob(
180+
'test_android',
181+
`rntester-apk/jsc/debug/app-jsc-${emulatorArch}-debug.apk`,
182+
);
183+
}
184+
185+
function downloadArtifact(artifactURL, destination) {
186+
exec(`rm -rf ${destination}`);
187+
exec(`curl ${artifactURL} -Lo ${destination}`);
188+
}
189+
190+
module.exports = {
191+
initialize,
192+
downloadArtifact,
193+
artifactURLForJSCRNTesterAPK,
194+
artifactURLForHermesRNTesterAPK,
195+
artifactURLForMavenLocal,
196+
artifactURLHermesDebug,
197+
baseTmpPath,
198+
};

scripts/npm-utils.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
'use strict';
11+
12+
/**
13+
* `package` is an object form of package.json
14+
* `dependencies` is a map of dependency to version string
15+
*
16+
* This replaces both dependencies and devDependencies in package.json
17+
*/
18+
function applyPackageVersions(originalPackageJson, packageVersions) {
19+
const packageJson = {...originalPackageJson};
20+
21+
for (const name of Object.keys(packageVersions)) {
22+
if (
23+
packageJson.dependencies != null &&
24+
packageJson.dependencies[name] != null
25+
) {
26+
packageJson.dependencies[name] = packageVersions[name];
27+
}
28+
29+
if (
30+
packageJson.devDependencies != null &&
31+
packageJson.devDependencies[name] != null
32+
) {
33+
packageJson.devDependencies[name] = packageVersions[name];
34+
}
35+
}
36+
return packageJson;
37+
}
38+
39+
module.exports = {
40+
applyPackageVersions,
41+
};

scripts/release-utils.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,6 @@ function generateiOSArtifacts(
9191
) {
9292
pushd(`${hermesCoreSourceFolder}`);
9393

94-
//Need to generate hermesc
95-
exec(
96-
`${hermesCoreSourceFolder}/utils/build-hermesc-xcode.sh ${hermesCoreSourceFolder}/build_host_hermesc`,
97-
);
98-
9994
//Generating iOS Artifacts
10095
exec(
10196
`JSI_PATH=${jsiFolder} BUILD_TYPE=${buildType} ${hermesCoreSourceFolder}/utils/build-mac-framework.sh`,

scripts/test-e2e-local-clean.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ if (isPackagerRunning() === 'running') {
4444
console.info('\n** Cleaning Gradle build artifacts **\n');
4545
exec('./gradlew cleanAll');
4646
exec('rm -rf /tmp/maven-local');
47+
exec('rm -rf /tmp/react-native-tmp');
4748

4849
// iOS
4950
console.info('\n** Nuking the derived data folder **\n');
@@ -56,9 +57,6 @@ exec('rm -rf ~/Library/Caches/CocoaPods/Pods/External/hermes-engine');
5657
console.info('\n** Removing the RNTester Pods **\n');
5758
exec('rm -rf packages/rn-tester/Pods');
5859

59-
// I'm not sure we want to also remove the lock file
60-
// exec('rm -rf packages/rn-tester/Podfile.lock');
61-
6260
// RNTestProject
6361
console.info('\n** Removing the RNTestProject folder **\n');
6462
exec('rm -rf /tmp/RNTestProject');

0 commit comments

Comments
 (0)