add per vendor support for check_profile check

This commit is contained in:
SoftFever
2026-09-02 18:50:50 +08:00
parent 7a0ca15df8
commit ab90aec001
3 changed files with 279 additions and 33 deletions

View File

@@ -1,5 +1,7 @@
@echo off @echo off
REM Runs check_profile.ps1, the Windows twin of check_profile.sh, from cmd. REM Runs check_profile.ps1, the Windows twin of check_profile.sh, from cmd.
REM Arguments are passed straight through, so anything the .ps1 takes works here:
REM scripts\check_profile.bat -Vendor Elegoo validate_custom
REM -ExecutionPolicy Bypass is needed because a Windows client defaults to Restricted, REM -ExecutionPolicy Bypass is needed because a Windows client defaults to Restricted,
REM which refuses to run a checked-out .ps1 at all. REM which refuses to run a checked-out .ps1 at all.
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0check_profile.ps1" %* powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0check_profile.ps1" %*

View File

@@ -23,6 +23,10 @@
has, is moved aside for the duration of the run and restored on exit. Only one run per work has, is moved aside for the duration of the run and restored on exit. Only one run per work
dir at a time. dir at a time.
-Vendor narrows a run to one vendor while working on that vendor's profiles - the one
deliberate divergence from CI, which always checks the whole tree. A check that cannot be
narrowed is left out of the run and reported as skipped.
x64 and ARM64 hosts are both supported. A locally built validator is chosen by the machine x64 and ARM64 hosts are both supported. A locally built validator is chosen by the machine
type in its PE header rather than by the name of its build tree, so build\ (x64) and type in its PE header rather than by the name of its build tree, so build\ (x64) and
build-arm64\ side by side resolve correctly; the published nightly is x64 only and runs build-arm64\ side by side resolve correctly; the published nightly is x64 only and runs
@@ -32,6 +36,14 @@
Profile tree to validate (default: resources\profiles). extra_json_check always looks at the Profile tree to validate (default: resources\profiles). extra_json_check always looks at the
tree next to the script, so this only redirects the validator checks. tree next to the script, so this only redirects the validator checks.
.PARAMETER Vendor
Check only this vendor, named after its <Vendor>.json (e.g. "Co Print"). validate_custom is
narrowed with it too, by keeping only that vendor's presets in each fixture tree. The one
check it cannot narrow is validate_slice for a vendor that ships no printers; the summary
reports that one as skipped, and naming it explicitly still runs it. extra_json_check keeps
its two cross-vendor checks (setting_id and filament_id) tree-wide, so a scoped run can still
fail on another vendor's files.
.PARAMETER Validator .PARAMETER Validator
OrcaSlicer_profile_validator.exe to use; also $env:ORCA_PROFILE_VALIDATOR. Default: the local OrcaSlicer_profile_validator.exe to use; also $env:ORCA_PROFILE_VALIDATOR. Default: the local
build*\ Release build (then RelWithDebInfo, MinSizeRel, Debug) for this architecture, else build*\ Release build (then RelWithDebInfo, MinSizeRel, Debug) for this architecture, else
@@ -58,6 +70,9 @@
.EXAMPLE .EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\check_profile.ps1 validate_system validate_slice powershell -ExecutionPolicy Bypass -File scripts\check_profile.ps1 validate_system validate_slice
.EXAMPLE
scripts\check_profile.bat -Vendor Elegoo
#> #>
# PositionalBinding is off so that the check names are the only positional arguments; left on, # PositionalBinding is off so that the check names are the only positional arguments; left on,
@@ -65,6 +80,7 @@
[CmdletBinding(PositionalBinding = $false)] [CmdletBinding(PositionalBinding = $false)]
param( param(
[Alias('p')] [string] $ProfilesDir, [Alias('p')] [string] $ProfilesDir,
[Alias('v')] [string] $Vendor,
[string] $Validator, [string] $Validator,
[string] $WorkDir, [string] $WorkDir,
[Alias('l')] [int] $LogLevel = 2, [Alias('l')] [int] $LogLevel = 2,
@@ -156,12 +172,42 @@ foreach ($name in $Checks) {
if ($name -like '-*') { Die "unknown option '$name' (try -Help)" } if ($name -like '-*') { Die "unknown option '$name' (try -Help)" }
if ($AllChecks -notcontains $name) { Die "unknown check '$name' (try -Help)" } if ($AllChecks -notcontains $name) { Die "unknown check '$name' (try -Help)" }
} }
# A check named on the command line always runs; only the default set is narrowed (see the run
# loop below).
$NamedChecks = [bool] $Checks
if (-not $Checks) { $Checks = $AllChecks } if (-not $Checks) { $Checks = $AllChecks }
if (-not $ProfilesDir) { $ProfilesDir = Join-Path $RepoRoot 'resources\profiles' } if (-not $ProfilesDir) { $ProfilesDir = Join-Path $RepoRoot 'resources\profiles' }
if (-not (Test-Path -LiteralPath $ProfilesDir -PathType Container)) { Die "profile directory not found: $ProfilesDir" } if (-not (Test-Path -LiteralPath $ProfilesDir -PathType Container)) { Die "profile directory not found: $ProfilesDir" }
$ProfilesDir = (Resolve-Path -LiteralPath $ProfilesDir).Path $ProfilesDir = (Resolve-Path -LiteralPath $ProfilesDir).Path
# A vendor neither tool knows is not an error to them: the static checks load nothing of their own
# and still report success, so a typo would otherwise be three green checks and one baffling slice
# failure. The match has to be made on the names themselves rather than with a Test-Path - the
# validator compares the <Vendor>.json stem case-sensitively, while Windows' case-insensitive
# filesystem would let "creality" pass a path test and then match no vendor. A vendor is a
# <name>.json with a sibling <name> directory; that pair is also what tells one apart from
# blacklist.json and retired_filament_ids.json, which sit in the same folder.
if ($Vendor) {
$found = $false
$suggestion = ''
foreach ($file in (Get-ChildItem -LiteralPath $ProfilesDir -Filter '*.json' -File)) {
$name = [IO.Path]::GetFileNameWithoutExtension($file.Name)
if (-not (Test-Path -LiteralPath (Join-Path $ProfilesDir $name) -PathType Container)) { continue }
if ($name -ceq $Vendor) { $found = $true; break }
if ($name -eq $Vendor) { $suggestion = $name }
}
if (-not $found) {
if ($suggestion) { Die "unknown vendor '$Vendor'; vendor names are case-sensitive, did you mean '$suggestion'?" }
Die "unknown vendor '$Vendor': no such vendor in $ProfilesDir"
}
}
# The validator's -v and orca_extra_profile_check.py's --vendor both take that stem; an unscoped
# run passes neither, so the checks below splat these in either way.
$VendorArgs = if ($Vendor) { @('-v', $Vendor) } else { @() }
$VendorPyArgs = if ($Vendor) { @('--vendor', $Vendor) } else { @() }
if (-not $WorkDir) { $WorkDir = Join-Path $RepoRoot '.test\check_profiles' } if (-not $WorkDir) { $WorkDir = Join-Path $RepoRoot '.test\check_profiles' }
$LogDir = Join-Path $WorkDir 'logs' $LogDir = Join-Path $WorkDir 'logs'
try { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } catch { Die "cannot create ${LogDir}: $_" } try { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } catch { Die "cannot create ${LogDir}: $_" }
@@ -350,32 +396,77 @@ function Resolve-Python {
Die 'no Python 3 found; install it (or the py launcher) and re-run' Die 'no Python 3 found; install it (or the py launcher) and re-run'
} }
# The fixtures name every preset "<vendor>_<parent preset>_orca_test", after the "name" inside
# <Vendor>.json rather than the file stem -Vendor takes - BBL.json is "Bambulab", iQ.json is
# "innovatiQ". Falls back to the stem for a vendor file with no readable name.
function Get-VendorDisplayName {
$name = ''
try { $name = (Get-Content -LiteralPath (Join-Path $ProfilesDir "$Vendor.json") -Raw -ErrorAction Stop | ConvertFrom-Json).name } catch { }
if ($name) { return "$name" }
return $Vendor
}
# Unpack just the presets one vendor's profiles own, and return how many that was. Each fixture
# preset was generated from a single system preset and inherits it by name - none inherits another
# user preset - so the selection is self-contained.
function Expand-VendorPresets([string] $Zip, [string] $Tree, [string] $Prefix) {
# .NET rather than Expand-Archive: a fixture holds around 20,000 entries, and unpacking every
# one of them to keep a twentieth costs more than the validation this is speeding up.
Add-Type -AssemblyName System.IO.Compression.FileSystem
$kept = 0
$archive = [IO.Compression.ZipFile]::OpenRead($Zip)
try {
foreach ($entry in $archive.Entries) {
# A directory entry has an empty Name, so it never matches and is never created.
if (-not $entry.Name.StartsWith($Prefix, [StringComparison]::Ordinal)) { continue }
$dest = Join-Path $Tree $entry.FullName.Replace('/', [IO.Path]::DirectorySeparatorChar)
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($dest)) | Out-Null
[IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $dest, $true)
$kept++
}
} finally {
$archive.Dispose()
}
return $kept
}
# ---------------------------------------------------------------------------- checks # ---------------------------------------------------------------------------- checks
$CheckBodies = @{ $CheckBodies = @{
extra_json_check = { extra_json_check = {
Invoke-Tool -Exe (Resolve-Python) -Arguments @((Join-Path $RepoRoot 'scripts\orca_extra_profile_check.py')) Invoke-Tool -Exe (Resolve-Python) -Arguments (@((Join-Path $RepoRoot 'scripts\orca_extra_profile_check.py')) + $VendorPyArgs)
} }
validate_system = { validate_system = {
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-l', "$LogLevel") Invoke-Tool -Exe $Validator -Arguments (@('-p', $ProfilesDir) + $VendorArgs + @('-l', "$LogLevel"))
} }
# Slices a two-colour cube through every printer so all custom g-code (incl. # Slices 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 # change_filament_gcode) is expanded - catches undefined-placeholder / invalid-flow bugs the
# static checks cannot see. # static checks cannot see.
validate_slice = { validate_slice = {
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-s', '-l', "$LogLevel") Invoke-Tool -Exe $Validator -Arguments (@('-p', $ProfilesDir) + $VendorArgs + @('-s', '-l', "$LogLevel"))
} }
validate_filament_subtypes = { validate_filament_subtypes = {
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-l', "$LogLevel", '-f') Invoke-Tool -Exe $Validator -Arguments (@('-p', $ProfilesDir) + $VendorArgs + @('-l', "$LogLevel", '-f'))
} }
# Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each # Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each
# is unpacked over the current system profiles and validated, so a profile change that would # is unpacked over the current system profiles and validated, so a profile change that would
# break an existing user's presets fails here. # break an existing user's presets fails here.
#
# Under -Vendor only that vendor's presets are unpacked, which is what makes the validator's
# -v usable here: -v filters the system vendors but never the user presets, so on a whole-tree
# snapshot it reports every other vendor's presets as unresolvable parents - thousands of
# errors saying nothing about the vendor under test. Teaching -v to filter user presets too is
# the deeper fix, but this runs against whatever validator is to hand, the published nightly
# included, so the picking has to happen here. It picks on the "<vendor display name>_"
# filename prefix that generate_custom_presets() (the validator's -g mode) gave every preset in
# these fixtures; a fixture cut from a renamed generator would surface as the "checked nothing"
# warning below. Presets whose source had no vendor carry no prefix - a few of those still name
# one in the middle, like "PET @BBL A1_orca_test" - and only an unscoped run covers them.
validate_custom = { validate_custom = {
$fixturesDir = Join-Path $WorkDir 'profile-fixtures' $fixturesDir = Join-Path $WorkDir 'profile-fixtures'
$outputDir = Join-Path $WorkDir 'custom-preset-validation' $outputDir = Join-Path $WorkDir 'custom-preset-validation'
@@ -392,6 +483,8 @@ $CheckBodies = @{
return 1 return 1
} }
$vendorPrefix = if ($Vendor) { "$(Get-VendorDisplayName)_" } else { '' }
$totalKept = 0
$status = 0 $status = 0
$failedLogs = @() $failedLogs = @()
$summary = @('## Custom Preset Fixture Validation', '', '| Version | Status | Log |', '| --- | --- | --- |') $summary = @('## Custom Preset Fixture Validation', '', '| Version | Status | Log |', '| --- | --- | --- |')
@@ -424,12 +517,23 @@ $CheckBodies = @{
Remove-Item -LiteralPath $profileTree -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $profileTree -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $profileTree | Out-Null New-Item -ItemType Directory -Force -Path $profileTree | Out-Null
# Piped rather than copied through <profiles>\*, so a vendor directory whose name # Piped rather than copied through <profiles>\*, so a vendor directory whose name
# holds a wildcard character is still copied by its literal path. # holds a wildcard character is still copied by its literal path. Under -Vendor the
Get-ChildItem -LiteralPath $ProfilesDir -Force | Copy-Item -Destination $profileTree -Recurse -Force # validator opens only that vendor and OrcaFilamentLibrary and skips the other sixty-odd
# (PresetBundle::load_system_presets_from_json), so the rest are left out of the copy:
# ~3s a fixture that was going on files nothing opens.
Get-ChildItem -LiteralPath $ProfilesDir -Force |
Where-Object { -not $Vendor -or -not $_.PSIsContainer -or $_.Name -eq $Vendor -or $_.Name -eq 'OrcaFilamentLibrary' } |
Copy-Item -Destination $profileTree -Recurse -Force
Remove-Item -LiteralPath (Join-Path $profileTree 'user') -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath (Join-Path $profileTree 'user') -Recurse -Force -ErrorAction SilentlyContinue
Expand-Archive -LiteralPath $fixtureZip -DestinationPath $profileTree -Force if ($Vendor) {
$kept = Expand-VendorPresets $fixtureZip $profileTree $vendorPrefix
$totalKept += $kept
Write-CheckLog " $kept $Vendor preset file(s)"
} else {
Expand-Archive -LiteralPath $fixtureZip -DestinationPath $profileTree -Force
}
$result = Invoke-Tool -Exe $Validator -Arguments @('-p', $profileTree, '-l', "$LogLevel") -OutFile $logPath $result = Invoke-Tool -Exe $Validator -Arguments (@('-p', $profileTree) + $VendorArgs + @('-l', "$LogLevel")) -OutFile $logPath
if ($result -eq 0) { if ($result -eq 0) {
$summary += "| $version | PASS | $version.log |" $summary += "| $version | PASS | $version.log |"
# Only failures are worth keeping; each tree is a full copy of resources\profiles. # Only failures are worth keeping; each tree is a full copy of resources\profiles.
@@ -441,6 +545,12 @@ $CheckBodies = @{
} }
} }
# A vendor added after the newest fixture was cut appears in none of them: nothing failed,
# but nothing was checked either.
if ($Vendor -and $totalKept -eq 0) {
Write-CheckLog "no $Vendor presets in any fixture; validate_custom checked nothing"
}
[IO.File]::WriteAllLines((Join-Path $outputDir 'summary.md'), [string[]] $summary) [IO.File]::WriteAllLines((Join-Path $outputDir 'summary.md'), [string[]] $summary)
$summary | ForEach-Object { Write-CheckLog $_ } $summary | ForEach-Object { Write-CheckLog $_ }
@@ -473,7 +583,7 @@ $CommentHeadings = @{
function Invoke-Check([string] $Name) { function Invoke-Check([string] $Name) {
$log = Join-Path $LogDir "$Name.log" $log = Join-Path $LogDir "$Name.log"
Write-Host '' Write-Host ''
Write-Host "==> $Name" -ForegroundColor Cyan Write-Host "==> $Name$(if ($Vendor) { " ($Vendor)" })" -ForegroundColor Cyan
$script:LogWriter = New-LogWriter $log $script:LogWriter = New-LogWriter $log
try { try {
@@ -510,26 +620,41 @@ try {
if ($Checks | Where-Object { $_ -ne 'extra_json_check' }) { $Validator = Resolve-Validator } if ($Checks | Where-Object { $_ -ne 'extra_json_check' }) { $Validator = Resolve-Validator }
# An empty printer set is a failure to the sweep, so validate_slice is recorded as skipped
# rather than run for a vendor that ships no printers (the filament-only OrcaFilamentLibrary);
# naming the check explicitly still runs it.
$results = [ordered] @{} $results = [ordered] @{}
$skipReasons = @{}
foreach ($name in $AllChecks) { foreach ($name in $AllChecks) {
if ($Checks -contains $name) { $results[$name] = Invoke-Check $name } if ($Checks -notcontains $name) { continue }
if ($name -eq 'validate_slice' -and -not $NamedChecks -and $Vendor -and
-not (Test-Path -LiteralPath (Join-Path (Join-Path $ProfilesDir $Vendor) 'machine') -PathType Container)) {
$results[$name] = 'skip'
$skipReasons[$name] = "$Vendor ships no printers"
} else {
$results[$name] = if (Invoke-Check $name) { 'pass' } else { 'fail' }
}
} }
Write-Host '' Write-Host ''
Write-Host '==> summary' -ForegroundColor Cyan Write-Host '==> summary' -ForegroundColor Cyan
foreach ($name in $results.Keys) { foreach ($name in $results.Keys) {
if ($results[$name]) { switch ($results[$name]) {
Write-Host " PASS $name" -ForegroundColor Green 'pass' { Write-Host " PASS $name" -ForegroundColor Green }
} else { 'skip' { Write-Host " SKIP $name ($($skipReasons[$name]))" -ForegroundColor Yellow }
Write-Host " FAIL $name ($(Join-Path $LogDir "$name.log"))" -ForegroundColor Red default { Write-Host " FAIL $name ($(Join-Path $LogDir "$name.log"))" -ForegroundColor Red }
} }
} }
$failed = @($results.Keys | Where-Object { -not $results[$_] }) $failed = @($results.Keys | Where-Object { $results[$_] -eq 'fail' })
if (-not $failed) { if (-not $failed) {
Remove-Item -LiteralPath (Join-Path $WorkDir 'pr_comment.md') -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath (Join-Path $WorkDir 'pr_comment.md') -Force -ErrorAction SilentlyContinue
Write-Host '' Write-Host ''
Write-Host "All checks passed. Logs: $LogDir" -ForegroundColor Green if (@($results.Values) -contains 'skip') {
Write-Host "Every check that ran passed, but CI runs the skipped ones too. Logs: $LogDir" -ForegroundColor Green
} else {
Write-Host "All checks passed. Logs: $LogDir" -ForegroundColor Green
}
exit 0 exit 0
} }

View File

@@ -14,6 +14,10 @@
# has, is moved aside for the duration of the run and restored on exit. Only one run per work # has, is moved aside for the duration of the run and restored on exit. Only one run per work
# dir at a time. # dir at a time.
# #
# -v/--vendor narrows a run to one vendor while working on that vendor's profiles - the one
# deliberate divergence from CI, which always checks the whole tree. A check that cannot be
# narrowed is left out of the run and reported as skipped; see usage().
#
# Usage: scripts/check_profile.sh [OPTION]... [CHECK]... # Usage: scripts/check_profile.sh [OPTION]... [CHECK]...
# The check_* functions run through run_check, which dispatches on the check name, so # The check_* functions run through run_check, which dispatches on the check name, so
@@ -33,14 +37,20 @@ HOST_ARCH="$(uname -m)"
PROFILES_DIR="${REPO_ROOT}/resources/profiles" PROFILES_DIR="${REPO_ROOT}/resources/profiles"
WORK_DIR="${REPO_ROOT}/.test/check_profiles" WORK_DIR="${REPO_ROOT}/.test/check_profiles"
VALIDATOR="${ORCA_PROFILE_VALIDATOR:-}" VALIDATOR="${ORCA_PROFILE_VALIDATOR:-}"
# Vendor to check, named after its <Vendor>.json - empty means every vendor, which is exactly what
# both the validator's -v and orca_extra_profile_check.py's --vendor take an empty value to mean.
# So the flag is passed unconditionally below rather than kept in an array bash 3.2 cannot expand
# empty under `set -u`.
VENDOR=""
LOG_LEVEL=2 LOG_LEVEL=2
PREFER_DOWNLOAD=0 PREFER_DOWNLOAD=0
REFRESH=0 REFRESH=0
ALL_CHECKS=(extra_json_check validate_system validate_slice validate_filament_subtypes validate_custom) ALL_CHECKS=(extra_json_check validate_system validate_slice validate_filament_subtypes validate_custom)
CHECKS=() CHECKS=()
# "<check><TAB>pass|fail" per check that ran; a string rather than an array because bash 3.2 # "<check><TAB>pass|fail" per check that ran, plus "<check><TAB>skip<TAB>why" for one a vendor
# (still the /bin/bash on macOS) cannot expand an empty array under `set -u`. # scope left out; a string rather than an array because bash 3.2 (still the /bin/bash on macOS)
# cannot expand an empty array under `set -u`.
RESULTS="" RESULTS=""
usage() { usage() {
@@ -58,6 +68,7 @@ Checks (default: all, in this order):
Options: Options:
-p, --profiles DIR profile tree to validate (default: resources/profiles) -p, --profiles DIR profile tree to validate (default: resources/profiles)
-v, --vendor NAME check only this vendor, named after its <Vendor>.json (e.g. "Co Print")
--validator BIN OrcaSlicer_profile_validator to use; also \$ORCA_PROFILE_VALIDATOR. --validator BIN OrcaSlicer_profile_validator to use; also \$ORCA_PROFILE_VALIDATOR.
Default: the local build*/ Release build (then RelWithDebInfo, then Default: the local build*/ Release build (then RelWithDebInfo, then
Debug) for this architecture, else the nightly release build is Debug) for this architecture, else the nightly release build is
@@ -70,6 +81,12 @@ Options:
Note: extra_json_check always looks at the tree next to the script Note: extra_json_check always looks at the tree next to the script
(<repo>/resources/profiles); --profiles only redirects the validator checks. (<repo>/resources/profiles); --profiles only redirects the validator checks.
Note: --vendor narrows validate_custom too, by keeping only that vendor's presets in each
fixture tree. The one check it cannot narrow is validate_slice for a vendor that ships no
printers; the summary reports that one as skipped, and naming it explicitly still runs it.
extra_json_check keeps its two cross-vendor checks (setting_id and filament_id) tree-wide,
so a scoped run can still fail on another vendor's files.
EOF EOF
} }
@@ -88,6 +105,7 @@ while [ $# -gt 0 ]; do
case "$1" in case "$1" in
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
-p|--profiles) [ $# -ge 2 ] || die "$1 needs a directory"; PROFILES_DIR="$2"; shift 2 ;; -p|--profiles) [ $# -ge 2 ] || die "$1 needs a directory"; PROFILES_DIR="$2"; shift 2 ;;
-v|--vendor) [ $# -ge 2 ] || die "$1 needs a vendor name"; VENDOR="$2"; shift 2 ;;
--validator) [ $# -ge 2 ] || die "$1 needs a path"; VALIDATOR="$2"; shift 2 ;; --validator) [ $# -ge 2 ] || die "$1 needs a path"; VALIDATOR="$2"; shift 2 ;;
--work-dir) [ $# -ge 2 ] || die "$1 needs a directory"; WORK_DIR="$2"; shift 2 ;; --work-dir) [ $# -ge 2 ] || die "$1 needs a directory"; WORK_DIR="$2"; shift 2 ;;
-l|--log-level) [ $# -ge 2 ] || die "$1 needs a number"; LOG_LEVEL="$2"; shift 2 ;; -l|--log-level) [ $# -ge 2 ] || die "$1 needs a number"; LOG_LEVEL="$2"; shift 2 ;;
@@ -106,11 +124,40 @@ while [ $# -gt 0 ]; do
esac esac
done done
[ "${#CHECKS[@]}" -gt 0 ] || CHECKS=("${ALL_CHECKS[@]}") # A check named on the command line always runs; only the default set is narrowed (see the run
# loop below).
NAMED_CHECKS="${#CHECKS[@]}"
[ "${NAMED_CHECKS}" -gt 0 ] || CHECKS=("${ALL_CHECKS[@]}")
[ -d "${PROFILES_DIR}" ] || die "profile directory not found: ${PROFILES_DIR}" [ -d "${PROFILES_DIR}" ] || die "profile directory not found: ${PROFILES_DIR}"
PROFILES_DIR="$(cd -- "${PROFILES_DIR}" && pwd)" PROFILES_DIR="$(cd -- "${PROFILES_DIR}" && pwd)"
# A vendor neither tool knows is not an error to them: the static checks load nothing of their own
# and still report success, so a typo would otherwise be three green checks and one baffling slice
# failure. The match has to be made on the names themselves rather than with a -f test - the
# validator compares the <Vendor>.json stem case-sensitively, while a case-insensitive filesystem
# (macOS, Windows) would let "creality" pass a file test and then match no vendor. A vendor is a
# <name>.json with a sibling <name>/ directory; that pair is also what tells one apart from
# blacklist.json and retired_filament_ids.json, which sit in the same folder.
if [ -n "${VENDOR}" ]; then
wanted="$(printf '%s' "${VENDOR}" | tr '[:upper:]' '[:lower:]')"
found=""
suggestion=""
for file in "${PROFILES_DIR}"/*.json; do
name="${file##*/}"; name="${name%.json}"
[ -d "${PROFILES_DIR}/${name}" ] || continue
if [ "${name}" = "${VENDOR}" ]; then
found=1
break
fi
[ "$(printf '%s' "${name}" | tr '[:upper:]' '[:lower:]')" = "${wanted}" ] && suggestion="${name}"
done
if [ -z "${found}" ]; then
[ -z "${suggestion}" ] || die "unknown vendor '${VENDOR}'; vendor names are case-sensitive, did you mean '${suggestion}'?"
die "unknown vendor '${VENDOR}': no such vendor in ${PROFILES_DIR}"
fi
fi
LOG_DIR="${WORK_DIR}/logs" LOG_DIR="${WORK_DIR}/logs"
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}" mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
WORK_DIR="$(cd -- "${WORK_DIR}" && pwd)" WORK_DIR="$(cd -- "${WORK_DIR}" && pwd)"
@@ -315,34 +362,65 @@ resolve_validator() {
# ---------------------------------------------------------------------------- checks # ---------------------------------------------------------------------------- checks
check_extra_json_check() { check_extra_json_check() {
python3 "${REPO_ROOT}/scripts/orca_extra_profile_check.py" python3 "${REPO_ROOT}/scripts/orca_extra_profile_check.py" --vendor "${VENDOR}"
} }
check_validate_system() { check_validate_system() {
"${VALIDATOR}" -p "${PROFILES_DIR}" -l "${LOG_LEVEL}" "${VALIDATOR}" -p "${PROFILES_DIR}" -v "${VENDOR}" -l "${LOG_LEVEL}"
} }
# Slices a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode) # Slices 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 cannot see. # is expanded - catches undefined-placeholder / invalid-flow bugs the static checks cannot see.
check_validate_slice() { check_validate_slice() {
"${VALIDATOR}" -p "${PROFILES_DIR}" -s -l "${LOG_LEVEL}" "${VALIDATOR}" -p "${PROFILES_DIR}" -v "${VENDOR}" -s -l "${LOG_LEVEL}"
} }
check_validate_filament_subtypes() { check_validate_filament_subtypes() {
"${VALIDATOR}" -p "${PROFILES_DIR}" -l "${LOG_LEVEL}" -f "${VALIDATOR}" -p "${PROFILES_DIR}" -v "${VENDOR}" -l "${LOG_LEVEL}" -f
}
# The fixtures name every preset "<vendor>_<parent preset>_orca_test", after the "name" inside
# <Vendor>.json rather than the file stem --vendor takes - BBL.json is "Bambulab", iQ.json is
# "innovatiQ". Falls back to the stem for a vendor file with no readable name.
vendor_display_name() {
local name
name="$(VENDOR_JSON="${PROFILES_DIR}/${VENDOR}.json" python3 - <<'PY'
import json
import os
try:
with open(os.environ["VENDOR_JSON"], encoding="utf-8") as fh:
print(json.load(fh).get("name", ""))
except Exception:
pass
PY
)"
printf '%s\n' "${name:-${VENDOR}}"
} }
# Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each is # Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each is
# unpacked over the current system profiles and validated, so a profile change that would break # unpacked over the current system profiles and validated, so a profile change that would break
# an existing user's presets fails here. # an existing user's presets fails here.
#
# Under --vendor only that vendor's presets are unpacked, which is what makes the validator's -v
# usable here: -v filters the system vendors but never the user presets, so on a whole-tree
# snapshot it reports every other vendor's presets as unresolvable parents - thousands of errors
# saying nothing about the vendor under test. Teaching -v to filter user presets too is the deeper
# fix, but this runs against whatever validator is to hand, the published nightly included, so the
# picking has to happen here. It picks on the "<vendor display name>_" filename prefix that
# generate_custom_presets() (the validator's -g mode) gave every preset in these fixtures; a
# fixture cut from a renamed generator would surface as the "checked nothing" warning below.
# Presets whose source had no vendor carry no prefix - a few of those still name one in the
# middle, like "PET @BBL A1_orca_test" - and only an unscoped run covers them.
check_validate_custom() { check_validate_custom() {
local fixtures_dir="${WORK_DIR}/profile-fixtures" local fixtures_dir="${WORK_DIR}/profile-fixtures"
local output_dir="${WORK_DIR}/custom-preset-validation" local output_dir="${WORK_DIR}/custom-preset-validation"
local summary="${output_dir}/summary.md" local summary="${output_dir}/summary.md"
local status=0 failed_logs="" local status=0 failed_logs="" vendor_prefix="" total_kept=0
local version asset expected_sha256 asset_url fixture_zip profile_tree log_path actual_sha256 result local version asset expected_sha256 asset_url fixture_zip profile_tree log_path actual_sha256 result kept
command -v unzip >/dev/null 2>&1 || { msg "unzip is required for validate_custom"; return 1; } command -v unzip >/dev/null 2>&1 || { msg "unzip is required for validate_custom"; return 1; }
[ -z "${VENDOR}" ] || vendor_prefix="$(vendor_display_name)_"
mkdir -p "${fixtures_dir}" "${output_dir}" || return 1 mkdir -p "${fixtures_dir}" "${output_dir}" || return 1
fetch "${FIXTURE_RELEASE_URL}/manifest.json" "${fixtures_dir}/manifest.json" force || return 1 fetch "${FIXTURE_RELEASE_URL}/manifest.json" "${fixtures_dir}/manifest.json" force || return 1
@@ -407,11 +485,28 @@ PY
msg "validating custom presets from ${version} ..." msg "validating custom presets from ${version} ..."
rm -rf "${profile_tree}" rm -rf "${profile_tree}"
mkdir -p "${profile_tree}" || return 1 mkdir -p "${profile_tree}" || return 1
cp -a "${PROFILES_DIR}/." "${profile_tree}/" || return 1 if [ -n "${VENDOR}" ]; then
rm -rf "${profile_tree}/user" # -v has the validator open this vendor and OrcaFilamentLibrary and skip the rest
unzip -q "${fixture_zip}" -d "${profile_tree}" || return 1 # (PresetBundle::load_system_presets_from_json), and unzip's * spans '/', so one
# pattern reaches every preset type. Copying and unpacking only those turns a ~5s
# setup per fixture into ~0.5s. Naming the vendor twice, when it is the library
# itself, is not an error to cp; exit 11 is unzip's "nothing matched", which is the
# fixture-predates-the-vendor case the warning below reports.
cp -a "${PROFILES_DIR}"/*.json "${PROFILES_DIR}/${VENDOR}" \
"${PROFILES_DIR}/OrcaFilamentLibrary" "${profile_tree}/" || return 1
unzip -q "${fixture_zip}" "user/*/${vendor_prefix}*" -d "${profile_tree}"
result=$?
[ "${result}" -eq 0 ] || [ "${result}" -eq 11 ] || return 1
kept="$(find "${profile_tree}/user" -type f 2>/dev/null | wc -l | tr -d ' ')"
total_kept=$((total_kept + kept))
msg " ${kept} ${VENDOR} preset file(s)"
else
cp -a "${PROFILES_DIR}/." "${profile_tree}/" || return 1
rm -rf "${profile_tree}/user"
unzip -q "${fixture_zip}" -d "${profile_tree}" || return 1
fi
"${VALIDATOR}" -p "${profile_tree}" -l "${LOG_LEVEL}" > "${log_path}" 2>&1 "${VALIDATOR}" -p "${profile_tree}" -v "${VENDOR}" -l "${LOG_LEVEL}" > "${log_path}" 2>&1
result=$? result=$?
if [ "${result}" -eq 0 ]; then if [ "${result}" -eq 0 ]; then
@@ -425,6 +520,12 @@ PY
fi fi
done < "${fixtures_dir}/fixtures.tsv" done < "${fixtures_dir}/fixtures.tsv"
# A vendor added after the newest fixture was cut appears in none of them: nothing failed, but
# nothing was checked either.
if [ -n "${VENDOR}" ] && [ "${total_kept}" -eq 0 ]; then
msg "no ${VENDOR} presets in any fixture; validate_custom checked nothing"
fi
cat "${summary}" cat "${summary}"
if [ -n "${failed_logs}" ]; then if [ -n "${failed_logs}" ]; then
echo "" echo ""
@@ -461,7 +562,7 @@ run_check() {
local name="$1" local name="$1"
local log="${LOG_DIR}/${name}.log" local log="${LOG_DIR}/${name}.log"
local result local result
printf '\n%s==> %s%s\n' "${C_BOLD}" "${name}" "${C_RESET}" printf '\n%s==> %s%s%s\n' "${C_BOLD}" "${name}" "${VENDOR:+ (${VENDOR})}" "${C_RESET}"
"check_${name}" 2>&1 | tee "${log}" "check_${name}" 2>&1 | tee "${log}"
result="${PIPESTATUS[0]}" result="${PIPESTATUS[0]}"
if [ "${result}" -eq 0 ]; then if [ "${result}" -eq 0 ]; then
@@ -477,17 +578,30 @@ if wants validate_system || wants validate_slice || wants validate_filament_subt
resolve_validator resolve_validator
fi fi
# An empty printer set is a failure to the sweep, so validate_slice is recorded as skipped rather
# than run for a vendor that ships no printers (the filament-only OrcaFilamentLibrary); naming the
# check explicitly still runs it.
for check in "${ALL_CHECKS[@]}"; do for check in "${ALL_CHECKS[@]}"; do
wants "${check}" && run_check "${check}" wants "${check}" || continue
if [ "${check}" = validate_slice ] && [ "${NAMED_CHECKS}" -eq 0 ] && [ -n "${VENDOR}" ] &&
[ ! -d "${PROFILES_DIR}/${VENDOR}/machine" ]; then
RESULTS="${RESULTS}${check}"$'\t'"skip"$'\t'"${VENDOR} ships no printers"$'\n'
else
run_check "${check}"
fi
done done
# Summary, plus the comment check_profiles_comment.yml would post when something fails. # Summary, plus the comment check_profiles_comment.yml would post when something fails.
failed=0 failed=0
skipped=0
printf '\n%s==> summary%s\n' "${C_BOLD}" "${C_RESET}" printf '\n%s==> summary%s\n' "${C_BOLD}" "${C_RESET}"
while IFS=$'\t' read -r name result; do while IFS=$'\t' read -r name result why; do
[ -n "${name}" ] || continue [ -n "${name}" ] || continue
if [ "${result}" = "pass" ]; then if [ "${result}" = "pass" ]; then
printf '%s PASS%s %s\n' "${C_GREEN}" "${C_RESET}" "${name}" printf '%s PASS%s %s\n' "${C_GREEN}" "${C_RESET}" "${name}"
elif [ "${result}" = "skip" ]; then
printf '%s SKIP%s %s (%s)\n' "${C_BOLD}" "${C_RESET}" "${name}" "${why}"
skipped=1
else else
printf '%s FAIL%s %s (%s)\n' "${C_RED}" "${C_RESET}" "${name}" "${LOG_DIR}/${name}.log" printf '%s FAIL%s %s (%s)\n' "${C_RED}" "${C_RESET}" "${name}" "${LOG_DIR}/${name}.log"
failed=1 failed=1
@@ -498,7 +612,12 @@ EOF
if [ "${failed}" -eq 0 ]; then if [ "${failed}" -eq 0 ]; then
rm -f "${WORK_DIR}/pr_comment.md" rm -f "${WORK_DIR}/pr_comment.md"
printf '\n%sAll checks passed.%s Logs: %s\n' "${C_GREEN}" "${C_RESET}" "${LOG_DIR}" if [ "${skipped}" -eq 0 ]; then
printf '\n%sAll checks passed.%s Logs: %s\n' "${C_GREEN}" "${C_RESET}" "${LOG_DIR}"
else
printf '\n%sEvery check that ran passed%s, but CI runs the skipped ones too. Logs: %s\n' \
"${C_GREEN}" "${C_RESET}" "${LOG_DIR}"
fi
exit 0 exit 0
fi fi