Files
pezkuwi-sdk/scripts/generate-umbrella.py
T

232 lines
7.3 KiB
Python

"""
Creates the Pezkuwi-SDK umbrella crate that re-exports all other crates.
This re-creates the `umbrella/` folder. Ensure that it does not contain any changes you want to keep.
Usage:
python3 generate-umbrella.py --sdk <path> --version <version>
Example:
python3 generate-umbrella.py --sdk .. --version 1.11.0
"""
import argparse
import os
import re
import toml
import shutil
import subprocess
from cargo_workspace import Workspace
"""
Crate names that should be excluded from the umbrella crate.
"""
def exclude(crate):
name = crate.name
if crate.metadata.get("pezkuwi-sdk.exclude-from-umbrella", False):
return True
# No fuzzers or examples:
if "example" in name or name.endswith("fuzzer"):
return True
# No runtime crates:
if name.endswith("-runtime"):
# Note: this is a bit hacky. We should use custom crate metadata instead.
return name != "sp-runtime" and name != "pezbp-runtime" and name != "frame-try-runtime"
# Exclude snowbridge crates.
if name.startswith("snowbridge-"):
return True
return False
def main(path, version):
delete_umbrella(path)
workspace = Workspace.from_path(path)
print(f'Indexed {workspace}')
std_crates = [] # name -> path. use list for sorting
nostd_crates = []
for crate in workspace.crates:
if crate.name == 'pezkuwi-sdk':
continue
if not crate.publish:
print(f"Skipping {crate.name} as it is not published")
continue
lib_path = os.path.dirname(crate.abs_path)
manifest_path = os.path.join(lib_path, "Cargo.toml")
lib_path = os.path.join(lib_path, "src", "lib.rs")
path = os.path.dirname(crate.rel_path)
# Guess which crates support no_std. Proc-macro crates are always no_std:
with open(manifest_path, "r") as f:
manifest = toml.load(f)
if 'lib' in manifest and 'proc-macro' in manifest['lib']:
if manifest['lib']['proc-macro']:
nostd_crates.append((crate, path))
continue
# Crates without a lib.rs cannot be no_std
if not os.path.exists(lib_path):
print(f"Skipping {crate.name} as it does not have a 'src/lib.rs'")
continue
if exclude(crate):
print(f"Skipping {crate.name} as it is in the exclude list")
continue
# No search for a no_std attribute:
with open(lib_path, "r") as f:
nostd_crate = False
for line in f:
line = line.strip()
if line == "#![no_std]" or line == '#![cfg_attr(not(feature = "std"), no_std)]':
nostd_crate = True
break
elif "no_std" in line:
print(line)
if nostd_crate:
nostd_crates.append((crate, path))
else:
std_crates.append((crate, path))
# Sort by name
std_crates.sort(key=lambda x: x[0].name)
nostd_crates.sort(key=lambda x: x[0].name)
runtime_crates = [crate for crate in nostd_crates if 'frame' in crate[0].name or crate[0].name.startswith('sp-')]
all_crates = std_crates + nostd_crates
all_crates.sort(key=lambda x: x[0].name)
dependencies = {}
for (crate, path) in nostd_crates:
dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True}
for (crate, path) in std_crates:
dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True}
# The empty features are filled by Zepter
features = {
"default": [ "std" ],
"std": [],
"runtime-benchmarks": [],
"try-runtime": [],
"serde": [],
"experimental": [],
"with-tracing": [],
"runtime-full": list([f"{d.name}" for d, _ in nostd_crates]),
"runtime": list([f"{d.name}" for d, _ in runtime_crates]),
"node": ["std"] + list([f"{d.name}" for d, _ in std_crates]),
"tuples-96": [],
}
manifest = {
"package": {
"name": "pezkuwi-sdk",
"version": version,
"edition": { "workspace": True },
"authors": { "workspace": True },
"description": "Pezkuwi SDK umbrella crate.",
"homepage": { "workspace": True },
"repository": { "workspace": True },
"license": "Apache-2.0",
"metadata": { "docs": { "rs": {
"features": ["runtime-full", "node"],
"targets": ["x86_64-unknown-linux-gnu"]
}}}
},
"dependencies": dependencies,
"features": features,
}
umbrella_dir = os.path.join(workspace.path, "umbrella")
manifest_path = os.path.join(umbrella_dir, "Cargo.toml")
lib_path = os.path.join(umbrella_dir, "src", "lib.rs")
# create all dir
os.makedirs(os.path.dirname(lib_path), exist_ok=True)
# Write the manifest
with open(manifest_path, "w") as f:
toml_manifest = toml.dumps(manifest)
f.write(toml_manifest)
print(f"Wrote {manifest_path}")
# Format with taplo to match CI expectations
taplo_config = os.path.join(workspace.path, ".config", "taplo.toml")
if os.path.exists(taplo_config):
try:
subprocess.run(["taplo", "format", "--config", taplo_config, manifest_path], check=True, capture_output=True)
print(f"Formatted {manifest_path} with taplo")
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"Warning: Could not format with taplo: {e}")
# and the lib.rs
with open(lib_path, "w") as f:
f.write('''// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
#![cfg_attr(not(feature = "std"), no_std)]
//! Pezkuwi SDK umbrella crate re-exporting all other published crates.
//!
//! This helps to set a single version number for all your dependencies. Docs are in the
//! `pezkuwi-sdk-docs` crate.
// This file is auto-generated and checked by the CI. You can edit it manually, but it must be
// exactly the way that the CI expects it.
''')
for crate, _ in all_crates:
use = crate.name.replace("-", "_")
desc = crate.description if crate.description.endswith(".") else crate.description + "."
f.write(f'\n/// {desc}')
f.write(f'\n#[cfg(feature = "{crate.name}")]\n')
f.write(f"pub use {use};\n")
print(f"Wrote {lib_path}")
add_to_workspace(workspace.path)
"""
Delete the umbrella folder and remove the umbrella crate from the workspace.
"""
def delete_umbrella(path):
# remove the umbrella crate from the workspace
manifest = os.path.join(path, "Cargo.toml")
manifest = open(manifest, "r").read()
manifest = re.sub(r'\s+"umbrella",\n', "", manifest)
with open(os.path.join(path, "Cargo.toml"), "w") as f:
f.write(manifest)
umbrella_dir = os.path.join(path, "umbrella")
if os.path.exists(umbrella_dir):
print(f"Deleting {umbrella_dir}")
os.remove(os.path.join(umbrella_dir, "Cargo.toml"))
shutil.rmtree(os.path.join(umbrella_dir, "src"))
"""
Create the umbrella crate and add it to the workspace.
"""
def add_to_workspace(path):
manifest = os.path.join(path, "Cargo.toml")
manifest = open(manifest, "r").read()
manifest = re.sub(r'^members = \[', 'members = [\n "umbrella",', manifest, flags=re.M)
with open(os.path.join(path, "Cargo.toml"), "w") as f:
f.write(manifest)
os.chdir(path) # hack
os.system("cargo metadata --format-version 1 > /dev/null") # update the lockfile
os.system(f"zepter") # enable the features
os.system(f"taplo format --config .config/taplo.toml Cargo.toml umbrella/Cargo.toml")
os.system(f"cargo fmt -- umbrella/src/lib.rs") # format lib.rs for rustfmt compliance
def parse_args():
parser = argparse.ArgumentParser(description="Create a pezkuwi-sdk crate")
parser.add_argument("--sdk", type=str, default="pezkuwi-sdk", help="Path to the pezkuwi-sdk crate")
parser.add_argument("--version", type=str, help="Version of the pezkuwi-sdk crate")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
main(args.sdk, args.version)