Skip to content

Commit 73bf121

Browse files
committed
Fix handling of members in crates.io sources
1 parent 661d543 commit 73bf121

File tree

1 file changed

+47
-35
lines changed

1 file changed

+47
-35
lines changed

easybuild/easyblocks/generic/cargo.py

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import shutil
3737
import tempfile
3838
from glob import glob
39+
from pathlib import Path
3940

4041
import easybuild.tools.environment as env
4142
import easybuild.tools.systemtools as systemtools
@@ -77,16 +78,16 @@
7778
CARGO_CHECKSUM_JSON = '{{"files": {{}}, "package": "{checksum}"}}'
7879

7980

80-
def get_workspace_members(crate_dir):
81+
def get_workspace_members(crate_dir: Path):
8182
"""Find all members of a cargo workspace in crate_dir.
8283
8384
(Minimally) parse the Cargo.toml file.
8485
8586
Return a tuple: (has_package, workspace-members).
86-
has_package determines if the manifest contains a '[package]' section and hence is not a virtual manifest.
87+
has_package determines if it is a virtual workspace ([workspace] and no [package])
8788
workspace-members are all members (subfolder names) if it is a workspace, otherwise None
8889
"""
89-
cargo_toml = os.path.join(crate_dir, 'Cargo.toml')
90+
cargo_toml = crate_dir / 'Cargo.toml'
9091
lines = [line.strip() for line in read_file(cargo_toml).splitlines()]
9192
# A virtual (workspace) manifest has no [package], but only a [workspace] section.
9293
has_package = '[package]' in lines
@@ -160,6 +161,10 @@ def extra_options(extra_vars=None):
160161

161162
return extra_vars
162163

164+
@staticmethod
165+
def src_parameter_names():
166+
return super().src_parameter_names() + ['crates']
167+
163168
@staticmethod
164169
def crate_src_filename(pkg_name, pkg_version, _url=None, rev=None):
165170
"""Crate tarball filename based on package name, version and optionally git revision"""
@@ -346,12 +351,11 @@ def _setup_offline_config(self, git_sources):
346351

347352
self.log.debug("Setting up checksum files and unpacking workspaces with virtual manifest")
348353
path_to_source = {src['finalpath']: src for src in self.src}
349-
tmp_dir = tempfile.mkdtemp(dir=self.builddir, prefix='tmp_crate_')
354+
tmp_dir = Path(tempfile.mkdtemp(dir=self.builddir, prefix='tmp_crate_'))
350355
# Add checksum file for each crate such that it is recognized by cargo.
351356
# Glob to catch multiple folders in a source archive.
352-
crate_dirs = [os.path.dirname(p) for p in glob(os.path.join(self.vendor_dir, '*', 'Cargo.toml'))]
353-
for crate_dir in crate_dirs:
354-
src = path_to_source.get(crate_dir)
357+
for crate_dir in (p.parent for p in Path(self.vendor_dir).glob('*/Cargo.toml')):
358+
src = path_to_source.get(str(crate_dir))
355359
if src:
356360
try:
357361
checksum = src[CHECKSUM_TYPE_SHA256]
@@ -361,42 +365,50 @@ def _setup_offline_config(self, git_sources):
361365
else:
362366
self.log.debug(f'No source found for {crate_dir}. Using nul-checksum for vendoring')
363367
checksum = 'null'
368+
cargo_pkg_dirs = [crate_dir] # Default case: Single crate
364369
# Sources might contain multiple crates/folders in a so-called "workspace".
365370
# We have to move the individual packages out of the workspace so cargo can find them.
366371
# If there is a main package it should to used too,
367372
# otherwise (Only "[workspace]" section and no "[package]" section)
368373
# we have to remove the top-level folder or cargo fails with:
369374
# "found a virtual manifest at [...]Cargo.toml instead of a package manifest"
370-
has_package, member_dirs = get_workspace_members(crate_dir)
371-
if member_dirs:
372-
self.log.info(f'Found workspace in {crate_dir}. Members: ' + ', '.join(member_dirs))
373-
cargo_pkg_dirs = []
374-
tmp_crate_dir = os.path.join(tmp_dir, os.path.basename(crate_dir))
375-
shutil.move(crate_dir, tmp_crate_dir)
376-
for crate in member_dirs:
377-
# A member crate might be in a subfolder, e.g. 'components/foo',
378-
# which we need to ignore and make the crate a top-level folder.
379-
target_path = os.path.join(self.vendor_dir, os.path.basename(crate))
380-
if os.path.exists(target_path):
381-
raise EasyBuildError(f'Cannot move {crate} out of {os.path.basename(crate_dir)} '
382-
f'as target path {target_path} exists')
383-
# Use copy_dir to resolve symlinks that might point to the parent folder
384-
copy_dir(os.path.join(tmp_crate_dir, crate), target_path, symlinks=False)
385-
cargo_pkg_dirs.append(target_path)
386-
if has_package:
387-
# Remove the copied crate folders
388-
for crate in member_dirs:
389-
remove_dir(os.path.join(tmp_crate_dir, crate))
390-
# Keep the main package in the original location
391-
shutil.move(tmp_crate_dir, crate_dir)
392-
cargo_pkg_dirs.append(crate_dir)
375+
has_package, members = get_workspace_members(crate_dir)
376+
if members:
377+
self.log.info(f'Found workspace in {crate_dir}. Members: ' + ', '.join(members))
378+
if not any((crate_dir / crate).is_dir() for crate in members):
379+
if not has_package:
380+
raise EasyBuildError(f'Virtual manifest found in {crate_dir} but none of the member folders '
381+
'exist. This cannot be handled by the build.')
382+
# Packages from crates.io contain only a single crate even if the Cargo.toml file lists multiple
383+
# members. Those members are in separate packages on crates.io, so this is a fairly common case.
384+
self.log.debug(f"Member folders of {crate_dir} don't exist so assuming they are in individual "
385+
"crates, e.g. from/on crates.io")
393386
else:
394-
self.log.info(f'Virtual manifest found in {crate_dir}, removing it')
395-
remove_dir(tmp_crate_dir)
396-
else:
397-
cargo_pkg_dirs = [crate_dir]
387+
cargo_pkg_dirs = []
388+
tmp_crate_dir = tmp_dir / crate_dir.name
389+
shutil.move(crate_dir, tmp_crate_dir)
390+
for member in members:
391+
# A member crate might be in a subfolder, e.g. 'components/foo',
392+
# which we need to ignore and make the crate a top-level folder.
393+
target_path = Path(self.vendor_dir, os.path.basename(member))
394+
if target_path.exists():
395+
raise EasyBuildError(f'Cannot move {member} out of {crate_dir.name} '
396+
f'as target path {target_path} exists')
397+
# Use copy_dir to resolve symlinks that might point to the parent folder
398+
copy_dir(tmp_crate_dir / member, target_path, symlinks=False)
399+
cargo_pkg_dirs.append(target_path)
400+
if has_package:
401+
# Remove the copied crate folders
402+
for member in members:
403+
remove_dir(tmp_crate_dir / member)
404+
# Keep the main package in the original location
405+
shutil.move(tmp_crate_dir, crate_dir)
406+
cargo_pkg_dirs.append(crate_dir)
407+
else:
408+
self.log.info(f'Virtual manifest found in {crate_dir}, removing it')
409+
remove_dir(tmp_crate_dir)
398410
for pkg_dir in cargo_pkg_dirs:
399-
self.log.info('creating .cargo-checksums.json file for %s', os.path.basename(pkg_dir))
411+
self.log.info('creating .cargo-checksums.json file for %s', pkg_dir.name)
400412
chkfile = os.path.join(pkg_dir, '.cargo-checksum.json')
401413
write_file(chkfile, CARGO_CHECKSUM_JSON.format(checksum=checksum))
402414

0 commit comments

Comments
 (0)