def run(self): print("Welcome to thread " + self.getName()) cmd = self.caller.make_cmd + [self.caller.file_name] self.caller.output("[Compiling " + self.caller.file_name + "]") if DEBUG: print(cmd.encode('UTF-8')) # Handle path; copied from exec.py if self.caller.path: old_path = os.environ["PATH"] # The user decides in the build system whether he wants to append $PATH # or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH" # Handle differently in Python 2 and 3, to be safe: if not _ST3: os.environ["PATH"] = os.path.expandvars( self.caller.path).encode(sys.getfilesystemencoding()) else: os.environ["PATH"] = os.path.expandvars(self.caller.path) try: if platform.system() == "Windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW proc = subprocess.Popen(cmd, startupinfo=startupinfo) else: proc = subprocess.Popen(cmd) except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.proc = None return # restore path if needed if self.caller.path: os.environ["PATH"] = old_path # Handle killing # First, save process handle into caller; then communicate (which blocks) self.caller.proc = proc # out, err = proc.communicate() proc.wait( ) # TODO: if needed, must use tempfiles instead of stdout/err # if DEBUG: # self.caller.output(out) # Here the process terminated, but it may have been killed. If so, do not process log file. # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? if not self.caller.proc: print(proc.returncode) self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: self.caller.proc = None print("Finished normally") print(proc.returncode) # this is a conundrum. We used (ST1) to open in binary mode ('rb') to avoid # issues, but maybe we just need to decode? # 12-10-27 NO! We actually do need rb, because MikTeX on Windows injects Ctrl-Z's in the # log file, and this just causes Python to stop reading the file. # OK, this seems solid: first we decode using the self.caller.encoding, # then we reencode using the default locale's encoding. # Note: we get this using ST2's own getdefaultencoding(), not the locale module # We ignore bad chars in both cases. # CHANGED 12/10/19: use platform encoding (self.caller.encoding), then # keep it that way! # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode on Windows # because silly MiKTeX inserts ASCII control characters in over/underfull warnings. # In particular it inserts EOFs, which stop reading altogether; reading in binary # prevents that. However, that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line in two. This messes up # line numbers in error reports. If, on the other hand, we invoke splitlines on a # byte array (? whatever read() returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant encoding. # 121101 -- moved splitting and decoding logic to parseTeXlog, where it belongs. # Note to self: need to think whether we don't want to codecs.open this, too... data = open(self.caller.tex_base + ".log", 'rb').read() errors = [] warnings = [] try: (errors, warnings) = parseTeXlog.parse_tex_log(data) content = ["", ""] if errors: content.append("There were errors in your LaTeX source") content.append("") content.extend(errors) else: content.append("Texification succeeded!") content.append("") if warnings: if errors: content.append("") content.append("There were also warnings.") else: content.append( "However, there were warnings in your LaTeX source") content.append("") content.extend(warnings) except Exception as e: content = ["", ""] content.append("LaTeXtools could not parse the TeX log file") content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: " + repr(e)) content.append("") content.append("Please let me know on GitHub. Thanks!") self.caller.output(content) if errors: self.caller.output("\n\n[Finished, with Errors...]\n") else: self.caller.output("\n\n[Done!]\n") self.caller.finish(len(errors) == 0)
def run ( self ): print ("Welcome to thread " + self.getName()) self.caller.output("[Compiling " + self.caller.file_name + "]") # Handle custom env variables if self.caller.env: old_env = os.environ; if not _ST3: os.environ.update(dict((k.encode(sys.getfilesystemencoding()), v) for (k, v) in self.caller.env.items())) else: os.environ.update(self.caller.env.items()); # Handle path; copied from exec.py if self.caller.path: # if we had an env, the old path is already backuped in the env if not self.caller.env: old_path = os.environ["PATH"] # The user decides in the build system whether he wants to append $PATH # or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH" # Handle differently in Python 2 and 3, to be safe: if not _ST3: os.environ["PATH"] = os.path.expandvars(self.caller.path).encode(sys.getfilesystemencoding()) else: os.environ["PATH"] = os.path.expandvars(self.caller.path) # Set up Windows-specific parameters if self.caller.plat == "windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # Now, iteratively call the builder iterator # cmd_iterator = self.caller.builder.commands() for (cmd, msg) in cmd_iterator: # If there is a message, display it if msg: self.caller.output(msg) # If there is nothing to be done, exit loop # (Avoids error with empty cmd_iterator) if cmd == "": break print(cmd) # Now create a Popen object try: if self.caller.plat == "windows": proc = subprocess.Popen(cmd, startupinfo=startupinfo, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) elif self.caller.plat == "osx": # Temporary (?) fix for Yosemite: pass environment proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=os.environ, preexec_fn=os.setsid ) else: # Must be linux proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, preexec_fn=os.setsid ) except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None if self.caller.env: os.environ = old_env elif self.caller.path: os.environ["PATH"] = old_path return # Now actually invoke the command, making sure we allow for killing # First, save process handle into caller; then communicate (which blocks) with self.caller.proc_lock: self.caller.proc = proc out, err = proc.communicate() self.caller.builder.set_output(out.decode(self.caller.encoding,"ignore")) # Here the process terminated, but it may have been killed. If so, stop and don't read log # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? with self.caller.proc_lock: if not self.caller.proc: print (proc.returncode) self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: with self.caller.proc_lock: self.caller.proc = None print ("Finished normally") print (proc.returncode) # At this point, out contains the output from the current command; # we pass it to the cmd_iterator and get the next command, until completion # Clean up cmd_iterator.close() # restore env or path if needed if self.caller.env: os.environ = old_env elif self.caller.path: os.environ["PATH"] = old_path # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode on Windows # because silly MiKTeX inserts ASCII control characters in over/underfull warnings. # In particular it inserts EOFs, which stop reading altogether; reading in binary # prevents that. However, that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line in two. This messes up # line numbers in error reports. If, on the other hand, we invoke splitlines on a # byte array (? whatever read() returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant encoding. # 121101 -- moved splitting and decoding logic to parseTeXlog, where it belongs. # Note to self: need to think whether we don't want to codecs.open this, too... # Also, we may want to move part of this logic to the builder... data = open(self.caller.tex_base + ".log", 'rb').read() errors = [] warnings = [] try: (errors, warnings) = parseTeXlog.parse_tex_log(data) content = [""] if errors: content.append("Errors:") content.append("") content.extend(errors) else: content.append("No errors.") if warnings: if errors: content.extend(["", "Warnings:"]) else: content[-1] = content[-1] + " Warnings:" content.append("") content.extend(warnings) else: content.append("") hide_panel = { "always": True, "no_errors": not errors, "no_warnings": not errors and not warnings, "never": False }.get(self.caller.hide_panel_level, False) if hide_panel: # hide the build panel (ST2 api is not thread save) if _ST3: self.caller.window.run_command("hide_panel", {"panel": "output.exec"}) else: sublime.set_timeout(lambda: self.caller.window.run_command("hide_panel", {"panel": "output.exec"}), 10) message = "build completed" if errors: message += " with errors" if warnings: message += " and" if errors else " with" message += " warnings" if _ST3: sublime.status_message(message) else: sublime.set_timeout(lambda: sublime.status_message(message), 10) except Exception as e: content=["",""] content.append("LaTeXtools could not parse the TeX log file") content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: " + repr(e)) content.append("") content.append("Please let me know on GitHub. Thanks!") self.caller.output(content) self.caller.output("\n\n[Done!]\n") self.caller.finish(len(errors) == 0)
def run ( self ): print ("Welcome to thread " + self.getName()) self.caller.output("[Compiling " + self.caller.file_name + "]") # Handle custom env variables if self.caller.env: old_env = os.environ; if not _ST3: os.environ.update(dict((k.encode(sys.getfilesystemencoding()), v) for (k, v) in self.caller.env.items())) else: os.environ.update(self.caller.env.items()); # Handle path; copied from exec.py if self.caller.path: # if we had an env, the old path is already backuped in the env if not self.caller.env: old_path = os.environ["PATH"] # The user decides in the build system whether he wants to append $PATH # or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH" # Handle differently in Python 2 and 3, to be safe: if not _ST3: os.environ["PATH"] = os.path.expandvars(self.caller.path).encode(sys.getfilesystemencoding()) else: os.environ["PATH"] = os.path.expandvars(self.caller.path) # Set up Windows-specific parameters if self.caller.plat == "windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # Now, iteratively call the builder iterator # cmd_iterator = self.caller.builder.commands() try: for (cmd, msg) in cmd_iterator: # If there is a message, display it if msg: self.caller.output(msg) # If there is nothing to be done, exit loop # (Avoids error with empty cmd_iterator) if cmd == "": break if isinstance(cmd, strbase) or isinstance(cmd, list): print(cmd) # Now create a Popen object try: if self.caller.plat == "windows": proc = subprocess.Popen(cmd, startupinfo=startupinfo, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) elif self.caller.plat == "osx": # Temporary (?) fix for Yosemite: pass environment proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=os.environ, preexec_fn=os.setsid ) else: # Must be linux proc = subprocess.Popen( cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, preexec_fn=os.setsid ) except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None print(traceback.format_exc()) return # Abundance of caution / for possible future extensions: elif isinstance(cmd, subprocess.Popen): proc = cmd else: # don't know what the command is continue # Now actually invoke the command, making sure we allow for killing # First, save process handle into caller; then communicate (which blocks) with self.caller.proc_lock: self.caller.proc = proc out, err = proc.communicate() self.caller.builder.set_output(out.decode(self.caller.encoding,"ignore")) # Here the process terminated, but it may have been killed. If so, stop and don't read log # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? with self.caller.proc_lock: if not self.caller.proc: print (proc.returncode) self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: with self.caller.proc_lock: self.caller.proc = None print ("Finished normally") print (proc.returncode) # At this point, out contains the output from the current command; # we pass it to the cmd_iterator and get the next command, until completion except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None print(traceback.format_exc()) return finally: # restore environment if self.caller.env: os.environ = old_env elif self.caller.path: os.environ['PATH'] = old_path # Clean up cmd_iterator.close() try: # Here we try to find the log file... # 1. Check the aux_directory if there is one # 2. Check the output_directory if there is one # 3. Assume the log file is in the same folder as the main file log_file_base = self.caller.tex_base + ".log" if self.caller.aux_directory is None: if self.caller.output_directory is None: log_file = log_file_base else: log_file = os.path.join( self.caller.output_directory, log_file_base ) else: log_file = os.path.join( self.caller.aux_directory, log_file_base ) if not os.path.exists(log_file): if (self.caller.output_directory is not None and self.caller.output_directory != self.caller.aux_directory ): log_file = os.path.join( self.caller.output_directory, log_file_base ) if not os.path.exists(log_file): log_file = log_file_base # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode # on Windows because silly MiKTeX inserts ASCII control characters in # over/underfull warnings. In particular it inserts EOFs, which # stop reading altogether; reading in binary prevents that. However, # that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line # in two. This messes up line numbers in error reports. If, on the # other hand, we invoke splitlines on a byte array (? whatever read() # returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant # encoding. # Note to self: need to think whether we don't want to codecs.open # this, too... Also, we may want to move part of this logic to the # builder... with open(log_file, 'rb') as f: data = f.read() except IOError: self.caller.output([ "", "" "Could not find log file {0}!".format(log_file_base), ]) try: self.handle_std_outputs(out, err) except: # if out or err don't yet exist self.caller.finish(False) else: errors = [] warnings = [] badboxes = [] try: (errors, warnings, badboxes) = parseTeXlog.parse_tex_log(data) content = [""] if errors: content.append("Errors:") content.append("") content.extend(errors) else: content.append("No errors.") if warnings: if errors: content.extend(["", "Warnings:"]) else: content[-1] = content[-1] + " Warnings:" content.append("") content.extend(warnings) else: if errors: content.append("") content.append("No warnings.") if badboxes and self.caller.display_bad_boxes: if warnings or errors: content.extend(["", "Bad Boxes:"]) else: content[-1] = content[-1] + " Bad Boxes:" content.append("") content.extend(badboxes) else: if self.caller.display_bad_boxes: if errors or warnings: content.append("") content.append("No bad boxes.") hide_panel = { "always": True, "no_errors": not errors, "no_warnings": not errors and not warnings, "no_badboxes": not errors and not warnings and \ (not self.caller.display_bad_boxes or not badboxes), "never": False }.get(self.caller.hide_panel_level, False) if hide_panel: # hide the build panel (ST2 api is not thread save) if _ST3: self.caller.window.run_command("hide_panel", {"panel": "output.latextools"}) else: sublime.set_timeout(lambda: self.caller.window.run_command("hide_panel", {"panel": "output.latextools"}), 10) message = "build completed" if errors: message += " with errors" if warnings: if errors: if badboxes and self.caller.display_bad_boxes: message += "," else: message += " and" else: message += " with" message += " warnings" if badboxes and self.caller.display_bad_boxes: if errors or warnings: message += " and" else: message += " with" message += " bad boxes" if _ST3: sublime.status_message(message) else: sublime.set_timeout(lambda: sublime.status_message(message), 10) except Exception as e: # dumpt exception to console traceback.print_exc() content = ["", ""] content.append( "LaTeXTools could not parse the TeX log file {0}".format( log_file ) ) content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: {0!r}".format(e)) content.append("") content.append( "The full error description can be found on the console." ) content.append("Please let us know on GitHub. Thanks!") self.caller.output(content) self.caller.output("\n\n[Done!]\n") self.caller.finish(len(errors) == 0)
def run ( self ): print "Welcome to thread " + self.getName() cmd = self.caller.make_cmd + [self.caller.file_name] self.caller.output("[Compiling " + self.caller.file_name + "]") if DEBUG: print cmd.encode('UTF-8') # Handle path; copied from exec.py if self.caller.path: old_path = os.environ["PATH"] # The user decides in the build system whether he wants to append $PATH # or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH" os.environ["PATH"] = os.path.expandvars(self.caller.path).encode(sys.getfilesystemencoding()) try: if platform.system() == "Windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW proc = subprocess.Popen(cmd, startupinfo=startupinfo) else: proc = subprocess.Popen(cmd) except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.proc = None return # restore path if needed if self.caller.path: os.environ["PATH"] = old_path # Handle killing # First, save process handle into caller; then communicate (which blocks) self.caller.proc = proc # out, err = proc.communicate() proc.wait() # TODO: if needed, must use tempfiles instead of stdout/err # if DEBUG: # self.caller.output(out) # Here the process terminated, but it may have been killed. If so, do not process log file. # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? if not self.caller.proc: print proc.returncode self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: self.caller.proc = None print "Finished normally" print proc.returncode # this is a conundrum. We used (ST1) to open in binary mode ('rb') to avoid # issues, but maybe we just need to decode? # 12-10-27 NO! We actually do need rb, because MikTeX on Windows injects Ctrl-Z's in the # log file, and this just causes Python to stop reading the file. # OK, this seems solid: first we decode using the self.caller.encoding, # then we reencode using the default locale's encoding. # Note: we get this using ST2's own getdefaultencoding(), not the locale module # We ignore bad chars in both cases. # CHANGED 12/10/19: use platform encoding (self.caller.encoding), then # keep it that way! # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode on Windows # because silly MiKTeX inserts ASCII control characters in over/underfull warnings. # In particular it inserts EOFs, which stop reading altogether; reading in binary # prevents that. However, that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line in two. This messes up # line numbers in error reports. If, on the other hand, we invoke splitlines on a # byte array (? whatever read() returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant encoding. # 121101 -- moved splitting and decoding logic to parseTeXlog, where it belongs. data = open(self.caller.tex_base + ".log", 'rb').read() errors = [] warnings = [] try: (errors, warnings) = parseTeXlog.parse_tex_log(data) content = ["",""] if errors: content.append("There were errors in your LaTeX source") content.append("") content.extend(errors) else: content.append("Texification succeeded: no errors!") content.append("") if warnings: if errors: content.append("") content.append("There were also warnings.") else: content.append("However, there were warnings in your LaTeX source") content.append("") content.extend(warnings) except Exception as e: content=["",""] content.append("LaTeXtools could not parse the TeX log file") content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: " + repr(e)) content.append("") content.append("Please let me know on GitHub. Thanks!") self.caller.output(content) self.caller.output("\n\n[Done!]\n") self.caller.finish(len(errors) == 0)
def run ( self ): print ("Welcome to thread " + self.getName()) self.caller.output("[Compiling " + self.caller.file_name + "]") env = dict(os.environ) if self.caller.path: env['PATH'] = self.caller.path # Handle custom env variables if self.caller.env: update_env(env, self.caller.env) # Now, iteratively call the builder iterator # cmd_iterator = self.caller.builder.commands() try: for (cmd, msg) in cmd_iterator: # If there is a message, display it if msg: self.caller.output(msg) # If there is nothing to be done, exit loop # (Avoids error with empty cmd_iterator) if cmd == "": break if isinstance(cmd, strbase) or isinstance(cmd, list): print(cmd) # Now create a Popen object try: proc = external_command( cmd, env=env, use_texpath=False, preexec_fn=os.setsid if self.caller.plat != 'windows' else None, cwd=self.caller.tex_dir ) except: self.caller.show_output_panel() self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None traceback.print_exc() return # Abundance of caution / for possible future extensions: elif isinstance(cmd, subprocess.Popen): proc = cmd else: # don't know what the command is continue # Now actually invoke the command, making sure we allow for killing # First, save process handle into caller; then communicate (which blocks) with self.caller.proc_lock: self.caller.proc = proc out, err = proc.communicate() self.caller.builder.set_output(out.decode(self.caller.encoding,"ignore")) # Here the process terminated, but it may have been killed. If so, stop and don't read log # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? with self.caller.proc_lock: if not self.caller.proc: print (proc.returncode) self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: with self.caller.proc_lock: self.caller.proc = None print ("Finished normally") print (proc.returncode) # At this point, out contains the output from the current command; # we pass it to the cmd_iterator and get the next command, until completion except: self.caller.show_output_panel() self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None traceback.print_exc() return # Clean up cmd_iterator.close() try: # Here we try to find the log file... # 1. Check the aux_directory if there is one # 2. Check the output_directory if there is one # 3. Assume the log file is in the same folder as the main file log_file_base = self.caller.tex_base + ".log" if self.caller.aux_directory is None: if self.caller.output_directory is None: log_file = os.path.join( self.caller.tex_dir, log_file_base ) else: log_file = os.path.join( self.caller.output_directory, log_file_base ) if not os.path.exists(log_file): log_file = os.path.join( self.caller.tex_dir, log_file_base ) else: log_file = os.path.join( self.caller.aux_directory, log_file_base ) if not os.path.exists(log_file): if ( self.caller.output_directory is not None and self.caller.output_directory != self.caller.aux_directory ): log_file = os.path.join( self.caller.output_directory, log_file_base ) if not os.path.exists(log_file): log_file = os.path.join( self.caller.tex_dir, log_file_base ) # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode # on Windows because silly MiKTeX inserts ASCII control characters in # over/underfull warnings. In particular it inserts EOFs, which # stop reading altogether; reading in binary prevents that. However, # that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line # in two. This messes up line numbers in error reports. If, on the # other hand, we invoke splitlines on a byte array (? whatever read() # returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant # encoding. # Note to self: need to think whether we don't want to codecs.open # this, too... Also, we may want to move part of this logic to the # builder... with open(log_file, 'rb') as f: data = f.read() except IOError: traceback.print_exc() self.caller.show_output_panel() content = ['', 'Could not read log file {0}.log'.format( self.caller.tex_base ), ''] if out is not None: content.extend(['Output from compilation:', '', out.decode('utf-8')]) if err is not None: content.extend(['Errors from compilation:', '', err.decode('utf-8')]) self.caller.output(content) # if we got here, there shouldn't be a PDF at all self.caller.finish(False) else: errors = [] warnings = [] badboxes = [] try: (errors, warnings, badboxes) = parseTeXlog.parse_tex_log( data, self.caller.tex_dir ) content = [""] if errors: content.append("Errors:") content.append("") content.extend(errors) else: content.append("No errors.") if warnings: if errors: content.extend(["", "Warnings:"]) else: content[-1] = content[-1] + " Warnings:" content.append("") content.extend(warnings) else: if errors: content.append("") content.append("No warnings.") else: content[-1] = content[-1] + " No warnings." if badboxes and self.caller.display_bad_boxes: if warnings or errors: content.extend(["", "Bad Boxes:"]) else: content[-1] = content[-1] + " Bad Boxes:" content.append("") content.extend(badboxes) else: if self.caller.display_bad_boxes: if errors or warnings: content.append("") content.append("No bad boxes.") else: content[-1] = content[-1] + " No bad boxes." show_panel = { "always": False, "no_errors": bool(errors), "no_warnings": bool(errors or warnings), "no_badboxes": bool( errors or warnings or (self.caller.display_bad_boxes and badboxes)), "never": True }.get(self.caller.hide_panel_level, bool(errors or warnings)) if show_panel: self.caller.progress_indicator.success_message = "Build completed" self.caller.show_output_panel(force=True) else: message = "Build completed" if errors: message += " with errors" if warnings: if errors: if badboxes and self.caller.display_bad_boxes: message += "," else: message += " and" else: message += " with" message += " warnings" if badboxes and self.caller.display_bad_boxes: if errors or warnings: message += " and" else: message += " with" message += " bad boxes" self.caller.progress_indicator.success_message = message except Exception as e: self.caller.show_output_panel() content = ["", ""] content.append( "LaTeXTools could not parse the TeX log file {0}".format( log_file ) ) content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: {0!r}".format(e)) content.append("") content.append( "The full error description can be found on the console." ) content.append("Please let us know on GitHub. Thanks!") traceback.print_exc() self.caller.output(content) self.caller.output("\n\n[Done!]\n") if _HAS_PHANTOMS: self.caller.errors = locals().get("errors", []) self.caller.warnings = locals().get("warnings", []) self.caller.badboxes = locals().get("badboxes", []) self.caller.finish(len(errors) == 0)
def run(self): print("Welcome to thread " + self.getName()) self.caller.output("[Compiling " + self.caller.file_name + "]") env = dict(os.environ) if self.caller.path: env['PATH'] = self.caller.path # Handle custom env variables if self.caller.env: update_env(env, self.caller.env) # Now, iteratively call the builder iterator # cmd_iterator = self.caller.builder.commands() try: for (cmd, msg) in cmd_iterator: # If there is a message, display it if msg: self.caller.output(msg) # If there is nothing to be done, exit loop # (Avoids error with empty cmd_iterator) if cmd == "": break if isinstance(cmd, strbase) or isinstance(cmd, list): # Now create a Popen object try: proc = external_command( cmd, env=env, use_texpath=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=os.setsid if self.caller.plat != 'windows' else None, cwd=self.caller.tex_dir) except: self.caller.show_output_panel() self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None traceback.print_exc() return # Abundance of caution / for possible future extensions: elif isinstance(cmd, subprocess.Popen): proc = cmd else: # don't know what the command is continue # Now actually invoke the command, making sure we allow for killing # First, save process handle into caller; then communicate (which blocks) with self.caller.proc_lock: self.caller.proc = proc out, err = proc.communicate() self.caller.builder.set_output( out.decode(self.caller.encoding, "ignore")) # Here the process terminated, but it may have been killed. If so, stop and don't read log # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? with self.caller.proc_lock: if not self.caller.proc: print(proc.returncode) self.caller.output( "\n\n[User terminated compilation process]\n") self.caller.finish( False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: with self.caller.proc_lock: self.caller.proc = None print("Finished normally") print(proc.returncode) # At this point, out contains the output from the current command; # we pass it to the cmd_iterator and get the next command, until completion except: self.caller.show_output_panel() self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None traceback.print_exc() return # Clean up cmd_iterator.close() try: # Here we try to find the log file... # 1. Check the aux_directory if there is one # 2. Check the output_directory if there is one # 3. Assume the log file is in the same folder as the main file log_file_base = self.caller.tex_base + ".log" if self.caller.aux_directory is None: if self.caller.output_directory is None: log_file = os.path.join(self.caller.tex_dir, log_file_base) else: log_file = os.path.join(self.caller.output_directory, log_file_base) if not os.path.exists(log_file): log_file = os.path.join(self.caller.tex_dir, log_file_base) else: log_file = os.path.join(self.caller.aux_directory, log_file_base) if not os.path.exists(log_file): if (self.caller.output_directory is not None and self.caller.output_directory != self.caller.aux_directory): log_file = os.path.join(self.caller.output_directory, log_file_base) if not os.path.exists(log_file): log_file = os.path.join(self.caller.tex_dir, log_file_base) # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode # on Windows because silly MiKTeX inserts ASCII control characters in # over/underfull warnings. In particular it inserts EOFs, which # stop reading altogether; reading in binary prevents that. However, # that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line # in two. This messes up line numbers in error reports. If, on the # other hand, we invoke splitlines on a byte array (? whatever read() # returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant # encoding. # Note to self: need to think whether we don't want to codecs.open # this, too... Also, we may want to move part of this logic to the # builder... with open(log_file, 'rb') as f: data = f.read() except IOError: traceback.print_exc() self.caller.show_output_panel() content = [ '', 'Could not read log file {0}.log'.format(self.caller.tex_base), '' ] if out is not None: content.extend( ['Output from compilation:', '', out.decode('utf-8')]) if err is not None: content.extend( ['Errors from compilation:', '', err.decode('utf-8')]) self.caller.output(content) # if we got here, there shouldn't be a PDF at all self.caller.finish(False) else: errors = [] warnings = [] badboxes = [] try: (errors, warnings, badboxes) = parseTeXlog.parse_tex_log(data, self.caller.tex_dir) content = [""] if errors: content.append("Errors:") content.append("") content.extend(errors) else: content.append("No errors.") if warnings: if errors: content.extend(["", "Warnings:"]) else: content[-1] = content[-1] + " Warnings:" content.append("") content.extend(warnings) else: if errors: content.append("") content.append("No warnings.") else: content[-1] = content[-1] + " No warnings." if badboxes and self.caller.display_bad_boxes: if warnings or errors: content.extend(["", "Bad Boxes:"]) else: content[-1] = content[-1] + " Bad Boxes:" content.append("") content.extend(badboxes) else: if self.caller.display_bad_boxes: if errors or warnings: content.append("") content.append("No bad boxes.") else: content[-1] = content[-1] + " No bad boxes." content.append("") content.append(log_file + ":1: Double-click here to open the full log.") show_panel = { "always": False, "no_errors": bool(errors), "no_warnings": bool(errors or warnings), "no_badboxes": bool(errors or warnings or (self.caller.display_bad_boxes and badboxes)), "never": True }.get(self.caller.hide_panel_level, bool(errors or warnings)) if show_panel: self.caller.progress_indicator.success_message = "Build completed" self.caller.show_output_panel(force=True) else: message = "Build completed" if errors: message += " with errors" if warnings: if errors: if badboxes and self.caller.display_bad_boxes: message += "," else: message += " and" else: message += " with" message += " warnings" if badboxes and self.caller.display_bad_boxes: if errors or warnings: message += " and" else: message += " with" message += " bad boxes" self.caller.progress_indicator.success_message = message except Exception as e: self.caller.show_output_panel() content = ["", ""] content.append( "LaTeXTools could not parse the TeX log file {0}".format( log_file)) content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: {0!r}".format(e)) content.append("") content.append( "The full error description can be found on the console.") content.append("Please let us know on GitHub. Thanks!") traceback.print_exc() self.caller.output(content) self.caller.output("\n\n[Done!]\n") if _HAS_PHANTOMS: self.caller.errors = locals().get("errors", []) self.caller.warnings = locals().get("warnings", []) self.caller.badboxes = locals().get("badboxes", []) self.caller.finish(len(errors) == 0)
def run ( self ): print ("Welcome to thread " + self.getName()) self.caller.output("[Compiling " + self.caller.file_name + "]") # Handle custom env variables if self.caller.env: old_env = os.environ; if not _ST3: os.environ.update(dict((k.encode(sys.getfilesystemencoding()), v) for (k, v) in self.caller.env.items())) else: os.environ.update(self.caller.env.items()); # Handle path; copied from exec.py if self.caller.path: # if we had an env, the old path is already backuped in the env if not self.caller.env: old_path = os.environ["PATH"] # The user decides in the build system whether he wants to append $PATH # or tuck it at the front: "$PATH;C:\\new\\path", "C:\\new\\path;$PATH" # Handle differently in Python 2 and 3, to be safe: if not _ST3: os.environ["PATH"] = os.path.expandvars(self.caller.path).encode(sys.getfilesystemencoding()) else: os.environ["PATH"] = os.path.expandvars(self.caller.path) # Set up Windows-specific parameters if self.caller.plat == "windows": # make sure console does not come up startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # Now, iteratively call the builder iterator # cmd_iterator = self.caller.builder.commands() for (cmd, msg) in cmd_iterator: # If there is a message, display it if msg: self.caller.output(msg) # If there is nothing to be done, exit loop # (Avoids error with empty cmd_iterator) if cmd == "": break print(cmd) # Now create a Popen object try: if self.caller.plat == "windows": proc = subprocess.Popen(cmd, startupinfo=startupinfo, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) elif self.caller.plat == "osx": # Temporary (?) fix for Yosemite: pass environment proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=os.environ) else: # Must be linux proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) except: self.caller.output("\n\nCOULD NOT COMPILE!\n\n") self.caller.output("Attempted command:") self.caller.output(" ".join(cmd)) self.caller.output("\nBuild engine: " + self.caller.builder.name) self.caller.proc = None if self.caller.env: os.environ = old_env elif self.caller.path: os.environ["PATH"] = old_path return # Now actually invoke the command, making sure we allow for killing # First, save process handle into caller; then communicate (which blocks) self.caller.proc = proc out, err = proc.communicate() self.caller.builder.set_output(out.decode(self.caller.encoding,"ignore")) # Here the process terminated, but it may have been killed. If so, stop and don't read log # Since we set self.caller.proc above, if it is None, the process must have been killed. # TODO: clean up? if not self.caller.proc: print (proc.returncode) self.caller.output("\n\n[User terminated compilation process]\n") self.caller.finish(False) # We kill, so won't switch to PDF anyway return # Here we are done cleanly: self.caller.proc = None print ("Finished normally") print (proc.returncode) # At this point, out contains the output from the current command; # we pass it to the cmd_iterator and get the next command, until completion # Clean up cmd_iterator.close() # restore env or path if needed if self.caller.env: os.environ = old_env elif self.caller.path: os.environ["PATH"] = old_path # CHANGED 12-10-27. OK, here's the deal. We must open in binary mode on Windows # because silly MiKTeX inserts ASCII control characters in over/underfull warnings. # In particular it inserts EOFs, which stop reading altogether; reading in binary # prevents that. However, that's not the whole story: if a FS character is encountered, # AND if we invoke splitlines on a STRING, it sadly breaks the line in two. This messes up # line numbers in error reports. If, on the other hand, we invoke splitlines on a # byte array (? whatever read() returns), this does not happen---we only break at \n, etc. # However, we must still decode the resulting lines using the relevant encoding. # 121101 -- moved splitting and decoding logic to parseTeXlog, where it belongs. # Note to self: need to think whether we don't want to codecs.open this, too... # Also, we may want to move part of this logic to the builder... data = open(self.caller.tex_base + ".log", 'rb').read() errors = [] warnings = [] try: (errors, warnings) = parseTeXlog.parse_tex_log(data) content = [""] if errors: content.append("Errors:") content.append("") content.extend(errors) else: content.append("No errors.") if warnings: if errors: content.extend(["", "Warnings:"]) else: content[-1] = content[-1] + " Warnings:" content.append("") content.extend(warnings) else: content.append("") except Exception as e: content=["",""] content.append("LaTeXtools could not parse the TeX log file") content.append("(actually, we never should have gotten here)") content.append("") content.append("Python exception: " + repr(e)) content.append("") content.append("Please let me know on GitHub. Thanks!") self.caller.output(content) self.caller.output("\n\n[Done!]\n") self.caller.finish(len(errors) == 0)