Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pr_changed_files script to detect changed files in a PR #148

Merged
merged 28 commits into from
Feb 11, 2025

Conversation

iangmaia
Copy link
Contributor

@iangmaia iangmaia commented Feb 4, 2025

Closes #131.

Description

This PR adds a new utility script that will help optimize CI pipelines by avoiding unnecessary builds, allowing steps to be skipped based on which files changed in a PR, for example.
It works by comparing PR changes against the base branch and supports glob patterns for file matching.

Usage

The pr_changed_files script can be used in the following ways:

  • Check if there are any changes in a given PR (no arguments)
  • Check if changes are limited to specific files (--only-in)
  • Check if changes include specific files (--includes)

Examples

Skip UI Tests when only docs changed

In .buildkite/shared-pipeline-vars, you can create a var that can later be used by conditions in YAML files:

SKIP_UI_TESTS=$(pr_changed_files --only-in "README.md" "docs/")

In .buildkite/pipeline.yml:

steps:
  label: "UI Tests"
  command: ./commands/run-ui-tests.sh
  if: !build.env('SKIP_UI_TESTS')

Or run specific checks according to what was changed

CODE_CHANGED=$(pr_changed_files --includes "*.swift")

And in .buildkite/pipeline.yml:

steps:
  label: SwiftLint
  agent: linter
  command: swiftlint
  if: build.env('CODE_CHANGED')

Just check if a PR has changes in a script

if [[ $(pr_changed_files) == "true" ]]; then
  echo "PR contains changes"
fi

  • I have considered if this change warrants release notes and have added them to the appropriate section in the CHANGELOG.md if necessary.

@iangmaia iangmaia added the enhancement New feature or request label Feb 4, 2025
@iangmaia iangmaia self-assigned this Feb 4, 2025
@iangmaia iangmaia force-pushed the iangmaia/pr-changed-files branch from c47eef6 to 7d8ea4a Compare February 4, 2025 19:34
@iangmaia iangmaia marked this pull request as ready for review February 4, 2025 20:26
@iangmaia iangmaia requested a review from a team February 4, 2025 20:26
bin/pr_changed_files Outdated Show resolved Hide resolved
Copy link
Contributor

@mokagio mokagio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know whether the implementation ChatGPT suggested is genuinely better, but I do like how it accounts for spaces in the file names etc.

Regardless, I think we might either come up with a way to test this script at the repo level, or bite the bullet and start using it (maybe in the repos that our team owns) and see how it behaves. I think it should be possible to devise a few integration tests, maybe with the help of Ruby, where we make commits in a dummy repo and then run the script and check what it returns.

bin/pr_changed_files Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
@@ -92,3 +92,29 @@ steps:
notify:
- github_commit_status:
context: "install_swiftpm_dependencies Tests: Xcode Workspace and Project - Explicit"

- group: ":git: pr_changed_files Tests"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

bin/pr_changed_files Outdated Show resolved Hide resolved
@@ -92,3 +92,29 @@ steps:
notify:
- github_commit_status:
context: "install_swiftpm_dependencies Tests: Xcode Workspace and Project - Explicit"

Copy link
Contributor Author

@iangmaia iangmaia Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mokagio and @AliSoftware !

Gathering everything in a comment to make sure I'm not missing anything (and I'll continue tomorrow).

I've implemented a few updates, and the most relevant change was to add some tests.

These are my next steps in the PR:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AliSoftware @mokagio
I've updated all the items above (I've added the commit in which each change was implemented).

Now I've been trying to use a8-ci-toolkit-buildkite-plugin by pointing it directly to this branch in another repo, to make sure the general solution works as expected. See woocommerce/woocommerce-ios#15086.

I'm facing an issue (that is a bit obvious, in retrospect) where the pr_changed_files command we're trying to run on .buildkite/shared-pipeline-vars isn't installed yet, as the version is defined in the .buildkite/shared-pipeline-vars itself (see the reported error).

One simple alternative I can think of: call buildkite-agent plugin install $CI_TOOLKIT directly in shared-pipeline-vars, but that would of course slow down the upload a bit 🤔
What do you think? Any other ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm facing an issue (that is a bit obvious, in retrospect) where the pr_changed_files command we're trying to run on .buildkite/shared-pipeline-vars isn't installed yet, as the version is defined in the .buildkite/shared-pipeline-vars itself

Oh shoot, didn't think about that… right…

One simple alternative I can think of: call buildkite-agent plugin install $CI_TOOLKIT directly in shared-pipeline-vars, but that would of course slow down the upload a bit 🤔

Yeah. Tricky.

1️⃣ buildkite-agent plugin install

I guess it depends how much time it will add.

It will only add that time for pipelines that do want to use pr_changed_files and thus need to install the plugin in the uploader agent—without affecting other pipelines that don't use pr_changed_files in their shared-pipeline-vars and won't call buildkite-agent plugin install. And given the goal of this trick is to be able to skip e.g. UI Test jobs on builds where they're not needed, if the installation of the plugin takes only 1–2 seconds, then the time lost will be balanced tenfold with the benefit to avoid the build time of many skippable jobs…

2️⃣ Another option would be to download just that binary ourselves (curl https://raw.githubusercontent.com/Automattic/a8c-ci-toolkit-buildkite-plugin/refs/heads/trunk/bin/pr_changed_files + some chmod +x…) but at that point that would kinda be reinventing the wheel compared to what buildkite-agent plugin install does (sure it'd reduce the download to only that specific binary instead of installing the whole plugin, but not sure there'd be much of a difference, those bin/ scripts in our plugin are not that big)

3️⃣ Bootstrap script on the uploader agent

The last option I could think of would be to install the script directly into the agents at the time we provision and deploy them.

At that point that last option 3️⃣ feels a bit too convoluted just to avoid the simpler call to buildkite-agent plugin install… but it's still a possibility to be aware of…

Copy link
Contributor Author

@iangmaia iangmaia Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out something like buildkite-agent plugin install was how I remembered we did to install it in the agents, but in fact we just download them during the agent provisioning 😓
I don't think there's an easy command to dynamically install plugins, only use them in pipeline jobs.

Which makes me think of two other alternatives:
4️⃣

  - label: "Check Changes"
    key: check-changes
    command: |
      SKIP_UI_TESTS=$(pr_changed_files --all-match "*.md")
      buildkite-agent meta-data set "SKIP_UI_TESTS" "$SKIP_UI_TESTS"
    plugins: [$CI_TOOLKIT]

And make the more expensive jobs depend on it 🤔 (assuming I can check then SKIP_UI_TESTS in the pipeline, but I haven't tested it).

And 5️⃣
Just use it on a case by case basis either in the script itself (e.g. .buildkite/commands/run-ui-tests.sh or equivalent) or in the command in yml attribute.


3️⃣ even if it is a bit more complex to build, it's easier to use in the end. Alternatively, 4️⃣ or 5️⃣ could be a simple starting point to try the command.

Copy link
Contributor

@AliSoftware AliSoftware Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4️⃣ wouldn't work, at least not as pitched of using SKIP_UI_TESTS metadata in the if: attribute of UI Tests step.

image

But alternatives of that include:

  • 4️⃣ 🅰️ either use buildkite-agent pipeline upload to upload the UITests steps conditionally (after having extracted their steps in a dedicated *.yml file)
  • 4️⃣ 🅱️ or keep buildkite-agent meta-data set and let the UITests steps start a job in all cases (instead of the creation of the job itself being skipped by if: attribute), and check the metadata value at the start of the job's script. Which would mean we'd still have the UITests job request an agent and start the build, but it'd exit early.

🅰️ Would have the benefit of preventing to create the UITests job to begin with, reducing the need to assign a CI agent for nothing. But that also means it wouldn't generate the GitHub CI check notification, and so that the "Run UI Tests" check would never be marked as done in the PR. If that check is marked as required for the PR to be merged, that is going to be an issue.

🅱️ Has the drawback of requesting a CI agent even if it will exit early and have nothing to build, but has the benefit of ensuring the CI check notification will be sent to the PR if that check is required.


But if we go with 4️⃣ 🅱️ at that point we might as well just run the pr_changed_files directly in the UI Tests command (instead of running it in a separate step, set it as metadata, and read the metadata in the UI Tests command…), aka your option 5️⃣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test with 4️⃣ 🅱️ / 5️⃣: woocommerce/woocommerce-ios#15086 / https://buildkite.com/automattic/woocommerce-ios/builds/28092#0194e1ba-9cf5-4ccc-8cfd-9bd73e0b1494/286-288.
It's pretty straightforward, but another downside as you mention is the fact that it still uses some CI resources.

In any case, I believe we can move forward with this PR, as the final solution on how this is going to be used should be independent of the implementation.

@@ -92,3 +92,29 @@ steps:
notify:
- github_commit_status:
context: "install_swiftpm_dependencies Tests: Xcode Workspace and Project - Explicit"

- group: ":github: pr_changed_files Tests"
Copy link
Contributor

@AliSoftware AliSoftware Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 What's a bit funny and meta is that once this new pr_changed_files helper lands in trunk, we could consider use it on itself to only run its unit tests if the script itself has changed, i.e.

Suggested change
- group: ":github: pr_changed_files Tests"
- group: ":github: pr_changed_files Tests"
if: build.env('RUN_PR_CHANGED_FILES_TESTS')

After adding RUN_PR_CHANGED_FILES_TESTS=$(pr_changed_files --includes "bin/pr_changed_files" "tests/pr_changed_files/*") to the shared-pipeline-vars of this repo 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES! I was thinking exactly the same 😄

Copy link
Contributor

@AliSoftware AliSoftware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dammit I just realized that I haven't submitted my pending comments of my last review… so posting them even if you asked for review only after I started this one 😅

bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
@@ -92,3 +92,29 @@ steps:
notify:
- github_commit_status:
context: "install_swiftpm_dependencies Tests: Xcode Workspace and Project - Explicit"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm facing an issue (that is a bit obvious, in retrospect) where the pr_changed_files command we're trying to run on .buildkite/shared-pipeline-vars isn't installed yet, as the version is defined in the .buildkite/shared-pipeline-vars itself

Oh shoot, didn't think about that… right…

One simple alternative I can think of: call buildkite-agent plugin install $CI_TOOLKIT directly in shared-pipeline-vars, but that would of course slow down the upload a bit 🤔

Yeah. Tricky.

1️⃣ buildkite-agent plugin install

I guess it depends how much time it will add.

It will only add that time for pipelines that do want to use pr_changed_files and thus need to install the plugin in the uploader agent—without affecting other pipelines that don't use pr_changed_files in their shared-pipeline-vars and won't call buildkite-agent plugin install. And given the goal of this trick is to be able to skip e.g. UI Test jobs on builds where they're not needed, if the installation of the plugin takes only 1–2 seconds, then the time lost will be balanced tenfold with the benefit to avoid the build time of many skippable jobs…

2️⃣ Another option would be to download just that binary ourselves (curl https://raw.githubusercontent.com/Automattic/a8c-ci-toolkit-buildkite-plugin/refs/heads/trunk/bin/pr_changed_files + some chmod +x…) but at that point that would kinda be reinventing the wheel compared to what buildkite-agent plugin install does (sure it'd reduce the download to only that specific binary instead of installing the whole plugin, but not sure there'd be much of a difference, those bin/ scripts in our plugin are not that big)

3️⃣ Bootstrap script on the uploader agent

The last option I could think of would be to install the script directly into the agents at the time we provision and deploy them.

At that point that last option 3️⃣ feels a bit too convoluted just to avoid the simpler call to buildkite-agent plugin install… but it's still a possibility to be aware of…

iangmaia and others added 2 commits February 7, 2025 19:46
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Outdated Show resolved Hide resolved
bin/pr_changed_files Show resolved Hide resolved
iangmaia and others added 7 commits February 10, 2025 18:10
Co-authored-by: Olivier Halligon <[email protected]>
Using single quotes to make it easier to understand the use of special characters—including in the code creating the test files
For consistency and to make it easier to understand the special characters we are playing with without having to escape them in double-quote contexts
Copy link
Contributor

@AliSoftware AliSoftware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit: 🍾

@iangmaia iangmaia merged commit 79cfdf1 into trunk Feb 11, 2025
17 checks passed
@iangmaia iangmaia deleted the iangmaia/pr-changed-files branch February 11, 2025 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add pr_only_includes_changed_files_from utility — to make it possible to skip CI jobs if e.g. no code change
3 participants