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
|