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

chore(NODE-6186): add downloading to FLE build script #5

Merged
merged 10 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 148 additions & 38 deletions .github/scripts/libmongocrypt.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,42 @@ import fs from 'node:fs/promises';
import child_process from 'node:child_process';
import events from 'node:events';
import path from 'node:path';
import https from 'node:https';
import stream from 'node:stream/promises';
import url from 'node:url';

const __dirname = path.dirname(url.fileURLToPath(import.meta.url));

/** Resolves to the root of this repository */
function resolveRoot(...paths) {
return path.resolve(__dirname, '..', '..', ...paths);
}

async function exists(fsPath) {
try {
await fs.access(fsPath);
return true;
} catch {
return false;
}
}

async function parseArguments() {
const jsonImport = { [process.version.split('.').at(0) === 'v16' ? 'assert' : 'with']: { type: 'json' } };
const pkg = (await import('../../package.json', jsonImport)).default;
const libmongocryptVersion = pkg['mongodb:libmongocrypt'];
const pkg = JSON.parse(await fs.readFile(resolveRoot('package.json'), 'utf8'));

const options = {
url: { short: 'u', type: 'string', default: 'https://github.com/mongodb/libmongocrypt.git' },
libversion: { short: 'l', type: 'string', default: libmongocryptVersion },
clean: { short: 'c', type: 'boolean' },
help: { short: 'h', type: 'boolean' }
gitURL: { short: 'u', type: 'string', default: 'https://github.com/mongodb/libmongocrypt.git' },
libVersion: { short: 'l', type: 'string', default: pkg['mongodb:libmongocrypt'] },
clean: { short: 'c', type: 'boolean', default: false },
build: { short: 'b', type: 'boolean', default: false },
help: { short: 'h', type: 'boolean', default: false }
};

const args = util.parseArgs({ args: process.argv.slice(2), options, allowPositionals: false });

if (args.values.help) {
console.log(
`${process.argv[1]} ${[...Object.keys(options)]
`${path.basename(process.argv[1])} ${[...Object.keys(options)]
.filter(k => k !== 'help')
.map(k => `[--${k}=${options[k].type}]`)
.join(' ')}`
Expand All @@ -30,46 +48,46 @@ async function parseArguments() {
}

return {
libmongocrypt: { url: args.values.url, ref: args.values.libversion },
clean: args.values.clean
libmongocrypt: { url: args.values.gitURL, ref: args.values.libVersion },
clean: args.values.clean,
build: args.values.build
};
}

/** `xtrace` style command runner, uses spawn so that stdio is inherited */
async function run(command, args = [], options = {}) {
console.error(`+ ${command} ${args.join(' ')}`, options.cwd ? `(in: ${options.cwd})` : '');
await events.once(child_process.spawn(command, args, { stdio: 'inherit', ...options }), 'exit');
const commandDetails = `+ ${command} ${args.join(' ')}${options.cwd ? ` (in: ${options.cwd})` : ''}`;
console.error(commandDetails);
const proc = child_process.spawn(command, args, {
stdio: 'inherit',
cwd: resolveRoot('.'),
...options
});
await events.once(proc, 'exit');

if (proc.exitCode != 0) throw new Error(`CRASH(${proc.exitCode}): ${commandDetails}`);
}

/** CLI flag maker: `toFlags({a: 1, b: 2})` yields `['-a=1', '-b=2']` */
function toFlags(object) {
return Array.from(Object.entries(object)).map(([k, v]) => `-${k}=${v}`);
}

const args = await parseArguments();
const libmongocryptRoot = path.resolve('_libmongocrypt');

const currentLibMongoCryptBranch = await fs.readFile(path.join(libmongocryptRoot, '.git', 'HEAD'), 'utf8').catch(() => '')
const libmongocryptAlreadyClonedAndCheckedOut = currentLibMongoCryptBranch.trim().endsWith(`r-${args.libmongocrypt.ref}`);

if (args.clean || !libmongocryptAlreadyClonedAndCheckedOut) {
console.error('fetching libmongocrypt...', args.libmongocrypt);
export async function cloneLibMongoCrypt(libmongocryptRoot, { url, ref }) {
console.error('fetching libmongocrypt...', { url, ref });
await fs.rm(libmongocryptRoot, { recursive: true, force: true });
await run('git', ['clone', args.libmongocrypt.url, libmongocryptRoot]);
await run('git', ['fetch', '--tags'], { cwd: libmongocryptRoot });
await run('git', ['checkout', args.libmongocrypt.ref, '-b', `r-${args.libmongocrypt.ref}`], { cwd: libmongocryptRoot });
} else {
console.error('libmongocrypt already up to date...', args.libmongocrypt);
await run('git', ['clone', url, libmongocryptRoot]);
if (ref !== 'latest') {
// Support "latest" as leaving the clone as-is so whatever the default branch name is works
await run('git', ['fetch', '--tags'], { cwd: libmongocryptRoot });
await run('git', ['checkout', ref, '-b', `r-${ref}`], { cwd: libmongocryptRoot });
}
}

const libmongocryptBuiltVersion = await fs.readFile(path.join(libmongocryptRoot, 'VERSION_CURRENT'), 'utf8').catch(() => '');
const libmongocryptAlreadyBuilt = libmongocryptBuiltVersion.trim() === args.libmongocrypt.ref;

if (args.clean || !libmongocryptAlreadyBuilt) {
console.error('building libmongocrypt...\n', args);
export async function buildLibMongoCrypt(libmongocryptRoot, nodeDepsRoot) {
console.error('building libmongocrypt...');

const nodeDepsRoot = path.resolve('deps');
const nodeBuildRoot = path.resolve(nodeDepsRoot, 'tmp', 'libmongocrypt-build');
const nodeBuildRoot = resolveRoot(nodeDepsRoot, 'tmp', 'libmongocrypt-build');

await fs.rm(nodeBuildRoot, { recursive: true, force: true });
await fs.mkdir(nodeBuildRoot, { recursive: true });
Expand Down Expand Up @@ -115,11 +133,103 @@ if (args.clean || !libmongocryptAlreadyBuilt) {
? toFlags({ DCMAKE_OSX_DEPLOYMENT_TARGET: '10.12' })
: [];

await run('cmake', [...CMAKE_FLAGS, ...WINDOWS_CMAKE_FLAGS, ...MACOS_CMAKE_FLAGS, libmongocryptRoot], { cwd: nodeBuildRoot });
await run('cmake', ['--build', '.', '--target', 'install', '--config', 'RelWithDebInfo'], { cwd: nodeBuildRoot });
} else {
console.error('libmongocrypt already built...');
await run(
'cmake',
[...CMAKE_FLAGS, ...WINDOWS_CMAKE_FLAGS, ...MACOS_CMAKE_FLAGS, libmongocryptRoot],
{ cwd: nodeBuildRoot }
);
await run('cmake', ['--build', '.', '--target', 'install', '--config', 'RelWithDebInfo'], {
cwd: nodeBuildRoot
});
}

export async function downloadLibMongoCrypt(nodeDepsRoot, { ref }) {
const downloadURL =
ref === 'latest'
? 'https://mciuploads.s3.amazonaws.com/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz'
: `https://mciuploads.s3.amazonaws.com/libmongocrypt/all/${ref}/libmongocrypt-all.tar.gz`;

console.error('downloading libmongocrypt...', downloadURL);
const destination = resolveRoot(`_libmongocrypt-${ref}`);

await fs.rm(destination, { recursive: true, force: true });
await fs.mkdir(destination);

const platformMatrix = {
['darwin-arm64']: 'macos',
['darwin-x64']: 'macos',
['linux-ppc64']: 'rhel-71-ppc64el',
['linux-s390x']: 'rhel72-zseries-test',
['linux-arm64']: 'ubuntu1804-arm64',
['linux-x64']: 'rhel-70-64-bit',
['win32-x64']: 'windows-test'
};

const detectedPlatform = `${process.platform}-${process.arch}`;
const prebuild = platformMatrix[detectedPlatform];
if (prebuild == null) throw new Error(`Unsupported: ${detectedPlatform}`);

console.error(`Platform: ${detectedPlatform} Prebuild: ${prebuild}`);

const unzipArgs = ['-xzv', '-C', destination, `${prebuild}/nocrypto`];
console.error(`+ tar ${unzipArgs.join(' ')}`);
const unzip = child_process.spawn('tar', unzipArgs, { stdio: ['pipe', 'inherit'] });

const [response] = await events.once(https.get(downloadURL), 'response');

const start = performance.now();
await stream.pipeline(response, unzip.stdin);
const end = performance.now();

console.error(`downloaded libmongocrypt in ${(end - start) / 1000} secs...`);

await fs.rm(nodeDepsRoot, { recursive: true, force: true });
await fs.cp(resolveRoot(destination, prebuild, 'nocrypto'), nodeDepsRoot, { recursive: true });
if (await exists(path.join(nodeDepsRoot, 'lib64'))) {
await fs.rename(path.join(nodeDepsRoot, 'lib64'), path.join(nodeDepsRoot, 'lib'));
}
durran marked this conversation as resolved.
Show resolved Hide resolved
}

async function main() {
const { libmongocrypt, build, clean } = await parseArguments();

const nodeDepsDir = resolveRoot('deps');

if (build) {
const libmongocryptCloneDir = resolveRoot('_libmongocrypt');

const currentLibMongoCryptBranch = await fs
.readFile(path.join(libmongocryptCloneDir, '.git', 'HEAD'), 'utf8')
.catch(() => '');
const isClonedAndCheckedOut = currentLibMongoCryptBranch
.trim()
.endsWith(`r-${libmongocrypt.ref}`);

if (clean || !isClonedAndCheckedOut) {
await cloneLibMongoCrypt(libmongocryptCloneDir, libmongocrypt);
}

const libmongocryptBuiltVersion = await fs
.readFile(path.join(libmongocryptCloneDir, 'VERSION_CURRENT'), 'utf8')
.catch(() => '');
const isBuilt = libmongocryptBuiltVersion.trim() === libmongocrypt.ref;

if (clean || !isBuilt) {
await buildLibMongoCrypt(libmongocryptCloneDir, nodeDepsDir);
}
} else {
// Download
await downloadLibMongoCrypt(nodeDepsDir, libmongocrypt);
}

await fs.rm(resolveRoot('build'), { force: true, recursive: true });
await fs.rm(resolveRoot('prebuilds'), { force: true, recursive: true });

// install with "ignore-scripts" so that we don't attempt to download a prebuild
await run('npm', ['install', '--ignore-scripts']);
// The prebuild command will make both a .node file in `./build` (local and CI testing will run on current code)
// it will also produce `./prebuild/xx.tgz`. prebuild has GH upload functionality.
await run('npm', ['run', 'prebuild']);
}

await run('npm', ['install', '--ignore-scripts']);
await run('npm', ['run', 'rebuild'], { env: { ...process.env, BUILD_TYPE: 'static' } });
await main();
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: {}

name: build
Expand All @@ -13,12 +15,16 @@ jobs:
node: ['20.x'] # '16.x', '18.x',
name: Node.js ${{ matrix.node }} build
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
registry-url: 'https://registry.npmjs.org'

- run: npm install -g npm@latest
shell: bash

- run: node .github/scripts/libmongocrypt.mjs
shell: bash
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ xunit.xml
lib
prebuilds

_libmongocrypt/
_libmongocrypt*
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"check:clang-format": "clang-format --style=file:.clang-format --dry-run --Werror addon/*",
"test": "mocha test",
"prepare": "tsc",
"rebuild": "prebuild --compile",
"prebuild": "prebuild --runtime napi --strip --verbose --all"
},
"author": {
Expand Down