blob: c184f18e19de6038a7f8217452e4a69bdc60dd04 [file] [log] [blame]
# Copyright 2010-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
from __future__ import unicode_literals
__all__ = (
import collections
import io
import warnings
import portage
from portage import os, eapi_is_supported, _encodings, _unicode_encode
from portage.const import CUSTOM_PROFILE_PATH, GLOBAL_CONFIG_PATH, \
from portage.eapi import eapi_allows_directories_on_profile_level_and_repository_level
from portage.exception import DirectoryNotFound, InvalidLocation, ParseError
from portage.localization import _
from portage.util import ensure_dirs, grabfile, \
normalize_path, read_corresponding_eapi_file, shlex_split, writemsg
from portage.util._path import exists_raise_eaccess, isdir_raise_eaccess
from portage.repository.config import parse_layout_conf, \
'package.mask', 'package.provided',
'package.use', 'package.use.mask', 'package.use.force',
'use.mask', 'use.force'])
_profile_node = collections.namedtuple('_profile_node',
('location', 'portage1_directories', 'user_config',
'profile_formats', 'eapi', 'allow_build_id'))
_allow_parent_colon = frozenset(
class LocationsManager(object):
def __init__(self, config_root=None, eprefix=None, config_profile_path=None, local_config=True, \
target_root=None, sysroot=None):
self.user_profile_dir = None
self._local_repo_conf_path = None
self.eprefix = eprefix
self.config_root = config_root
self.target_root = target_root
self.sysroot = sysroot
self._user_config = local_config
if self.eprefix is None:
self.eprefix = portage.const.EPREFIX
elif self.eprefix:
self.eprefix = normalize_path(self.eprefix)
if self.eprefix == os.sep:
self.eprefix = ""
if self.config_root is None:
self.config_root = portage.const.EPREFIX + os.sep
self.config_root = normalize_path(os.path.abspath(
self.config_root or os.sep)).rstrip(os.sep) + os.sep
self._check_var_directory("PORTAGE_CONFIGROOT", self.config_root)
self.abs_user_config = os.path.join(self.config_root, USER_CONFIG_PATH)
self.config_profile_path = config_profile_path
if self.sysroot is None:
self.sysroot = "/"
self.sysroot = normalize_path(os.path.abspath(self.sysroot or os.sep)).rstrip(os.sep) + os.sep
# TODO: Set this via the constructor using
self.broot = portage.const.EPREFIX
def load_profiles(self, repositories, known_repository_paths):
known_repository_paths = set(os.path.realpath(x)
for x in known_repository_paths)
known_repos = []
for x in known_repository_paths:
repo = repositories.get_repo_for_location(x)
except KeyError:
layout_data = parse_layout_conf(x)[0]
layout_data = {
"profile-formats": repo.profile_formats,
"profile_eapi_when_unspecified": repo.eapi
# force a trailing '/' for ease of doing startswith checks
known_repos.append((x + '/', layout_data))
known_repos = tuple(known_repos)
if self.config_profile_path is None:
deprecated_profile_path = os.path.join(
self.config_root, 'etc', 'make.profile')
self.config_profile_path = \
os.path.join(self.config_root, PROFILE_PATH)
if isdir_raise_eaccess(self.config_profile_path):
self.profile_path = self.config_profile_path
if isdir_raise_eaccess(deprecated_profile_path) and not \
# Don't warn if they refer to the same path, since
# that can be used for backward compatibility with
# old software.
writemsg("!!! %s\n" %
_("Found 2 make.profile dirs: "
"using '%s', ignoring '%s'") %
(self.profile_path, deprecated_profile_path),
self.config_profile_path = deprecated_profile_path
if isdir_raise_eaccess(self.config_profile_path):
self.profile_path = self.config_profile_path
self.profile_path = None
# NOTE: repoman may pass in an empty string
# here, in order to create an empty profile
# for checking dependencies of packages with
# empty KEYWORDS.
self.profile_path = self.config_profile_path
# The symlink might not exist or might not be a symlink.
self.profiles = []
self.profiles_complex = []
if self.profile_path:
repositories, known_repos)
except ParseError as e:
if not portage._sync_mode:
writemsg(_("!!! Unable to parse profile: '%s'\n") % self.profile_path, noiselevel=-1)
writemsg("!!! ParseError: %s\n" % str(e), noiselevel=-1)
self.profiles = []
self.profiles_complex = []
if self._user_config and self.profiles:
custom_prof = os.path.join(
self.config_root, CUSTOM_PROFILE_PATH)
if os.path.exists(custom_prof):
# For read_corresponding_eapi_file, specify default=None
# in order to allow things like wildcard atoms when
# is no explicit EAPI setting.
self.user_profile_dir = custom_prof
_profile_node(custom_prof, True, True,
('profile-bashrcs', 'profile-set'),
custom_prof + os.sep, default=None),
del custom_prof
self.profiles = tuple(self.profiles)
self.profiles_complex = tuple(self.profiles_complex)
def _check_var_directory(self, varname, var):
if not isdir_raise_eaccess(var):
writemsg(_("!!! Error: %s='%s' is not a directory. "
"Please correct this.\n") % (varname, var),
raise DirectoryNotFound(var)
def _addProfile(self, currentPath, repositories, known_repos):
current_abs_path = os.path.abspath(currentPath)
allow_directories = True
allow_parent_colon = True
repo_loc = None
compat_mode = False
current_formats = ()
eapi = None
intersecting_repos = [x for x in known_repos
if current_abs_path.startswith(x[0])]
if intersecting_repos:
# Handle nested repositories. The longest path
# will be the correct one.
repo_loc, layout_data = max(intersecting_repos,
key=lambda x:len(x[0]))
eapi = layout_data.get("profile_eapi_when_unspecified")
eapi_file = os.path.join(currentPath, "eapi")
eapi = eapi or "0"
f = None
f =,
encoding=_encodings['fs'], errors='strict'),
mode='r', encoding=_encodings['content'], errors='replace')
eapi = f.readline().strip()
except IOError:
if not eapi_is_supported(eapi):
raise ParseError(_(
"Profile contains unsupported "
"EAPI '%s': '%s'") % \
(eapi, os.path.realpath(eapi_file),))
if f is not None:
if intersecting_repos:
allow_directories = eapi_allows_directories_on_profile_level_and_repository_level(eapi) or \
any(x in _portage1_profiles_allow_directories for x in layout_data['profile-formats'])
compat_mode = not eapi_allows_directories_on_profile_level_and_repository_level(eapi) and \
layout_data['profile-formats'] == ('portage-1-compat',)
allow_parent_colon = any(x in _allow_parent_colon
for x in layout_data['profile-formats'])
current_formats = tuple(layout_data['profile-formats'])
if compat_mode:
offenders = _PORTAGE1_DIRECTORIES.intersection(os.listdir(currentPath))
offenders = sorted(x for x in offenders
if os.path.isdir(os.path.join(currentPath, x)))
if offenders:
"\nThe selected profile is implicitly using the 'portage-1' format:\n"
"\tprofile = %(profile_path)s\n"
"But this repository is not using that format:\n"
"\trepo = %(repo_name)s\n"
"This will break in the future. Please convert these dirs to files:\n"
"Or, add this line to the repository's layout.conf:\n"
"\tprofile-formats = portage-1")
% dict(profile_path=currentPath, repo_name=repo_loc,
parentsFile = os.path.join(currentPath, "parent")
if exists_raise_eaccess(parentsFile):
parents = grabfile(parentsFile)
if not parents:
raise ParseError(
_("Empty parent file: '%s'") % parentsFile)
for parentPath in parents:
abs_parent = parentPath[:1] == os.sep
if not abs_parent and allow_parent_colon:
parentPath = self._expand_parent_colon(parentsFile,
parentPath, repo_loc, repositories)
# NOTE: This os.path.join() call is intended to ignore
# currentPath if parentPath is already absolute.
parentPath = normalize_path(os.path.join(
currentPath, parentPath))
if abs_parent or repo_loc is None or \
not parentPath.startswith(repo_loc):
# It seems that this parent may point outside
# of the current repo, so realpath it.
parentPath = os.path.realpath(parentPath)
if exists_raise_eaccess(parentPath):
self._addProfile(parentPath, repositories, known_repos)
raise ParseError(
_("Parent '%s' not found: '%s'") % \
(parentPath, parentsFile))
_profile_node(currentPath, allow_directories, False,
current_formats, eapi, 'build-id' in current_formats))
def _expand_parent_colon(self, parentsFile, parentPath,
repo_loc, repositories):
colon = parentPath.find(":")
if colon == -1:
return parentPath
if colon == 0:
if repo_loc is None:
raise ParseError(
_("Parent '%s' not found: '%s'") % \
(parentPath, parentsFile))
parentPath = normalize_path(os.path.join(
repo_loc, 'profiles', parentPath[colon+1:]))
p_repo_name = parentPath[:colon]
p_repo_loc = repositories.get_location_for_name(p_repo_name)
except KeyError:
raise ParseError(
_("Parent '%s' not found: '%s'") % \
(parentPath, parentsFile))
parentPath = normalize_path(os.path.join(
p_repo_loc, 'profiles', parentPath[colon+1:]))
return parentPath
def set_root_override(self, root_overwrite=None):
# Allow ROOT setting to come from make.conf if it's not overridden
# by the constructor argument (from the calling environment).
if self.target_root is None and root_overwrite is not None:
self.target_root = root_overwrite
if not self.target_root.strip():
self.target_root = None
self.target_root = self.target_root or os.sep
self.target_root = normalize_path(os.path.abspath(
self.target_root)).rstrip(os.path.sep) + os.path.sep
if self.sysroot != "/" and self.target_root == "/":
writemsg(_("!!! Error: SYSROOT (currently %s) must "
"be set to / when ROOT is /.\n") % self.sysroot,
raise InvalidLocation(self.sysroot)
self._check_var_directory("ROOT", self.target_root)
self.eroot = self.target_root.rstrip(os.sep) + self.eprefix + os.sep
# In a cross-prefix scenario where SYSROOT=/ and
# ROOT=/, assume we want ESYSROOT to point to the
# target prefix.
if self.sysroot == self.target_root:
self.esysroot = self.sysroot.rstrip(os.sep) + self.eprefix + os.sep
elif self.sysroot == "/":
self.esysroot = self.broot + os.sep
self.esysroot = self.sysroot
self.global_config_path = GLOBAL_CONFIG_PATH
if portage.const.EPREFIX:
self.global_config_path = os.path.join(portage.const.EPREFIX,
def set_port_dirs(self, portdir, portdir_overlay):
self.portdir = portdir
self.portdir_overlay = portdir_overlay
if self.portdir_overlay is None:
self.portdir_overlay = ""
self.overlay_profiles = []
for ov in shlex_split(self.portdir_overlay):
ov = normalize_path(ov)
profiles_dir = os.path.join(ov, "profiles")
if isdir_raise_eaccess(profiles_dir):
self.profile_locations = [os.path.join(portdir, "profiles")] + self.overlay_profiles
self.profile_and_user_locations = self.profile_locations[:]
if self._user_config:
self.profile_locations = tuple(self.profile_locations)
self.profile_and_user_locations = tuple(self.profile_and_user_locations)