From f415c192d068a7bfd63d6180535522ac15feb8d3 Mon Sep 17 00:00:00 2001 From: Shen Date: Wed, 26 Aug 2026 21:57:28 +0800 Subject: [PATCH 1/7] cmake: Add ENABLE_MULTILIB option to build a combined 8/10/12-bit library Adds an ENABLE_MULTILIB option that builds the 10-bit and 12-bit static libraries as sub-build targets and links them into the 8-bit API library through EXTRA_LIB, so that a single library can switch bit depths at runtime via x265_api_get. For static builds, the three archives are merged into one in a POST_BUILD step (GNU ar MRI, libtool on macOS, lib.exe on MSVC), following the build/linux/multilib.sh recipe. The sub-builds inherit the generator, toolchain and compiler settings from the parent build. --- source/CMakeLists.txt | 31 ++++++++++ source/cmake/multilib.cmake | 112 ++++++++++++++++++++++++++++++++++ source/cmake/x265-merge.cmake | 104 +++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 source/cmake/multilib.cmake create mode 100644 source/cmake/x265-merge.cmake diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index cbda0ca65..d7ecf3e2a 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -752,6 +752,14 @@ if(EXTRA_LIB) endif(EXTRA_LIB) mark_as_advanced(EXTRA_LIB EXTRA_LINK_FLAGS) +# Build a single library containing the 8-bit, 10-bit and 12-bit encoders, +# switching bit depths at runtime (see build/linux/multilib.sh) +option(ENABLE_MULTILIB "Build a single library containing all supported bit depths" OFF) +set(MULTILIB_ARCHIVER "" CACHE STRING "Archiver used to merge the multilib static libraries") +if(ENABLE_MULTILIB) + include(cmake/multilib.cmake) +endif() + if(X64 OR ARM64 OR PPC64 OR RISCV64 OR LOONGARCH64) # NOTE: We only officially support high-bit-depth compiles of x265 # on 64bit architectures. Main10 plus large resolution plus slow @@ -1088,6 +1096,24 @@ endif() if(EXTRA_LIB) target_link_libraries(x265-static ${EXTRA_LIB}) endif() +if(ENABLE_MULTILIB AND NOT ENABLE_SHARED) + # Statically merge the 10/12-bit archives into the 8-bit API archive so + # that consumers only need to link against a single library + add_custom_command(TARGET x265-static POST_BUILD + COMMAND "${CMAKE_COMMAND}" + "-DOUTPUT=$" + "-DINPUT1=$" + "-DINPUT2=${CMAKE_CURRENT_BINARY_DIR}/multilib/10bit/${_multilib_archive_name}" + "-DINPUT3=${CMAKE_CURRENT_BINARY_DIR}/multilib/12bit/${_multilib_archive_name}" + "-DARCHIVER=${MULTILIB_ARCHIVER}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/x265-merge.cmake" + VERBATIM) +endif() +if(ENABLE_MULTILIB) + # Make sure the 10/12-bit sub-builds are done before this library is + # archived and merged + add_dependencies(x265-static x265-multilib-10bit x265-multilib-12bit) +endif() if(ENABLE_LIBVMAF) target_link_libraries(x265-static ${VMAF}) endif() @@ -1176,6 +1202,11 @@ if(ENABLE_SHARED) endif() if(EXTRA_LIB) target_link_libraries(x265-shared ${EXTRA_LIB}) + endif() + if(ENABLE_MULTILIB) + # Make sure the 10/12-bit sub-builds are done before linking this + # shared library + add_dependencies(x265-shared x265-multilib-10bit x265-multilib-12bit) endif() target_link_libraries(x265-shared ${PLATFORM_LIBS}) if(SVTHEVC_FOUND) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake new file mode 100644 index 000000000..9a9e1694c --- /dev/null +++ b/source/cmake/multilib.cmake @@ -0,0 +1,112 @@ +# multilib.cmake - Build the 10-bit and 12-bit static libraries as nested +# sub-builds and link them into the 8-bit API library so that a single library +# can switch bit depths at runtime, following the upstream +# build/linux/multilib.sh recipe. +# +# Included from the top-level CMakeLists.txt when ENABLE_MULTILIB is ON. The +# sub-builds are defined as regular build targets so that they are only built +# when the library is built; the resulting archives are then merged into the +# 8-bit API library by a POST_BUILD step (see CMakeLists.txt). + +if(NOT ENABLE_MULTILIB) + return() +endif() + +set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") +set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") + +# Inherit the generator, toolchain and platform settings from this build +set(_multilib_common_args + "-DCMAKE_GENERATOR=${CMAKE_GENERATOR}" + "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" + "-DENABLE_SHARED=OFF" + "-DENABLE_CLI=OFF" + "-DENABLE_LIBNUMA=OFF" + "-DENABLE_PIC=ON" + "-DEXPORT_C_API=OFF" + "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=ON" +) +if(DEFINED CMAKE_GENERATOR_PLATFORM) + list(APPEND _multilib_common_args "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") +endif() +if(DEFINED CMAKE_MAKE_PROGRAM) + list(APPEND _multilib_common_args "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") +endif() +if(DEFINED CMAKE_BUILD_TYPE) + list(APPEND _multilib_common_args "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") +endif() +if(DEFINED CMAKE_SYSTEM_VERSION) + list(APPEND _multilib_common_args "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}") +endif() +if(CMAKE_CROSSCOMPILING AND DEFINED CMAKE_SYSTEM_NAME) + # Required for cross-compilation; not propagated for native builds, where + # an explicit CMAKE_SYSTEM_NAME would wrongly put the nested builds into + # cross-compiling mode + list(APPEND _multilib_common_args "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}") +endif() +if(DEFINED VERSION) + list(APPEND _multilib_common_args "-DVERSION=${VERSION}") +endif() +if(DEFINED NASM_EXECUTABLE) + list(APPEND _multilib_common_args "-DNASM_EXECUTABLE=${NASM_EXECUTABLE}") +endif() +if(DEFINED ENABLE_ASSEMBLY) + list(APPEND _multilib_common_args "-DENABLE_ASSEMBLY=${ENABLE_ASSEMBLY}") +endif() +# vcpkg: propagate the triplet settings so the nested builds use the same +# compiler and flags as the parent build +foreach(_var IN ITEMS VCPKG_TARGET_TRIPLET VCPKG_CHAINLOAD_TOOLCHAIN_FILE VCPKG_CXX_FLAGS + VCPKG_CXX_FLAGS_RELEASE VCPKG_CXX_FLAGS_DEBUG VCPKG_C_FLAGS + VCPKG_C_FLAGS_RELEASE VCPKG_C_FLAGS_DEBUG VCPKG_CRT_LINKAGE + VCPKG_LINKER_FLAGS VCPKG_LINKER_FLAGS_RELEASE VCPKG_LINKER_FLAGS_DEBUG + VCPKG_TARGET_ARCHITECTURE VCPKG_SET_CHARSET_FLAG VCPKG_PLATFORM_TOOLSET + VCPKG_INSTALLED_DIR _VCPKG_INSTALLED_DIR VCPKG_MANIFEST_INSTALL) + if(DEFINED ${_var}) + list(APPEND _multilib_common_args "-D${_var}=${${_var}}") + endif() +endforeach() +# The triplet's configure options (e.g. ANDROID_ABI) are spliced into the +# configure command line by vcpkg_cmake_configure, so they are visible here as +# plain cache variables and must reach the nested builds too +foreach(_var IN ITEMS ANDROID_ABI ANDROID_ARM_NEON ANDROID_ARM_MODE + OHOS_ARCH CMAKE_PLATFORM_NO_VERSIONED_SONAME) + if(DEFINED ${_var}) + list(APPEND _multilib_common_args "-D${_var}=${${_var}}") + endif() +endforeach() + +# The nested builds produce the static archive of the x265-static target +# (libx265.a on non-MSVC, x265-static.lib on MSVC) +if(MSVC) + set(_multilib_archive_name "x265-static.lib") +else() + set(_multilib_archive_name "libx265.a") +endif() + +# Define the bit-depth sub-builds as regular targets that declare their +# produced archive as an output, so that the build system knows how to build +# the archives the main library links against; the main library targets depend +# on them (see CMakeLists.txt) +function(x265_define_multilib_variant name dir archive) + add_custom_command( + OUTPUT "${archive}" + COMMAND "${CMAKE_COMMAND}" -S "${_multilib_source}" -B "${dir}" + ${_multilib_common_args} ${ARGN} + COMMAND "${CMAKE_COMMAND}" --build "${dir}" --config "$" + COMMENT "Building the ${name} x265 sub-library" + VERBATIM) + add_custom_target(${name} DEPENDS "${archive}") +endfunction() + +x265_define_multilib_variant(x265-multilib-10bit "${_multilib_root}/10bit" + "${_multilib_root}/10bit/${_multilib_archive_name}" "-DHIGH_BIT_DEPTH=ON") +x265_define_multilib_variant(x265-multilib-12bit "${_multilib_root}/12bit" + "${_multilib_root}/12bit/${_multilib_archive_name}" "-DHIGH_BIT_DEPTH=ON" "-DMAIN12=ON") + +# Link the 10/12-bit archives into this build; the LINKED_* options enable the +# bit-depth dispatch in the exported C API (see encoder/api.cpp) +set(EXTRA_LIB + "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}" + CACHE STRING "Extra libraries to link against" FORCE) +set(LINKED_10BIT ON CACHE BOOL "10bit libx265 is being linked with this library" FORCE) +set(LINKED_12BIT ON CACHE BOOL "12bit libx265 is being linked with this library" FORCE) diff --git a/source/cmake/x265-merge.cmake b/source/cmake/x265-merge.cmake new file mode 100644 index 000000000..a77e28779 --- /dev/null +++ b/source/cmake/x265-merge.cmake @@ -0,0 +1,104 @@ +# x265-merge.cmake - Merge several static archives into one. +# +# Usage: +# cmake -DOUTPUT= [-DINPUT1=] [-DINPUT2=] ... +# [-DARCHIVER=] -P x265-merge.cmake +# +# The merge is performed with GNU ar (MRI script) on Linux/MinGW, libtool on +# macOS and lib.exe/llvm-lib on MSVC. When ARCHIVER is not given, the tool is +# looked up on the system. + +if(NOT DEFINED OUTPUT) + message(FATAL_ERROR "OUTPUT is required") +endif() + +get_filename_component(_out_name "${OUTPUT}" NAME) +get_filename_component(_out_dir "${OUTPUT}" DIRECTORY) +set(_work "${_out_dir}/.x265-merge") +file(REMOVE_RECURSE "${_work}") +file(MAKE_DIRECTORY "${_work}") + +set(_inputs) +set(_index 1) +while(DEFINED INPUT${_index}) + set(_input "${INPUT${_index}}") + if(NOT EXISTS "${_input}") + message(FATAL_ERROR "Input archive does not exist: ${_input}") + endif() + get_filename_component(_input_name "${_input}" NAME) + get_filename_component(_input_ext "${_input}" EXT) + file(COPY "${_input}" DESTINATION "${_work}") + file(RENAME "${_work}/${_input_name}" "${_work}/in${_index}${_input_ext}") + list(APPEND _inputs "in${_index}${_input_ext}") + math(EXPR _index "${_index} + 1") +endwhile() +if(NOT _inputs) + message(FATAL_ERROR "At least one INPUT argument is required") +endif() + +if(DEFINED ARCHIVER AND ARCHIVER) + get_filename_component(_ar_name "${ARCHIVER}" NAME) + if(_ar_name MATCHES "^(lib|llvm-lib)(\\.exe)?$") + set(_mode "msvc") + elseif(_ar_name MATCHES "^(ar|llvm-ar)(\\.exe)?$") + set(_mode "mri") + else() + set(_mode "mri") + endif() + set(_archiver "${ARCHIVER}") +else() + if(WIN32 AND NOT MINGW) + find_program(_archiver NAMES lib.exe llvm-lib) + set(_mode "msvc") + elseif(APPLE) + find_program(_archiver NAMES libtool) + set(_mode "libtool") + else() + find_program(_archiver NAMES ar) + set(_mode "mri") + endif() + if(NOT _archiver) + message(FATAL_ERROR "Unable to find an archiver to merge the x265 multilib libraries") + endif() +endif() + +if(_mode STREQUAL "mri") + set(_script_contents "CREATE ${_out_name}\n") + foreach(_input IN LISTS _inputs) + string(APPEND _script_contents "ADDLIB ${_input}\n") + endforeach() + string(APPEND _script_contents "SAVE\nEND\n") + file(WRITE "${_work}/merge.script" "${_script_contents}") + execute_process( + COMMAND "${_archiver}" -M + WORKING_DIRECTORY "${_work}" + INPUT_FILE "${_work}/merge.script" + RESULT_VARIABLE _result) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Failed to merge static archives with ${_archiver}: ${_result}") + endif() +elseif(_mode STREQUAL "msvc") + set(_command "/NOLOGO" "/OUT:${_work}/${_out_name}") + foreach(_input IN LISTS _inputs) + list(APPEND _command "${_work}/${_input}") + endforeach() + execute_process(COMMAND "${_archiver}" ${_command} WORKING_DIRECTORY "${_work}" RESULT_VARIABLE _result) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Failed to merge static archives with ${_archiver}: ${_result}") + endif() +else() + set(_command libtool -static -o "${_work}/${_out_name}") + foreach(_input IN LISTS _inputs) + list(APPEND _command "${_work}/${_input}") + endforeach() + execute_process(COMMAND ${_command} WORKING_DIRECTORY "${_work}" RESULT_VARIABLE _result) + if(NOT _result EQUAL 0) + message(FATAL_ERROR "Failed to merge static archives with libtool: ${_result}") + endif() +endif() + +if(EXISTS "${OUTPUT}") + file(REMOVE "${OUTPUT}") +endif() +file(RENAME "${_work}/${_out_name}" "${OUTPUT}") +file(REMOVE_RECURSE "${_work}") From 934d4aece2fae12bef540b75ebbfc98add467492 Mon Sep 17 00:00:00 2001 From: Shen Date: Fri, 28 Aug 2026 22:08:39 +0800 Subject: [PATCH 2/7] cmake: Force sub-build archive output to a fixed directory Multi-config generators (e.g. Visual Studio) place the sub-build archives in a per-configuration subdirectory, so the hardcoded archive path used by EXTRA_LIB and the merge step did not exist and linking failed with LNK1181. Set CMAKE_ARCHIVE_OUTPUT_DIRECTORY (for every configuration) in the sub-builds so the archives always land in the multilib/ subdirectories. Also gate the VLD and libnuma options to the platforms where they apply, and drop the unreachable guard. --- source/cmake/multilib.cmake | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index 9a9e1694c..8a5e87f22 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -8,10 +8,6 @@ # when the library is built; the resulting archives are then merged into the # 8-bit API library by a POST_BUILD step (see CMakeLists.txt). -if(NOT ENABLE_MULTILIB) - return() -endif() - set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") @@ -21,11 +17,17 @@ set(_multilib_common_args "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DENABLE_SHARED=OFF" "-DENABLE_CLI=OFF" - "-DENABLE_LIBNUMA=OFF" "-DENABLE_PIC=ON" "-DEXPORT_C_API=OFF" - "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=ON" ) +if(WIN32) + # VLD is a Windows-only optional dependency + list(APPEND _multilib_common_args "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=ON") +endif() +if(UNIX) + # libnuma is only probed on Linux + list(APPEND _multilib_common_args "-DENABLE_LIBNUMA=OFF") +endif() if(DEFINED CMAKE_GENERATOR_PLATFORM) list(APPEND _multilib_common_args "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") endif() @@ -86,12 +88,22 @@ endif() # Define the bit-depth sub-builds as regular targets that declare their # produced archive as an output, so that the build system knows how to build # the archives the main library links against; the main library targets depend -# on them (see CMakeLists.txt) +# on them (see CMakeLists.txt). +# +# CMAKE_ARCHIVE_OUTPUT_DIRECTORY is forced for every configuration so that the +# produced archive always lands in the sub-build directory, independent of the +# generator (multi-config generators such as Visual Studio would otherwise +# place it in a per-configuration subdirectory). function(x265_define_multilib_variant name dir archive) add_custom_command( OUTPUT "${archive}" COMMAND "${CMAKE_COMMAND}" -S "${_multilib_source}" -B "${dir}" ${_multilib_common_args} ${ARGN} + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${dir}" + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG=${dir}" + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE=${dir}" + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL=${dir}" + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO=${dir}" COMMAND "${CMAKE_COMMAND}" --build "${dir}" --config "$" COMMENT "Building the ${name} x265 sub-library" VERBATIM) From 45c4ab4ebfea2bcb8359429ac02caa30b7a00d4b Mon Sep 17 00:00:00 2001 From: Shen Date: Sat, 29 Aug 2026 13:04:42 +0800 Subject: [PATCH 3/7] cmake: Make the multilib sub-builds generator- and toolchain-agnostic Removes the packaging-tool variables from the multilib logic: the nested builds now inherit only standard CMake settings (generator, toolchain file, system settings) and this project's own build options. Toolchain-specific target selection can be passed through the new MULTILIB_CMAKE_ARGS variable. Also fixes the empty -T argument on generators without toolset support, adds source tracking so the nested builds re-run when the sources change, and forwards CMAKE_BUILD_PARALLEL_LEVEL to the nested builds. --- source/CMakeLists.txt | 19 ++++++----- source/cmake/multilib.cmake | 68 ++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d7ecf3e2a..1e131ec0a 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -752,14 +752,6 @@ if(EXTRA_LIB) endif(EXTRA_LIB) mark_as_advanced(EXTRA_LIB EXTRA_LINK_FLAGS) -# Build a single library containing the 8-bit, 10-bit and 12-bit encoders, -# switching bit depths at runtime (see build/linux/multilib.sh) -option(ENABLE_MULTILIB "Build a single library containing all supported bit depths" OFF) -set(MULTILIB_ARCHIVER "" CACHE STRING "Archiver used to merge the multilib static libraries") -if(ENABLE_MULTILIB) - include(cmake/multilib.cmake) -endif() - if(X64 OR ARM64 OR PPC64 OR RISCV64 OR LOONGARCH64) # NOTE: We only officially support high-bit-depth compiles of x265 # on 64bit architectures. Main10 plus large resolution plus slow @@ -938,6 +930,17 @@ endif() configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in" "${PROJECT_BINARY_DIR}/x265_config.h") +# Build a single library containing the 8-bit, 10-bit and 12-bit encoders, +# switching bit depths at runtime (see build/linux/multilib.sh). Toolchain +# settings that CMake cannot infer (e.g. a packaging toolchain's target +# triplet) can be passed through MULTILIB_CMAKE_ARGS. +option(ENABLE_MULTILIB "Build a single library containing all supported bit depths" OFF) +set(MULTILIB_CMAKE_ARGS "" CACHE STRING "Extra CMake arguments for the multilib sub-builds") +set(MULTILIB_ARCHIVER "" CACHE STRING "Archiver used to merge the multilib static libraries") +if(ENABLE_MULTILIB) + include(cmake/multilib.cmake) +endif() + add_subdirectory(encoder) add_subdirectory(common) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index 8a5e87f22..86a496abc 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -7,6 +7,11 @@ # sub-builds are defined as regular build targets so that they are only built # when the library is built; the resulting archives are then merged into the # 8-bit API library by a POST_BUILD step (see CMakeLists.txt). +# +# The nested builds inherit the generator, the toolchain and this project's +# build options. Toolchain-specific settings that CMake cannot infer (e.g. a +# target-selection variable such as ANDROID_ABI, or a packaging toolchain's +# triplet) can be passed through MULTILIB_CMAKE_ARGS. set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") @@ -20,17 +25,12 @@ set(_multilib_common_args "-DENABLE_PIC=ON" "-DEXPORT_C_API=OFF" ) -if(WIN32) - # VLD is a Windows-only optional dependency - list(APPEND _multilib_common_args "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=ON") -endif() -if(UNIX) - # libnuma is only probed on Linux - list(APPEND _multilib_common_args "-DENABLE_LIBNUMA=OFF") -endif() if(DEFINED CMAKE_GENERATOR_PLATFORM) list(APPEND _multilib_common_args "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") endif() +if(DEFINED CMAKE_GENERATOR_TOOLSET AND NOT CMAKE_GENERATOR_TOOLSET STREQUAL "") + list(APPEND _multilib_common_args "-T${CMAKE_GENERATOR_TOOLSET}") +endif() if(DEFINED CMAKE_MAKE_PROGRAM) list(APPEND _multilib_common_args "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") endif() @@ -46,36 +46,32 @@ if(CMAKE_CROSSCOMPILING AND DEFINED CMAKE_SYSTEM_NAME) # cross-compiling mode list(APPEND _multilib_common_args "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}") endif() -if(DEFINED VERSION) - list(APPEND _multilib_common_args "-DVERSION=${VERSION}") +# Inherit this project's build options +foreach(_option IN ITEMS ENABLE_ASSEMBLY ENABLE_LIBNUMA ENABLE_HDR10_PLUS + ENABLE_SVT_HEVC ENABLE_LIBVMAF ENABLE_ALPHA + ENABLE_MULTIVIEW ENABLE_SCC_EXT) + if(DEFINED ${_option}) + list(APPEND _multilib_common_args "-D${_option}=${${_option}}") + endif() +endforeach() +if(DEFINED CMAKE_DISABLE_FIND_PACKAGE_VLD) + list(APPEND _multilib_common_args "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=${CMAKE_DISABLE_FIND_PACKAGE_VLD}") endif() -if(DEFINED NASM_EXECUTABLE) - list(APPEND _multilib_common_args "-DNASM_EXECUTABLE=${NASM_EXECUTABLE}") +# Packager hook: extra CMake arguments for the nested builds (e.g. the +# toolchain's target selection such as ANDROID_ABI) +if(DEFINED MULTILIB_CMAKE_ARGS AND NOT MULTILIB_CMAKE_ARGS STREQUAL "") + separate_arguments(_multilib_extra_args NATIVE_COMMAND "${MULTILIB_CMAKE_ARGS}") + list(APPEND _multilib_common_args ${_multilib_extra_args}) endif() -if(DEFINED ENABLE_ASSEMBLY) - list(APPEND _multilib_common_args "-DENABLE_ASSEMBLY=${ENABLE_ASSEMBLY}") +# Forward the parallelism of the parent build when it is known +set(_multilib_parallel_args "") +if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL} AND NOT "$ENV{CMAKE_BUILD_PARALLEL_LEVEL}" STREQUAL "") + set(_multilib_parallel_args "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}") endif() -# vcpkg: propagate the triplet settings so the nested builds use the same -# compiler and flags as the parent build -foreach(_var IN ITEMS VCPKG_TARGET_TRIPLET VCPKG_CHAINLOAD_TOOLCHAIN_FILE VCPKG_CXX_FLAGS - VCPKG_CXX_FLAGS_RELEASE VCPKG_CXX_FLAGS_DEBUG VCPKG_C_FLAGS - VCPKG_C_FLAGS_RELEASE VCPKG_C_FLAGS_DEBUG VCPKG_CRT_LINKAGE - VCPKG_LINKER_FLAGS VCPKG_LINKER_FLAGS_RELEASE VCPKG_LINKER_FLAGS_DEBUG - VCPKG_TARGET_ARCHITECTURE VCPKG_SET_CHARSET_FLAG VCPKG_PLATFORM_TOOLSET - VCPKG_INSTALLED_DIR _VCPKG_INSTALLED_DIR VCPKG_MANIFEST_INSTALL) - if(DEFINED ${_var}) - list(APPEND _multilib_common_args "-D${_var}=${${_var}}") - endif() -endforeach() -# The triplet's configure options (e.g. ANDROID_ABI) are spliced into the -# configure command line by vcpkg_cmake_configure, so they are visible here as -# plain cache variables and must reach the nested builds too -foreach(_var IN ITEMS ANDROID_ABI ANDROID_ARM_NEON ANDROID_ARM_MODE - OHOS_ARCH CMAKE_PLATFORM_NO_VERSIONED_SONAME) - if(DEFINED ${_var}) - list(APPEND _multilib_common_args "-D${_var}=${${_var}}") - endif() -endforeach() + +# Track the x265 sources so that the nested builds re-run when they change; +# without this the produced archive would be considered up to date forever +file(GLOB_RECURSE _multilib_depends CONFIGURE_DEPENDS "${_multilib_source}/*") # The nested builds produce the static archive of the x265-static target # (libx265.a on non-MSVC, x265-static.lib on MSVC) @@ -105,6 +101,8 @@ function(x265_define_multilib_variant name dir archive) "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL=${dir}" "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO=${dir}" COMMAND "${CMAKE_COMMAND}" --build "${dir}" --config "$" + ${_multilib_parallel_args} + DEPENDS ${_multilib_depends} COMMENT "Building the ${name} x265 sub-library" VERBATIM) add_custom_target(${name} DEPENDS "${archive}") From b49713bbebe2e53adcfaad9c620e75577bc14222 Mon Sep 17 00:00:00 2001 From: Shen Date: Fri, 4 Sep 2026 22:53:55 +0800 Subject: [PATCH 4/7] cmake: Address review feedback on the multilib sub-builds Guards the EXTRA_LIB overwrite behind a warning so a user-provided value is not silently discarded, silences the harmless libtool duplicate-member warnings on macOS, and chains the 10-bit and 12-bit sub-builds sequentially (like build/linux/multilib.sh) so that a parallel parent build does not oversubscribe the machine with two concurrent nested builds. --- source/cmake/multilib.cmake | 17 ++++++++++++----- source/cmake/x265-merge.cmake | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index 86a496abc..d29844caf 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -112,11 +112,18 @@ x265_define_multilib_variant(x265-multilib-10bit "${_multilib_root}/10bit" "${_multilib_root}/10bit/${_multilib_archive_name}" "-DHIGH_BIT_DEPTH=ON") x265_define_multilib_variant(x265-multilib-12bit "${_multilib_root}/12bit" "${_multilib_root}/12bit/${_multilib_archive_name}" "-DHIGH_BIT_DEPTH=ON" "-DMAIN12=ON") +# Build the bit-depth sub-libraries sequentially (like build/linux/multilib.sh) +# so that a parallel parent build does not run two nested builds at once +add_dependencies(x265-multilib-12bit x265-multilib-10bit) # Link the 10/12-bit archives into this build; the LINKED_* options enable the # bit-depth dispatch in the exported C API (see encoder/api.cpp) -set(EXTRA_LIB - "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}" - CACHE STRING "Extra libraries to link against" FORCE) -set(LINKED_10BIT ON CACHE BOOL "10bit libx265 is being linked with this library" FORCE) -set(LINKED_12BIT ON CACHE BOOL "12bit libx265 is being linked with this library" FORCE) +if(EXTRA_LIB) + message(WARNING "ENABLE_MULTILIB is enabled but EXTRA_LIB is already set; the multilib archives will not be linked automatically") +else() + set(EXTRA_LIB + "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}" + CACHE STRING "Extra libraries to link against" FORCE) + set(LINKED_10BIT ON CACHE BOOL "10bit libx265 is being linked with this library" FORCE) + set(LINKED_12BIT ON CACHE BOOL "12bit libx265 is being linked with this library" FORCE) +endif() diff --git a/source/cmake/x265-merge.cmake b/source/cmake/x265-merge.cmake index a77e28779..348ba3ce9 100644 --- a/source/cmake/x265-merge.cmake +++ b/source/cmake/x265-merge.cmake @@ -91,9 +91,19 @@ else() foreach(_input IN LISTS _inputs) list(APPEND _command "${_work}/${_input}") endforeach() - execute_process(COMMAND ${_command} WORKING_DIRECTORY "${_work}" RESULT_VARIABLE _result) + execute_process(COMMAND ${_command} WORKING_DIRECTORY "${_work}" + OUTPUT_VARIABLE _out ERROR_VARIABLE _err RESULT_VARIABLE _result) if(NOT _result EQUAL 0) - message(FATAL_ERROR "Failed to merge static archives with libtool: ${_result}") + message(FATAL_ERROR "Failed to merge static archives with libtool: ${_result}\n${_out}${_err}") + endif() + # libtool warns about the identically-named object members that the three + # bit-depth builds produce; harmless, filter them from the output + if(_err) + string(REGEX REPLACE "[^\n]*member name[^\n]*\n?" "" _err "${_err}") + string(STRIP "${_err}" _err) + if(NOT _err STREQUAL "") + message(STATUS "${_err}") + endif() endif() endif() From 65ff1a06a428d283fc1ed6b381b3bea64f42ad72 Mon Sep 17 00:00:00 2001 From: Shen Date: Fri, 2 Oct 2026 23:49:51 +0800 Subject: [PATCH 5/7] cmake: Address remaining multilib review feedback Move the ENABLE_SHARED option above the multilib logic so it is defined before use, and clear the EXTRA_LIB/LINKED_10BIT/LINKED_12BIT cache entries that ENABLE_MULTILIB force-writes when the option is switched back OFF in an existing build tree. Forward the parent compilers and compiler launcher to the nested builds so a clang parent no longer silently builds the 10/12-bit libraries with gcc, and inherit ENABLE_PIC instead of forcing it on (forcing it broke clang on Windows). Also forward VERSION, NASM_EXECUTABLE and the common Android/OHOS target variables. Use the target toolchain's archiver for the static merge when cross-compiling: x265-merge.cmake runs in script mode, where WIN32 describes the host, so it otherwise picks a host tool that cannot read the target objects (e.g. llvm-lib on Android ELF objects). Only pass the per-configuration CMAKE_ARCHIVE_OUTPUT_DIRECTORY variants for multi-config generators, and fall back to ProcessorCount for the nested -j when CMAKE_BUILD_PARALLEL_LEVEL is not set. Tested: Windows x64 (MSVC VS static, MSVC Ninja shared, clang-cl static and shared), Linux x86_64 gcc static/shared, Linux arm64 gcc static/shared and clang static, macOS arm64 clang static/shared, and Android x86_64 NDK clang static. --- source/CMakeLists.txt | 16 +++++- source/cmake/multilib.cmake | 106 +++++++++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1e131ec0a..5d505d730 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -930,6 +930,10 @@ endif() configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in" "${PROJECT_BINARY_DIR}/x265_config.h") +# Declared before the multilib logic below, which reads it to decide whether +# the 10/12-bit archives should be merged into the static library. +option(ENABLE_SHARED "Build shared library" ON) + # Build a single library containing the 8-bit, 10-bit and 12-bit encoders, # switching bit depths at runtime (see build/linux/multilib.sh). Toolchain # settings that CMake cannot infer (e.g. a packaging toolchain's target @@ -939,6 +943,15 @@ set(MULTILIB_CMAKE_ARGS "" CACHE STRING "Extra CMake arguments for the multilib set(MULTILIB_ARCHIVER "" CACHE STRING "Archiver used to merge the multilib static libraries") if(ENABLE_MULTILIB) include(cmake/multilib.cmake) +elseif(X265_MULTILIB_CACHE_OWNED) + # ENABLE_MULTILIB was switched back OFF in an existing build tree; drop the + # cache entries that the ON configuration force-wrote so the 8-bit library + # stops trying to link the (now stale) multilib archives. + unset(EXTRA_LIB CACHE) + unset(EXTRA_LIB) + unset(LINKED_10BIT CACHE) + unset(LINKED_12BIT CACHE) + unset(X265_MULTILIB_CACHE_OWNED CACHE) endif() add_subdirectory(encoder) @@ -1108,7 +1121,7 @@ if(ENABLE_MULTILIB AND NOT ENABLE_SHARED) "-DINPUT1=$" "-DINPUT2=${CMAKE_CURRENT_BINARY_DIR}/multilib/10bit/${_multilib_archive_name}" "-DINPUT3=${CMAKE_CURRENT_BINARY_DIR}/multilib/12bit/${_multilib_archive_name}" - "-DARCHIVER=${MULTILIB_ARCHIVER}" + "-DARCHIVER=${_multilib_archiver}" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/x265-merge.cmake" VERBATIM) endif() @@ -1184,7 +1197,6 @@ endif() if(NOT (MSVC_IDE OR XCODE)) add_custom_target(clean-generated COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/clean-generated.cmake) endif() -option(ENABLE_SHARED "Build shared library" ON) if(ENABLE_SHARED) if(ENABLE_HDR10_PLUS) add_library(x265-shared SHARED "${PROJECT_BINARY_DIR}/x265.def" ${ASM_OBJS} diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index d29844caf..977955094 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -9,9 +9,9 @@ # 8-bit API library by a POST_BUILD step (see CMakeLists.txt). # # The nested builds inherit the generator, the toolchain and this project's -# build options. Toolchain-specific settings that CMake cannot infer (e.g. a -# target-selection variable such as ANDROID_ABI, or a packaging toolchain's -# triplet) can be passed through MULTILIB_CMAKE_ARGS. +# build options, plus the common Android/OHOS target-selection variables. Any +# other toolchain-specific setting that CMake cannot infer (e.g. a packaging +# toolchain's triplet) can be passed through MULTILIB_CMAKE_ARGS. set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") @@ -22,7 +22,6 @@ set(_multilib_common_args "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DENABLE_SHARED=OFF" "-DENABLE_CLI=OFF" - "-DENABLE_PIC=ON" "-DEXPORT_C_API=OFF" ) if(DEFINED CMAKE_GENERATOR_PLATFORM) @@ -34,6 +33,21 @@ endif() if(DEFINED CMAKE_MAKE_PROGRAM) list(APPEND _multilib_common_args "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") endif() +# Inherit the compilers (and any launcher such as ccache/sccache) so the nested +# builds use the same toolchain as the parent instead of whatever CMake happens +# to detect in the build environment +if(DEFINED CMAKE_C_COMPILER) + list(APPEND _multilib_common_args "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}") +endif() +if(DEFINED CMAKE_CXX_COMPILER) + list(APPEND _multilib_common_args "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") +endif() +if(DEFINED CMAKE_C_COMPILER_LAUNCHER AND NOT CMAKE_C_COMPILER_LAUNCHER STREQUAL "") + list(APPEND _multilib_common_args "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}") +endif() +if(DEFINED CMAKE_CXX_COMPILER_LAUNCHER AND NOT CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "") + list(APPEND _multilib_common_args "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}") +endif() if(DEFINED CMAKE_BUILD_TYPE) list(APPEND _multilib_common_args "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") endif() @@ -47,9 +61,9 @@ if(CMAKE_CROSSCOMPILING AND DEFINED CMAKE_SYSTEM_NAME) list(APPEND _multilib_common_args "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}") endif() # Inherit this project's build options -foreach(_option IN ITEMS ENABLE_ASSEMBLY ENABLE_LIBNUMA ENABLE_HDR10_PLUS - ENABLE_SVT_HEVC ENABLE_LIBVMAF ENABLE_ALPHA - ENABLE_MULTIVIEW ENABLE_SCC_EXT) +foreach(_option IN ITEMS ENABLE_ASSEMBLY ENABLE_PIC ENABLE_LIBNUMA + ENABLE_HDR10_PLUS ENABLE_SVT_HEVC ENABLE_LIBVMAF + ENABLE_ALPHA ENABLE_MULTIVIEW ENABLE_SCC_EXT) if(DEFINED ${_option}) list(APPEND _multilib_common_args "-D${_option}=${${_option}}") endif() @@ -57,16 +71,44 @@ endforeach() if(DEFINED CMAKE_DISABLE_FIND_PACKAGE_VLD) list(APPEND _multilib_common_args "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=${CMAKE_DISABLE_FIND_PACKAGE_VLD}") endif() +# These are often provided on the configure command line rather than by the +# toolchain file, so forward them explicitly when present +if(DEFINED VERSION) + list(APPEND _multilib_common_args "-DVERSION=${VERSION}") +endif() +if(DEFINED NASM_EXECUTABLE) + list(APPEND _multilib_common_args "-DNASM_EXECUTABLE=${NASM_EXECUTABLE}") +endif() +# Toolchain target selection (e.g. the Android NDK ABI) is passed on the +# command line and lives in plain cache variables, not in the toolchain file; +# forward the common ones so the nested builds target the same ABI. +foreach(_var IN ITEMS ANDROID_ABI ANDROID_ARM_NEON ANDROID_ARM_MODE + ANDROID_PLATFORM ANDROID_STL ANDROID_NDK + ANDROID_TOOLCHAIN ANDROID_CPP_FEATURES + OHOS_ARCH CMAKE_PLATFORM_NO_VERSIONED_SONAME) + if(DEFINED ${_var}) + list(APPEND _multilib_common_args "-D${_var}=${${_var}}") + endif() +endforeach() # Packager hook: extra CMake arguments for the nested builds (e.g. the # toolchain's target selection such as ANDROID_ABI) if(DEFINED MULTILIB_CMAKE_ARGS AND NOT MULTILIB_CMAKE_ARGS STREQUAL "") separate_arguments(_multilib_extra_args NATIVE_COMMAND "${MULTILIB_CMAKE_ARGS}") list(APPEND _multilib_common_args ${_multilib_extra_args}) endif() -# Forward the parallelism of the parent build when it is known +# Forward the parallelism of the parent build so the nested builds do not fall +# back to -j1. Prefer CMAKE_BUILD_PARALLEL_LEVEL and otherwise fall back to the +# machine's processor count; the two bit-depth sub-builds are chained +# sequentially, so each may safely use all cores. set(_multilib_parallel_args "") if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL} AND NOT "$ENV{CMAKE_BUILD_PARALLEL_LEVEL}" STREQUAL "") set(_multilib_parallel_args "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}") +else() + include(ProcessorCount) + ProcessorCount(_multilib_ncpu) + if(_multilib_ncpu GREATER 0) + set(_multilib_parallel_args "-j${_multilib_ncpu}") + endif() endif() # Track the x265 sources so that the nested builds re-run when they change; @@ -81,25 +123,42 @@ else() set(_multilib_archive_name "libx265.a") endif() +# Archiver used by the static merge. When cross-compiling, x265-merge.cmake +# would auto-detect a host tool (in `cmake -P` mode WIN32 reflects the host), +# which cannot process target objects; pass the target toolchain's archiver +# instead. Native builds keep the script's platform default (libtool on macOS, +# lib.exe/llvm-lib on MSVC, ar elsewhere). +if(MULTILIB_ARCHIVER) + set(_multilib_archiver "${MULTILIB_ARCHIVER}") +elseif(CMAKE_CROSSCOMPILING AND CMAKE_AR) + set(_multilib_archiver "${CMAKE_AR}") +else() + set(_multilib_archiver "") +endif() + # Define the bit-depth sub-builds as regular targets that declare their # produced archive as an output, so that the build system knows how to build # the archives the main library links against; the main library targets depend # on them (see CMakeLists.txt). # -# CMAKE_ARCHIVE_OUTPUT_DIRECTORY is forced for every configuration so that the -# produced archive always lands in the sub-build directory, independent of the -# generator (multi-config generators such as Visual Studio would otherwise -# place it in a per-configuration subdirectory). +# CMAKE_ARCHIVE_OUTPUT_DIRECTORY is forced so that the produced archive always +# lands directly in the sub-build directory. Multi-config generators such as +# Visual Studio additionally need the per-configuration variants, otherwise +# they append a per-configuration subdirectory to the archive path. function(x265_define_multilib_variant name dir archive) + set(_archive_dir_args "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${dir}") + if(CMAKE_CONFIGURATION_TYPES) + # Multi-config generators (Visual Studio, Xcode) append a per-config + # subdirectory to the generic path unless the per-config path is set + foreach(_config IN ITEMS DEBUG RELEASE MINSIZEREL RELWITHDEBINFO) + list(APPEND _archive_dir_args "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_${_config}=${dir}") + endforeach() + endif() add_custom_command( OUTPUT "${archive}" COMMAND "${CMAKE_COMMAND}" -S "${_multilib_source}" -B "${dir}" ${_multilib_common_args} ${ARGN} - "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${dir}" - "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG=${dir}" - "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE=${dir}" - "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL=${dir}" - "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO=${dir}" + ${_archive_dir_args} COMMAND "${CMAKE_COMMAND}" --build "${dir}" --config "$" ${_multilib_parallel_args} DEPENDS ${_multilib_depends} @@ -117,13 +176,18 @@ x265_define_multilib_variant(x265-multilib-12bit "${_multilib_root}/12bit" add_dependencies(x265-multilib-12bit x265-multilib-10bit) # Link the 10/12-bit archives into this build; the LINKED_* options enable the -# bit-depth dispatch in the exported C API (see encoder/api.cpp) -if(EXTRA_LIB) +# bit-depth dispatch in the exported C API (see encoder/api.cpp). A user +# provided EXTRA_LIB is respected (with a warning) instead of being silently +# discarded; the cache entries are marked as owned so they can be cleared when +# ENABLE_MULTILIB is turned back OFF in the same build tree. +set(_multilib_extra_lib + "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}") +if(EXTRA_LIB AND NOT "${EXTRA_LIB}" STREQUAL "${_multilib_extra_lib}") message(WARNING "ENABLE_MULTILIB is enabled but EXTRA_LIB is already set; the multilib archives will not be linked automatically") else() - set(EXTRA_LIB - "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}" + set(EXTRA_LIB "${_multilib_extra_lib}" CACHE STRING "Extra libraries to link against" FORCE) set(LINKED_10BIT ON CACHE BOOL "10bit libx265 is being linked with this library" FORCE) set(LINKED_12BIT ON CACHE BOOL "12bit libx265 is being linked with this library" FORCE) + set(X265_MULTILIB_CACHE_OWNED ON CACHE INTERNAL "x265 multilib force-set EXTRA_LIB/LINKED_10BIT/LINKED_12BIT") endif() From cb6956feb857315fea65f39e3d10e86c0e3bc1c7 Mon Sep 17 00:00:00 2001 From: Shen Date: Tue, 13 Oct 2026 00:19:32 +0800 Subject: [PATCH 6/7] cmake: Fix the multilib issues found in the latest review round Merge the 10/12-bit archives into the static library regardless of ENABLE_SHARED: the static library is built and installed either way, and a consumer linking only libx265.a previously got an incomplete archive (undefined references to the x265_10bit/x265_12bit namespaces) when the shared library was also built, which is the default. Make the merge archiver target-aware. x265-merge.cmake runs in cmake -P script mode where WIN32/APPLE/MINGW describe the host, so a MinGW build on Windows made it look for lib.exe instead of ar; pass the target toolchain's CMAKE_AR for MinGW (and non-Apple cross builds), while Apple targets (including iOS/tvOS) keep the script's libtool default, since Apple's ar does not support the MRI scripts. The script now also recognizes libtool/llvm-libtool and prefixed cross archivers when ARCHIVER is given. Forward STATIC_LINK_CRT (a /MT parent with /MD sub-builds failed with LNK2038 on MSVC), CMAKE_C_FLAGS/CMAKE_CXX_FLAGS, CMAKE_MSVC_RUNTIME_LIBRARY and CMAKE_OSX_ARCHITECTURES. Values containing list separators are escaped so they reach the nested configure as one argument; without this, a universal macOS build (arm64;x86_64) configured the sub-builds for arm64 only and the x86_64 slice of the merged library lacked the 10/12-bit objects. Require CMake 3.13 for ENABLE_MULTILIB with a clear configure-time error (older versions can use build/linux/multilib.sh), and pass -j and the level as separate arguments since cmake --build rejects the combined -jN form before 3.14. Exclude the build tree from the source tracking glob: when the build directory is located inside source/ (CLion and VS Code do this when they open source/), the generated files made the sub-builds depend on their own outputs and broke the build with circular dependency warnings. Fix the interactions with a user-provided EXTRA_LIB: it now resets the stale LINKED_10BIT/LINKED_12BIT values a previous multilib run had forced, survives turning ENABLE_MULTILIB back OFF, and no longer fails to configure at encoder/CMakeLists.txt when none of the LINKED_* options is set (empty COMPILE_FLAGS). Serialize the sub-builds after the 8-bit objects, like multilib.sh builds the bit-depth libraries one after another, and run the nested build with an empty MAKEFLAGS so a parallel parent make no longer emits jobserver warnings and the build logs do not interleave. Tested: Linux gcc (Unix Makefiles with make -j8: no jobserver warnings, serialized logs; Ninja shared build with merged 228-member static lib), build dir inside source/ (three consecutive builds), user EXTRA_LIB scenarios, MSVC VS static with STATIC_LINK_CRT=ON (no LNK2038, nested /MT), MSVC Ninja shared (231-member merged lib), macOS arm64 shared (271-member merged lib) and universal arm64+x86_64 (fat merged archive, x86_64 consumer links), Android x86_64 (228-member llvm-ar merge), CMake 3.13.5 full build and CMake 3.12.4 clean error. --- source/CMakeLists.txt | 16 +++++- source/cmake/multilib.cmake | 105 ++++++++++++++++++++++++++++------ source/cmake/x265-merge.cmake | 4 ++ source/encoder/CMakeLists.txt | 7 ++- 4 files changed, 111 insertions(+), 21 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 5d505d730..0d25fe930 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1112,9 +1112,12 @@ endif() if(EXTRA_LIB) target_link_libraries(x265-static ${EXTRA_LIB}) endif() -if(ENABLE_MULTILIB AND NOT ENABLE_SHARED) +if(ENABLE_MULTILIB) # Statically merge the 10/12-bit archives into the 8-bit API archive so - # that consumers only need to link against a single library + # that consumers only need to link against a single library. This runs + # for shared builds as well: the static library is built and installed + # either way, and without the merge it would lack the 10/12-bit objects + # (multilib.sh merges them in this case too) add_custom_command(TARGET x265-static POST_BUILD COMMAND "${CMAKE_COMMAND}" "-DOUTPUT=$" @@ -1129,6 +1132,15 @@ if(ENABLE_MULTILIB) # Make sure the 10/12-bit sub-builds are done before this library is # archived and merged add_dependencies(x265-static x265-multilib-10bit x265-multilib-12bit) + # Run the sub-builds after the 8-bit objects have been compiled (the + # bit-depth libraries are also built one after another), like + # multilib.sh does: a parallel parent build would otherwise interleave + # the nested build logs with the main compile output + foreach(_multilib_objects IN ITEMS encoder common dynamicHDR10 x265-asm) + if(TARGET ${_multilib_objects}) + add_dependencies(x265-multilib-10bit ${_multilib_objects}) + endif() + endforeach() endif() if(ENABLE_LIBVMAF) target_link_libraries(x265-static ${VMAF}) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index 977955094..8e6798255 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -16,6 +16,27 @@ set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") +# The nested builds are driven with `cmake -S/-B`, which needs CMake 3.13; +# plain x265 builds keep supporting older CMake versions +if(CMAKE_VERSION VERSION_LESS 3.13) + message(FATAL_ERROR "ENABLE_MULTILIB requires CMake 3.13 or later. " + "Use build/linux/multilib.sh or the build/vc*-x86_64/multilib.bat " + "scripts to build the multi-bit-depth library with older CMake.") +endif() + +# Append "-D=" to the argument list, escaping the list separator. +# Semicolons cannot be stored in list elements, so a value containing a list +# (e.g. CMAKE_OSX_ARCHITECTURES=arm64;x86_64 or ANDROID_ABI with several ABIs) +# is escaped to "\;" here and arrives at the nested configure as a single +# argument with the semicolons restored. +function(x265_multilib_append_d list_var var) + if(DEFINED ${var} AND NOT "${${var}}" STREQUAL "") + string(REPLACE ";" "\\;" _value "${${var}}") + list(APPEND ${list_var} "-D${var}=${_value}") + set(${list_var} "${${list_var}}" PARENT_SCOPE) + endif() +endfunction() + # Inherit the generator, toolchain and platform settings from this build set(_multilib_common_args "-DCMAKE_GENERATOR=${CMAKE_GENERATOR}" @@ -60,14 +81,29 @@ if(CMAKE_CROSSCOMPILING AND DEFINED CMAKE_SYSTEM_NAME) # cross-compiling mode list(APPEND _multilib_common_args "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}") endif() -# Inherit this project's build options +# Inherit this project's build options (STATIC_LINK_CRT matters: mixing /MT +# and /MD objects fails with LNK2038 on MSVC) foreach(_option IN ITEMS ENABLE_ASSEMBLY ENABLE_PIC ENABLE_LIBNUMA - ENABLE_HDR10_PLUS ENABLE_SVT_HEVC ENABLE_LIBVMAF - ENABLE_ALPHA ENABLE_MULTIVIEW ENABLE_SCC_EXT) + STATIC_LINK_CRT ENABLE_HDR10_PLUS ENABLE_SVT_HEVC + ENABLE_LIBVMAF ENABLE_ALPHA ENABLE_MULTIVIEW + ENABLE_SCC_EXT) if(DEFINED ${_option}) list(APPEND _multilib_common_args "-D${_option}=${${_option}}") endif() endforeach() +# Inherit user-provided compiler flags so the sub-builds are compiled with +# the same settings as the parent +x265_multilib_append_d(_multilib_common_args CMAKE_C_FLAGS) +x265_multilib_append_d(_multilib_common_args CMAKE_CXX_FLAGS) +# The MSVC runtime selection must match the parent's (LNK2038 otherwise) +if(MSVC) + x265_multilib_append_d(_multilib_common_args CMAKE_MSVC_RUNTIME_LIBRARY) +endif() +# Universal macOS builds (e.g. -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64) must +# produce universal sub-libraries as well +if(APPLE) + x265_multilib_append_d(_multilib_common_args CMAKE_OSX_ARCHITECTURES) +endif() if(DEFINED CMAKE_DISABLE_FIND_PACKAGE_VLD) list(APPEND _multilib_common_args "-DCMAKE_DISABLE_FIND_PACKAGE_VLD=${CMAKE_DISABLE_FIND_PACKAGE_VLD}") endif() @@ -81,14 +117,14 @@ if(DEFINED NASM_EXECUTABLE) endif() # Toolchain target selection (e.g. the Android NDK ABI) is passed on the # command line and lives in plain cache variables, not in the toolchain file; -# forward the common ones so the nested builds target the same ABI. +# forward the common ones so the nested builds target the same ABI. The +# values may themselves be lists (several ANDROID_ABI entries, +# ANDROID_CPP_FEATURES=rtti;exceptions), hence the escaped append. foreach(_var IN ITEMS ANDROID_ABI ANDROID_ARM_NEON ANDROID_ARM_MODE ANDROID_PLATFORM ANDROID_STL ANDROID_NDK ANDROID_TOOLCHAIN ANDROID_CPP_FEATURES OHOS_ARCH CMAKE_PLATFORM_NO_VERSIONED_SONAME) - if(DEFINED ${_var}) - list(APPEND _multilib_common_args "-D${_var}=${${_var}}") - endif() + x265_multilib_append_d(_multilib_common_args ${_var}) endforeach() # Packager hook: extra CMake arguments for the nested builds (e.g. the # toolchain's target selection such as ANDROID_ABI) @@ -99,21 +135,36 @@ endif() # Forward the parallelism of the parent build so the nested builds do not fall # back to -j1. Prefer CMAKE_BUILD_PARALLEL_LEVEL and otherwise fall back to the # machine's processor count; the two bit-depth sub-builds are chained -# sequentially, so each may safely use all cores. +# sequentially, so each may safely use all cores. The level is passed as a +# separate argument: `cmake --build` rejects the combined "-jN" form before +# CMake 3.14. set(_multilib_parallel_args "") if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL} AND NOT "$ENV{CMAKE_BUILD_PARALLEL_LEVEL}" STREQUAL "") - set(_multilib_parallel_args "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}") + set(_multilib_parallel_args -j "$ENV{CMAKE_BUILD_PARALLEL_LEVEL}") else() include(ProcessorCount) ProcessorCount(_multilib_ncpu) if(_multilib_ncpu GREATER 0) - set(_multilib_parallel_args "-j${_multilib_ncpu}") + set(_multilib_parallel_args -j "${_multilib_ncpu}") endif() endif() # Track the x265 sources so that the nested builds re-run when they change; -# without this the produced archive would be considered up to date forever +# without this the produced archive would be considered up to date forever. +# The build tree is excluded from the tracking: it can be located inside +# source/ (CLion and VS Code do this when they open source/ as the project +# root), and its generated files would make the sub-builds depend on their +# own outputs (circular dependency). file(GLOB_RECURSE _multilib_depends CONFIGURE_DEPENDS "${_multilib_source}/*") +set(_multilib_build_prefix "${CMAKE_CURRENT_BINARY_DIR}/") +set(_multilib_sources "") +foreach(_dep IN LISTS _multilib_depends) + string(FIND "${_dep}" "${_multilib_build_prefix}" _multilib_dep_pos) + if(NOT _multilib_dep_pos EQUAL 0) + list(APPEND _multilib_sources "${_dep}") + endif() +endforeach() +set(_multilib_depends "${_multilib_sources}") # The nested builds produce the static archive of the x265-static target # (libx265.a on non-MSVC, x265-static.lib on MSVC) @@ -123,14 +174,18 @@ else() set(_multilib_archive_name "libx265.a") endif() -# Archiver used by the static merge. When cross-compiling, x265-merge.cmake -# would auto-detect a host tool (in `cmake -P` mode WIN32 reflects the host), -# which cannot process target objects; pass the target toolchain's archiver -# instead. Native builds keep the script's platform default (libtool on macOS, -# lib.exe/llvm-lib on MSVC, ar elsewhere). +# Archiver used by the static merge. x265-merge.cmake runs in `cmake -P` +# script mode, where WIN32/APPLE/MINGW describe the *host* rather than the +# target, so it cannot be left to auto-detect the tool whenever host and +# target can differ; pass the target toolchain's archiver instead (llvm-ar +# for the Android NDK, GNU ar for MinGW, ...). Apple targets (macOS and +# iOS/tvOS cross-builds) keep the script default: libtool merges Mach-O +# archives for every Apple platform, while Apple's ar does not support the +# MRI scripts used for ELF/COFF archives. Native MSVC and Linux builds also +# keep the default (lib.exe / ar). if(MULTILIB_ARCHIVER) set(_multilib_archiver "${MULTILIB_ARCHIVER}") -elseif(CMAKE_CROSSCOMPILING AND CMAKE_AR) +elseif(MINGW OR (CMAKE_CROSSCOMPILING AND NOT APPLE)) set(_multilib_archiver "${CMAKE_AR}") else() set(_multilib_archiver "") @@ -159,7 +214,11 @@ function(x265_define_multilib_variant name dir archive) COMMAND "${CMAKE_COMMAND}" -S "${_multilib_source}" -B "${dir}" ${_multilib_common_args} ${ARGN} ${_archive_dir_args} - COMMAND "${CMAKE_COMMAND}" --build "${dir}" --config "$" + # Run the nested build with an empty MAKEFLAGS so the nested make does + # not try to join the parent's jobserver ("resetting jobserver mode" + # warnings); the explicit -j above gives it its own parallelism + COMMAND "${CMAKE_COMMAND}" -E env MAKEFLAGS= "${CMAKE_COMMAND}" + --build "${dir}" --config "$" ${_multilib_parallel_args} DEPENDS ${_multilib_depends} COMMENT "Building the ${name} x265 sub-library" @@ -184,6 +243,16 @@ set(_multilib_extra_lib "${_multilib_root}/10bit/${_multilib_archive_name};${_multilib_root}/12bit/${_multilib_archive_name}") if(EXTRA_LIB AND NOT "${EXTRA_LIB}" STREQUAL "${_multilib_extra_lib}") message(WARNING "ENABLE_MULTILIB is enabled but EXTRA_LIB is already set; the multilib archives will not be linked automatically") + if(X265_MULTILIB_CACHE_OWNED) + # A previous ENABLE_MULTILIB configuration force-set these entries; + # the user's own EXTRA_LIB takes over now, so restore the LINKED_* + # defaults (they referred to the multilib archives, which are no + # longer linked) and release the ownership so that turning + # ENABLE_MULTILIB back OFF will not delete the user's value + unset(LINKED_10BIT CACHE) + unset(LINKED_12BIT CACHE) + unset(X265_MULTILIB_CACHE_OWNED CACHE) + endif() else() set(EXTRA_LIB "${_multilib_extra_lib}" CACHE STRING "Extra libraries to link against" FORCE) diff --git a/source/cmake/x265-merge.cmake b/source/cmake/x265-merge.cmake index 348ba3ce9..3aff35b12 100644 --- a/source/cmake/x265-merge.cmake +++ b/source/cmake/x265-merge.cmake @@ -40,9 +40,13 @@ if(DEFINED ARCHIVER AND ARCHIVER) get_filename_component(_ar_name "${ARCHIVER}" NAME) if(_ar_name MATCHES "^(lib|llvm-lib)(\\.exe)?$") set(_mode "msvc") + elseif(_ar_name MATCHES "^(libtool|llvm-libtool)(\\.exe)?$") + set(_mode "libtool") elseif(_ar_name MATCHES "^(ar|llvm-ar)(\\.exe)?$") set(_mode "mri") else() + # Prefixed cross archivers such as x86_64-w64-mingw32-ar or + # aarch64-linux-android-ar speak the GNU ar MRI syntax set(_mode "mri") endif() set(_archiver "${ARCHIVER}") diff --git a/source/encoder/CMakeLists.txt b/source/encoder/CMakeLists.txt index 8c8090786..c83804427 100644 --- a/source/encoder/CMakeLists.txt +++ b/source/encoder/CMakeLists.txt @@ -22,7 +22,12 @@ if(EXTRA_LIB) list(APPEND APIFLAGS "-DLINKED_12BIT=1") endif(LINKED_12BIT) string(REPLACE ";" " " APIFLAGSTR "${APIFLAGS}") - set_source_files_properties(api.cpp PROPERTIES COMPILE_FLAGS ${APIFLAGSTR}) + if(APIFLAGSTR) + # Guard against an empty flag list: EXTRA_LIB may be set without any + # of the LINKED_* options, and COMPILE_FLAGS without a value is a + # configure error + set_source_files_properties(api.cpp PROPERTIES COMPILE_FLAGS ${APIFLAGSTR}) + endif() endif(EXTRA_LIB) add_library(encoder OBJECT ../x265.h From fc91503c629bab004af2e758623b3bd879e2b404 Mon Sep 17 00:00:00 2001 From: Shen Date: Tue, 13 Oct 2026 21:05:46 +0800 Subject: [PATCH 7/7] cmake: Support ENABLE_MULTILIB down to CMake 3.12 Drive the nested configure through the documented cmake -E make_directory and cmake -E chdir tools with the source given as a positional argument instead of cmake -S/-B, which needs CMake 3.13; cmake -E chdir does not create the build directory, hence the explicit make_directory. The remaining requirements are file(GLOB_RECURSE ... CONFIGURE_DEPENDS) and the -j option of cmake --build, both introduced in CMake 3.12, so the version guard is lowered accordingly. Tested: full multilib build with CMake 3.12.4 (228-member merged archive, no jobserver warnings, 8bit+10bit+12bit CLI), CMake 3.11.4 fails at configure time with a pointer to multilib.sh, and regressions on Linux gcc (Unix Makefiles, make -j8), MSVC VS static (231 members) and macOS arm64 static (271 members). --- source/cmake/multilib.cmake | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/source/cmake/multilib.cmake b/source/cmake/multilib.cmake index 8e6798255..a0954c08a 100644 --- a/source/cmake/multilib.cmake +++ b/source/cmake/multilib.cmake @@ -16,10 +16,11 @@ set(_multilib_root "${CMAKE_CURRENT_BINARY_DIR}/multilib") set(_multilib_source "${CMAKE_CURRENT_SOURCE_DIR}") -# The nested builds are driven with `cmake -S/-B`, which needs CMake 3.13; -# plain x265 builds keep supporting older CMake versions -if(CMAKE_VERSION VERSION_LESS 3.13) - message(FATAL_ERROR "ENABLE_MULTILIB requires CMake 3.13 or later. " +# The nested builds need `file(GLOB_RECURSE ... CONFIGURE_DEPENDS)` and the +# -j option of `cmake --build`, both introduced in CMake 3.12; plain x265 +# builds keep supporting older CMake versions +if(CMAKE_VERSION VERSION_LESS 3.12) + message(FATAL_ERROR "ENABLE_MULTILIB requires CMake 3.12 or later. " "Use build/linux/multilib.sh or the build/vc*-x86_64/multilib.bat " "scripts to build the multi-bit-depth library with older CMake.") endif() @@ -211,9 +212,16 @@ function(x265_define_multilib_variant name dir archive) endif() add_custom_command( OUTPUT "${archive}" - COMMAND "${CMAKE_COMMAND}" -S "${_multilib_source}" -B "${dir}" + # The nested configure is driven through the documented `cmake -E + # make_directory`/`-E chdir` tools with the source given as a + # positional argument instead of `cmake -S/-B`, which needs CMake 3.13; + # `cmake -E chdir` does not create the build directory, hence the + # explicit make_directory + COMMAND "${CMAKE_COMMAND}" -E make_directory "${dir}" + COMMAND "${CMAKE_COMMAND}" -E chdir "${dir}" "${CMAKE_COMMAND}" ${_multilib_common_args} ${ARGN} ${_archive_dir_args} + "${_multilib_source}" # Run the nested build with an empty MAKEFLAGS so the nested make does # not try to join the parent's jobserver ("resetting jobserver mode" # warnings); the explicit -j above gives it its own parallelism