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

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众多输入参数中,包含localSystem和crossSystem2。

  • localSystem

    The system packages will be built on.

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

  • crossSystem

    The system packages will ultimately be run on.

    程序运行的平台。

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

localSystem和crossSystem的语法

localSystem和crossSystem由4个维度去刻画一个系统:cpu, vendor, kernel, abi。 localSystem和crossSystem的值为字符串或者{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"

localSystem和crossSystem的应用

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

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

↓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