├── LICENSE ├── Readme.md ├── flake.lock ├── flake.nix ├── funcs └── expensive1.nix └── lib.nix /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DavHau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Cache evaluation of nix functions 2 | 3 | This is an implementation of a nix eval cache for individual function calls. It works by offloading the function evaluation into a nix build and caching the result in the nix store. 4 | 5 | On subsequent calls of that function the cached value will be read from the store without the function being re-executed. 6 | 7 | This only works if: 8 | - the function is defined inside a single file containing just the function expression 9 | - the function expects an attrset argument 10 | - all function args are json serializable 11 | - the result is json serializable 12 | 13 | The function arguments are allowed to reference store paths, therefore you can pass in nixpkgs and other libraries etc. without a problem. 14 | 15 | This can be used to cache expensive function calls like parsing lock files etc. 16 | 17 | The cost: 18 | - refactoring your code might be necessary to use this eval cache 19 | - the initial function evaluation will be more expensive since libraries or nixpkgs might need to be re-imported inside the function (inside the nix build). 20 | - import from derivation is used in order to read the function result 21 | 22 | 23 | ### Usage example 24 | Given the expensive function is defined in `./expensive.nix` (see example in `./funcs/expensive1.nix`). 25 | 26 | Nix repl: 27 | ```shell 28 | nix-repl> cachedCall = (builtins.getFlake "github:davhau/nix-eval-cache").lib.x86_64-linux.cachedCall 29 | 30 | nix-repl> cachedCall ./expensive.nix { nixpkgs = ; input = "test";} 31 | ``` 32 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1649012074, 6 | "narHash": "sha256-vQUROcJ3FfT3GTB/nJrXwVvjuq8WfK0ImN+RUgDVN1c=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "bc4b9eef3ce3d5a90d8693e8367c9cbfc9fc1e13", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Cache evaluation of nix functions"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | }; 7 | 8 | outputs = { 9 | nixpkgs, 10 | self, 11 | } @ inp: let 12 | l = builtins // nixpkgs.lib; 13 | supportedSystems = ["x86_64-linux"]; 14 | 15 | forAllSystems = f: l.genAttrs supportedSystems 16 | (system: f system nixpkgs.legacyPackages.${system}); 17 | 18 | in { 19 | 20 | lib = forAllSystems (system: pkgs: { 21 | cachedCall = 22 | (import ./lib.nix {inherit pkgs; lib = nixpkgs.lib;}).cachedCall; 23 | }); 24 | 25 | evalTest = forAllSystems (system: pkgs: 26 | self.lib.${system}.cachedCall ./funcs/expensive1.nix { 27 | input = "test"; 28 | nixpkgs = pkgs.path; 29 | } 30 | ); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /funcs/expensive1.nix: -------------------------------------------------------------------------------- 1 | { 2 | input, 3 | nixpkgs, 4 | pkgs ? import nixpkgs {}, 5 | lib ? pkgs.lib, 6 | }: 7 | let 8 | makeList = size: list: 9 | if size > 0 10 | then makeList (size - 1) (list ++ [input]) 11 | else list; 12 | 13 | initList = makeList 300 []; 14 | 15 | burn = x: 16 | lib.unique 17 | (lib.foldl' 18 | (all: next: all ++ initList) 19 | [] 20 | initList); 21 | 22 | burnMore = intensity: 23 | if intensity > 0 24 | then lib.unique ((burn intensity) ++ burnMore (intensity - 1)) 25 | else burn 0; 26 | in 27 | builtins.toJSON (burnMore 300) 28 | 29 | -------------------------------------------------------------------------------- /lib.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib ? pkgs.lib, 3 | pkgs ? import {}, 4 | }: let 5 | l = lib // builtins; 6 | in { 7 | cachedCall = funcFile: args: 8 | let 9 | jsonArgs = l.toJSON args; 10 | jsonArgsFile = pkgs.writeText "args.json" jsonArgs; 11 | 12 | resultJson = pkgs.runCommand "result.json" 13 | { 14 | nativeBuildInputs = with pkgs;[ 15 | nix 16 | ]; 17 | } 18 | '' 19 | export NIX_CONFIG="${'' 20 | experimental-features = nix-command 21 | ''}" 22 | mkdir store 23 | nix --store ./store eval --offline --impure --raw --expr \ 24 | ' 25 | let 26 | func = import ${funcFile}; 27 | 28 | argsBase = rec { 29 | pkgs = import ${pkgs.path} {}; 30 | lib = pkgs.lib; 31 | }; 32 | 33 | args = 34 | argsBase 35 | // (builtins.fromJSON (builtins.readFile ${jsonArgsFile})); 36 | 37 | result = func args; 38 | 39 | in 40 | builtins.toJSON result 41 | ' \ 42 | > $out 43 | ''; 44 | 45 | in 46 | l.fromJSON (l.readFile resultJson); 47 | 48 | } 49 | --------------------------------------------------------------------------------