From 9b21c1369c19898c0330d72551241e5573245ade Mon Sep 17 00:00:00 2001 From: Aleksandr Shabelnikov Date: Mon, 20 Jul 2026 10:40:28 +0200 Subject: [PATCH] =?UTF-8?q?build:=20PR0=20=E2=80=94=20production=20native?= =?UTF-8?q?=20build=20config,=20C++23,=20dead=20code=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR0 (Production-Like Native Build) — align the native build system with production requirements and remove verified dead code. C++23 (full stack): - FptnLib/CMakeLists.txt: -std=c++23, cxx_std_23. - project.yml + Tools/FptnCLI/project.yml: CLANG_CXX_LANGUAGE_STANDARD c++23. - fptn submodule pinned to 7f52909 (C++23 + YAFF ranges patch). YAFF fix: C++23 libc++ ranges probes .begin() eagerly via input_or_output_iterator concept; patch defers BaseArray::begin/end to out-of-line definitions after ArrayIterator is fully defined. Dead code removal (verified zero references): - Remove -DO3 (preprocessor macro, not optimization flag). - Remove unconditional -DNDEBUG (now Debug-only; assertions active). - Remove all WSP_GGML_* defines (vestigial whisper project). - Remove -framework Accelerate/Metal/MetalKit (zero references). - Remove duplicate -lssl/-lcrypto (OpenSSL:: targets = BoringSSL). Size-oriented release flags: - MinSizeRel/Release: -Oz -ffunction-sections -fdata-sections with -Wl,-dead_strip. Build configuration mapping: - build_fptn_lib.sh: Debug→Debug, Release/Measurement→MinSizeRel. - Config-specific build dirs + build manifest for stale detection. Swift/C++ interop: - SWIFT_NONCOPYABLE on WebsocketSwiftBridge and SwiftApiClient. AGENTS.md: updated stale architecture docs. Tests: 22/22 passed. Debug build succeeded. --- .gitignore | 1 + AGENTS.md | 23 ++-- FptnLib/CMakeLists.txt | 45 ++++--- FptnLib/conanfile.py | 9 +- FptnLib/fptn | 2 +- FptnLib/src/https/SwiftApiClient.h | 5 +- .../websocket/WrapperWebsocketClientBridge.h | 5 +- Tools/FptnCLI/project.yml | 2 +- build_fptn_lib.sh | 127 +++++++++++++++--- project.yml | 2 +- 10 files changed, 157 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index 4093976..3df3835 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,4 @@ FptnLib/build* .env.local.bak.* .gh_secrets.bak.* secrets/* +**/Cpp/fptn_native_lib.build-manifest.json diff --git a/AGENTS.md b/AGENTS.md index 7008964..6b932aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t ## Project Overview -FptnVPN is an iOS VPN client app that connects to FPTN servers. It uses a pre-built C++ native framework (`fptn_native_lib.framework`) for HTTPS and WebSocket communication, wrapped through an Objective-C++ bridge layer. +FptnVPN is an iOS VPN client app that connects to FPTN servers. It uses a pre-built C++ native framework (`fptn_native_lib.framework`) for HTTPS and WebSocket communication, accessed via Swift C++ interop (C++23, `SWIFT_OBJC_INTEROP_MODE: objcxx`). ## Build @@ -57,8 +57,8 @@ Open `FptnVPN.xcodeproj` in Xcode. Tests are in `FptnVPNTests` (Swift Testing fr ### App Targets -- **FptnVPN** — Main SwiftUI app. Bundle ID: `org.fptn.FptnVPN` -- **FptnVPNTunnel** — `NEAppProxyProvider` network extension. Bundle ID: `org.fptn.FptnVPN.FptnVPNTunnel`. Currently a stub — VPN traffic is handled in the main app via WebSocket. +- **FptnVPN** — Main SwiftUI app. Bundle ID: `net.mrmidi.FptnVPN` +- **FptnVPNTunnel** — `NEPacketTunnelProvider` network extension. Bundle ID: `net.mrmidi.FptnVPN.FptnVPNTunnel`. Handles VPN traffic via WebSocket through the native C++ bridge. - **FptnLib** — C++ shared library (`fptn_native_lib.framework`). Built with CMake/Conan; not a Swift package. ### Authentication & Token Format @@ -75,15 +75,16 @@ Users receive a token from `@fptn_bot` (Telegram). Token format: `fptn: #include #include +#include namespace fptn::protocol::https { class ApiClient; } -class SwiftApiClient { +// PR0: SWIFT_NONCOPYABLE makes Swift import this as ~Copyable, +// removing the implicitly-unwrapped-optional workaround in Swift wrappers. +class SWIFT_NONCOPYABLE SwiftApiClient { public: struct Response { std::string body; diff --git a/FptnLib/src/websocket/WrapperWebsocketClientBridge.h b/FptnLib/src/websocket/WrapperWebsocketClientBridge.h index 2d8e396..e6c3b76 100644 --- a/FptnLib/src/websocket/WrapperWebsocketClientBridge.h +++ b/FptnLib/src/websocket/WrapperWebsocketClientBridge.h @@ -10,6 +10,7 @@ Distributed under the MIT License (https://opensource.org/licenses/MIT) #include #include #include +#include // Forward declaration of internal wrapper implementation struct WebsocketClientWrapper; @@ -30,7 +31,9 @@ struct WebsocketClientBridgeStatus { bool in_packet_callback; }; -class WebsocketSwiftBridge { +// PR0: SWIFT_NONCOPYABLE makes Swift import this as ~Copyable, +// removing the implicitly-unwrapped-optional workaround in Swift wrappers. +class SWIFT_NONCOPYABLE WebsocketSwiftBridge { public: // Callbacks using IPPacketCallback = void (*)(const uint8_t* packet_data, uint32_t length, void* context); diff --git a/Tools/FptnCLI/project.yml b/Tools/FptnCLI/project.yml index 11ce364..6c60d62 100644 --- a/Tools/FptnCLI/project.yml +++ b/Tools/FptnCLI/project.yml @@ -9,7 +9,7 @@ settings: base: SWIFT_CXX_INTEROPERABILITY_MODE: default SWIFT_OBJC_INTEROP_MODE: objcxx - CLANG_CXX_LANGUAGE_STANDARD: c++20 + CLANG_CXX_LANGUAGE_STANDARD: c++23 configs: Debug: debug diff --git a/build_fptn_lib.sh b/build_fptn_lib.sh index 80656b4..31f3d09 100755 --- a/build_fptn_lib.sh +++ b/build_fptn_lib.sh @@ -44,6 +44,30 @@ resolve_default_target() { TARGET="${1:-$(resolve_default_target)}" +# PR0: map Xcode configuration to CMake build type. +# Debug → Debug, Release/Measurement → MinSizeRel. +# Absent $CONFIGURATION (manual invocation) defaults to Debug. +# Unknown named configurations fail to prevent silent misbuilds. +resolve_build_type() { + case "${CONFIGURATION:-}" in + "") + echo "Debug" + ;; + Debug) + echo "Debug" + ;; + Release|Measurement) + echo "MinSizeRel" + ;; + *) + echo "error: unknown CONFIGURATION '${CONFIGURATION}'. Expected Debug, Release, or Measurement." >&2 + exit 1 + ;; + esac +} + +BUILD_TYPE="$(resolve_build_type)" + resolve_framework_binary() { local framework_path="$1" if [ -f "${framework_path}/Versions/1.0.0/fptn_native_lib" ]; then @@ -162,7 +186,7 @@ case "$TARGET" in ;; ios|ios-device) HOST_PROFILE="conan-device-profile" - OUTPUT_DIR="build-ios" + OUTPUT_DIR="build-ios-${BUILD_TYPE}" DEST_DIR="${ROOT_DIR}/FptnVPN/Cpp" SECONDARY_DEST_DIR="" MIN_PLATFORM_VERSION="17.0" @@ -170,7 +194,7 @@ case "$TARGET" in ;; ios-simulator) HOST_PROFILE="conan-simulator-profile" - OUTPUT_DIR="build-simulator" + OUTPUT_DIR="build-simulator-${BUILD_TYPE}" DEST_DIR="${ROOT_DIR}/FptnVPN/Cpp" SECONDARY_DEST_DIR="" MIN_PLATFORM_VERSION="17.0" @@ -178,7 +202,7 @@ case "$TARGET" in ;; tvos|tvos-device) HOST_PROFILE="conan-tvos-profile" - OUTPUT_DIR="build-tvos" + OUTPUT_DIR="build-tvos-${BUILD_TYPE}" DEST_DIR="${ROOT_DIR}/Fptn-tvOS/Cpp" SECONDARY_DEST_DIR="${ROOT_DIR}/Fptn-tvOS-Tunnel/Cpp" MIN_PLATFORM_VERSION="15.6" @@ -186,7 +210,7 @@ case "$TARGET" in ;; tvos-simulator) HOST_PROFILE="conan-tvos-simulator-profile" - OUTPUT_DIR="build-tvos-simulator" + OUTPUT_DIR="build-tvos-simulator-${BUILD_TYPE}" DEST_DIR="${ROOT_DIR}/Fptn-tvOS/Cpp" SECONDARY_DEST_DIR="${ROOT_DIR}/Fptn-tvOS-Tunnel/Cpp" MIN_PLATFORM_VERSION="15.6" @@ -194,7 +218,7 @@ case "$TARGET" in ;; macos) HOST_PROFILE="conan-macos-profile" - OUTPUT_DIR="build-macos" + OUTPUT_DIR="build-macos-${BUILD_TYPE}" DEST_DIR="${ROOT_DIR}/Fptn-macOS/Cpp" SECONDARY_DEST_DIR="" MIN_PLATFORM_VERSION="" @@ -216,9 +240,40 @@ if [ "${FPTN_NATIVE_BUILD_IF_MISSING:-0}" = "1" ]; then macos) EXPECTED_PLATFORM="MACOS" ;; esac + # PR0: validate full cache identity via the build manifest so a + # stale framework is never silently reused. Checks configuration, + # fptn commit, wrapper source hash, and build-file hash. + manifest_matches() { + local manifest_path="$1/fptn_native_lib.build-manifest.json" + [ -f "$manifest_path" ] || return 1 + + local current_fptn_commit current_wrapper_hash current_build_hash current_compiler_id + current_fptn_commit="$(git -C "${LIB_DIR}/fptn" rev-parse HEAD 2>/dev/null || echo unknown)" + current_wrapper_hash="$(find "${LIB_DIR}/src" -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -print0 2>/dev/null | sort -z | xargs -0 shasum -a 256 2>/dev/null | shasum -a 256 | awk '{print $1}')" + current_build_hash="$( + { + printf '%s\n' \ + "${SCRIPT_PATH}" \ + "${LIB_DIR}/CMakeLists.txt" \ + "${LIB_DIR}/conanfile.py" \ + "${LIB_DIR}/Info.plist.in" + find "${LIB_DIR}" -maxdepth 1 -type f -name 'conan-*-profile' -print + } | sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | awk '{print $1}' + )" + current_compiler_id="$(xcrun clang++ --version 2>/dev/null | head -1 || echo unknown)" + + grep -q "\"configuration\": \"${BUILD_TYPE}\"" "$manifest_path" 2>/dev/null || return 1 + grep -q "\"fptn_commit\": \"${current_fptn_commit}\"" "$manifest_path" 2>/dev/null || return 1 + grep -q "\"wrapper_hash\": \"${current_wrapper_hash}\"" "$manifest_path" 2>/dev/null || return 1 + grep -q "\"build_hash\": \"${current_build_hash}\"" "$manifest_path" 2>/dev/null || return 1 + grep -Fq "\"compiler\": \"${current_compiler_id}\"" "$manifest_path" 2>/dev/null || return 1 + return 0 + } + if framework_matches_target "${DEST_DIR}/fptn_native_lib.framework" "$EXPECTED_PLATFORM" && + manifest_matches "$DEST_DIR" && { [ -z "$SECONDARY_DEST_DIR" ] || framework_matches_target "${SECONDARY_DEST_DIR}/fptn_native_lib.framework" "$EXPECTED_PLATFORM"; }; then - echo "fptn_native_lib already built for ${TARGET}; skipping native build." + echo "fptn_native_lib already built for ${TARGET} (${BUILD_TYPE}); skipping native build." copy_existing_dsym_to_archive_products exit 0 fi @@ -228,27 +283,27 @@ cd "$LIB_DIR" if [ "$TARGET" = "macos" ]; then # ── arm64 slice ────────────────────────────────────────────────────────── - ARM64_DIR="build-macos" - echo "Building arm64 slice..." - conan install . --profile:host="conan-macos-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$ARM64_DIR" + ARM64_DIR="build-macos-${BUILD_TYPE}" + echo "Building arm64 slice (${BUILD_TYPE})..." + conan install . --profile:host="conan-macos-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$ARM64_DIR" -s build_type="$BUILD_TYPE" cd "$ARM64_DIR" - cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/Debug/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE=Debug \ + cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_OSX_ARCHITECTURES=arm64 rm -rf fptn_native_lib.framework fptn_native_lib.framework.dSYM - cmake --build . --config Debug + cmake --build . --config "$BUILD_TYPE" cd "$LIB_DIR" # ── x86_64 slice ───────────────────────────────────────────────────────── - X86_DIR="build-macos-x86_64" - echo "Building x86_64 slice..." - conan install . --profile:host="conan-macos-x86_64-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$X86_DIR" + X86_DIR="build-macos-x86_64-${BUILD_TYPE}" + echo "Building x86_64 slice (${BUILD_TYPE})..." + conan install . --profile:host="conan-macos-x86_64-profile" --profile:build=conan-macos-profile --build=missing --output-folder="$X86_DIR" -s build_type="$BUILD_TYPE" cd "$X86_DIR" - cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/Debug/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE=Debug \ + cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_OSX_ARCHITECTURES=x86_64 rm -rf fptn_native_lib.framework fptn_native_lib.framework.dSYM - cmake --build . --config Debug + cmake --build . --config "$BUILD_TYPE" cd "$LIB_DIR" # ── Combine into universal binary with lipo ─────────────────────────────── @@ -299,12 +354,12 @@ if [ "$TARGET" = "macos" ]; then exit 0 fi -conan install . --profile:host="$HOST_PROFILE" --profile:build=conan-macos-profile --build=missing --output-folder="$OUTPUT_DIR" +conan install . --profile:host="$HOST_PROFILE" --profile:build=conan-macos-profile --build=missing --output-folder="$OUTPUT_DIR" -s build_type="$BUILD_TYPE" cd "$OUTPUT_DIR" -cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/Debug/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug +cmake .. -DCMAKE_TOOLCHAIN_FILE=./build/${BUILD_TYPE}/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="$BUILD_TYPE" rm -rf fptn_native_lib.framework fptn_native_lib.framework.dSYM -cmake --build . --config Debug +cmake --build . --config "$BUILD_TYPE" generate_framework_dsym "fptn_native_lib.framework" if [ "$TARGET" = "ios-device" ] || [ "$TARGET" = "ios" ]; then @@ -327,6 +382,36 @@ copy_framework_to_dest() { rm -rf "${destination}/fptn_native_lib.framework.dSYM" + # PR0: write the build manifest BESIDE the framework (not inside it) + # BEFORE codesign, so the signature is not invalidated. The manifest + # records full cache identity: configuration, fptn commit, wrapper + # source hash, build-file hash, and compiler identity. + local fptn_commit wrapper_hash build_hash compiler_id + fptn_commit="$(git -C "${LIB_DIR}/fptn" rev-parse HEAD 2>/dev/null || echo unknown)" + wrapper_hash="$(find "${LIB_DIR}/src" -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -print0 2>/dev/null | sort -z | xargs -0 shasum -a 256 2>/dev/null | shasum -a 256 | awk '{print $1}')" + build_hash="$( + { + printf '%s\n' \ + "${SCRIPT_PATH}" \ + "${LIB_DIR}/CMakeLists.txt" \ + "${LIB_DIR}/conanfile.py" \ + "${LIB_DIR}/Info.plist.in" + find "${LIB_DIR}" -maxdepth 1 -type f -name 'conan-*-profile' -print + } | sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | awk '{print $1}' + )" + compiler_id="$(xcrun clang++ --version 2>/dev/null | head -1 || echo unknown)" + cat > "${destination}/fptn_native_lib.build-manifest.json" <