Repository URL to install this package:
|
Version:
1.26.0.dev0+gite506aa5f ▾
|
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import logging
import os
import sys
import pkg_resources
from pants.base.build_environment import pants_version
from pants.base.exceptions import BuildConfigurationError
from pants.build_graph.build_configuration import BuildConfiguration
from pants.goal.goal import Goal
from pants.init.extension_loader import load_backends_and_plugins
from pants.init.global_subsystems import GlobalSubsystems
from pants.init.plugin_resolver import PluginResolver
from pants.option.global_options import GlobalOptionsRegistrar
from pants.subsystem.subsystem import Subsystem
from pants.util.dirutil import fast_relpath_optional
logger = logging.getLogger(__name__)
class BuildConfigInitializer:
"""Initializes a BuildConfiguration object.
This class uses a class-level cache for the internally generated `BuildConfiguration` object,
which permits multiple invocations in the same runtime context without re-incurring backend &
plugin loading, which can be expensive and cause issues (double task registration, etc).
"""
_cached_build_config = None
@classmethod
def get(cls, options_bootstrapper):
if cls._cached_build_config is None:
cls._cached_build_config = cls(options_bootstrapper).setup()
return cls._cached_build_config
@classmethod
def reset(cls):
cls._cached_build_config = None
def __init__(self, options_bootstrapper):
self._options_bootstrapper = options_bootstrapper
self._bootstrap_options = options_bootstrapper.get_bootstrap_options().for_global_scope()
self._working_set = PluginResolver(self._options_bootstrapper).resolve()
def _load_plugins(self):
# Add any extra paths to python path (e.g., for loading extra source backends).
for path in self._bootstrap_options.pythonpath:
if path not in sys.path:
sys.path.append(path)
pkg_resources.fixup_namespace_packages(path)
# Load plugins and backends.
return load_backends_and_plugins(
self._bootstrap_options.plugins,
self._bootstrap_options.plugins2,
self._working_set,
self._bootstrap_options.backend_packages,
self._bootstrap_options.backend_packages2,
BuildConfiguration(),
)
def setup(self):
"""Load backends and plugins.
:returns: A `BuildConfiguration` object constructed during backend/plugin loading.
"""
return self._load_plugins()
class OptionsInitializer:
"""Initializes options."""
@staticmethod
def _construct_options(options_bootstrapper, build_configuration):
"""Parse and register options.
:returns: An Options object representing the full set of runtime options.
"""
# Now that plugins and backends are loaded, we can gather the known scopes.
# Gather the optionables that are not scoped to any other. All known scopes are reachable
# via these optionables' known_scope_infos() methods.
top_level_optionables = (
{GlobalOptionsRegistrar}
| GlobalSubsystems.get()
| build_configuration.optionables()
| set(Goal.get_optionables())
)
# Now that we have the known scopes we can get the full options. `get_full_options` will
# sort and de-duplicate these for us.
known_scope_infos = [
si for optionable in top_level_optionables for si in optionable.known_scope_infos()
]
return options_bootstrapper.get_full_options(known_scope_infos)
@staticmethod
def compute_pants_ignore(buildroot, global_options):
"""Computes the merged value of the `--pants-ignore` flag.
This inherently includes the workdir and distdir locations if they are located under the
buildroot.
"""
pants_ignore = list(global_options.pants_ignore)
def add(absolute_path, include=False):
# To ensure that the path is ignored regardless of whether it is a symlink or a directory, we
# strip trailing slashes (which would signal that we wanted to ignore only directories).
maybe_rel_path = fast_relpath_optional(absolute_path, buildroot)
if maybe_rel_path:
rel_path = maybe_rel_path.rstrip(os.path.sep)
prefix = "!" if include else ""
pants_ignore.append(f"{prefix}/{rel_path}")
add(global_options.pants_workdir)
add(global_options.pants_distdir)
# NB: We punch a hole in the ignore patterns to allow pants to directly watch process
# metadata that is written to disk.
add(global_options.pants_subprocessdir, include=True)
return pants_ignore
@staticmethod
def compute_pantsd_invalidation_globs(buildroot, bootstrap_options):
"""Computes the merged value of the `--pantsd-invalidation-globs` option.
Combines --pythonpath and --pants-config-files files that are in {buildroot} dir with those
invalidation_globs provided by users.
"""
invalidation_globs = set()
globs = set(
sys.path
+ bootstrap_options.pythonpath
+ bootstrap_options.pants_config_files
+ bootstrap_options.pantsd_invalidation_globs
)
for glob in globs:
if glob.startswith("!"):
invalidation_globs.add(glob)
continue
glob_relpath = fast_relpath_optional(glob, buildroot) if os.path.isabs(glob) else glob
if glob_relpath:
invalidation_globs.update([glob_relpath, glob_relpath + "/**"])
else:
logger.debug(
f"Changes to {glob}, outside of the buildroot, will not be invalidated."
)
return list(sorted(invalidation_globs))
@classmethod
def create(cls, options_bootstrapper, build_configuration, init_subsystems=True):
global_bootstrap_options = options_bootstrapper.get_bootstrap_options().for_global_scope()
if global_bootstrap_options.pants_version != pants_version():
raise BuildConfigurationError(
f"Version mismatch: Requested version was {global_bootstrap_options.pants_version}, "
f"our version is {pants_version()}."
)
# Parse and register options.
options = cls._construct_options(options_bootstrapper, build_configuration)
GlobalOptionsRegistrar.validate_instance(options.for_global_scope())
if init_subsystems:
Subsystem.set_options(options)
return options