batched upload resave: --resave-batch CLI + one staging pass per bulk upload (tasks-runner 0004)
kicad_tools --resave-batch <outdir> <file>...: N resaves in one process (one WASM init — marginal resave is ~50ms vs ~0.3s/process). Outputs in <outdir>/<index>/, per-file "RESAVE-BATCH <index> <exit-code>" stderr verdicts with the single-file 0/4/5 contract; exit 0 = loop completed, so an invalid file mid-batch can't mask its neighbors. loadSchematicHeadless now binds each schematic to ITS project via SETTINGS_MANAGER::GetProject, never Prj(): with aSetActive=false projects accumulate across batch entries and Prj() keeps returning the first one — the writer then stamped a wrong/empty project name into saved symbol-instance data. (UnloadProject is no fix: unloading the active project immediately reloads a null "" project.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AH2iPekUGsEAYnMUD5BmAi
This commit is contained in:
parent
864ddb00a4
commit
90bc4e0222
3 changed files with 108 additions and 3 deletions
|
|
@ -10,7 +10,7 @@
|
||||||
*
|
*
|
||||||
* Run: cd tests && npm run tools:contract
|
* Run: cd tests && npm run tools:contract
|
||||||
*/
|
*/
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync, spawnSync } from "node:child_process";
|
||||||
import {
|
import {
|
||||||
existsSync,
|
existsSync,
|
||||||
mkdtempSync,
|
mkdtempSync,
|
||||||
|
|
@ -127,6 +127,43 @@ try {
|
||||||
console.log("skip footprint fixtures (kicad submodule not initialized)");
|
console.log("skip footprint fixtures (kicad submodule not initialized)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- resave-batch: N files, one process ----------------------------------
|
||||||
|
// The bulk-upload normalization mode: outputs under <outdir>/<index>/,
|
||||||
|
// per-file verdicts as "RESAVE-BATCH <index> <exit-code>" stderr lines with
|
||||||
|
// the single-file code contract, process exit 0 when the loop completed —
|
||||||
|
// an invalid file mid-batch must not mask its neighbors.
|
||||||
|
{
|
||||||
|
const badMid = out("mid-garbage.kicad_sch");
|
||||||
|
writeFileSync(badMid, "not a schematic (");
|
||||||
|
const r = spawnSync("node", [cli, "--resave-batch", out("batch"), demoPcb, badMid, demoSch], {
|
||||||
|
stdio: ["ignore", "ignore", "pipe"],
|
||||||
|
});
|
||||||
|
const stderr = r.stderr?.toString() ?? "";
|
||||||
|
const codes = new Map<number, number>();
|
||||||
|
for (const m of stderr.matchAll(/^RESAVE-BATCH (\d+) (-?\d+)$/gm)) {
|
||||||
|
codes.set(Number(m[1]), Number(m[2]));
|
||||||
|
}
|
||||||
|
check("batch exits 0 despite an invalid entry", r.status === 0, `exit ${r.status}`);
|
||||||
|
check("batch reports one verdict line per entry", codes.size === 3, `${codes.size}`);
|
||||||
|
check("batch board entry verdicts 0", codes.get(0) === 0, `${codes.get(0)}`);
|
||||||
|
check("batch invalid entry verdicts 4", codes.get(1) === 4, `${codes.get(1)}`);
|
||||||
|
check("batch schematic entry verdicts 0", codes.get(2) === 0, `${codes.get(2)}`);
|
||||||
|
const producedPcb = path.join(out("batch"), "0", "demo.kicad_pcb");
|
||||||
|
check(
|
||||||
|
"batch board output lands in its index dir and bumps the version",
|
||||||
|
existsSync(producedPcb) && version(producedPcb) > version(demoPcb),
|
||||||
|
);
|
||||||
|
const producedSch = path.join(out("batch"), "2", "demo.kicad_sch");
|
||||||
|
check("batch schematic output lands in its index dir", existsSync(producedSch));
|
||||||
|
check(
|
||||||
|
"batch invalid entry produced no output dir",
|
||||||
|
!existsSync(path.join(out("batch"), "1", path.basename(badMid))),
|
||||||
|
);
|
||||||
|
check("batch outputs lint clean", run(["--lint", producedPcb, producedSch]).code === 0);
|
||||||
|
|
||||||
|
check("batch usage (no files) exits 2", run(["--resave-batch", out("bu")]).code === 2);
|
||||||
|
}
|
||||||
|
|
||||||
// --- exit-code contract ---------------------------------------------------
|
// --- exit-code contract ---------------------------------------------------
|
||||||
{
|
{
|
||||||
check("usage (no args) exits 2", run(["--resave"]).code === 2);
|
check("usage (no args) exits 2", run(["--resave"]).code === 2);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@
|
||||||
* pcbToolsResaveBoard callback, .kicad_sch one file per
|
* pcbToolsResaveBoard callback, .kicad_sch one file per
|
||||||
* sheet, .kicad_sym/.lib via ConvertLibrary. Exit 4 =
|
* sheet, .kicad_sym/.lib via ConvertLibrary. Exit 4 =
|
||||||
* input invalid, 5 = write failed)
|
* input invalid, 5 = write failed)
|
||||||
|
* kicad_tools --resave-batch <outdir> <file> [<file>...]
|
||||||
|
* (N resaves, one runtime init — bulk-upload
|
||||||
|
* normalization. Outputs in <outdir>/<index>/; per-file
|
||||||
|
* verdicts as "RESAVE-BATCH <index> <exit-code>" stderr
|
||||||
|
* lines with the single-file code contract; exit 0 when
|
||||||
|
* the loop completed)
|
||||||
* kicad_tools --erc [--json] [--strict] <file.kicad_sch> [<out>]
|
* kicad_tools --erc [--json] [--strict] <file.kicad_sch> [<out>]
|
||||||
* kicad_tools --netlist [--xml] <file.kicad_sch> [<out>]
|
* kicad_tools --netlist [--xml] <file.kicad_sch> [<out>]
|
||||||
* kicad_tools --bom <file.kicad_sch> [<out>]
|
* kicad_tools --bom <file.kicad_sch> [<out>]
|
||||||
|
|
|
||||||
|
|
@ -616,8 +616,15 @@ std::unique_ptr<SCHEMATIC> loadSchematicHeadless( const char* aInPath )
|
||||||
wxFileName pro( fn );
|
wxFileName pro( fn );
|
||||||
pro.SetExt( wxS( "kicad_pro" ) );
|
pro.SetExt( wxS( "kicad_pro" ) );
|
||||||
trace( "loadSchematicHeadless: LoadProject" );
|
trace( "loadSchematicHeadless: LoadProject" );
|
||||||
manager.LoadProject( pro.FileExists() ? pro.GetFullPath() : wxString( wxEmptyString ), false );
|
const wxString proPath = pro.FileExists() ? pro.GetFullPath() : wxString( wxEmptyString );
|
||||||
PROJECT& project = manager.Prj();
|
manager.LoadProject( proPath, false );
|
||||||
|
// Bind THIS file's project, not Prj(): with aSetActive=false projects
|
||||||
|
// accumulate across --resave-batch entries and Prj() keeps returning the
|
||||||
|
// first one — the s-expr writer would then stamp the wrong (or an empty)
|
||||||
|
// project name into the saved symbol-instance data. GetProject resolves
|
||||||
|
// the exact project the LoadProject call above created (or reused).
|
||||||
|
PROJECT* loaded = manager.GetProject( proPath );
|
||||||
|
PROJECT& project = loaded ? *loaded : manager.Prj();
|
||||||
project.SetElem( PROJECT::ELEM::LEGACY_SYMBOL_LIBS, nullptr );
|
project.SetElem( PROJECT::ELEM::LEGACY_SYMBOL_LIBS, nullptr );
|
||||||
|
|
||||||
auto schematic = std::make_unique<SCHEMATIC>( &project );
|
auto schematic = std::make_unique<SCHEMATIC>( &project );
|
||||||
|
|
@ -1258,6 +1265,60 @@ int symConvertMain( int argc, char** argv )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( argc >= 2 && std::strcmp( argv[1], "--resave-batch" ) == 0 )
|
||||||
|
{
|
||||||
|
// Batch flavor of --resave (bulk-upload normalization): one process,
|
||||||
|
// one runtime init, N inputs. Each input's outputs land in
|
||||||
|
// <outdir>/<index>/ (index = position in the input list) so
|
||||||
|
// hierarchical-schematic sheet outputs from different roots never
|
||||||
|
// collide. Per-file verdicts use the single-file exit contract
|
||||||
|
// (0/2/4/5) and are reported on stderr as
|
||||||
|
// RESAVE-BATCH <index> <exit-code>
|
||||||
|
// The process exits 0 when the batch LOOP completed — per-file
|
||||||
|
// failures live in the verdict lines, so one bad file can't mask the
|
||||||
|
// rest of the batch. Exit 2 = usage.
|
||||||
|
wxDisableAsserts();
|
||||||
|
|
||||||
|
if( argc < 4 )
|
||||||
|
{
|
||||||
|
std::fprintf( stderr,
|
||||||
|
"usage: kicad_tools --resave-batch <outdir> <file> [<file>...]\n" );
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int n = 3; n < argc; n++ )
|
||||||
|
{
|
||||||
|
const int index = n - 3;
|
||||||
|
char sub[32];
|
||||||
|
std::snprintf( sub, sizeof( sub ), "/%d", index );
|
||||||
|
const std::string outDir = std::string( argv[2] ) + sub;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rc = runResave( argv[n], outDir.c_str() );
|
||||||
|
}
|
||||||
|
catch( const std::exception& e )
|
||||||
|
{
|
||||||
|
std::fprintf( stderr, "%s: error: %s\n", argv[n], e.what() );
|
||||||
|
rc = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries can come from DIFFERENT projects (a bulk upload of a
|
||||||
|
// multi-project tree); with aSetActive=false the loaded projects
|
||||||
|
// simply accumulate. That is safe because loadSchematicHeadless
|
||||||
|
// binds each schematic to ITS OWN project via GetProject — never
|
||||||
|
// to Prj(), which keeps returning the first loaded project.
|
||||||
|
// (UnloadProject is no help here: unloading the active project
|
||||||
|
// immediately reloads a null "" project, which would then shadow
|
||||||
|
// every later entry's project name in the saved instance data.)
|
||||||
|
|
||||||
|
std::fprintf( stderr, "RESAVE-BATCH %d %d\n", index, rc );
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if( argc >= 2 && std::strcmp( argv[1], "--convert-lib" ) == 0 && argc >= 4 )
|
if( argc >= 2 && std::strcmp( argv[1], "--convert-lib" ) == 0 && argc >= 4 )
|
||||||
{
|
{
|
||||||
// The legacy plugin asserts on relative paths and (worse) writes an
|
// The legacy plugin asserts on relative paths and (worse) writes an
|
||||||
|
|
@ -1286,6 +1347,7 @@ int symConvertMain( int argc, char** argv )
|
||||||
std::fprintf( stderr, "usage: kicad_tools --convert-lib <input.lib> <output.kicad_sym>\n"
|
std::fprintf( stderr, "usage: kicad_tools --convert-lib <input.lib> <output.kicad_sym>\n"
|
||||||
" kicad_tools --lint [--strict] <file> [<file>...]\n"
|
" kicad_tools --lint [--strict] <file> [<file>...]\n"
|
||||||
" kicad_tools --resave <file> <outdir>\n"
|
" kicad_tools --resave <file> <outdir>\n"
|
||||||
|
" kicad_tools --resave-batch <outdir> <file> [<file>...]\n"
|
||||||
" kicad_tools --erc [--json] [--strict] <file.kicad_sch> [<out>]\n"
|
" kicad_tools --erc [--json] [--strict] <file.kicad_sch> [<out>]\n"
|
||||||
" kicad_tools --netlist [--xml] <file.kicad_sch> [<out>]\n"
|
" kicad_tools --netlist [--xml] <file.kicad_sch> [<out>]\n"
|
||||||
" kicad_tools --bom <file.kicad_sch> [<out>]\n"
|
" kicad_tools --bom <file.kicad_sch> [<out>]\n"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue