1616
1717package nextflow.processor
1818
19+ import java.io.UncheckedIOException
20+ import java.lang.reflect.Modifier
1921import java.nio.file.Path
2022
2123import groovy.transform.CompileDynamic
@@ -25,7 +27,10 @@ import groovy.util.logging.Slf4j
2527import nextflow.exception.IllegalArityException
2628import nextflow.exception.MissingFileException
2729import nextflow.exception.MissingValueException
30+ import nextflow.extension.Bolts
2831import nextflow.script.params.v2.ProcessFileOutput
32+ import nextflow.script.types.Record
33+ import nextflow.util.RecordMap
2934import org.codehaus.groovy.runtime.InvokerHelper
3035/**
3136 * Implements the resolution of task outputs.
@@ -49,6 +54,75 @@ class TaskOutputResolver implements Map<String,Object> {
4954 this . delegate = task. context
5055 }
5156
57+ /**
58+ * Resolve and normalize an output expression before it is emitted.
59+ *
60+ * Values from the task context may contain {@link TaskPath}, which is a
61+ * task-local view of an input file. It is valid for script interpolation,
62+ * but it must not escape through output channels because downstream tasks
63+ * need durable source/work-directory paths for hashing and staging.
64+ *
65+ * @param value
66+ * A lazy output expression, such as a closure or GString
67+ * @return
68+ * The resolved output value with nested TaskPath instances converted
69+ * back to durable Path values
70+ */
71+ Object resolveOutput (Object value ) {
72+ return normalizeOutputValue(Bolts . resolveLazy(this , value))
73+ }
74+
75+ static Object normalizeOutputValue (Object value ) {
76+ if ( value instanceof TaskPath ) {
77+ try {
78+ return value. toRealPath()
79+ }
80+ catch ( IOException e ) {
81+ throw new UncheckedIOException (e)
82+ }
83+ }
84+
85+ if ( value instanceof RecordMap ) {
86+ final normalized = new LinkedHashMap<String ,Object > ()
87+ for ( final entry : value. entrySet() )
88+ normalized. put(entry. key, normalizeOutputValue(entry. value))
89+ return new RecordMap (normalized)
90+ }
91+
92+ if ( value instanceof Map ) {
93+ final normalized = new LinkedHashMap<Object ,Object > ()
94+ for ( final entry : value. entrySet() )
95+ normalized. put(entry. key, normalizeOutputValue(entry. value))
96+ return normalized
97+ }
98+
99+ if ( value instanceof Set ) {
100+ final normalized = new LinkedHashSet<Object > ()
101+ for ( final item : value )
102+ normalized. add(normalizeOutputValue(item))
103+ return normalized
104+ }
105+
106+ if ( value instanceof Collection ) {
107+ final normalized = new ArrayList<Object > ()
108+ for ( final item : value )
109+ normalized. add(normalizeOutputValue(item))
110+ return normalized
111+ }
112+
113+ if ( value instanceof Record ) {
114+ final fields = value. getClass(). getFields()
115+ .findAll { field -> ! Modifier . isStatic(field. modifiers) && ! field. synthetic }
116+ .sort { it. name }
117+ final normalized = new LinkedHashMap<String ,Object > ()
118+ for ( final field : fields )
119+ normalized. put(field. name, normalizeOutputValue(field. get(value)))
120+ return new RecordMap (normalized)
121+ }
122+
123+ return value
124+ }
125+
52126 /**
53127 * Get an environment variable from the task environment.
54128 *
0 commit comments