Thanks to visit codestin.com
Credit goes to github.com

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
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,14 @@ python_extract_version_info(
message(STATUS "PY_VERSION : ${PY_VERSION}")
message(STATUS "PY_VERSION_LONG: ${PY_VERSION_LONG}")

# Extract "Field3" value and set variable PY_FIELD3_VALUE
python_compute_release_field3_value(
VERSION_PATCH "${PY_VERSION_PATCH}"
RELEASE_LEVEL "${PY_RELEASE_LEVEL}"
RELEASE_SERIAL "${PY_RELEASE_SERIAL}"
)
message(STATUS "PY_FIELD3_VALUE: ${PY_FIELD3_VALUE}")

# Check version
if(NOT DEFINED _download_${PY_VERSION_LONG}_md5)
message(WARNING "warning: selected python version '${PY_VERSION_LONG}' is not tested.")
Expand Down Expand Up @@ -634,6 +642,9 @@ add_subdirectory(cmake/tools CMakeBuild/tools)
if(BUILD_WININST)
add_subdirectory(cmake/PC/bdist_wininst CMakeBuild/bdist_wininst)
endif()
if(WIN32)
add_subdirectory(cmake/PC/launcher CMakeBuild/launcher)
endif()

# Ensure the "_testcapi" extension introduced in Python 3.12 can find
# find "Python3.lib" as it is specified in "PC/pyconfig." using
Expand Down Expand Up @@ -861,6 +872,10 @@ if(BUILD_TESTING)
python_extract_version_info
cmake/PythonExtractVersionInfo.cmake
)
add_cmakescript_test(
python_compute_release_field3_value
cmake/PythonExtractVersionInfo.cmake
)
endif()

include(CMakePackageConfigHelpers)
Expand Down
139 changes: 139 additions & 0 deletions cmake/PC/launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@

function(_add_executable_without_windows target_name)
# Work around the lack of a "target_remove_definitions()" CMake command by
# explicitly undefining _WINDOWS. The following alternatives are either ineffective
# or not applicable:
#
# (1) Modifying and restoring CMAKE_C_FLAGS does not work reliably, as CMake appears
# to apply cached values during the generation step after the function executes.
#
# (2) Using get_target_property/set_property to manipulate COMPILE_DEFINITIONS
# is ineffective, since /D_WINDOWS is introduced via CMAKE_C_FLAGS and not
# associated with the target's properties.
#
# See: https://gitlab.kitware.com/cmake/cmake/-/issues/19796

add_executable(${target_name} ${ARGN})

# Note: /U_WINDOWS overrides the implicit /D_WINDOWS flag, resulting in MSVC warning D9025.
# This warning is harmless and cannot be suppressed directly.
target_compile_options(${target_name} PRIVATE /U_WINDOWS)
endfunction()

# Install tree directory
set(LAUNCHER_INSTALL_DIR ${BIN_BUILD_DIR})

# Build tree directory
set(LAUNCHER_BUILD_DIR ${PROJECT_BINARY_DIR}/${LAUNCHER_INSTALL_DIR})

set(launcher_targets)

set(build_venvlauncher 0)
# While support for building "venvlauncher" was introduced in Python 3.3,
# we require at least Python 3.9 to avoid the need to generate "pythonnt_rc.h",
# which was removed in python/cpython@4efc3360c9a ("bpo-41054: Simplify resource compilation on Windows (GH-21004)", 2020-06-24).
if(PY_VERSION VERSION_GREATER_EQUAL "3.9")
set(build_venvlauncher 1)
endif()

if(build_venvlauncher)
set(target_sources
${SRC_DIR}/PC/launcher.c
${SRC_DIR}/PC/pylauncher.rc
)
set(target_include_dirs
${SRC_DIR}/PC/
)
set(target_libraries
version
)

# venvlauncher
set(target_name "venvlauncher")

_add_executable_without_windows(${target_name} ${target_sources})
target_include_directories(${target_name} PRIVATE ${target_include_dirs})
target_link_libraries(${target_name} PRIVATE ${target_libraries})
target_compile_definitions(${target_name}
PRIVATE
_CONSOLE
_UNICODE
VENV_REDIRECT
PY_ICON # For "PC/pylauncher.rc"
FIELD3=${PY_FIELD3_VALUE}
)
list(APPEND launcher_targets ${target_name})

# venvwlauncher
set(target_name "venvwlauncher")

add_executable(${target_name} WIN32 ${target_sources})
target_include_directories(${target_name} PRIVATE ${target_include_dirs})
target_link_libraries(${target_name} PRIVATE ${target_libraries})
target_compile_definitions(${target_name}
PRIVATE
_UNICODE
_WINDOWS
VENV_REDIRECT
PYW_ICON # For "PC/pylauncher.rc"
FIELD3=${PY_FIELD3_VALUE}
)
list(APPEND launcher_targets ${target_name})
endif()

set(build_pylauncher 0)
if(PY_VERSION VERSION_GREATER_EQUAL "3.11")
set(build_pylauncher 1)
endif()

if(build_pylauncher)
set(target_sources
${SRC_DIR}/PC/launcher2.c
${SRC_DIR}/PC/pylauncher.rc
)
set(target_include_dirs
${SRC_DIR}/PC/
)
set(target_libraries
pathcch
shell32
)

# pylauncher
set(target_name "pylauncher")

_add_executable_without_windows(${target_name} ${target_sources})
target_include_directories(${target_name} PRIVATE ${target_include_dirs})
target_link_libraries(${target_name} PRIVATE ${target_libraries})
target_compile_definitions(${target_name}
PRIVATE
_CONSOLE
_UNICODE
FIELD3=${PY_FIELD3_VALUE}
)
list(APPEND launcher_targets ${target_name})

# pywlauncher
set(target_name "pywlauncher")

add_executable(${target_name} WIN32 ${target_sources})
target_include_directories(${target_name} PRIVATE ${target_include_dirs})
target_link_libraries(${target_name} PRIVATE ${target_libraries})
target_compile_definitions(${target_name}
PRIVATE
_UNICODE
_WINDOWS
FIELD3=${PY_FIELD3_VALUE}
)

list(APPEND launcher_targets ${target_name})
endif()

if(launcher_targets)
set_target_properties(${launcher_targets}
PROPERTIES
LINK_FLAGS "/MANIFEST:NO"
RUNTIME_OUTPUT_DIRECTORY ${LAUNCHER_INSTALL_DIR}
)
install(TARGETS ${launcher_targets} RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime)
endif()
128 changes: 128 additions & 0 deletions cmake/PythonExtractVersionInfo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,131 @@ endfunction()
if(TEST_python_extract_version_info)
python_extract_version_info_test()
endif()

# Compute the "Field3" value from a Python version's patch, release level, and serial.
#
# The Field3 value is defined as:
# Field3 = patch * 1000 + release_level_number * 10 + release_serial
#
# Where release_level_number is:
# - 10 for alpha (a)
# - 11 for beta (b)
# - 12 for release candidate (rc)
# - 15 for final (no pre-release tag)
#
# Arguments:
# VERSION_PATCH - Patch version (Z in X.Y.Z)
# RELEASE_LEVEL - One of 'a', 'b', 'rc', or empty
# RELEASE_SERIAL - An integer (or empty for final)
#
# Output:
# Sets variable PY_FIELD3_VALUE in the caller's scope.
function(python_compute_release_field3_value)
set(options)
set(oneValueArgs
VERSION_PATCH
RELEASE_LEVEL
RELEASE_SERIAL
)
set(multiValueArgs)
cmake_parse_arguments(MY
"${options}"
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN}
)

# Default ReleaseLevelNumber = 15 (final release)
set(_level_number 15)

# Map release level string to numeric code
if(MY_RELEASE_LEVEL STREQUAL "a")
set(_level_number 10)
elseif(MY_RELEASE_LEVEL STREQUAL "b")
set(_level_number 11)
elseif(MY_RELEASE_LEVEL STREQUAL "rc")
set(_level_number 12)
endif()

# Fallback for empty serial
if("${MY_RELEASE_SERIAL}" STREQUAL "")
set(MY_RELEASE_SERIAL 0)
endif()

# Convert to integers
set(_patch "${MY_VERSION_PATCH}")
set(_serial "${MY_RELEASE_SERIAL}")
math(EXPR _field3 "${_patch} * 1000 + ${_level_number} * 10 + ${_serial}")

# Return in the variable specified by caller
set(PY_FIELD3_VALUE "${_field3}" PARENT_SCOPE)
endfunction()

#
# cmake -DTEST_python_compute_release_field3_value:BOOL=ON -P PythonExtractVersionInfo.cmake
#
function(python_compute_release_field3_value_test)

function(display_field3_test_values)
message(" PATCH: ${patch}")
message(" LEVEL: ${level}")
message(" SERIAL: ${serial}")
message(" Expected: ${expected}")
message(" Computed: ${PY_FIELD3_VALUE}")
endfunction()

set(id 1)
set(case${id}_patch 2)
set(case${id}_level "") # final release
set(case${id}_serial "") # default to 0
set(case${id}_expected 2150) # 2*1000 + 15*10 + 0

set(id 2)
set(case${id}_patch 2)
set(case${id}_level a)
set(case${id}_serial 1)
set(case${id}_expected 2101) # 2*1000 + 10*10 + 1

set(id 3)
set(case${id}_patch 5)
set(case${id}_level b)
set(case${id}_serial 0)
set(case${id}_expected 5110) # 5*1000 + 11*10 + 0

set(id 4)
set(case${id}_patch 14)
set(case${id}_level rc)
set(case${id}_serial 3)
set(case${id}_expected 14123) # 14*1000 + 12*10 + 3

set(id 5)
set(case${id}_patch 0)
set(case${id}_level "") # final
set(case${id}_serial "") # default 0
set(case${id}_expected 150) # 0 * 1000 + 15 * 10 + 0

foreach(caseid RANGE 1 ${id})
set(patch "${case${caseid}_patch}")
set(level "${case${caseid}_level}")
set(serial "${case${caseid}_serial}")
set(expected "${case${caseid}_expected}")

python_compute_release_field3_value(
VERSION_PATCH "${patch}"
RELEASE_LEVEL "${level}"
RELEASE_SERIAL "${serial}"
)

if(NOT "${PY_FIELD3_VALUE}" STREQUAL "${expected}")
message("FAILED: case ${caseid}")
display_field3_test_values()
message(FATAL_ERROR "Test failed at case ${caseid}")
endif()
endforeach()

message("SUCCESS")
endfunction()

if(TEST_python_compute_release_field3_value)
python_compute_release_field3_value_test()
endif()