#!/usr/bin/env bash set -euo pipefail source_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) workspace_dir=$(cd "$source_dir/.." && pwd) base_lib_dir=${FREECAD_WEB_BASE_LIB_DIR:-"$workspace_dir/FreeCADBaseLib"} build_root=${FREECAD_WEB_BUILD_ROOT:-"$source_dir/build-web"} jobs=${FREECAD_WEB_JOBS:-8} yaml_source="$base_lib_dir/sources/src/YAML-CPP-0.8.0" eigen_source="$base_lib_dir/sources/src/Eigen-3.4.0" boost_source="$base_lib_dir/sources/src/Boost-1.83.0" zlib_source="$base_lib_dir/sources/src/zlib-1.3" xerces_source="$base_lib_dir/sources/src/Xerces-C-3.2.4" qt_source="$base_lib_dir/sources/src/Qt-5.15.13/qtbase" python_source="$base_lib_dir/sources/src/Python-3.12.3" yaml_build="$build_root/yaml-cpp" yaml_install="$build_root/yaml-install" eigen_build="$build_root/eigen" eigen_install="$build_root/eigen-install" boost_build="$build_root/boost-build" boost_install="$build_root/boost-install" zlib_build="$build_root/zlib" zlib_install="$build_root/zlib-install" xerces_build="$build_root/xerces" xerces_install="$build_root/xerces-install" qt_build="$build_root/qt-probe" qt_install="$build_root/qt-install" python_build="$build_root/python-probe" python_install="$build_root/python-install" icu_install="$build_root/icu-install" occt_install="$build_root/occt-install" icu_download="$build_root/icu-download/icu4c-68_2-src.tgz" icu_port_dir="$build_root/emscripten-cache/ports/icu" emscripten_cache="$build_root/emscripten-cache" icu_domestic_url=${FREECAD_WEB_ICU_DOMESTIC_URL:-"https://mirrors.aliyun.com/blfs/conglomeration/icu/icu4c-68_2-src.tgz"} icu_official_url=${FREECAD_WEB_ICU_OFFICIAL_URL:-"https://github.com/unicode-org/icu/releases/download/release-68-2/icu4c-68_2-src.zip"} icu_domestic_sha512="24f3b7981667b37df58118f56ae76a09b719358c1efa8d6f044d28ef8df1c722a894957acadfb3c37aee39ac845893924fc3370d6bbe09875aec8d937059f84b" icu_official_sha512="12c3db5966c234c94e7918fb8acc8bd0838edc36a620f3faa788e7ff27b06f1aa431eb117401026e3963622b9323212f444b735d5c9dd3d0b82d772a4834b993" icu_archive_format=tgz mkdir -p "$build_root" "$source_dir/../docs" "$emscripten_cache" exec > >(tee "$source_dir/../docs/web-dependency-install.log") 2>&1 printf 'ICU domestic mirror preference: %s\n' "$icu_domestic_url" printf 'ICU official fallback: %s\n' "$icu_official_url" if [[ ! -f "$yaml_source/.source-complete" || ! -f "$boost_source/.source-complete" || ! -f "$eigen_source/.source-complete" || ! -f "$zlib_source/.source-complete" || ! -f "$xerces_source/.source-complete" || ! -f "$qt_source/configure" || ! -f "$python_source/configure" ]]; then "$base_lib_dir/download_sources.sh" fi toolchain=${FREECAD_WEB_EMSCRIPTEN_TOOLCHAIN:-} if [[ -z "$toolchain" ]]; then emcc_path=$(realpath "$(command -v emcc)") toolchain="$(dirname "$emcc_path")/cmake/Modules/Platform/Emscripten.cmake" fi if [[ ! -f "$toolchain" ]]; then printf 'Emscripten toolchain not found: %s\n' "$toolchain" >&2 exit 1 fi emscripten_root=$(cd "$(dirname "$toolchain")/../../.." && pwd) emscripten_config=${FREECAD_WEB_EMSCRIPTEN_CONFIG:-"$build_root/emscripten-config.py"} if [[ -z "${FREECAD_WEB_EMSCRIPTEN_CONFIG:-}" ]] && { [[ ! -f "$emscripten_config" ]] || ! grep -Fq "CACHE = r'$emscripten_cache'" "$emscripten_config"; }; then cp "$emscripten_root/.emscripten" "$emscripten_config" sed -i "s|^FROZEN_CACHE = True.*|FROZEN_CACHE = False\nCACHE = r'$emscripten_cache'\nPORTS = r'$emscripten_cache/ports'|" \ "$emscripten_config" fi mkdir -p "$(dirname "$icu_download")" if [[ ! -f "$icu_download" ]]; then printf 'Downloading ICU from domestic mirror: %s\n' "$icu_domestic_url" if ! curl --fail --location --retry 2 --connect-timeout 10 --max-time 600 \ "$icu_domestic_url" -o "$icu_download"; then printf 'Domestic ICU mirror failed; falling back to official source: %s\n' "$icu_official_url" icu_archive_format=zip curl --fail --location --retry 2 --connect-timeout 10 --max-time 600 \ "$icu_official_url" -o "$icu_download" fi fi actual_icu_sha512=$(sha512sum "$icu_download" | awk '{print $1}') printf 'ICU source archive: %s (SHA-512 %s)\n' "$icu_download" "$actual_icu_sha512" if [[ "$actual_icu_sha512" != "$icu_domestic_sha512" && "$actual_icu_sha512" != "$icu_official_sha512" ]]; then printf 'Unexpected ICU archive SHA-512: %s\n' "$actual_icu_sha512" >&2 exit 1 fi if [[ "$actual_icu_sha512" == "$icu_official_sha512" ]]; then icu_archive_format=zip else icu_archive_format=tgz fi # Initialize the selected Emscripten cache before seeding the local port. A # changed config can clear the cache during this first compiler invocation. printf 'int main() { return 0; }\n' | \ EM_CONFIG="$emscripten_config" EM_FROZEN_CACHE= \ em++ -x c++ - -sWASM=1 -o "$build_root/emscripten-sanity.js" # Emscripten 3.1.6's ICU port hard-codes a GitHub URL. Seed its local port # cache from the verified domestic archive so the compiler never needs that # slow network path during a Web dependency preparation run. if [[ ! -f "$icu_port_dir/icu/source/common/unicode/utypes.h" ]]; then mkdir -p "$icu_port_dir" if [[ "$icu_archive_format" == zip ]]; then unzip -q "$icu_download" -d "$icu_port_dir" else tar -xzf "$icu_download" -C "$icu_port_dir" fi fi printf '%s\n' "$icu_official_url" > "$icu_port_dir/.emscripten_url" printf 'Building Emscripten ICU port from the verified local source\n' printf 'int main() { return 0; }\n' | \ EM_CONFIG="$emscripten_config" EM_FROZEN_CACHE= EM_PORTS="$emscripten_cache/ports" \ em++ -x c++ - -sUSE_ICU=1 -sWASM=1 \ -o "$build_root/icu-port-smoke.js" mkdir -p "$icu_install/include" "$icu_install/lib" rm -rf "$icu_install/include/unicode" cp -a "$emscripten_cache/sysroot/include/unicode" "$icu_install/include/" cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_common.a" "$icu_install/lib/libicuuc.a" cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_i18n.a" "$icu_install/lib/libicui18n.a" cp "$emscripten_cache/sysroot/lib/wasm32-emscripten/libicu_stubdata.a" "$icu_install/lib/libicudata.a" cmake -S "$yaml_source" -B "$yaml_build" -G Ninja \ -DCMAKE_TOOLCHAIN_FILE="$toolchain" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$yaml_install" \ -DYAML_BUILD_SHARED_LIBS=OFF \ -DYAML_CPP_BUILD_TESTS=OFF \ -DYAML_CPP_BUILD_TOOLS=OFF \ -DYAML_CPP_INSTALL=ON cmake --build "$yaml_build" --parallel "$jobs" cmake --install "$yaml_build" cmake -S "$eigen_source" -B "$eigen_build" -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$eigen_install" \ -DEIGEN_BUILD_DOC=OFF \ -DBUILD_TESTING=OFF cmake --install "$eigen_build" --component Devel boost_b2=${FREECAD_WEB_B2_EXECUTABLE:-} if [[ -z "$boost_b2" ]]; then boost_b2=$(command -v b2 || command -v bjam || true) fi if [[ -z "$boost_b2" ]]; then printf 'B2 executable not found; set FREECAD_WEB_B2_EXECUTABLE.\n' >&2 exit 1 fi boost_user_config="$build_root/boost-user-config.jam" printf 'using clang : wasm : %s ;\n' "$(command -v em++)" > "$boost_user_config" "$boost_b2" -q \ --user-config="$boost_user_config" \ --build-dir="$boost_build" \ --prefix="$boost_install" \ --layout=system -j"$jobs" \ toolset=clang-wasm variant=release link=static runtime-link=static \ threading=single cxxflags=-fexceptions linkflags=-fexceptions \ --with-program_options --with-regex --with-thread --with-date_time install if [[ ! -f "$zlib_build/Makefile" ]]; then mkdir -p "$zlib_build" cp -a "$zlib_source/." "$zlib_build/" ( cd "$zlib_build" emconfigure ./configure --static --prefix="$zlib_install" ) fi emmake make -C "$zlib_build" --jobs="$jobs" emmake make -C "$zlib_build" install # Emscripten's link-only check_function_exists probes report arbitrary symbols # as present. Xerces-C would then omit its portable replacements for the # non-standard case-insensitive string functions and fail at compile time. cmake -S "$xerces_source" -B "$xerces_build" -G Ninja \ -DCMAKE_TOOLCHAIN_FILE="$toolchain" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$xerces_install" \ -DCMAKE_INSTALL_LIBDIR=lib \ -DBUILD_SHARED_LIBS=OFF \ -Dnetwork=OFF \ -Dtranscoder=icu \ -Dmessage-loader=icu \ -Dmutex-manager=nothreads \ -DHAVE_STRICMP=0 \ -DHAVE_STRNICMP=0 \ -DICU_ROOT="$icu_install" \ -DICU_INCLUDE_DIR="$icu_install/include" \ -DICU_UC_LIBRARY="$icu_install/lib/libicuuc.a" \ -DICU_UC_LIBRARY_RELEASE="$icu_install/lib/libicuuc.a" \ -DICU_DATA_LIBRARY="$icu_install/lib/libicudata.a" \ -DICU_DATA_LIBRARY_RELEASE="$icu_install/lib/libicudata.a" cmake --build "$xerces_build" --target xerces-c --parallel "$jobs" # Installing the whole project also installs sample executables, which are # intentionally not built for the dependency package. Install only the # library, headers, and CMake package emitted by the src directory. cmake -DCMAKE_INSTALL_COMPONENT=runtime \ -P "$xerces_build/src/cmake_install.cmake" cmake -DCMAKE_INSTALL_COMPONENT=development \ -P "$xerces_build/src/cmake_install.cmake" cmake -DCMAKE_INSTALL_COMPONENT=Unspecified \ -P "$xerces_build/src/cmake_install.cmake" if [[ ! -f "$qt_build/Makefile" ]]; then mkdir -p "$qt_build" ( cd "$qt_build" "$qt_source/configure" \ -xplatform wasm-emscripten \ -opensource -confirm-license -release -static \ -no-feature-thread -nomake examples -nomake tests \ -prefix "$qt_install" ) fi make -C "$qt_build/src" --jobs="$jobs" sub-network sub-xml make -C "$qt_build/src" --jobs="$jobs" \ sub-corelib-install_subtargets \ sub-network-install_subtargets \ sub-xml-install_subtargets make -C "$qt_build/src" --jobs="$jobs" sub-rcc make -C "$qt_build/src" sub-rcc-install_subtargets make -C "$qt_build" install_mkspecs install -m 755 "$qt_build/bin/qmake" "$qt_install/bin/qmake" install -m 644 "$qt_build/bin/qt.conf" "$qt_install/bin/qt.conf" if [[ ! -f "$python_build/Makefile" ]]; then mkdir -p "$python_build" ( cd "$python_build" CONFIG_SITE="$python_source/Tools/wasm/config.site-wasm32-emscripten" \ EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \ emconfigure "$python_source/configure" -C \ --host=wasm32-unknown-emscripten \ --build=x86_64-pc-linux-gnu \ --with-emscripten-target=browser \ --with-build-python="${FREECAD_WEB_HOST_PYTHON_EXECUTABLE:-/usr/bin/python3.12}" \ --without-ensurepip ) fi EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \ make -C "$python_build" --jobs="$jobs" EM_CACHE="$emscripten_cache" EM_FROZEN_CACHE= \ make -C "$python_build" --jobs="$jobs" prefix="$python_install" install required_files=( "$yaml_install/lib/libyaml-cpp.a" "$yaml_install/lib/cmake/yaml-cpp/yaml-cpp-config.cmake" "$eigen_install/include/eigen3/signature_of_eigen3_matrix_library" "$eigen_install/include/eigen3/Eigen/src/Core/util/Macros.h" "$boost_install/include/boost/version.hpp" "$boost_install/lib/libboost_program_options.a" "$boost_install/lib/libboost_regex.a" "$boost_install/lib/libboost_thread.a" "$boost_install/lib/libboost_date_time.a" "$boost_install/lib/libboost_atomic.a" "$zlib_install/include/zlib.h" "$zlib_install/include/zconf.h" "$zlib_install/lib/libz.a" "$xerces_install/lib/libxerces-c.a" "$xerces_install/lib/cmake/XercesC/XercesCConfig.cmake" "$qt_install/lib/libQt5Core.a" "$qt_install/lib/libQt5Network.a" "$qt_install/lib/libQt5Xml.a" "$qt_install/lib/cmake/Qt5/Qt5Config.cmake" "$python_install/include/python3.12/Python.h" "$python_install/lib/libpython3.12.a" "$icu_install/include/unicode/utypes.h" "$icu_install/lib/libicuuc.a" "$icu_install/lib/libicui18n.a" "$icu_install/lib/libicudata.a" "$occt_install/include/opencascade/Standard_Version.hxx" "$occt_install/lib/libTKernel.a" "$boost_install/lib/libboost_program_options.a" \ "$boost_install/lib/libboost_regex.a" \ "$boost_install/lib/libboost_thread.a" \ "$boost_install/lib/libboost_date_time.a" \ "$boost_install/lib/libboost_atomic.a" "$occt_install/lib/cmake/opencascade/OpenCASCADEConfig.cmake" ) for required_file in "${required_files[@]}"; do if [[ ! -f "$required_file" ]]; then printf 'Missing Web dependency artifact: %s\n' "$required_file" >&2 exit 1 fi done file "$yaml_install/lib/libyaml-cpp.a" \ "$zlib_install/lib/libz.a" \ "$xerces_install/lib/libxerces-c.a" \ "$qt_install/lib/libQt5Core.a" \ "$qt_install/lib/libQt5Network.a" \ "$qt_install/lib/libQt5Xml.a" \ "$python_install/lib/libpython3.12.a" \ "$icu_install/lib/libicuuc.a" \ "$icu_install/lib/libicui18n.a" \ "$icu_install/lib/libicudata.a" \ "$occt_install/lib/libTKernel.a" printf 'Web dependency installations are ready under %s\n' "$build_root"