Python3 交叉编译 numpy pandas scipy scikit-learn

1. 概述

由于需要将Python3.7 和一些软件包交叉编译到 armv7 平台硬件,如果是arm64位的系统,很多包都有预编译好的版本,可直接下载。本文主要在基于 crossenv(https://github.com/benfogle/crossenv)环境下交叉编译。

2. 编译环境搭建

  • 创建编译环境路径 /home/ym/python-build,创建 /home/ym/python-build/install路径用于安装主机编译后的python, 创建路径/home/ym/python-build/install-arm用于安装交叉编译的python。

  • 下载python3 源码到 home/ym/python-build, 并解压,下载路径https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz

  • 编译主机安装 Docker 环境,不同的linux发行版本安装方式不同, Debian -> apt, Redhat -> yum,我安装的版本是

  • 在编译路径下创建一个 Dockerfile 文件:

    FROM ubuntu:18.04
    ENV TZ=Asia/Shanghai
    ENV LANG=en_US.UTF-8

    RUN sed -i -e 's|archive.ubuntu.com|mirrors.tuna.tsinghua.edu.cn|g' \
    -e 's|security.ubuntu.com|mirrors.tuna.tsinghua.edu.cn|g' \
    /etc/apt/sources.list

    RUN apt update && apt install -y vim curl proxchains gcc \
    build-essential crossbuild-essential-armhf \
    libsdl1.2-dev xterm mesa-common-dev zstd liblz4-tool libffi-dev \
    cmake libssl-dev bc device-tree-compiler flex bison libncurses-dev
    lzma liblzma-dev libbz2-dev gfortran libopenblas-dev liblapack-dev
    gfortran-arm-linux-gnueabihf

    RUN rm -rf /etc/apt/apt.conf.d/docker-clean

    RUN mkdir -p /root/project
    WORKDIR /root
    CMD ["/bin/bash"]

  • 基于 Dockerfile 构建容器

    docker build -f Dockerfile -t ubuntu:python .

  • 运行容器,将编译路径python-build映射至容器路径/root/project路径下:

    docker run -v /home/ym/python-build/:/root/project -it ubuntu:python

3. 编译主机版本 python

  • 进入python源码目录

    ./configure --prefix=/root/project/install && make && make install

4. 交叉编译目标板 python

  • 交叉编译 openssl

    Adapted from https://github.com/japaric/cross

    set -ex

    INSTALL_DIR=/root/project/openssl
    mkdir -p $INSTALL_DIR
    main() {

    复制代码
      local version=1.1.1l
      local os=linux-armv4
      local triple=arm-linux-gnueabihf-
      local sysroot=$INSTALL_DIR
    
      local dependencies=(
          ca-certificates
          curl
          m4
          make
          perl
      )
    
      # NOTE cross toolchain must be already installed
      apt-get update
      for dep in ${dependencies[@]}; do
          if ! dpkg -L $dep; then
              apt-get install --no-install-recommends -y $dep
          fi
      done
    
      td=$(mktemp -d)
    
      pushd $td
      [ -e ./openssl-$version.tar.gz ] || {
          curl -L https://www.openssl.org/source/openssl-$version.tar.gz -o ./openssl-$version.tar.gz
      }
      tar --strip-components 1 -xz -f ./openssl-$version.tar.gz
    
      AR=${triple}ar CC=${triple}gcc ./Configure \
        --prefix=${sysroot}/usr \
        --openssldir=${sysroot}/usr \
        shared \
        no-asm \
        $os \
        -fPIC \
        ${@:4}
      make -j$(nproc)
      make install_sw
      
      # clean up
    
      popd
    
      rm -rf $td
      #rm $0
      #cp /usr/local/arm/usr/lib/pkgconfig/* /usr/share/pkgconfig/

    }

    main "${@}"

  • 交叉编译 bzip2, zlib, lzma 这些包会被python内建模块识别调用,并被 pandas 所依赖,同时需要把 zlib, lzma的动态库拷贝到目标板内核库的路径下。

  • lzma 下载地址https://xz.tukaani.org/xz-utils/#releases

  • 交叉编译libffi, 该包与 python ctypes 模块关联,注意交叉编译完成后需要将安装后的内容拷贝到交叉工具链的路径下(cp -rfp libffi/* /usr/arm-linux-gnueabihf/),并在 Python 配置选项中指定--with-system-ffi参数,交叉编译时 Python 自动构建 ctypes 模块,测试发现通过 LIBS 变量指定不会生效。

  • 创建一个 config.site 文件:

    ac_cv_file__dev_ptc=no
    ac_cv_buggy_getaddrinfo=no
    ac_cv_file__dev_ptmx=no

  • 清除 python 源码相关环境(make distclean),运行下面脚本交叉编译目标版本 python-target(armv7),安装到路径/root/project/install-arm

    #!/bin/sh
    CROSS_COMPILE=arm-linux-gnueabihf
    export CC=CROSS_COMPILE-gcc export CXX=CROSS_COMPILE-g++
    export AR=CROSS_COMPILE-ar export STRIP=CROSS_COMPILE-strip
    export LD=CROSS_COMPILE-ld export RANLIB=CROSS_COMPILE-ranlib
    export READELF=CROSS_COMPILE-readelf export PATH=PATH:/root/project/install/bin
    export CONFIG_SITE=/root/project/config.site

    cd Python-3.7.2 && ./configure --enable-optimizations --with-openssl=/root/project/openssl/usr --with-system-ffi
    LDFLAGS="-L/root/project/bzip2-1.0.8 -L/root/project/zlib/lib -L/root/project/lzma/lib"
    LIBS="-lbz2 -lz -llzma"
    --host=arm-linux-gnueabihf
    --build=x86_64-linux-gnu
    --target=arm-linux-gnueabihf
    --disable-ipv6
    --without-pydebug
    --without-dtrace
    --prefix=/root/project/install-arm
    make -j4 && make install

到此如果编译过程中没有出错的话,交叉编译后的 python 应该可以直接在 目标板上工作了,在目标板上创建一个 python 目录,将 install-arm 中的文件全部拷贝至该路径中,构建一个软连接 ln -sf /python/bin/python3.7 /usr/bin/python,然后在控制运行 python 查看是否正常:

5. 通过 crossenv 交叉编译 numpy,pandas

  • 如果只是想交叉编译 numpy 或者 pandas, 看到这一小节就行了,其中pandas 依赖 numpy。如果还要编译其他包请用第六节脚本编译的方法交叉编译 numpy.

    1. 在主机环境下安装 crossenv
      cd /root/project/install/bin
      ./pip3 install crossenv
    2. 使用crossenv创建 python-target 编译的虚拟环境
      ./python3 -m crossenv /root/project/install-arm/bin/python3.7 cross_venv
    3. 激活虚拟环境
      . cross_venv/bin/activate
      注意没有代理可使用国内镜像如 pip install xx -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
    4. 编译安装 numpy
      build-pip -v install numpy
      pip -v install numpy
    5. 编译安装 pandas
      build-pip -v install pandas
      pip -v install pandas
      注意pandas 依赖 dateutil,dateutil会使用目标板的时区信息,请检查目标板是否有 /usr/share/zoneinfo 和 /etc/localtime 文件,没有可能在导入 pandas 包时会报错,可将交叉编译后的 dateutil/zoneinfo/dateutil-zoneinfo.tar.gz 解压至 /usr/share/zoneinfo中,并创建软连接 ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
      编译成功后可在 cross_venv/cross/lib/pythonVERSION/site-packages 路径下查看编译完后的包
      其中包主要分两类:
      1). package -> 对应包的实际文件
      2). package-VERSION-dist-info -> 包的信息
    6. 将对应的 package 拷贝到目标板 path/to/python/lib/pythonVERSION/site-package 路径下
  • 这里导入包的时候,可能会报一些库文件缺失,需要把对应的库拷贝到目标板库/lib的路径下。

6. 通过 crossenv 交叉编译 scikit-learn

  • 这里先列一下依赖项

  • scikit-learn:

    • NumPy
    • SciPy :
      • NumPy
      • openblas
    • joblib
    • threadpoolctl
  • 由于 SciPy 强依赖 numpy 中 openblas 包,openblas 是一个数学运算库,可用于加速 numpy 运算,如果只是单纯编译 numpy,那么 openblas 是一个可选项,但是如果需要编译 SciPy,openblas 就是一个强依赖项了,但是 openblas 是由 Fortran 语言编写,直接用 crossenv 环境编译会报错,下面用一个脚本来交叉编译 numpy 和 SciPy,编译之前,先用交叉工具链编译 openblas,并在虚拟环境中 build-pip/pip安装 Cython < 3 版本:

    1. 确认以下几个包已安装
      apt-get install gfortran libopenblas-dev liblapack-dev -y
    2. 安装 fortran 交叉编译工具
      apt-get install gfortran-arm-linux-gnueabihf
    3. 交叉编译 OpenBLAS-0.3.22
      cd OpenBLAS-0.3.22 && make TARGET=ARMV7 HOSTCC=gcc BINARY=32 CC=arm-linux-gnueabihf-gcc FC=arm-linux-gnueabihf-gfortran
      make TARGET=ARMV7 PREFIX=/root/project/openblas install
  • 交叉编译 scipy 脚本

    #!/bin/bash

    ####################

    Script to build numpy and scipy wheels for ARM.

    set -ex

    OUTPUT=PWD/output WORKING=OUTPUT/build
    if [ ! -d "WORKING" ];then mkdir -p WORKING
    fi

    GFORTRAN=arm-linux-gnueabihf-gfortran
    OPENBLAS_INSTALL_DIR=$PWD/openblas

    BUILD_PYTHON=PWD/install/bin/python3 HOST_PYTHON=PWD/install-arm/bin/python3

    NUMPY_URL=https://files.pythonhosted.org/packages/45/b7/de7b8e67f2232c26af57c205aaad29fe17754f793404f59c8a730c7a191a/numpy-1.21.6.zip
    SCIPY_URL=https://files.pythonhosted.org/packages/a7/5c/495190b8c7cc71977c3d3fafe788d99d43eeb4740ac56856095df6a23fbd/scipy-1.3.3.tar.gz
    NUMPY_VERSION={NUMPY_URL##*/} if [[ NUMPY_VERSION =~ .zip* ]];then
    NUMPY_DIR={NUMPY_VERSION%%.zip*} elif [[ NUMPY_VERSION =~ .tar.gz* ]];then
    NUMPY_DIR={NUMPY_VERSION%%.tar.gz*} fi SCIPY_VERSION={SCIPY_URL##/}
    SCIPY_DIR=${SCIPY_VERSION%%.tar.gz
    }

    PYPI_MIRROR="-i http://pypi.douban.com/simple --trusted-host pypi.douban.com"
    ################################################################

    Set up crossenv

    BUILD_PYTHON -m pip install crossenv CROSS_VENV=PWD/install/bin/cross_venv
    if [ ! -d "CROSS_VENV" ];then BUILD_PYTHON -m crossenv HOST_PYTHON CROSS_VENV
    fi
    . CROSS_VENV/bin/activate pip install wheel PYPI_MIRROR

    BUILD_SITE=PWD/install/bin/cross_venv/build/lib/python3.7/site-packages CROSS_SITE=PWD/install/bin/cross_venv/cross/lib/python3.7/site-packages

    ################################################################

    Host-numpy

    Install so we get the libnpymath.a in the right place.

    if [ ! -f "NUMPY_VERSION" ];then curl -OL NUMPY_URL
    fi
    if [ ! -d "NUMPY_DIR" ];then if [[ NUMPY_VERSION =~ .zip* ]];then
    unzip NUMPY_VERSION elif [[ NUMPY_VERSION =~ .tar.gz* ]];then
    tar xf NUMPY_VERSION fi fi cd NUMPY_DIR
    cat > site.cfg <<EOF
    [openblas]
    libraries = openblas
    library_dirs = OPENBLAS_INSTALL_DIR/lib include_dirs = OPENBLAS_INSTALL_DIR/include
    extra_link_args = -lgfortran
    EOF
    F90=GFORTRAN cross-python setup.py install F90=GFORTRAN cross-python setup.py bdist_wheel
    cd ..

    ################################################################

    Build-numpy. Need to patch after install.

    NUMPY_PIP_VERSION={NUMPY_DIR##*-} build-pip install -v numpy==NUMPY_PIP_VERSION PYPI_MIRROR INI=(find BUILD_SITE -name 'npymath.ini') LIBDIR=(find CROSS_SITE -path '*/numpy/core/lib') INCDIR=(find $CROSS_SITE -path '*/numpy/core/include')

    cat > $INI <<EOF
    [meta]
    Name=npymath
    Description=Portable, core math library implementing C99 standard
    Version=0.1

    [variables]

    Force it to find cross-build libs when we build scipy

    libdir=LIBDIR includedir=INCDIR

    [default]
    Libs=-L${libdir} -lnpymath
    Cflags=-I${includedir}
    Requires=mlib

    [msvc]
    Libs=/LIBPATH:${libdir} npymath.lib
    Cflags=/INCLUDE:${includedir}
    Requires=mlib
    EOF

    #################################################################

    host-scipy

    if [ ! -f "SCIPY_VERSION" ];then curl -OL SCIPY_URL
    fi
    if [ ! -d "SCIPY_DIR" ];then tar xf SCIPY_VERSION
    fi
    cd SCIPY_DIR cat > site.cfg <OPENBLAS_INSTALL_DIR/lib
    include_dirs = $OPENBLAS_INSTALL_DIR/include
    extra_link_args = -lgfortran
    EOF

    F90=GFORTRAN python setup.py bdist_wheel F90=GFORTRAN cross-python setup.py install
    cd ..

  • 交叉编译 scikit-learn: :

  • SciPy -> 先前利用脚本已经完成了交叉编译,现在使用 build-pip 安装 build 版本:

    build-pip install -v scipy=1.3.3 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

  • joblib -> build-pip/pip 直接安装没有其他依赖

    build-pip install joblib==0.11 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
    pip install joblib==0.11 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

  • threadpoolctl -> build-pip/pip 直接安装没有其他依赖

    build-pip install threadpoolctl==2.0.0 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
    pip install threadpoolctl==2.0.0 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

  • 安装 scikit-learn:

    build-pip install -v scikit-learn==1.0.2 --no-build-isolation -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
    pip install -v scikit-learn==1.0.2 --no-build-isolation -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

至此交叉编译全部完成,将对应的包拷贝到目标板查看

相关推荐
数据智能老司机6 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机7 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机7 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i7 小时前
drf初步梳理
python·django
每日AI新事件7 小时前
python的异步函数
python
这里有鱼汤8 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
databook17 小时前
Manim实现脉冲闪烁特效
后端·python·动效
程序设计实验室17 小时前
2025年了,在 Django 之外,Python Web 框架还能怎么选?
python
倔强青铜三19 小时前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试