224 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python
 | 
						|
# Copyright 2024-2025 Gentoo Authors
 | 
						|
# Distributed under the terms of the GNU General Public License v2
 | 
						|
#
 | 
						|
#
 | 
						|
# Takes the manifests/*.xml file from the NVIDIA CUDA Toolkit as input.
 | 
						|
# Those files contain a nested tree of package items.
 | 
						|
#
 | 
						|
# For each package node a if block is output that checks the package's
 | 
						|
#  name attribute against the environment provided array SKIP_COMPONENTS
 | 
						|
#  and skips that whole package if it's listed in it.
 | 
						|
#
 | 
						|
# Each item contains four nodes that reference things to install,
 | 
						|
#  `dir`, `file`, `desktopFile`, & `pcfile`, and metadata that further
 | 
						|
#  detail these. This script will output calls to do* calls that
 | 
						|
#  use the metadata.
 | 
						|
# - dodir would create an empty file - we handle this in dofile
 | 
						|
# - dofile copies a regex file glob
 | 
						|
# - dodesktopFile creates a .desktop file
 | 
						|
# - dopcfile creates a pkgconfig file
 | 
						|
#
 | 
						|
# The resulting bash code can be run inside src_install().
 | 
						|
#
 | 
						|
# Usage: python parse_manifest.py <cuda_aarch64.xml|cuda_x86_64.xml>
 | 
						|
 | 
						|
 | 
						|
import argparse
 | 
						|
import xml.etree.ElementTree
 | 
						|
 | 
						|
import defusedxml.ElementTree
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
ind = "\t"
 | 
						|
indent = 1
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    parser = argparse.ArgumentParser()
 | 
						|
    parser.add_argument('filename')  # positional argument
 | 
						|
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    basedir = Path(args.filename).parents[1]
 | 
						|
 | 
						|
    et = defusedxml.ElementTree.parse(args.filename)
 | 
						|
 | 
						|
    # Find all packages under the package with the id main (i.e. under "CUDA Installer")
 | 
						|
    for e in et.findall("[@id='main']/package"):
 | 
						|
        def p_package(el: xml.etree.ElementTree.Element, level: int = 0):
 | 
						|
 | 
						|
            skip = {
 | 
						|
                "Documentation",  # obsolete
 | 
						|
                "Driver",  # unused
 | 
						|
                # "Kernel Objects",  # split
 | 
						|
                # "Demo Suite",
 | 
						|
                # "Visual Tools",
 | 
						|
                # old eclipse
 | 
						|
                # "nsight",
 | 
						|
                # old java
 | 
						|
                # "nvvp",
 | 
						|
                # "cuda-gdb-src"
 | 
						|
            }
 | 
						|
 | 
						|
            name = el.get("name")
 | 
						|
 | 
						|
            # trim leading CUDA and trailing version
 | 
						|
            if name.startswith("CUDA"):
 | 
						|
                name2 = ' '.join(name.split(" ")[1:-1])
 | 
						|
            else:
 | 
						|
                name2 = name
 | 
						|
 | 
						|
            if name2 in skip:
 | 
						|
                return
 | 
						|
 | 
						|
            # avoid having to deal with whitespaces in bash
 | 
						|
            name2 = name2.replace(" ", "_")
 | 
						|
 | 
						|
            path = ""
 | 
						|
 | 
						|
            print(f"{ind * (level + 0) * indent}if ! has {name2} \"${{SKIP_COMPONENTS[@]}}\"; then # \"{name}\"")
 | 
						|
 | 
						|
            # output attributes from unhandled tags
 | 
						|
            for child in el:
 | 
						|
                if child.tag == "package":
 | 
						|
                    continue
 | 
						|
                if child.tag == "file":
 | 
						|
                    continue
 | 
						|
                if child.tag == "desktopFile":
 | 
						|
                    continue
 | 
						|
                if child.tag == "pcfile":
 | 
						|
                    continue
 | 
						|
                for attrib in child.attrib:
 | 
						|
                    print(f"{child.tag} {attrib}={child.attrib[attrib]}")
 | 
						|
 | 
						|
            # <name>CUDA Installer</name>
 | 
						|
            # only for CUDA Installer
 | 
						|
            # unused
 | 
						|
            for node in el.findall("./name"):
 | 
						|
                print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <type>compiler</type>
 | 
						|
            # category. We use the package name instead.
 | 
						|
            # unused
 | 
						|
            # for node in el.findall("./type"):
 | 
						|
            #     print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <priority>1</priority>
 | 
						|
            # probably sorting for the tui installer
 | 
						|
            # unused
 | 
						|
            # for node in el.findall("./priority"):
 | 
						|
            #     print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <single-selection/>
 | 
						|
            # proably for tui installer
 | 
						|
            # unused
 | 
						|
            # for node in el.findall("./single-selection"):
 | 
						|
            #     print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <koversion>2.24.2</koversion>
 | 
						|
            # version of the installed kernel object (Kernel Objects only)
 | 
						|
            # unused
 | 
						|
            for node in el.findall("./koversion"):
 | 
						|
                print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <installPath>/usr/local/cuda-12.8</installPath>
 | 
						|
            # overrides the install location
 | 
						|
            # unused
 | 
						|
            # for node in el.findall("./installPath"):
 | 
						|
            #     print(f"{ind * (level + 1) * indent}# {node.tag}: \"{node.text}\"")
 | 
						|
 | 
						|
            # <buildPath>./builds/cuda_cccl/</buildPath>
 | 
						|
            # path where the package files are found
 | 
						|
            # we cd into it
 | 
						|
            for node in el.findall("./buildPath"):
 | 
						|
                path = node.text.removeprefix('./')
 | 
						|
                print(f"{ind * (level + 1) * indent}cd \"${{S}}/{path}\" || die \"cd ${{S}}/{path} failed\"")
 | 
						|
                print()
 | 
						|
 | 
						|
            # <dir>bin</dir>
 | 
						|
            # would install empty dirs
 | 
						|
            # unused
 | 
						|
            # for node in el.findall("./dir"):
 | 
						|
            #     pass
 | 
						|
 | 
						|
            # <file dir="bin/">.*</file>
 | 
						|
            # <file>targets/x86_64-linux/lib/.*\.so</file>
 | 
						|
            # regex glob of files to install.
 | 
						|
            for node in el.findall("./file"):
 | 
						|
                # unescape '.*' -> '*' & '\.' -> '.'
 | 
						|
                file = (node.text
 | 
						|
                        .replace(".*", "*")
 | 
						|
                        .replace(r"\.", ".")
 | 
						|
                        .replace("x86_64", "${narch}")
 | 
						|
                        .replace("sbsa", "${narch}")
 | 
						|
                        )
 | 
						|
 | 
						|
                # optional dir offset, we merge it into path
 | 
						|
                dir = ""
 | 
						|
                if "dir" in node.attrib:
 | 
						|
                    dir = f" \"{Path(node.attrib['dir'])}\""
 | 
						|
 | 
						|
                filepath = basedir / path / file
 | 
						|
 | 
						|
                # ignore existing symlinks ( include, lib* ) and the uninstallers
 | 
						|
                if not filepath.is_symlink() and not file.endswith("-uninstaller"):
 | 
						|
                    print(f"{ind * (level + 1) * indent}dofile \"{file}\"{dir}")
 | 
						|
 | 
						|
            # <pcfile description="CUDA Runtime Library">opencl-12.8.pc</pcfile>
 | 
						|
            # create a pkgconfig file for the given description and lib name/version
 | 
						|
            for node in el.findall("./pcfile"):
 | 
						|
                offset = node.text.rfind('-')
 | 
						|
                if offset == -1:
 | 
						|
                    raise RuntimeError(f"failed to split pcfile {node.text}")
 | 
						|
 | 
						|
                lib_name = node.text[:offset]
 | 
						|
 | 
						|
                if not node.text.endswith('.pc'):
 | 
						|
                    raise RuntimeError(f"pcfile does not end in '.pc' {node.text}")
 | 
						|
                lib_version = node.text[offset+1:-3]
 | 
						|
 | 
						|
                if "description" not in node.attrib:
 | 
						|
                    raise RuntimeError(f"no description for {node.text}")
 | 
						|
 | 
						|
                subdir = ""
 | 
						|
                if "subdir" in node.attrib:
 | 
						|
                    subdir = f" \"{node.attrib['subdir']}\""
 | 
						|
 | 
						|
                print(f"{ind * (level + 1) * indent}dopcfile "
 | 
						|
                      f"\"{lib_name}\" "
 | 
						|
                      f"\"{lib_version}\" "
 | 
						|
                      f"\"{node.attrib['description']}\"{subdir}")
 | 
						|
 | 
						|
            # <desktopFile
 | 
						|
            #  filename="nsight"
 | 
						|
            #  name="Nsight Eclipse Edition"
 | 
						|
            #  categories="Development;IDE;Debugger;ParallelComputing"
 | 
						|
            #  keywords="cuda;gpu;nvidia;debugger;"
 | 
						|
            #  iconPath="libnsight/icon.xpm"
 | 
						|
            #  execPath="bin/nsight"
 | 
						|
            #  tryExecPath="bin/nsight"
 | 
						|
            # />
 | 
						|
            # create a .desktop file
 | 
						|
            for node in el.findall("./desktopFile"):
 | 
						|
                print(f"{ind * (level + 1) * indent}dodesktopFile \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['filename']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['name']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['categories']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['keywords']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['iconPath']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['execPath']}\" \\")
 | 
						|
                print(f"{ind * (level + 2) * indent}\"{node.attrib['tryExecPath']}\"")
 | 
						|
 | 
						|
            # iterator over all nested packages
 | 
						|
            for node in el.findall("./package"):
 | 
						|
                p_package(node, level + 1)
 | 
						|
 | 
						|
            print(f"{ind * (level + 0) * indent}fi")
 | 
						|
 | 
						|
        p_package(e)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |