github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bob/nix-builder/nix_builder.go (about) 1 package nixbuilder 2 3 import ( 4 "fmt" 5 6 "github.com/benchkram/bob/pkg/envutil" 7 "github.com/benchkram/errz" 8 9 "github.com/benchkram/bob/bob/bobfile" 10 "github.com/benchkram/bob/pkg/nix" 11 "github.com/benchkram/bob/pkg/usererror" 12 ) 13 14 // NB acts as a wrapper for github.com/benchkram/bob/pkg/nix package 15 // and is used for building tasks dependencies 16 type NB struct { 17 // cache allows caching the dependency to store path 18 cache *nix.Cache 19 20 // shellCache allows caching of the nix-shell --command='env' output 21 shellCache *nix.ShellCache 22 23 // envStore is filled by NixBuilder with the environment 24 // used by tasks. 25 envStore envutil.Store 26 } 27 28 type NixOption func(n *NB) 29 30 func WithCache(cache *nix.Cache) NixOption { 31 return func(n *NB) { 32 n.cache = cache 33 } 34 } 35 36 func WithShellCache(cache *nix.ShellCache) NixOption { 37 return func(n *NB) { 38 n.shellCache = cache 39 } 40 } 41 42 func WithEnvironmentStore(store envutil.Store) NixOption { 43 return func(n *NB) { 44 n.envStore = store 45 } 46 } 47 48 // NewNB instantiates a new Nix builder instance 49 func New(opts ...NixOption) *NB { 50 n := &NB{ 51 envStore: envutil.NewStore(), 52 } 53 54 for _, opt := range opts { 55 if opt == nil { 56 continue 57 } 58 opt(n) 59 } 60 61 return n 62 } 63 64 func (n *NB) EnvStore() envutil.Store { 65 return n.envStore 66 } 67 68 // BuildNixDependenciesInPipeline collects and builds nix-dependencies for a pipeline starting at taskName. 69 func (n *NB) BuildNixDependenciesInPipeline(ag *bobfile.Bobfile, taskName string) (err error) { 70 defer errz.Recover(&err) 71 72 if !nix.IsInstalled() { 73 return usererror.Wrap(fmt.Errorf("nix is not installed on your system. Get it from %s", nix.DownloadURl())) 74 } 75 76 tasksInPipeline, err := ag.BTasks.CollectTasksInPipeline(taskName) 77 errz.Fatal(err) 78 79 return n.BuildNixDependencies(ag, tasksInPipeline, []string{}) 80 } 81 82 // BuildNixDependencies builds nix dependencies and prepares the affected tasks 83 // by setting the store paths on each task in the given aggregate. 84 func (n *NB) BuildNixDependencies(ag *bobfile.Bobfile, buildTasksInPipeline, runTasksInPipeline []string) (err error) { 85 defer errz.Recover(&err) 86 87 if !nix.IsInstalled() { 88 return usererror.Wrap(fmt.Errorf("nix is not installed on your system. Get it from %s", nix.DownloadURl())) 89 } 90 91 // Resolve nix storePaths from dependencies 92 // and rewrite the affected tasks. 93 for _, name := range buildTasksInPipeline { 94 t := ag.BTasks[name] 95 96 // construct used dependencies for this task 97 var deps []nix.Dependency 98 deps = append(deps, t.Dependencies()...) 99 deps = nix.UniqueDeps(deps) 100 101 t.SetNixpkgs(ag.Nixpkgs) 102 103 hash, err := nix.HashDependencies(deps) 104 errz.Fatal(err) 105 106 if _, ok := n.envStore[envutil.Hash(hash)]; !ok { 107 nixShellEnv, err := n.BuildEnvironment(deps, ag.Nixpkgs) 108 errz.Fatal(err) 109 n.envStore[envutil.Hash(hash)] = nixShellEnv 110 } 111 t.SetEnvID(envutil.Hash(hash)) 112 113 ag.BTasks[name] = t 114 } 115 116 // FIXME: environment cache is a workaround... 117 // either use envSTore and adapt run tasks to use ist as well 118 // or remove run tasks entirely. 119 environmentCache := make(map[string][]string) 120 for _, name := range runTasksInPipeline { 121 t := ag.RTasks[name] 122 123 // construct used dependencies for this task 124 var deps []nix.Dependency 125 deps = append(deps, t.Dependencies()...) 126 deps = nix.UniqueDeps(deps) 127 128 t.SetNixpkgs(ag.Nixpkgs) 129 130 hash, err := nix.HashDependencies(deps) 131 errz.Fatal(err) 132 133 if _, ok := environmentCache[hash]; !ok { 134 nixShellEnv, err := n.BuildEnvironment(deps, ag.Nixpkgs) 135 errz.Fatal(err) 136 environmentCache[hash] = nixShellEnv 137 } 138 t.SetEnv(envutil.Merge(environmentCache[hash], t.Env())) 139 140 ag.RTasks[name] = t 141 } 142 143 return nil 144 } 145 146 // BuildDependencies builds the list of all nix deps 147 func (n *NB) BuildDependencies(deps []nix.Dependency) error { 148 return nix.BuildDependencies(deps, n.cache) 149 } 150 151 // BuildEnvironment builds the environment with all nix deps 152 func (n *NB) BuildEnvironment(deps []nix.Dependency, nixpkgs string) (_ []string, err error) { 153 return nix.BuildEnvironment(deps, nixpkgs, n.cache, n.shellCache) 154 } 155 156 // Clean removes all cached nix dependencies 157 func (n *NB) Clean() (err error) { 158 return n.cache.Clean() 159 } 160 161 // CleanNixShellCache removes all cached nix-shell --command='env' output 162 func (n *NB) CleanNixShellCache() (err error) { 163 if n.shellCache == nil { 164 return nil 165 } 166 return n.shellCache.Clean() 167 }