Пример #1
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)
Пример #2
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")
Пример #3
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)
Пример #4
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")