Skip to content

Commit cd99156

Browse files
committed
Allow separate output locations for work and log file
1 parent c372c63 commit cd99156

File tree

4 files changed

+92
-57
lines changed

4 files changed

+92
-57
lines changed

nextflow/command.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ def run(*args, **kwargs):
1919
"""Runs a pipeline and returns the execution.
2020
2121
:param str pipeline_path: the absolute path to the pipeline .nf file.
22-
:param str run_path: the location to run the pipeline in.
23-
:param str output_path: the location to store the output in.
22+
:param str run_path: the location to run the pipeline in (if not current directory).
23+
:param str output_path: the location to store the output in (if not run path).
24+
:param str log_path: the location to store the log in (if not output path).
2425
:param resume: whether to resume an existing execution.
2526
:param function runner: a function to run the pipeline command.
2627
:param str version: the nextflow version to use.
@@ -42,8 +43,9 @@ def run_and_poll(*args, **kwargs):
4243
update.
4344
4445
:param str pipeline_path: the absolute path to the pipeline .nf file.
45-
:param str run_path: the location to run the pipeline in.
46-
:param str output_path: the location to store the output in.
46+
:param str run_path: the location to run the pipeline in (if not current directory).
47+
:param str output_path: the location to store the output in (if not run path).
48+
:param str log_path: the location to store the log in (if not output path).
4749
:param resume: whether to resume an existing execution.
4850
:param function runner: a function to run the pipeline command.
4951
:param str version: the nextflow version to use.
@@ -63,14 +65,17 @@ def run_and_poll(*args, **kwargs):
6365

6466

6567
def _run(
66-
pipeline_path, resume=False, poll=False, run_path=None, output_path=None, runner=None,
68+
pipeline_path, resume=False, poll=False, run_path=None, output_path=None,
69+
log_path=None,runner=None,
6770
version=None, configs=None, params=None, profiles=None, timezone=None,
6871
report=None, timeline=None, dag=None, trace=None, sleep=1
6972
):
7073
if not run_path: run_path = os.path.abspath(".")
74+
if not output_path: output_path = run_path
75+
if not log_path: log_path = output_path
7176
nextflow_command = make_nextflow_command(
72-
run_path, output_path, pipeline_path, resume, version, configs, params,
73-
profiles, timezone, report, timeline, dag, trace
77+
run_path, output_path, log_path, pipeline_path, resume, version, configs,
78+
params, profiles, timezone, report, timeline, dag, trace
7479
)
7580
start = datetime.now()
7681
if runner:
@@ -81,11 +86,11 @@ def _run(
8186
nextflow_command, universal_newlines=True, shell=True
8287
)
8388
execution, log_start = None, 0
84-
if resume: wait_for_log_creation(output_path or run_path, start)
89+
if resume: wait_for_log_creation(log_path, start)
8590
while True:
8691
time.sleep(sleep)
8792
execution, diff = get_execution(
88-
output_path or run_path, nextflow_command, execution, log_start
93+
output_path, log_path, nextflow_command, execution, log_start
8994
)
9095
log_start += diff
9196
if execution and poll: yield execution
@@ -95,11 +100,12 @@ def _run(
95100
break
96101

97102

98-
def make_nextflow_command(run_path, output_path, pipeline_path, resume,version, configs, params, profiles, timezone, report, timeline, dag, trace):
103+
def make_nextflow_command(run_path, output_path, log_path, pipeline_path, resume,version, configs, params, profiles, timezone, report, timeline, dag, trace):
99104
"""Generates the `nextflow run` commmand.
100105
101106
:param str run_path: the location to run the pipeline in.
102107
:param str output_path: the location to store the output in.
108+
:param str log_path: the location to store the log in.
103109
:param str pipeline_path: the absolute path to the pipeline .nf file.
104110
:param bool resume: whether to resume an existing execution.
105111
:param str version: the nextflow version to use.
@@ -113,10 +119,10 @@ def make_nextflow_command(run_path, output_path, pipeline_path, resume,version,
113119
:param str trace: the filename to use for the trace report.
114120
:rtype: ``str``"""
115121

116-
env = make_nextflow_command_env_string(version, timezone, output_path)
122+
env = make_nextflow_command_env_string(version, timezone, output_path, run_path)
117123
if env: env += " "
118124
nf = "nextflow -Duser.country=US"
119-
log = make_nextflow_command_log_string(output_path)
125+
log = make_nextflow_command_log_string(log_path, run_path)
120126
if log: log += " "
121127
configs = make_nextflow_command_config_string(configs)
122128
if configs: configs += " "
@@ -126,15 +132,15 @@ def make_nextflow_command(run_path, output_path, pipeline_path, resume,version,
126132
profiles = make_nextflow_command_profiles_string(profiles)
127133
reports = make_reports_string(output_path, report, timeline, dag, trace)
128134
command = f"{env}{nf} {log}{configs}run {pipeline_path} {resume}{params} {profiles} {reports}"
129-
if run_path: command = f"cd {run_path}; {command}"
130-
prefix = (str(output_path) + os.path.sep) if output_path else ""
135+
if run_path != os.path.abspath("."): command = f"cd {run_path}; {command}"
136+
prefix = (str(output_path) + os.path.sep) if output_path != run_path else ""
131137
command = command.rstrip() + f" >{prefix}"
132138
command += f"stdout.txt 2>{prefix}"
133139
command += f"stderr.txt; echo $? >{prefix}rc.txt"
134140
return command
135141

136142

137-
def make_nextflow_command_env_string(version, timezone, output_path):
143+
def make_nextflow_command_env_string(version, timezone, output_path, run_path):
138144
"""Creates the environment variable setting portion of the nextflow run
139145
command string.
140146
@@ -146,18 +152,18 @@ def make_nextflow_command_env_string(version, timezone, output_path):
146152
env = {"NXF_ANSI_LOG": "false"}
147153
if version: env["NXF_VER"] = version
148154
if timezone: env["TZ"] = timezone
149-
if output_path: env["NXF_WORK"] = os.path.join(output_path, "work")
155+
if output_path != run_path: env["NXF_WORK"] = os.path.join(output_path, "work")
150156
return " ".join([f"{k}={v}" for k, v in env.items()])
151157

152158

153-
def make_nextflow_command_log_string(output_path):
159+
def make_nextflow_command_log_string(log_path, run_path):
154160
"""Creates the log setting portion of the nextflow run command string.
155161
156-
:param str output_path: the location to store the output in.
162+
:param str log_path: the location to store the log file in.
157163
:rtype: ``str``"""
158164

159-
if not output_path: return ""
160-
return f"-log '{os.path.join(output_path, '.nextflow.log')}'"
165+
if log_path == run_path: return ""
166+
return f"-log '{os.path.join(log_path, '.nextflow.log')}'"
161167

162168

163169
def make_nextflow_command_config_string(configs):
@@ -245,17 +251,18 @@ def wait_for_log_creation(output_path, start):
245251
time.sleep(0.1)
246252

247253

248-
def get_execution(execution_path, nextflow_command, execution=None, log_start=0):
254+
def get_execution(execution_path, log_path, nextflow_command, execution=None, log_start=0):
249255
"""Creates an execution object from a location. If you are polling, you can
250256
pass in the previous execution to update it with new information.
251257
252258
:param str execution_path: the location of the execution.
259+
:param str log_path: the location of the log.
253260
:param str nextflow_command: the command used to run the pipeline.
254261
:param nextflow.models.Execution execution: the existing execution, if any.
255262
:param int log_start: the number of lines already read from the log.
256263
:rtype: ``nextflow.models.Execution``"""
257264

258-
log = get_file_text(os.path.join(execution_path, ".nextflow.log"))
265+
log = get_file_text(os.path.join(log_path, ".nextflow.log"))
259266
if not log: return None, 0
260267
log = log[log_start:]
261268
execution = make_or_update_execution(log, execution_path, nextflow_command, execution)

tests/integration/base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ def check_running_execution(self, execution, last_stdout, output_path=None):
3333
return execution.stdout
3434

3535

36-
def check_execution(self, execution, line_count=24, output_path=None, version=None, timezone=None, report=None, timeline=None, dag=None, trace=None, check_stderr=True):
36+
def check_execution(self, execution, line_count=24, output_path=None, log_path=None, version=None, timezone=None, report=None, timeline=None, dag=None, trace=None, check_stderr=True):
3737
# Files created
3838
if not output_path: self.assertIn(".nextflow", os.listdir(self.get_path("rundirectory")))
39-
self.assertIn(".nextflow.log", os.listdir(output_path or self.get_path("rundirectory")))
39+
if log_path:
40+
self.assertIn(".nextflow.log", os.listdir(log_path))
41+
else:
42+
self.assertIn(".nextflow.log", os.listdir(output_path or self.get_path("rundirectory")))
4043

4144
# Execution is correct
4245
self.assertTrue(re.match(r"[a-z]+_[a-z]+", execution.identifier))

tests/integration/test_run.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ def test_can_handle_pipeline_error(self):
133133
self.assertEqual(lower2.return_code, "0")
134134
self.assertFalse(lower2.cached)
135135

136-
137-
138-
139136

140137

141138
class CustomRunningTests(RunTestCase):
@@ -202,6 +199,30 @@ def test_can_run_with_specific_run_location_and_output_location(self):
202199
shutil.rmtree(outputs_path)
203200

204201

202+
def test_can_run_with_specific_log_location(self):
203+
# Make location for outputs
204+
os.chdir(self.rundirectory)
205+
log_path = os.path.sep + os.path.join(*self.rundirectory.split(os.path.sep)[:-1], "log_loc")
206+
os.mkdir(log_path)
207+
208+
try:
209+
# Run basic execution
210+
execution = nextflow.run(
211+
pipeline_path=self.get_path("pipeline.nf"),
212+
log_path=str(log_path),
213+
params={
214+
"input": self.get_path("files/data.txt"), "count": "12",
215+
"suffix": self.get_path("files/suffix.txt")
216+
}
217+
)
218+
219+
# Execution is fine
220+
self.check_execution(execution, log_path=str(log_path))
221+
finally:
222+
# Remove outputs
223+
shutil.rmtree(log_path)
224+
225+
205226
def test_can_run_with_runner(self):
206227
# Make runner function
207228
def runner(command):

0 commit comments

Comments
 (0)