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..90c8d7baed0 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,69 @@ 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 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 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 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..4d0df7aa04e 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,28 @@ } 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 = [ + {{- 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 +113,7 @@ }; DEVBOX_DEBUG = 1; + src = self; builder = "${devbox}/bin/devbox"; args = [ "patch" "--restore-refs" ] ++ (if glibc != null then [ "--glibc" "${glibc}" ] else [ ]) ++ @@ -103,6 +122,7 @@ }; in { + {{- with .Outputs }} packages = { {{- range $system, $packages := .Packages }} {{ $system }} = { @@ -118,6 +138,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" + } + } + } + } +}