Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
012e8e1
chore: add sync sentinel markers; simplify unsupported-version fallback
coatless Jun 23, 2026
2132baa
test: add bash test harness and upstream HTML fixture
coatless Jun 23, 2026
ed68473
feat: add sync-openmp.sh parse_page (upstream HTML -> records)
coatless Jun 23, 2026
cb87be2
fix: skip HTML preamble in parse_page to silence false WARN
coatless Jun 23, 2026
7febaef
feat: add case/help/readme block renderers
coatless Jun 23, 2026
6f40479
feat: add current_pin, resolve_sha (compute-on-change), replace_block
coatless Jun 23, 2026
c8bc335
feat: implement sync-openmp.sh main with --check and compute-on-change
coatless Jun 23, 2026
237dd0a
ci: weekly OpenMP upstream sync that opens a PR on change
coatless Jun 23, 2026
5a88ad2
fix: enforce pipefail so a failed sync fails the CI step
coatless Jun 23, 2026
035a4e5
fix: make test-e2e set a deterministic pre-state, decoupling from rep…
coatless Jun 23, 2026
76ee4a0
chore: sync OpenMP 19.1.0 -> 19.1.5 from upstream
coatless Jun 23, 2026
5f3ede8
fix: guard replace_block against missing markers and preserve file mode
coatless Jun 23, 2026
87fa869
feat: add test-openmp.sh C toolchain correctness check
coatless Jun 23, 2026
b80e3a5
fix: tolerate nonzero OMP_NUM_THREADS run so the diagnostic prints
coatless Jun 23, 2026
aa3d05a
feat: add R package OpenMP correctness check to test-openmp.sh
coatless Jun 23, 2026
0767159
test: syntax-guard test-openmp.sh in the cross-platform suite
coatless Jun 23, 2026
e188b83
ci: run OpenMP correctness test on macOS for runtime-affecting changes
coatless Jun 23, 2026
d0e622e
fix: capture C-test exit code without aborting so FAIL diagnostics print
coatless Jun 23, 2026
8ec2f61
ci: bump actions/checkout to v7
coatless Jun 23, 2026
2aa588b
ci: bump peter-evans/create-pull-request to v8
coatless Jun 23, 2026
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
38 changes: 38 additions & 0 deletions .github/workflows/check-upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Check OpenMP upstream

on:
schedule:
- cron: "17 6 * * 1" # Mondays 06:17 UTC
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Run sync
id: sync
shell: bash
run: |
set -euo pipefail
./sync-openmp.sh | tee sync-summary.txt

- name: Run tests
run: bash tests/run-tests.sh

- name: Open PR on change
uses: peter-evans/create-pull-request@v8
with:
branch: chore/sync-openmp-upstream
title: "chore: sync OpenMP versions from upstream"
body-path: sync-summary.txt
commit-message: "chore: sync OpenMP versions from upstream"
delete-branch: true
add-paths: |
install-openmp.sh
README.md
34 changes: 34 additions & 0 deletions .github/workflows/test-openmp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: OpenMP correctness

on:
push:
branches: [main]
paths:
- install-openmp.sh
- test-openmp.sh
- .github/workflows/test-openmp.yml
pull_request:
branches: [main]
paths:
- install-openmp.sh
- test-openmp.sh
- .github/workflows/test-openmp.yml
workflow_dispatch:

jobs:
openmp-correctness:
strategy:
fail-fast: false
matrix:
os: [macos-14, macos-15]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7

- name: Install OpenMP runtime
run: ./install-openmp.sh --yes

- uses: r-lib/actions/setup-r@v2

- name: Run OpenMP correctness test
run: ./test-openmp.sh
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ chmod +x uninstall-openmp.sh

| Xcode Version | Apple Clang | OpenMP Version | Download |
|---------------|-------------|----------------|----------|
| 16.3+ | 1700.x | 19.1.0 | [openmp-19.1.0-darwin20-Release.tar.gz](https://mac.r-project.org/openmp/openmp-19.1.0-darwin20-Release.tar.gz) |
<!-- >>> BEGIN GENERATED README TABLE (managed by sync-openmp.sh; do not edit) >>> -->
| 16.3-26.3 | 1700.x | 19.1.5 | [openmp-19.1.5-darwin20-Release.tar.gz](https://mac.r-project.org/openmp/openmp-19.1.5-darwin20-Release.tar.gz) |
| 16.0-16.2 | 1600.x | 17.0.6 | [openmp-17.0.6-darwin20-Release.tar.gz](https://mac.r-project.org/openmp/openmp-17.0.6-darwin20-Release.tar.gz) |
| 15.x | 1500.x | 16.0.4 | [openmp-16.0.4-darwin20-Release.tar.gz](https://mac.r-project.org/openmp/openmp-16.0.4-darwin20-Release.tar.gz) |
| 14.3.x | 1403.x | 15.0.7 | [openmp-15.0.7-darwin20-Release.tar.gz](https://mac.r-project.org/openmp/openmp-15.0.7-darwin20-Release.tar.gz) |
Expand All @@ -49,6 +50,7 @@ chmod +x uninstall-openmp.sh
| 11.4-11.7 | 1103.x | 9.0.1 | [openmp-9.0.1-darwin17-Release.tar.gz](https://mac.r-project.org/openmp/openmp-9.0.1-darwin17-Release.tar.gz) |
| 11.0-11.3.1 | 1100.x | 8.0.1 | [openmp-8.0.1-darwin17-Release.tar.gz](https://mac.r-project.org/openmp/openmp-8.0.1-darwin17-Release.tar.gz) |
| 10.2-10.3 | 1001.x | 7.1.0 | [openmp-7.1.0-darwin17-Release.tar.gz](https://mac.r-project.org/openmp/openmp-7.1.0-darwin17-Release.tar.gz) |
<!-- <<< END GENERATED README TABLE <<< -->

> [!NOTE]
>
Expand Down
47 changes: 19 additions & 28 deletions install-openmp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,20 @@ show_help() {
echo " - sudo privileges for installation to /usr/local/"
echo ""
echo -e "${YELLOW}SUPPORTED VERSIONS:${NC}"
echo " Xcode 16.3+ → OpenMP 19.1.0"
echo " Xcode 16.0-16.2 → OpenMP 17.0.6"
echo " Xcode 15.x → OpenMP 16.0.4"
echo " Xcode 14.3.x → OpenMP 15.0.7"
echo " Xcode 14.0-14.2 → OpenMP 14.0.6"
echo " Xcode 13.3-13.4.1 → OpenMP 13.0.0"
echo " Xcode 13.0-13.2.1 → OpenMP 12.0.1"
echo " Xcode 12.5 → OpenMP 11.0.1"
echo " Xcode 12.0-12.4 → OpenMP 10.0.0"
echo " Xcode 11.4-11.7 → OpenMP 9.0.1"
echo " Xcode 11.0-11.3.1 → OpenMP 8.0.1"
echo " Xcode 10.2-10.3 → OpenMP 7.1.0"
# >>> BEGIN GENERATED HELP VERSIONS (managed by sync-openmp.sh; do not edit) >>>
echo " Xcode 16.3-26.3 → OpenMP 19.1.5"
echo " Xcode 16.0-16.2 → OpenMP 17.0.6"
echo " Xcode 15.x → OpenMP 16.0.4"
echo " Xcode 14.3.x → OpenMP 15.0.7"
echo " Xcode 14.0-14.2 → OpenMP 14.0.6"
echo " Xcode 13.3-13.4.1 → OpenMP 13.0.0"
echo " Xcode 13.0-13.2.1 → OpenMP 12.0.1"
echo " Xcode 12.5 → OpenMP 11.0.1"
echo " Xcode 12.0-12.4 → OpenMP 10.0.0"
echo " Xcode 11.4-11.7 → OpenMP 9.0.1"
echo " Xcode 11.0-11.3.1 → OpenMP 8.0.1"
echo " Xcode 10.2-10.3 → OpenMP 7.1.0"
# <<< END GENERATED HELP VERSIONS <<<
echo ""
echo "MORE INFO:"
echo " https://mac.r-project.org/openmp/"
Expand Down Expand Up @@ -140,10 +142,11 @@ EXPECTED_SHA1=""
BASE_URL="https://mac.r-project.org/openmp"

case $CLANG_VERSION in
# >>> BEGIN GENERATED VERSION CASES (managed by sync-openmp.sh; do not edit) >>>
1700)
OPENMP_VERSION="19.1.0"
OPENMP_VERSION="19.1.5"
DARWIN_TARGET="darwin20"
EXPECTED_SHA1="42a22fa5852bafc23ab31241d064f9be9aab8a0d"
EXPECTED_SHA1="5b44175bcbaa334b0c57391482e068ea185c95a2"
;;
1600)
OPENMP_VERSION="17.0.6"
Expand Down Expand Up @@ -200,22 +203,10 @@ case $CLANG_VERSION in
DARWIN_TARGET="darwin17"
EXPECTED_SHA1="6891ff6f83f2ed83eeed42160de819b50cf643cd"
;;
# <<< END GENERATED VERSION CASES <<<
*)
echo -e "${RED}Error: Unsupported clang version $CLANG_VERSION${NC}"
echo "Supported versions and their corresponding OpenMP builds:"
echo " 1700 (Xcode 16.3+) → OpenMP 19.1.0"
echo " 1600 (Xcode 16.0-16.2) → OpenMP 17.0.6"
echo " 1500 (Xcode 15.x) → OpenMP 16.0.4"
echo " 1403 (Xcode 14.3.x) → OpenMP 15.0.7"
echo " 1400 (Xcode 14.0-14.2) → OpenMP 14.0.6"
echo " 1316 (Xcode 13.3-13.4.1) → OpenMP 13.0.0"
echo " 1300 (Xcode 13.0-13.2.1) → OpenMP 12.0.1"
echo " 1205 (Xcode 12.5) → OpenMP 11.0.1"
echo " 1200 (Xcode 12.0-12.4) → OpenMP 10.0.0"
echo " 1103 (Xcode 11.4-11.7) → OpenMP 9.0.1"
echo " 1100 (Xcode 11.0-11.3.1) → OpenMP 8.0.1"
echo " 1001 (Xcode 10.2-10.3) → OpenMP 7.1.0"
echo ""
echo "Run '$0 --help' to see the supported Xcode/clang/OpenMP versions."
echo "Please check https://mac.r-project.org/openmp/ for updates."
exit 1
;;
Expand Down
197 changes: 197 additions & 0 deletions sync-openmp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#!/usr/bin/env bash

# OpenMP Setup - Automatic OpenMP installer for macOS
# Copyright (C) 2025: James J Balamuta
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

set -euo pipefail

BASE_URL="https://mac.r-project.org/openmp"

# parse_page <html_file>
# Emits one record per *primary* tier: clang<TAB>version<TAB>darwin<TAB>sha1<TAB>xcode
parse_page() {
local html_file="$1"
# Strip HTML comments (defensive: drops the commented git build), then put one <tr> per line.
perl -0pe 's/<!--.*?-->//gs' "$html_file" \
| tr '\n' ' ' | sed 's/<tr/\n<tr/Ig' \
| while IFS= read -r row || [ -n "$row" ]; do
case "$row" in
"<tr"*"Apple clang"*) : ;;
*) continue ;;
esac
local clang llvm rel ver darwin sha xcode
clang=$(printf '%s' "$row" | grep -oiE 'Apple clang [0-9]+' | head -1 | grep -oE '[0-9]+')
llvm=$(printf '%s' "$row" | grep -oE 'LLVM [0-9]+\.[0-9]+\.[0-9]+' | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
rel=$(printf '%s' "$row" | grep -oE 'openmp-[0-9]+\.[0-9]+\.[0-9]+-darwin[0-9]+-Release\.tar\.gz' | head -1)
if [ -z "$rel" ]; then echo "WARN: clang ${clang:-?} has no Release tarball; skipping" >&2; continue; fi
ver=$(printf '%s' "$rel" | sed -E 's/^openmp-([0-9.]+)-darwin.*/\1/')
darwin=$(printf '%s' "$rel" | grep -oE 'darwin[0-9]+')
sha=$(printf '%s' "$row" | grep -oiE '[0-9a-f]{40}' | head -1)
if [ -z "$sha" ]; then echo "WARN: clang ${clang:-?} has no SHA1; skipping" >&2; continue; fi
xcode=$(printf '%s' "$row" | grep -oE 'Xcode [^(<]+' | head -1 | sed 's/[[:space:]]*$//')
if [ "$ver" != "$llvm" ]; then echo "WARN: clang $clang Release version ($ver) != LLVM heading ($llvm)" >&2; fi
printf '%s\t%s\t%s\t%s\t%s\n' "$clang" "$ver" "$darwin" "$sha" "$xcode"
done | sort -t"$(printf '\t')" -k1,1nr
}

# All renderers read records (clang ver darwin sha xcode) from stdin.
render_case() {
while IFS=$'\t' read -r clang ver darwin sha xcode; do
printf ' %s)\n OPENMP_VERSION="%s"\n DARWIN_TARGET="%s"\n EXPECTED_SHA1="%s"\n ;;\n' \
"$clang" "$ver" "$darwin" "$sha"
done
}

render_help() {
while IFS=$'\t' read -r clang ver darwin sha xcode; do
printf ' echo " %s → OpenMP %s"\n' "$xcode" "$ver"
done
}

render_readme() {
while IFS=$'\t' read -r clang ver darwin sha xcode; do
local short="${xcode#Xcode }"
local file="openmp-${ver}-${darwin}-Release.tar.gz"
printf '| %s | %s.x | %s | [%s](%s/%s) |\n' "$short" "$clang" "$ver" "$file" "$BASE_URL" "$file"
done
}

# current_pin <clang> <install_file> -> "version<TAB>sha1" or empty
current_pin() {
local clang="$1" file="$2"
awk -v key=" ${clang})" '
$0==key {inblock=1; next}
inblock && /OPENMP_VERSION=/ {v=$0; sub(/.*OPENMP_VERSION="/,"",v); sub(/".*/,"",v)}
inblock && /EXPECTED_SHA1=/ {s=$0; sub(/.*EXPECTED_SHA1="/,"",s); sub(/".*/,"",s)}
inblock && /;;/ {print v"\t"s; exit}
' "$file"
}

sha1_of() {
if command -v shasum >/dev/null 2>&1; then shasum -a 1 "$1" | cut -d' ' -f1
else sha1sum "$1" | cut -d' ' -f1; fi
}

# resolve_sha <ver> <darwin> <page_sha>
resolve_sha() {
local ver="$1" darwin="$2" page_sha="$3"
if [ "${OPENMP_SYNC_TRUST_PAGE:-0}" = "1" ]; then printf '%s' "$page_sha"; return 0; fi
local url="$BASE_URL/openmp-${ver}-${darwin}-Release.tar.gz" tmp got
tmp=$(mktemp)
if ! curl -fsS -o "$tmp" "$url"; then echo "ERROR: download failed: $url" >&2; rm -f "$tmp"; return 1; fi
got=$(sha1_of "$tmp"); rm -f "$tmp"
if [ -n "$page_sha" ] && [ "$got" != "$page_sha" ]; then
echo "WARN: computed SHA1 ($got) != page SHA1 ($page_sha) for $ver" >&2
fi
printf '%s' "$got"
}

# replace_block <file> <begin_substr> <end_substr> <content_file>
replace_block() {
local file="$1" begin="$2" end="$3" content="$4" tmp
local nb ne
nb=$(grep -cF -- "$begin" "$file") || true
ne=$(grep -cF -- "$end" "$file") || true
if [ "$nb" != 1 ] || [ "$ne" != 1 ]; then
echo "ERROR: replace_block: markers not found exactly once in $file (begin=$nb end=$ne)" >&2
return 1
fi
local mode
mode=$(stat -f '%Lp' "$file" 2>/dev/null || stat -c '%a' "$file")
tmp=$(mktemp)
awk -v b="$begin" -v e="$end" -v cf="$content" '
index($0,b){print; while((getline line < cf)>0) print line; close(cf); skip=1; next}
index($0,e){skip=0; print; next}
!skip{print}
' "$file" > "$tmp" || { rm -f "$tmp"; return 1; }
chmod "$mode" "$tmp" && mv "$tmp" "$file"
}

# Build resolved records (compute-on-change) and the per-tier summary.
# Writes resolved records to stdout; appends summary lines to the file named by $1.
resolve_records() {
local summary_file="$1" install="$2" page_records="$3"
while IFS=$'\t' read -r clang ver darwin sha_page xcode; do
local pin cur_ver cur_sha sha
pin=$(current_pin "$clang" "$install")
cur_ver=$(printf '%s' "$pin" | cut -f1); cur_sha=$(printf '%s' "$pin" | cut -f2)
if [ "$cur_ver" = "$ver" ] && [ -n "$cur_sha" ]; then
sha="$cur_sha"
else
sha=$(resolve_sha "$ver" "$darwin" "$sha_page") || return 1
if [ -z "$cur_ver" ]; then echo "NEW $clang: $ver" >> "$summary_file"
else echo "UPDATED $clang: $cur_ver -> $ver" >> "$summary_file"; fi
fi
printf '%s\t%s\t%s\t%s\t%s\n' "$clang" "$ver" "$darwin" "$sha" "$xcode"
done <<< "$page_records"
}

main() {
local check=0
[ "${1:-}" = "--check" ] && check=1

local script_dir install readme page records summary
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
install="$script_dir/install-openmp.sh"
readme="$script_dir/README.md"

page=$(mktemp)
if [ -n "${OPENMP_SYNC_PAGE:-}" ]; then cp "$OPENMP_SYNC_PAGE" "$page"
else curl -fsS "$BASE_URL/" -o "$page" || { echo "ERROR: cannot fetch $BASE_URL/" >&2; exit 1; }; fi

records=$(parse_page "$page")
rm -f "$page"
[ -n "$records" ] || { echo "ERROR: parsed zero records (page structure changed?)" >&2; exit 1; }

summary=$(mktemp)
local resolved
resolved=$(resolve_records "$summary" "$install" "$records") || { echo "ERROR: SHA resolution failed" >&2; exit 1; }

# Render blocks into temp files.
local f_case f_help f_readme
f_case=$(mktemp); f_help=$(mktemp); f_readme=$(mktemp)
printf '%s\n' "$resolved" | render_case > "$f_case"
printf '%s\n' "$resolved" | render_help > "$f_help"
printf '%s\n' "$resolved" | render_readme > "$f_readme"

if [ "$check" = 1 ]; then
# Apply to copies and diff; write nothing to the real files.
local ci cr; ci=$(mktemp); cr=$(mktemp); cp "$install" "$ci"; cp "$readme" "$cr"
replace_block "$ci" "BEGIN GENERATED VERSION CASES" "END GENERATED VERSION CASES" "$f_case"
replace_block "$ci" "BEGIN GENERATED HELP VERSIONS" "END GENERATED HELP VERSIONS" "$f_help"
replace_block "$cr" "BEGIN GENERATED README TABLE" "END GENERATED README TABLE" "$f_readme"
local rc=0
diff -q "$ci" "$install" >/dev/null || rc=1
diff -q "$cr" "$readme" >/dev/null || rc=1
rm -f "$f_case" "$f_help" "$f_readme" "$summary" "$ci" "$cr"
[ "$rc" = 0 ] && echo "up to date" || echo "DRIFT: run sync-openmp.sh"
exit "$rc"
fi

replace_block "$install" "BEGIN GENERATED VERSION CASES" "END GENERATED VERSION CASES" "$f_case"
replace_block "$install" "BEGIN GENERATED HELP VERSIONS" "END GENERATED HELP VERSIONS" "$f_help"
replace_block "$readme" "BEGIN GENERATED README TABLE" "END GENERATED README TABLE" "$f_readme"
rm -f "$f_case" "$f_help" "$f_readme"

echo "Sync complete."
if [ -s "$summary" ]; then cat "$summary"; else echo "No version changes."; fi
rm -f "$summary"
}

if [ "${OPENMP_SYNC_LIB:-}" != "1" ]; then
main "$@"
fi
Loading