diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e81d4b4..9cfba59 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -92,6 +92,12 @@ jobs: skip: "*musllinux*" arch: "x86_64" + # cibuildwheel's "auto" on Windows means AMD64 *and* x86, but dcmqi publishes no + # 32-bit binaries. The win32 wheel it produced could therefore only ever contain + # the 64-bit ones, so the architecture is pinned rather than probed. + - os: windows-latest + arch: "AMD64" + - os: macos-14 arch: "arm64" @@ -102,6 +108,9 @@ jobs: - os: ubuntu-latest arch: "auto" + - os: windows-latest + arch: "auto" + steps: - uses: actions/checkout@v7 with: diff --git a/.github/workflows/update-dcmqi.yml b/.github/workflows/update-dcmqi.yml index 55bbc7f..4d1058b 100644 --- a/.github/workflows/update-dcmqi.yml +++ b/.github/workflows/update-dcmqi.yml @@ -84,9 +84,16 @@ jobs: esac done - # Generate dcmqiUrls.cmake + # Generate dcmqiUrls.cmake. This file carries data only -- the version, and one + # filename/checksum pair per published asset. Choosing between those assets is done + # by dcmqiArchive.cmake, which is hand-written and must not be emitted here: it was + # previously generated below, so any fix to the selection logic was silently + # reverted by the next run of this workflow. { echo '# Checksums computed from assets associated with the dcmqi GitHub release' + echo '#' + echo '# Generated by .github/workflows/update-dcmqi.yml -- do not edit by hand.' + echo '# Which of these assets a build uses is decided in dcmqiArchive.cmake.' echo '' echo "set(version \"$latest_version\")" echo '' @@ -117,44 +124,6 @@ jobs: win_file="dcmqi-${latest_version}-win64.zip" echo "set(win64_filename \"$win_file\")" echo "set(win64_sha256 \"${checksums[$win_file]}\")" - echo '' - echo '' - - # Platform detection - echo 'cmake_host_system_information(RESULT is_64bit QUERY IS_64BIT)' - echo '' - echo 'set(archive "linux")' - echo '' - echo 'if(APPLE)' - if [ "$has_mac_split" = true ]; then - echo ' if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")' - echo ' set(archive "macos_arm64")' - echo ' else()' - echo ' set(archive "macos_x86_64")' - echo ' endif()' - else - echo ' set(archive "macos")' - fi - echo 'endif()' - echo '' - echo 'if(WIN32)' - echo ' if(is_64bit AND NOT (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "ARM64"))' - echo ' set(archive "win64")' - echo ' endif()' - echo 'endif()' - echo '' - echo 'if(NOT DEFINED "${archive}_filename")' - echo ' message(FATAL_ERROR "Failed to determine which archive to download: '"'"'${archive}_filename'"'"' variable is not defined")' - echo 'endif()' - echo '' - echo 'if(NOT DEFINED "${archive}_sha256")' - echo ' message(FATAL_ERROR "Could you make sure variable '"'"'${archive}_sha256'"'"' is defined ?")' - echo 'endif()' - echo '' - echo 'set(dcmqi_archive_filename "${${archive}_filename}")' - echo 'set(dcmqi_archive_sha256 "${${archive}_sha256}")' - echo '' - echo 'set(dcmqi_archive_url "https://github.com/QIICR/dcmqi/releases/download/v${version}/${dcmqi_archive_filename}")' } > dcmqiUrls.cmake # Update binaries.txt with discovered binary list diff --git a/CMakeLists.txt b/CMakeLists.txt index 533a6e2..f273a84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,12 @@ cmake_minimum_required(VERSION 3.15...3.26) project(${SKBUILD_PROJECT_NAME} LANGUAGES NONE) -# Set in the current scope the following variables: +# dcmqiUrls.cmake is generated: it lists every published asset and its checksum. +# dcmqiArchive.cmake then picks the one matching this build and sets in the current scope: # - dcmqi_archive_url # - dcmqi_archive_sha256 include(${CMAKE_CURRENT_SOURCE_DIR}/dcmqiUrls.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/dcmqiArchive.cmake) # # Download & extract archive diff --git a/dcmqiArchive.cmake b/dcmqiArchive.cmake new file mode 100644 index 0000000..cbada1a --- /dev/null +++ b/dcmqiArchive.cmake @@ -0,0 +1,99 @@ +# Selects the dcmqi release asset matching the platform and architecture being built for. +# +# The available assets and their checksums come from dcmqiUrls.cmake, which the update-dcmqi +# workflow regenerates; this file holds the logic and is written by hand. +# +# Picking the wrong asset is silent: a mismatched archive still extracts and installs, and the +# wheel only fails later, when a user runs a binary that cannot execute on their machine. So +# anything short of an exact platform/architecture match is a hard error here. + +if(NOT DEFINED version) + message(FATAL_ERROR "dcmqiUrls.cmake must be included before dcmqiArchive.cmake") +endif() + +# The architecture this build *targets*, which is not always the host's: +# +# * Visual Studio generators carry the target in CMAKE_GENERATOR_PLATFORM, and +# scikit-build-core sets it from the interpreter the wheel is being built for. A 32-bit +# Python on a 64-bit runner therefore reports "Win32" here while the host still looks +# 64-bit -- precisely the case that used to be mistaken for win64. +# * macOS cross builds set CMAKE_OSX_ARCHITECTURES (scikit-build-core derives it from +# ARCHFLAGS, which cibuildwheel sets). +# +# CMAKE_SYSTEM_PROCESSOR reports the host whenever we are not cross-compiling, so it only +# serves as the fallback. +set(_dcmqi_arch "${CMAKE_SYSTEM_PROCESSOR}") +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _dcmqi_arch_count) + if(_dcmqi_arch_count GREATER 1) + message(FATAL_ERROR + "CMAKE_OSX_ARCHITECTURES asks for a universal build (${CMAKE_OSX_ARCHITECTURES}), but " + "dcmqi publishes one archive per architecture. Build a separate wheel for each.") + endif() + set(_dcmqi_arch "${CMAKE_OSX_ARCHITECTURES}") +elseif(CMAKE_GENERATOR_PLATFORM) + set(_dcmqi_arch "${CMAKE_GENERATOR_PLATFORM}") +endif() + +string(TOLOWER "${_dcmqi_arch}" _dcmqi_arch) +if(_dcmqi_arch MATCHES "^(x86_64|x64|amd64)$") + set(_dcmqi_arch "x86_64") +elseif(_dcmqi_arch MATCHES "^(arm64|aarch64)$") + set(_dcmqi_arch "arm64") +elseif(_dcmqi_arch MATCHES "^(win32|x86|i[3-6]86)$") + set(_dcmqi_arch "x86") +endif() + +if(APPLE) + set(_dcmqi_platform "macos") +elseif(WIN32) + set(_dcmqi_platform "win") +elseif(UNIX) + set(_dcmqi_platform "linux") +else() + message(FATAL_ERROR + "dcmqi publishes no binary package for this operating system " + "(CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME})") +endif() + +# Asset variables are named _. The two assets whose upstream names predate +# architecture qualification get an alias: "linux" and "win64" both mean x86_64. See +# CPACK_SYSTEM_NAME in QIICR/dcmqi. +set(_dcmqi_candidates "${_dcmqi_platform}_${_dcmqi_arch}") +if(_dcmqi_platform STREQUAL "linux" AND _dcmqi_arch STREQUAL "x86_64") + list(APPEND _dcmqi_candidates "linux") +elseif(_dcmqi_platform STREQUAL "win" AND _dcmqi_arch STREQUAL "x86_64") + list(APPEND _dcmqi_candidates "win64") +elseif(_dcmqi_platform STREQUAL "macos") + # Older releases shipped a single, unqualified macOS archive; the generator still emits + # that name if upstream ever stops splitting by architecture. + list(APPEND _dcmqi_candidates "macos") +endif() + +set(archive "") +foreach(_dcmqi_candidate IN LISTS _dcmqi_candidates) + if(DEFINED ${_dcmqi_candidate}_filename AND DEFINED ${_dcmqi_candidate}_sha256) + set(archive "${_dcmqi_candidate}") + break() + endif() +endforeach() + +if(NOT archive) + set(_dcmqi_tried "") + foreach(_dcmqi_candidate IN LISTS _dcmqi_candidates) + list(APPEND _dcmqi_tried "${_dcmqi_candidate}_filename") + endforeach() + string(REPLACE ";" ", " _dcmqi_tried "${_dcmqi_tried}") + message(FATAL_ERROR + "dcmqi v${version} publishes no binary package for ${_dcmqi_platform}/${_dcmqi_arch}, so no " + "wheel can be built for it. Looked in dcmqiUrls.cmake for ${_dcmqi_tried} (and a matching " + "_sha256). Published assets: https://github.com/QIICR/dcmqi/releases/tag/v${version}") +endif() + +set(dcmqi_archive_filename "${${archive}_filename}") +set(dcmqi_archive_sha256 "${${archive}_sha256}") +set(dcmqi_archive_url + "https://github.com/QIICR/dcmqi/releases/download/v${version}/${dcmqi_archive_filename}") + +message(STATUS + "dcmqi archive for ${_dcmqi_platform}/${_dcmqi_arch}: ${dcmqi_archive_filename}") diff --git a/dcmqiUrls.cmake b/dcmqiUrls.cmake index 239be5e..506df90 100644 --- a/dcmqiUrls.cmake +++ b/dcmqiUrls.cmake @@ -1,4 +1,7 @@ # Checksums computed from assets associated with the dcmqi GitHub release +# +# Generated by .github/workflows/update-dcmqi.yml -- do not edit by hand. +# Which of these assets a build uses is decided in dcmqiArchive.cmake. set(version "1.5.6") @@ -13,35 +16,3 @@ set(macos_x86_64_sha256 "6678f2a60547c3a14a9f0376932217c3faee28158b6805c3b89d3 set(win64_filename "dcmqi-1.5.6-win64.zip") set(win64_sha256 "72f4bf6bc6b265e5843c42436267fb59a8079ca765095f218300d019f9e15e3d") - - -cmake_host_system_information(RESULT is_64bit QUERY IS_64BIT) - -set(archive "linux") - -if(APPLE) - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") - set(archive "macos_arm64") - else() - set(archive "macos_x86_64") - endif() -endif() - -if(WIN32) - if(is_64bit AND NOT (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "ARM64")) - set(archive "win64") - endif() -endif() - -if(NOT DEFINED "${archive}_filename") - message(FATAL_ERROR "Failed to determine which archive to download: '${archive}_filename' variable is not defined") -endif() - -if(NOT DEFINED "${archive}_sha256") - message(FATAL_ERROR "Could you make sure variable '${archive}_sha256' is defined ?") -endif() - -set(dcmqi_archive_filename "${${archive}_filename}") -set(dcmqi_archive_sha256 "${${archive}_sha256}") - -set(dcmqi_archive_url "https://github.com/QIICR/dcmqi/releases/download/v${version}/${dcmqi_archive_filename}")