Skip to content

Commit

Permalink
feat(cli): auto choose the tooling for cross compiling (#1367)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Nov 20, 2022
1 parent 035def0 commit 696c2dd
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 22 deletions.
30 changes: 30 additions & 0 deletions cli/CHANGELOG.md
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion 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",
Expand Down
56 changes: 56 additions & 0 deletions 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
<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 */
`
167 changes: 146 additions & 21 deletions cli/src/build.ts
Expand Up @@ -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'
Expand All @@ -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(
Expand Down Expand Up @@ -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',
)}`,
},
Expand Down Expand Up @@ -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(' ')
Expand All @@ -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}`,
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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`,
)
}
}

0 comments on commit 696c2dd

Please sign in to comment.