Benjamin Schubert pushed to branch bschubert/pipeline-bak at BuildStream / buildstream
Commits:
- 
fc3dcec8
by Angelos Evripiotis at 2019-01-11T16:45:00Z
 - 
f86b7ff3
by Angelos Evripiotis at 2019-01-11T16:45:00Z
 - 
d983f231
by Angelos Evripiotis at 2019-01-11T17:19:06Z
 - 
829a2b93
by Tristan Van Berkom at 2019-01-12T21:32:13Z
 - 
993e30ae
by Tristan Van Berkom at 2019-01-12T22:02:55Z
 - 
32732e01
by Javier Jardón at 2019-01-14T09:04:01Z
 - 
4f5f1184
by Valentin David at 2019-01-14T09:40:56Z
 - 
bb80a2b8
by Chandan Singh at 2019-01-14T14:30:15Z
 - 
10b3ee62
by Chandan Singh at 2019-01-14T14:57:08Z
 - 
542cdaf0
by Jürg Billeter at 2019-01-14T18:46:57Z
 - 
ff666e76
by James Ennis at 2019-01-14T18:46:57Z
 - 
273b0f55
by Tristan Van Berkom at 2019-01-14T20:24:57Z
 - 
2e3c2ea2
by Javier Jardón at 2019-01-14T22:26:04Z
 - 
d60d2e31
by Javier Jardón at 2019-01-15T00:39:22Z
 - 
605836c1
by Chandan Singh at 2019-01-15T00:57:05Z
 - 
006370af
by Chandan Singh at 2019-01-15T01:21:33Z
 - 
2d82468a
by Benjamin Schubert at 2019-01-15T11:37:14Z
 - 
df8c4324
by Benjamin Schubert at 2019-01-15T11:37:15Z
 
12 changed files:
- .gitlab-ci.yml
 - CONTRIBUTING.rst
 - buildstream/_frontend/cli.py
 - buildstream/_pipeline.py
 - buildstream/_scheduler/queues/buildqueue.py
 - buildstream/_scheduler/queues/fetchqueue.py
 - buildstream/_scheduler/queues/pullqueue.py
 - buildstream/_scheduler/queues/queue.py
 - buildstream/_stream.py
 - buildstream/element.py
 - requirements/dev-requirements.txt
 - tests/completions/completions.py
 
Changes:
| ... | ... | @@ -61,7 +61,7 @@ tests-ubuntu-18.04: | 
| 61 | 61 | 
   <<: *tests
 | 
| 62 | 62 | 
 | 
| 63 | 63 | 
 overnight-fedora-28-aarch64:
 | 
| 64 | 
-  image: buildstream/testsuite-fedora:aarch64-28-06bab030-32a101f6
 | 
|
| 64 | 
+  image: buildstream/testsuite-fedora:aarch64-28-5da27168-32c47d1c
 | 
|
| 65 | 65 | 
   tags:
 | 
| 66 | 66 | 
     - aarch64
 | 
| 67 | 67 | 
   <<: *tests
 | 
| ... | ... | @@ -70,6 +70,12 @@ overnight-fedora-28-aarch64: | 
| 70 | 70 | 
   except: []
 | 
| 71 | 71 | 
   only:
 | 
| 72 | 72 | 
   - schedules
 | 
| 73 | 
+  before_script:
 | 
|
| 74 | 
+    # grpcio needs to be compiled from source on aarch64 so we additionally
 | 
|
| 75 | 
+    # need a C++ compiler here.
 | 
|
| 76 | 
+    # FIXME: Ideally this would be provided by the base image. This will be
 | 
|
| 77 | 
+    # unblocked by https://gitlab.com/BuildStream/buildstream-docker-images/issues/34
 | 
|
| 78 | 
+    - dnf install -y gcc-c++
 | 
|
| 73 | 79 | 
 | 
| 74 | 80 | 
 tests-unix:
 | 
| 75 | 81 | 
   # Use fedora here, to a) run a test on fedora and b) ensure that we
 | 
| ... | ... | @@ -90,7 +96,6 @@ tests-unix: | 
| 90 | 96 | 
     # Since the unix platform is required to run as root, no user change required
 | 
| 91 | 97 | 
     - ${TEST_COMMAND}
 | 
| 92 | 98 | 
 | 
| 93 | 
-  | 
|
| 94 | 99 | 
 tests-fedora-missing-deps:
 | 
| 95 | 100 | 
   # Ensure that tests behave nicely while missing bwrap and ostree
 | 
| 96 | 101 | 
   image: buildstream/testsuite-fedora:28-5da27168-32c47d1c
 | 
| ... | ... | @@ -108,6 +113,22 @@ tests-fedora-missing-deps: | 
| 108 | 113 | 
 | 
| 109 | 114 | 
     - ${TEST_COMMAND}
 | 
| 110 | 115 | 
 | 
| 116 | 
+tests-fedora-update-deps:
 | 
|
| 117 | 
+  # Check if the tests pass after updating requirements to their latest
 | 
|
| 118 | 
+  # allowed version.
 | 
|
| 119 | 
+  allow_failure: true
 | 
|
| 120 | 
+  image: buildstream/testsuite-fedora:28-5da27168-32c47d1c
 | 
|
| 121 | 
+  <<: *tests
 | 
|
| 122 | 
+  | 
|
| 123 | 
+  script:
 | 
|
| 124 | 
+    - useradd -Um buildstream
 | 
|
| 125 | 
+    - chown -R buildstream:buildstream .
 | 
|
| 126 | 
+  | 
|
| 127 | 
+    - make --always-make --directory requirements
 | 
|
| 128 | 
+    - cat requirements/*.txt
 | 
|
| 129 | 
+  | 
|
| 130 | 
+    - su buildstream -c "${TEST_COMMAND}"
 | 
|
| 131 | 
+  | 
|
| 111 | 132 | 
 # Lint separately from testing
 | 
| 112 | 133 | 
 lint:
 | 
| 113 | 134 | 
   stage: test
 | 
| ... | ... | @@ -140,8 +161,8 @@ docs: | 
| 140 | 161 | 
   stage: test
 | 
| 141 | 162 | 
   variables:
 | 
| 142 | 163 | 
     BST_EXT_URL: git+https://gitlab.com/BuildStream/bst-external.git
 | 
| 143 | 
-    BST_EXT_REF: 573843768f4d297f85dc3067465b3c7519a8dcc3 # 0.7.0
 | 
|
| 144 | 
-    FD_SDK_REF: 612f66e218445eee2b1a9d7dd27c9caba571612e # freedesktop-sdk-18.08.19-54-g612f66e2
 | 
|
| 164 | 
+    BST_EXT_REF: 0.9.0-0-g63a19e8068bd777bd9cd59b1a9442f9749ea5a85
 | 
|
| 165 | 
+    FD_SDK_REF: freedesktop-sdk-18.08.25-0-g250939d465d6dd7768a215f1fa59c4a3412fc337
 | 
|
| 145 | 166 | 
   before_script:
 | 
| 146 | 167 | 
   - |
 | 
| 147 | 168 | 
     mkdir -p "${HOME}/.config"
 | 
| ... | ... | @@ -1534,6 +1534,10 @@ You can always abort on the first failure by running:: | 
| 1534 | 1534 | 
 | 
| 1535 | 1535 | 
   tox -- -x
 | 
| 1536 | 1536 | 
 | 
| 1537 | 
+Similarly, you may also be interested in the ``--last-failed`` and
 | 
|
| 1538 | 
+``--failed-first`` options as per the
 | 
|
| 1539 | 
+`pytest cache <https://docs.pytest.org/en/latest/cache.html>`_ documentation.
 | 
|
| 1540 | 
+  | 
|
| 1537 | 1541 | 
 If you want to run a specific test or a group of tests, you
 | 
| 1538 | 1542 | 
 can specify a prefix to match. E.g. if you want to run all of
 | 
| 1539 | 1543 | 
 the frontend tests you can do::
 | 
| ... | ... | @@ -1545,6 +1549,12 @@ If you wanted to run the test_build_track test within frontend/buildtrack.py you | 
| 1545 | 1549 | 
 | 
| 1546 | 1550 | 
   tox -- tests/frontend/buildtrack.py::test_build_track
 | 
| 1547 | 1551 | 
 | 
| 1552 | 
+When running only a few tests, you may find the coverage and timing output
 | 
|
| 1553 | 
+excessive, there are options to trim them. Note that coverage step will fail.
 | 
|
| 1554 | 
+Here is an example::
 | 
|
| 1555 | 
+  | 
|
| 1556 | 
+  tox -- --no-cov --durations=1 tests/frontend/buildtrack.py::test_build_track
 | 
|
| 1557 | 
+  | 
|
| 1548 | 1558 | 
 We also have a set of slow integration tests that are disabled by
 | 
| 1549 | 1559 | 
 default - you will notice most of them marked with SKIP in the pytest
 | 
| 1550 | 1560 | 
 output. To run them, you can use::
 | 
| ... | ... | @@ -2,6 +2,7 @@ import os | 
| 2 | 2 | 
 import sys
 | 
| 3 | 3 | 
 from contextlib import ExitStack
 | 
| 4 | 4 | 
 from fnmatch import fnmatch
 | 
| 5 | 
+from functools import partial
 | 
|
| 5 | 6 | 
 from tempfile import TemporaryDirectory
 | 
| 6 | 7 | 
 | 
| 7 | 8 | 
 import click
 | 
| ... | ... | @@ -111,14 +112,25 @@ def complete_target(args, incomplete): | 
| 111 | 112 | 
     return complete_list
 | 
| 112 | 113 | 
 | 
| 113 | 114 | 
 | 
| 114 | 
-def complete_artifact(args, incomplete):
 | 
|
| 115 | 
+def complete_artifact(orig_args, args, incomplete):
 | 
|
| 115 | 116 | 
     from .._context import Context
 | 
| 116 | 117 | 
     ctx = Context()
 | 
| 117 | 118 | 
 | 
| 118 | 119 | 
     config = None
 | 
| 119 | 
-    for i, arg in enumerate(args):
 | 
|
| 120 | 
-        if arg in ('-c', '--config'):
 | 
|
| 121 | 
-            config = args[i + 1]
 | 
|
| 120 | 
+    if orig_args:
 | 
|
| 121 | 
+        for i, arg in enumerate(orig_args):
 | 
|
| 122 | 
+            if arg in ('-c', '--config'):
 | 
|
| 123 | 
+                try:
 | 
|
| 124 | 
+                    config = orig_args[i + 1]
 | 
|
| 125 | 
+                except IndexError:
 | 
|
| 126 | 
+                    pass
 | 
|
| 127 | 
+    if args:
 | 
|
| 128 | 
+        for i, arg in enumerate(args):
 | 
|
| 129 | 
+            if arg in ('-c', '--config'):
 | 
|
| 130 | 
+                try:
 | 
|
| 131 | 
+                    config = args[i + 1]
 | 
|
| 132 | 
+                except IndexError:
 | 
|
| 133 | 
+                    pass
 | 
|
| 122 | 134 | 
     ctx.load(config)
 | 
| 123 | 135 | 
 | 
| 124 | 136 | 
     # element targets are valid artifact names
 | 
| ... | ... | @@ -128,8 +140,9 @@ def complete_artifact(args, incomplete): | 
| 128 | 140 | 
     return complete_list
 | 
| 129 | 141 | 
 | 
| 130 | 142 | 
 | 
| 131 | 
-def override_completions(cmd, cmd_param, args, incomplete):
 | 
|
| 143 | 
+def override_completions(orig_args, cmd, cmd_param, args, incomplete):
 | 
|
| 132 | 144 | 
     """
 | 
| 145 | 
+    :param orig_args: original, non-completion args
 | 
|
| 133 | 146 | 
     :param cmd_param: command definition
 | 
| 134 | 147 | 
     :param args: full list of args typed before the incomplete arg
 | 
| 135 | 148 | 
     :param incomplete: the incomplete text to autocomplete
 | 
| ... | ... | @@ -150,7 +163,7 @@ def override_completions(cmd, cmd_param, args, incomplete): | 
| 150 | 163 | 
                 cmd_param.opts == ['--track-except']):
 | 
| 151 | 164 | 
             return complete_target(args, incomplete)
 | 
| 152 | 165 | 
         if cmd_param.name == 'artifacts':
 | 
| 153 | 
-            return complete_artifact(args, incomplete)
 | 
|
| 166 | 
+            return complete_artifact(orig_args, args, incomplete)
 | 
|
| 154 | 167 | 
 | 
| 155 | 168 | 
     raise CompleteUnhandled()
 | 
| 156 | 169 | 
 | 
| ... | ... | @@ -161,7 +174,7 @@ def override_main(self, args=None, prog_name=None, complete_var=None, | 
| 161 | 174 | 
     # Hook for the Bash completion.  This only activates if the Bash
 | 
| 162 | 175 | 
     # completion is actually enabled, otherwise this is quite a fast
 | 
| 163 | 176 | 
     # noop.
 | 
| 164 | 
-    if main_bashcomplete(self, prog_name, override_completions):
 | 
|
| 177 | 
+    if main_bashcomplete(self, prog_name, partial(override_completions, args)):
 | 
|
| 165 | 178 | 
 | 
| 166 | 179 | 
         # If we're running tests we cant just go calling exit()
 | 
| 167 | 180 | 
         # from the main process.
 | 
| ... | ... | @@ -136,9 +136,6 @@ class Pipeline(): | 
| 136 | 136 | 
                 # Preflight
 | 
| 137 | 137 | 
                 element._preflight()
 | 
| 138 | 138 | 
 | 
| 139 | 
-                # Determine initial element state.
 | 
|
| 140 | 
-                element._update_state()
 | 
|
| 141 | 
-  | 
|
| 142 | 139 | 
     # dependencies()
 | 
| 143 | 140 | 
     #
 | 
| 144 | 141 | 
     # Generator function to iterate over elements and optionally
 | 
| ... | ... | @@ -71,9 +71,6 @@ class BuildQueue(Queue): | 
| 71 | 71 | 
         return element._assemble()
 | 
| 72 | 72 | 
 | 
| 73 | 73 | 
     def status(self, element):
 | 
| 74 | 
-        # state of dependencies may have changed, recalculate element state
 | 
|
| 75 | 
-        element._update_state()
 | 
|
| 76 | 
-  | 
|
| 77 | 74 | 
         if not element._is_required():
 | 
| 78 | 75 | 
             # Artifact is not currently required but it may be requested later.
 | 
| 79 | 76 | 
             # Keep it in the queue.
 | 
| ... | ... | @@ -44,9 +44,6 @@ class FetchQueue(Queue): | 
| 44 | 44 | 
         element._fetch()
 | 
| 45 | 45 | 
 | 
| 46 | 46 | 
     def status(self, element):
 | 
| 47 | 
-        # state of dependencies may have changed, recalculate element state
 | 
|
| 48 | 
-        element._update_state()
 | 
|
| 49 | 
-  | 
|
| 50 | 47 | 
         if not element._is_required():
 | 
| 51 | 48 | 
             # Artifact is not currently required but it may be requested later.
 | 
| 52 | 49 | 
             # Keep it in the queue.
 | 
| ... | ... | @@ -72,7 +69,7 @@ class FetchQueue(Queue): | 
| 72 | 69 | 
         if status == JobStatus.FAIL:
 | 
| 73 | 70 | 
             return
 | 
| 74 | 71 | 
 | 
| 75 | 
-        element._update_state()
 | 
|
| 76 | 
-  | 
|
| 72 | 
+        element._fetch_done()
 | 
|
| 73 | 
+        
 | 
|
| 77 | 74 | 
         # Successful fetch, we must be CACHED now
 | 
| 78 | 75 | 
         assert element._get_consistency() == Consistency.CACHED
 | 
| ... | ... | @@ -39,9 +39,6 @@ class PullQueue(Queue): | 
| 39 | 39 | 
             raise SkipJob(self.action_name)
 | 
| 40 | 40 | 
 | 
| 41 | 41 | 
     def status(self, element):
 | 
| 42 | 
-        # state of dependencies may have changed, recalculate element state
 | 
|
| 43 | 
-        element._update_state()
 | 
|
| 44 | 
-  | 
|
| 45 | 42 | 
         if not element._is_required():
 | 
| 46 | 43 | 
             # Artifact is not currently required but it may be requested later.
 | 
| 47 | 44 | 
             # Keep it in the queue.
 | 
| ... | ... | @@ -170,9 +170,9 @@ class Queue(): | 
| 170 | 170 | 
         skip = [job for job in jobs if self.status(job.element) == QueueStatus.SKIP]
 | 
| 171 | 171 | 
         wait = [job for job in jobs if job not in skip]
 | 
| 172 | 172 | 
 | 
| 173 | 
+        self.skipped_elements.extend([job.element for job in skip])
 | 
|
| 173 | 174 | 
         self._wait_queue.extend(wait)
 | 
| 174 | 175 | 
         self._done_queue.extend(skip)
 | 
| 175 | 
-        self.skipped_elements.extend(skip)
 | 
|
| 176 | 176 | 
 | 
| 177 | 177 | 
     # dequeue()
 | 
| 178 | 178 | 
     #
 | 
| ... | ... | @@ -1018,17 +1018,6 @@ class Stream(): | 
| 1018 | 1018 | 
 | 
| 1019 | 1019 | 
         _, status = self._scheduler.run(self.queues)
 | 
| 1020 | 1020 | 
 | 
| 1021 | 
-        # Force update element states after a run, such that the summary
 | 
|
| 1022 | 
-        # is more coherent
 | 
|
| 1023 | 
-        try:
 | 
|
| 1024 | 
-            for element in self.total_elements:
 | 
|
| 1025 | 
-                element._update_state()
 | 
|
| 1026 | 
-        except BstError as e:
 | 
|
| 1027 | 
-            self._message(MessageType.ERROR, "Error resolving final state", detail=str(e))
 | 
|
| 1028 | 
-            set_last_task_error(e.domain, e.reason)
 | 
|
| 1029 | 
-        except Exception as e:   # pylint: disable=broad-except
 | 
|
| 1030 | 
-            self._message(MessageType.BUG, "Unhandled exception while resolving final state", detail=str(e))
 | 
|
| 1031 | 
-  | 
|
| 1032 | 1021 | 
         if status == SchedStatus.ERROR:
 | 
| 1033 | 1022 | 
             raise StreamError()
 | 
| 1034 | 1023 | 
         elif status == SchedStatus.TERMINATED:
 | 
| ... | ... | @@ -197,6 +197,7 @@ class Element(Plugin): | 
| 197 | 197 | 
 | 
| 198 | 198 | 
         self.__runtime_dependencies = []        # Direct runtime dependency Elements
 | 
| 199 | 199 | 
         self.__build_dependencies = []          # Direct build dependency Elements
 | 
| 200 | 
+        self.__reverse_build_dependencies = []    # Direct reverse build dependency Elements
 | 
|
| 200 | 201 | 
         self.__sources = []                     # List of Sources
 | 
| 201 | 202 | 
         self.__weak_cache_key = None            # Our cached weak cache key
 | 
| 202 | 203 | 
         self.__strict_cache_key = None          # Our cached cache key for strict builds
 | 
| ... | ... | @@ -227,6 +228,8 @@ class Element(Plugin): | 
| 227 | 228 | 
         self.__metadata_workspaced = {}               # Boolean of whether it's workspaced
 | 
| 228 | 229 | 
         self.__metadata_workspaced_dependencies = {}  # List of which dependencies are workspaced
 | 
| 229 | 230 | 
 | 
| 231 | 
+        self.__is_workspaced = None
 | 
|
| 232 | 
+  | 
|
| 230 | 233 | 
         # Ensure we have loaded this class's defaults
 | 
| 231 | 234 | 
         self.__init_defaults(plugin_conf)
 | 
| 232 | 235 | 
 | 
| ... | ... | @@ -370,6 +373,13 @@ class Element(Plugin): | 
| 370 | 373 | 
     #############################################################
 | 
| 371 | 374 | 
     #                       Public Methods                      #
 | 
| 372 | 375 | 
     #############################################################
 | 
| 376 | 
+    @property
 | 
|
| 377 | 
+    def is_workspaced(self):
 | 
|
| 378 | 
+        if self.__is_workspaced is None:
 | 
|
| 379 | 
+            self.__is_workspaced = self._get_workspace() is not None
 | 
|
| 380 | 
+  | 
|
| 381 | 
+        return self.__is_workspaced
 | 
|
| 382 | 
+  | 
|
| 373 | 383 | 
     def sources(self):
 | 
| 374 | 384 | 
         """A generator function to enumerate the element sources
 | 
| 375 | 385 | 
 | 
| ... | ... | @@ -439,6 +449,27 @@ class Element(Plugin): | 
| 439 | 449 | 
         if should_yield and (recurse or recursed) and scope != Scope.BUILD:
 | 
| 440 | 450 | 
             yield self
 | 
| 441 | 451 | 
 | 
| 452 | 
+    def reverse_build_dependencies(self, recurse=False):
 | 
|
| 453 | 
+        if not recurse:
 | 
|
| 454 | 
+            yield from self.__reverse_build_dependencies
 | 
|
| 455 | 
+            return
 | 
|
| 456 | 
+  | 
|
| 457 | 
+        # visited = set()
 | 
|
| 458 | 
+  | 
|
| 459 | 
+        def recurse_rdeps(element):
 | 
|
| 460 | 
+            # if element in visited:
 | 
|
| 461 | 
+                # return
 | 
|
| 462 | 
+  | 
|
| 463 | 
+            # visited.add(element)
 | 
|
| 464 | 
+  | 
|
| 465 | 
+            yield element
 | 
|
| 466 | 
+  | 
|
| 467 | 
+            for rdep in element.__reverse_build_dependencies:
 | 
|
| 468 | 
+                yield from recurse_rdeps(rdep)
 | 
|
| 469 | 
+  | 
|
| 470 | 
+        for rdep in self.__reverse_build_dependencies:
 | 
|
| 471 | 
+            yield from recurse_rdeps(rdep)
 | 
|
| 472 | 
+  | 
|
| 442 | 473 | 
     def search(self, scope, name):
 | 
| 443 | 474 | 
         """Search for a dependency by name
 | 
| 444 | 475 | 
 | 
| ... | ... | @@ -930,6 +961,7 @@ class Element(Plugin): | 
| 930 | 961 | 
         for meta_dep in meta.build_dependencies:
 | 
| 931 | 962 | 
             dependency = Element._new_from_meta(meta_dep)
 | 
| 932 | 963 | 
             element.__build_dependencies.append(dependency)
 | 
| 964 | 
+            dependency.__reverse_build_dependencies.append(element)
 | 
|
| 933 | 965 | 
 | 
| 934 | 966 | 
         return element
 | 
| 935 | 967 | 
 | 
| ... | ... | @@ -1279,6 +1311,9 @@ class Element(Plugin): | 
| 1279 | 1311 | 
         for source in self.sources():
 | 
| 1280 | 1312 | 
             source._preflight()
 | 
| 1281 | 1313 | 
 | 
| 1314 | 
+        # Determine initial element state.
 | 
|
| 1315 | 
+        self._update_state()
 | 
|
| 1316 | 
+  | 
|
| 1282 | 1317 | 
     # _schedule_tracking():
 | 
| 1283 | 1318 | 
     #
 | 
| 1284 | 1319 | 
     # Force an element state to be inconsistent. Any sources appear to be
 | 
| ... | ... | @@ -1306,6 +1341,9 @@ class Element(Plugin): | 
| 1306 | 1341 | 
 | 
| 1307 | 1342 | 
         self._update_state()
 | 
| 1308 | 1343 | 
 | 
| 1344 | 
+        for rdep in self.reverse_build_dependencies(recurse=True):
 | 
|
| 1345 | 
+            rdep._update_state()
 | 
|
| 1346 | 
+  | 
|
| 1309 | 1347 | 
     # _track():
 | 
| 1310 | 1348 | 
     #
 | 
| 1311 | 1349 | 
     # Calls track() on the Element sources
 | 
| ... | ... | @@ -1446,6 +1484,7 @@ class Element(Plugin): | 
| 1446 | 1484 | 
     # This unblocks pull/fetch/build.
 | 
| 1447 | 1485 | 
     #
 | 
| 1448 | 1486 | 
     def _set_required(self):
 | 
| 1487 | 
+        # FIXME: this should enqueue stuff in the queue, it should not be here by default
 | 
|
| 1449 | 1488 | 
         if self.__required:
 | 
| 1450 | 1489 | 
             # Already done
 | 
| 1451 | 1490 | 
             return
 | 
| ... | ... | @@ -1456,6 +1495,7 @@ class Element(Plugin): | 
| 1456 | 1495 | 
         for dep in self.dependencies(Scope.RUN, recurse=False):
 | 
| 1457 | 1496 | 
             dep._set_required()
 | 
| 1458 | 1497 | 
 | 
| 1498 | 
+        # FIXME: this should not be done at all here
 | 
|
| 1459 | 1499 | 
         self._update_state()
 | 
| 1460 | 1500 | 
 | 
| 1461 | 1501 | 
     # _is_required():
 | 
| ... | ... | @@ -1499,10 +1539,16 @@ class Element(Plugin): | 
| 1499 | 1539 | 
 | 
| 1500 | 1540 | 
         self.__assemble_scheduled = False
 | 
| 1501 | 1541 | 
         self.__assemble_done = True
 | 
| 1502 | 
-  | 
|
| 1542 | 
+        # FIXME: only if workspaced
 | 
|
| 1503 | 1543 | 
         self._update_state()
 | 
| 1504 | 1544 | 
 | 
| 1505 | 
-        if self._get_workspace() and self._cached_success():
 | 
|
| 1545 | 
+        if self.is_workspaced:
 | 
|
| 1546 | 
+  | 
|
| 1547 | 
+            # Update the state of all reverse dependencies
 | 
|
| 1548 | 
+            for reverse_dependency in self.reverse_build_dependencies(recurse=True):
 | 
|
| 1549 | 
+                reverse_dependency._update_state()
 | 
|
| 1550 | 
+  | 
|
| 1551 | 
+        if self.is_workspaced and self._cached_success():
 | 
|
| 1506 | 1552 | 
             assert utils._is_main_process(), \
 | 
| 1507 | 1553 | 
                 "Attempted to save workspace configuration from child process"
 | 
| 1508 | 1554 | 
             #
 | 
| ... | ... | @@ -2035,6 +2081,9 @@ class Element(Plugin): | 
| 2035 | 2081 | 
                 source._fetch(previous_sources)
 | 
| 2036 | 2082 | 
             previous_sources.append(source)
 | 
| 2037 | 2083 | 
 | 
| 2084 | 
+    def _fetch_done(self):
 | 
|
| 2085 | 
+        self._update_state()
 | 
|
| 2086 | 
+  | 
|
| 2038 | 2087 | 
     #############################################################
 | 
| 2039 | 2088 | 
     #                   Private Local Methods                   #
 | 
| 2040 | 2089 | 
     #############################################################
 | 
| ... | ... | @@ -2,7 +2,7 @@ coverage==4.4 | 
| 2 | 2 | 
 pylint==2.2.2
 | 
| 3 | 3 | 
 pycodestyle==2.4.0
 | 
| 4 | 4 | 
 pytest==4.0.2
 | 
| 5 | 
-pytest-cov==2.6.0
 | 
|
| 5 | 
+pytest-cov==2.6.1
 | 
|
| 6 | 6 | 
 pytest-datafiles==2.0
 | 
| 7 | 7 | 
 pytest-env==0.6.2
 | 
| 8 | 8 | 
 pytest-xdist==1.25.0
 | 
| ... | ... | @@ -281,3 +281,44 @@ def test_argument_element_invalid(datafiles, cli, project, cmd, word_idx, expect | 
| 281 | 281 | 
 ])
 | 
| 282 | 282 | 
 def test_help_commands(cli, cmd, word_idx, expected):
 | 
| 283 | 283 | 
     assert_completion(cli, cmd, word_idx, expected)
 | 
| 284 | 
+  | 
|
| 285 | 
+  | 
|
| 286 | 
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'project'))
 | 
|
| 287 | 
+def test_argument_artifact(cli, tmpdir, datafiles):
 | 
|
| 288 | 
+    project = os.path.join(datafiles.dirname, datafiles.basename)
 | 
|
| 289 | 
+  | 
|
| 290 | 
+    # Build an import element with no dependencies (as there will only be ONE cache key)
 | 
|
| 291 | 
+    result = cli.run(project=project, args=['build', 'import-bin.bst'])  # Has no dependencies
 | 
|
| 292 | 
+    result.assert_success()
 | 
|
| 293 | 
+  | 
|
| 294 | 
+    # Get the key and the artifact ref ($project/$element_name/$key)
 | 
|
| 295 | 
+    key = cli.get_element_key(project, 'import-bin.bst')
 | 
|
| 296 | 
+    artifact = os.path.join('test', 'import-bin', key)
 | 
|
| 297 | 
+  | 
|
| 298 | 
+    # Test autocompletion of the artifact
 | 
|
| 299 | 
+    cmds = [
 | 
|
| 300 | 
+        'bst artifact log ',
 | 
|
| 301 | 
+        'bst artifact log t',
 | 
|
| 302 | 
+        'bst artifact log test/'
 | 
|
| 303 | 
+    ]
 | 
|
| 304 | 
+  | 
|
| 305 | 
+    for i, cmd in enumerate(cmds):
 | 
|
| 306 | 
+        word_idx = 3
 | 
|
| 307 | 
+        result = cli.run(project=project, cwd=project, env={
 | 
|
| 308 | 
+            '_BST_COMPLETION': 'complete',
 | 
|
| 309 | 
+            'COMP_WORDS': cmd,
 | 
|
| 310 | 
+            'COMP_CWORD': str(word_idx)
 | 
|
| 311 | 
+        })
 | 
|
| 312 | 
+        words = []
 | 
|
| 313 | 
+        if result.output:
 | 
|
| 314 | 
+            words = result.output.splitlines()  # This leaves an extra space on each e.g. ['foo.bst ']
 | 
|
| 315 | 
+            words = [word.strip() for word in words]
 | 
|
| 316 | 
+  | 
|
| 317 | 
+            if i == 0:
 | 
|
| 318 | 
+                expected = PROJECT_ELEMENTS + [artifact]  # We should now be able to see the artifact
 | 
|
| 319 | 
+            elif i == 1:
 | 
|
| 320 | 
+                expected = ['target.bst', artifact]
 | 
|
| 321 | 
+            elif i == 2:
 | 
|
| 322 | 
+                expected = [artifact]
 | 
|
| 323 | 
+  | 
|
| 324 | 
+            assert expected == words
 | 
