diff --git a/.github/workflows/scenario-tests.yml b/.github/workflows/scenario-tests.yml new file mode 100644 index 0000000..a8e2628 --- /dev/null +++ b/.github/workflows/scenario-tests.yml @@ -0,0 +1,47 @@ +name: Scenario Tests + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + scenario-basic: + name: Basic Scenario (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - windows-latest + - ubuntu-latest + - macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: false + fetch-depth: 0 + filter: blob:none + + - name: Verify CMake + shell: pwsh + run: | + Get-Command cmake + cmake --version + + - name: Install Pester + shell: pwsh + run: | + Install-Module -Name Pester -MinimumVersion 5.0 -Force -SkipPublisherCheck -Scope CurrentUser + + - name: Run scenario tests + shell: pwsh + env: + NUGET_TOOLS_PATH: ${{ github.workspace }}/__tools + run: | + $result = Invoke-Pester scenarios/basic/Basic.Tests.ps1 -Output Detailed -PassThru + exit $result.FailedCount diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..feed242 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__* +.vs +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index af1fea3..efb35ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,8 +44,13 @@ define_property(TARGET location. If `NUGET_TOOLS_PATH` is not set, then it will default to `${CMAKE_BINARY_DIR}/__tools`. ====================================================================================================================]]# function(ensure_nunuget) + set(NUNUGET_NAME "NuNuGet") + if(WIN32) + set(NUNUGET_NAME "${NUNUGET_NAME}.exe") + endif() + find_program(NUNUGET_PATH - NAMES nunuget nunuget.exe + NAMES ${NUNUGET_NAME} ) if(NUNUGET_PATH) @@ -109,7 +114,13 @@ function(ensure_nunuget) DESTINATION "${NUGET_TOOLS_PATH}/${PACKAGE_NAME}.${PACKAGE_VERSION}/" ) - set(NUNUGET_PATH "${NUGET_TOOLS_PATH}/${PACKAGE_NAME}.${PACKAGE_VERSION}/tools/any/${PACKAGE_SYSTEM}-${PACKAGE_ARCH}/nunuget.exe" CACHE FILEPATH "The location of 'NuNuGet' executable" FORCE) + set(NUNUGET_PATH "${NUGET_TOOLS_PATH}/${PACKAGE_NAME}.${PACKAGE_VERSION}/tools/any/${PACKAGE_SYSTEM}-${PACKAGE_ARCH}/${NUNUGET_NAME}") + set(NUNUGET_PATH "${NUNUGET_PATH}" CACHE FILEPATH "The location of 'NuNuGet' executable" FORCE) + + if(NOT WIN32) + file(CHMOD "${NUNUGET_PATH}" FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + endif() + endfunction() #[[==================================================================================================================== @@ -255,7 +266,7 @@ function(add_nuget_packages) if(NUNUGET_RESULT EQUAL 100) message(FATAL_ERROR "add_nuget_packages: The specified NuGet packages differ from the lock file: ${ADD_NUGET_LOCK_FILE}.\nPlease delete the lock file and re-run configuration to regenerate the lock file with the updated packages.") endif() - message(FATAL_ERROR "add_nuget_packages: NuNuGet failed with error: ${NUNUGET_ERROR}") + message(FATAL_ERROR "add_nuget_packages: NuNuGet failed with exit code ${NUNUGET_RESULT}. Output: ${NUNUGET_OUTPUT}\nError: ${NUNUGET_ERROR}") endif() # Convert the output from NuNuGet into a list of lines to parse the resolved packages and their paths. @@ -284,15 +295,17 @@ function(add_nuget_packages) set(PACKAGE_NAME ${CMAKE_MATCH_1}) set(PACKAGE_VERSION ${CMAKE_MATCH_2}) + string(TOUPPER ${PACKAGE_NAME} PACKAGE_NAME_UPPER) + string(TOLOWER ${PACKAGE_NAME} PACKAGE_NAME_LOWER) + message(STATUS "NuGet package: ${PACKAGE_NAME}/${PACKAGE_VERSION}") - cmake_path(SET NUGET_LOCATION NORMALIZE "${GLOBAL_PACKAGES_PATH}/${PACKAGE_NAME}/${PACKAGE_VERSION}") + # The location of the package in the global packages folder is: ${GLOBAL_PACKAGES_PATH}/${PACKAGE_NAME_LOWER}/${PACKAGE_VERSION} + cmake_path(SET NUGET_LOCATION NORMALIZE "${GLOBAL_PACKAGES_PATH}/${PACKAGE_NAME_LOWER}/${PACKAGE_VERSION}") # Build 'PACKAGE_NAME_PROPERTY' - the CMake-property friendly form of PACKAGE_NAME. # 1. convert to uppercase to follow CMake variable naming conventions # 2. replace '.' and '-' with '_' to make it a valid CMake variable name - string(TOUPPER ${PACKAGE_NAME} PACKAGE_NAME_UPPER) - string(TOLOWER ${PACKAGE_NAME} PACKAGE_NAME_LOWER) string(REPLACE "." "_" PACKAGE_NAME_PROPERTY ${PACKAGE_NAME_UPPER}) string(REPLACE "-" "_" PACKAGE_NAME_PROPERTY ${PACKAGE_NAME_PROPERTY}) diff --git a/scenarios/basic/.gitignore b/scenarios/basic/.gitignore new file mode 100644 index 0000000..4f007b9 --- /dev/null +++ b/scenarios/basic/.gitignore @@ -0,0 +1,2 @@ +# Ignore the 'packages.lock.json' file - it will be updated by the tests but we don't want to commit those changes. +packages.lock.json diff --git a/scenarios/basic/Basic.Tests.ps1 b/scenarios/basic/Basic.Tests.ps1 new file mode 100644 index 0000000..7fc3374 --- /dev/null +++ b/scenarios/basic/Basic.Tests.ps1 @@ -0,0 +1,25 @@ +BeforeAll { + $script:ScenarioDir = $PSScriptRoot + $script:RepoRoot = (Resolve-Path "$PSScriptRoot/../..").Path + $script:NuGetToolsPath = if ($env:NUGET_TOOLS_PATH) { $env:NUGET_TOOLS_PATH } else { Join-Path $script:RepoRoot "__tools" } + $script:BuildRoot = Join-Path $script:RepoRoot "__build/scenario-basic" +} + +Describe "Basic Scenario" { + It "Step 1: cmake configure generates packages.lock.json" { + Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $script:ScenarioDir "packages.lock.json") + + cmake -S $script:ScenarioDir -B (Join-Path $script:BuildRoot "step1") --log-level=VERBOSE -DNUGET_TOOLS_PATH="$($script:NuGetToolsPath)" + + $LASTEXITCODE | Should -Be 0 + Join-Path $script:ScenarioDir "packages.lock.json" | + Should -Exist + } + + It "Step 2: cmake configure verifies packages.lock.json" { + cmake -S $script:ScenarioDir -B (Join-Path $script:BuildRoot "step2") --log-level=VERBOSE -DNUGET_TOOLS_PATH="$($script:NuGetToolsPath)" + + $LASTEXITCODE | + Should -Be 0 + } +} diff --git a/scenarios/basic/CMakeLists.txt b/scenarios/basic/CMakeLists.txt new file mode 100644 index 0000000..4cdcce9 --- /dev/null +++ b/scenarios/basic/CMakeLists.txt @@ -0,0 +1,102 @@ +cmake_minimum_required(VERSION 3.31) +project(NuGetCMakePackage.Scenarios.Basic) + +include(FetchContent) + +# Use the local checkout of NuGetCMakePackage rather than fetching from network. +FetchContent_Declare( + NuGetCMakePackage + SOURCE_DIR "${CMAKE_SOURCE_DIR}/../.." +) +FetchContent_MakeAvailable(NuGetCMakePackage) + +add_nuget_packages( + PACKAGES + Microsoft.Windows.ImplementationLibrary 1.* + Microsoft.Windows.CppWinRT 2.0.250303.1 + Microsoft.Windows.SDK.CPP 10.0.26100.7705 + CONFIG_FILE + "${CMAKE_CURRENT_LIST_DIR}/nuget.config" + LOCK_FILE + "${CMAKE_CURRENT_LIST_DIR}/packages.lock.json" +) + +# ── Assertion helpers ───────────────────────────────────────────────────────── + +macro(assert_variable_set VAR_NAME) + if("${${VAR_NAME}}" STREQUAL "") + message(FATAL_ERROR "Assertion failed: variable '${VAR_NAME}' is empty or unset.") + endif() +endmacro() + +macro(assert_path_exists PATH_EXPR LABEL) + if(NOT EXISTS "${PATH_EXPR}") + message(FATAL_ERROR "Assertion failed: path for '${LABEL}' does not exist: ${PATH_EXPR}") + endif() +endmacro() + +macro(assert_target_property_set TARGET PROP) + get_target_property(_assert_prop_val ${TARGET} ${PROP}) + if("${_assert_prop_val}" MATCHES "-NOTFOUND$" OR "${_assert_prop_val}" STREQUAL "") + message(FATAL_ERROR "Assertion failed: target '${TARGET}' property '${PROP}' is unset.") + endif() +endmacro() + +# Verify that NuNuGet was downloaded +assert_variable_set(NUNUGET_PATH) +assert_path_exists("${NUNUGET_PATH}" "NUNUGET_PATH") + +# Verify that GLOBAL properties are set for all packages +foreach(_package_name IN ITEMS + MICROSOFT_WINDOWS_IMPLEMENTATIONLIBRARY + MICROSOFT_WINDOWS_CPPWINRT + MICROSOFT_WINDOWS_SDK_CPP +) + get_property(_ver GLOBAL PROPERTY "NUGET_VERSION-${_package_name}") + get_property(_loc GLOBAL PROPERTY "NUGET_LOCATION-${_package_name}") + + if("${_ver}" STREQUAL "") + message(FATAL_ERROR "Assertion failed: NUGET_VERSION-${_package_name} is unset.") + endif() + if("${_loc}" STREQUAL "") + message(FATAL_ERROR "Assertion failed: NUGET_LOCATION-${_package_name} is unset.") + endif() + assert_path_exists("${_loc}" "NUGET_LOCATION-${_package_name}") +endforeach() + +# Verify that Microsoft.Windows.ImplementationLibrary's floating version resolved to 1.x +get_property(_wil_version GLOBAL PROPERTY "NUGET_VERSION-MICROSOFT_WINDOWS_IMPLEMENTATIONLIBRARY") +if(NOT _wil_version MATCHES "^1\\.") + message(FATAL_ERROR "Assertion failed: Microsoft.Windows.ImplementationLibrary floating version resolved to '${_wil_version}', expected 1.x") +endif() + +# Verify that find_package variables are set +cmake_policy(GET CMP0074 _cmp0074) +cmake_policy(GET CMP0144 _cmp0144) +if(_cmp0074 STREQUAL "NEW") + if(_cmp0144 STREQUAL "NEW") + assert_variable_set(MICROSOFT.WINDOWS.IMPLEMENTATIONLIBRARY_ROOT) + else() + assert_variable_set(Microsoft.Windows.ImplementationLibrary_ROOT) + endif() +else() + assert_variable_set(Microsoft.Windows.ImplementationLibrary_DIR) +endif() + +# Verify that find_package for Microsoft.Windows.ImplementationLibrary succeeds. +# — the target should exist +# — the CMake within Microsoft.Windows.ImplementationLibrary should have set the INTERFACE_INCLUDE_DIRECTORIES property +find_package(Microsoft.Windows.ImplementationLibrary CONFIG REQUIRED) + +if(NOT TARGET Microsoft.Windows.ImplementationLibrary) + message(FATAL_ERROR "Assertion failed: target 'Microsoft.Windows.ImplementationLibrary' not created by find_package.") +endif() + +assert_target_property_set(Microsoft.Windows.ImplementationLibrary INTERFACE_INCLUDE_DIRECTORIES) + +get_target_property(_wil_includes Microsoft.Windows.ImplementationLibrary INTERFACE_INCLUDE_DIRECTORIES) +foreach(_inc IN LISTS _wil_includes) + assert_path_exists("${_inc}" "WIL INTERFACE_INCLUDE_DIRECTORIES") +endforeach() + +message(STATUS "All scenario assertions passed.") diff --git a/scenarios/basic/nuget.config b/scenarios/basic/nuget.config new file mode 100644 index 0000000..9513f30 --- /dev/null +++ b/scenarios/basic/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + + +