Skip to content
cpbenchy 0.1.0.dev0 is in alpha: until version 1.0, commands, options, the Python API and the result format may still change. Pin the version you use.

cpbenchy submission

Builds what you send to a competition, ready to upload and tested before you do.

Updated View as Markdown
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 HiGHS
submission.tomltoml
name = "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.

src/cpbenchy/submission.pypython
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
Navigation

Type to search…

↑↓ navigate↵ selectEsc close