Files
FreeCADWasmDev/tools/prepare_web_dependencies.sh
wangdequan b5a086fda5
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled
FreeCAD WebAssembly development snapshot
2026-08-20 07:00:53 +08:00

308 lines
13 KiB
Bash
Executable File

#!/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"