From 4c934abbfbf7d4edc9db3cf25aa767f9c448dc0b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 13:42:43 -0400 Subject: [PATCH 01/10] chore: prefer downloading libmongocrypt to building --- .github/scripts/libmongocrypt.mjs | 99 ++++++++++++++++++++++++------- .gitignore | 2 +- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index 5846bd2..a328bfc 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -4,24 +4,29 @@ 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 { pipeline } from 'node:stream/promises'; async function parseArguments() { - const jsonImport = { [process.version.split('.').at(0) === 'v16' ? 'assert' : 'with']: { type: 'json' } }; + 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 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: libmongocryptVersion }, + 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(' ')}` @@ -30,8 +35,9 @@ 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 }; } @@ -49,24 +55,30 @@ function toFlags(object) { 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}`); +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) { +if (args.build && (args.clean || !libmongocryptAlreadyClonedAndCheckedOut)) { console.error('fetching libmongocrypt...', args.libmongocrypt); 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', ['checkout', args.libmongocrypt.ref, '-b', `r-${args.libmongocrypt.ref}`], { + cwd: libmongocryptRoot + }); } -const libmongocryptBuiltVersion = await fs.readFile(path.join(libmongocryptRoot, 'VERSION_CURRENT'), 'utf8').catch(() => ''); +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); +if (args.build && (args.clean || !libmongocryptAlreadyBuilt)) { + console.error('building libmongocrypt...\n', args.libmongocrypt); const nodeDepsRoot = path.resolve('deps'); const nodeBuildRoot = path.resolve(nodeDepsRoot, 'tmp', 'libmongocrypt-build'); @@ -115,10 +127,55 @@ 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 + }); +} + +if (!args.build) { + const downloadURL = + args.libmongocrypt.ref === 'latest' + ? 'https://mciuploads.s3.amazonaws.com/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz' + : `https://mciuploads.s3.amazonaws.com/libmongocrypt/all/${args.libmongocrypt.ref}/libmongocrypt-all.tar.gz`; + + console.error('downloading libmongocrypt...', downloadURL); + const destination = `_libmongocrypt-${args.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 platform = platformMatrix[`${process.platform}-${process.arch}`]; + if (platform == null) throw new Error(`${process.platform}-${process.arch}`); + + const unzip = child_process.spawn('tar', ['-xz', '-C', destination, ...new Set(Object.values(platformMatrix))], { + stdio: ['pipe'] + }); + + const [response] = await events.once(https.get(downloadURL), 'response'); + + const start = performance.now(); + await pipeline(response, unzip.stdin); + const end = performance.now(); + + console.error(`downloaded libmongocrypt in ${(end - start) / 1000} secs...`); + + await fs.rm('deps', { recursive: true, force: true }); + await fs.cp(path.join(destination, platform, 'nocrypto'), 'deps', { recursive: true }); } await run('npm', ['install', '--ignore-scripts']); diff --git a/.gitignore b/.gitignore index 677c37f..6e615cf 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,4 @@ xunit.xml lib prebuilds -_libmongocrypt/ +_libmongocrypt* From 6ebf689fc994786c9b38fc2616641f59f14a8b1b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 13:49:52 -0400 Subject: [PATCH 02/10] fix: lib64 on some platforms --- .github/scripts/libmongocrypt.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index a328bfc..9e210b3 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -162,7 +162,7 @@ if (!args.build) { const platform = platformMatrix[`${process.platform}-${process.arch}`]; if (platform == null) throw new Error(`${process.platform}-${process.arch}`); - const unzip = child_process.spawn('tar', ['-xz', '-C', destination, ...new Set(Object.values(platformMatrix))], { + const unzip = child_process.spawn('tar', ['-xz', '-C', destination, `${platform}/nocrypto`], { stdio: ['pipe'] }); @@ -176,6 +176,9 @@ if (!args.build) { await fs.rm('deps', { recursive: true, force: true }); await fs.cp(path.join(destination, platform, 'nocrypto'), 'deps', { recursive: true }); + if (await fs.access(path.join('deps', 'lib64')).then(() => true, () => false)) { + await fs.rename(path.join('deps', 'lib64'), path.join('deps', 'lib')); + } } await run('npm', ['install', '--ignore-scripts']); From bc62e1110e29a3996e94287f1a3236bce9d4b1bb Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 13:52:32 -0400 Subject: [PATCH 03/10] fix: import --- .github/scripts/libmongocrypt.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index 9e210b3..0a05d62 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -5,7 +5,7 @@ import child_process from 'node:child_process'; import events from 'node:events'; import path from 'node:path'; import https from 'node:https'; -import { pipeline } from 'node:stream/promises'; +import stream from 'node:stream/promises'; async function parseArguments() { const jsonImport = { @@ -169,7 +169,7 @@ if (!args.build) { const [response] = await events.once(https.get(downloadURL), 'response'); const start = performance.now(); - await pipeline(response, unzip.stdin); + await stream.pipeline(response, unzip.stdin); const end = performance.now(); console.error(`downloaded libmongocrypt in ${(end - start) / 1000} secs...`); From 8ef162d429f1ae19bca944ec94e7a55a03d2f07b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 14:42:42 -0400 Subject: [PATCH 04/10] chore: build on PR --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86d30cc..31599da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,8 @@ on: push: branches: [main] + pull_request: + branches: [main] workflow_dispatch: {} name: build From a787dbe465644560384bf9fb54f6b7dbe86ee37d Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 14:42:59 -0400 Subject: [PATCH 05/10] chore: yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31599da..fb815b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ on: push: branches: [main] pull_request: - branches: [main] + branches: [main] workflow_dispatch: {} name: build From d19019683b0fe2c4f45b2bbd1416b5e3c1067100 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 22 May 2024 15:02:01 -0400 Subject: [PATCH 06/10] =?UTF-8?q?chore:=20get=20code=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb815b2..ce15838 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,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 From a4ebe59ef57c38a48bd1179ebdd68457b8290d3d Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 23 May 2024 10:05:14 -0400 Subject: [PATCH 07/10] chore: make script work from any directory, organize steps into main and functions, remove rebuild and only use prebuild --- .github/scripts/libmongocrypt.mjs | 141 ++++++++++++++++++++---------- package.json | 1 - 2 files changed, 93 insertions(+), 49 deletions(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index 0a05d62..7782ea6 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -6,17 +6,30 @@ 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 = { gitURL: { short: 'u', type: 'string', default: 'https://github.com/mongodb/libmongocrypt.git' }, - libVersion: { short: 'l', type: 'string', default: libmongocryptVersion }, + 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 } @@ -44,7 +57,10 @@ async function parseArguments() { /** `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'); + await events.once( + child_process.spawn(command, args, { stdio: 'inherit', cwd: resolveRoot('.'), ...options }), + 'exit' + ); } /** CLI flag maker: `toFlags({a: 1, b: 2})` yields `['-a=1', '-b=2']` */ @@ -52,36 +68,21 @@ 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.build && (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 - }); + 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.build && (args.clean || !libmongocryptAlreadyBuilt)) { - console.error('building libmongocrypt...\n', args.libmongocrypt); +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 }); @@ -137,14 +138,14 @@ if (args.build && (args.clean || !libmongocryptAlreadyBuilt)) { }); } -if (!args.build) { +export async function downloadLibMongoCrypt(nodeDepsRoot, { ref }) { const downloadURL = - args.libmongocrypt.ref === 'latest' + ref === 'latest' ? 'https://mciuploads.s3.amazonaws.com/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz' - : `https://mciuploads.s3.amazonaws.com/libmongocrypt/all/${args.libmongocrypt.ref}/libmongocrypt-all.tar.gz`; + : `https://mciuploads.s3.amazonaws.com/libmongocrypt/all/${ref}/libmongocrypt-all.tar.gz`; console.error('downloading libmongocrypt...', downloadURL); - const destination = `_libmongocrypt-${args.libmongocrypt.ref}`; + const destination = resolveRoot(`_libmongocrypt-${ref}`); await fs.rm(destination, { recursive: true, force: true }); await fs.mkdir(destination); @@ -159,12 +160,15 @@ if (!args.build) { ['win32-x64']: 'windows-test' }; - const platform = platformMatrix[`${process.platform}-${process.arch}`]; - if (platform == null) throw new Error(`${process.platform}-${process.arch}`); + const detectedPlatform = `${process.platform}-${process.arch}`; + const prebuild = platformMatrix[detectedPlatform]; + if (prebuild == null) throw new Error(`Unsupported: ${detectedPlatform}`); - const unzip = child_process.spawn('tar', ['-xz', '-C', destination, `${platform}/nocrypto`], { - stdio: ['pipe'] - }); + 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'); @@ -174,12 +178,53 @@ if (!args.build) { console.error(`downloaded libmongocrypt in ${(end - start) / 1000} secs...`); - await fs.rm('deps', { recursive: true, force: true }); - await fs.cp(path.join(destination, platform, 'nocrypto'), 'deps', { recursive: true }); - if (await fs.access(path.join('deps', 'lib64')).then(() => true, () => false)) { - await fs.rename(path.join('deps', 'lib64'), path.join('deps', 'lib')); + 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')); } } -await run('npm', ['install', '--ignore-scripts']); -await run('npm', ['run', 'rebuild'], { env: { ...process.env, BUILD_TYPE: 'static' } }); +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 main(); diff --git a/package.json b/package.json index 76a75cd..090045a 100644 --- a/package.json +++ b/package.json @@ -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": { From edc5f267098de9009a7544fd82cedb525495dd5a Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 23 May 2024 10:39:29 -0400 Subject: [PATCH 08/10] chore: errexit --- .github/scripts/libmongocrypt.mjs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index 7782ea6..d30d071 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -56,11 +56,16 @@ async function parseArguments() { /** `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', cwd: resolveRoot('.'), ...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']` */ From 09ccdd310368e4a7786d59eae0a76eb90653eccc Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 29 May 2024 11:37:15 -0400 Subject: [PATCH 09/10] chore(NODE-6176): add prebuilds in CI (#7) --- .github/docker/Dockerfile.glibc | 11 +++++ .github/scripts/libmongocrypt.mjs | 10 +++-- .github/workflows/build.yml | 75 ++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 .github/docker/Dockerfile.glibc diff --git a/.github/docker/Dockerfile.glibc b/.github/docker/Dockerfile.glibc new file mode 100644 index 0000000..c114db0 --- /dev/null +++ b/.github/docker/Dockerfile.glibc @@ -0,0 +1,11 @@ +ARG NODE_BUILD_IMAGE=node:16.20.1-bullseye +FROM $NODE_BUILD_IMAGE AS build + +WORKDIR /mongodb-client-encryption +COPY . . + +RUN node /mongodb-client-encryption/.github/scripts/libmongocrypt.mjs + +FROM scratch + +COPY --from=build /mongodb-client-encryption/prebuilds/ / diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index d30d071..47f647a 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -59,6 +59,7 @@ async function run(command, args = [], options = {}) { const commandDetails = `+ ${command} ${args.join(' ')}${options.cwd ? ` (in: ${options.cwd})` : ''}`; console.error(commandDetails); const proc = child_process.spawn(command, args, { + shell: process.platform === 'win32', stdio: 'inherit', cwd: resolveRoot('.'), ...options @@ -171,9 +172,12 @@ export async function downloadLibMongoCrypt(nodeDepsRoot, { ref }) { console.error(`Platform: ${detectedPlatform} Prebuild: ${prebuild}`); - const unzipArgs = ['-xzv', '-C', destination, `${prebuild}/nocrypto`]; + const unzipArgs = ['-xzv', '-C', `_libmongocrypt-${ref}`, `${prebuild}/nocrypto`]; console.error(`+ tar ${unzipArgs.join(' ')}`); - const unzip = child_process.spawn('tar', unzipArgs, { stdio: ['pipe', 'inherit'] }); + const unzip = child_process.spawn('tar', unzipArgs, { + stdio: ['pipe', 'inherit'], + cwd: resolveRoot('.') + }); const [response] = await events.once(https.get(downloadURL), 'response'); @@ -228,7 +232,7 @@ async function main() { // 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. + // it will also produce `./prebuilds/mongodb-client-encryption-vVERSION-napi-vNAPI_VERSION-OS-ARCH.tar.gz`. await run('npm', ['run', 'prebuild']); } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce15838..1270be7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,23 +8,74 @@ on: name: build jobs: - build: + host_builds: + strategy: + matrix: + os: [macos-11, macos-latest, windows-2019] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Build ${{ matrix.os }} Prebuild + run: node .github/scripts/libmongocrypt.mjs ${{ runner.os == 'Windows' && '--build' || '' }} + shell: bash + + - id: upload + name: Upload prebuild + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.os }} + path: prebuilds/ + if-no-files-found: 'error' + retention-days: 1 + compression-level: 0 + + container_builds: + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} runs-on: ubuntu-latest strategy: - matrix: - node: ['20.x'] # '16.x', '18.x', - name: Node.js ${{ matrix.node }} build + matrix: + linux_arch: [s390x, arm64, amd64] steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Run Buildx + run: | + docker buildx create --name builder --bootstrap --use + docker buildx build --platform linux/${{ matrix.linux_arch }} --output type=local,dest=./prebuilds,platform-split=false -f ./.github/docker/Dockerfile.glibc . + + - id: upload + name: Upload prebuild + uses: actions/upload-artifact@v4 with: - node-version: ${{ matrix.node }} - cache: 'npm' - registry-url: 'https://registry.npmjs.org' + name: build-linux-${{ matrix.linux_arch }} + path: prebuilds/ + if-no-files-found: 'error' + retention-days: 1 + compression-level: 0 - - run: npm install -g npm@latest - shell: bash + collect: + needs: [host_builds, container_builds] + runs-on: ubunutu-latest + steps: + - uses: actions/download-artifact@v4 - - run: node .github/scripts/libmongocrypt.mjs - shell: bash + - name: Display structure of downloaded files + run: ls -R + + - id: upload + name: Upload all prebuilds + uses: actions/upload-artifact@v4 + with: + name: all-build + path: '*.tar.gz' + if-no-files-found: 'error' + retention-days: 1 + compression-level: 0 From 1d765371b5b06b4982c8de66b8deeee3082dc2db Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 29 May 2024 11:58:29 -0400 Subject: [PATCH 10/10] chore: try catch rename in script --- .github/scripts/libmongocrypt.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/scripts/libmongocrypt.mjs b/.github/scripts/libmongocrypt.mjs index 47f647a..3566aa8 100644 --- a/.github/scripts/libmongocrypt.mjs +++ b/.github/scripts/libmongocrypt.mjs @@ -189,8 +189,11 @@ export async function downloadLibMongoCrypt(nodeDepsRoot, { ref }) { 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')); + const currentPath = path.join(nodeDepsRoot, 'lib64'); + try { + await fs.rename(currentPath, path.join(nodeDepsRoot, 'lib')); + } catch (error) { + console.error(`error renaming ${currentPath}: ${error.message}`); } }