Jonathan Maw pushed to branch 537-mirror-fallback-does-not-work-for-git at BuildStream / buildstream
Commits:
-
42aa3999
by William Salmon at 2018-08-07T13:41:02Z
-
2ceb5dec
by Will Salmon at 2018-08-07T14:46:36Z
-
eee4b674
by Jürg Billeter at 2018-08-07T15:36:35Z
-
ea27e389
by Jürg Billeter at 2018-08-07T15:36:35Z
-
fa5a59f0
by Jürg Billeter at 2018-08-07T16:48:21Z
-
7befbcad
by Jonathan Maw at 2018-08-08T09:33:09Z
-
b0e564e9
by Jonathan Maw at 2018-08-08T09:33:09Z
-
a1fb15b8
by Jonathan Maw at 2018-08-08T09:33:09Z
7 changed files:
- buildstream/_artifactcache/cascache.py
- buildstream/_pipeline.py
- buildstream/plugins/sources/git.py
- buildstream/source.py
- setup.py
- tests/frontend/mirror.py
- tests/sources/git.py
Changes:
| ... | ... | @@ -846,6 +846,9 @@ class _CASRemote(): |
| 846 | 846 |
|
| 847 | 847 |
|
| 848 | 848 |
def _grouper(iterable, n):
|
| 849 |
- # pylint: disable=stop-iteration-return
|
|
| 850 | 849 |
while True:
|
| 851 |
- yield itertools.chain([next(iterable)], itertools.islice(iterable, n - 1))
|
|
| 850 |
+ try:
|
|
| 851 |
+ current = next(iterable)
|
|
| 852 |
+ except StopIteration:
|
|
| 853 |
+ return
|
|
| 854 |
+ yield itertools.chain([current], itertools.islice(iterable, n - 1))
|
| ... | ... | @@ -358,10 +358,24 @@ class Pipeline(): |
| 358 | 358 |
inconsistent.append(element)
|
| 359 | 359 |
|
| 360 | 360 |
if inconsistent:
|
| 361 |
- detail = "Exact versions are missing for the following elements\n" + \
|
|
| 362 |
- "Try tracking these elements first with `bst track`\n\n"
|
|
| 361 |
+ detail = "Exact versions are missing for the following elements:\n\n"
|
|
| 362 |
+ |
|
| 363 |
+ missingTrack = 0
|
|
| 363 | 364 |
for element in inconsistent:
|
| 364 |
- detail += " " + element._get_full_name() + "\n"
|
|
| 365 |
+ detail += " " + element._get_full_name()
|
|
| 366 |
+ for source in element.sources():
|
|
| 367 |
+ if not source._get_consistency() and not source.get_ref():
|
|
| 368 |
+ if hasattr(source, 'tracking') and source.tracking is None:
|
|
| 369 |
+ detail += ": Source {} is missing ref and track. ".format(source._get_full_name()) + \
|
|
| 370 |
+ "Please specify a ref or branch/tag to track."
|
|
| 371 |
+ missingTrack = 1
|
|
| 372 |
+ |
|
| 373 |
+ detail += "\n"
|
|
| 374 |
+ |
|
| 375 |
+ if missingTrack:
|
|
| 376 |
+ detail += "\nThen track these elements with `bst track`\n"
|
|
| 377 |
+ else:
|
|
| 378 |
+ detail += "\nTry tracking these elements first with `bst track`\n"
|
|
| 365 | 379 |
raise PipelineError("Inconsistent pipeline", detail=detail, reason="inconsistent-pipeline")
|
| 366 | 380 |
|
| 367 | 381 |
#############################################################
|
| ... | ... | @@ -91,16 +91,18 @@ GIT_MODULES = '.gitmodules' |
| 91 | 91 |
#
|
| 92 | 92 |
class GitMirror(SourceFetcher):
|
| 93 | 93 |
|
| 94 |
- def __init__(self, source, path, url, ref):
|
|
| 94 |
+ def __init__(self, source, path, url, ref, *, parent=None):
|
|
| 95 | 95 |
|
| 96 | 96 |
super().__init__()
|
| 97 | 97 |
self.source = source
|
| 98 |
- self.path = path
|
|
| 98 |
+ self.parent = parent
|
|
| 99 | 99 |
self.url = url
|
| 100 |
- self.ref = ref
|
|
| 101 | 100 |
self.mirror = os.path.join(source.get_mirror_directory(), utils.url_directory_name(url))
|
| 102 | 101 |
self.mark_download_url(url)
|
| 103 | 102 |
|
| 103 |
+ self._path = path
|
|
| 104 |
+ self._ref = ref
|
|
| 105 |
+ |
|
| 104 | 106 |
# Ensures that the mirror exists
|
| 105 | 107 |
def ensure(self, alias_override=None):
|
| 106 | 108 |
|
| ... | ... | @@ -223,8 +225,7 @@ class GitMirror(SourceFetcher): |
| 223 | 225 |
fail="Failed to checkout git ref {}".format(self.ref),
|
| 224 | 226 |
cwd=fullpath)
|
| 225 | 227 |
|
| 226 |
- # List the submodules (path/url tuples) present at the given ref of this repo
|
|
| 227 |
- def submodule_list(self):
|
|
| 228 |
+ def _read_gitmodules(self):
|
|
| 228 | 229 |
modules = "{}:{}".format(self.ref, GIT_MODULES)
|
| 229 | 230 |
exit_code, output = self.source.check_output(
|
| 230 | 231 |
[self.source.host_git, 'show', modules], cwd=self.mirror)
|
| ... | ... | @@ -247,10 +248,15 @@ class GitMirror(SourceFetcher): |
| 247 | 248 |
for section in parser.sections():
|
| 248 | 249 |
# validate section name against the 'submodule "foo"' pattern
|
| 249 | 250 |
if re.match(r'submodule "(.*)"', section):
|
| 250 |
- path = parser.get(section, 'path')
|
|
| 251 |
- url = parser.get(section, 'url')
|
|
| 251 |
+ yield (parser, section)
|
|
| 252 | 252 |
|
| 253 |
- yield (path, url)
|
|
| 253 |
+ # List the submodules (path/url tuples) present at the given ref of this repo
|
|
| 254 |
+ def submodule_list(self):
|
|
| 255 |
+ for parser, section in self._read_gitmodules():
|
|
| 256 |
+ path = parser.get(section, 'path')
|
|
| 257 |
+ url = parser.get(section, 'url')
|
|
| 258 |
+ |
|
| 259 |
+ yield (path, url)
|
|
| 254 | 260 |
|
| 255 | 261 |
# Fetch the ref which this mirror requires its submodule to have,
|
| 256 | 262 |
# at the given ref of this mirror.
|
| ... | ... | @@ -287,6 +293,33 @@ class GitMirror(SourceFetcher): |
| 287 | 293 |
|
| 288 | 294 |
return None
|
| 289 | 295 |
|
| 296 |
+ def get_submodule_path(self, url):
|
|
| 297 |
+ for parser, section in self._read_gitmodules():
|
|
| 298 |
+ parsed_url = parser.get(section, 'url')
|
|
| 299 |
+ if parsed_url == url:
|
|
| 300 |
+ return parser.get(section, 'path')
|
|
| 301 |
+ |
|
| 302 |
+ raise SourceError("{}: No submodule found with url '{}'".format(self.source, url))
|
|
| 303 |
+ |
|
| 304 |
+ @property
|
|
| 305 |
+ def path(self):
|
|
| 306 |
+ if self._path is None:
|
|
| 307 |
+ self._path = self.parent.get_submodule_path()
|
|
| 308 |
+ |
|
| 309 |
+ return self._path
|
|
| 310 |
+ |
|
| 311 |
+ @property
|
|
| 312 |
+ def ref(self):
|
|
| 313 |
+ # The top-level GitMirror may have ref as None, submodules don't.
|
|
| 314 |
+ if self._ref is None and self.parent:
|
|
| 315 |
+ self._ref = self.parent.submodule_ref(self.path)
|
|
| 316 |
+ |
|
| 317 |
+ return self._ref
|
|
| 318 |
+ |
|
| 319 |
+ @ref.setter
|
|
| 320 |
+ def ref(self, ref):
|
|
| 321 |
+ self._ref = ref
|
|
| 322 |
+ |
|
| 290 | 323 |
|
| 291 | 324 |
class GitSource(Source):
|
| 292 | 325 |
# pylint: disable=attribute-defined-outside-init
|
| ... | ... | @@ -303,6 +336,8 @@ class GitSource(Source): |
| 303 | 336 |
self.checkout_submodules = self.node_get_member(node, bool, 'checkout-submodules', True)
|
| 304 | 337 |
self.submodules = []
|
| 305 | 338 |
|
| 339 |
+ self.using_source_fetchers = (self.original_url != self.translate_url(self.original_url))
|
|
| 340 |
+ |
|
| 306 | 341 |
# Parse a dict of submodule overrides, stored in the submodule_overrides
|
| 307 | 342 |
# and submodule_checkout_overrides dictionaries.
|
| 308 | 343 |
self.submodule_overrides = {}
|
| ... | ... | @@ -311,6 +346,11 @@ class GitSource(Source): |
| 311 | 346 |
for path, _ in self.node_items(modules):
|
| 312 | 347 |
submodule = self.node_get_member(modules, Mapping, path)
|
| 313 | 348 |
url = self.node_get_member(submodule, str, 'url', None)
|
| 349 |
+ |
|
| 350 |
+ if self.using_source_fetchers:
|
|
| 351 |
+ submodule_mirror = GitMirror(self, None, url, None, parent=self.mirror)
|
|
| 352 |
+ self.submodules.append(submodule_mirror)
|
|
| 353 |
+ |
|
| 314 | 354 |
self.submodule_overrides[path] = url
|
| 315 | 355 |
if 'checkout' in submodule:
|
| 316 | 356 |
checkout = self.node_get_member(submodule, bool, 'checkout')
|
| ... | ... | @@ -363,6 +403,12 @@ class GitSource(Source): |
| 363 | 403 |
|
| 364 | 404 |
# If self.tracking is not specified it's not an error, just silently return
|
| 365 | 405 |
if not self.tracking:
|
| 406 |
+ # Is there a better way to check if a ref is given.
|
|
| 407 |
+ if self.mirror.ref is None:
|
|
| 408 |
+ detail = 'Without a tracking branch ref can not be updated. Please ' + \
|
|
| 409 |
+ 'provide a ref or a track.'
|
|
| 410 |
+ raise SourceError("{}: No track or ref".format(self),
|
|
| 411 |
+ detail=detail, reason="track-attempt-no-track")
|
|
| 366 | 412 |
return None
|
| 367 | 413 |
|
| 368 | 414 |
with self.timed_activity("Tracking {} from {}"
|
| ... | ... | @@ -376,6 +422,24 @@ class GitSource(Source): |
| 376 | 422 |
|
| 377 | 423 |
return ret
|
| 378 | 424 |
|
| 425 |
+ def fetch(self):
|
|
| 426 |
+ |
|
| 427 |
+ with self.timed_activity("Fetching {}".format(self.mirror.url), silent_nested=True):
|
|
| 428 |
+ |
|
| 429 |
+ # Here we are only interested in ensuring that our mirror contains
|
|
| 430 |
+ # the self.mirror.ref commit.
|
|
| 431 |
+ self.mirror.ensure()
|
|
| 432 |
+ if not self.mirror.has_ref():
|
|
| 433 |
+ self.mirror.fetch()
|
|
| 434 |
+ |
|
| 435 |
+ self.mirror.assert_ref()
|
|
| 436 |
+ |
|
| 437 |
+ # Here after performing any fetches, we need to also ensure that
|
|
| 438 |
+ # we've cached the desired refs in our mirrors of submodules.
|
|
| 439 |
+ #
|
|
| 440 |
+ self.refresh_submodules()
|
|
| 441 |
+ self.fetch_submodules()
|
|
| 442 |
+ |
|
| 379 | 443 |
def init_workspace(self, directory):
|
| 380 | 444 |
# XXX: may wish to refactor this as some code dupe with stage()
|
| 381 | 445 |
self.refresh_submodules()
|
| ... | ... | @@ -408,8 +472,11 @@ class GitSource(Source): |
| 408 | 472 |
mirror.stage(directory)
|
| 409 | 473 |
|
| 410 | 474 |
def get_source_fetchers(self):
|
| 411 |
- self.refresh_submodules()
|
|
| 412 |
- return [self.mirror] + self.submodules
|
|
| 475 |
+ # If the url does not contain an alias, then it does not need SourceFetchers
|
|
| 476 |
+ if self.mirror.url == self.translate_url(self.mirror.url):
|
|
| 477 |
+ return []
|
|
| 478 |
+ else:
|
|
| 479 |
+ return [self.mirror] + self.submodules
|
|
| 413 | 480 |
|
| 414 | 481 |
###########################################################
|
| 415 | 482 |
# Local Functions #
|
| ... | ... | @@ -432,6 +499,11 @@ class GitSource(Source): |
| 432 | 499 |
# Assumes that we have our mirror and we have the ref which we point to
|
| 433 | 500 |
#
|
| 434 | 501 |
def refresh_submodules(self):
|
| 502 |
+ |
|
| 503 |
+ # When using source fetchers, the submodule list is defined by the 'submodules' config field
|
|
| 504 |
+ if self.using_source_fetchers:
|
|
| 505 |
+ return
|
|
| 506 |
+ |
|
| 435 | 507 |
self.mirror.ensure()
|
| 436 | 508 |
submodules = []
|
| 437 | 509 |
|
| ... | ... | @@ -454,6 +526,19 @@ class GitSource(Source): |
| 454 | 526 |
|
| 455 | 527 |
self.submodules = submodules
|
| 456 | 528 |
|
| 529 |
+ # Ensures that we have mirrored git repositories for all
|
|
| 530 |
+ # the submodules existing at the given commit of the main git source.
|
|
| 531 |
+ #
|
|
| 532 |
+ # Also ensure that these mirrors have the required commits
|
|
| 533 |
+ # referred to at the given commit of the main git source.
|
|
| 534 |
+ #
|
|
| 535 |
+ def fetch_submodules(self):
|
|
| 536 |
+ for mirror in self.submodules:
|
|
| 537 |
+ mirror.ensure()
|
|
| 538 |
+ if not mirror.has_ref():
|
|
| 539 |
+ mirror.fetch()
|
|
| 540 |
+ mirror.assert_ref()
|
|
| 541 |
+ |
|
| 457 | 542 |
|
| 458 | 543 |
# Plugin entry point
|
| 459 | 544 |
def setup():
|
| ... | ... | @@ -393,8 +393,8 @@ class Source(Plugin): |
| 393 | 393 |
"""Get the objects that are used for fetching
|
| 394 | 394 |
|
| 395 | 395 |
If this source doesn't download from multiple URLs,
|
| 396 |
- returning None and falling back on the default behaviour
|
|
| 397 |
- is recommended.
|
|
| 396 |
+ returning an empty list and falling back on the default
|
|
| 397 |
+ behaviour is recommended.
|
|
| 398 | 398 |
|
| 399 | 399 |
Returns:
|
| 400 | 400 |
list: A list of SourceFetchers. If SourceFetchers are not supported,
|
| ... | ... | @@ -272,6 +272,5 @@ setup(name='BuildStream', |
| 272 | 272 |
'pytest-cov >= 2.5.0',
|
| 273 | 273 |
# Provide option to run tests in parallel, less reliable
|
| 274 | 274 |
'pytest-xdist',
|
| 275 |
- 'pytest >= 3.1.0',
|
|
| 276 |
- 'pylint >= 1.8 , < 2'],
|
|
| 275 |
+ 'pytest >= 3.1.0'],
|
|
| 277 | 276 |
zip_safe=False)
|
| ... | ... | @@ -139,6 +139,63 @@ def test_mirror_fetch(cli, tmpdir, datafiles, kind): |
| 139 | 139 |
result.assert_success()
|
| 140 | 140 |
|
| 141 | 141 |
|
| 142 |
+@pytest.mark.datafiles(DATA_DIR)
|
|
| 143 |
+@pytest.mark.parametrize("kind", [(kind) for kind in ALL_REPO_KINDS])
|
|
| 144 |
+def test_mirror_fetch_upstream_absent(cli, tmpdir, datafiles, kind):
|
|
| 145 |
+ bin_files_path = os.path.join(str(datafiles), 'files', 'bin-files', 'usr')
|
|
| 146 |
+ dev_files_path = os.path.join(str(datafiles), 'files', 'dev-files', 'usr')
|
|
| 147 |
+ upstream_repodir = os.path.join(str(tmpdir), 'upstream')
|
|
| 148 |
+ mirror_repodir = os.path.join(str(tmpdir), 'mirror')
|
|
| 149 |
+ project_dir = os.path.join(str(tmpdir), 'project')
|
|
| 150 |
+ os.makedirs(project_dir)
|
|
| 151 |
+ element_dir = os.path.join(project_dir, 'elements')
|
|
| 152 |
+ |
|
| 153 |
+ # Create repo objects of the upstream and mirror
|
|
| 154 |
+ upstream_repo = create_repo(kind, upstream_repodir)
|
|
| 155 |
+ ref = upstream_repo.create(dev_files_path)
|
|
| 156 |
+ mirror_repo = upstream_repo.copy(mirror_repodir)
|
|
| 157 |
+ |
|
| 158 |
+ element = {
|
|
| 159 |
+ 'kind': 'import',
|
|
| 160 |
+ 'sources': [
|
|
| 161 |
+ upstream_repo.source_config(ref=ref)
|
|
| 162 |
+ ]
|
|
| 163 |
+ }
|
|
| 164 |
+ |
|
| 165 |
+ element_name = 'test.bst'
|
|
| 166 |
+ element_path = os.path.join(element_dir, element_name)
|
|
| 167 |
+ full_repo = element['sources'][0]['url']
|
|
| 168 |
+ upstream_map, repo_name = os.path.split(full_repo)
|
|
| 169 |
+ alias = 'foo-' + kind
|
|
| 170 |
+ aliased_repo = alias + ':' + repo_name
|
|
| 171 |
+ element['sources'][0]['url'] = aliased_repo
|
|
| 172 |
+ full_mirror = mirror_repo.source_config()['url']
|
|
| 173 |
+ mirror_map, _ = os.path.split(full_mirror)
|
|
| 174 |
+ os.makedirs(element_dir)
|
|
| 175 |
+ _yaml.dump(element, element_path)
|
|
| 176 |
+ |
|
| 177 |
+ project = {
|
|
| 178 |
+ 'name': 'test',
|
|
| 179 |
+ 'element-path': 'elements',
|
|
| 180 |
+ 'aliases': {
|
|
| 181 |
+ alias: 'http://www.example.com/'
|
|
| 182 |
+ },
|
|
| 183 |
+ 'mirrors': [
|
|
| 184 |
+ {
|
|
| 185 |
+ 'name': 'middle-earth',
|
|
| 186 |
+ 'aliases': {
|
|
| 187 |
+ alias: [mirror_map + "/"],
|
|
| 188 |
+ },
|
|
| 189 |
+ },
|
|
| 190 |
+ ]
|
|
| 191 |
+ }
|
|
| 192 |
+ project_file = os.path.join(project_dir, 'project.conf')
|
|
| 193 |
+ _yaml.dump(project, project_file)
|
|
| 194 |
+ |
|
| 195 |
+ result = cli.run(project=project_dir, args=['fetch', element_name])
|
|
| 196 |
+ result.assert_success()
|
|
| 197 |
+ |
|
| 198 |
+ |
|
| 142 | 199 |
@pytest.mark.datafiles(DATA_DIR)
|
| 143 | 200 |
def test_mirror_fetch_multi(cli, tmpdir, datafiles):
|
| 144 | 201 |
output_file = os.path.join(str(tmpdir), "output.txt")
|
| ... | ... | @@ -359,3 +359,45 @@ def test_submodule_track_ignore_inconsistent(cli, tmpdir, datafiles): |
| 359 | 359 |
|
| 360 | 360 |
# Assert that we are just fine without it, and emit a warning to the user.
|
| 361 | 361 |
assert "Ignoring inconsistent submodule" in result.stderr
|
| 362 |
+ |
|
| 363 |
+ |
|
| 364 |
+@pytest.mark.skipif(HAVE_GIT is False, reason="git is not available")
|
|
| 365 |
+@pytest.mark.datafiles(os.path.join(DATA_DIR, 'template'))
|
|
| 366 |
+def test_submodule_track_no_ref_or_track(cli, tmpdir, datafiles):
|
|
| 367 |
+ project = os.path.join(datafiles.dirname, datafiles.basename)
|
|
| 368 |
+ |
|
| 369 |
+ # Create the repo from 'repofiles' subdir
|
|
| 370 |
+ repo = create_repo('git', str(tmpdir))
|
|
| 371 |
+ ref = repo.create(os.path.join(project, 'repofiles'))
|
|
| 372 |
+ |
|
| 373 |
+ # Write out our test target
|
|
| 374 |
+ gitsource = repo.source_config(ref=None)
|
|
| 375 |
+ gitsource.pop('track')
|
|
| 376 |
+ element = {
|
|
| 377 |
+ 'kind': 'import',
|
|
| 378 |
+ 'sources': [
|
|
| 379 |
+ gitsource
|
|
| 380 |
+ ]
|
|
| 381 |
+ }
|
|
| 382 |
+ |
|
| 383 |
+ _yaml.dump(element, os.path.join(project, 'target.bst'))
|
|
| 384 |
+ |
|
| 385 |
+ # Track will encounter an inconsistent submodule without any ref
|
|
| 386 |
+ result = cli.run(project=project, args=['track', 'target.bst'])
|
|
| 387 |
+ result.assert_main_error(ErrorDomain.STREAM, None)
|
|
| 388 |
+ result.assert_task_error(ErrorDomain.SOURCE, 'track-attempt-no-track')
|
|
| 389 |
+ |
|
| 390 |
+ # Assert that we are just fine without it, and emit a warning to the user.
|
|
| 391 |
+ assert "FAILURE git source at" in result.stderr
|
|
| 392 |
+ assert "Without a tracking branch ref can not be updated. Please " + \
|
|
| 393 |
+ "provide a ref or a track." in result.stderr
|
|
| 394 |
+ |
|
| 395 |
+ # Track will encounter an inconsistent submodule without any ref
|
|
| 396 |
+ result = cli.run(project=project, args=['build', 'target.bst'])
|
|
| 397 |
+ result.assert_main_error(ErrorDomain.PIPELINE, 'inconsistent-pipeline')
|
|
| 398 |
+ result.assert_task_error(None, None)
|
|
| 399 |
+ |
|
| 400 |
+ # Assert that we are just fine without it, and emit a warning to the user.
|
|
| 401 |
+ assert "Exact versions are missing for the following elements" in result.stderr
|
|
| 402 |
+ assert "is missing ref and track." in result.stderr
|
|
| 403 |
+ assert "Then track these elements with `bst track`" in result.stderr
|
