diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 436a2a8..944a7ee 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,7 @@ updates: - package-ecosystem: "github-actions" # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.) directory: "/" + target-branch: "admin" labels: - "github_actions" schedule: @@ -19,6 +20,7 @@ updates: - package-ecosystem: "gradle" directory: "/" # Location of package manifests registries: "*" + target-branch: "admin" labels: - "gradle dependencies" schedule: diff --git a/.github/workflows/admin-orchestrator.yaml b/.github/workflows/admin-orchestrator.yaml new file mode 100644 index 0000000..eb34da2 --- /dev/null +++ b/.github/workflows/admin-orchestrator.yaml @@ -0,0 +1,101 @@ +name: Admin branch orchestration + +on: + create: + + push: + branches: + - "main" + + schedule: + - cron: "0 3 * * 1" # Weekly, Monday 03:00 UTC + + workflow_dispatch: + + pull_request: + branches: + - admin + pull_request_review: + types: + - submitted + check_suite: + types: + - completed + +permissions: + contents: write + pull-requests: write + +jobs: + admin-orchestrator: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # ------------------------------------------------------------ + # Detect default branch + # ------------------------------------------------------------ + - name: Detect default branch + id: default + run: | + DEFAULT_BRANCH=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p') + echo "branch=$DEFAULT_BRANCH" >> "$GITHUB_OUTPUT" + + # ------------------------------------------------------------ + # Ensure admin branch exists + # ------------------------------------------------------------ + - name: Ensure admin branch exists + run: | + if git show-ref --verify --quiet refs/remotes/origin/admin; then + echo "admin branch already exists" + else + git checkout "${{ steps.default.outputs.branch }}" + git checkout -b admin + git push origin admin + fi + + # ------------------------------------------------------------ + # Periodically rebase admin onto default (true rebase) + # ------------------------------------------------------------ + - name: Rebase admin onto default + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + run: | + set -e + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git fetch origin + git checkout admin + + # Rebase admin commits on top of default branch + git rebase origin/${{ steps.default.outputs.branch }} + + # Push updated admin branch + git push --force-with-lease origin admin + + # ------------------------------------------------------------ + # Guardrail: warn if non-Dependabot PR targets admin + # (no hard failure without branch protection) + # ------------------------------------------------------------ + - name: Warn on non-Dependabot PRs + if: github.event_name == 'pull_request' + run: | + if [[ "${{ github.actor }}" != "dependabot[bot]" ]]; then + echo "::warning::PR to admin opened by non-Dependabot actor" + fi + + # ------------------------------------------------------------ + # Auto-merge Dependabot PRs + # ------------------------------------------------------------ + - name: Auto-merge Dependabot PR + if: | + github.event_name == 'pull_request' && + github.event.pull_request.user.login == 'dependabot[bot]' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 23362e2..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Build - -on: - push: - pull_request: - -jobs: - build: - name: Build - runs-on: ubuntu-latest - container: wpilib/roborio-cross-ubuntu:2024-22.04 - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - name: Grant execute permission - run: chmod +x gradlew - - name: Build robot code - run: ./gradlew build diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c83fbb6 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + pull_request: + +jobs: + # ------------------------------------------------------------ + # Main build job + # ------------------------------------------------------------ + build: + runs-on: ubuntu-latest + container: wpilib/roborio-cross-ubuntu:2024-22.04 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request == null }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Mark repo as safe for git + run: git config --global --add safe.directory $GITHUB_WORKSPACE + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Set up Gradle cache + uses: gradle/actions/setup-gradle@v4 + + - name: Build robot code + run: ./gradlew build --no-daemon --parallel --build-cache + + # ------------------------------------------------------------ + # Spotless job (separate for branch protection) + # ------------------------------------------------------------ + spotless: + runs-on: ubuntu-latest + container: wpilib/roborio-cross-ubuntu:2024-22.04 + # Only run for PRs or pushes targeting main/develop + if: github.base_ref == 'main' || github.base_ref == 'develop' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Set up Gradle cache + uses: gradle/actions/setup-gradle@v4 + + - name: Run Spotless check + run: ./gradlew spotlessCheck --no-daemon --parallel --build-cache diff --git a/.github/workflows/create-dependabot-labels.yaml b/.github/workflows/create-dependabot-labels.yaml new file mode 100644 index 0000000..d2795e5 --- /dev/null +++ b/.github/workflows/create-dependabot-labels.yaml @@ -0,0 +1,47 @@ +name: "Ensure Dependabot Labels Exist" + +on: + push: + branches: [main] + paths: + - '.github/dependabot.yml' + workflow_dispatch: + +jobs: + create-labels: + runs-on: ubuntu-latest + permissions: + issues: write # Needed to create labels via API + steps: + - name: Create labels for Dependabot PRs + uses: actions/github-script@v8 + with: + script: | + const labels = [ + { name: "github_actions", color: "BFFFD1", description: "Updates to GitHub Actions workflows" }, + { name: "gradle dependencies", color: "02303A", description: "Gradle dependency updates" }, + ]; + + for (const label of labels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + }); + core.info(`✅ Label '${label.name}' already exists.`); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label.name, + color: label.color, + description: label.description, + }); + core.info(`🎨 Created label '${label.name}'.`); + } else { + throw error; + } + } + } diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index 3513795..16d9455 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -1,5 +1,9 @@ name: Dependency Submission +# Purpose: Generate and submit Gradle dependency metadata (graph) to Gradle’s services. +# Trigger: Push to main branch only. +# Effect: Helps with dependency insight, conflict detection, and build analytics. + on: push: branches: [ 'main' ] @@ -12,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Java uses: actions/setup-java@v5 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index e89a56f..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This is a basic workflow to build robot code. - -name: CI - -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the main branch. -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a job called "build" which is the main build - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # This grabs the WPILib docker container - container: wpilib/roborio-cross-ubuntu:2024-22.04 - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v5 - - # Declares the repository safe and not under dubious ownership. - - name: Add repository to git safe directories - run: git config --global --add safe.directory $GITHUB_WORKSPACE - - # Grant execute permission for gradlew - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - # Runs a single command using the runners shell - - name: Compile and run tests on robot code - run: ./gradlew build - - # This workflow contains a job called "build" which is the main build - spotless: - # The type of runner that the job will run on - runs-on: ubuntu-latest - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - uses: actions/setup-java@v5 - with: - distribution: 'zulu' - java-version: 17 - - run: ./gradlew spotlessCheck \ No newline at end of file diff --git a/.github/workflows/record_template_origin.yaml b/.github/workflows/record-template-origin.yaml similarity index 86% rename from .github/workflows/record_template_origin.yaml rename to .github/workflows/record-template-origin.yaml index 4fdbd01..f4133b0 100644 --- a/.github/workflows/record_template_origin.yaml +++ b/.github/workflows/record-template-origin.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -64,9 +64,14 @@ jobs: - name: Record template origin file run: | - echo "Template: ${{ steps.template.outputs.template_repo }}" > TEMPLATE_ORIGIN.txt - echo "Template Branch: ${{ steps.template.outputs.template_branch }}" >> TEMPLATE_ORIGIN.txt - echo "Template Commit: ${{ steps.template.outputs.template_commit }}" >> TEMPLATE_ORIGIN.txt + timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + { + echo "Template: ${{ steps.template.outputs.template_repo }}" + echo "Template Branch: ${{ steps.template.outputs.template_branch }}" + echo "Template Commit: ${{ steps.template.outputs.template_commit }}" + echo "Recorded At (UTC): $timestamp" + } > TEMPLATE_ORIGIN.txt + echo "Recorded template origin:" cat TEMPLATE_ORIGIN.txt diff --git a/.github/workflows/sync-template-updates.yaml b/.github/workflows/sync-template-updates.yaml index 93db06e..c7df71f 100644 --- a/.github/workflows/sync-template-updates.yaml +++ b/.github/workflows/sync-template-updates.yaml @@ -1,8 +1,11 @@ -name: Sync Template Updates +name: "Sync with Az-RBSI Template" on: + # cronjob trigger schedule: - - cron: '0 0 * * *' # Runs daily at midnight UTC + # Every Monday at 4:30AM + - cron: "30 04 * * 1" + # manual trigger workflow_dispatch: permissions: @@ -12,13 +15,21 @@ permissions: jobs: sync-template: runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: + # ------------------------------- + # Step 1: Checkout repository + # ------------------------------- - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: - fetch-depth: 0 + fetch-depth: 0 # Full history needed for cherry-picking + # ------------------------------- + # Step 2: Read template origin info + # ------------------------------- - name: Read template origin id: template run: | @@ -28,68 +39,185 @@ jobs: exit 0 fi - template_repo=$(sed -n 's/^Template: //p' TEMPLATE_ORIGIN.txt) - template_branch=$(sed -n 's/^Template Branch: //p' TEMPLATE_ORIGIN.txt) - template_commit=$(sed -n 's/^Template Commit: //p' TEMPLATE_ORIGIN.txt) + template_repo=$(sed -n 's/^Template: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]') + last_applied_commit=$(sed -n 's/^Template Commit: //p' TEMPLATE_ORIGIN.txt | tr -d '[:space:]') - echo "Template repo: $template_repo" - echo "Template branch: $template_branch" - echo "Template commit: $template_commit" - - # Skip if placeholder values - if [ "$template_repo" = "none" ] || [ "$template_branch" = "none" ] || [ "$template_commit" = "none" ]; then - echo "Repository was not created from a template. Skipping sync." + if [ "$template_repo" = "none" ] || [ "$last_applied_commit" = "none" ]; then + echo "Repository is not from a template. Skipping sync." echo "skip_sync=true" >> $GITHUB_OUTPUT exit 0 fi + # Get default branch of template repo dynamically + template_branch=$(gh repo view "$template_repo" --json defaultBranchRef --jq '.defaultBranchRef.name') echo "skip_sync=false" >> $GITHUB_OUTPUT echo "template_repo=$template_repo" >> $GITHUB_OUTPUT echo "template_branch=$template_branch" >> $GITHUB_OUTPUT - echo "template_commit=$template_commit" >> $GITHUB_OUTPUT + echo "last_applied_commit=$last_applied_commit" >> $GITHUB_OUTPUT + # ------------------------------- + # Step 3: Install GitHub CLI and jq + # ------------------------------- - name: Install GitHub CLI if: steps.template.outputs.skip_sync == 'false' - run: sudo apt-get install -y gh jq + run: sudo apt-get update && sudo apt-get install -y gh jq - - name: Fetch commits from template since last sync + # ------------------------------- + # Step 4: Add template repo as remote and fetch commits + # ------------------------------- + - name: Add template repo as remote and fetch if: steps.template.outputs.skip_sync == 'false' - id: commits run: | - # Get the list of commits from template repo that are not yet in this repo - commits=$(gh api repos/${{ steps.template.outputs.template_repo }}/commits --jq '.[] | .sha' | tac) - echo "$commits" > template_commits.txt - echo "Commits to consider:" - cat template_commits.txt - echo "commits=$(cat template_commits.txt)" >> $GITHUB_OUTPUT - - - name: Cherry-pick commits and create PR + git remote add template_repo https://github.com/${{ steps.template.outputs.template_repo }}.git + git fetch template_repo ${{ steps.template.outputs.template_branch }} + + # ------------------------------- + # Step 5: Get new commits from template (only non-merge) + # ------------------------------- + - name: Get new commits from template if: steps.template.outputs.skip_sync == 'false' + id: commits + run: | + last_commit=${{ steps.template.outputs.last_applied_commit }} + branch=${{ steps.template.outputs.template_branch }} + + # List commits after last applied commit, oldest first + all_commits=$(git rev-list --reverse ${last_commit}..template_repo/${branch} || true) + all_commits=$(echo "$all_commits" | grep -v "^$last_commit$" || true) + + # Filter out merge commits (those with >1 parent) + non_merge_commits="" + for sha in $all_commits; do + parent_count=$(git rev-list --parents -n 1 "$sha" | wc -w) + if [ "$parent_count" -le 2 ]; then + non_merge_commits+="$sha"$'\n' + fi + done + + non_merge_commits=$(echo "$non_merge_commits" | grep -v '^$' || true) + commit_count=$(echo "$non_merge_commits" | grep -c . || true) + + echo "commit_count=$commit_count" >> $GITHUB_OUTPUT + + if [ -z "$non_merge_commits" ]; then + echo "No new template commits to cherry-pick." + else + echo "Non-merge commits to cherry-pick:" + echo "$non_merge_commits" + fi + + echo "new_commits<> $GITHUB_OUTPUT + echo "$non_merge_commits" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # ------------------------------- + # Step 6: Cherry-pick commits, squash, and create/update PR + # ------------------------------- + - name: Cherry-pick commits, squash, and create/update PR + if: steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits != '' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + set -e git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - branch_name="template-sync-$(date +%Y%m%d%H%M%S)" - git checkout -b "$branch_name" - pr_message="Template Sync Commit Summary:\n\n" + branch_name="template-sync" + target_branch=$(gh repo view $GITHUB_REPOSITORY --json defaultBranchRef --jq '.defaultBranchRef.name') + sync_date=$(date -u +"%Y-%m-%d") + timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + # Ensure branch exists or create new + if git ls-remote --exit-code --heads origin "$branch_name"; then + git fetch origin "$branch_name" + git checkout "$branch_name" + git pull origin "$branch_name" + else + git checkout -b "$branch_name" origin/"$target_branch" || git checkout -b "$branch_name" + fi + + # Reset to target branch to avoid conflicts + git fetch origin "$target_branch" + git reset --hard origin/"$target_branch" - while read commit_sha; do - # Skip empty lines + # Initialize commit summary + commit_summary="" + newest_commit="" + commit_list=$(echo "${{ steps.commits.outputs.new_commits }}" | tr -d '\r') + + # Cherry-pick each commit safely + while IFS= read -r commit_sha; do [ -z "$commit_sha" ] && continue - echo "Cherry-picking $commit_sha" - git cherry-pick "$commit_sha" || git cherry-pick --abort - pr_message="$pr_message- $commit_sha\n" - done < template_commits.txt - - git push origin "$branch_name" - - # Create PR - gh pr create \ - --title "Sync Template Updates" \ - --body "$pr_message" \ - --base main \ - --head "$branch_name" \ - --label "template-sync" + + short_sha=$(git rev-parse --short=7 "$commit_sha") + commit_msg=$(git log -1 --pretty=%B "$commit_sha") + first_line=${commit_msg%%$'\n'*} + + # Add only once to the summary + commit_summary+="- $short_sha: $first_line"$'\n' + + # Cherry-pick without committing yet + git cherry-pick --no-commit "$commit_sha" + newest_commit="$commit_sha" + done <<< "$commit_list" + + # Squash all cherry-picked commits into a single commit + git commit -m "Template Sync Updates"$'\n\n'"$commit_summary" + + # Update TEMPLATE_ORIGIN.txt (with new Recorded At) + if [ -n "$newest_commit" ]; then + { + echo "Template: ${{ steps.template.outputs.template_repo }}" + echo "Template Branch: ${{ steps.template.outputs.template_branch }}" + echo "Template Commit: $newest_commit" + echo "Recorded At (UTC): $timestamp" + } > TEMPLATE_ORIGIN.txt + git add TEMPLATE_ORIGIN.txt + git commit --amend --no-edit + fi + + # Push changes + git push origin "$branch_name" -f + + # Ensure template-sync label exists + if ! gh label list | grep -q "^template-sync"; then + gh label create template-sync --color BC8F8F --description "Updates synced from template repository" + fi + + # Build PR title with date and non-merge commit count + pr_count="${{ steps.commits.outputs.commit_count }}" + [ -z "$pr_count" ] && pr_count=0 + plural="s" + [ "$pr_count" -eq 1 ] && plural="" + pr_title="Sync Template Updates ($pr_count commit$plural, $sync_date)" + + # Build PR body with summary + source info + pr_body=$(printf "Template Sync Commit Summary:\n\n%s\n_Synced from:_ [%s](https://github.com/%s/tree/%s) at commit \`%s\`\n\n_Last recorded at (UTC): %s_" \ + "$commit_summary" \ + "${{ steps.template.outputs.template_repo }}" \ + "${{ steps.template.outputs.template_repo }}" \ + "${{ steps.template.outputs.template_branch }}" \ + "$newest_commit" \ + "$timestamp") + + existing_pr=$(gh pr list --head "$branch_name" --state open --json number --jq '.[0].number') + + if [ -n "$existing_pr" ]; then + echo "Updating existing PR #$existing_pr" + printf "%s" "$pr_body" | gh pr edit "$existing_pr" --title "$pr_title" --body-file - --add-label "template-sync" + else + echo "Creating a new PR" + printf "%s" "$pr_body" | gh pr create \ + --title "$pr_title" \ + --body-file - \ + --base "$target_branch" \ + --head "$branch_name" \ + --label "template-sync" + fi + + # ------------------------------- + # Step 7: No new commits + # ------------------------------- + - name: No new commits + if: steps.template.outputs.skip_sync == 'false' && steps.commits.outputs.new_commits == '' + run: echo "No new template commits to sync. Exiting." diff --git a/.github/workflows/template-sync.yml b/.github/workflows/template-sync.yml deleted file mode 100644 index e07dae0..0000000 --- a/.github/workflows/template-sync.yml +++ /dev/null @@ -1,32 +0,0 @@ -# File: .github/workflows/template-sync.yml -name: "Sync with Az-RBSI Template" - -on: - # cronjob trigger - # schedule: - # - cron: "30 04 * * 1" - # manual trigger - workflow_dispatch: -jobs: - repo-sync: - runs-on: ubuntu-latest - # https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs - permissions: - contents: write - pull-requests: write - - steps: - # To use this repository's private action, you must check out the repository - - name: Checkout - uses: actions/checkout@v5 - - - name: actions-template-sync - uses: AZ-First/actions-template-sync@v1 - with: - # github_token: ${{ secrets.GITHUB_TOKEN }} - source_repo_path: AZ-First/Az-RBSI - pr_title: "[bot] Update Robot Code with latest version of Az-RBSI" - pr_body: "Automated PR to synchronize this repository's code with the latest version of [Az-RBSI](${SOURCE_REPO}).\nThe target commit in the template repository is [${TEMPLATE_GIT_HASH}](https://github.com/AZ-First/Az-RBSI/commit/${TEMPLATE_GIT_HASH}).\nSee the [Az-RBSI Releases page](https://github.com/AZ-First/Az-RBSI/releases) for more information." - pr_commit_msg: "Update to the latest version of Az-RBSI" - pr_branch_name_prefix: "Az-RBSI_template_sync" - is_pr_cleanup: true diff --git a/src/main/java/frc/robot/generated/TunerConstants.java b/src/main/java/frc/robot/generated/TunerConstants.java index 929773a..46c54d7 100755 --- a/src/main/java/frc/robot/generated/TunerConstants.java +++ b/src/main/java/frc/robot/generated/TunerConstants.java @@ -291,9 +291,9 @@ public TunerSwerveDrivetrain( * @param odometryUpdateFrequency The frequency to run the odometry loop. If unspecified or set * to 0 Hz, this is 250 Hz on CAN FD, and 100 Hz on CAN 2.0. * @param odometryStandardDeviation The standard deviation for odometry calculation in the form - * [x, y, theta]ᵀ, with units in meters and radians + * [x, y, theta]T, with units in meters and radians * @param visionStandardDeviation The standard deviation for vision calculation in the form [x, - * y, theta]ᵀ, with units in meters and radians + * y, theta]T, with units in meters and radians * @param modules Constants for each specific module */ public TunerSwerveDrivetrain( diff --git a/vendordeps/maple-sim.json b/vendordeps/maple-sim.json index 0dcb2a0..65f4413 100644 --- a/vendordeps/maple-sim.json +++ b/vendordeps/maple-sim.json @@ -1,7 +1,7 @@ { "fileName": "maple-sim.json", "name": "maplesim", - "version": "0.3.13", + "version": "0.3.14", "frcYear": "2025", "uuid": "c39481e8-4a63-4a4c-9df6-48d91e4da37b", "mavenUrls": [ @@ -13,7 +13,7 @@ { "groupId": "org.ironmaple", "artifactId": "maplesim-java", - "version": "0.3.13" + "version": "0.3.14" }, { "groupId": "org.dyn4j",