From 42cd079017a683f8d0e857979b523fbdb82339be Mon Sep 17 00:00:00 2001 From: dranik Date: Tue, 26 May 2026 17:12:16 +0300 Subject: [PATCH] Feat: Add Support ARM Windows --- cmake/CPack.cmake | 8 +- cmake/conan_provider.cmake | 6 + cmake/recipes_bootstrap.cmake | 4 + conanfile.py | 11 +- deploy/build.bat | 9 +- .../__pycache__/windows_cgo.cpython-312.pyc | Bin 0 -> 12436 bytes recipes/_helpers/cgo_cc.py | 115 ++++++++++ recipes/_helpers/windows_cgo.py | 198 ++++++++++++++++++ recipes/amnezia-xray-bindings/cgo_cc.py | 115 ++++++++++ recipes/amnezia-xray-bindings/cgo_cl_shim.cmd | 37 ++++ recipes/amnezia-xray-bindings/conanfile.py | 118 +++++++++-- recipes/amnezia-xray-bindings/windows_cgo.py | 198 ++++++++++++++++++ recipes/awg-windows/cgo_cc.py | 115 ++++++++++ recipes/awg-windows/cgo_cl_shim.cmd | 37 ++++ recipes/awg-windows/conanfile.py | 68 ++++-- recipes/awg-windows/windows_cgo.py | 198 ++++++++++++++++++ recipes/go/conandata.yml | 5 +- recipes/libssh/conanfile.py | 2 + recipes/openvpn/conanfile.py | 22 +- recipes/tun2socks/conanfile.py | 49 +++-- recipes/win-split-tunnel/conanfile.py | 31 ++- 21 files changed, 1269 insertions(+), 77 deletions(-) create mode 100644 recipes/_helpers/__pycache__/windows_cgo.cpython-312.pyc create mode 100644 recipes/_helpers/cgo_cc.py create mode 100644 recipes/_helpers/windows_cgo.py create mode 100644 recipes/amnezia-xray-bindings/cgo_cc.py create mode 100644 recipes/amnezia-xray-bindings/cgo_cl_shim.cmd create mode 100644 recipes/amnezia-xray-bindings/windows_cgo.py create mode 100644 recipes/awg-windows/cgo_cc.py create mode 100644 recipes/awg-windows/cgo_cl_shim.cmd create mode 100644 recipes/awg-windows/windows_cgo.py diff --git a/cmake/CPack.cmake b/cmake/CPack.cmake index 7e91e5abd0..2e2599b1e9 100644 --- a/cmake/CPack.cmake +++ b/cmake/CPack.cmake @@ -1,7 +1,13 @@ set(CPACK_PACKAGE_VENDOR AmneziaVPN) set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION}) if(WIN32) - set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_windows_x64") + if(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" + OR CMAKE_CXX_COMPILER_ARCHITECTURE_ID MATCHES "ARM64|arm64") + set(_amnezia_win_arch arm64) + else() + set(_amnezia_win_arch x64) + endif() + set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_windows_${_amnezia_win_arch}") elseif(APPLE AND NOT IOS AND NOT MACOS_NE) set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_macos_x64") elseif(LINUX AND NOT ANDROID) diff --git a/cmake/conan_provider.cmake b/cmake/conan_provider.cmake index 9535e66cdb..c44be3e058 100644 --- a/cmake/conan_provider.cmake +++ b/cmake/conan_provider.cmake @@ -431,6 +431,12 @@ function(detect_host_profile output_file) string(APPEND profile "build_type=${build_type}\n") endif() + # OpenSSL ASM on Windows/ARM64 needs clang-cl; disable ASM when building from CMake-Conan. + if(os STREQUAL "Windows" AND arch STREQUAL "armv8") + string(APPEND profile "\n[options]\n") + string(APPEND profile "openssl/*:no_asm=True\n") + endif() + if(NOT DEFINED output_file) set(file_name "${CMAKE_BINARY_DIR}/profile") else() diff --git a/cmake/recipes_bootstrap.cmake b/cmake/recipes_bootstrap.cmake index a4deaffa95..ecf23cd2d4 100644 --- a/cmake/recipes_bootstrap.cmake +++ b/cmake/recipes_bootstrap.cmake @@ -7,6 +7,10 @@ find_program(CONAN_COMMAND "conan" REQUIRED file(GLOB_RECURSE LOCAL_RECIPES "${CMAKE_SOURCE_DIR}/recipes/*/conanfile.py") foreach(RECIPE ${LOCAL_RECIPES}) get_filename_component(RECIPE_DIR ${RECIPE} DIRECTORY) + get_filename_component(RECIPE_NAME ${RECIPE_DIR} NAME) + if(RECIPE_NAME MATCHES "^[-_]") + continue() + endif() execute_process( COMMAND ${CONAN_COMMAND} export ${RECIPE_DIR} ) diff --git a/conanfile.py b/conanfile.py index 03e9e3a89e..c3963173b6 100644 --- a/conanfile.py +++ b/conanfile.py @@ -11,6 +11,12 @@ class AmneziaVPN(ConanFile): "macos_ne": False } + def configure(self): + # OpenSSL 3.x ASM on Windows/ARM64 needs clang-cl (VS "C++ Clang tools"). + # Without it, nmake fails on *.asm; pure C build is fine for the client. + if str(self.settings.os) == "Windows" and str(self.settings.arch) == "armv8": + self.options["openssl/*"].no_asm = True + def requirements(self): os = str(self.settings.os) @@ -43,5 +49,8 @@ def requirements(self): # expicitly use libssh@amnezia to prevent it from being downloaded from conan-center self.requires("libssh/0.11.3@amnezia") - self.requires("openssl/3.6.1") + if str(self.settings.os) == "Windows" and str(self.settings.arch) == "armv8": + self.requires("openssl/3.6.1", options={"no_asm": True}) + else: + self.requires("openssl/3.6.1") self.requires("zlib/1.3.2") diff --git a/deploy/build.bat b/deploy/build.bat index 000880981c..33a3bf446f 100644 --- a/deploy/build.bat +++ b/deploy/build.bat @@ -22,11 +22,13 @@ if /i "%ARCH%" == "x64" set "ARCH=amd64" if /i "%ARCH%" == "amd64" ( if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "_vcvars_arg=amd64" if /i "%PROCESSOR_ARCHITECTURE%" == "x86" set "_vcvars_arg=x86_amd64" + if /i "%PROCESSOR_ARCHITECTURE%" == "ARM64" set "_vcvars_arg=arm64_x64" set "_qt_postfix_arg=64" ) if /i "%ARCH%" == "arm64" ( if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "_vcvars_arg=amd64_arm64" if /i "%PROCESSOR_ARCHITECTURE%" == "x86" set "_vcvars_arg=x86_arm64" + if /i "%PROCESSOR_ARCHITECTURE%" == "ARM64" set "_vcvars_arg=arm64" set "_qt_postfix_arg=arm64" ) if not defined _vcvars_arg ( @@ -94,8 +96,13 @@ if exist "%VCVARS_PATH%" ( ) :: build project and installers +if /i "%_qt_postfix_arg%" == "arm64" ( + set "_cmake_arch=-A ARM64" +) else ( + set "_cmake_arch=-A x64" +) @echo on -cmake -S "%PROJECT_DIR%" -B "%BUILD_DIR%" -DCMAKE_BUILD_TYPE=Release "-DCMAKE_PREFIX_PATH=%QT_ROOT_PATH%\msvc2022_%_qt_postfix_arg%" "-DCMAKE_VS_GLOBALS=UseMultiToolTask=true;EnforceProcessCountAcrossBuilds=true" || goto :fail +cmake -S "%PROJECT_DIR%" -B "%BUILD_DIR%" -DCMAKE_BUILD_TYPE=Release %_cmake_arch% "-DCMAKE_PREFIX_PATH=%QT_ROOT_PATH%\msvc2022_%_qt_postfix_arg%" "-DCMAKE_VS_GLOBALS=UseMultiToolTask=true;EnforceProcessCountAcrossBuilds=true" || goto :fail cmake --build "%BUILD_DIR%" --config Release -- /m || goto :fail @echo off for %%I in (%ARG_BUILD_INSTALLERS%) do ( diff --git a/recipes/_helpers/__pycache__/windows_cgo.cpython-312.pyc b/recipes/_helpers/__pycache__/windows_cgo.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b97c08fb311311d1acb2f0c1d896ba63a5854223 GIT binary patch literal 12436 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 rDJNyLJg>r3dlzkc>Tu;5c;c>b str | None: + pf = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(pf, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + return None + r = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if r.returncode != 0 or not r.stdout.strip(): + return None + return r.stdout.strip().splitlines()[0] + + +def _find_clang_cl() -> str | None: + install = _vs_install() + if not install: + return None + llvm = os.path.join(install, "VC", "Tools", "Llvm") + if not os.path.isdir(llvm): + return None + preferred: list[str] = [] + fallback: list[str] = [] + for root, _, files in os.walk(llvm): + if "clang-cl.exe" not in files: + continue + path = os.path.join(root, "clang-cl.exe") + if "arm64" in root.lower(): + preferred.append(path) + else: + fallback.append(path) + if preferred: + return preferred[0] + if fallback: + return fallback[0] + direct = os.path.join(llvm, "bin", "clang-cl.exe") + return direct if os.path.isfile(direct) else None + + +def _gcc_args_to_msvc(args: list[str]) -> list[str]: + out: list[str] = [] + i = 0 + while i < len(args): + a = args[i] + if a == "-x" and i + 1 < len(args): + i += 2 + continue + if a == "-o" and i + 1 < len(args): + obj = args[i + 1] + if obj.endswith((".o", ".obj")): + out.append("/Fo" + obj) + else: + out.append("/Fe" + obj) + i += 2 + continue + if a.startswith("-W") or a.startswith("-f") or a.startswith("-d"): + i += 1 + continue + if a.startswith("-m") or a.startswith("-std=") or a in ("-pthread",): + i += 1 + continue + if a == "-O2": + out.append("/O2") + i += 1 + continue + if a == "-O0": + out.append("/Od") + i += 1 + continue + if a == "-g": + out.append("/Zi") + i += 1 + continue + if a == "-c": + out.append("/c") + i += 1 + continue + if a.startswith("-D"): + out.append("/D" + a[2:]) + i += 1 + continue + if a.startswith("-I"): + out.append("/I" + a[2:]) + i += 1 + continue + if not a.startswith("-"): + out.append(a) + i += 1 + return out + + +def main() -> int: + args = sys.argv[1:] + clang = _find_clang_cl() + if clang: + return subprocess.call([clang, *args]) + + msvc = _gcc_args_to_msvc(args) + return subprocess.call(["cl", *msvc]) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/recipes/_helpers/windows_cgo.py b/recipes/_helpers/windows_cgo.py new file mode 100644 index 0000000000..aa2393bf3e --- /dev/null +++ b/recipes/_helpers/windows_cgo.py @@ -0,0 +1,198 @@ +"""Windows CGO helpers: llvm-mingw for ARM64, MinGW via Conan for x86_64.""" + +import os +import re +import subprocess + +# https://github.com/mstorsjo/llvm-mingw/releases +LLVM_MINGW_VERSION = "20260519" +LLVM_MINGW_URL = ( + f"https://github.com/mstorsjo/llvm-mingw/releases/download/" + f"{LLVM_MINGW_VERSION}/llvm-mingw-{LLVM_MINGW_VERSION}-ucrt-aarch64.zip" +) +LLVM_MINGW_SHA256 = "7bb5e3c9964dc29555cababb55e69eea02a51e98e1240eb9513e6540c7bae0ea" + + +def is_windows_arm64(conanfile) -> bool: + return ( + str(conanfile.settings.get_safe("os", "")).startswith("Windows") + and str(conanfile.settings.arch) == "armv8" + ) + + +def msvc_machine(arch: str) -> str: + return {"x86_64": "X64", "armv8": "ARM64", "x86": "X86"}.get(arch, "ARM64") + + +def go_tool_exe(conanfile) -> str: + go = conanfile.dependencies.build["go"] + return os.path.join(go.package_folder, "bin", "go.exe") + + +def _vs_install_path(conanfile) -> str: + program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(program_files_x86, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + raise RuntimeError(f"{conanfile}: vswhere.exe not found at {vswhere}") + + completed = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if completed.returncode != 0 or not completed.stdout.strip(): + raise RuntimeError( + f"{conanfile}: vswhere failed ({completed.returncode}): " + f"{completed.stderr or completed.stdout}" + ) + return completed.stdout.strip().splitlines()[0] + + +def _vcvars_ver_flag(conanfile) -> str: + version = str(conanfile.settings.get_safe("compiler.version", "")) + if not version: + return "" + minor = {"192": "14.2", "193": "14.3", "194": "14.4", "195": "14.5"} + ver = minor.get(version) + return f" -vcvars_ver={ver}" if ver else "" + + +def _vcvars_arch_arg(target_arch: str) -> str: + import platform + + host = platform.machine().lower() + host_is_arm = host in ("arm64", "aarch64") + + if target_arch == "armv8": + return "arm64" if host_is_arm else "amd64_arm64" + if target_arch == "x86_64": + if host_is_arm: + return "arm64_x64" + return "amd64" if host in ("amd64", "x86_64") else "x86_amd64" + return "arm64" if host_is_arm else "amd64" + + +def msvc_vcvars_cmd(conanfile) -> str: + target_arch = str(conanfile.settings.arch) + install_path = _vs_install_path(conanfile) + vcvarsall = os.path.join(install_path, "VC", "Auxiliary", "Build", "vcvarsall.bat") + arch_arg = _vcvars_arch_arg(target_arch) + ver_flag = _vcvars_ver_flag(conanfile) + return f'call "{vcvarsall}" {arch_arg}{ver_flag}' + + +def _find_llvm_mingw_toolchain(root_dir: str) -> tuple[str, str]: + gcc_names = ( + "aarch64-w64-windows-gnu-gcc.exe", + "aarch64-w64-mingw32-gcc.exe", + ) + for gcc_name in gcc_names: + for root, _, files in os.walk(root_dir): + if gcc_name in files: + bindir = os.path.join(root, "") + return os.path.join(root, gcc_name), bindir + + for root, _, files in os.walk(root_dir): + for fname in files: + if fname.endswith("-gcc.exe") and "aarch64" in fname: + return os.path.join(root, fname), root + raise RuntimeError(f"llvm-mingw gcc not found under {root_dir}") + + +def ensure_llvm_mingw(conanfile) -> tuple[str, str]: + """Download and extract llvm-mingw (UCRT, aarch64). Returns (gcc.exe, bin_dir).""" + from conan.tools.files import download, unzip + + dest = os.path.join(conanfile.build_folder, "llvm-mingw") + ready = os.path.join(dest, ".extracted") + if not os.path.isfile(ready): + zip_path = os.path.join(conanfile.build_folder, "llvm-mingw.zip") + conanfile.output.info(f"Downloading llvm-mingw {LLVM_MINGW_VERSION} for Windows ARM64 CGO") + download(conanfile, LLVM_MINGW_URL, zip_path, sha256=LLVM_MINGW_SHA256) + unzip(conanfile, zip_path, dest) + with open(ready, "w", encoding="ascii") as marker: + marker.write(LLVM_MINGW_VERSION) + + gcc, bindir = _find_llvm_mingw_toolchain(dest) + conanfile.output.info(f"CGO compiler: {gcc}") + return gcc, bindir + + +def run_llvm_mingw_go_build( + conanfile, + src: str, + out: str, + goarch: str, + *, + goarm=None, + extra_args="-ldflags=-w -buildmode=c-shared", +) -> None: + """Build c-shared with llvm-mingw GCC (same approach as other Go Windows/arm64 projects).""" + gcc, bindir = ensure_llvm_mingw(conanfile) + go = go_tool_exe(conanfile) + goarm_set = f"&& set GOARM={goarm}" if goarm else "" + bindir_q = bindir.replace('"', '""') + gcc_q = gcc.replace('"', '""') + conanfile.run( + f'set "PATH={bindir_q};%PATH%"&& set CGO_ENABLED=1&& set GOOS=windows&& set GOARCH={goarch}{goarm_set}&& ' + f'set "CC={gcc_q}"&& set "CXX={gcc_q}"&& ' + f'"{go}" build -C "{src}" {extra_args} -o "{out}" .', + ) + + +def ensure_msvc_import_lib( + conanfile, + build_dir: str, + dll_name: str, + base_name: str, +) -> None: + """MSVC import library for linking the DLL from the main app (MSVC/Qt).""" + lib_path = os.path.join(build_dir, f"{base_name}.lib") + if os.path.isfile(lib_path): + return + + dll_path = os.path.join(build_dir, dll_name) + if not os.path.isfile(dll_path): + raise RuntimeError(f"{conanfile}: DLL not found: {dll_path}") + + machine = msvc_machine(str(conanfile.settings.arch)) + def_path = os.path.join(build_dir, f"{base_name}.def") + exports_txt = os.path.join(build_dir, f"{base_name}.exports.txt") + vc = msvc_vcvars_cmd(conanfile) + + conanfile.run(f'{vc} && dumpbin /nologo /exports "{dll_path}" > "{exports_txt}"') + + with open(exports_txt, encoding="utf-8", errors="replace") as exports_file: + text = exports_file.read() + + exports = [] + in_table = False + for line in text.splitlines(): + if "ordinal hint RVA" in line: + in_table = True + continue + if not in_table: + continue + match = re.match(r"\s+\d+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+)", line) + if match: + name = match.group(1) + if name != "[noname]": + exports.append(name) + + if not exports: + raise RuntimeError(f"{conanfile}: No exports found in {dll_path}") + + with open(def_path, "w", encoding="ascii") as def_file: + def_file.write(f"LIBRARY {base_name}\nEXPORTS\n") + for symbol in exports: + def_file.write(f" {symbol}\n") + + conanfile.run( + f'{vc} && lib /nologo /def:"{def_path}" /out:"{lib_path}" /machine:{machine}', + ) + + try: + os.remove(exports_txt) + except OSError: + pass diff --git a/recipes/amnezia-xray-bindings/cgo_cc.py b/recipes/amnezia-xray-bindings/cgo_cc.py new file mode 100644 index 0000000000..d8c77da605 --- /dev/null +++ b/recipes/amnezia-xray-bindings/cgo_cc.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""CGO compiler shim: clang-cl if present, otherwise MSVC cl with GCC-flag filtering.""" + +from __future__ import annotations + +import os +import subprocess +import sys + + +def _vs_install() -> str | None: + pf = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(pf, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + return None + r = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if r.returncode != 0 or not r.stdout.strip(): + return None + return r.stdout.strip().splitlines()[0] + + +def _find_clang_cl() -> str | None: + install = _vs_install() + if not install: + return None + llvm = os.path.join(install, "VC", "Tools", "Llvm") + if not os.path.isdir(llvm): + return None + preferred: list[str] = [] + fallback: list[str] = [] + for root, _, files in os.walk(llvm): + if "clang-cl.exe" not in files: + continue + path = os.path.join(root, "clang-cl.exe") + if "arm64" in root.lower(): + preferred.append(path) + else: + fallback.append(path) + if preferred: + return preferred[0] + if fallback: + return fallback[0] + direct = os.path.join(llvm, "bin", "clang-cl.exe") + return direct if os.path.isfile(direct) else None + + +def _gcc_args_to_msvc(args: list[str]) -> list[str]: + out: list[str] = [] + i = 0 + while i < len(args): + a = args[i] + if a == "-x" and i + 1 < len(args): + i += 2 + continue + if a == "-o" and i + 1 < len(args): + obj = args[i + 1] + if obj.endswith((".o", ".obj")): + out.append("/Fo" + obj) + else: + out.append("/Fe" + obj) + i += 2 + continue + if a.startswith("-W") or a.startswith("-f") or a.startswith("-d"): + i += 1 + continue + if a.startswith("-m") or a.startswith("-std=") or a in ("-pthread",): + i += 1 + continue + if a == "-O2": + out.append("/O2") + i += 1 + continue + if a == "-O0": + out.append("/Od") + i += 1 + continue + if a == "-g": + out.append("/Zi") + i += 1 + continue + if a == "-c": + out.append("/c") + i += 1 + continue + if a.startswith("-D"): + out.append("/D" + a[2:]) + i += 1 + continue + if a.startswith("-I"): + out.append("/I" + a[2:]) + i += 1 + continue + if not a.startswith("-"): + out.append(a) + i += 1 + return out + + +def main() -> int: + args = sys.argv[1:] + clang = _find_clang_cl() + if clang: + return subprocess.call([clang, *args]) + + msvc = _gcc_args_to_msvc(args) + return subprocess.call(["cl", *msvc]) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/recipes/amnezia-xray-bindings/cgo_cl_shim.cmd b/recipes/amnezia-xray-bindings/cgo_cl_shim.cmd new file mode 100644 index 0000000000..e90917c8ca --- /dev/null +++ b/recipes/amnezia-xray-bindings/cgo_cl_shim.cmd @@ -0,0 +1,37 @@ +@echo off +REM CGO driver for MSVC cl: drop GCC-only flags (-Werror, -f*, -d*, -m*, etc.). +setlocal EnableDelayedExpansion +set "_args=" +:argloop +if "%~1"=="" goto invoke +set "_a=%~1" +if /i "!_a!"=="-Werror" goto next +if /i "!_a!"=="-Wall" goto next +if /i "!_a!"=="-Wno-error" goto next +if "!_a:~0,2!"=="-W" goto next +if "!_a:~0,2!"=="-f" goto next +if "!_a:~0,2!"=="-d" goto next +if "!_a:~0,2!"=="-m" goto next +if "!_a:~0,5!"=="-std=" goto next +if /i "!_a!"=="-pthread" goto next +if /i "!_a!"=="-O2" (set "_args=!_args! /O2") & goto next +if /i "!_a!"=="-O0" (set "_args=!_args! /Od") & goto next +if /i "!_a!"=="-g" (set "_args=!_args! /Zi") & goto next +if /i "!_a!"=="-c" (set "_args=!_args! /c") & goto next +if /i "!_a!"=="-x" (shift & goto next) +if /i "%~1"=="c" goto next +if "!_a:~0,2!"=="-D" (set "_args=!_args! /D!_a:~2!") & goto next +if "!_a:~0,2!"=="-I" (set "_args=!_args! /I!_a:~2!") & goto next +if /i "!_a!"=="-o" ( + set "_o=%~2" + if /i "!_o:~-2!"==".o" set "_args=!_args! /Fo!_o!" + shift + goto next +) +if not "!_a:~0,1!"=="-" set "_args=!_args! "!_a!"" +:next +shift +goto argloop +:invoke +cl !_args! +exit /b !ERRORLEVEL! diff --git a/recipes/amnezia-xray-bindings/conanfile.py b/recipes/amnezia-xray-bindings/conanfile.py index fb7d6a53bb..3cd705d515 100644 --- a/recipes/amnezia-xray-bindings/conanfile.py +++ b/recipes/amnezia-xray-bindings/conanfile.py @@ -3,13 +3,23 @@ from conan.tools.layout import basic_layout from conan.errors import ConanInvalidConfiguration from conan.tools.gnu import Autotools, AutotoolsToolchain +from conan.tools.env import VirtualBuildEnv import os +import sys + +_recipe_dir = os.path.dirname(os.path.abspath(__file__)) +if _recipe_dir not in sys.path: + sys.path.insert(0, _recipe_dir) + +from windows_cgo import ensure_msvc_import_lib, is_windows_arm64, run_llvm_mingw_go_build + class AmneziaXrayBindings(ConanFile): name = "amnezia-xray-bindings" version = "1.1.0" settings = "os", "arch", "compiler" + exports = "windows_cgo.py" @property def _goos(self): @@ -19,7 +29,7 @@ def _goos(self): "Macos": "darwin", "Windows": "windows" }.get(str(self.settings.os)) - + @property def _goarch(self): return { @@ -27,19 +37,23 @@ def _goarch(self): "x86_64": "amd64", "armv8": "arm64" }.get(str(self.settings.arch)) - + @property def _is_windows(self): return str(self.settings.os).startswith("Windows") + @property + def _windows_arm64(self): + return is_windows_arm64(self) + def config_options(self): self.package_type = "shared-library" if self._is_windows else "static-library" def configure(self): self.settings.rm_safe("compiler.libcxx") self.settings.rm_safe("compiler.cppstd") - if self._is_windows: - # mingw-builds is being used on Windows + # Keep compiler settings on Windows ARM64 so VCVars can expose cl/link to CGO. + if self._is_windows and str(self.settings.arch) != "armv8": del self.settings.compiler def layout(self): @@ -47,10 +61,8 @@ def layout(self): def build_requirements(self): self.tool_requires("go/1.26.0") - if self._is_windows: - self.win_bash = True - if not self.conf.get("tools.microsoft.bash:path", check_type=str): - self.tool_requires("msys2/cci.latest") + if self._is_windows and not self._windows_arm64: + # x86/x64: MinGW gendef + dlltool for import libraries. self.tool_requires("mingw-builds/15.1.0") def validate(self): @@ -64,33 +76,86 @@ def source(self): sha256="6ea768ec7002cedd422a39aea17704b888acaf794432aa5937cfc92fb6d80eb5", strip_root=True) def generate(self): + # Go + (MinGW or MSVC) on PATH for all Windows/Linux variants. + VirtualBuildEnv(self).generate() + + if self._is_windows and self._windows_arm64: + # MSVC CGO: activate vcvars only in build() (see run_msvc_go_build). + return + tc = AutotoolsToolchain(self) - tc.make_args = [ - "LIB_ARC=libamnezia_xray.a" - ] + if not self._is_windows: + tc.make_args = [ + "LIB_ARC=libamnezia_xray.a" + ] env = tc.environment() env.define("ARCH", self._goarch) env.define("GOARCH", self._goarch) env.define("GOOS", self._goos) - env.define("CGO_LDFLAGS", tc.ldflags) - env.define("CGO_CFLAGS", tc.cflags) if self._is_windows: env.define("OS", "windows") + env.define("CGO_ENABLED", "1") + env.define("CGO_LDFLAGS", tc.ldflags) + env.define("CGO_CFLAGS", tc.cflags) + else: + env.define("CGO_LDFLAGS", tc.ldflags) + env.define("CGO_CFLAGS", tc.cflags) tc.generate(env) def build(self): + if self._is_windows: + self._build_windows_native() + return with chdir(self, self.source_folder): autotools = Autotools(self) autotools.make() + @property + def _windows_artifact_dir(self): + """Keep Windows outputs under build_folder (not source_folder/llvm-mingw tree).""" + return os.path.join(self.build_folder, "build") + + def _build_windows_native(self): + """ + Windows: c-shared DLL. x86_64 uses MinGW gendef + dlltool; ARM64 uses llvm-mingw CGO. + """ + src = self.source_folder + bdir = self._windows_artifact_dir + os.makedirs(bdir, exist_ok=True) + dll_name = "amnezia_xray.dll" + dll_path = os.path.join(bdir, dll_name) + + if self._windows_arm64: + run_llvm_mingw_go_build(self, src, dll_path, self._goarch) + ensure_msvc_import_lib(self, bdir, dll_name, "amnezia_xray") + else: + self.run( + f'cd /d "{src}" && go build -ldflags=-w -o "{dll_path}" -buildmode=c-shared .', + env="conanbuild", + ) + if not self._windows_arm64: + self.run( + f'cd /d "{bdir}" && gendef {dll_name}', + env="conanbuild", + ) + self.run( + f'cd /d "{bdir}" && dlltool -d amnezia_xray.def -l amnezia_xray.lib -D {dll_name}', + env="conanbuild", + ) + + def_path = os.path.join(bdir, "amnezia_xray.def") + try: + if os.path.isfile(def_path): + os.remove(def_path) + except OSError: + pass + def _rename_header(self): if not self._is_windows: rename(self, os.path.join(self.package_folder, "include", "libamnezia_xray.h"), - os.path.join(self.package_folder, "include", "amnezia_xray.h")) + os.path.join(self.package_folder, "include", "amnezia_xray.h")) def _rename_libs(self): - # workaround of bad naming strategy in amnezia-xray-bindings - # TODO: change it and kick out the code below lib_dir = os.path.join(self.package_folder, "lib") for fname in os.listdir(lib_dir): if not fname.startswith("lib"): @@ -99,12 +164,23 @@ def _rename_libs(self): os.rename(src, dst) def package(self): - copy(self, "*.h", src=self.build_folder, dst=os.path.join(self.package_folder, "include")) - copy(self, "*.a", src=self.build_folder, dst=os.path.join(self.package_folder, "lib")) - copy(self, "*.lib", src=self.build_folder, dst=os.path.join(self.package_folder, "lib")) - copy(self, "*.dll", src=self.build_folder, dst=os.path.join(self.package_folder, "bin")) + if self._is_windows: + art = self._windows_artifact_dir + else: + art = os.path.join(self.build_folder, "build") + if not os.path.isdir(art): + art = self.build_folder + copy(self, "*.h", src=art, dst=os.path.join(self.package_folder, "include"), keep_path=False) + copy(self, "*.a", src=art, dst=os.path.join(self.package_folder, "lib"), keep_path=False) + copy(self, "*.lib", src=art, dst=os.path.join(self.package_folder, "lib"), keep_path=False) + copy(self, "*.dll", src=art, dst=os.path.join(self.package_folder, "bin"), keep_path=False) self._rename_header() def package_info(self): self.cpp_info.set_property("cmake_target_name", "amnezia::xray-bindings") - self.cpp_info.libs = collect_libs(self) + if self._is_windows: + self.cpp_info.libs = ["amnezia_xray"] + self.cpp_info.libdirs = ["lib"] + self.cpp_info.bindirs = ["bin"] + else: + self.cpp_info.libs = collect_libs(self) diff --git a/recipes/amnezia-xray-bindings/windows_cgo.py b/recipes/amnezia-xray-bindings/windows_cgo.py new file mode 100644 index 0000000000..aa2393bf3e --- /dev/null +++ b/recipes/amnezia-xray-bindings/windows_cgo.py @@ -0,0 +1,198 @@ +"""Windows CGO helpers: llvm-mingw for ARM64, MinGW via Conan for x86_64.""" + +import os +import re +import subprocess + +# https://github.com/mstorsjo/llvm-mingw/releases +LLVM_MINGW_VERSION = "20260519" +LLVM_MINGW_URL = ( + f"https://github.com/mstorsjo/llvm-mingw/releases/download/" + f"{LLVM_MINGW_VERSION}/llvm-mingw-{LLVM_MINGW_VERSION}-ucrt-aarch64.zip" +) +LLVM_MINGW_SHA256 = "7bb5e3c9964dc29555cababb55e69eea02a51e98e1240eb9513e6540c7bae0ea" + + +def is_windows_arm64(conanfile) -> bool: + return ( + str(conanfile.settings.get_safe("os", "")).startswith("Windows") + and str(conanfile.settings.arch) == "armv8" + ) + + +def msvc_machine(arch: str) -> str: + return {"x86_64": "X64", "armv8": "ARM64", "x86": "X86"}.get(arch, "ARM64") + + +def go_tool_exe(conanfile) -> str: + go = conanfile.dependencies.build["go"] + return os.path.join(go.package_folder, "bin", "go.exe") + + +def _vs_install_path(conanfile) -> str: + program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(program_files_x86, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + raise RuntimeError(f"{conanfile}: vswhere.exe not found at {vswhere}") + + completed = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if completed.returncode != 0 or not completed.stdout.strip(): + raise RuntimeError( + f"{conanfile}: vswhere failed ({completed.returncode}): " + f"{completed.stderr or completed.stdout}" + ) + return completed.stdout.strip().splitlines()[0] + + +def _vcvars_ver_flag(conanfile) -> str: + version = str(conanfile.settings.get_safe("compiler.version", "")) + if not version: + return "" + minor = {"192": "14.2", "193": "14.3", "194": "14.4", "195": "14.5"} + ver = minor.get(version) + return f" -vcvars_ver={ver}" if ver else "" + + +def _vcvars_arch_arg(target_arch: str) -> str: + import platform + + host = platform.machine().lower() + host_is_arm = host in ("arm64", "aarch64") + + if target_arch == "armv8": + return "arm64" if host_is_arm else "amd64_arm64" + if target_arch == "x86_64": + if host_is_arm: + return "arm64_x64" + return "amd64" if host in ("amd64", "x86_64") else "x86_amd64" + return "arm64" if host_is_arm else "amd64" + + +def msvc_vcvars_cmd(conanfile) -> str: + target_arch = str(conanfile.settings.arch) + install_path = _vs_install_path(conanfile) + vcvarsall = os.path.join(install_path, "VC", "Auxiliary", "Build", "vcvarsall.bat") + arch_arg = _vcvars_arch_arg(target_arch) + ver_flag = _vcvars_ver_flag(conanfile) + return f'call "{vcvarsall}" {arch_arg}{ver_flag}' + + +def _find_llvm_mingw_toolchain(root_dir: str) -> tuple[str, str]: + gcc_names = ( + "aarch64-w64-windows-gnu-gcc.exe", + "aarch64-w64-mingw32-gcc.exe", + ) + for gcc_name in gcc_names: + for root, _, files in os.walk(root_dir): + if gcc_name in files: + bindir = os.path.join(root, "") + return os.path.join(root, gcc_name), bindir + + for root, _, files in os.walk(root_dir): + for fname in files: + if fname.endswith("-gcc.exe") and "aarch64" in fname: + return os.path.join(root, fname), root + raise RuntimeError(f"llvm-mingw gcc not found under {root_dir}") + + +def ensure_llvm_mingw(conanfile) -> tuple[str, str]: + """Download and extract llvm-mingw (UCRT, aarch64). Returns (gcc.exe, bin_dir).""" + from conan.tools.files import download, unzip + + dest = os.path.join(conanfile.build_folder, "llvm-mingw") + ready = os.path.join(dest, ".extracted") + if not os.path.isfile(ready): + zip_path = os.path.join(conanfile.build_folder, "llvm-mingw.zip") + conanfile.output.info(f"Downloading llvm-mingw {LLVM_MINGW_VERSION} for Windows ARM64 CGO") + download(conanfile, LLVM_MINGW_URL, zip_path, sha256=LLVM_MINGW_SHA256) + unzip(conanfile, zip_path, dest) + with open(ready, "w", encoding="ascii") as marker: + marker.write(LLVM_MINGW_VERSION) + + gcc, bindir = _find_llvm_mingw_toolchain(dest) + conanfile.output.info(f"CGO compiler: {gcc}") + return gcc, bindir + + +def run_llvm_mingw_go_build( + conanfile, + src: str, + out: str, + goarch: str, + *, + goarm=None, + extra_args="-ldflags=-w -buildmode=c-shared", +) -> None: + """Build c-shared with llvm-mingw GCC (same approach as other Go Windows/arm64 projects).""" + gcc, bindir = ensure_llvm_mingw(conanfile) + go = go_tool_exe(conanfile) + goarm_set = f"&& set GOARM={goarm}" if goarm else "" + bindir_q = bindir.replace('"', '""') + gcc_q = gcc.replace('"', '""') + conanfile.run( + f'set "PATH={bindir_q};%PATH%"&& set CGO_ENABLED=1&& set GOOS=windows&& set GOARCH={goarch}{goarm_set}&& ' + f'set "CC={gcc_q}"&& set "CXX={gcc_q}"&& ' + f'"{go}" build -C "{src}" {extra_args} -o "{out}" .', + ) + + +def ensure_msvc_import_lib( + conanfile, + build_dir: str, + dll_name: str, + base_name: str, +) -> None: + """MSVC import library for linking the DLL from the main app (MSVC/Qt).""" + lib_path = os.path.join(build_dir, f"{base_name}.lib") + if os.path.isfile(lib_path): + return + + dll_path = os.path.join(build_dir, dll_name) + if not os.path.isfile(dll_path): + raise RuntimeError(f"{conanfile}: DLL not found: {dll_path}") + + machine = msvc_machine(str(conanfile.settings.arch)) + def_path = os.path.join(build_dir, f"{base_name}.def") + exports_txt = os.path.join(build_dir, f"{base_name}.exports.txt") + vc = msvc_vcvars_cmd(conanfile) + + conanfile.run(f'{vc} && dumpbin /nologo /exports "{dll_path}" > "{exports_txt}"') + + with open(exports_txt, encoding="utf-8", errors="replace") as exports_file: + text = exports_file.read() + + exports = [] + in_table = False + for line in text.splitlines(): + if "ordinal hint RVA" in line: + in_table = True + continue + if not in_table: + continue + match = re.match(r"\s+\d+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+)", line) + if match: + name = match.group(1) + if name != "[noname]": + exports.append(name) + + if not exports: + raise RuntimeError(f"{conanfile}: No exports found in {dll_path}") + + with open(def_path, "w", encoding="ascii") as def_file: + def_file.write(f"LIBRARY {base_name}\nEXPORTS\n") + for symbol in exports: + def_file.write(f" {symbol}\n") + + conanfile.run( + f'{vc} && lib /nologo /def:"{def_path}" /out:"{lib_path}" /machine:{machine}', + ) + + try: + os.remove(exports_txt) + except OSError: + pass diff --git a/recipes/awg-windows/cgo_cc.py b/recipes/awg-windows/cgo_cc.py new file mode 100644 index 0000000000..d8c77da605 --- /dev/null +++ b/recipes/awg-windows/cgo_cc.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""CGO compiler shim: clang-cl if present, otherwise MSVC cl with GCC-flag filtering.""" + +from __future__ import annotations + +import os +import subprocess +import sys + + +def _vs_install() -> str | None: + pf = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(pf, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + return None + r = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if r.returncode != 0 or not r.stdout.strip(): + return None + return r.stdout.strip().splitlines()[0] + + +def _find_clang_cl() -> str | None: + install = _vs_install() + if not install: + return None + llvm = os.path.join(install, "VC", "Tools", "Llvm") + if not os.path.isdir(llvm): + return None + preferred: list[str] = [] + fallback: list[str] = [] + for root, _, files in os.walk(llvm): + if "clang-cl.exe" not in files: + continue + path = os.path.join(root, "clang-cl.exe") + if "arm64" in root.lower(): + preferred.append(path) + else: + fallback.append(path) + if preferred: + return preferred[0] + if fallback: + return fallback[0] + direct = os.path.join(llvm, "bin", "clang-cl.exe") + return direct if os.path.isfile(direct) else None + + +def _gcc_args_to_msvc(args: list[str]) -> list[str]: + out: list[str] = [] + i = 0 + while i < len(args): + a = args[i] + if a == "-x" and i + 1 < len(args): + i += 2 + continue + if a == "-o" and i + 1 < len(args): + obj = args[i + 1] + if obj.endswith((".o", ".obj")): + out.append("/Fo" + obj) + else: + out.append("/Fe" + obj) + i += 2 + continue + if a.startswith("-W") or a.startswith("-f") or a.startswith("-d"): + i += 1 + continue + if a.startswith("-m") or a.startswith("-std=") or a in ("-pthread",): + i += 1 + continue + if a == "-O2": + out.append("/O2") + i += 1 + continue + if a == "-O0": + out.append("/Od") + i += 1 + continue + if a == "-g": + out.append("/Zi") + i += 1 + continue + if a == "-c": + out.append("/c") + i += 1 + continue + if a.startswith("-D"): + out.append("/D" + a[2:]) + i += 1 + continue + if a.startswith("-I"): + out.append("/I" + a[2:]) + i += 1 + continue + if not a.startswith("-"): + out.append(a) + i += 1 + return out + + +def main() -> int: + args = sys.argv[1:] + clang = _find_clang_cl() + if clang: + return subprocess.call([clang, *args]) + + msvc = _gcc_args_to_msvc(args) + return subprocess.call(["cl", *msvc]) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/recipes/awg-windows/cgo_cl_shim.cmd b/recipes/awg-windows/cgo_cl_shim.cmd new file mode 100644 index 0000000000..e90917c8ca --- /dev/null +++ b/recipes/awg-windows/cgo_cl_shim.cmd @@ -0,0 +1,37 @@ +@echo off +REM CGO driver for MSVC cl: drop GCC-only flags (-Werror, -f*, -d*, -m*, etc.). +setlocal EnableDelayedExpansion +set "_args=" +:argloop +if "%~1"=="" goto invoke +set "_a=%~1" +if /i "!_a!"=="-Werror" goto next +if /i "!_a!"=="-Wall" goto next +if /i "!_a!"=="-Wno-error" goto next +if "!_a:~0,2!"=="-W" goto next +if "!_a:~0,2!"=="-f" goto next +if "!_a:~0,2!"=="-d" goto next +if "!_a:~0,2!"=="-m" goto next +if "!_a:~0,5!"=="-std=" goto next +if /i "!_a!"=="-pthread" goto next +if /i "!_a!"=="-O2" (set "_args=!_args! /O2") & goto next +if /i "!_a!"=="-O0" (set "_args=!_args! /Od") & goto next +if /i "!_a!"=="-g" (set "_args=!_args! /Zi") & goto next +if /i "!_a!"=="-c" (set "_args=!_args! /c") & goto next +if /i "!_a!"=="-x" (shift & goto next) +if /i "%~1"=="c" goto next +if "!_a:~0,2!"=="-D" (set "_args=!_args! /D!_a:~2!") & goto next +if "!_a:~0,2!"=="-I" (set "_args=!_args! /I!_a:~2!") & goto next +if /i "!_a!"=="-o" ( + set "_o=%~2" + if /i "!_o:~-2!"==".o" set "_args=!_args! /Fo!_o!" + shift + goto next +) +if not "!_a:~0,1!"=="-" set "_args=!_args! "!_a!"" +:next +shift +goto argloop +:invoke +cl !_args! +exit /b !ERRORLEVEL! diff --git a/recipes/awg-windows/conanfile.py b/recipes/awg-windows/conanfile.py index 49632d2769..e2429d1fde 100644 --- a/recipes/awg-windows/conanfile.py +++ b/recipes/awg-windows/conanfile.py @@ -3,13 +3,23 @@ from conan.errors import ConanInvalidConfiguration from conan.tools.files import get, copy, chdir from conan.tools.gnu import AutotoolsToolchain +from conan.tools.env import Environment, VirtualBuildEnv import os +import sys + +_recipe_dir = os.path.dirname(os.path.abspath(__file__)) +if _recipe_dir not in sys.path: + sys.path.insert(0, _recipe_dir) + +from windows_cgo import is_windows_arm64, run_llvm_mingw_go_build + class AwgWindows(ConanFile): name = "awg-windows" version = "0.1.8" - settings = "os", "arch" + settings = "os", "arch", "compiler", "build_type" + exports = "windows_cgo.py" @property def _goarm(self): @@ -22,7 +32,7 @@ def _goarm(self): "armv7s": "7", "armv7k": "7", }.get(str(self.settings.arch)) - + @property def _goarch(self): return { @@ -41,9 +51,18 @@ def _goarch(self): "arm64ec": "arm64" }.get(str(self.settings.arch)) + @property + def _windows_arm64(self): + return is_windows_arm64(self) + def layout(self): basic_layout(self) + def configure(self): + if not self._windows_arm64: + self.settings.rm_safe("compiler") + self.settings.rm_safe("build_type") + def validate(self): if not str(self.settings.os).startswith("Windows"): raise ConanInvalidConfiguration( @@ -55,7 +74,8 @@ def validate(self): ) def build_requirements(self): - self.tool_requires("mingw-builds/15.1.0") + if not self._windows_arm64: + self.tool_requires("mingw-builds/15.1.0") self.tool_requires("go/1.26.0") def requirements(self): @@ -64,8 +84,19 @@ def requirements(self): def source(self): get(self, f"https://github.com/amnezia-vpn/amneziawg-windows/archive/refs/tags/v{self.version}.zip", sha256="1de472832b332515c96cdf14ea887edde42ed7ad173675280c51baa9a3ef62f2", strip_root=True) - + def generate(self): + VirtualBuildEnv(self).generate() + if self._windows_arm64: + return + + env = Environment() + env.define("GOOS", "windows") + if self._goarm: + env.define("GOARM", self._goarm) + env.define("GOARCH", self._goarch) + env.define("CGO_ENABLED", "1") + tc = AutotoolsToolchain(self) tc.extra_cflags = [ "-Wall", @@ -73,25 +104,36 @@ def generate(self): "-Wno-switch", "-DWINVER=0x0601" ] - tc.extra_ldflags = [ + tc.extra_ldflags = [ "-Wl,--dynamicbase", "-Wl,--nxcompat", "-Wl,--export-all-symbols", "-Wl,--high-entropy-va" ] - env = tc.environment() - env.define("GOOS", "windows") - if self._goarm: - env.define("GOARM", self._goarm) - env.define("GOARCH", self._goarch) - env.define("CGO_ENABLED", "1") env.define("CGO_LDFLAGS", tc.ldflags) env.define("CGO_CFLAGS", tc.cflags) - tc.generate(env) + + env.vars(self, scope="build").save_script("conanbuild") def build(self): + out_dll = os.path.join(self.build_folder, "tunnel.dll") + if self._windows_arm64: + run_llvm_mingw_go_build( + self, + self.source_folder, + out_dll, + self._goarch, + goarm=self._goarm, + extra_args='-buildmode c-shared -ldflags="-w -s" -trimpath -v', + ) + return + with chdir(self, self.source_folder): - self.run(f'go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "{os.path.join(self.build_folder, "tunnel.dll")}"') + self.run( + f'go build -buildmode c-shared -ldflags="-w -s" -trimpath -v ' + f'-o "{out_dll}"', + env="conanbuild", + ) def package(self): copy(self, "tunnel.dll", src=self.build_folder, dst=os.path.join(self.package_folder, "bin")) diff --git a/recipes/awg-windows/windows_cgo.py b/recipes/awg-windows/windows_cgo.py new file mode 100644 index 0000000000..aa2393bf3e --- /dev/null +++ b/recipes/awg-windows/windows_cgo.py @@ -0,0 +1,198 @@ +"""Windows CGO helpers: llvm-mingw for ARM64, MinGW via Conan for x86_64.""" + +import os +import re +import subprocess + +# https://github.com/mstorsjo/llvm-mingw/releases +LLVM_MINGW_VERSION = "20260519" +LLVM_MINGW_URL = ( + f"https://github.com/mstorsjo/llvm-mingw/releases/download/" + f"{LLVM_MINGW_VERSION}/llvm-mingw-{LLVM_MINGW_VERSION}-ucrt-aarch64.zip" +) +LLVM_MINGW_SHA256 = "7bb5e3c9964dc29555cababb55e69eea02a51e98e1240eb9513e6540c7bae0ea" + + +def is_windows_arm64(conanfile) -> bool: + return ( + str(conanfile.settings.get_safe("os", "")).startswith("Windows") + and str(conanfile.settings.arch) == "armv8" + ) + + +def msvc_machine(arch: str) -> str: + return {"x86_64": "X64", "armv8": "ARM64", "x86": "X86"}.get(arch, "ARM64") + + +def go_tool_exe(conanfile) -> str: + go = conanfile.dependencies.build["go"] + return os.path.join(go.package_folder, "bin", "go.exe") + + +def _vs_install_path(conanfile) -> str: + program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + vswhere = os.path.join(program_files_x86, "Microsoft Visual Studio", "Installer", "vswhere.exe") + if not os.path.isfile(vswhere): + raise RuntimeError(f"{conanfile}: vswhere.exe not found at {vswhere}") + + completed = subprocess.run( + [vswhere, "-latest", "-products", "*", "-property", "installationPath"], + capture_output=True, + text=True, + check=False, + ) + if completed.returncode != 0 or not completed.stdout.strip(): + raise RuntimeError( + f"{conanfile}: vswhere failed ({completed.returncode}): " + f"{completed.stderr or completed.stdout}" + ) + return completed.stdout.strip().splitlines()[0] + + +def _vcvars_ver_flag(conanfile) -> str: + version = str(conanfile.settings.get_safe("compiler.version", "")) + if not version: + return "" + minor = {"192": "14.2", "193": "14.3", "194": "14.4", "195": "14.5"} + ver = minor.get(version) + return f" -vcvars_ver={ver}" if ver else "" + + +def _vcvars_arch_arg(target_arch: str) -> str: + import platform + + host = platform.machine().lower() + host_is_arm = host in ("arm64", "aarch64") + + if target_arch == "armv8": + return "arm64" if host_is_arm else "amd64_arm64" + if target_arch == "x86_64": + if host_is_arm: + return "arm64_x64" + return "amd64" if host in ("amd64", "x86_64") else "x86_amd64" + return "arm64" if host_is_arm else "amd64" + + +def msvc_vcvars_cmd(conanfile) -> str: + target_arch = str(conanfile.settings.arch) + install_path = _vs_install_path(conanfile) + vcvarsall = os.path.join(install_path, "VC", "Auxiliary", "Build", "vcvarsall.bat") + arch_arg = _vcvars_arch_arg(target_arch) + ver_flag = _vcvars_ver_flag(conanfile) + return f'call "{vcvarsall}" {arch_arg}{ver_flag}' + + +def _find_llvm_mingw_toolchain(root_dir: str) -> tuple[str, str]: + gcc_names = ( + "aarch64-w64-windows-gnu-gcc.exe", + "aarch64-w64-mingw32-gcc.exe", + ) + for gcc_name in gcc_names: + for root, _, files in os.walk(root_dir): + if gcc_name in files: + bindir = os.path.join(root, "") + return os.path.join(root, gcc_name), bindir + + for root, _, files in os.walk(root_dir): + for fname in files: + if fname.endswith("-gcc.exe") and "aarch64" in fname: + return os.path.join(root, fname), root + raise RuntimeError(f"llvm-mingw gcc not found under {root_dir}") + + +def ensure_llvm_mingw(conanfile) -> tuple[str, str]: + """Download and extract llvm-mingw (UCRT, aarch64). Returns (gcc.exe, bin_dir).""" + from conan.tools.files import download, unzip + + dest = os.path.join(conanfile.build_folder, "llvm-mingw") + ready = os.path.join(dest, ".extracted") + if not os.path.isfile(ready): + zip_path = os.path.join(conanfile.build_folder, "llvm-mingw.zip") + conanfile.output.info(f"Downloading llvm-mingw {LLVM_MINGW_VERSION} for Windows ARM64 CGO") + download(conanfile, LLVM_MINGW_URL, zip_path, sha256=LLVM_MINGW_SHA256) + unzip(conanfile, zip_path, dest) + with open(ready, "w", encoding="ascii") as marker: + marker.write(LLVM_MINGW_VERSION) + + gcc, bindir = _find_llvm_mingw_toolchain(dest) + conanfile.output.info(f"CGO compiler: {gcc}") + return gcc, bindir + + +def run_llvm_mingw_go_build( + conanfile, + src: str, + out: str, + goarch: str, + *, + goarm=None, + extra_args="-ldflags=-w -buildmode=c-shared", +) -> None: + """Build c-shared with llvm-mingw GCC (same approach as other Go Windows/arm64 projects).""" + gcc, bindir = ensure_llvm_mingw(conanfile) + go = go_tool_exe(conanfile) + goarm_set = f"&& set GOARM={goarm}" if goarm else "" + bindir_q = bindir.replace('"', '""') + gcc_q = gcc.replace('"', '""') + conanfile.run( + f'set "PATH={bindir_q};%PATH%"&& set CGO_ENABLED=1&& set GOOS=windows&& set GOARCH={goarch}{goarm_set}&& ' + f'set "CC={gcc_q}"&& set "CXX={gcc_q}"&& ' + f'"{go}" build -C "{src}" {extra_args} -o "{out}" .', + ) + + +def ensure_msvc_import_lib( + conanfile, + build_dir: str, + dll_name: str, + base_name: str, +) -> None: + """MSVC import library for linking the DLL from the main app (MSVC/Qt).""" + lib_path = os.path.join(build_dir, f"{base_name}.lib") + if os.path.isfile(lib_path): + return + + dll_path = os.path.join(build_dir, dll_name) + if not os.path.isfile(dll_path): + raise RuntimeError(f"{conanfile}: DLL not found: {dll_path}") + + machine = msvc_machine(str(conanfile.settings.arch)) + def_path = os.path.join(build_dir, f"{base_name}.def") + exports_txt = os.path.join(build_dir, f"{base_name}.exports.txt") + vc = msvc_vcvars_cmd(conanfile) + + conanfile.run(f'{vc} && dumpbin /nologo /exports "{dll_path}" > "{exports_txt}"') + + with open(exports_txt, encoding="utf-8", errors="replace") as exports_file: + text = exports_file.read() + + exports = [] + in_table = False + for line in text.splitlines(): + if "ordinal hint RVA" in line: + in_table = True + continue + if not in_table: + continue + match = re.match(r"\s+\d+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+(\S+)", line) + if match: + name = match.group(1) + if name != "[noname]": + exports.append(name) + + if not exports: + raise RuntimeError(f"{conanfile}: No exports found in {dll_path}") + + with open(def_path, "w", encoding="ascii") as def_file: + def_file.write(f"LIBRARY {base_name}\nEXPORTS\n") + for symbol in exports: + def_file.write(f" {symbol}\n") + + conanfile.run( + f'{vc} && lib /nologo /def:"{def_path}" /out:"{lib_path}" /machine:{machine}', + ) + + try: + os.remove(exports_txt) + except OSError: + pass diff --git a/recipes/go/conandata.yml b/recipes/go/conandata.yml index 1507e54bef..e29013312a 100644 --- a/recipes/go/conandata.yml +++ b/recipes/go/conandata.yml @@ -23,4 +23,7 @@ sources: sha256: "50674f3d6a071fa1a4c1d76dc37fafa0330df87d84087a262fee020da5396b6b" x86_64: url: "https://go.dev/dl/go1.26.0.windows-amd64.zip" - sha256: "9bbe0fc64236b2b51f6255c05c4232532b8ecc0e6d2e00950bd3021d8a4d07d4" \ No newline at end of file + sha256: "9bbe0fc64236b2b51f6255c05c4232532b8ecc0e6d2e00950bd3021d8a4d07d4" + armv8: + url: "https://go.dev/dl/go1.26.0.windows-arm64.zip" + sha256: "73bdbb9f64aa152758024485c5243a1098182bb741fcc603b6fb664ee5e0fe35" \ No newline at end of file diff --git a/recipes/libssh/conanfile.py b/recipes/libssh/conanfile.py index 30f71a67b1..036c906abb 100644 --- a/recipes/libssh/conanfile.py +++ b/recipes/libssh/conanfile.py @@ -45,6 +45,8 @@ def configure(self): self.options.rm_safe("fPIC") self.settings.rm_safe("compiler.libcxx") self.settings.rm_safe("compiler.cppstd") + if self.settings.os == "Windows" and str(self.settings.arch) == "armv8": + self.options["openssl/*"].no_asm = True def layout(self): cmake_layout(self, src_folder="src") diff --git a/recipes/openvpn/conanfile.py b/recipes/openvpn/conanfile.py index d568e84dfa..fdfd11cb08 100644 --- a/recipes/openvpn/conanfile.py +++ b/recipes/openvpn/conanfile.py @@ -1,5 +1,5 @@ from conan import ConanFile -from conan.tools.files import get, copy, replace_in_file, apply_conandata_patches, export_conandata_patches +from conan.tools.files import get, copy, replace_in_file from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps, PkgConfigDeps from conan.tools.layout import basic_layout from conan.tools.cmake import cmake_layout, CMakeToolchain, CMake, CMakeDeps @@ -17,7 +17,6 @@ def _is_windows(self): return str(self.settings.os).startswith("Windows") def export_sources(self): - export_conandata_patches(self) copy(self, "*applink.c", src=self.recipe_folder, dst=self.export_sources_folder) def layout(self): @@ -37,7 +36,8 @@ def build_requirements(self): self.tool_requires("pkgconf/2.5.1") def requirements(self): - self.requires("openssl/3.6.1", visible=False) + openssl_opts = {"no_asm": True} if self._is_windows and str(self.settings.arch) == "armv8" else {} + self.requires("openssl/3.6.1", visible=False, options=openssl_opts) self.requires("lz4/1.10.0", visible=False) self.requires("lzo/2.10", visible=False) if self.settings.os == "Linux": @@ -52,10 +52,15 @@ def source(self): ) def _patch_sources(self): - replace_in_file(self, - os.path.join(self.source_folder, "CMakeLists.txt"), - "/Qspectre", - "" + cmakelists = os.path.join(self.source_folder, "CMakeLists.txt") + replace_in_file(self, cmakelists, "/Qspectre", "") + # Empty CMAKE_GENERATOR_PLATFORM (Ninja + MSVC) breaks + # `if (${CMAKE_GENERATOR_PLATFORM} STREQUAL ...)` → invalid `if (STREQUAL ...)`. + replace_in_file( + self, + cmakelists, + 'if (${CMAKE_GENERATOR_PLATFORM} STREQUAL "x64" OR ${CMAKE_GENERATOR_PLATFORM} STREQUAL "x86")', + 'if (CMAKE_GENERATOR_PLATFORM STREQUAL "x64" OR CMAKE_GENERATOR_PLATFORM STREQUAL "x86" OR CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")', ) def generate(self): @@ -73,6 +78,8 @@ def generate(self): tc.extra_cxxflags = [ f"-I{tap_include_path}", f"-I{applink_include_path}" ] tc.cache_variables["BUILD_TESTING"] = False tc.cache_variables["ENABLE_PKCS11"] = False + # Upstream defaults USE_WERROR=ON → /WX on MSVC; breaks on newer toolchains (warnings from deps / OpenVPN). + tc.cache_variables["USE_WERROR"] = False tc.generate() deps = CMakeDeps(self) deps.generate() @@ -85,7 +92,6 @@ def generate(self): deps.generate() def build(self): - apply_conandata_patches(self) if self._is_windows: cmake = CMake(self) cmake.configure() diff --git a/recipes/tun2socks/conanfile.py b/recipes/tun2socks/conanfile.py index 417bcfd101..ce79a0a7f9 100644 --- a/recipes/tun2socks/conanfile.py +++ b/recipes/tun2socks/conanfile.py @@ -2,8 +2,7 @@ from conan.tools.layout import basic_layout from conan.tools.files import get, copy, chdir from conan.errors import ConanInvalidConfiguration -from conan.tools.gnu import Autotools, AutotoolsToolchain -from conan.tools.env import Environment +from conan.tools.env import VirtualBuildEnv import os @@ -28,11 +27,11 @@ def _goarch(self): "x86_64": "amd64", "armv8": "arm64" }.get(str(self.settings.arch)) - + @property def _is_windows(self): return str(self.settings.get_safe("os")).startswith("Windows") - + @property def _ext(self): return ".exe" if self._is_windows else "" @@ -47,12 +46,8 @@ def validate(self): ) def build_requirements(self): + # Upstream Makefile: CGO_ENABLED=0 — pure Go; no MSYS2 / MinGW / make on Windows. self.tool_requires("go/1.26.0") - if self._is_windows: - self.win_bash = True - if not self.conf.get("tools.microsoft.bash:path", check_type=str): - self.tool_requires("msys2/cci.latest") - self.tool_requires("mingw-builds/15.1.0") def requirements(self): if self._is_windows: @@ -64,25 +59,33 @@ def source(self): ) def generate(self): - tc = AutotoolsToolchain(self) - env = tc.environment() - env.define("LDFLAGS", "") - env.define("CGO_LDFLAGS", tc.ldflags) - env.define("CGO_CFLAGS", tc.cflags) - env.define("GOOS", self._goos) - env.define("GOARCH", self._goarch) - tc.generate(env) + VirtualBuildEnv(self).generate() def build(self): + # Makefile default: BUILD_DIR=build, CGO_ENABLED=0, target tun2socks -> build/tun2socks(.exe) + out_dir = os.path.join(self.source_folder, "build") + os.makedirs(out_dir, exist_ok=True) + out_name = f"tun2socks{self._ext}" + out_path = os.path.join(out_dir, out_name) + goos = self._goos + goarch = self._goarch with chdir(self, self.source_folder): - at = Autotools(self) - at.make("tun2socks") + if self._is_windows: + self.run( + f'set "GOOS={goos}"&& set "GOARCH={goarch}"&& set "CGO_ENABLED=0"&& set "GO111MODULE=on"&& ' + f'go build -trimpath -ldflags="-w -s -buildid=" -o "{out_path}" .', + env="conanbuild", + ) + else: + self.run( + f'GOOS={goos} GOARCH={goarch} CGO_ENABLED=0 GO111MODULE=on ' + f'go build -trimpath -ldflags="-w -s -buildid=" -o "{out_path}" .', + env="conanbuild", + ) def package(self): - copy(self, "tun2socks", src=self.build_folder, dst=self.package_folder) - if self._is_windows: - with chdir(self, self.package_folder): - os.rename(src="tun2socks", dst="tun2socks.exe") + out_dir = os.path.join(self.source_folder, "build") + copy(self, f"tun2socks{self._ext}", src=out_dir, dst=self.package_folder) def package_info(self): self.cpp_info.exe = True diff --git a/recipes/win-split-tunnel/conanfile.py b/recipes/win-split-tunnel/conanfile.py index e089106706..10dbfaeb8b 100644 --- a/recipes/win-split-tunnel/conanfile.py +++ b/recipes/win-split-tunnel/conanfile.py @@ -29,18 +29,33 @@ def validate(self): raise ConanInvalidConfiguration( f"{self.name} v{self.version} supports only Windows" ) + if not self._arch: + raise ConanInvalidConfiguration( + f"{self.name} v{self.version} does not support {self.settings.arch}" + ) + + def _prebuilt_files(self): + # Mullvad binaries per target triple (ff0e3746 pin in URL below). + by_arch = { + "x86_64": [ + ("mullvad-split-tunnel.cat", "9bbd10b95a2cf2226b266a52077300c280f7782def69ebbeb892bb60505d9a5f"), + ("mullvad-split-tunnel.inf", "4ec3c2bdefc2a1df9e4e19cd4301b5774624ea07e61add588067b16f8925f5d7"), + ("mullvad-split-tunnel.pdb", "ee0e246b18a3bfecfc27104b40f9492ffd2b735582870fbd572f93d55e8e9eaa"), + ("mullvad-split-tunnel.sys", "4056b22d08115c1a83bc2cafa17de0bb17db3705eac382de77fd7935eeff7edb"), + ], + "aarch64": [ + ("mullvad-split-tunnel.cat", "d6c27e43b6aaca989d59ca63c5b53f1876d3dbe9a189d654ee0f49c5b37b8a44"), + ("mullvad-split-tunnel.inf", "acd2e65ea51ad2c76725c3a8f3afd65f0e5b2a7e1c5410371db67a3fb0f3cb72"), + ("mullvad-split-tunnel.pdb", "a10382fd0534aec18a998b0b6068a47a1111f42da196240ddf066f1cdb475d92"), + ("mullvad-split-tunnel.sys", "dd5befd1537e6f1e90fe2a0139c8d91deda41faaef84a234b780ffc49037b9be"), + ], + } + return by_arch[self._arch] def build(self): url = f"https://raw.githubusercontent.com/mullvad/mullvadvpn-app-binaries/ff0e3746c89a04314377cffeb52faaa976413a69/{self._target}/split-tunnel" - files = [ - ("mullvad-split-tunnel.cat", "9bbd10b95a2cf2226b266a52077300c280f7782def69ebbeb892bb60505d9a5f"), - ("mullvad-split-tunnel.inf", "4ec3c2bdefc2a1df9e4e19cd4301b5774624ea07e61add588067b16f8925f5d7"), - ("mullvad-split-tunnel.pdb", "ee0e246b18a3bfecfc27104b40f9492ffd2b735582870fbd572f93d55e8e9eaa"), - ("mullvad-split-tunnel.sys", "4056b22d08115c1a83bc2cafa17de0bb17db3705eac382de77fd7935eeff7edb"), - ] - - for name, sha256 in files: + for name, sha256 in self._prebuilt_files(): download(self, f"{url}/{name}", os.path.join("prebuilt", name), sha256=sha256) def package(self):