From 1e6db9b24e5295ed6fb126d55960c12d36495fbd Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Wed, 18 Mar 2026 15:49:25 -0700 Subject: [PATCH 1/6] Move comment This only applies to --cores and --resources, not --local-storage-prefix. --- nextstrain/cli/command/build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nextstrain/cli/command/build.py b/nextstrain/cli/command/build.py index 0aa35d7a..b16b9b9d 100644 --- a/nextstrain/cli/command/build.py +++ b/nextstrain/cli/command/build.py @@ -243,12 +243,12 @@ 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": 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] From 1aa85f40fb54270aab60d55df579dc0885a01247 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Wed, 18 Mar 2026 15:52:30 -0700 Subject: [PATCH 2/6] Set default Snakemake args in default_exec_args This is more consistent with the run and view commands. --- nextstrain/cli/command/build.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nextstrain/cli/command/build.py b/nextstrain/cli/command/build.py index b16b9b9d..4367f486 100644 --- a/nextstrain/cli/command/build.py +++ b/nextstrain/cli/command/build.py @@ -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 @@ -244,6 +244,11 @@ def run(opts): if opts.exec == "snakemake": + opts.default_exec_args += [ + # Useful to see what's going on; see also 08ffc925. + "--printshellcmds", + ] + snakemake_opts = parse_snakemake_args(opts.extra_exec_args) # Automatically pass thru appropriate resource options to Snakemake to From 518b21457da4dec64bb2a5af717fb1ca807f5314 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Wed, 18 Mar 2026 16:51:28 -0700 Subject: [PATCH 3/6] Add compatibility checks to Conda runner Parallels IMAGE_FEATURE for Docker. --- nextstrain/cli/runner/conda.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/nextstrain/cli/runner/conda.py b/nextstrain/cli/runner/conda.py index 8f41583d..9ca51564 100644 --- a/nextstrain/cli/runner/conda.py +++ b/nextstrain/cli/runner/conda.py @@ -106,6 +106,7 @@ import sys import tarfile import traceback +from enum import Enum from pathlib import Path, PurePosixPath from tempfile import TemporaryFile from typing import IO, Iterable, List, NamedTuple, Optional, cast @@ -173,6 +174,26 @@ } +class ENV_FEATURE(Enum): + pass + + +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. From cbe4f2476634e8c86335eb243db6e72f8f12fea5 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Thu, 19 Mar 2026 12:57:32 -0700 Subject: [PATCH 4/6] Add helper function for Singularity default image Preparing for reuse. --- nextstrain/cli/runner/__init__.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/nextstrain/cli/runner/__init__.py b/nextstrain/cli/runner/__init__.py index 779d7cca..402bdad6 100644 --- a/nextstrain/cli/runner/__init__.py +++ b/nextstrain/cli/runner/__init__.py @@ -239,10 +239,7 @@ def run(opts: Options, working_volume: NamedVolume = None, extra_env: Env = {}, supports these runtimes with `nextstrain check-setup`. """) - # Account for potentially different defaults for --image depending on the - # selected runner. - if opts.__runner__ is singularity and opts.image is docker.DEFAULT_IMAGE: - opts.image = singularity.DEFAULT_IMAGE + opts.image = configured_image(opts) if envdirs := os.environ.get("NEXTSTRAIN_RUNTIME_ENVDIRS"): try: @@ -277,3 +274,15 @@ def replace_ellipsis(items, elided_items): y for x in items for y in (elided_items if x is ... else [x]) ] + + +def configured_image(opts: Options) -> str: + """ + Return the effective image for the selected runner. + """ + # Account for potentially different defaults for --image depending on the + # selected runner. + if opts.__runner__ is singularity and opts.image is docker.DEFAULT_IMAGE: + return singularity.DEFAULT_IMAGE + + return opts.image From 657e01adc395a5f5db5f5b961fb689110abd0da3 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Wed, 18 Mar 2026 15:59:22 -0700 Subject: [PATCH 5/6] Run Snakemake with --benchmark-extended MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commands used to find compatible versions for Docker¹ and Conda²: curl -s "https://registry.hub.docker.com/v2/repositories/nextstrain/base/tags?page_size=1000" \ | jq -r '.results[].name | select(startswith("build-20250717"))' micromamba search -c nextstrain "nextstrain-base >20250716,<20250718" ¹ https://github.com/nextstrain/docker-base/commit/0d9577d5457edf962666430ba5f8e164b0b9cf0c ² https://github.com/nextstrain/conda-base/commit/0d7a87f7aede2f568121929087b76eecf29fdc76 --- CHANGES.md | 5 +++++ doc/changes.md | 5 +++++ nextstrain/cli/command/build.py | 23 ++++++++++++++++++++++- nextstrain/cli/command/run.py | 4 ++++ nextstrain/cli/runner/conda.py | 3 ++- nextstrain/cli/runner/docker.py | 3 +++ 6 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d96eaead..52055703 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,11 @@ development source code and as such may not be routinely kept up to date. ([#501](https://github.com/nextstrain/cli/pull/501)) +* `nextstrain build` and `nextstrain run` now run Snakemake with the + `--benchmark-extended` option for more detailed benchmark files when used with + recent versions of managed runtimes. + ([#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..6772a9b4 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -28,6 +28,11 @@ development source code and as such may not be routinely kept up to date. ([#501](https://github.com/nextstrain/cli/pull/501)) +* `nextstrain build` and `nextstrain run` now run Snakemake with the + `--benchmark-extended` option for more detailed benchmark files when used with + recent versions of managed runtimes. + ([#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 4367f486..c395e112 100644 --- a/nextstrain/cli/command/build.py +++ b/nextstrain/cli/command/build.py @@ -24,7 +24,7 @@ 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 ..runner import docker, conda, singularity, aws_batch from ..util import byte_quantity, runner_name, split_image_name, warn from ..volume import NamedVolume @@ -247,6 +247,10 @@ def run(opts): 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) @@ -448,6 +452,23 @@ def pathogen_volumes(directory: Path, *, name = "build") -> Tuple[NamedVolume, N return build_volume, working_volume +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__ in (docker, aws_batch, singularity): + image = runner.configured_image(opts) + if opts.__runner__ is singularity: + image = singularity.docker_image_name(image) + return docker.image_supports(docker.IMAGE_FEATURE.benchmark_extended, image) + + if opts.__runner__ is conda: + return conda.env_supports(conda.ENV_FEATURE.benchmark_extended) + + return False + + def parse_snakemake_args(args): """ Inspects a tiny subset of Snakemake's CLI arguments in order to determine diff --git a/nextstrain/cli/command/run.py b/nextstrain/cli/command/run.py index 1966a85b..73d2de45 100644 --- a/nextstrain/cli/command/run.py +++ b/nextstrain/cli/command/run.py @@ -292,6 +292,10 @@ def run(opts): # Useful to see what's going on; see also 08ffc925. "--printshellcmds", + # Useful to have additional information in benchmark files. + *(["--benchmark-extended"] + if build.supports_benchmark_extended(opts) else []), + # 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 9ca51564..29859606 100644 --- a/nextstrain/cli/runner/conda.py +++ b/nextstrain/cli/runner/conda.py @@ -175,7 +175,8 @@ class ENV_FEATURE(Enum): - pass + # --benchmark-extended was introduced in Snakemake 8.11.0. + benchmark_extended = "20250717T164942Z" def env_supports(feature: ENV_FEATURE) -> bool: 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 From 6724e91002a2b6d2cb71048737626b4345005b38 Mon Sep 17 00:00:00 2001 From: Victor Lin Date: Thu, 19 Mar 2026 10:43:32 -0700 Subject: [PATCH 6/6] Note future alternatives for remove_prefix/suffix --- nextstrain/cli/util.py | 1 + 1 file changed, 1 insertion(+) 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)