From 93d04310fcd72319e7af7572df19d73ad633adc8 Mon Sep 17 00:00:00 2001 From: Jorge Aguilera Date: Fri, 21 Oct 2022 09:27:27 +0200 Subject: [PATCH 1/6] allow nullables in input/output path closes #2678 Signed-off-by: Jorge Aguilera --- docs/process.rst | 12 ++++++++++ .../nextflow/processor/TaskProcessor.groovy | 17 +++++++++++--- .../nextflow/script/params/FileInParam.groovy | 10 ++++++++ .../script/params/FileOutParam.groovy | 5 ++++ .../groovy/nextflow/util/NullablePath.groovy | 23 +++++++++++++++++++ .../script/params/ParamsOutTest.groovy | 5 +++- .../input-nullablepath-fails.nf/.checks | 18 +++++++++++++++ .../output-nullablepath-fails.nf/.checks | 18 +++++++++++++++ tests/input-nullablepath-fails.nf | 23 +++++++++++++++++++ tests/output-nullablepath-fails.nf | 13 +++++++++++ 10 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy create mode 100644 tests/checks/input-nullablepath-fails.nf/.checks create mode 100644 tests/checks/output-nullablepath-fails.nf/.checks create mode 100644 tests/input-nullablepath-fails.nf create mode 100644 tests/output-nullablepath-fails.nf diff --git a/docs/process.rst b/docs/process.rst index d8eed62ecb..15f2496f13 100644 --- a/docs/process.rst +++ b/docs/process.rst @@ -537,6 +537,17 @@ section:: foo('/some/data/file.txt') } +In the case an input `path` doesn't exist, the process will be aborted. If you want the process will be executed +independently if the file exists or not, you can set the attribute `nullable` as true: + + process foo { + input: + path x, stageAs: 'data.txt', nullable:true from '/some/data/file.txt' + + """ + [[ -f data.txt ]] your_command --in data.txt || other_command + """ + } Multiple input files -------------------- @@ -1004,6 +1015,7 @@ Name Description ``type`` Type of paths returned, either ``file``, ``dir`` or ``any`` (default: ``any``, or ``file`` if the specified file name pattern contains a double star (``**``)) ``maxDepth`` Maximum number of directory levels to visit (default: no limit) ``includeInputs`` When ``true`` any input files matching an output file glob pattern are included. +``nullable`` When ``true`` emit a NullablePath instead to abort the process ================== ===================== diff --git a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy index 80c8fbc778..d28d5cdf70 100644 --- a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy @@ -16,6 +16,8 @@ */ package nextflow.processor +import nextflow.util.NullablePath + import static nextflow.processor.ErrorStrategy.* import java.lang.reflect.InvocationTargetException @@ -1505,6 +1507,10 @@ class TaskProcessor { def path = param.glob ? splitter.strip(filePattern) : filePattern def file = workDir.resolve(path) def exists = param.followLinks ? file.exists() : file.exists(LinkOption.NOFOLLOW_LINKS) + if( !exists && param.nullable){ + file = new NullablePath(path) + exists = true + } if( exists ) result = [file] else @@ -1719,7 +1725,11 @@ class TaskProcessor { return new FileHolder(source, result) } - protected Path normalizeToPath( obj ) { + protected Path normalizeToPath( obj, boolean nullable=false ) { + + if( obj instanceof NullablePath && !nullable) + throw new ProcessUnrecoverableException("Path value cannot be null") + if( obj instanceof Path ) return obj @@ -1742,7 +1752,8 @@ class TaskProcessor { throw new ProcessUnrecoverableException("Not a valid path value: '$str'") } - protected List normalizeInputToFiles( Object obj, int count, boolean coerceToPath, FilePorter.Batch batch ) { + protected List normalizeInputToFiles( Object obj, int count, boolean coerceToPath, FilePorter.Batch batch, + boolean nullable=false ) { Collection allItems = obj instanceof Collection ? obj : [obj] def len = allItems.size() @@ -1752,7 +1763,7 @@ class TaskProcessor { for( def item : allItems ) { if( item instanceof Path || coerceToPath ) { - def path = normalizeToPath(item) + def path = normalizeToPath(item, nullable) def target = executor.isForeignFile(path) ? batch.addToForeign(path) : path def holder = new FileHolder(target) files << holder diff --git a/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy b/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy index 7148c5ea29..852a69bbca 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy @@ -35,6 +35,8 @@ class FileInParam extends BaseInParam implements PathQualifier { private boolean pathQualifier + private boolean nullable + @Override String getTypeName() { pathQualifier ? 'path' : 'file' } @Override String getTypeSimpleName() { getTypeName() + "inparam" } @@ -154,4 +156,12 @@ class FileInParam extends BaseInParam implements PathQualifier { return this } + FileInParam setNullable(boolean value) { + this.nullable = value + return this + } + + boolean isNullable() { + return nullable + } } diff --git a/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy b/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy index 50608f6886..98f26c8f51 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy @@ -81,6 +81,11 @@ class FileOutParam extends BaseOutParam implements OutParam, OptionalParam, Path */ boolean followLinks = true + /** + * When true, if file doesn't exist, emit a Nullable instead an exception + */ + boolean nullable = false + boolean glob = true private GString gstring diff --git a/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy b/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy new file mode 100644 index 0000000000..1bfe209fd1 --- /dev/null +++ b/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy @@ -0,0 +1,23 @@ +package nextflow.util + +import groovy.transform.EqualsAndHashCode +import groovy.transform.PackageScope +import groovy.util.logging.Slf4j +import java.nio.file.Path + +/** + * @author : jorge + * + */ +@EqualsAndHashCode +@Slf4j +class NullablePath implements Path { + + @PackageScope + @Delegate + Path delegate + + NullablePath(String path) { + delegate = of(path) + } +} diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy index 3d0f034ba4..cd5ae3d821 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy @@ -1151,7 +1151,8 @@ class ParamsOutTest extends Specification { separatorChar: '#', glob: false, optional: false, - includeInputs: false + includeInputs: false, + nullable: true path y, maxDepth:5, @@ -1181,6 +1182,7 @@ class ParamsOutTest extends Specification { !out0.getGlob() !out0.getOptional() !out0.getIncludeInputs() + out0.isNullable() and: out1.getMaxDepth() == 5 @@ -1191,6 +1193,7 @@ class ParamsOutTest extends Specification { out1.getGlob() out1.getOptional() out1.getIncludeInputs() + !out1.isNullable() } def 'should set file options' () { diff --git a/tests/checks/input-nullablepath-fails.nf/.checks b/tests/checks/input-nullablepath-fails.nf/.checks new file mode 100644 index 0000000000..346e4a2ea0 --- /dev/null +++ b/tests/checks/input-nullablepath-fails.nf/.checks @@ -0,0 +1,18 @@ +set +e + +# +# run normal mode +# +echo '' +$NXF_RUN +[[ $? == 0 ]] && exit 1 + + +# +# RESUME mode +# +echo '' +$NXF_RUN -resume +[[ $? == 0 ]] && exit 1 + +exit 0 \ No newline at end of file diff --git a/tests/checks/output-nullablepath-fails.nf/.checks b/tests/checks/output-nullablepath-fails.nf/.checks new file mode 100644 index 0000000000..346e4a2ea0 --- /dev/null +++ b/tests/checks/output-nullablepath-fails.nf/.checks @@ -0,0 +1,18 @@ +set +e + +# +# run normal mode +# +echo '' +$NXF_RUN +[[ $? == 0 ]] && exit 1 + + +# +# RESUME mode +# +echo '' +$NXF_RUN -resume +[[ $? == 0 ]] && exit 1 + +exit 0 \ No newline at end of file diff --git a/tests/input-nullablepath-fails.nf b/tests/input-nullablepath-fails.nf new file mode 100644 index 0000000000..09c1292e33 --- /dev/null +++ b/tests/input-nullablepath-fails.nf @@ -0,0 +1,23 @@ +nextflow.enable.dsl=2 +process test_process1 { + input: + val id + output: + path("output.txt", nullable:true) + exec: + println id +} + +process test_process2 { + input: + path(file) + output: + val file + exec: + sleep 1000L + println file +} + +workflow { + channel.of('foo') | test_process1 | test_process2 | view() +} \ No newline at end of file diff --git a/tests/output-nullablepath-fails.nf b/tests/output-nullablepath-fails.nf new file mode 100644 index 0000000000..df93d518e3 --- /dev/null +++ b/tests/output-nullablepath-fails.nf @@ -0,0 +1,13 @@ +nextflow.enable.dsl=2 + +process test_process1 { + input: + val id + output: + path("output.txt") + exec: + println 'hi' +} +workflow { + test_process1('foo').out +} \ No newline at end of file From 199b694f207f622c266ef0accaa3ecdd371fb5bd Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Tue, 21 Feb 2023 21:20:16 -0600 Subject: [PATCH 2/6] cleanup Signed-off-by: Ben Sherman --- docs/process.rst | 16 ++++---- .../nextflow/processor/TaskProcessor.groovy | 12 +++--- .../nextflow/script/params/FileInParam.groovy | 21 +++++----- .../script/params/FileOutParam.groovy | 9 +++-- .../main/groovy/nextflow/util/NullPath.groovy | 40 +++++++++++++++++++ .../groovy/nextflow/util/NullablePath.groovy | 23 ----------- .../script/params/ParamsOutTest.groovy | 10 ++--- 7 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 modules/nextflow/src/main/groovy/nextflow/util/NullPath.groovy delete mode 100644 modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy diff --git a/docs/process.rst b/docs/process.rst index 15f2496f13..1cdfec0909 100644 --- a/docs/process.rst +++ b/docs/process.rst @@ -537,16 +537,16 @@ section:: foo('/some/data/file.txt') } -In the case an input `path` doesn't exist, the process will be aborted. If you want the process will be executed -independently if the file exists or not, you can set the attribute `nullable` as true: +By default, the process will be aborted if an input ``path`` is null. If you want the process +to be executed even if the path is null, you can set the ``nullable`` attribute to true: process foo { - input: - path x, stageAs: 'data.txt', nullable:true from '/some/data/file.txt' + input: + path(x, stageAs: 'data.txt', nullable: true) - """ - [[ -f data.txt ]] your_command --in data.txt || other_command - """ + """ + [[ -f data.txt ]] your_command --in data.txt || other_command + """ } Multiple input files @@ -1015,7 +1015,7 @@ Name Description ``type`` Type of paths returned, either ``file``, ``dir`` or ``any`` (default: ``any``, or ``file`` if the specified file name pattern contains a double star (``**``)) ``maxDepth`` Maximum number of directory levels to visit (default: no limit) ``includeInputs`` When ``true`` any input files matching an output file glob pattern are included. -``nullable`` When ``true`` emit a NullablePath instead to abort the process +``nullable`` When ``true`` emit ``null`` (instead of aborting) if the output path doesn't exist (default: ``false``) ================== ===================== diff --git a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy index d28d5cdf70..4a000c7a4a 100644 --- a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy @@ -16,8 +16,6 @@ */ package nextflow.processor -import nextflow.util.NullablePath - import static nextflow.processor.ErrorStrategy.* import java.lang.reflect.InvocationTargetException @@ -106,6 +104,7 @@ import nextflow.util.CacheHelper import nextflow.util.CollectionHelper import nextflow.util.LockManager import nextflow.util.LoggerHelper +import nextflow.util.NullPath import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer /** @@ -1507,8 +1506,8 @@ class TaskProcessor { def path = param.glob ? splitter.strip(filePattern) : filePattern def file = workDir.resolve(path) def exists = param.followLinks ? file.exists() : file.exists(LinkOption.NOFOLLOW_LINKS) - if( !exists && param.nullable){ - file = new NullablePath(path) + if( !exists && param.nullable ) { + file = new NullPath(path) exists = true } if( exists ) @@ -1727,7 +1726,7 @@ class TaskProcessor { protected Path normalizeToPath( obj, boolean nullable=false ) { - if( obj instanceof NullablePath && !nullable) + if( obj instanceof NullPath && !nullable ) throw new ProcessUnrecoverableException("Path value cannot be null") if( obj instanceof Path ) @@ -1752,8 +1751,7 @@ class TaskProcessor { throw new ProcessUnrecoverableException("Not a valid path value: '$str'") } - protected List normalizeInputToFiles( Object obj, int count, boolean coerceToPath, FilePorter.Batch batch, - boolean nullable=false ) { + protected List normalizeInputToFiles( Object obj, int count, boolean coerceToPath, FilePorter.Batch batch, boolean nullable=false ) { Collection allItems = obj instanceof Collection ? obj : [obj] def len = allItems.size() diff --git a/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy b/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy index 852a69bbca..50170a9f8c 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/params/FileInParam.groovy @@ -33,10 +33,10 @@ class FileInParam extends BaseInParam implements PathQualifier { protected filePattern - private boolean pathQualifier - private boolean nullable + private boolean pathQualifier + @Override String getTypeName() { pathQualifier ? 'path' : 'file' } @Override String getTypeSimpleName() { getTypeName() + "inparam" } @@ -123,6 +123,15 @@ class FileInParam extends BaseInParam implements PathQualifier { return value } + FileInParam setNullable(boolean value) { + this.nullable = value + return this + } + + boolean isNullable() { + return nullable + } + @Override FileInParam setPathQualifier(boolean flag) { pathQualifier = flag @@ -156,12 +165,4 @@ class FileInParam extends BaseInParam implements PathQualifier { return this } - FileInParam setNullable(boolean value) { - this.nullable = value - return this - } - - boolean isNullable() { - return nullable - } } diff --git a/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy b/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy index 98f26c8f51..619def68d5 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/params/FileOutParam.groovy @@ -82,12 +82,15 @@ class FileOutParam extends BaseOutParam implements OutParam, OptionalParam, Path boolean followLinks = true /** - * When true, if file doesn't exist, emit a Nullable instead an exception + * When true the specified name is interpreted as a glob pattern */ - boolean nullable = false - boolean glob = true + /** + * When true emit null (instead of aborting) if the output path doesn't exist + */ + boolean nullable = false + private GString gstring private Closure dynamicObj private String filePattern diff --git a/modules/nextflow/src/main/groovy/nextflow/util/NullPath.groovy b/modules/nextflow/src/main/groovy/nextflow/util/NullPath.groovy new file mode 100644 index 0000000000..fa9b201df7 --- /dev/null +++ b/modules/nextflow/src/main/groovy/nextflow/util/NullPath.groovy @@ -0,0 +1,40 @@ +/* + * Copyright 2023, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nextflow.util + +import java.nio.file.Path + +import groovy.transform.EqualsAndHashCode +import groovy.transform.PackageScope +import groovy.util.logging.Slf4j + +/** + * @author : jorge + * + */ +@EqualsAndHashCode +@Slf4j +class NullPath implements Path { + + @PackageScope + @Delegate + Path delegate + + NullPath(String path) { + delegate = of(path) + } +} diff --git a/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy b/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy deleted file mode 100644 index 1bfe209fd1..0000000000 --- a/modules/nextflow/src/main/groovy/nextflow/util/NullablePath.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package nextflow.util - -import groovy.transform.EqualsAndHashCode -import groovy.transform.PackageScope -import groovy.util.logging.Slf4j -import java.nio.file.Path - -/** - * @author : jorge - * - */ -@EqualsAndHashCode -@Slf4j -class NullablePath implements Path { - - @PackageScope - @Delegate - Path delegate - - NullablePath(String path) { - delegate = of(path) - } -} diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy index cd5ae3d821..2ce0c7da9b 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy @@ -1151,8 +1151,7 @@ class ParamsOutTest extends Specification { separatorChar: '#', glob: false, optional: false, - includeInputs: false, - nullable: true + includeInputs: false path y, maxDepth:5, @@ -1162,7 +1161,8 @@ class ParamsOutTest extends Specification { separatorChar: ':', glob: true, optional: true, - includeInputs: true + includeInputs: true, + nullable: true return '' } @@ -1182,7 +1182,7 @@ class ParamsOutTest extends Specification { !out0.getGlob() !out0.getOptional() !out0.getIncludeInputs() - out0.isNullable() + !out0.isNullable() and: out1.getMaxDepth() == 5 @@ -1193,7 +1193,7 @@ class ParamsOutTest extends Specification { out1.getGlob() out1.getOptional() out1.getIncludeInputs() - !out1.isNullable() + out1.isNullable() } def 'should set file options' () { From 4e7148edf37fa48d9cfafbd736a4af07d98c1a76 Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Tue, 21 Feb 2023 21:34:49 -0600 Subject: [PATCH 3/6] Update e2e tests Signed-off-by: Ben Sherman --- .../nextflow/processor/TaskProcessor.groovy | 2 +- .../.checks | 0 .../.checks | 0 .../checks/nullable-path-succeeds.nf/.checks | 18 ++++++++++++++ tests/input-nullablepath-fails.nf | 23 ------------------ tests/nullable-path-input-fails.nf | 24 +++++++++++++++++++ tests/nullable-path-output-fails.nf | 14 +++++++++++ tests/nullable-path-succeeds.nf | 24 +++++++++++++++++++ tests/output-nullablepath-fails.nf | 13 ---------- 9 files changed, 81 insertions(+), 37 deletions(-) rename tests/checks/{input-nullablepath-fails.nf => nullable-path-input-fails.nf}/.checks (100%) rename tests/checks/{output-nullablepath-fails.nf => nullable-path-output-fails.nf}/.checks (100%) create mode 100644 tests/checks/nullable-path-succeeds.nf/.checks delete mode 100644 tests/input-nullablepath-fails.nf create mode 100644 tests/nullable-path-input-fails.nf create mode 100644 tests/nullable-path-output-fails.nf create mode 100644 tests/nullable-path-succeeds.nf delete mode 100644 tests/output-nullablepath-fails.nf diff --git a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy index 4a000c7a4a..60688f0036 100644 --- a/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy @@ -1975,7 +1975,7 @@ class TaskProcessor { final param = entry.getKey() final val = entry.getValue() final fileParam = param as FileInParam - final normalized = normalizeInputToFiles(val, count, fileParam.isPathQualifier(), batch) + final normalized = normalizeInputToFiles(val, count, fileParam.isPathQualifier(), batch, fileParam.isNullable()) final resolved = expandWildcards( fileParam.getFilePattern(ctx), normalized ) ctx.put( param.name, singleItemOrList(resolved, task.type) ) count += resolved.size() diff --git a/tests/checks/input-nullablepath-fails.nf/.checks b/tests/checks/nullable-path-input-fails.nf/.checks similarity index 100% rename from tests/checks/input-nullablepath-fails.nf/.checks rename to tests/checks/nullable-path-input-fails.nf/.checks diff --git a/tests/checks/output-nullablepath-fails.nf/.checks b/tests/checks/nullable-path-output-fails.nf/.checks similarity index 100% rename from tests/checks/output-nullablepath-fails.nf/.checks rename to tests/checks/nullable-path-output-fails.nf/.checks diff --git a/tests/checks/nullable-path-succeeds.nf/.checks b/tests/checks/nullable-path-succeeds.nf/.checks new file mode 100644 index 0000000000..346e4a2ea0 --- /dev/null +++ b/tests/checks/nullable-path-succeeds.nf/.checks @@ -0,0 +1,18 @@ +set +e + +# +# run normal mode +# +echo '' +$NXF_RUN +[[ $? == 0 ]] && exit 1 + + +# +# RESUME mode +# +echo '' +$NXF_RUN -resume +[[ $? == 0 ]] && exit 1 + +exit 0 \ No newline at end of file diff --git a/tests/input-nullablepath-fails.nf b/tests/input-nullablepath-fails.nf deleted file mode 100644 index 09c1292e33..0000000000 --- a/tests/input-nullablepath-fails.nf +++ /dev/null @@ -1,23 +0,0 @@ -nextflow.enable.dsl=2 -process test_process1 { - input: - val id - output: - path("output.txt", nullable:true) - exec: - println id -} - -process test_process2 { - input: - path(file) - output: - val file - exec: - sleep 1000L - println file -} - -workflow { - channel.of('foo') | test_process1 | test_process2 | view() -} \ No newline at end of file diff --git a/tests/nullable-path-input-fails.nf b/tests/nullable-path-input-fails.nf new file mode 100644 index 0000000000..088838b7c8 --- /dev/null +++ b/tests/nullable-path-input-fails.nf @@ -0,0 +1,24 @@ +#!/usr/bin/env nextflow + +process foo { + input: + val id + output: + path('output.txt', nullable: true) + exec: + println id +} + +process bar { + input: + path(file) + output: + val file + exec: + sleep 1000L + println file +} + +workflow { + channel.of('foo') | foo | bar | view() +} \ No newline at end of file diff --git a/tests/nullable-path-output-fails.nf b/tests/nullable-path-output-fails.nf new file mode 100644 index 0000000000..7c6ed2514b --- /dev/null +++ b/tests/nullable-path-output-fails.nf @@ -0,0 +1,14 @@ +#!/usr/bin/env nextflow + +process foo { + input: + val id + output: + path('output.txt') + exec: + println 'hi' +} + +workflow { + foo('foo') +} \ No newline at end of file diff --git a/tests/nullable-path-succeeds.nf b/tests/nullable-path-succeeds.nf new file mode 100644 index 0000000000..16a3c3c3cd --- /dev/null +++ b/tests/nullable-path-succeeds.nf @@ -0,0 +1,24 @@ +#!/usr/bin/env nextflow + +process foo { + input: + val id + output: + path('output.txt', nullable: true) + exec: + println id +} + +process bar { + input: + path(file, nullable: true) + output: + val file + exec: + sleep 1000L + println file +} + +workflow { + channel.of('foo') | foo | bar | view() +} \ No newline at end of file diff --git a/tests/output-nullablepath-fails.nf b/tests/output-nullablepath-fails.nf deleted file mode 100644 index df93d518e3..0000000000 --- a/tests/output-nullablepath-fails.nf +++ /dev/null @@ -1,13 +0,0 @@ -nextflow.enable.dsl=2 - -process test_process1 { - input: - val id - output: - path("output.txt") - exec: - println 'hi' -} -workflow { - test_process1('foo').out -} \ No newline at end of file From ff8ecc96a55861c1a4fba957d061751d1a1b6b99 Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Tue, 21 Feb 2023 21:42:34 -0600 Subject: [PATCH 4/6] Add tuples to nullable path e2e tests Signed-off-by: Ben Sherman --- tests/nullable-path-input-fails.nf | 4 ++-- tests/nullable-path-output-fails.nf | 2 +- tests/nullable-path-succeeds.nf | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/nullable-path-input-fails.nf b/tests/nullable-path-input-fails.nf index 088838b7c8..aad3dde4ff 100644 --- a/tests/nullable-path-input-fails.nf +++ b/tests/nullable-path-input-fails.nf @@ -4,14 +4,14 @@ process foo { input: val id output: - path('output.txt', nullable: true) + tuple val(id), path('output.txt', nullable: true) exec: println id } process bar { input: - path(file) + tuple val(id), path(file) output: val file exec: diff --git a/tests/nullable-path-output-fails.nf b/tests/nullable-path-output-fails.nf index 7c6ed2514b..e3cd052084 100644 --- a/tests/nullable-path-output-fails.nf +++ b/tests/nullable-path-output-fails.nf @@ -4,7 +4,7 @@ process foo { input: val id output: - path('output.txt') + tuple val(id), path('output.txt') exec: println 'hi' } diff --git a/tests/nullable-path-succeeds.nf b/tests/nullable-path-succeeds.nf index 16a3c3c3cd..afadb4a1f5 100644 --- a/tests/nullable-path-succeeds.nf +++ b/tests/nullable-path-succeeds.nf @@ -4,14 +4,14 @@ process foo { input: val id output: - path('output.txt', nullable: true) + tuple val(id), path('output.txt', nullable: true) exec: println id } process bar { input: - path(file, nullable: true) + tuple val(id), path(file, nullable: true) output: val file exec: From 26cc4290f7ddaf2e9f8b64c1924f6a58d02e15ea Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Wed, 22 Feb 2023 03:24:33 -0600 Subject: [PATCH 5/6] Fix integration test Signed-off-by: Ben Sherman --- tests/checks/nullable-path-succeeds.nf/.checks | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/checks/nullable-path-succeeds.nf/.checks b/tests/checks/nullable-path-succeeds.nf/.checks index 346e4a2ea0..5c332305d9 100644 --- a/tests/checks/nullable-path-succeeds.nf/.checks +++ b/tests/checks/nullable-path-succeeds.nf/.checks @@ -5,7 +5,7 @@ set +e # echo '' $NXF_RUN -[[ $? == 0 ]] && exit 1 +[[ $? == 0 ]] || false # @@ -13,6 +13,4 @@ $NXF_RUN # echo '' $NXF_RUN -resume -[[ $? == 0 ]] && exit 1 - -exit 0 \ No newline at end of file +[[ $? == 0 ]] || false From 19209212cf2bdf53794953b89210e554e3ce50a6 Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Fri, 24 Feb 2023 14:43:12 -0600 Subject: [PATCH 6/6] Add note about optional vs nullable [ci skip] Signed-off-by: Ben Sherman --- docs/process.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/process.rst b/docs/process.rst index c60d02e09d..a662b6c283 100644 --- a/docs/process.rst +++ b/docs/process.rst @@ -1265,6 +1265,11 @@ In this example, the process is normally expected to produce an ``output.txt`` f cases where the file is legitimately missing, the process does not fail. The output channel will only contain values for those processes that produce ``output.txt``. +.. note:: + Path outputs can also be defined as ``nullable``. Whereas an ``optional`` path will emit nothing + if the output file does not exist, a ``nullable`` path will emit a "null" path that can trigger + downstream computations, such as a process that has a ``nullable`` path input. + When ====