From 9600b04a3cba7f526bf678183ff0a9b59e6d73eb Mon Sep 17 00:00:00 2001 From: Shawn Zeng Date: Fri, 7 Nov 2025 22:34:56 +0800 Subject: [PATCH] Bug 5591814: Support "make install" for Windows --- CMakeLists.txt | 3 + cmake/InstallSamples.cmake | 306 ++++++++++++++++++++++++++++--------- 2 files changed, 239 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dd20c7b..92e580e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,4 +22,7 @@ endif() set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --extended-lambda") +# Include installation configuration before processing samples +include(cmake/InstallSamples.cmake) + add_subdirectory(Samples) diff --git a/cmake/InstallSamples.cmake b/cmake/InstallSamples.cmake index fc4b2c7e..ee6f269c 100644 --- a/cmake/InstallSamples.cmake +++ b/cmake/InstallSamples.cmake @@ -15,57 +15,105 @@ # # Users can override by setting CMAKE_INSTALL_PREFIX or CUDA_SAMPLES_INSTALL_DIR -# Detect target architecture - use lowercase of CMAKE_SYSTEM_PROCESSOR -string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" TARGET_ARCH) +# Configure paths only once (but always define the function below) +if(NOT CUDA_SAMPLES_INSTALL_CONFIGURED) + set(CUDA_SAMPLES_INSTALL_CONFIGURED TRUE CACHE INTERNAL "InstallSamples configuration guard") -# Detect target OS -if(WIN32) - set(TARGET_OS "windows") -elseif(APPLE) - set(TARGET_OS "darwin") -elseif(UNIX) - if(CMAKE_SYSTEM_NAME MATCHES QNX) - set(TARGET_OS "qnx") + # Detect target architecture - use lowercase of CMAKE_SYSTEM_PROCESSOR + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" TARGET_ARCH_TEMP) + set(TARGET_ARCH "${TARGET_ARCH_TEMP}" CACHE INTERNAL "Target architecture") + + # Detect target OS + if(WIN32) + set(TARGET_OS_TEMP "windows") + elseif(APPLE) + set(TARGET_OS_TEMP "darwin") + elseif(UNIX) + if(CMAKE_SYSTEM_NAME MATCHES QNX) + set(TARGET_OS_TEMP "qnx") + else() + set(TARGET_OS_TEMP "linux") + endif() else() - set(TARGET_OS "linux") + set(TARGET_OS_TEMP "unknown") endif() -else() - set(TARGET_OS "unknown") -endif() + set(TARGET_OS "${TARGET_OS_TEMP}" CACHE INTERNAL "Target OS") -# Get build type (default to release if not specified) -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "release") -endif() -string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) + # Detect if using multi-config generator (Visual Studio, Xcode, Ninja Multi-Config) + get_property(MULTI_CONFIG_TEMP GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + set(MULTI_CONFIG "${MULTI_CONFIG_TEMP}" CACHE INTERNAL "Multi-config generator flag") -# Set default install prefix to build/bin if not explicitly set by user -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/bin" CACHE PATH "Installation directory" FORCE) -endif() + # Get build type + if(MULTI_CONFIG) + # Multi-config generators: use $ which is evaluated at build/install time + set(BUILD_TYPE_EXPR "$>" CACHE INTERNAL "Build type expression") + set(BUILD_TYPE_MSG "multi-config (specified at build time)") + else() + # Single-config generators: use CMAKE_BUILD_TYPE (default to release if not specified) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") + endif() + string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER_TEMP) + set(BUILD_TYPE_EXPR "${BUILD_TYPE_LOWER_TEMP}" CACHE INTERNAL "Build type expression") + set(BUILD_TYPE_MSG "${BUILD_TYPE_LOWER_TEMP}") + endif() -# Create the installation path: bin/$(TARGET_ARCH)/$(TARGET_OS)/$(BUILD_TYPE) -set(CUDA_SAMPLES_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${TARGET_ARCH}/${TARGET_OS}/${BUILD_TYPE_LOWER}" CACHE PATH "Installation directory for CUDA samples") + # Set default install prefix to build/bin if not explicitly set by user + # Use the root binary directory (where CMakeCache.txt is located) + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + # Find the directory containing CMakeCache.txt (the root build directory) + set(ROOT_BINARY_DIR "${CMAKE_BINARY_DIR}") + set(PREV_DIR "") + set(MAX_ITERATIONS 50) + set(ITERATION 0) + while(NOT EXISTS "${ROOT_BINARY_DIR}/CMakeCache.txt" + AND NOT "${ROOT_BINARY_DIR}" STREQUAL "/" + AND NOT "${ROOT_BINARY_DIR}" STREQUAL "" + AND NOT "${ROOT_BINARY_DIR}" STREQUAL "${PREV_DIR}" + AND ITERATION LESS MAX_ITERATIONS) + set(PREV_DIR "${ROOT_BINARY_DIR}") + get_filename_component(ROOT_BINARY_DIR "${ROOT_BINARY_DIR}" DIRECTORY) + math(EXPR ITERATION "${ITERATION} + 1") + endwhile() + # If CMakeCache.txt wasn't found, fall back to CMAKE_BINARY_DIR + if(NOT EXISTS "${ROOT_BINARY_DIR}/CMakeCache.txt") + set(ROOT_BINARY_DIR "${CMAKE_BINARY_DIR}") + endif() + set(CMAKE_INSTALL_PREFIX "${ROOT_BINARY_DIR}/bin" CACHE PATH "Installation directory" FORCE) + endif() -# Print installation configuration only once -if(NOT CUDA_SAMPLES_INSTALL_CONFIG_PRINTED) + # Create the installation path: bin/$(TARGET_ARCH)/$(TARGET_OS)/$(BUILD_TYPE) + # For multi-config generators, this will be evaluated at install time + set(CUDA_SAMPLES_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${TARGET_ARCH}/${TARGET_OS}/${BUILD_TYPE_EXPR}" CACHE INTERNAL "Install directory for samples") + + # Print installation configuration message(STATUS "CUDA Samples installation configured:") message(STATUS " Architecture: ${TARGET_ARCH}") message(STATUS " OS: ${TARGET_OS}") - message(STATUS " Build Type: ${BUILD_TYPE_LOWER}") - message(STATUS " Install Directory: ${CUDA_SAMPLES_INSTALL_DIR}") - set(CUDA_SAMPLES_INSTALL_CONFIG_PRINTED TRUE CACHE INTERNAL "Installation config printed flag") + message(STATUS " Build Type: ${BUILD_TYPE_MSG}") + message(STATUS " Install Prefix: ${CMAKE_INSTALL_PREFIX}") + if(NOT MULTI_CONFIG) + message(STATUS " Install Directory: ${CUDA_SAMPLES_INSTALL_DIR}") + else() + message(STATUS " Install Directory: ${CMAKE_INSTALL_PREFIX}/${TARGET_ARCH}/${TARGET_OS}/") + endif() endif() # Function to setup installation for regular samples # This should be called after all targets are defined function(setup_samples_install) # Create an install script that will copy executables and specific file types - # - Executables: copied to flat root directory (easy access) - # - Data files: copied to subdirectories (preserves relative paths) - # - run_tests.py automatically tries flattened paths as fallback + # All files are copied to flat root directory # This script runs at install time, after the build is complete install(CODE " + # Determine the actual install directory based on the configuration + # CMAKE_INSTALL_CONFIG_NAME is available at install time for multi-config generators + if(DEFINED CMAKE_INSTALL_CONFIG_NAME) + string(TOLOWER \"\${CMAKE_INSTALL_CONFIG_NAME}\" INSTALL_BUILD_TYPE) + else() + set(INSTALL_BUILD_TYPE \"${BUILD_TYPE_EXPR}\") + endif() + set(INSTALL_DIR \"${CMAKE_INSTALL_PREFIX}/${TARGET_ARCH}/${TARGET_OS}/\${INSTALL_BUILD_TYPE}\") # Search in the current project's binary directory for built executables file(GLOB_RECURSE BINARY_FILES @@ -90,8 +138,45 @@ function(setup_samples_install) \"${CMAKE_CURRENT_SOURCE_DIR}/../../../../Common/data/*\") endif() + # Copy shared library files from bin/win64 directory (Windows only) + # These are pre-built DLLs like freeglut.dll, glew64.dll, etc. + set(SHARED_LIB_FILES \"\") + if(CMAKE_HOST_WIN32) + # Determine build configuration at install time + # CMAKE_INSTALL_CONFIG_NAME is set by CMake at install time for multi-config generators + if(DEFINED CMAKE_INSTALL_CONFIG_NAME) + string(TOLOWER \"\${CMAKE_INSTALL_CONFIG_NAME}\" INSTALL_CONFIG_LOWER) + else() + # Fallback for single-config generators + set(INSTALL_CONFIG_LOWER \"${BUILD_TYPE_EXPR}\") + endif() + + # Try multiple possible paths for bin/win64 directory + set(BIN_WIN64_PATHS + \"${CMAKE_CURRENT_SOURCE_DIR}/../../../bin/win64/\${INSTALL_CONFIG_LOWER}\" + \"${CMAKE_CURRENT_SOURCE_DIR}/../../../../bin/win64/\${INSTALL_CONFIG_LOWER}\" + \"${CMAKE_SOURCE_DIR}/bin/win64/\${INSTALL_CONFIG_LOWER}\" + ) + foreach(BIN_PATH IN LISTS BIN_WIN64_PATHS) + if(EXISTS \"\${BIN_PATH}\") + file(GLOB SHARED_LIB_FILES + LIST_DIRECTORIES false + \"\${BIN_PATH}/*.dll\") + if(SHARED_LIB_FILES) + break() + endif() + endif() + endforeach() + endif() + # Combine all lists - set(SAMPLE_FILES \${BINARY_FILES} \${SAMPLE_DATA_FILES} \${COMMON_DATA_FILES}) + set(SAMPLE_FILES \${BINARY_FILES} \${SAMPLE_DATA_FILES} \${COMMON_DATA_FILES} \${SHARED_LIB_FILES}) + + # Remove duplicates to avoid copying the same file multiple times + # This preserves the order, so files from earlier sources take precedence + list(REMOVE_DUPLICATES SAMPLE_FILES) + + set(INSTALLED_COUNT 0) # Filter to include executable files and specific file types foreach(SAMPLE_FILE IN LISTS SAMPLE_FILES) @@ -102,25 +187,38 @@ function(setup_samples_install) set(SHOULD_INSTALL FALSE) - # Skip build artifacts and CMake files - if(NOT SAMPLE_EXT MATCHES \"\\\\.(o|a|so|cmake)$\" AND + # Skip build artifacts, source files, and CMake files + # Note: .lib (Windows import libs) and .a (static libs) are excluded - link-time only + # .so (Linux shared libs) and .dll (Windows DLLs) are included - runtime dependencies + # Source files (.cu, .cpp, .c, .h, etc.) are excluded - not needed at runtime + if(NOT SAMPLE_EXT MATCHES \"\\\\.(o|a|cmake|obj|lib|exp|ilk|pdb|cu|cpp|cxx|cc|c|h|hpp|hxx|cuh|inl)$\" AND NOT SAMPLE_NAME MATCHES \"^(Makefile|cmake_install\\\\.cmake)$\" AND - NOT \"\${SAMPLE_FILE}\" MATCHES \"/CMakeFiles/\") + NOT \"\${SAMPLE_FILE}\" MATCHES \"/CMakeFiles/\" AND + NOT \"\${SAMPLE_FILE}\" MATCHES \"\\\\\\\\CMakeFiles\\\\\\\\\") # Check if file has required extension (fatbin, ptx, bc, raw, ppm) or is executable if(SAMPLE_EXT MATCHES \"\\\\.(fatbin|ptx|bc|raw|ppm)$\") set(SHOULD_INSTALL TRUE) + # Check for shared libraries: .dll (Windows) or .so (Linux) + elseif(SAMPLE_EXT MATCHES \"\\\\.(dll|so)$\") + set(SHOULD_INSTALL TRUE) + # On Windows, check for .exe extension + elseif(CMAKE_HOST_WIN32 AND SAMPLE_EXT MATCHES \"\\\\.(exe)$\") + set(SHOULD_INSTALL TRUE) else() - # Check if file is executable - if(IS_SYMLINK \"\${SAMPLE_FILE}\" OR - (EXISTS \"\${SAMPLE_FILE}\" AND NOT IS_DIRECTORY \"\${SAMPLE_FILE}\")) - execute_process( - COMMAND test -x \"\${SAMPLE_FILE}\" - RESULT_VARIABLE IS_EXEC - OUTPUT_QUIET ERROR_QUIET - ) - if(IS_EXEC EQUAL 0) - set(SHOULD_INSTALL TRUE) + # On Unix-like systems, check if file has executable permissions + if(NOT CMAKE_HOST_WIN32) + if(IS_SYMLINK \"\${SAMPLE_FILE}\" OR + (EXISTS \"\${SAMPLE_FILE}\" AND NOT IS_DIRECTORY \"\${SAMPLE_FILE}\")) + # Use test -x to check if file has executable permissions + execute_process( + COMMAND test -x \"\${SAMPLE_FILE}\" + RESULT_VARIABLE IS_EXEC + OUTPUT_QUIET ERROR_QUIET + ) + if(IS_EXEC EQUAL 0) + set(SHOULD_INSTALL TRUE) + endif() endif() endif() endif() @@ -128,36 +226,104 @@ function(setup_samples_install) if(SHOULD_INSTALL) get_filename_component(FILE_NAME \"\${SAMPLE_FILE}\" NAME) - set(DEST_FILE \"${CUDA_SAMPLES_INSTALL_DIR}/\${FILE_NAME}\") + set(DEST_FILE \"\${INSTALL_DIR}/\${FILE_NAME}\") - # Check if file is executable - execute_process( - COMMAND test -x \"\${SAMPLE_FILE}\" - RESULT_VARIABLE HAS_EXEC_BIT - OUTPUT_QUIET ERROR_QUIET - ) + # Determine file type based on extension + get_filename_component(FILE_EXT \"\${SAMPLE_FILE}\" EXT) + set(IS_EXECUTABLE FALSE) + set(IS_SHARED_LIB FALSE) + set(IS_DATA_FILE FALSE) + + # Check for known data file extensions first + if(FILE_EXT MATCHES \"\\\\.(fatbin|ptx|bc|raw|ppm)$\") + set(IS_DATA_FILE TRUE) + # Check if it's a shared library + elseif(FILE_EXT MATCHES \"\\\\.(dll|so)$\") + set(IS_SHARED_LIB TRUE) + # Check if it's an executable + else() + # On Windows, check for .exe extension (not .dll - those are libraries) + if(CMAKE_HOST_WIN32) + if(FILE_EXT MATCHES \"\\\\.(exe)$\") + set(IS_EXECUTABLE TRUE) + endif() + else() + # On Unix-like systems, check for no extension (typical for executables) + # .so files are shared libraries, not executables + if(FILE_EXT STREQUAL \"\") + set(IS_EXECUTABLE TRUE) + endif() + endif() + endif() get_filename_component(DEST_DIR \"\${DEST_FILE}\" DIRECTORY) - if(HAS_EXEC_BIT EQUAL 0) - # File is executable - copy with execute permissions - message(STATUS \"Installing executable: \${DEST_FILE}\") - file(COPY \"\${SAMPLE_FILE}\" - DESTINATION \"\${DEST_DIR}\" - FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE) - else() - # Regular file - copy without execute permissions - message(STATUS \"Installing data file: \${DEST_FILE}\") - file(COPY \"\${SAMPLE_FILE}\" - DESTINATION \"\${DEST_DIR}\" - FILE_PERMISSIONS OWNER_READ OWNER_WRITE - GROUP_READ - WORLD_READ) + # Check if this is a Common data file that already exists + # Skip copying to avoid redundant operations when multiple samples use the same files + set(SKIP_COPY FALSE) + if(\"\${SAMPLE_FILE}\" MATCHES \"/Common/data/\" AND EXISTS \"\${DEST_FILE}\") + set(SKIP_COPY TRUE) endif() + + if(NOT SKIP_COPY) + if(IS_DATA_FILE) + # Data file (.raw, .ppm, .ptx, .fatbin, .bc) - copy without execute permissions + message(STATUS \"Installing data file: \${DEST_FILE}\") + if(CMAKE_HOST_WIN32) + file(COPY \"\${SAMPLE_FILE}\" DESTINATION \"\${DEST_DIR}\") + else() + file(COPY \"\${SAMPLE_FILE}\" + DESTINATION \"\${DEST_DIR}\" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE + GROUP_READ + WORLD_READ) + endif() + math(EXPR INSTALLED_COUNT \"\${INSTALLED_COUNT} + 1\") + elseif(IS_EXECUTABLE) + # File is executable - copy with execute permissions (Unix) or as-is (Windows) + message(STATUS \"Installing executable: \${DEST_FILE}\") + if(CMAKE_HOST_WIN32) + file(COPY \"\${SAMPLE_FILE}\" DESTINATION \"\${DEST_DIR}\") + else() + file(COPY \"\${SAMPLE_FILE}\" + DESTINATION \"\${DEST_DIR}\" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + endif() + math(EXPR INSTALLED_COUNT \"\${INSTALLED_COUNT} + 1\") + elseif(IS_SHARED_LIB) + # Shared library - copy with appropriate permissions + message(STATUS \"Installing shared library: \${DEST_FILE}\") + if(CMAKE_HOST_WIN32) + file(COPY \"\${SAMPLE_FILE}\" DESTINATION \"\${DEST_DIR}\") + else() + file(COPY \"\${SAMPLE_FILE}\" + DESTINATION \"\${DEST_DIR}\" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE + GROUP_READ + WORLD_READ) + endif() + math(EXPR INSTALLED_COUNT \"\${INSTALLED_COUNT} + 1\") + else() + # Unknown file type - copy as regular file without execute permissions + message(STATUS \"Installing file: \${DEST_FILE}\") + if(CMAKE_HOST_WIN32) + file(COPY \"\${SAMPLE_FILE}\" DESTINATION \"\${DEST_DIR}\") + else() + file(COPY \"\${SAMPLE_FILE}\" + DESTINATION \"\${DEST_DIR}\" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE + GROUP_READ + WORLD_READ) + endif() + math(EXPR INSTALLED_COUNT \"\${INSTALLED_COUNT} + 1\") + endif() + endif() # NOT SKIP_COPY endif() endif() endforeach() + + message(STATUS \"Installation complete: \${INSTALLED_COUNT} files installed to \${INSTALL_DIR}\") ") endfunction()