From 20b1edc53b38fe3b4cf3c628351fcdfcdeff8037 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 26 Mar 2022 17:01:12 +0800 Subject: [PATCH 1/2] feat(cli): add support for building binaries --- Cargo.toml | 1 + cli/src/build.ts | 222 ++++++++++++++++++++--------------- examples/binary/.gitignore | 2 + examples/binary/Cargo.toml | 7 ++ examples/binary/package.json | 14 +++ examples/binary/src/main.rs | 3 + package.json | 3 +- yarn.lock | 8 ++ 8 files changed, 167 insertions(+), 93 deletions(-) create mode 100644 examples/binary/.gitignore create mode 100644 examples/binary/Cargo.toml create mode 100644 examples/binary/package.json create mode 100644 examples/binary/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 628c3fd5..a6fc619d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "./crates/sys", "./examples/napi", "./examples/napi-compat-mode", + "./examples/binary", "./bench", "./memory-testing", ] diff --git a/cli/src/build.ts b/cli/src/build.ts index 344d61f7..bd4b231d 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -100,6 +100,10 @@ export class BuildCommand extends Command { description: `Bypass to ${chalk.green('cargo build --features')}`, }) + bin?: string = Option.String('--bin', { + description: `Bypass to ${chalk.green('cargo build --bin')}`, + }) + dts?: string = Option.String('--dts', 'index.d.ts', { description: `The filename and path of ${chalk.green( '.d.ts', @@ -180,12 +184,54 @@ export class BuildCommand extends Command { const cwd = this.cargoCwd ? join(process.cwd(), this.cargoCwd) : process.cwd() + + let tomlContentString: string + let tomlContent: any + try { + debug('Start read toml') + tomlContentString = await readFileAsync(join(cwd, 'Cargo.toml'), 'utf-8') + } catch { + throw new TypeError(`Could not find Cargo.toml in ${cwd}`) + } + + try { + debug('Start parse toml') + tomlContent = toml.parse(tomlContentString) + } catch { + throw new TypeError('Could not parse the Cargo.toml') + } + + let cargoPackageName: string + if (tomlContent.package?.name) { + cargoPackageName = tomlContent.package.name + } else { + throw new TypeError('No package.name field in Cargo.toml') + } + + const cargoMetadata = JSON.parse( + execSync('cargo metadata --format-version 1', { + stdio: 'pipe', + }).toString('utf8'), + ) + const packages = cargoMetadata.packages + const cargoPackage = packages.find( + (p: { name: string }) => p.name === cargoPackageName, + ) + if ( + !this.bin && + cargoPackage?.targets?.length === 1 && + cargoPackage?.targets[0].kind.length === 1 && + cargoPackage?.targets[0].kind[0] === 'bin' + ) { + this.bin = cargoPackageName + } const releaseFlag = this.isRelease ? `--release` : '' const targetFlag = this.targetTripleDir ? `--target ${this.targetTripleDir}` : '' const featuresFlag = this.features ? `--features ${this.features}` : '' + const binFlag = this.bin ? `--bin ${this.bin}` : '' const triple = this.targetTripleDir ? parseTriple(this.targetTripleDir) : getDefaultTargetTriple( @@ -199,6 +245,7 @@ export class BuildCommand extends Command { releaseFlag, targetFlag, featuresFlag, + binFlag, pFlag, this.cargoFlags, ] @@ -224,7 +271,7 @@ export class BuildCommand extends Command { const rustflags = process.env.RUSTFLAGS ? process.env.RUSTFLAGS.split(' ') : [] - if (triple.raw.includes('musl')) { + if (triple.raw.includes('musl') && !this.bin) { if (!rustflags.includes('target-feature=-crt-static')) { rustflags.push('-C target-feature=-crt-static') } @@ -317,34 +364,15 @@ export class BuildCommand extends Command { cwd, }) const { binaryName, packageName } = getNapiConfig(this.configFileName) - let dylibName = this.cargoName - if (!dylibName) { - let tomlContentString: string - let tomlContent: any - try { - debug('Start read toml') - tomlContentString = await readFileAsync( - join(cwd, 'Cargo.toml'), - 'utf-8', - ) - } catch { - throw new TypeError(`Could not find Cargo.toml in ${cwd}`) - } - - try { - debug('Start parse toml') - tomlContent = toml.parse(tomlContentString) - } catch { - throw new TypeError('Could not parse the Cargo.toml') - } - - if (tomlContent.package?.name) { - dylibName = tomlContent.package.name.replace(/-/g, '_') + let cargoArtifactName = this.cargoName + if (!cargoArtifactName) { + if (this.bin) { + cargoArtifactName = cargoPackageName } else { - throw new TypeError('No package.name field in Cargo.toml') + cargoArtifactName = cargoPackageName.replace(/-/g, '_') } - if (!tomlContent.lib?.['crate-type']?.includes?.('cdylib')) { + if (!this.bin && !tomlContent.lib?.['crate-type']?.includes?.('cdylib')) { throw new TypeError( `Missing ${chalk.green('crate-type = ["cdylib"]')} in ${chalk.green( '[lib]', @@ -353,34 +381,40 @@ export class BuildCommand extends Command { } } - debug(`Dylib name: ${chalk.greenBright(dylibName)}`) + if (this.bin) { + debug(`Binary name: ${chalk.greenBright(cargoArtifactName)}`) + } else { + debug(`Dylib name: ${chalk.greenBright(cargoArtifactName)}`) + } const platform = triple.platform - let libExt + let libExt = '' debug(`Platform: ${chalk.greenBright(platform)}`) // Platform based massaging for build commands - switch (platform) { - case 'darwin': - libExt = '.dylib' - dylibName = `lib${dylibName}` - break - case 'win32': - libExt = '.dll' - break - case 'linux': - case 'freebsd': - case 'openbsd': - case 'android': - case 'sunos': - dylibName = `lib${dylibName}` - libExt = '.so' - break - default: - throw new TypeError( - 'Operating system not currently supported or recognized by the build script', - ) + if (!this.bin) { + switch (platform) { + case 'darwin': + libExt = '.dylib' + cargoArtifactName = `lib${cargoArtifactName}` + break + case 'win32': + libExt = '.dll' + break + case 'linux': + case 'freebsd': + case 'openbsd': + case 'android': + case 'sunos': + cargoArtifactName = `lib${cargoArtifactName}` + libExt = '.so' + break + default: + throw new TypeError( + 'Operating system not currently supported or recognized by the build script', + ) + } } const targetRootDir = await findUp(cwd) @@ -399,7 +433,9 @@ export class BuildCommand extends Command { : '' debug(`Platform name: ${platformName || chalk.green('[Empty]')}`) - const distFileName = `${binaryName}${platformName}.node` + const distFileName = this.bin + ? cargoArtifactName! + : `${binaryName}${platformName}.node` const distModulePath = join(this.destDir ?? '.', distFileName) @@ -419,7 +455,7 @@ export class BuildCommand extends Command { targetRootDir, 'target', targetDir, - `${dylibName}${libExt}`, + `${cargoArtifactName}${libExt}`, ) if (existsSync(distModulePath)) { @@ -430,50 +466,52 @@ export class BuildCommand extends Command { debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`) await copyFileAsync(sourcePath, distModulePath) - const dtsFilePath = join( - process.cwd(), - this.destDir ?? '.', - this.dts ?? 'index.d.ts', - ) + if (!this.bin) { + const dtsFilePath = join( + process.cwd(), + this.destDir ?? '.', + this.dts ?? 'index.d.ts', + ) - const idents = await processIntermediateTypeFile( - intermediateTypeFile, - dtsFilePath, - ) - if (this.pipe) { - const pipeCommand = `${this.pipe} ${dtsFilePath}` - console.info(`Run ${chalk.green(pipeCommand)}`) - try { - execSync(pipeCommand, { stdio: 'inherit', env: process.env }) - } catch (e) { - console.warn( - chalk.bgYellowBright('Pipe the dts file to command failed'), - e, - ) + if (this.pipe) { + const pipeCommand = `${this.pipe} ${dtsFilePath}` + console.info(`Run ${chalk.green(pipeCommand)}`) + try { + execSync(pipeCommand, { stdio: 'inherit', env: process.env }) + } catch (e) { + console.warn( + chalk.bgYellowBright('Pipe the dts file to command failed'), + e, + ) + } } - } - const jsBindingFilePath = - this.jsBinding && - this.jsBinding !== 'false' && - this.appendPlatformToFilename - ? join(process.cwd(), this.jsBinding) - : null - await writeJsBinding( - binaryName, - this.jsPackageName ?? packageName, - jsBindingFilePath, - idents, - ) - if (this.pipe && jsBindingFilePath) { - const pipeCommand = `${this.pipe} ${jsBindingFilePath}` - console.info(`Run ${chalk.green(pipeCommand)}`) - try { - execSync(pipeCommand, { stdio: 'inherit', env: process.env }) - } catch (e) { - console.warn( - chalk.bgYellowBright('Pipe the js binding file to command failed'), - e, - ) + const jsBindingFilePath = + this.jsBinding && + this.jsBinding !== 'false' && + this.appendPlatformToFilename + ? join(process.cwd(), this.jsBinding) + : null + const idents = await processIntermediateTypeFile( + intermediateTypeFile, + dtsFilePath, + ) + await writeJsBinding( + binaryName, + this.jsPackageName ?? packageName, + jsBindingFilePath, + idents, + ) + if (this.pipe && jsBindingFilePath) { + const pipeCommand = `${this.pipe} ${jsBindingFilePath}` + console.info(`Run ${chalk.green(pipeCommand)}`) + try { + execSync(pipeCommand, { stdio: 'inherit', env: process.env }) + } catch (e) { + console.warn( + chalk.bgYellowBright('Pipe the js binding file to command failed'), + e, + ) + } } } } diff --git a/examples/binary/.gitignore b/examples/binary/.gitignore new file mode 100644 index 00000000..df5e7cca --- /dev/null +++ b/examples/binary/.gitignore @@ -0,0 +1,2 @@ +*.node +napi-examples-binary diff --git a/examples/binary/Cargo.toml b/examples/binary/Cargo.toml new file mode 100644 index 00000000..d38d0683 --- /dev/null +++ b/examples/binary/Cargo.toml @@ -0,0 +1,7 @@ +[package] +edition = "2021" +name = "napi-examples-binary" +publish = false +version = "0.1.0" + +[dependencies] diff --git a/examples/binary/package.json b/examples/binary/package.json new file mode 100644 index 00000000..0f03a2c9 --- /dev/null +++ b/examples/binary/package.json @@ -0,0 +1,14 @@ +{ + "name": "binary", + "private": true, + "version": "0.0.0", + "bin": "napi-examples-binary", + "scripts": { + "build": "node ../../cli/scripts/index.js build --js false", + "build-aarch64": "node ../../cli/scripts/index.js build --js false --target aarch64-unknown-linux-gnu", + "build-armv7": "node ../../cli/scripts/index.js build --js false --target armv7-unknown-linux-gnueabihf", + "build-i686": "node ../../cli/scripts/index.js build --js false --target i686-pc-windows-msvc", + "build-i686-release": "node ../../cli/scripts/index.js build --js false --release --target i686-pc-windows-msvc", + "build-release": "node ../../cli/scripts/index.js build --js false --release" + } +} diff --git a/examples/binary/src/main.rs b/examples/binary/src/main.rs new file mode 100644 index 00000000..05c98b27 --- /dev/null +++ b/examples/binary/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello World!"); +} diff --git a/package.json b/package.json index c7ff0306..8b21c565 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "triples", "memory-testing", "examples/napi", - "examples/napi-compat-mode" + "examples/napi-compat-mode", + "examples/binary" ], "repository": { "type": "git", diff --git a/yarn.lock b/yarn.lock index 9006409a..f215427f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2217,6 +2217,14 @@ __metadata: languageName: node linkType: hard +"binary@workspace:examples/binary": + version: 0.0.0-use.local + resolution: "binary@workspace:examples/binary" + bin: + binary: napi-examples-binary + languageName: unknown + linkType: soft + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" From c32f7f5dbc2bac2316e5cdf39f72c081bf601ae0 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 1 Apr 2022 14:04:29 +0800 Subject: [PATCH 2/2] ci: add binary build check --- .github/workflows/cli-binary.yml | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/cli-binary.yml diff --git a/.github/workflows/cli-binary.yml b/.github/workflows/cli-binary.yml new file mode 100644 index 00000000..b7c3dd00 --- /dev/null +++ b/.github/workflows/cli-binary.yml @@ -0,0 +1,57 @@ +name: Cli Build Binary + +env: + DEBUG: 'napi:*' + +on: + push: + branches: + - main + pull_request: + +jobs: + build_binary_crate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + check-latest: true + cache: 'yarn' + + - name: Install + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Cache cargo registry + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: stable-cargo-cache-build-binary + + - name: 'Install dependencies' + run: yarn install --mode=skip-build --immutable --network-timeout 300000 + + - name: 'Build TypeScript' + run: yarn build + + - name: Build and run binary + run: | + yarn workspace binary build + ./examples/binary/napi-examples-binary + env: + RUST_BACKTRACE: 1 + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache