Skip to content

Commit d5fe140

Browse files
committed
ocaml-ref-check: add script and CI
1 parent 847b0de commit d5fe140

File tree

2 files changed

+366
-0
lines changed

2 files changed

+366
-0
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
#!/usr/bin/env bash
2+
# Script to validate OCaml reference comments in Rust code
3+
# Usage: ./.github/scripts/check-ocaml-refs.sh [--repo REPO_URL] [--branch BRANCH] [--update]
4+
5+
set -euo pipefail
6+
7+
# Default configuration
8+
OCAML_REPO="${OCAML_REPO:-https://github.com/MinaProtocol/mina.git}"
9+
OCAML_BRANCH="${OCAML_BRANCH:-compatible}"
10+
UPDATE_MODE="${UPDATE_MODE:-false}"
11+
RUST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12+
13+
# Parse arguments
14+
while [[ $# -gt 0 ]]; do
15+
case $1 in
16+
--repo)
17+
OCAML_REPO="$2"
18+
shift 2
19+
;;
20+
--branch)
21+
OCAML_BRANCH="$2"
22+
shift 2
23+
;;
24+
--update)
25+
UPDATE_MODE="true"
26+
shift
27+
;;
28+
*)
29+
echo "Unknown option: $1"
30+
echo "Usage: ./.github/scripts/check-ocaml-refs.sh [--repo REPO_URL] [--branch BRANCH] [--update]"
31+
exit 1
32+
;;
33+
esac
34+
done
35+
36+
echo "Checking OCaml references against ${OCAML_REPO} (branch: ${OCAML_BRANCH})"
37+
38+
# Create temporary directory
39+
TEMP_DIR=$(mktemp -d)
40+
trap 'rm -rf "$TEMP_DIR"' EXIT
41+
42+
# Extract GitHub owner and repo from URL (e.g., https://github.com/MinaProtocol/mina.git)
43+
GITHUB_URL_PATTERN="https://github.com/([^/]+)/(.+)"
44+
if [[ "$OCAML_REPO" =~ $GITHUB_URL_PATTERN ]]; then
45+
GITHUB_OWNER="${BASH_REMATCH[1]}"
46+
GITHUB_REPO="${BASH_REMATCH[2]%.git}" # Remove .git suffix if present
47+
else
48+
echo "Error: Repository URL must be a GitHub URL"
49+
exit 1
50+
fi
51+
52+
# Get current commit hash for the branch using GitHub API
53+
echo "Fetching current commit from ${OCAML_BRANCH}..."
54+
CURRENT_COMMIT=$(curl -s "https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/commits/${OCAML_BRANCH}" | grep -o '"sha": "[^"]*"' | head -1 | cut -d'"' -f4)
55+
56+
if [ -z "$CURRENT_COMMIT" ]; then
57+
echo "Error: Could not fetch current commit for branch ${OCAML_BRANCH}"
58+
exit 1
59+
fi
60+
61+
echo "Current OCaml commit: ${CURRENT_COMMIT}"
62+
63+
# Find all Rust files with OCaml references
64+
cd "${RUST_ROOT}"
65+
RUST_FILES=$(rg -l "^/// OCaml reference:" --type rust || true)
66+
67+
if [ -z "$RUST_FILES" ]; then
68+
echo "No OCaml references found in Rust code"
69+
exit 0
70+
fi
71+
72+
# Use temporary files to accumulate results
73+
RESULTS_FILE="${TEMP_DIR}/results.txt"
74+
touch "$RESULTS_FILE"
75+
76+
echo ""
77+
echo "Validating references..."
78+
echo "========================"
79+
80+
# Process each file
81+
echo "$RUST_FILES" | while IFS= read -r rust_file; do
82+
# Extract OCaml reference comments from the file
83+
awk '
84+
/^\/\/\/ OCaml reference:/ {
85+
ref = $0
86+
getline
87+
if ($0 ~ /^\/\/\/ Commit:/) {
88+
commit = $0
89+
getline
90+
if ($0 ~ /^\/\/\/ Last verified:/) {
91+
verified = $0
92+
print ref
93+
print commit
94+
print verified
95+
print "---"
96+
}
97+
}
98+
}
99+
' "$rust_file" | while IFS= read -r line; do
100+
if [[ "$line" == "/// OCaml reference:"* ]]; then
101+
# Extract file path and line range
102+
# Format: src/lib/mina_base/transaction_status.ml L:9-113
103+
FULL_REF="${line#/// OCaml reference: }"
104+
OCAML_PATH="${FULL_REF%% L:*}"
105+
LINE_RANGE=$(echo "$FULL_REF" | grep -o 'L:[0-9-]*' | sed 's/L://' || echo "")
106+
107+
# Read next two lines
108+
read -r commit_line
109+
read -r _verified_line
110+
read -r _separator
111+
112+
COMMIT="${commit_line#/// Commit: }"
113+
# LAST_VERIFIED could be extracted from _verified_line if needed for future validation
114+
115+
# Fetch the OCaml file from the current branch
116+
CURRENT_FILE="${TEMP_DIR}/current_${rust_file//\//_}_${OCAML_PATH//\//_}"
117+
CURRENT_URL="https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${OCAML_BRANCH}/${OCAML_PATH}"
118+
119+
if ! curl -sf "$CURRENT_URL" -o "$CURRENT_FILE"; then
120+
echo "INVALID|${rust_file}|${OCAML_PATH}|FILE_NOT_FOUND" >> "$RESULTS_FILE"
121+
echo "❌ INVALID: ${rust_file}"
122+
echo " OCaml file not found: ${OCAML_PATH}"
123+
else
124+
# Validate line range if specified
125+
RANGE_VALID=true
126+
if [ -n "$LINE_RANGE" ]; then
127+
FILE_LINES=$(wc -l < "$CURRENT_FILE")
128+
# START_LINE is not currently used but could be useful for validation
129+
# START_LINE=$(echo "$LINE_RANGE" | cut -d'-' -f1)
130+
END_LINE=$(echo "$LINE_RANGE" | cut -d'-' -f2)
131+
132+
if [ "$END_LINE" -gt "$FILE_LINES" ]; then
133+
echo "INVALID|${rust_file}|${OCAML_PATH}|LINE_RANGE_EXCEEDED|L:${LINE_RANGE}|${FILE_LINES}" >> "$RESULTS_FILE"
134+
echo "❌ INVALID: ${rust_file}"
135+
echo " Line range L:${LINE_RANGE} exceeds file length (${FILE_LINES} lines): ${OCAML_PATH}"
136+
RANGE_VALID=false
137+
fi
138+
fi
139+
140+
if [ "$RANGE_VALID" = "true" ]; then
141+
# Verify that the code at the referenced commit matches the current branch
142+
CODE_MATCHES=true
143+
if [ -n "$LINE_RANGE" ]; then
144+
START_LINE=$(echo "$LINE_RANGE" | cut -d'-' -f1)
145+
END_LINE=$(echo "$LINE_RANGE" | cut -d'-' -f2)
146+
147+
# Fetch the file from the referenced commit
148+
COMMIT_FILE="${TEMP_DIR}/commit_${rust_file//\//_}_${OCAML_PATH//\//_}"
149+
COMMIT_URL="https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${COMMIT}/${OCAML_PATH}"
150+
151+
if ! curl -sf "$COMMIT_URL" -o "$COMMIT_FILE"; then
152+
echo "INVALID|${rust_file}|${OCAML_PATH}|COMMIT_NOT_FOUND|${COMMIT}" >> "$RESULTS_FILE"
153+
echo "❌ INVALID: ${rust_file}"
154+
echo " Referenced commit does not exist: ${COMMIT}"
155+
CODE_MATCHES=false
156+
else
157+
# Extract the specific line ranges from both files and compare
158+
CURRENT_LINES=$(sed -n "${START_LINE},${END_LINE}p" "$CURRENT_FILE")
159+
COMMIT_LINES=$(sed -n "${START_LINE},${END_LINE}p" "$COMMIT_FILE")
160+
161+
if [ "$CURRENT_LINES" != "$COMMIT_LINES" ]; then
162+
echo "INVALID|${rust_file}|${OCAML_PATH}|CODE_MISMATCH|${COMMIT}" >> "$RESULTS_FILE"
163+
echo "❌ INVALID: ${rust_file}"
164+
echo " Code at L:${LINE_RANGE} differs between commit ${COMMIT} and current branch"
165+
echo " Referenced: https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/blob/${COMMIT}/${OCAML_PATH}#L${START_LINE}-L${END_LINE}"
166+
echo " Current: https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/blob/${OCAML_BRANCH}/${OCAML_PATH}#L${START_LINE}-L${END_LINE}"
167+
CODE_MATCHES=false
168+
fi
169+
fi
170+
fi
171+
172+
if [ "$CODE_MATCHES" = "true" ]; then
173+
# Check if commit is stale
174+
if [ "$COMMIT" != "$CURRENT_COMMIT" ]; then
175+
echo "STALE|${rust_file}|${OCAML_PATH}|${COMMIT}|${LINE_RANGE}" >> "$RESULTS_FILE"
176+
echo "✓ VALID: ${rust_file} -> ${OCAML_PATH} L:${LINE_RANGE}"
177+
echo " ⚠ STALE COMMIT: ${COMMIT} (current: ${CURRENT_COMMIT})"
178+
else
179+
echo "VALID|${rust_file}|${OCAML_PATH}|${LINE_RANGE}" >> "$RESULTS_FILE"
180+
echo "✓ VALID: ${rust_file} -> ${OCAML_PATH} L:${LINE_RANGE}"
181+
fi
182+
fi
183+
fi
184+
fi
185+
fi
186+
done
187+
done
188+
189+
# Count results
190+
TOTAL_REFS=$(wc -l < "$RESULTS_FILE")
191+
VALID_REFS=$(grep -c "^VALID|" "$RESULTS_FILE" || true)
192+
INVALID_REFS=$(grep -c "^INVALID|" "$RESULTS_FILE" || true)
193+
STALE_COMMITS=$(grep -c "^STALE|" "$RESULTS_FILE" || true)
194+
195+
echo ""
196+
echo "Summary"
197+
echo "======="
198+
echo "Total references found: ${TOTAL_REFS}"
199+
echo "Valid references: $((VALID_REFS + STALE_COMMITS))"
200+
echo "Invalid references: ${INVALID_REFS}"
201+
echo "Stale commits: ${STALE_COMMITS}"
202+
203+
if [ "$UPDATE_MODE" = "true" ] && [ "${STALE_COMMITS}" -gt 0 ]; then
204+
echo ""
205+
echo "Updating stale commit hashes and verification dates..."
206+
207+
CURRENT_DATE=$(date +%Y-%m-%d)
208+
209+
# Update each file with stale commits
210+
grep "^STALE|" "$RESULTS_FILE" | while IFS='|' read -r _status rust_file ocaml_path _old_commit _line_range; do
211+
echo "Updating ${rust_file}..."
212+
213+
# Find and replace the old commit with the new one
214+
sed -i.bak \
215+
-e "/^\/\/\/ OCaml reference: ${ocaml_path//\//\\/}/,/^\/\/\/ Last verified:/ {
216+
s/^\/\/\/ Commit: .*/\/\/\/ Commit: ${CURRENT_COMMIT}/
217+
s/^\/\/\/ Last verified: .*/\/\/\/ Last verified: ${CURRENT_DATE}/
218+
}" \
219+
"${RUST_ROOT}/${rust_file}"
220+
rm -f "${RUST_ROOT}/${rust_file}.bak"
221+
done
222+
223+
echo "Updated ${STALE_COMMITS} reference(s)"
224+
fi
225+
226+
# Exit with error if there are invalid references
227+
if [ "${INVALID_REFS}" -gt 0 ]; then
228+
echo ""
229+
echo "❌ Validation failed: ${INVALID_REFS} invalid reference(s) found"
230+
exit 1
231+
fi
232+
233+
if [ "${STALE_COMMITS}" -gt 0 ] && [ "$UPDATE_MODE" = "false" ]; then
234+
echo ""
235+
echo "⚠ Warning: ${STALE_COMMITS} reference(s) have stale commits"
236+
echo "Run with --update to update them automatically"
237+
exit 0
238+
fi
239+
240+
echo ""
241+
echo "✓ All OCaml references are valid!"
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Check OCaml References
2+
#
3+
# This workflow validates OCaml reference comments in the Rust codebase
4+
# and automatically updates stale commit hashes.
5+
#
6+
# Run locally with:
7+
# gh act schedule -W .github/workflows/check-ocaml-refs.yaml
8+
# gh act workflow_dispatch -W .github/workflows/check-ocaml-refs.yaml
9+
# gh act workflow_dispatch -W .github/workflows/check-ocaml-refs.yaml --input branch=develop
10+
11+
name: Check OCaml References
12+
13+
on:
14+
schedule:
15+
# Run every Monday at 9:00 AM UTC
16+
- cron: '0 9 * * 1'
17+
workflow_dispatch:
18+
inputs:
19+
repo:
20+
description: 'OCaml repository URL'
21+
required: false
22+
default: 'https://github.com/MinaProtocol/mina.git'
23+
branch:
24+
description: 'OCaml repository branch'
25+
required: false
26+
default: 'compatible'
27+
28+
permissions:
29+
contents: write
30+
pull-requests: write
31+
32+
jobs:
33+
check-refs:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: Checkout repository
37+
uses: actions/checkout@v5
38+
39+
- name: Install ripgrep
40+
run: sudo apt-get update && sudo apt-get install -y ripgrep
41+
42+
- name: Run OCaml reference validation
43+
id: check
44+
env:
45+
OCAML_REPO: ${{ github.event.inputs.repo || 'https://github.com/MinaProtocol/mina.git' }}
46+
OCAML_BRANCH: ${{ github.event.inputs.branch || 'compatible' }}
47+
run: |
48+
set +e
49+
./.github/scripts/check-ocaml-refs.sh \
50+
--repo "$OCAML_REPO" \
51+
--branch "$OCAML_BRANCH"
52+
exit_code=$?
53+
if [ $exit_code -ne 0 ]; then
54+
echo "has_issues=true" >> $GITHUB_OUTPUT
55+
exit 0
56+
fi
57+
58+
- name: Update references if stale
59+
if: steps.check.outputs.has_issues != 'true'
60+
env:
61+
OCAML_REPO: ${{ github.event.inputs.repo || 'https://github.com/MinaProtocol/mina.git' }}
62+
OCAML_BRANCH: ${{ github.event.inputs.branch || 'compatible' }}
63+
run: |
64+
./.github/scripts/check-ocaml-refs.sh \
65+
--repo "$OCAML_REPO" \
66+
--branch "$OCAML_BRANCH" \
67+
--update
68+
69+
- name: Check for changes
70+
id: changes
71+
run: |
72+
if git diff --quiet; then
73+
echo "has_changes=false" >> $GITHUB_OUTPUT
74+
else
75+
echo "has_changes=true" >> $GITHUB_OUTPUT
76+
fi
77+
78+
- name: Create Pull Request
79+
if: steps.changes.outputs.has_changes == 'true'
80+
uses: peter-evans/create-pull-request@v6
81+
with:
82+
token: ${{ secrets.GITHUB_TOKEN }}
83+
commit-message: |
84+
Update OCaml reference verification dates
85+
86+
Automated update of OCaml reference commit hashes and verification
87+
dates based on latest compatible branch.
88+
branch: update-ocaml-refs-${{ github.run_number }}
89+
delete-branch: true
90+
title: 'Update OCaml reference verification dates'
91+
body: |
92+
## Automated OCaml Reference Update
93+
94+
This PR updates the OCaml reference comments in the Rust codebase to
95+
reflect the latest commit from the OCaml repository.
96+
97+
### Changes
98+
- Updated commit hashes to match latest OCaml compatible branch
99+
- Updated verification dates to today
100+
101+
### Validation
102+
All OCaml file references have been validated to ensure they still
103+
exist at the specified paths.
104+
105+
**Repository**: ${{ github.event.inputs.repo || 'https://github.com/MinaProtocol/mina.git' }}
106+
**Branch**: ${{ github.event.inputs.branch || 'compatible' }}
107+
108+
This PR was automatically generated by the `check-ocaml-refs`
109+
workflow.
110+
labels: |
111+
automation
112+
documentation
113+
114+
- name: Post summary
115+
if: always()
116+
run: |
117+
echo "## OCaml Reference Validation Summary" >> $GITHUB_STEP_SUMMARY
118+
echo "" >> $GITHUB_STEP_SUMMARY
119+
if [ "${{ steps.check.outputs.has_issues }}" == "true" ]; then
120+
echo "❌ Validation failed - some OCaml references are invalid" >> $GITHUB_STEP_SUMMARY
121+
elif [ "${{ steps.changes.outputs.has_changes }}" == "true" ]; then
122+
echo "✓ Validation passed - created PR to update stale references" >> $GITHUB_STEP_SUMMARY
123+
else
124+
echo "✓ All references are up to date" >> $GITHUB_STEP_SUMMARY
125+
fi

0 commit comments

Comments
 (0)