2626from __future__ import annotations
2727
2828import argparse
29+ import re
2930import subprocess
3031import sys
3132from pathlib import Path
@@ -47,6 +48,7 @@ def _snakemake_cmd(
4748 state_dir : Path | None = None ,
4849 targets : list [str ] | None = None ,
4950 extra : list [str ] | None = None ,
51+ jobname_prefix : str | None = None ,
5052) -> list [str ]:
5153 """Build one snakemake invocation.
5254
@@ -66,6 +68,9 @@ def _snakemake_cmd(
6668 cmd += ["--directory" , str (state_dir .resolve ())]
6769 if profile :
6870 cmd += ["--workflow-profile" , str ((workflow_dir / profile ).resolve ())]
71+ if jobname_prefix :
72+ # A SLURM-executor setting, so only valid alongside the profile.
73+ cmd += ["--slurm-jobname-prefix" , jobname_prefix ]
6974 else :
7075 cmd += ["--cores" , str (cores ), "--rerun-triggers" , "mtime" ]
7176 if dry_run :
@@ -79,6 +84,29 @@ def _snakemake_cmd(
7984 return cmd
8085
8186
87+ def slurm_jobname_prefix (label : str ) -> str :
88+ """Sanitise *label* into a SLURM job-name prefix the executor accepts.
89+
90+ The SLURM executor names every job after its run UUID and refuses a
91+ ``--job-name`` in ``slurm_extra``, so a prefix is the only way to get
92+ something recognisable into ``squeue``. It becomes ``<prefix>_<uuid>``,
93+ which puts the readable part first -- the part that survives truncation
94+ in a queue listing.
95+
96+ The executor requires alphanumerics, underscores and hyphens only, at
97+ most 50 characters, and rejects the whole run otherwise.
98+
99+ Examples
100+ --------
101+ >>> slurm_jobname_prefix("nuclei_labels")
102+ 'pw-nuclei_labels'
103+ >>> slurm_jobname_prefix("cilia/v2 (test)")
104+ 'pw-cilia-v2--test-'
105+ """
106+ safe = re .sub (r"[^A-Za-z0-9_-]" , "-" , label )
107+ return f"pw-{ safe } " [:50 ]
108+
109+
82110def _run (cmd : list [str ], workflow_dir : Path ) -> int :
83111 print (f"[run_multi] $ { ' ' .join (cmd )} " , flush = True )
84112 return subprocess .run (cmd , cwd = workflow_dir ).returncode
@@ -235,6 +263,7 @@ def main() -> None:
235263 dry_run = args .dry_run ,
236264 state_dir = Path (work_dir ) / ".snakemake_convert" ,
237265 targets = [f"{ image_store } /zarr.json" ],
266+ jobname_prefix = slurm_jobname_prefix ("convert" ),
238267 ),
239268 workflow_dir ,
240269 )
@@ -287,6 +316,8 @@ def main() -> None:
287316 cores = args .cores ,
288317 dry_run = args .dry_run ,
289318 state_dir = Path (cfg ["work_dir" ]) / cfg ["label_name" ] / ".snakemake" ,
319+ # Names the config in squeue, so concurrent runs are tellable apart.
320+ jobname_prefix = slurm_jobname_prefix (cfg ["label_name" ]),
290321 )
291322 print (f"[run_multi] $ { ' ' .join (cmd )} " , flush = True )
292323 procs .append ((cfg_path .name , subprocess .Popen (cmd , cwd = workflow_dir )))
0 commit comments