Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for injecting checksums for cargo crates #4661

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4751,8 +4751,8 @@ def make_checksum_lines(checksums, indent_level):
if app.src:
placeholder = '# PLACEHOLDER FOR SOURCES/PATCHES WITH CHECKSUMS'

# grab raw lines for source_urls, sources, patches
keys = ['patches', 'source_urls', 'sources']
# grab raw lines for the following params
keys = ['source_urls', 'sources', 'crates', 'patches']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this approach is a bit weird, as it breaks the boundary between framework and easyblocks, since crates is not a general easyconfig parameter...

Thoughts on this @Micket?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we come up with an approach that would also work for components (in Bundle easyblock)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in the confcall I added a method src_parameter_names to the easyblock class.

In the Cargo easyblock we can override this to also return "crates".

For components I don't think this is useful. For now they look like:

components = [
    (name, version, {
        'source_urls': ['https://poppler.freedesktop.org/'],
        'sources': [SOURCE_TAR_XZ],
        'checksums': ['86b09e5a02de40081a3916ef8711c5128eaf4b1fc59d5f87d0ec66f04f595db4'],
        'configopts': "-DENABLE_BOOST=ON",
    }),
    ('poppler-data', '0.4.10', {
        'source_urls': ['https://poppler.freedesktop.org/'],
        'sources': [SOURCE_TAR_GZ],
        'checksums': ['6e2fcef66ec8c44625f94292ccf8af9f1d918b410d5aa69c274ce67387967b30'],
    }),
]

While the Bundle easyblock seemingly supports specifying checksums for everything at the top-level (outside the components) I don't think it actually does: It creates a full list of sources, patches and checksums during __init__ but the actual checksum check (at least the contrib check) requires them in each component as above.

However with this design it would be possible to add the checksum list after the components but then we'd need to include logic to remove them from each component

The general approach is different here anyway: crates are just another way to specify sources while components are similar to exts_list which yield actual easyblock instances.

raw = {}
for key in keys:
regex = re.compile(r'^(%s(?:.|\n)*?\])\s*$' % key, re.M)
Expand All @@ -4763,13 +4763,11 @@ def make_checksum_lines(checksums, indent_level):

_log.debug("Raw lines for %s easyconfig parameters: %s", '/'.join(keys), raw)

# inject combination of source_urls/sources/patches/checksums into easyconfig
# by replacing first occurence of placeholder that was put in place
sources_raw = raw.get('sources', '')
source_urls_raw = raw.get('source_urls', '')
patches_raw = raw.get('patches', '')
# inject combination of the grabbed lines and the checksums into the easyconfig
# by replacing first the occurence of the placeholder that was put in place
raw_text = ''.join(raw.get(key, '') for key in keys)
regex = re.compile(placeholder + '\n', re.M)
ectxt = regex.sub(source_urls_raw + sources_raw + patches_raw + checksums_txt + '\n', ectxt, count=1)
ectxt = regex.sub(raw_text + checksums_txt + '\n', ectxt, count=1)

# get rid of potential remaining placeholders
ectxt = regex.sub('', ectxt)
Expand Down
10 changes: 9 additions & 1 deletion test/framework/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

LIST_EASYBLOCKS_SIMPLE_TXT = """EasyBlock
|-- bar
|-- Cargo
|-- ConfigureMake
| |-- MakeCp
|-- EB_EasyBuildMeta
Expand Down Expand Up @@ -76,6 +77,7 @@

LIST_EASYBLOCKS_DETAILED_TXT = """EasyBlock (easybuild.framework.easyblock)
|-- bar (easybuild.easyblocks.generic.bar @ %(topdir)s/generic/bar.py)
|-- Cargo (easybuild.easyblocks.generic.cargo @ %(topdir)s/generic/cargo.py)
|-- ConfigureMake (easybuild.easyblocks.generic.configuremake @ %(topdir)s/generic/configuremake.py)
| |-- MakeCp (easybuild.easyblocks.generic.makecp @ %(topdir)s/generic/makecp.py)
|-- EB_EasyBuildMeta (easybuild.easyblocks.easybuildmeta @ %(topdir)s/e/easybuildmeta.py)
Expand Down Expand Up @@ -109,6 +111,7 @@
LIST_EASYBLOCKS_SIMPLE_RST = """* **EasyBlock**

* bar
* Cargo
* ConfigureMake

* MakeCp
Expand Down Expand Up @@ -157,6 +160,7 @@
LIST_EASYBLOCKS_DETAILED_RST = """* **EasyBlock** (easybuild.framework.easyblock)

* bar (easybuild.easyblocks.generic.bar @ %(topdir)s/generic/bar.py)
* Cargo (easybuild.easyblocks.generic.cargo @ %(topdir)s/generic/cargo.py)
* ConfigureMake (easybuild.easyblocks.generic.configuremake @ %(topdir)s/generic/configuremake.py)

* MakeCp (easybuild.easyblocks.generic.makecp @ %(topdir)s/generic/makecp.py)
Expand Down Expand Up @@ -204,6 +208,7 @@

LIST_EASYBLOCKS_SIMPLE_MD = """- **EasyBlock**
- bar
- Cargo
- ConfigureMake
- MakeCp
- EB_EasyBuildMeta
Expand Down Expand Up @@ -236,6 +241,7 @@

LIST_EASYBLOCKS_DETAILED_MD = """- **EasyBlock** (easybuild.framework.easyblock)
- bar (easybuild.easyblocks.generic.bar @ %(topdir)s/generic/bar.py)
- Cargo (easybuild.easyblocks.generic.cargo @ %(topdir)s/generic/cargo.py)
- ConfigureMake (easybuild.easyblocks.generic.configuremake @ %(topdir)s/generic/configuremake.py)
- MakeCp (easybuild.easyblocks.generic.makecp @ %(topdir)s/generic/makecp.py)
- EB_EasyBuildMeta (easybuild.easyblocks.easybuildmeta @ %(topdir)s/e/easybuildmeta.py)
Expand Down Expand Up @@ -513,7 +519,7 @@ def test_get_easyblock_classes(self):
# result should correspond with test easyblocks in test/framework/sandbox/easybuild/easyblocks/generic
eb_classes = get_easyblock_classes('easybuild.easyblocks.generic')
eb_names = [x.__name__ for x in eb_classes]
expected = ['ConfigureMake', 'DummyExtension', 'MakeCp', 'ModuleRC',
expected = ['Cargo', 'ConfigureMake', 'DummyExtension', 'MakeCp', 'ModuleRC',
'PythonBundle', 'Toolchain', 'Toy_Extension', 'bar']
self.assertEqual(sorted(eb_names), expected)

Expand Down Expand Up @@ -724,6 +730,7 @@ def test_list_software(self):
'homepage: https://easybuilders.github.io/easybuild',
'',
" * toy v0.0: gompi/2018a, system",
" * toy v0.0 (versionsuffix: '-cargo'): system",
" * toy v0.0 (versionsuffix: '-deps'): system",
" * toy v0.0 (versionsuffix: '-iter'): system",
" * toy v0.0 (versionsuffix: '-multiple'): system",
Expand All @@ -746,6 +753,7 @@ def test_list_software(self):
'version versionsuffix toolchain',
'======= ============= ===========================',
'``0.0`` ``gompi/2018a``, ``system``',
'``0.0`` ``-cargo`` ``system``',
'``0.0`` ``-deps`` ``system``',
'``0.0`` ``-iter`` ``system``',
'``0.0`` ``-multiple`` ``system``',
Expand Down
17 changes: 17 additions & 0 deletions test/framework/easyconfigs/test_ecs/t/toy/toy-0.0-cargo.eb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name = 'toy'
version = '0.0'
versionsuffix = '-cargo'

easyblock = 'Cargo'

homepage = 'https://easybuilders.github.io/easybuild'
description = "Toy C program, 100% toy."

toolchain = SYSTEM

crates = [
('toy', 'extra.txt'),
('toy', '0.0_gzip.patch.gz'),
]

moduleclass = 'tools'
4 changes: 3 additions & 1 deletion test/framework/filetools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2470,11 +2470,13 @@ def test_index_functions(self):
# load_index just returns None if there is no index in specified directory
self.assertEqual(ft.load_index(self.test_prefix), None)

num_files = sum(len(files) for _, _, files in os.walk(test_ecs))

# create index for test easyconfigs;
# test with specified path with and without trailing '/'s
for path in [test_ecs, test_ecs + '/', test_ecs + '//']:
index = ft.create_index(path)
self.assertEqual(len(index), 92)
self.assertEqual(len(index), num_files)

expected = [
os.path.join('b', 'bzip2', 'bzip2-1.0.6-GCC-4.9.2.eb'),
Expand Down
50 changes: 50 additions & 0 deletions test/framework/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,7 @@ def test_000_list_easyblocks(self):
expected = '\n'.join([
r'EasyBlock',
r'\|-- bar',
r'\|-- Cargo',
r'\|-- ConfigureMake',
r'\| \|-- MakeCp',
r'\|-- EB_EasyBuildMeta',
Expand Down Expand Up @@ -6285,6 +6286,55 @@ def test_inject_checksums(self):
]
self.assertEqual(ext_opts['checksums'], expected_checksums)

# Also works for cargo crates
cargo_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-cargo.eb')
copy_file(cargo_ec, test_ec)
stdout, stderr = self._run_mock_eb([test_ec, '--inject-checksums'], raise_error=True, strip=True)
self.assertIn("injecting sha256 checksums in", stdout)
self.assertEqual(stderr, '')
expected_checksums = [
{'toy-extra.txt': '4196b56771140d8e2468fb77f0240bc48ddbf5dabafe0713d612df7fafb1e458'},
{'toy-0.0_gzip.patch.gz': 'c5c51dd4b00fd490f8f8226f5fa609c30b66bda7ef6d3391ab2631508f3d5e41'},
]
patterns = [r"^== injecting sha256 checksums for sources & patches in test\.eb\.\.\.$"]
patterns.extend(r"^== \* %s: %s$" % next(iter(entry.items())) for entry in expected_checksums)
for pattern in patterns:
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(stdout), "Pattern '%s' found in: %s" % (regex.pattern, stdout))

ec = EasyConfigParser(test_ec).get_config_dict()
self.assertEqual(ec['checksums'], expected_checksums)

# crates also work with sources and patches (unusual use case)
copy_file(cargo_ec, test_ec)
write_file(test_ec, textwrap.dedent("""
sources = [SOURCE_TAR_GZ]
patches = [
'toy-0.0_fix-silly-typo-in-printf-statement.patch',
]
"""), append=True)
stdout, stderr = self._run_mock_eb([test_ec, '--inject-checksums'], raise_error=True, strip=True)
self.assertIn("injecting sha256 checksums in", stdout)
self.assertEqual(stderr, '')
expected_checksums = [
# Main source
{'toy-0.0.tar.gz': '44332000aa33b99ad1e00cbd1a7da769220d74647060a10e807b916d73ea27bc'},
# Specified as "crates"
{'toy-extra.txt': '4196b56771140d8e2468fb77f0240bc48ddbf5dabafe0713d612df7fafb1e458'},
{'toy-0.0_gzip.patch.gz': 'c5c51dd4b00fd490f8f8226f5fa609c30b66bda7ef6d3391ab2631508f3d5e41'},
# Patch
{'toy-0.0_fix-silly-typo-in-printf-statement.patch':
'81a3accc894592152f81814fbf133d39afad52885ab52c25018722c7bda92487'},
]
patterns = [r"^== injecting sha256 checksums for sources & patches in test\.eb\.\.\.$"]
patterns.extend(r"^== \* %s: %s$" % next(iter(entry.items())) for entry in expected_checksums)
for pattern in patterns:
regex = re.compile(pattern, re.M)
self.assertTrue(regex.search(stdout), "Pattern '%s' found in: %s" % (regex.pattern, stdout))

ec = EasyConfigParser(test_ec).get_config_dict()
self.assertEqual(ec['checksums'], expected_checksums)

# passing easyconfig filename as argument to --inject-checksums results in error being reported,
# because it's not a valid type of checksum
args = ['--inject-checksums', test_ec]
Expand Down
51 changes: 51 additions & 0 deletions test/framework/sandbox/easybuild/easyblocks/generic/cargo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
##
# Copyright 2009-2024 Ghent University
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
##
from easybuild.framework.easyblock import EasyBlock
from easybuild.framework.easyconfig import CUSTOM


class Cargo(EasyBlock):
"""Generic support for building/installing cargo crates."""

@staticmethod
def extra_options():
"""Custom easyconfig parameters for bar."""
extra_vars = {
'crates': [[], "List of (crate, version, [repo, rev]) tuples to use", CUSTOM],
}
return EasyBlock.extra_options(extra_vars)

def __init__(self, *args, **kwargs):
"""Constructor for Cargo easyblock."""
super(Cargo, self).__init__(*args, **kwargs)

# Populate sources from "crates" list of tuples
# For simplicity just assume (name,version.ext) tuples
sources = ['%s-%s' % crate_info for crate_info in self.cfg['crates']]

# copy EasyConfig instance before we make changes to it
self.cfg = self.cfg.copy()

self.cfg.update('sources', sources)
Loading