3636import shutil
3737import tempfile
3838from glob import glob
39+ from pathlib import Path
3940
4041import easybuild .tools .environment as env
4142import easybuild .tools .systemtools as systemtools
7778CARGO_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