From 9be7c2b1ac63d2299ed8293c381ef5877003b5c4 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Tue, 27 Feb 2024 10:47:04 -0800 Subject: [PATCH] feat: Portable, fully-static release executables on Linux (#1351) This adds the option FULLY_STATIC to create fully-static executables. To create portable, fully-static release executables on Linux, we need to use musl instead of glibc. Static executables from glibc are not portable. The popular musl-gcc wrapper does not support C++, so instead we use a full musl cross-compiler toolchain in the build workflow. To build FULLY_STATIC, the user must point to the appropriate cross-compiler, as we do in the workflow. On systems where musl is the native libc (such as Alpine Linux), this is not necessary. I have also read that musl's allocator is not very fast in multi-threaded applications. So when FULLY_STATIC is enabled, we will also enable mimalloc, a replacement allocator that is very fast. I tested a very basic packaging command to compare speeds of dynamic glibc, static musl, and static musl+mimalloc: dynamic glibc: runs: 2.527, 2.798, 2.703, 2.756, 2.959 avg = 2.749, std dev = 0.156s static musl: runs: 2.813, 2.920, 3.129, 3.003, 2.738 avg = 2.921s, std dev = 0.154s static musl+mimalloc: runs: 2.291, 2.034, 2.415, 2.303, 2.265 avg = 2.262s, std dev = 0.140s The mimalloc build is 82% faster than musl default allocator, 77% faster than glibc, and has more consistent runtime characteristics (lower standard deviation). --- .github/workflows/build.yaml | 41 +++++++++++++++++++- .gitmodules | 3 ++ CMakeLists.txt | 8 +++- docs/source/build_instructions.md | 12 ++++++ packager/CMakeLists.txt | 5 +++ packager/fully-static.cmake | 27 +++++++++++++ packager/third_party/CMakeLists.txt | 1 + packager/third_party/curl/CMakeLists.txt | 1 + packager/third_party/libpng/CMakeLists.txt | 2 + packager/third_party/mimalloc/CMakeLists.txt | 22 +++++++++++ packager/third_party/mimalloc/source | 1 + 11 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 packager/fully-static.cmake create mode 100644 packager/third_party/mimalloc/CMakeLists.txt create mode 160000 packager/third_party/mimalloc/source diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5650fc27f2..6f0c159e0f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -108,7 +108,7 @@ jobs: kitware_key_path="/usr/share/keyrings/kitware-archive-keyring.gpg" kitware_sources_path="/etc/apt/sources.list.d/kitware.list" - wget -O - "$kitware_key_url" 2>/dev/null | gpg --dearmor - \ + curl -sL "$kitware_key_url" | gpg --dearmor - \ | sudo tee "$kitware_key_path" >/dev/null . /etc/lsb-release # Defines $DISTRIB_CODENAME (jammy, focal, etc) @@ -149,9 +149,32 @@ jobs: export PACKAGER_LOW_MEMORY_BUILD=yes fi + # Do fully static release builds on Linux. + BUILD_CONFIG="${{ matrix.build_type }}-${{ matrix.lib_type }}" + if [[ "${{ runner.os }}" == "Linux" && \ + "$BUILD_CONFIG" == "Release-static" ]]; then + # Enable build settings for fully-static. + FULLY_STATIC="ON" + + # Use a musl toolchain, since glibc static executables are not + # portable. + if [[ "${{matrix.target_arch}}" == "arm64" ]]; then + MUSL_ARCH="aarch64" + else + MUSL_ARCH="x86_64" + fi + curl -LO https://musl.cc/"$MUSL_ARCH"-linux-musl-native.tgz + tar xf "$MUSL_ARCH"-linux-musl-native.tgz + export CC=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-gcc + export CXX=`pwd`/"$MUSL_ARCH"-linux-musl-native/bin/"$MUSL_ARCH"-linux-musl-g++ + else + FULLY_STATIC="OFF" + fi + cmake \ -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \ -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" \ + -DFULLY_STATIC="$FULLY_STATIC" \ -S . \ -B build/ @@ -179,7 +202,21 @@ jobs: exit 0 fi - # TODO: Check static executables? + # Check static executables + if [[ "${{ runner.os }}" == "Linux" ]]; then + echo "::group::Check static executables" + for exe in build/packager/{packager,mpd_generator}; do + # Capture information about the executables, but also let it be + # logged to stdout. + ldd "$exe" | tee static.log + # The phrase "statically linked" means we got it right. Fail if + # we don't find it. + if ! cat static.log | grep -q statically; then + exit 1 + fi + done + echo "::endgroup::" + fi echo "::group::Prepare artifacts folder" mkdir artifacts diff --git a/.gitmodules b/.gitmodules index dfd4de8b34..b986b41076 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "packager/third_party/c-ares/source"] path = packager/third_party/c-ares/source url = https://github.com/c-ares/c-ares +[submodule "packager/third_party/mimalloc/source"] + path = packager/third_party/mimalloc/source + url = https://github.com/microsoft/mimalloc diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c90d093fc..6c1e8d132d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,10 +15,14 @@ include("packager/policies.cmake") # Project name. May not contain spaces. Versioning is managed elsewhere. project(shaka-packager VERSION "") -# The only build option for Shaka Packager is whether to build a shared -# libpackager library. By default, don't. +# Whether to build a shared libpackager library. By default, don't. option(BUILD_SHARED_LIBS "Build libpackager as a shared library" OFF) +# Whether to attempt a static linking of the command line front-end. +# Only supported on Linux and with musl. This will also cause us to link +# against mimalloc to replace the standard allocator in musl, which is slow. +option(FULLY_STATIC "Attempt fully static linking of all CLI apps" OFF) + # Enable CMake's test infrastructure. enable_testing() diff --git a/docs/source/build_instructions.md b/docs/source/build_instructions.md index 84423f3db5..3f830ac5b8 100644 --- a/docs/source/build_instructions.md +++ b/docs/source/build_instructions.md @@ -106,6 +106,18 @@ After configuring CMake you can run the build with cmake --build build --parallel ``` +To build portable, fully-static executables on Linux, you will need either musl +as your system libc, or a musl toolchain. (See [musl.cc](https://musl.cc). +To create a portable, fully-static build for Linux, configure CMake with: + +```shell + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS="OFF" \ + -DFULLY_STATIC="ON" \ + -DCMAKE_C_COMPILER=/path/to/x86_64-linux-musl-gcc \ + -DCMAKE_CXX_COMPILER=/path/to/x86_64-linux-musl-g++ +``` + #### Windows Windows build instructions are similar. Using Tools > Command Line > diff --git a/packager/CMakeLists.txt b/packager/CMakeLists.txt index 243a1efdab..a1f8bb2555 100644 --- a/packager/CMakeLists.txt +++ b/packager/CMakeLists.txt @@ -57,6 +57,9 @@ include_directories(..) # Public include folder, to reference public headers as packager/foo.h include_directories(../include) +# Include settings for optional fully-static binaries. +include("fully-static.cmake") + # Include our module for gtest-based testing. include("gtest.cmake") @@ -171,6 +174,7 @@ target_link_libraries(packager libpackager license_notice string_utils + ${EXTRA_EXE_LIBRARIES} ) add_executable(mpd_generator @@ -187,6 +191,7 @@ target_link_libraries(mpd_generator license_notice mpd_builder mpd_util + ${EXTRA_EXE_LIBRARIES} ) add_executable(packager_test diff --git a/packager/fully-static.cmake b/packager/fully-static.cmake new file mode 100644 index 0000000000..3190b99ad6 --- /dev/null +++ b/packager/fully-static.cmake @@ -0,0 +1,27 @@ +# Copyright 2024 Google LLC. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# Fully-static build settings. +if(FULLY_STATIC) + # This is the "object" version of mimalloc, as opposed to the library + # version. This is important for a static override of malloc and friends. + set(EXTRA_EXE_LIBRARIES $) + + # Keep the linker from searching for dynamic libraries. + set(CMAKE_LINK_SEARCH_START_STATIC OFF) + set(CMAKE_LINK_SEARCH_END_STATIC OFF) + + # Tell CMake not to plan to relink the executables, which wouldn't make sense + # in this context and causes CMake to fail at configure time when using a + # musl toolchain for static builds. + set(CMAKE_SKIP_BUILD_RPATH ON) + + # Set extra linker options necessary for fully static linking. These apply + # to all executables, which is critical when using a musl toolchain. Without + # applying these to all executables, we could create dynamic musl executables + # as intermediate outputs, which then could not run on a glibc host system. + add_link_options(-static-libgcc -static-libstdc++ -static) +endif() diff --git a/packager/third_party/CMakeLists.txt b/packager/third_party/CMakeLists.txt index 8455637d20..c883c0b7eb 100644 --- a/packager/third_party/CMakeLists.txt +++ b/packager/third_party/CMakeLists.txt @@ -46,6 +46,7 @@ add_subdirectory(libpng EXCLUDE_FROM_ALL) add_subdirectory(libwebm EXCLUDE_FROM_ALL) add_subdirectory(libxml2 EXCLUDE_FROM_ALL) add_subdirectory(mbedtls EXCLUDE_FROM_ALL) +add_subdirectory(mimalloc EXCLUDE_FROM_ALL) add_subdirectory(mongoose EXCLUDE_FROM_ALL) add_subdirectory(protobuf EXCLUDE_FROM_ALL) add_subdirectory(zlib EXCLUDE_FROM_ALL) diff --git a/packager/third_party/curl/CMakeLists.txt b/packager/third_party/curl/CMakeLists.txt index 4be4452edb..e4665ab102 100644 --- a/packager/third_party/curl/CMakeLists.txt +++ b/packager/third_party/curl/CMakeLists.txt @@ -14,6 +14,7 @@ set(USE_LIBIDN2 OFF) set(USE_LIBRTMP OFF) set(CURL_USE_LIBSSH2 OFF) set(CURL_ZLIB OFF CACHE STRING "Force curl not to search for system zlib") +set(BUILD_CURL_EXE OFF) if(UNIX AND NOT APPLE) # Use c-ares to fix static linking on Linux. Set USE_ARES directly, not the diff --git a/packager/third_party/libpng/CMakeLists.txt b/packager/third_party/libpng/CMakeLists.txt index 097c344368..c9d33a828c 100644 --- a/packager/third_party/libpng/CMakeLists.txt +++ b/packager/third_party/libpng/CMakeLists.txt @@ -25,6 +25,8 @@ set(PNG_BUILD_ZLIB ON) set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/source/") # Tell libpng where to find zlib library to link to. set(ZLIB_LIBRARY zlibstatic) +# Tell libpng where to find libm on Linux (-lm). +set(M_LIBRARY m) # With these set in scope of this folder, load the library's own CMakeLists.txt. add_subdirectory(source) diff --git a/packager/third_party/mimalloc/CMakeLists.txt b/packager/third_party/mimalloc/CMakeLists.txt new file mode 100644 index 0000000000..ddbe25ccb5 --- /dev/null +++ b/packager/third_party/mimalloc/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2024 Google LLC. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +# CMake build file to host mimalloc configuration. +# This is only used to produce static binaries on Linux, as a replacement for +# the default allocator in musl, which is slower. + +# Turn these off to save time. +set(MI_BUILD_SHARED OFF) +set(MI_BUILD_STATIC OFF) +set(MI_BUILD_TESTS OFF) + +# Turn these on. They are already on by default as of the date we wrote this +# file, but in case the defaults ever change, these settings are critical. +set(MI_OVERRIDE ON) +set(MI_BUILD_OBJECT ON) + +# With these set in scope of this folder, load the library's own CMakeLists.txt. +add_subdirectory(source) diff --git a/packager/third_party/mimalloc/source b/packager/third_party/mimalloc/source new file mode 160000 index 0000000000..4e50d6714d --- /dev/null +++ b/packager/third_party/mimalloc/source @@ -0,0 +1 @@ +Subproject commit 4e50d6714d471b72b2285e25a3df6c92db944593