Deployment and lifecycle management for Nix
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cli: add use-eval-cache

+58 -96
+27 -30
apps/nix/lib/nix/eval.ex
··· 25 25 field :from, pid() 26 26 field :pid, pid() 27 27 field :ospid, integer() 28 + field :use_eval_cache, boolean() 28 29 end 29 30 30 31 def run(target, opts \\ []) ··· 34 35 end 35 36 36 37 def run(%Eval.Request{} = req, opts) do 37 - {:ok, pid} = GenServer.start_link(Eval, {req, opts}) 38 + {:ok, pid} = GenServer.start_link(Eval, {req, opts} |> dbg()) 38 39 39 40 # set a 10 hour timeout for the genserver 40 41 # if this timeout is exceeded the child process may not be killed ··· 51 52 52 53 state = 53 54 %Eval.Exec{ 55 + use_eval_cache: Keyword.get(opts, :use_eval_cache, false), 54 56 eval: %Eval{ 55 57 request: req, 56 58 memory_limit_kb: Keyword.get(opts, :memory_limit_kb, @default_memory_limit_kb) ··· 79 81 :flake -> 80 82 [ 81 83 System.find_executable("nix"), 82 - "eval", 83 - "--json", 84 - Eval.Request.to_flake_uri(state.eval.request), 85 - "--no-eval-cache", 86 - "--apply", 87 - """ 88 - x: #{expr_body} 89 - """ 90 - ] 84 + "eval" 85 + ] ++ 86 + if(state.use_eval_cache, do: [], else: ["--no-eval-cache"]) ++ 87 + [ 88 + "--json", 89 + Eval.Request.to_flake_uri(state.eval.request), 90 + "--apply", 91 + """ 92 + x: #{expr_body} 93 + """ 94 + ] 91 95 92 96 :path -> 93 97 [ 94 98 System.find_executable("nix-instantiate"), 95 99 "--eval", 96 100 "--strict", 97 - "--json", 98 - "--option", 99 - "eval-cache", 100 - "false", 101 - "--expr", 102 - """ 103 - let 104 - x = #{Eval.Request.to_import(state.eval.request)}; 105 - in 106 - #{expr_body} 107 - """ 108 - ] 101 + "--json" 102 + ] ++ 103 + if(state.use_eval_cache, do: [], else: ["--option", "eval-cache", "false"]) ++ 104 + [ 105 + "--expr", 106 + """ 107 + let 108 + x = #{Eval.Request.to_import(state.eval.request)}; 109 + in 110 + #{expr_body} 111 + """ 112 + ] 109 113 end 110 114 111 115 Logger.debug(msg: "Running command", cmd: Enum.join(cmd, " ")) ··· 147 151 mem > state.eval.memory_limit_kb -> 148 152 Logger.warning( 149 153 msg: "Memory has exceeded limit, killing", 154 + eval_id: state.eval.request.id, 150 155 ospid: state.ospid, 151 156 memory_limit_kb: state.eval.memory_limit_kb, 152 157 active_memory_kb: mem 153 158 ) 154 159 155 160 :exec.kill(state.pid, :sigterm) 156 - 157 - mem > state.eval.memory_limit_kb * 0.75 -> 158 - Logger.debug( 159 - msg: "Memory above 1/2 of limit", 160 - ospid: state.ospid, 161 - memory_limit_kb: state.eval.memory_limit_kb, 162 - active_memory_kb: mem 163 - ) 164 161 165 162 true -> 166 163 :ok
+5 -3
apps/nix/lib/nix/eval/jobs.ex
··· 14 14 field :results, list() 15 15 field :max_workers, integer() 16 16 field :memory_limit_kb, integer() 17 + field :use_eval_cache, boolean(), default: false 17 18 field :from, {pid(), term()} 18 19 field :supervisor, pid() 19 20 field :start_time, DateTime.t() ··· 52 53 results: [], 53 54 max_workers: Keyword.get(opts, :workers, 8), 54 55 memory_limit_kb: Keyword.get(opts, :memory_limit_kb, 4_000_000), 56 + use_eval_cache: Keyword.get(opts, :use_eval_cache, false), 55 57 from: nil, 56 58 request: request, 57 59 supervisor: supervisor, ··· 104 106 Enum.reduce(requests, state.running, fn request, acc_running -> 105 107 task = 106 108 Task.Supervisor.async(state.supervisor, fn -> 107 - evaluate_request(request, state.memory_limit_kb) 109 + evaluate_request(request, state) 108 110 end) 109 111 110 112 Map.put(acc_running, task.ref, request) ··· 117 119 end 118 120 119 121 # Evaluate a single request and return the result 120 - defp evaluate_request(request, memory_limit_kb) do 121 - case Nix.Eval.run(request, memory_limit_kb: memory_limit_kb) do 122 + defp evaluate_request(request, %__MODULE__{memory_limit_kb: mem, use_eval_cache: cache}) do 123 + case Nix.Eval.run(request, memory_limit_kb: mem, use_eval_cache: cache) do 122 124 {_, %{output: output} = eval} when is_map(output) or is_nil(output) -> 123 125 # This is a derivation (returns map with drvPath, outPath, meta) 124 126 {:leaf, eval}
+7 -3
apps/sower_cli/lib/sower_cli.ex
··· 68 68 help: "Enable debug logging" 69 69 ], 70 70 eval_only: [ 71 - short: "-e", 72 71 long: "--eval-only", 73 72 help: "Only evaluate, don't build" 74 73 ], ··· 81 80 short: "-s", 82 81 long: "--seed", 83 82 help: "Full pipeline: build, push, and register with server" 83 + ], 84 + use_eval_cache: [ 85 + long: "--use-eval-cache", 86 + help: 87 + "Enable evaluation caching. This is disabled by default, unlike standard commands." 84 88 ], 85 89 fail_fast: [ 86 90 short: "-f", ··· 111 115 help: "Add metadata tag (can be repeated)", 112 116 multiple: true 113 117 ], 114 - type: [ 115 - long: "--type", 118 + eval_type: [ 119 + long: "--eval-type", 116 120 value_name: "TYPE", 117 121 help: "Evaluation type: auto, flake, or path (default: auto)", 118 122 parser: &parse_nix_type/1,
+18 -32
apps/sower_cli/lib/sower_cli/build.ex
··· 15 15 16 16 typedstruct do 17 17 field :flake, String.t() 18 + field :flags, map() 18 19 field :options, map() 19 20 field :evals, [Nix.Eval.t()] 20 21 field :builds, [Nix.Build.t()] ··· 22 23 field :cache_config, map() 23 24 end 24 25 25 - @doc """ 26 - Run the build pipeline based on flags. 27 - 28 - ## Options 29 - - `:cache` - Cache URL (required for push/seed) 30 - - `:jobs` - Number of parallel workers 31 - - `:tag` - Metadata tags (for seed) 32 - - `:fail_fast` - Exit immediately if any step fails (default: false, continue with successful items) 33 - """ 34 26 def run(flake, flags, options) do 35 - steps = build_steps(flags) 36 - 37 - options = Map.put(options, :fail_fast, flags[:fail_fast] || false) 27 + steps = build_steps(flags, options) 38 28 39 29 state = %__MODULE__{ 40 30 flake: flake, 41 - options: options 31 + options: options, 32 + flags: flags 42 33 } 43 34 44 35 case validate_options(steps, options) do ··· 50 41 end 51 42 end 52 43 53 - defp build_steps(%{eval_only: true}), do: [:eval] 54 - defp build_steps(%{push: true}), do: [:eval, :build, :push] 55 - defp build_steps(%{seed: true}), do: [:eval, :build, :push, :seed] 56 - defp build_steps(_), do: [:eval, :build] 44 + defp build_steps(%{eval_only: true}, _), do: [:eval] 45 + defp build_steps(_, %{push: true}), do: [:eval, :build, :push] 46 + defp build_steps(_, %{seed: true}), do: [:eval, :build, :push, :seed] 47 + defp build_steps(_, _), do: [:eval, :build] 57 48 58 49 defp validate_options(steps, options) do 59 50 cond do ··· 61 52 Output.error("--cache is required for --push") 62 53 {:error, :missing_cache} 63 54 64 - :seed in steps and is_nil(options.cache) -> 65 - Output.error("--cache is required for --seed") 66 - {:error, :missing_cache} 67 - 68 - not is_nil(options.cache) -> 69 - case Cache.parse_url(options.cache) do 70 - {:ok, _} -> :ok 71 - {:error, msg} -> Output.error(msg) && {:error, :invalid_cache} 72 - end 55 + :seed in steps and is_nil(options.endpoint) -> 56 + Output.error("--endpoint is required for --seed") 57 + {:error, :missing_endpoint} 73 58 74 59 true -> 75 60 :ok ··· 84 69 defp run_steps([:eval | rest], %__MODULE__{} = state) do 85 70 Output.step("Evaluating #{state.flake}") 86 71 87 - workers = state.options.jobs || 8 88 - eval_type = state.options.type || :auto 89 - 90 - opts = [workers: workers, type: eval_type] 72 + opts = [ 73 + workers: state.options.jobs, 74 + type: state.options.eval_type, 75 + use_eval_cache: state.flags.use_eval_cache 76 + ] 91 77 92 78 case Nix.Eval.Jobs.run(state.flake, opts) do 93 79 {:ok, %{results: results}} -> ··· 98 84 Output.eval_summary(results) 99 85 Output.eval_errors(results) 100 86 101 - if state.options.fail_fast do 87 + if state.flags.fail_fast do 102 88 {:error, :eval_failed} 103 89 else 104 90 successful = Enum.filter(results, &(&1.status == :ok)) ··· 126 112 builds = Output.build_summary(result) 127 113 Output.build_errors(builds) 128 114 129 - if state.options.fail_fast do 115 + if state.flags.fail_fast do 130 116 {:error, :build_failed} 131 117 else 132 118 successful = Enum.filter(builds, &(&1.status == :ok))
+1 -28
apps/sower_cli/lib/sower_cli/cache.ex
··· 1 1 defmodule SowerCli.Cache do 2 2 @moduledoc """ 3 3 Cache URL parsing and backend selection. 4 - 5 - Supports auto-detection of cache backends from URL prefixes: 6 - - `attic://server:cache` -> Nix.Cache.Attic 7 - - `ssh://`, `s3://`, `file://`, `https://` -> Nix.Cache.NixCopy 8 4 """ 9 5 10 6 @doc """ ··· 17 13 18 14 iex> SowerCli.Cache.parse_url("ssh://user@host") 19 15 {:ok, {Nix.Cache.NixCopy, %{destination: "ssh://user@host"}}} 20 - 21 - iex> SowerCli.Cache.parse_url("invalid") 22 - {:error, "Unknown cache URL format: invalid"} 23 16 """ 24 17 def parse_url("attic://" <> rest) do 25 18 {:ok, {Nix.Cache.Attic, %{cache: rest}}} 26 19 end 27 20 28 - def parse_url("ssh://" <> _ = url) do 21 + def parse_url(url) do 29 22 {:ok, {Nix.Cache.NixCopy, %{destination: url}}} 30 - end 31 - 32 - def parse_url("s3://" <> _ = url) do 33 - {:ok, {Nix.Cache.NixCopy, %{destination: url}}} 34 - end 35 - 36 - def parse_url("file://" <> _ = url) do 37 - {:ok, {Nix.Cache.NixCopy, %{destination: url}}} 38 - end 39 - 40 - def parse_url("https://" <> _ = url) do 41 - {:ok, {Nix.Cache.NixCopy, %{destination: url}}} 42 - end 43 - 44 - def parse_url("http://" <> _ = url) do 45 - {:ok, {Nix.Cache.NixCopy, %{destination: url}}} 46 - end 47 - 48 - def parse_url(url) do 49 - {:error, "Unknown cache URL format: #{url}. Expected attic://, ssh://, s3://, file://, or https://"} 50 23 end 51 24 end