Repository URL to install this package:
|
Version:
1.26.0.dev0+gite506aa5f ▾
|
# 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}")