@@ -4,24 +4,42 @@ import fs from 'node:fs/promises';
4
4
import child_process from 'node:child_process' ;
5
5
import events from 'node:events' ;
6
6
import path from 'node:path' ;
7
+ import https from 'node:https' ;
8
+ import stream from 'node:stream/promises' ;
9
+ import url from 'node:url' ;
10
+
11
+ const __dirname = path . dirname ( url . fileURLToPath ( import . meta. url ) ) ;
12
+
13
+ /** Resolves to the root of this repository */
14
+ function resolveRoot ( ...paths ) {
15
+ return path . resolve ( __dirname , '..' , '..' , ...paths ) ;
16
+ }
17
+
18
+ async function exists ( fsPath ) {
19
+ try {
20
+ await fs . access ( fsPath ) ;
21
+ return true ;
22
+ } catch {
23
+ return false ;
24
+ }
25
+ }
7
26
8
27
async function parseArguments ( ) {
9
- const jsonImport = { [ process . version . split ( '.' ) . at ( 0 ) === 'v16' ? 'assert' : 'with' ] : { type : 'json' } } ;
10
- const pkg = ( await import ( '../../package.json' , jsonImport ) ) . default ;
11
- const libmongocryptVersion = pkg [ 'mongodb:libmongocrypt' ] ;
28
+ const pkg = JSON . parse ( await fs . readFile ( resolveRoot ( 'package.json' ) , 'utf8' ) ) ;
12
29
13
30
const options = {
14
- url : { short : 'u' , type : 'string' , default : 'https://github.com/mongodb/libmongocrypt.git' } ,
15
- libversion : { short : 'l' , type : 'string' , default : libmongocryptVersion } ,
16
- clean : { short : 'c' , type : 'boolean' } ,
17
- help : { short : 'h' , type : 'boolean' }
31
+ gitURL : { short : 'u' , type : 'string' , default : 'https://github.com/mongodb/libmongocrypt.git' } ,
32
+ libVersion : { short : 'l' , type : 'string' , default : pkg [ 'mongodb:libmongocrypt' ] } ,
33
+ clean : { short : 'c' , type : 'boolean' , default : false } ,
34
+ build : { short : 'b' , type : 'boolean' , default : false } ,
35
+ help : { short : 'h' , type : 'boolean' , default : false }
18
36
} ;
19
37
20
38
const args = util . parseArgs ( { args : process . argv . slice ( 2 ) , options, allowPositionals : false } ) ;
21
39
22
40
if ( args . values . help ) {
23
41
console . log (
24
- `${ process . argv [ 1 ] } ${ [ ...Object . keys ( options ) ]
42
+ `${ path . basename ( process . argv [ 1 ] ) } ${ [ ...Object . keys ( options ) ]
25
43
. filter ( k => k !== 'help' )
26
44
. map ( k => `[--${ k } =${ options [ k ] . type } ]` )
27
45
. join ( ' ' ) } `
@@ -30,46 +48,47 @@ async function parseArguments() {
30
48
}
31
49
32
50
return {
33
- libmongocrypt : { url : args . values . url , ref : args . values . libversion } ,
34
- clean : args . values . clean
51
+ libmongocrypt : { url : args . values . gitURL , ref : args . values . libVersion } ,
52
+ clean : args . values . clean ,
53
+ build : args . values . build
35
54
} ;
36
55
}
37
56
38
57
/** `xtrace` style command runner, uses spawn so that stdio is inherited */
39
58
async function run ( command , args = [ ] , options = { } ) {
40
- console . error ( `+ ${ command } ${ args . join ( ' ' ) } ` , options . cwd ? `(in: ${ options . cwd } )` : '' ) ;
41
- await events . once ( child_process . spawn ( command , args , { stdio : 'inherit' , ...options } ) , 'exit' ) ;
59
+ const commandDetails = `+ ${ command } ${ args . join ( ' ' ) } ${ options . cwd ? ` (in: ${ options . cwd } )` : '' } ` ;
60
+ console . error ( commandDetails ) ;
61
+ const proc = child_process . spawn ( command , args , {
62
+ shell : process . platform === 'win32' ,
63
+ stdio : 'inherit' ,
64
+ cwd : resolveRoot ( '.' ) ,
65
+ ...options
66
+ } ) ;
67
+ await events . once ( proc , 'exit' ) ;
68
+
69
+ if ( proc . exitCode != 0 ) throw new Error ( `CRASH(${ proc . exitCode } ): ${ commandDetails } ` ) ;
42
70
}
43
71
44
72
/** CLI flag maker: `toFlags({a: 1, b: 2})` yields `['-a=1', '-b=2']` */
45
73
function toFlags ( object ) {
46
74
return Array . from ( Object . entries ( object ) ) . map ( ( [ k , v ] ) => `-${ k } =${ v } ` ) ;
47
75
}
48
76
49
- const args = await parseArguments ( ) ;
50
- const libmongocryptRoot = path . resolve ( '_libmongocrypt' ) ;
51
-
52
- const currentLibMongoCryptBranch = await fs . readFile ( path . join ( libmongocryptRoot , '.git' , 'HEAD' ) , 'utf8' ) . catch ( ( ) => '' )
53
- const libmongocryptAlreadyClonedAndCheckedOut = currentLibMongoCryptBranch . trim ( ) . endsWith ( `r-${ args . libmongocrypt . ref } ` ) ;
54
-
55
- if ( args . clean || ! libmongocryptAlreadyClonedAndCheckedOut ) {
56
- console . error ( 'fetching libmongocrypt...' , args . libmongocrypt ) ;
77
+ export async function cloneLibMongoCrypt ( libmongocryptRoot , { url, ref } ) {
78
+ console . error ( 'fetching libmongocrypt...' , { url, ref } ) ;
57
79
await fs . rm ( libmongocryptRoot , { recursive : true , force : true } ) ;
58
- await run ( 'git' , [ 'clone' , args . libmongocrypt . url , libmongocryptRoot ] ) ;
59
- await run ( 'git' , [ 'fetch' , '--tags' ] , { cwd : libmongocryptRoot } ) ;
60
- await run ( 'git' , [ 'checkout' , args . libmongocrypt . ref , '-b' , `r-${ args . libmongocrypt . ref } ` ] , { cwd : libmongocryptRoot } ) ;
61
- } else {
62
- console . error ( 'libmongocrypt already up to date...' , args . libmongocrypt ) ;
80
+ await run ( 'git' , [ 'clone' , url , libmongocryptRoot ] ) ;
81
+ if ( ref !== 'latest' ) {
82
+ // Support "latest" as leaving the clone as-is so whatever the default branch name is works
83
+ await run ( 'git' , [ 'fetch' , '--tags' ] , { cwd : libmongocryptRoot } ) ;
84
+ await run ( 'git' , [ 'checkout' , ref , '-b' , `r-${ ref } ` ] , { cwd : libmongocryptRoot } ) ;
85
+ }
63
86
}
64
87
65
- const libmongocryptBuiltVersion = await fs . readFile ( path . join ( libmongocryptRoot , 'VERSION_CURRENT' ) , 'utf8' ) . catch ( ( ) => '' ) ;
66
- const libmongocryptAlreadyBuilt = libmongocryptBuiltVersion . trim ( ) === args . libmongocrypt . ref ;
67
-
68
- if ( args . clean || ! libmongocryptAlreadyBuilt ) {
69
- console . error ( 'building libmongocrypt...\n' , args ) ;
88
+ export async function buildLibMongoCrypt ( libmongocryptRoot , nodeDepsRoot ) {
89
+ console . error ( 'building libmongocrypt...' ) ;
70
90
71
- const nodeDepsRoot = path . resolve ( 'deps' ) ;
72
- const nodeBuildRoot = path . resolve ( nodeDepsRoot , 'tmp' , 'libmongocrypt-build' ) ;
91
+ const nodeBuildRoot = resolveRoot ( nodeDepsRoot , 'tmp' , 'libmongocrypt-build' ) ;
73
92
74
93
await fs . rm ( nodeBuildRoot , { recursive : true , force : true } ) ;
75
94
await fs . mkdir ( nodeBuildRoot , { recursive : true } ) ;
@@ -115,11 +134,109 @@ if (args.clean || !libmongocryptAlreadyBuilt) {
115
134
? toFlags ( { DCMAKE_OSX_DEPLOYMENT_TARGET : '10.12' } )
116
135
: [ ] ;
117
136
118
- await run ( 'cmake' , [ ...CMAKE_FLAGS , ...WINDOWS_CMAKE_FLAGS , ...MACOS_CMAKE_FLAGS , libmongocryptRoot ] , { cwd : nodeBuildRoot } ) ;
119
- await run ( 'cmake' , [ '--build' , '.' , '--target' , 'install' , '--config' , 'RelWithDebInfo' ] , { cwd : nodeBuildRoot } ) ;
120
- } else {
121
- console . error ( 'libmongocrypt already built...' ) ;
137
+ await run (
138
+ 'cmake' ,
139
+ [ ...CMAKE_FLAGS , ...WINDOWS_CMAKE_FLAGS , ...MACOS_CMAKE_FLAGS , libmongocryptRoot ] ,
140
+ { cwd : nodeBuildRoot }
141
+ ) ;
142
+ await run ( 'cmake' , [ '--build' , '.' , '--target' , 'install' , '--config' , 'RelWithDebInfo' ] , {
143
+ cwd : nodeBuildRoot
144
+ } ) ;
145
+ }
146
+
147
+ export async function downloadLibMongoCrypt ( nodeDepsRoot , { ref } ) {
148
+ const downloadURL =
149
+ ref === 'latest'
150
+ ? 'https://mciuploads.s3.amazonaws.com/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz'
151
+ : `https://mciuploads.s3.amazonaws.com/libmongocrypt/all/${ ref } /libmongocrypt-all.tar.gz` ;
152
+
153
+ console . error ( 'downloading libmongocrypt...' , downloadURL ) ;
154
+ const destination = resolveRoot ( `_libmongocrypt-${ ref } ` ) ;
155
+
156
+ await fs . rm ( destination , { recursive : true , force : true } ) ;
157
+ await fs . mkdir ( destination ) ;
158
+
159
+ const platformMatrix = {
160
+ [ 'darwin-arm64' ] : 'macos' ,
161
+ [ 'darwin-x64' ] : 'macos' ,
162
+ [ 'linux-ppc64' ] : 'rhel-71-ppc64el' ,
163
+ [ 'linux-s390x' ] : 'rhel72-zseries-test' ,
164
+ [ 'linux-arm64' ] : 'ubuntu1804-arm64' ,
165
+ [ 'linux-x64' ] : 'rhel-70-64-bit' ,
166
+ [ 'win32-x64' ] : 'windows-test'
167
+ } ;
168
+
169
+ const detectedPlatform = `${ process . platform } -${ process . arch } ` ;
170
+ const prebuild = platformMatrix [ detectedPlatform ] ;
171
+ if ( prebuild == null ) throw new Error ( `Unsupported: ${ detectedPlatform } ` ) ;
172
+
173
+ console . error ( `Platform: ${ detectedPlatform } Prebuild: ${ prebuild } ` ) ;
174
+
175
+ const unzipArgs = [ '-xzv' , '-C' , `_libmongocrypt-${ ref } ` , `${ prebuild } /nocrypto` ] ;
176
+ console . error ( `+ tar ${ unzipArgs . join ( ' ' ) } ` ) ;
177
+ const unzip = child_process . spawn ( 'tar' , unzipArgs , {
178
+ stdio : [ 'pipe' , 'inherit' ] ,
179
+ cwd : resolveRoot ( '.' )
180
+ } ) ;
181
+
182
+ const [ response ] = await events . once ( https . get ( downloadURL ) , 'response' ) ;
183
+
184
+ const start = performance . now ( ) ;
185
+ await stream . pipeline ( response , unzip . stdin ) ;
186
+ const end = performance . now ( ) ;
187
+
188
+ console . error ( `downloaded libmongocrypt in ${ ( end - start ) / 1000 } secs...` ) ;
189
+
190
+ await fs . rm ( nodeDepsRoot , { recursive : true , force : true } ) ;
191
+ await fs . cp ( resolveRoot ( destination , prebuild , 'nocrypto' ) , nodeDepsRoot , { recursive : true } ) ;
192
+ const currentPath = path . join ( nodeDepsRoot , 'lib64' ) ;
193
+ try {
194
+ await fs . rename ( currentPath , path . join ( nodeDepsRoot , 'lib' ) ) ;
195
+ } catch ( error ) {
196
+ console . error ( `error renaming ${ currentPath } : ${ error . message } ` ) ;
197
+ }
198
+ }
199
+
200
+ async function main ( ) {
201
+ const { libmongocrypt, build, clean } = await parseArguments ( ) ;
202
+
203
+ const nodeDepsDir = resolveRoot ( 'deps' ) ;
204
+
205
+ if ( build ) {
206
+ const libmongocryptCloneDir = resolveRoot ( '_libmongocrypt' ) ;
207
+
208
+ const currentLibMongoCryptBranch = await fs
209
+ . readFile ( path . join ( libmongocryptCloneDir , '.git' , 'HEAD' ) , 'utf8' )
210
+ . catch ( ( ) => '' ) ;
211
+ const isClonedAndCheckedOut = currentLibMongoCryptBranch
212
+ . trim ( )
213
+ . endsWith ( `r-${ libmongocrypt . ref } ` ) ;
214
+
215
+ if ( clean || ! isClonedAndCheckedOut ) {
216
+ await cloneLibMongoCrypt ( libmongocryptCloneDir , libmongocrypt ) ;
217
+ }
218
+
219
+ const libmongocryptBuiltVersion = await fs
220
+ . readFile ( path . join ( libmongocryptCloneDir , 'VERSION_CURRENT' ) , 'utf8' )
221
+ . catch ( ( ) => '' ) ;
222
+ const isBuilt = libmongocryptBuiltVersion . trim ( ) === libmongocrypt . ref ;
223
+
224
+ if ( clean || ! isBuilt ) {
225
+ await buildLibMongoCrypt ( libmongocryptCloneDir , nodeDepsDir ) ;
226
+ }
227
+ } else {
228
+ // Download
229
+ await downloadLibMongoCrypt ( nodeDepsDir , libmongocrypt ) ;
230
+ }
231
+
232
+ await fs . rm ( resolveRoot ( 'build' ) , { force : true , recursive : true } ) ;
233
+ await fs . rm ( resolveRoot ( 'prebuilds' ) , { force : true , recursive : true } ) ;
234
+
235
+ // install with "ignore-scripts" so that we don't attempt to download a prebuild
236
+ await run ( 'npm' , [ 'install' , '--ignore-scripts' ] ) ;
237
+ // The prebuild command will make both a .node file in `./build` (local and CI testing will run on current code)
238
+ // it will also produce `./prebuilds/mongodb-client-encryption-vVERSION-napi-vNAPI_VERSION-OS-ARCH.tar.gz`.
239
+ await run ( 'npm' , [ 'run' , 'prebuild' ] ) ;
122
240
}
123
241
124
- await run ( 'npm' , [ 'install' , '--ignore-scripts' ] ) ;
125
- await run ( 'npm' , [ 'run' , 'rebuild' ] , { env : { ...process . env , BUILD_TYPE : 'static' } } ) ;
242
+ await main ( ) ;
0 commit comments