From bbf73d92535140a667a24fe1b1524c413e721094 Mon Sep 17 00:00:00 2001 From: "Daniele E. Domenichelli" Date: Thu, 27 Feb 2025 17:25:37 +0100 Subject: [PATCH 1/4] gh_utils: Add get_latest_version_in_github This function parses the tag and returns just the version number. Also add --get-latest-version argument to the command line utility. --- src/gh_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gh_utils.py b/src/gh_utils.py index 0cea589..33bf34d 100755 --- a/src/gh_utils.py +++ b/src/gh_utils.py @@ -16,6 +16,7 @@ import utils from version_utils import is_numeric, previous_version, get_current_version_in_cmake from changelog_utils import get_changelog +import re def get_latest_release_tag_in_github(repo, repo_path, main_branch, via_tag=False): @@ -54,6 +55,15 @@ def get_latest_release_tag_in_github(repo, repo_path, main_branch, via_tag=False return version +def extract_version_from_tag(tag): + match = re.search(r'\d+(?:\.\d+)*', tag) + return match.group(0) if match else None + + +def get_latest_version_in_github(repo, repo_path, main_branch, via_tag=False): + return extract_version_from_tag(get_latest_release_tag_in_github(repo, repo_path, main_branch, via_tag)) + + def tag_exists(repo, tag): return run_command_silent(f"gh api repos/KDAB/{repo}/git/refs/tags/{tag}") @@ -526,9 +536,14 @@ def commit_and_push_pr(commit_msg, gh_repo, repo_path, remote, branch, tmp_branc parser = argparse.ArgumentParser() parser.add_argument('--get-latest-release', metavar='REPO', help="returns latest release for a repo") + parser.add_argument('--get-latest-version', metavar='REPO', + help="returns latest version for a repo") args = parser.parse_args() if args.get_latest_release: print(get_latest_release_tag_in_github( args.get_latest_release, None, None)) + if args.get_latest_version: + print(get_latest_version_in_github( + args.get_latest_version, None, None)) # print_submodule_versions('..') From ca822d7c24ecf0e3dd3cb27b258bca0d8c84e2f5 Mon Sep 17 00:00:00 2001 From: "Daniele E. Domenichelli" Date: Thu, 27 Feb 2025 17:28:03 +0100 Subject: [PATCH 2/4] Add vcpkg_utils.py script For the moment, it contains: * fetch_vcpkg_port_vcpkg_json_file: Fetches the vcpkg.json file for a port and returns its content. * extract_version_from_vcpkg_json_file_content: Extracts the version from the vcpkg.json file content. * get_latest_version_in_vcpkg: Get the latest version for a vcpkg port --- src/vcpkg_utils.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/vcpkg_utils.py diff --git a/src/vcpkg_utils.py b/src/vcpkg_utils.py new file mode 100644 index 0000000..d8ee6db --- /dev/null +++ b/src/vcpkg_utils.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-License-Identifier: MIT + +# Scripts related to vcpkg + +import requests +import json +import argparse +import sys + + +def fetch_vcpkg_port_vcpkg_json_file(port_name, vcpkg_repo="microsoft/vcpkg", vcpkg_branch="master"): + """Fetches the vcpkg.json file for a port and returns its content.""" + url = f"https://raw.githubusercontent.com/{vcpkg_repo}/refs/heads/{vcpkg_branch}/ports/{port_name}/vcpkg.json" + try: + response = requests.get(url, timeout=10) + response.raise_for_status() # Raise an exception for HTTP errors + return response.text + except requests.RequestException as e: + print(f"Error fetching JSON: {e}") + return None + + +def extract_version_from_vcpkg_json_file_content(vcpks_json_content): + """Extracts the version from the vcpkg.json file content.""" + try: + data = json.loads(vcpks_json_content) + return data.get("version", None) + except json.JSONDecodeError: + print("Error parsing vcpkg.json file") + return None + + +def get_latest_version_in_vcpkg(port_name, vcpkg_repo="microsoft/vcpkg", vcpkg_branch="master"): + """Get the latest version for a vcpkg port.""" + json_data = fetch_vcpkg_port_vcpkg_json_file(port_name, vcpkg_repo, vcpkg_branch) + if json_data: + version = extract_version_from_vcpkg_json_file_content(json_data) + return version + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--get-latest-vcpkg-version', type=str, metavar='PORT_NAME', + help="returns latest vcpkg version for a port") + parser.add_argument('--vcpkg-repository', type=str, metavar='VCPKG_REPO', default="microsoft/vcpkg", + help="The vcpkg repository (optional, default: 'microsoft/vcpkg').") + parser.add_argument('--vcpkg-branch', type=str, metavar='VCPKG_BRANCH', default="master", + help="The branch of the vcpkg repository (optional, default: 'master').") + args = parser.parse_args() + + if args.get_latest_vcpkg_version: + ret = get_latest_version_in_vcpkg(args.get_latest_vcpkg_version, args.vcpkg_repository, args.vcpkg_branch) + if ret is None: + sys.exit(1) + print(ret) + sys.exit(0) + + parser.print_help() + sys.exit(1) From b46f0f2de1eb59dc10c7f33bd0b4c048b451f7d1 Mon Sep 17 00:00:00 2001 From: "Daniele E. Domenichelli" Date: Thu, 27 Feb 2025 19:30:47 +0100 Subject: [PATCH 3/4] Add has_newer_version utility This utility compares the version currently in use (for example the version currently packaged for vcpkg) with the latest version (usually the version on the repository). Also allow calling it from command line. --- src/version_utils.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/version_utils.py b/src/version_utils.py index 5b71eea..e66facb 100755 --- a/src/version_utils.py +++ b/src/version_utils.py @@ -5,6 +5,9 @@ import re from utils import download_file_as_string, get_project +from packaging import version +import argparse +import sys def previous_version(version): @@ -65,3 +68,46 @@ def is_numeric(version): return True except: return False + +def has_newer_version(version_in_use, latest_version): + """ + Compare two semantic version strings. + Returns True if version_in_use < latest_version, False if version_in_use == latest_version. + This function expects that latest_version is greater or equal than version_in_use. + If this is not true, this function raises ValueError. + """ + version_in_use_parsed = version.parse(version_in_use) + latest_version_parsed = version.parse(latest_version) + if version_in_use_parsed < latest_version_parsed: + return True + elif version_in_use_parsed == latest_version_parsed: + return False + else: + raise ValueError(f"Error: Version {version_in_use} is greater than {latest_version}.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--has-newer-version", + nargs=2, + metavar=("VERSION_IN_USE", "LATEST_VERSION"), + help="Check if VERSION_IN_USE is less than LATEST_VERSION.", + ) + args = parser.parse_args() + + if args.has_newer_version: + version_in_use, latest_version = args.has_newer_version + try: + result = has_newer_version(version_in_use, latest_version) + if result: + print("true") + sys.exit(0) + else: + print("false") + sys.exit(0) + except ValueError as e: + print(e) + sys.exit(1) + + parser.print_help() + sys.exit(1) From b9f0bc6cc9d0887ccc433a5cab0632a38c273cab Mon Sep 17 00:00:00 2001 From: "Daniele E. Domenichelli" Date: Tue, 30 Dec 2025 12:19:57 +0100 Subject: [PATCH 4/4] Add release_vcpkg.yml --- .github/workflows/release_vcpkg.yml | 192 ++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 .github/workflows/release_vcpkg.yml diff --git a/.github/workflows/release_vcpkg.yml b/.github/workflows/release_vcpkg.yml new file mode 100644 index 0000000..ea323dc --- /dev/null +++ b/.github/workflows/release_vcpkg.yml @@ -0,0 +1,192 @@ +# SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +# SPDX-License-Identifier: MIT + +name: vcpkg PR Creation +on: + workflow_dispatch: + inputs: + namespace: + description: 'The project namespace (change if testing workflow from a fork)' + default: 'KDAB' + project: + type: choice + description: "Select a project" + options: + - "kdsoap" + - "kdreports" + - "kdbindings" + - "kdalgorithms" + - "gammaray" + port_version: + description: "The vcpkg port-version number (0 for a release not yet packaged in vcpkg)" + default: 0 + vcpkg_fork_namespace: + description: 'The namespace for the vcpkg fork where the branch will be created' + default: 'KDABLabs' + vcpkg_origin_namespace: + description: 'The namespace for the vcpkg repository where the PR will be created' + # default: 'microsoft' # Change this to create PRs directly on the microsoft/vcpkg repository + default: 'KDABLabs' + force_push: + type: boolean + description: "Continue if branch already exists (WARNING: force push created branch)" + default: false + +jobs: + prepare-branch: + runs-on: ubuntu-24.04 + outputs: + branch: ${{ steps.write-output.outputs.branch }} + new_version: ${{ steps.write-output.outputs.new_version }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ci-release-tools + + - name: Setup CI_RELEASE_TOOLS environment variable + run: | + CI_RELEASE_TOOLS=$PWD/ci-release-tools + echo "CI_RELEASE_TOOLS=${CI_RELEASE_TOOLS}" | tee -a ${GITHUB_ENV} + + - name: Configure Git committer + run: | + git config --global user.name "KDAB GitHub Actions" + git config --global user.email "gh@kdab" + + - name: Print latest version + run: | + REPO_VERSION=$(python3 ${CI_RELEASE_TOOLS}/src/gh_utils.py --get-latest-version ${{ inputs.namespace }}/${{ inputs.project }}) + echo "REPO_VERSION=${REPO_VERSION}" | tee -a ${GITHUB_ENV} + env: + GH_TOKEN: ${{ github.token }} + + - name: Print current version on vcpkg repository + run: | + VCPKG_VERSION=$(python3 ${CI_RELEASE_TOOLS}/src/vcpkg_utils.py --get-latest-vcpkg-version ${{ inputs.project }}) + echo "VCPKG_VERSION=${VCPKG_VERSION}" | tee -a ${GITHUB_ENV} + + - name: Compare versions + run: | + echo VCPKG_VERSION = $VCPKG_VERSION + echo REPO_VERSION = $REPO_VERSION + NEW_VERSION_AVAILABLE=$(python3 ${CI_RELEASE_TOOLS}/src/version_utils.py --has-newer-version "$VCPKG_VERSION" "$REPO_VERSION") + echo "NEW_VERSION_AVAILABLE=${NEW_VERSION_AVAILABLE}" | tee -a ${GITHUB_ENV} + + - name: Clone vcpkg repository and configure KDAB fork + if: env.NEW_VERSION_AVAILABLE == 'true' + run: | + git clone https://github.com/${{ inputs.vcpkg_origin_namespace }}/vcpkg.git + git -C vcpkg remote add fork https://github.com/${{ inputs.vcpkg_fork_namespace }}/vcpkg.git + git -C vcpkg remote set-url --push fork https://x-access-token:${{ secrets.FORK_PUSH_TOKEN }}@github.com/${{ inputs.vcpkg_fork_namespace }}/vcpkg.git + git -C vcpkg fetch --all --prune + + - name: Check if the branch exists + if: env.NEW_VERSION_AVAILABLE == 'true' + run: | + BRANCH="${{ inputs.project }}_$REPO_VERSION" + echo "BRANCH=${BRANCH}" | tee -a ${GITHUB_ENV} + BRANCH_EXISTS=$(git -C vcpkg ls-remote --heads fork $BRANCH | grep -q $BRANCH && echo true || echo false) + echo "BRANCH_EXISTS=${BRANCH_EXISTS}" | tee -a ${GITHUB_ENV} + + - name: Setup vcpkg + if: env.NEW_VERSION_AVAILABLE == 'true' && ( env.BRANCH_EXISTS == 'false' || inputs.force_push == true ) + run: | + cd vcpkg + ./bootstrap-vcpkg.sh -disableMetrics + echo "VCPKG_ROOT=${PWD}" | tee -a ${GITHUB_ENV} + + # TODO This step could probably be refactored into a python function + - name: Create the branch and update the port version + if: env.NEW_VERSION_AVAILABLE == 'true' && ( env.BRANCH_EXISTS == 'false' || inputs.force_push == true ) + run: | + cd vcpkg + git switch -c $BRANCH + + VCPKG_PORT_PATH="ports/${{ inputs.project }}" + VCPKG_JSON_FILE="${VCPKG_PORT_PATH}/vcpkg.json" + VCPKG_PORTFILE_CMAKE_FILE="${VCPKG_PORT_PATH}/portfile.cmake" + + sed -i "s|\"version\": \"$VCPKG_VERSION\"|\"version\": \"$REPO_VERSION\"|g" "${VCPKG_JSON_FILE}" + if [[ "${{ inputs.port_version }}" -eq 0 ]]; then + sed -Ei '/"port-version"/d' "${VCPKG_JSON_FILE}" + else + if grep -q '"port-version"' "${VCPKG_JSON_FILE}"; then + sed -Ei "s/\"port-version\":[[:space:]]*[0-9]+/\"port-version\": ${{ inputs.port_version }}/" "${VCPKG_JSON_FILE}" + else + sed -Ei "/\"version\"/a \"port-version\": ${{ inputs.port_version }}," "${VCPKG_JSON_FILE}" + fi + fi + + sed -i "s|https://github.com/[^/]\+/|https://github.com/${{ inputs.namespace }}/|g" "${VCPKG_PORTFILE_CMAKE_FILE}" + + ./vcpkg format-manifest "${VCPKG_JSON_FILE}" + + LATEST_RELEASE=$(python3 ${CI_RELEASE_TOOLS}/src/gh_utils.py --get-latest-release KDAB/${{ inputs.project }}) + curl -o ../${LATEST_RELEASE}.tar.gz https://github.com/${{ inputs.namespace }}/${{ inputs.project }}/archive/refs/tags/${LATEST_RELEASE}.tar.gz + SHA512=$(shasum -a 512 ../${LATEST_RELEASE}.tar.gz | awk '{print $1}') + sed -i "s|SHA512 [a-fA-F0-9]*|SHA512 ${SHA512}|" "${VCPKG_PORTFILE_CMAKE_FILE}" + + git add "${VCPKG_JSON_FILE}" "${VCPKG_PORTFILE_CMAKE_FILE}" + git commit -m "[${{ inputs.project }}] update to $REPO_VERSION" + + ./vcpkg x-add-version --all + git add versions + git commit --amend --no-edit --date=now + + env: + GH_TOKEN: ${{ github.token }} + + - name: Push the branch on the vcpkg fork + if: env.NEW_VERSION_AVAILABLE == 'true' && ( env.BRANCH_EXISTS == 'false' || inputs.force_push == true ) + run: | + cd vcpkg + git show + git remote set-url fork https://x-access-token:${{ secrets.FORK_PUSH_TOKEN }}@github.com/${{ inputs.vcpkg_fork_namespace }}/vcpkg.git + + MAYBE_FORCE="" + if [[ ${{ inputs.force_push }} == true ]]; then + MAYBE_FORCE="--force" + fi + git push ${MAYBE_FORCE} fork ${BRANCH} + + env: + GH_TOKEN: ${{ github.token }} + + - name: Write output when branch is ready + id: write-output + if: env.NEW_VERSION_AVAILABLE == 'true' && ( env.BRANCH_EXISTS == 'false' || inputs.force_push == true ) + run: | + echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" + echo "new_version=${REPO_VERSION}" >> "$GITHUB_OUTPUT" + + # TODO Add test workflow here + + prepare-pull-request: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + needs: + - prepare-branch + steps: + - name: Create the PR body + run: | + echo "This PR was automatically created by the release workflow." > body.md + echo >> body.md + echo "Please **double check that everything is in order**, and **update the description** before marking it as \"Ready for review\"." >> body.md + echo >> body.md + curl -s https://raw.githubusercontent.com/${{ inputs.vcpkg_origin_namespace }}/vcpkg/refs/heads/master/.github/pull_request_template.md >> body.md + cat body.md + + - name: Create the PR + run: | + gh pr create \ + --head "${{ inputs.vcpkg_fork_namespace }}:${{ needs.prepare-branch.outputs.branch }}" \ + --base master \ + --repo "${{ inputs.vcpkg_origin_namespace }}/vcpkg" \ + --title "[${{ inputs.project }}] Update to ${{ needs.prepare-branch.outputs.new_version }}" \ + --body-file body.md \ + --draft + env: + GH_TOKEN: ${{ secrets.FORK_PUSH_TOKEN }}