Skip to content

Commit 4208302

Browse files
authored
Merge pull request #3294 from kelseymh/3290_git_in_exts-list
Add 'git_config' support within 'exts_list'
2 parents f8da620 + 68ca0b9 commit 4208302

File tree

2 files changed

+168
-41
lines changed

2 files changed

+168
-41
lines changed

easybuild/framework/easyblock.py

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,13 @@ def get_checksum_for(self, checksums, filename=None, index=None):
323323
Obtain checksum for given filename.
324324
325325
:param checksums: a list or tuple of checksums (or None)
326-
:param filename: name of the file to obtain checksum for
326+
:param filename: name of the file to obtain checksum for (Deprecated)
327327
:param index: index of file in list
328328
"""
329+
# Filename has never been used; flag it as deprecated
330+
if filename:
331+
self.log.deprecated("Filename argument to get_checksum_for() is deprecated", '5.0')
332+
329333
# if checksums are provided as a dict, lookup by source filename as key
330334
if isinstance(checksums, (list, tuple)):
331335
if index is not None and index < len(checksums) and (index >= 0 or abs(index) <= len(checksums)):
@@ -337,6 +341,59 @@ def get_checksum_for(self, checksums, filename=None, index=None):
337341
else:
338342
raise EasyBuildError("Invalid type for checksums (%s), should be list, tuple or None.", type(checksums))
339343

344+
def fetch_source(self, source, checksum=None, extension=False):
345+
"""
346+
Get a specific source (tarball, iso, url)
347+
Will be tested for existence or can be located
348+
349+
:param source: source to be found (single dictionary in 'sources' list, or filename)
350+
:param checksum: checksum corresponding to source
351+
:param extension: flag if being called from fetch_extension_sources()
352+
"""
353+
filename, download_filename, extract_cmd, source_urls, git_config = None, None, None, None, None
354+
355+
if source is None:
356+
raise EasyBuildError("fetch_source called with empty 'source' argument")
357+
elif isinstance(source, string_type):
358+
filename = source
359+
elif isinstance(source, dict):
360+
# Making a copy to avoid modifying the object with pops
361+
source = source.copy()
362+
filename = source.pop('filename', None)
363+
extract_cmd = source.pop('extract_cmd', None)
364+
download_filename = source.pop('download_filename', None)
365+
source_urls = source.pop('source_urls', None)
366+
git_config = source.pop('git_config', None)
367+
if source:
368+
raise EasyBuildError("Found one or more unexpected keys in 'sources' specification: %s", source)
369+
370+
elif isinstance(source, (list, tuple)) and len(source) == 2:
371+
self.log.deprecated("Using a 2-element list/tuple to specify sources is deprecated, "
372+
"use a dictionary with 'filename', 'extract_cmd' keys instead", '4.0')
373+
filename, extract_cmd = source
374+
else:
375+
raise EasyBuildError("Unexpected source spec, not a string or dict: %s", source)
376+
377+
# check if the sources can be located
378+
force_download = build_option('force_download') in [FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_SOURCES]
379+
path = self.obtain_file(filename, extension=extension, download_filename=download_filename,
380+
force_download=force_download, urls=source_urls, git_config=git_config)
381+
if path is None:
382+
raise EasyBuildError('No file found for source %s', filename)
383+
384+
self.log.debug('File %s found for source %s' % (path, filename))
385+
386+
src = {
387+
'name': filename,
388+
'path': path,
389+
'cmd': extract_cmd,
390+
'checksum': checksum,
391+
# always set a finalpath
392+
'finalpath': self.builddir,
393+
}
394+
395+
return src
396+
340397
def fetch_sources(self, sources=None, checksums=None):
341398
"""
342399
Add a list of source files (can be tarballs, isos, urls).
@@ -350,46 +407,22 @@ def fetch_sources(self, sources=None, checksums=None):
350407
if checksums is None:
351408
checksums = self.cfg['checksums']
352409

410+
# Single source should be re-wrapped as a list, and checksums with it
411+
if isinstance(sources, dict):
412+
sources = [sources]
413+
if isinstance(checksums, string_type):
414+
checksums = [checksums]
415+
416+
# Loop over the list of sources; list of checksums must match >= in size
353417
for index, source in enumerate(sources):
354-
extract_cmd, download_filename, source_urls, git_config = None, None, None, None
355-
356-
if isinstance(source, string_type):
357-
filename = source
358-
359-
elif isinstance(source, dict):
360-
# Making a copy to avoid modifying the object with pops
361-
source = source.copy()
362-
filename = source.pop('filename', None)
363-
extract_cmd = source.pop('extract_cmd', None)
364-
download_filename = source.pop('download_filename', None)
365-
source_urls = source.pop('source_urls', None)
366-
git_config = source.pop('git_config', None)
367-
if source:
368-
raise EasyBuildError("Found one or more unexpected keys in 'sources' specification: %s", source)
369-
370-
elif isinstance(source, (list, tuple)) and len(source) == 2:
371-
self.log.deprecated("Using a 2-element list/tuple to specify sources is deprecated, "
372-
"use a dictionary with 'filename', 'extract_cmd' keys instead", '4.0')
373-
filename, extract_cmd = source
374-
else:
375-
raise EasyBuildError("Unexpected source spec, not a string or dict: %s", source)
418+
if source is None:
419+
raise EasyBuildError("Empty source in sources list at index %d", index)
376420

377-
# check if the sources can be located
378-
force_download = build_option('force_download') in [FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_SOURCES]
379-
path = self.obtain_file(filename, download_filename=download_filename, force_download=force_download,
380-
urls=source_urls, git_config=git_config)
381-
if path:
382-
self.log.debug('File %s found for source %s' % (path, filename))
383-
self.src.append({
384-
'name': filename,
385-
'path': path,
386-
'cmd': extract_cmd,
387-
'checksum': self.get_checksum_for(checksums, filename=filename, index=index),
388-
# always set a finalpath
389-
'finalpath': self.builddir,
390-
})
421+
src_spec = self.fetch_source(source, self.get_checksum_for(checksums=checksums, index=index))
422+
if src_spec:
423+
self.src.append(src_spec)
391424
else:
392-
raise EasyBuildError('No file found for source %s', filename)
425+
raise EasyBuildError("Unable to retrieve source %s", source)
393426

394427
self.log.info("Added sources: %s", self.src)
395428

@@ -436,7 +469,7 @@ def fetch_patches(self, patch_specs=None, extension=False, checksums=None):
436469
patchspec = {
437470
'name': patch_file,
438471
'path': path,
439-
'checksum': self.get_checksum_for(checksums, filename=patch_file, index=index),
472+
'checksum': self.get_checksum_for(checksums, index=index),
440473
}
441474
if suff:
442475
if copy_file:
@@ -514,9 +547,27 @@ def fetch_extension_sources(self, skip_checksums=False):
514547

515548
if ext_options.get('nosource', None):
516549
exts_sources.append(ext_src)
550+
551+
elif ext_options.get('sources', None):
552+
sources = ext_options['sources']
553+
554+
if isinstance(sources, list):
555+
if len(sources) == 1:
556+
source = sources[0]
557+
else:
558+
error_msg = "'sources' spec for %s in exts_list must be single element list"
559+
raise EasyBuildError(error_msg, ext_name, sources)
560+
else:
561+
source = sources
562+
563+
src = self.fetch_source(source, checksums, extension=True)
564+
# Copy 'path' entry to 'src' for use with extensions
565+
ext_src.update({'src': src['path']})
566+
exts_sources.append(ext_src)
517567
else:
518568
source_urls = ext_options.get('source_urls', [])
519569
force_download = build_option('force_download') in [FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_SOURCES]
570+
520571
src_fn = self.obtain_file(fn, extension=True, urls=source_urls, force_download=force_download)
521572

522573
if src_fn:
@@ -530,7 +581,7 @@ def fetch_extension_sources(self, skip_checksums=False):
530581

531582
# verify checksum (if provided)
532583
self.log.debug('Verifying checksums for extension source...')
533-
fn_checksum = self.get_checksum_for(checksums, filename=src_fn, index=0)
584+
fn_checksum = self.get_checksum_for(checksums, index=0)
534585
if verify_checksum(src_fn, fn_checksum):
535586
self.log.info('Checksum for extension source %s verified', fn)
536587
elif build_option('ignore_checksums'):
@@ -556,7 +607,7 @@ def fetch_extension_sources(self, skip_checksums=False):
556607
self.log.debug('Verifying checksums for extension patches...')
557608
for idx, patch in enumerate(ext_patches):
558609
patch = patch['path']
559-
checksum = self.get_checksum_for(checksums[1:], filename=patch, index=idx)
610+
checksum = self.get_checksum_for(checksums[1:], index=idx)
560611
if verify_checksum(patch, checksum):
561612
self.log.info('Checksum for extension patch %s verified', patch)
562613
elif build_option('ignore_checksums'):

test/framework/toy_build.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,82 @@ def test_toy_extension_patches(self):
11961196

11971197
self.test_toy_build(ec_file=test_ec)
11981198

1199+
def test_toy_extension_sources_single_item_list(self):
1200+
"""Test install toy that includes extensions with 'sources' spec (as single-item list)."""
1201+
test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
1202+
toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')
1203+
toy_ec_txt = read_file(toy_ec)
1204+
1205+
test_ec = os.path.join(self.test_prefix, 'test.eb')
1206+
1207+
# test use of single-element list in 'sources' with just the filename
1208+
test_ec_txt = '\n'.join([
1209+
toy_ec_txt,
1210+
'exts_list = [',
1211+
' ("bar", "0.0", {',
1212+
' "sources": ["bar-%(version)s.tar.gz"],',
1213+
' }),',
1214+
']',
1215+
])
1216+
write_file(test_ec, test_ec_txt)
1217+
self.test_toy_build(ec_file=test_ec)
1218+
1219+
def test_toy_extension_sources_str(self):
1220+
"""Test install toy that includes extensions with 'sources' spec (as string value)."""
1221+
test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
1222+
toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')
1223+
toy_ec_txt = read_file(toy_ec)
1224+
1225+
test_ec = os.path.join(self.test_prefix, 'test.eb')
1226+
1227+
# test use of single-element list in 'sources' with just the filename
1228+
test_ec_txt = '\n'.join([
1229+
toy_ec_txt,
1230+
'exts_list = [',
1231+
' ("bar", "0.0", {',
1232+
' "sources": "bar-%(version)s.tar.gz",',
1233+
' }),',
1234+
']',
1235+
])
1236+
write_file(test_ec, test_ec_txt)
1237+
self.test_toy_build(ec_file=test_ec)
1238+
1239+
def test_toy_extension_sources_git_config(self):
1240+
"""Test install toy that includes extensions with 'sources' spec including 'git_config'."""
1241+
test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
1242+
toy_ec = os.path.join(test_ecs, 't', 'toy', 'toy-0.0.eb')
1243+
toy_ec_txt = read_file(toy_ec)
1244+
1245+
# Tar-ball which should be created via 'git_config', and one file
1246+
ext_tgz = 'exts-git.tar.gz'
1247+
ext_tarball = os.path.join(self.test_sourcepath, 't', 'toy', ext_tgz)
1248+
ext_tarfile = 'a_directory/a_file.txt'
1249+
1250+
# Dummy source code required for extensions build_step to pass
1251+
ext_code = 'int main() { return 0; }'
1252+
ext_cfile = 'exts-git.c'
1253+
1254+
test_ec = os.path.join(self.test_prefix, 'test.eb')
1255+
test_ec_txt = '\n'.join([
1256+
toy_ec_txt,
1257+
'prebuildopts = "echo \\\"%s\\\" > %s && ",' % (ext_code, ext_cfile),
1258+
'exts_list = [',
1259+
' ("exts-git", "0.0", {',
1260+
' "buildopts": "&& ls -l %s %s",' % (ext_tarball, ext_tarfile),
1261+
' "sources": {',
1262+
' "filename": "%(name)s.tar.gz",',
1263+
' "git_config": {',
1264+
' "repo_name": "testrepository",',
1265+
' "url": "https://github.com/easybuilders",',
1266+
' "tag": "master",',
1267+
' },',
1268+
' },',
1269+
' }),',
1270+
']',
1271+
])
1272+
write_file(test_ec, test_ec_txt)
1273+
self.test_toy_build(ec_file=test_ec)
1274+
11991275
def test_toy_module_fulltxt(self):
12001276
"""Strict text comparison of generated module file."""
12011277
self.test_toy_tweaked()

0 commit comments

Comments
 (0)