Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
pantsbuild.pants / init / repro.py
Size: Mime:
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
import os
import sys

from pants.base.build_environment import get_buildroot
from pants.subsystem.subsystem import Subsystem
from pants.util.contextutil import open_tar, temporary_file
from pants.util.dirutil import chmod_plus_x

logger = logging.getLogger(__name__)


class ReproError(Exception):
    pass


class Reproducer(Subsystem):
    options_scope = "repro"

    @classmethod
    def register_options(cls, register):
        register(
            "--capture",
            metavar="<repro_path>",
            default=None,
            help="Capture information about this pants run (including the entire workspace) "
            "into a tar.gz file that can be used to help debug build problems.",
        )
        register(
            "--ignore",
            type=list,
            help="Any paths specified here will not be included in repro tarballs.",
        )

    def create_repro(self):
        """Return a Repro instance for capturing a repro of the current workspace state.

        :return: a Repro instance, or None if no repro was requested.
        :rtype: `pants.bin.repro.Repro`
        """
        path = self.get_options().capture
        if path is None:
            return None
        buildroot = get_buildroot()
        # Ignore a couple of common cases. Note: If we support SCMs other than git in the future,
        # add their (top-level only) metadata dirs here if relevant.
        ignore = [".git", os.path.relpath(self.get_options().pants_distdir, buildroot)]
        if self.get_options().ignore:
            ignore.extend(self.get_options().ignore)
        return Repro(path, buildroot, ignore)


class Repro:
    def __init__(self, path, buildroot, ignore):
        """Create a Repro instance.

        :param string path: Write the captured repro data to this path.
        :param string buildroot: Capture the workspace at this buildroot.
        :param ignore: Ignore these top-level files/dirs under buildroot.
        """
        path = os.path.expanduser(path)
        if os.path.realpath(path).startswith(buildroot):
            raise ReproError("Repro capture file location must be outside the build root.")
        if not path.endswith("tar.gz") and not path.endswith(".tgz"):
            path += ".tar.gz"
        if os.path.exists(path):
            raise ReproError(f"Repro capture file already exists: {path}")
        self._path = path
        self._buildroot = buildroot
        self._ignore = ignore

    def capture(self, run_info_dict):
        # Force the scm discovery logging messages to appear before ours, so the startup delay
        # is properly associated in the user's mind with us and not with scm.
        logger.info(f"Capturing repro information to {self._path}")
        with open_tar(self._path, "w:gz", dereference=True, compresslevel=6) as tarout:
            for relpath in os.listdir(self._buildroot):
                if relpath not in self._ignore:
                    tarout.add(os.path.join(self._buildroot, relpath), relpath)

            with temporary_file(binary_mode=False) as tmpfile:
                tmpfile.write("# Pants repro captured for the following build:\n")
                for k, v in sorted(run_info_dict.items()):
                    tmpfile.write(f"#  {k}: {v}\n")
                cmd_line = list(sys.argv)
                # Use 'pants' instead of whatever the full executable path was on the user's system.
                cmd_line[0] = "pants"
                # Remove any repro-related flags. The repro-ing user won't want to call those.
                cmd_line = [x for x in cmd_line if not x.startswith("--repro-")]
                tmpfile.write("'" + "' '".join(cmd_line) + "'\n")
                tmpfile.flush()
                chmod_plus_x(tmpfile.name)
                tarout.add(tmpfile.name, "repro.sh")

    def log_location_of_repro_file(self):
        if not self._path:
            return  # No repro requested.
        logger.info(f"Captured repro information to {self._path}")