"""
This script generates a unified Rust wrapper crate by combining multiple Rust crates into a single static library.
It can parse crate information from command line arguments.
The script creates a Cargo.toml and src/lib.rs that re-export all specified crates.
Workflow:
1. Parse command line arguments to determine crate sources:
- Use --crate-name/--crate-path for explicit lists
- Use --crate for combined format (name:path)
2. Validate and collect crate information (name and path for each crate)
3. Create the output directory (default: ./rust_unified_lib or as specified)
4. Generate Cargo.toml listing all crates as dependencies
5. Generate src/lib.rs re-exporting all crates as modules
6. Generate .cargo/config.toml file from RUST_UNIFIED_LIB_CONFIG environment variable
7. Print status messages for each step
8. Exit with status code 0 on success, 1 on error
Usage:
python generate_rust_unified_lib.py --crate name:path [--crate ...] [--output-dir DIR]
python generate_rust_unified_lib.py --crate-name NAME --crate-path PATH [--output-dir DIR]
Environment Variables:
RUST_UNIFIED_LIB_CONFIG: Content to write to the .cargo/config.toml file (optional)
"""
import argparse
import os
import sys
from pathlib import Path
def split_and_strip(items):
"""
Split a string or list of strings by semicolon and strip whitespace from each item.
Returns a flat list of non-empty, stripped strings.
"""
if isinstance(items, str):
items = items.split(";")
result = []
for item in items:
if isinstance(item, str):
result.extend([x.strip() for x in item.split(";") if x.strip()])
else:
result.append(str(item).strip())
return result
def parse_crates_from_args(crate_names=None, crate_paths=None):
"""
Parse crate information from separate command line arguments for names and paths.
Returns a list of dicts with 'name' and 'path' for each crate.
"""
if not crate_names or not crate_paths:
return []
crate_names = split_and_strip(crate_names)
crate_paths = split_and_strip(crate_paths)
if len(crate_names) != len(crate_paths):
print("Warning: Number of crate names and paths don't match")
return []
return [
{"name": name, "path": path} for name, path in zip(crate_names, crate_paths)
]
def create_cargo_toml(crate_dir, crate_info):
"""
Create Cargo.toml in the output directory, listing all crates as dependencies.
"""
cargo_toml_path = crate_dir / "Cargo.toml"
cargo_content = """[package]
name = "rust_unified_lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[profile.dev]
panic = "abort"
[profile.release]
lto = true
codegen-units = 1
opt-level = "z"
debug = true
panic = "abort"
[dependencies]
"""
for crate in crate_info:
crate_name = crate["name"]
crate_path = crate["path"]
cargo_content += f'{crate_name} = {{ path = "{crate_path}" }}\n'
with open(cargo_toml_path, "w") as f:
f.write(cargo_content)
print(f"Created {cargo_toml_path}")
def create_lib_rs(crate_dir, crate_info):
"""
Create src/lib.rs in the output directory, re-exporting all crates as modules.
"""
lib_rs_dir = crate_dir / "src"
lib_rs_dir.mkdir(exist_ok=True)
lib_rs_path = lib_rs_dir / "lib.rs"
lib_content = "// Automatically generated unified Rust library\n\n"
for crate in crate_info:
crate_name = crate["name"]
module_name = crate_name.replace("-", "_")
lib_content += f"// Re-exporting {crate_name} as {module_name}\n"
lib_content += f"pub use {module_name};\n\n"
with open(lib_rs_path, "w") as f:
f.write(lib_content)
print(f"Created {lib_rs_path}")
def create_config_file(crate_dir):
"""
Create .cargo/config.toml file in the output directory from RUST_UNIFIED_LIB_CONFIG environment variable.
"""
cargo_dir = crate_dir / ".cargo"
cargo_dir.mkdir(exist_ok=True)
config_path = cargo_dir / "config.toml"
config_content = ""
if "RUST_UNIFIED_LIB_CONFIG" in os.environ:
config_content = os.environ["RUST_UNIFIED_LIB_CONFIG"]
else:
print(
"Info: RUST_UNIFIED_LIB_CONFIG environment variable not found, using minimal config.toml"
)
config_content = "# Minimal config.toml file for Rust unified library\n"
script_dir = Path(__file__).parent
registry_dir = script_dir.parent.parent / "external" / "rust" / "registry"
config_content = f"""{config_content}
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "{registry_dir}"
[net]
offline = true
"""
with open(config_path, "w") as f:
f.write(config_content)
print(f"Created {config_path}")
def main():
"""
Main entry point for the script. Parses arguments, gathers crate info, and generates unified Rust crate files.
"""
parser = argparse.ArgumentParser(
description="Generate a unified Rust wrapper crate"
)
parser.add_argument(
"--crate",
action="append",
help="Crate information in format 'name:path' (can be specified multiple times)",
)
parser.add_argument(
"--crate-name",
action="append",
help="Crate name (can be specified multiple times)",
)
parser.add_argument(
"--crate-path",
action="append",
help="Crate path (can be specified multiple times)",
)
parser.add_argument(
"--output-dir",
default="./rust_unified_lib",
help="Output directory for the unified crate",
)
args = parser.parse_args()
if args.crate_name and args.crate_path:
crate_info = parse_crates_from_args(
crate_names=args.crate_name, crate_paths=args.crate_path
)
elif args.crate:
crate_names = []
crate_paths = []
for crate_arg in args.crate:
crate_list = crate_arg.split(",")
for crate_str in crate_list:
if ":" in crate_str:
name, path = crate_str.split(":", 1)
crate_names.append(name.strip())
crate_paths.append(path.strip())
crate_info = parse_crates_from_args(
crate_names=crate_names, crate_paths=crate_paths
)
else:
print("Error: Either --crate-name/--crate-path or --crate must be specified")
return 1
if not crate_info:
print("No crate information found. Exiting.")
return 1
crate_dir = Path(args.output_dir)
crate_dir.mkdir(parents=True, exist_ok=True)
create_cargo_toml(crate_dir, crate_info)
create_config_file(crate_dir)
create_lib_rs(crate_dir, crate_info)
print(f"Successfully created unified Rust crate in {crate_dir}")
return 0
if __name__ == "__main__":
sys.exit(main())