diff --git a/.github/vcpkg-baseline.txt b/.github/vcpkg-baseline.txt new file mode 100644 index 00000000..88a710bf --- /dev/null +++ b/.github/vcpkg-baseline.txt @@ -0,0 +1 @@ +e99d87dcf02926bfd629560e11e156329d5720c1 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d75eb056..68dae750 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -53,11 +53,16 @@ jobs: sudo mkdir -p /usr/local/gfortran sudo ln -sf $HOMEBREW_PREFIX/Cellar/gcc@${GCC_VERSION}/*/lib/gcc/${GCC_VERSION} /usr/local/gfortran/lib + - name: 🔖 Read vcpkg baseline + id: vcpkg-baseline + shell: bash + run: echo "ref=$(cat .github/vcpkg-baseline.txt)" >> "$GITHUB_OUTPUT" + - name: 🐕 Checkout vcpkg uses: actions/checkout@v7 with: repository: microsoft/vcpkg - ref: 9e593bb18ea69cc5095e012465dcd675a822ed0d # 2026.07.29 - TODO: can we have a canonical baseline for tests? + ref: ${{ steps.vcpkg-baseline.outputs.ref }} path: vcpkg fetch-depth: 1 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0a7ed083..2a27f06a 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -40,11 +40,16 @@ jobs: with: arch: ${{ matrix.arch }} + - name: 🔖 Read vcpkg baseline + id: vcpkg-baseline + shell: bash + run: echo "ref=$(cat .github/vcpkg-baseline.txt)" >> "$GITHUB_OUTPUT" + - name: 🐕 Checkout vcpkg uses: actions/checkout@v7 with: repository: microsoft/vcpkg - ref: 9e593bb18ea69cc5095e012465dcd675a822ed0d # 2026.07.29 - TODO: can we have a canonical baseline for tests? + ref: ${{ steps.vcpkg-baseline.outputs.ref }} path: vcpkg fetch-depth: 1 diff --git a/ports/arrow/0008-pyarrow-relative-rpath.patch b/ports/arrow/0008-pyarrow-relative-rpath.patch new file mode 100644 index 00000000..21fe79d7 --- /dev/null +++ b/ports/arrow/0008-pyarrow-relative-rpath.patch @@ -0,0 +1,23 @@ +diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt +index 3024945..8d5835b 100644 +--- a/python/CMakeLists.txt ++++ b/python/CMakeLists.txt +@@ -298,8 +298,16 @@ find_package(Arrow REQUIRED) + if(APPLE + AND NOT PYARROW_BUNDLE_ARROW_CPP + AND ARROW_SHARED_LIB) +- get_filename_component(_arrow_lib_dir "${ARROW_SHARED_LIB}" DIRECTORY) +- list(APPEND CMAKE_INSTALL_RPATH "${_arrow_lib_dir}") ++ if(PYARROW_INSTALL_RPATH) ++ # vcpkg stages the install tree and then moves it, so the location of ++ # libarrow is only stable relative to the extension modules. Let the ++ # caller supply that relative RPATH instead of baking in the absolute ++ # directory the libraries happen to sit in at build time. ++ list(APPEND CMAKE_INSTALL_RPATH "${PYARROW_INSTALL_RPATH}") ++ else() ++ get_filename_component(_arrow_lib_dir "${ARROW_SHARED_LIB}" DIRECTORY) ++ list(APPEND CMAKE_INSTALL_RPATH "${_arrow_lib_dir}") ++ endif() + endif() + + macro(define_option name description arrow_option) diff --git a/ports/arrow/portfile.cmake b/ports/arrow/portfile.cmake index bdcf4553..3f8fbb10 100644 --- a/ports/arrow/portfile.cmake +++ b/ports/arrow/portfile.cmake @@ -1,8 +1,11 @@ +# AUTO_UPDATE +# GITHUB_REPO apache/arrow +# GITHUB_REF apache-arrow-${VERSION} vcpkg_download_distfile( ARCHIVE_PATH URLS "https://archive.apache.org/dist/arrow/arrow-${VERSION}/apache-arrow-${VERSION}.tar.gz" FILENAME apache-arrow-${VERSION}.tar.gz - SHA512 c687e50dfcdbf7e0e39710224360d35d9aa734452b3a47adc8c101f3019b6b4116310c05b9f3cd0a5ed4ad9b7bd8fb88edb70e79b3cbd413a57e5e35e4554a6c + SHA512 e75d384b4fdbdee29eb8ad29800c731843e7c43d90a43995dcc77390008723537791e212333178625345c718bdab15e0f3d8c12aa86b336918598c7d3fefc6e5 ) vcpkg_extract_source_archive( SOURCE_PATH @@ -13,6 +16,7 @@ vcpkg_extract_source_archive( 0004-android-datetime.patch 0005-cmake-msvcruntime.patch 0007-use-vcpkg-mimalloc.patch + 0008-pyarrow-relative-rpath.patch ) # Check cpp/cmake_modules/DefineOptions.cmake for option dependencies - @@ -199,16 +203,67 @@ if("python" IN_LIST FEATURES) set(ENV{SETUPTOOLS_SCM_PRETEND_VERSION} "${VERSION}") set(ENV{PDM_BUILD_SCM_VERSION} "${VERSION}") - if (NOT "${VCPKG_BUILD_TYPE}" STREQUAL "") - set(build_opts "--build-type=${VCPKG_BUILD_TYPE}") + # Since 24.0.0 pyarrow has no setup.py and builds with scikit-build-core, + # which shells out to cmake and ninja. When it cannot find them on PATH it + # asks pip for them as wheels instead, which --no-index forbids, so hand it + # the ones vcpkg already has. The generator has no config-setting; CMake + # picks it up from the environment. + vcpkg_find_acquire_program(NINJA) + get_filename_component(ninja_dir "${NINJA}" DIRECTORY) + get_filename_component(cmake_dir "${CMAKE_COMMAND}" DIRECTORY) + vcpkg_add_to_path(PREPEND "${ninja_dir}") + vcpkg_add_to_path(PREPEND "${cmake_dir}") + set(ENV{CMAKE_GENERATOR} "Ninja") + + if(VCPKG_BUILD_TYPE STREQUAL "debug") + set(py_build_type "Debug") else() - set(build_opts "--build-type=release") + set(py_build_type "Release") + endif() + + # pyarrow's find_package(Python3Alt) asks CMake for Development.Module and + # NumPy. CMake derives the header and library locations from the + # interpreter's prefix, but the python3 port moves them: headers go to + # include/python and, on Windows, the import library is lib/pythonXY.lib. + # Left alone this fails with + # Could NOT find Python3 (missing: Development.Module NumPy) + # so point CMake straight at them. + execute_process( + COMMAND "${PYTHON3_VENV}" -c "import sys; print('%d.%d' % sys.version_info[:2])" + OUTPUT_VARIABLE py_version + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND "${PYTHON3_VENV}" -c "import numpy; print(numpy.get_include())" + OUTPUT_VARIABLE py_numpy_include + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) + + set(py_hints + "--config-settings=cmake.define.Python3_EXECUTABLE=${PYTHON3_VENV}" + "--config-settings=cmake.define.Python3_INCLUDE_DIR=${CURRENT_INSTALLED_DIR}/include/python${py_version}" + "--config-settings=cmake.define.Python3_NumPy_INCLUDE_DIR=${py_numpy_include}" + ) + if(VCPKG_TARGET_IS_WINDOWS) + # Extension modules must link the import library on Windows + string(REPLACE "." "" py_version_nodot "${py_version}") + list(APPEND py_hints + "--config-settings=cmake.define.Python3_LIBRARY=${CURRENT_INSTALLED_DIR}/lib/python${py_version_nodot}.lib") endif() + # The extensions land in /lib/python3.X/site-packages/pyarrow, so + # libarrow in /lib is three directories up. Only a relative RPATH + # survives vcpkg moving the staged tree into the installed one + # (see 0008-pyarrow-relative-rpath.patch). vcpkg_execute_required_process( - COMMAND "${PYTHON3_VENV}" "setup.py" - "build_ext" ${build_opts} "--cmake-generator" "Ninja" "--rpath" "@loader_path/../../../" - "install" "--prefix" "${CURRENT_PACKAGES_DIR}" + COMMAND "${PYTHON3_VENV}" -m pip install "${SOURCE_PATH}/python" + --no-build-isolation --no-deps --no-index + --prefix "${CURRENT_PACKAGES_DIR}" + "--config-settings=cmake.build-type=${py_build_type}" + "--config-settings=cmake.define.PYARROW_INSTALL_RPATH=@loader_path/../../../" + ${py_hints} LOGNAME "python-build-${TARGET_TRIPLET}" WORKING_DIRECTORY "${SOURCE_PATH}/python" ) diff --git a/ports/arrow/vcpkg.json b/ports/arrow/vcpkg.json index 4c1904e0..96597bea 100644 --- a/ports/arrow/vcpkg.json +++ b/ports/arrow/vcpkg.json @@ -1,7 +1,6 @@ { "name": "arrow", - "version": "23.0.1", - "port-version": 1, + "version": "25.0.1", "description": "Cross-language development platform for in-memory analytics", "homepage": "https://arrow.apache.org", "license": "Apache-2.0", diff --git a/ports/gdal/portfile.cmake b/ports/gdal/portfile.cmake index 6bba1829..2f73ad60 100644 --- a/ports/gdal/portfile.cmake +++ b/ports/gdal/portfile.cmake @@ -1,8 +1,9 @@ +# AUTO_UPDATE vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO OSGeo/gdal REF "v${VERSION}" - SHA512 3b915c38cc7c9eb139df9335a90a2f6fd123c54e19ecb1f22670400eac76e317c5334f95dff5875c9c3fde8a0ef0f7aea86fa58ad42afaee823a46c6616e9c17 + SHA512 57e371c02557ed0db210a6f992648602d9a7344679069a5101b3e25a49503683487009e96ad0707c780bac15cbd78415894096728d058304ea7f305b90628f69 HEAD_REF master PATCHES find-link-libraries.patch diff --git a/ports/gdal/sqlite3.diff b/ports/gdal/sqlite3.diff index daeea017..c6b99984 100644 --- a/ports/gdal/sqlite3.diff +++ b/ports/gdal/sqlite3.diff @@ -1,8 +1,8 @@ diff --git a/cmake/modules/packages/FindSQLite3.cmake b/cmake/modules/packages/FindSQLite3.cmake -index 903465b3c9..5d3a067e50 100644 +index 1dd364f..b9a0963 100644 --- a/cmake/modules/packages/FindSQLite3.cmake +++ b/cmake/modules/packages/FindSQLite3.cmake -@@ -77,7 +77,7 @@ if(SQLite3_INCLUDE_DIR AND SQLite3_LIBRARY) +@@ -79,7 +79,7 @@ if(SQLite3_INCLUDE_DIR AND SQLite3_LIBRARY) cmake_push_check_state(RESET) # check column metadata set(CMAKE_REQUIRED_INCLUDES ${SQLite3_INCLUDE_DIR}) @@ -11,7 +11,7 @@ index 903465b3c9..5d3a067e50 100644 if(PC_SQLITE3_STATIC_LDFLAGS) set(CMAKE_REQUIRED_LIBRARIES ${PC_SQLITE3_STATIC_LDFLAGS}) else() -@@ -111,15 +111,10 @@ if(SQLite3_INCLUDE_DIR AND SQLite3_LIBRARY) +@@ -113,15 +113,10 @@ if(SQLite3_INCLUDE_DIR AND SQLite3_LIBRARY) endif() else() set(CMAKE_REQUIRED_LIBRARIES ${SQLite3_LIBRARY}) @@ -29,21 +29,22 @@ index 903465b3c9..5d3a067e50 100644 # Invalidate cached variables if SQLite3_LIBRARY changes file(TIMESTAMP "${SQLite3_LIBRARY}" SQLite3_LIBRARY_TIMESTAMP) if( SQLite3_LIBRARY_TIMESTAMP_OLD_VAL AND -@@ -179,6 +174,7 @@ if(SQLite3_FOUND) +@@ -181,6 +176,7 @@ if(SQLite3_FOUND) INTERFACE_INCLUDE_DIRECTORIES "${SQLite3_INCLUDE_DIRS}" IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${SQLite3_LIBRARY}") + endif() if(SQLite3_HAS_COLUMN_METADATA) - set_property(TARGET SQLite::SQLite3 APPEND PROPERTY + set_property(TARGET SQLite3::SQLite3 APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "SQLite3_HAS_COLUMN_METADATA") -@@ -187,5 +183,9 @@ if(SQLite3_FOUND) - set_property(TARGET SQLite::SQLite3 APPEND PROPERTY +@@ -189,6 +185,10 @@ if(SQLite3_FOUND) + set_property(TARGET SQLite3::SQLite3 APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "SQLite3_HAS_RTREE") endif() -+ get_target_property(definitions SQLite::SQLite3 INTERFACE_COMPILE_DEFINITIONS) ++ get_target_property(definitions SQLite3::SQLite3 INTERFACE_COMPILE_DEFINITIONS) + if(definitions) + list(REMOVE_DUPLICATES definitions) -+ set_target_properties(SQLite::SQLite3 PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${definitions}") ++ set_target_properties(SQLite3::SQLite3 PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${definitions}") endif() - endif() + + # Alias SQLite3::SQLite3 to SQLite::SQLite3 since that's what PROJ will diff --git a/ports/gdal/vcpkg.json b/ports/gdal/vcpkg.json index 121d2249..8b24a441 100644 --- a/ports/gdal/vcpkg.json +++ b/ports/gdal/vcpkg.json @@ -1,6 +1,6 @@ { "name": "gdal", - "version-semver": "3.12.4", + "version-semver": "3.13.3", "description": "The Geographic Data Abstraction Library for reading and writing geospatial raster and vector data", "homepage": "https://gdal.org", "license": null, diff --git a/ports/py-isort/portfile.cmake b/ports/py-isort/portfile.cmake index c20ab04c..d2c60495 100644 --- a/ports/py-isort/portfile.cmake +++ b/ports/py-isort/portfile.cmake @@ -2,7 +2,7 @@ vcpkg_from_pythonhosted( OUT_SOURCE_PATH SOURCE_PATH PACKAGE_NAME isort VERSION ${VERSION} - SHA512 6ea1a3cd6ca4cc489e332f7d7c7e0d8138a734ecc0cf305a0324fe42c8ea0af9f6e28bd5b3cd7108d260fa93bf89a844b59589baf75c04d82e7112c54b7d305f + SHA512 edf386ec3497c1b9e540015dea76f8328593f43d2aa391acad459f175fe1b3a552d4ca0e75187bc92ef1319cb4c77bb774cb22ef9b46cf89daacc754bb454f93 ) vcpkg_python_build_and_install_wheel(SOURCE_PATH "${SOURCE_PATH}") diff --git a/ports/py-isort/vcpkg.json b/ports/py-isort/vcpkg.json index 99b36e68..3f3a0d36 100644 --- a/ports/py-isort/vcpkg.json +++ b/ports/py-isort/vcpkg.json @@ -1,10 +1,11 @@ { "name": "py-isort", - "version": "8.0.1", + "version": "9.0.1", "description": "A Python utility / library to sort Python imports.", "homepage": "https://pycqa.github.io/isort/", "dependencies": [ "py-hatchling", + "py-mypy-extensions", { "name": "py-poetry-core", "host": true diff --git a/ports/py-mypy-extensions/portfile.cmake b/ports/py-mypy-extensions/portfile.cmake new file mode 100644 index 00000000..bbb0c567 --- /dev/null +++ b/ports/py-mypy-extensions/portfile.cmake @@ -0,0 +1,17 @@ +set(VCPKG_BUILD_TYPE release) + +vcpkg_from_pythonhosted( + OUT_SOURCE_PATH SOURCE_PATH + PACKAGE_NAME mypy-extensions + VERSION ${VERSION} + SHA512 b946d48ff85a2c384049058a3bd822942a91114f818374764722962869e47ae0b86efee4b58b3c14a974e6711a8c1651ca6a1c0a448fa4ea03f9190c63b3dae0 + FILENAME mypy_extensions +) + +vcpkg_python_build_and_install_wheel(SOURCE_PATH "${SOURCE_PATH}") + +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") + +vcpkg_python_test_import(MODULE "mypy_extensions") + +set(VCPKG_POLICY_EMPTY_INCLUDE_FOLDER enabled) diff --git a/ports/py-mypy-extensions/vcpkg.json b/ports/py-mypy-extensions/vcpkg.json new file mode 100644 index 00000000..d34dc10c --- /dev/null +++ b/ports/py-mypy-extensions/vcpkg.json @@ -0,0 +1,22 @@ +{ + "name": "py-mypy-extensions", + "version": "1.1.0", + "description": "Experimental type system extensions for programs checked with mypy", + "homepage": "https://github.com/python/mypy_extensions", + "license": "MIT", + "dependencies": [ + { + "name": "py-flit-core", + "host": true + }, + { + "name": "py-setuptools", + "host": true + }, + "python3", + { + "name": "vcpkg-python-scripts", + "host": true + } + ] +} diff --git a/scripts/update-ports.py b/scripts/update-ports.py index bc9e3db7..d06f2ee5 100644 --- a/scripts/update-ports.py +++ b/scripts/update-ports.py @@ -259,6 +259,26 @@ def get_latest_tag(repo, ref_pattern, headers): return None, None +def is_auto_update_enabled(dir_name, portfile_content): + """Ports named py-* are updated by default, others must opt in. + + An opt-in port carries a marker comment in its portfile: + + # AUTO_UPDATE + """ + if dir_name.startswith("py-"): + return True + return re.search(r"#\s*AUTO_UPDATE\b", portfile_content) is not None + + +def get_version_key(vcpkg_data): + """Return the version field a manifest uses (version, version-date, ...).""" + for key in ("version-date", "version-semver", "version-string"): + if key in vcpkg_data: + return key + return "version" + + def update_github_ports(): updated = [] failed = [] @@ -269,153 +289,286 @@ def update_github_ports(): if token: headers["Authorization"] = f"token {token}" - for root, dirs, files in os.walk(PORTS_DIR): - for dir_name in dirs: - if not dir_name.startswith("py-"): - continue + for dir_name in sorted(os.listdir(PORTS_DIR)): + dir_path = os.path.join(PORTS_DIR, dir_name) + portfile_cmake = os.path.join(dir_path, "portfile.cmake") + vcpkg_json = os.path.join(dir_path, "vcpkg.json") - dir_path = os.path.join(root, dir_name) - portfile_cmake = os.path.join(dir_path, "portfile.cmake") - vcpkg_json = os.path.join(dir_path, "vcpkg.json") + if not os.path.isfile(portfile_cmake): + continue - if not os.path.isfile(portfile_cmake): - continue + with open(portfile_cmake, "r") as f: + portfile_content = f.read() - with open(portfile_cmake, "r") as f: - portfile_content = f.read() + if "vcpkg_from_github" not in portfile_content: + continue - if "vcpkg_from_github" not in portfile_content: - continue + if not is_auto_update_enabled(dir_name, portfile_content): + continue - # Parse REPO - repo_match = re.search(r"REPO\s+(\S+)", portfile_content) - if not repo_match: - print(f" Could not find REPO in {portfile_cmake}") - continue - repo = repo_match.group(1) + # Parse REPO + repo_match = re.search(r"REPO\s+(\S+)", portfile_content) + if not repo_match: + print(f" Could not find REPO in {portfile_cmake}") + continue + repo = repo_match.group(1) - # Parse REF (strip optional quotes) - ref_match = re.search(r'REF\s+"?(\S+?)"?\s*$', portfile_content, re.MULTILINE) - if not ref_match: - print(f" Could not find REF in {portfile_cmake}") - continue - ref_value = ref_match.group(1) + # Parse REF (strip optional quotes) + ref_match = re.search(r'REF\s+"?(\S+?)"?\s*$', portfile_content, re.MULTILINE) + if not ref_match: + print(f" Could not find REF in {portfile_cmake}") + continue + ref_value = ref_match.group(1) - # Skip commit-hash based REFs - if re.match(r"^[0-9a-f]{40}$", ref_value): - print(f"Skipping {dir_name}: commit-hash based REF") - unchanged.append(dir_name) - continue + # Skip commit-hash based REFs + if re.match(r"^[0-9a-f]{40}$", ref_value): + print(f"Skipping {dir_name}: commit-hash based REF") + unchanged.append(dir_name) + continue - # Determine REF pattern - uses_version_var = "${VERSION}" in ref_value + # Determine REF pattern + uses_version_var = "${VERSION}" in ref_value - print(f"Updating {dir_name} (GitHub: {repo})") + print(f"Updating {dir_name} (GitHub: {repo})") - # Read vcpkg.json early to determine version scheme - if not os.path.isfile(vcpkg_json): - print(f" vcpkg.json not found for {dir_name}") - failed.append(f"{dir_name} (vcpkg.json not found)") - continue + # Read vcpkg.json early to determine version scheme + if not os.path.isfile(vcpkg_json): + print(f" vcpkg.json not found for {dir_name}") + failed.append(f"{dir_name} (vcpkg.json not found)") + continue - with open(vcpkg_json, "r") as f: - vcpkg_data = json.load(f) + with open(vcpkg_json, "r") as f: + vcpkg_data = json.load(f) - is_version_date = "version-date" in vcpkg_data + is_version_date = "version-date" in vcpkg_data - # Build ref_pattern for get_latest_tag - if uses_version_var: - ref_pattern = ref_value - elif is_version_date: - # For version-date ports with literal REFs, match date-like tags (YYYY.MM.DD) - ref_pattern = r"(\d{4}\.\d{2}\.\d{2})" - else: - ref_pattern = None + # Build ref_pattern for get_latest_tag + if uses_version_var: + ref_pattern = ref_value + elif is_version_date: + # For version-date ports with literal REFs, match date-like tags (YYYY.MM.DD) + ref_pattern = r"(\d{4}\.\d{2}\.\d{2})" + else: + ref_pattern = None - tag_name, version_from_tag = get_latest_tag(repo, ref_pattern, headers) + tag_name, version_from_tag = get_latest_tag(repo, ref_pattern, headers) - if not tag_name: - print(f" No matching tag found for {repo}") - failed.append(f"{dir_name} (no matching tag)") - continue + if not tag_name: + print(f" No matching tag found for {repo}") + failed.append(f"{dir_name} (no matching tag)") + continue - # Determine version key and new version value - if is_version_date: - version_key = "version-date" - new_version = version_from_tag.replace(".", "-") - elif "version-semver" in vcpkg_data: - version_key = "version-semver" - new_version = version_from_tag - elif "version-string" in vcpkg_data: - version_key = "version-string" - new_version = version_from_tag - else: - version_key = "version" - new_version = version_from_tag - - # vcpkg relaxed versions only allow digits and dots - if version_key in ("version", "version-semver") and not re.fullmatch( - r"\d+(\.\d+)*", new_version - ): - print( - f" Skipping {dir_name}: version '{new_version}' is not a valid vcpkg relaxed version" - ) - unchanged.append(dir_name) - continue + # Determine version key and new version value + version_key = get_version_key(vcpkg_data) + if is_version_date: + new_version = version_from_tag.replace(".", "-") + else: + new_version = version_from_tag + + # vcpkg relaxed versions only allow digits and dots + if version_key in ("version", "version-semver") and not re.fullmatch( + r"\d+(\.\d+)*", new_version + ): + print( + f" Skipping {dir_name}: version '{new_version}' is not a valid vcpkg relaxed version" + ) + unchanged.append(dir_name) + continue - current_version = vcpkg_data.get(version_key) - if current_version == new_version: - print(f" {dir_name} is already up to date ({current_version}). Skipping...") - unchanged.append(dir_name) - continue + current_version = vcpkg_data.get(version_key) + if current_version == new_version: + print(f" {dir_name} is already up to date ({current_version}). Skipping...") + unchanged.append(dir_name) + continue - print(f" Updating {dir_name} from {current_version} to {new_version} (tag: {tag_name})") + print(f" Updating {dir_name} from {current_version} to {new_version} (tag: {tag_name})") - # Download tarball and compute SHA512 - tarball_url = f"https://github.com/{repo}/archive/{tag_name}.tar.gz" - resp = requests.get(tarball_url, stream=True) - if resp.status_code != 200: - print(f" Failed to download tarball from {tarball_url} (status {resp.status_code})") - failed.append(f"{dir_name} (download failed)") - continue + # Download tarball and compute SHA512 + tarball_url = f"https://github.com/{repo}/archive/{tag_name}.tar.gz" + resp = requests.get(tarball_url, stream=True) + if resp.status_code != 200: + print(f" Failed to download tarball from {tarball_url} (status {resp.status_code})") + failed.append(f"{dir_name} (download failed)") + continue - temp_file_path = os.path.join(dir_path, "temp_source_file") - with open(temp_file_path, "wb") as f: - for chunk in resp.iter_content(chunk_size=8192): - f.write(chunk) - sha512_checksum = calculate_sha512(temp_file_path) - os.remove(temp_file_path) - print(f" Calculated SHA512 checksum: {sha512_checksum}") - - # Update vcpkg.json - vcpkg_data.pop("port-version", None) - vcpkg_data[version_key] = new_version - with open(vcpkg_json, "w") as f: - json.dump(vcpkg_data, f, indent=2) - print(f" Updated {vcpkg_json} to version {new_version}") - - # Update portfile.cmake SHA512 (only the first occurrence, - # which corresponds to the main source archive) + temp_file_path = os.path.join(dir_path, "temp_source_file") + with open(temp_file_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=8192): + f.write(chunk) + sha512_checksum = calculate_sha512(temp_file_path) + os.remove(temp_file_path) + print(f" Calculated SHA512 checksum: {sha512_checksum}") + + # Update vcpkg.json + vcpkg_data.pop("port-version", None) + vcpkg_data[version_key] = new_version + with open(vcpkg_json, "w") as f: + json.dump(vcpkg_data, f, indent=2) + print(f" Updated {vcpkg_json} to version {new_version}") + + # Update portfile.cmake SHA512 (only the first occurrence, + # which corresponds to the main source archive) + new_portfile_content = re.sub( + r"(SHA512\s+)[a-f0-9]+", + f"\\g<1>{sha512_checksum}", + portfile_content, + count=1, + ) + + # If REF is a literal value (not ${VERSION}), update it too + if not uses_version_var: new_portfile_content = re.sub( - r"(SHA512\s+)[a-f0-9]+", - f"\\g<1>{sha512_checksum}", - portfile_content, + r'(REF\s+"?)\S+"?', + f"\\g<1>{tag_name}", + new_portfile_content, count=1, ) - # If REF is a literal value (not ${VERSION}), update it too - if not uses_version_var: - new_portfile_content = re.sub( - r'(REF\s+"?)\S+"?', - f"\\g<1>{tag_name}", - new_portfile_content, - count=1, - ) + with open(portfile_cmake, "w") as f: + f.write(new_portfile_content) + print(f" Updated {portfile_cmake}") + updated.append(f"{dir_name} ({current_version} -> {new_version})") + + return updated, failed, unchanged + + +def update_distfile_ports(): + """Update ports that fetch a release archive with vcpkg_download_distfile. + + Such ports have no REPO to derive the upstream from, so the GitHub + repository holding the releases is declared in the portfile alongside the + # AUTO_UPDATE opt-in: + + # AUTO_UPDATE + # GITHUB_REPO apache/arrow + # GITHUB_REF apache-arrow-${VERSION} + + GITHUB_REF is the tag pattern (defaults to "${VERSION}"). The download URL + is taken from the URLS argument, with ${VERSION} substituted. + """ + updated = [] + failed = [] + unchanged = [] + + headers = {} + token = os.environ.get("GITHUB_TOKEN") + if token: + headers["Authorization"] = f"token {token}" + + for dir_name in sorted(os.listdir(PORTS_DIR)): + dir_path = os.path.join(PORTS_DIR, dir_name) + portfile_cmake = os.path.join(dir_path, "portfile.cmake") + vcpkg_json = os.path.join(dir_path, "vcpkg.json") + + if not os.path.isfile(portfile_cmake): + continue + + with open(portfile_cmake, "r") as f: + portfile_content = f.read() + + if "vcpkg_download_distfile" not in portfile_content: + continue + + if not is_auto_update_enabled(dir_name, portfile_content): + continue + + # Without GITHUB_REPO there is no upstream to query; such ports are + # not managed here (PyPI sdists are handled by update_pypi_ports) + repo_match = re.search(r"#\s*GITHUB_REPO\s+(\S+)", portfile_content) + if not repo_match: + continue + repo = repo_match.group(1) + + ref_match = re.search(r"#\s*GITHUB_REF\s+(\S+)", portfile_content) + ref_pattern = ref_match.group(1) if ref_match else "${VERSION}" + + print(f"Updating {dir_name} (GitHub release: {repo})") + + if not os.path.isfile(vcpkg_json): + print(f" vcpkg.json not found for {dir_name}") + failed.append(f"{dir_name} (vcpkg.json not found)") + continue + + with open(vcpkg_json, "r") as f: + vcpkg_data = json.load(f) + + url_match = re.search(r'URLS\s+"?(\S+?)"?\s*$', portfile_content, re.MULTILINE) + if not url_match: + print(f" Could not find URLS in {portfile_cmake}") + failed.append(f"{dir_name} (no URLS)") + continue + url_template = url_match.group(1) + + if "${VERSION}" not in url_template: + print(f" URLS in {portfile_cmake} is not version-templated") + failed.append(f"{dir_name} (URLS not version-templated)") + continue + + tag_name, version_from_tag = get_latest_tag(repo, ref_pattern, headers) + + if not tag_name: + print(f" No matching tag found for {repo}") + failed.append(f"{dir_name} (no matching tag)") + continue + + version_key = get_version_key(vcpkg_data) + if version_key == "version-date": + new_version = version_from_tag.replace(".", "-") + else: + new_version = version_from_tag + + # vcpkg relaxed versions only allow digits and dots + if version_key in ("version", "version-semver") and not re.fullmatch( + r"\d+(\.\d+)*", new_version + ): + print( + f" Skipping {dir_name}: version '{new_version}' is not a valid vcpkg relaxed version" + ) + unchanged.append(dir_name) + continue + + current_version = vcpkg_data.get(version_key) + if current_version == new_version: + print(f" {dir_name} is already up to date ({current_version}). Skipping...") + unchanged.append(dir_name) + continue + + print(f" Updating {dir_name} from {current_version} to {new_version} (tag: {tag_name})") - with open(portfile_cmake, "w") as f: - f.write(new_portfile_content) - print(f" Updated {portfile_cmake}") - updated.append(f"{dir_name} ({current_version} -> {new_version})") + # Download the release archive and compute SHA512 + source_url = url_template.replace("${VERSION}", new_version) + resp = requests.get(source_url, stream=True) + if resp.status_code != 200: + print(f" Failed to download archive from {source_url} (status {resp.status_code})") + failed.append(f"{dir_name} (download failed)") + continue + + temp_file_path = os.path.join(dir_path, "temp_source_file") + with open(temp_file_path, "wb") as f: + for chunk in resp.iter_content(chunk_size=8192): + f.write(chunk) + sha512_checksum = calculate_sha512(temp_file_path) + os.remove(temp_file_path) + print(f" Calculated SHA512 checksum: {sha512_checksum}") + + vcpkg_data.pop("port-version", None) + vcpkg_data[version_key] = new_version + with open(vcpkg_json, "w") as f: + json.dump(vcpkg_data, f, indent=2) + print(f" Updated {vcpkg_json} to version {new_version}") + + # Update the SHA512 of the main source archive (first occurrence) + new_portfile_content = re.sub( + r"(SHA512\s+)[a-f0-9]+", + f"\\g<1>{sha512_checksum}", + portfile_content, + count=1, + ) + + with open(portfile_cmake, "w") as f: + f.write(new_portfile_content) + print(f" Updated {portfile_cmake}") + updated.append(f"{dir_name} ({current_version} -> {new_version})") return updated, failed, unchanged @@ -440,12 +593,18 @@ def main(): parser = argparse.ArgumentParser(description="Update vcpkg ports to latest versions") parser.add_argument("--pypi", action="store_true", help="Update ports using vcpkg_from_pythonhosted") parser.add_argument("--github", action="store_true", help="Update ports using vcpkg_from_github") + parser.add_argument( + "--distfile", + action="store_true", + help="Update ports using vcpkg_download_distfile with a # GITHUB_REPO marker", + ) args = parser.parse_args() - # Default to both if neither specified - if not args.pypi and not args.github: + # Default to all if none specified + if not args.pypi and not args.github and not args.distfile: args.pypi = True args.github = True + args.distfile = True all_updated = [] all_failed = [] @@ -461,6 +620,11 @@ def main(): all_updated.extend(updated) all_failed.extend(failed) all_unchanged.extend(unchanged) + if args.distfile: + updated, failed, unchanged = update_distfile_ports() + all_updated.extend(updated) + all_failed.extend(failed) + all_unchanged.extend(unchanged) print_report("Summary", all_updated, all_failed, all_unchanged) diff --git a/versions/a-/arrow.json b/versions/a-/arrow.json index 6c13c711..682b8ad8 100644 --- a/versions/a-/arrow.json +++ b/versions/a-/arrow.json @@ -1,5 +1,10 @@ { "versions": [ + { + "git-tree": "c4211b38fd9ce7d0fe99ab0e73e643cff708a6fe", + "version": "25.0.1", + "port-version": 0 + }, { "git-tree": "dff884f37304d2bd7803cc3c38c711b77d6c81f9", "version": "23.0.1", diff --git a/versions/baseline.json b/versions/baseline.json index 1bd15d46..bc7add6d 100644 --- a/versions/baseline.json +++ b/versions/baseline.json @@ -1,11 +1,11 @@ { "default": { "arrow": { - "baseline": "23.0.1", - "port-version": 1 + "baseline": "25.0.1", + "port-version": 0 }, "gdal": { - "baseline": "3.12.4", + "baseline": "3.13.3", "port-version": 0 }, "py-adbc-driver-manager": { @@ -165,7 +165,7 @@ "port-version": 1 }, "py-isort": { - "baseline": "8.0.1", + "baseline": "9.0.1", "port-version": 0 }, "py-itsdangerous": { @@ -224,6 +224,10 @@ "baseline": "0.20.0", "port-version": 2 }, + "py-mypy-extensions": { + "baseline": "1.1.0", + "port-version": 0 + }, "py-narwhals": { "baseline": "2.24.0", "port-version": 0 diff --git a/versions/g-/gdal.json b/versions/g-/gdal.json index fef20c7f..32d4651e 100644 --- a/versions/g-/gdal.json +++ b/versions/g-/gdal.json @@ -1,5 +1,10 @@ { "versions": [ + { + "git-tree": "28458c5d91b7b239a7abf5bba33d84c5d4e38834", + "version-semver": "3.13.3", + "port-version": 0 + }, { "git-tree": "52829bbf14ce28964f37ed6b9381dd73bad0e2f2", "version-semver": "3.12.4", diff --git a/versions/p-/py-isort.json b/versions/p-/py-isort.json index b971b395..53ad0246 100644 --- a/versions/p-/py-isort.json +++ b/versions/p-/py-isort.json @@ -1,5 +1,10 @@ { "versions": [ + { + "git-tree": "2153307c0f960afea993a2e9dec5c251223ea020", + "version": "9.0.1", + "port-version": 0 + }, { "git-tree": "3adf2f841557fd7f08cdfa06c0e05912615023ff", "version": "8.0.1", diff --git a/versions/p-/py-mypy-extensions.json b/versions/p-/py-mypy-extensions.json new file mode 100644 index 00000000..ac728caf --- /dev/null +++ b/versions/p-/py-mypy-extensions.json @@ -0,0 +1,9 @@ +{ + "versions": [ + { + "git-tree": "dd95f27108c347141e13c0ee1cbd3b5362e2d348", + "version": "1.1.0", + "port-version": 0 + } + ] +}