Jürg Billeter pushed to branch juerg/symlinks at BuildStream / buildstream
Commits:
-
03111d39
by Dor Askayo at 2019-01-30T10:35:06Z
-
7256bb0c
by James Ennis at 2019-01-30T11:34:04Z
-
36746730
by Chandan Singh at 2019-01-31T10:50:05Z
-
fa4a21ce
by Chandan Singh at 2019-01-31T12:15:43Z
-
ebc31bd5
by Jürg Billeter at 2019-01-31T13:58:54Z
-
aa83f158
by Jürg Billeter at 2019-01-31T13:58:54Z
-
f778de6c
by Jürg Billeter at 2019-01-31T13:58:54Z
-
3127e109
by Jürg Billeter at 2019-01-31T13:58:54Z
-
07acea63
by Jürg Billeter at 2019-01-31T14:49:57Z
8 changed files:
- buildstream/plugins/elements/filter.py
- buildstream/sandbox/sandbox.py
- buildstream/storage/_casbaseddirectory.py
- buildstream/utils.py
- tests/elements/filter.py
- + tests/elements/filter/basic/elements/input-with-deps.bst
- + tests/elements/filter/basic/elements/output-include-with-indirect-deps.bst
- tox.ini
Changes:
| ... | ... | @@ -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 ""
|
| ... | ... | @@ -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 |
|
| ... | ... | @@ -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 |
|
| ... | ... | @@ -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'
|
| ... | ... | @@ -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"))
|
| 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
|
| 1 |
+kind: filter
|
|
| 2 |
+ |
|
| 3 |
+depends:
|
|
| 4 |
+- filename: input-with-deps.bst
|
|
| 5 |
+ type: build
|
| ... | ... | @@ -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
|
