diff --git a/.circleci/config.yml b/.circleci/config.yml index b5f7604059020..bc1db679ec7d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -170,16 +170,28 @@ jobs: - *restore_node_modules - run: yarn build-combined - persist_to_workspace: - root: build2 + root: . paths: - - facebook-www - - facebook-react-native - - facebook-relay - - oss-stable - - oss-experimental - - react-native - - dist - - sizes/*.json + - build2 + + get_base_build: + docker: *docker + environment: *environment + steps: + - checkout + - run: yarn workspaces info | head -n -1 > workspace_info.txt + - *restore_node_modules + - run: + name: Download artifacts for base revision + command: | + git fetch origin master + cd ./scripts/release && yarn && cd ../../ + scripts/release/download-experimental-build.js --commit=$(git merge-base HEAD origin/master) + mv ./build2 ./base-build + - persist_to_workspace: + root: . + paths: + - base-build process_artifacts_combined: docker: *docker @@ -187,14 +199,28 @@ jobs: steps: - checkout - attach_workspace: - at: build2 + at: . - run: yarn workspaces info | head -n -1 > workspace_info.txt - *restore_node_modules + - run: echo "<< pipeline.git.revision >>" >> build2/COMMIT_SHA # Compress build directory into a single tarball for easy download - run: tar -zcvf ./build2.tgz ./build2 - store_artifacts: path: ./build2.tgz + sizebot: + docker: *docker + environment: *environment + steps: + - checkout + - attach_workspace: + at: . + - run: echo "<< pipeline.git.revision >>" >> build2/COMMIT_SHA + - run: yarn workspaces info | head -n -1 > workspace_info.txt + - *restore_node_modules + - run: + command: node ./scripts/tasks/danger + build_devtools_and_process_artifacts: docker: *docker environment: *environment @@ -261,38 +287,6 @@ jobs: process_artifacts: *process_artifacts process_artifacts_experimental: *process_artifacts - sizebot_stable: - docker: *docker - environment: *environment - steps: - - checkout - - attach_workspace: *attach_workspace - - run: yarn workspaces info | head -n -1 > workspace_info.txt - - *restore_node_modules - # This runs in the process_artifacts job, too, but it's faster to run - # this step in both jobs instead of running the jobs sequentially - - run: node ./scripts/rollup/consolidateBundleSizes.js - - run: - environment: - RELEASE_CHANNEL: stable - command: node ./scripts/tasks/danger - - sizebot_experimental: - docker: *docker - environment: *environment - steps: - - checkout - - attach_workspace: *attach_workspace - - run: yarn workspaces info | head -n -1 > workspace_info.txt - - *restore_node_modules - # This runs in the process_artifacts job, too, but it's faster to run - # this step in both jobs instead of running the jobs sequentially - - run: node ./scripts/rollup/consolidateBundleSizes.js - - run: - environment: - RELEASE_CHANNEL: experimental - command: node ./scripts/tasks/danger - yarn_lint_build: docker: *docker environment: *environment @@ -341,7 +335,7 @@ jobs: steps: - checkout - attach_workspace: - at: build2 + at: . - run: yarn workspaces info | head -n -1 > workspace_info.txt - *restore_node_modules - run: yarn test --build <> --ci @@ -394,9 +388,6 @@ workflows: - process_artifacts: requires: - RELEASE_CHANNEL_stable_yarn_build - - sizebot_stable: - requires: - - RELEASE_CHANNEL_stable_yarn_build - RELEASE_CHANNEL_stable_yarn_lint_build: requires: - RELEASE_CHANNEL_stable_yarn_build @@ -413,9 +404,6 @@ workflows: - process_artifacts_experimental: requires: - yarn_build - - sizebot_experimental: - requires: - - yarn_build - yarn_lint_build: requires: - yarn_build @@ -495,6 +483,21 @@ workflows: # - "-r=www-modern --env=production --variant" # TODO: Test more persistent configurations? + - get_base_build: + filters: + branches: + ignore: + - master + requires: + - setup + - sizebot: + filters: + branches: + ignore: + - master + requires: + - get_base_build + - yarn_build_combined fuzz_tests: triggers: - schedule: diff --git a/dangerfile.js b/dangerfile.js index 86e7e422a5b40..209d7a284926d 100644 --- a/dangerfile.js +++ b/dangerfile.js @@ -26,34 +26,10 @@ // `DANGER_GITHUB_API_TOKEN=[ENV_ABOVE] yarn danger pr https://github.com/facebook/react/pull/11865 const {markdown, danger, warn} = require('danger'); -const fetch = require('node-fetch'); const {generateResultsArray} = require('./scripts/rollup/stats'); -const {existsSync, readFileSync} = require('fs'); -const {exec} = require('child_process'); - -// This must match the name of the CI job that creates the build artifacts -const RELEASE_CHANNEL = - process.env.RELEASE_CHANNEL === 'experimental' ? 'experimental' : 'stable'; -const artifactsJobName = - process.env.RELEASE_CHANNEL === 'experimental' - ? 'process_artifacts_experimental' - : 'process_artifacts'; - -if (!existsSync('./build/bundle-sizes.json')) { - // This indicates the build failed previously. - // In that case, there's nothing for the Dangerfile to do. - // Exit early to avoid leaving a redundant (and potentially confusing) PR comment. - warn( - 'No bundle size information found. This indicates the build ' + - 'job failed.' - ); - process.exit(0); -} - -const currentBuildResults = JSON.parse( - readFileSync('./build/bundle-sizes.json') -); +const {readFileSync, readdirSync} = require('fs'); +const path = require('path'); /** * Generates a Markdown table @@ -100,99 +76,23 @@ function setBoldness(row, isBold) { } } -/** - * Gets the commit that represents the merge between the current branch - * and master. - */ -function git(args) { - return new Promise(res => { - exec('git ' + args, (err, stdout, stderr) => { - if (err) { - throw err; - } else { - res(stdout.trim()); - } - }); - }); -} - -(async function() { - // Use git locally to grab the commit which represents the place - // where the branches differ - const upstreamRepo = danger.github.pr.base.repo.full_name; - if (upstreamRepo !== 'facebook/react') { - // Exit unless we're running in the main repo - return; - } - - markdown(`## Size changes (${RELEASE_CHANNEL})`); - - const upstreamRef = danger.github.pr.base.ref; - await git(`remote add upstream https://github.com/facebook/react.git`); - await git('fetch upstream'); - const baseCommit = await git(`merge-base HEAD upstream/${upstreamRef}`); - - let previousBuildResults = null; - try { - let baseCIBuildId = null; - const statusesResponse = await fetch( - `https://api.github.com/repos/facebook/react/commits/${baseCommit}/status` - ); - const {statuses, state} = await statusesResponse.json(); - if (state === 'failure') { - warn(`Base commit is broken: ${baseCommit}`); - return; - } - for (let i = 0; i < statuses.length; i++) { - const status = statuses[i]; - if (status.context === `ci/circleci: ${artifactsJobName}`) { - if (status.state === 'success') { - baseCIBuildId = /\/facebook\/react\/([0-9]+)/.exec( - status.target_url - )[1]; - break; - } - if (status.state === 'pending') { - warn(`Build job for base commit is still pending: ${baseCommit}`); - return; - } - } +function getBundleSizes(pathToSizesDir) { + const filenames = readdirSync(pathToSizesDir); + let bundleSizes = []; + for (let i = 0; i < filenames.length; i++) { + const filename = filenames[i]; + if (filename.endsWith('.json')) { + const json = readFileSync(path.join(pathToSizesDir, filename)); + bundleSizes.push(...JSON.parse(json).bundleSizes); } - - if (baseCIBuildId === null) { - warn(`Could not find build artifacts for base commit: ${baseCommit}`); - return; - } - - const baseArtifactsInfoResponse = await fetch( - `https://circleci.com/api/v1.1/project/github/facebook/react/${baseCIBuildId}/artifacts` - ); - const baseArtifactsInfo = await baseArtifactsInfoResponse.json(); - - for (let i = 0; i < baseArtifactsInfo.length; i++) { - const info = baseArtifactsInfo[i]; - if (info.path.endsWith('bundle-sizes.json')) { - const resultsResponse = await fetch(info.url); - previousBuildResults = await resultsResponse.json(); - break; - } - } - } catch (error) { - warn(`Failed to fetch build artifacts for base commit: ${baseCommit}`); - return; - } - - if (previousBuildResults === null) { - warn(`Could not find build artifacts for base commit: ${baseCommit}`); - return; } + return {bundleSizes}; +} +async function printResultsForChannel(baseResults, headResults) { // Take the JSON of the build response and // make an array comparing the results for printing - const results = generateResultsArray( - currentBuildResults, - previousBuildResults - ); + const results = generateResultsArray(baseResults, headResults); const packagesToShow = results .filter( @@ -281,15 +181,62 @@ function git(args) {
Details of bundled changes. -

Comparing: ${baseCommit}...${danger.github.pr.head.sha}

- - ${allTables.join('\n')}
`; - markdown(summary); + return summary; } else { - markdown('No significant bundle size changes to report.'); + return 'No significant bundle size changes to report.'; } +} + +(async function() { + // Use git locally to grab the commit which represents the place + // where the branches differ + + const upstreamRepo = danger.github.pr.base.repo.full_name; + if (upstreamRepo !== 'facebook/react') { + // Exit unless we're running in the main repo + return; + } + + let headSha; + let headSizesStable; + let headSizesExperimental; + + let baseSha; + let baseSizesStable; + let baseSizesExperimental; + + try { + headSha = (readFileSync('./build2/COMMIT_SHA') + '').trim(); + headSizesStable = getBundleSizes('./build2/sizes-stable'); + headSizesExperimental = getBundleSizes('./build2/sizes-experimental'); + + baseSha = (readFileSync('./base-build/COMMIT_SHA') + '').trim(); + baseSizesStable = getBundleSizes('./base-build/sizes-stable'); + baseSizesExperimental = getBundleSizes('./base-build/sizes-experimental'); + } catch { + warn( + "Failed to read build artifacts. It's possible a build configuration " + + 'has changed upstream. Try pulling the latest changes from the ' + + 'main branch.' + ); + return; + } + + markdown(` +## Size changes + +

Comparing: ${baseSha}...${headSha}

+ +### Stable channel + +${await printResultsForChannel(baseSizesStable, headSizesStable)} + +### Experimental channel + +${await printResultsForChannel(baseSizesExperimental, headSizesExperimental)} +`); })(); diff --git a/scripts/release/download-experimental-build.js b/scripts/release/download-experimental-build.js index e722306d6039c..7f8b11dba122d 100755 --- a/scripts/release/download-experimental-build.js +++ b/scripts/release/download-experimental-build.js @@ -9,7 +9,6 @@ const { handleError, } = require('./utils'); -const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); const downloadBuildArtifacts = require('./shared-commands/download-build-artifacts'); const parseParams = require('./shared-commands/parse-params'); const printSummary = require('./download-experimental-build-commands/print-summary'); @@ -26,7 +25,6 @@ const run = async () => { params.cwd = join(__dirname, '..', '..'); params.packages = await getPublicPackages(true); - await checkEnvironmentVariables(params); await downloadBuildArtifacts(params); printSummary(params); diff --git a/scripts/release/get-build-id-for-commit.js b/scripts/release/get-build-id-for-commit.js index fca6d0280d3a3..15cbe7de52c46 100644 --- a/scripts/release/get-build-id-for-commit.js +++ b/scripts/release/get-build-id-for-commit.js @@ -35,13 +35,15 @@ async function getBuildIdForCommit(sha) { if (status.state === 'failure') { throw new Error(`Build job for commit failed: ${sha}`); } - if (Date.now() < retryLimit) { - await wait(POLLING_INTERVAL); - continue retry; - } - throw new Error('Exceeded retry limit. Build job is still pending.'); } } + if (state === 'pending') { + if (Date.now() < retryLimit) { + await wait(POLLING_INTERVAL); + continue retry; + } + throw new Error('Exceeded retry limit. Build job is still pending.'); + } throw new Error('Could not find build for commit: ' + sha); } } diff --git a/scripts/release/prepare-release-from-ci.js b/scripts/release/prepare-release-from-ci.js index 76885ff7af9ad..3ec76a6a7a463 100755 --- a/scripts/release/prepare-release-from-ci.js +++ b/scripts/release/prepare-release-from-ci.js @@ -5,7 +5,6 @@ const {join} = require('path'); const {addDefaultParamValue, handleError} = require('./utils'); -const checkEnvironmentVariables = require('./shared-commands/check-environment-variables'); const downloadBuildArtifacts = require('./shared-commands/download-build-artifacts'); const parseParams = require('./shared-commands/parse-params'); const printPrereleaseSummary = require('./shared-commands/print-prerelease-summary'); @@ -19,7 +18,6 @@ const run = async () => { const params = await parseParams(); params.cwd = join(__dirname, '..', '..'); - await checkEnvironmentVariables(params); await downloadBuildArtifacts(params); if (!params.skipTests) { diff --git a/scripts/release/shared-commands/check-environment-variables.js b/scripts/release/shared-commands/check-environment-variables.js deleted file mode 100644 index a041d399678dd..0000000000000 --- a/scripts/release/shared-commands/check-environment-variables.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const theme = require('../theme'); - -module.exports = () => { - if (!process.env.CIRCLE_CI_API_TOKEN) { - console.error( - theme` - {error Missing CircleCI API token} - - The CircleCI API is used to download build artifacts. - This API requires a token which must be exposed via a {underline CIRCLE_CI_API_TOKEN} environment var. - In order to run this script you will need to create your own API token. - Instructions can be found at: - - {link https://app.circleci.com/settings/user/tokens} - - To make this token available to the release script, add it to your {path .bash_profile} like so: - - {dimmed # React release script} - export CIRCLE_CI_API_TOKEN= - ` - .replace(/\n +/g, '\n') - .trim() - ); - process.exit(1); - } -}; diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 3aa81329c166c..ca9803b3b3693 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -58,31 +58,8 @@ const extractCommitFromVersionNumber = version => { }; const getArtifactsList = async buildID => { - const buildMetadataURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildID}?circle-token=${process.env.CIRCLE_CI_API_TOKEN}`; - const buildMetadata = await http.get(buildMetadataURL, true); - if (!buildMetadata.workflows || !buildMetadata.workflows.workflow_id) { - console.log( - theme`{error Could not find workflow info for build ${buildID}.}` - ); - process.exit(1); - } - const artifactsJobName = 'process_artifacts_combined'; - const workflowID = buildMetadata.workflows.workflow_id; - const workflowMetadataURL = `https://circleci.com/api/v2/workflow/${workflowID}/job?circle-token=${process.env.CIRCLE_CI_API_TOKEN}`; - const workflowMetadata = await http.get(workflowMetadataURL, true); - const job = workflowMetadata.items.find( - ({name}) => name === artifactsJobName - ); - if (!job || !job.job_number) { - console.log( - theme`{error Could not find "${artifactsJobName}" job for workflow ${workflowID}.}` - ); - process.exit(1); - } - - const jobArtifactsURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${job.job_number}/artifacts?circle-token=${process.env.CIRCLE_CI_API_TOKEN}`; + const jobArtifactsURL = `https://circleci.com/api/v1.1/project/github/facebook/react/${buildID}/artifacts`; const jobArtifacts = await http.get(jobArtifactsURL, true); - return jobArtifacts; };