Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/scenario-tests.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__*
.vs
.vscode
25 changes: 19 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()

#[[====================================================================================================================
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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})

Expand Down
2 changes: 2 additions & 0 deletions scenarios/basic/.gitignore
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions scenarios/basic/Basic.Tests.ps1
Original file line number Diff line number Diff line change
@@ -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
}
}
102 changes: 102 additions & 0 deletions scenarios/basic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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.")
10 changes: 10 additions & 0 deletions scenarios/basic/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<config>
<add key="globalPackagesFolder" value="../../__nuget-packages" />
</config>
</configuration>