mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-19 13:41:03 +00:00
A profile stores each custom G-code setting as one JSON string, so GitHub shows a G-code edit as one long changed line and reviewers cannot see what changed. Add a gcode-diff command to orca_profile_tool.py. For each changed profile file it decodes, on both sides, every *_gcode and multiline string option PrintConfig.cpp defines, the options Tab.cpp edits as code, and any string value that spans several lines. It prints one collapsed unified diff per setting as markdown. Files with the same diff for a setting share one section. Trailing spaces are removed, file names cannot mention GitHub users, and the output is cut to whole sections to fit a GitHub comment. Check profiles runs it between the PR's merge commit and its first parent and adds the result to its artifact, with a line naming the PR commit. Post profile check comment posts it under its own marker. It keeps the comment while the diff is unchanged, edits it in place when only the commit changed, replaces it when the diff changes, and deletes it when the PR no longer changes any G-code. The posting job still runs no PR code. It now posts only when the run checked the head of the PR the artifact names, which covers the validation comment too.
343 lines
15 KiB
YAML
343 lines
15 KiB
YAML
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/**'
|
|
- 'resources/info/legacy_compatible_printers.json'
|
|
- 'scripts/**'
|
|
# orca_profile_tool.py reads the variant key sets from PrintConfig.cpp, and its
|
|
# tests the obsolete keys, so a PR changing either must be checked against the profiles.
|
|
- 'src/libslic3r/PrintConfig.cpp'
|
|
- ".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 <vendor>.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]}
|
|
|
|
# The app migrates user filament presets made against 2.4.2 back to the printers their
|
|
# parent listed then, from resources/info/legacy_compatible_printers.json. A PR that
|
|
# renames a filament preset or narrows its compatible_printers changes what that map
|
|
# must say, so it has to regenerate it.
|
|
- name: Check the legacy compatible printers map
|
|
id: legacy_compat
|
|
continue-on-error: true
|
|
run: |
|
|
set -o pipefail
|
|
base="${{ runner.temp }}/v2.4.2"
|
|
git fetch --no-tags --depth=1 origin refs/tags/v2.4.2:refs/tags/v2.4.2
|
|
mkdir -p "$base"
|
|
git archive v2.4.2 resources/profiles | tar -x -C "$base"
|
|
python3 ./scripts/orca_profile_tool.py legacy-compat --base "$base/resources/profiles" --check 2>&1 | tee ${{ runner.temp }}/legacy_compat.log
|
|
|
|
# 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]}
|
|
# The validator above is the nightly build of main, so it cannot slice profiles that use
|
|
# settings a PR adds to the engine: it reports their placeholders as undefined. A PR that
|
|
# changes src/ also runs Build all, whose Slice check runs this same sweep with the
|
|
# validator built from the PR, so the sweep below only runs for the other PRs.
|
|
- name: Detect engine changes
|
|
id: engine_changes
|
|
if: ${{ github.event_name == 'pull_request' }}
|
|
run: |
|
|
base=${{ github.event.pull_request.base.sha }}
|
|
if git fetch --no-tags --depth=1 origin "$base" && ! git diff --quiet "$base" HEAD -- src/; then
|
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
echo "::notice::This PR changes src/, so Build all's Slice check slices the profiles with the PR-built validator."
|
|
fi
|
|
# Slice a two-colour cube through every printer, and through every system process/filament whose
|
|
# templates no printer's own slice reaches, so every custom g-code and filename_format shipped is
|
|
# expanded (names in {if} branches not taken included) - catches undefined-placeholder /
|
|
# invalid-flow bugs the static checks above cannot see.
|
|
- name: validate slice (expand custom g-code)
|
|
id: validate_slice
|
|
if: ${{ steps.engine_changes.outputs.changed != 'true' }}
|
|
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, 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}" != "<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
|
|
|
|
# Builds a readable diff of the custom G-code the PR changes, which GitHub shows as
|
|
# one long line per setting, for check_profiles_comment.yml to post.
|
|
- name: Diff custom G-code
|
|
if: ${{ always() && github.event_name == 'pull_request' }}
|
|
continue-on-error: true
|
|
env:
|
|
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
git fetch --no-tags --depth=2 origin "$(git rev-parse HEAD)"
|
|
results=${{ runner.temp }}/profile-check-results
|
|
diff=${{ runner.temp }}/gcode_diff.md
|
|
# HEAD is the PR's merge commit, so HEAD^1 is the base it merges onto.
|
|
python3 ./scripts/orca_profile_tool.py gcode-diff --base HEAD^1 --head HEAD > "$diff"
|
|
if [ -s "$diff" ]; then
|
|
{
|
|
echo "<!-- profile-gcode-diff-comment -->"
|
|
cat "$diff"
|
|
echo ""
|
|
# The comment workflow finds this line as the only one starting with <sub>.
|
|
echo "<sub>For commit ${PR_HEAD_SHA} of this PR.</sub>"
|
|
} > "$results/gcode_diff.md"
|
|
fi
|
|
# Tells the comment workflow that a missing gcode_diff.md means no G-code changed.
|
|
touch "$results/gcode_diff_done.txt"
|
|
|
|
- name: Prepare comment artifact
|
|
if: ${{ always() && github.event_name == 'pull_request' && (steps.profile_tool.outcome == 'failure' || steps.legacy_compat.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 "<!-- profile-validation-comment -->"
|
|
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.legacy_compat.outcome }}" = "failure" ]; then
|
|
echo "### Legacy Compatible Printers Map Out of Date"
|
|
echo ""
|
|
echo '```'
|
|
head -c 30000 ${{ runner.temp }}/legacy_compat.log || echo "No output captured"
|
|
echo '```'
|
|
echo ""
|
|
echo 'Regenerate it: `git archive v2.4.2 resources/profiles | tar -x -C /tmp/v2.4.2`, then `python3 scripts/orca_profile_tool.py legacy-compat --base /tmp/v2.4.2/resources/profiles`.'
|
|
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.legacy_compat.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
|