feat(cli): auto choose the tooling for cross compiling (#1367)

This commit is contained in:
LongYinan 2022-11-20 22:24:02 +08:00 committed by GitHub
parent 035def0600
commit 696c2ddcd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 233 additions and 22 deletions

View file

@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.13.0-alpha.6](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.5...@napi-rs/cli@2.13.0-alpha.6) (2022-11-20)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.5](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.4...@napi-rs/cli@2.13.0-alpha.5) (2022-11-20)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.4](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.3...@napi-rs/cli@2.13.0-alpha.4) (2022-11-20)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.3](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.2...@napi-rs/cli@2.13.0-alpha.3) (2022-11-20)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.2](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.1...@napi-rs/cli@2.13.0-alpha.2) (2022-11-17)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.1](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.13.0-alpha.0...@napi-rs/cli@2.13.0-alpha.1) (2022-11-17)
**Note:** Version bump only for package @napi-rs/cli
# [2.13.0-alpha.0](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.12.1...@napi-rs/cli@2.13.0-alpha.0) (2022-11-17)
### Features
- **cli:** auto choose the tooling for cross compiling ([7faf4fc](https://github.com/napi-rs/napi-rs/commit/7faf4fc4cc3b2e9dc47c892a9acf9bcf7e0571ad))
## [2.12.1](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.12.0...@napi-rs/cli@2.12.1) (2022-11-12) ## [2.12.1](https://github.com/napi-rs/napi-rs/compare/@napi-rs/cli@2.12.0...@napi-rs/cli@2.12.1) (2022-11-12)
### Bug Fixes ### Bug Fixes

View file

@ -1,6 +1,6 @@
{ {
"name": "@napi-rs/cli", "name": "@napi-rs/cli",
"version": "2.12.1", "version": "2.13.0-alpha.6",
"description": "Cli tools for napi-rs", "description": "Cli tools for napi-rs",
"keywords": [ "keywords": [
"cli", "cli",

56
cli/src/arm-features.h.ts Normal file
View file

@ -0,0 +1,56 @@
export const ARM_FEATURES_H = `/* Macros to test for CPU features on ARM. Generic ARM version.
Copyright (C) 2012-2022 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library. If not, see
<https://www.gnu.org/licenses/>. */
#ifndef _ARM_ARM_FEATURES_H
#define _ARM_ARM_FEATURES_H 1
/* An OS-specific arm-features.h file should define ARM_HAVE_VFP to
an appropriate expression for testing at runtime whether the VFP
hardware is present. We'll then redefine it to a constant if we
know at compile time that we can assume VFP. */
#ifndef __SOFTFP__
/* The compiler is generating VFP instructions, so we're already
assuming the hardware exists. */
# undef ARM_HAVE_VFP
# define ARM_HAVE_VFP 1
#endif
/* An OS-specific arm-features.h file may define ARM_ASSUME_NO_IWMMXT
to indicate at compile time that iWMMXt hardware is never present
at runtime (or that we never care about its state) and so need not
be checked for. */
/* A more-specific arm-features.h file may define ARM_ALWAYS_BX to indicate
that instructions using pc as a destination register must never be used,
so a "bx" (or "blx") instruction is always required. */
/* The log2 of the minimum alignment required for an address that
is the target of a computed branch (i.e. a "bx" instruction).
A more-specific arm-features.h file may define this to set a more
stringent requirement.
Using this only makes sense for code in ARM mode (where instructions
always have a fixed size of four bytes), or for Thumb-mode code that is
specifically aligning all the related branch targets to match (since
Thumb instructions might be either two or four bytes). */
#ifndef ARM_BX_ALIGN_LOG2
# define ARM_BX_ALIGN_LOG2 2
#endif
/* An OS-specific arm-features.h file may define ARM_NO_INDEX_REGISTER to
indicate that the two-register addressing modes must never be used. */
#endif /* arm-features.h */
`

View file

@ -8,6 +8,7 @@ import { Command, Option } from 'clipanion'
import envPaths from 'env-paths' import envPaths from 'env-paths'
import { groupBy } from 'lodash-es' import { groupBy } from 'lodash-es'
import { ARM_FEATURES_H } from './arm-features.h'
import { getNapiConfig } from './consts' import { getNapiConfig } from './consts'
import { debugFactory } from './debug' import { debugFactory } from './debug'
import { createJsBinding } from './js-binding-template' import { createJsBinding } from './js-binding-template'
@ -34,8 +35,14 @@ const ZIG_PLATFORM_TARGET_MAP = {
'aarch64-apple-darwin': 'aarch64-macos', 'aarch64-apple-darwin': 'aarch64-macos',
'aarch64-unknown-linux-gnu': 'aarch64-linux-gnu', 'aarch64-unknown-linux-gnu': 'aarch64-linux-gnu',
'aarch64-unknown-linux-musl': 'aarch64-linux-musl', 'aarch64-unknown-linux-musl': 'aarch64-linux-musl',
'armv7-unknown-linux-gnueabihf': 'arm-linux-gnueabihf',
} }
const DEFAULT_GLIBC_TARGET = process.env.GLIBC_ABI_TARGET ?? '2.17'
const SHEBANG_NODE = process.platform === 'win32' ? '' : '#!/usr/bin/env node\n'
const SHEBANG_SH = process.platform === 'win32' ? '' : '#!/usr/bin/env sh\n'
function processZigLinkerArgs(platform: string, args: string[]) { function processZigLinkerArgs(platform: string, args: string[]) {
if (platform.includes('apple')) { if (platform.includes('apple')) {
const newArgs = args.filter( const newArgs = args.filter(
@ -158,7 +165,7 @@ export class BuildCommand extends Command {
'lto', 'lto',
)} and increase ${chalk.green( )} and increase ${chalk.green(
'codegen-units', 'codegen-units',
)}. Enabled by default. See ${chalk.underline.blue( )}. Disabled by default. See ${chalk.underline.blue(
'https://github.com/napi-rs/napi-rs/issues/297', 'https://github.com/napi-rs/napi-rs/issues/297',
)}`, )}`,
}, },
@ -256,11 +263,29 @@ export class BuildCommand extends Command {
] ]
.filter((flag) => Boolean(flag)) .filter((flag) => Boolean(flag))
.join(' ') .join(' ')
const cargo = process.env.CARGO ?? 'cargo' const additionalEnv = {}
const isCrossForWin =
triple.platform === 'win32' && process.platform !== 'win32'
const isCrossForLinux =
triple.platform === 'linux' &&
(process.platform !== 'linux' ||
triple.arch !== process.arch ||
(function () {
const glibcVersionRuntime =
// @ts-expect-error
process.report?.getReport()?.header?.glibcVersionRuntime
const libc = glibcVersionRuntime ? 'gnu' : 'musl'
return triple.abi !== libc
})())
const isCrossForMacOS =
triple.platform === 'darwin' && process.platform !== 'darwin'
const cargo = process.env.CARGO ?? isCrossForWin ? 'cargo-xwin' : 'cargo'
if (isCrossForWin && triple.arch === 'ia32') {
additionalEnv['XWIN_ARCH'] = 'x86'
}
const cargoCommand = `${cargo} build ${externalFlags}` const cargoCommand = `${cargo} build ${externalFlags}`
const intermediateTypeFile = join(tmpdir(), `type_def.${Date.now()}.tmp`) const intermediateTypeFile = join(tmpdir(), `type_def.${Date.now()}.tmp`)
debug(`Run ${chalk.green(cargoCommand)}`) debug(`Run ${chalk.green(cargoCommand)}`)
const additionalEnv = {}
const rustflags = process.env.RUSTFLAGS const rustflags = process.env.RUSTFLAGS
? process.env.RUSTFLAGS.split(' ') ? process.env.RUSTFLAGS.split(' ')
@ -278,16 +303,39 @@ export class BuildCommand extends Command {
if (rustflags.length > 0) { if (rustflags.length > 0) {
additionalEnv['RUSTFLAGS'] = rustflags.join(' ') additionalEnv['RUSTFLAGS'] = rustflags.join(' ')
} }
let isZigExisted = false
if (isCrossForLinux || isCrossForMacOS) {
try {
execSync('zig version')
isZigExisted = true
} catch (e) {
if (this.useZig) {
throw new TypeError(
`Could not find ${chalk.green('zig')} on the PATH`,
)
} else {
debug(
`Could not find ${chalk.green(
'zig',
)} on the PATH, fallback to normal linker`,
)
}
}
}
if (this.useZig) { if ((this.useZig || isCrossForLinux || isCrossForMacOS) && isZigExisted) {
const zigABIVersion =
this.zigABIVersion ?? (isCrossForLinux && triple.abi === 'gnu')
? DEFAULT_GLIBC_TARGET
: null
const zigTarget = `${ZIG_PLATFORM_TARGET_MAP[triple.raw]}${ const zigTarget = `${ZIG_PLATFORM_TARGET_MAP[triple.raw]}${
this.zigABIVersion ? `.${this.zigABIVersion}` : '' zigABIVersion ? `.${zigABIVersion}` : ''
}` }`
if (!zigTarget) { if (!zigTarget) {
throw new Error(`${triple.raw} can not be cross compiled by zig`) throw new Error(`${triple.raw} can not be cross compiled by zig`)
} }
const paths = envPaths('napi-rs') const paths = envPaths('napi-rs')
const shellFileExt = process.platform === 'win32' ? 'bat' : 'sh' const shellFileExt = process.platform === 'win32' ? 'cmd' : 'sh'
const linkerWrapperShell = join( const linkerWrapperShell = join(
paths.cache, paths.cache,
`zig-linker-${triple.raw}.${shellFileExt}`, `zig-linker-${triple.raw}.${shellFileExt}`,
@ -302,31 +350,43 @@ export class BuildCommand extends Command {
) )
const linkerWrapper = join(paths.cache, `zig-cc-${triple.raw}.js`) const linkerWrapper = join(paths.cache, `zig-cc-${triple.raw}.js`)
mkdirSync(paths.cache, { recursive: true }) mkdirSync(paths.cache, { recursive: true })
const forwardArgs = process.platform === 'win32' ? '%*' : '$@' const forwardArgs = process.platform === 'win32' ? '"%*"' : '$@'
if (triple.arch === 'arm') {
await patchArmFeaturesHForArmTargets()
}
await writeFileAsync( await writeFileAsync(
linkerWrapperShell, linkerWrapperShell,
`#!/bin/sh\nnode ${linkerWrapper} ${forwardArgs}`, process.platform === 'win32'
? `@IF EXIST "%~dp0\\node.exe" (
"%~dp0\\node.exe" "${linkerWrapper}" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "${linkerWrapper}" %*
)`
: `${SHEBANG_SH}node ${linkerWrapper} ${forwardArgs}`,
{ {
mode: '777', mode: '777',
}, },
) )
await writeFileAsync( await writeFileAsync(
CCWrapperShell, CCWrapperShell,
`#!/bin/sh\nzig cc -target ${zigTarget} ${forwardArgs}`, `${SHEBANG_SH}zig cc -target ${zigTarget} ${forwardArgs}`,
{ {
mode: '777', mode: '777',
}, },
) )
await writeFileAsync( await writeFileAsync(
CXXWrapperShell, CXXWrapperShell,
`#!/bin/sh\nzig c++ -target ${zigTarget} ${forwardArgs}`, `${SHEBANG_SH}zig c++ -target ${zigTarget} ${forwardArgs}`,
{ {
mode: '777', mode: '777',
}, },
) )
await writeFileAsync( await writeFileAsync(
linkerWrapper, linkerWrapper,
`#!/usr/bin/env node\nconst{writeFileSync} = require('fs')\n${processZigLinkerArgs.toString()}\nconst {status} = require('child_process').spawnSync('zig', ['${ `${SHEBANG_NODE}const{writeFileSync} = require('fs')\n${processZigLinkerArgs.toString()}\nconst {status} = require('child_process').spawnSync('zig', ['${
triple.platform === 'win32' ? 'c++' : 'cc' triple.platform === 'win32' ? 'c++' : 'cc'
}', ...processZigLinkerArgs('${ }', ...processZigLinkerArgs('${
triple.raw triple.raw
@ -349,16 +409,54 @@ export class BuildCommand extends Command {
}) })
additionalEnv[`CARGO_TARGET_${envTarget}_LINKER`] = linkerWrapperShell additionalEnv[`CARGO_TARGET_${envTarget}_LINKER`] = linkerWrapperShell
} }
debug(`Platform: ${JSON.stringify(triple, null, 2)}`)
execSync(cargoCommand, { if (triple.platform === 'android') {
env: { const { ANDROID_NDK_LATEST_HOME } = process.env
...process.env, if (!ANDROID_NDK_LATEST_HOME) {
...additionalEnv, console.info(
TYPE_DEF_TMP_PATH: intermediateTypeFile, `${chalk.yellow(
}, 'ANDROID_NDK_LATEST_HOME',
stdio: 'inherit', )} environment variable is missing`,
cwd, )
}) }
const targetArch = triple.arch === 'arm' ? 'armv7a' : 'aarch64'
const targetPlatform =
triple.arch === 'arm' ? 'androideabi24' : 'android24'
Object.assign(additionalEnv, {
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-android24-clang`,
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-androideabi24-clang`,
CC: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang`,
CXX: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${targetArch}-linux-${targetPlatform}-clang++`,
AR: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar`,
PATH: `${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${process.env.PATH}`,
})
}
try {
execSync(cargoCommand, {
env: {
...process.env,
...additionalEnv,
TYPE_DEF_TMP_PATH: intermediateTypeFile,
},
stdio: 'inherit',
cwd,
})
} catch (e) {
if (cargo === 'cargo-xwin') {
console.warn(
`You are cross compiling ${chalk.underline(
triple.raw,
)} target on ${chalk.green(process.platform)} host`,
)
} else if (isCrossForLinux || isCrossForMacOS) {
console.warn(
`You are cross compiling ${chalk.underline(
triple.raw,
)} on ${chalk.green(process.platform)} host`,
)
}
throw e
}
const { binaryName, packageName } = getNapiConfig(this.configFileName) const { binaryName, packageName } = getNapiConfig(this.configFileName)
let cargoArtifactName = this.cargoName let cargoArtifactName = this.cargoName
if (!cargoArtifactName) { if (!cargoArtifactName) {
@ -721,3 +819,30 @@ async function writeJsBinding(
) )
} }
} }
async function patchArmFeaturesHForArmTargets() {
let zigExePath: string
try {
const zigEnv = JSON.parse(execSync(`zig env`, { encoding: 'utf8' }).trim())
zigExePath = zigEnv['zig_exe']
} catch (e) {
throw new Error(
'Cannot get zig env correctly, please ensure the zig is installed correctly on your system',
)
}
try {
await writeFileAsync(
join(zigExePath, '../lib/libc/glibc/sysdeps/arm/arm-features.h'),
ARM_FEATURES_H,
{
mode: 0o644,
},
)
} catch (e) {
throw new Error(
`Cannot patch arm-features.h, error: ${
(e as Error).message || e
}. See: https://github.com/ziglang/zig/issues/3287`,
)
}
}