├── COPYING ├── README.md ├── default.nix └── flake.nix /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 Eelco Dolstra and the flake-compat contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flake-compat 2 | 3 | > This is a maintained fork of 4 | 5 | ## Introduction 6 | 7 | **[Nix Flakes: Using flakes project from a legacy Nix.](https://wiki.nixos.org/wiki/Flakes#Using_flakes_project_from_a_legacy_Nix)** 8 | 9 | ## Usage 10 | 11 | To use, add the following to your `flake.nix`: 12 | 13 | ```nix 14 | inputs.flake-compat.url = "github:nix-community/flake-compat"; 15 | ``` 16 | 17 | Example in a `flake.nix` file: 18 | 19 | ```nix 20 | { 21 | description = "My first flake"; 22 | 23 | inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; 24 | inputs.flake-compat.url = "github:nix-community/flake-compat"; 25 | 26 | outputs = { self, nixpkgs, ... }: 27 | let 28 | eachSystem = f: nixpkgs.lib.genAttrs self.lib.supportedSystems (system: f nixpkgs.legacyPackages.${system}); 29 | in 30 | { 31 | lib.supportedSystems = [ 32 | "aarch64-darwin" 33 | "aarch64-linux" 34 | "x86_64-darwin" 35 | "x86_64-linux" 36 | ]; 37 | 38 | devShells = eachSystem (pkgs: { 39 | default = pkgs.mkShell { buildInputs = [ pkgs.hello pkgs.cowsay ]; }; 40 | }); 41 | }; 42 | } 43 | ``` 44 | 45 | Update the `flake.lock` to include the hashes for `flake-compat`: 46 | 47 | ```sh 48 | $ nix --extra-experimental-features "flakes nix-command" flake lock 49 | ``` 50 | You can say no to potential questions about adding additional substituters, 51 | public keys or other configuration, we just care about the updates to 52 | `flake.lock` here. 53 | 54 | Afterwards, create a `default.nix` file containing the following: 55 | 56 | ```nix 57 | # This file provides backward compatibility to nix < 2.4 clients 58 | { system ? builtins.currentSystem }: 59 | let 60 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 61 | 62 | root = lock.nodes.${lock.root}; 63 | inherit (lock.nodes.${root.inputs.flake-compat}.locked) owner repo rev narHash; 64 | 65 | flake-compat = fetchTarball { 66 | url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; 67 | sha256 = narHash; 68 | }; 69 | 70 | flake = import flake-compat { inherit system; src = ./.; }; 71 | in 72 | flake.defaultNix 73 | ``` 74 | 75 | If you would like a `shell.nix` file, create one containing the above, replacing `defaultNix` with `shellNix`. 76 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Compatibility function to allow flakes to be used by 2 | # non-flake-enabled Nix versions. Given a source tree containing a 3 | # 'flake.nix' and 'flake.lock' file, it fetches the flake inputs and 4 | # calls the flake's 'outputs' function. It then returns an attrset 5 | # containing 'defaultNix' (to be used in 'default.nix'), 'shellNix' 6 | # (to be used in 'shell.nix'). 7 | 8 | { src, system ? builtins.currentSystem or "unknown-system" }: 9 | 10 | let 11 | 12 | lockFilePath = src + "/flake.lock"; 13 | 14 | lockFile = builtins.fromJSON (builtins.readFile lockFilePath); 15 | 16 | fetchTree = 17 | info: 18 | if info.type == "github" then 19 | { 20 | outPath = 21 | fetchTarball 22 | ({ url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}"; } 23 | // (if info ? narHash then { sha256 = info.narHash; } else { }) 24 | ); 25 | rev = info.rev; 26 | shortRev = builtins.substring 0 7 info.rev; 27 | lastModified = info.lastModified; 28 | lastModifiedDate = formatSecondsSinceEpoch info.lastModified; 29 | narHash = info.narHash; 30 | } 31 | else if info.type == "git" then 32 | { 33 | outPath = 34 | builtins.fetchGit 35 | ({ url = info.url; } 36 | // (if info ? rev then { inherit (info) rev; } else { }) 37 | // (if info ? ref then { inherit (info) ref; } else { }) 38 | // (if info ? submodules then { inherit (info) submodules; } else { }) 39 | ); 40 | lastModified = info.lastModified; 41 | lastModifiedDate = formatSecondsSinceEpoch info.lastModified; 42 | narHash = info.narHash; 43 | } // (if info ? rev then { 44 | rev = info.rev; 45 | shortRev = builtins.substring 0 7 info.rev; 46 | } else { }) 47 | else if info.type == "path" then 48 | { 49 | outPath = builtins.path { 50 | path = 51 | if builtins.substring 0 1 info.path != "/" 52 | then src + ("/" + info.path) 53 | else info.path; 54 | }; 55 | narHash = info.narHash; 56 | } 57 | else if info.type == "tarball" then 58 | { 59 | outPath = 60 | fetchTarball 61 | ({ inherit (info) url; } 62 | // (if info ? narHash then { sha256 = info.narHash; } else { }) 63 | ); 64 | } 65 | else if info.type == "gitlab" then 66 | { 67 | inherit (info) rev narHash lastModified; 68 | outPath = 69 | fetchTarball 70 | ({ url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}"; } 71 | // (if info ? narHash then { sha256 = info.narHash; } else { }) 72 | ); 73 | shortRev = builtins.substring 0 7 info.rev; 74 | } 75 | else 76 | # FIXME: add Mercurial, tarball inputs. 77 | throw "flake input has unsupported input type '${info.type}'"; 78 | 79 | callLocklessFlake = flakeSrc: 80 | let 81 | flake = import (flakeSrc + "/flake.nix"); 82 | outputs = flakeSrc // (flake.outputs ({ self = outputs; })); 83 | in 84 | outputs; 85 | 86 | rootSrc = 87 | let 88 | # Try to clean the source tree by using fetchGit, if this source 89 | # tree is a valid git repository. 90 | tryFetchGit = src: 91 | if isGit && !isShallow 92 | then 93 | let res = builtins.fetchGit src; 94 | in if res.rev == "0000000000000000000000000000000000000000" then removeAttrs res [ "rev" "shortRev" ] else res 95 | else { outPath = src; }; 96 | # NB git worktrees have a file for .git, so we don't check the type of .git 97 | isGit = builtins.pathExists (src + "/.git"); 98 | isShallow = builtins.pathExists (src + "/.git/shallow"); 99 | 100 | in 101 | { lastModified = 0; lastModifiedDate = formatSecondsSinceEpoch 0; } 102 | // (if src ? outPath then src else tryFetchGit src); 103 | 104 | # Format number of seconds in the Unix epoch as %Y%m%d%H%M%S. 105 | formatSecondsSinceEpoch = t: 106 | let 107 | rem = x: y: x - x / y * y; 108 | days = t / 86400; 109 | secondsInDay = rem t 86400; 110 | hours = secondsInDay / 3600; 111 | minutes = (rem secondsInDay 3600) / 60; 112 | seconds = rem t 60; 113 | 114 | # Courtesy of https://stackoverflow.com/a/32158604. 115 | z = days + 719468; 116 | era = (if z >= 0 then z else z - 146096) / 146097; 117 | doe = z - era * 146097; 118 | yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; 119 | y = yoe + era * 400; 120 | doy = doe - (365 * yoe + yoe / 4 - yoe / 100); 121 | mp = (5 * doy + 2) / 153; 122 | d = doy - (153 * mp + 2) / 5 + 1; 123 | m = mp + (if mp < 10 then 3 else -9); 124 | y' = y + (if m <= 2 then 1 else 0); 125 | 126 | pad = s: if builtins.stringLength s < 2 then "0" + s else s; 127 | in 128 | "${toString y'}${pad (toString m)}${pad (toString d)}${pad (toString hours)}${pad (toString minutes)}${pad (toString seconds)}"; 129 | 130 | allNodes = 131 | builtins.mapAttrs 132 | (key: node: 133 | let 134 | sourceInfo = 135 | if key == lockFile.root 136 | then rootSrc 137 | else fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]); 138 | 139 | subdir = if key == lockFile.root then "" else node.locked.dir or ""; 140 | 141 | flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix"); 142 | 143 | inputs = builtins.mapAttrs 144 | (_inputName: inputSpec: allNodes.${resolveInput inputSpec}) 145 | (node.inputs or { }); 146 | 147 | # Resolve a input spec into a node name. An input spec is 148 | # either a node name, or a 'follows' path from the root 149 | # node. 150 | resolveInput = inputSpec: 151 | if builtins.isList inputSpec 152 | then getInputByPath lockFile.root inputSpec 153 | else inputSpec; 154 | 155 | # Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the 156 | # root node, returning the final node. 157 | getInputByPath = nodeName: path: 158 | if path == [ ] 159 | then nodeName 160 | else 161 | getInputByPath 162 | # Since this could be a 'follows' input, call resolveInput. 163 | (resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path}) 164 | (builtins.tail path); 165 | 166 | outputs = flake.outputs (inputs // { self = result; }); 167 | 168 | result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; }; 169 | 170 | in 171 | if node.flake or true then 172 | assert builtins.isFunction flake.outputs; 173 | result 174 | else 175 | sourceInfo 176 | ) 177 | lockFile.nodes; 178 | 179 | result = 180 | if !(builtins.pathExists lockFilePath) 181 | then callLocklessFlake rootSrc 182 | else if lockFile.version >= 5 && lockFile.version <= 7 183 | then allNodes.${lockFile.root} 184 | else throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}"; 185 | 186 | in 187 | rec { 188 | defaultNix = 189 | (builtins.removeAttrs result [ "__functor" ]) 190 | // (if result ? defaultPackage.${system} then { default = result.defaultPackage.${system}; } else { }) 191 | // (if result ? packages.${system}.default then { default = result.packages.${system}.default; } else { }); 192 | 193 | shellNix = 194 | defaultNix 195 | // (if result ? devShell.${system} then { default = result.devShell.${system}; } else { }) 196 | // (if result ? devShells.${system}.default then { default = result.devShells.${system}.default; } else { }); 197 | } 198 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "flake-compat"; 3 | outputs = _: { 4 | lib = import ./.; 5 | }; 6 | } 7 | --------------------------------------------------------------------------------