Skip to content

Commit acaca0e

Browse files
authoredNov 15, 2021
Merge pull request #22 from robinchrist/1.x
Enhance configurations, deprecate NAN
2 parents 07f0cba + 758966d commit acaca0e

File tree

8 files changed

+162
-49
lines changed

8 files changed

+162
-49
lines changed
 

‎README.md

+18-6
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,25 @@ Configuration is done entirely via `package.json`. You can specify multiple buil
1212

1313
```js
1414
"cmake-ts": {
15-
"nodeAPI": "node-addon-api" // Specify the node API package such as `node-addon-api`, `nan`, or the path to a directory that has the nodeAPI header. By default `nan` is considered.
15+
"nodeAPI": "node-addon-api" // Specify the node API package such as `node-addon-api`, `nan`, or the path to a directory that has the nodeAPI header. Default is `node-addon-api`, a warning is emitted if nan is used
1616
"configurations": [
1717
{
18+
"name": "win-x64", // name for named-configs mode
1819
"os": "win32", // win32, linux and darwin are supported
1920
"arch": "x64", // x64, x86 should work
2021
"runtime": "electron", // node or electron
2122
"runtimeVersion": "4.0.1", // Version of the runtime which it is built
22-
"toolchainFile": "/windows.cmake" // CMake Toolchain file to use for crosscompiling
23+
"toolchainFile": "/windows.cmake", // CMake Toolchain file to use for crosscompiling
24+
"CMakeOptions": [ //Same syntax as for the globalCMakeOptions
25+
{
26+
"name": "MY_CMAKE_OPTION",
27+
"value": "my_value",
28+
}
29+
],
30+
"addonSubdirectory": "avx2-generic" // if you build addons for multiple architectures in high performance scenarios, you can put the addon inside another subdirectory
2331
}, // more build configurations...
2432
{
33+
"dev": true, // whether this configuration is eligible to be used in a dev test build
2534
"os": "linux", // win32, linux and darwin are supported
2635
"arch": "x64", // x64, x86 should work
2736
"runtime": "node", // node or electron
@@ -46,15 +55,18 @@ Configuration is done entirely via `package.json`. You can specify multiple buil
4655

4756
## Workflow
4857

49-
While it is desirable to perform a full build (all configurations) within a CI environment, long build times hinder local package development. Therefore cmake-ts knows not only the `build` target but also two other targets:
58+
While it is desirable to perform a full build (all configurations) within a CI environment, long build times hinder local package development. Therefore cmake-ts knows multiple build modes:
5059

51-
- `nativeonly` -> Builds the native code **only** for the runtime cmake-ts is currently running on, ignoring all previously specified configurations. This is useful if you'd like to run some unit tests against the compiled code. When running `cmake-ts nativeonly`, cmake-ts will determine the runtime, ABI, and platform from the environment, and build only the configuration required to run on this platform.
60+
- **TODO** `nativeonly` -> Builds the native code **only** for the runtime cmake-ts is currently running on, ignoring all previously specified configurations. This is useful if you'd like to run some unit tests against the compiled code. When running `cmake-ts nativeonly`, cmake-ts will determine the runtime, ABI, and platform from the environment, and build only the configuration required to run on this platform.
5261
- *Example using the configuration above*
5362
- You run `cmake-ts nativeonly` on **NodeJS 11.7 on MacOS**, `cmake-ts` will **ignore** all specified configurations above and build the native addon for **NodeJS 11.7 on MacOS**
54-
- `osonly` -> Builds the native code for all configurations which match the current operating system. This is useful for those developing for example an electron addon and want to test their code in electron. In such a case, you would specify electron and NodeJS runtimes for several platforms in your configuration and you can use `cmake-ts osonly` to build a local package you can install in your application.
63+
- **TODO** `osonly` -> Builds the native code for all configurations which match the current operating system. This is useful for those developing for example an electron addon and want to test their code in electron. In such a case, you would specify electron and NodeJS runtimes for several platforms in your configuration and you can use `cmake-ts osonly` to build a local package you can install in your application.
5564
- *Example using the configuration above*
5665
- You run `cmake-ts osonly` on **NodeJS 11.7 on Linux**, `cmake-ts` will **ignore** all configurations above where `os != linux` and build the native addon for **all** remaining configurations, in this case it will build for **NodeJS 10.3 on Linux**.
57-
- **HINT**: For both `osonly` and `nativeonly`, the specified CMake Toolchain files are ignored since I assume you got your toolchain set up correctly for your **own** operating system.
66+
- **TODO** **HINT**: For both `osonly` and `nativeonly`, the specified CMake Toolchain files are ignored since I assume you got your toolchain set up correctly for your **own** operating system.
67+
- None / Omitted: Builds all configs
68+
- `dev-os-only` builds the first config that has `dev == true` and `os` matches the current OS
69+
- `named-configs arg1 arg2 ...` builds all configs for which `name` is one of the args
5870

5971
## Cross Compilation
6072

‎src/argumentBuilder.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ export class ArgumentBuilder {
6464
}
6565

6666
// Search nodeAPI if installed and required
67-
const nodeApiInclude = await getNodeApiInclude(this.options.packageDirectory, this.options.nodeAPI ?? "nan");
68-
if(Boolean(this.options.nodeAPI) && !nodeApiInclude) {
67+
if (this.options.nodeAPI?.includes('nan')) {
68+
console.warn(`WARNING: specified nodeAPI ${this.options.nodeAPI} seems to be nan - The usage of nan is discouraged due to subtle and hard-to-fix ABI issues! Consider using node-addon-api / N-API instead!`)
69+
}
70+
if (!this.options.nodeAPI) {
71+
console.warn('WARNING: nodeAPI was not specified. The default changed from "nan" to "node-addon-api" in v0.3.0! Please make sure this is intended.');
72+
}
73+
const nodeApiInclude = await getNodeApiInclude(this.options.packageDirectory, this.options.nodeAPI ?? "node-addon-api");
74+
if (this.options.nodeAPI && !nodeApiInclude) {
6975
console.log(`WARNING: nodeAPI was specified, but module "${this.options.nodeAPI}" could not be found!`);
7076
}
7177
if (nodeApiInclude) {
@@ -90,8 +96,8 @@ export class ArgumentBuilder {
9096
retVal.push([j.name, j.value.replace(/\$ROOT\$/g, resolve(this.options.packageDirectory))]);
9197
});
9298
}
93-
if (this.config.cmakeOptions && this.config.cmakeOptions.length > 0) {
94-
this.config.cmakeOptions.forEach(j => {
99+
if (this.config.CMakeOptions && this.config.CMakeOptions.length > 0) {
100+
this.config.CMakeOptions.forEach(j => {
95101
retVal.push([j.name, j.value.replace(/\$ROOT\$/g, resolve(this.options.packageDirectory))]);
96102
});
97103
}

‎src/buildMode.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export type BuildMode = {
2+
type: 'osonly'
3+
} | {
4+
type: 'nativeonly'
5+
} | {
6+
type: 'all'
7+
} | {
8+
type: 'dev-os-only'
9+
} | {
10+
type: 'named-configs',
11+
configsToBuild: string[]
12+
}
13+
14+
export function determineBuildMode(argv: string[]): BuildMode {
15+
16+
// If no arguments are specified, build all setups
17+
if (argv.length === 0) {
18+
return { type: 'all' };
19+
}
20+
21+
if (argv[0] === 'nativeonly') {
22+
return { type: 'nativeonly' };
23+
}
24+
25+
if (argv[0] === 'osonly') {
26+
return { type: 'osonly' };
27+
}
28+
29+
if (argv[0] === 'dev-os-only') {
30+
return { type: 'dev-os-only' };
31+
}
32+
33+
if (argv[0] === 'named-configs') {
34+
if (argv.length < 2) {
35+
console.error(`'named-configs' needs at least one config name`);
36+
process.exit(1);
37+
}
38+
return {
39+
type: 'named-configs',
40+
configsToBuild: argv.slice(1),
41+
};
42+
}
43+
44+
// Yeah whatever, we don't have any proper error handling anyway at the moment
45+
console.error(`Unknown command line option ${argv[0]} - Valid are none/omitted, 'nativeonly', 'osonly', 'dev-os-only' and 'named-configs'`);
46+
process.exit(1);
47+
}

‎src/download.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,8 @@ export async function downloadToString(url: string): Promise<string> {
5656
return result.toString()
5757
}
5858

59-
export async function downloadFile(url: string, options: string | DownloadOptions): Promise<string| null> {
60-
if (isString(options)) {
61-
options = { path: options };
62-
}
59+
export async function downloadFile(url: string, opts: string | DownloadOptions): Promise<string| null> {
60+
const options = isString(opts) ? { path: opts } : opts;
6361

6462
const result = createWriteStream(options.path as string);
6563
const sum = await downloadToStream(url, result, options.hashType);
@@ -69,10 +67,9 @@ export async function downloadFile(url: string, options: string | DownloadOption
6967
return sum;
7068
}
7169

72-
export async function downloadTgz(url: string, options: string | DownloadOptions): Promise<string | null> {
73-
if (isString(options)) {
74-
options = { cwd: options };
75-
}
70+
export async function downloadTgz(url: string, opts: string | DownloadOptions): Promise<string | null> {
71+
const options = isString(opts) ? { path: opts } : opts;
72+
7673
const gunzip = createGunzip();
7774
const extractor = extractTar(options);
7875
gunzip.pipe(extractor);
@@ -83,10 +80,9 @@ export async function downloadTgz(url: string, options: string | DownloadOptions
8380
return sum;
8481
}
8582

86-
export async function downloadZip(url: string, options: string | DownloadOptions): Promise<string | null> {
87-
if (isString(options)) {
88-
options = { path: options };
89-
}
83+
export async function downloadZip(url: string, opts: string | DownloadOptions): Promise<string | null> {
84+
const options = isString(opts) ? { path: opts } : opts;
85+
9086
const extractor = extractZip({ path: options.path as string });
9187
const sum = await downloadToStream(url, extractor, options.hashType);
9288
if (!checkHashSum(sum, options)) {

‎src/lib.ts

+65-16
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import which from 'which';
22
import { GET_CMAKE_VS_GENERATOR } from './util';
3+
import { BuildMode } from './buildMode'
34

45
export type ArrayOrSingle<T> = T | T[];
56

67
export type BuildConfigurationDefaulted = {
8+
name: string,
9+
dev: boolean,
710
os: typeof process.platform,
811
arch: typeof process.arch,
912
runtime: string,
1013
runtimeVersion: string,
1114
toolchainFile: string | null,
12-
cmakeOptions?: { name: string, value: string }[];
15+
CMakeOptions?: { name: string, value: string }[];
16+
addonSubdirectory: string,
1317

1418
// list of additional definitions to fixup node quirks for some specific versions
1519
additionalDefines: string[];
@@ -18,35 +22,49 @@ export type BuildConfigurationDefaulted = {
1822
export type BuildConfiguration = Partial<BuildConfigurationDefaulted>;
1923

2024
export function defaultBuildConfiguration(config: BuildConfiguration): BuildConfigurationDefaulted {
25+
if (config.name === undefined) {
26+
config.name = '' //Empty name should be fine (TM)
27+
}
28+
if (config.dev === undefined) {
29+
config.dev = false
30+
}
2131
if (config.os === undefined) {
2232
config.os = process.platform;
23-
console.warn(`'os' was missing in the 'configurations'. Considering the current operating system ${config.os}`);
33+
console.warn(`'os' was missing in the 'configurations'. Defaulting to the current operating system ${config.os}`);
2434
}
2535

2636
if (config.arch === undefined) {
2737
config.arch = process.arch;
28-
console.warn(`'arch' was missing in the 'configurations'. Considering the current architecture ${config.arch}`);
38+
console.warn(`'arch' was missing in the 'configurations'. Defaulting to the current architecture ${config.arch}`);
2939
}
3040

3141
if (config.runtime === undefined) {
3242
config.runtime = "node";
33-
console.warn("`runtime` was missing in the `configurations`. Considering `node`");
43+
console.warn("`runtime` was missing in the `configurations`. Defaulting to `node`");
3444
}
3545

3646
if (config.runtimeVersion === undefined) {
3747
config.runtimeVersion = process.versions.node;
38-
console.warn(`'runtimeVersion' was missing in the 'configurations'. Considering the current runtimeVersion ${config.runtimeVersion}`);
48+
console.warn(`'runtimeVersion' was missing in the 'configurations'. Defaulting to the current runtimeVersion ${config.runtimeVersion}`);
3949
}
4050

4151
if (config.toolchainFile === undefined) {
4252
config.toolchainFile = null;
4353
}
4454

45-
if (config.cmakeOptions === undefined) {
46-
config.cmakeOptions = [];
55+
if (config.CMakeOptions === undefined) {
56+
config.CMakeOptions = [];
57+
}
58+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
59+
if ((config as any).cmakeOptions !== undefined) {
60+
console.warn('cmakeOptions was specified which was disabled in the 0.3.0 release. Please rename it to CMakeOptions');
61+
}
62+
63+
if (config.addonSubdirectory === undefined) {
64+
config.addonSubdirectory = ''
4765
}
4866

49-
config.additionalDefines = [];
67+
config.additionalDefines = []; //internal variable, not supposed to be set by the user
5068

5169
return config as BuildConfigurationDefaulted;
5270
}
@@ -72,7 +90,7 @@ export type BuildOptionsDefaulted = {
7290
buildType: string,
7391
// global cmake options and defines
7492
globalCMakeOptions?: { name: string, value: string }[];
75-
// custom native node abstractions package name if you use a fork instead of official nan
93+
// node abstraction API to use (e.g. nan or node-addon-api)
7694
nodeAPI?: string;
7795
}
7896

@@ -96,23 +114,20 @@ async function whichWrapped(cmd: string): Promise<string | null> {
96114
}
97115
}
98116

99-
export async function defaultBuildOptions(configs: BuildOptions, nativeonly: boolean, osonly: boolean): Promise<BuildOptionsDefaulted> {
117+
export async function defaultBuildOptions(configs: BuildOptions, buildmode: BuildMode): Promise<BuildOptionsDefaulted> {
100118

101119
// Handle missing configs.configurations
102120
// TODO handle without nativeonly and osonly
103-
if (nativeonly && osonly) {
104-
console.error(`'osonly' and 'nativeonly' have been specified together. exiting.`);
105-
process.exit(1);
106-
}
107-
if (nativeonly) {
121+
if (buildmode.type === 'nativeonly') {
108122
console.log(
109123
`--------------------------------------------------
110124
WARNING: Building only for the current runtime.
111125
WARNING: DO NOT SHIP THE RESULTING PACKAGE
112126
--------------------------------------------------`);
127+
//Yeah this pretty ugly, but whatever
113128
configs.configurations = [defaultBuildConfiguration({})];
114129
}
115-
if (osonly) {
130+
if (buildmode.type === 'osonly') {
116131
console.log(
117132
`--------------------------------------------------
118133
WARNING: Building only for the current OS.
@@ -123,11 +138,45 @@ export async function defaultBuildOptions(configs: BuildOptions, nativeonly: boo
123138
process.exit(1);
124139
}
125140
configs.configurations = configs.configurations.filter(j => j.os === process.platform);
141+
if(configs.configurations.length === 0) {
142+
console.error(`No configuration left to build!`);
143+
process.exit(1);
144+
}
126145
for (const config of configs.configurations) {
127146
// A native build should be possible without toolchain file.
128147
config.toolchainFile = null;
129148
}
130149
}
150+
if (buildmode.type === 'dev-os-only') {
151+
console.log(
152+
`--------------------------------------------------
153+
WARNING: Building dev-os-only package
154+
WARNING: DO NOT SHIP THE RESULTING PACKAGE
155+
--------------------------------------------------`);
156+
if (configs.configurations === undefined) {
157+
console.error('No `configurations` entry was found in the package.json');
158+
process.exit(1);
159+
}
160+
const candidateConfig = configs.configurations.find(j => j.os === process.platform && j.dev)
161+
if (candidateConfig === undefined) {
162+
console.error(`No matching entry with \`dev == true\` and \`os == ${process.platform}\` in \`configurations\``);
163+
process.exit(1);
164+
}
165+
configs.configurations = [candidateConfig]
166+
//todo toolchain file?
167+
}
168+
if(buildmode.type === 'named-configs') {
169+
if (configs.configurations === undefined) {
170+
console.error('No `configurations` entry was found in the package.json');
171+
process.exit(1);
172+
}
173+
//unnamed configs are always filtered out
174+
configs.configurations = configs.configurations.filter(j => (j.name ? buildmode.configsToBuild.includes(j.name) : false))
175+
if(configs.configurations.length === 0) {
176+
console.error(`No configuration left to build!`);
177+
process.exit(1);
178+
}
179+
}
131180

132181

133182
if (configs.packageDirectory === undefined) {

‎src/main.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import { ArgumentBuilder } from './argumentBuilder';
99
import { RUN } from './util';
1010
import { ensureDir, remove, copy, pathExists } from 'fs-extra';
1111
import { applyOverrides } from './override';
12+
import { determineBuildMode } from './buildMode'
1213

1314
const DEBUG_LOG = Boolean(process.env.CMAKETSDEBUG);
1415

1516
(async (): Promise<void> => {
1617

17-
const argv = process.argv;
18+
const argv = process.argv.slice(2); //Yeah, we don't need advanced command line handling yet
1819
let packJson: {'cmake-ts': BuildOptions | undefined} & Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
1920
try {
2021
// TODO getting the path from the CLI
@@ -31,11 +32,10 @@ const DEBUG_LOG = Boolean(process.env.CMAKETSDEBUG);
3132
}
3233

3334
// check if `nativeonly` or `osonly` option is specified
34-
const nativeonly = argv.includes('nativeonly');
35-
const osonly = argv.includes('osonly');
35+
const buildMode = await determineBuildMode(argv);
3636

3737
// set the missing options to their default value
38-
const configs = await defaultBuildOptions(configsGiven, nativeonly, osonly);
38+
const configs = await defaultBuildOptions(configsGiven, buildMode);
3939

4040
// Setup directory structure in configs
4141
// Target directory
@@ -73,20 +73,21 @@ const DEBUG_LOG = Boolean(process.env.CMAKETSDEBUG);
7373
console.log('[ DONE ]');
7474

7575
process.stdout.write('> Building directories... ');
76-
const stagingDir = resolve(join(configs.stagingDirectory, config.os, config.arch, config.runtime, `${dist.abi}`));
77-
const targetDir = resolve(join(configs.targetDirectory, config.os, config.arch, config.runtime, `${dist.abi}`));
76+
const stagingDir = resolve(join(configs.stagingDirectory, config.os, config.arch, config.runtime, `${dist.abi}`, config.addonSubdirectory));
77+
const targetDir = resolve(join(configs.targetDirectory, config.os, config.arch, config.runtime, `${dist.abi}`, config.addonSubdirectory));
7878
console.log('[ DONE ]');
7979

8080
process.stdout.write('> Applying overrides... ');
8181
const appliedOverrides = applyOverrides(config);
8282
console.log(`[ DONE, ${appliedOverrides} applied ]`);
8383

8484
console.log('--------------- CONFIG SUMMARY ---------------');
85+
console.log('Name: ', config.name ? config.name : "N/A");
8586
console.log('OS/Arch:', config.os, config.arch);
8687
console.log('Runtime:', config.runtime, config.runtimeVersion);
8788
console.log('Target ABI:', dist.abi);
8889
console.log('Toolchain File:', config.toolchainFile);
89-
console.log('Custom options:', (config.cmakeOptions && config.cmakeOptions.length > 0) ? 'yes' : 'no');
90+
console.log('Custom CMake options:', (config.CMakeOptions && config.CMakeOptions.length > 0) ? 'yes' : 'no');
9091
console.log('Staging area:', stagingDir);
9192
console.log('Target directory:', targetDir);
9293
console.log('Build Type', configs.buildType);

‎src/modules.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare module "memory-stream" {
99
private buffer;
1010
private options;
1111
constructor(options?: MemoryStreamOptions);
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1213
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
1314
get(): Buffer;
1415
toString(): string;

‎src/nodeAPIInclude/search.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ async function dirHasFile(dir: string, fileName: string) {
2727
};
2828

2929
function goUp(dir: string) {
30-
const items = dir.split(pathSeparator);
30+
let myDir = dir;
31+
const items = myDir.split(pathSeparator);
3132
const scope = items[items.length - 2];
3233
if (scope && scope.charAt(0) === '@') {
33-
dir = joinPath(dir, '..');
34+
myDir = joinPath(myDir, '..');
3435
}
35-
dir = joinPath(dir, '..', '..');
36-
return normalizePath(dir);
36+
myDir = joinPath(myDir, '..', '..');
37+
return normalizePath(myDir);
3738
}

0 commit comments

Comments
 (0)
Please sign in to comment.