Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: speed up isolated next instance test setup #56525

Merged
merged 9 commits into from
Oct 6, 2023
182 changes: 109 additions & 73 deletions .github/actions/next-stats-action/src/prepare/repo-setup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const path = require('path')
const fs = require('fs-extra')
const fse = require('fs-extra')
const fs = require('fs')
const fsp = require('fs/promises')
const exec = require('../util/exec')
const { remove } = require('fs-extra')
const logger = require('../util/logger')
Expand Down Expand Up @@ -52,13 +54,25 @@ module.exports = (actionInfo) => {
}
}
},
async linkPackages({ repoDir, nextSwcVersion }) {
async linkPackages({ repoDir, nextSwcVersion, usePnpmPack = false }) {
const pkgPaths = new Map()

/**
* @typedef {Object} PkgData
* @property {string} pkgDataPath Where the package.json file is located
* @property {string} pkg The folder name of the package
* @property {string} pkgPath The path to the package folder
* @property {any} pkgData The content of package.json
* @property {string} packedPkgPath The npm pack output .tgz file path
*/

/** @type {Map<string, PkgData>} */
const pkgDatas = new Map()

let pkgs

try {
pkgs = await fs.readdir(path.join(repoDir, 'packages'))
pkgs = await fsp.readdir(path.join(repoDir, 'packages'))
} catch (err) {
if (err.code === 'ENOENT') {
require('console').log('no packages to link')
Expand All @@ -67,69 +81,81 @@ module.exports = (actionInfo) => {
throw err
}

for (const pkg of pkgs) {
const pkgPath = path.join(repoDir, 'packages', pkg)
const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`)

const pkgDataPath = path.join(pkgPath, 'package.json')
if (!fs.existsSync(pkgDataPath)) {
require('console').log(`Skipping ${pkgDataPath}`)
continue
}
const pkgData = require(pkgDataPath)
const { name } = pkgData

pkgDatas.set(name, {
pkgDataPath,
pkg,
pkgPath,
pkgData,
packedPkgPath,
await Promise.all(
pkgs.map(async (pkg) => {
const pkgPath = path.join(repoDir, 'packages', pkg)
const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`)

const pkgDataPath = path.join(pkgPath, 'package.json')
if (fs.existsSync(pkgDataPath)) {
const pkgData = JSON.parse(await fsp.readFile(pkgDataPath))
huozhi marked this conversation as resolved.
Show resolved Hide resolved
const { name } = pkgData

pkgDatas.set(name, {
pkgDataPath,
pkg,
pkgPath,
pkgData,
packedPkgPath,
})
pkgPaths.set(name, packedPkgPath)
} else {
require('console').log(`Skipping ${pkgDataPath}`)
}
})
pkgPaths.set(name, packedPkgPath)
}

for (const pkg of pkgDatas.keys()) {
const { pkgDataPath, pkgData } = pkgDatas.get(pkg)
)

for (const pkg of pkgDatas.keys()) {
const { packedPkgPath } = pkgDatas.get(pkg)
for (const [
pkg,
{ pkgDataPath, pkgData, pkgPath },
] of pkgDatas.entries()) {
// update the current package dependencies to point to packed tgz path
for (const [pkg, { packedPkgPath }] of pkgDatas.entries()) {
if (!pkgData.dependencies || !pkgData.dependencies[pkg]) continue
pkgData.dependencies[pkg] = packedPkgPath
}

// make sure native binaries are included in local linking
if (pkg === '@next/swc') {
if (!pkgData.files) {
pkgData.files = []
}
pkgData.files ||= []

pkgData.files.push('native')
require('console').log(
'using swc binaries: ',
await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`)
)
}

if (pkg === 'next') {
try {
const swcBinariesDirContents = await fsp.readdir(
path.join(pkgPath, 'native')
)
require('console').log(
'using swc binaries: ',
swcBinariesDirContents.join(', ')
)
} catch (err) {
if (err.code === 'ENOENT') {
require('console').log('swc binaries dir is missing!')
}
throw err
}
} else if (pkg === 'next') {
const nextSwcPkg = pkgDatas.get('@next/swc')

console.log('using swc dep', {
nextSwcVersion,
nextSwcPkg: pkgDatas.get('@next/swc'),
nextSwcPkg,
})
if (nextSwcVersion) {
Object.assign(pkgData.dependencies, {
'@next/swc-linux-x64-gnu': nextSwcVersion,
})
} else {
if (pkgDatas.get('@next/swc')) {
pkgData.dependencies['@next/swc'] =
pkgDatas.get('@next/swc').packedPkgPath
if (nextSwcPkg) {
pkgData.dependencies['@next/swc'] = nextSwcPkg.packedPkgPath
} else {
pkgData.files.push('native')
}
}
}

await fs.writeFile(
await fsp.writeFile(
pkgDataPath,
JSON.stringify(pkgData, null, 2),
'utf8'
Expand All @@ -139,41 +165,51 @@ module.exports = (actionInfo) => {
// wait to pack packages until after dependency paths have been updated
// to the correct versions
await Promise.all(
Array.from(pkgDatas.keys()).map(async (pkgName) => {
const { pkgPath, packedPkgPath } = pkgDatas.get(pkgName)

let cleanup = null

if (pkgName === '@next/swc') {
// next-swc uses a gitignore to prevent the committing of native builds but it doesn't
// use files in package.json because it publishes to individual packages based on architecture.
// When we used yarn to pack these packages the gitignore was ignored so the native builds were packed
// however npm does respect gitignore when packing so we need to remove it in this specific case
// to ensure the native builds are packed for use in gh actions and related scripts
await fs.rename(
path.join(pkgPath, 'native/.gitignore'),
path.join(pkgPath, 'disabled-native-gitignore')
)
cleanup = async () => {
await fs.rename(
path.join(pkgPath, 'disabled-native-gitignore'),
path.join(pkgPath, 'native/.gitignore')
Array.from(pkgDatas.entries()).map(
async ([pkgName, { pkgPath, packedPkgPath }]) => {
/** @type {null | () => Promise<void>} */
let cleanup = null

if (pkgName === '@next/swc') {
// next-swc uses a gitignore to prevent the committing of native builds but it doesn't
// use files in package.json because it publishes to individual packages based on architecture.
// When we used yarn to pack these packages the gitignore was ignored so the native builds were packed
// however npm does respect gitignore when packing so we need to remove it in this specific case
// to ensure the native builds are packed for use in gh actions and related scripts

const nativeGitignorePath = path.join(
pkgPath,
'native/.gitignore'
)
const renamedGitignorePath = path.join(
pkgPath,
'disabled-native-gitignore'
)

await fsp.rename(nativeGitignorePath, renamedGitignorePath)
cleanup = async () => {
await fsp.rename(renamedGitignorePath, nativeGitignorePath)
}
}
}

const { stdout } = await execa('npm', ['pack'], {
cwd: pkgPath,
env: {
...process.env,
COREPACK_ENABLE_STRICT: '0',
},
})
await fs.rename(path.resolve(pkgPath, stdout.trim()), packedPkgPath)
if (cleanup) {
await cleanup()
const { stdout } = await execa(
usePnpmPack ? 'pnpm' : 'npm',
huozhi marked this conversation as resolved.
Show resolved Hide resolved
['pack'],
{
cwd: pkgPath,
env: {
...process.env,
COREPACK_ENABLE_STRICT: '0',
},
}
)

return Promise.all([
fsp.rename(path.resolve(pkgPath, stdout.trim()), packedPkgPath),
cleanup?.(),
])
}
})
)
)
return pkgPaths
},
Expand Down
15 changes: 8 additions & 7 deletions test/lib/create-next-install.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const { linkPackages } =
* @param {string} cwd - The project directory where pnpm configuration is set.
* @returns {Promise<void>}
*/
async function setPnpmResolutionMode(cwd) {
await execa(
function setPnpmResolutionMode(cwd) {
return execa(
'pnpm',
['config', 'set', '--location=project', 'resolution-mode', 'highest'],
{
Expand Down Expand Up @@ -99,8 +99,8 @@ async function createNextInstall({
for (const item of ['package.json', 'packages']) {
await rootSpan
.traceChild(`copy ${item} to temp dir`)
.traceAsyncFn(async () => {
await fs.copy(
.traceAsyncFn(() =>
fs.copy(
path.join(origRepoDir, item),
path.join(tmpRepoDir, item),
{
Expand All @@ -116,12 +116,13 @@ async function createNextInstall({
},
}
)
})
)
}

pkgPaths = await rootSpan.traceChild('linkPackages').traceAsyncFn(() =>
linkPackages({
repoDir: tmpRepoDir,
usePnpmPack: true,
})
)
}
Expand Down Expand Up @@ -177,7 +178,7 @@ async function createNextInstall({
} else {
await rootSpan
.traceChild('run generic install command')
.traceAsyncFn(async () => {
.traceAsyncFn(() => {
const args = [
'install',
'--strict-peer-dependencies=false',
Expand All @@ -188,7 +189,7 @@ async function createNextInstall({
args.push('--prefer-offline')
}

await execa('pnpm', args, {
return execa('pnpm', args, {
cwd: installDir,
stdio: ['ignore', 'inherit', 'inherit'],
env: process.env,
Expand Down
Loading