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 commands(self): # Print greeting self.display("\n\nScriptBuilder: ") # create an environment to be used for all subprocesses # adds any settings from the `env` dict to the current # environment env = dict(os.environ) env['PATH'] = self.texpath if self.env is not None and isinstance(self.env, dict): update_env(env, self.env) if self.cmd is None: sublime.error_message( "You MUST set a command in your LaTeXTools.sublime-settings " + "file before launching the script builder." ) # I'm not sure this is the best way to handle things... raise StopIteration() if isinstance(self.cmd, strbase): self.cmd = [self.cmd] for cmd in self.cmd: replaced_var = False if isinstance(cmd, strbase): cmd, replaced_var = self.substitute(cmd) else: for i, component in enumerate(cmd): cmd[i], replaced = self.substitute(component) replaced_var = replaced_var or replaced if not replaced_var: if isinstance(cmd, strbase): cmd += ' ' + self.base_name else: cmd.append(self.base_name) if not isinstance(cmd, strbase): self.display("Invoking '{0}'... ".format( u' '.join([quote(s) for s in cmd]) )) else: self.display("Invoking '{0}'... ".format(cmd)) yield ( # run with use_texpath=False as we have already configured # the environment above, including the texpath external_command( cmd, env=env, cwd=self.tex_dir, use_texpath=False, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ), "" ) self.display("done.\n") # This is for debugging purposes if self.display_log and self.out is not None: self.display("\nCommand results:\n") self.display(self.out) self.display("\n\n")
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 commands(self): # Print greeting self.display("\n\nWineBuilder: ") # create an environment to be used for all subprocesses # adds any settings from the `env` dict to the current # environment env = dict(os.environ) env['PATH'] = self.texpath if self.env is not None and isinstance(self.env, dict): update_env(env, self.env) latex = ["latex", "-src", "-interaction=nonstopmode", "-synctex=1"] bibtex = ["bibtex"] dvi2ps = ["dvips"] # Get the platform plat = sublime.platform() # ps2pdf command, with reasonable executable (NOTE: we assume you installed 64-bit Ghostscrit) gs = "gswin64c" if plat == "windows" else "gs-952-linux-x86_64" ps2pdf = [ gs, "-dNOPAUSE", "-dBATCH", "-sDEVICE=pdfwrite", "-dPDFSETTINGS=/prepress", "-dCompatibilityLevel=1.4", "-dSubsetFonts=true", "-dEmbedAllFonts=true", '-sOutputFile="' + self.base_name + '.pdf"', "-c", "save", "pop", "-f", ] # Regex to look for missing citations # This works for plain latex; apparently natbib requires special handling # TODO: does it work with biblatex? citations_rx = re.compile( r"Warning: Citation `.+' on page \d+ undefined") # We have commands in our PATH, and are in the same dir as the master file # This is for debugging purposes def display_results(n): if self.display_log: self.display("Command results, run %d:\n" % (n, )) self.display(self.out) self.display("\n") run = 1 brun = 0 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) # Check for citations # Use search, not match: match looks at the beginning of the string # We need to run latex twice after bibtex if citations_rx.search(self.out): brun = brun + 1 yield (bibtex + [self.base_name], "bibtex run %d; " % (brun, )) display_results(1) run = run + 1 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) run = run + 1 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) # Apparently natbib needs separate processing if "Package natbib Warning: There were undefined citations." in self.out: brun = brun + 1 yield (bibtex + [self.base_name], "bibtex run %d; " % (brun, )) display_results(2) run = run + 1 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) run = run + 1 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) # Check for changed labels # Do this at the end, so if there are also citations to resolve, # we may save one pdflatex run if "Rerun to get cross-references right." in self.out: run = run + 1 yield (latex + [self.base_name], "latex run %d; " % (run, )) display_results(run) yield (dvi2ps + [self.base_name], "dvips run %d; " % (run, )) display_results(run) # NOTE: here, ps2pdf is considered as an external command, because we use customized # executable instead of the built-in on cmd = external_command(ps2pdf + [self.base_name + ".ps"], env=env, cwd=self.tex_dir, use_texpath=False, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) yield (cmd, "ps2pdf run %d; " % (run, )) display_results(run) self.display("done.\n")