Skip to content
Open
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
3 changes: 3 additions & 0 deletions .breakage/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[deps]
GitHub = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26"
PkgDeps = "839e9fc8-855b-5b3c-a3b7-2833d3dd1f59"
18 changes: 18 additions & 0 deletions .breakage/get_jso_users.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import GitHub, PkgDeps # both export users()

length(ARGS) >= 1 || error("specify at least one JSO package as argument")

jso_repos, _ = GitHub.repos("JuliaSmoothOptimizers")
jso_names = [splitext(x.name)[1] for x ∈ jso_repos]

name = splitext(ARGS[1])[1]
name ∈ jso_names || error("argument should be one of ", jso_names)

dependents = String[]
try
global dependents = filter(x -> x ∈ jso_names, PkgDeps.users(name))
catch e
# package not registered; don't insert into dependents
end

println(dependents)
136 changes: 136 additions & 0 deletions .github/workflows/Breakage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: Breakage

on:
workflow_call:

jobs:
# Build dynamically the matrix on which the "break" job will run.
# The matrix contains the packages that depend on ${{ env.pkg }}.
# Job "setup_matrix" outputs variable "matrix", which is in turn
# the output of the "getmatrix" step.
# The contents of "matrix" is a JSON description of a matrix used
# in the next step. It has the form
# {
# "pkg": [
# "PROPACK",
# "LLSModels",
# "FletcherPenaltySolver"
# ]
# }
setup_matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.getmatrix.outputs.matrix }}
env:
pkg: ${{ github.event.repository.name }}
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: 1
arch: x64
- id: getmatrix
run: |
git clone https://github.com/JuliaSmoothOptimizers/.github.git breakage-tools
julia -e 'using Pkg; Pkg.Registry.add(RegistrySpec(url = "https://github.com/JuliaRegistries/General.git"))'
julia --project=breakage-tools/.breakage -e 'using Pkg; Pkg.update(); Pkg.instantiate()'
pkgs=$(julia --project=breakage-tools/.breakage breakage-tools/.breakage/get_jso_users.jl ${{ env.pkg }})
vs='["latest", "stable"]'
# Check if pkgs is empty, and set it to a JSON array if necessary
if [[ -z "$pkgs" || "$pkgs" == "String[]" ]]; then
echo "No packages found; exiting successfully."
exit 0
fi
vs='["latest", "stable"]'
matrix=$(jq -cn --argjson deps "$pkgs" --argjson vers "$vs" '{pkg: $deps, pkgversion: $vers}') # don't escape quotes like many posts suggest
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"

break:
needs: setup_matrix
if: needs.setup_matrix.result == 'success' && needs.setup_matrix.outputs.matrix != ''
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup_matrix.outputs.matrix) }}

steps:
- uses: actions/checkout@v4

# Install Julia
- uses: julia-actions/setup-julia@v2
with:
version: 1
arch: x64
- uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@v1

# Breakage test
- name: 'Breakage of ${{ matrix.pkg }}, ${{ matrix.pkgversion }} version'
env:
PKG: ${{ matrix.pkg }}
VERSION: ${{ matrix.pkgversion }}
run: |
set -v
mkdir -p ./breakage
git clone https://github.com/JuliaSmoothOptimizers/$PKG.jl.git
cd $PKG.jl
if [ $VERSION == "stable" ]; then
TAG=$(git tag -l "v*" --sort=-creatordate | head -n1)
if [ -z "$TAG" ]; then
TAG="no_tag"
else
git checkout $TAG
fi
else
TAG=$VERSION
fi
export TAG
julia -e 'using Pkg;
PKG, TAG, VERSION = ENV["PKG"], ENV["TAG"], ENV["VERSION"]
joburl = joinpath(ENV["GITHUB_SERVER_URL"], ENV["GITHUB_REPOSITORY"], "actions/runs", ENV["GITHUB_RUN_ID"])
open("../breakage/breakage-$PKG-$VERSION", "w") do io
try
TAG == "no_tag" && error("No tag for $VERSION")
pkg"activate .";
pkg"instantiate";
pkg"dev ../";
if TAG == "latest"
global TAG = chomp(read(`git rev-parse --short HEAD`, String))
end
pkg"build";
pkg"test";

print(io, "[![](https://img.shields.io/badge/$TAG-Pass-green)]($joburl)");
catch e
@error e;
print(io, "[![](https://img.shields.io/badge/$TAG-Fail-red)]($joburl)");
end;
end'

- uses: actions/upload-artifact@v4
with:
name: breakage-${{ matrix.pkg }}-${{ matrix.pkgversion }}
path: breakage/breakage-*

upload_pr_number:
runs-on: ubuntu-latest
steps:
- name: Write PR number
run: |
mkdir -p breakage-pr-number
echo "${{ github.event.pull_request.number }}" > breakage-pr-number/pr-number.txt

- name: Upload PR number artifact
uses: actions/upload-artifact@v4
with:
name: pr-number
path: breakage-pr-number/pr-number.txt
70 changes: 70 additions & 0 deletions .github/workflows/CommentBreakage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Comment Breakage

on:
workflow_call:

jobs:
comment_pr:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Download PR number artifact
uses: dawidd6/action-download-artifact@v17
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: Breakage
run_id: ${{ github.event.workflow_run.id }}
name: pr-number
path: pr-number
use_unzip: true

- name: Read PR number
id: read_pr
run: |
PR_NUMBER=$(cat pr-number/pr-number.txt)
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"

- name: Download breakage artifacts
uses: dawidd6/action-download-artifact@v17
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: Breakage
run_id: ${{ github.event.workflow_run.id }}
name: breakage-*
name_is_regexp: true
path: breakage
merge_multiple: true
use_unzip: true
- run: ls -R
- name: Generate summary
run: |
cd breakage
echo "| Package name | latest | stable |" > summary.md
echo "|--|--|--|" >> summary.md
count=0
for file in breakage-*
do
if [ $count == "0" ]; then
name=$(echo $file | cut -f2 -d-)
echo -n "| $name | "
else
echo -n "| "
fi
cat $file
if [ $count == "0" ]; then
echo -n " "
count=1
else
echo " |"
count=0
fi
done >> summary.md
Comment on lines +47 to +64
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The summary generation will fail if no files match breakage-* (the glob will expand to the literal string and cat breakage-* will error). Add an explicit check for matching files (or enable nullglob) and handle the empty-artifact case so the workflow can still post a useful message.

Suggested change
count=0
for file in breakage-*
do
if [ $count == "0" ]; then
name=$(echo $file | cut -f2 -d-)
echo -n "| $name | "
else
echo -n "| "
fi
cat $file
if [ $count == "0" ]; then
echo -n " "
count=1
else
echo " |"
count=0
fi
done >> summary.md
shopt -s nullglob
files=(breakage-*)
if [ ${#files[@]} -eq 0 ]; then
echo "| No breakage artifacts found | | |" >> summary.md
else
count=0
for file in "${files[@]}"
do
if [ "$count" = "0" ]; then
name=$(echo "$file" | cut -f2 -d-)
echo -n "| $name | "
else
echo -n "| "
fi
cat "$file"
if [ "$count" = "0" ]; then
echo -n " "
count=1
else
echo " |"
count=0
fi
done >> summary.md
fi

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +64
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The markdown table construction relies on file ordering and an even number of breakage-* files (toggling count to decide when to close the row). If a package is missing one of the two expected files, or the glob order changes, you’ll produce malformed rows (e.g., missing trailing |). Consider iterating per package and explicitly reading the expected latest/stable files, or add validation and a final row-close to keep the output well-formed.

Suggested change
for file in breakage-*
do
if [ $count == "0" ]; then
name=$(echo $file | cut -f2 -d-)
echo -n "| $name | "
else
echo -n "| "
fi
cat $file
if [ $count == "0" ]; then
echo -n " "
count=1
else
echo " |"
count=0
fi
done >> summary.md
{
for file in breakage-*
do
if [ "$count" -eq 0 ]; then
name=$(echo "$file" | cut -f2 -d-)
echo -n "| $name | "
else
echo -n "| "
fi
cat "$file"
if [ "$count" -eq 0 ]; then
echo -n " "
count=1
else
echo " |"
count=0
fi
done
if [ "$count" -eq 1 ]; then
echo " |"
fi
} >> summary.md

Copilot uses AI. Check for mistakes.

- name: PR comment with file
uses: thollander/actions-comment-pull-request@v2
with:
filePath: breakage/summary.md
pr_number: ${{ steps.read_pr.outputs.pr_number }}