From 696c2ddcd841d416f53a8917fc55cf893d3a0642 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sun, 20 Nov 2022 22:24:02 +0800 Subject: [PATCH] feat(cli): auto choose the tooling for cross compiling (#1367) --- cli/CHANGELOG.md | 30 +++++++ cli/package.json | 2 +- cli/src/arm-features.h.ts | 56 +++++++++++++ cli/src/build.ts | 167 +++++++++++++++++++++++++++++++++----- 4 files changed, 233 insertions(+), 22 deletions(-) create mode 100644 cli/src/arm-features.h.ts diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 7d367fb2..35acc11b 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file. 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) ### Bug Fixes diff --git a/cli/package.json b/cli/package.json index 8a8dc5d6..948996d8 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@napi-rs/cli", - "version": "2.12.1", + "version": "2.13.0-alpha.6", "description": "Cli tools for napi-rs", "keywords": [ "cli", diff --git a/cli/src/arm-features.h.ts b/cli/src/arm-features.h.ts new file mode 100644 index 00000000..2399f15a --- /dev/null +++ b/cli/src/arm-features.h.ts @@ -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 +. */ + +#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 */ +` diff --git a/cli/src/build.ts b/cli/src/build.ts index 2a99dc6d..94c79771 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -8,6 +8,7 @@ import { Command, Option } from 'clipanion' import envPaths from 'env-paths' import { groupBy } from 'lodash-es' +import { ARM_FEATURES_H } from './arm-features.h' import { getNapiConfig } from './consts' import { debugFactory } from './debug' import { createJsBinding } from './js-binding-template' @@ -34,8 +35,14 @@ const ZIG_PLATFORM_TARGET_MAP = { 'aarch64-apple-darwin': 'aarch64-macos', 'aarch64-unknown-linux-gnu': 'aarch64-linux-gnu', '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[]) { if (platform.includes('apple')) { const newArgs = args.filter( @@ -158,7 +165,7 @@ export class BuildCommand extends Command { 'lto', )} and increase ${chalk.green( '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', )}`, }, @@ -256,11 +263,29 @@ export class BuildCommand extends Command { ] .filter((flag) => Boolean(flag)) .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 intermediateTypeFile = join(tmpdir(), `type_def.${Date.now()}.tmp`) debug(`Run ${chalk.green(cargoCommand)}`) - const additionalEnv = {} const rustflags = process.env.RUSTFLAGS ? process.env.RUSTFLAGS.split(' ') @@ -278,16 +303,39 @@ export class BuildCommand extends Command { if (rustflags.length > 0) { 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]}${ - this.zigABIVersion ? `.${this.zigABIVersion}` : '' + zigABIVersion ? `.${zigABIVersion}` : '' }` if (!zigTarget) { throw new Error(`${triple.raw} can not be cross compiled by zig`) } const paths = envPaths('napi-rs') - const shellFileExt = process.platform === 'win32' ? 'bat' : 'sh' + const shellFileExt = process.platform === 'win32' ? 'cmd' : 'sh' const linkerWrapperShell = join( paths.cache, `zig-linker-${triple.raw}.${shellFileExt}`, @@ -302,31 +350,43 @@ export class BuildCommand extends Command { ) const linkerWrapper = join(paths.cache, `zig-cc-${triple.raw}.js`) mkdirSync(paths.cache, { recursive: true }) - const forwardArgs = process.platform === 'win32' ? '%*' : '$@' + const forwardArgs = process.platform === 'win32' ? '"%*"' : '$@' + if (triple.arch === 'arm') { + await patchArmFeaturesHForArmTargets() + } await writeFileAsync( 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', }, ) await writeFileAsync( CCWrapperShell, - `#!/bin/sh\nzig cc -target ${zigTarget} ${forwardArgs}`, + `${SHEBANG_SH}zig cc -target ${zigTarget} ${forwardArgs}`, { mode: '777', }, ) await writeFileAsync( CXXWrapperShell, - `#!/bin/sh\nzig c++ -target ${zigTarget} ${forwardArgs}`, + `${SHEBANG_SH}zig c++ -target ${zigTarget} ${forwardArgs}`, { mode: '777', }, ) + await writeFileAsync( 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' }', ...processZigLinkerArgs('${ triple.raw @@ -349,16 +409,54 @@ export class BuildCommand extends Command { }) additionalEnv[`CARGO_TARGET_${envTarget}_LINKER`] = linkerWrapperShell } - - execSync(cargoCommand, { - env: { - ...process.env, - ...additionalEnv, - TYPE_DEF_TMP_PATH: intermediateTypeFile, - }, - stdio: 'inherit', - cwd, - }) + debug(`Platform: ${JSON.stringify(triple, null, 2)}`) + if (triple.platform === 'android') { + const { ANDROID_NDK_LATEST_HOME } = process.env + if (!ANDROID_NDK_LATEST_HOME) { + console.info( + `${chalk.yellow( + 'ANDROID_NDK_LATEST_HOME', + )} environment variable is missing`, + ) + } + 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) let cargoArtifactName = this.cargoName 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`, + ) + } +}