diff --git a/CHANGES.md b/CHANGES.md index d96eaead..b6184129 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,10 @@ development source code and as such may not be routinely kept up to date. ([#501](https://github.com/nextstrain/cli/pull/501)) +* The `build` and `run` commands now run `snakemake` with the + `--benchmark-extended` option for more detailed benchmark files. + ([#467](https://github.com/nextstrain/cli/issues/467)) + # 10.4.2 (7 January 2026) ## Improvements diff --git a/doc/changes.md b/doc/changes.md index 0480819d..4c18eea8 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -28,6 +28,10 @@ development source code and as such may not be routinely kept up to date. ([#501](https://github.com/nextstrain/cli/pull/501)) +* The `build` and `run` commands now run `snakemake` with the + `--benchmark-extended` option for more detailed benchmark files. + ([#467](https://github.com/nextstrain/cli/issues/467)) + (v10-4-2)= ## 10.4.2 (7 January 2026) diff --git a/nextstrain/cli/command/build.py b/nextstrain/cli/command/build.py index 0aa35d7a..4d402cf5 100644 --- a/nextstrain/cli/command/build.py +++ b/nextstrain/cli/command/build.py @@ -24,8 +24,8 @@ from ..argparse import add_extended_help_flags, AppendOverwriteDefault, SKIP_AUTO_DEFAULT_IN_HELP from ..debug import debug from ..errors import UsageError, UserError -from ..runner import docker, singularity, aws_batch -from ..util import byte_quantity, runner_name, split_image_name, warn +from ..runner import conda, docker, singularity, aws_batch +from ..util import byte_quantity, remove_prefix, runner_name, split_image_name, warn from ..volume import NamedVolume @@ -206,7 +206,7 @@ def register_parser(subparser): nargs = "?") # Register runner flags and arguments - runner.register_runners(parser, exec = ["snakemake", "--printshellcmds", ...]) + runner.register_runners(parser, exec = ["snakemake", ...]) return parser @@ -243,12 +243,21 @@ def run(opts): opts.volumes.append(build_volume) # for Docker, Singularity, and AWS Batch - # Automatically pass thru appropriate resource options to Snakemake to - # avoid the user having to repeat themselves (once for us, once for - # snakemake). if opts.exec == "snakemake": + opts.default_exec_args += [ + # Useful to see what's going on; see also 08ffc925. + "--printshellcmds", + + # Useful to have additional information in benchmark files. + *(["--benchmark-extended"] + if supports_benchmark_extended(opts) else []), + ] + snakemake_opts = parse_snakemake_args(opts.extra_exec_args) + # Automatically pass thru appropriate resource options to Snakemake to + # avoid the user having to repeat themselves (once for us, once for + # snakemake). if not snakemake_opts["--cores"]: if opts.cpus: opts.extra_exec_args += ["--cores=%d" % opts.cpus] @@ -308,6 +317,22 @@ def run(opts): return runner.run(opts, working_volume = working_volume, cpus = opts.cpus, memory = opts.memory) +def supports_benchmark_extended(opts) -> bool: + """ + Check if the runner's image or environment supports Snakemake's + ``--benchmark-extended`` option (requires Snakemake ≥8.11.0). + """ + if opts.__runner__ is docker: + return docker.image_supports(docker.IMAGE_FEATURE.benchmark_extended, opts.image) + if opts.__runner__ is aws_batch: + return docker.image_supports(docker.IMAGE_FEATURE.benchmark_extended, opts.image) + if opts.__runner__ is singularity: + return docker.image_supports(docker.IMAGE_FEATURE.benchmark_extended, remove_prefix("docker://", opts.image)) + if opts.__runner__ is conda: + return conda.env_supports(conda.ENV_FEATURE.benchmark_extended) + return False + + def assert_overlay_volumes_support(opts): """ Check that runtime overlays are supported, if given. diff --git a/nextstrain/cli/command/run.py b/nextstrain/cli/command/run.py index 1966a85b..3f0237d1 100644 --- a/nextstrain/cli/command/run.py +++ b/nextstrain/cli/command/run.py @@ -292,6 +292,9 @@ def run(opts): # Useful to see what's going on; see also 08ffc925. "--printshellcmds", + # Useful to have additional information in benchmark files. + "--benchmark-extended", + # In our experience,¹ it's rarely useful to fail on incomplete outputs # (Snakemake's default behaviour) instead of automatically regenerating # them. diff --git a/nextstrain/cli/runner/conda.py b/nextstrain/cli/runner/conda.py index 8f41583d..19377fa2 100644 --- a/nextstrain/cli/runner/conda.py +++ b/nextstrain/cli/runner/conda.py @@ -98,6 +98,7 @@ usable for the platform. """ +from enum import Enum import json import os import platform @@ -173,6 +174,27 @@ } +class ENV_FEATURE(Enum): + # --benchmark-extended was introduced in Snakemake 8.11.0. + benchmark_extended = "20250717T164942Z" + + +def env_supports(feature: ENV_FEATURE) -> bool: + """ + Test if the conda environment supports a *feature*, i.e. by version + comparison of the nextstrain-base package against the feature's first + release. + + If the nextstrain-base package is not found, it is assumed to not have + support for the feature. + """ + meta = package_meta(NEXTSTRAIN_BASE) + if not meta: + return False + version = meta.get("version", "0") + return parse_version_lax(version) >= parse_version_lax(feature.value) + + def register_arguments(parser) -> None: """ No-op. No arguments necessary. diff --git a/nextstrain/cli/runner/docker.py b/nextstrain/cli/runner/docker.py index ea5182b3..c46eefa1 100644 --- a/nextstrain/cli/runner/docker.py +++ b/nextstrain/cli/runner/docker.py @@ -116,6 +116,9 @@ class IMAGE_FEATURE(Enum): # file overwriting) in ZIP extraction. aws_batch_overlays = "build-20250321T184358Z" + # --benchmark-extended was introduced in Snakemake 8.11.0. + benchmark_extended = "build-20250717T164950Z" + def register_arguments(parser) -> None: # Docker development options diff --git a/nextstrain/cli/util.py b/nextstrain/cli/util.py index 91ec1537..331bb1bf 100644 --- a/nextstrain/cli/util.py +++ b/nextstrain/cli/util.py @@ -64,6 +64,7 @@ def colored(color, text): ) +# TODO: Use str.removeprefix/removesuffix once Python 3.9 is the minimum supported version. def remove_prefix(prefix, string): return re.sub('^' + re.escape(prefix), '', string)