Summary: Pull Request resolved: https://github.com/facebook/yoga/pull/1506 In order to build the website, you must build a Yoga binary. This usually requires installing native toolchains. We have warning messages for these right now, but an even better solution is to just manage the dependencies ourselves. This does that, bringing in specific CMake and Ninja binaries from NPM, and caching a local copy of Emscripten during the build. A downside is that the CMake packages are chunky, so we add 130MB to node_modules (for a repo total around 350MB). This also delays acquiring Emscripten (which is even chunkier) in CI builds until it is needed, and I added some caching for it as well. The upside of JS users being able to run and test (inc the website) without installing and managing their own versions of toolchains is a real time-saver though, and is probably worth it. allow-large-files Reviewed By: yungsters Differential Revision: D52013026 fbshipit-source-id: 3d307f751463a21c5e5d5b98b8e9e63db9d3d52e
237 lines
5.3 KiB
JavaScript
237 lines
5.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
const {
|
|
argv,
|
|
cleanTask,
|
|
logger,
|
|
jestTask,
|
|
option,
|
|
parallel,
|
|
series,
|
|
spawn,
|
|
task,
|
|
tscTask,
|
|
} = require('just-scripts');
|
|
|
|
const {existsSync} = require('fs');
|
|
const {readFile, writeFile, rm} = require('fs/promises');
|
|
|
|
const glob = require('glob');
|
|
const path = require('path');
|
|
const which = require('which');
|
|
|
|
const node = process.execPath;
|
|
|
|
option('fix');
|
|
|
|
task('clean', cleanTask({paths: ['.emsdk', 'binaries', 'build']}));
|
|
|
|
task(
|
|
'build',
|
|
series(installEmsdkTask(), emcmakeGenerateTask(), cmakeBuildTask()),
|
|
);
|
|
|
|
task(
|
|
'test',
|
|
series(
|
|
'build',
|
|
jestTask({
|
|
config: path.join(__dirname, 'jest.config.js'),
|
|
nodeArgs: ['--experimental-vm-modules'],
|
|
}),
|
|
),
|
|
);
|
|
|
|
task('benchmark', series('build', runBenchTask()));
|
|
|
|
task('clang-format', clangFormatTask({fix: argv().fix}));
|
|
|
|
task('prepack-package-json', async () => {
|
|
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
const packageJsonContents = await readFile(packageJsonPath);
|
|
const packageJson = JSON.parse(packageJsonContents.toString('utf-8'));
|
|
|
|
recursiveReplace(packageJson, /(.\/src\/.*)\.ts/, '$1.js');
|
|
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
});
|
|
|
|
task(
|
|
'prepack',
|
|
series(
|
|
parallel('build', tscTask({emitDeclarationOnly: true})),
|
|
babelTransformTask({dir: 'src'}),
|
|
'prepack-package-json',
|
|
),
|
|
);
|
|
|
|
function recursiveReplace(obj, pattern, replacement) {
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
if (typeof value === 'string') {
|
|
obj[key] = value.replace(pattern, replacement);
|
|
} else if (typeof value === 'object' && value != null) {
|
|
recursiveReplace(value, pattern, replacement);
|
|
}
|
|
}
|
|
}
|
|
|
|
function babelTransformTask(opts) {
|
|
return () => {
|
|
const args = [
|
|
opts.dir,
|
|
'--source-maps',
|
|
'--out-dir',
|
|
opts.dir,
|
|
'--extensions',
|
|
'.js,.cjs,.mjs,.ts,.cts,.mts',
|
|
];
|
|
logger.info(`Transforming "${path.resolve(opts.dir)}"`);
|
|
|
|
return spawn(node, [require.resolve('@babel/cli/bin/babel'), ...args], {
|
|
cwd: __dirname,
|
|
env: {
|
|
// Trigger distribution-specific Babel transforms
|
|
NODE_ENV: 'dist',
|
|
},
|
|
});
|
|
};
|
|
}
|
|
|
|
function runBenchTask() {
|
|
return () => {
|
|
const files = glob.sync('./tests/Benchmarks/**/*');
|
|
|
|
const args = [
|
|
'--loader=babel-register-esm',
|
|
'./tests/bin/run-bench.ts',
|
|
...files,
|
|
];
|
|
logger.info(['node', ...args].join(' '));
|
|
|
|
return spawn(node, args, {
|
|
stdio: 'inherit',
|
|
});
|
|
};
|
|
}
|
|
|
|
const emsdkVersion = '3.1.28';
|
|
const emsdkPath = path.join(__dirname, '.emsdk');
|
|
const emsdkBin = path.join(
|
|
emsdkPath,
|
|
process.platform === 'win32' ? 'emsdk.bat' : 'emsdk',
|
|
);
|
|
const emcmakeBin = path.join(
|
|
emsdkPath,
|
|
'upstream',
|
|
'emscripten',
|
|
process.platform === 'win32' ? 'emcmake.bat' : 'emcmake',
|
|
);
|
|
|
|
function installEmsdkTask() {
|
|
return async () => {
|
|
if (await isEmsdkReadyAndActivated()) {
|
|
logger.verbose(
|
|
`emsdk ${emsdkVersion} is already installed and activated`,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
logger.info(`installing emsdk ${emsdkVersion} to ${emsdkPath}`);
|
|
await rm(emsdkPath, {recursive: true, force: true});
|
|
|
|
await spawn(
|
|
'git',
|
|
['clone', 'https://github.com/emscripten-core/emsdk.git', emsdkPath],
|
|
{stdio: 'inherit'},
|
|
);
|
|
|
|
await spawn(emsdkBin, ['install', emsdkVersion], {stdio: 'inherit'});
|
|
|
|
await spawn(emsdkBin, ['activate', emsdkVersion], {
|
|
stdio: logger.enableVerbose ? 'inherit' : 'ignore',
|
|
});
|
|
};
|
|
}
|
|
|
|
async function isEmsdkReadyAndActivated() {
|
|
if (!existsSync(emcmakeBin)) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const emsdkReleases = JSON.parse(
|
|
await readFile(path.join(emsdkPath, 'emscripten-releases-tags.json')),
|
|
).releases;
|
|
|
|
const versionHash = emsdkReleases[emsdkVersion];
|
|
if (!versionHash) {
|
|
return false;
|
|
}
|
|
|
|
const activatedVersion = await readFile(
|
|
path.join(emsdkPath, 'upstream', '.emsdk_version'),
|
|
);
|
|
|
|
return activatedVersion.toString().includes(versionHash);
|
|
} catch {
|
|
// Something is wrong. Pave and redo.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function emcmakeGenerateTask() {
|
|
return () => {
|
|
logger.verbose(`emcmake path: ${emcmakeBin}`);
|
|
|
|
const args = [
|
|
'cmake',
|
|
'-S',
|
|
'.',
|
|
'-B',
|
|
'build',
|
|
...(process.platform === 'win32' ? [] : ['-G', 'Ninja']),
|
|
];
|
|
logger.info(['emcmake', ...args].join(' '));
|
|
|
|
return spawn(emcmakeBin, args, {
|
|
stdio: logger.enableVerbose ? 'inherit' : 'ignore',
|
|
});
|
|
};
|
|
}
|
|
|
|
function cmakeBuildTask(opts) {
|
|
return () => {
|
|
const cmake = which.sync('cmake');
|
|
logger.verbose(`cmake path: ${cmake}`);
|
|
|
|
const args = [
|
|
'--build',
|
|
'build',
|
|
...(opts?.targets ? ['--target', ...opts.targets] : []),
|
|
];
|
|
logger.info(['cmake', ...args].join(' '));
|
|
|
|
return spawn(cmake, args, {stdio: 'inherit'});
|
|
};
|
|
}
|
|
|
|
function clangFormatTask(opts) {
|
|
return () => {
|
|
const args = [
|
|
...(opts?.fix ? ['-i'] : ['--dry-run', '--Werror']),
|
|
...glob.sync('**/*.{h,hh,hpp,c,cpp,cc,m,mm}'),
|
|
];
|
|
logger.info(['clang-format', ...args].join(' '));
|
|
|
|
return spawn(node, [require.resolve('clang-format'), ...args], {
|
|
stdio: 'inherit',
|
|
});
|
|
};
|
|
}
|