All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nextflow.script.WorkflowDef.groovy Maven / Gradle / Ivy

Go to download

A DSL modelled around the UNIX pipe concept, that simplifies writing parallel and scalable pipelines in a portable manner

There is a newer version: 24.11.0-edge
Show newest version
/*
 * Copyright 2013-2024, 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.script

import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import groovyx.gpars.dataflow.DataflowWriteChannel
import nextflow.exception.MissingProcessException
import nextflow.exception.MissingValueException
import nextflow.exception.ScriptRuntimeException
import nextflow.extension.CH
/**
 * Models a script workflow component
 *
 * @author Paolo Di Tommaso 
 */
@Slf4j
@CompileStatic
class WorkflowDef extends BindableDef implements ChainableDef, IterableDef, ExecutionContext {

    private String name

    private BodyDef body

    private List declaredInputs

    private List declaredOutputs

    private Set variableNames

    private BaseScript owner

    // -- following attributes are mutable and instance dependent
    // -- therefore should not be cloned

    private ChannelOut output

    private WorkflowBinding binding

    WorkflowDef(BaseScript owner, Closure rawBody, String name=null) {
        this.owner = owner
        this.name = name
        // invoke the body resolving in/out params
        final copy = (Closure)rawBody.clone()
        final resolver = new WorkflowParamsDsl()
        copy.setResolveStrategy(Closure.DELEGATE_FIRST)
        copy.setDelegate(resolver)
        this.body = copy.call()
        // now it can access the parameters
        this.declaredInputs = new ArrayList<>(resolver.getTakes().keySet())
        this.declaredOutputs = new ArrayList<>(resolver.getEmits().keySet())
        this.variableNames = getVarNames0()
    }

    /* ONLY FOR TESTING PURPOSE */
    protected WorkflowDef() {}

    WorkflowDef clone() {
        final copy = (WorkflowDef)super.clone()
        copy.@body = body.clone()
        return copy
    }

    WorkflowDef cloneWithName(String name) {
        def result = clone()
        result.@name = name
        return result
    }

    BaseScript getOwner() { owner }

    String getName() { name }

    WorkflowBinding getBinding() { binding }

    ChannelOut getOut() {
        if( output==null )
            throw new ScriptRuntimeException("Access to '${name}.out' is undefined since the workflow '$name' has not been invoked before accessing the output attribute")
        if( output.size()==0 )
            throw new ScriptRuntimeException("Access to '${name}.out' is undefined since the workflow '$name' doesn't declare any output")

        return output
    }

    @PackageScope BodyDef getBody() { body }

    @PackageScope List getDeclaredInputs() { declaredInputs }

    @PackageScope List getDeclaredOutputs() { declaredOutputs }

    @PackageScope Map getDeclaredPublish() { declaredPublish }

    @PackageScope String getSource() { body.source }

    @PackageScope List getDeclaredVariables() { new ArrayList(variableNames) }

    String getType() { 'workflow' }

    private Set getVarNames0() {
        def variableNames = body.getValNames()
        if( variableNames ) {
            Set declaredNames = []
            declaredNames.addAll( declaredInputs )
            if( declaredNames )
                variableNames = variableNames - declaredNames
        }
        return variableNames
    }


    protected void collectInputs(Binding context, Object[] args) {
        final params = ChannelOut.spread(args)
        if( params.size() != declaredInputs.size() ) {
            final prefix = name ? "Workflow `$name`" : "Main workflow"
            throw new IllegalArgumentException("$prefix declares ${declaredInputs.size()} input channels but ${params.size()} were given")
        }

        // attach declared inputs with the invocation arguments
        for( int i=0; i< declaredInputs.size(); i++ ) {
            final name = declaredInputs[i]
            context.setProperty( name, params[i] )
        }
    }

    protected ChannelOut collectOutputs(List emissions) {
        // make sure feedback channel cardinality matches
        if( feedbackChannels && feedbackChannels.size() != emissions.size() )
            throw new ScriptRuntimeException("Workflow `$name` inputs and outputs do not have the same cardinality - Feedback loop is not supported"  )

        final channels = new LinkedHashMap(emissions.size())
        for( int i=0; i1 )
                    throw new IllegalArgumentException("Cannot emit a multi-channel output: $targetName")
                if( obj.size()==0 )
                    throw new MissingValueException("Cannot emit empty output: $targetName")
                channels.put(targetName, target(i, obj.get(0)))
            }

            else {
                if( feedbackChannels!=null )
                    throw new ScriptRuntimeException("Workflow `$name` static output is not allowed when using recursion - Check output: $targetName")
                final value = CH.create(true)
                value.bind(obj)
                channels.put(targetName, value)
            }
        }
        return new ChannelOut(channels)
    }

    protected DataflowWriteChannel target(int index, Object output) {
        if( feedbackChannels==null )
            return (DataflowWriteChannel)output
        // otherwise the output should be forwarded into the feedback channel
        final feedback = feedbackChannels[index]
        CH.getReadChannel(output).into( feedback )
        return feedback
    }

    Object run(Object[] args) {
        binding = new WorkflowBinding(owner)
        ExecutionStack.push(this)
        try {
            return run0(args)
        }
        catch (MissingMethodException e) {
            throw new MissingProcessException(this.binding.scriptMeta, e)
        }
        finally {
            ExecutionStack.pop()
        }
    }

    private Object run0(Object[] args) {
        collectInputs(binding, args)
        // invoke the workflow execution
        final closure = body.closure
        closure.setDelegate(binding)
        closure.setResolveStrategy(Closure.DELEGATE_FIRST)
        closure.call()
        // collect the workflow outputs
        output = collectOutputs(declaredOutputs)
        return output
    }

}

/**
 * Implements the DSL for defining workflow takes and emits
 */
@Slf4j
@CompileStatic
class WorkflowParamsDsl {

    static final private String TAKE_PREFIX = '_take_'
    static final private String EMIT_PREFIX = '_emit_'


    Map takes = new LinkedHashMap<>(10)
    Map emits = new LinkedHashMap<>(10)

    @Override
    def invokeMethod(String name, Object args) {
        if( name.startsWith(TAKE_PREFIX) )
            takes.put(name.substring(TAKE_PREFIX.size()), args)

        else if( name.startsWith(EMIT_PREFIX) )
            emits.put(name.substring(EMIT_PREFIX.size()), args)

        else
            throw new MissingMethodException(name, WorkflowDef, args)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy