Vendor MsQuic with build and install support, add DeviceManager status to configurable heartbeats, and report only enabled devices. Add the local QUIC gateway, protocol coverage, real MsQuic E2E tests, process smoke tests, and updated integration documentation.
413 lines
14 KiB
Bash
Executable File
413 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: script/build_msquic.sh [options]
|
|
|
|
Build the pinned MsQuic source into:
|
|
dependency/<arch>/third_party/msquic/v<version>
|
|
|
|
Options:
|
|
--arch x86|arm Dependency architecture (default: native host)
|
|
--version VERSION Supported pinned version without leading v (default: 2.5.9)
|
|
--jobs N Parallel build jobs (default: nproc)
|
|
--clean Recreate the MsQuic build and staging directories
|
|
-h, --help Show this help
|
|
|
|
Environment:
|
|
CMVR_CMAKE Absolute CMake executable override
|
|
CMVR_MSQUIC_TOOLCHAIN_FILE CMake toolchain file for cross-compilation
|
|
CC, CXX Native compiler overrides
|
|
|
|
The first run needs network access to clone the official MsQuic tag and its
|
|
QuicTLS submodule. No sudo or system MsQuic installation is used.
|
|
EOF
|
|
}
|
|
|
|
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
repo_root="$(cd -- "${script_dir}/.." && pwd -P)"
|
|
version="2.5.9"
|
|
arch=""
|
|
jobs=""
|
|
clean_build=false
|
|
|
|
while (($# > 0)); do
|
|
case "$1" in
|
|
--arch)
|
|
[[ $# -ge 2 ]] || { echo "missing value for --arch" >&2; exit 2; }
|
|
arch="$2"
|
|
shift 2
|
|
;;
|
|
--version)
|
|
[[ $# -ge 2 ]] || { echo "missing value for --version" >&2; exit 2; }
|
|
version="${2#v}"
|
|
shift 2
|
|
;;
|
|
--jobs)
|
|
[[ $# -ge 2 ]] || { echo "missing value for --jobs" >&2; exit 2; }
|
|
jobs="$2"
|
|
shift 2
|
|
;;
|
|
--clean)
|
|
clean_build=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "unknown option: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
host_machine="$(uname -m)"
|
|
case "${host_machine}" in
|
|
x86_64|amd64)
|
|
native_arch="x86"
|
|
;;
|
|
aarch64|arm64|armv8*)
|
|
native_arch="arm"
|
|
;;
|
|
*)
|
|
echo "unsupported host architecture: ${host_machine}" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
arch="${arch:-${native_arch}}"
|
|
if [[ "${arch}" != "x86" && "${arch}" != "arm" ]]; then
|
|
echo "--arch must be x86 or arm" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "--version must use the form MAJOR.MINOR.PATCH" >&2
|
|
exit 2
|
|
fi
|
|
case "${version}" in
|
|
2.5.9)
|
|
expected_source_commit="87b53085d76bd7920d490a6f226c9999b6614d14"
|
|
;;
|
|
*)
|
|
echo "unsupported MsQuic version: ${version}" >&2
|
|
echo "add its reviewed tag and commit to script/build_msquic.sh first" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
if [[ -z "${jobs}" ]]; then
|
|
jobs="$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN || echo 2)"
|
|
fi
|
|
if [[ ! "${jobs}" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "--jobs must be a positive integer" >&2
|
|
exit 2
|
|
fi
|
|
|
|
toolchain_args=()
|
|
if [[ "${arch}" != "${native_arch}" ]]; then
|
|
if [[ -z "${CMVR_MSQUIC_TOOLCHAIN_FILE:-}" ]]; then
|
|
echo "cross-building ${arch} on ${host_machine} requires" >&2
|
|
echo "CMVR_MSQUIC_TOOLCHAIN_FILE=/absolute/path/to/toolchain.cmake" >&2
|
|
exit 2
|
|
fi
|
|
if [[ ! -f "${CMVR_MSQUIC_TOOLCHAIN_FILE}" ]]; then
|
|
echo "toolchain file does not exist: ${CMVR_MSQUIC_TOOLCHAIN_FILE}" >&2
|
|
exit 2
|
|
fi
|
|
toolchain_file="$(realpath "${CMVR_MSQUIC_TOOLCHAIN_FILE}")"
|
|
toolchain_args+=("-DCMAKE_TOOLCHAIN_FILE=${toolchain_file}")
|
|
fi
|
|
|
|
tag="v${version}"
|
|
source_dir="${repo_root}/build/third_party/msquic-src/${tag}"
|
|
build_dir="${repo_root}/build/third_party/msquic-build/${arch}-${tag}"
|
|
stage_dir="${repo_root}/build/third_party/msquic-stage/${arch}-${tag}"
|
|
stage_prefix="${stage_dir}/prefix"
|
|
install_root="${repo_root}/dependency/${arch}/third_party/msquic/${tag}"
|
|
backup_root="${install_root}.previous"
|
|
|
|
for guarded_path in \
|
|
"${source_dir}" "${build_dir}" "${stage_dir}" \
|
|
"${install_root}" "${backup_root}"; do
|
|
case "${guarded_path}" in
|
|
"${repo_root}"/build/third_party/*|\
|
|
"${repo_root}"/dependency/"${arch}"/third_party/msquic/"${tag}"|\
|
|
"${repo_root}"/dependency/"${arch}"/third_party/msquic/"${tag}".previous)
|
|
;;
|
|
*)
|
|
echo "refusing unsafe path: ${guarded_path}" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -n "${CMVR_CMAKE:-}" ]]; then
|
|
cmake_bin="$(realpath "${CMVR_CMAKE}")"
|
|
else
|
|
bundled_cmake="${repo_root}/dependency/${arch}/third_party/cmake/v3.30.3/cmake-3.30.3-linux-$(
|
|
[[ "${arch}" == "x86" ]] && echo x86_64 || echo aarch64
|
|
)/bin/cmake"
|
|
if [[ "${arch}" == "${native_arch}" && -x "${bundled_cmake}" ]]; then
|
|
cmake_bin="${bundled_cmake}"
|
|
else
|
|
cmake_bin="$(command -v cmake)"
|
|
fi
|
|
fi
|
|
[[ -x "${cmake_bin}" ]] || {
|
|
echo "CMake executable is unavailable: ${cmake_bin}" >&2
|
|
exit 2
|
|
}
|
|
|
|
git_bin="/usr/bin/git"
|
|
[[ -x "${git_bin}" ]] || git_bin="$(command -v git)"
|
|
[[ -x "${git_bin}" ]] || { echo "git is required" >&2; exit 2; }
|
|
|
|
clean_path="$(dirname "${cmake_bin}"):/usr/local/bin:/usr/bin:/bin"
|
|
export PATH="${clean_path}"
|
|
unset CONDA_PREFIX CONDA_DEFAULT_ENV CMAKE_PREFIX_PATH PKG_CONFIG_PATH \
|
|
OPENSSL_ROOT_DIR OPENSSL_DIR LD_LIBRARY_PATH LIBRARY_PATH CPATH \
|
|
C_INCLUDE_PATH CPLUS_INCLUDE_PATH CFLAGS CXXFLAGS CPPFLAGS LDFLAGS \
|
|
PERL5LIB PERL5OPT
|
|
|
|
if [[ "${arch}" == "${native_arch}" ]]; then
|
|
export CC="${CC:-/usr/bin/cc}"
|
|
export CXX="${CXX:-/usr/bin/c++}"
|
|
fi
|
|
|
|
mkdir -p "$(dirname "${source_dir}")" "$(dirname "${install_root}")"
|
|
if [[ ! -d "${source_dir}/.git" ]]; then
|
|
"${git_bin}" clone \
|
|
--branch "${tag}" \
|
|
--depth 1 \
|
|
https://github.com/microsoft/msquic.git \
|
|
"${source_dir}"
|
|
fi
|
|
|
|
source_commit="$("${git_bin}" -C "${source_dir}" rev-parse HEAD)"
|
|
tag_commit="$("${git_bin}" -C "${source_dir}" rev-list -n 1 "${tag}")"
|
|
if [[ "${source_commit}" != "${tag_commit}" ]]; then
|
|
echo "${source_dir} is not checked out at ${tag}" >&2
|
|
echo "remove that cache directory and rerun the script" >&2
|
|
exit 2
|
|
fi
|
|
if [[ "${source_commit}" != "${expected_source_commit}" ]]; then
|
|
echo "${tag} resolved to an unexpected source commit" >&2
|
|
echo "expected: ${expected_source_commit}" >&2
|
|
echo "actual: ${source_commit}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
unexpected_initialized_submodules="$(
|
|
"${git_bin}" -C "${source_dir}" submodule status |
|
|
awk '$2 != "submodules/quictls" && substr($1, 1, 1) != "-" {
|
|
print $2 " (" $1 ")"
|
|
}'
|
|
)"
|
|
if [[ -n "${unexpected_initialized_submodules}" ]]; then
|
|
echo "MsQuic source cache contains initialized non-QuicTLS submodules:" >&2
|
|
echo "${unexpected_initialized_submodules}" >&2
|
|
echo "deinitialize those submodules or use a clean source cache before building" >&2
|
|
exit 2
|
|
fi
|
|
|
|
"${git_bin}" -C "${source_dir}" submodule sync -- submodules/quictls
|
|
"${git_bin}" -C "${source_dir}" submodule update \
|
|
--init --depth 1 -- submodules/quictls
|
|
|
|
source_changes="$("${git_bin}" -C "${source_dir}" status \
|
|
--porcelain --untracked-files=all --ignore-submodules=all)"
|
|
if [[ -n "${source_changes}" ]]; then
|
|
echo "MsQuic source cache contains local changes:" >&2
|
|
echo "${source_changes}" >&2
|
|
echo "use a clean source cache before building" >&2
|
|
exit 2
|
|
fi
|
|
quictls_dir="${source_dir}/submodules/quictls"
|
|
expected_quictls_commit="$("${git_bin}" -C "${source_dir}" \
|
|
rev-parse HEAD:submodules/quictls)"
|
|
actual_quictls_commit="$("${git_bin}" -C "${quictls_dir}" rev-parse HEAD)"
|
|
quictls_changes="$("${git_bin}" -C "${quictls_dir}" status \
|
|
--porcelain --untracked-files=all)"
|
|
if [[ "${actual_quictls_commit}" != "${expected_quictls_commit}" ||
|
|
-n "${quictls_changes}" ]]; then
|
|
echo "QuicTLS source cache is not at the clean pinned commit" >&2
|
|
echo "expected: ${expected_quictls_commit}" >&2
|
|
echo "actual: ${actual_quictls_commit}" >&2
|
|
[[ -z "${quictls_changes}" ]] || echo "${quictls_changes}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "${clean_build}" == true ]]; then
|
|
"${cmake_bin}" -E remove_directory "${build_dir}"
|
|
fi
|
|
"${cmake_bin}" -E remove_directory "${stage_dir}"
|
|
"${cmake_bin}" -E make_directory "${build_dir}" "${stage_prefix}"
|
|
|
|
toolchain_fingerprint="native"
|
|
if [[ ${#toolchain_args[@]} -ne 0 ]]; then
|
|
toolchain_fingerprint="$(
|
|
sha256sum "${toolchain_file}" | awk '{print $1}'
|
|
)"
|
|
fi
|
|
build_recipe_version="5"
|
|
build_fingerprint="$(
|
|
printf '%s' \
|
|
"${source_commit}|${build_recipe_version}|${arch}|" \
|
|
"${CC:-toolchain}|${CXX:-toolchain}|" \
|
|
"${toolchain_fingerprint}|${cmake_bin}"
|
|
)"
|
|
fingerprint_file="${build_dir}/cmvr-msquic-build.fingerprint"
|
|
if [[ -f "${build_dir}/CMakeCache.txt" ]]; then
|
|
if [[ ! -f "${fingerprint_file}" ]]; then
|
|
echo "existing MsQuic build cache predates compiler fingerprinting" >&2
|
|
echo "rerun with --clean" >&2
|
|
exit 2
|
|
fi
|
|
existing_fingerprint="$(<"${fingerprint_file}")"
|
|
if [[ "${existing_fingerprint}" != "${build_fingerprint}" ]]; then
|
|
echo "MsQuic compiler/toolchain fingerprint changed" >&2
|
|
echo "rerun with --clean" >&2
|
|
exit 2
|
|
fi
|
|
fi
|
|
printf '%s\n' "${build_fingerprint}" >"${fingerprint_file}"
|
|
|
|
prefix_map_flags="\
|
|
-ffile-prefix-map=${repo_root}=. -fmacro-prefix-map=${repo_root}=."
|
|
"${cmake_bin}" \
|
|
-S "${source_dir}" \
|
|
-B "${build_dir}" \
|
|
-G "Unix Makefiles" \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
"-DCMAKE_INSTALL_PREFIX=${stage_prefix}" \
|
|
"-DCMAKE_MODULE_PATH=${repo_root}/cmake/msquic" \
|
|
"-DCMVR_MSQUIC_PROCESSOR_COUNT=${jobs}" \
|
|
"-DCMAKE_C_FLAGS=${prefix_map_flags}" \
|
|
"-DCMAKE_CXX_FLAGS=${prefix_map_flags}" \
|
|
-DQUIC_BUILD_SHARED=ON \
|
|
-DQUIC_BUILD_TEST=OFF \
|
|
-DQUIC_BUILD_TOOLS=OFF \
|
|
-DQUIC_BUILD_PERF=OFF \
|
|
-DQUIC_ENABLE_LOGGING=OFF \
|
|
-DQUIC_TLS_LIB=quictls \
|
|
-DQUIC_USE_SYSTEM_LIBCRYPTO=OFF \
|
|
-DNUMA:STRING=FALSE \
|
|
"${toolchain_args[@]}"
|
|
|
|
# QuicTLS derives MODULESDIR from its temporary --prefix and compiles that
|
|
# absolute path into libcrypto. Dynamic providers are disabled above
|
|
# (no-shared, no-legacy and no-fips), so keep the unused fallback path stable
|
|
# instead of leaking the build workspace into the shipped MsQuic runtime.
|
|
openssl_makefile_target="_deps/opensslquic-build/submodules/quictls/Makefile"
|
|
openssl_build_rules="_deps/opensslquic-build/CMakeFiles/OpenSSL_Target.dir/build.make"
|
|
make_bin="$(command -v make)"
|
|
[[ -x "${make_bin}" ]] || {
|
|
echo "GNU Make is required to configure the bundled QuicTLS source" >&2
|
|
exit 2
|
|
}
|
|
"${make_bin}" -C "${build_dir}" -f "${openssl_build_rules}" \
|
|
"${openssl_makefile_target}"
|
|
openssl_makefile="${build_dir}/${openssl_makefile_target}"
|
|
test -f "${openssl_makefile}"
|
|
if ! grep -Fx 'MODULESDIR=/usr/lib/ssl/ossl-modules' \
|
|
"${openssl_makefile}" >/dev/null; then
|
|
sed -i -E \
|
|
's|^MODULESDIR=.*$|MODULESDIR=/usr/lib/ssl/ossl-modules|' \
|
|
"${openssl_makefile}"
|
|
fi
|
|
grep -Fx 'MODULESDIR=/usr/lib/ssl/ossl-modules' \
|
|
"${openssl_makefile}" >/dev/null
|
|
|
|
"${cmake_bin}" --build "${build_dir}" --parallel "${jobs}"
|
|
"${cmake_bin}" --install "${build_dir}"
|
|
|
|
# Keep only the public Linux API and shared runtime that cmvr-es consumes.
|
|
find "${stage_prefix}/include" -maxdepth 1 -type f \
|
|
! -name msquic.h \
|
|
! -name msquic_posix.h \
|
|
! -name quic_sal_stub.h \
|
|
-delete
|
|
"${cmake_bin}" -E rm -f "${stage_prefix}/lib/libmsquic_platform.a"
|
|
"${cmake_bin}" -E remove_directory "${stage_prefix}/share"
|
|
"${cmake_bin}" -E make_directory "${stage_prefix}/share/licenses/msquic"
|
|
"${cmake_bin}" -E copy "${source_dir}/LICENSE" \
|
|
"${stage_prefix}/share/licenses/msquic/LICENSE"
|
|
"${cmake_bin}" -E copy "${source_dir}/THIRD-PARTY-NOTICES" \
|
|
"${stage_prefix}/share/licenses/msquic/THIRD-PARTY-NOTICES"
|
|
|
|
cat >"${stage_prefix}/BUILD-INFO.txt" <<EOF
|
|
msquic_version=${version}
|
|
source_tag=${tag}
|
|
source_commit=${source_commit}
|
|
dependency_arch=${arch}
|
|
tls_backend=quictls-static
|
|
dynamic_tls_providers=false
|
|
system_libcrypto=false
|
|
numa=false
|
|
EOF
|
|
|
|
test -f "${stage_prefix}/include/msquic.h"
|
|
test -f "${stage_prefix}/include/msquic_posix.h"
|
|
test -f "${stage_prefix}/include/quic_sal_stub.h"
|
|
test -f "${stage_prefix}/lib/libmsquic.so.${version}"
|
|
test -L "${stage_prefix}/lib/libmsquic.so"
|
|
|
|
if command -v readelf >/dev/null 2>&1; then
|
|
if readelf -d "${stage_prefix}/lib/libmsquic.so.${version}" |
|
|
grep -E 'NEEDED.*lib(ssl|crypto|numa)' >/dev/null; then
|
|
echo "MsQuic unexpectedly depends on system TLS or NUMA libraries" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
if grep -R -F --exclude='libmsquic.so*' \
|
|
"${repo_root}" "${stage_prefix}" >/dev/null 2>&1; then
|
|
echo "staged MsQuic metadata contains a non-relocatable workspace path" >&2
|
|
exit 1
|
|
fi
|
|
if command -v strings >/dev/null 2>&1 &&
|
|
strings "${stage_prefix}/lib/libmsquic.so.${version}" |
|
|
grep -F "${repo_root}" >/dev/null; then
|
|
echo "staged MsQuic runtime contains a non-relocatable workspace path" >&2
|
|
exit 1
|
|
fi
|
|
|
|
swap_in_progress=false
|
|
restore_install_on_exit() {
|
|
if [[ "${swap_in_progress}" != true ]]; then
|
|
return
|
|
fi
|
|
if [[ -e "${install_root}" ]]; then
|
|
"${cmake_bin}" -E remove_directory "${backup_root}" || true
|
|
elif [[ -e "${backup_root}" ]]; then
|
|
mv "${backup_root}" "${install_root}" || true
|
|
fi
|
|
}
|
|
trap restore_install_on_exit EXIT
|
|
|
|
if [[ -e "${backup_root}" ]]; then
|
|
if [[ ! -e "${install_root}" ]]; then
|
|
mv "${backup_root}" "${install_root}"
|
|
else
|
|
echo "stale MsQuic backup requires manual inspection:" >&2
|
|
echo " ${backup_root}" >&2
|
|
exit 2
|
|
fi
|
|
fi
|
|
if [[ -e "${install_root}" ]]; then
|
|
swap_in_progress=true
|
|
mv "${install_root}" "${backup_root}"
|
|
fi
|
|
mv "${stage_prefix}" "${install_root}"
|
|
swap_in_progress=false
|
|
"${cmake_bin}" -E remove_directory "${backup_root}"
|
|
trap - EXIT
|
|
|
|
echo "MsQuic ${tag} installed to:"
|
|
echo " ${install_root}"
|
|
echo "Configure cmvr-es with:"
|
|
echo " -DCMVR_ENABLE_MSQUIC_BACKEND=ON -DCMVR_REQUIRE_MSQUIC=ON"
|