Skip to content

Commit 1dfb0e9

Browse files
authored
Add workflow outputs to WRROC report (#47)
1 parent a817111 commit 1dfb0e9

File tree

12 files changed

+308
-235
lines changed

12 files changed

+308
-235
lines changed

nf-prov-test/main.nf

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11

2+
nextflow.preview.output = true
3+
24
params.constant = "foo"
35

46
process ECHO_SCRIPT {
57
tag "${prefix}"
6-
publishDir params.outdir, mode: 'copy'
78

89
input:
910
tuple val(prefix), val(constant)
@@ -20,7 +21,6 @@ process ECHO_SCRIPT {
2021

2122
process ECHO_EXEC {
2223
tag "${prefix}"
23-
publishDir params.outdir, mode: 'copy'
2424

2525
input:
2626
tuple val(prefix), val(constant)
@@ -45,6 +45,7 @@ process WC_SAMPLE {
4545
}
4646

4747
workflow {
48+
main:
4849
prefixes_ch = channel.of('r1', 'r2', 'r3')
4950
constant_ch = channel.value(params.constant)
5051
inputs_ch = prefixes_ch.combine(constant_ch)
@@ -53,4 +54,24 @@ workflow {
5354

5455
samples_ch = channel.fromPath(params.input).splitCsv(header: true)
5556
WC_SAMPLE(samples_ch)
57+
58+
publish:
59+
script = ECHO_SCRIPT.out
60+
exec = ECHO_EXEC.out
61+
}
62+
63+
output {
64+
script {
65+
path 'script'
66+
index {
67+
path 'script.json'
68+
}
69+
}
70+
71+
exec {
72+
path 'exec'
73+
index {
74+
path 'exec.json'
75+
}
76+
}
5677
}

nf-prov-test/nextflow.config

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ params {
77
outdir = 'results'
88
}
99

10+
outputDir = params.outdir
11+
1012
prov {
1113
formats {
1214
bco {
@@ -43,7 +45,7 @@ manifest {
4345
homePage = "https://github.com/nextflow-io/nf-prov"
4446
description = "Test pipeline for nf-prov"
4547
mainScript = "main.nf"
46-
nextflowVersion = "!>=24.10.0"
47-
version = "0.3.0"
48+
nextflowVersion = "!>=25.04.0"
49+
version = "0.4.0"
4850
license = "https://spdx.org/licenses/Apache-2.0"
4951
}

src/main/groovy/nextflow/prov/ProvConfig.groovy

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,52 @@ import nextflow.script.dsl.Description
2828
class ProvConfig implements ConfigScope {
2929

3030
@ConfigOption
31-
@Description('Create the provenance report (default: `true` if plugin is loaded).')
32-
boolean enabled
31+
@Description('''
32+
Create the provenance report (default: `true` if plugin is loaded).
33+
''')
34+
final boolean enabled
35+
36+
@Description('''
37+
Configuration scope for the desired output formats.
38+
''')
39+
final ProvFormatsConfig formats
3340

34-
@Description('Configuration scope for the desired output formats.')
35-
ProvFormatsConfig formats
41+
@Description('''
42+
List of file patterns to include in the provenance report, from the set of published files. By default, all published files are included.
43+
''')
44+
final List<String> patterns
45+
46+
/* required by extension point -- do not remove */
47+
ProvConfig() {}
48+
49+
ProvConfig(Map opts) {
50+
enabled = opts.enabled != null ? opts.enabled as boolean : true
51+
formats = opts.formats ? new ProvFormatsConfig(opts.formats as Map) : null
52+
patterns = opts.patterns as List<String> ?: []
53+
}
3654
}
3755

3856

3957
class ProvFormatsConfig implements ConfigScope {
4058

4159
@Description('Configuration scope for the BCO output format.')
42-
ProvBcoConfig bco
60+
final ProvBcoConfig bco
4361

4462
@Description('Configuration scope for the DAG output format.')
45-
ProvDagConfig dag
63+
final ProvDagConfig dag
4664

4765
@Description('Configuration scope for the legacy output format.')
48-
ProvLegacyConfig legacy
66+
final ProvLegacyConfig legacy
4967

5068
@Description('Configuration scope for the WRROC output format.')
51-
ProvWrrocConfig wrroc
69+
final ProvWrrocConfig wrroc
70+
71+
ProvFormatsConfig(Map opts) {
72+
bco = opts.bco ? new ProvBcoConfig(opts.bco as Map) : null
73+
dag = opts.dag ? new ProvDagConfig(opts.dag as Map) : null
74+
legacy = opts.legacy ? new ProvLegacyConfig(opts.legacy as Map) : null
75+
wrroc = opts.wrroc ? new ProvWrrocConfig(opts.wrroc as Map) : null
76+
}
5277
}
5378

5479

@@ -57,14 +82,19 @@ class ProvBcoConfig implements ConfigScope {
5782
@ConfigOption
5883
@Description('''
5984
The file name of the BCO manifest.
60-
''')
61-
String file
85+
''')
86+
final String file
6287

6388
@ConfigOption
6489
@Description('''
6590
When `true` overwrites any existing BCO manifest with the same name.
66-
''')
67-
boolean overwrite
91+
''')
92+
final boolean overwrite
93+
94+
ProvBcoConfig(Map opts) {
95+
file = opts.file
96+
overwrite = opts.overwrite as boolean
97+
}
6898
}
6999

70100

@@ -73,14 +103,19 @@ class ProvDagConfig implements ConfigScope {
73103
@ConfigOption
74104
@Description('''
75105
The file name of the DAG diagram.
76-
''')
106+
''')
77107
String file
78108

79109
@ConfigOption
80110
@Description('''
81111
When `true` overwrites any existing DAG diagram with the same name.
82-
''')
112+
''')
83113
boolean overwrite
114+
115+
ProvDagConfig(Map opts) {
116+
file = opts.file
117+
overwrite = opts.overwrite as boolean
118+
}
84119
}
85120

86121

@@ -89,14 +124,19 @@ class ProvLegacyConfig implements ConfigScope {
89124
@ConfigOption
90125
@Description('''
91126
The file name of the legacy manifest.
92-
''')
93-
String file
127+
''')
128+
final String file
94129

95130
@ConfigOption
96131
@Description('''
97132
When `true` overwrites any existing legacy manifest with the same name.
98-
''')
99-
boolean overwrite
133+
''')
134+
final boolean overwrite
135+
136+
ProvLegacyConfig(Map opts) {
137+
file = opts.file
138+
overwrite = opts.overwrite as boolean
139+
}
100140
}
101141

102142

@@ -105,18 +145,24 @@ class ProvWrrocConfig implements ConfigScope {
105145
@ConfigOption
106146
@Description('''
107147
The file name of the Workflow Run RO-Crate.
108-
''')
109-
String file
148+
''')
149+
final String file
110150

111151
@ConfigOption
112152
@Description('''
113153
When `true` overwrites any existing Workflow Run RO-Crate with the same name.
114-
''')
115-
boolean overwrite
154+
''')
155+
final boolean overwrite
116156

117157
@ConfigOption
118158
@Description('''
119159
The license for the Workflow Run RO-Crate.
120-
''')
121-
String license
160+
''')
161+
final String license
162+
163+
ProvWrrocConfig(Map opts) {
164+
file = opts.file
165+
overwrite = opts.overwrite as boolean
166+
license = opts.license
167+
}
122168
}

src/main/groovy/nextflow/prov/ProvObserver.groovy

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ import java.util.concurrent.locks.ReentrantLock
2525
import groovy.transform.CompileStatic
2626
import groovy.util.logging.Slf4j
2727
import nextflow.Session
28-
import nextflow.processor.TaskHandler
2928
import nextflow.processor.TaskRun
3029
import nextflow.prov.renderers.BcoRenderer
3130
import nextflow.prov.renderers.DagRenderer
3231
import nextflow.prov.renderers.LegacyRenderer
3332
import nextflow.prov.renderers.WrrocRenderer
34-
import nextflow.trace.TraceObserver
35-
import nextflow.trace.TraceRecord
33+
import nextflow.trace.TraceObserverV2
34+
import nextflow.trace.event.FilePublishEvent
35+
import nextflow.trace.event.TaskEvent
36+
import nextflow.trace.event.WorkflowOutputEvent
3637

3738
/**
3839
* Plugin observer of workflow events
@@ -42,7 +43,7 @@ import nextflow.trace.TraceRecord
4243
*/
4344
@Slf4j
4445
@CompileStatic
45-
class ProvObserver implements TraceObserver {
46+
class ProvObserver implements TraceObserverV2 {
4647

4748
public static final List<String> VALID_FORMATS = ['bco', 'dag', 'legacy', 'wrroc']
4849

@@ -54,33 +55,37 @@ class ProvObserver implements TraceObserver {
5455

5556
private Set<TaskRun> tasks = []
5657

57-
private Map<Path,Path> workflowOutputs = [:]
58+
private Map<String,Path> workflowOutputs
59+
60+
private Map<Path,Path> publishedFiles = [:]
5861

5962
private Lock lock = new ReentrantLock()
6063

61-
ProvObserver(Map<String,Map> formats, List<String> patterns) {
62-
this.renderers = formats.collect( (name, config) -> createRenderer(name, config) )
64+
ProvObserver(ProvFormatsConfig formats, List<String> patterns) {
65+
this.renderers = createRenderers(formats)
6366
this.matchers = patterns.collect( pattern ->
6467
FileSystems.getDefault().getPathMatcher("glob:**/${pattern}")
6568
)
6669
}
6770

68-
private Renderer createRenderer(String name, Map opts) {
69-
if( name == 'bco' )
70-
return new BcoRenderer(opts)
71+
private List<Renderer> createRenderers(ProvFormatsConfig config) {
72+
final List<Renderer> result = []
73+
74+
if( config.bco )
75+
result.add(new BcoRenderer(config.bco))
7176

72-
if( name == 'dag' )
73-
return new DagRenderer(opts)
77+
if( config.dag )
78+
result.add(new DagRenderer(config.dag))
7479

75-
if( name == 'legacy' ) {
80+
if( config.legacy ) {
7681
log.warn "The legacy format is deprecated -- it will be removed in a future version"
77-
return new LegacyRenderer(opts)
82+
result.add(new LegacyRenderer(config.legacy))
7883
}
7984

80-
if( name == 'wrroc' )
81-
return new WrrocRenderer(opts)
85+
if( config.wrroc )
86+
result.add(new WrrocRenderer(config.wrroc))
8287

83-
throw new IllegalArgumentException("Invalid provenance format -- valid formats are ${VALID_FORMATS.join(', ')}")
88+
return result
8489
}
8590

8691
@Override
@@ -89,9 +94,9 @@ class ProvObserver implements TraceObserver {
8994
}
9095

9196
@Override
92-
void onProcessComplete(TaskHandler handler, TraceRecord trace) {
97+
void onTaskComplete(TaskEvent event) {
9398
// skip failed tasks
94-
final task = handler.task
99+
final task = event.handler.task
95100
if( !task.isSuccess() )
96101
return
97102

@@ -101,20 +106,35 @@ class ProvObserver implements TraceObserver {
101106
}
102107

103108
@Override
104-
void onProcessCached(TaskHandler handler, TraceRecord trace) {
109+
void onTaskCached(TaskEvent event) {
105110
lock.withLock {
106-
tasks << handler.task
111+
tasks << event.handler.task
107112
}
108113
}
109114

110115
@Override
111-
void onFilePublish(Path destination, Path source) {
112-
final match = matchers.isEmpty() || matchers.any { matcher -> matcher.matches(destination) }
116+
void onWorkflowOutput(WorkflowOutputEvent event) {
117+
if( workflowOutputs == null )
118+
workflowOutputs = [:]
119+
120+
final value = event.value instanceof Path
121+
? event.value as Path
122+
: event.index
123+
124+
if( !value )
125+
log.warn "Workflow output `${event.name}` should either be a single path or declare an index file in order to be included in provenance reports"
126+
127+
workflowOutputs[event.name] = value
128+
}
129+
130+
@Override
131+
void onFilePublish(FilePublishEvent event) {
132+
final match = matchers.isEmpty() || matchers.any { matcher -> matcher.matches(event.target) }
113133
if( !match )
114134
return
115135

116136
lock.withLock {
117-
workflowOutputs[source] = destination
137+
publishedFiles[event.source] = event.target
118138
}
119139
}
120140

@@ -123,8 +143,25 @@ class ProvObserver implements TraceObserver {
123143
if( !session.isSuccess() )
124144
return
125145

126-
for( final renderer : renderers )
127-
renderer.render(session, tasks, workflowOutputs)
146+
for( final renderer : renderers ) {
147+
try {
148+
renderer.render(session, tasks, workflowOutputs, publishedFiles)
149+
}
150+
catch( Exception e ) {
151+
log.warn "Error occurred while rendering ${rendererName(renderer)} provenance report -- see Nextflow log for details"
152+
log.debug "${e}"
153+
}
154+
}
155+
}
156+
157+
private static String rendererName(Renderer renderer) {
158+
return switch( renderer ) {
159+
case BcoRenderer -> 'BCO';
160+
case DagRenderer -> 'DAG';
161+
case LegacyRenderer -> 'legacy';
162+
case WrrocRenderer -> 'WRROC';
163+
default -> null;
164+
}
128165
}
129166

130167
}

0 commit comments

Comments
 (0)