commit: 74a65d4b6351542d084ecedaa2a07c3e653fb5ee Author: Sam James <sam <AT> gentoo <DOT> org> AuthorDate: Sat Jul 23 02:58:31 2022 +0000 Commit: Sam James <sam <AT> gentoo <DOT> org> CommitDate: Sat Jul 23 02:59:52 2022 +0000 URL: https://gitweb.gentoo.org/proj/qa-scripts.git/commit/?id=74a65d4b
leaf-packages.py: add new script See https://leo3418.github.io/2021/07/18/find-leaf-packages.html from Leo3418's GSoC 2021 work. Thanks-to: Yuan Liao <liaoyuan <AT> gmail.com> Signed-off-by: Sam James <sam <AT> gentoo.org> leaf-packages.py | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/leaf-packages.py b/leaf-packages.py new file mode 100755 index 0000000..15cafe8 --- /dev/null +++ b/leaf-packages.py @@ -0,0 +1,113 @@ +# From Leo3418's GSoC 2021 work +# https://leo3418.github.io/2021/07/18/find-leaf-packages.html + +import concurrent.futures +import os +import re +import subprocess +import sys + +method="pkgcore" + +def main() -> None: + if len(sys.argv) > 1: + repo = sys.argv[1] + else: + repo = 'gentoo' + zero_in_degree = create_ebuild_dict(repo) + with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) \ + as executor: + for ebuild in zero_in_degree: + # Let the executor run function call + # update_for_deps_of(ebuild, zero_in_degree) + if method == "pkgcore": + executor.submit(update_for, ebuild, zero_in_degree, repo) + else: + executor.submit(update_for_deps_of, ebuild, zero_in_degree) + + # Print leaf ebuilds to standard output + for ebuild in zero_in_degree: + if zero_in_degree[ebuild]: + print(ebuild) + + +def update_for(ebuild: str, zero_in_degree: dict, repo: str) -> None: + """ + Update the boolean value for the specified ebuild in the given dictionary. + Reverse dependencies of the ebuild will be searched in the specified + repository only. + """ + print(f"Processing {ebuild} ...", file=sys.stderr) + proc = subprocess.run(f'pquery --first --restrict-revdep ={ebuild} ' + f'--repo {repo} --raw --unfiltered', + capture_output=True, text=True, shell=True) + zero_in_degree[ebuild] = len(proc.stdout) == 0 + +def create_ebuild_dict(repo: str) -> dict: + """ + Create a dictionary with all ebuilds in the specified repository as keys + that maps each key to a boolean value indicating whether it is a leaf + ebuild with zero in-degree. + """ + zero_in_degree = {} + proc = subprocess.run(f'pquery --repo {repo} --raw --unfiltered', + capture_output=True, text=True, + shell=True, check=True) + ebuilds = proc.stdout.splitlines() + for ebuild in ebuilds: + zero_in_degree[ebuild] = True + return zero_in_degree + + +def update_for_deps_of(ebuild: str, zero_in_degree: dict) -> None: + """ + For ebuilds that can be pulled as the specified ebuild's dependencies, + update the boolean value for them in the given dictionary accordingly. + """ + + def get_dep_atoms() -> list: + """ + Return a list of all dependency specification atoms. + """ + dep_atoms = [] + equery_dep_atom_pattern = re.compile(r'\(.+/.+\)') + proc = subprocess.run(f'equery -CN depgraph -MUl {ebuild}', + capture_output=True, text=True, shell=True) + out_lines = proc.stdout.splitlines() + for line in out_lines: + dep_atom_match = equery_dep_atom_pattern.findall(line) + dep_atom = [dep.strip('()') for dep in dep_atom_match] + dep_atoms.extend(dep_atom) + return dep_atoms + + def find_matching_ebuilds(atom: str) -> list: + """ + Return a list of ebuilds that satisfy an atom. + """ + proc = subprocess.run(f"equery list -op -F '$cpv' '{atom}'", + capture_output=True, text=True, shell=True) + return proc.stdout.splitlines() + + #print(f"Processing {ebuild} ...", file=sys.stderr) + + # Get dependency specifications in the ebuild; + # equivalent to dep_graph[ebuild] in the examples above + dep_atoms = get_dep_atoms() + + # Convert list of atoms to list of ebuilds that satisfy them + dep_ebuilds = [] + for dep_atom in dep_atoms: + dep_ebuilds.extend(find_matching_ebuilds(dep_atom)) + + # Register dependency ebuilds as non-leaves + for dep_ebuild in dep_ebuilds: + # An ebuild in an overlay might depend on ebuilds from ::gentoo and/or + # other repositories, but we only care about ebuilds in the dictionary + # passed to this function + if dep_ebuild in zero_in_degree: + zero_in_degree[dep_ebuild] = False + + +if __name__ == '__main__': + main() +