From 9c758fdfda2f15c20ebfa82dfd9e99092def1e79 Mon Sep 17 00:00:00 2001 From: Greg Curtis Date: Tue, 24 Sep 2024 11:57:44 -0400 Subject: [PATCH 1/3] patchpkg: patch python to use devbox CUDA libs Automatically patch python to use any `cudaPackages.*` packages that are in devbox.json. This will only work if the CUDA drivers are already installed on the host system. The patching process is: 1. When generating the patch flake, look for the system's `libcuda.so` library (installed by the driver) and copy it into the flake's directory. 2. Nix copies the flake's source directory (and therefore libcuda.so) into the nix store when building it. 3. The flake calls `devbox patch` which adds a `DT_NEEDED` entry to the python binary for `libcuda.so`. It also adds the lib directories of any other `cudaPackages.*` packages that it finds in the environment. --- internal/devpkg/package.go | 28 +- internal/patchpkg/builder.go | 81 ++++- internal/patchpkg/search.go | 96 +++++ internal/shellgen/flake_plan.go | 154 +++++++- internal/shellgen/tmpl/glibc-patch.nix.tmpl | 21 +- .../languages/python_patch_cuda.test.txt | 329 ++++++++++++++++++ 6 files changed, 679 insertions(+), 30 deletions(-) create mode 100644 testscripts/languages/python_patch_cuda.test.txt diff --git a/internal/devpkg/package.go b/internal/devpkg/package.go index da6d4332651..a9eea9a4ea9 100644 --- a/internal/devpkg/package.go +++ b/internal/devpkg/package.go @@ -171,9 +171,7 @@ func resolve(pkg *Package) error { if err != nil { return err } - - // TODO savil. Check with Greg about setting the user-specified outputs - // somehow here. + parsed.Outputs = strings.Join(pkg.outputs.selectedNames, ",") pkg.setInstallable(parsed, pkg.lockfile.ProjectDir()) return nil @@ -308,7 +306,10 @@ func (p *Package) InstallableForOutput(output string) (string, error) { // a valid flake reference parsable by ParseFlakeRef, optionally followed by an // #attrpath and/or an ^output. func (p *Package) FlakeInstallable() (flake.Installable, error) { - return flake.ParseInstallable(p.Raw) + if err := p.resolve(); err != nil { + return flake.Installable{}, err + } + return p.installable, nil } // urlForInstall is used during `nix profile install`. @@ -322,15 +323,16 @@ func (p *Package) urlForInstall() (string, error) { } func (p *Package) NormalizedDevboxPackageReference() (string, error) { - if err := p.resolve(); err != nil { + installable, err := p.FlakeInstallable() + if err != nil { return "", err } - if p.installable.AttrPath == "" { + if installable.AttrPath == "" { return "", nil } - clone := p.installable - clone.AttrPath = fmt.Sprintf("legacyPackages.%s.%s", nix.System(), clone.AttrPath) - return clone.String(), nil + installable.AttrPath = fmt.Sprintf("legacyPackages.%s.%s", nix.System(), installable.AttrPath) + installable.Outputs = "" + return installable.String(), nil } // PackageAttributePath returns the short attribute path for a package which @@ -376,11 +378,12 @@ func (p *Package) NormalizedPackageAttributePath() (string, error) { // normalizePackageAttributePath calls nix search to find the normalized attribute // path. It may be an expensive call (~100ms). func (p *Package) normalizePackageAttributePath() (string, error) { - if err := p.resolve(); err != nil { + installable, err := p.FlakeInstallable() + if err != nil { return "", err } - - query := p.installable.String() + installable.Outputs = "" + query := installable.String() if query == "" { query = p.Raw } @@ -388,7 +391,6 @@ func (p *Package) normalizePackageAttributePath() (string, error) { // We prefer nix.Search over just trying to parse the package's "URL" because // nix.Search will guarantee that the package exists for the current system. var infos map[string]*nix.Info - var err error if p.IsDevboxPackage && !p.IsRunX() { // Perf optimization: For queries of the form nixpkgs/#foo, we can // use a nix.Search cache. diff --git a/internal/patchpkg/builder.go b/internal/patchpkg/builder.go index 7a0e7ce3780..36da96c62b0 100644 --- a/internal/patchpkg/builder.go +++ b/internal/patchpkg/builder.go @@ -17,6 +17,7 @@ import ( "path" "path/filepath" "regexp" + "strings" ) //go:embed glibc-patch.bash @@ -42,6 +43,10 @@ type DerivationBuilder struct { RestoreRefs bool bytePatches map[string][]fileSlice + + // src contains the source files of the derivation. For flakes, this is + // anything in the flake.nix directory. + src *packageFS } // NewDerivationBuilder initializes a new DerivationBuilder from the current @@ -79,6 +84,9 @@ func (d *DerivationBuilder) init() error { return fmt.Errorf("patchpkg: can't patch gcc using %s: %v", d.Gcc, err) } } + if src := os.Getenv("src"); src != "" { + d.src = newPackageFS(src) + } return nil } @@ -95,6 +103,11 @@ func (d *DerivationBuilder) Build(ctx context.Context, pkgStorePath string) erro } func (d *DerivationBuilder) build(ctx context.Context, pkg, out *packageFS) error { + // Create the derivation's $out directory. + if err := d.copyDir(out, "."); err != nil { + return err + } + if d.RestoreRefs { if err := d.restoreMissingRefs(ctx, pkg); err != nil { // Don't break the flake build if we're unable to @@ -103,12 +116,19 @@ func (d *DerivationBuilder) build(ctx context.Context, pkg, out *packageFS) erro slog.ErrorContext(ctx, "unable to restore all removed refs", "err", err) } } + if err := d.findCUDA(ctx, out); err != nil { + slog.ErrorContext(ctx, "unable to patch CUDA libraries", "err", err) + } var err error for path, entry := range allFiles(pkg, ".") { if ctx.Err() != nil { return ctx.Err() } + if path == "." { + // Skip the $out directory - we already created it. + continue + } switch { case entry.IsDir(): @@ -167,7 +187,7 @@ func (d *DerivationBuilder) copyDir(out *packageFS, path string) error { if err != nil { return err } - return os.Mkdir(path, 0o777) + return os.MkdirAll(path, 0o777) } func (d *DerivationBuilder) copyFile(ctx context.Context, pkg, out *packageFS, path string) error { @@ -302,6 +322,65 @@ func (d *DerivationBuilder) findRemovedRefs(ctx context.Context, pkg *packageFS) return refs, nil } +func (d *DerivationBuilder) findCUDA(ctx context.Context, out *packageFS) error { + if d.src == nil { + return fmt.Errorf("patch flake didn't set $src to the path to its source tree") + } + + glob, err := fs.Glob(d.src, "lib/libcuda.so*") + if err != nil { + return fmt.Errorf("glob system libraries: %v", err) + } + if len(glob) != 0 { + err := d.copyDir(out, "lib") + if err != nil { + return fmt.Errorf("copy system library: %v", err) + } + } + for _, lib := range glob { + slog.DebugContext(ctx, "found system CUDA library in flake", "path", lib) + + err := d.copyFile(ctx, d.src, out, lib) + if err != nil { + return fmt.Errorf("copy system library: %v", err) + } + need, err := out.OSPath(lib) + if err != nil { + return fmt.Errorf("get absolute path to library: %v", err) + } + d.glibcPatcher.needed = append(d.glibcPatcher.needed, need) + + slog.DebugContext(ctx, "added DT_NEEDED entry for system CUDA library", "path", need) + } + + slog.DebugContext(ctx, "looking for nix CUDA libraries in $patchDependencies") + deps := os.Getenv("patchDependencies") + for _, pkg := range strings.Split(deps, " ") { + slog.DebugContext(ctx, "checking for nix CUDA libraries in package", "pkg", pkg) + + pkgFS := newPackageFS(pkg) + libs, err := fs.Glob(pkgFS, "lib*/*.so*") + if err != nil { + return fmt.Errorf("glob nix package libraries: %v", err) + } + + sonameRegexp := regexp.MustCompile(`(^|/).+\.so\.\d+`) + for _, lib := range libs { + if !sonameRegexp.MatchString(lib) { + continue + } + need, err := pkgFS.OSPath(lib) + if err != nil { + return fmt.Errorf("get absolute path to nix package library: %v", err) + } + d.glibcPatcher.needed = append(d.glibcPatcher.needed, need) + + slog.DebugContext(ctx, "added DT_NEEDED entry for nix CUDA library", "path", need) + } + } + return nil +} + // packageFS is the tree of files for a package in the Nix store. type packageFS struct { fs.FS diff --git a/internal/patchpkg/search.go b/internal/patchpkg/search.go index a9667c27c9a..db4a3a71978 100644 --- a/internal/patchpkg/search.go +++ b/internal/patchpkg/search.go @@ -4,7 +4,9 @@ import ( "fmt" "io" "io/fs" + "iter" "os" + "path/filepath" "regexp" "strings" "sync" @@ -80,3 +82,97 @@ func searchEnv(re *regexp.Regexp) string { } return "" } + +// SystemCUDALibraries returns an iterator over the system CUDA library paths. +// It yields them in priority order, where the first path is the most likely to +// be the correct version. +var SystemCUDALibraries iter.Seq[string] = func(yield func(string) bool) { + // Quick overview of Unix-like shared object versioning. + // + // Libraries have 3 different names (using libcuda as an example): + // + // 1. libcuda.so - the "linker name". Typically a symlink pointing to + // the soname. The compiler looks for this name. + // 2. libcuda.so.1 - the "soname". Typically a symlink pointing to the + // real name. The dynamic linker looks for this name. + // 3. libcuda.so.550.107.02 - the "real name". The actual ELF shared + // library. Usually never referred to directly because that would + // make versioning hard. + // + // Because we don't know what version of CUDA the user's program + // actually needs, we're going to try to find linker names (libcuda.so) + // and trust that the system is pointing it to the correct version. + // We'll fall back to sonames (libcuda.so.1) that we find if none of the + // linker names work. + + // Common direct paths to try first. + linkerNames := []string{ + "/usr/lib/x86_64-linux-gnu/libcuda.so", // Debian + "/usr/lib64/libcuda.so", // Red Hat + "/usr/lib/libcuda.so", + } + for _, path := range linkerNames { + // Return what the link is pointing to because the dynamic + // linker will want libcuda.so.1, not libcuda.so. + soname, err := os.Readlink(path) + if err != nil { + continue + } + if filepath.IsLocal(soname) { + soname = filepath.Join(filepath.Dir(path), soname) + } + if !yield(soname) { + return + } + } + + // Directories to recursively search. + prefixes := []string{ + "/usr/lib", + "/usr/lib64", + "/lib", + "/lib64", + "/usr/local/lib", + "/usr/local/lib64", + "/opt/cuda", + "/opt/nvidia", + "/usr/local/cuda", + "/usr/local/nvidia", + } + sonameRegex := regexp.MustCompile(`^libcuda\.so\.\d+$`) + var sonames []string + for _, path := range prefixes { + _ = filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return nil + } + if entry.Name() == "libcuda.so" && isSymlink(entry.Type()) { + soname, err := os.Readlink(path) + if err != nil { + return nil + } + if filepath.IsLocal(soname) { + soname = filepath.Join(filepath.Dir(path), soname) + } + if !yield(soname) { + return filepath.SkipAll + } + } + + // Save potential soname matches for later after we've + // exhausted all the potential linker names. + if sonameRegex.MatchString(entry.Name()) { + sonames = append(sonames, entry.Name()) + } + return nil + }) + } + + // We didn't find any symlinks named libcuda.so. Fall back to trying any + // sonames (e.g., libcuda.so.1) that we found. + for _, path := range sonames { + if !yield(path) { + return + } + } +} diff --git a/internal/shellgen/flake_plan.go b/internal/shellgen/flake_plan.go index ca529db48de..5a4d1269a10 100644 --- a/internal/shellgen/flake_plan.go +++ b/internal/shellgen/flake_plan.go @@ -3,13 +3,18 @@ package shellgen import ( "context" "fmt" + "io" + "log/slog" "os" "path/filepath" "runtime/trace" + "slices" "strings" "go.jetpack.io/devbox/internal/devpkg" "go.jetpack.io/devbox/internal/nix" + "go.jetpack.io/devbox/internal/patchpkg" + "go.jetpack.io/devbox/nix/flake" ) // flakePlan contains the data to populate the top level flake.nix file @@ -87,6 +92,12 @@ type glibcPatchFlake struct { Outputs struct { Packages map[string]map[string]string } + + // Dependencies is set of extra packages that are dependencies of the + // patched packages. For example, a patched Python interpreter might + // need CUDA packages, but the CUDA packages themselves don't need + // patching. + Dependencies []string } func newGlibcPatchFlake(nixpkgsGlibcRev string, packages []*devpkg.Package) (glibcPatchFlake, error) { @@ -106,45 +117,125 @@ func newGlibcPatchFlake(nixpkgsGlibcRev string, packages []*devpkg.Package) (gli NixpkgsGlibcFlakeRef: "flake:nixpkgs/" + nixpkgsGlibcRev, } for _, pkg := range packages { + // Check to see if this is a CUDA package. If so, we need to add + // it to the flake dependencies so that we can patch other + // packages to reference it (like Python). + relAttrPath, err := flake.systemRelativeAttrPath(pkg) + if err != nil { + return glibcPatchFlake{}, err + } + if strings.HasPrefix(relAttrPath, "cudaPackages") { + if err := flake.addDependency(pkg); err != nil { + return glibcPatchFlake{}, err + } + } + if !pkg.Patch { continue } - - err := flake.addPackageOutput(pkg) - if err != nil { + if err := flake.addOutput(pkg); err != nil { return glibcPatchFlake{}, err } } return flake, nil } -func (g *glibcPatchFlake) addPackageOutput(pkg *devpkg.Package) error { +// addInput adds a flake input that provides pkg. +func (g *glibcPatchFlake) addInput(pkg *devpkg.Package) error { if g.Inputs == nil { g.Inputs = make(map[string]string) } + installable, err := pkg.FlakeInstallable() + if err != nil { + return err + } inputName := pkg.FlakeInputName() - g.Inputs[inputName] = pkg.URLForFlakeInput() + g.Inputs[inputName] = installable.Ref.String() + return nil +} - attrPath, err := pkg.FullPackageAttributePath() - if err != nil { +// addOutput adds a flake output that provides the patched version of pkg. +func (g *glibcPatchFlake) addOutput(pkg *devpkg.Package) error { + if err := g.addInput(pkg); err != nil { return err } - // Remove the legacyPackages. prefix. - outputName := strings.SplitN(attrPath, ".", 3)[2] + relAttrPath, err := g.systemRelativeAttrPath(pkg) + if err != nil { + return err + } if g.Outputs.Packages == nil { g.Outputs.Packages = map[string]map[string]string{nix.System(): {}} } if cached, err := pkg.IsInBinaryCache(); err == nil && cached { if expr, err := g.fetchClosureExpr(pkg); err == nil { - g.Outputs.Packages[nix.System()][outputName] = expr + g.Outputs.Packages[nix.System()][relAttrPath] = expr return nil } } - g.Outputs.Packages[nix.System()][outputName] = strings.Join([]string{"pkgs", inputName, nix.System(), outputName}, ".") + + inputAttrPath, err := g.inputRelativeAttrPath(pkg) + if err != nil { + return err + } + g.Outputs.Packages[nix.System()][relAttrPath] = inputAttrPath return nil } +// addDependency adds pkg to the derivation's patchDependencies attribute, +// making it available at patch build-time. +func (g *glibcPatchFlake) addDependency(pkg *devpkg.Package) error { + if err := g.addInput(pkg); err != nil { + return err + } + inputAttrPath, err := g.inputRelativeAttrPath(pkg) + if err != nil { + return err + } + + installable, err := pkg.FlakeInstallable() + if err != nil { + return err + } + switch installable.Outputs { + case flake.DefaultOutputs: + expr := "selectDefaultOutputs " + inputAttrPath + g.Dependencies = append(g.Dependencies, expr) + case flake.AllOutputs: + expr := "selectAllOutputs " + inputAttrPath + g.Dependencies = append(g.Dependencies, expr) + default: + expr := fmt.Sprintf("selectOutputs %s %q", inputAttrPath, installable.SplitOutputs()) + g.Dependencies = append(g.Dependencies, expr) + } + return nil +} + +// systemRelativeAttrPath strips any leading "legacyPackages." prefix +// from a package's attribute path. +func (g *glibcPatchFlake) systemRelativeAttrPath(pkg *devpkg.Package) (string, error) { + installable, err := pkg.FlakeInstallable() + if err != nil { + return "", err + } + if strings.HasPrefix(installable.AttrPath, "legacyPackages") { + // Remove the legacyPackages. prefix. + return strings.SplitN(installable.AttrPath, ".", 3)[2], nil + } + return installable.AttrPath, nil +} + +// inputRelativeAttrPath joins the package's corresponding flake input with its +// attribute path. +func (g *glibcPatchFlake) inputRelativeAttrPath(pkg *devpkg.Package) (string, error) { + relAttrPath, err := g.systemRelativeAttrPath(pkg) + if err != nil { + return "", err + } + atrrPath := strings.Join([]string{"pkgs", pkg.FlakeInputName(), nix.System(), relAttrPath}, ".") + return atrrPath, nil +} + // TODO: this only handles the first store path, but we should handle all of them func (g *glibcPatchFlake) fetchClosureExpr(pkg *devpkg.Package) (string, error) { storePaths, err := pkg.InputAddressedPaths() @@ -162,5 +253,46 @@ func (g *glibcPatchFlake) fetchClosureExpr(pkg *devpkg.Package) (string, error) } func (g *glibcPatchFlake) writeTo(dir string) error { + wantCUDA := slices.ContainsFunc(g.Dependencies, func(dep string) bool { + return strings.Contains(dep, "cudaPackages") + }) + if wantCUDA { + slog.Debug("found CUDA package in devbox environment, attempting to find system driver libraries") + + libDir := filepath.Join(dir, "lib") + if err := os.MkdirAll(libDir, 0o755); err != nil { + return err + } + + // Look for the system's CUDA driver library and copy it into + // the flake directory. + for lib := range patchpkg.SystemCUDALibraries { + slog.Debug("found system CUDA library", "path", lib) + + src, err := os.Open(lib) + if err != nil { + slog.Error("can't open system CUDA library, searching for another", "err", err) + continue + } + defer src.Close() + + dst, err := os.Create(filepath.Join(libDir, filepath.Base(lib))) + if err != nil { + slog.Error("can't create copy of system CUDA library in flake directory", "err", err) + break + } + defer dst.Close() + + _, err = io.Copy(dst, src) + if err != nil { + slog.Error("can't copy system CUDA library, searching for another", "err", err) + continue + } + if err := dst.Close(); err == nil { + slog.Debug("copied system CUDA library to flake directory", "src", src.Name(), "dst", dst.Name()) + break + } + } + } return writeFromTemplate(dir, g, "glibc-patch.nix", "flake.nix") } diff --git a/internal/shellgen/tmpl/glibc-patch.nix.tmpl b/internal/shellgen/tmpl/glibc-patch.nix.tmpl index 51bb2176d4e..f68157eb462 100644 --- a/internal/shellgen/tmpl/glibc-patch.nix.tmpl +++ b/internal/shellgen/tmpl/glibc-patch.nix.tmpl @@ -15,7 +15,6 @@ }; outputs = args@{ self, local-devbox, nixpkgs-glibc {{- range $name, $_ := .Inputs -}}, {{ $name }} {{- end }} }: - {{ with .Outputs -}} let # Initialize each nixpkgs input into a new attribute set with the # schema "pkgs...". @@ -24,7 +23,7 @@ pkgs = builtins.mapAttrs (name: flake: if builtins.hasAttr "legacyPackages" flake then { - {{- range $system, $_ := .Packages }} + {{- range $system, $_ := .Outputs.Packages }} {{ $system }} = (import flake { system = "{{ $system }}"; config.allowUnfree = true; @@ -34,9 +33,19 @@ } else null) args; + selectDefaultOutputs = drv: selectOutputs drv (drv.meta.outputsToInstall or [ drv.out ]); + selectAllOutputs = drv: drv.all; + selectOutputs = drv: builtins.map (output: drv.${output}); + + patchDependencies = [ + {{- range .Dependencies }} + ({{ . }}) + {{- end }} + ]; + patchGlibc = pkg: derivation rec { - # The package we're patching. - inherit pkg; + # The package we're patching and any dependencies the patch needs. + inherit pkg patchDependencies; # Keep the name the same as the package we're patching so that the # length of the store path doesn't change. Otherwise patching binaries @@ -95,6 +104,7 @@ }; DEVBOX_DEBUG = 1; + src = self; builder = "${devbox}/bin/devbox"; args = [ "patch" "--restore-refs" ] ++ (if glibc != null then [ "--glibc" "${glibc}" ] else [ ]) ++ @@ -103,6 +113,7 @@ }; in { + {{- with .Outputs }} packages = { {{- range $system, $packages := .Packages }} {{ $system }} = { @@ -118,6 +129,6 @@ {{ $system }} = nixpkgs-glibc.legacyPackages.{{ $system }}.nixpkgs-fmt; {{- end }} }; + {{- end }} }; - {{- end }} } diff --git a/testscripts/languages/python_patch_cuda.test.txt b/testscripts/languages/python_patch_cuda.test.txt new file mode 100644 index 00000000000..7d2bd209a1c --- /dev/null +++ b/testscripts/languages/python_patch_cuda.test.txt @@ -0,0 +1,329 @@ +# Python Patch CUDA Test +# +# Install TensorFlow as a binary wheel using pip and verify that it's able to +# use CUDA. +# +# Nix is unable to install the CUDA driver and driver library (libcuda.so) on +# non-NixOS distros. Even when they're installed via the system's package +# manager, Nix-built binaries are unable to find them. +# +# Devbox attempts to find those system libraries, copy them to the Nix store, +# and patch the DT_NEEDED section of the Python binary so it can find them. + +[!env:DEVBOX_RUN_FAILING_TESTS] skip 'this test requires a CUDA-enabled GPU' + +exec devbox install + +# pip install tensorflow +exec devbox run venv -- pip install tf_nightly==2.18.0.dev20240910 +stdout 'Successfully installed.* tf_nightly-2.18.0.dev20240910' + +# run a python test script that prints the tensorflow devices and check that a +# GPU is found. +exec devbox run -e LD_DEBUG=files,libs,versions -e LD_DEBUG_OUTPUT=lddebug venv -- python main.py +stdout 'TensorFlow Version: 2.18.0-dev20240910' +stdout 'CUDA Built with: 12.3.2' +stdout 'cuDNN Built with: 9' +stdout 'Device: /device:GPU:\d+' +! stderr 'libstdc\+\+\.so\.6: cannot open shared object file: No such file or directory' +! stderr 'Could not find cuda drivers on your machine, GPU will not be used.' + +-- main.py -- +import tensorflow as tf + +print("TensorFlow Version:", tf.__version__) +print("CUDA Built with:", tf.sysconfig.get_build_info()["cuda_version"]) +print("cuDNN Built with:", tf.sysconfig.get_build_info()["cudnn_version"]) + +from tensorflow.python.client import device_lib + +for device in device_lib.list_local_devices(): + if device.device_type == 'GPU': + print(f"Device: {device.name}") + print(f" Type: {device.device_type}") + print(f" Memory Limit: {device.memory_limit / (1024**3):.2f} GB") + print(f" Description: {device.physical_device_desc}\n") + +-- devbox.json -- +{ + "packages": { + "python": "latest", + "cudaPackages.cudatoolkit": "latest", + "cudaPackages.cuda_cudart": {"version": "latest", "outputs": ["lib"]}, + "cudaPackages.cudnn": {"version": "latest", "outputs": ["lib"]}, + "cudaPackages.libcublas": {"version": "latest", "outputs": ["lib"]} + }, + "env": { + "PIP_DISABLE_PIP_VERSION_CHECK": "1", + "PIP_NO_CACHE_DIR": "1", + "PIP_NO_INPUT": "1", + "PIP_NO_PYTHON_VERSION_WARNING": "1", + "PIP_ONLY_BINARY": "tf_nightly", + "PIP_PROGRESS_BAR": "off", + "PIP_REQUIRE_VIRTUALENV": "1", + "PIP_ROOT_USER_ACTION": "ignore", + "TF_CPP_MIN_LOG_LEVEL": "0" + }, + "shell": { + "scripts": { + "venv": ". $VENV_DIR/bin/activate && \"$@\"" + } + } +} + + +-- devbox.lock -- +{ + "lockfile_version": "1", + "packages": { + "cudaPackages.cuda_cudart@latest": { + "last_modified": "2024-09-20T05:11:28Z", + "resolved": "github:NixOS/nixpkgs/79454ee9aacc9714653a4e7eb2a52b717728caff#cudaPackages.cuda_cudart", + "source": "devbox-search", + "version": "12.4.99", + "systems": { + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/iq0qdg5810zvhr490wlzpbj6nqj7v3w9-cuda_cudart-12.4.99", + "default": true + }, + { + "name": "stubs", + "path": "/nix/store/p3dyhp4wbizigkhz19shf74yv1q02pnz-cuda_cudart-12.4.99-stubs" + }, + { + "name": "dev", + "path": "/nix/store/fi1rpc8qym035bx0sfm1avpfmiwflvf0-cuda_cudart-12.4.99-dev" + }, + { + "name": "lib", + "path": "/nix/store/m2ziw7vwj9wjw6ms0c929qqvmw2040hb-cuda_cudart-12.4.99-lib" + }, + { + "name": "static", + "path": "/nix/store/8k0mfk5530xsd6ln1pvpdlskfvkbijzn-cuda_cudart-12.4.99-static" + } + ], + "store_path": "/nix/store/iq0qdg5810zvhr490wlzpbj6nqj7v3w9-cuda_cudart-12.4.99" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/lss47wk7xb7fx2p80z4hfzra1awrhizf-cuda_cudart-12.4.99", + "default": true + }, + { + "name": "stubs", + "path": "/nix/store/p6ca6q92i6fnj9gl9cqj81v1v2r0svix-cuda_cudart-12.4.99-stubs" + }, + { + "name": "dev", + "path": "/nix/store/38aycjz9r5y1fdn7wy89jcyinag1qb1p-cuda_cudart-12.4.99-dev" + }, + { + "name": "lib", + "path": "/nix/store/jzp0hpr9avl6i7gkx19dz59xirp0q7m2-cuda_cudart-12.4.99-lib" + }, + { + "name": "static", + "path": "/nix/store/w86y50a4m33l57aw2a6acprc1m2ynpm8-cuda_cudart-12.4.99-static" + } + ], + "store_path": "/nix/store/lss47wk7xb7fx2p80z4hfzra1awrhizf-cuda_cudart-12.4.99" + } + } + }, + "cudaPackages.cudatoolkit@latest": { + "last_modified": "2024-09-10T15:01:03Z", + "resolved": "github:NixOS/nixpkgs/5ed627539ac84809c78b2dd6d26a5cebeb5ae269#cudaPackages.cudatoolkit", + "source": "devbox-search", + "version": "12.4", + "systems": { + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/p2dga6q4kclqrg8fbppm8v0swi9438dd-cuda-merged-12.4", + "default": true + } + ], + "store_path": "/nix/store/p2dga6q4kclqrg8fbppm8v0swi9438dd-cuda-merged-12.4" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/6y5smq0gvqvwsarlmqnn7x6w40098yg6-cuda-merged-12.4", + "default": true + } + ], + "store_path": "/nix/store/6y5smq0gvqvwsarlmqnn7x6w40098yg6-cuda-merged-12.4" + } + } + }, + "cudaPackages.cudnn@latest": { + "last_modified": "2024-09-19T11:39:46Z", + "resolved": "github:NixOS/nixpkgs/268bb5090a3c6ac5e1615b38542a868b52ef8088#cudaPackages.cudnn", + "source": "devbox-search", + "version": "9.3.0.75", + "systems": { + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/xxissjyczq2dvb1cwars5dygny0701a1-cudnn-9.3.0.75", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/5fgcg8yaz5c5bdkgh52h4kqf5i76x16v-cudnn-9.3.0.75-dev" + }, + { + "name": "lib", + "path": "/nix/store/x36hqzpz3q28rbmrd45l5llag9nibn25-cudnn-9.3.0.75-lib" + }, + { + "name": "static", + "path": "/nix/store/f9v3gjzc861jw48lqilgysj9clmiab3b-cudnn-9.3.0.75-static" + } + ], + "store_path": "/nix/store/xxissjyczq2dvb1cwars5dygny0701a1-cudnn-9.3.0.75" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/kb14br97pimdmx43xdkaqdlxj7gih2ap-cudnn-9.3.0.75", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/v94m7vd9wczw72gnwvkx7iqvdqq5wmjb-cudnn-9.3.0.75-dev" + }, + { + "name": "lib", + "path": "/nix/store/gzh9l8q8v35lvgp8ywmbna9njz5zw2k8-cudnn-9.3.0.75-lib" + }, + { + "name": "static", + "path": "/nix/store/3c5hc4q6gaf99pgsrc6n4ylzg4k2c2nn-cudnn-9.3.0.75-static" + } + ], + "store_path": "/nix/store/kb14br97pimdmx43xdkaqdlxj7gih2ap-cudnn-9.3.0.75" + } + } + }, + "cudaPackages.libcublas@latest": { + "last_modified": "2024-09-20T22:35:44Z", + "resolved": "github:NixOS/nixpkgs/a1d92660c6b3b7c26fb883500a80ea9d33321be2#cudaPackages.libcublas", + "source": "devbox-search", + "version": "12.4.2.65", + "systems": { + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/3744yg0pfywwz0l4n5n9hhsg0h61i3j9-libcublas-12.4.2.65", + "default": true + }, + { + "name": "dev", + "path": "/nix/store/ysmhl62cdag39gvm612vv6cb7aph5var-libcublas-12.4.2.65-dev" + }, + { + "name": "lib", + "path": "/nix/store/g59p0ml3v53bzag7qzfndhxjvjyscvqr-libcublas-12.4.2.65-lib" + }, + { + "name": "static", + "path": "/nix/store/gm716qm8m7syh2gbcrv7k3qjyjj4gyal-libcublas-12.4.2.65-static" + } + ], + "store_path": "/nix/store/3744yg0pfywwz0l4n5n9hhsg0h61i3j9-libcublas-12.4.2.65" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/zk22yb9jdhnzvfyar58390bq2kimxm41-libcublas-12.4.2.65", + "default": true + }, + { + "name": "static", + "path": "/nix/store/f4jdxn797sr8bwcxb3q21rrwxb5g784c-libcublas-12.4.2.65-static" + }, + { + "name": "dev", + "path": "/nix/store/8s8a7ilxi8h9yamq63ddsmjkhn5jnf5n-libcublas-12.4.2.65-dev" + }, + { + "name": "lib", + "path": "/nix/store/gj56jqxsgmvzgf9kdnbhciw5p48h78lb-libcublas-12.4.2.65-lib" + } + ], + "store_path": "/nix/store/zk22yb9jdhnzvfyar58390bq2kimxm41-libcublas-12.4.2.65" + } + } + }, + "python@latest": { + "last_modified": "2024-08-31T10:12:23Z", + "plugin_version": "0.0.4", + "resolved": "github:NixOS/nixpkgs/5629520edecb69630a3f4d17d3d33fc96c13f6fe#python3", + "source": "devbox-search", + "version": "3.12.5", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/75j38g8ii1nqkmpf6sdlj3s5dyah3gas-python3-3.12.5", + "default": true + } + ], + "store_path": "/nix/store/75j38g8ii1nqkmpf6sdlj3s5dyah3gas-python3-3.12.5" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/ajjwc8k8sk3ksrl3dq4fsg83m1j8n8s3-python3-3.12.5", + "default": true + }, + { + "name": "debug", + "path": "/nix/store/9qvs74485a1v5255w2ps0xf4rxww6w89-python3-3.12.5-debug" + } + ], + "store_path": "/nix/store/ajjwc8k8sk3ksrl3dq4fsg83m1j8n8s3-python3-3.12.5" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/rv3rj95fxv57c7qwgl43qa7n0fabdy0a-python3-3.12.5", + "default": true + } + ], + "store_path": "/nix/store/rv3rj95fxv57c7qwgl43qa7n0fabdy0a-python3-3.12.5" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/pgb120fb7srbh418v4i2a70aq1w9dawd-python3-3.12.5", + "default": true + }, + { + "name": "debug", + "path": "/nix/store/4ws5lqhgsxdpfb924n49ma6ll7i8x0hf-python3-3.12.5-debug" + } + ], + "store_path": "/nix/store/pgb120fb7srbh418v4i2a70aq1w9dawd-python3-3.12.5" + } + } + } + } +} From de17905a3511a1f07c035875b7b9a35f41360fe6 Mon Sep 17 00:00:00 2001 From: Greg Curtis Date: Thu, 26 Sep 2024 17:16:29 -0400 Subject: [PATCH 2/3] add comments for selectOutputs --- internal/shellgen/tmpl/glibc-patch.nix.tmpl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/shellgen/tmpl/glibc-patch.nix.tmpl b/internal/shellgen/tmpl/glibc-patch.nix.tmpl index f68157eb462..4d0df7aa04e 100644 --- a/internal/shellgen/tmpl/glibc-patch.nix.tmpl +++ b/internal/shellgen/tmpl/glibc-patch.nix.tmpl @@ -33,8 +33,17 @@ } else null) args; + # selectDefaultOutputs takes a derivation and returns a list of its + # default outputs. selectDefaultOutputs = drv: selectOutputs drv (drv.meta.outputsToInstall or [ drv.out ]); + + # selectAllOutputs takes a derivation and returns all of its outputs (^*). selectAllOutputs = drv: drv.all; + + # selectOutputs takes a derivation and a list of output names, and returns + # those outputs. + # + # Example: selectOutputs nixpkgs#foo [ "out", "lib" ] selectOutputs = drv: builtins.map (output: drv.${output}); patchDependencies = [ From 490bfe58ea3b602744b1d5898386982b3ff4d431 Mon Sep 17 00:00:00 2001 From: Greg Curtis Date: Thu, 26 Sep 2024 17:19:40 -0400 Subject: [PATCH 3/3] bail if patchDependencies is empty --- internal/patchpkg/builder.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/patchpkg/builder.go b/internal/patchpkg/builder.go index 36da96c62b0..90c8d7baed0 100644 --- a/internal/patchpkg/builder.go +++ b/internal/patchpkg/builder.go @@ -353,10 +353,14 @@ func (d *DerivationBuilder) findCUDA(ctx context.Context, out *packageFS) error slog.DebugContext(ctx, "added DT_NEEDED entry for system CUDA library", "path", need) } - slog.DebugContext(ctx, "looking for nix CUDA libraries in $patchDependencies") + slog.DebugContext(ctx, "looking for nix libraries in $patchDependencies") deps := os.Getenv("patchDependencies") + if strings.TrimSpace(deps) == "" { + slog.DebugContext(ctx, "$patchDependencies is empty") + return nil + } for _, pkg := range strings.Split(deps, " ") { - slog.DebugContext(ctx, "checking for nix CUDA libraries in package", "pkg", pkg) + slog.DebugContext(ctx, "checking for nix libraries in package", "pkg", pkg) pkgFS := newPackageFS(pkg) libs, err := fs.Glob(pkgFS, "lib*/*.so*") @@ -375,7 +379,7 @@ func (d *DerivationBuilder) findCUDA(ctx context.Context, out *packageFS) error } d.glibcPatcher.needed = append(d.glibcPatcher.needed, need) - slog.DebugContext(ctx, "added DT_NEEDED entry for nix CUDA library", "path", need) + slog.DebugContext(ctx, "added DT_NEEDED entry for nix library", "path", need) } } return nil