Daniel Silverstone pushed to branch danielsilverstone-ct/roaring-bitmaps at BuildStream / buildstream
Commits:
-
cd8e5e27
by Tom Pollard at 2019-02-05T11:22:01Z
-
a46650d0
by Tom Pollard at 2019-02-05T12:41:30Z
-
9c94e8e5
by Daniel Silverstone at 2019-02-05T13:15:24Z
-
7682ef49
by Daniel Silverstone at 2019-02-05T13:15:24Z
9 changed files:
- NEWS
- buildstream/_frontend/cli.py
- buildstream/_loader/loadelement.py
- buildstream/_stream.py
- requirements/requirements.in
- requirements/requirements.txt
- tests/frontend/completions.py
- tests/frontend/pull.py
- tests/frontend/push.py
Changes:
... | ... | @@ -122,6 +122,10 @@ buildstream 1.3.1 |
122 | 122 |
'shell', 'show', 'source-checkout', 'track', 'workspace close' and 'workspace reset'
|
123 | 123 |
commands are affected.
|
124 | 124 |
|
125 |
+ o bst 'build' now has '--remote, -r' option, inline with bst 'push' & 'pull'.
|
|
126 |
+ Providing a remote will limit build's pull/push remote actions to the given
|
|
127 |
+ remote specifically, ignoring those defined via user or project configuration.
|
|
128 |
+ |
|
125 | 129 |
|
126 | 130 |
=================
|
127 | 131 |
buildstream 1.1.5
|
... | ... | @@ -338,10 +338,12 @@ def init(app, project_name, format_version, element_path, force): |
338 | 338 |
help="Allow tracking to cross junction boundaries")
|
339 | 339 |
@click.option('--track-save', default=False, is_flag=True,
|
340 | 340 |
help="Deprecated: This is ignored")
|
341 |
+@click.option('--remote', '-r', default=None,
|
|
342 |
+ help="The URL of the remote cache (defaults to the first configured cache)")
|
|
341 | 343 |
@click.argument('elements', nargs=-1,
|
342 | 344 |
type=click.Path(readable=False))
|
343 | 345 |
@click.pass_obj
|
344 |
-def build(app, elements, all_, track_, track_save, track_all, track_except, track_cross_junctions):
|
|
346 |
+def build(app, elements, all_, track_, track_save, track_all, track_except, track_cross_junctions, remote):
|
|
345 | 347 |
"""Build elements in a pipeline
|
346 | 348 |
|
347 | 349 |
Specifying no elements will result in building the default targets
|
... | ... | @@ -376,7 +378,8 @@ def build(app, elements, all_, track_, track_save, track_all, track_except, trac |
376 | 378 |
track_except=track_except,
|
377 | 379 |
track_cross_junctions=track_cross_junctions,
|
378 | 380 |
ignore_junction_targets=ignore_junction_targets,
|
379 |
- build_all=all_)
|
|
381 |
+ build_all=all_,
|
|
382 |
+ remote=remote)
|
|
380 | 383 |
|
381 | 384 |
|
382 | 385 |
##################################################################
|
... | ... | @@ -1012,7 +1015,7 @@ def artifact_checkout(app, force, deps, integrate, hardlinks, tar, directory, el |
1012 | 1015 |
@click.option('--deps', '-d', default='none',
|
1013 | 1016 |
type=click.Choice(['none', 'all']),
|
1014 | 1017 |
help='The dependency artifacts to pull (default: none)')
|
1015 |
-@click.option('--remote', '-r',
|
|
1018 |
+@click.option('--remote', '-r', default=None,
|
|
1016 | 1019 |
help="The URL of the remote cache (defaults to the first configured cache)")
|
1017 | 1020 |
@click.argument('elements', nargs=-1,
|
1018 | 1021 |
type=click.Path(readable=False))
|
... | ... | @@ -19,6 +19,9 @@ |
19 | 19 |
|
20 | 20 |
# System imports
|
21 | 21 |
from collections.abc import Mapping
|
22 |
+from itertools import count
|
|
23 |
+ |
|
24 |
+from roaringbitmap import RoaringBitmap, ImmutableRoaringBitmap # pylint: disable=no-name-in-module
|
|
22 | 25 |
|
23 | 26 |
# BuildStream toplevel imports
|
24 | 27 |
from .._exceptions import LoadError, LoadErrorReason
|
... | ... | @@ -54,6 +57,8 @@ class LoadElement(): |
54 | 57 |
self.element = element
|
55 | 58 |
self.dep_type = dep_type
|
56 | 59 |
|
60 |
+ _counter = count()
|
|
61 |
+ |
|
57 | 62 |
def __init__(self, node, filename, loader):
|
58 | 63 |
|
59 | 64 |
#
|
... | ... | @@ -63,6 +68,7 @@ class LoadElement(): |
63 | 68 |
self.name = filename # The element name
|
64 | 69 |
self.full_name = None # The element full name (with associated junction)
|
65 | 70 |
self.deps = None # The list of Dependency objects
|
71 |
+ self.node_id = next(self._counter)
|
|
66 | 72 |
|
67 | 73 |
#
|
68 | 74 |
# Private members
|
... | ... | @@ -107,7 +113,7 @@ class LoadElement(): |
107 | 113 |
#
|
108 | 114 |
def depends(self, other):
|
109 | 115 |
self._ensure_depends_cache()
|
110 |
- return self._dep_cache.get(other.full_name) is not None
|
|
116 |
+ return other.node_id in self._dep_cache
|
|
111 | 117 |
|
112 | 118 |
###########################################
|
113 | 119 |
# Private Methods #
|
... | ... | @@ -117,7 +123,8 @@ class LoadElement(): |
117 | 123 |
if self._dep_cache:
|
118 | 124 |
return
|
119 | 125 |
|
120 |
- self._dep_cache = {}
|
|
126 |
+ self._dep_cache = RoaringBitmap()
|
|
127 |
+ |
|
121 | 128 |
for dep in self.dependencies:
|
122 | 129 |
elt = dep.element
|
123 | 130 |
|
... | ... | @@ -125,11 +132,13 @@ class LoadElement(): |
125 | 132 |
elt._ensure_depends_cache()
|
126 | 133 |
|
127 | 134 |
# We depend on this element
|
128 |
- self._dep_cache[elt.full_name] = True
|
|
135 |
+ self._dep_cache.add(elt.node_id)
|
|
129 | 136 |
|
130 | 137 |
# And we depend on everything this element depends on
|
131 | 138 |
self._dep_cache.update(elt._dep_cache)
|
132 | 139 |
|
140 |
+ self._dep_cache = ImmutableRoaringBitmap(self._dep_cache)
|
|
141 |
+ |
|
133 | 142 |
|
134 | 143 |
# _extract_depends_from_node():
|
135 | 144 |
#
|
... | ... | @@ -197,26 +197,36 @@ class Stream(): |
197 | 197 |
# ignore_junction_targets (bool): Whether junction targets should be filtered out
|
198 | 198 |
# build_all (bool): Whether to build all elements, or only those
|
199 | 199 |
# which are required to build the target.
|
200 |
+ # remote (str): The URL of a specific remote server to push to, or None
|
|
201 |
+ #
|
|
202 |
+ # If `remote` specified as None, then regular configuration will be used
|
|
203 |
+ # to determine where to push artifacts to.
|
|
200 | 204 |
#
|
201 | 205 |
def build(self, targets, *,
|
202 | 206 |
track_targets=None,
|
203 | 207 |
track_except=None,
|
204 | 208 |
track_cross_junctions=False,
|
205 | 209 |
ignore_junction_targets=False,
|
206 |
- build_all=False):
|
|
210 |
+ build_all=False,
|
|
211 |
+ remote=None):
|
|
207 | 212 |
|
208 | 213 |
if build_all:
|
209 | 214 |
selection = PipelineSelection.ALL
|
210 | 215 |
else:
|
211 | 216 |
selection = PipelineSelection.PLAN
|
212 | 217 |
|
218 |
+ use_config = True
|
|
219 |
+ if remote:
|
|
220 |
+ use_config = False
|
|
221 |
+ |
|
213 | 222 |
elements, track_elements = \
|
214 | 223 |
self._load(targets, track_targets,
|
215 | 224 |
selection=selection, track_selection=PipelineSelection.ALL,
|
216 | 225 |
track_except_targets=track_except,
|
217 | 226 |
track_cross_junctions=track_cross_junctions,
|
218 | 227 |
ignore_junction_targets=ignore_junction_targets,
|
219 |
- use_artifact_config=True,
|
|
228 |
+ use_artifact_config=use_config,
|
|
229 |
+ artifact_remote_url=remote,
|
|
220 | 230 |
fetch_subprojects=True,
|
221 | 231 |
dynamic_plan=True)
|
222 | 232 |
|
... | ... | @@ -13,3 +13,6 @@ psutil |
13 | 13 |
# See issues #571 and #790.
|
14 | 14 |
ruamel.yaml >= 0.15.41, < 0.15.52
|
15 | 15 |
setuptools
|
16 |
+# (Potentially) short-term need for roaring bitmaps for the
|
|
17 |
+# loader dependency sorting
|
|
18 |
+roaringbitmap
|
... | ... | @@ -13,6 +13,9 @@ psutil==5.4.8 |
13 | 13 |
# See issues #571 and #790.
|
14 | 14 |
ruamel.yaml==0.15.51
|
15 | 15 |
setuptools==39.0.1
|
16 |
+# (Potentially) short-term need for roaring bitmaps for the
|
|
17 |
+# loader dependency sorting
|
|
18 |
+roaringbitmap==0.6
|
|
16 | 19 |
## The following requirements were added by pip freeze:
|
17 | 20 |
MarkupSafe==1.1.0
|
18 | 21 |
six==1.12.0
|
... | ... | @@ -141,7 +141,8 @@ def test_commands(cli, cmd, word_idx, expected): |
141 | 141 |
('bst --no-colors build -', 3, ['--all ', '--track ', '--track-all ',
|
142 | 142 |
'--track-except ',
|
143 | 143 |
'--track-cross-junctions ', '-J ',
|
144 |
- '--track-save ']),
|
|
144 |
+ '--track-save ',
|
|
145 |
+ '--remote ', '-r ']),
|
|
145 | 146 |
|
146 | 147 |
# Test the behavior of completing after an option that has a
|
147 | 148 |
# parameter that cannot be completed, vs an option that has
|
... | ... | @@ -408,3 +408,56 @@ def test_pull_missing_notifies_user(caplog, cli, tmpdir, datafiles): |
408 | 408 |
|
409 | 409 |
assert "INFO Remote ({}) does not have".format(share.repo) in result.stderr
|
410 | 410 |
assert "SKIPPED Pull" in result.stderr
|
411 |
+ |
|
412 |
+ |
|
413 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
414 |
+def test_build_remote_option(caplog, cli, tmpdir, datafiles):
|
|
415 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
416 |
+ caplog.set_level(1)
|
|
417 |
+ |
|
418 |
+ with create_artifact_share(os.path.join(str(tmpdir), 'artifactshare1')) as shareuser,\
|
|
419 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare2')) as shareproject,\
|
|
420 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare3')) as sharecli:
|
|
421 |
+ |
|
422 |
+ # Add shareproject repo url to project.conf
|
|
423 |
+ with open(os.path.join(project, "project.conf"), "a") as projconf:
|
|
424 |
+ projconf.write("artifacts:\n url: {}\n push: True".format(shareproject.repo))
|
|
425 |
+ |
|
426 |
+ # Configure shareuser remote in user conf
|
|
427 |
+ cli.configure({
|
|
428 |
+ 'artifacts': {'url': shareuser.repo, 'push': True}
|
|
429 |
+ })
|
|
430 |
+ |
|
431 |
+ # Push the artifacts to the shareuser and shareproject remotes.
|
|
432 |
+ # Assert that shareuser and shareproject have the artfifacts cached,
|
|
433 |
+ # but sharecli doesn't, then delete locally cached elements
|
|
434 |
+ result = cli.run(project=project, args=['build', 'target.bst'])
|
|
435 |
+ result.assert_success()
|
|
436 |
+ all_elements = ['target.bst', 'import-bin.bst', 'compose-all.bst']
|
|
437 |
+ for element_name in all_elements:
|
|
438 |
+ assert element_name in result.get_pushed_elements()
|
|
439 |
+ assert_not_shared(cli, sharecli, project, element_name)
|
|
440 |
+ assert_shared(cli, shareuser, project, element_name)
|
|
441 |
+ assert_shared(cli, shareproject, project, element_name)
|
|
442 |
+ cli.remove_artifact_from_cache(project, element_name)
|
|
443 |
+ |
|
444 |
+ # Now check that a build with cli set as sharecli results in nothing being pulled,
|
|
445 |
+ # as it doesn't have them cached and shareuser/shareproject should be ignored. This
|
|
446 |
+ # will however result in the artifacts being built and pushed to it
|
|
447 |
+ result = cli.run(project=project, args=['build', '--remote', sharecli.repo, 'target.bst'])
|
|
448 |
+ result.assert_success()
|
|
449 |
+ for element_name in all_elements:
|
|
450 |
+ assert element_name not in result.get_pulled_elements()
|
|
451 |
+ assert_shared(cli, sharecli, project, element_name)
|
|
452 |
+ cli.remove_artifact_from_cache(project, element_name)
|
|
453 |
+ |
|
454 |
+ # Now check that a clean build with cli set as sharecli should result in artifacts only
|
|
455 |
+ # being pulled from it, as that was provided via the cli and is populated
|
|
456 |
+ result = cli.run(project=project, args=['build', '--remote', sharecli.repo, 'target.bst'])
|
|
457 |
+ result.assert_success()
|
|
458 |
+ for element_name in all_elements:
|
|
459 |
+ assert cli.get_element_state(project, element_name) == 'cached'
|
|
460 |
+ assert element_name in result.get_pulled_elements()
|
|
461 |
+ assert shareproject.repo not in result.stderr
|
|
462 |
+ assert shareuser.repo not in result.stderr
|
|
463 |
+ assert sharecli.repo in result.stderr
|
... | ... | @@ -416,3 +416,33 @@ def test_push_already_cached(caplog, cli, tmpdir, datafiles): |
416 | 416 |
assert not result.get_pushed_elements(), "No elements should have been pushed since the cache was populated"
|
417 | 417 |
assert "INFO Remote ({}) already has ".format(share.repo) in result.stderr
|
418 | 418 |
assert "SKIPPED Push" in result.stderr
|
419 |
+ |
|
420 |
+ |
|
421 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
422 |
+def test_build_remote_option(caplog, cli, tmpdir, datafiles):
|
|
423 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
424 |
+ caplog.set_level(1)
|
|
425 |
+ |
|
426 |
+ with create_artifact_share(os.path.join(str(tmpdir), 'artifactshare1')) as shareuser,\
|
|
427 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare2')) as shareproject,\
|
|
428 |
+ create_artifact_share(os.path.join(str(tmpdir), 'artifactshare3')) as sharecli:
|
|
429 |
+ |
|
430 |
+ # Add shareproject repo url to project.conf
|
|
431 |
+ with open(os.path.join(project, "project.conf"), "a") as projconf:
|
|
432 |
+ projconf.write("artifacts:\n url: {}\n push: True".format(shareproject.repo))
|
|
433 |
+ |
|
434 |
+ # Configure shareuser remote in user conf
|
|
435 |
+ cli.configure({
|
|
436 |
+ 'artifacts': {'url': shareuser.repo, 'push': True}
|
|
437 |
+ })
|
|
438 |
+ |
|
439 |
+ result = cli.run(project=project, args=['build', '--remote', sharecli.repo, 'target.bst'])
|
|
440 |
+ |
|
441 |
+ # Artifacts should have only been pushed to sharecli, as that was provided via the cli
|
|
442 |
+ result.assert_success()
|
|
443 |
+ all_elements = ['target.bst', 'import-bin.bst', 'compose-all.bst']
|
|
444 |
+ for element_name in all_elements:
|
|
445 |
+ assert element_name in result.get_pushed_elements()
|
|
446 |
+ assert_shared(cli, sharecli, project, element_name)
|
|
447 |
+ assert_not_shared(cli, shareuser, project, element_name)
|
|
448 |
+ assert_not_shared(cli, shareproject, project, element_name)
|