From afecedb9fb62f539ba74c49d523e047e930c7c71 Mon Sep 17 00:00:00 2001 From: Selim Sandal <49725809+selimsandal@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:58:08 +0000 Subject: [PATCH] Add MinGW-w64 cross-compilation support for Windows Add CMake toolchain file and module to enable cross-compiling openFPGALoader for Windows from Linux using MinGW-w64. Features: - Automatic download and build of libusb and libftdi dependencies - CMake toolchain file for x86_64-w64-mingw32 - Fully static executable (only depends on Windows system DLLs) - Works on Debian/Ubuntu, Fedora/RHEL/Rocky Linux Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-x86_64-w64-mingw32.cmake .. cmake --build . --parallel Co-Authored-By: Claude Opus 4.5 --- CMakeLists.txt | 45 +++++- cmake/Modules/CrossCompileWindows.cmake | 198 +++++++++++++++++++++++ cmake/Toolchain-x86_64-w64-mingw32.cmake | 39 +++++ doc/guide/install.rst | 64 ++++++++ 4 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 cmake/Modules/CrossCompileWindows.cmake create mode 100644 cmake/Toolchain-x86_64-w64-mingw32.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb01b4..6dd5f8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,12 @@ cmake_minimum_required(VERSION 3.10) project(openFPGALoader VERSION "1.0.0" LANGUAGES CXX) add_definitions(-DVERSION=\"v${PROJECT_VERSION}\") +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(windows_crosscompile ON) +else() + set(windows_crosscompile OFF) +endif() + #################################################################################################### # Generics Options #################################################################################################### @@ -30,7 +36,12 @@ option(ENABLE_CABLE_ALL "Enable all cables" ON option(ENABLE_ANLOGIC_CABLE "enable Anlogic cable (requires libUSB)" ${ENABLE_CABLE_ALL}) option(ENABLE_CH347 "enable CH347 cable (requires libUSB)" ${ENABLE_CABLE_ALL}) -option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ${ENABLE_CABLE_ALL}) +# CMSIS-DAP requires hidapi which is complex to cross-compile, disable by default for cross-compilation +if (NOT windows_crosscompile) + option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" ${ENABLE_CABLE_ALL}) +else() + option(ENABLE_CMSISDAP "enable cmsis DAP interface (requires hidapi)" OFF) +endif() option(ENABLE_DIRTYJTAG "enable dirtyJtag cable (requires libUSB)" ${ENABLE_CABLE_ALL}) option(ENABLE_ESP_USB "enable ESP32S3 cable (requires libUSB)" ${ENABLE_CABLE_ALL}) option(ENABLE_JLINK "enable JLink cable (requires libUSB)" ${ENABLE_CABLE_ALL}) @@ -60,6 +71,38 @@ else() set(ENABLE_XILINX_VIRTUAL_CABLE OFF) endif() +#################################################################################################### +# Cross-compilation support +#################################################################################################### + +# Detect cross-compilation for Windows from Linux/macOS +if(windows_crosscompile) + set(CROSS_COMPILING_WINDOWS TRUE) + message(STATUS "Cross-compiling for Windows from ${CMAKE_HOST_SYSTEM_NAME}") + + # Option to automatically download and build cross-compile dependencies + option(CROSS_COMPILE_DEPS "Download and build Windows dependencies for cross-compilation" ON) + + if(CROSS_COMPILE_DEPS) + # Include the cross-compilation module + list(INSERT CMAKE_MODULE_PATH 0 ${PROJECT_SOURCE_DIR}/cmake/Modules) + include(CrossCompileWindows) + + # Setup the dependencies (downloads libusb, builds libftdi) + setup_windows_cross_compile_deps() + + # Update pkg-config path for the dependencies + set(ENV{PKG_CONFIG_PATH} "${CROSS_DEPS_INSTALL_DIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + + # Add the cross-compiled dependencies to the search path + list(APPEND CMAKE_PREFIX_PATH ${CROSS_DEPS_INSTALL_DIR}) + include_directories(${CROSS_DEPS_INSTALL_DIR}/include) + link_directories(${CROSS_DEPS_INSTALL_DIR}/lib) + endif() +else() + set(CROSS_COMPILING_WINDOWS FALSE) +endif() + #################################################################################################### # VENDORS Options #################################################################################################### diff --git a/cmake/Modules/CrossCompileWindows.cmake b/cmake/Modules/CrossCompileWindows.cmake new file mode 100644 index 0000000..a5a7bcc --- /dev/null +++ b/cmake/Modules/CrossCompileWindows.cmake @@ -0,0 +1,198 @@ +# CrossCompileWindows.cmake +# Downloads and builds dependencies for cross-compiling openFPGALoader to Windows. + +include(ExternalProject) +include(FetchContent) + +set(LIBUSB_VERSION "1.0.27" CACHE STRING "libusb version") +set(LIBFTDI_VERSION "1.5" CACHE STRING "libftdi version") + +set(LIBUSB_URL "https://github.com/libusb/libusb/releases/download/v${LIBUSB_VERSION}/libusb-${LIBUSB_VERSION}.7z") +set(LIBFTDI_URL "https://www.intra2net.com/en/developer/libftdi/download/libftdi1-${LIBFTDI_VERSION}.tar.bz2") + +if(NOT DEFINED CROSS_DEPS_DIR) + set(CROSS_DEPS_DIR "${CMAKE_BINARY_DIR}/cross-deps") +endif() + +set(CROSS_DEPS_INSTALL_DIR "${CROSS_DEPS_DIR}/install") +set(CROSS_DEPS_BUILD_DIR "${CROSS_DEPS_DIR}/build") +set(CROSS_DEPS_SRC_DIR "${CROSS_DEPS_DIR}/src") + +file(MAKE_DIRECTORY ${CROSS_DEPS_DIR}) +file(MAKE_DIRECTORY ${CROSS_DEPS_INSTALL_DIR}) +file(MAKE_DIRECTORY ${CROSS_DEPS_INSTALL_DIR}/include) +file(MAKE_DIRECTORY ${CROSS_DEPS_INSTALL_DIR}/lib) +file(MAKE_DIRECTORY ${CROSS_DEPS_INSTALL_DIR}/lib/pkgconfig) +file(MAKE_DIRECTORY ${CROSS_DEPS_BUILD_DIR}) +file(MAKE_DIRECTORY ${CROSS_DEPS_SRC_DIR}) + +find_program(SEVENZIP_EXECUTABLE NAMES 7z 7za p7zip) +if(NOT SEVENZIP_EXECUTABLE) + message(FATAL_ERROR "7z/p7zip not found. Please install p7zip or 7zip.") +endif() + +function(setup_libusb_windows) + set(LIBUSB_ARCHIVE "${CROSS_DEPS_SRC_DIR}/libusb-${LIBUSB_VERSION}.7z") + set(LIBUSB_EXTRACT_DIR "${CROSS_DEPS_SRC_DIR}/libusb-${LIBUSB_VERSION}") + + if(NOT EXISTS ${LIBUSB_ARCHIVE}) + message(STATUS "Downloading libusb ${LIBUSB_VERSION}...") + file(DOWNLOAD ${LIBUSB_URL} ${LIBUSB_ARCHIVE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(NOT STATUS_CODE EQUAL 0) + message(FATAL_ERROR "Failed to download libusb: ${DOWNLOAD_STATUS}") + endif() + endif() + + if(NOT EXISTS "${LIBUSB_EXTRACT_DIR}/MinGW64") + message(STATUS "Extracting libusb...") + file(MAKE_DIRECTORY ${LIBUSB_EXTRACT_DIR}) + execute_process( + COMMAND ${SEVENZIP_EXECUTABLE} x -y -o${LIBUSB_EXTRACT_DIR} ${LIBUSB_ARCHIVE} + WORKING_DIRECTORY ${CROSS_DEPS_SRC_DIR} + RESULT_VARIABLE EXTRACT_RESULT + ) + if(NOT EXTRACT_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to extract libusb") + endif() + endif() + + message(STATUS "Installing libusb headers and libraries...") + file(MAKE_DIRECTORY "${CROSS_DEPS_INSTALL_DIR}/include/libusb-1.0") + file(COPY "${LIBUSB_EXTRACT_DIR}/include/libusb.h" + DESTINATION "${CROSS_DEPS_INSTALL_DIR}/include/libusb-1.0/") + file(COPY "${LIBUSB_EXTRACT_DIR}/MinGW64/static/libusb-1.0.a" + DESTINATION "${CROSS_DEPS_INSTALL_DIR}/lib/") + + file(WRITE "${CROSS_DEPS_INSTALL_DIR}/lib/pkgconfig/libusb-1.0.pc" +"prefix=${CROSS_DEPS_INSTALL_DIR} +exec_prefix=\${prefix} +libdir=\${exec_prefix}/lib +includedir=\${prefix}/include/libusb-1.0 + +Name: libusb-1.0 +Description: C API for USB device access from Windows +Version: ${LIBUSB_VERSION} +Libs: -L\${libdir} -lusb-1.0 +Cflags: -I\${includedir} +") + + set(LIBUSB_FOUND TRUE PARENT_SCOPE) + set(LIBUSB_INCLUDE_DIRS "${CROSS_DEPS_INSTALL_DIR}/include/libusb-1.0" PARENT_SCOPE) + set(LIBUSB_LIBRARIES "${CROSS_DEPS_INSTALL_DIR}/lib/libusb-1.0.a" PARENT_SCOPE) + set(LIBUSB_VERSION ${LIBUSB_VERSION} PARENT_SCOPE) +endfunction() + +function(setup_libftdi_windows) + set(LIBFTDI_ARCHIVE "${CROSS_DEPS_SRC_DIR}/libftdi1-${LIBFTDI_VERSION}.tar.bz2") + set(LIBFTDI_SRC_DIR "${CROSS_DEPS_SRC_DIR}/libftdi1-${LIBFTDI_VERSION}") + set(LIBFTDI_BUILD_DIR "${CROSS_DEPS_BUILD_DIR}/libftdi1") + + if(NOT EXISTS ${LIBFTDI_ARCHIVE}) + message(STATUS "Downloading libftdi ${LIBFTDI_VERSION}...") + file(DOWNLOAD ${LIBFTDI_URL} ${LIBFTDI_ARCHIVE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(NOT STATUS_CODE EQUAL 0) + message(FATAL_ERROR "Failed to download libftdi: ${DOWNLOAD_STATUS}") + endif() + endif() + + if(NOT EXISTS ${LIBFTDI_SRC_DIR}) + message(STATUS "Extracting libftdi...") + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xjf ${LIBFTDI_ARCHIVE} + WORKING_DIRECTORY ${CROSS_DEPS_SRC_DIR} + RESULT_VARIABLE EXTRACT_RESULT + ) + if(NOT EXTRACT_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to extract libftdi") + endif() + endif() + + if(NOT EXISTS "${CROSS_DEPS_INSTALL_DIR}/lib/libftdi1.a") + message(STATUS "Building libftdi for Windows...") + file(MAKE_DIRECTORY ${LIBFTDI_BUILD_DIR}) + + execute_process( + COMMAND ${CMAKE_COMMAND} + -DCMAKE_SYSTEM_NAME=Windows + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_INSTALL_PREFIX=${CROSS_DEPS_INSTALL_DIR} + -DCMAKE_PREFIX_PATH=${CROSS_DEPS_INSTALL_DIR} + -DLIBUSB_INCLUDE_DIRS=${CROSS_DEPS_INSTALL_DIR}/include/libusb-1.0 + -DLIBUSB_LIBRARIES=${CROSS_DEPS_INSTALL_DIR}/lib/libusb-1.0.a + -DFTDIPP=OFF -DBUILD_TESTS=OFF -DDOCUMENTATION=OFF + -DEXAMPLES=OFF -DFTDI_EEPROM=OFF -DPYTHON_BINDINGS=OFF + -DSTATICLIBS=ON + ${LIBFTDI_SRC_DIR} + WORKING_DIRECTORY ${LIBFTDI_BUILD_DIR} + RESULT_VARIABLE CONFIG_RESULT + ) + if(NOT CONFIG_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to configure libftdi") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} --build . --parallel + WORKING_DIRECTORY ${LIBFTDI_BUILD_DIR} + RESULT_VARIABLE BUILD_RESULT + ) + if(NOT BUILD_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to build libftdi") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} --install . + WORKING_DIRECTORY ${LIBFTDI_BUILD_DIR} + RESULT_VARIABLE INSTALL_RESULT + ) + if(NOT INSTALL_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to install libftdi") + endif() + + file(WRITE "${CROSS_DEPS_INSTALL_DIR}/lib/pkgconfig/libftdi1.pc" +"prefix=${CROSS_DEPS_INSTALL_DIR} +exec_prefix=\${prefix} +libdir=\${prefix}/lib +includedir=\${prefix}/include/libftdi1 + +Name: libftdi1 +Description: Library to program and control the FTDI USB controller +Requires: libusb-1.0 +Version: ${LIBFTDI_VERSION} +Libs: -L\${libdir} -lftdi1 +Cflags: -I\${includedir} +") + endif() + + set(LIBFTDI_FOUND TRUE PARENT_SCOPE) + set(LIBFTDI_INCLUDE_DIRS "${CROSS_DEPS_INSTALL_DIR}/include/libftdi1" PARENT_SCOPE) + set(LIBFTDI_LIBRARIES "${CROSS_DEPS_INSTALL_DIR}/lib/libftdi1.a" PARENT_SCOPE) + set(LIBFTDI_VERSION ${LIBFTDI_VERSION} PARENT_SCOPE) +endfunction() + +function(setup_windows_cross_compile_deps) + message(STATUS "Setting up Windows cross-compilation dependencies...") + message(STATUS " Dependencies directory: ${CROSS_DEPS_DIR}") + message(STATUS " Install directory: ${CROSS_DEPS_INSTALL_DIR}") + + setup_libusb_windows() + setup_libftdi_windows() + + list(APPEND CMAKE_PREFIX_PATH ${CROSS_DEPS_INSTALL_DIR}) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) + set(ENV{PKG_CONFIG_PATH} "${CROSS_DEPS_INSTALL_DIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + + set(LIBUSB_FOUND TRUE PARENT_SCOPE) + set(LIBUSB_INCLUDE_DIRS "${CROSS_DEPS_INSTALL_DIR}/include/libusb-1.0" PARENT_SCOPE) + set(LIBUSB_LIBRARIES "${CROSS_DEPS_INSTALL_DIR}/lib/libusb-1.0.a" PARENT_SCOPE) + + set(LIBFTDI_FOUND TRUE PARENT_SCOPE) + set(LIBFTDI_INCLUDE_DIRS "${CROSS_DEPS_INSTALL_DIR}/include/libftdi1" PARENT_SCOPE) + set(LIBFTDI_LIBRARIES "${CROSS_DEPS_INSTALL_DIR}/lib/libftdi1.a" PARENT_SCOPE) + + set(CROSS_DEPS_INSTALL_DIR ${CROSS_DEPS_INSTALL_DIR} PARENT_SCOPE) + + message(STATUS "Windows cross-compilation dependencies ready!") +endfunction() diff --git a/cmake/Toolchain-x86_64-w64-mingw32.cmake b/cmake/Toolchain-x86_64-w64-mingw32.cmake new file mode 100644 index 0000000..c50d94e --- /dev/null +++ b/cmake/Toolchain-x86_64-w64-mingw32.cmake @@ -0,0 +1,39 @@ +# CMake Toolchain file for cross-compiling to Windows x64 using MinGW-w64 +# Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-x86_64-w64-mingw32.cmake .. + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +find_program(CMAKE_C_COMPILER NAMES x86_64-w64-mingw32-gcc) +find_program(CMAKE_CXX_COMPILER NAMES x86_64-w64-mingw32-g++) +find_program(CMAKE_RC_COMPILER NAMES x86_64-w64-mingw32-windres) + +if(NOT CMAKE_C_COMPILER) + message(FATAL_ERROR "x86_64-w64-mingw32-gcc not found. Please install mingw-w64 toolchain.") +endif() + +option(CROSS_COMPILE_DEPS "Download and build Windows dependencies for cross-compilation" ON) + +set(CROSS_DEPS_DIR "${CMAKE_BINARY_DIR}/cross-deps" CACHE PATH "Directory for cross-compiled dependencies") + +if(EXISTS "/usr/x86_64-w64-mingw32/sys-root/mingw") + set(MINGW_SYSROOT "/usr/x86_64-w64-mingw32/sys-root/mingw") +elseif(EXISTS "/usr/x86_64-w64-mingw32") + set(MINGW_SYSROOT "/usr/x86_64-w64-mingw32") +else() + set(MINGW_SYSROOT "") +endif() + +set(CMAKE_FIND_ROOT_PATH ${CROSS_DEPS_DIR} ${MINGW_SYSROOT}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +set(CMAKE_EXE_LINKER_FLAGS_INIT "-static -static-libgcc -static-libstdc++") + +set(ENABLE_UDEV OFF CACHE BOOL "" FORCE) +set(ENABLE_LIBGPIOD OFF CACHE BOOL "" FORCE) +set(ENABLE_REMOTEBITBANG OFF CACHE BOOL "" FORCE) +set(ENABLE_XILINX_VIRTUAL_CABLE OFF CACHE BOOL "" FORCE) +set(BUILD_STATIC ON CACHE BOOL "" FORCE) diff --git a/doc/guide/install.rst b/doc/guide/install.rst index 4d543bb..1f43172 100644 --- a/doc/guide/install.rst +++ b/doc/guide/install.rst @@ -194,6 +194,70 @@ Alternatively, if you want to build it by hand: Windows ======= +MSYS2 (Native Build) +-------------------- + +openFPGALoader can be installed via MSYS2: + +.. code-block:: bash + + pacman -S mingw-w64-ucrt-x86_64-openFPGALoader + +Cross-compilation from Linux +---------------------------- + +openFPGALoader can be cross-compiled for Windows from Linux using MinGW-w64. +The build system will automatically download and build the required dependencies +(libusb, libftdi). + +**Prerequisites (Debian/Ubuntu):** + +.. code-block:: bash + + sudo apt install \ + mingw-w64 \ + cmake \ + pkg-config \ + p7zip-full + +**Prerequisites (Fedora/RHEL/Rocky):** + +.. code-block:: bash + + sudo dnf install \ + mingw64-gcc \ + mingw64-gcc-c++ \ + mingw64-zlib \ + mingw64-zlib-static \ + cmake \ + p7zip \ + p7zip-plugins + +**Build:** + +.. code-block:: bash + + git clone https://github.com/trabucayre/openFPGALoader + cd openFPGALoader + mkdir build-win64 + cd build-win64 + cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-x86_64-w64-mingw32.cmake .. + cmake --build . --parallel + +The resulting ``openFPGALoader.exe`` will be a statically-linked executable +that only depends on standard Windows system DLLs (KERNEL32, msvcrt, WS2_32). + +**Optional: Strip debug symbols to reduce size:** + +.. code-block:: bash + + x86_64-w64-mingw32-strip openFPGALoader.exe + +**Cross-compilation options:** + +- ``-DCROSS_COMPILE_DEPS=OFF`` - Disable automatic dependency download (use system libraries) +- ``-DENABLE_CMSISDAP=ON`` - Enable CMSIS-DAP support (requires manually providing hidapi) + Common ======