name: Check profiles on: pull_request: # release/* is included because pr-merge-bot.yml lets delegates merge into # it, and it gates on this workflow's result. Without it a delegated merge # into a release branch would run no profile validation at all. branches: - main - release/* paths: - 'resources/profiles/**' # orca_profile_tool.py also validates resources/printers/bambu_filament_ids.json, and # both it and its tests live in scripts/, so a PR touching only those must still run # this workflow. - 'resources/printers/**' - 'scripts/**' - ".github/workflows/check_profiles.yml" workflow_dispatch: inputs: logLevel: description: 'Log level' required: true default: 'warning' permissions: contents: read jobs: check_profiles: # This job name is the check-run name pr-merge-bot.yml requires before a # delegated merge. Renaming it silently disables that gate. name: Check profiles runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v7 # Deliberately not continue-on-error, unlike every check below: if the tool itself is # broken, nothing it then reports about the profiles is worth reading. - name: Run the profile tool's own unit tests run: python3 -m unittest discover -s scripts/tests -t scripts # What the validator below cannot see. It loads the tree the way the slicer does, so # it never notices a profile no .json indexes, a preset name two files claim, # an id that is not the mint of its own triple, or a file that normalize and # update-index would still rewrite. # The step id is the handle the PR comment and the failure gate below use; renaming it # silently disables them. - name: Check profiles (orca_profile_tool.py) id: profile_tool continue-on-error: true run: | set +e python3 ./scripts/orca_profile_tool.py check 2>&1 | tee ${{ runner.temp }}/profile_tool.log exit ${PIPESTATUS[0]} # download - name: Download working-directory: ${{ github.workspace }} run: | curl -L -o OrcaSlicer_profile_validator https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/OrcaSlicer_profile_validator_Linux_Ubuntu2404_nightly chmod +x ./OrcaSlicer_profile_validator # Validate all system profiles. - name: validate system profiles id: validate_system continue-on-error: true run: | set +e ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log exit ${PIPESTATUS[0]} # Slice a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode) # is expanded - catches undefined-placeholder / invalid-flow bugs the static checks above cannot see. - name: validate slice (expand custom g-code) id: validate_slice continue-on-error: true run: | set +e ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -s -l 2 2>&1 | tee ${{ runner.temp }}/validate_slice.log exit ${PIPESTATUS[0]} # All vendors' filament_id collisions were fixed (see scripts/filament_id_snapshot.json), # so the duplicate-filament-subtype check runs tree-wide. - name: validate filament subtype check id: validate_filament_subtypes continue-on-error: true run: | set +e ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -f 2>&1 | tee ${{ runner.temp }}/validate_filament_subtypes.log exit ${PIPESTATUS[0]} - name: validate custom presets id: validate_custom continue-on-error: true working-directory: ${{ github.workspace }} run: | fixtures_dir="${{ runner.temp }}/profile-fixtures" output_dir="${{ runner.temp }}/custom-preset-validation" combined_log="${{ runner.temp }}/validate_custom.log" summary="${output_dir}/summary.md" release_url="https://github.com/OrcaSlicer/OrcaSlicer-profile-validator/releases/download/fixture-archive" rm -rf "${fixtures_dir}" "${output_dir}" mkdir -p "${fixtures_dir}" "${output_dir}" curl -fsSL -o "${fixtures_dir}/manifest.json" "${release_url}/manifest.json" MANIFEST_PATH="${fixtures_dir}/manifest.json" python3 <<'PY' > "${fixtures_dir}/fixtures.tsv" import json import os with open(os.environ["MANIFEST_PATH"], encoding="utf-8") as fh: manifest = json.load(fh) if isinstance(manifest, dict): entries = manifest.get("fixtures", []) else: entries = manifest for entry in entries: version = entry.get("version", "") asset = entry.get("asset", "") sha256 = entry.get("asset_sha256", "") if not version or not asset: continue print(f"{version}\t{asset}\t{sha256}") PY if [ ! -s "${fixtures_dir}/fixtures.tsv" ]; then echo "No custom preset fixtures found in ${release_url}/manifest.json" | tee "${combined_log}" exit 1 fi { echo "## Custom Preset Fixture Validation" echo "" echo "| Version | Status | Log |" echo "| --- | --- | --- |" } > "${summary}" status=0 failed_logs=() while IFS=$'\t' read -r version asset expected_sha256; do fixture_zip="${fixtures_dir}/${asset}" asset_url_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${asset}")" profile_tree="${output_dir}/profiles-${version}" log_path="${output_dir}/${version}.log" curl -fsSL -o "${fixture_zip}" "${release_url}/${asset_url_name}" if [ -n "${expected_sha256}" ] && [ "${expected_sha256}" != "" ]; then echo "${expected_sha256} ${fixture_zip}" | sha256sum -c - fi rm -rf "${profile_tree}" mkdir -p "${profile_tree}" cp -a "${{ github.workspace }}/resources/profiles/." "${profile_tree}/" rm -rf "${profile_tree}/user" unzip -q "${fixture_zip}" -d "${profile_tree}" set +e ./OrcaSlicer_profile_validator -p "${profile_tree}" -l 2 > "${log_path}" 2>&1 result=$? set -e if [ "${result}" -eq 0 ]; then echo "| ${version} | PASS | ${version}.log |" >> "${summary}" else echo "| ${version} | FAIL | ${version}.log |" >> "${summary}" failed_logs+=("${log_path}") status=1 fi done < "${fixtures_dir}/fixtures.tsv" { cat "${summary}" if [ "${#failed_logs[@]}" -gt 0 ]; then echo "" echo "## Failed Fixture Logs" for log_path in "${failed_logs[@]}"; do echo "" echo "### $(basename "${log_path}" .log)" echo '```' head -c 12000 "${log_path}" || echo "No output captured" echo '```' done fi } | tee "${combined_log}" exit "${status}" - name: Prepare PR number for comment workflow if: ${{ always() && github.event_name == 'pull_request' }} run: | mkdir -p ${{ runner.temp }}/profile-check-results echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt - name: Prepare comment artifact if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} run: | { # Marker matched by check_profiles_comment.yml to delete prior comments. echo "" echo "## :x: Profile Validation Errors" echo "" if [ "${{ steps.profile_tool.outcome }}" = "failure" ]; then echo "### Profile Check Failed (orca_profile_tool.py)" echo "" echo '```' head -c 30000 ${{ runner.temp }}/profile_tool.log || echo "No output captured" echo '```' echo "" fi if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then echo "### System Profile Validation Failed" echo "" echo '```' head -c 30000 ${{ runner.temp }}/validate_system.log || echo "No output captured" echo '```' echo "" fi if [ "${{ steps.validate_slice.outcome }}" = "failure" ]; then echo "### Slice Validation Failed (custom g-code expansion)" echo "" echo '```' head -c 30000 ${{ runner.temp }}/validate_slice.log || echo "No output captured" echo '```' echo "" fi if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then echo "### Filament Subtype Validation Failed" echo "" echo '```' head -c 30000 ${{ runner.temp }}/validate_filament_subtypes.log || echo "No output captured" echo '```' echo "" fi if [ "${{ steps.validate_custom.outcome }}" = "failure" ]; then echo "### Custom Preset Validation Failed" echo "" echo '```' head -c 30000 ${{ runner.temp }}/validate_custom.log || echo "No output captured" echo '```' echo "" fi echo "---" echo '*Fix the errors above and push a new commit. To reproduce this run locally: `scripts/check_profile.sh`, or `scripts\check_profile.bat` on Windows.*' } > ${{ runner.temp }}/profile-check-results/pr_comment.md - name: Upload comment artifact if: ${{ always() && github.event_name == 'pull_request' }} uses: actions/upload-artifact@v7 with: name: profile-check-results path: ${{ runner.temp }}/profile-check-results/ retention-days: 1 - name: Fail if any check failed if: ${{ always() && (steps.profile_tool.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} run: | echo "One or more profile checks failed; see the step logs above." echo 'Reproduce the whole run locally with scripts/check_profile.sh (scripts\check_profile.bat on Windows).' exit 1