[Notes] [Git][BuildStream/buildstream][juerg/symlinks] 9 commits: filter.py: don't recurse when staging dependencies



Title: GitLab

Jürg Billeter pushed to branch juerg/symlinks at BuildStream / buildstream

Commits:

8 changed files:

Changes:

  • buildstream/plugins/elements/filter.py
    ... ... @@ -47,6 +47,8 @@ from buildstream import Element, ElementError, Scope
    47 47
     class FilterElement(Element):
    
    48 48
         # pylint: disable=attribute-defined-outside-init
    
    49 49
     
    
    50
    +    BST_ARTIFACT_VERSION = 1
    
    51
    +
    
    50 52
         # The filter element's output is its dependencies, so
    
    51 53
         # we must rebuild if the dependencies change even when
    
    52 54
         # not in strict build plans.
    
    ... ... @@ -102,7 +104,7 @@ class FilterElement(Element):
    102 104
     
    
    103 105
         def assemble(self, sandbox):
    
    104 106
             with self.timed_activity("Staging artifact", silent_nested=True):
    
    105
    -            for dep in self.dependencies(Scope.BUILD):
    
    107
    +            for dep in self.dependencies(Scope.BUILD, recurse=False):
    
    106 108
                     dep.stage_artifact(sandbox, include=self.include,
    
    107 109
                                        exclude=self.exclude, orphans=self.include_orphans)
    
    108 110
             return ""
    

  • buildstream/sandbox/sandbox.py
    ... ... @@ -525,11 +525,11 @@ class Sandbox():
    525 525
         #         (bool): Whether a command exists inside the sandbox.
    
    526 526
         def _has_command(self, command, env=None):
    
    527 527
             if os.path.isabs(command):
    
    528
    -            return os.path.exists(os.path.join(
    
    528
    +            return os.path.lexists(os.path.join(
    
    529 529
                     self._root, command.lstrip(os.sep)))
    
    530 530
     
    
    531 531
             for path in env.get('PATH').split(':'):
    
    532
    -            if os.path.exists(os.path.join(
    
    532
    +            if os.path.lexists(os.path.join(
    
    533 533
                         self._root, path.lstrip(os.sep), command)):
    
    534 534
                     return True
    
    535 535
     
    

  • buildstream/storage/_casbaseddirectory.py
    ... ... @@ -435,6 +435,7 @@ class CasBasedDirectory(Directory):
    435 435
                 return self
    
    436 436
     
    
    437 437
         def _resolve(self, name, absolute_symlinks_resolve=True, force_create=False):
    
    438
    +        # TODO drop symlink resolution and mangling, matching changes in utils.py
    
    438 439
             resolver = _Resolver(absolute_symlinks_resolve, force_create)
    
    439 440
             return resolver.resolve(name, self)
    
    440 441
     
    

  • buildstream/utils.py
    ... ... @@ -774,37 +774,22 @@ def _copy_directories(srcdir, destdir, target):
    774 774
                                     'directory expected: {}'.format(old_dir))
    
    775 775
     
    
    776 776
     
    
    777
    -@functools.lru_cache(maxsize=64)
    
    778
    -def _resolve_symlinks(path):
    
    779
    -    return os.path.realpath(path)
    
    780
    -
    
    781
    -
    
    782
    -def _ensure_real_directory(root, destpath):
    
    783
    -    # The realpath in the sandbox may refer to a file outside of the
    
    784
    -    # sandbox when any of the direcory branches are a symlink to an
    
    785
    -    # absolute path.
    
    786
    -    #
    
    787
    -    # This should not happen as we rely on relative_symlink_target() below
    
    788
    -    # when staging the actual symlinks which may lead up to this path.
    
    789
    -    #
    
    790
    -    destpath_resolved = _resolve_symlinks(destpath)
    
    791
    -    if not destpath_resolved.startswith(_resolve_symlinks(root)):
    
    792
    -        raise UtilError('Destination path resolves to a path outside ' +
    
    793
    -                        'of the staging area\n\n' +
    
    794
    -                        '  Destination path: {}\n'.format(destpath) +
    
    795
    -                        '  Real path: {}'.format(destpath_resolved))
    
    796
    -
    
    797
    -    # Ensure the real destination path exists before trying to get the mode
    
    798
    -    # of the real destination path.
    
    799
    -    #
    
    800
    -    # It is acceptable that chunks create symlinks inside artifacts which
    
    801
    -    # refer to non-existing directories, they will be created on demand here
    
    802
    -    # at staging time.
    
    803
    -    #
    
    804
    -    if not os.path.exists(destpath_resolved):
    
    805
    -        os.makedirs(destpath_resolved)
    
    806
    -
    
    807
    -    return destpath_resolved
    
    777
    +# _ensure_real_directory()
    
    778
    +#
    
    779
    +# Ensure `path` is a real directory and there are no symlink components.
    
    780
    +#
    
    781
    +# Symlink components are allowed in `root`.
    
    782
    +#
    
    783
    +def _ensure_real_directory(root, path):
    
    784
    +    destpath = root
    
    785
    +    for name in os.path.split(path):
    
    786
    +        destpath = os.path.join(destpath, name)
    
    787
    +        try:
    
    788
    +            deststat = os.lstat(destpath)
    
    789
    +            if not stat.S_ISDIR(deststat.st_mode):
    
    790
    +                raise UtilError('Destination is not a directory {} for {}'.format(destpath, path))
    
    791
    +        except FileNotFoundError:
    
    792
    +            os.makedirs(destpath)
    
    808 793
     
    
    809 794
     
    
    810 795
     # _process_list()
    
    ... ... @@ -843,6 +828,10 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
    843 828
             srcpath = os.path.join(srcdir, path)
    
    844 829
             destpath = os.path.join(destdir, path)
    
    845 830
     
    
    831
    +        # Ensure that the parent of the destination path exists without symlink
    
    832
    +        # components.
    
    833
    +        _ensure_real_directory(destdir, os.path.dirname(path))
    
    834
    +
    
    846 835
             # Add to the results the list of files written
    
    847 836
             if report_written:
    
    848 837
                 result.files_written.append(path)
    
    ... ... @@ -854,11 +843,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
    854 843
             # The destination directory may not have been created separately
    
    855 844
             permissions.extend(_copy_directories(srcdir, destdir, path))
    
    856 845
     
    
    857
    -        # Ensure that broken symlinks to directories have their targets
    
    858
    -        # created before attempting to stage files across broken
    
    859
    -        # symlink boundaries
    
    860
    -        _ensure_real_directory(destdir, os.path.dirname(destpath))
    
    861
    -
    
    862 846
             try:
    
    863 847
                 file_stat = os.lstat(srcpath)
    
    864 848
                 mode = file_stat.st_mode
    
    ... ... @@ -872,13 +856,7 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
    872 856
     
    
    873 857
             if stat.S_ISDIR(mode):
    
    874 858
                 # Ensure directory exists in destination
    
    875
    -            if not os.path.exists(destpath):
    
    876
    -                _ensure_real_directory(destdir, destpath)
    
    877
    -
    
    878
    -            dest_stat = os.lstat(_resolve_symlinks(destpath))
    
    879
    -            if not stat.S_ISDIR(dest_stat.st_mode):
    
    880
    -                raise UtilError('Destination not a directory. source has {}'
    
    881
    -                                ' destination has {}'.format(srcpath, destpath))
    
    859
    +            _ensure_real_directory(os.path.dirname(destpath), os.path.basename(path))
    
    882 860
                 permissions.append((destpath, os.stat(srcpath).st_mode))
    
    883 861
     
    
    884 862
             elif stat.S_ISLNK(mode):
    
    ... ... @@ -887,7 +865,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
    887 865
                     continue
    
    888 866
     
    
    889 867
                 target = os.readlink(srcpath)
    
    890
    -            target = _relative_symlink_target(destdir, destpath, target)
    
    891 868
                 os.symlink(target, destpath)
    
    892 869
     
    
    893 870
             elif stat.S_ISREG(mode):
    
    ... ... @@ -925,51 +902,6 @@ def _process_list(srcdir, destdir, filelist, actionfunc, result,
    925 902
             os.chmod(d, perms)
    
    926 903
     
    
    927 904
     
    
    928
    -# _relative_symlink_target()
    
    929
    -#
    
    930
    -# Fetches a relative path for symlink with an absolute target
    
    931
    -#
    
    932
    -# @root:    The staging area root location
    
    933
    -# @symlink: Location of the symlink in staging area (including the root path)
    
    934
    -# @target:  The symbolic link target, which may be an absolute path
    
    935
    -#
    
    936
    -# If @target is an absolute path, a relative path from the symbolic link
    
    937
    -# location will be returned, otherwise if @target is a relative path, it will
    
    938
    -# be returned unchanged.
    
    939
    -#
    
    940
    -# Using relative symlinks helps to keep the target self contained when staging
    
    941
    -# files onto the target.
    
    942
    -#
    
    943
    -def _relative_symlink_target(root, symlink, target):
    
    944
    -
    
    945
    -    if os.path.isabs(target):
    
    946
    -        # First fix the input a little, the symlink itself must not have a
    
    947
    -        # trailing slash, otherwise we fail to remove the symlink filename
    
    948
    -        # from its directory components in os.path.split()
    
    949
    -        #
    
    950
    -        # The absolute target filename must have its leading separator
    
    951
    -        # removed, otherwise os.path.join() will discard the prefix
    
    952
    -        symlink = symlink.rstrip(os.path.sep)
    
    953
    -        target = target.lstrip(os.path.sep)
    
    954
    -
    
    955
    -        # We want a relative path from the directory in which symlink
    
    956
    -        # is located, not from the symlink itself.
    
    957
    -        symlinkdir, _ = os.path.split(_resolve_symlinks(symlink))
    
    958
    -
    
    959
    -        # Create a full path to the target, including the leading staging
    
    960
    -        # directory
    
    961
    -        fulltarget = os.path.join(_resolve_symlinks(root), target)
    
    962
    -
    
    963
    -        # now get the relative path from the directory where the symlink
    
    964
    -        # is located within the staging root, to the target within the same
    
    965
    -        # staging root
    
    966
    -        newtarget = os.path.relpath(fulltarget, symlinkdir)
    
    967
    -
    
    968
    -        return newtarget
    
    969
    -    else:
    
    970
    -        return target
    
    971
    -
    
    972
    -
    
    973 905
     # _set_deterministic_user()
    
    974 906
     #
    
    975 907
     # Set the uid/gid for every file in a directory tree to the process'
    

  • tests/elements/filter.py
    ... ... @@ -464,3 +464,23 @@ def test_filter_track_multi_exclude(datafiles, cli, tmpdir):
    464 464
         assert "ref" not in new_input["sources"][0]
    
    465 465
         new_input2 = _yaml.load(input2_file)
    
    466 466
         assert new_input2["sources"][0]["ref"] == ref
    
    467
    +
    
    468
    +
    
    469
    +@pytest.mark.datafiles(os.path.join(DATA_DIR, 'basic'))
    
    470
    +def test_filter_include_with_indirect_deps(datafiles, cli, tmpdir):
    
    471
    +    project = os.path.join(datafiles.dirname, datafiles.basename)
    
    472
    +    result = cli.run(project=project, args=[
    
    473
    +        'build', 'output-include-with-indirect-deps.bst'])
    
    474
    +    result.assert_success()
    
    475
    +
    
    476
    +    checkout = os.path.join(tmpdir.dirname, tmpdir.basename, 'checkout')
    
    477
    +    result = cli.run(project=project, args=[
    
    478
    +        'artifact', 'checkout', 'output-include-with-indirect-deps.bst', '--directory', checkout])
    
    479
    +    result.assert_success()
    
    480
    +
    
    481
    +    # direct dependencies should be staged and filtered
    
    482
    +    assert os.path.exists(os.path.join(checkout, "baz"))
    
    483
    +
    
    484
    +    # indirect dependencies shouldn't be staged and filtered
    
    485
    +    assert not os.path.exists(os.path.join(checkout, "foo"))
    
    486
    +    assert not os.path.exists(os.path.join(checkout, "bar"))

  • tests/elements/filter/basic/elements/input-with-deps.bst
    1
    +kind: import
    
    2
    +
    
    3
    +depends:
    
    4
    +- filename: input.bst
    
    5
    +
    
    6
    +sources:
    
    7
    +- kind: local
    
    8
    +  path: files
    
    9
    +
    
    10
    +public:
    
    11
    +  bst:
    
    12
    +    split-rules:
    
    13
    +      baz:
    
    14
    +      - /baz

  • tests/elements/filter/basic/elements/output-include-with-indirect-deps.bst
    1
    +kind: filter
    
    2
    +
    
    3
    +depends:
    
    4
    +- filename: input-with-deps.bst
    
    5
    +  type: build

  • tox.ini
    ... ... @@ -88,5 +88,5 @@ whitelist_externals =
    88 88
     commands =
    
    89 89
         python3 setup.py --command-packages=click_man.commands man_pages
    
    90 90
     deps =
    
    91
    -    click-man
    
    91
    +    click-man >= 0.3.0
    
    92 92
         -rrequirements/requirements.txt



  • [Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]