diff --git a/lib/ossbuild-common.bash b/lib/ossbuild-common.bash new file mode 100644 index 0000000..cea30d1 --- /dev/null +++ b/lib/ossbuild-common.bash @@ -0,0 +1,6 @@ +# Helper functions for project build scripts. + +# Check is current system is running GNU/Linux based QNAP QTS OS. +is-qnap-qts() { + [ -r '/etc/os-release' ] && grep -q '^NAME="QTS"$' '/etc/os-release' +} diff --git a/projects/llvm/README.org b/projects/llvm/README.org new file mode 100644 index 0000000..d15afd9 --- /dev/null +++ b/projects/llvm/README.org @@ -0,0 +1,21 @@ +#+title: LLVM Compiler Infrastructure + +See https://llvm.org. + +Tools: +- clang(++) +- clang-extra-tools + - clangd + - clang-format + - clang-tidy + - ... +- lld +- lldb +- ... + +Runtimes +- libc++ +- libc++abi +- libunwind + +To build the runtimes, the build script uses the [[https://libcxx.llvm.org/BuildingLibcxx.html#bootstrapping-build][Bootstrapping build]] method. diff --git a/projects/llvm/ossbuild-llvm.bash b/projects/llvm/ossbuild-llvm.bash new file mode 100755 index 0000000..406c53b --- /dev/null +++ b/projects/llvm/ossbuild-llvm.bash @@ -0,0 +1,284 @@ +#!/usr/bin/env bash + +LIB_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../../lib" +source "${LIB_DIR}/ossbuild-common.bash" + +llvm-source-workon() { + echo "[INF] ${FUNCNAME[0]} ..." + local version="${1}" + + local repository="https://github.com/llvm/llvm-project" + local archive="llvm-project-${version}.src.tar.xz" + pushd "${HOME}/work/oss" &> /dev/null + [ -e "${archive}" ] || { + wget "${repository}/releases/download/llvmorg-${version}/${archive}" + } + + local src_dir + src_dir="${HOME}/work/oss/$(basename "${archive}" .tar.xz)" + + [ -d "${src_dir}" ] || tar xvf "${archive}" + popd &> /dev/null + pushd "${src_dir}" &> /dev/null +} + +llvm-cmake-generate() { + echo "[INF] ${FUNCNAME[0]} ..." + local build_type="${1:-Release}" + local projects="${2:-}" + local prefix="${3:-}" + + local source_dir='llvm' + [ -d "${source_dir}" ] || { + echo "[ERR] could not find source dir: '${source_dir}'" + return 1 + } + + local -a cmake_extra_args + local build_dir="build-${build_type,,}" + if [ -n "${projects}" ]; then + build_dir="${build_dir}.${projects//;/.}" + else + # get runtime target from GCC + local gcc_runtime_target + gcc_runtime_target="$(gcc -xc /dev/null -v -E 2>&1 | grep '^Target:' | cut -d' ' -f2)" + #projects='clang;clang-tools-extra;lld;lldb;compiler-rt;libunwind;libcxx;libcxxabi' + projects='clang;clang-tools-extra;compiler-rt;lld;lldb' + cmake_extra_args+=( + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" + + # must match triple of GCC used for first build step (here: + # /opt/lib/gcc/x86_64-openwrt-linux-gnu/8.4.0) + -DLLVM_RUNTIME_TARGETS="${gcc_runtime_target}" + # required )in case of a non-standard triple) in order to allow the + # final clang to find the GCC resource dir which contains files + # required by the linker (like crti.o, libgcc, libc_nonshared.a in + # e.g. /opt/lib/gcc/x86_64-openwrt-linux-gnu/8.4.0) + # + # Note: This also sets LLVM_DEFAULT_TARGET_TRIPLE. + -DLLVM_HOST_TRIPLE="${gcc_runtime_target}" + + # Avoid error during cmake toolchain generation: + # + # ERROR: Compiler doesn't support generation of unwind tables if + # exception support is disabled. Building libunwind DSO with runtime + # dependency on C++ ABI library is not supported. + -DRUNTIMES_${gcc_runtime_target}_LIBUNWIND_ENABLE_SHARED="Off" + ) + fi + local log="${build_dir/build-/cmake-}.log" + + if [ -z "${prefix}" ]; then + if is-qnap-qts; then + prefix="$(readlink /opt)/local" + else + prefix='/usr/local' + fi + fi + + if [ -d "${build_dir}" ]; then + if [ -v FORCE_REGENERATE ]; then + if [ "${FORCE_REGENERATE}" != "no-rm" ]; then + rm -rf "${build_dir}" + fi + else + echo "[INF] keep existing build dir: '${build_dir}'" + return 0 + fi + fi + + local -a cmd=( + cmake + -S "${source_dir}" + -B "${build_dir}" + -G Ninja + -DCMAKE_BUILD_TYPE="${build_type}" + -DCMAKE_INSTALL_PREFIX="${prefix}" + -DLLVM_ENABLE_PROJECTS="${projects}" + #-DCMAKE_CXX_LINK_FLAGS="-Wl,-rpath,\$ORIGIN/../lib -L${prefix}/lib" + -DCMAKE_CXX_LINK_FLAGS="-Wl,-rpath,${prefix}/lib -L${prefix}/lib -L/opt/lib" + "${cmake_extra_args[@]}" + ) + + if command -v ccache &> /dev/null; then + cmd+=( + -DLLVM_CCACHE_BUILD="ON" + -DLLVM_CCACHE_PARAMS="CCACHE_CPP2=yes CCACHE_HASHDIR=yes CCACHE_BASEDIR='${PWD}/${build_dir}'" + ) + fi + + if is-qnap-qts; then + # get gcc_install_prefix + local gcc_install_prefix + gcc_install_prefix="$(gcc -x c -E /dev/null -v 2>&1 | rg 'Configured with' | sed -e 's/.* --prefix=\([^ ]\+\).*/\1/')" + + cmd+=( + # enable clang to locate the GCC toolchain if it is in a + # non-standard location (like ~/opt~ on QNAP Entware) + -DGCC_INSTALL_PREFIX="${gcc_install_prefix}" + + # CMAKE_EXE_LINKER_FLAGS and LLVM_PARALLEL_LINK_JOBS flags reduce linker + # memory usage (without them, llvm build fails on qnas) + -DCMAKE_EXE_LINKER_FLAGS="-Wl,--reduce-memory-overheads -Wl,--hash-size=1021" + -DLLVM_PARALLEL_LINK_JOBS=1 + + # avoid cmake warning: 'runtime library [libz.so.1] in /lib may be + # hidden by files in ...' + -DZLIB_ROOT="${prefix}" + ) + + # From https://lists.llvm.org/pipermail/cfe-users/2016-November/001076.html: + # + # ... gcc's configure file comes with handy flags like: + # - --with-local-prefix= which allows to override the default system + # header include /usr/local/include + # - --with-native-system-header-dir= to override the default system + # header include /include + # - --enable-linker-build-id to enable build ids by default (in clang + # the equivalent is -DENABLE_LINKER_BUILD_ID) + # - --with-linker-hash-style=gnu to force the gnu hash style when + # emitting the symbol table. + # + # However, LLVM/clang doesn't seem to provide an equivalent for + # --with-local-prefix, --with-native-system-header-dir and + # --with-linker-hash-style. So instead, some sources need to be + # patched. + echo + local clang_toolchain_src + clang_toolchain_src='clang/lib/Driver/ToolChains/Linux.cpp' + [ -e "${clang_toolchain_src}.orig" ] || { + echo "[INF] patching ${clang_toolchain_src} ..." + sed -i.orig \ + -e 's,/usr/,/opt/,g' "${clang_toolchain_src}" + } + echo "[INF] using patch:" + diff -u "${clang_toolchain_src}"{.orig,} || true + echo "[INF] end of patch (${clang_toolchain_src})" + + echo + clang_toolchain_src='clang/lib/Driver/ToolChains/Gnu.cpp' + [ -e "${clang_toolchain_src}.orig" ] || { + echo "[INF] patching ${clang_toolchain_src} ..." + sed -i.orig \ + -e 's,\(basePath = "/\)usr\(/lib/gcc/"\),\1opt\2,g' \ + "${clang_toolchain_src}" + } + echo "[INF] using patch:" + diff -u "${clang_toolchain_src}"{.orig,} || true + echo "[INF] end of patch (${clang_toolchain_src})" + fi + + echo "[INF] generating build system for: ${projects}" | tee "${log}" + echo "${cmd[@]}" | tee -a "${log}" + "${cmd[@]}" 2>&1 | tee -a "${log}" + local -i retval=${?} + + echo + local logprfx="INF" + if [ ${retval} -ne 0 ] || grep -q "Configuring incomplete" "${log}" ; then + logprfx="ERR" + echo "[${logprfx}] failed to generate build system for: ${projects}" + [ ${retval} -ne 0 ] || retval=10 + else + echo "[${logprfx}] successfully generated build system for: ${projects}" + fi + + echo "[${logprfx}] build dir: '${build_dir}'" + echo "[${logprfx}] cmake log: '${log}'" + + return ${retval} +} + +# shellcheck disable=SC2120 +llvm-cmake-build() { + echo "[INF] ${FUNCNAME[0]} ..." + local build_type="${1:-Release}" + local build_args="${2:-}" + local build_dir="${3:-build-${build_type,,}}" + local cmd=( + cmake --build "${build_dir}" + -- + ) + + if [ -n "${build_args}" ]; then + read -a extra_args <<< "${build_args}" + cmd+=( + "${extra_args[@]}" + ) + elif is-qnap-qts; then + cmd+=( + # compiling on QTS sometimes fails (probably due to memory + # limitations), so limit the parallel jobs to 2 + -j2 + # don't stop at failures + -k0 + ) + fi + + # enable verbose logging when V is non-empty + [ -z "${V:-}" ] || cmd+=(-v) + + echo "${cmd[@]}" + "${cmd[@]}" +} + +llvm-cmake-install() { + echo "[INF] ${FUNCNAME[0]} ..." + local build_type="${1:-Release}" + local build_dir="${2:-build-${build_type,,}}" + local cmd=( + sudo cmake --build "${build_dir}" + -- + install + ) + echo "${cmd[@]}" + "${cmd[@]}" +} + +llvm-cmake-generate-fallback() { + echo "[INF] ${FUNCNAME[0]} ..." + local build_type="${1:-Release}" + local build_dir="${2:-build-${build_type,,}}" + + # Fallback 1: Build a smaller subset of LLVM projects + local llvm_projects=( + clang + clang-tools-extra + lld + ) + local IFS=';' + llvm-cmake-generate "${build_type}" "${llvm_projects[*]}" + + # Fallback 2: Generate build systems for individual projects + IFS=';' read -a LLVM_PROJECTS \ + <<< $(grep 'set(LLVM_ALL_PROJECTS ' llvm/CMakeLists.txt | cut -d\" -f2) + local project + for project in "${llvm_projects[@]}"; do + llvm-cmake-generate "${build_type}" "${project}" + done +} + +ossbuild-llvm() { + local version="${1:-15.0.5}" + local build_type="${2:-Release}" # or 'RelWithDebInfo', but beware of the huge binaries + + llvm-source-workon "${version}" + llvm-cmake-generate "${build_type}" + + # Ideally, llcm-cmake-build should build the most LLVM projects. However, if + # it doesn't, it might make sense to build projects individually (see + # `llvm-cmake-generate-fallback` above). + llvm-cmake-build "${build_type}" + # Install the toolchain + llvm-cmake-install "${build_type}" + + return $? +} + + +# stop here when sourced +return 0 2>/dev/null || true +set -euo pipefail +[ "${DEBUG:-0}" -le 0 ] || set -x + +ossbuild-llvm "${@}"