diff --git a/.github/actions/vcpkg_update_report/action.yml b/.github/actions/vcpkg_update_report/action.yml new file mode 100644 index 000000000000..b89672d48ede --- /dev/null +++ b/.github/actions/vcpkg_update_report/action.yml @@ -0,0 +1,49 @@ +name: Compare vcpkg install changes +description: Compares vcpkg install outputs between the base and head refs on pull requests and generates a report. + +inputs: + vcpkg-manifest-dir: + description: 'Directory containing the vcpkg.json manifest' + required: true + default: '.' + type: string + triplet: + description: 'Triplet to use for vcpkg installation' + required: true + default: 'x64-linux' + type: string + +outputs: + report: + description: 'The report of added and removed packages after vcpkg installation comparison' + value: ${{ steps.compare.outputs.report }} + +runs: + using: "composite" + steps: + # Run vcpkg install --dry-run on the head ref + - name: Run vcpkg install (HEAD) + shell: bash + run: | + vcpkg install --dry-run --triplet ${{ inputs.triplet }} --x-manifest-root=${{ inputs.vcpkg-manifest-dir }} > /tmp/vcpkg-head-output.txt + + # Run vcpkg install --dry-run on the base ref + - name: Run vcpkg install (BASE) + shell: bash + run: | + git worktree add .base-ref ${{ github.event.pull_request.base.sha }} + vcpkg install --dry-run --triplet ${{ inputs.triplet }} --x-manifest-root=.base-ref/${{ inputs.vcpkg-manifest-dir }} > /tmp/vcpkg-base-output.txt + + + # Compare the outputs and generate a report + - name: Compare vcpkg outputs + shell: bash + id: compare + run: | + python3 ${GITHUB_ACTION_PATH}/vcpkg-diff.py > /tmp/vcpkg-report.txt + cat /tmp/vcpkg-report.txt + { + echo 'report<> "$GITHUB_OUTPUT" diff --git a/.github/actions/vcpkg_update_report/vcpkg-diff.py b/.github/actions/vcpkg_update_report/vcpkg-diff.py new file mode 100644 index 000000000000..bc895b038b6c --- /dev/null +++ b/.github/actions/vcpkg_update_report/vcpkg-diff.py @@ -0,0 +1,101 @@ +import re + + +def extract_packages(data): + """ + Extract package name, triplet, version, and features information from the file content. + """ + packages = {} + lines = data.strip().split("\n") + for line in lines: + # Regex to match the package format and capture features inside brackets + match = re.match( + r"\s*\*\s+([^\[\]:]+)(?:\[(.*?)\])?:([^\[\]@]+)@([^\s]+)\s+--", line + ) + if match: + package_name = match.group(1) + features = match.group(2) if match.group(2) else "" + triplet = match.group(3) + version = match.group(4) + features_list = ( + [feature.strip() for feature in features.split(",")] if features else [] + ) + packages[package_name] = (triplet, version, features_list) + return packages + + +def compare_features(features1, features2): + """ + Compare two feature lists and return the differences. + """ + added_features = set(features2) - set(features1) + removed_features = set(features1) - set(features2) + return added_features, removed_features + + +def generate_report(file1_content, file2_content): + # Extract package information from both files + file1_packages = extract_packages(file1_content) + file2_packages = extract_packages(file2_content) + + added = [] + removed = [] + updated = [] + + # Identify removed and updated packages + for pkg in file1_packages: + if pkg not in file2_packages: + removed.append(pkg) + else: + # Compare version and features + triplet1, version1, features1 = file1_packages[pkg] + triplet2, version2, features2 = file2_packages[pkg] + updated_parts = [] + if version1 != version2 or triplet1 != triplet2: + updated_parts.append(f"{version1} -> {version2}") + added_features, removed_features = compare_features(features1, features2) + if added_features: + updated_parts.append("+" + ", ".join(added_features)) + if removed_features: + updated_parts.append("-" + ", ".join(removed_features)) + if updated_parts: + updated.append(f"{pkg}: " + " ".join(updated_parts)) + + # Identify added packages + for pkg in file2_packages: + if pkg not in file1_packages: + added.append(pkg) + + # Print the report + if added: + print("**Added packages:**") + for pkg in added: + triplet, version, features = file2_packages[pkg] + print(f" 🍓 {pkg}: {version} (Features: {', '.join(features)})") + + if removed: + print("\n**Removed packages:**") + for pkg in removed: + triplet, version, features = file1_packages[pkg] + print(f" 🍄 {pkg}: {version} (Features: {', '.join(features)})") + + if updated: + print("\n**Updated packages:**") + for pkg in updated: + print(f" 🍇 {pkg}") + + +def read_file(file_path): + """ + Read the content of a file. + """ + with open(file_path, "r") as file: + return file.read() + + +# Read files +file1_content = read_file("/tmp/vcpkg-base-output.txt") +file2_content = read_file("/tmp/vcpkg-head-output.txt") + +# Generate the report +generate_report(file1_content, file2_content) diff --git a/.github/workflows/vcpkg-update-report.yml b/.github/workflows/vcpkg-update-report.yml new file mode 100644 index 000000000000..0d084d6d3d7b --- /dev/null +++ b/.github/workflows/vcpkg-update-report.yml @@ -0,0 +1,35 @@ +--- +name: 🧮 Vcpkg report +on: + pull_request: + paths: + - 'vcpkg/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + vcpkg-check: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 30 + + - name: Generate diff report + id: vcpkg_diff + uses: ./.github/actions/vcpkg_update_report + with: + vcpkg-manifest-dir: vcpkg + triplet: x64-linux + + - name: Schedule report comment + uses: ./.github/actions/post_sticky_comment + if: github.event_name == 'pull_request' + with: + marker: vcpkg-report + body: | + ### 🧮 Vcpkg update report + ${{ steps.vcpkg_diff.outputs.report }} + pr: ${{ github.event.number }}