Skip to content

Commit 9bb45b0

Browse files
committed
Fix module binaries PATH not injected on nextflow module run (#7087)
When a module is launched directly via `nextflow module run`, the module main.nf is loaded as the entry script, so `ScriptMeta.isModule()` is false and `TaskProcessor.getModuleBundle()` was returning null, so the `resources/usr/bin` directory was not added to the task PATH. Track the "running as a module" state on the Session: `CmdRun` exposes a `isModuleRun()` hook (default false) which `CmdModuleRun` overrides to true, and `Session.moduleRun` is set from there. The bundle is now resolved when the owner script is either an included module (`meta.isModule()`) or the entry script of a `nextflow module run` invocation (`session.isModuleRun()`). The feature remains opt-in via `nextflow.enable.moduleBinaries`. A null `scriptPath` guard is added to keep `getModuleBundle()` robust against scripts that have not had their path registered. Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
1 parent 656ff4e commit 9bb45b0

5 files changed

Lines changed: 77 additions & 1 deletion

File tree

modules/nextflow/src/main/groovy/nextflow/Session.groovy

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,23 @@ class Session implements ISession {
902902
NF.isModuleBinariesEnabled()
903903
}
904904

905+
/**
906+
* Whether the entry script was launched directly as a module via
907+
* `nextflow module run`. Used to decide whether the entry script's
908+
* `resources/` bundle (and module bin paths) should be picked up
909+
* even though the script is not being loaded via `include`.
910+
*/
911+
private volatile boolean moduleRun
912+
913+
boolean isModuleRun() {
914+
return moduleRun
915+
}
916+
917+
Session setModuleRun(boolean value) {
918+
this.moduleRun = value
919+
return this
920+
}
921+
905922
boolean failOnIgnore() {
906923
config.navigate('workflow.failOnIgnore', false) as boolean
907924
}

modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ class CmdRun extends CmdBase implements HubOptions {
414414
runner.session.agentLog = SysEnv.isAgentMode()
415415
runner.session.debug = launcher.options.remoteDebug
416416
runner.session.disableJobsCancellation = getDisableJobsCancellation()
417+
runner.session.setModuleRun(isModuleRun())
417418

418419
final isTowerEnabled = config.navigate('tower.enabled') as Boolean
419420
final isDataEnabled = config.navigate("lineage.enabled") as Boolean
@@ -491,6 +492,12 @@ class CmdRun extends CmdBase implements HubOptions {
491492
printLaunchInfo(repo, head, revision)
492493
}
493494

495+
/**
496+
* @return {@code true} when the entry script is being launched directly as a
497+
* module via `nextflow module run`. Overridden by {@link nextflow.cli.module.CmdModuleRun}.
498+
*/
499+
protected boolean isModuleRun() { false }
500+
494501
static void detectModuleBinaryFeature(ConfigMap config) {
495502
final moduleBinaries = config.navigate('nextflow.enable.moduleBinaries', false)
496503
if( moduleBinaries ) {

modules/nextflow/src/main/groovy/nextflow/cli/module/CmdModuleRun.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class CmdModuleRun extends CmdRun {
5454
return 'run'
5555
}
5656

57+
@Override
58+
protected boolean isModuleRun() { true }
59+
5760
@Override
5861
void run() {
5962
if( !args ) {

modules/nextflow/src/main/groovy/nextflow/processor/TaskProcessor.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,12 @@ class TaskProcessor {
15691569
ResourcesBundle getModuleBundle() {
15701570
final script = this.getOwnerScript()
15711571
final meta = ScriptMeta.get(script)
1572-
return meta?.isModule() ? meta.getModuleBundle() : null
1572+
if( meta?.scriptPath == null )
1573+
return null
1574+
// The bundle is resolved when the owner script is either an included
1575+
// module, or it is the entry script of a `nextflow module run`
1576+
// invocation -- see #7087
1577+
return (meta.isModule() || session.isModuleRun()) ? meta.getModuleBundle() : null
15731578
}
15741579

15751580
@Memoized

modules/nextflow/src/test/groovy/nextflow/processor/TaskProcessorTest.groovy

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import nextflow.script.BaseScript
3535
import nextflow.script.BodyDef
3636
import nextflow.script.ProcessConfig
3737
import nextflow.script.ProcessConfigV1
38+
import nextflow.script.ScriptMeta
3839
import nextflow.script.ScriptType
3940
import nextflow.script.bundle.ResourcesBundle
4041
import nextflow.script.params.FileInParam
@@ -100,6 +101,49 @@ class TaskProcessorTest extends Specification {
100101

101102
}
102103

104+
@Unroll
105+
def 'should resolve module bundle for entry script when running as module #desc' () {
106+
given:
107+
def folder = Files.createTempDirectory('test')
108+
def mod = folder.resolve('mod1'); mod.mkdir()
109+
def bin = mod.resolve('resources/usr/bin'); bin.mkdirs()
110+
def scriptPath = mod.resolve('main.nf'); Files.createFile(scriptPath)
111+
Files.createFile(bin.resolve('echo.sh'))
112+
and:
113+
def script = Mock(BaseScript)
114+
def meta = Mock(ScriptMeta) {
115+
getScriptPath() >> scriptPath
116+
isModule() >> IS_MODULE
117+
getModuleBundle() >> ResourcesBundle.scan(mod.resolve('resources'))
118+
}
119+
and:
120+
def session = Mock(Session) {
121+
getConfig() >> [:]
122+
isModuleRun() >> IS_MODULE_RUN
123+
}
124+
def executor = Mock(Executor) {}
125+
def processor = Spy(TaskProcessor, constructorArgs: [[session:session, executor:executor]])
126+
processor.getOwnerScript() >> script
127+
128+
when:
129+
ResourcesBundle bundle
130+
GroovyMock(ScriptMeta, global: true)
131+
ScriptMeta.get(script) >> meta
132+
bundle = processor.getModuleBundle()
133+
134+
then:
135+
(bundle != null) == EXPECTED
136+
137+
cleanup:
138+
folder?.deleteDir()
139+
140+
where:
141+
desc | IS_MODULE | IS_MODULE_RUN | EXPECTED
142+
'(included module)' | true | false | true
143+
'(nextflow module run entry)' | false | true | true
144+
'(plain entry, no module run flag)' | false | false | false
145+
}
146+
103147
@Unroll
104148
def 'should add module bin paths to task env' () {
105149
given:

0 commit comments

Comments
 (0)