MultipleExecCommand
Command intended to execute multiple build_systems synchronously.
multiple_exec.py:
import re
import sys
import os
import time
import sublime
import functools
from Default.exec import AsyncProcess
from Default.exec import ExecCommand
class MultipleExecCommand(ExecCommand):
def run(self, build_systems=[]):
if build_systems:
self.build_systems = build_systems
self.cmd_index = -1
self.common_working_dir = os.path.commonprefix(
[bs["working_dir"] for bs in build_systems]
)
if self.build_systems:
bs = self.build_systems.pop(0)
self.cmd_index += 1
self.rel_working_dir = os.path.relpath(
bs["working_dir"], self.common_working_dir
)
self._run(**bs)
def _run(
self,
cmd=None,
shell_cmd=None,
file_regex="",
line_regex="",
working_dir="",
encoding="utf-8",
env={},
quiet=False,
kill=False,
update_phantoms_only=False,
hide_phantoms_only=False,
word_wrap=True,
syntax="Packages/Text/Plain text.tmLanguage",
# Catches "path" and "shell"
**kwargs):
self.file_regex = file_regex
if update_phantoms_only:
if self.show_errors_inline:
self.update_phantoms()
return
if hide_phantoms_only:
self.hide_phantoms()
return
# clear the text_queue
self.text_queue_lock.acquire()
try:
self.text_queue.clear()
self.text_queue_proc = None
finally:
self.text_queue_lock.release()
if kill:
if self.proc:
self.proc.kill()
self.proc = None
self.append_string(None, "[Cancelled]")
return
if not hasattr(self, 'output_view'):
print('creating output panel')
# Try not to call get_output_panel until the regexes are assigned
self.output_view = self.window.create_output_panel("exec")
# Default the to the current files directory if no working directory was
# given
if working_dir == "" and self.window.active_view() and self.window.active_view().file_name():
working_dir = os.path.dirname(self.window.active_view().file_name())
self.output_view.settings().set("result_file_regex", file_regex)
self.output_view.settings().set("result_line_regex", line_regex)
self.output_view.settings().set(
"result_base_dir", self.common_working_dir)
self.output_view.settings().set("word_wrap", word_wrap)
self.output_view.settings().set("line_numbers", False)
self.output_view.settings().set("gutter", False)
self.output_view.settings().set("scroll_past_end", False)
self.output_view.assign_syntax(syntax)
self.encoding = encoding
self.quiet = quiet
self.proc = None
if not self.quiet:
if shell_cmd:
print("{} - Running {}".format(self.cmd_index, shell_cmd))
elif cmd:
print("{} - Running {}".format(self.cmd_index, " ".join(cmd)))
sublime.status_message("Building")
show_panel_on_build = sublime.load_settings(
"Preferences.sublime-settings").get("show_panel_on_build", True)
if show_panel_on_build:
self.window.run_command("show_panel", {"panel": "output.exec"})
self.hide_phantoms()
self.show_errors_inline = sublime.load_settings(
"Preferences.sublime-settings").get("show_errors_inline", True)
merged_env = env.copy()
if self.window.active_view():
user_env = self.window.active_view().settings().get('build_env')
if user_env:
merged_env.update(user_env)
# Change to the working dir, rather than spawning the process with it,
# so that emitted working dir relative path names make sense
if working_dir != "":
os.chdir(working_dir)
self.debug_text = ""
if shell_cmd:
self.debug_text += "[shell_cmd: " + shell_cmd + "]\n"
else:
self.debug_text += "[cmd: " + str(cmd) + "]\n"
self.debug_text += "[dir: " + str(os.getcwd()) + "]\n"
if "PATH" in merged_env:
self.debug_text += "[path: " + str(merged_env["PATH"]) + "]"
else:
self.debug_text += "[path: " + str(os.environ["PATH"]) + "]"
try:
# Forward kwargs to AsyncProcess
self.proc = AsyncProcess(cmd, shell_cmd, merged_env, self, **kwargs)
self.text_queue_lock.acquire()
try:
self.text_queue_proc = self.proc
finally:
self.text_queue_lock.release()
except Exception as e:
self.append_string(None, str(e) + "\n")
self.append_string(None, self.debug_text + "\n")
if not self.quiet:
self.append_string(None, "[Finished]")
def on_data(self, proc, data):
try:
characters = data.decode(self.encoding)
except:
characters = "[Decode error - output not " + self.encoding + "]\n"
proc = None
# Normalize newlines, Sublime Text always uses a single \n separator
# in memory.
characters = characters.replace('\r\n', '\n').replace('\r', '\n')
lines = characters.split("\n")
while lines:
line = lines.pop(0)
m = re.match(self.file_regex, line)
if m:
line = "{}{}{}".format(
line[:m.start(1)],
os.path.join(self.rel_working_dir, m.group(1)),
line[m.end(1):]
)
final_line = "\n" if line == '' else line
self.append_string(proc, final_line)
def on_finished(self, proc):
if not self.quiet:
elapsed = time.time() - proc.start_time
exit_code = proc.exit_code()
if exit_code == 0 or exit_code is None:
self.append_string(proc, "\n[Finished in %.1fs]\n" % elapsed)
else:
self.append_string(
proc, "\n[Finished in %.1fs with exit code %d]\n" % (elapsed, exit_code))
self.append_string(proc, self.debug_text)
self.append_string(proc, "-" * 80 + "\n")
if proc != self.proc:
return
errs = self.output_view.find_all_results()
if len(errs) == 0:
sublime.status_message("Build finished")
else:
sublime.status_message("Build finished with %d errors" % len(errs))
# -------- Consume another build_system --------
self.run()
Usage example
Create the below 3 files and use the UnitTesting package.
foo.cpp:
#include <stdio.h>
void main() { foo1 }
foo2/foo2.cpp
#include <stdio.h>
void main() { foo2 }
test_multiple_exec.py:
import os
from multiple_exec import MultipleExecCommand
import sublime
code = "import time; print('pythontest_'*30);time.sleep(1)"
build_systems = [
{
"file_regex": "^(?:\\.{2}\\\\)*([a-z]?:?[^\\(\n]+)\\(([^\\)]+)\\): ([^\n]+)",
"shell_cmd": "cl /c foo.cpp",
"working_dir": os.path.join(os.path.dirname(__file__), "."),
"quiet": False
},
{
"file_regex": "^(?:\\.{2}\\\\)*([a-z]?:?[^\\(\n]+)\\(([^\\)]+)\\): ([^\n]+)",
"shell_cmd": 'python -c "{}"'.format(code),
"working_dir": os.path.join(os.path.dirname(__file__), "foo"),
"quiet": False
},
{
"file_regex": "^(?:\\.{2}\\\\)*([a-z]?:?[^\\(\n]+)\\(([^\\)]+)\\): ([^\n]+)",
"shell_cmd": "cl /c foo2.cpp",
"working_dir": os.path.join(os.path.dirname(__file__), "foo"),
"quiet": False
},
]
command = MultipleExecCommand(sublime.active_window())
command.run(build_systems)
Question
Problem with the above command is that the output is totally undeterministic… take a look to this video to see what I mean.
So, how can I make sure given a certain set of build_systems S = [b1,b2,…, bN] the output is always the same? For instance:
build_system1 line1
build_system1 line2
...
build_system1 lineN
[Finished in build_system1_time s]
--------------------------------------------------------------------------------
build_system2 line1
build_system2 line2
...
build_system2 lineN
[Finished in build_system2_time s]
--------------------------------------------------------------------------------
...
--------------------------------------------------------------------------------
build_systemN line1
build_systemN line2
...
build_system2 lineN
[Finished in build_systemN_time s]
--------------------------------------------------------------------------------
Thanks in advance.