Valentin David pushed to branch valentindavid/sysroot_dependencies at BuildStream / buildstream
Commits:
- 
65a66dbc
by Valentin David at 2018-11-26T16:26:36Z
29 changed files:
- buildstream/_loader/loadelement.py
- buildstream/_loader/loader.py
- buildstream/buildelement.py
- buildstream/element.py
- buildstream/plugins/elements/compose.py
- doc/source/format_declaring.rst
- tests/loader/dependencies.py
- + tests/sysroot_depends/project/elements/a.bst
- + tests/sysroot_depends/project/elements/b.bst
- + tests/sysroot_depends/project/elements/base.bst
- + tests/sysroot_depends/project/elements/base/base-alpine.bst
- + tests/sysroot_depends/project/elements/compose-integration.bst
- + tests/sysroot_depends/project/elements/compose-layers-with-sysroot.bst
- + tests/sysroot_depends/project/elements/compose-layers.bst
- + tests/sysroot_depends/project/elements/integration.bst
- + tests/sysroot_depends/project/elements/layer1-files.bst
- + tests/sysroot_depends/project/elements/layer1.bst
- + tests/sysroot_depends/project/elements/layer2-files.bst
- + tests/sysroot_depends/project/elements/layer2.bst
- + tests/sysroot_depends/project/elements/manual-integration-runtime.bst
- + tests/sysroot_depends/project/elements/manual-integration.bst
- + tests/sysroot_depends/project/elements/target-variable.bst
- + tests/sysroot_depends/project/elements/target.bst
- + tests/sysroot_depends/project/files/a/a.txt
- + tests/sysroot_depends/project/files/b/b.txt
- + tests/sysroot_depends/project/files/layer1/1
- + tests/sysroot_depends/project/files/layer2/2
- + tests/sysroot_depends/project/project.conf
- + tests/sysroot_depends/sysroot_depends.py
Changes:
| ... | ... | @@ -72,10 +72,24 @@ class LoadElement(): | 
| 72 | 72 |              'variables', 'environment', 'environment-nocache',
 | 
| 73 | 73 |              'config', 'public', 'description',
 | 
| 74 | 74 |              'build-depends', 'runtime-depends',
 | 
| 75 | +            'sysroots',
 | |
| 75 | 76 |          ])
 | 
| 76 | 77 |  | 
| 78 | +        self.deps = []
 | |
| 79 | +        sysroots = _yaml.node_get(node, list, 'sysroots', default_value=[])
 | |
| 80 | +        for sysroot in sysroots:
 | |
| 81 | +            _yaml.node_validate(sysroot, ['path', 'depends', 'build-depends'])
 | |
| 82 | +            path = _yaml.node_get(sysroot, str, 'path')
 | |
| 83 | +            for dep in _extract_depends_from_node(sysroot):
 | |
| 84 | +                if dep.dep_type == Symbol.RUNTIME:
 | |
| 85 | +                    raise LoadError(LoadErrorReason.INVALID_DATA,
 | |
| 86 | +                                    "{}: Sysroot'ed dependencies can not be of type 'runtime'"
 | |
| 87 | +                                    .format(dep.provenance))
 | |
| 88 | +                self.deps.append((path, dep))
 | |
| 89 | + | |
| 77 | 90 |          # Extract the Dependencies
 | 
| 78 | -        self.deps = _extract_depends_from_node(self.node)
 | |
| 91 | +        for dep in _extract_depends_from_node(self.node):
 | |
| 92 | +            self.deps.append(('/', dep))
 | |
| 79 | 93 |  | 
| 80 | 94 |      # depends():
 | 
| 81 | 95 |      #
 | 
| ... | ... | @@ -101,7 +115,7 @@ class LoadElement(): | 
| 101 | 115 |              return
 | 
| 102 | 116 |  | 
| 103 | 117 |          self._dep_cache = {}
 | 
| 104 | -        for dep in self.deps:
 | |
| 118 | +        for _, dep in self.deps:
 | |
| 105 | 119 |              elt = self._loader.get_element_for_dep(dep)
 | 
| 106 | 120 |  | 
| 107 | 121 |              # Ensure the cache of the element we depend on
 | 
| ... | ... | @@ -121,7 +121,7 @@ class Loader(): | 
| 121 | 121 |                  junction, name, loader = self._parse_name(target, rewritable, ticker,
 | 
| 122 | 122 |                                                            fetch_subprojects=fetch_subprojects)
 | 
| 123 | 123 |                  loader._load_file(name, rewritable, ticker, fetch_subprojects, yaml_cache)
 | 
| 124 | -                deps.append(Dependency(name, junction=junction))
 | |
| 124 | +                deps.append(('/', Dependency(name, junction=junction)))
 | |
| 125 | 125 |                  profile_end(Topics.LOAD_PROJECT, target)
 | 
| 126 | 126 |  | 
| 127 | 127 |          #
 | 
| ... | ... | @@ -269,7 +269,7 @@ class Loader(): | 
| 269 | 269 |          self._elements[filename] = element
 | 
| 270 | 270 |  | 
| 271 | 271 |          # Load all dependency files for the new LoadElement
 | 
| 272 | -        for dep in element.deps:
 | |
| 272 | +        for _, dep in element.deps:
 | |
| 273 | 273 |              if dep.junction:
 | 
| 274 | 274 |                  self._load_file(dep.junction, rewritable, ticker, fetch_subprojects, yaml_cache)
 | 
| 275 | 275 |                  loader = self._get_loader(dep.junction, rewritable=rewritable, ticker=ticker,
 | 
| ... | ... | @@ -330,7 +330,7 @@ class Loader(): | 
| 330 | 330 |          # Push / Check each dependency / Pop
 | 
| 331 | 331 |          check_elements[element_name] = True
 | 
| 332 | 332 |          sequence.append(element_name)
 | 
| 333 | -        for dep in element.deps:
 | |
| 333 | +        for _, dep in element.deps:
 | |
| 334 | 334 |              loader = self._get_loader_for_dep(dep)
 | 
| 335 | 335 |              loader._check_circular_deps(dep.name, check_elements, validated, sequence)
 | 
| 336 | 336 |          del check_elements[element_name]
 | 
| ... | ... | @@ -365,14 +365,21 @@ class Loader(): | 
| 365 | 365 |          if visited.get(element_name) is not None:
 | 
| 366 | 366 |              return
 | 
| 367 | 367 |  | 
| 368 | -        for dep in element.deps:
 | |
| 368 | +        for _, dep in element.deps:
 | |
| 369 | 369 |              loader = self._get_loader_for_dep(dep)
 | 
| 370 | 370 |              loader._sort_dependencies(dep.name, visited=visited)
 | 
| 371 | 371 |  | 
| 372 | -        def dependency_cmp(dep_a, dep_b):
 | |
| 372 | +        def dependency_cmp(sdep_a, sdep_b):
 | |
| 373 | +            sysroot_a, dep_a = sdep_a
 | |
| 374 | +            sysroot_b, dep_b = sdep_b
 | |
| 373 | 375 |              element_a = self.get_element_for_dep(dep_a)
 | 
| 374 | 376 |              element_b = self.get_element_for_dep(dep_b)
 | 
| 375 | 377 |  | 
| 378 | +            if sysroot_a < sysroot_b:
 | |
| 379 | +                return -1
 | |
| 380 | +            if sysroot_b < sysroot_a:
 | |
| 381 | +                return 1
 | |
| 382 | + | |
| 376 | 383 |              # Sort on inter element dependency first
 | 
| 377 | 384 |              if element_a.depends(element_b):
 | 
| 378 | 385 |                  return 1
 | 
| ... | ... | @@ -471,11 +478,11 @@ class Loader(): | 
| 471 | 478 |          self._meta_elements[element_name] = meta_element
 | 
| 472 | 479 |  | 
| 473 | 480 |          # Descend
 | 
| 474 | -        for dep in element.deps:
 | |
| 481 | +        for sysroot, dep in element.deps:
 | |
| 475 | 482 |              loader = self._get_loader_for_dep(dep)
 | 
| 476 | 483 |              meta_dep = loader._collect_element(dep.name)
 | 
| 477 | 484 |              if dep.dep_type != 'runtime':
 | 
| 478 | -                meta_element.build_dependencies.append(meta_dep)
 | |
| 485 | +                meta_element.build_dependencies.append((sysroot, meta_dep))
 | |
| 479 | 486 |              if dep.dep_type != 'build':
 | 
| 480 | 487 |                  meta_element.dependencies.append(meta_dep)
 | 
| 481 | 488 |  | 
| ... | ... | @@ -216,8 +216,9 @@ class BuildElement(Element): | 
| 216 | 216 |          # Run any integration commands provided by the dependencies
 | 
| 217 | 217 |          # once they are all staged and ready
 | 
| 218 | 218 |          with self.timed_activity("Integrating sandbox"):
 | 
| 219 | -            for dep in self.dependencies(Scope.BUILD):
 | |
| 220 | -                dep.integrate(sandbox)
 | |
| 219 | +            for sysroot, dep in self.dependencies(Scope.BUILD, with_sysroot=True):
 | |
| 220 | +                if sysroot == '/':
 | |
| 221 | +                    dep.integrate(sandbox)
 | |
| 221 | 222 |  | 
| 222 | 223 |          # Stage sources in the build root
 | 
| 223 | 224 |          self.stage_sources(sandbox, self.get_variable('build-root'))
 | 
| ... | ... | @@ -376,7 +376,8 @@ class Element(Plugin): | 
| 376 | 376 |          for source in self.__sources:
 | 
| 377 | 377 |              yield source
 | 
| 378 | 378 |  | 
| 379 | -    def dependencies(self, scope, *, recurse=True, visited=None, recursed=False):
 | |
| 379 | +    def dependencies(self, scope, *, recurse=True, visited=None, recursed=False,
 | |
| 380 | +                     with_sysroot=False, sysroot='/'):
 | |
| 380 | 381 |          """dependencies(scope, *, recurse=True)
 | 
| 381 | 382 |  | 
| 382 | 383 |          A generator function which yields the dependencies of the given element.
 | 
| ... | ... | @@ -401,40 +402,56 @@ class Element(Plugin): | 
| 401 | 402 |  | 
| 402 | 403 |          scope_set = set((Scope.BUILD, Scope.RUN)) if scope == Scope.ALL else set((scope,))
 | 
| 403 | 404 |  | 
| 404 | -        if full_name in visited and scope_set.issubset(visited[full_name]):
 | |
| 405 | +        if with_sysroot:
 | |
| 406 | +            key = (sysroot, full_name)
 | |
| 407 | +        else:
 | |
| 408 | +            key = full_name
 | |
| 409 | + | |
| 410 | +        if key in visited and scope_set.issubset(visited[key]):
 | |
| 405 | 411 |              return
 | 
| 406 | 412 |  | 
| 407 | 413 |          should_yield = False
 | 
| 408 | -        if full_name not in visited:
 | |
| 409 | -            visited[full_name] = scope_set
 | |
| 414 | +        if key not in visited:
 | |
| 415 | +            visited[key] = scope_set
 | |
| 410 | 416 |              should_yield = True
 | 
| 411 | 417 |          else:
 | 
| 412 | -            visited[full_name] |= scope_set
 | |
| 418 | +            visited[key] |= scope_set
 | |
| 413 | 419 |  | 
| 414 | 420 |          if recurse or not recursed:
 | 
| 415 | 421 |              if scope == Scope.ALL:
 | 
| 416 | -                for dep in self.__build_dependencies:
 | |
| 422 | +                build_deps = []
 | |
| 423 | +                for dep_sysroot, dep in self.__build_dependencies:
 | |
| 424 | +                    new_sysroot = self._subst_string(dep_sysroot) if not recursed else sysroot
 | |
| 417 | 425 |                      yield from dep.dependencies(Scope.ALL, recurse=recurse,
 | 
| 418 | -                                                visited=visited, recursed=True)
 | |
| 426 | +                                                visited=visited, recursed=True,
 | |
| 427 | +                                                sysroot=new_sysroot, with_sysroot=with_sysroot)
 | |
| 428 | +                    build_deps.append(dep)
 | |
| 419 | 429 |  | 
| 420 | 430 |                  for dep in self.__runtime_dependencies:
 | 
| 421 | -                    if dep not in self.__build_dependencies:
 | |
| 431 | +                    if dep not in build_deps:
 | |
| 422 | 432 |                          yield from dep.dependencies(Scope.ALL, recurse=recurse,
 | 
| 423 | -                                                    visited=visited, recursed=True)
 | |
| 433 | +                                                    visited=visited, recursed=True,
 | |
| 434 | +                                                    sysroot=sysroot, with_sysroot=with_sysroot)
 | |
| 424 | 435 |  | 
| 425 | 436 |              elif scope == Scope.BUILD:
 | 
| 426 | -                for dep in self.__build_dependencies:
 | |
| 437 | +                for dep_sysroot, dep in self.__build_dependencies:
 | |
| 438 | +                    new_sysroot = self._subst_string(dep_sysroot) if not recursed else sysroot
 | |
| 427 | 439 |                      yield from dep.dependencies(Scope.RUN, recurse=recurse,
 | 
| 428 | -                                                visited=visited, recursed=True)
 | |
| 440 | +                                                visited=visited, recursed=True,
 | |
| 441 | +                                                sysroot=new_sysroot, with_sysroot=with_sysroot)
 | |
| 429 | 442 |  | 
| 430 | 443 |              elif scope == Scope.RUN:
 | 
| 431 | 444 |                  for dep in self.__runtime_dependencies:
 | 
| 432 | 445 |                      yield from dep.dependencies(Scope.RUN, recurse=recurse,
 | 
| 433 | -                                                visited=visited, recursed=True)
 | |
| 446 | +                                                visited=visited, recursed=True,
 | |
| 447 | +                                                sysroot=sysroot, with_sysroot=with_sysroot)
 | |
| 434 | 448 |  | 
| 435 | 449 |          # Yeild self only at the end, after anything needed has been traversed
 | 
| 436 | 450 |          if should_yield and (recurse or recursed) and (scope in (Scope.ALL, Scope.RUN)):
 | 
| 437 | -            yield self
 | |
| 451 | +            if with_sysroot:
 | |
| 452 | +                yield sysroot, self
 | |
| 453 | +            else:
 | |
| 454 | +                yield self
 | |
| 438 | 455 |  | 
| 439 | 456 |      def search(self, scope, name):
 | 
| 440 | 457 |          """Search for a dependency by name
 | 
| ... | ... | @@ -633,7 +650,7 @@ class Element(Plugin): | 
| 633 | 650 |              vbasedir = sandbox.get_virtual_directory()
 | 
| 634 | 651 |              vstagedir = vbasedir \
 | 
| 635 | 652 |                  if path is None \
 | 
| 636 | -                else vbasedir.descend(path.lstrip(os.sep).split(os.sep))
 | |
| 653 | +                else vbasedir.descend(path.lstrip(os.sep).split(os.sep), create=True)
 | |
| 637 | 654 |  | 
| 638 | 655 |              files = list(self.__compute_splits(include, exclude, orphans))
 | 
| 639 | 656 |  | 
| ... | ... | @@ -651,7 +668,8 @@ class Element(Plugin): | 
| 651 | 668 |          return link_result.combine(copy_result)
 | 
| 652 | 669 |  | 
| 653 | 670 |      def stage_dependency_artifacts(self, sandbox, scope, *, path=None,
 | 
| 654 | -                                   include=None, exclude=None, orphans=True):
 | |
| 671 | +                                   include=None, exclude=None, orphans=True,
 | |
| 672 | +                                   build=True):
 | |
| 655 | 673 |          """Stage element dependencies in scope
 | 
| 656 | 674 |  | 
| 657 | 675 |          This is primarily a convenience wrapper around
 | 
| ... | ... | @@ -681,7 +699,14 @@ class Element(Plugin): | 
| 681 | 699 |          if self.__can_build_incrementally() and workspace.last_successful:
 | 
| 682 | 700 |              old_dep_keys = self.__get_artifact_metadata_dependencies(workspace.last_successful)
 | 
| 683 | 701 |  | 
| 684 | -        for dep in self.dependencies(scope):
 | |
| 702 | +        def deps():
 | |
| 703 | +            if build:
 | |
| 704 | +                yield from self.dependencies(scope, with_sysroot=True)
 | |
| 705 | +            else:
 | |
| 706 | +                for dep in self.dependencies(scope, with_sysroot=False):
 | |
| 707 | +                    yield '/', dep
 | |
| 708 | + | |
| 709 | +        for sysroot, dep in deps():
 | |
| 685 | 710 |              # If we are workspaced, and we therefore perform an
 | 
| 686 | 711 |              # incremental build, we must ensure that we update the mtimes
 | 
| 687 | 712 |              # of any files created by our dependencies since the last
 | 
| ... | ... | @@ -706,8 +731,13 @@ class Element(Plugin): | 
| 706 | 731 |                      if utils._is_main_process():
 | 
| 707 | 732 |                          self._get_context().get_workspaces().save_config()
 | 
| 708 | 733 |  | 
| 734 | +            if build:
 | |
| 735 | +                sub_path = os.path.join(path, os.path.relpath(sysroot, '/')) if path else sysroot
 | |
| 736 | +            else:
 | |
| 737 | +                sub_path = path
 | |
| 738 | + | |
| 709 | 739 |              result = dep.stage_artifact(sandbox,
 | 
| 710 | -                                        path=path,
 | |
| 740 | +                                        path=sub_path,
 | |
| 711 | 741 |                                          include=include,
 | 
| 712 | 742 |                                          exclude=exclude,
 | 
| 713 | 743 |                                          orphans=orphans,
 | 
| ... | ... | @@ -906,9 +936,9 @@ class Element(Plugin): | 
| 906 | 936 |          for meta_dep in meta.dependencies:
 | 
| 907 | 937 |              dependency = Element._new_from_meta(meta_dep)
 | 
| 908 | 938 |              element.__runtime_dependencies.append(dependency)
 | 
| 909 | -        for meta_dep in meta.build_dependencies:
 | |
| 939 | +        for sysroot, meta_dep in meta.build_dependencies:
 | |
| 910 | 940 |              dependency = Element._new_from_meta(meta_dep)
 | 
| 911 | -            element.__build_dependencies.append(dependency)
 | |
| 941 | +            element.__build_dependencies.append((sysroot, dependency))
 | |
| 912 | 942 |  | 
| 913 | 943 |          return element
 | 
| 914 | 944 |  | 
| ... | ... | @@ -1088,14 +1118,11 @@ class Element(Plugin): | 
| 1088 | 1118 |              # Weak cache key includes names of direct build dependencies
 | 
| 1089 | 1119 |              # but does not include keys of dependencies.
 | 
| 1090 | 1120 |              if self.BST_STRICT_REBUILD:
 | 
| 1091 | -                dependencies = [
 | |
| 1092 | -                    e._get_cache_key(strength=_KeyStrength.WEAK)
 | |
| 1093 | -                    for e in self.dependencies(Scope.BUILD)
 | |
| 1094 | -                ]
 | |
| 1121 | +                dependencies = [(sysroot, e._get_cache_key(strength=_KeyStrength.WEAK))
 | |
| 1122 | +                                for sysroot, e in self.dependencies(Scope.BUILD, with_sysroot=True)]
 | |
| 1095 | 1123 |              else:
 | 
| 1096 | -                dependencies = [
 | |
| 1097 | -                    e.name for e in self.dependencies(Scope.BUILD, recurse=False)
 | |
| 1098 | -                ]
 | |
| 1124 | +                dependencies = [(sysroot, e.name)
 | |
| 1125 | +                                for sysroot, e in self.dependencies(Scope.BUILD, with_sysroot=True)]
 | |
| 1099 | 1126 |  | 
| 1100 | 1127 |              self.__weak_cache_key = self.__calculate_cache_key(dependencies)
 | 
| 1101 | 1128 |  | 
| ... | ... | @@ -1123,9 +1150,8 @@ class Element(Plugin): | 
| 1123 | 1150 |                      return
 | 
| 1124 | 1151 |  | 
| 1125 | 1152 |          if self.__strict_cache_key is None:
 | 
| 1126 | -            dependencies = [
 | |
| 1127 | -                e.__strict_cache_key for e in self.dependencies(Scope.BUILD)
 | |
| 1128 | -            ]
 | |
| 1153 | +            dependencies = [(sysroot, e.__strict_cache_key)
 | |
| 1154 | +                            for sysroot, e in self.dependencies(Scope.BUILD, with_sysroot=True)]
 | |
| 1129 | 1155 |              self.__strict_cache_key = self.__calculate_cache_key(dependencies)
 | 
| 1130 | 1156 |  | 
| 1131 | 1157 |              if self.__strict_cache_key is None:
 | 
| ... | ... | @@ -1165,10 +1191,8 @@ class Element(Plugin): | 
| 1165 | 1191 |                  strong_key, _ = self.__get_artifact_metadata_keys()
 | 
| 1166 | 1192 |                  self.__cache_key = strong_key
 | 
| 1167 | 1193 |              elif self.__assemble_scheduled or self.__assemble_done:
 | 
| 1168 | -                # Artifact will or has been built, not downloaded
 | |
| 1169 | -                dependencies = [
 | |
| 1170 | -                    e._get_cache_key() for e in self.dependencies(Scope.BUILD)
 | |
| 1171 | -                ]
 | |
| 1194 | +                dependencies = [(sysroot, e._get_cache_key())
 | |
| 1195 | +                                for sysroot, e in self.dependencies(Scope.BUILD, with_sysroot=True)]
 | |
| 1172 | 1196 |                  self.__cache_key = self.__calculate_cache_key(dependencies)
 | 
| 1173 | 1197 |  | 
| 1174 | 1198 |              if self.__cache_key is None:
 | 
| ... | ... | @@ -1333,7 +1357,7 @@ class Element(Plugin): | 
| 1333 | 1357 |                      # Stage deps in the sandbox root
 | 
| 1334 | 1358 |                      if deps == 'run':
 | 
| 1335 | 1359 |                          with self.timed_activity("Staging dependencies", silent_nested=True):
 | 
| 1336 | -                            self.stage_dependency_artifacts(sandbox, scope)
 | |
| 1360 | +                            self.stage_dependency_artifacts(sandbox, scope, build=False)
 | |
| 1337 | 1361 |  | 
| 1338 | 1362 |                          # Run any integration commands provided by the dependencies
 | 
| 1339 | 1363 |                          # once they are all staged and ready
 | 
| ... | ... | @@ -2011,8 +2035,11 @@ class Element(Plugin): | 
| 2011 | 2035 |      #
 | 
| 2012 | 2036 |      def __calculate_cache_key(self, dependencies):
 | 
| 2013 | 2037 |          # No cache keys for dependencies which have no cache keys
 | 
| 2014 | -        if None in dependencies:
 | |
| 2015 | -            return None
 | |
| 2038 | +        for dep in dependencies:
 | |
| 2039 | +            if dep[1] is None:
 | |
| 2040 | +                return None
 | |
| 2041 | +        # Do not break cache keys
 | |
| 2042 | +        dependencies = [(sysroot, key) if sysroot != '/' else key for sysroot, key in dependencies]
 | |
| 2016 | 2043 |  | 
| 2017 | 2044 |          # Generate dict that is used as base for all cache keys
 | 
| 2018 | 2045 |          if self.__cache_key_dict is None:
 | 
| ... | ... | @@ -122,8 +122,9 @@ class ComposeElement(Element): | 
| 122 | 122 |                      snapshot = set(vbasedir.list_relative_paths())
 | 
| 123 | 123 |                      vbasedir.mark_unmodified()
 | 
| 124 | 124 |  | 
| 125 | -                for dep in self.dependencies(Scope.BUILD):
 | |
| 126 | -                    dep.integrate(sandbox)
 | |
| 125 | +                for sysroot, dep in self.dependencies(Scope.BUILD, with_sysroot=True):
 | |
| 126 | +                    if sysroot == '/':
 | |
| 127 | +                        dep.integrate(sandbox)
 | |
| 127 | 128 |  | 
| 128 | 129 |                  if require_split:
 | 
| 129 | 130 |                      # Calculate added, modified and removed files
 | 
| ... | ... | @@ -159,6 +159,57 @@ See :ref:`format_dependencies` for more information on the dependency model. | 
| 159 | 159 |  | 
| 160 | 160 |     The ``runtime-depends`` configuration is available since :ref:`format version 14 <project_format_version>`
 | 
| 161 | 161 |  | 
| 162 | +Sysroot'ed dependencies
 | |
| 163 | +~~~~~~~~~~~~~~~~~~~~~~~
 | |
| 164 | + | |
| 165 | +Sysroot'ed dependencies are intended for bootstraping base systems or
 | |
| 166 | +cross-compiling.
 | |
| 167 | + | |
| 168 | +.. code:: yaml
 | |
| 169 | + | |
| 170 | +   # Specify some sysroot'ed dependencies
 | |
| 171 | +   sysroots:
 | |
| 172 | +   - path: /sysroot
 | |
| 173 | +     depends:
 | |
| 174 | +     - element1.bst
 | |
| 175 | +     - element2.bst
 | |
| 176 | + | |
| 177 | +During build, or initialization of build shell, sysroot'ed build
 | |
| 178 | +dependencies will be staged in the given sysroot path instead of '/'
 | |
| 179 | +together with the runtime dependencies of those sysroot'ed build
 | |
| 180 | +dependencies.
 | |
| 181 | + | |
| 182 | +It is possible to end up with indirect runtime dependencies in
 | |
| 183 | +different sysroots if they are staged from build dependencies with
 | |
| 184 | +different sysroots. They will be staged multiple times.
 | |
| 185 | + | |
| 186 | +Sysroot paths only apply to build dependencies. It is not possible to
 | |
| 187 | +define runtime dependencies either with ``type: runtime`` or
 | |
| 188 | +``runtime-depends``. It is possible to use ``all`` dependencies, but
 | |
| 189 | +the sysroot part is only for the build part not the runtime.
 | |
| 190 | + | |
| 191 | +For example:
 | |
| 192 | + | |
| 193 | +.. code:: yaml
 | |
| 194 | + | |
| 195 | +   sysroots:
 | |
| 196 | +   - path: /sysroot
 | |
| 197 | +     depends:
 | |
| 198 | +     - element.bst
 | |
| 199 | + | |
| 200 | +is equivalent to:
 | |
| 201 | + | |
| 202 | +.. code:: yaml
 | |
| 203 | + | |
| 204 | +   runtime-depends:
 | |
| 205 | +   - element.bst
 | |
| 206 | +   sysroots:
 | |
| 207 | +   - path: /sysroot
 | |
| 208 | +     build-depends:
 | |
| 209 | +     - element.bst
 | |
| 210 | + | |
| 211 | +:ref:`Integration commands <public_integration>` are never executed for
 | |
| 212 | +sysroot'ed dependencies.
 | |
| 162 | 213 |  | 
| 163 | 214 |  .. _format_sources:
 | 
| 164 | 215 |  | 
| ... | ... | @@ -135,7 +135,7 @@ def test_build_dependency(datafiles): | 
| 135 | 135 |  | 
| 136 | 136 |      assert(len(element.build_dependencies) == 1)
 | 
| 137 | 137 |      firstdep = element.build_dependencies[0]
 | 
| 138 | -    assert(isinstance(firstdep, MetaElement))
 | |
| 138 | +    assert(isinstance(firstdep[1], MetaElement))
 | |
| 139 | 139 |  | 
| 140 | 140 |      assert(len(element.dependencies) == 0)
 | 
| 141 | 141 |  | 
| ... | ... | @@ -170,7 +170,7 @@ def test_build_runtime_dependency(datafiles): | 
| 170 | 170 |      firstdep = element.dependencies[0]
 | 
| 171 | 171 |      assert(isinstance(firstdep, MetaElement))
 | 
| 172 | 172 |      firstbuilddep = element.build_dependencies[0]
 | 
| 173 | -    assert(firstdep == firstbuilddep)
 | |
| 173 | +    assert(firstdep == firstbuilddep[1])
 | |
| 174 | 174 |  | 
| 175 | 175 |  | 
| 176 | 176 |  @pytest.mark.datafiles(DATA_DIR)
 | 
| ... | ... | @@ -187,7 +187,7 @@ def test_all_dependency(datafiles): | 
| 187 | 187 |      firstdep = element.dependencies[0]
 | 
| 188 | 188 |      assert(isinstance(firstdep, MetaElement))
 | 
| 189 | 189 |      firstbuilddep = element.build_dependencies[0]
 | 
| 190 | -    assert(firstdep == firstbuilddep)
 | |
| 190 | +    assert(firstdep == firstbuilddep[1])
 | |
| 191 | 191 |  | 
| 192 | 192 |  | 
| 193 | 193 |  @pytest.mark.datafiles(DATA_DIR)
 | 
| 1 | +kind: import
 | |
| 2 | +sources:
 | |
| 3 | +  - kind: local
 | |
| 4 | +    path: files/a | 
| 1 | +kind: import
 | |
| 2 | +sources:
 | |
| 3 | +  - kind: local
 | |
| 4 | +    path: files/b | 
| 1 | +kind: stack
 | |
| 2 | +depends:
 | |
| 3 | +- base/base-alpine.bst | 
| 1 | +kind: import
 | |
| 2 | + | |
| 3 | +description: |
 | |
| 4 | +  Alpine Linux base for tests
 | |
| 5 | + | |
| 6 | +  Generated using the `tests/integration-tests/base/generate-base.sh` script.
 | |
| 7 | + | |
| 8 | +sources:
 | |
| 9 | +  - kind: tar
 | |
| 10 | +    url: alpine:integration-tests-base.v1.x86_64.tar.xz
 | |
| 11 | +    base-dir: ''
 | |
| 12 | +    ref: 3eb559250ba82b64a68d86d0636a6b127aa5f6d25d3601a79f79214dc9703639 | 
| 1 | +kind: compose
 | |
| 2 | + | |
| 3 | +sysroots:
 | |
| 4 | +- path: /sysroot
 | |
| 5 | +  build-depends:
 | |
| 6 | +  - integration.bst | 
| 1 | +kind: compose
 | |
| 2 | + | |
| 3 | +sysroots:
 | |
| 4 | +- path: /other-sysroot
 | |
| 5 | +  build-depends:
 | |
| 6 | +  - layer2.bst | 
| 1 | +kind: compose
 | |
| 2 | + | |
| 3 | +build-depends:
 | |
| 4 | +- layer2.bst | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- base.bst
 | |
| 5 | + | |
| 6 | +config:
 | |
| 7 | +  install-commands:
 | |
| 8 | +    - echo 0 >"%{install-root}/integrated.txt"
 | |
| 9 | + | |
| 10 | +public:
 | |
| 11 | +  bst:
 | |
| 12 | +    integration-commands:
 | |
| 13 | +    - echo 1 >/integrated.txt | 
| 1 | +kind: import
 | |
| 2 | +sources:
 | |
| 3 | +- kind: local
 | |
| 4 | +  path: files/layer1 | 
| 1 | +kind: stack
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- layer1-files.bst | 
| 1 | +kind: import
 | |
| 2 | +sources:
 | |
| 3 | +- kind: local
 | |
| 4 | +  path: files/layer2 | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- layer2-files.bst
 | |
| 5 | + | |
| 6 | +build-depends:
 | |
| 7 | +- base.bst
 | |
| 8 | + | |
| 9 | +sysroots:
 | |
| 10 | +- path: /sysroot
 | |
| 11 | +  depends:
 | |
| 12 | +  - layer1.bst
 | |
| 13 | + | |
| 14 | +config:
 | |
| 15 | +  install-commands:
 | |
| 16 | +  - mkdir -p "%{install-root}"
 | |
| 17 | +  - |
 | |
| 18 | +    for file in /*; do
 | |
| 19 | +      if test -f "${file}"; then
 | |
| 20 | +        cp "${file}" "%{install-root}"
 | |
| 21 | +      fi
 | |
| 22 | +    done | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +depends:
 | |
| 4 | +- base.bst
 | |
| 5 | + | |
| 6 | +sysroots:
 | |
| 7 | +- path: /sysroot
 | |
| 8 | +  depends:
 | |
| 9 | +  - integration.bst
 | |
| 10 | + | |
| 11 | +config:
 | |
| 12 | +  install-commands:
 | |
| 13 | +    - mkdir -p "%{install-root}"
 | |
| 14 | +    - echo dummy >"%{install-root}/dummy.txt" | 
| 1 | +kind: manual
 | |
| 2 | + | |
| 3 | +build-depends:
 | |
| 4 | +- base.bst
 | |
| 5 | + | |
| 6 | +sysroots:
 | |
| 7 | +- path: /sysroot
 | |
| 8 | +  build-depends:
 | |
| 9 | +  - integration.bst
 | |
| 10 | + | |
| 11 | +config:
 | |
| 12 | +  install-commands:
 | |
| 13 | +    - mkdir -p "%{install-root}/sysroot"
 | |
| 14 | +    - if test -f /sysroot/integrated.txt; then cp /sysroot/integrated.txt "%{install-root}/sysroot"; fi
 | |
| 15 | +    - if test -f /integrated.txt; then cp /integrated.txt "%{install-root}"; fi | 
| 1 | +kind: compose
 | |
| 2 | + | |
| 3 | +build-depends:
 | |
| 4 | +- a.bst
 | |
| 5 | + | |
| 6 | +variables:
 | |
| 7 | +  mydir: test
 | |
| 8 | + | |
| 9 | +sysroots:
 | |
| 10 | +- path: "/path/%{mydir}"
 | |
| 11 | +  build-depends:
 | |
| 12 | +  - b.bst | 
| 1 | +kind: compose
 | |
| 2 | + | |
| 3 | +build-depends:
 | |
| 4 | +- a.bst
 | |
| 5 | + | |
| 6 | +sysroots:
 | |
| 7 | +- path: /sysroot
 | |
| 8 | +  build-depends:
 | |
| 9 | +  - b.bst | 
| 1 | +test | 
| 1 | +test | 
| 1 | +1 | 
| 1 | +2 | 
| 1 | +name: test
 | |
| 2 | +element-path: elements
 | |
| 3 | +aliases:
 | |
| 4 | +  alpine: https://bst-integration-test-images.ams3.cdn.digitaloceanspaces.com/
 | |
| 5 | +options:
 | |
| 6 | +  linux:
 | |
| 7 | +    type: bool
 | |
| 8 | +    description: Whether to expect a linux platform
 | |
| 9 | +    default: True | 
| 1 | +import os
 | |
| 2 | +import pytest
 | |
| 3 | +from tests.testutils import cli_integration as cli
 | |
| 4 | + | |
| 5 | + | |
| 6 | +# Project directory
 | |
| 7 | +DATA_DIR = os.path.join(
 | |
| 8 | +    os.path.dirname(os.path.realpath(__file__)),
 | |
| 9 | +    "project",
 | |
| 10 | +)
 | |
| 11 | + | |
| 12 | + | |
| 13 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 14 | +def test_sysroot_dependency_smoke_test(datafiles, cli, tmpdir):
 | |
| 15 | +    "Test simple sysroot use case without integration"
 | |
| 16 | + | |
| 17 | +    project = str(datafiles)
 | |
| 18 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 19 | + | |
| 20 | +    result = cli.run(project=project,
 | |
| 21 | +                     args=['build', 'target.bst'])
 | |
| 22 | +    result.assert_success()
 | |
| 23 | + | |
| 24 | +    result = cli.run(project=project,
 | |
| 25 | +                     args=['checkout', 'target.bst', checkout])
 | |
| 26 | +    result.assert_success()
 | |
| 27 | +    assert os.path.exists(os.path.join(checkout, 'a.txt'))
 | |
| 28 | +    assert os.path.exists(os.path.join(checkout, 'sysroot', 'b.txt'))
 | |
| 29 | + | |
| 30 | + | |
| 31 | +@pytest.mark.integration
 | |
| 32 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 33 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 34 | +def test_skip_integration_commands_compose(datafiles, cli, tmpdir):
 | |
| 35 | +    "Integration commands are not run on sysroots"
 | |
| 36 | + | |
| 37 | +    project = str(datafiles)
 | |
| 38 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 39 | + | |
| 40 | +    result = cli.run(project=project,
 | |
| 41 | +                     args=['build', 'compose-integration.bst'])
 | |
| 42 | +    result.assert_success()
 | |
| 43 | + | |
| 44 | +    result = cli.run(project=project,
 | |
| 45 | +                     args=['checkout', 'compose-integration.bst', checkout])
 | |
| 46 | +    result.assert_success()
 | |
| 47 | + | |
| 48 | +    integrated = os.path.join(checkout, 'sysroot', 'integrated.txt')
 | |
| 49 | +    assert os.path.exists(integrated)
 | |
| 50 | +    with open(integrated, 'r') as f:
 | |
| 51 | +        assert f.read() == '0\n'
 | |
| 52 | + | |
| 53 | + | |
| 54 | +@pytest.mark.integration
 | |
| 55 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 56 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 57 | +def test_skip_integration_commands_build_element(datafiles, cli, tmpdir):
 | |
| 58 | +    "Integration commands are not run on sysroots"
 | |
| 59 | + | |
| 60 | +    project = str(datafiles)
 | |
| 61 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 62 | + | |
| 63 | +    result = cli.run(project=project,
 | |
| 64 | +                     args=['build', 'manual-integration.bst'])
 | |
| 65 | +    result.assert_success()
 | |
| 66 | + | |
| 67 | +    result = cli.run(project=project,
 | |
| 68 | +                     args=['checkout', 'manual-integration.bst', checkout])
 | |
| 69 | +    result.assert_success()
 | |
| 70 | + | |
| 71 | +    sysroot_integrated = os.path.join(checkout, 'sysroot', 'integrated.txt')
 | |
| 72 | +    integrated = os.path.join(checkout, 'integrated.txt')
 | |
| 73 | +    assert os.path.exists(sysroot_integrated)
 | |
| 74 | +    with open(sysroot_integrated, 'r') as f:
 | |
| 75 | +        assert f.read() == '0\n'
 | |
| 76 | +    # We need to make sure that integration command has not been run on / either.
 | |
| 77 | +    assert not os.path.exists(integrated)
 | |
| 78 | + | |
| 79 | + | |
| 80 | +@pytest.mark.integration
 | |
| 81 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 82 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 83 | +def test_sysroot_only_for_build(cli, tmpdir, datafiles):
 | |
| 84 | +    project = str(datafiles)
 | |
| 85 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 86 | + | |
| 87 | +    result = cli.run(project=project,
 | |
| 88 | +                     args=['build', 'compose-layers.bst'])
 | |
| 89 | +    result.assert_success()
 | |
| 90 | + | |
| 91 | +    result = cli.run(project=project,
 | |
| 92 | +                     args=['checkout', 'compose-layers.bst', checkout])
 | |
| 93 | + | |
| 94 | +    result.assert_success()
 | |
| 95 | +    assert os.path.exists(os.path.join(checkout, '1'))
 | |
| 96 | +    assert os.path.exists(os.path.join(checkout, '2'))
 | |
| 97 | +    assert not os.path.exists(os.path.join(checkout, 'sysroot', '1'))
 | |
| 98 | + | |
| 99 | + | |
| 100 | +@pytest.mark.integration
 | |
| 101 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 102 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 103 | +def test_sysroot_only_for_build_with_sysroot(cli, tmpdir, datafiles):
 | |
| 104 | +    project = str(datafiles)
 | |
| 105 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 106 | + | |
| 107 | +    result = cli.run(project=project,
 | |
| 108 | +                     args=['build', 'compose-layers-with-sysroot.bst'])
 | |
| 109 | +    result.assert_success()
 | |
| 110 | + | |
| 111 | +    result = cli.run(project=project,
 | |
| 112 | +                     args=['checkout', 'compose-layers-with-sysroot.bst', checkout])
 | |
| 113 | + | |
| 114 | +    result.assert_success()
 | |
| 115 | +    assert os.path.exists(os.path.join(checkout, 'other-sysroot', '1'))
 | |
| 116 | +    assert os.path.exists(os.path.join(checkout, 'other-sysroot', '2'))
 | |
| 117 | +    assert not os.path.exists(os.path.join(checkout, 'sysroot', '1'))
 | |
| 118 | + | |
| 119 | + | |
| 120 | +@pytest.mark.integration
 | |
| 121 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 122 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 123 | +def test_shell_no_sysroot(cli, tmpdir, datafiles):
 | |
| 124 | +    "bst shell does not have sysroots and dependencies are integrated"
 | |
| 125 | + | |
| 126 | +    project = str(datafiles)
 | |
| 127 | + | |
| 128 | +    result = cli.run(project=project,
 | |
| 129 | +                     args=['build', 'base.bst', 'manual-integration-runtime.bst'])
 | |
| 130 | +    result.assert_success()
 | |
| 131 | + | |
| 132 | +    result = cli.run(project=project,
 | |
| 133 | +                     args=['shell', 'manual-integration-runtime.bst', '--', 'cat', '/integrated.txt'])
 | |
| 134 | +    result.assert_success()
 | |
| 135 | +    assert result.output == '1\n'
 | |
| 136 | + | |
| 137 | +    result = cli.run(project=project,
 | |
| 138 | +                     args=['shell', 'manual-integration-runtime.bst', '--', 'ls', '/sysroot/integrated.txt'])
 | |
| 139 | +    assert result.exit_code != 0
 | |
| 140 | +    assert result.output == ''
 | |
| 141 | + | |
| 142 | + | |
| 143 | +@pytest.mark.integration
 | |
| 144 | +@pytest.mark.skipif(IS_LINUX and not HAVE_BWRAP, reason='Only available with bubblewrap on Linux')
 | |
| 145 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 146 | +def test_shell_build_sysroot(cli, tmpdir, datafiles):
 | |
| 147 | +    "Build shell should stage build dependencies sysroot'ed non integrated"
 | |
| 148 | + | |
| 149 | +    project = str(datafiles)
 | |
| 150 | + | |
| 151 | +    result = cli.run(project=project,
 | |
| 152 | +                     args=['build', 'base.bst', 'integration.bst'])
 | |
| 153 | +    result.assert_success()
 | |
| 154 | + | |
| 155 | +    result = cli.run(project=project,
 | |
| 156 | +                     args=['shell', '-b', 'manual-integration.bst', '--', 'cat', '/sysroot/integrated.txt'])
 | |
| 157 | +    result.assert_success()
 | |
| 158 | +    assert result.output == '0\n'
 | |
| 159 | + | |
| 160 | + | |
| 161 | +@pytest.mark.integration
 | |
| 162 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 163 | +def test_show_dependencies_only_once(cli, tmpdir, datafiles):
 | |
| 164 | +    """Dependencies should not show up in status several times when they
 | |
| 165 | +    are staged with multiple sysroots"""
 | |
| 166 | + | |
| 167 | +    project = str(datafiles)
 | |
| 168 | + | |
| 169 | +    result = cli.run(project=project,
 | |
| 170 | +                     args=['show', '--format', '%{name}', 'manual-integration.bst'])
 | |
| 171 | +    result.assert_success()
 | |
| 172 | +    pipeline = result.output.splitlines()
 | |
| 173 | +    assert pipeline == ['base/base-alpine.bst',
 | |
| 174 | +                        'base.bst',
 | |
| 175 | +                        'integration.bst',
 | |
| 176 | +                        'manual-integration.bst']
 | |
| 177 | + | |
| 178 | + | |
| 179 | +@pytest.mark.datafiles(DATA_DIR)
 | |
| 180 | +def test_sysroot_path_subst_variable(datafiles, cli, tmpdir):
 | |
| 181 | +    "Test that variables are expanded in sysroot path"
 | |
| 182 | + | |
| 183 | +    project = str(datafiles)
 | |
| 184 | +    checkout = os.path.join(str(tmpdir), 'checkout')
 | |
| 185 | + | |
| 186 | +    result = cli.run(project=project,
 | |
| 187 | +                     args=['build', 'target-variable.bst'])
 | |
| 188 | +    result.assert_success()
 | |
| 189 | + | |
| 190 | +    result = cli.run(project=project,
 | |
| 191 | +                     args=['checkout', 'target-variable.bst', checkout])
 | |
| 192 | +    result.assert_success()
 | |
| 193 | + | |
| 194 | +    assert os.path.exists(os.path.join(checkout, 'a.txt'))
 | |
| 195 | +    assert os.path.exists(os.path.join(checkout, 'path/test', 'b.txt')) | 
