Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cmd output type #4493

Merged
merged 23 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c5531a8
Add cmd output type
pditommaso Nov 8, 2023
79e4a54
Merge branch 'master' into output-cmd-type
pditommaso Dec 5, 2023
3dbe230
Add support for multiline env and cmd
pditommaso Dec 5, 2023
9b92706
Fix smoke tests
pditommaso Dec 5, 2023
b0a6b34
Merge branch 'master' into output-cmd-type
pditommaso Dec 5, 2023
b49a382
Improve get env names
pditommaso Dec 5, 2023
eacc159
Merge branch 'master' into output-cmd-type
pditommaso Dec 10, 2023
be6084b
Add support for err handling
pditommaso Dec 10, 2023
624d864
Fix support for variables
pditommaso Dec 10, 2023
5667775
Add support for tuple
pditommaso Dec 11, 2023
dddca03
Fix failing tests
pditommaso Dec 11, 2023
8d0c737
Restore lost changes
bentsherman Dec 10, 2023
a1a7f74
Merge branch 'master' into output-cmd-type
marcodelapierre Dec 14, 2023
197dafa
Merge branch 'master' into output-cmd-type
marcodelapierre Jan 8, 2024
3d609eb
Merge branch 'master' into output-cmd-type
marcodelapierre Jan 15, 2024
48fc497
Merge branch 'master' into output-cmd-type
marcodelapierre Jan 17, 2024
2c1109c
Merge branch 'master' into output-cmd-type
marcodelapierre Jan 22, 2024
533d2a0
Merge branch 'master' into output-cmd-type
marcodelapierre Feb 5, 2024
11b21db
Merge branch 'master' into output-cmd-type
pditommaso Feb 5, 2024
5a3f67c
Merge branch 'master' into output-cmd-type
pditommaso Feb 10, 2024
605c262
Merge branch 'master' into output-cmd-type
pditommaso Feb 10, 2024
1d97d49
Improvement and cleanup
pditommaso Feb 10, 2024
c4c6182
Fix failing tests
pditommaso Feb 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 23 additions & 25 deletions docs/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1057,43 +1057,41 @@ To sum up, the use of output files with static names over dynamic ones is prefer

The `env` qualifier allows you to output a variable defined in the process execution environment:

```groovy
process myTask {
output:
env FOO

script:
'''
FOO=$(ls -la)
'''
}

workflow {
myTask | view { "directory contents: $it" }
}
```{literalinclude} snippets/process-out-env.nf
:language: groovy
```

:::{versionchanged} 23.12.0-edge
Prior to this version, if the environment variable contained multiple lines of output, the output would be compressed to a single line by converting newlines to spaces.
:::

(process-stdout)=

### Output type `stdout`

The `stdout` qualifier allows you to output the `stdout` of the executed process:

```groovy
process sayHello {
output:
stdout
```{literalinclude} snippets/process-stdout.nf
:language: groovy
```

"""
echo Hello world!
"""
}
(process-out-cmd)=

workflow {
sayHello | view { "I say... $it" }
}
### Output type `cmd`

:::{versionadded} 23.12.0-edge
:::

The `cmd` qualifier allows you to capture the standard output of an arbitrary shell command:

```{literalinclude} snippets/process-out-cmd.nf
:language: groovy
```

Only one-line Bash commands are supported. You can use a semi-colon `;` to specify multiple Bash commands on a single line, and many interpreters can execute arbitrary code on the command line, e.g. `python -c 'print("Hello world!")'`.

If the command fails, the task will also fail. In Bash, you can append `|| true` to a command to suppress any command failure.

(process-set)=

### Output type `set`
Expand Down
12 changes: 12 additions & 0 deletions docs/snippets/process-out-cmd.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
process sayHello {
output:
cmd('bash --version')

"""
echo Hello world!
"""
}

workflow {
sayHello | view
}
6 changes: 6 additions & 0 deletions docs/snippets/process-out-cmd.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
13 changes: 13 additions & 0 deletions docs/snippets/process-out-env.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
process myTask {
output:
env FOO

script:
'''
FOO=$(ls -a)
'''
}

workflow {
myTask | view
}
8 changes: 8 additions & 0 deletions docs/snippets/process-out-env.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.
..
.command.begin
.command.err
.command.log
.command.out
.command.run
.command.sh
12 changes: 12 additions & 0 deletions docs/snippets/process-stdout.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
process sayHello {
output:
stdout

"""
echo Hello world!
"""
}

workflow {
sayHello | view { "I say... $it" }
}
2 changes: 2 additions & 0 deletions docs/snippets/process-stdout.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
I say... Hello world!

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import nextflow.script.BaseScript
import nextflow.script.BodyDef
import nextflow.script.IncludeDef
import nextflow.script.TaskClosure
import nextflow.script.TokenCmdCall
import nextflow.script.TokenEnvCall
import nextflow.script.TokenFileCall
import nextflow.script.TokenPathCall
Expand Down Expand Up @@ -955,7 +956,7 @@ class NextflowDSLImpl implements ASTTransformation {
def nested = methodCall.objectExpression instanceof MethodCallExpression
log.trace "convert > output method: $methodName"

if( methodName in ['val','env','file','set','stdout','path','tuple'] && !nested ) {
if( methodName in ['val','env','cmd','file','set','stdout','path','tuple'] && !nested ) {
// prefix the method name with the string '_out_'
methodCall.setMethod( new ConstantExpression('_out_' + methodName) )
fixMethodCall(methodCall)
Expand Down Expand Up @@ -1123,6 +1124,11 @@ class NextflowDSLImpl implements ASTTransformation {
return createX( TokenEnvCall, args )
}

if( methodCall.methodAsString == 'cmd' && withinTupleMethod ) {
def args = (TupleExpression) varToStrX(methodCall.arguments)
return createX( TokenCmdCall, args )
}

/*
* input:
* tuple val(x), .. from q
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2013-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.exception

import groovy.transform.CompileStatic

/**
* Exception thrown when a command output returns a non-zero exit status
*
* @author Paolo Di Tommaso <[email protected]>
*/
@CompileStatic
class ProcessCommandException extends RuntimeException implements ShowOnlyExceptionMessage {

String command
String output
int status

ProcessCommandException(String message, String command, String output, int status) {
super(message)
this.command = command
this.output = output
this.status = status
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package nextflow.executor

import static java.nio.file.StandardOpenOption.*

import java.nio.file.FileSystemException
import java.nio.file.FileSystems
import java.nio.file.Files
Expand All @@ -35,11 +37,7 @@ import nextflow.processor.TaskProcessor
import nextflow.processor.TaskRun
import nextflow.secret.SecretsLoader
import nextflow.util.Escape

import static java.nio.file.StandardOpenOption.*

import nextflow.util.MemoryUnit

/**
* Builder to create the Bash script which is used to
* wrap and launch the user task
Expand Down Expand Up @@ -177,18 +175,36 @@ class BashWrapperBuilder {
}
}

protected String getOutputEnvCaptureSnippet(List<String> names) {
def result = new StringBuilder()
result.append('\n')
result.append('# capture process environment\n')
result.append('set +u\n')
result.append('cd "$NXF_TASK_WORKDIR"\n')
for( int i=0; i<names.size(); i++) {
final key = names[i]
result.append "echo $key=\${$key[@]} "
result.append( i==0 ? '> ' : '>> ' )
result.append(TaskRun.CMD_ENV)
result.append('\n')
protected String getOutputEnvCaptureSnippet(List<String> outEnvs, Map<String,String> outCmds) {
// load the env template
def template = BashWrapperBuilder.class
.getResourceAsStream('command-env.txt')
.newReader()
def binding = Map.of('env_file', TaskRun.CMD_ENV)
def result = engine.render(template, binding)
// avoid nulls
if( outEnvs==null )
outEnvs = List.<String>of()
if( outCmds==null )
outCmds = Map.<String,String>of()
// out env
for( String key : outEnvs ) {
result += "#\n"
result += "echo $key=\"\${$key[@]}\" >> ${TaskRun.CMD_ENV}\n"
result += "echo /$key/ >> ${TaskRun.CMD_ENV}\n"
}
// out cmd
for( Map.Entry<String,String> cmd : outCmds ) {
result += "#\n"
result += "nxf_catch STDOUT STDERR ${cmd.value}\n"
result += 'status=$?\n'
result += 'if [ $status -eq 0 ]; then\n'
result += " echo $cmd.key=\"\$STDOUT\" >> ${TaskRun.CMD_ENV}\n"
result += " echo /$cmd.key/=exit:0 >> ${TaskRun.CMD_ENV}\n"
result += 'else\n'
result += " echo $cmd.key=\"\$STDERR\" >> ${TaskRun.CMD_ENV}\n"
result += " echo /$cmd.key/=exit:\$status >> ${TaskRun.CMD_ENV}\n"
result += 'fi\n'
}
result.toString()
}
Expand Down Expand Up @@ -239,9 +255,12 @@ class BashWrapperBuilder {
*/
final interpreter = TaskProcessor.fetchInterpreter(script)

if( outputEnvNames ) {
if( !isBash(interpreter) ) throw new IllegalArgumentException("Process output of type env is only allowed with Bash process command -- Current interpreter: $interpreter")
script += getOutputEnvCaptureSnippet(outputEnvNames)
if( outputEnvNames || outputCommands ) {
if( !isBash(interpreter) && outputEnvNames )
throw new IllegalArgumentException("Process output of type env is only allowed with Bash process scripts -- Current interpreter: $interpreter")
if( !isBash(interpreter) && outputCommands )
throw new IllegalArgumentException("Process output of type cmd is only allowed with Bash process scripts -- Current interpreter: $interpreter")
script += getOutputEnvCaptureSnippet(outputEnvNames, outputCommands)
}

final binding = new HashMap<String,String>(20)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class TaskBean implements Serializable, Cloneable {

List<String> outputEnvNames

Map<String,String> outputCommands

String beforeScript

String afterScript
Expand Down Expand Up @@ -144,6 +146,7 @@ class TaskBean implements Serializable, Cloneable {

// stats
this.outputEnvNames = task.getOutputEnvNames()
this.outputCommands = task.getOutputCommands()
this.statsEnabled = task.getProcessor().getSession().statsEnabled

this.inputFiles = task.getInputFilesMap()
Expand Down
Loading
Loading