| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package NBI::Launcher; |
|
2
|
|
|
|
|
|
|
#ABSTRACT: Base class for nbilaunch tool wrappers |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# NBI::Launcher - Declarative base class for HPC tool launchers. |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# DESCRIPTION: |
|
7
|
|
|
|
|
|
|
# Every tool wrapper inherits from this class. Subclasses provide: |
|
8
|
|
|
|
|
|
|
# - A constructor that calls SUPER::new() with the launcher spec |
|
9
|
|
|
|
|
|
|
# - make_command(%args) - the tool invocation string (only override needed |
|
10
|
|
|
|
|
|
|
# for most tools) |
|
11
|
|
|
|
|
|
|
# |
|
12
|
|
|
|
|
|
|
# The base class provides everything else: |
|
13
|
|
|
|
|
|
|
# - Spec storage and introspection (arg_spec, input_mode, sample_name) |
|
14
|
|
|
|
|
|
|
# - Input / param / output validation |
|
15
|
|
|
|
|
|
|
# - Shell script generation (generate_script) |
|
16
|
|
|
|
|
|
|
# - NBI::Job construction and NBI::Manifest creation (build) |
|
17
|
|
|
|
|
|
|
# |
|
18
|
|
|
|
|
|
|
# RELATIONSHIPS: |
|
19
|
|
|
|
|
|
|
# - Subclasses live in NBI::Launcher::*, ./launchers/, or ~/.nbi/launchers/. |
|
20
|
|
|
|
|
|
|
# - build() returns (NBI::Job, NBI::Manifest) - consumed by bin/nbilaunch. |
|
21
|
|
|
|
|
|
|
# - NBI::Pipeline wraps multiple NBI::Job objects for multi-step launchers. |
|
22
|
|
|
|
|
|
|
# |
|
23
|
|
|
|
|
|
|
|
|
24
|
2
|
|
|
2
|
|
239168
|
use 5.012; |
|
|
2
|
|
|
|
|
6
|
|
|
25
|
2
|
|
|
2
|
|
7
|
use strict; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
33
|
|
|
26
|
2
|
|
|
2
|
|
9
|
use warnings; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
128
|
|
|
27
|
2
|
|
|
2
|
|
9
|
use Carp qw(confess croak); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
197
|
|
|
28
|
2
|
|
|
2
|
|
16
|
use File::Basename qw(basename); |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
125
|
|
|
29
|
2
|
|
|
2
|
|
9
|
use Cwd qw(realpath); |
|
|
2
|
|
|
|
|
8
|
|
|
|
2
|
|
|
|
|
68
|
|
|
30
|
2
|
|
|
2
|
|
846
|
use POSIX qw(strftime); |
|
|
2
|
|
|
|
|
11383
|
|
|
|
2
|
|
|
|
|
10
|
|
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
$NBI::Launcher::VERSION = $NBI::Slurm::VERSION; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# ── Constructor ─────────────────────────────────────────────────────────────── |
|
35
|
|
|
|
|
|
|
# All parameters are named (not the -param style used by NBI::Opts/NBI::Job). |
|
36
|
|
|
|
|
|
|
# |
|
37
|
|
|
|
|
|
|
# Required: name, activate, inputs, outputs, outdir |
|
38
|
|
|
|
|
|
|
# Optional: description, version, slurm_defaults, params, scratch, success_check |
|
39
|
|
|
|
|
|
|
sub new { |
|
40
|
10
|
|
|
10
|
1
|
1954
|
my ($class, %args) = @_; |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# ── Validate activate spec ──────────────────────────────────────────────── |
|
43
|
|
|
|
|
|
|
my $activate = $args{activate} |
|
44
|
10
|
100
|
|
|
|
127
|
or confess "ERROR NBI::Launcher: 'activate' is required\n"; |
|
45
|
9
|
50
|
|
|
|
20
|
ref $activate eq 'HASH' |
|
46
|
|
|
|
|
|
|
or confess "ERROR NBI::Launcher: 'activate' must be a hashref\n"; |
|
47
|
9
|
|
|
|
|
18
|
my @act_keys = keys %$activate; |
|
48
|
|
|
|
|
|
|
confess "ERROR NBI::Launcher: 'activate' must have exactly one key (module|singularity|conda), got: @act_keys\n" |
|
49
|
9
|
100
|
100
|
|
|
240
|
unless @act_keys == 1 && grep { /^(module|singularity|conda)$/ } @act_keys; |
|
|
8
|
|
|
|
|
156
|
|
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# ── Validate slurm_defaults ─────────────────────────────────────────────── |
|
52
|
7
|
|
100
|
|
|
19
|
my $slurm_defaults = $args{slurm_defaults} // {}; |
|
53
|
7
|
|
|
|
|
16
|
for my $k (keys %$slurm_defaults) { |
|
54
|
8
|
100
|
|
|
|
133
|
confess "ERROR NBI::Launcher: unknown slurm_defaults key '$k'\n" |
|
55
|
|
|
|
|
|
|
unless $k =~ /^(queue|threads|memory|runtime)$/; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
6
|
|
|
|
|
10
|
my $self = bless {}, $class; |
|
59
|
|
|
|
|
|
|
|
|
60
|
6
|
100
|
|
|
|
127
|
$self->{name} = $args{name} or confess "ERROR NBI::Launcher: 'name' is required\n"; |
|
61
|
5
|
|
100
|
|
|
14
|
$self->{description} = $args{description} // ''; |
|
62
|
5
|
|
100
|
|
|
12
|
$self->{version} = $args{version} // 'unknown'; |
|
63
|
5
|
|
|
|
|
8
|
$self->{activate} = $activate; |
|
64
|
|
|
|
|
|
|
$self->{slurm_defaults} = { |
|
65
|
5
|
|
|
|
|
15
|
queue => 'qib-short', |
|
66
|
|
|
|
|
|
|
threads => 1, |
|
67
|
|
|
|
|
|
|
memory => 4, # GB |
|
68
|
|
|
|
|
|
|
runtime => '01:00:00', |
|
69
|
|
|
|
|
|
|
%$slurm_defaults, # caller overrides |
|
70
|
|
|
|
|
|
|
}; |
|
71
|
5
|
|
50
|
|
|
12
|
$self->{inputs} = $args{inputs} // []; |
|
72
|
5
|
|
50
|
|
|
12
|
$self->{params} = $args{params} // []; |
|
73
|
5
|
|
50
|
|
|
8
|
$self->{outputs} = $args{outputs} // []; |
|
74
|
5
|
|
50
|
|
|
10
|
$self->{outdir} = $args{outdir} // { flag => '--outdir', short => '-o', required => 1 }; |
|
75
|
5
|
|
100
|
|
|
12
|
$self->{scratch} = $args{scratch} // { use_tmpdir => 0, cleanup_on_failure => 0 }; |
|
76
|
5
|
|
|
|
|
9
|
$self->{success_check} = $args{success_check}; # optional coderef |
|
77
|
|
|
|
|
|
|
|
|
78
|
5
|
|
|
|
|
19
|
return $self; |
|
79
|
|
|
|
|
|
|
} |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# ── activation_lines() ──────────────────────────────────────────────────────── |
|
82
|
|
|
|
|
|
|
# Returns the shell snippet that loads the tool environment. |
|
83
|
|
|
|
|
|
|
# For singularity: returns "" because the prefix goes into make_command() |
|
84
|
|
|
|
|
|
|
# via singularity_prefix(). |
|
85
|
|
|
|
|
|
|
sub activation_lines { |
|
86
|
5
|
|
|
5
|
1
|
14
|
my ($self) = @_; |
|
87
|
5
|
|
|
|
|
5
|
my ($type, $value) = each %{ $self->{activate} }; |
|
|
5
|
|
|
|
|
11
|
|
|
88
|
|
|
|
|
|
|
|
|
89
|
5
|
100
|
|
|
|
13
|
if ($type eq 'module') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
90
|
3
|
|
|
|
|
20
|
return "module load $value\n"; |
|
91
|
|
|
|
|
|
|
} elsif ($type eq 'conda') { |
|
92
|
|
|
|
|
|
|
# Use 'source activate' for broadest HPC compatibility. |
|
93
|
|
|
|
|
|
|
# On systems with conda >= 4.4, 'conda activate' also works. |
|
94
|
1
|
|
|
|
|
6
|
return "source activate $value\n"; |
|
95
|
|
|
|
|
|
|
} elsif ($type eq 'singularity') { |
|
96
|
|
|
|
|
|
|
# Singularity prefix is applied per-command in make_command() |
|
97
|
|
|
|
|
|
|
# via singularity_prefix(). Nothing needed here. |
|
98
|
1
|
|
|
|
|
5
|
return ''; |
|
99
|
|
|
|
|
|
|
} |
|
100
|
0
|
|
|
|
|
0
|
return ''; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# ── singularity_prefix() ────────────────────────────────────────────────────── |
|
104
|
|
|
|
|
|
|
# Helper for make_command() overrides: returns "singularity exec $image " |
|
105
|
|
|
|
|
|
|
# when the launcher uses singularity activation, empty string otherwise. |
|
106
|
|
|
|
|
|
|
# Subclass make_command() calls this and prepends it to the tool invocation. |
|
107
|
|
|
|
|
|
|
sub singularity_prefix { |
|
108
|
2
|
|
|
2
|
1
|
3
|
my ($self) = @_; |
|
109
|
2
|
|
|
|
|
4
|
my $img = $self->{activate}{singularity}; |
|
110
|
2
|
100
|
|
|
|
9
|
return defined $img ? "singularity exec $img " : ''; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# ── sample_name(%args) ──────────────────────────────────────────────────────── |
|
114
|
|
|
|
|
|
|
# Derives the sample name from the first file-type required input (usually r1). |
|
115
|
|
|
|
|
|
|
# Strips known FASTQ extensions in order: .gz .fastq .fq _R1 _R2 _1 _2 |
|
116
|
|
|
|
|
|
|
# Override with --sample-name on the nbilaunch command line. |
|
117
|
|
|
|
|
|
|
sub sample_name { |
|
118
|
6
|
|
|
6
|
1
|
16
|
my ($self, %args) = @_; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# Explicit override takes priority |
|
121
|
6
|
100
|
|
|
|
21
|
return $args{sample_name} if defined $args{sample_name}; |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Find the first required file-type input |
|
124
|
5
|
|
|
|
|
5
|
my $source; |
|
125
|
5
|
|
|
|
|
5
|
for my $inp (@{ $self->{inputs} }) { |
|
|
5
|
|
|
|
|
11
|
|
|
126
|
5
|
50
|
50
|
|
|
43
|
if (($inp->{type} // '') eq 'file' && ($inp->{required} // 0)) { |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
127
|
5
|
|
|
|
|
10
|
$source = $args{ $inp->{name} }; |
|
128
|
5
|
50
|
|
|
|
9
|
last if defined $source; |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
} |
|
131
|
5
|
50
|
|
|
|
9
|
confess "ERROR NBI::Launcher ($self->{name}): cannot derive sample name - no file input found\n" |
|
132
|
|
|
|
|
|
|
unless defined $source; |
|
133
|
|
|
|
|
|
|
|
|
134
|
5
|
|
|
|
|
176
|
my $name = basename($source); |
|
135
|
|
|
|
|
|
|
# Strip extensions from right to left |
|
136
|
5
|
|
|
|
|
19
|
$name =~ s/\.gz$//i; |
|
137
|
5
|
|
|
|
|
16
|
$name =~ s/\.(fastq|fq)$//i; |
|
138
|
5
|
|
|
|
|
10
|
$name =~ s/_R[12]$//; |
|
139
|
5
|
|
|
|
|
7
|
$name =~ s/_[12]$//; |
|
140
|
5
|
|
|
|
|
19
|
return $name; |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# ── input_mode(%args) ──────────────────────────────────────────────────────── |
|
144
|
|
|
|
|
|
|
# Returns "paired" if both r1 and r2 are defined, "single" otherwise. |
|
145
|
|
|
|
|
|
|
# Subclasses may override for tools with different pairing logic. |
|
146
|
|
|
|
|
|
|
sub input_mode { |
|
147
|
2
|
|
|
2
|
1
|
446
|
my ($self, %args) = @_; |
|
148
|
2
|
100
|
66
|
|
|
18
|
return (defined $args{r1} && defined $args{r2}) ? 'paired' : 'single'; |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# ── arg_spec() ──────────────────────────────────────────────────────────────── |
|
152
|
|
|
|
|
|
|
# Returns the full CLI surface of this launcher for nbilaunch to use when |
|
153
|
|
|
|
|
|
|
# generating --help text and parsing command-line arguments. |
|
154
|
|
|
|
|
|
|
# slurm_sync params are excluded (they are derived, not user-settable). |
|
155
|
|
|
|
|
|
|
sub arg_spec { |
|
156
|
2
|
|
|
2
|
1
|
893
|
my ($self) = @_; |
|
157
|
|
|
|
|
|
|
return { |
|
158
|
|
|
|
|
|
|
name => $self->{name}, |
|
159
|
|
|
|
|
|
|
description => $self->{description}, |
|
160
|
|
|
|
|
|
|
version => $self->{version}, |
|
161
|
|
|
|
|
|
|
activate => $self->{activate}, |
|
162
|
4
|
|
|
|
|
10
|
inputs => [ grep { !$_->{slurm_sync} } @{ $self->{inputs} } ], |
|
|
2
|
|
|
|
|
5
|
|
|
163
|
5
|
|
|
|
|
18
|
params => [ grep { !$_->{slurm_sync} } @{ $self->{params} } ], |
|
|
2
|
|
|
|
|
5
|
|
|
164
|
|
|
|
|
|
|
outputs => $self->{outputs}, |
|
165
|
|
|
|
|
|
|
outdir => $self->{outdir}, |
|
166
|
|
|
|
|
|
|
slurm_defaults => $self->{slurm_defaults}, |
|
167
|
2
|
|
|
|
|
6
|
}; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# ── validate(%args) ─────────────────────────────────────────────────────────── |
|
171
|
|
|
|
|
|
|
# Dies with a helpful message if required inputs/params are missing or if |
|
172
|
|
|
|
|
|
|
# file/dir values do not exist on disk. |
|
173
|
|
|
|
|
|
|
sub validate { |
|
174
|
7
|
|
|
7
|
1
|
5527
|
my ($self, %args) = @_; |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Check inputs |
|
177
|
7
|
|
|
|
|
10
|
for my $inp (@{ $self->{inputs} }) { |
|
|
7
|
|
|
|
|
16
|
|
|
178
|
12
|
50
|
|
|
|
31
|
next if $inp->{slurm_sync}; |
|
179
|
12
|
|
|
|
|
15
|
my $name = $inp->{name}; |
|
180
|
12
|
|
|
|
|
16
|
my $val = $args{$name}; |
|
181
|
|
|
|
|
|
|
|
|
182
|
12
|
100
|
100
|
|
|
40
|
if ($inp->{required} && !defined $val) { |
|
183
|
1
|
|
|
|
|
150
|
confess "ERROR NBI::Launcher ($self->{name}): missing required input '--$name'\n"; |
|
184
|
|
|
|
|
|
|
} |
|
185
|
11
|
100
|
|
|
|
20
|
next unless defined $val; |
|
186
|
|
|
|
|
|
|
|
|
187
|
6
|
|
50
|
|
|
14
|
my $type = $inp->{type} // 'string'; |
|
188
|
6
|
100
|
66
|
|
|
248
|
if ($type eq 'file' && !-f $val) { |
|
|
|
50
|
33
|
|
|
|
|
|
189
|
1
|
|
|
|
|
141
|
confess "ERROR NBI::Launcher ($self->{name}): input '$name' - file not found: $val\n"; |
|
190
|
|
|
|
|
|
|
} elsif ($type eq 'dir' && !-d $val) { |
|
191
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): input '$name' - directory not found: $val\n"; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# Check params (with default_env and default fallback) |
|
196
|
5
|
|
|
|
|
8
|
for my $p (@{ $self->{params} }) { |
|
|
5
|
|
|
|
|
32
|
|
|
197
|
11
|
100
|
|
|
|
27
|
next if $p->{slurm_sync}; |
|
198
|
7
|
|
|
|
|
11
|
my $name = $p->{name}; |
|
199
|
7
|
|
|
|
|
17
|
my $val = $args{$name}; |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Try default_env, then default |
|
202
|
7
|
100
|
100
|
|
|
22
|
if (!defined $val && $p->{default_env}) { |
|
203
|
4
|
|
|
|
|
13
|
$val = $ENV{ $p->{default_env} }; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
7
|
|
66
|
|
|
18
|
$val //= $p->{default}; |
|
206
|
|
|
|
|
|
|
|
|
207
|
7
|
50
|
66
|
|
|
25
|
if ($p->{required} && !defined $val) { |
|
208
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): missing required param '--$name'\n"; |
|
209
|
|
|
|
|
|
|
} |
|
210
|
7
|
50
|
|
|
|
12
|
next unless defined $val; |
|
211
|
|
|
|
|
|
|
|
|
212
|
7
|
|
50
|
|
|
15
|
my $type = $p->{type} // 'string'; |
|
213
|
7
|
50
|
33
|
|
|
201
|
if ($type eq 'int' && $val !~ /^\d+$/) { |
|
|
|
50
|
66
|
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): param '$name' must be an integer, got: $val\n"; |
|
215
|
|
|
|
|
|
|
} elsif ($type eq 'float' && $val !~ /^[\d.]+$/) { |
|
216
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): param '$name' must be a number, got: $val\n"; |
|
217
|
|
|
|
|
|
|
} elsif ($type eq 'file' && !-f $val) { |
|
218
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): param '$name' - file not found: $val\n"; |
|
219
|
|
|
|
|
|
|
} elsif ($type eq 'dir' && !-d $val) { |
|
220
|
1
|
|
|
|
|
284
|
confess "ERROR NBI::Launcher ($self->{name}): param '$name' - directory not found: $val\n"; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Check outdir is provided |
|
225
|
4
|
100
|
66
|
|
|
19
|
if ($self->{outdir}{required} && !defined $args{outdir}) { |
|
226
|
1
|
|
|
|
|
137
|
confess "ERROR NBI::Launcher ($self->{name}): missing required '--outdir'\n"; |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
3
|
|
|
|
|
11
|
return 1; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# ── make_command(%args) ─────────────────────────────────────────────────────── |
|
233
|
|
|
|
|
|
|
# Returns the tool invocation string to embed in the job script. |
|
234
|
|
|
|
|
|
|
# Subclasses SHOULD override this method. |
|
235
|
|
|
|
|
|
|
# The default implementation raises an error - every launcher needs a command. |
|
236
|
|
|
|
|
|
|
# |
|
237
|
|
|
|
|
|
|
# %args contains all resolved inputs, params, and derived keys: |
|
238
|
|
|
|
|
|
|
# $args{sample} - derived sample name |
|
239
|
|
|
|
|
|
|
# $args{threads} - from slurm_sync or slurm_defaults |
|
240
|
|
|
|
|
|
|
# For scratch paths, use literal \$SCRATCH (shell variable). |
|
241
|
|
|
|
|
|
|
sub make_command { |
|
242
|
0
|
|
|
0
|
|
0
|
my ($self, %args) = @_; |
|
243
|
0
|
|
|
|
|
0
|
confess "ERROR NBI::Launcher ($self->{name}): make_command() not implemented\n"; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# ── generate_script(%args) ─────────────────────────────────────────────────── |
|
247
|
|
|
|
|
|
|
# Assembles the script body (everything after the #SBATCH header generated by |
|
248
|
|
|
|
|
|
|
# NBI::Opts->header()). Returns a single string. |
|
249
|
|
|
|
|
|
|
# |
|
250
|
|
|
|
|
|
|
# Sections (in order): |
|
251
|
|
|
|
|
|
|
# 1. set -euo pipefail + metadata comment |
|
252
|
|
|
|
|
|
|
# 2. Manifest update shell functions + ERR trap |
|
253
|
|
|
|
|
|
|
# 3. Activation (module load / conda / empty for singularity) |
|
254
|
|
|
|
|
|
|
# 4. SAMPLE, OUTDIR, SCRATCH variables |
|
255
|
|
|
|
|
|
|
# 5. EXIT trap (scratch cleanup) |
|
256
|
|
|
|
|
|
|
# 6. Tool command from make_command() |
|
257
|
|
|
|
|
|
|
# 7. Required-output validation |
|
258
|
|
|
|
|
|
|
# 8. Promote from scratch to outdir + success update |
|
259
|
|
|
|
|
|
|
sub generate_script { |
|
260
|
2
|
|
|
2
|
1
|
271
|
my ($self, %args) = @_; |
|
261
|
|
|
|
|
|
|
|
|
262
|
2
|
|
|
|
|
7
|
my $tool = $self->{name}; |
|
263
|
2
|
|
|
|
|
4
|
my $version = $self->{version}; |
|
264
|
2
|
50
|
|
|
|
7
|
my $sample = $args{sample} or confess "generate_script: 'sample' required\n"; |
|
265
|
2
|
50
|
|
|
|
13
|
my $outdir = $args{outdir} or confess "generate_script: 'outdir' required\n"; |
|
266
|
2
|
|
33
|
|
|
4
|
my $manifest_rel = $args{manifest_path} // ".nbilaunch/$sample.manifest.json"; |
|
267
|
2
|
|
|
|
|
68
|
my $submitted_at = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()); |
|
268
|
|
|
|
|
|
|
|
|
269
|
2
|
|
|
|
|
15
|
my $launcher_class = ref($self); |
|
270
|
2
|
|
50
|
|
|
15
|
my $nbi_version = $NBI::Slurm::VERSION // 'unknown'; |
|
271
|
2
|
|
50
|
|
|
55
|
my $nbi_l_version = $NBI::Launcher::VERSION // '0.1.0'; |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# ── Resolve %args for make_command ─────────────────────────────────────── |
|
274
|
|
|
|
|
|
|
# Apply default_env / default fallbacks for params before calling make_command |
|
275
|
2
|
|
|
|
|
15
|
my %cmd_args = %args; |
|
276
|
2
|
|
|
|
|
6
|
for my $p (@{ $self->{params} }) { |
|
|
2
|
|
|
|
|
11
|
|
|
277
|
5
|
|
|
|
|
9
|
my $name = $p->{name}; |
|
278
|
5
|
50
|
|
|
|
26
|
next if defined $cmd_args{$name}; |
|
279
|
0
|
0
|
0
|
|
|
0
|
if ($p->{default_env} && defined $ENV{ $p->{default_env} }) { |
|
|
|
0
|
|
|
|
|
|
|
280
|
0
|
|
|
|
|
0
|
$cmd_args{$name} = $ENV{ $p->{default_env} }; |
|
281
|
|
|
|
|
|
|
} elsif (defined $p->{default}) { |
|
282
|
0
|
|
|
|
|
0
|
$cmd_args{$name} = $p->{default}; |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
2
|
|
|
|
|
22
|
my $tool_command = $self->make_command(%cmd_args); |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# ── Output validation checks ────────────────────────────────────────────── |
|
289
|
2
|
|
|
|
|
6
|
my $validation = ''; |
|
290
|
2
|
|
|
|
|
3
|
for my $out (@{ $self->{outputs} }) { |
|
|
2
|
|
|
|
|
3
|
|
|
291
|
3
|
100
|
|
|
|
8
|
next unless $out->{required}; |
|
292
|
2
|
|
50
|
|
|
19
|
my $pat = $out->{pattern} // next; |
|
293
|
|
|
|
|
|
|
# Substitute {sample} with shell variable reference |
|
294
|
2
|
|
|
|
|
11
|
(my $shell_pat = $pat) =~ s/\{sample\}/\${SAMPLE}/g; |
|
295
|
2
|
|
|
|
|
6
|
$validation .= <<" BASH"; |
|
296
|
|
|
|
|
|
|
if [[ ! -s "\$SCRATCH/$shell_pat" ]]; then |
|
297
|
|
|
|
|
|
|
echo "[nbilaunch] ERROR: required output not found or empty: $shell_pat" >&2 |
|
298
|
|
|
|
|
|
|
exit 1 |
|
299
|
|
|
|
|
|
|
fi |
|
300
|
|
|
|
|
|
|
BASH |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
2
|
|
|
|
|
9
|
my $activation = $self->activation_lines(); |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# ── Scratch setup ───────────────────────────────────────────────────────── |
|
306
|
|
|
|
|
|
|
# Priority: explicit scratch_dir arg > $TMPDIR (use_tmpdir) > /tmp |
|
307
|
2
|
|
50
|
|
|
5
|
my $use_tmpdir = $self->{scratch}{use_tmpdir} // 0; |
|
308
|
|
|
|
|
|
|
my $scratch_base = defined $args{scratch_dir} ? $args{scratch_dir} |
|
309
|
2
|
50
|
|
|
|
9
|
: $use_tmpdir ? '${TMPDIR:-/tmp}' |
|
|
|
50
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
: '/tmp'; |
|
311
|
2
|
|
|
|
|
4
|
my $scratch_init = qq{SCRATCH=\$(mktemp -d "$scratch_base/${tool}_XXXXXXXX")}; |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# ── Assemble script ─────────────────────────────────────────────────────── |
|
314
|
2
|
|
|
|
|
2
|
my $sep = '# ' . '─' x 74; |
|
315
|
|
|
|
|
|
|
|
|
316
|
2
|
|
|
|
|
18
|
my $script = <<"SCRIPT"; |
|
317
|
|
|
|
|
|
|
set -euo pipefail |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
$sep |
|
320
|
|
|
|
|
|
|
# Generated by nbilaunch v${nbi_l_version} / NBI::Slurm v${nbi_version} |
|
321
|
|
|
|
|
|
|
# Tool: $tool v$version |
|
322
|
|
|
|
|
|
|
# Launcher: $launcher_class |
|
323
|
|
|
|
|
|
|
# Submitted: $submitted_at |
|
324
|
|
|
|
|
|
|
# Manifest: $manifest_rel |
|
325
|
|
|
|
|
|
|
$sep |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
$sep |
|
328
|
|
|
|
|
|
|
# Runtime variables - defined first so traps and manifest can reference them |
|
329
|
|
|
|
|
|
|
SAMPLE="$sample" |
|
330
|
|
|
|
|
|
|
OUTDIR="\$(realpath "$outdir" 2>/dev/null || echo "$outdir")" |
|
331
|
|
|
|
|
|
|
$scratch_init |
|
332
|
|
|
|
|
|
|
MANIFEST="\$OUTDIR/.nbilaunch/$sample.manifest.json" |
|
333
|
|
|
|
|
|
|
$sep |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
$sep |
|
336
|
|
|
|
|
|
|
# Manifest update - called by ERR trap (failure) and at end (success). |
|
337
|
|
|
|
|
|
|
# Uses a perl one-liner so there is no jq dependency on the HPC. |
|
338
|
|
|
|
|
|
|
_nbi_manifest_update() { |
|
339
|
|
|
|
|
|
|
local status="\$1" exit_code="\$2" completed_at |
|
340
|
|
|
|
|
|
|
completed_at=\$(date -u +"%Y-%m-%dT%H:%M:%SZ") |
|
341
|
|
|
|
|
|
|
perl -i -0777 -pe " |
|
342
|
|
|
|
|
|
|
s{\\\"status\\\":\\\\s*\\\"[^\\\"]+\\\"}{\\\"status\\\": \\\"\$status\\\"}; |
|
343
|
|
|
|
|
|
|
s{\\\"exit_code\\\":\\\\s*null}{\\\"exit_code\\\": \$exit_code}; |
|
344
|
|
|
|
|
|
|
s{\\\"completed_at\\\":\\\\s*null}{\\\"completed_at\\\": \\\"\$completed_at\\\"}; |
|
345
|
|
|
|
|
|
|
" "\$MANIFEST" |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
trap '_nbi_manifest_update failure \$?' ERR |
|
349
|
|
|
|
|
|
|
$sep |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
$sep |
|
352
|
|
|
|
|
|
|
# Scratch cleanup on exit (runs even on success - scratch is empty after mv) |
|
353
|
|
|
|
|
|
|
trap 'rm -rf "\$SCRATCH"' EXIT |
|
354
|
|
|
|
|
|
|
$sep |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
SCRIPT |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
# Activation section (empty for singularity) |
|
359
|
2
|
50
|
|
|
|
4
|
if ($activation) { |
|
360
|
2
|
|
|
|
|
15
|
$script .= <<"SCRIPT"; |
|
361
|
|
|
|
|
|
|
$sep |
|
362
|
|
|
|
|
|
|
# Environment activation |
|
363
|
|
|
|
|
|
|
$activation$sep |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
SCRIPT |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
|
|
368
|
2
|
|
|
|
|
5
|
$script .= <<"SCRIPT"; |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
$sep |
|
371
|
|
|
|
|
|
|
# Tool command |
|
372
|
|
|
|
|
|
|
$tool_command |
|
373
|
|
|
|
|
|
|
$sep |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
SCRIPT |
|
376
|
|
|
|
|
|
|
|
|
377
|
2
|
50
|
|
|
|
5
|
if ($validation) { |
|
378
|
2
|
|
|
|
|
4
|
$script .= <<"SCRIPT"; |
|
379
|
|
|
|
|
|
|
$sep |
|
380
|
|
|
|
|
|
|
# Output validation |
|
381
|
|
|
|
|
|
|
${validation}$sep |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
SCRIPT |
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
|
|
386
|
2
|
|
|
|
|
6
|
$script .= <<"SCRIPT"; |
|
387
|
|
|
|
|
|
|
$sep |
|
388
|
|
|
|
|
|
|
# Promote outputs from scratch to outdir and record success |
|
389
|
|
|
|
|
|
|
mkdir -p "\$OUTDIR" "\$OUTDIR/.nbilaunch" |
|
390
|
|
|
|
|
|
|
mv "\$SCRATCH"/* "\$OUTDIR"/ |
|
391
|
|
|
|
|
|
|
_nbi_manifest_update success 0 |
|
392
|
|
|
|
|
|
|
echo "[nbilaunch] Done. Outputs in: \$OUTDIR" |
|
393
|
|
|
|
|
|
|
$sep |
|
394
|
|
|
|
|
|
|
SCRIPT |
|
395
|
|
|
|
|
|
|
|
|
396
|
2
|
|
|
|
|
23
|
return $script; |
|
397
|
|
|
|
|
|
|
} |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# ── _resolve_args(%args) ────────────────────────────────────────────────────── |
|
400
|
|
|
|
|
|
|
# Internal: apply default_env and default fallbacks, inject slurm_sync values, |
|
401
|
|
|
|
|
|
|
# and resolve absolute paths. Returns the resolved %args hash. |
|
402
|
|
|
|
|
|
|
sub _resolve_args { |
|
403
|
1
|
|
|
1
|
|
5
|
my ($self, %args) = @_; |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Apply param defaults |
|
406
|
1
|
|
|
|
|
2
|
for my $p (@{ $self->{params} }) { |
|
|
1
|
|
|
|
|
2
|
|
|
407
|
3
|
|
|
|
|
5
|
my $name = $p->{name}; |
|
408
|
3
|
100
|
|
|
|
5
|
next if defined $args{$name}; |
|
409
|
2
|
100
|
66
|
|
|
9
|
if ($p->{default_env} && defined $ENV{ $p->{default_env} }) { |
|
|
|
50
|
|
|
|
|
|
|
410
|
1
|
|
|
|
|
3
|
$args{$name} = $ENV{ $p->{default_env} }; |
|
411
|
|
|
|
|
|
|
} elsif (defined $p->{default}) { |
|
412
|
1
|
|
|
|
|
2
|
$args{$name} = $p->{default}; |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Resolve absolute paths for file/dir inputs and params |
|
417
|
1
|
|
|
|
|
2
|
for my $spec (@{ $self->{inputs} }, @{ $self->{params} }) { |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
2
|
|
|
418
|
5
|
|
|
|
|
9
|
my $name = $spec->{name}; |
|
419
|
5
|
100
|
|
|
|
6
|
next unless defined $args{$name}; |
|
420
|
4
|
|
50
|
|
|
8
|
my $type = $spec->{type} // ''; |
|
421
|
4
|
100
|
100
|
|
|
12
|
if ($type eq 'file' || $type eq 'dir') { |
|
422
|
2
|
|
33
|
|
|
42
|
$args{$name} = realpath($args{$name}) // $args{$name}; |
|
423
|
|
|
|
|
|
|
} |
|
424
|
|
|
|
|
|
|
} |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
# Absolute outdir |
|
427
|
1
|
50
|
|
|
|
2
|
if (defined $args{outdir}) { |
|
428
|
|
|
|
|
|
|
# realpath requires the path to exist; use abs_path fallback |
|
429
|
1
|
|
|
|
|
2
|
my $abs = eval { realpath($args{outdir}) }; |
|
|
1
|
|
|
|
|
58
|
|
|
430
|
1
|
50
|
|
|
|
6
|
$args{outdir} = $abs if defined $abs; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
1
|
|
|
|
|
8
|
return %args; |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# ── _runtime_to_hours($str) ─────────────────────────────────────────────────── |
|
437
|
|
|
|
|
|
|
# Convert HH:MM:SS or simple hour strings to decimal hours for NBI::Opts. |
|
438
|
|
|
|
|
|
|
sub _runtime_to_hours { |
|
439
|
1
|
|
|
1
|
|
2
|
my ($rt) = @_; |
|
440
|
1
|
50
|
|
|
|
8
|
if ($rt =~ /^(\d+):(\d+):(\d+)$/) { |
|
441
|
1
|
|
|
|
|
7
|
return $1 + $2 / 60 + $3 / 3600; |
|
442
|
|
|
|
|
|
|
} |
|
443
|
0
|
0
|
|
|
|
0
|
if ($rt =~ /^(\d+):(\d+)$/) { |
|
444
|
0
|
|
|
|
|
0
|
return $1 + $2 / 60; |
|
445
|
|
|
|
|
|
|
} |
|
446
|
0
|
0
|
|
|
|
0
|
if ($rt =~ /^(\d+)$/) { |
|
447
|
0
|
|
|
|
|
0
|
return $1; # bare integer = hours |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
# Fall back: try NBI::Opts internal parser pattern |
|
450
|
0
|
|
|
|
|
0
|
my $hours = 0; |
|
451
|
0
|
|
|
|
|
0
|
my $upper = uc $rt; |
|
452
|
0
|
|
|
|
|
0
|
while ($upper =~ /(\d+)([DHMS])/g) { |
|
453
|
0
|
|
|
|
|
0
|
my ($v, $u) = ($1, $2); |
|
454
|
0
|
0
|
|
|
|
0
|
$hours += $v * 24 if $u eq 'D'; |
|
455
|
0
|
0
|
|
|
|
0
|
$hours += $v if $u eq 'H'; |
|
456
|
0
|
0
|
|
|
|
0
|
$hours += $v / 60 if $u eq 'M'; |
|
457
|
0
|
0
|
|
|
|
0
|
$hours += $v / 3600 if $u eq 'S'; |
|
458
|
|
|
|
|
|
|
} |
|
459
|
0
|
|
0
|
|
|
0
|
return $hours || 1; |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# ── build(%args) ───────────────────────────────────────────────────────────── |
|
463
|
|
|
|
|
|
|
# The main entry point called by bin/nbilaunch. |
|
464
|
|
|
|
|
|
|
# |
|
465
|
|
|
|
|
|
|
# Validates inputs, resolves defaults, builds NBI::Job and NBI::Manifest. |
|
466
|
|
|
|
|
|
|
# Returns a two-element list: ($job, $manifest). |
|
467
|
|
|
|
|
|
|
# |
|
468
|
|
|
|
|
|
|
# %args keys: |
|
469
|
|
|
|
|
|
|
# All inputs/params by name (from nbilaunch arg parsing) |
|
470
|
|
|
|
|
|
|
# outdir - output directory (required) |
|
471
|
|
|
|
|
|
|
# sample_name - optional override for derived sample name |
|
472
|
|
|
|
|
|
|
# slurm_queue - override slurm_defaults{queue} |
|
473
|
|
|
|
|
|
|
# slurm_threads - override slurm_defaults{threads} |
|
474
|
|
|
|
|
|
|
# slurm_memory - override slurm_defaults{memory} (GB) |
|
475
|
|
|
|
|
|
|
# slurm_runtime - override slurm_defaults{runtime} (HH:MM:SS or hours) |
|
476
|
|
|
|
|
|
|
sub build { |
|
477
|
1
|
|
|
1
|
1
|
3
|
my ($self, %args) = @_; |
|
478
|
|
|
|
|
|
|
|
|
479
|
1
|
|
|
|
|
8
|
require NBI::Job; |
|
480
|
1
|
|
|
|
|
2
|
require NBI::Opts; |
|
481
|
1
|
|
|
|
|
634
|
require NBI::Manifest; |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# ── Resolve Slurm resource values ───────────────────────────────────────── |
|
484
|
1
|
|
33
|
|
|
16
|
my $queue = $args{slurm_queue} // $self->{slurm_defaults}{queue}; |
|
485
|
1
|
|
33
|
|
|
4
|
my $threads = $args{slurm_threads} // $self->{slurm_defaults}{threads}; |
|
486
|
1
|
|
33
|
|
|
5
|
my $mem_gb = $args{slurm_memory} // $self->{slurm_defaults}{memory}; |
|
487
|
1
|
|
33
|
|
|
6
|
my $runtime = $args{slurm_runtime} // $self->{slurm_defaults}{runtime}; |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# Inject slurm_sync params (e.g. threads param mirrors Slurm --cpus) |
|
490
|
1
|
|
|
|
|
1
|
for my $p (@{ $self->{params} }) { |
|
|
1
|
|
|
|
|
4
|
|
|
491
|
3
|
100
|
|
|
|
5
|
next unless $p->{slurm_sync}; |
|
492
|
1
|
50
|
|
|
|
4
|
if ($p->{slurm_sync} eq 'threads') { |
|
|
|
0
|
|
|
|
|
|
|
493
|
1
|
|
|
|
|
3
|
$args{ $p->{name} } = $threads; |
|
494
|
|
|
|
|
|
|
} elsif ($p->{slurm_sync} eq 'memory') { |
|
495
|
0
|
|
|
|
|
0
|
$args{ $p->{name} } = $mem_gb; |
|
496
|
|
|
|
|
|
|
} |
|
497
|
|
|
|
|
|
|
} |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# ── Resolve and validate args ───────────────────────────────────────────── |
|
500
|
1
|
|
|
|
|
10
|
%args = $self->_resolve_args(%args); |
|
501
|
1
|
|
|
|
|
5
|
$self->validate(%args); |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
# ── Derive sample name ──────────────────────────────────────────────────── |
|
504
|
1
|
|
|
|
|
9
|
my $sample = $self->sample_name(%args); |
|
505
|
1
|
|
|
|
|
2
|
$args{sample} = $sample; |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
# ── Paths ───────────────────────────────────────────────────────────────── |
|
508
|
1
|
|
|
|
|
1
|
my $outdir = $args{outdir}; |
|
509
|
1
|
|
|
|
|
2
|
my $nbi_dir = "$outdir/.nbilaunch"; |
|
510
|
1
|
|
|
|
|
2
|
my $job_name = "$self->{name}_$sample"; |
|
511
|
1
|
|
|
|
|
1
|
my $manifest_path = "$nbi_dir/$sample.manifest.json"; |
|
512
|
1
|
|
|
|
|
2
|
my $script_rel = ".nbilaunch/${job_name}.script.sh"; |
|
513
|
|
|
|
|
|
|
|
|
514
|
1
|
|
|
|
|
1
|
$args{manifest_path} = $manifest_path; |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# ── Generate script body ────────────────────────────────────────────────── |
|
517
|
1
|
|
|
|
|
7
|
my $script_body = $self->generate_script(%args); |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# ── Build NBI::Opts ─────────────────────────────────────────────────────── |
|
520
|
1
|
|
|
|
|
3
|
my $hours = _runtime_to_hours($runtime); |
|
521
|
1
|
|
|
|
|
2
|
my $mem_mb = $mem_gb * 1024; |
|
522
|
|
|
|
|
|
|
|
|
523
|
1
|
|
|
|
|
9
|
my $opts = NBI::Opts->new( |
|
524
|
|
|
|
|
|
|
-queue => $queue, |
|
525
|
|
|
|
|
|
|
-threads => $threads, |
|
526
|
|
|
|
|
|
|
-memory => $mem_mb, |
|
527
|
|
|
|
|
|
|
-time => $hours, |
|
528
|
|
|
|
|
|
|
-tmpdir => $nbi_dir, |
|
529
|
|
|
|
|
|
|
); |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
# ── Build NBI::Job ──────────────────────────────────────────────────────── |
|
532
|
1
|
|
|
|
|
8
|
my $job = NBI::Job->new( |
|
533
|
|
|
|
|
|
|
-name => $job_name, |
|
534
|
|
|
|
|
|
|
-command => $script_body, |
|
535
|
|
|
|
|
|
|
-opts => $opts, |
|
536
|
|
|
|
|
|
|
); |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
# Log/err go to provenance directory; %j is expanded by Slurm to the job ID |
|
539
|
1
|
|
|
|
|
3
|
$job->outputfile = "$nbi_dir/${job_name}.%j.log"; |
|
540
|
1
|
|
|
|
|
4
|
$job->errorfile = "$nbi_dir/${job_name}.%j.err"; |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# ── Collect resolved inputs/params/outputs for manifest ─────────────────── |
|
543
|
1
|
|
|
|
|
2
|
my %inp_snapshot; |
|
544
|
1
|
|
|
|
|
2
|
for my $inp (@{ $self->{inputs} }) { |
|
|
1
|
|
|
|
|
3
|
|
|
545
|
|
|
|
|
|
|
$inp_snapshot{ $inp->{name} } = $args{ $inp->{name} } |
|
546
|
2
|
100
|
|
|
|
7
|
if defined $args{ $inp->{name} }; |
|
547
|
|
|
|
|
|
|
} |
|
548
|
|
|
|
|
|
|
|
|
549
|
1
|
|
|
|
|
2
|
my %par_snapshot; |
|
550
|
1
|
|
|
|
|
2
|
for my $p (@{ $self->{params} }) { |
|
|
1
|
|
|
|
|
2
|
|
|
551
|
|
|
|
|
|
|
$par_snapshot{ $p->{name} } = $args{ $p->{name} } |
|
552
|
3
|
50
|
|
|
|
8
|
if defined $args{ $p->{name} }; |
|
553
|
|
|
|
|
|
|
} |
|
554
|
|
|
|
|
|
|
|
|
555
|
1
|
|
|
|
|
2
|
my %out_snapshot; |
|
556
|
1
|
|
|
|
|
1
|
for my $out (@{ $self->{outputs} }) { |
|
|
1
|
|
|
|
|
2
|
|
|
557
|
2
|
|
50
|
|
|
5
|
my $pat = $out->{pattern} // next; |
|
558
|
2
|
|
|
|
|
7
|
(my $filename = $pat) =~ s/\{sample\}/$sample/g; |
|
559
|
2
|
|
|
|
|
5
|
$out_snapshot{ $out->{name} } = $filename; |
|
560
|
|
|
|
|
|
|
} |
|
561
|
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
# ── Build manifest ──────────────────────────────────────────────────────── |
|
563
|
|
|
|
|
|
|
my $manifest = NBI::Manifest->new( |
|
564
|
|
|
|
|
|
|
tool => $self->{name}, |
|
565
|
|
|
|
|
|
|
tool_version => $self->{version}, |
|
566
|
1
|
|
|
|
|
7
|
sample => $sample, |
|
567
|
|
|
|
|
|
|
outdir => $outdir, |
|
568
|
|
|
|
|
|
|
inputs => \%inp_snapshot, |
|
569
|
|
|
|
|
|
|
params => \%par_snapshot, |
|
570
|
|
|
|
|
|
|
outputs => \%out_snapshot, |
|
571
|
|
|
|
|
|
|
slurm_queue => $queue, |
|
572
|
|
|
|
|
|
|
slurm_cpus => $threads, |
|
573
|
|
|
|
|
|
|
slurm_mem_gb => $mem_gb, |
|
574
|
|
|
|
|
|
|
script => $script_rel, |
|
575
|
|
|
|
|
|
|
status => 'submitted', |
|
576
|
|
|
|
|
|
|
); |
|
577
|
|
|
|
|
|
|
|
|
578
|
1
|
|
|
|
|
8
|
return ($job, $manifest); |
|
579
|
|
|
|
|
|
|
} |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
1; |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
__END__ |