diff --git a/tools/fedora-helper.py b/tools/fedora-helper.py deleted file mode 100755 index 6111024..0000000 --- a/tools/fedora-helper.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/python3 - -import collections -import os -from pathlib import Path -import sys -import tempfile - -import click -import requests -from requests.compat import urljoin -import solv - -XDG_CACHE_HOME = Path(os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))) -CACHEDIR = XDG_CACHE_HOME / "rust2rpm" - -# Arch that is used for resolving dependencies and such -ARCH = "x86_64" -# The repo that contains crates -REPO_URL = f"https://kojipkgs.fedoraproject.org/repos/f35-build/latest/{ARCH}/" -# Just some sane chunk size -CHUNK_SIZE = 8192 - - -def _download(fname, chksum=None, uncompress=False): - url = urljoin(REPO_URL, fname) - with requests.get(urljoin(REPO_URL, fname), stream=True) as r: - tmp = tempfile.TemporaryFile() - total = int(r.headers["Content-Length"]) - with click.progressbar(length=total, label=f"Downloading {url}", file=sys.stderr) as pb: - for chunk in r.iter_content(chunk_size=CHUNK_SIZE): - pb.update(tmp.write(chunk)) - tmp.seek(0) - - if chksum is not None: - fchksum = solv.Chksum(chksum.type) - fchksum.add_fd(tmp.fileno()) - if chksum != fchksum: - raise Exception(f"Checksums do not match: {fchksum} != {chksum}") - - return solv.xfopen_fd(os.path.basename(url) if uncompress else None, tmp.fileno()) - - -@click.group() -def cli(): - pass - - -@cli.command() -@click.pass_context -@click.argument("source", type=click.Path(exists=True)) -@click.option( - "-a", - "--add", - multiple=True, - type=click.Path(exists=True), - help="Add local RPMs to the pool that is used in dependency resolution.", -) -@click.option( - "-v", - "--verbose", - is_flag=True, - help="Enable verbose mode.", -) -def get_binary_license(ctx, source, add, verbose): - """Get the resulting license of a binary. - - Since all crates (aka `rust-*-devel`) are linked statically, the resulting - binary license should be result of combining source license and all used - crates. - - Combined with dynamic BuildRequires generation, the usage would look like: - - \b - rpmbuild -br *.spec -D "_sourcedir $PWD" --without check - fedora-helper.py get-binary-license /path/to/buildreqs.nosrc.rpm - - This command resolves dependencies and outputs final license(s). - """ - pool = solv.Pool() - pool.setarch(ARCH) - - repo = pool.add_repo("upstream") - fd = _download("repodata/repomd.xml") - chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256) - chksum.add("1.1") - chksum.add_fd(fd.fileno()) - cookie = chksum.raw() - repo.add_repomdxml(fd) - fd.close() - - # Find "primary" - di = repo.Dataiterator_meta(solv.REPOSITORY_REPOMD_TYPE, "primary", solv.Dataiterator.SEARCH_STRING) - di.prepend_keyname(solv.REPOSITORY_REPOMD) - for d in di: - dp = d.parentpos() - filename = dp.lookup_str(solv.REPOSITORY_REPOMD_LOCATION) - checksum = dp.lookup_checksum(solv.REPOSITORY_REPOMD_CHECKSUM) - # FIXME: Can filename be None? - - cache_file = CACHEDIR / "upstream.solv" - try: - with open(cache_file, "rb") as f: - clen = len(cookie) - f.seek(-clen, os.SEEK_END) - fcookie = f.read(clen) - if fcookie != cookie: - raise IOError("repomd.xml has changed") - f.seek(0) - fd = solv.xfopen_fd(None, f.fileno()) - repo.add_solv(fd) - fd.close() - cached = True - except IOError: - # Failed to load from cache - cached = False - - if not cached: - fd = _download(filename, chksum=checksum, uncompress=True) - repo.add_rpmmd(fd, None) - fd.close() - - os.makedirs(CACHEDIR, exist_ok=True) - with tempfile.NamedTemporaryFile(prefix=".newsolv-", dir=CACHEDIR) as tmp: - fd = solv.xfopen_fd(None, tmp.fileno()) - repo.write(fd) - fd.flush() - fd.write(cookie) - fd.close() - if repo.iscontiguous(): - repo.empty() - repo.add_solv(tmp.name) - try: - os.unlink(cache_file) - except FileNotFoundError: - pass - os.link(tmp.name, cache_file) - - repo_local = pool.add_repo("local") - repo_local.priority = 99 - s = repo_local.add_rpm(source) - for f in add: - repo_local.add_rpm(f) - - pool.addfileprovides() - pool.createwhatprovides() - - solver = pool.Solver() - solver.set_flag(solv.Solver.SOLVER_FLAG_IGNORE_RECOMMENDED, True) - problems = solver.solve([pool.Job(solv.Job.SOLVER_SOLVABLE | solv.Job.SOLVER_INSTALL, s.id)]) - if problems: - click.echo("Failed to resolve dependencies:", err=True) - for problem in problems: - for rule in problem.findallproblemrules(): - for info in rule.allinfos(): - click.echo(f" - ", nl=False) - click.secho(info.problemstr(), fg="red") - ctx.abort() - - licenses = collections.defaultdict(set) - for p in solver.transaction().newsolvables(): - if not (p.name.startswith("rust-") and p.name.endswith("-devel")) and not p == s: - continue - licenses[p.lookup_str(solv.SOLVABLE_LICENSE)].add(p) - # XXX: This is pure hack and we should optimize license set in much better way - if "MIT or ASL 2.0" in licenses and "ASL 2.0 or MIT" in licenses: - licenses["MIT or ASL 2.0"].update(licenses.pop("ASL 2.0 or MIT")) - - for license, packages in sorted(licenses.items()): - click.echo(f"# {license}") - if verbose: - for package in packages: - click.secho(f"# * {package}", fg="white") - - -if __name__ == "__main__": - cli()