|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import json |
| 4 | +import subprocess |
| 5 | +from functools import cached_property |
| 6 | +from pathlib import Path |
| 7 | + |
| 8 | + |
| 9 | +class CargoProject: |
| 10 | + working_directory: Path |
| 11 | + |
| 12 | + def __init__(self, working_directory: Path) -> None: |
| 13 | + self.working_directory = working_directory |
| 14 | + |
| 15 | + @cached_property |
| 16 | + def metadata(self) -> dict: |
| 17 | + """Metadata about the cargo project""" |
| 18 | + |
| 19 | + command = ['cargo', 'metadata', '--format-version', '1'] |
| 20 | + command_result = subprocess.run(command, capture_output=True, text=True, cwd=self.working_directory) |
| 21 | + metadata = json.loads(command_result.stdout) |
| 22 | + return metadata |
| 23 | + |
| 24 | + @cached_property |
| 25 | + def manifest(self) -> dict: |
| 26 | + """Information about the Cargo.toml file""" |
| 27 | + |
| 28 | + command = ['cargo', 'read-manifest'] |
| 29 | + command_result = subprocess.run(command, capture_output=True, text=True, cwd=self.working_directory) |
| 30 | + manifest = json.loads(command_result.stdout) |
| 31 | + return manifest |
| 32 | + |
| 33 | + @property |
| 34 | + def build_messages(self) -> list[dict]: |
| 35 | + """Output of messages from `cargo build`""" |
| 36 | + |
| 37 | + command = ['cargo', 'build', '--message-format=json'] |
| 38 | + command_result = subprocess.run(command, capture_output=True, text=True, cwd=self.working_directory) |
| 39 | + split_command_result = command_result.stdout.splitlines() |
| 40 | + messages = [json.loads(message) for message in split_command_result] |
| 41 | + return messages |
| 42 | + |
| 43 | + def smir_for(self, target: str) -> Path: |
| 44 | + """Gets the latest smir json in the target directory for the given target. Raises a RuntimeError if none exist.""" |
| 45 | + |
| 46 | + is_artifact = lambda message: message.get('reason') == 'compiler-artifact' |
| 47 | + is_my_target = lambda artifact: artifact.get('target', {}).get('name') == target |
| 48 | + artifact = next(message for message in self.build_messages if is_artifact(message) and is_my_target(message)) |
| 49 | + executable_name = Path(artifact['executable']) |
| 50 | + deps_dir = executable_name.parent / 'deps' |
| 51 | + |
| 52 | + # Get the smir file(s) and sort them by modified time |
| 53 | + smir_files = deps_dir.glob(f'{target}*.smir.json') |
| 54 | + sorted_smir_files = sorted(smir_files, key=lambda f: f.stat().st_mtime, reverse=True) |
| 55 | + |
| 56 | + if len(sorted_smir_files) == 0: |
| 57 | + raise RuntimeError( |
| 58 | + f'Unable to find smir json for target {target!r}. Have you built it using stable-mir-json?' |
| 59 | + ) |
| 60 | + |
| 61 | + # Return the most recently modified smir file |
| 62 | + return sorted_smir_files[0] |
| 63 | + |
| 64 | + @cached_property |
| 65 | + def default_target(self) -> str: |
| 66 | + """Returns the name of the default binary target. If it can't find a default, raises a RuntimeError""" |
| 67 | + |
| 68 | + default_run = self.manifest.get('default_run') |
| 69 | + if isinstance(default_run, str): |
| 70 | + return default_run |
| 71 | + |
| 72 | + is_bin = lambda target: any(kind == 'bin' for kind in target.get('kind')) |
| 73 | + targets = self.manifest.get('targets') |
| 74 | + assert isinstance(targets, list) |
| 75 | + bin_targets = [target.get('name') for target in targets if is_bin(target)] |
| 76 | + |
| 77 | + if len(bin_targets) != 1: |
| 78 | + raise RuntimeError( |
| 79 | + f"Can't determine which binary to run. Use --bin, or the 'default-run' manifest key. Found {bin_targets}" |
| 80 | + ) |
| 81 | + |
| 82 | + return bin_targets[0] |
0 commit comments