Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • willsalmon/buildstream
  • CumHoleZH/buildstream
  • tchaik/buildstream
  • DCotyPortfolio/buildstream
  • jesusoctavioas/buildstream
  • patrickmmartin/buildstream
  • franred/buildstream
  • tintou/buildstream
  • alatiera/buildstream
  • martinblanchard/buildstream
  • neverdie22042524/buildstream
  • Mattlk13/buildstream
  • PServers/buildstream
  • phamnghia610909/buildstream
  • chiaratolentino/buildstream
  • eysz7-x-x/buildstream
  • kerrick1/buildstream
  • matthew-yates/buildstream
  • twofeathers/buildstream
  • mhadjimichael/buildstream
  • pointswaves/buildstream
  • Mr.JackWilson/buildstream
  • Tw3akG33k/buildstream
  • AlexFazakas/buildstream
  • eruidfkiy/buildstream
  • clamotion2/buildstream
  • nanonyme/buildstream
  • wickyjaaa/buildstream
  • nmanchev/buildstream
  • bojorquez.ja/buildstream
  • mostynb/buildstream
  • highpit74/buildstream
  • Demo112/buildstream
  • ba2014sheer/buildstream
  • tonimadrino/buildstream
  • usuario2o/buildstream
  • Angelika123456/buildstream
  • neo355/buildstream
  • corentin-ferlay/buildstream
  • coldtom/buildstream
  • wifitvbox81/buildstream
  • 358253885/buildstream
  • seanborg/buildstream
  • SotK/buildstream
  • DouglasWinship/buildstream
  • karansthr97/buildstream
  • louib/buildstream
  • bwh-ct/buildstream
  • robjh/buildstream
  • we88c0de/buildstream
  • zhengxian5555/buildstream
51 results
Show changes
Commits on Source (9)
  • Tom Pollard's avatar
    Add cli main & user conf option for 'cache-buildtrees' context · 118644b2
    Tom Pollard authored
    _context.py: Add cache_buildtrees global user context, the default
    of which is set to by default to 'always' via the addition of
    cache-buildtrees to userconfig.yaml cache group. 'failure' & 'never'
    can be given as valid options.
    
    app.py & cli.py: Add --cache-buildtrees as a bst main option, which
    when passed with a valid option can override the default or user
    defined context for cache_buildtrees.
    
    tests/completions/completions.py: Update for the added flag.
    118644b2
  • Tom Pollard's avatar
    _frontend/cli.py: Ensure failed buildtree warning is correct · b546bac1
    Tom Pollard authored
    not _cached_sucess() could resolve to true if the element wasn't
    cached at all. switch to _cached_failure() to ensure condition
    reflects expected artifact state
    b546bac1
  • Tom Pollard's avatar
    Provide configuration for the optional creation of buildtrees · d2029444
    Tom Pollard authored
    Artifacts can be cached explicitly with an empty `build tree` when
    built via the cli main options or user config for all or only
    successful build artifacts. Default behaviour is to still create
    and cache all expected buildtrees.
    
    element.py: _cache_artifact() Check if context for cache_buildtrees
    has been set to always or failure with a corresponding build
    result, if not skip attempting to export the build-root. Element
    types without a build-root are cached with an empty buildtree
    regardless. Update _stage_sources_at() to warn the user that the
    buildtree import is empty.
    
    tests/integration: Add test to artifact.py for the optional caching
    of buildtree content from bst build. Rename build-tree.py to
    shellbuildtrees.py to reflect included test cases, add test for
    empty buildtree warning and failure option.
    
    NEWS: Add entry for new option.
    d2029444
  • Jürg Billeter's avatar
    Merge branch 'tpollard/896' into 'master' · 54ec032a
    Jürg Billeter authored
    Optional creation of buildtrees
    
    Closes #896
    
    See merge request !1135
    54ec032a
  • Jürg Billeter's avatar
    utils.py: Change _ensure_real_directory() to not resolve symlinks · 98091991
    Jürg Billeter authored
    Resolving symlinks during staging causes various issues:
    
    * Split rules may not work properly as the resolved paths will differ
      depending on whether another artifact with a directory symlink has
      been staged in the same root directory or not, e.g., as part of
      compose.
    
    * The order of symlinks in file lists is difficult to get right to
      guarantee consistent and predictable behavior as paths in a file list
      might rely on symlinks in the same file list. See #647 and #817.
    
    * Staging order differences can lead to surprising results. See #390.
    
    * Difficult to properly support absolute symlinks. Absolute symlinks are
      currently converted to relative symlinks, however, this doesn't always
      work. See #606 and #830.
    
    This will require changes in projects that rely on the current behavior.
    However, the changes are expected to be small and are often a sign of
    buggy element files. E.g., elements that don't fully obey `bindir` or
    `sbindir` variables.
    98091991
  • Jürg Billeter's avatar
    _casbaseddirectory.py: Do not resolve symlinks · 6d2573e9
    Jürg Billeter authored
    This matches the change in utils._process_list().
    
    This also removes the _Resolver class as it is now unused. We may want
    to support controlled symlink resolution in the future, in which case
    the _Resolver class can be resurrected from this commit.
    6d2573e9
  • Jürg Billeter's avatar
    utils.py: Do not mangle absolute symlinks · 38eae603
    Jürg Billeter authored
    Copy symlinks as they are, absolute or relative. We no longer resolve
    symlinks when copying files, which makes this safe.
    38eae603
  • Jürg Billeter's avatar
    b32c494d
  • Jürg Billeter's avatar
    5a32663b
Showing
with 103 additions and 304 deletions
......@@ -126,6 +126,18 @@ buildstream 1.3.1
Providing a remote will limit build's pull/push remote actions to the given
remote specifically, ignoring those defined via user or project configuration.
o Artifacts can now be cached explicitly with an empty `build tree` when built.
Element types without a build-root were already cached with an empty build tree
directory, this can now be extended to all or successful artifacts to save on cache
overheads. The cli main option '--cache-buildtrees' or the user configuration cache
group option 'cache-buildtrees' can be set as 'always', 'failure' or 'never', with
the default being always. Note, as the cache-key for the artifact is independant of
the cached build tree input it will remain unaltered, however the availbility of the
build tree content may differ.
o BREAKING CHANGE: Symlinks are no longer resolved during staging and absolute
symlinks are now preserved instead of being converted to relative symlinks.
=================
buildstream 1.1.5
......
......@@ -121,6 +121,9 @@ class Context():
# Whether or not to attempt to pull build trees globally
self.pull_buildtrees = None
# Whether or not to cache build trees on artifact creation
self.cache_buildtrees = None
# Boolean, whether we double-check with the user that they meant to
# close the workspace when they're using it to access the project.
self.prompt_workspace_close_project_inaccessible = None
......@@ -201,7 +204,7 @@ class Context():
# our artifactdir - the artifactdir may not have been created
# yet.
cache = _yaml.node_get(defaults, Mapping, 'cache')
_yaml.node_validate(cache, ['quota', 'pull-buildtrees'])
_yaml.node_validate(cache, ['quota', 'pull-buildtrees', 'cache-buildtrees'])
self.config_cache_quota = _yaml.node_get(cache, str, 'quota')
......@@ -213,6 +216,10 @@ class Context():
# Load pull build trees configuration
self.pull_buildtrees = _yaml.node_get(cache, bool, 'pull-buildtrees')
# Load cache build trees configuration
self.cache_buildtrees = _node_get_option_str(
cache, 'cache-buildtrees', ['always', 'failure', 'never'])
# Load logging config
logging = _yaml.node_get(defaults, Mapping, 'logging')
_yaml.node_validate(logging, [
......
......@@ -183,7 +183,8 @@ class App():
'builders': 'sched_builders',
'pushers': 'sched_pushers',
'network_retries': 'sched_network_retries',
'pull_buildtrees': 'pull_buildtrees'
'pull_buildtrees': 'pull_buildtrees',
'cache_buildtrees': 'cache_buildtrees'
}
for cli_option, context_attr in override_map.items():
option_value = self._main_options.get(cli_option)
......
......@@ -251,6 +251,9 @@ def print_version(ctx, param, value):
help="The mirror to fetch from first, before attempting other mirrors")
@click.option('--pull-buildtrees', is_flag=True, default=None,
help="Include an element's build tree when pulling remote element artifacts")
@click.option('--cache-buildtrees', default=None,
type=click.Choice(['always', 'failure', 'never']),
help="Cache artifact build tree content on creation")
@click.pass_context
def cli(context, **kwargs):
"""Build and manipulate BuildStream projects
......@@ -572,7 +575,8 @@ def shell(app, element, sysroot, mount, isolate, build_, cli_buildtree, command)
if choice != "never":
use_buildtree = choice
if use_buildtree and not element._cached_success():
# Raise warning if the element is cached in a failed state
if use_buildtree and element._cached_failure():
click.echo("WARNING: using a buildtree from a failed build.", err=True)
try:
......
......@@ -33,4 +33,4 @@ BST_FORMAT_VERSION = 21
# or if buildstream was changed in a way which can cause
# the same cache key to produce something that is no longer
# the same.
BST_CORE_ARTIFACT_VERSION = 6
BST_CORE_ARTIFACT_VERSION = 7
......@@ -41,6 +41,15 @@ cache:
# Whether to pull build trees when downloading element artifacts
pull-buildtrees: False
# Whether to cache build trees on artifact creation:
#
# always - Always cache artifact build tree content
# failure - Only cache build trees of failed builds
# never - Don't cache artifact build tree content
#
cache-buildtrees: always
#
# Scheduler
#
......
......@@ -1457,6 +1457,9 @@ class Element(Plugin):
elif usebuildtree:
artifact_base, _ = self.__extract()
import_dir = os.path.join(artifact_base, 'buildtree')
if not os.listdir(import_dir):
detail = "Element type either does not expect a buildtree or it was explictily cached without one."
self.warn("WARNING: {} Artifact contains an empty buildtree".format(self.name), detail=detail)
else:
# No workspace or cached buildtree, stage source directly
for source in self.sources():
......@@ -1663,6 +1666,8 @@ class Element(Plugin):
# No collect directory existed
collectvdir = None
context = self._get_context()
# Create artifact directory structure
assembledir = os.path.join(rootdir, 'artifact')
filesdir = os.path.join(assembledir, 'files')
......@@ -1680,20 +1685,30 @@ class Element(Plugin):
if collect is not None and collectvdir is not None:
collectvdir.export_files(filesdir, can_link=True)
try:
sandbox_vroot = sandbox.get_virtual_directory()
sandbox_build_dir = sandbox_vroot.descend(
self.get_variable('build-root').lstrip(os.sep).split(os.sep))
# Hard link files from build-root dir to buildtreedir directory
sandbox_build_dir.export_files(buildtreedir)
except VirtualDirectoryError:
# Directory could not be found. Pre-virtual
# directory behaviour was to continue silently
# if the directory could not be found.
pass
cache_buildtrees = context.cache_buildtrees
build_success = self.__build_result[0]
# cache_buildtrees defaults to 'always', as such the
# default behaviour is to attempt to cache them. If only
# caching failed artifact buildtrees, then query the build
# result. Element types without a build-root dir will be cached
# with an empty buildtreedir regardless of this configuration.
if cache_buildtrees == 'always' or (cache_buildtrees == 'failure' and not build_success):
try:
sandbox_vroot = sandbox.get_virtual_directory()
sandbox_build_dir = sandbox_vroot.descend(
self.get_variable('build-root').lstrip(os.sep).split(os.sep))
# Hard link files from build-root dir to buildtreedir directory
sandbox_build_dir.export_files(buildtreedir)
except VirtualDirectoryError:
# Directory could not be found. Pre-virtual
# directory behaviour was to continue silently
# if the directory could not be found.
pass
# Copy build log
log_filename = self._get_context().get_log_filename()
log_filename = context.get_log_filename()
self._build_log_path = os.path.join(logsdir, 'build.log')
if log_filename:
shutil.copyfile(log_filename, self._build_log_path)
......@@ -1834,7 +1849,7 @@ class Element(Plugin):
return True
# Do not push elements that aren't cached, or that are cached with a dangling buildtree
# artifact unless element type is expected to have an an empty buildtree directory
# ref unless element type is expected to have an an empty buildtree directory
if not self._cached_buildtree():
return True
......@@ -2036,6 +2051,8 @@ class Element(Plugin):
# Returns:
# (bool): True if artifact cached with buildtree, False if
# element not cached or missing expected buildtree.
# Note this only confirms if a buildtree is present,
# not its contents.
#
def _cached_buildtree(self):
context = self._get_context()
......
......@@ -79,151 +79,6 @@ class UnexpectedFileException(ResolutionException):
super().__init__(message)
class _Resolver():
"""A class for resolving symlinks inside CAS-based directories. As
well as providing a namespace for some functions, this also
contains two flags which are constant throughout one resolution
operation and the 'seen_objects' list used to detect infinite
symlink loops.
"""
def __init__(self, absolute_symlinks_resolve=True, force_create=False):
self.absolute_symlinks_resolve = absolute_symlinks_resolve
self.force_create = force_create
self.seen_objects = []
def resolve(self, name, directory):
"""Resolves any name to an object. If the name points to a symlink in
the directory, it returns the thing it points to,
recursively.
Returns a CasBasedDirectory, FileNode or None. None indicates
either that 'target' does not exist in this directory, or is a
symlink chain which points to a nonexistent name (broken
symlink).
Raises:
- InfiniteSymlinkException if 'name' points to an infinite
symlink loop.
- AbsoluteSymlinkException if 'name' points to an absolute
symlink and absolute_symlinks_resolve is False.
- UnexpectedFileException if at any point during resolution we
find a file which we expected to be a directory or symlink.
If force_create is set, this will attempt to create
directories to make symlinks and directories resolve. Files
present in symlink target paths will also be removed and
replaced with directories. If force_create is off, this will
never alter 'directory'.
"""
# First check for nonexistent things or 'normal' objects and return them
if name not in directory.index:
return None
index_entry = directory.index[name]
if isinstance(index_entry.buildstream_object, Directory):
return index_entry.buildstream_object
elif isinstance(index_entry.pb_object, remote_execution_pb2.FileNode):
return index_entry.pb_object
# Now we must be dealing with a symlink.
assert isinstance(index_entry.pb_object, remote_execution_pb2.SymlinkNode)
symlink_object = index_entry.pb_object
if symlink_object in self.seen_objects:
# Infinite symlink loop detected
message = ("Infinite symlink loop found during resolution. " +
"First repeated element is {}".format(name))
raise InfiniteSymlinkException(message=message)
self.seen_objects.append(symlink_object)
components = symlink_object.target.split(CasBasedDirectory._pb2_path_sep)
absolute = symlink_object.target.startswith(CasBasedDirectory._pb2_absolute_path_prefix)
if absolute:
if self.absolute_symlinks_resolve:
directory = directory.find_root()
# Discard the first empty element
components.pop(0)
else:
# Unresolvable absolute symlink
message = "{} is an absolute symlink, which was disallowed during resolution".format(name)
raise AbsoluteSymlinkException(message=message)
resolution = directory
while components and isinstance(resolution, CasBasedDirectory):
c = components.pop(0)
directory = resolution
try:
resolution = self._resolve_path_component(c, directory, components)
except UnexpectedFileException as original:
errormsg = ("Reached a file called {} while trying to resolve a symlink; " +
"cannot proceed. The remaining path components are {}.")
raise UnexpectedFileException(errormsg.format(c, components)) from original
return resolution
def _resolve_path_component(self, c, directory, components_remaining):
if c == ".":
resolution = directory
elif c == "..":
if directory.parent is not None:
resolution = directory.parent
else:
# If directory.parent *is* None, this is an attempt to
# access '..' from the root, which is valid under
# POSIX; it just returns the root.
resolution = directory
elif c in directory.index:
try:
resolution = self._resolve_through_files(c, directory, components_remaining)
except UnexpectedFileException as original:
errormsg = ("Reached a file called {} while trying to resolve a symlink; " +
"cannot proceed. The remaining path components are {}.")
raise UnexpectedFileException(errormsg.format(c, components_remaining)) from original
else:
# c is not in our index
if self.force_create:
resolution = directory.descend(c, create=True)
else:
resolution = None
return resolution
def _resolve_through_files(self, c, directory, require_traversable):
"""A wrapper to resolve() which deals with files being found
in the middle of paths, for example trying to resolve a symlink
which points to /usr/lib64/libfoo when 'lib64' is a file.
require_traversable: If this is True, never return a file
node. Instead, if force_create is set, destroy the file node,
then create and return a normal directory in its place. If
force_create is off, throws ResolutionException.
"""
resolved_thing = self.resolve(c, directory)
if isinstance(resolved_thing, remote_execution_pb2.FileNode):
if require_traversable:
# We have components still to resolve, but one of the path components
# is a file.
if self.force_create:
directory.delete_entry(c)
resolved_thing = directory.descend(c, create=True)
else:
# This is a signal that we hit a file, but don't
# have the data to give a proper message, so the
# caller should reraise this with a proper
# description.
raise UnexpectedFileException()
return resolved_thing
# CasBasedDirectory intentionally doesn't call its superclass constuctor,
# which is meant to be unimplemented.
# pylint: disable=super-init-not-called
......@@ -407,10 +262,6 @@ class CasBasedDirectory(Directory):
if isinstance(entry, CasBasedDirectory):
return entry.descend(subdirectory_spec[1:], create)
else:
# May be a symlink
target = self._resolve(subdirectory_spec[0], force_create=create)
if isinstance(target, CasBasedDirectory):
return target
error = "Cannot descend into {}, which is a '{}' in the directory {}"
raise VirtualDirectoryError(error.format(subdirectory_spec[0],
type(self.index[subdirectory_spec[0]].pb_object).__name__,
......@@ -434,10 +285,6 @@ class CasBasedDirectory(Directory):
else:
return self
def _resolve(self, name, absolute_symlinks_resolve=True, force_create=False):
resolver = _Resolver(absolute_symlinks_resolve, force_create)
return resolver.resolve(name, self)
def _check_replacement(self, name, path_prefix, fileListResult):
""" Checks whether 'name' exists, and if so, whether we can overwrite it.
If we can, add the name to 'overwritten_files' and delete the existing entry.
......@@ -468,36 +315,13 @@ class CasBasedDirectory(Directory):
.format(name, type(existing_entry)))
return False # In case asserts are disabled
def _replace_anything_with_dir(self, name, path_prefix, overwritten_files_list):
self.delete_entry(name)
subdir = self._add_directory(name)
overwritten_files_list.append(os.path.join(path_prefix, name))
return subdir
def _import_files_from_directory(self, source_directory, files, path_prefix=""):
""" Imports files from a traditional directory. """
def _ensure_followable(name, path_prefix):
""" Makes sure 'name' is a directory or symlink to a directory which can be descended into. """
if isinstance(self.index[name].buildstream_object, Directory):
return self.descend(name)
try:
target = self._resolve(name, force_create=True)
except InfiniteSymlinkException:
return self._replace_anything_with_dir(name, path_prefix, result.overwritten)
if isinstance(target, CasBasedDirectory):
return target
elif isinstance(target, remote_execution_pb2.FileNode):
return self._replace_anything_with_dir(name, path_prefix, result.overwritten)
return target
def _import_directory_recursively(directory_name, source_directory, remaining_path, path_prefix):
""" _import_directory_recursively and _import_files_from_directory will be called alternately
as a directory tree is descended. """
if directory_name in self.index:
subdir = _ensure_followable(directory_name, path_prefix)
else:
subdir = self._add_directory(directory_name)
subdir = self.descend(directory_name, create=True)
new_path_prefix = os.path.join(path_prefix, directory_name)
subdir_result = subdir._import_files_from_directory(os.path.join(source_directory, directory_name),
[os.path.sep.join(remaining_path)],
......@@ -566,15 +390,8 @@ class CasBasedDirectory(Directory):
if dirname not in processed_directories:
# Now strip off the first directory name and import files recursively.
subcomponents = CasBasedDirectory._files_in_subdir(files, dirname)
# We will fail at this point if there is a file or symlink to file called 'dirname'.
if dirname in self.index:
resolved_component = self._resolve(dirname, force_create=True)
if isinstance(resolved_component, remote_execution_pb2.FileNode):
dest_subdir = self._replace_anything_with_dir(dirname, path_prefix, result.overwritten)
else:
dest_subdir = resolved_component
else:
dest_subdir = self.descend(dirname, create=True)
# We will fail at this point if there is a file or symlink called 'dirname'.
dest_subdir = self.descend(dirname, create=True)
src_subdir = source_directory.descend(dirname)
import_result = dest_subdir._partial_import_cas_into_cas(src_subdir, subcomponents,
path_prefix=fullname,
......
......@@ -34,7 +34,6 @@ import string
import subprocess
import tempfile
import itertools
import functools
from contextlib import contextmanager
import psutil
......@@ -767,37 +766,23 @@ def _copy_directories(srcdir, destdir, target):
'directory expected: {}'.format(old_dir))
@functools.lru_cache(maxsize=64)
def _resolve_symlinks(path):
return os.path.realpath(path)
def _ensure_real_directory(root, destpath):
# The realpath in the sandbox may refer to a file outside of the
# sandbox when any of the direcory branches are a symlink to an
# absolute path.
#
# This should not happen as we rely on relative_symlink_target() below
# when staging the actual symlinks which may lead up to this path.
#
destpath_resolved = _resolve_symlinks(destpath)
if not destpath_resolved.startswith(_resolve_symlinks(root)):
raise UtilError('Destination path resolves to a path outside ' +
'of the staging area\n\n' +
' Destination path: {}\n'.format(destpath) +
' Real path: {}'.format(destpath_resolved))
# Ensure the real destination path exists before trying to get the mode
# of the real destination path.
#
# It is acceptable that chunks create symlinks inside artifacts which
# refer to non-existing directories, they will be created on demand here
# at staging time.
#
if not os.path.exists(destpath_resolved):
os.makedirs(destpath_resolved)
return destpath_resolved
# _ensure_real_directory()
#
# Ensure `path` is a real directory and there are no symlink components.
#
# Symlink components are allowed in `root`.
#
def _ensure_real_directory(root, path):
destpath = root
for name in os.path.split(path):
destpath = os.path.join(destpath, name)
try:
deststat = os.lstat(destpath)
if not stat.S_ISDIR(deststat.st_mode):
relpath = destpath[len(root):]
raise UtilError('Destination is not a directory: {}'.format(relpath))
except FileNotFoundError:
os.makedirs(destpath)
# _process_list()
......@@ -836,6 +821,10 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
srcpath = os.path.join(srcdir, path)
destpath = os.path.join(destdir, path)
# Ensure that the parent of the destination path exists without symlink
# components.
_ensure_real_directory(destdir, os.path.dirname(path))
# Add to the results the list of files written
if report_written:
result.files_written.append(path)
......@@ -847,11 +836,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
# The destination directory may not have been created separately
permissions.extend(_copy_directories(srcdir, destdir, path))
# Ensure that broken symlinks to directories have their targets
# created before attempting to stage files across broken
# symlink boundaries
_ensure_real_directory(destdir, os.path.dirname(destpath))
try:
file_stat = os.lstat(srcpath)
mode = file_stat.st_mode
......@@ -865,13 +849,7 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
if stat.S_ISDIR(mode):
# Ensure directory exists in destination
if not os.path.exists(destpath):
_ensure_real_directory(destdir, destpath)
dest_stat = os.lstat(_resolve_symlinks(destpath))
if not stat.S_ISDIR(dest_stat.st_mode):
raise UtilError('Destination not a directory. source has {}'
' destination has {}'.format(srcpath, destpath))
_ensure_real_directory(os.path.dirname(destpath), os.path.basename(path))
permissions.append((destpath, os.stat(srcpath).st_mode))
elif stat.S_ISLNK(mode):
......@@ -880,7 +858,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
continue
target = os.readlink(srcpath)
target = _relative_symlink_target(destdir, destpath, target)
os.symlink(target, destpath)
elif stat.S_ISREG(mode):
......@@ -918,51 +895,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
os.chmod(d, perms)
# _relative_symlink_target()
#
# Fetches a relative path for symlink with an absolute target
#
# @root: The staging area root location
# @symlink: Location of the symlink in staging area (including the root path)
# @target: The symbolic link target, which may be an absolute path
#
# If @target is an absolute path, a relative path from the symbolic link
# location will be returned, otherwise if @target is a relative path, it will
# be returned unchanged.
#
# Using relative symlinks helps to keep the target self contained when staging
# files onto the target.
#
def _relative_symlink_target(root, symlink, target):
if os.path.isabs(target):
# First fix the input a little, the symlink itself must not have a
# trailing slash, otherwise we fail to remove the symlink filename
# from its directory components in os.path.split()
#
# The absolute target filename must have its leading separator
# removed, otherwise os.path.join() will discard the prefix
symlink = symlink.rstrip(os.path.sep)
target = target.lstrip(os.path.sep)
# We want a relative path from the directory in which symlink
# is located, not from the symlink itself.
symlinkdir, _ = os.path.split(_resolve_symlinks(symlink))
# Create a full path to the target, including the leading staging
# directory
fulltarget = os.path.join(_resolve_symlinks(root), target)
# now get the relative path from the directory where the symlink
# is located within the staging root, to the target within the same
# staging root
newtarget = os.path.relpath(fulltarget, symlinkdir)
return newtarget
else:
return target
# _set_deterministic_user()
#
# Set the uid/gid for every file in a directory tree to the process'
......
298bb7caab56c60bc12f59df15db2b1107a604fdb0ce3fa5729aa23d6d37f6e4
\ No newline at end of file
14abd914df752b07e2fa8329081351827110055de88eb8839f202a7136efa13c
\ No newline at end of file
c745f07458a2488180ae7f5b0601a38853dcdfd2b6771c18ecbb622f1af10cf1
\ No newline at end of file
be76f66ecfa1a8fe12c2facbe5cbd0f2eb3553b9c09504495d3465e19c40b653
\ No newline at end of file
d759c8b7262c4782c12b425bfca0bc8f4c30656d91c98e309c10f11d4179b101
\ No newline at end of file
324a37481dbb0e6cf82ed49a0fa41bf1148bdbc82d27781a381e5e0b2e0a086c
\ No newline at end of file
01e22d37b82c9b35c35d3d8485e05fe6de526d3af00137bbff51a309b1fa3401
\ No newline at end of file
a1c46f2f8ccad279256abe0ab52d9576cea6972d8fcd71721484b143fb8d34d4
\ No newline at end of file
22b846d1928155b5a21497e8048af2945ee03a826338f91d4f00d74597cc3ef7
\ No newline at end of file
df79c575459f7d36c347226a4a02a08e17bdc1da3b1d3479e4afe6cec82b5f6a
\ No newline at end of file
0e00f203ce9bc4163be0ed304fcf2abb6ee39aad4ee9a12d80719708a4327337
\ No newline at end of file
45000ffa96eabec981bb95d31dd226df66ea050f871f38ff9fe64f2019dd177f
\ No newline at end of file
3d8dd9fef508c27961edeb21dd469d04e7ea097c86a98e7c3bf725975548748f
\ No newline at end of file
18985834536cd863fbd34b3a522ec76975eb5378c47eafc9f3a17163f854aa05
\ No newline at end of file
02f863aedaf175f4b4bb9979a538bd1fb6eabd0ef9d77a633b141c072d957df5
\ No newline at end of file
e4e79ab3c644817deafe89eddcf594852e44860b2997932ee22adb614a2629ba
\ No newline at end of file
80ee70ad27ee4640400533585d1b2313b1abdb0034f4159dad42f545310d9c24
\ No newline at end of file
61908dbbaf08474d2ea18eb09ba62b65d2a00e34449f858ac9f418e79c4e74e3
\ No newline at end of file
b30d79b763ba3048945784d06482ee378d2642dff712ecffae737aa6f9391b9f
\ No newline at end of file
1386411ff7503f145f1cdc5825543c8f1a2e4436dd94b5f8f2f5cf625ba8e006
\ No newline at end of file
fb69c9237156bdeb0f948078f735189e9d8a4c4dca3c67b6f1ed5fa314f88707
\ No newline at end of file
d2cc6b4728679c7eb3cc9d34632c2e69a648e5dcf493f79bb283f254e7a9d76d
\ No newline at end of file