【鸿蒙南向开发】如何优雅的一键编译OpenHarmony的llvm工具链

一、工具链基础介绍

1.1 工具链下载

可通过如何优雅的一键下载 OpenHarmony 代码进行下载
运行脚本选择 4.1

1.2 环境配置

下载 clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz 为 github 网址,当前已经下载了,可以直接使用

bash ./toolchain/llvm-project/llvm-build/env_prepare.sh
​

1.3 安装依赖

1.3.1 基础组件安装
sudo apt-get install autoconf
sudo apt-get install binutils-dev
sudo apt-get install automake
sudo apt-get install autotools-dev
sudo apt-get install texinfo
sudo apt-get install libncursesw5-dev
sudo apt-get install swig3.0
sudo apt-get install liblzma-dev
sudo apt-get install liblua5.3-dev
sudo apt-get install libedit-dev
sudo apt-get install libsphinxbase-dev
sudo apt-get install lzma-dev
sudo apt-get install swig
sudo apt-get install lua5.3
sudo apt install libncurses5
sudo apt install python3-sphinx
python3 -m pip install pyyaml
pip3 install -U Sphinx -i https://mirrors.aliyun.com/pypi/simple
pip3 install recommonmark -i https://mirrors.aliyun.com/pypi/simple

1.4 工具链编译

1.4.1 clang15.0.4 版本全量编译
python3 ./toolchain/llvm-project/llvm-build/build.py

1.4.2 clang15.0.4 版本不编译 windows 平台
python3 ./toolchain/llvm-project/llvm-build/build.py --no-build windows

1.4.3 clang12.0.1 版本全量编译

【Note】:该版本编译不支持 gtx30xx 系列显卡的电脑编译

# 方法一:官方指导
repo init -u https://gitee.com/OpenHarmony/manifest.git -b master -m llvm-toolchain.xml
# 将.repo/manifests/llvm-toolchain.xml文件内容替换成https://repo.huaweicloud.com/harmonyos/compiler/clang/12.0.1-1971c8/manifest-20230313.xml里面内容
repo sync -c
repo forall -c 'git lfs pull'
./toolchain/llvm-project/llvm-build/env_prepare.sh
python3 ./toolchain/llvm-project/llvm-build/build.py

# 方法二:本地指导

二、工具链脚本介绍

文档作为基于 x86_64 的 Ubuntu20.04 环境的解析,对于 windows 以及 mac 下编译直接忽略

2.1 env_prepare.sh 脚本解析

脚本路径:toolchain/llvm-project/llvm-build/env_prepare.sh

Ubuntu 下功能:

●下载 cmake 解压到 prebuilts/cmake/linux-x86 里面
●下载 llvm 解压到 prebuilts/clang/ohos/linux-x86_64
●下载 clang 解压到 prebuilts/clang/ohos/linux-x86_64
●重命名 prebuilts/clang/ohos/linux-x86_64/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04 为 prebuilts/clang/ohos/linux-x86_64/clang-10.0.1

2.1.1 设置 host_platform

当前是在 Ubuntu 中编译,所以 host_platform=linux

case $(uname -s) in
    Linux)

        host_platform=linux
        ;;
    Darwin)
        host_platform=darwin
        ;;
    *)
        echo "Unsupported host platform: $(uname -s)"
        exit 1
esac
​

2.1.2 设置 host_cpu

当前使用的是 x86_64,所以 host_cpu=x86_64

case $(uname -m) in
    arm64)

        host_cpu=arm64
        ;;
    *)
        host_cpu=x86_64
esac
​

2.1.3 设置当前根目录路径 code_dir

code_dir=$(pwd)
​

2.1.4 设置并创建下载安装包目录 bin_dir

bin_dir=${code_dir}/download_packages
if [ ! -d "${bin_dir}" ];then
    mkdir -p "${bin_dir}"
fi


2.1.5 download_and_archive 函数介绍

下载文件并解压

function download_and_archive() {
    archive_dir=$1
    download_source_url=$2
    # 获取download_source_url链接中的文件名,默认以/为分隔符
    bin_file=$(basename ${download_source_url})
    # 下载download_source_ur里的内容保存到${bin_dir}/${bin_file}文件中
    # -t3表示最大尝试链接次数为3次
    # -T10设定响应超时的秒数为10s
    # -O 把文档写到FILE文件中
    wget -t3 -T10 -O "${bin_dir}/${bin_file}" "${download_source_url}"
    if [ ! -d "${code_dir}/${archive_dir}" ];then
        mkdir -p "${code_dir}/${archive_dir}"
    fi
    # ${bin_file:0-3}表示获取bin_file的最后3个字符
    if [ "X${bin_file:0-3}" = "Xzip" ];then
        unzip -o "${bin_dir}/${bin_file}" -d "${code_dir}/${archive_dir}/"
    elif [ "X${bin_file:0-6}" = "Xtar.gz" ];then
        tar -xvzf "${bin_dir}/${bin_file}" -C "${code_dir}/${archive_dir}"
    else
        tar -xvf "${bin_dir}/${bin_file}" -C "${code_dir}/${archive_dir}"
    fi
}

2.1.6 配置下载路径和链接

copy_config="""
"""

copy_config_linux_x86_64="""
prebuilts/cmake,https://mirrors.huaweicloud.com/harmonyos/compiler/cmake/3.16.5/${host_platform}/cmake-${host_platform}-x86-3.16.5.tar.gz
prebuilts/clang/ohos/${host_platform}-${host_cpu},https://mirrors.huaweicloud.com/openharmony/compiler/clang/10.0.1-62608/${host_platform}/llvm.tar.gz
prebuilts/clang/ohos/${host_platform}-${host_cpu},https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
"""

copy_config_darwin_x86_64="""
prebuilts/cmake,https://mirrors.huaweicloud.com/harmonyos/compiler/cmake/3.16.5/${host_platform}/cmake-${host_platform}-x86-3.16.5.tar.gz
prebuilts/clang/ohos/${host_platform}-${host_cpu},https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz
"""

if [[ "${host_platform}" == "linux" ]]; then
    if [[ "${host_cpu}" == "x86_64" ]]; then
        copy_config+=${copy_config_linux_x86_64}
        echo "add ubuntu here"
    else
        echo "unknwon host_cpu - ${host_cpu} for linux"
    fi
elif [[ "${host_platform}" == "darwin" ]]; then
    if [[ "${host_cpu}" == "x86_64" ]]; then
        copy_config+=${copy_config_darwin_x86_64}
        echo "add x86-64 mac here"
    elif [[ "${host_cpu}" == "arm64" ]]; then
        echo "add m1 config here"
    else
        echo "unknwon host_cpu - ${host_cpu} for darwin"
    fi
else
    echo "unknown ${host_platform}"
fi

# 当前配置完成后
copy_config="""
prebuilts/cmake,https://mirrors.huaweicloud.com/harmonyos/compiler/cmake/3.16.5/${host_platform}/cmake-${host_platform}-x86-3.16.5.tar.gz
prebuilts/clang/ohos/${host_platform}-${host_cpu},https://mirrors.huaweicloud.com/openharmony/compiler/clang/10.0.1-62608/${host_platform}/llvm.tar.gz
prebuilts/clang/ohos/${host_platform}-${host_cpu},https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.1/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04.tar.xz
"""

2.1.7 下载代码

for i in $(echo ${copy_config})
do
    unzip_dir=$(echo $i|awk -F ',' '{print $1}')
    remote_url=$(echo $i|awk -F ',' '{print $2}')
    download_and_archive "${unzip_dir}" "${remote_url}"
done
​

2.1.8 配置工具链

# 不执行
if [ -d "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang-62608" ];then
    rm -rf "${code_dir}/prebuilts/clang/ohos/linux-x86_64/llvm"
    mv "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang-62608" "${code_dir}/prebuilts/clang/ohos/linux-x86_64/llvm"
    ln -snf 10.0.1 "${code_dir}/prebuilts/clang/ohos/linux-x86_64/llvm/lib/clang/current"
fi

# 执行
if [ -d "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04" ];then
    rm -rf "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang-10.0.1"
    mv "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang+llvm-10.0.1-x86_64-linux-gnu-ubuntu-16.04" "${code_dir}/prebuilts/clang/ohos/linux-x86_64/clang-10.0.1"
fi

# 不执行
if [ -d "${code_dir}/prebuilts/clang/ohos/darwin-x86_64/clang+llvm-12.0.0-x86_64-apple-darwin" ];then
    rm -rf "${code_dir}/prebuilts/clang/ohos/darwin-x86_64/clang-10.0.1"
    mv "${code_dir}/prebuilts/clang/ohos/darwin-x86_64/clang+llvm-12.0.0-x86_64-apple-darwin" "${code_dir}/prebuilts/clang/ohos/darwin-x86_64/clang-10.0.1"
fi
​

2.2 build.py 脚本解析

2.2.1 BuildConfig 类解析
2.2.1.1 parse_args 方法解析

定义所有编译的参数选项,然后返回该对象,从返回的对象中可以获取编译选项的状态

def parse_args(self):

    # description 用于描述该程序用来做什么
    parser = argparse.ArgumentParser(description='Process some integers.')

    # Options to skip build or packaging, can't skip two
    # 创建一个互斥组,确保互斥组中只有一个参数在命令行中可用
    # 参考用例:https://blog.youkuaiyun.com/weixin_36670529/article/details/113783239
    build_package_group = parser.add_mutually_exclusive_group()
    build_package_group.add_argument(
        '--skip-build',
        '-sb',
        action='store_true',
        default=False,
        help='Omit the build, perform the packaging step directly.')

    build_package_group.add_argument(
        '--skip-package',
        '-sp',
        action='store_true',
        default=False,
        help='Omit the packaging, perform the packaging step directly.')

    self.parse_add_argument(parser)

    known_platforms = ('windows', 'libs', 'lldb-mi', 'lldb-server', 'linux', 'check-api')
    # 将known_platforms的值以, + 空格串联起来,结果为"windows, libs, lldb-mi, lldb-server, linux, check-api"
    known_platforms_str = ', '.join(known_platforms)

    class SeparatedListByCommaAction(argparse.Action):
        # 把类当做一个方法使用
        # 参考用例:https://zhuanlan.zhihu.com/p/165245990
        def __call__(self, parser, namespace, vals, option_string):
            for val in vals.split(','):
                if val in known_platforms:
                    continue
                else:
                    error = '\'{}\' invalid.  Choose from {}'.format(val, known_platforms)
                    raise argparse.ArgumentError(self, error)
            setattr(namespace, self.dest, vals.split(','))

    parser.add_argument(
        '--no-build',
        action=SeparatedListByCommaAction,
        default=list(),
        help='Don\'t build toolchain for specified platforms.  Choices: ' + known_platforms_str)

    return parser.parse_args()

2.2.1.2 parse_add_argument 方法解析

增加运行命令的可能参数

@staticmethod
def parse_add_argument(parser):

    parser.add_argument(
        '--enable-assertions',
        action='store_true',
        default=False,
        help='Apply assertions, some parameters are affected.')

    parser.add_argument(
        '--build-name',
        default='dev',
        help='Release name for the package.')

    parser.add_argument(
        '--debug',
        action='store_true',
        default=False,
        help='Building Clang and LLVM Tools for Debugging (only affects stage2)')

    parser.add_argument(
        '--strip',
        action='store_true',
        default=False,
        help='Strip final LLVM binaries.')

    parser.add_argument(
        '--no-build-arm',
        action='store_true',
        default=False,
        help='Omit build os target: arm.')

    parser.add_argument(
        '--no-build-aarch64',
        action='store_true',
        default=False,
        help='Omit build os target: aarch64.')

    parser.add_argument(
        '--no-build-riscv64',
        action='store_true',
        default=False,
        help='Omit build os target: 64-bit RISC-V.')

    parser.add_argument(
        '--no-build-mipsel',
        action='store_true',
        default=False,
        help='Omit build os target: mipsel.')

    parser.add_argument(
        '--no-build-x86_64',
        action='store_true',
        default=False,
        help='Omit build os target: x86_64.')

    parser.add_argument(
        '--no-lto',
        action='store_true',
        default=False,
        help='Accelerate builds by disabling LTO (only affects llvm product)')

    parser.add_argument(
        '--build-instrumented',
        action='store_true',
        default=False,
        help='Using the PGO instrumentation to build LLVM tool')

    parser.add_argument(
        '--xunit-xml-output',
        default=None,
        help='Output path for LLVM unit tests XML report')

2.2.1.3 discover_paths 方法解析

设置各种路径

def discover_paths(self):
    # 获取当前脚本所在目录的绝对路径
    self.LLVM_BUILD_DIR = os.path.abspath(os.path.dirname(__file__))

    # 获取脚本所在的目录的父目录名称
    parent_of_llvm_build = os.path.basename(os.path.dirname(self.LLVM_BUILD_DIR))
    if parent_of_llvm_build == 'toolchain':
        # 设置编译root路径
        self.REPOROOT_DIR = os.path.abspath(os.path.join(self.LLVM_BUILD_DIR, '../..'))
    else:
        assert parent_of_llvm_build == 'llvm-project'
        self.REPOROOT_DIR = os.path.abspath(os.path.join(self.LLVM_BUILD_DIR, '../../..'))

    # 设置llvm-project路径
    self.LLVM_PROJECT_DIR = os.path.join(self.REPOROOT_DIR, 'toolchain', 'llvm-project')
    # 设置out路径
    self.OUT_PATH = os.path.join(self.REPOROOT_DIR, 'out')

2.2.1.4 logging.basicConfig 方法解析
logging.basicConfig(level=logging.INFO)

2.2.2 ClangVersion 类解析
2.2.2.1 _parse_version_file 方法解析

从 version_file 文件中获取 major、minor、patch 的值

def _parse_version_file(self, version_file):
    with open(version_file, 'r') as fp:
        text = fp.read()
    self.major = self._parse(text, 'CLANG_VERSION_MAJOR')
    self.minor = self._parse(text, 'CLANG_VERSION_MINOR')
    self.patch = self._parse(text, 'CLANG_VERSION_PATCHLEVEL')
​
2.2.2.2 _parse 方法解析

通过正则表达式来获取字符串中变量对应的值

@staticmethod
def _parse(text, key):
    # %s 表示搜索到匹配key字符串
    # \s+ 表示匹配一个或多个空白符合(空格、回车、Tab等)
    # () 表示提取匹配字符串
    # \d+ 表示一个或多个数字
    # 整个意思是查找每个key字符串后提取后面的数字
    return re.findall(r'%s\s+(\d+)' % key, text)[0]
​
2.2.2.3 long_version 方法解析

将 major、minor、patch 以 major.minor.patch 连接

def long_version(self):
    return '.'.join([self.major, self.minor, self.patch])
​
2.2.2.4 short_version 方法解析

将 major、minor 以 major.minor 连接

def short_version(self):
    return '.'.join([self.major, self.minor])
​
2.2.2.5 major_version 方法解析
def major_version(self):
    return self.major
​
2.2.3 BuildUtils 类解析
2.2.3.0 init 方法解析
def __init__(self, build_config):
    self.build_config = build_config

    # 结果是prebuilts/cmake/linux-x86/bin
    self.CMAKE_BIN_DIR = os.path.abspath(
        os.path.join(self.build_config.REPOROOT_DIR, 'prebuilts/cmake', self.platform_prefix(), 'bin'))
​
2.2.3.1 platform_prefix 方法解析

基于 Ubuntu 的 x86_64 架构直接返回 linux-x86

def platform_prefix(self):
    prefix = self.use_platform()
    if (prefix.endswith('x86_64')):
        return prefix[:-3]
    return prefix
​
2.2.3.2 use_platform 方法解析

直接返回的是 linux-x86_64

@staticmethod
def use_platform():
    # 以小写字母返回当前系统名称,这里返回的是linux
    sysstr = platform.system().lower()
    # 返回当前的架构类型,这里返回的是x86_64
    arch = platform.machine()
    return "%s-%s" % (sysstr, arch)
​
2.2.3.3 open_ohos_triple 方法解析

返回值推测是 ${arch}-linux-ohos

def open_ohos_triple(self, arch):
    return arch + self.build_config.OPENHOS_SFX
​
2.2.3.4 liteos_triple 方法解析

返回值推测是 ${arch}-liteos-ohos

def liteos_triple(self, arch):
    return arch + self.build_config.LITEOS_SFX
​
2.2.3.4 set_clang_version 方法解析

从 prebuilts/clang/ohos/linux-x86_64/clang-10.0.1/include/clang/Basic/Version.inc 文件中获取 long_version,结果是 10.0.1

def set_clang_version(self, install_dir):
    self.build_config.VERSION = self.get_clang_version(install_dir).long_version()
​
2.2.3.5 get_clang_version 方法解析

打开 prebuilts/clang/ohos/linux-x86_64/clang-10.0.1/include/clang/Basic/Version.inc 文件,用于获取其中的 version

@staticmethod
def get_clang_version(llvm_install):
    version_file = os.path.join(llvm_install, 'include', 'clang', 'Basic',
                            'Version.inc')
    return ClangVersion(version_file)
​
2.2.3.5 invoke_ninja 方法解析
def invoke_ninja(self,
                 out_path,
                 env,
                 target=None,
                 install=True,
                 build_threads=False):

    # 获取的ninja路径为:prebuilts/cmake/linux-x86/bin/ninja
    ninja_bin_path = os.path.join(self.CMAKE_BIN_DIR, 'ninja')

    ninja_list = ['-l{}'.format(build_threads)] if build_threads else []

    ninja_target = [target] if target else []

    self.check_call([ninja_bin_path] + ninja_list + ninja_target, cwd=out_path, env=env)
    if install:
        self.check_call([ninja_bin_path, 'install'], cwd=out_path, env=env)
​
2.2.3.6 check_call 方法解析
def check_call(self, cmd, *args, **kwargs):
    """subprocess.check_call with logging."""
    self.logger().info('check_call:%s %s',
                       datetime.datetime.now().strftime("%H:%M:%S"), subprocess.list2cmdline(cmd))

    subprocess.check_call(cmd, *args, **kwargs)
​
2.2.3.7 invoke_cmake 方法解析
def invoke_cmake(self,
                 cmake_path,
                 out_path,
                 invoke_defines,
                 env):

    cmake_bin_path = os.path.join(self.CMAKE_BIN_DIR, 'cmake')
    # 设置后面编译使用Ninja
    flags = ['-G', 'Ninja']
    flags += ['-DCMAKE_PREFIX_PATH=%s' % self.CMAKE_BIN_DIR]

    for key in invoke_defines:
        # 将invoke_defines中的宏定义字典按照'-Dkey=value'重新组合
        newdef = ''.join(['-D', key, '=', invoke_defines[key]])
        flags += [newdef]

    flags += [cmake_path]
    self.check_create_dir(out_path)

    self.check_call([cmake_bin_path] + flags, cwd=out_path, env=env)

# 具体的执行命令形式可能是这样的
# cmake -G "Ninja" \
#   -DCOMPILER_RT_BUILD_BUILTINS=ON \
#   -DCOMPILER_RT_INCLUDE_TESTS=OFF \
#   -DCOMPILER_RT_BUILD_SANITIZERS=ON \
#   -DCOMPILER_RT_BUILD_XRAY=ON \
#   -DCOMPILER_RT_BUILD_LIBFUZZER=ON \
#   -DCOMPILER_RT_BUILD_PROFILE=ON \
#   -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \
#   -DCMAKE_C_COMPILER=clang \
#   -DCMAKE_CXX_COMPILER=clang++ \
#   -DCMAKE_ASM_COMPILER_TARGET="riscv64-unknown-linux-gnu" \
#   -DCMAKE_C_COMPILER_TARGET="riscv64-unknown-linux-gnu" \
#   -DCMAKE_CXX_COMPILER_TARGET="riscv64-unknown-linux-gnu" \
#   -DCMAKE_ASM_FLAGS="-O2 --gcc-toolchain=/home/wen_fei/toolchains/riscv64-gnu-gcc -march=rv64imafdc -mabi=lp64d -mno-relax" \
#   -DCMAKE_C_FLAGS="-O2 --gcc-toolchain=/home/wen_fei/toolchains/riscv64-gnu-gcc -march=rv64imafdc -mabi=lp64d -mno-relax" \
#   -DCMAKE_CXX_FLAGS="-O2 --gcc-toolchain=/home/wen_fei/toolchains/riscv64-gnu-gcc -march=rv64imafdc -mabi=lp64d -mno-relax" \
#   -DCMAKE_EXE_LINKER_FLAGS="-z separate-code" \
#   -DCMAKE_SYSROOT="/home/wen_fei/toolchains/riscv64-gnu-gcc/sysroot" \
#   -DCMAKE_INSTALL_PREFIX="/home/wen_fei/toolchains/sysroot" \
#   -DCMAKE_C_COMPILER_WORKS=1 \
#   -DCMAKE_CXX_COMPILER_WORKS=1 \
#   -DCMAKE_SIZEOF_VOID_P=8 \
#   ../compiler-rt
​
2.2.3.8 check_create_dir 方法解析

递归创建目录

def check_create_dir(self, path):
    if not os.path.exists(path):
        """Proxy for os.makedirs with logging and dry-run support."""
        self.logger().info('makedirs %s', path)
        os.makedirs(path)
​
2.2.3.9 check_rm_tree 方法解析

递归删除目录

def check_rm_tree(self, tree_dir):
    """Removes directory tree."""
    def chmod_and_retry(func, path, _):
        if not os.access(path, os.W_OK):
            os.chmod(path, stat.S_IWUSR)
            return func(path)
        raise IOError("rmtree on %s failed" % path)

    if os.path.exists(tree_dir):
        self.logger().info('shutil rmtree %s', tree_dir)
        shutil.rmtree(tree_dir, onerror=chmod_and_retry)
​
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值