一、工具链基础介绍
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)