feat(cli): add support for building binaries

This commit is contained in:
messense 2022-03-26 17:01:12 +08:00 committed by LongYinan
parent 1a2198e13c
commit 20b1edc53b
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
8 changed files with 167 additions and 93 deletions

View file

@ -7,6 +7,7 @@ members = [
"./crates/sys", "./crates/sys",
"./examples/napi", "./examples/napi",
"./examples/napi-compat-mode", "./examples/napi-compat-mode",
"./examples/binary",
"./bench", "./bench",
"./memory-testing", "./memory-testing",
] ]

View file

@ -100,6 +100,10 @@ export class BuildCommand extends Command {
description: `Bypass to ${chalk.green('cargo build --features')}`, 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', { dts?: string = Option.String('--dts', 'index.d.ts', {
description: `The filename and path of ${chalk.green( description: `The filename and path of ${chalk.green(
'.d.ts', '.d.ts',
@ -180,12 +184,54 @@ export class BuildCommand extends Command {
const cwd = this.cargoCwd const cwd = this.cargoCwd
? join(process.cwd(), this.cargoCwd) ? join(process.cwd(), this.cargoCwd)
: process.cwd() : 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 releaseFlag = this.isRelease ? `--release` : ''
const targetFlag = this.targetTripleDir const targetFlag = this.targetTripleDir
? `--target ${this.targetTripleDir}` ? `--target ${this.targetTripleDir}`
: '' : ''
const featuresFlag = this.features ? `--features ${this.features}` : '' const featuresFlag = this.features ? `--features ${this.features}` : ''
const binFlag = this.bin ? `--bin ${this.bin}` : ''
const triple = this.targetTripleDir const triple = this.targetTripleDir
? parseTriple(this.targetTripleDir) ? parseTriple(this.targetTripleDir)
: getDefaultTargetTriple( : getDefaultTargetTriple(
@ -199,6 +245,7 @@ export class BuildCommand extends Command {
releaseFlag, releaseFlag,
targetFlag, targetFlag,
featuresFlag, featuresFlag,
binFlag,
pFlag, pFlag,
this.cargoFlags, this.cargoFlags,
] ]
@ -224,7 +271,7 @@ export class BuildCommand extends Command {
const rustflags = process.env.RUSTFLAGS const rustflags = process.env.RUSTFLAGS
? process.env.RUSTFLAGS.split(' ') ? process.env.RUSTFLAGS.split(' ')
: [] : []
if (triple.raw.includes('musl')) { if (triple.raw.includes('musl') && !this.bin) {
if (!rustflags.includes('target-feature=-crt-static')) { if (!rustflags.includes('target-feature=-crt-static')) {
rustflags.push('-C target-feature=-crt-static') rustflags.push('-C target-feature=-crt-static')
} }
@ -317,34 +364,15 @@ export class BuildCommand extends Command {
cwd, cwd,
}) })
const { binaryName, packageName } = getNapiConfig(this.configFileName) const { binaryName, packageName } = getNapiConfig(this.configFileName)
let dylibName = this.cargoName let cargoArtifactName = this.cargoName
if (!dylibName) { if (!cargoArtifactName) {
let tomlContentString: string if (this.bin) {
let tomlContent: any cargoArtifactName = cargoPackageName
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, '_')
} else { } 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( throw new TypeError(
`Missing ${chalk.green('crate-type = ["cdylib"]')} in ${chalk.green( `Missing ${chalk.green('crate-type = ["cdylib"]')} in ${chalk.green(
'[lib]', '[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 const platform = triple.platform
let libExt let libExt = ''
debug(`Platform: ${chalk.greenBright(platform)}`) debug(`Platform: ${chalk.greenBright(platform)}`)
// Platform based massaging for build commands // Platform based massaging for build commands
switch (platform) { if (!this.bin) {
case 'darwin': switch (platform) {
libExt = '.dylib' case 'darwin':
dylibName = `lib${dylibName}` libExt = '.dylib'
break cargoArtifactName = `lib${cargoArtifactName}`
case 'win32': break
libExt = '.dll' case 'win32':
break libExt = '.dll'
case 'linux': break
case 'freebsd': case 'linux':
case 'openbsd': case 'freebsd':
case 'android': case 'openbsd':
case 'sunos': case 'android':
dylibName = `lib${dylibName}` case 'sunos':
libExt = '.so' cargoArtifactName = `lib${cargoArtifactName}`
break libExt = '.so'
default: break
throw new TypeError( default:
'Operating system not currently supported or recognized by the build script', throw new TypeError(
) 'Operating system not currently supported or recognized by the build script',
)
}
} }
const targetRootDir = await findUp(cwd) const targetRootDir = await findUp(cwd)
@ -399,7 +433,9 @@ export class BuildCommand extends Command {
: '' : ''
debug(`Platform name: ${platformName || chalk.green('[Empty]')}`) 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) const distModulePath = join(this.destDir ?? '.', distFileName)
@ -419,7 +455,7 @@ export class BuildCommand extends Command {
targetRootDir, targetRootDir,
'target', 'target',
targetDir, targetDir,
`${dylibName}${libExt}`, `${cargoArtifactName}${libExt}`,
) )
if (existsSync(distModulePath)) { if (existsSync(distModulePath)) {
@ -430,50 +466,52 @@ export class BuildCommand extends Command {
debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`) debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`)
await copyFileAsync(sourcePath, distModulePath) await copyFileAsync(sourcePath, distModulePath)
const dtsFilePath = join( if (!this.bin) {
process.cwd(), const dtsFilePath = join(
this.destDir ?? '.', process.cwd(),
this.dts ?? 'index.d.ts', this.destDir ?? '.',
) this.dts ?? 'index.d.ts',
)
const idents = await processIntermediateTypeFile( if (this.pipe) {
intermediateTypeFile, const pipeCommand = `${this.pipe} ${dtsFilePath}`
dtsFilePath, console.info(`Run ${chalk.green(pipeCommand)}`)
) try {
if (this.pipe) { execSync(pipeCommand, { stdio: 'inherit', env: process.env })
const pipeCommand = `${this.pipe} ${dtsFilePath}` } catch (e) {
console.info(`Run ${chalk.green(pipeCommand)}`) console.warn(
try { chalk.bgYellowBright('Pipe the dts file to command failed'),
execSync(pipeCommand, { stdio: 'inherit', env: process.env }) e,
} catch (e) { )
console.warn( }
chalk.bgYellowBright('Pipe the dts file to command failed'),
e,
)
} }
} const jsBindingFilePath =
const jsBindingFilePath = this.jsBinding &&
this.jsBinding && this.jsBinding !== 'false' &&
this.jsBinding !== 'false' && this.appendPlatformToFilename
this.appendPlatformToFilename ? join(process.cwd(), this.jsBinding)
? join(process.cwd(), this.jsBinding) : null
: null const idents = await processIntermediateTypeFile(
await writeJsBinding( intermediateTypeFile,
binaryName, dtsFilePath,
this.jsPackageName ?? packageName, )
jsBindingFilePath, await writeJsBinding(
idents, binaryName,
) this.jsPackageName ?? packageName,
if (this.pipe && jsBindingFilePath) { jsBindingFilePath,
const pipeCommand = `${this.pipe} ${jsBindingFilePath}` idents,
console.info(`Run ${chalk.green(pipeCommand)}`) )
try { if (this.pipe && jsBindingFilePath) {
execSync(pipeCommand, { stdio: 'inherit', env: process.env }) const pipeCommand = `${this.pipe} ${jsBindingFilePath}`
} catch (e) { console.info(`Run ${chalk.green(pipeCommand)}`)
console.warn( try {
chalk.bgYellowBright('Pipe the js binding file to command failed'), execSync(pipeCommand, { stdio: 'inherit', env: process.env })
e, } catch (e) {
) console.warn(
chalk.bgYellowBright('Pipe the js binding file to command failed'),
e,
)
}
} }
} }
} }

2
examples/binary/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.node
napi-examples-binary

View file

@ -0,0 +1,7 @@
[package]
edition = "2021"
name = "napi-examples-binary"
publish = false
version = "0.1.0"
[dependencies]

View file

@ -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"
}
}

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello World!");
}

View file

@ -8,7 +8,8 @@
"triples", "triples",
"memory-testing", "memory-testing",
"examples/napi", "examples/napi",
"examples/napi-compat-mode" "examples/napi-compat-mode",
"examples/binary"
], ],
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -2217,6 +2217,14 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "bl@npm:^4.0.3, bl@npm:^4.1.0":
version: 4.1.0 version: 4.1.0
resolution: "bl@npm:4.1.0" resolution: "bl@npm:4.1.0"