Skip to content

Commit fb11ab3

Browse files
committed
Implement merging Cargo.toml file with workspace file
Resolve backreferences like `authors.workspace = true`
1 parent e8fcd04 commit fb11ab3

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

easybuild/easyblocks/generic/cargo.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def _clean_line(line: str, expected_end: Union[str, None]) -> str:
153153
return line[:idx].strip()
154154

155155

156-
def parse_toml(file: Path) -> Dict[str, str]:
156+
def parse_toml(file_or_content: Union[Path, str]) -> Dict[str, str]:
157157
"""Minimally parse a TOML file into sections, keys and values
158158
159159
Values will be the raw strings (including quotes for string-typed values)"""
@@ -163,7 +163,7 @@ def parse_toml(file: Path) -> Dict[str, str]:
163163
pending_value = None
164164
expected_end = None
165165
current_section = None
166-
content = read_file(file)
166+
content = read_file(file_or_content) if isinstance(file_or_content, Path) else file_or_content
167167
num = raw_line = None
168168
start_end = {
169169
'[': ']',
@@ -196,7 +196,7 @@ def parse_toml(file: Path) -> Dict[str, str]:
196196
result[current_section][pending_key] = pending_value.strip()
197197
pending_key = None
198198
except Exception as e:
199-
raise ValueError(f'Failed to parse {file}, error {e} at line {num}: {raw_line}')
199+
raise ValueError(f'Failed to parse {file_or_content}, error {e} at line {num}: {raw_line}')
200200
return result
201201

202202

@@ -236,6 +236,35 @@ def get_workspace_members(cargo_toml: Dict[str, str]):
236236
return has_package, members
237237

238238

239+
def merge_sub_crate(cargo_toml_path: Path, workspace_toml: Dict[str, str]):
240+
"""Resolve workspace references in the Cargo.toml file"""
241+
# Lines such as 'authors.workspace = true' must be replaced by 'authors = <value from workspace.package>'
242+
content: str = read_file(cargo_toml_path)
243+
SUFFIX = '.workspace'
244+
if SUFFIX not in content:
245+
return
246+
cargo_toml = parse_toml(content)
247+
lines = content.splitlines()
248+
249+
def do_replacement(section, workspace_section):
250+
if not section or not workspace_section:
251+
return
252+
253+
for key, value in section.items():
254+
if key.endswith(SUFFIX) and value == 'true':
255+
real_key = key[:-len(SUFFIX)]
256+
value = workspace_section[real_key]
257+
idx = next(idx for idx, line in enumerate(lines) if key in line)
258+
lines[idx] = f'{real_key} = {value}'
259+
260+
do_replacement(cargo_toml.get('package'), workspace_toml.get('workspace.package'))
261+
do_replacement(cargo_toml.get('dependencies'), workspace_toml.get('workspace.dependencies'))
262+
do_replacement(cargo_toml.get('build-dependencies'), workspace_toml.get('workspace.dependencies'))
263+
do_replacement(cargo_toml.get('dev-dependencies'), workspace_toml.get('workspace.dependencies'))
264+
265+
write_file(cargo_toml_path, '\n'.join(lines))
266+
267+
239268
def get_checksum(src, log):
240269
"""Get the checksum from an extracted source"""
241270
checksum = src['checksum']
@@ -502,6 +531,8 @@ def _setup_offline_config(self, git_sources):
502531
# Use copy_dir to resolve symlinks that might point to the parent folder
503532
copy_dir(tmp_crate_dir / member, target_path, symlinks=False)
504533
cargo_pkg_dirs.append(target_path)
534+
self.log.info(f'Resolving workspace values for crate {member}')
535+
merge_sub_crate(target_path / 'Cargo.toml', parsed_toml)
505536
if has_package:
506537
# Remove the copied crate folders
507538
for member in members:

test/easyblocks/easyblock_specific.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
from easybuild.tools.build_log import EasyBuildError
5454
from easybuild.tools.config import GENERAL_CLASS, get_module_syntax
5555
from easybuild.tools.environment import modify_env
56-
from easybuild.tools.filetools import adjust_permissions, mkdir, move_file, remove_dir, symlink, write_file
56+
from easybuild.tools.filetools import adjust_permissions, mkdir, move_file, read_file, remove_dir, symlink, write_file
5757
from easybuild.tools.modules import modules_tool
5858
from easybuild.tools.options import set_tmpdir
5959
from easybuild.tools.run import RunShellCmdResult
@@ -421,6 +421,62 @@ def test_cargo_toml_parsers(self):
421421
self.assertTrue(has_package)
422422
self.assertEqual(members, ["nothing", "src-tauri"])
423423

424+
def test_cargo_merge_sub_crate(self):
425+
"""Test merge_sub_crate in the Cargo easyblock"""
426+
crate_dir = Path(tempfile.mkdtemp())
427+
cargo_toml = crate_dir / 'Cargo.toml'
428+
write_file(cargo_toml, textwrap.dedent("""
429+
[workspace]
430+
members = ["bar"]
431+
432+
[workspace.package]
433+
version = "1.2.3"
434+
authors = ["Nice Folks"]
435+
description = "A short description of my package"
436+
documentation = "https://example.com/bar"
437+
438+
[workspace.dependencies]
439+
regex = { version = "1.6.0", default-features = false, features = ["std"] }
440+
cc = "1.0.73"
441+
rand = "0.8.5"
442+
"""))
443+
ws_parsed = cargo.parse_toml(cargo_toml)
444+
write_file(cargo_toml, textwrap.dedent("""
445+
[package]
446+
name = "bar"
447+
version.workspace = true
448+
authors.workspace = true
449+
description.workspace = true
450+
documentation.workspace = true
451+
452+
[dependencies]
453+
regex.workspace = true
454+
455+
[build-dependencies]
456+
cc.workspace = true
457+
458+
[dev-dependencies]
459+
rand.workspace = true
460+
"""))
461+
cargo.merge_sub_crate(cargo_toml, ws_parsed)
462+
self.assertEqual(read_file(cargo_toml).strip(), textwrap.dedent("""
463+
[package]
464+
name = "bar"
465+
version = "1.2.3"
466+
authors = ["Nice Folks"]
467+
description = "A short description of my package"
468+
documentation = "https://example.com/bar"
469+
470+
[dependencies]
471+
regex = { version = "1.6.0", default-features = false, features = ["std"] }
472+
473+
[build-dependencies]
474+
cc = "1.0.73"
475+
476+
[dev-dependencies]
477+
rand = "0.8.5"
478+
""").strip())
479+
424480
def test_handle_local_py_install_scheme(self):
425481
"""Test handle_local_py_install_scheme function provided by PythonPackage easyblock."""
426482

0 commit comments

Comments
 (0)