From fa72d8de43c73124171730274d2ff31fe7e072e3 Mon Sep 17 00:00:00 2001 From: spectrum Date: Sat, 25 Apr 2026 23:03:38 +0300 Subject: [PATCH] build: make conan tvOS pipeline build build: make tvOS awg prebuilt explicit docs: make Apple TV build manual portable --- apple-tv-build.md | 340 +++++++++++++++++++++ client/CMakeLists.txt | 48 +-- client/cmake/3rdparty.cmake | 11 +- client/cmake/QSimpleCrypto.cmake | 3 +- client/cmake/ios.cmake | 3 +- client/ios/networkextension/CMakeLists.txt | 3 +- client/platforms/ios/tvos_cgo_stubs.c | 6 + cmake/platform_settings.cmake | 12 +- conanfile.py | 9 +- recipes/awg-apple/conanfile.py | 104 ++++++- recipes/libssh/conanfile.py | 2 + 11 files changed, 498 insertions(+), 43 deletions(-) create mode 100644 apple-tv-build.md create mode 100644 client/platforms/ios/tvos_cgo_stubs.c diff --git a/apple-tv-build.md b/apple-tv-build.md new file mode 100644 index 000000000..e95f58b76 --- /dev/null +++ b/apple-tv-build.md @@ -0,0 +1,340 @@ +# AmneziaVPN Apple TV Build + +This document describes how to build the current branch for Apple TV from the repository root. + +The pipeline is: + +1. Use a separately built static Qt 6.9.2 for `tvOS`. +2. Let Conan build/provide C/C++ dependencies. +3. Generate an Xcode project with `qt-cmake`. +4. Build the `.app` and embedded Network Extension with `xcodebuild`. + +Important: + +- Run the project commands from the repository root. +- This is a device build for `appletvos`, not a simulator build. +- `xcodebuild build` produces `.app`. +- `.ipa` is produced later via `archive` and `-exportArchive`. +- The current tvOS Network Extension scope is WireGuard-only. +- The temporary tvOS WireGuard bridge prebuilt is opt-in. The Conan recipe does not contain machine-specific fallback paths. +- Do not initialize or update submodules just for this build. If a clean checkout has empty `client/3rd` folders, pass `AMNEZIA_THIRDPARTY_ROOT` to an already initialized read-only `client/3rd` tree. + +## 1. Environment + +Set these paths for your machine: + +```bash +export REPO_ROOT="$PWD" +export QT_DESKTOP_PREFIX="$HOME/Qt/6.9.2/macos" +export QT_TVOS_SRC="$HOME/Qt_tv/qt-6.9.2-tvos-src" +export QT_TVOS_PREFIX="$HOME/Qt_tv/6.9.2/tvos-device" +export BUILD_DIR="$REPO_ROOT/build-tvos-device-conan" +``` + +If this checkout does not have initialized `client/3rd` sources, point CMake at an initialized tree: + +```bash +export AMNEZIA_THIRDPARTY_ROOT="/path/to/initialized/amnezia/client/3rd" +``` + +If you are using a temporary prebuilt tvOS WireGuard bridge, point Conan at it explicitly: + +```bash +export AMNEZIA_TVOS_AWG_PREBUILT_DIR="/path/to/WireGuardKitGo-appletvos" +export AMNEZIA_TVOS_AWG_VERSION_HEADER_DIR="/path/to/directory/with/wireguard-go-version.h" +``` + +`AMNEZIA_TVOS_AWG_PREBUILT_DIR` must contain `libwg-go.a`. + +`AMNEZIA_TVOS_AWG_VERSION_HEADER_DIR` is optional when `wireguard-go-version.h` lives in the same directory as `libwg-go.a`. + +If the env vars are not set, the recipe uses the normal source build path. Rebuilding and publishing the tvOS WireGuard bridge through the registry is a separate task. + +## 2. Required Local Tools + +Conan must be available: + +```bash +uv tool install conan +export PATH="$HOME/.local/bin:$PATH" +conan --version +``` + +Validated version: + +```text +Conan version 2.27.1 +``` + +The build uses Xcode's AppleTVOS SDK: + +```bash +xcrun --sdk appletvos --show-sdk-path +``` + +## 3. Prepare Qt Sources + +Do not edit the installed Qt sources in place. Copy them into a separate tvOS fork: + +```bash +mkdir -p "$HOME/Qt_tv" +rsync -a "$HOME/Qt/6.9.2/Src/" "$QT_TVOS_SRC/" +``` + +Recommended for reproducibility: + +```bash +cd "$QT_TVOS_SRC" +git init +git add . +git commit -m "Qt 6.9.2 source snapshot" +``` + +## 4. Apply the Qt tvOS Patchset + +Apply the local Qt tvOS patchset to `$QT_TVOS_SRC`. + +If you need to recreate the patchset from a fresh copy, compare these files against `$HOME/Qt/6.9.2/Src` and reapply the same changes: + +- `qtbase/cmake/QtBaseGlobalTargets.cmake` +- `qtbase/cmake/QtBaseHelpers.cmake` +- `qtbase/cmake/QtBuildPathsHelpers.cmake` +- `qtbase/cmake/QtMkspecHelpers.cmake` +- `qtbase/cmake/QtConfig.cmake.in` +- `qtbase/mkspecs/unsupported/macx-tvos-clang/qplatformdefs.h` +- `qtbase/src/corelib/CMakeLists.txt` +- `qtbase/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm` +- `qtbase/src/gui/CMakeLists.txt` +- `qtbase/src/widgets/CMakeLists.txt` +- `qtbase/src/network/kernel/qnetworkproxy_darwin.cpp` +- `qtbase/src/testlib/qtestcrashhandler.cpp` +- `qtbase/src/plugins/platforms/ios/qiosapplicationdelegate.mm` +- `qtbase/src/plugins/platforms/ios/qiosscreen.mm` +- `qtbase/src/plugins/platforms/ios/qiostheme.mm` +- `qtbase/src/plugins/platforms/ios/quiview.mm` +- `qtbase/src/plugins/platforms/ios/qiosclipboard.mm` + +Recommended after patching: + +```bash +git -C "$QT_TVOS_SRC" diff > "$HOME/Qt_tv/qt-6.9.2-tvos.patch" +``` + +Do not use `QT_APPLE_SDK=appletvos`. The working path is `CMAKE_SYSTEM_NAME=tvOS` with `CMAKE_OSX_SYSROOT=appletvos`. + +## 5. Build Qt 6.9.2 for Apple TV + +Only the modules required by this project are built. + +```bash +mkdir -p /private/tmp/qt6.9.2-tvos-device-build +cd /private/tmp/qt6.9.2-tvos-device-build + +"$QT_TVOS_SRC/configure" \ + -release -static -appstore-compliant \ + -nomake tests -nomake examples \ + -submodules qtbase,qtdeclarative,qtshadertools,qtremoteobjects,qtsvg,qt5compat,qttools \ + -qt-host-path "$QT_DESKTOP_PREFIX" \ + -prefix "$QT_TVOS_PREFIX" \ + -- \ + -G Ninja \ + -DQT_QMAKE_TARGET_MKSPEC=macx-tvos-clang \ + -DCMAKE_SYSTEM_NAME=tvOS \ + -DCMAKE_OSX_SYSROOT=appletvos \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 \ + -DBUILD_SHARED_LIBS=OFF \ + -DQT_NO_APPLE_SDK_MAX_VERSION_CHECK=ON + +cmake --build . --parallel 8 +cmake --install . +``` + +Sanity checks: + +```bash +"$QT_TVOS_PREFIX/bin/qt-cmake" --version +"$QT_TVOS_PREFIX/bin/qmake" -query QMAKE_XSPEC +``` + +Expected `QMAKE_XSPEC`: + +```text +macx-tvos-clang +``` + +Return to the repository root after building Qt: + +```bash +cd "$REPO_ROOT" +``` + +## 6. Conan Dependency Behavior + +For `CMAKE_SYSTEM_NAME=tvOS`, the project-level Conan graph is intentionally reduced: + +- included: `awg-apple/2.0.1` +- included: `libssh/0.11.3@amnezia` +- included: `openssl/3.6.1` with `no_apps=True` +- excluded: `openvpnadapter` +- excluded: `hev-socks5-tunnel` + +This keeps the current Apple TV target in the same practical scope as before: app plus WireGuard-only Network Extension. + +`libssh` is built with `WITH_EXEC=OFF` on tvOS because tvOS does not provide `fork()` or `execv()`. + +## 7. Configure the Project + +From the repository root: + +```bash +cd "$REPO_ROOT" + +"$QT_TVOS_PREFIX/bin/qt-cmake" \ + -B"$BUILD_DIR" \ + -GXcode \ + -DQT_HOST_PATH="$QT_DESKTOP_PREFIX" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_SYSTEM_NAME=tvOS \ + -DCMAKE_OSX_SYSROOT=appletvos +``` + +If you need to provide an external initialized `client/3rd` tree: + +```bash +"$QT_TVOS_PREFIX/bin/qt-cmake" \ + -B"$BUILD_DIR" \ + -GXcode \ + -DQT_HOST_PATH="$QT_DESKTOP_PREFIX" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_SYSTEM_NAME=tvOS \ + -DCMAKE_OSX_SYSROOT=appletvos \ + -DAMNEZIA_THIRDPARTY_ROOT="$AMNEZIA_THIRDPARTY_ROOT" +``` + +Expected non-fatal configure warnings: + +```text +Warning: plug-in QIOSIntegrationPlugin is not known to the current Qt installation. +Warning: plug-in QJpegPlugin is not known to the current Qt installation. +... +``` + +In this repo those warnings are tolerated because `client/cmake/ios.cmake` also links the static plugin targets explicitly when available. + +## 8. Build the Apple TV App + +```bash +xcodebuild -quiet \ + -project "$BUILD_DIR/AmneziaVPN.xcodeproj" \ + -scheme AmneziaVPN \ + -configuration RelWithDebInfo \ + -sdk appletvos \ + CODE_SIGNING_ALLOWED=NO \ + build +``` + +Outputs: + +- `$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app` +- `$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex` + +Verification: + +```bash +file "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/AmneziaVPN" +file "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/AmneziaVPNNetworkExtension" +lipo -info "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/AmneziaVPN" +lipo -info "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/AmneziaVPNNetworkExtension" +``` + +Expected: + +```text +Mach-O 64-bit executable arm64 +Non-fat file: ... is architecture: arm64 +``` + +Useful plist checks: + +```bash +plutil -p "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/Info.plist" | rg 'CFBundleIdentifier|DTPlatformName|UIDeviceFamily|MinimumOSVersion' -C 1 +plutil -p "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/Info.plist" | rg 'CFBundleIdentifier|NSExtension|DTPlatformName|MinimumOSVersion' -C 1 +``` + +Expected: + +- `DTPlatformName => appletvos` +- `UIDeviceFamily => 3` +- `MinimumOSVersion => 17.0` +- extension point `com.apple.networkextension.packet-tunnel` + +## 9. `.app` vs `.ipa` + +This is the normal sequence: + +1. `xcodebuild build` -> `.app` +2. `xcodebuild archive` -> `.xcarchive` +3. `xcodebuild -exportArchive` -> `.ipa` + +So seeing `.app` after a successful `build` is correct. + +## 10. Optional Archive and Export + +The commands below are the next step for packaging, but signing and provisioning must be configured first. + +Archive: + +```bash +xcodebuild \ + -project "$BUILD_DIR/AmneziaVPN.xcodeproj" \ + -scheme AmneziaVPN \ + -configuration RelWithDebInfo \ + -sdk appletvos \ + -archivePath "$BUILD_DIR/AmneziaVPN-tvos.xcarchive" \ + archive +``` + +Export: + +```bash +xcodebuild -exportArchive \ + -archivePath "$BUILD_DIR/AmneziaVPN-tvos.xcarchive" \ + -exportPath "$BUILD_DIR/export-tvos" \ + -exportOptionsPlist /absolute/path/to/ExportOptions.plist +``` + +The resulting `.ipa` should appear under: + +```text +$BUILD_DIR/export-tvos +``` + +## 11. Known Non-Fatal Warnings + +The validated `xcodebuild` still prints warnings that do not break the build: + +- missing Swift search path under the active Xcode Metal toolchain +- `SDKROOT[sdk=...]` target-level warnings generated by Xcode project export +- Swift conditional compilation flag warnings such as `GROUP_ID="..."` +- asset catalog warnings because the current icon set is still iOS-shaped, not a full tvOS Top Shelf asset set +- Go/WireGuard umbrella-header warnings from the temporary local `libwg-go.a` bridge +- deprecated libssh SCP API warnings in existing app code +- `qt_import_plugins()` warnings shown during configure + +If the static platform plugin is not linked correctly, the typical failure is: + +- `_OBJC_CLASS_$_QIOSApplicationDelegate` +- `_qt_main_wrapper` + +Those are cleanup tasks, not blockers for the current build proof. + +## 12. Fast Rebuild Checklist + +If everything is already built once: + +1. Reuse `$QT_TVOS_PREFIX` +2. Reuse Conan cache under `$HOME/.conan2` +3. Reuse or pass an initialized `AMNEZIA_THIRDPARTY_ROOT` +4. Re-run `qt-cmake` into `$BUILD_DIR` +5. Re-run `xcodebuild -quiet ... build` diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index df8558621..55092eff8 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) project(${PROJECT}) +set(AMNEZIA_THIRDPARTY_ROOT "${CMAKE_CURRENT_LIST_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources") +get_filename_component(AMNEZIA_THIRDPARTY_CLIENT_ROOT "${AMNEZIA_THIRDPARTY_ROOT}/.." ABSOLUTE) + set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") @@ -108,6 +111,7 @@ include_directories( ${CMAKE_CURRENT_LIST_DIR}/../ipc ${CMAKE_CURRENT_LIST_DIR}/../common/logger ${CMAKE_CURRENT_LIST_DIR} + ${AMNEZIA_THIRDPARTY_CLIENT_ROOT} ${CMAKE_CURRENT_BINARY_DIR} ) @@ -209,26 +213,28 @@ else() qt_finalize_target(${PROJECT}) endif() -install(TARGETS ${PROJECT} - DESTINATION ${CMAKE_INSTALL_BINDIR} - COMPONENT AmneziaVPN -) -install(FILES $ - DESTINATION ${CMAKE_INSTALL_BINDIR} - COMPONENT AmneziaVPN -) +if(NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") + install(TARGETS ${PROJECT} + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT AmneziaVPN + ) + install(FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT AmneziaVPN + ) -set(deploy_tool_options "") -if(WIN32) - set(deploy_tool_options "--force-openssl --force") + set(deploy_tool_options "") + if(WIN32) + set(deploy_tool_options "--force-openssl --force") + endif() + + qt_generate_deploy_qml_app_script( + TARGET ${PROJECT} + OUTPUT_SCRIPT QT_DEPLOY_SCRIPT + NO_UNSUPPORTED_PLATFORM_ERROR + DEPLOY_TOOL_OPTIONS ${deploy_tool_options} + ) + install(SCRIPT ${QT_DEPLOY_SCRIPT} + COMPONENT AmneziaVPN + ) endif() - -qt_generate_deploy_qml_app_script( - TARGET ${PROJECT} - OUTPUT_SCRIPT QT_DEPLOY_SCRIPT - NO_UNSUPPORTED_PLATFORM_ERROR - DEPLOY_TOOL_OPTIONS ${deploy_tool_options} -) -install(SCRIPT ${QT_DEPLOY_SCRIPT} - COMPONENT AmneziaVPN -) diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake index 765995439..4a4cf23c7 100644 --- a/client/cmake/3rdparty.cmake +++ b/client/cmake/3rdparty.cmake @@ -1,18 +1,19 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) +set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}") -add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel) +add_subdirectory(${AMNEZIA_THIRDPARTY_ROOT}/SortFilterProxyModel ${CMAKE_CURRENT_BINARY_DIR}/3rd/SortFilterProxyModel) set(LIBS ${LIBS} SortFilterProxyModel) include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake) -include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake) +include(${AMNEZIA_THIRDPARTY_ROOT}/qrcodegen/qrcodegen.cmake) add_compile_definitions(_WINSOCKAPI_) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_WITH_QT6 ON) -add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL) +add_subdirectory(${AMNEZIA_THIRDPARTY_ROOT}/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL) if(ANDROID) # Use qtgamepad from amnezia-vpn/qtgamepad repository @@ -36,8 +37,8 @@ endif() set(LIBS ${LIBS} qt6keychain) include_directories( - ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include - ${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain + ${AMNEZIA_THIRDPARTY_ROOT}/QSimpleCrypto/src/include + ${AMNEZIA_THIRDPARTY_ROOT}/qtkeychain/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ) diff --git a/client/cmake/QSimpleCrypto.cmake b/client/cmake/QSimpleCrypto.cmake index ec43cb833..9b48e15da 100644 --- a/client/cmake/QSimpleCrypto.cmake +++ b/client/cmake/QSimpleCrypto.cmake @@ -1,5 +1,6 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) -set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src) +set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources") +set(QSIMPLECRYPTO_DIR ${AMNEZIA_THIRDPARTY_ROOT}/QSimpleCrypto/src) include_directories(${QSIMPLECRYPTO_DIR}) diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index a71506517..d4e2d1f01 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -211,7 +211,8 @@ if(AMNEZIA_IOS_APPLETV) endif() endif() -set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) +set(AMNEZIA_THIRDPARTY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources") +set(WG_APPLE_SOURCE_DIR ${AMNEZIA_THIRDPARTY_ROOT}/amneziawg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 145ca9226..47c795375 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -1,6 +1,7 @@ enable_language(Swift) set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) +set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources") set(AMNEZIA_IOS_APPLETV ${AMNEZIA_IOS_ENABLE_APPLETV_TARGET}) if(AMNEZIA_IOS_APPLETV) @@ -94,7 +95,7 @@ endif() target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) -set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${AMNEZIA_THIRDPARTY_ROOT}/amneziawg-apple/Sources) set(NE_COMMON_SOURCES ${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift diff --git a/client/platforms/ios/tvos_cgo_stubs.c b/client/platforms/ios/tvos_cgo_stubs.c new file mode 100644 index 000000000..5f8fa7e70 --- /dev/null +++ b/client/platforms/ios/tvos_cgo_stubs.c @@ -0,0 +1,6 @@ +/* + * tvOS does not export these iOS runtime helpers used by Go cgo archives. + * WireGuardKitGo references them indirectly; provide no-op stubs for tvOS. + */ +void darwin_arm_init_mach_exception_handler(void) {} +void darwin_arm_init_thread_exception_port(void) {} diff --git a/cmake/platform_settings.cmake b/cmake/platform_settings.cmake index c290b6623..948954392 100644 --- a/cmake/platform_settings.cmake +++ b/cmake/platform_settings.cmake @@ -1,11 +1,21 @@ if(APPLE) get_property(generator_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if (generator_is_multi_config) - set(CONAN_INSTALL_BUILD_CONFIGURATIONS Release Debug MinSizeRel RelWithDebInfo) + if(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + set(CONAN_INSTALL_BUILD_CONFIGURATIONS RelWithDebInfo) + else() + set(CONAN_INSTALL_BUILD_CONFIGURATIONS Release Debug MinSizeRel RelWithDebInfo) + endif() endif() if(CMAKE_SYSTEM_NAME STREQUAL "iOS") set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "" FORCE) set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE) + elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + set(CONAN_INSTALL_ARGS "--build=missing;-o;openssl/3.6.1:no_apps=True" CACHE STRING "" FORCE) + set(AMNEZIA_IOS_ENABLE_APPLETV_TARGET ON CACHE BOOL "" FORCE) + set(CMAKE_CONFIGURATION_TYPES "RelWithDebInfo" CACHE STRING "" FORCE) + set(CMAKE_OSX_DEPLOYMENT_TARGET "17.0" CACHE STRING "" FORCE) + set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE) elseif(MACOS_NE) set(CONAN_INSTALL_ARGS "--build=missing;-o=&:macos_ne=True" CACHE STRING "" FORCE) set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "" FORCE) diff --git a/conanfile.py b/conanfile.py index 46154d055..9e630b342 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,7 +14,7 @@ class AmneziaVPN(ConanFile): def requirements(self): os = str(self.settings.os) - has_ne = os == "iOS" or (os == "Macos" and self.options.macos_ne) + has_ne = os in ["iOS", "tvOS"] or (os == "Macos" and self.options.macos_ne) has_service = os == "Windows" or os == "Linux" or (os == "Macos" and not has_ne) if has_service: @@ -33,8 +33,9 @@ class AmneziaVPN(ConanFile): if has_ne: self.requires("awg-apple/2.0.1") - self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True}) - self.requires("openvpnadapter/1.0.0") + if os != "tvOS": + self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True}) + self.requires("openvpnadapter/1.0.0") if os == "Android": self.requires("amnezia-libxray/1.0.0") @@ -42,4 +43,4 @@ class AmneziaVPN(ConanFile): self.requires("openvpn-pt-android/1.0.0") self.requires("libssh/0.11.3@amnezia") - self.requires("openssl/3.6.1") + self.requires("openssl/3.6.1", options={"no_apps": os == "tvOS"}, force=os == "tvOS") diff --git a/recipes/awg-apple/conanfile.py b/recipes/awg-apple/conanfile.py index 1b6722f60..633bc0274 100644 --- a/recipes/awg-apple/conanfile.py +++ b/recipes/awg-apple/conanfile.py @@ -1,11 +1,16 @@ from conan import ConanFile from conan.errors import ConanInvalidConfiguration from conan.tools.layout import basic_layout -from conan.tools.files import get, copy, collect_libs +from conan.tools.files import get, copy, collect_libs, replace_in_file from conan.tools.apple import is_apple_os from conan.tools.gnu import AutotoolsToolchain, Autotools import os +import subprocess + +TVOS_PREBUILT_DIR_ENV = "AMNEZIA_TVOS_AWG_PREBUILT_DIR" +TVOS_VERSION_HEADER_DIR_ENV = "AMNEZIA_TVOS_AWG_VERSION_HEADER_DIR" + class AwgApple(ConanFile): name = "awg-apple" @@ -18,6 +23,10 @@ class AwgApple(ConanFile): "as_framework": False } + @property + def _is_tvos(self): + return str(self.settings.os) == "tvOS" + @property def _goarch(self): arch_map = { @@ -27,9 +36,6 @@ class AwgApple(ConanFile): archs = str(self.settings.arch).split("|") return " ".join(arch_map.get(arch, arch) for arch in archs) - def build_requirements(self): - self.tool_requires("go/1.26.0") - def layout(self): basic_layout(self, build_folder=os.path.join(self.folders.source, "Sources/WireGuardKitGo")) @@ -44,16 +50,96 @@ class AwgApple(ConanFile): sha256="9fe4f8cfbb6a751558b54b7979db3a5ea46e49731912aae99f093e84a1433e97", strip_root=True ) + def _go_env(self): + env = os.environ.copy() + env["GOTOOLCHAIN"] = "local" + return env + + def _go_root(self): + try: + return subprocess.check_output( + ["go", "env", "GOROOT"], + text=True, + env=self._go_env(), + cwd="/", + ).strip() + except (FileNotFoundError, subprocess.CalledProcessError) as error: + raise ConanInvalidConfiguration( + "Go is required to build awg-apple. Install Go or provide a tvOS " + f"prebuilt bridge through {TVOS_PREBUILT_DIR_ENV}." + ) from error + + def _env_path(self, name): + value = os.environ.get(name) + if not value: + return None + return os.path.abspath(os.path.expanduser(value)) + + def _uses_tvos_prebuilt_bridge(self): + return self._is_tvos and self._env_path(TVOS_PREBUILT_DIR_ENV) is not None + + def _make_args(self): + if self._uses_tvos_prebuilt_bridge(): + return [] + + sdk = self.settings.get_safe("os.sdk", "macosx") + args = [ + f"ARCHS={self._goarch}", + f"PLATFORM_NAME={sdk}", + f"REAL_GOROOT={self._go_root()}", + "GOTOOLCHAIN=local", + ] + if self._is_tvos: + args.extend([ + "GOOS_appletvos=ios", + "GOFLAGS=-tags=netgo", + "DEPLOYMENT_TARGET_CLANG_FLAG_NAME=mtvos-version-min", + "DEPLOYMENT_TARGET_CLANG_ENV_NAME=TVOS_DEPLOYMENT_TARGET", + f"TVOS_DEPLOYMENT_TARGET={self.settings.os.version}", + ]) + return args + def generate(self): tc = AutotoolsToolchain(self) - sdk = self.settings.get_safe("os.sdk", "macosx") - tc.make_args = [ - f"ARCHS={self._goarch}", - f"PLATFORM_NAME={sdk}" - ] + tc.make_args = self._make_args() tc.generate() + def _copy_tvos_prebuilt_bridge(self): + prebuilt_dir = self._env_path(TVOS_PREBUILT_DIR_ENV) + if not prebuilt_dir: + return False + + lib_path = os.path.join(prebuilt_dir, "libwg-go.a") + if not os.path.isfile(lib_path): + raise ConanInvalidConfiguration( + f"{TVOS_PREBUILT_DIR_ENV} is set to '{prebuilt_dir}', but libwg-go.a was not found there." + ) + + header_dir = self._env_path(TVOS_VERSION_HEADER_DIR_ENV) or prebuilt_dir + header_path = os.path.join(header_dir, "wireguard-go-version.h") + if not os.path.isfile(header_path): + raise ConanInvalidConfiguration( + f"wireguard-go-version.h was not found in '{header_dir}'. Set " + f"{TVOS_VERSION_HEADER_DIR_ENV} if the header lives outside {TVOS_PREBUILT_DIR_ENV}." + ) + + self.output.warning( + f"Using explicit tvOS WireGuard bridge from {TVOS_PREBUILT_DIR_ENV}={prebuilt_dir}" + ) + out_dir = os.path.join(self.build_folder, "out") + copy(self, "libwg-go.a", src=prebuilt_dir, dst=out_dir) + copy(self, "wireguard-go-version.h", src=header_dir, dst=out_dir) + return True + + def _patch_tvos_go_version_for_local_toolchain(self): + replace_in_file(self, os.path.join(self.build_folder, "go.mod"), "go 1.26", "go 1.25") + def build(self): + if self._is_tvos: + if self._copy_tvos_prebuilt_bridge(): + return + self._patch_tvos_go_version_for_local_toolchain() + autotools = Autotools(self) autotools.make() autotools.make("version-header") diff --git a/recipes/libssh/conanfile.py b/recipes/libssh/conanfile.py index 30f71a67b..1c3e40496 100644 --- a/recipes/libssh/conanfile.py +++ b/recipes/libssh/conanfile.py @@ -78,6 +78,8 @@ class LibSSHRecipe(ConanFile): tc.cache_variables["WITH_MBEDTLS"] = self.options.crypto_backend == "mbedtls" tc.cache_variables["WITH_NACL"] = False tc.cache_variables["WITH_SYMBOL_VERSIONING"] = self.options.get_safe("with_symbol_versioning", True) + if self.settings.os == "tvOS": + tc.cache_variables["WITH_EXEC"] = False tc.variables["WITH_ZLIB"] = self.options.with_zlib if is_msvc(self): tc.cache_variables["USE_MSVC_RUNTIME_LIBRARY_DLL"] = not is_msvc_static_runtime(self)