diff --git a/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java b/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java index 21e5db49f4..7e3adc4e41 100644 --- a/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/config/control/VariableScopeVisitor.java @@ -107,7 +107,7 @@ public void visitConfigApply(ConfigApplyNode node) { checkMethodCall(node); } - private boolean inProcess; + private boolean inProcessScope; private boolean inClosure; @@ -117,34 +117,50 @@ public void visitConfigAssign(ConfigAssignNode node) { configScopes.add(node.names.get(i)); var scopes = currentConfigScopes(); - inProcess = !scopes.isEmpty() && "process".equals(scopes.get(0)); + inProcessScope = isProcessScope(scopes, node); inClosure = node.value instanceof ClosureExpression; - if( inClosure ) { - if( isWorkflowHandler(scopes, node) ) - vsc.addWarning("The use of workflow handlers in the config is deprecated -- use the entry workflow or a plugin instead", String.join(".", node.names), node); - else if( !inProcess ) - vsc.addError("Dynamic config options are only allowed in the `process` scope", node); - } + if( isWorkflowHandler(scopes, node) ) + vsc.addWarning("The use of workflow handlers in the config is deprecated -- use the entry workflow or a plugin instead", String.join(".", node.names), node); if( inClosure ) { vsc.pushScope(ScriptDsl.class); - if( inProcess ) + if( inProcessScope ) vsc.pushScope(ProcessDsl.class); } super.visitConfigAssign(node); if( inClosure ) { - if( inProcess ) + if( inProcessScope ) vsc.popScope(); vsc.popScope(); } inClosure = false; - inProcess = false; + inProcessScope = false; for( int i = 0; i < node.names.size() - 1; i++ ) configScopes.pop(); } + /** + * Determine whether a config option can access the process + * DSL for dynamic settings. + * + * This includes options in the `process` config scope and `executor.jobName`. + * + * @param scopes + * @param node + */ + private static boolean isProcessScope(List scopes, ConfigAssignNode node) { + if( scopes.isEmpty() ) + return false; + if( "process".equals(scopes.get(0)) ) + return true; + var option = node.names.get(node.names.size() - 1); + return scopes.size() == 1 + && "executor".equals(scopes.get(0)) + && "jobName".equals(option); + } + private static boolean isWorkflowHandler(List scopes, ConfigAssignNode node) { var option = node.names.get(node.names.size() - 1); return scopes.size() == 1 @@ -157,7 +173,7 @@ public void visitMapEntryExpression(MapEntryExpression node) { node.getKeyExpression().visit(this); var ic = inClosure; - if( inProcess && node.getValueExpression() instanceof ClosureExpression ) + if( inProcessScope && node.getValueExpression() instanceof ClosureExpression ) inClosure = true; node.getValueExpression().visit(this); inClosure = ic; @@ -313,7 +329,7 @@ public void visitVariableExpression(VariableExpression node) { var name = node.getName(); Variable variable = vsc.findVariableDeclaration(name, node); if( variable == null ) { - if( inProcess && inClosure ) { + if( inProcessScope && inClosure ) { // dynamic process directives can reference process inputs which are not known at this point } else { diff --git a/modules/nf-lang/src/test/groovy/nextflow/config/control/ConfigResolveTest.groovy b/modules/nf-lang/src/test/groovy/nextflow/config/control/ConfigResolveTest.groovy index 447f03ccf0..1acd686ae9 100644 --- a/modules/nf-lang/src/test/groovy/nextflow/config/control/ConfigResolveTest.groovy +++ b/modules/nf-lang/src/test/groovy/nextflow/config/control/ConfigResolveTest.groovy @@ -63,29 +63,6 @@ class ConfigResolveTest extends Specification { errors[0].getOriginalMessage() == '`process` is not defined' } - def 'should report an error for an invalid dynamic config option' () { - when: - def errors = check( - '''\ - report.file = { "report.html" } - ''' - ) - then: - errors.size() == 1 - errors[0].getStartLine() == 1 - errors[0].getStartColumn() == 1 - errors[0].getOriginalMessage() == 'Dynamic config options are only allowed in the `process` scope' - - when: - errors = check( - '''\ - process.clusterOptions = { "--cpus ${task.cpus}" } - ''' - ) - then: - errors.size() == 0 - } - def 'should report an error for an invalid config include' () { given: def root = tempDir() @@ -192,6 +169,15 @@ class ConfigResolveTest extends Specification { errors[0].getStartLine() == 2 errors[0].getStartColumn() == 23 errors[0].getOriginalMessage() == '`meta` is not defined' + + when: + errors = check( + '''\ + executor.jobName = { "$task.name - $task.hash" } + ''' + ) + then: + errors.size() == 0 } }