from __future__ import annotations
import itertools
import tomllib
import json
import typing
import os
import logging
import sys
def powerset[T](features: typing.Sequence[T]) -> typing.Iterator[typing.Tuple[T, ...]]:
return itertools.chain.from_iterable(
itertools.combinations(features, r) for r in range(len(features) + 1)
)
def resolve_dependencies[T: typing.Hashable](
features: typing.Iterable[T], tree: typing.Mapping[T, typing.Sequence[T]]
) -> typing.Generator[T]:
for feature in features:
dependencies = tree.get(feature)
if dependencies is None:
continue
elif len(dependencies):
yield from resolve_dependencies(dependencies, tree)
yield feature
def all_unique[T: typing.Hashable](iterable: typing.Iterable[T]) -> bool:
seen = set()
for e in iterable:
if e in seen:
return False
seen.add(e)
return True
def main(manifest_path: str, output_file: typing.Optional[typing.IO] = None):
with open(manifest_path, "rb") as f:
data = tomllib.load(f)
msrv = data["package"].get("rust-version", "")
logging.debug("Minimum supported Rust version: %s", msrv)
if "features" in data:
logging.debug("Features found")
try:
del data["features"]["default"]
except KeyError:
logging.debug("No default feature set found")
features = sorted(data["features"].keys())
logging.debug("Features: %s", features)
seen = set()
unique_disjoint_combinations = []
for combination in powerset(features):
resolved = tuple(
sorted(resolve_dependencies(combination, data["features"]))
)
if resolved not in seen and all_unique(resolved):
seen.add(resolved)
unique_disjoint_combinations.append(",".join(resolved))
logging.debug("Unique disjoint combinations: %s", unique_disjoint_combinations)
else:
logging.debug("No features found")
features = []
unique_disjoint_combinations = []
json.dump(
{
"powerset": unique_disjoint_combinations,
"features": features,
"msrv": msrv,
},
output_file or sys.stdout,
)
if __name__ == "__main__":
log_level = os.environ.get("LOG_LEVEL", "INFO").upper()
logging.basicConfig(level=log_level)
main(os.environ.get("CARGO_MANIFEST_PATH", "Cargo.toml"))