napi-rs/cli/src/build.ts

208 lines
5.4 KiB
TypeScript
Raw Normal View History

import { execSync } from 'child_process'
2020-09-04 17:22:15 +09:00
import { join, parse, sep } from 'path'
2020-07-27 00:53:09 +09:00
2020-09-04 17:22:15 +09:00
import chalk from 'chalk'
import { Command, Option } from 'clipanion'
2020-07-27 00:53:09 +09:00
import toml from 'toml'
2020-09-04 17:22:15 +09:00
import { getNapiConfig } from './consts'
import { debugFactory } from './debug'
import { getDefaultTargetTriple, parseTriple } from './parse-triple'
import {
copyFileAsync,
existsAsync,
mkdirAsync,
readFileAsync,
unlinkAsync,
} from './utils'
2020-09-04 17:22:15 +09:00
const debug = debugFactory('build')
2020-07-27 00:53:09 +09:00
export class BuildCommand extends Command {
static usage = Command.Usage({
2020-12-23 23:43:43 +09:00
description: 'Build and copy native module into specified dir',
2020-07-27 00:53:09 +09:00
})
static paths = [['build']]
2020-07-27 00:53:09 +09:00
appendPlatformToFilename = Option.Boolean(`--platform`, false)
2020-07-27 00:53:09 +09:00
isRelease = Option.Boolean(`--release`, false)
2020-09-04 17:22:15 +09:00
configFileName?: string = Option.String('--config,-c')
cargoName?: string = Option.String('--cargo-name')
targetTripleDir = Option.String('--target', process.env.RUST_TARGET ?? '')
features?: string = Option.String('--features')
cargoFlags = Option.String('--cargo-flags', '')
2020-12-22 16:02:57 +09:00
cargoCwd?: string = Option.String('--cargo-cwd')
destDir = Option.String({
2020-09-04 17:22:15 +09:00
required: false,
})
2020-07-27 00:53:09 +09:00
async execute() {
2020-12-22 16:02:57 +09:00
const cwd = this.cargoCwd
? join(process.cwd(), this.cargoCwd)
: process.cwd()
const releaseFlag = this.isRelease ? `--release` : ''
const targetFLag = this.targetTripleDir
? `--target ${this.targetTripleDir}`
: ''
const featuresFlag = this.features ? `--features ${this.features}` : ''
const triple = this.targetTripleDir
? parseTriple(this.targetTripleDir)
: getDefaultTargetTriple(
execSync('rustup show active-toolchain', {
env: process.env,
}).toString('utf8'),
)
debug(`Current triple is: ${chalk.green(triple.raw)}`)
const externalFlags = [
releaseFlag,
targetFLag,
featuresFlag,
this.cargoFlags,
]
.filter((flag) => Boolean(flag))
.join(' ')
const cargoCommand = `cargo build ${externalFlags}`
debug(`Run ${chalk.green(cargoCommand)}`)
execSync(cargoCommand, {
env: process.env,
stdio: 'inherit',
2020-12-22 16:02:57 +09:00
cwd,
})
2020-09-04 17:22:15 +09:00
const { binaryName } = getNapiConfig(this.configFileName)
let dylibName = this.cargoName
if (!dylibName) {
let tomlContentString: string
let tomlContent: any
try {
debug('Start read toml')
tomlContentString = await readFileAsync(
2020-12-22 16:02:57 +09:00
join(cwd, 'Cargo.toml'),
'utf-8',
)
} catch {
2020-12-22 16:02:57 +09:00
throw new TypeError(`Could not find Cargo.toml in ${cwd}`)
}
2020-07-27 00:53:09 +09:00
try {
debug('Start parse toml')
tomlContent = toml.parse(tomlContentString)
} catch {
throw new TypeError('Could not parse the Cargo.toml')
}
2020-09-04 17:22:15 +09:00
if (tomlContent.package?.name) {
dylibName = tomlContent.package.name.replace(/-/g, '_')
} else {
throw new TypeError('No package.name field in Cargo.toml')
}
if (!tomlContent.lib?.['crate-type']?.includes?.('cdylib')) {
throw new TypeError(
`Missing ${chalk.green('create-type = ["cdylib"]')} in ${chalk.green(
'[lib]',
)}`,
)
}
2020-07-27 00:53:09 +09:00
}
2020-09-04 17:22:15 +09:00
debug(`Dylib name: ${chalk.greenBright(dylibName)}`)
const platform = triple.platform
2020-07-27 00:53:09 +09:00
let libExt
2020-09-04 17:22:15 +09:00
debug(`Platform: ${chalk.greenBright(platform)}`)
2020-07-27 00:53:09 +09:00
// Platform based massaging for build commands
switch (platform) {
case 'darwin':
libExt = '.dylib'
2020-09-04 17:22:15 +09:00
dylibName = `lib${dylibName}`
2020-07-27 00:53:09 +09:00
break
case 'win32':
libExt = '.dll'
break
case 'linux':
case 'freebsd':
case 'openbsd':
case 'android':
case 'sunos':
2020-09-04 17:22:15 +09:00
dylibName = `lib${dylibName}`
2020-07-27 00:53:09 +09:00
libExt = '.so'
break
default:
2020-09-04 17:22:15 +09:00
throw new TypeError(
2020-07-27 00:53:09 +09:00
'Operating system not currently supported or recognized by the build script',
)
}
const targetRootDir = await findUp(cwd)
if (!targetRootDir) {
throw new TypeError('No target dir found')
}
const targetDir = join(
this.targetTripleDir,
this.isRelease ? 'release' : 'debug',
)
2020-07-27 00:53:09 +09:00
2020-08-21 22:51:14 +09:00
const platformName = this.appendPlatformToFilename
? `.${triple.platformArchABI}`
2020-07-27 00:53:09 +09:00
: ''
debug(`Platform name: ${platformName || chalk.green('[Empty]')}`)
const distFileName = `${binaryName}${platformName}.node`
const distModulePath = join(this.destDir ?? '.', distFileName)
2020-07-27 00:53:09 +09:00
2020-09-04 17:22:15 +09:00
const parsedDist = parse(distModulePath)
2020-07-27 00:53:09 +09:00
if (parsedDist.dir && !(await existsAsync(parsedDist.dir))) {
await mkdirAsync(parsedDist.dir, { recursive: true }).catch((e) => {
console.warn(
chalk.bgYellowBright(
`Create dir [${parsedDist.dir}] failed, reason: ${e.message}`,
),
)
})
2020-07-27 00:53:09 +09:00
}
const sourcePath = join(
targetRootDir,
'target',
targetDir,
`${dylibName}${libExt}`,
)
2020-09-04 17:22:15 +09:00
if (await existsAsync(distModulePath)) {
debug(`remove old binary [${chalk.yellowBright(distModulePath)}]`)
await unlinkAsync(distModulePath)
}
2020-09-04 17:22:15 +09:00
debug(`Write binary content to [${chalk.yellowBright(distModulePath)}]`)
await copyFileAsync(sourcePath, distModulePath)
2020-07-27 00:53:09 +09:00
}
}
2020-09-04 17:22:15 +09:00
async function findUp(dir = process.cwd()): Promise<string | null> {
const dist = join(dir, 'target')
if (await existsAsync(dist)) {
return dir
}
const dirs = dir.split(sep)
if (dirs.length < 2) {
return null
}
dirs.pop()
return findUp(dirs.join(sep))
}