CommandBuilt incomes with cpbenchy
cpbenchy submission build --test --archive zip- Version
- cpbenchy 0.1.0.dev0
- Last updated
- 6 Oct 2026 · 1 commit
- Authors
- ThomSerg
- Tags
- competitionsubmissioncommand
What it does
Builds a competition submission: a directory (and an archive) that the competition runs as your solver. From one small file, it makes a launcher per track that takes the competition’s placeholders, pins the packages you tested with, adds an install script and a README with the command lines to register, and tests every track before you upload.
Use it
cpbenchy submission init --rules xcsp3-2025 --rules xcsp3-2025-fast -s ortools # writes submission.toml
cpbenchy submission build --test --archive zip
cpbenchy submission build -s highs --test --archive zip # the same, for HiGHSname = "cpmpy-{solver}"
description = "CPMpy, with {solver} as its backend"
solver = "ortools"
requirements = ["pycsp3"]
include = ["doc/description.pdf"]
[[tracks]]
name = "COP"
rules = "xcsp3-2025"
test = ["instances/small.xml"]Options
cpbenchy submission build |
|
|---|---|
FILE |
the submission file (default submission.toml) |
-s SOLVER |
build it for this solver instead of the file’s |
-o DIR |
output directory (default: the submission’s name) |
--archive zip|gztar|tar |
also pack it |
--wheels |
bundle the packages’ wheels, to install offline |
--force |
replace an earlier build (only a directory cpbenchy built) |
--test, --test-time |
run each track on its test instances |
Notes
Competition submissions explains the file, the placeholders and what is built in full.
Implementation
The build in src/cpbenchy/submission.py, lines 186–236 of 634, as of this version of the docs.
def build(sub: Submission, out: Path, *, wheels: bool = False, force: bool = False) -> Path:
"""Write the submission to `out`, and return it."""
if out.exists() and any(out.iterdir()):
if not force:
raise UsageError(f"{out} exists; pass --force to replace it")
if not (out / MARKER).exists():
raise UsageError(f"{out} is not a submission built by cpbenchy ({MARKER} missing); not replacing it")
shutil.rmtree(out)
for d in ("bin", "code", "rules"):
(out / d).mkdir(parents=True, exist_ok=True)
vendored = _vendor(out / "code")
bundled = {ref: _bundle(sub.base, ref, out / "code") for ref in [*sub.plugins, sub.loader] if ref}
for track in sub.tracks:
rules_file = out / "rules" / f"{track.rules.name}.toml"
if not rules_file.exists():
_copy_rules(track.rules, rules_file, sub.base, out / "code", bundled)
launcher = out / "bin" / f"run_{track.name}"
launcher.write_text(_launcher(sub, track, rules_file.name, bundled))
launcher.chmod(0o755)
for path in sub.include:
if not path.exists():
raise UsageError(f"can't include {path}: it doesn't exist")
target = out / path.relative_to(sub.base) if path.is_relative_to(sub.base) else out / path.name
target.parent.mkdir(parents=True, exist_ok=True)
(shutil.copytree if path.is_dir() else shutil.copy2)(path, target)
pins, warnings = requirements(sub, vendored)
(out / "requirements.txt").write_text("".join(f"{p}\n" for p in pins))
if wheels:
_download_wheels(out / "requirements.txt", out / "wheels")
install = out / "install.sh"
install.write_text(INSTALL.format(name=sub.name))
install.chmod(0o755)
(out / "README.md").write_text(_readme(sub, pins))
info = {
"name": sub.name,
"built": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"cpbenchy": cpbenchy.__version__,
"python": sys.version.split()[0],
"vendored": vendored,
"requirements": pins,
"submission": sub.source,
"tracks": {
t.name: {"rules": t.rules.to_dict(), "solver": t.solver, "arguments": t.arguments} for t in sub.tracks
},
}
(out / MARKER).write_text(json.dumps(info, indent=2, default=str) + "\n")
for warning in warnings:
print(f"warning: {warning}", file=sys.stderr)
return out