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

fix(MONGOSH-1808): static building on intel macs and windows #24

Merged
merged 10 commits into from
Jun 28, 2024
92 changes: 56 additions & 36 deletions .github/scripts/libmongocrypt.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ts-check
// @ts-check

import util from 'node:util';
import process from 'node:process';
import fs from 'node:fs/promises';
Expand All @@ -24,7 +25,9 @@ async function parseArguments() {
libVersion: { short: 'l', type: 'string', default: pkg['mongodb:libmongocrypt'] },
clean: { short: 'c', type: 'boolean', default: false },
build: { short: 'b', type: 'boolean', default: false },
dynamic: { type: 'boolean', default: false },
fastDownload: { type: 'boolean', default: false }, // Potentially incorrect download, only for the brave and impatient
'skip-bindings': { type: 'boolean', default: false },
help: { short: 'h', type: 'boolean', default: false }
};

Expand All @@ -46,6 +49,8 @@ async function parseArguments() {
fastDownload: args.values.fastDownload,
clean: args.values.clean,
build: args.values.build,
dynamic: args.values.dynamic,
skipBindings: args.values['skip-bindings'],
pkg
};
}
Expand Down Expand Up @@ -81,7 +86,7 @@ export async function cloneLibMongoCrypt(libmongocryptRoot, { url, ref }) {
}
}

export async function buildLibMongoCrypt(libmongocryptRoot, nodeDepsRoot) {
export async function buildLibMongoCrypt(libmongocryptRoot, nodeDepsRoot, options) {
console.error('building libmongocrypt...');

const nodeBuildRoot = resolveRoot(nodeDepsRoot, 'tmp', 'libmongocrypt-build');
Expand Down Expand Up @@ -115,7 +120,7 @@ export async function buildLibMongoCrypt(libmongocryptRoot, nodeDepsRoot) {
/**
* Where to install libmongocrypt
* Note that `binding.gyp` will set `./deps/include`
* as an include path if BUILD_TYPE=static
* as an include path if libmongocrypt_link_type=static
*/
DCMAKE_INSTALL_PREFIX: nodeDepsRoot
});
Expand All @@ -125,18 +130,22 @@ export async function buildLibMongoCrypt(libmongocryptRoot, nodeDepsRoot) {
? toFlags({ Thost: 'x64', A: 'x64', DENABLE_WINDOWS_STATIC_RUNTIME: 'ON' })
: [];

const MACOS_CMAKE_FLAGS =
process.platform === 'darwin' // The minimum macos target version we want for
const DARWIN_CMAKE_FLAGS =
process.platform === 'darwin' // The minimum darwin target version we want for
? toFlags({ DCMAKE_OSX_DEPLOYMENT_TARGET: '10.12' })
: [];

const cmakeProgram = process.platform === 'win32' ? 'cmake.exe' : 'cmake';

await run(
'cmake',
[...CMAKE_FLAGS, ...WINDOWS_CMAKE_FLAGS, ...MACOS_CMAKE_FLAGS, libmongocryptRoot],
{ cwd: nodeBuildRoot }
cmakeProgram,
[...CMAKE_FLAGS, ...WINDOWS_CMAKE_FLAGS, ...DARWIN_CMAKE_FLAGS, libmongocryptRoot],
{ cwd: nodeBuildRoot, shell: process.platform === 'win32' }
);
await run('cmake', ['--build', '.', '--target', 'install', '--config', 'RelWithDebInfo'], {
cwd: nodeBuildRoot

await run(cmakeProgram, ['--build', '.', '--target', 'install', '--config', 'RelWithDebInfo'], {
cwd: nodeBuildRoot,
shell: process.platform === 'win32'
});
}

Expand Down Expand Up @@ -228,13 +237,45 @@ export async function downloadLibMongoCrypt(nodeDepsRoot, { ref, fastDownload })
}
}

async function buildBindings(args, pkg) {
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 `./prebuilds/mongodb-client-encryption-vVERSION-napi-vNAPI_VERSION-OS-ARCH.tar.gz`.

let gypDefines = process.env.GYP_DEFINES ?? '';
if (args.dynamic) {
gypDefines += ' libmongocrypt_link_type=dynamic';
}

gypDefines = gypDefines.trim();
const prebuildOptions =
gypDefines.length > 0
? { env: { ...process.env, GYP_DEFINES: gypDefines } }
: undefined;

await run('npm', ['run', 'prebuild'], prebuildOptions);
// Compile Typescript
await run('npm', ['run', 'prepare']);

if (process.platform === 'darwin' && process.arch === 'arm64') {
// The "arm64" build is actually a universal binary
const armTar = `mongodb-client-encryption-v${pkg.version}-napi-v4-darwin-arm64.tar.gz`;
const x64Tar = `mongodb-client-encryption-v${pkg.version}-napi-v4-darwin-x64.tar.gz`;
await fs.copyFile(resolveRoot('prebuilds', armTar), resolveRoot('prebuilds', x64Tar));
}
}

async function main() {
const { pkg, ...args } = await parseArguments();
console.log(args);

const nodeDepsDir = resolveRoot('deps');

if (args.build) {
if (args.build && !args.dynamic) {
const libmongocryptCloneDir = resolveRoot('_libmongocrypt');

const currentLibMongoCryptBranch = await fs
Expand All @@ -252,36 +293,15 @@ async function main() {
const isBuilt = libmongocryptBuiltVersion.trim() === args.ref;

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

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 `./prebuilds/mongodb-client-encryption-vVERSION-napi-vNAPI_VERSION-OS-ARCH.tar.gz`.
await run('npm', ['run', 'prebuild']);
// Compile Typescript
await run('npm', ['run', 'prepare']);

if (process.platform === 'darwin') {
// The "arm64" build is actually a universal binary
await fs.copyFile(
resolveRoot(
'prebuilds',
`mongodb-client-encryption-v${pkg.version}-napi-v4-darwin-arm64.tar.gz`
),
resolveRoot(
'prebuilds',
`mongodb-client-encryption-v${pkg.version}-napi-v4-darwin-x64.tar.gz`
)
);
if (!args.skipBindings) {
await buildBindings(args, pkg);
}
}

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ npm run install:libmongocrypt
#### `libmongocrypt.mjs`

```
node libmongocrypt.mjs [--gitURL=string] [--libVersion=string] [--clean] [--build] [--no-crypto] [--fastDownload]
node libmongocrypt.mjs [optional flags]

By default attempts to download and compile the bindings with the crypto prebuilds of libmongocrypt.
Can be configured to clone and build without crypto.
Expand All @@ -47,6 +47,9 @@ Can be configured to clone and build without crypto.
You may use "latest" to get current libmongocrypt `HEAD`.
--clean Combined with --build, the script will not skip cloning and rebuilding libmongocrypt.
--build Instead of downloading, clone and build libmongocrypt along with the bindings.
--dynamic Skips cloning or downloading libmongocrypt, runs prebuild with build_type set to "dynamic" to compile
a prebuild that links to a system copy of libmongocrypt.
--skip-bindings Skips running prebuild. Useful if only the libmongocrypt dependency is desired.

Only suitable for local development:

Expand Down
46 changes: 17 additions & 29 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
{
'targets': [{
'target_name': 'mongocrypt',
'type': 'loadable_module',
'include_dirs': [
"<!(node -p \"require('node-addon-api').include_dir\")",
],
'variables': {
'variables': {
'build_type%': "dynamic",
},
'conditions': [
['OS=="win"', {
'build_type' : "<!(echo %BUILD_TYPE%)"
}],
['OS!="win"', {
'build_type' : "<!(echo $BUILD_TYPE)",
}]
]
'ARCH': '<(host_arch)',
'libmongocrypt_link_type%': 'static',
},
'sources': [
'addon/mongocrypt.cc'
Expand All @@ -24,35 +16,31 @@
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.12',
"OTHER_CFLAGS": [
"-arch x86_64",
"-arch arm64"
],
"OTHER_LDFLAGS": [
"-arch x86_64",
"-arch arm64"
]
'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
},
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
},
'conditions': [
['OS=="mac"', {
'cflags+': ['-fvisibility=hidden'],
['OS=="mac"', { 'cflags+': ['-fvisibility=hidden'] }],
['_type!="static_library" and ARCH=="arm64"', {
'xcode_settings': {
'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
"OTHER_CFLAGS": [
"-arch x86_64",
"-arch arm64"
],
"OTHER_LDFLAGS": [
"-arch x86_64",
"-arch arm64"
]
}
}],
['build_type=="dynamic"', {
'link_settings': {
'libraries': [
'-lmongocrypt'
]
}
['libmongocrypt_link_type=="dynamic"', {
'link_settings': { 'libraries': ['-lmongocrypt'] }
}],
['build_type!="dynamic"', {
['libmongocrypt_link_type=="static"', {
'conditions': [
['OS!="win"', {
'include_dirs': [
Expand Down