交叉编译和安装跨平台程序

nixpkgs原生支持x86和arm指令集。 通过对nixpkgs配置可以轻松实现交叉编译, 跨平台程序的安装等功能。

太长不看

  • x86_64上的aarch64交叉编译器

    (with import <nixpkgs> {crossSystem="aarch64-linux";}; stdenv.cc)

  • aarch64的hello应用程序

    (with import <nixpkgs> {localSystem.system="aarch64-linux";crossSystem="aarch64-linux";}; hello)

  • 应用于nix-shell的例子

    shell_cross_platform.nix

目录

简介

nixpkgs1众多输入参数中,包含localSystemcrossSystem2

  • localSystem

    The system packages will be built on.

    本地系统,即工具链运行的平台。

  • crossSystem

    The system packages will ultimately be run on.

    程序运行的平台。

通过localSystemcrossSystem不同值的组合, 可以实现交叉编译、安装其他架构的原生应用。 下面从localSystemcrossSystem的语法和应用两方面进行介绍。 语法章节从nixpkgs源码的角度出发,介绍其语法的组成。 应用章节围绕一个nix-shell脚本的实际例子, 介绍x86_64平台的交叉编译和安装aarch64架构的原生应用的方法。

localSystemcrossSystem的语法

localSystemcrossSystem由4个维度去刻画一个系统:cpu, vendor, kernel, abi。 localSystemcrossSystem的值为字符串或者{system=字符串;}3。 system字符串为可以包含前述4个维度的1~4个维度。 nix在解析时会将省略的维度按以某些默认值补充完整。 维度之间之间用-分割。 因此system字符串形式上为"cpu-vendor-kernel-abi"。 字符串不同数量的维度及其可用的值, 按匹配优先级由高到低列举如下4

cpu-vendor-kernel-abi

system字符串cpuvendorkernelabi
"avr"avrnoneunknown
"{cpu}-cygwin"{cpu}windowscygnus
"{cpu}-windows"{cpu}windowsmsvc
"{cpu}-elf"{cpu}unknownnoneelf
"{cpu}-{kernel}"{cpu}{kernel}
"{cpu}-apple-{kernel}"{cpu}apple{kernel}
"{cpu}-linux-gnu"{cpu}linuxgnu
"{cpu}-{vendor}-mingw32"{cpu}{vendor}windows
"{cpu}-{vendor}-wasi"{cpu}{vendor}wasi
"{cpu}-{vendor}-redox"{cpu}{vendor}redox
"{cpu}-{vendor}-mmixware"{cpu}{vendor}mmixware
"{cpu}-{vendor}-netbsd*"{cpu}{vendor}netbsd*
"{cpu}-{vendor}-eabi"{cpu}unknown{kernel}eabi
"{cpu}-{vendor}-eabihf"{cpu}unknown{kernel}eabihf
"{cpu}-{kernel}-elf"{cpu}unknown{kernel}elf
"{cpu}-*-{ghcjs}"{cpu}unknownghcjs
"{cpu}-{vendor}-genode"{cpu}{vendor}genode
"{cpu}-{vendor}-{kernel}-{abi} "{cpu}{vendor}{kernel}{abi}

cpu

cpu字符串可取的值列举如下5,

cpu字符串bitssignificantBytefamilyversionarch
"arm"32littleEndian"arm"
"armv5tel"32littleEndian"arm""5""armv5t"
"armv6m"32littleEndian"arm""6""armv6-m"
"armv6l"32littleEndian"arm""6""armv6"
"armv7a"32littleEndian"arm""7""armv7-a"
"armv7r"32littleEndian"arm""7""armv7-r"
"armv7m"32littleEndian"arm""7""armv7-m"
"armv7l"32littleEndian"arm""7""armv7"
"armv8a"32littleEndian"arm""8""armv8-a"
"armv8r"32littleEndian"arm""8""armv8-a"
"armv8m"32littleEndian"arm""8""armv8-m"
"aarch64"64littleEndian"arm""8""armv8-a"
"aarch64_be"64bigEndian"arm""8""armv8-a"
"i386"32littleEndian"x86""i386"
"i486"32littleEndian"x86""i486"
"i586"32littleEndian"x86""i586"
"i686"32littleEndian"x86""i686"
"x86_64"64littleEndian"x86""x86-64"
"mips"32bigEndian"mips"
"mipsel"32littleEndian"mips"
"mips64"64bigEndian"mips"
"mips64el"64littleEndian"mips"
"mmix"64bigEndian"mmix"
"m68k"32bigEndian"m68k"
"powerpc"32bigEndian"power"
"powerpc64"64bigEndian"power"
"powerpc64le"64littleEndian"power"
"powerpcle"32littleEndian"power"
"riscv32"32littleEndian"riscv"
"riscv64"64littleEndian"riscv"
"s390"32bigEndian"s390"
"s390x"64bigEndian"s390"
"sparc"32bigEndian"sparc"
"sparc64"64bigEndian"sparc"
"wasm32"32littleEndian"wasm"
"wasm64"64littleEndian"wasm"
"alpha"64littleEndian"alpha"
"msp430"16littleEndian"msp430"
"avr"8"avr"
"vc4"32littleEndian"vc4"
"or1k"32bigEndian"or1k"
"js"32littleEndian"js"
cpuTypes

cpu之间的兼容性(具有传递性和自反性)如下6

vendor

vendor字符串可取值"apple", "pc"(windows), "w64"(MinGW-w64), "none", "unknown"(default)。

kernel

kernel字符串可取值如下表7

kernel字符串execFormatfamilies
"macos"machodarwin
"darwin"
"ios"machodarwin
"watchos"
"tvos"
"freebsd"elfbsd
"linux"elf
"netbsd"elfbsd
"none"unknown
"openbsd"elfbsd
"solaris"elf
"wasi"wasm
"redox"elf
"windows"pe
"win32"
"ghcjs"unknown
"genode"elf
"mmixware"unknown

abi

abi字符串可取的值列举如下8

abi字符串floatabiNote
"cygnus"
"msvc"
"eabi"softfor ARM, PowerPC
"eabihf"hardfor ARM, PowerPC
"elf"
"androideabi"
"android"not 32-bit
"gnueabi"soft
"gnueabihf"hard
"gnu"not 32-bit
"gnuabi64"64
"musleabi"soft
"musleabihf"hard
"musl"
"uclibceabihf"soft
"uclibceabi"hard
"uclibc"
"unknown"

localSystemcrossSystem的应用

aarch64交叉工具链和程序的详细例子

以x86为本地指令集,localSystemcrossSystem的组合有以下效果

crossSystem↓ →localSystem"x86_64-linux""aarch64-linux"
"x86_64-linux"通常情况
"aarch64-linux"交叉编译aarch64原生aarch64应用

因此基于这3种组合,可以在同一个shell环境中配置出3种软件, 代码见[cross_platform.nix]({{ site.repo_url }}/scripts/shell/cross_platform.nix)。

pkgs_arm_cross软件包的stdenv.cc为x86平台的arm交叉编译器。 nixpkgs channel只包含了原生x86应用和原生arm应用。 交叉编译的arm应用和原生arm应用的derivation不一样。 因此使用pkgs_arm_cross中的应用, 则会使用交叉编译器从源码开始编译arm应用, 而不是直接拉取nixpkgs channel的原生arm应用。

pkgs_arm_native软件包包含原生arm软件包。 从这个软件包拉取的应用和在arm平台的拉取到的应用一致。 例如figlet将直接从nix channel中拉取。

pkgs即x86原生的软件包。

shell_cross_platform.nix使用例子,

# 创建一个新的shell环境,包含stdenv.cc, figlet, qemu
$ nix-shell shell_cross_platform.nix

# 使用交叉编译工具链的c编译器
$ aarch64-unknown-linux-gnu-gcc helloworld.c -o helloworld
$ file helloworld
helloworld: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/01kw0gb38phviarfv3fca49dpqh0qwlx-glibc-aarch64-unknown-linux-gnu-2.33-123/lib/ld-linux-aarch64.so.1, for GNU/Linux 2.6.32, with debug_info, not stripped

# arm原生应用
$ file `command -v figlet`
/nix/store/4f70f04bvd664n00jlnzccyzxd35lykw-figlet-2.2.5/bin/figlet: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/rjc27shzir243n1w3127w713fijamf6v-glibc-2.33-123/lib/ld-linux-aarch64.so.1, for GNU/Linux 2.6.32, not stripped

# 直接执行figlet会出错
$ figlet
bash: /nix/store/4f70f04bvd664n00jlnzccyzxd35lykw-figlet-2.2.5/bin/figlet: cannot execute binary file: Exec format error

# 使用QEMU执行figlet即可
$ qemu-aarch64 `command -v figlet` miao!
           _             _
 _ __ ___ (_) __ _  ___ | |
| '_ ` _ \| |/ _` |/ _ \| |
| | | | | | | (_| | (_) |_|
|_| |_| |_|_|\__,_|\___/(_)

mips交叉工具链的例子

[cross_mips.nix]({{ site.repo_url }}/scripts/shell/cross_mips.nix)

crossSystem的传递

  • pkgs/top-level/stage.nix:
    • pkgsCross = lib.mapAttrs (n: crossSystem:
        nixpkgsFun { inherit crossSystem; })
        lib.systems.examples;
      
  • 其中pkgs/top-level/default.nix: nixpkgsFun = newArgs: import ./. (args // newArgs);
  • 也就是说lib.systems.examples的每个元素作为crossSystem传递给了pkgs/top-level/default.nix
  • pkgs/top-level/default.nix:
    • 首先crossSystem = lib.systems.elaborate crossSystem0
    • 接下来是推导
    • pkgs = boot stages;
    • pkgs = boot (stdenvStages {inherit crossSystem ...});
    • pkgs = boot (import ../stdenv {inherit crossSystem ...})
  • pkgs/stdenv/default.nix:
    • pkgs = boot (import ./cross {inherit crossSystem ...})
  • pkgs/stdenv/cross/default.nix:
    • pkgs = boot (lib.init bootStages ++ [(...) (..crossSystem..) (..crossSystem..)])
    • 包含了若干个stages,包括本地的bootStages的前几个stages、和crossSystem相关的3个stages(其实只有最后两个stages使用了crossSystem)
    • 这里需要搞清楚boot是怎么调用stages的
    • pkgs/top-level/default.nix:
      • boot = import ../stdenv/bo(__raw=false或未定义)oter.nix { inherit lib allPackages; };
      • boot = import ../stdenv/booter.nix { ...; allPackages = newArgs: import ./stage.nix ({inherit lib nixpkgsFun;} // newArgs);};
    • pkgs/stdenv/booter.nix:
      • 注释写了boot的功能,如下: 参数为一个函数数组,每个函数的输入为pkgset,输出要么是pkgset的参数(__raw=false或未定义),要么是pkgsset(__raw=true)。 (这里说的“pkgset的参数”会传给allPackages(是个函数,见上))

        # Type:
        #   [ pkgset -> (args to stage/default.nix) or ({ __raw = true; } // pkgs) ]
        #   -> pkgset
        #
        # In english: This takes a list of function from the previous stage pkgset and
        # returns the final pkgset. Each of those functions returns, if `__raw` is
        # undefined or false, args for this stage's pkgset (the most complex and
        # important arg is the stdenv), or, if `__raw = true`, simply this stage's
        # pkgset itself.
        
    • 所以最终还是得看下面两个使用了crossSystem的stages
  • pkgs/stdenv/cross/default.nix:
    • pkgs = boot (lib.init bootStages ++ [(...) ❶(..crossSystem..) ❷(..crossSystem..)])

    •   # Build tool Packages
        (vanillaPackages: {
          ...
          stdenv = assert ...;
            vanillaPackages.stdenv.override { targetPlatform = crossSystem; };
          ...
        })
      

      输入是vanillaPackages,输出是buildPackages的参数(为生成下一级的输入pkgset即buildPackages)。 因此,这一级(即buildPackages,pkgsCross..buildPackages)的3个平台为:

      buildhosttarget
      localSystemlocalSystemcrossSystem

      因此这一级的包是用来构建各种交叉工具链的包。

      注:nixpkgs里也有叫buildPackages的包,定义位于pkgs/top-level/stage.nix: buildPackages是pkgsBuildHost的别名,其中pkgs

    • # Run Packages
      (buildPackages: let
        ...
        stdenNoCC = adaptStdenv (buildPackages.stdenv.override (old: rec {
          buildPlatform = localSystem;
          hostPlatform = crossSystem;
          targetPlatform = crossSystem;
          ...
        }));
      in {
        ...
        stdenv = let
          baseStdenv = stdenNoCC.override {
            cc = ... buildPackages.gcc;
          }
        in ... baseStdenv;
        ...
      })
      

      这一级输入是buildPackages,输出就是我们平时pkgsCross.的包集合了。 这一级(即pkgsCross.)的3个平台为

      buildhosttarget
      localSystemcrossSystemcrossSystem

      其中要注意的是,pkgsCross..stdenv.cc就是pkgsCross..buildPackages.gcc。

引用

1

nixpkgs版本2022.01.20, commit hash: 7e149abe9db1509fa974bb2286f862a761ca0a07

2

nixpkgs/pkgs/top-level/default.nix

3

nixpkgs/lib/systems/default.nix: elaborate

4

nixpkgs/lib/systems/parse.nix: mkSkeletonFromList

5

nixpkgs/lib/systems/parse.nix: cpuTypes

6

nixpkgs/lib/systems/parse.nix: isCompatible

7

nixpkgs/lib/systems/parse.nix: kernels

8

nixpkgs/lib/systems/parse.nix: abis