build: make conan tvOS pipeline build

build: make tvOS awg prebuilt explicit

docs: make Apple TV build manual portable
This commit is contained in:
spectrum
2026-04-25 23:03:38 +03:00
parent e43973f8ad
commit fa72d8de43
11 changed files with 498 additions and 43 deletions

340
apple-tv-build.md Normal file
View File

@@ -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`

View File

@@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT}) 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 USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_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}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger ${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}
${AMNEZIA_THIRDPARTY_CLIENT_ROOT}
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
) )
@@ -209,26 +213,28 @@ else()
qt_finalize_target(${PROJECT}) qt_finalize_target(${PROJECT})
endif() endif()
install(TARGETS ${PROJECT} if(NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
DESTINATION ${CMAKE_INSTALL_BINDIR} install(TARGETS ${PROJECT}
COMPONENT AmneziaVPN DESTINATION ${CMAKE_INSTALL_BINDIR}
) COMPONENT AmneziaVPN
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}> )
DESTINATION ${CMAKE_INSTALL_BINDIR} install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
COMPONENT AmneziaVPN DESTINATION ${CMAKE_INSTALL_BINDIR}
) COMPONENT AmneziaVPN
)
set(deploy_tool_options "") set(deploy_tool_options "")
if(WIN32) if(WIN32)
set(deploy_tool_options "--force-openssl --force") 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() 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
)

View File

@@ -1,18 +1,19 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 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}") 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) set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake) 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_) add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON) 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) if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository # Use qtgamepad from amnezia-vpn/qtgamepad repository
@@ -36,8 +37,8 @@ endif()
set(LIBS ${LIBS} qt6keychain) set(LIBS ${LIBS} qt6keychain)
include_directories( include_directories(
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include ${AMNEZIA_THIRDPARTY_ROOT}/QSimpleCrypto/src/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain ${AMNEZIA_THIRDPARTY_ROOT}/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
) )

View File

@@ -1,5 +1,6 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 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}) include_directories(${QSIMPLECRYPTO_DIR})

View File

@@ -211,7 +211,8 @@ if(AMNEZIA_IOS_APPLETV)
endif() endif()
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 target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift

View File

@@ -1,6 +1,7 @@
enable_language(Swift) enable_language(Swift)
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) 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}) set(AMNEZIA_IOS_APPLETV ${AMNEZIA_IOS_ENABLE_APPLETV_TARGET})
if(AMNEZIA_IOS_APPLETV) 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 -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) 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 set(NE_COMMON_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift ${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift

View File

@@ -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) {}

View File

@@ -1,11 +1,21 @@
if(APPLE) if(APPLE)
get_property(generator_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) get_property(generator_is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (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() endif()
if(CMAKE_SYSTEM_NAME STREQUAL "iOS") if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "" FORCE) set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "" FORCE)
set(CMAKE_OSX_ARCHITECTURES "arm64" 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) elseif(MACOS_NE)
set(CONAN_INSTALL_ARGS "--build=missing;-o=&:macos_ne=True" CACHE STRING "" FORCE) set(CONAN_INSTALL_ARGS "--build=missing;-o=&:macos_ne=True" CACHE STRING "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "" FORCE) set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "" FORCE)

View File

@@ -14,7 +14,7 @@ class AmneziaVPN(ConanFile):
def requirements(self): def requirements(self):
os = str(self.settings.os) 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) has_service = os == "Windows" or os == "Linux" or (os == "Macos" and not has_ne)
if has_service: if has_service:
@@ -33,8 +33,9 @@ class AmneziaVPN(ConanFile):
if has_ne: if has_ne:
self.requires("awg-apple/2.0.1") self.requires("awg-apple/2.0.1")
self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True}) if os != "tvOS":
self.requires("openvpnadapter/1.0.0") self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True})
self.requires("openvpnadapter/1.0.0")
if os == "Android": if os == "Android":
self.requires("amnezia-libxray/1.0.0") self.requires("amnezia-libxray/1.0.0")
@@ -42,4 +43,4 @@ class AmneziaVPN(ConanFile):
self.requires("openvpn-pt-android/1.0.0") self.requires("openvpn-pt-android/1.0.0")
self.requires("libssh/0.11.3@amnezia") 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")

View File

@@ -1,11 +1,16 @@
from conan import ConanFile from conan import ConanFile
from conan.errors import ConanInvalidConfiguration from conan.errors import ConanInvalidConfiguration
from conan.tools.layout import basic_layout 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.apple import is_apple_os
from conan.tools.gnu import AutotoolsToolchain, Autotools from conan.tools.gnu import AutotoolsToolchain, Autotools
import os 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): class AwgApple(ConanFile):
name = "awg-apple" name = "awg-apple"
@@ -18,6 +23,10 @@ class AwgApple(ConanFile):
"as_framework": False "as_framework": False
} }
@property
def _is_tvos(self):
return str(self.settings.os) == "tvOS"
@property @property
def _goarch(self): def _goarch(self):
arch_map = { arch_map = {
@@ -27,9 +36,6 @@ class AwgApple(ConanFile):
archs = str(self.settings.arch).split("|") archs = str(self.settings.arch).split("|")
return " ".join(arch_map.get(arch, arch) for arch in archs) 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): def layout(self):
basic_layout(self, build_folder=os.path.join(self.folders.source, "Sources/WireGuardKitGo")) 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 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): def generate(self):
tc = AutotoolsToolchain(self) tc = AutotoolsToolchain(self)
sdk = self.settings.get_safe("os.sdk", "macosx") tc.make_args = self._make_args()
tc.make_args = [
f"ARCHS={self._goarch}",
f"PLATFORM_NAME={sdk}"
]
tc.generate() 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): 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 = Autotools(self)
autotools.make() autotools.make()
autotools.make("version-header") autotools.make("version-header")

View File

@@ -78,6 +78,8 @@ class LibSSHRecipe(ConanFile):
tc.cache_variables["WITH_MBEDTLS"] = self.options.crypto_backend == "mbedtls" tc.cache_variables["WITH_MBEDTLS"] = self.options.crypto_backend == "mbedtls"
tc.cache_variables["WITH_NACL"] = False tc.cache_variables["WITH_NACL"] = False
tc.cache_variables["WITH_SYMBOL_VERSIONING"] = self.options.get_safe("with_symbol_versioning", True) 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 tc.variables["WITH_ZLIB"] = self.options.with_zlib
if is_msvc(self): if is_msvc(self):
tc.cache_variables["USE_MSVC_RUNTIME_LIBRARY_DLL"] = not is_msvc_static_runtime(self) tc.cache_variables["USE_MSVC_RUNTIME_LIBRARY_DLL"] = not is_msvc_static_runtime(self)