diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c779136 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# MapWinGIS6 native build. Windows and MSVC v145 only. +# +# pwsh -File build\scripts\bootstrap-vcpkg.ps1 +# cmake --preset x64-debug +# cmake --build --preset x64-debug +# +# See docs/BUILDING.md and docs/CMAKE-MIGRATION.md. + +# 3.15: CMP0092 (no default /W3), 3.21: TARGET_RUNTIME_DLLS, +# 3.26: copy_directory_if_different, 3.28: tested minimum. +cmake_minimum_required(VERSION 3.28...4.3) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# --- options needed before project() ----------------------------------------- +option(MWG_BUILD_TESTS "Build the GoogleTest suite (adds the 'tests' vcpkg feature)" ON) +option(MWG_BUILD_SAMPLES "Build the samples" ON) +set(MWG_WINDOWS_SDK "10.0.28000.0" CACHE STRING + "Windows SDK version (MSBuild: MwgWindowsSdk). 10.0 means the newest installed SDK") + +# --- configurations ---------------------------------------------------------- +# Debug and Release only, as in MSBuild. CLion offers RelWithDebInfo and +# MinSizeRel profiles by default; they are rejected instead of being built with +# flags that no other build uses. +if(CMAKE_GENERATOR MATCHES "^Visual Studio|Multi-Config$") + set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Supported configurations" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Debug or Release" FORCE) + endif() + if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release)$") + message(FATAL_ERROR + "CMAKE_BUILD_TYPE '${CMAKE_BUILD_TYPE}' is not supported; MapWinGIS6 builds Debug and Release only.\n" + "Use one of the presets in CMakePresets.json (CLion: disable the default profiles and enable the preset profiles).") + endif() + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release) +endif() + +# --- toolchain --------------------------------------------------------------- +include(MwgVcpkg) + +# Visual Studio generators select the Windows SDK from CMAKE_SYSTEM_VERSION; +# "10.0" lets them pick the newest one. Ninja uses the vcvars environment. +if(CMAKE_GENERATOR MATCHES "^Visual Studio" AND NOT DEFINED CMAKE_SYSTEM_VERSION) + set(CMAKE_SYSTEM_VERSION "${MWG_WINDOWS_SDK}" CACHE STRING "Windows SDK selected by the Visual Studio generator") +endif() + +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MwgFlagOverrides.cmake") + +project(MapWinGIS6 + VERSION 6.0.0 + DESCRIPTION "Open-source mapping engine behind MapWindow" + HOMEPAGE_URL "https://github.com/MapWindow/MapWinGIS6" + LANGUAGES C CXX) + +# --- output layout (docs/CMAKE-MIGRATION.md, D1) ----------------------------- +# One runtime folder per build tree, separate from MSBuild's bin\\. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") + +include(MwgPlatform) +include(MwgCompilerOptions) +include(MwgRuntimeDeploy) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +mwg_add_runtime_data_target() + +add_subdirectory(src/MapWinGIS.Core) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..7f3771b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,124 @@ +{ + "version": 6, + "cmakeMinimumRequired": { "major": 3, "minor": 28, "patch": 0 }, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "description": "Shared settings. The vcpkg toolchain is resolved by CMakeLists.txt (MWG_VCPKG_ROOT, ../vcpkg, VCPKG_ROOT).", + "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" }, + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "VCPKG_HOST_TRIPLET": "x64-windows-v145", + "VCPKG_MANIFEST_FEATURES": "tests" + } + }, + { + "name": "x64", + "hidden": true, + "description": "x64. 'architecture' only informs Visual Studio; in CLion assign a Visual Studio toolchain with architecture amd64.", + "architecture": { "value": "x64", "strategy": "external" }, + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x64-windows-v145", + "VCPKG_INSTALLED_DIR": "${sourceDir}/vcpkg_installed/cmake-x64" + } + }, + { + "name": "x86", + "hidden": true, + "description": "Win32. 'architecture' only informs Visual Studio; in CLion assign a Visual Studio toolchain with architecture x86 or amd64_x86.", + "architecture": { "value": "x86", "strategy": "external" }, + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x86-windows-v145", + "VCPKG_INSTALLED_DIR": "${sourceDir}/vcpkg_installed/cmake-Win32" + } + }, + { + "name": "debug", + "hidden": true, + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "release", + "hidden": true, + "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "ninja", + "hidden": true, + "description": "Single-config Ninja. Toolset and Windows SDK come from the vcvars environment (CLion toolchain, VS Developer PowerShell).", + "generator": "Ninja" + }, + { + "name": "vs2026", + "hidden": true, + "description": "Multi-config Visual Studio generator; pins toolset v145 and the Windows SDK (MWG_WINDOWS_SDK).", + "generator": "Visual Studio 18 2026", + "toolset": { "value": "v145", "strategy": "set" } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug (Ninja)", + "inherits": [ "windows-base", "ninja", "x64", "debug" ], + "binaryDir": "${sourceDir}/out/b/x64d" + }, + { + "name": "x64-release", + "displayName": "x64 Release (Ninja)", + "inherits": [ "windows-base", "ninja", "x64", "release" ], + "binaryDir": "${sourceDir}/out/b/x64r" + }, + { + "name": "x86-debug", + "displayName": "Win32 Debug (Ninja)", + "inherits": [ "windows-base", "ninja", "x86", "debug" ], + "binaryDir": "${sourceDir}/out/b/x86d" + }, + { + "name": "x86-release", + "displayName": "Win32 Release (Ninja)", + "inherits": [ "windows-base", "ninja", "x86", "release" ], + "binaryDir": "${sourceDir}/out/b/x86r" + }, + { + "name": "vs2026-x64", + "displayName": "x64 (Visual Studio 18 2026)", + "inherits": [ "windows-base", "vs2026", "x64" ], + "architecture": { "value": "x64", "strategy": "set" }, + "binaryDir": "${sourceDir}/out/b/vs-x64" + }, + { + "name": "vs2026-x86", + "displayName": "Win32 (Visual Studio 18 2026)", + "inherits": [ "windows-base", "vs2026", "x86" ], + "architecture": { "value": "Win32", "strategy": "set" }, + "binaryDir": "${sourceDir}/out/b/vs-x86" + } + ], + "buildPresets": [ + { "name": "x64-debug", "configurePreset": "x64-debug" }, + { "name": "x64-release", "configurePreset": "x64-release" }, + { "name": "x86-debug", "configurePreset": "x86-debug" }, + { "name": "x86-release", "configurePreset": "x86-release" }, + { "name": "vs2026-x64-debug", "configurePreset": "vs2026-x64", "configuration": "Debug" }, + { "name": "vs2026-x64-release", "configurePreset": "vs2026-x64", "configuration": "Release" }, + { "name": "vs2026-x86-debug", "configurePreset": "vs2026-x86", "configuration": "Debug" }, + { "name": "vs2026-x86-release", "configurePreset": "vs2026-x86", "configuration": "Release" } + ], + "testPresets": [ + { + "name": "test-base", + "hidden": true, + "output": { "outputOnFailure": true }, + "filter": { "exclude": { "label": "interactive" } } + }, + { "name": "x64-debug", "inherits": "test-base", "configurePreset": "x64-debug" }, + { "name": "x64-release", "inherits": "test-base", "configurePreset": "x64-release" }, + { "name": "x86-debug", "inherits": "test-base", "configurePreset": "x86-debug" }, + { "name": "x86-release", "inherits": "test-base", "configurePreset": "x86-release" }, + { "name": "vs2026-x64-debug", "inherits": "test-base", "configurePreset": "vs2026-x64", "configuration": "Debug" }, + { "name": "vs2026-x64-release", "inherits": "test-base", "configurePreset": "vs2026-x64", "configuration": "Release" }, + { "name": "vs2026-x86-debug", "inherits": "test-base", "configurePreset": "vs2026-x86", "configuration": "Debug" }, + { "name": "vs2026-x86-release", "inherits": "test-base", "configurePreset": "vs2026-x86", "configuration": "Release" } + ] +} diff --git a/build/scripts/Compare-CompileCommands.ps1 b/build/scripts/Compare-CompileCommands.ps1 index 8fdc61f..006051a 100644 --- a/build/scripts/Compare-CompileCommands.ps1 +++ b/build/scripts/Compare-CompileCommands.ps1 @@ -73,6 +73,18 @@ $ignoredOptions = @( ) $ignoredOptionPrefixes = @('/Fo', '/Fd', '/Fe', '/Fp', '/ILK:', '/PDB:', '/IMPLIB:', '/OUT:', '/MP') +# Link differences that are deliberate (docs/CMAKE-MIGRATION.md). They are +# reported under 'explained' and do not count as lost options. +$explainedLinkDifferences = [ordered]@{ + '^/manifest(:embed|uac:.*)?$' = 'CMake''s vs_link wrapper generates and embeds the manifest with mt.exe; the embedded manifest is identical' + '^/tlbid:1$' = 'Only used when a type library resource is embedded' + '^/libpath:/' = 'vcpkg auto-link replaced by imported targets with full library paths' + '^lib:/.*\*\.lib$' = 'vcpkg auto-link replaced by imported targets with full library paths' + '^lib:/' = 'Library from an imported target (find_package)' + '^lib:odbc(cp)?32\.lib$' = 'MSBuild default library; unused, not in CMake''s default list' + '^/version:0\.0$' = 'CMake default image version; MSBuild leaves it at 0.0 too' +} + # MSBuild project names that differ from the CMake target names. $targetNames = @{ 'Translate' = 'MapWinGIS.Samples.Translate' } @@ -214,6 +226,8 @@ function ConvertTo-OptionSet([string[]] $arguments, [string] $baseDir, [hashtabl break } } + # Linker options are case-insensitive (/DEBUG and /debug are the same). + if ($isLink) { $arg = $arg.ToLowerInvariant() } $options.Add($arg) } @@ -458,11 +472,32 @@ if ($CMakeBuildDir) { $linkDiff = [ordered]@{} foreach ($binary in ($msbuildCommands.Links.Keys | Sort-Object)) { - if (-not $cmakeCommands.Links.ContainsKey($binary)) { continue } + if (-not $cmakeCommands.Links.ContainsKey($binary)) { + # A binary that CMake builds but that was up to date has no link + # line in the log; that is not parity. + $cmakeTarget = [System.IO.Path]::GetFileNameWithoutExtension($binary) + if ($CMakeBuildLog -and ($cmakeTargets -contains $cmakeTarget)) { + Write-Warning "$binary was not linked in $CMakeBuildLog; delete it and rebuild to compare its link options." + } + continue + } $diff = Compare-OptionSets $msbuildCommands.Links[$binary] $cmakeCommands.Links[$binary] + $explained = [ordered]@{} + $lost = @(); $added = @() + foreach ($pair in @(@{ Items = $diff.Lost; Kind = 'lost' }, @{ Items = $diff.Added; Kind = 'added' })) { + foreach ($option in $pair.Items) { + $reason = $null + foreach ($pattern in $explainedLinkDifferences.Keys) { + if ($option -match $pattern) { $reason = $explainedLinkDifferences[$pattern]; break } + } + if ($reason) { $explained["$($pair.Kind) $option"] = $reason } + elseif ($pair.Kind -eq 'lost') { $lost += $option } + else { $added += $option } + } + } if ($diff.Lost.Count -or $diff.Added.Count) { - $linkDiff[$binary] = $diff - $lostTotal += $diff.Lost.Count + $linkDiff[$binary] = [pscustomobject]@{ Lost = $lost; Added = $added; Explained = $explained } + $lostTotal += $lost.Count } } diff --git a/cmake/MwgCompilerOptions.cmake b/cmake/MwgCompilerOptions.cmake new file mode 100644 index 0000000..c54ef4d --- /dev/null +++ b/cmake/MwgCompilerOptions.cmake @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# mwg_apply_common_options() +# +# The compiler and linker settings of build/props/MapWinGIS.Cpp.props plus the +# MSBuild defaults they rely on, applied PRIVATE to one target so that nothing +# leaks to consumers of MapWinGIS.Core. The effective MSBuild options are listed +# in docs/CMAKE-MIGRATION.md; keep both in sync. + +option(MWG_ENABLE_ASAN "Compile with /fsanitize=address (MSBuild: MwgEnableAsan)" OFF) + +function(mwg_apply_common_options target) + get_target_property(type ${target} TYPE) + + set_target_properties(${target} PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + # The MIDL-generated _i.c is C. + C_STANDARD 17 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL" + # PDBs in Debug and Release, as in MSBuild (CMake's default is Debug only). + MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase + # MSBuild passes the vcpkg include directory as a plain /I, so warnings + # in GDAL headers are reported at /W4. CMake would make imported + # include directories /external:I with /external:W0 and hide them. + NO_SYSTEM_FROM_IMPORTED ON + ) + + set(c_cxx "$") + set(debug "$>") + set(release "$>") + + target_compile_options(${target} PRIVATE + # WarningLevel, DisableSpecificWarnings. 4251 is safe: one toolset and + # one CRT for every binary of the product. + "$<${c_cxx}:/W4;/WX-;/wd4251;/external:W4>" + # AdditionalOptions + "$<${c_cxx}:/utf-8;/Zc:__cplusplus;/Zc:preprocessor;/Zc:throwingNew;/bigobj>" + # ConformanceMode, SDLCheck, ExceptionHandling and MSBuild defaults + "$<${c_cxx}:/permissive-;/sdl;/EHsc;/GS;/Gd;/Gm-;/fp:precise;/Zc:wchar_t;/Zc:forScope;/Zc:inline>" + "$<${debug}:/Od;/JMC>" + "$<${release}:/O2;/Oi;/Gy;/guard:cf>" + ) + if(MWG_ENABLE_ASAN) + target_compile_options(${target} PRIVATE "$<${c_cxx}:/fsanitize=address>") + else() + # BasicRuntimeChecks; incompatible with AddressSanitizer. + target_compile_options(${target} PRIVATE "$<${debug}:/RTC1>") + endif() + + target_compile_definitions(${target} PRIVATE + # CharacterSet=Unicode, also for the resource compiler. + UNICODE + _UNICODE + "$<${c_cxx}:WIN32_LEAN_AND_MEAN;NOMINMAX;_WIN32_WINNT=0x0A00;WINVER=0x0A00;NTDDI_VERSION=0x0A00000B;_CRT_SECURE_NO_WARNINGS>" + "$<${c_cxx}:MWG_VERSION=\"${PROJECT_VERSION}\">" + "$<${debug}:_DEBUG>" + "$<${release}:NDEBUG>" + ) + if(type STREQUAL "SHARED_LIBRARY") + # MSBuild defines _WINDLL for every DLL project. + target_compile_definitions(${target} PRIVATE "$<${c_cxx}:_WINDLL>") + endif() + + # CMake's defaults already add /machine, /debug /INCREMENTAL (Debug) and + # /INCREMENTAL:NO (Release). + target_link_options(${target} PRIVATE + /DYNAMICBASE + /NXCOMPAT + "$<$:/DEBUG;/OPT:REF;/OPT:ICF;/guard:cf>" + ) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + target_link_options(${target} PRIVATE /HIGHENTROPYVA) + else() + # MSBuild Win32 defaults: keep frame pointers, no code analysis, and + # safe exception handler tables in Release. + target_compile_options(${target} PRIVATE "$<${c_cxx}:/Oy-;/analyze->") + target_link_options(${target} PRIVATE "$<$:/SAFESEH>") + endif() + if(type STREQUAL "SHARED_LIBRARY") + target_link_options(${target} PRIVATE /SUBSYSTEM:WINDOWS) + endif() +endfunction() diff --git a/cmake/MwgFlagOverrides.cmake b/cmake/MwgFlagOverrides.cmake new file mode 100644 index 0000000..1151d4c --- /dev/null +++ b/cmake/MwgFlagOverrides.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# CMAKE_USER_MAKE_RULES_OVERRIDE file: runs inside project(), after CMake's +# MSVC platform module and before the CMAKE__FLAGS* cache entries are +# created. +# +# CMake's MSVC defaults (/DWIN32 /D_WINDOWS /EHsc, /Ob0 /Od /RTC1, /O2 /Ob2 +# /DNDEBUG) do not match the MSBuild build, which defines neither WIN32 nor +# _WINDOWS. Every compile option comes from mwg_apply_common_options() +# instead (cmake/MwgCompilerOptions.cmake), so the targets carry exactly the +# flags documented in docs/CMAKE-MIGRATION.md and nothing leaks to consumers. +# Linker defaults (/machine, /debug /INCREMENTAL, /INCREMENTAL:NO) are kept. + +foreach(_mwg_lang IN ITEMS C CXX) + set(CMAKE_${_mwg_lang}_FLAGS_INIT "") + set(CMAKE_${_mwg_lang}_FLAGS_DEBUG_INIT "") + set(CMAKE_${_mwg_lang}_FLAGS_RELEASE_INIT "") +endforeach() +unset(_mwg_lang) diff --git a/cmake/MwgPlatform.cmake b/cmake/MwgPlatform.cmake new file mode 100644 index 0000000..00ccd5e --- /dev/null +++ b/cmake/MwgPlatform.cmake @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Toolchain and platform validation, run after project(). +# +# Sets: +# MWG_PLATFORM MSBuild platform name: x64 or Win32 +# MWG_RUNTIME_DIR runtime output folder of this build (may contain $) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + message(FATAL_ERROR "MapWinGIS6 builds on Windows only.") +endif() + +foreach(_mwg_lang IN ITEMS C CXX) + if(NOT CMAKE_${_mwg_lang}_COMPILER_ID STREQUAL "MSVC") + message(FATAL_ERROR + "MapWinGIS6 needs the MSVC compiler (cl.exe); the ${_mwg_lang} compiler is " + "'${CMAKE_${_mwg_lang}_COMPILER_ID}' (${CMAKE_${_mwg_lang}_COMPILER}).\n" + "In CLion select a Visual Studio toolchain; from a shell, configure inside a " + "VS 2026 Developer PowerShell or vcvarsall.bat environment.") + endif() +endforeach() +unset(_mwg_lang) + +# v145 is MSVC 19.50 and later. The toolset name is not available under Ninja, +# so the compiler version is the check that works for every generator. +if(MSVC_VERSION LESS 1950) + message(FATAL_ERROR + "MapWinGIS6 needs the MSVC v145 toolset (Visual Studio 2026, MSVC 19.50 or later); " + "found MSVC ${CMAKE_CXX_COMPILER_VERSION} (${CMAKE_CXX_COMPILER}).") +endif() +if(CMAKE_GENERATOR_TOOLSET AND NOT CMAKE_GENERATOR_TOOLSET MATCHES "^v145") + message(FATAL_ERROR "MapWinGIS6 needs the v145 toolset; the generator uses '${CMAKE_GENERATOR_TOOLSET}'.") +endif() + +# --- architecture ------------------------------------------------------------- +# is an MSBuild concept: map the CMake architecture to x64 / Win32. +if(CMAKE_VS_PLATFORM_NAME) + set(MWG_PLATFORM "${CMAKE_VS_PLATFORM_NAME}") +elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(MWG_PLATFORM "x64") +else() + set(MWG_PLATFORM "Win32") +endif() + +if(MWG_PLATFORM STREQUAL "x64" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_mwg_expected_triplet "x64-windows-v145") +elseif(MWG_PLATFORM STREQUAL "Win32" AND CMAKE_SIZEOF_VOID_P EQUAL 4) + set(_mwg_expected_triplet "x86-windows-v145") +else() + message(FATAL_ERROR "Unsupported target architecture '${MWG_PLATFORM}' (pointer size ${CMAKE_SIZEOF_VOID_P}).") +endif() + +# A mismatch means the CLion toolchain or vcvars environment targets another +# architecture than the preset; linking would fail much later with LNK1112. +if(NOT VCPKG_TARGET_TRIPLET STREQUAL _mwg_expected_triplet) + message(FATAL_ERROR + "VCPKG_TARGET_TRIPLET is '${VCPKG_TARGET_TRIPLET}', but the compiler targets ${MWG_PLATFORM} " + "(expected '${_mwg_expected_triplet}').\n" + "In CLion, assign this profile a Visual Studio toolchain with the matching architecture " + "(amd64 for x64 presets, x86 or amd64_x86 for x86 presets), then Reset Cache and Reload.") +endif() +unset(_mwg_expected_triplet) + +# --- Windows SDK -------------------------------------------------------------- +# Visual Studio generators pin the SDK through CMAKE_SYSTEM_VERSION (set from +# MWG_WINDOWS_SDK before project()). Ninja takes the SDK from the vcvars +# environment and cannot pin it, so a mismatch is only reported. +if(NOT MWG_WINDOWS_SDK STREQUAL "10.0") + if(CMAKE_GENERATOR MATCHES "^Visual Studio") + if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION STREQUAL MWG_WINDOWS_SDK) + message(FATAL_ERROR + "Windows SDK ${MWG_WINDOWS_SDK} is not installed (Visual Studio selected " + "'${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}'). Install it, or configure with " + "-DMWG_WINDOWS_SDK=10.0 to use the newest installed SDK.") + endif() + else() + set(_mwg_sdk "$ENV{WindowsSDKVersion}") + string(REGEX REPLACE "[\\/]+$" "" _mwg_sdk "${_mwg_sdk}") + if(_mwg_sdk STREQUAL "" AND CMAKE_RC_COMPILER MATCHES "Windows Kits/10/bin/([0-9.]+)/") + set(_mwg_sdk "${CMAKE_MATCH_1}") + endif() + if(NOT _mwg_sdk STREQUAL MWG_WINDOWS_SDK) + message(WARNING + "The build environment uses Windows SDK '${_mwg_sdk}', not ${MWG_WINDOWS_SDK}. " + "Ninja cannot pin the SDK; it comes from vcvarsall.bat. Set the Version field of the " + "CLion Visual Studio toolchain to ${MWG_WINDOWS_SDK} (equivalent to " + "'vcvarsall.bat ${MWG_WINDOWS_SDK}'), or set -DMWG_WINDOWS_SDK=10.0 to accept the newest SDK.") + endif() + unset(_mwg_sdk) + endif() +endif() + +# --- runtime folder ----------------------------------------------------------- +get_property(_mwg_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(_mwg_multi_config) + set(MWG_RUNTIME_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$") +else() + set(MWG_RUNTIME_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() +unset(_mwg_multi_config) + +message(STATUS "MapWinGIS6: ${MWG_PLATFORM}, MSVC ${CMAKE_CXX_COMPILER_VERSION}, triplet ${VCPKG_TARGET_TRIPLET}") diff --git a/cmake/MwgRuntimeDeploy.cmake b/cmake/MwgRuntimeDeploy.cmake new file mode 100644 index 0000000..10bfbdf --- /dev/null +++ b/cmake/MwgRuntimeDeploy.cmake @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Runtime data deployment (MSBuild: MwgDeployGdalRuntimeData). +# +# vcpkg installs the GDAL and PROJ data under share/; GdalEnvironment looks for +# "gdal-data" and "proj" beside the loaded module. The target +# MapWinGIS.RuntimeData copies both into the runtime folder. A stamp file in +# that folder keeps the copy from running on every build: it reruns only when +# a data file changes or the runtime folder was cleaned. + +option(MWG_DEPLOY_RUNTIME_DATA "Copy gdal-data and proj into the runtime folder (MSBuild: MwgDeployRuntimeData)" ON) + +function(mwg_add_runtime_data_target) + set(share_dir "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share") + set(gdal_dir "${share_dir}/gdal") + set(proj_dir "${share_dir}/proj") + + if(NOT MWG_DEPLOY_RUNTIME_DATA) + add_custom_target(MapWinGIS.RuntimeData) + return() + endif() + + if(NOT EXISTS "${gdal_dir}") + message(WARNING + "GDAL runtime data was not found in ${gdal_dir}. " + "Run build\\scripts\\bootstrap-vcpkg.ps1 and reconfigure.") + add_custom_target(MapWinGIS.RuntimeData) + return() + endif() + + file(GLOB_RECURSE data_files CONFIGURE_DEPENDS "${gdal_dir}/*" "${proj_dir}/*") + + set(stamp "${MWG_RUNTIME_DIR}/.mwg-runtime-data.stamp") + set(commands + COMMAND "${CMAKE_COMMAND}" -E copy_directory_if_different "${gdal_dir}" "${MWG_RUNTIME_DIR}/gdal-data") + if(EXISTS "${proj_dir}") + list(APPEND commands + COMMAND "${CMAKE_COMMAND}" -E copy_directory_if_different "${proj_dir}" "${MWG_RUNTIME_DIR}/proj") + endif() + + add_custom_command( + OUTPUT "${stamp}" + ${commands} + COMMAND "${CMAKE_COMMAND}" -E touch "${stamp}" + DEPENDS ${data_files} + COMMENT "Deploying GDAL and PROJ runtime data" + VERBATIM + ) + add_custom_target(MapWinGIS.RuntimeData DEPENDS "${stamp}") +endfunction() diff --git a/cmake/MwgVcpkg.cmake b/cmake/MwgVcpkg.cmake new file mode 100644 index 0000000..30c3e50 --- /dev/null +++ b/cmake/MwgVcpkg.cmake @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# vcpkg integration. Included by the root CMakeLists.txt BEFORE project(), so +# that the toolchain file and the vcpkg variables are in place when project() +# runs vcpkg's manifest install. +# +# Values from a preset or the command line always win; this module only fills +# in what is missing. + +# --- vcpkg root --------------------------------------------------------------- +# Same order as build/scripts/bootstrap-vcpkg.ps1 and Directory.Build.props: +# 1. MWG_VCPKG_ROOT (cache variable or environment variable) +# 2. /../vcpkg +# 3. VCPKG_ROOT +function(_mwg_is_vcpkg_root out_var dir) + if(EXISTS "${dir}/.vcpkg-root" AND EXISTS "${dir}/scripts/buildsystems/vcpkg.cmake") + set(${out_var} TRUE PARENT_SCOPE) + else() + set(${out_var} FALSE PARENT_SCOPE) + endif() +endfunction() + +function(_mwg_resolve_vcpkg_root out_var) + # An explicit MWG_VCPKG_ROOT that is not a vcpkg root is a mistake, not a hint. + set(explicit "${MWG_VCPKG_ROOT}") + if(explicit STREQUAL "" AND DEFINED ENV{MWG_VCPKG_ROOT}) + set(explicit "$ENV{MWG_VCPKG_ROOT}") + endif() + if(NOT explicit STREQUAL "") + file(TO_CMAKE_PATH "${explicit}" explicit) + _mwg_is_vcpkg_root(ok "${explicit}") + if(NOT ok) + message(FATAL_ERROR + "MWG_VCPKG_ROOT points to '${explicit}', which is not a vcpkg root.\n" + "Run 'pwsh -File build\\scripts\\bootstrap-vcpkg.ps1' or fix MWG_VCPKG_ROOT.") + endif() + set(${out_var} "${explicit}" PARENT_SCOPE) + return() + endif() + + cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH repo_parent) + set(candidates "${repo_parent}/vcpkg") + if(DEFINED ENV{VCPKG_ROOT} AND NOT "$ENV{VCPKG_ROOT}" STREQUAL "") + file(TO_CMAKE_PATH "$ENV{VCPKG_ROOT}" env_root) + list(APPEND candidates "${env_root}") + endif() + + foreach(candidate IN LISTS candidates) + _mwg_is_vcpkg_root(ok "${candidate}") + if(ok) + set(${out_var} "${candidate}" PARENT_SCOPE) + return() + endif() + endforeach() + + message(FATAL_ERROR + "No vcpkg installation found (looked at MWG_VCPKG_ROOT, ${repo_parent}/vcpkg and VCPKG_ROOT).\n" + "Run 'pwsh -File build\\scripts\\bootstrap-vcpkg.ps1' once, or pass -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake.") +endfunction() + +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) + _mwg_resolve_vcpkg_root(_mwg_vcpkg_root) + set(CMAKE_TOOLCHAIN_FILE "${_mwg_vcpkg_root}/scripts/buildsystems/vcpkg.cmake" + CACHE FILEPATH "vcpkg toolchain file, resolved from MWG_VCPKG_ROOT, ../vcpkg or VCPKG_ROOT") + message(STATUS "vcpkg root: ${_mwg_vcpkg_root}") +endif() + +# --- triplets ----------------------------------------------------------------- +# The presets pin the triplets. Without a preset, derive the target triplet from +# the generator platform (Visual Studio) or the vcvars environment (Ninja). +# Without an explicit triplet vcpkg silently picks x64-windows and builds GDAL a +# second time with a different toolset. MwgPlatform.cmake checks after +# project() that the triplet matches the compiler. +if(NOT DEFINED VCPKG_TARGET_TRIPLET) + if(CMAKE_GENERATOR_PLATFORM MATCHES "^(Win32|x86)" OR "$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "x86") + set(_mwg_default_triplet "x86-windows-v145") + else() + set(_mwg_default_triplet "x64-windows-v145") + endif() + set(VCPKG_TARGET_TRIPLET "${_mwg_default_triplet}" CACHE STRING "vcpkg target triplet") +endif() + +if(NOT DEFINED VCPKG_HOST_TRIPLET) + set(VCPKG_HOST_TRIPLET "x64-windows-v145" CACHE STRING "vcpkg host triplet") +endif() + +# --- install tree ------------------------------------------------------------- +# One CMake install tree per platform, shared by Debug and Release and separate +# from the MSBuild tree (vcpkg_installed/), see docs/CMAKE-MIGRATION.md (D2). +if(NOT DEFINED VCPKG_INSTALLED_DIR) + if(VCPKG_TARGET_TRIPLET MATCHES "^x86-") + set(_mwg_vcpkg_platform "Win32") + else() + set(_mwg_vcpkg_platform "x64") + endif() + set(VCPKG_INSTALLED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg_installed/cmake-${_mwg_vcpkg_platform}" + CACHE PATH "vcpkg install tree used by the CMake build") +endif() + +# --- manifest features -------------------------------------------------------- +# vcpkg.cmake runs 'vcpkg install' on every configure with these features, so a +# change takes effect on the next reconfigure (CLion: Reset Cache and Reload +# when a cached value is stale). +if(NOT DEFINED VCPKG_MANIFEST_FEATURES) + set(_mwg_features "") + if(MWG_BUILD_TESTS) + list(APPEND _mwg_features "tests") + endif() + set(VCPKG_MANIFEST_FEATURES "${_mwg_features}" CACHE STRING "vcpkg manifest features (tests, full-drivers)") +elseif(MWG_BUILD_TESTS AND NOT "tests" IN_LIST VCPKG_MANIFEST_FEATURES) + message(STATUS "MWG_BUILD_TESTS is ON: adding the 'tests' vcpkg manifest feature") + list(APPEND VCPKG_MANIFEST_FEATURES "tests") +endif() diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 53a2fa9..d8315d0 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -196,6 +196,67 @@ the MSBuild solution above stays the supported build. The audit of the current MSBuild settings and the decisions the CMake build follows are in [CMAKE-MIGRATION.md](CMAKE-MIGRATION.md). +### Building with CMake + +The CMake build currently covers `MapWinGIS.Core.dll`; the ActiveX control, +tests and samples follow. It needs CMake 3.28 or later and Ninja; both ship +with Visual Studio 2026 and CLion. Run the bootstrap script first, then +configure from a VS 2026 Developer PowerShell (or any shell after +`vcvarsall.bat x64`): + +```powershell +pwsh -File build\scripts\bootstrap-vcpkg.ps1 +cmake --preset x64-debug +cmake --build --preset x64-debug +``` + +| Preset | Generator | Build directory | +|---|---|---| +| `x64-debug`, `x64-release` | Ninja, x64 | `out\b\x64d`, `out\b\x64r` | +| `x86-debug`, `x86-release` | Ninja, Win32 (vcvars `x86` or `amd64_x86`) | `out\b\x86d`, `out\b\x86r` | +| `vs2026-x64`, `vs2026-x86` | Visual Studio 18 2026, `-T v145` (build presets `vs2026-x64-debug`, ...) | `out\b\vs-x64`, `out\b\vs-x86` | + +- Output goes to `\bin` (`bin\` for the Visual Studio + presets) and import libraries to `lib`, never to the MSBuild `bin\` folder, so + both builds can be used side by side. +- The vcpkg root is found in the same order as above. CMake uses its own install + tree, `vcpkg_installed\cmake-`, shared by Debug and Release; the + binary cache makes it cheap. +- Manifest features come from `VCPKG_MANIFEST_FEATURES` (presets: `tests`). A + change takes effect on the next configure; in CLion use + **Tools | CMake | Reset Cache and Reload Project** when a cached value is stale. +- Only `Debug` and `Release` are supported. Other `CMAKE_BUILD_TYPE` values, + such as CLion's default `RelWithDebInfo`, are rejected. +- Ninja takes the toolset and Windows SDK from the vcvars environment and + cannot pin them; configure warns when the SDK differs from `MWG_WINDOWS_SDK`. + The Visual Studio presets pin both. +- The presets' `architecture` only informs Visual Studio. In CLion, give each + preset profile a Visual Studio toolchain with the matching architecture + (`amd64` for x64, `x86` or `amd64_x86` for x86); a mismatch stops configure + with an explanation. + +| CMake option | Default | MSBuild equivalent | +|---|---|---| +| `MWG_WINDOWS_SDK` | `10.0.28000.0` | `MwgWindowsSdk` (`10.0` = newest) | +| `MWG_VCPKG_ROOT` | resolved | `MwgVcpkgRoot` | +| `VCPKG_MANIFEST_FEATURES` | `tests` | `MwgVcpkgFeatures` | +| `MWG_ENABLE_ASAN` | `OFF` | `MwgEnableAsan` | +| `MWG_DEPLOY_RUNTIME_DATA` | `ON` | `MwgDeployRuntimeData` | +| `MWG_BUILD_TESTS` | `ON` | — (adds the `tests` feature) | +| `MWG_BUILD_SAMPLES` | `ON` | — | + +Machine-specific settings belong in an uncommitted `CMakeUserPresets.json` +that inherits from the presets, for example: + +```json +{ + "version": 6, + "configurePresets": [ + { "name": "my-x64-debug", "inherits": "x64-debug", "cacheVariables": { "MWG_WINDOWS_SDK": "10.0" } } + ] +} +``` + To measure flag and binary parity, `build\scripts\Compare-CompileCommands.ps1` records a detailed MSBuild log and writes a normalised report to `out\parity\`. It finds MSBuild through `MWG_MSBUILD`, `PATH` or `vswhere`, so it also works diff --git a/docs/CMAKE-MIGRATION.md b/docs/CMAKE-MIGRATION.md index 89e6eb6..7cf1e68 100644 --- a/docs/CMAKE-MIGRATION.md +++ b/docs/CMAKE-MIGRATION.md @@ -300,5 +300,31 @@ pwsh -File build\scripts\Compare-CompileCommands.ps1 -MSBuildLog out\parity\msbu | Native tests | 173 run, 170 passed, 3 skipped | — | The three skipped tests need `MWG_TEST_ACTIVEX_HOST`, `MWG_RUN_NETWORK_GDAL_TESTS` -or `MWG_RUN_RENDERING_BASELINE`. The Win32 baseline is collected in the phase -that first builds Win32 with CMake. +or `MWG_RUN_RENDERING_BASELINE`. The Win32 baseline was collected in Phase 1 +(Debug and Release, 42 TUs / 4 links, 0 warnings). + +## Parity status + +### MapWinGIS.Core (Phase 1) + +Measured for Debug and Release, x64 and Win32 (Ninja presets): + +- Compile options: none lost; 0 warnings in both builds. +- Link options: none lost. The remaining differences are explained by the + parity script (`diff.links..Explained`): + - `/MANIFEST /manifest:embed /MANIFESTUAC:...`: CMake's `vs_link_dll` wrapper + generates and embeds the manifest with `mt.exe`; the embedded manifest + resource is byte-identical to the MSBuild one. + - `/TLBID:1`: only relevant when a type library resource is embedded (OCX). + - vcpkg `/LIBPATH` and `*.lib` auto-link: replaced by `GDAL::GDAL` and + `GEOS::geos_c` with full library paths. + - `odbc32.lib odbccp32.lib`: MSBuild defaults, unused, not in CMake's default + `CMAKE_CXX_STANDARD_LIBRARIES`. + - `/version:0.0`: added by CMake; the image version is 0.0 in both builds. +- Added compile options, from the imported targets: `/I/include/geos` + (GEOS's interface include directory) and, in Debug, `/DGDAL_DEBUG` + (`GDAL::GDAL` interface definition for the debug library). +- Win32 needs `/Oy- /analyze-` and, in Release, `/SAFESEH`; MSBuild adds them + by default for that platform. +- `dumpbin`: identical dependents, exports (260), subsystem and DLL + characteristics. diff --git a/src/MapWinGIS.Core/CMakeLists.txt b/src/MapWinGIS.Core/CMakeLists.txt new file mode 100644 index 0000000..9f7bcc3 --- /dev/null +++ b/src/MapWinGIS.Core/CMakeLists.txt @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# MapWinGIS.Core.dll: the mapping engine. No COM, ATL or MFC. + +find_package(GDAL CONFIG REQUIRED) +find_package(GEOS CONFIG REQUIRED) + +set(MWG_CORE_PUBLIC_HEADERS + include/mapwingis/core/Api.h + include/mapwingis/core/Enums.h + include/mapwingis/core/Error.h + include/mapwingis/core/Extent.h + include/mapwingis/gdal/GdalAlgorithmRunner.h + include/mapwingis/gdal/GdalConfig.h + include/mapwingis/gdal/GdalDiagnostics.h + include/mapwingis/gdal/GdalEnvironment.h + include/mapwingis/gdal/GdalModern.h + include/mapwingis/gdal/GdalOperationResult.h + include/mapwingis/gdal/GdalProgress.h + include/mapwingis/gdal/GdalTypedOptions.h + include/mapwingis/gdal/GdalUtils.h + include/mapwingis/rendering/VectorRenderScene.h + include/mapwingis/vector/FieldInfo.h + include/mapwingis/vector/VectorDataset.h +) + +set(MWG_CORE_PRIVATE_HEADERS + src/internal/GdalExecution.h + src/internal/GdalProgressAdapterAccess.h + src/internal/Utf8.h + src/internal/VectorLayerAccess.h +) + +set(MWG_CORE_SOURCES + src/gdal/GdalAlgorithmRunner.cpp + src/gdal/GdalEnvironment.cpp + src/gdal/GdalModern.cpp + src/gdal/GdalOperationResult.cpp + src/gdal/GdalRuntime.cpp + src/gdal/GdalTypedOptions.cpp + src/gdal/GdalUtils.cpp + src/rendering/VectorRenderScene.cpp + src/vector/VectorDataset.cpp +) + +add_library(MapWinGIS.Core SHARED) +add_library(MapWinGIS::Core ALIAS MapWinGIS.Core) + +target_sources(MapWinGIS.Core PRIVATE + ${MWG_CORE_PUBLIC_HEADERS} + ${MWG_CORE_PRIVATE_HEADERS} + ${MWG_CORE_SOURCES} +) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${MWG_CORE_PUBLIC_HEADERS} + ${MWG_CORE_PRIVATE_HEADERS} + ${MWG_CORE_SOURCES} +) + +target_include_directories(MapWinGIS.Core + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" +) + +# MWG_CORE_BUILD selects __declspec(dllexport) in mapwingis/core/Api.h. Using it +# as the DEFINE_SYMBOL also replaces CMake's default MapWinGIS_Core_EXPORTS. +set_target_properties(MapWinGIS.Core PROPERTIES DEFINE_SYMBOL MWG_CORE_BUILD) + +# No public header includes GDAL or GEOS, so both stay private. GEOS is used +# directly: GdalEnvironment::GeosVersion() calls GEOSversion(). +target_link_libraries(MapWinGIS.Core PRIVATE GDAL::GDAL GEOS::geos_c) + +mwg_apply_common_options(MapWinGIS.Core) + +set_target_properties(MapWinGIS.Core PROPERTIES FOLDER "src") + +add_dependencies(MapWinGIS.Core MapWinGIS.RuntimeData)