├── .github ├── dependabot.yml └── workflows │ ├── netlify.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README.tpl.md ├── app ├── Niv.hs └── NivTest.hs ├── data ├── README.md └── nixpkgs.json ├── default.nix ├── examples └── cpp-libosmium │ ├── CMakeLists.txt │ ├── cmake │ ├── Dependencies.cmake │ └── Modules │ │ └── FindOsmium.cmake │ ├── default.nix │ ├── nix │ ├── sources.json │ └── sources.nix │ ├── overlay.nix │ ├── overlay │ └── libosmium.nix │ └── src │ ├── CMakeLists.txt │ └── main.cpp ├── foo └── default.nix ├── niv.svg ├── nix ├── default.nix ├── sources.json ├── sources.nix └── termtosvg.nix ├── package.yaml ├── script ├── fmt ├── gen ├── test └── upload ├── shell.nix ├── src ├── Data │ ├── Aeson │ │ └── Extended.hs │ ├── HashMap │ │ └── Strict │ │ │ └── Extended.hs │ └── Text │ │ └── Extended.hs └── Niv │ ├── Cli.hs │ ├── Cmd.hs │ ├── Git │ ├── Cmd.hs │ └── Test.hs │ ├── GitHub.hs │ ├── GitHub │ ├── API.hs │ ├── Cmd.hs │ └── Test.hs │ ├── Local │ └── Cmd.hs │ ├── Logger.hs │ ├── Sources.hs │ ├── Sources │ └── Test.hs │ ├── Test.hs │ ├── Update.hs │ └── Update │ └── Test.hs └── tests ├── eval └── default.nix ├── git └── default.nix └── github ├── data ├── archives │ ├── 571b40d3f50466d3e91c1e609d372de96d782793.tar.gz │ ├── a489b65a5c3a29983701069d1ce395b23d9bde64.tar.gz │ └── abc51449406ba3279c466b4d356b4ae8522ceb58.tar.gz └── repos │ ├── NixOS │ └── nixpkgs-channels │ │ ├── commits.json │ │ └── repository.json │ └── nmattia │ └── niv │ ├── commits.json │ └── repository.json ├── default.nix └── expected └── niv-init.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/netlify.yml: -------------------------------------------------------------------------------- 1 | name: "netlify deploy" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | netlify: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: "deploy to netlify" 12 | uses: netlify/actions/cli@375963b92b795c7b979927c580dd6f2a65ebcf28 13 | with: 14 | args: deploy --dir=./site --message="$GITHUB_SHA" --prod 15 | env: 16 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 17 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | tests: 9 | strategy: 10 | matrix: 11 | os: [ubuntu-24.04, macos-15] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: cachix/install-nix-action@v30 16 | with: 17 | nix_path: 'nixpkgs=./nix' 18 | - name: "Install Cachix" 19 | uses: cachix/cachix-action@v15 20 | with: 21 | name: niv 22 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 23 | - name: "Run tests" 24 | run: ./script/test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result* 2 | tags 3 | # In case anyone uses cabal or stack 4 | dist 5 | dist-newstyle 6 | .stack-work 7 | # Cabal file can be generated from package.yaml but shouldn't be checked in 8 | *.cabal 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.2.22] 2023-03-12 4 | 5 | ## Changed 6 | * Add statix sugegstions 7 | 8 | ## [0.2.21] 2022-05-06 9 | 10 | ## Added 11 | * Fetch submodules (if supported, otherwise warn) 12 | 13 | ## [0.2.20] 2022-04-04 14 | 15 | ## Added 16 | * Add ability to pass submodules to `builtins.fetchGit` 17 | 18 | ## Changed 19 | * Remove warnings about GITHUB_INSECURE and GITHUB_PATH 20 | * Support newer GHC 21 | * Update optparse-applicative and aeson (aeson 2) 22 | * Update nixpkgs and track master 23 | * Speed up initialization (skip `nix-prefetch-url`) 24 | * Bunch of documentation fixes 25 | * Fix badges and GitHub Actions 26 | 27 | 28 | ## [0.2.19] 2021-01-05 29 | ## Added 30 | * There is a new flag `--no-colors` for avoiding colors in the output 31 | ## Changed 32 | * `GITHUB_PATH` was renamed to `NIV_GITHUB_PATH` https://github.com/nmattia/niv/issues/280 33 | * `GITHUB_INSECURE` was renamed to `NIV_GITHUB_INSECURE` https://github.com/nmattia/niv/issues/280 34 | * If `-T` is provided on the command line for `niv add` it will be respected and no guessing from the url is done. 35 | * `type` attribute is now respected. 36 | * If an url template is updated, `type` needs to be adjusted manuall as it is not guessed from the new url template. 37 | 38 | ## [0.2.17] 2020-09-08 39 | ## Added 40 | * There is a new flag `-r/--rev` for specifying a revision during update and add 41 | * There is a new flag `--version` that instructs niv to show its version and exit 42 | ## Changed 43 | * The derivation name of sources is sanitized 44 | * The `ref` field was deprecated in favor of `branch` and `tag` in `git` 45 | sources 46 | 47 | ## [0.2.16] 2020-08-14 48 | ## Changed 49 | * The `sources.nix` can now be imported when there's no local `sources.json` 50 | 51 | ## [0.2.15] 2020-08-13 52 | ## Added 53 | * The sources can be overriden with `NIV_OVERRIDE_` 54 | ## Changed 55 | * When `nix-prefetch-url` fails the command is shown 56 | * IO operations during update are cached 57 | 58 | ## [0.2.14] 2020-07-15 59 | ## Added 60 | * `niv add local` for local sources. 61 | * Custom nixpkgs can be specified during `init`. 62 | ## Changed 63 | * The derivation name for package `foo` is now `foo-src`. 64 | * The extension `.tgz` is considered as `.tar.gz`. 65 | * The default nixpkgs is `release-20.03`. 66 | * Nixpkgs is now pulled from `NixOS/nixpkgs`, not from channels. 67 | ## Removed 68 | * The types `builtin-tarbal` and `builtin-url` were removed. 69 | 70 | ## [0.2.13] 2020-02-02 71 | ## Added 72 | * `niv modify -n NAME` to rename a package 73 | ## Changed 74 | * README mentions the `modify` command 75 | * `niv` is not shipped with the `niv-test` executable anymore 76 | * `cabal-upload` was simplified 77 | 78 | ## [0.2.12] 2020-01-17 79 | ## Added 80 | * Examples for building C++ libraries with niv 81 | * Documentation for using niv from nixpkgs 82 | ## Changed 83 | * Only depend on executables in `default.nix` (`-A niv`) for smaller closure 84 | size 85 | * Ensure `` is not evaluated in `sources.nix` unless necessary 86 | 87 | ## [0.2.11] 2020-01-07 88 | ## Changed 89 | * Users can set custom `pkgs` when `import`ing `sources.nix` 90 | 91 | ## [0.2.10] 2020-01-06 92 | ## Changed 93 | * The bundled `nix/sources.nix` is formatted with `nixpkgs-fmt` 94 | 95 | ## [0.2.9] 2019-12-17 96 | ## Changed 97 | * `niv init` uses nixpkgs 19.09 98 | 99 | ## [0.2.8] 2019-12-09 100 | ## Changed 101 | * Fixed message in `niv init` with custom `sources.json` 102 | 103 | ## [0.2.7] 2019-12-08 104 | ## Added 105 | * Support for custom path `sources.json` with `--sources-json` 106 | 107 | ## [0.2.6] 2019-12-05 108 | ## Changed 109 | * Fix `niv update` with `git` specs 110 | 111 | ## [0.2.5] 2019-12-01 112 | ## Changed 113 | * Fix `niv show` adding extra newlines 114 | 115 | ## [0.2.4] 2019-12-01 116 | ### Added 117 | * Experimental support for `add` subcommands, in particular `niv add git` 118 | ## Changed 119 | * Various error message fixes 120 | 121 | ## [0.2.3] 2019-11-28 122 | ### Added 123 | * A new CLI option (`-s`) reads attributes as raw strings. 124 | ### Changed 125 | * The attribute CLI option (`-a`) now allows JSON value. 126 | * Some typos were fixed. 127 | * A deprecation warning was added for `builtin-tarball`. 128 | 129 | ## [0.2.2] 2019-11-27 130 | ### Added 131 | * The `sources.nix` are now versioned. 132 | * Show the help when no arguments are provided. 133 | ### Changed 134 | * The `show` command was prettified. 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Nicolas Mattia 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # niv 2 | 3 | [![Test](https://github.com/nmattia/niv/actions/workflows/test.yml/badge.svg)](https://github.com/nmattia/niv/actions/workflows/test.yml) 4 | [![Netlify Status](https://api.netlify.com/api/v1/badges/48532eaa-259f-4ca2-aadf-67f7c6b957fd/deploy-status)](https://niv.nmattia.com) 5 | 6 | Painless dependencies for [Nix] projects. Read more in the [Getting started](#getting-started) section below. 7 | 8 |

9 | 10 |

11 | 12 | 13 | * [Install](#install) 14 | * [Build](#build) 15 | * [Usage](#usage) 16 | * [FAQ](#frequently-asked-questions) 17 | 18 | ## Install 19 | 20 | `niv` is available in [`nixpkgs`](https://github.com/NixOS/nixpkgs) as `niv`: 21 | 22 | ``` bash 23 | $ nix-env -iA nixpkgs.niv 24 | ``` 25 | 26 | Alternatively, run the following command to install the development version: 27 | 28 | ``` bash 29 | $ nix-env -iA niv -f https://github.com/nmattia/niv/tarball/master \ 30 | --substituters https://niv.cachix.org \ 31 | --trusted-public-keys niv.cachix.org-1:X32PCg2e/zAm3/uD1ScqW2z/K0LtDyNV7RdaxIuLgQM= 32 | ``` 33 | 34 | ## Build 35 | 36 | Inside the provided nix shell: 37 | 38 | ``` bash 39 | $ repl 40 | ``` 41 | 42 | Run the test suite with this command: 43 | 44 | ``` bash 45 | $ ./script/test 46 | ``` 47 | 48 | ## Usage 49 | 50 | `niv` simplifies [adding](#add) and [updating](#update) dependencies in Nix 51 | projects. It uses a single file, `nix/sources.json`, where it stores the data 52 | necessary for fetching and updating the packages. 53 | 54 | * [Add](#add): inserts a package in `nix/sources.json`. 55 | * [Update](#update): updates one or all packages in `nix/sources.json`. 56 | * [Drop](#drop): deletes a package from `nix/sources.json`. 57 | 58 | `niv` has some utility functions: 59 | 60 | * [Init](#init): bootstraps a Nix project, in particular creates a 61 | `nix/sources.json` file containing `niv` and `nixpkgs` as well as a 62 | `nix/sources.nix` file that returns the sources as a Nix object. 63 | * [Show](#show): shows the packages' information. 64 | * [Modify](#modify): modifies attributes _without_ performing an update. 65 | 66 | ### Configuration 67 | 68 | The following environment variables are read by `niv`: 69 | 70 | | Name | Note | 71 | | --------------- | ---- | 72 | | GITHUB_TOKEN or NIV_GITHUB_TOKEN | When set, the value is used to authenticate GitHub API requests. | 73 | | GITHUB_HOST or NIV_GITHUB_HOST | The GitHub host to use when fetching packages. Port may be appended here. | 74 | | GITHUB_API_HOST or NIV_GITHUB_API_HOST | The host used when performing GitHub API requests. Use `GITHUB_API_PORT` for specifying the port. | 75 | | GITHUB_API_PORT or NIV_GITHUB_API_PORT | The port used when performing GitHub API requests. Defaults to `443` for secure requests. Defaults to `80` for insecure requests. See also: `GITHUB_INSECURE`. | 76 | | NIV_GITHUB_INSECURE | When set to anything but the empty string, requests are performed over `http` instead of `https`. | 77 | | NIV_GITHUB_PATH | The base path used when performing GitHub API requests. | 78 | 79 | The next two sections cover [common use cases](#getting-started) and [full command 80 | description](#commands). 81 | 82 | ### Getting started 83 | 84 | Nix is a very powerful tool for building code and setting up environments. `niv` complements it by making it easy to describe and update remote dependencies (URLs, GitHub repos, etc). It is a simple, practical alternative to [Nix flakes](https://wiki.nixos.org/wiki/Flakes). 85 | 86 | This section covers common use cases: 87 | 88 | * [Bootstrapping a Nix project](#bootstrapping-a-nix-project). 89 | * [Tracking a different nixpkgs branch](#tracking-a-nixpkgs-branch). 90 | * [Importing packages from GitHub](#importing-packages-from-github). 91 | * [Fetching packages from custom URLs](#using-custom-urls). 92 | 93 | #### Bootstrapping a Nix project 94 | 95 | Use the `init` command when starting a new Nix project or when porting an 96 | existing Nix project to niv: 97 | 98 | ``` shell 99 | $ niv init 100 | ... 101 | $ tree 102 | . 103 | └── nix 104 |    ├── sources.json 105 |    └── sources.nix 106 | 107 | 1 directory, 2 files 108 | ``` 109 | 110 | The file `nix/sources.json` is the file used by niv to store versions and is 111 | initialized with nixpkgs: 112 | 113 | ``` json 114 | { 115 | "nixpkgs": { 116 | "branch": "nixos-unstable", 117 | "description": "Nix Packages collection", 118 | "homepage": null, 119 | "owner": "NixOS", 120 | "repo": "nixpkgs", 121 | "rev": "6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe", 122 | "sha256": "16f329z831bq7l3wn1dfvbkh95l2gcggdwn6rk3cisdmv2aa3189", 123 | "type": "tarball", 124 | "url": "https://github.com/NixOS/nixpkgs/archive/6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe.tar.gz", 125 | "url_template": "https://github.com///archive/.tar.gz" 126 | } 127 | } 128 | ``` 129 | 130 | To use this dependency, `import` the file `nix/sources.nix`, e.g.: 131 | 132 | ``` nix 133 | { sources ? import ./sources.nix }: # import the sources 134 | import sources.nixpkgs # and use them again! 135 | { overlays = [] ; config = {}; } 136 | ``` 137 | 138 | For more information about importing sources to your nix files, check 139 | the [frequently asked questions](#Frequently-asked-questions). 140 | 141 | #### Tracking a nixpkgs branch 142 | 143 | The `init` command sets the `nix/sources.json` to the content of the file 144 | [data/nixpkgs.json](data/nixpkgs.json). Currently, you would be tracking the 145 | `nixos-unstable` branch. 146 | Run the following command to 147 | update it to the last commit of the configured branch: 148 | 149 | ``` shell 150 | $ niv update nixpkgs 151 | ``` 152 | 153 | To change the branch being tracked run this command: 154 | 155 | ``` shell 156 | $ niv update nixpkgs -b master # equivalent to --branch master 157 | ``` 158 | 159 | #### Importing packages from GitHub 160 | 161 | The `add` command will infer information about the package being added, when 162 | possible. This works very well for GitHub repositories. Run this command to add 163 | [jq] to your project: 164 | 165 | 166 | ``` shell 167 | $ niv add stedolan/jq 168 | ``` 169 | 170 | The following data was added in `nix/sources.json` for `jq`: 171 | 172 | ``` json 173 | { 174 | "homepage": "http://stedolan.github.io/jq/", 175 | "url": "https://github.com/stedolan/jq/archive/9fa2e51099c55af56e3e541dc4b399f11de74abe.tar.gz", 176 | "owner": "stedolan", 177 | "branch": "master", 178 | "url_template": "https://github.com///archive/.tar.gz", 179 | "repo": "jq", 180 | "sha256": "0819rvk8057qgcqvgn7fpldvly2pfdw9fxcjrlqa8gr59p8a1cic", 181 | "description": "Command-line JSON processor", 182 | "rev": "9fa2e51099c55af56e3e541dc4b399f11de74abe" 183 | } 184 | ``` 185 | 186 | #### Using custom URLs 187 | 188 | It is possible to use niv to fetch packages from custom URLs. Run this command 189 | to add the Haskell compiler [GHC] to your `nix/sources.json`: 190 | 191 | ``` shell 192 | $ niv add ghc \ 193 | -v 8.4.3 \ 194 | -t 'https://downloads.haskell.org/~ghc//ghc--i386-deb8-linux.tar.xz' 195 | ``` 196 | 197 | The option `-v` sets the "version" attribute to `8.4.3`. The option `-t` sets a 198 | template that can be reused by niv when fetching a new URL (see the 199 | documentation for [add](#add) and [update](#update)). 200 | 201 | The type of the dependency is guessed from the provided URL template, if `-T` 202 | is not specified. 203 | 204 | For updating the version of GHC used run this command: 205 | 206 | ``` shell 207 | $ niv update ghc -v 8.6.2 208 | ``` 209 | 210 | ### Commands 211 | 212 | ``` 213 | niv - dependency manager for Nix projects 214 | 215 | version: 0.2.22 216 | 217 | Usage: niv [-s|--sources-file FILE] [--no-colors] COMMAND 218 | 219 | Available options: 220 | -s,--sources-file FILE Use FILE instead of nix/sources.json 221 | --no-colors Don't use colors in output 222 | -h,--help Show this help text 223 | --version Print version 224 | 225 | Available commands: 226 | init Initialize a Nix project. Existing files won't be 227 | modified. 228 | add Add a GitHub dependency 229 | show 230 | update Update dependencies 231 | modify Modify dependency attributes without performing an 232 | update 233 | drop Drop dependency 234 | ``` 235 | 236 | #### Add 237 | 238 | ``` 239 | Examples: 240 | 241 | niv add stedolan/jq 242 | niv add NixOS/nixpkgs -n nixpkgs -b nixpkgs-unstable 243 | niv add my-package -v alpha-0.1 -t http://example.com/archive/.zip 244 | 245 | Usage: niv add PACKAGE [-n|--name NAME] 246 | [(-a|--attribute KEY=VAL) | (-s|--string-attribute KEY=VAL) | 247 | (-b|--branch BRANCH) | (-o|--owner OWNER) | (-r|--rev REV) | 248 | (-v|--version VERSION) | (-t|--template URL) | 249 | (-T|--type TYPE)] 250 | 251 | Add a GitHub dependency 252 | 253 | Available options: 254 | -n,--name NAME Set the package name to 255 | -a,--attribute KEY=VAL Set the package spec attribute to , where 256 | may be JSON. 257 | -s,--string-attribute KEY=VAL 258 | Set the package spec attribute to . 259 | -b,--branch BRANCH Equivalent to --attribute branch= 260 | -o,--owner OWNER Equivalent to --attribute owner= 261 | -r,--rev REV Equivalent to --attribute rev= 262 | -v,--version VERSION Equivalent to --attribute version= 263 | -t,--template URL Used during 'update' when building URL. Occurrences 264 | of are replaced with attribute 'foo'. 265 | -T,--type TYPE The type of the URL target. The value can be either 266 | 'file' or 'tarball'. If not set, the value is 267 | inferred from the suffix of the URL. 268 | -h,--help Show this help text 269 | 270 | Experimental commands: 271 | git Add a git dependency. Experimental. 272 | github Add a GitHub dependency 273 | local Add a local dependency. Experimental. 274 | ``` 275 | 276 | #### Update 277 | 278 | ``` 279 | Examples: 280 | 281 | niv update # update all packages 282 | niv update nixpkgs # update nixpkgs 283 | niv update my-package -v beta-0.2 # update my-package to version "beta-0.2" 284 | 285 | Usage: niv update [PACKAGE 286 | [(-a|--attribute KEY=VAL) | 287 | (-s|--string-attribute KEY=VAL) | (-b|--branch BRANCH) | 288 | (-o|--owner OWNER) | (-r|--rev REV) | 289 | (-v|--version VERSION) | (-t|--template URL) | 290 | (-T|--type TYPE)]] 291 | 292 | Update dependencies 293 | 294 | Available options: 295 | -a,--attribute KEY=VAL Set the package spec attribute to , where 296 | may be JSON. 297 | -s,--string-attribute KEY=VAL 298 | Set the package spec attribute to . 299 | -b,--branch BRANCH Equivalent to --attribute branch= 300 | -o,--owner OWNER Equivalent to --attribute owner= 301 | -r,--rev REV Equivalent to --attribute rev= 302 | -v,--version VERSION Equivalent to --attribute version= 303 | -t,--template URL Used during 'update' when building URL. Occurrences 304 | of are replaced with attribute 'foo'. 305 | -T,--type TYPE The type of the URL target. The value can be either 306 | 'file' or 'tarball'. If not set, the value is 307 | inferred from the suffix of the URL. 308 | -h,--help Show this help text 309 | ``` 310 | 311 | #### Modify 312 | 313 | ``` 314 | Examples: 315 | 316 | niv modify nixpkgs -v beta-0.2 317 | niv modify nixpkgs -a branch=nixpkgs-unstable 318 | 319 | Usage: niv modify PACKAGE [-n|--name NAME] 320 | [(-a|--attribute KEY=VAL) | (-s|--string-attribute KEY=VAL) | 321 | (-b|--branch BRANCH) | (-o|--owner OWNER) | (-r|--rev REV) | 322 | (-v|--version VERSION) | (-t|--template URL) | 323 | (-T|--type TYPE)] 324 | 325 | Modify dependency attributes without performing an update 326 | 327 | Available options: 328 | -n,--name NAME Set the package name to 329 | -a,--attribute KEY=VAL Set the package spec attribute to , where 330 | may be JSON. 331 | -s,--string-attribute KEY=VAL 332 | Set the package spec attribute to . 333 | -b,--branch BRANCH Equivalent to --attribute branch= 334 | -o,--owner OWNER Equivalent to --attribute owner= 335 | -r,--rev REV Equivalent to --attribute rev= 336 | -v,--version VERSION Equivalent to --attribute version= 337 | -t,--template URL Used during 'update' when building URL. Occurrences 338 | of are replaced with attribute 'foo'. 339 | -T,--type TYPE The type of the URL target. The value can be either 340 | 'file' or 'tarball'. If not set, the value is 341 | inferred from the suffix of the URL. 342 | -h,--help Show this help text 343 | ``` 344 | 345 | #### Drop 346 | 347 | ``` 348 | Examples: 349 | 350 | niv drop jq 351 | niv drop my-package version 352 | 353 | Usage: niv drop PACKAGE [ATTRIBUTE] 354 | 355 | Drop dependency 356 | 357 | Available options: 358 | -h,--help Show this help text 359 | ``` 360 | 361 | #### Init 362 | 363 | ``` 364 | Usage: niv init [--fast | --latest | --nixpkgs OWNER/REPO 365 | (-b|--nixpkgs-branch ARG) | 366 | --no-nixpkgs] 367 | 368 | Initialize a Nix project. Existing files won't be modified. 369 | 370 | Available options: 371 | --fast Use the latest nixpkgs cached at 372 | 'https://github.com/nmattia/niv/blob/master/data/nixpkgs.json'. 373 | This is the default. 374 | --latest Pull the latest unstable nixpkgs from NixOS/nixpkgs. 375 | --nixpkgs OWNER/REPO Use a custom nixpkgs repository from GitHub. 376 | -b,--nixpkgs-branch ARG The nixpkgs branch when using --nixpkgs .... 377 | --no-nixpkgs Don't add a nixpkgs entry to sources.json. 378 | -h,--help Show this help text 379 | ``` 380 | 381 | #### show 382 | 383 | ``` 384 | Usage: niv show [PACKAGE] 385 | 386 | Available options: 387 | -h,--help Show this help text 388 | ``` 389 | 390 | [Nix]: https://nixos.org/nix/ 391 | [jq]: https://stedolan.github.io/jq/ 392 | [GHC]: https://www.haskell.org/ghc/ 393 | 394 | 395 | ## Frequently Asked Questions 396 | 397 | * [Can I use private GitHub repositories?](#can-i-use-private-github-repositories) 398 | * [How do I import and use the content of a source?](#how-do-i-import-and-use-the-content-of-a-source) 399 | * [How do I import a subpath of a source?](#how-do-i-import-a-subpath-of-a-source) 400 | * [How do I import NixOS modules](#how-do-i-import-nixos-modules) 401 | * [Can I use local packages?](#can-i-use-local-packages) 402 | * [Can I use git submodules?](#can-i-use-git-submodules) 403 | 404 | ### Can I use private GitHub repositories? 405 | 406 | Yes. There are two ways: 407 | 408 | #### 1. Use the git protocol 409 | 410 | When using the git protocol, your public SSH keypair is used to authenticate 411 | you: 412 | 413 | ``` shell 414 | $ niv add git git@github.com:my_user/my_private_repo 415 | ``` 416 | 417 | ##### 2. Use the netrc file 418 | 419 | in order to `niv add` a private github repo you'll need to: 420 | 421 | 1. create a .netrc file with the following content 422 | ``` 423 | machine github.com 424 | login YOUR_GITHUB_USER_NAME 425 | password YOUR_GITHUB_TOKEN 426 | ``` 427 | 428 | 2. add the path to the above file to `/etc/nix/nix.conf`: 429 | ``` 430 | netrc-file = /PATH/TO/.netrc 431 | ``` 432 | 433 | 3. set `GITHUB_TOKEN` env var when calling `niv add` 434 | ``` 435 | GITHUB_TOKEN=$YOUR_GITHUB_TOKEN niv add ... 436 | ``` 437 | 438 | ### How do I import and use the content of a source? 439 | 440 | The way to import a source depend mainly on the content targetted by this 441 | source. A source could be a file, a repository with no knowledge of nix 442 | or a repository already in the nix ecosystem. 443 | 444 | #### 1. Direct import of a nix based source 445 | 446 | In the case of a nix based source, you'll often find a `default.nix` at the 447 | root. Let's take this repository as example. We can add it to our `sources.json` 448 | with the following command. 449 | 450 | ``` shell 451 | $ niv add nmattia/niv 452 | ``` 453 | 454 | We can now import niv to use it a nix expression, e.g.: 455 | 456 | ``` nix 457 | { sources ? import nix/sources.nix }: 458 | let niv = import sources.niv {}; 459 | in { inherit niv; } # A glorious expression using the reference to niv 460 | ``` 461 | 462 | #### 2. Import of a nix based source via an overlay 463 | 464 | Rather than use the resulting derivation directly, you can add it to your custom 465 | nixpkgs via the overlay system. 466 | 467 | ``` nix 468 | { sources ? import nix/sources.nix}: 469 | let overlay = _: pkgs: { 470 | niv = (import sources.niv {}).niv; 471 | }; 472 | nixpkgs = import sources.nixpkgs { overlays = [ overlay ]; config = {}; }; 473 | in { inherit (nixpkgs) niv; } # A glorious expression where nixpkgs.niv is referenced 474 | ``` 475 | 476 | #### 3. Reference to the source's files in the nix store 477 | 478 | You can also reference a simple file, a folder or a repo without nix knowledge 479 | with niv. In these cases, you can use the source in your nix expression without 480 | importing it. 481 | 482 | The following exemple will compile gnu hello while using this technique to retrieve 483 | the source. First, we need to add the new source. 484 | 485 | ``` shell 486 | $ niv add hello-src -v 2.10 -t 'https://ftp.gnu.org/gnu/hello/hello-.tar.gz' 487 | ``` 488 | 489 | Then, we can use it inside a nix expression. 490 | 491 | ``` nix 492 | { sources ? import nix/sources.nix }: 493 | let hello_src = sources.hello-src; 494 | nixpkgs = import sources.nixpkgs {}; 495 | in nixpkgs.stdenv.mkDerivation { 496 | pname = "hello"; 497 | version = "custom"; 498 | src = hello_src; 499 | } 500 | ``` 501 | 502 | :warning: If you have problems, consider using the outPath of the source 503 | (e.g. `sources.hello-src.outPath`) instead of the source directly. See 504 | [this issue](https://github.com/nmattia/niv/issues/325) for more details. 505 | 506 | ### How do I import a subpath of a source? 507 | 508 | In order to use the directory `dir` of a `my-package`, use the following 509 | pattern: 510 | 511 | ``` nix 512 | let 513 | sources = import ./nix/sources.nix; 514 | in sources.my-package + "/dir" 515 | ``` 516 | 517 | in this example, `sources.my-package` becomes `my-package`'s root directory, and `+ "/dir"` appends the 518 | subdirectory. 519 | 520 | ### How do I import NixOS modules? 521 | 522 | After the package containing the modules has been `niv add`ed, importing the 523 | modules is straightforward: 524 | 525 | ``` nix 526 | let 527 | sources = import ./nix/sources.nix; 528 | in { 529 | imports = [ (sources.package + "/path/to/module") ]; 530 | } 531 | ``` 532 | 533 | ### Can I use local packages? 534 | 535 | If you need to use a local path as a source -- especially convenient when 536 | modifying dependencies -- `niv` allows you to override the `sources.json` via 537 | environment variables. To override a source `foo` with a local path 538 | `./bar/baz`, set the environment variable `NIV_OVERRIDE_foo` to `./bar/baz`. 539 | 540 | Generally, if the environment variable `NIV_OVERRIDE_` is set _and_ you 541 | have a source named `` then `niv` will use the value of 542 | `NIV_OVERRIDE_` as the `outPath` of that source. All non-alphanumeric 543 | characters in the source name are escaped to the character `_`; i.e. to 544 | override the package `my package-foo` you need to set the environment variable 545 | `NIV_OVERRIDE_my_package_foo`. 546 | 547 | ### Can I use a git dependency with submodules? 548 | 549 | Yes, however you need to follow some steps. 550 | 551 | Add your dependency as git dependency to your `sources.json`: 552 | ``` 553 | niv add git git@github.com:user/repo -n name 554 | ``` 555 | 556 | Add `"submodules": true,` to your dependecy in the source.json: 557 | ``` 558 | { 559 | "name": { 560 | "branch": "main", 561 | "repo": "git@github.com:user/repo", 562 | "rev": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 563 | "submodules": true, 564 | "type": "git" 565 | } 566 | } 567 | ``` 568 | -------------------------------------------------------------------------------- /README.tpl.md: -------------------------------------------------------------------------------- 1 | # niv 2 | 3 | [![Test](https://github.com/nmattia/niv/actions/workflows/test.yml/badge.svg)](https://github.com/nmattia/niv/actions/workflows/test.yml) 4 | [![Netlify Status](https://api.netlify.com/api/v1/badges/48532eaa-259f-4ca2-aadf-67f7c6b957fd/deploy-status)](https://niv.nmattia.com) 5 | 6 | Painless dependencies for [Nix] projects. Read more in the [Getting started](#getting-started) section below. 7 | 8 |

9 | 10 |

11 | 12 | 13 | * [Install](#install) 14 | * [Build](#build) 15 | * [Usage](#usage) 16 | * [FAQ](#frequently-asked-questions) 17 | 18 | ## Install 19 | 20 | `niv` is available in [`nixpkgs`](https://github.com/NixOS/nixpkgs) as `niv`: 21 | 22 | ``` bash 23 | $ nix-env -iA nixpkgs.niv 24 | ``` 25 | 26 | Alternatively, run the following command to install the development version: 27 | 28 | ``` bash 29 | $ nix-env -iA niv -f https://github.com/nmattia/niv/tarball/master \ 30 | --substituters https://niv.cachix.org \ 31 | --trusted-public-keys niv.cachix.org-1:X32PCg2e/zAm3/uD1ScqW2z/K0LtDyNV7RdaxIuLgQM= 32 | ``` 33 | 34 | ## Build 35 | 36 | Inside the provided nix shell: 37 | 38 | ``` bash 39 | $ repl 40 | ``` 41 | 42 | Run the test suite with this command: 43 | 44 | ``` bash 45 | $ ./script/test 46 | ``` 47 | 48 | ## Usage 49 | 50 | `niv` simplifies [adding](#add) and [updating](#update) dependencies in Nix 51 | projects. It uses a single file, `nix/sources.json`, where it stores the data 52 | necessary for fetching and updating the packages. 53 | 54 | * [Add](#add): inserts a package in `nix/sources.json`. 55 | * [Update](#update): updates one or all packages in `nix/sources.json`. 56 | * [Drop](#drop): deletes a package from `nix/sources.json`. 57 | 58 | `niv` has some utility functions: 59 | 60 | * [Init](#init): bootstraps a Nix project, in particular creates a 61 | `nix/sources.json` file containing `niv` and `nixpkgs` as well as a 62 | `nix/sources.nix` file that returns the sources as a Nix object. 63 | * [Show](#show): shows the packages' information. 64 | * [Modify](#modify): modifies attributes _without_ performing an update. 65 | 66 | ### Configuration 67 | 68 | The following environment variables are read by `niv`: 69 | 70 | | Name | Note | 71 | | --------------- | ---- | 72 | | GITHUB_TOKEN or NIV_GITHUB_TOKEN | When set, the value is used to authenticate GitHub API requests. | 73 | | GITHUB_HOST or NIV_GITHUB_HOST | The GitHub host to use when fetching packages. Port may be appended here. | 74 | | GITHUB_API_HOST or NIV_GITHUB_API_HOST | The host used when performing GitHub API requests. Use `GITHUB_API_PORT` for specifying the port. | 75 | | GITHUB_API_PORT or NIV_GITHUB_API_PORT | The port used when performing GitHub API requests. Defaults to `443` for secure requests. Defaults to `80` for insecure requests. See also: `GITHUB_INSECURE`. | 76 | | NIV_GITHUB_INSECURE | When set to anything but the empty string, requests are performed over `http` instead of `https`. | 77 | | NIV_GITHUB_PATH | The base path used when performing GitHub API requests. | 78 | 79 | The next two sections cover [common use cases](#getting-started) and [full command 80 | description](#commands). 81 | 82 | ### Getting started 83 | 84 | Nix is a very powerful tool for building code and setting up environments. `niv` complements it by making it easy to describe and update remote dependencies (URLs, GitHub repos, etc). It is a simple, practical alternative to [Nix flakes](https://wiki.nixos.org/wiki/Flakes). 85 | 86 | This section covers common use cases: 87 | 88 | * [Bootstrapping a Nix project](#bootstrapping-a-nix-project). 89 | * [Tracking a different nixpkgs branch](#tracking-a-nixpkgs-branch). 90 | * [Importing packages from GitHub](#importing-packages-from-github). 91 | * [Fetching packages from custom URLs](#using-custom-urls). 92 | 93 | #### Bootstrapping a Nix project 94 | 95 | Use the `init` command when starting a new Nix project or when porting an 96 | existing Nix project to niv: 97 | 98 | ``` shell 99 | $ niv init 100 | ... 101 | $ tree 102 | . 103 | └── nix 104 |    ├── sources.json 105 |    └── sources.nix 106 | 107 | 1 directory, 2 files 108 | ``` 109 | 110 | The file `nix/sources.json` is the file used by niv to store versions and is 111 | initialized with nixpkgs: 112 | 113 | ``` json 114 | { 115 | "nixpkgs": { 116 | "branch": "nixos-unstable", 117 | "description": "Nix Packages collection", 118 | "homepage": null, 119 | "owner": "NixOS", 120 | "repo": "nixpkgs", 121 | "rev": "6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe", 122 | "sha256": "16f329z831bq7l3wn1dfvbkh95l2gcggdwn6rk3cisdmv2aa3189", 123 | "type": "tarball", 124 | "url": "https://github.com/NixOS/nixpkgs/archive/6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe.tar.gz", 125 | "url_template": "https://github.com///archive/.tar.gz" 126 | } 127 | } 128 | ``` 129 | 130 | To use this dependency, `import` the file `nix/sources.nix`, e.g.: 131 | 132 | ``` nix 133 | { sources ? import ./sources.nix }: # import the sources 134 | import sources.nixpkgs # and use them again! 135 | { overlays = [] ; config = {}; } 136 | ``` 137 | 138 | For more information about importing sources to your nix files, check 139 | the [frequently asked questions](#Frequently-asked-questions). 140 | 141 | #### Tracking a nixpkgs branch 142 | 143 | The `init` command sets the `nix/sources.json` to the content of the file 144 | [data/nixpkgs.json](data/nixpkgs.json). Currently, you would be tracking the 145 | `nixos-unstable` branch. 146 | Run the following command to 147 | update it to the last commit of the configured branch: 148 | 149 | ``` shell 150 | $ niv update nixpkgs 151 | ``` 152 | 153 | To change the branch being tracked run this command: 154 | 155 | ``` shell 156 | $ niv update nixpkgs -b master # equivalent to --branch master 157 | ``` 158 | 159 | #### Importing packages from GitHub 160 | 161 | The `add` command will infer information about the package being added, when 162 | possible. This works very well for GitHub repositories. Run this command to add 163 | [jq] to your project: 164 | 165 | 166 | ``` shell 167 | $ niv add stedolan/jq 168 | ``` 169 | 170 | The following data was added in `nix/sources.json` for `jq`: 171 | 172 | ``` json 173 | { 174 | "homepage": "http://stedolan.github.io/jq/", 175 | "url": "https://github.com/stedolan/jq/archive/9fa2e51099c55af56e3e541dc4b399f11de74abe.tar.gz", 176 | "owner": "stedolan", 177 | "branch": "master", 178 | "url_template": "https://github.com///archive/.tar.gz", 179 | "repo": "jq", 180 | "sha256": "0819rvk8057qgcqvgn7fpldvly2pfdw9fxcjrlqa8gr59p8a1cic", 181 | "description": "Command-line JSON processor", 182 | "rev": "9fa2e51099c55af56e3e541dc4b399f11de74abe" 183 | } 184 | ``` 185 | 186 | #### Using custom URLs 187 | 188 | It is possible to use niv to fetch packages from custom URLs. Run this command 189 | to add the Haskell compiler [GHC] to your `nix/sources.json`: 190 | 191 | ``` shell 192 | $ niv add ghc \ 193 | -v 8.4.3 \ 194 | -t 'https://downloads.haskell.org/~ghc//ghc--i386-deb8-linux.tar.xz' 195 | ``` 196 | 197 | The option `-v` sets the "version" attribute to `8.4.3`. The option `-t` sets a 198 | template that can be reused by niv when fetching a new URL (see the 199 | documentation for [add](#add) and [update](#update)). 200 | 201 | The type of the dependency is guessed from the provided URL template, if `-T` 202 | is not specified. 203 | 204 | For updating the version of GHC used run this command: 205 | 206 | ``` shell 207 | $ niv update ghc -v 8.6.2 208 | ``` 209 | 210 | ### Commands 211 | 212 | ``` 213 | replace_niv_help 214 | ``` 215 | 216 | #### Add 217 | 218 | ``` 219 | replace_niv_add_help 220 | ``` 221 | 222 | #### Update 223 | 224 | ``` 225 | replace_niv_update_help 226 | ``` 227 | 228 | #### Modify 229 | 230 | ``` 231 | replace_niv_modify_help 232 | ``` 233 | 234 | #### Drop 235 | 236 | ``` 237 | replace_niv_drop_help 238 | ``` 239 | 240 | #### Init 241 | 242 | ``` 243 | replace_niv_init_help 244 | ``` 245 | 246 | #### show 247 | 248 | ``` 249 | replace_niv_show_help 250 | ``` 251 | 252 | [Nix]: https://nixos.org/nix/ 253 | [jq]: https://stedolan.github.io/jq/ 254 | [GHC]: https://www.haskell.org/ghc/ 255 | 256 | 257 | ## Frequently Asked Questions 258 | 259 | * [Can I use private GitHub repositories?](#can-i-use-private-github-repositories) 260 | * [How do I import and use the content of a source?](#how-do-i-import-and-use-the-content-of-a-source) 261 | * [How do I import a subpath of a source?](#how-do-i-import-a-subpath-of-a-source) 262 | * [How do I import NixOS modules](#how-do-i-import-nixos-modules) 263 | * [Can I use local packages?](#can-i-use-local-packages) 264 | * [Can I use git submodules?](#can-i-use-git-submodules) 265 | 266 | ### Can I use private GitHub repositories? 267 | 268 | Yes. There are two ways: 269 | 270 | #### 1. Use the git protocol 271 | 272 | When using the git protocol, your public SSH keypair is used to authenticate 273 | you: 274 | 275 | ``` shell 276 | $ niv add git git@github.com:my_user/my_private_repo 277 | ``` 278 | 279 | ##### 2. Use the netrc file 280 | 281 | in order to `niv add` a private github repo you'll need to: 282 | 283 | 1. create a .netrc file with the following content 284 | ``` 285 | machine github.com 286 | login YOUR_GITHUB_USER_NAME 287 | password YOUR_GITHUB_TOKEN 288 | ``` 289 | 290 | 2. add the path to the above file to `/etc/nix/nix.conf`: 291 | ``` 292 | netrc-file = /PATH/TO/.netrc 293 | ``` 294 | 295 | 3. set `GITHUB_TOKEN` env var when calling `niv add` 296 | ``` 297 | GITHUB_TOKEN=$YOUR_GITHUB_TOKEN niv add ... 298 | ``` 299 | 300 | ### How do I import and use the content of a source? 301 | 302 | The way to import a source depend mainly on the content targetted by this 303 | source. A source could be a file, a repository with no knowledge of nix 304 | or a repository already in the nix ecosystem. 305 | 306 | #### 1. Direct import of a nix based source 307 | 308 | In the case of a nix based source, you'll often find a `default.nix` at the 309 | root. Let's take this repository as example. We can add it to our `sources.json` 310 | with the following command. 311 | 312 | ``` shell 313 | $ niv add nmattia/niv 314 | ``` 315 | 316 | We can now import niv to use it a nix expression, e.g.: 317 | 318 | ``` nix 319 | { sources ? import nix/sources.nix }: 320 | let niv = import sources.niv {}; 321 | in { inherit niv; } # A glorious expression using the reference to niv 322 | ``` 323 | 324 | #### 2. Import of a nix based source via an overlay 325 | 326 | Rather than use the resulting derivation directly, you can add it to your custom 327 | nixpkgs via the overlay system. 328 | 329 | ``` nix 330 | { sources ? import nix/sources.nix}: 331 | let overlay = _: pkgs: { 332 | niv = (import sources.niv {}).niv; 333 | }; 334 | nixpkgs = import sources.nixpkgs { overlays = [ overlay ]; config = {}; }; 335 | in { inherit (nixpkgs) niv; } # A glorious expression where nixpkgs.niv is referenced 336 | ``` 337 | 338 | #### 3. Reference to the source's files in the nix store 339 | 340 | You can also reference a simple file, a folder or a repo without nix knowledge 341 | with niv. In these cases, you can use the source in your nix expression without 342 | importing it. 343 | 344 | The following exemple will compile gnu hello while using this technique to retrieve 345 | the source. First, we need to add the new source. 346 | 347 | ``` shell 348 | $ niv add hello-src -v 2.10 -t 'https://ftp.gnu.org/gnu/hello/hello-.tar.gz' 349 | ``` 350 | 351 | Then, we can use it inside a nix expression. 352 | 353 | ``` nix 354 | { sources ? import nix/sources.nix }: 355 | let hello_src = sources.hello-src; 356 | nixpkgs = import sources.nixpkgs {}; 357 | in nixpkgs.stdenv.mkDerivation { 358 | pname = "hello"; 359 | version = "custom"; 360 | src = hello_src; 361 | } 362 | ``` 363 | 364 | :warning: If you have problems, consider using the outPath of the source 365 | (e.g. `sources.hello-src.outPath`) instead of the source directly. See 366 | [this issue](https://github.com/nmattia/niv/issues/325) for more details. 367 | 368 | ### How do I import a subpath of a source? 369 | 370 | In order to use the directory `dir` of a `my-package`, use the following 371 | pattern: 372 | 373 | ``` nix 374 | let 375 | sources = import ./nix/sources.nix; 376 | in sources.my-package + "/dir" 377 | ``` 378 | 379 | in this example, `sources.my-package` becomes `my-package`'s root directory, and `+ "/dir"` appends the 380 | subdirectory. 381 | 382 | ### How do I import NixOS modules? 383 | 384 | After the package containing the modules has been `niv add`ed, importing the 385 | modules is straightforward: 386 | 387 | ``` nix 388 | let 389 | sources = import ./nix/sources.nix; 390 | in { 391 | imports = [ (sources.package + "/path/to/module") ]; 392 | } 393 | ``` 394 | 395 | ### Can I use local packages? 396 | 397 | If you need to use a local path as a source -- especially convenient when 398 | modifying dependencies -- `niv` allows you to override the `sources.json` via 399 | environment variables. To override a source `foo` with a local path 400 | `./bar/baz`, set the environment variable `NIV_OVERRIDE_foo` to `./bar/baz`. 401 | 402 | Generally, if the environment variable `NIV_OVERRIDE_` is set _and_ you 403 | have a source named `` then `niv` will use the value of 404 | `NIV_OVERRIDE_` as the `outPath` of that source. All non-alphanumeric 405 | characters in the source name are escaped to the character `_`; i.e. to 406 | override the package `my package-foo` you need to set the environment variable 407 | `NIV_OVERRIDE_my_package_foo`. 408 | 409 | ### Can I use a git dependency with submodules? 410 | 411 | Yes, however you need to follow some steps. 412 | 413 | Add your dependency as git dependency to your `sources.json`: 414 | ``` 415 | niv add git git@github.com:user/repo -n name 416 | ``` 417 | 418 | Add `"submodules": true,` to your dependecy in the source.json: 419 | ``` 420 | { 421 | "name": { 422 | "branch": "main", 423 | "repo": "git@github.com:user/repo", 424 | "rev": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 425 | "submodules": true, 426 | "type": "git" 427 | } 428 | } 429 | ``` 430 | -------------------------------------------------------------------------------- /app/Niv.hs: -------------------------------------------------------------------------------- 1 | module Niv where 2 | 3 | import Niv.Cli 4 | 5 | main :: IO () 6 | main = Niv.Cli.cli 7 | -------------------------------------------------------------------------------- /app/NivTest.hs: -------------------------------------------------------------------------------- 1 | module NivTest where 2 | 3 | import Niv.Test 4 | 5 | main :: IO () 6 | main = Niv.Test.test 7 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | `./nixpkgs.json` is the default value used by niv when initializing `nixpkgs`. 4 | The executable fetches the latest version from the repo. 5 | -------------------------------------------------------------------------------- /data/nixpkgs.json: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "nixos-unstable", 3 | "description": "Nix Packages collection", 4 | "homepage": null, 5 | "owner": "NixOS", 6 | "repo": "nixpkgs", 7 | "rev": "6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe", 8 | "sha256": "16f329z831bq7l3wn1dfvbkh95l2gcggdwn6rk3cisdmv2aa3189", 9 | "type": "tarball", 10 | "url": "https://github.com/NixOS/nixpkgs/archive/6c43a3495a11e261e5f41e5d7eda2d71dae1b2fe.tar.gz", 11 | "url_template": "https://github.com///archive/.tar.gz" 12 | } 13 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./nix/sources.nix 2 | , pkgs ? import ./nix { inherit sources; } 3 | }: 4 | 5 | let 6 | sourceByRegex = name: src: regexes: 7 | builtins.path { 8 | filter = path: type: 9 | let 10 | relPath = pkgs.lib.removePrefix (toString src + "/") (toString path); 11 | accept = pkgs.lib.any (re: builtins.match re relPath != null) regexes; 12 | in 13 | accept; 14 | inherit name; 15 | path = src; 16 | }; 17 | 18 | niv-source = sourceByRegex "niv" ./. [ 19 | "^package.yaml$" 20 | "^README.md$" 21 | "^LICENSE$" 22 | "^app$" 23 | "^app.*.hs$" 24 | "^src$" 25 | "^src/Data$" 26 | "^src/Data/Aeson$" 27 | "^src/Data/HashMap$" 28 | "^src/Data/HashMap/Strict$" 29 | "^src/Data/Text$" 30 | "^src/Niv$" 31 | "^src/Niv/Git$" 32 | "^src/Niv/GitHub$" 33 | "^src/Niv/Local$" 34 | "^src/Niv/Sources$" 35 | "^src/Niv/Update$" 36 | "^src.*.hs$" 37 | "^README.md$" 38 | "^nix$" 39 | "^nix.sources.nix$" 40 | ]; 41 | 42 | haskellPackages = pkgs.haskellPackages.override { 43 | overrides = self: super: { 44 | 45 | niv = 46 | pkgs.haskell.lib.justStaticExecutables ( 47 | pkgs.haskell.lib.failOnAllWarnings ( 48 | pkgs.haskell.lib.disableExecutableProfiling ( 49 | pkgs.haskell.lib.disableLibraryProfiling ( 50 | pkgs.haskellPackages.generateOptparseApplicativeCompletions [ "niv" ] ( 51 | (pkgs.callPackage ./foo { haskellPackages = self; }).buildPackage { root = ./.; src = niv-source; } 52 | ) 53 | ) 54 | ) 55 | ) 56 | ); 57 | }; 58 | }; 59 | 60 | inherit (haskellPackages) niv; 61 | 62 | niv-sdist = pkgs.haskell.lib.sdistTarball niv; 63 | 64 | niv-cabal-upload = 65 | let 66 | niv-version = niv.version; 67 | in 68 | pkgs.writeShellScript "cabal-upload" 69 | '' 70 | cabal upload "$@" "${niv-sdist}/niv-${niv-version}.tar.gz" 71 | ''; 72 | 73 | # WARNING: extremely disgusting hack below. 74 | # 75 | # 76 | # I was trying to fix this issue: https://github.com/nmattia/niv/issues/109 77 | # Basically, --help should also show niv's version. As usual when trying to 78 | # lookup the documentation for `Paths_` I google "cabal Paths_ module", end 79 | # up here: 80 | # https://stackoverflow.com/questions/21588500/haskell-cabal-package-cant-find-paths-module, 81 | # click this link: 82 | # https://downloads.haskell.org/~ghc/7.0.3/docs/html/Cabal/authors.html#paths-module, 83 | # and then try to find my way to the latest doc. 84 | # 85 | # (╯°□°)╯︵ ┻━┻ 86 | # 87 | # But now that we're using cabal Paths_, the `ghci` `repl` function won't 88 | # work! So I wonder: stack or cabal? Well, stack, it's been a while! But 89 | # _of course_ there's no way to easily tell stack to use the system GHC! 90 | # Even if there's no `stack.yaml`, stack will infer "some" LTS and 91 | # `--system-ghc` will be discarded because the GHC versions don't match!! 92 | # 93 | # (╯°□°)╯︵ ┻━┻ 94 | # 95 | # So I give good old `cabal repl` a try and, (not much of a) surprise, it 96 | # complains that it can't start because niv contains more than one cabal 97 | # component. 98 | # 99 | # (╯°□°)╯︵ ┻━┻ 100 | # 101 | # WTF, there's now repl, new-repl, v1-repl, v2-repl. WHAT? 102 | # 103 | # (˚Õ˚)ر ~~~~╚╩╩╝ 104 | # 105 | # So I try `cabal new-repl`, and it doesn't load _any_ main function 106 | # (without having to actually write `main = Niv.Test.test` or doing weird 107 | # stuff to load a `Main` module). 108 | # 109 | # (╯°□°)╯︵ ┻━┻ 110 | # 111 | # And there's no `cabal new-repl -main-is ...` 112 | # 113 | # (╯°□°)╯︵ ┻━┻ 114 | # 115 | # The workaround here: 116 | # https://github.com/haskell/cabal/issues/5374#issuecomment-410431619 117 | # suggests using -ghci-script=foo where foo specifies `main = bar`, but, as 118 | # pointed out, the ghci script runs too early (and that's after having 119 | # tried --ghci-options which doesn't work; must use --repl-options). 120 | # 121 | # (╯°□°)╯︵ ┻━┻ (˚Õ˚)ر ~~~~╚╩╩╝ 122 | # 123 | # But there's hope! The `cabal new-repl` doc says you can load a 124 | # "component". So I try with `cabal new-repl niv-test`. FIRST it builds 125 | # everything (actually generates `.o`s and stuff) and then loads the 126 | # _wrong_ module. At this point I can't help thinking that a monkey 127 | # (bonobo, baboon, pick any) will still manage to pick the correct main 50% 128 | # of the time. I don't want to jump to conclusion: I only gave cabal one 129 | # chance, so I'm not sure how exactly it scores on the "pick the correct 130 | # main" game. 131 | # 132 | # (゜-゜) 133 | # 134 | # Well, 135 | # => rm -rf ~/.stack 136 | # => rm -rf ~/.cabal 137 | # => rm -rf dist 138 | # => rm -rf dist-newstyle 139 | # 140 | # 141 | # 142 | # In order to make `Paths_niv(version)` available in `ghci`, we parse the 143 | # version from `package.yaml` and create a dummy module that we inject in the 144 | # `ghci` command. 145 | niv-devshell = haskellPackages.shellFor { 146 | packages = ps: [ ps.niv ]; 147 | buildInputs = [ pkgs.ormolu pkgs.glibcLocales ]; 148 | shellHook = '' 149 | repl_for() { 150 | haskell_version=$(jq <./package.yaml -cMr '.version' | sed 's/\./,/g') 151 | 152 | paths_niv=$(mktemp -d)/Paths_niv.hs 153 | 154 | echo "module Paths_niv where" >> $paths_niv 155 | echo "import qualified Data.Version" >> $paths_niv 156 | echo "version :: Data.Version.Version" >> $paths_niv 157 | echo "version = Data.Version.Version [$haskell_version] []" >> $paths_niv 158 | 159 | niv_main="" 160 | 161 | shopt -s globstar 162 | ghci -clear-package-db -global-package-db -Wall app/$1.hs src/**/*.hs $paths_niv 163 | } 164 | 165 | repl() { 166 | repl_for NivTest 167 | } 168 | 169 | repl_niv() { 170 | repl_for Niv 171 | } 172 | 173 | echo "To start a REPL for the test suite, run:" 174 | echo " > repl" 175 | echo " > :main" 176 | echo " (tests run)" 177 | echo 178 | echo "To start a REPL session emulating the niv executable, run:" 179 | echo " > repl_niv" 180 | echo " > :main --help ..." 181 | echo " NIV - Version manager for Nix projects" 182 | echo " ..." 183 | ''; 184 | }; 185 | in 186 | rec 187 | { 188 | inherit niv niv-sdist niv-source niv-devshell niv-cabal-upload; 189 | 190 | tests-github = pkgs.callPackage ./tests/github { inherit niv; }; 191 | tests-git = pkgs.callPackage ./tests/git { inherit niv; }; 192 | tests-eval = pkgs.callPackage ./tests/eval { }; 193 | 194 | fmt-check = 195 | pkgs.stdenv.mkDerivation 196 | { 197 | name = "fmt-check"; 198 | buildInputs = [ pkgs.ormolu pkgs.glibcLocales ]; 199 | src = niv-source; 200 | phases = [ "unpackPhase" "checkPhase" ]; 201 | LANG = "en_US.UTF-8"; 202 | checkPhase = '' 203 | cp ${./script/fmt} ./fmt 204 | patchShebangs ./fmt 205 | chmod +x fmt 206 | bash fmt -c 207 | touch $out 208 | ''; 209 | doCheck = true; 210 | }; 211 | 212 | readme = pkgs.runCommand "README.md" { nativeBuildInputs = [ niv pkgs.moreutils ]; } 213 | '' 214 | cp ${./README.tpl.md} $out 215 | chmod +w $out 216 | 217 | sed "/replace_niv_help/r"<(niv --help) $out | sponge $out 218 | sed "/replace_niv_help/d" $out | sponge $out 219 | 220 | sed "/replace_niv_add_help/r"<(niv add --help) $out | sponge $out $out 221 | sed "/replace_niv_add_help/d" $out | sponge $out 222 | 223 | sed "/replace_niv_update_help/r"<(niv update --help) $out| sponge $out $out 224 | sed "/replace_niv_update_help/d" $out | sponge $out 225 | 226 | sed "/replace_niv_modify_help/r"<(niv modify --help) $out | sponge $out 227 | sed "/replace_niv_modify_help/d" $out | sponge $out 228 | 229 | sed "/replace_niv_drop_help/r"<(niv drop --help) $out | sponge $out 230 | sed "/replace_niv_drop_help/d" $out | sponge $out 231 | 232 | sed "/replace_niv_init_help/r"<(niv init --help) $out | sponge $out 233 | sed "/replace_niv_init_help/d" $out | sponge $out 234 | 235 | sed "/replace_niv_show_help/r"<(niv show --help) $out | sponge $out 236 | sed "/replace_niv_show_help/d" $out | sponge $out 237 | 238 | echo done 239 | ''; 240 | 241 | readme-test = pkgs.runCommand "README-test" { } 242 | '' 243 | err() { 244 | echo 245 | echo -e "\e[31mERR\e[0m: README.md out of date" 246 | echo -e "please run \e[1m./script/gen\e[0m" 247 | echo 248 | exit 1 249 | } 250 | 251 | diff ${./README.md} ${readme} && echo dummy > $out || err ; 252 | ''; 253 | 254 | niv-svg-test = pkgs.runCommand "niv-svg-test" { } 255 | '' 256 | # XXX: This test means that the svg needs to be regenerated 257 | # by hand on (virtually) every commit. 258 | # TODO: figure out a nicer way 259 | touch $out 260 | exit 0 261 | 262 | err() { 263 | echo 264 | echo -e "\e[31mERR\e[0m: niv.svg out of date" 265 | echo -e "please run \e[1m./script/gen\e[0m" 266 | echo 267 | exit 1 268 | } 269 | 270 | expected_hash=$(${pkgs.nix}/bin/nix-hash ${niv-svg-gen}) 271 | actual_hash=$(grep -oP 'id="\K[^"]+' ${./niv.svg} -m 1) 272 | 273 | echo "expected $expected_hash" 274 | echo "actual $actual_hash" 275 | 276 | [ $expected_hash == $actual_hash ] && echo dymmy > $out || err 277 | ''; 278 | 279 | # TODO: use nivForTest for this one 280 | niv-svg-cmds = pkgs.writeScript "niv-svg-cmds" 281 | '' 282 | #!${pkgs.stdenv.shell} 283 | set -euo pipefail 284 | echo '$ niv init' 285 | niv init 286 | echo 287 | echo '$ niv add stedolan/jq' 288 | niv add stedolan/jq 289 | ''; 290 | 291 | niv-svg-gen = pkgs.writeScript "niv-svg-gen" 292 | '' 293 | #!${pkgs.stdenv.shell} 294 | set -euo pipefail 295 | export PATH=${haskellPackages.niv}/bin:${pkgs.nix}/bin:$PATH 296 | 297 | pushd $(mktemp -d) 298 | ${pkgs.termtosvg}/bin/termtosvg \ 299 | -g 82x26 -M 2000 -m 2000 -t gjm8 \ 300 | -c '${niv-svg-cmds}' $PWD/niv.svg 301 | 302 | echo done rendering 303 | popd 304 | ''; 305 | } 306 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | 3 | set (CMAKE_CXX_STANDARD 17) 4 | 5 | # ---[ Project name 6 | project(hello-world) 7 | 8 | # ---[ Output directory 9 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 10 | 11 | # ---[ Using cmake scripts and modules 12 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) 13 | 14 | # ---[ Includes 15 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src/include) 16 | include_directories(${EXTERNAL_INCLUDE_DIRECTORIES}) 17 | 18 | if(NOT CMAKE_BUILD_TYPE) 19 | set(CMAKE_BUILD_TYPE Release) 20 | endif() 21 | 22 | # ---[ Compiler flags 23 | set(CMAKE_CXX_FLAGS_DEBUG "-g -pthread") 24 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -pthread") 25 | add_compile_options(-Wall -Wextra -pedantic -Werror) 26 | 27 | # ---[ Handle dependencies 28 | include(cmake/Dependencies.cmake) 29 | 30 | # ---[ Subdirectories 31 | add_subdirectory(src) 32 | 33 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/cmake/Dependencies.cmake: -------------------------------------------------------------------------------- 1 | # ---[ LibOsmium 2 | find_package(Osmium REQUIRED COMPONENTS pbf) 3 | include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS}) 4 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/cmake/Modules/FindOsmium.cmake: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # 3 | # FindOsmium.cmake 4 | # 5 | # Find the Libosmium headers and, optionally, several components needed for 6 | # different Libosmium functions. 7 | # 8 | #---------------------------------------------------------------------- 9 | # 10 | # Usage: 11 | # 12 | # Copy this file somewhere into your project directory, where cmake can 13 | # find it. Usually this will be a directory called "cmake" which you can 14 | # add to the CMake module search path with the following line in your 15 | # CMakeLists.txt: 16 | # 17 | # list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 18 | # 19 | # Then add the following in your CMakeLists.txt: 20 | # 21 | # find_package(Osmium REQUIRED COMPONENTS ) 22 | # include_directories(SYSTEM ${OSMIUM_INCLUDE_DIRS}) 23 | # 24 | # For the substitute a space separated list of one or more of the 25 | # following components: 26 | # 27 | # pbf - include libraries needed for PBF input and output 28 | # xml - include libraries needed for XML input and output 29 | # io - include libraries needed for any type of input/output 30 | # geos - include if you want to use any of the GEOS functions 31 | # gdal - include if you want to use any of the OGR functions 32 | # proj - include if you want to use any of the Proj.4 functions 33 | # sparsehash - include if you use the sparsehash index 34 | # 35 | # You can check for success with something like this: 36 | # 37 | # if(NOT OSMIUM_FOUND) 38 | # message(WARNING "Libosmium not found!\n") 39 | # endif() 40 | # 41 | #---------------------------------------------------------------------- 42 | # 43 | # Variables: 44 | # 45 | # OSMIUM_FOUND - True if Osmium found. 46 | # OSMIUM_INCLUDE_DIRS - Where to find include files. 47 | # OSMIUM_XML_LIBRARIES - Libraries needed for XML I/O. 48 | # OSMIUM_PBF_LIBRARIES - Libraries needed for PBF I/O. 49 | # OSMIUM_IO_LIBRARIES - Libraries needed for XML or PBF I/O. 50 | # OSMIUM_LIBRARIES - All libraries Osmium uses somewhere. 51 | # 52 | #---------------------------------------------------------------------- 53 | 54 | # Look for the header file. 55 | find_path(OSMIUM_INCLUDE_DIR osmium/osm.hpp 56 | PATH_SUFFIXES include 57 | PATHS 58 | ../libosmium 59 | ~/Library/Frameworks 60 | /Library/Frameworks 61 | /opt/local # DarwinPorts 62 | /opt 63 | ) 64 | 65 | set(OSMIUM_INCLUDE_DIRS "${OSMIUM_INCLUDE_DIR}") 66 | 67 | #---------------------------------------------------------------------- 68 | # 69 | # Check for optional components 70 | # 71 | #---------------------------------------------------------------------- 72 | if(Osmium_FIND_COMPONENTS) 73 | foreach(_component ${Osmium_FIND_COMPONENTS}) 74 | string(TOUPPER ${_component} _component_uppercase) 75 | set(Osmium_USE_${_component_uppercase} TRUE) 76 | endforeach() 77 | endif() 78 | 79 | #---------------------------------------------------------------------- 80 | # Component 'io' is an alias for 'pbf' and 'xml' 81 | if(Osmium_USE_IO) 82 | set(Osmium_USE_PBF TRUE) 83 | set(Osmium_USE_XML TRUE) 84 | endif() 85 | 86 | #---------------------------------------------------------------------- 87 | # Component 'ogr' is an alias for 'gdal' 88 | if(Osmium_USE_OGR) 89 | set(Osmium_USE_GDAL TRUE) 90 | endif() 91 | 92 | #---------------------------------------------------------------------- 93 | # Component 'pbf' 94 | if(Osmium_USE_PBF) 95 | find_package(ZLIB) 96 | find_package(Threads) 97 | 98 | list(APPEND OSMIUM_EXTRA_FIND_VARS ZLIB_FOUND Threads_FOUND) 99 | if(ZLIB_FOUND AND Threads_FOUND) 100 | list(APPEND OSMIUM_PBF_LIBRARIES 101 | ${ZLIB_LIBRARIES} 102 | ${CMAKE_THREAD_LIBS_INIT} 103 | ) 104 | if(WIN32) 105 | list(APPEND OSMIUM_PBF_LIBRARIES ws2_32) 106 | endif() 107 | list(APPEND OSMIUM_INCLUDE_DIRS 108 | ${ZLIB_INCLUDE_DIR} 109 | ) 110 | else() 111 | message(WARNING "Osmium: Can not find some libraries for PBF input/output, please install them or configure the paths.") 112 | endif() 113 | endif() 114 | 115 | #---------------------------------------------------------------------- 116 | # Component 'xml' 117 | if(Osmium_USE_XML) 118 | find_package(EXPAT) 119 | find_package(BZip2) 120 | find_package(ZLIB) 121 | find_package(Threads) 122 | 123 | list(APPEND OSMIUM_EXTRA_FIND_VARS EXPAT_FOUND BZIP2_FOUND ZLIB_FOUND Threads_FOUND) 124 | if(EXPAT_FOUND AND BZIP2_FOUND AND ZLIB_FOUND AND Threads_FOUND) 125 | list(APPEND OSMIUM_XML_LIBRARIES 126 | ${EXPAT_LIBRARIES} 127 | ${BZIP2_LIBRARIES} 128 | ${ZLIB_LIBRARIES} 129 | ${CMAKE_THREAD_LIBS_INIT} 130 | ) 131 | list(APPEND OSMIUM_INCLUDE_DIRS 132 | ${EXPAT_INCLUDE_DIR} 133 | ${BZIP2_INCLUDE_DIR} 134 | ${ZLIB_INCLUDE_DIR} 135 | ) 136 | else() 137 | message(WARNING "Osmium: Can not find some libraries for XML input/output, please install them or configure the paths.") 138 | endif() 139 | endif() 140 | 141 | #---------------------------------------------------------------------- 142 | list(APPEND OSMIUM_IO_LIBRARIES 143 | ${OSMIUM_PBF_LIBRARIES} 144 | ${OSMIUM_XML_LIBRARIES} 145 | ) 146 | 147 | list(APPEND OSMIUM_LIBRARIES 148 | ${OSMIUM_IO_LIBRARIES} 149 | ) 150 | 151 | #---------------------------------------------------------------------- 152 | # Component 'geos' 153 | if(Osmium_USE_GEOS) 154 | find_path(GEOS_INCLUDE_DIR geos/geom.h) 155 | find_library(GEOS_LIBRARY NAMES geos) 156 | 157 | list(APPEND OSMIUM_EXTRA_FIND_VARS GEOS_INCLUDE_DIR GEOS_LIBRARY) 158 | if(GEOS_INCLUDE_DIR AND GEOS_LIBRARY) 159 | SET(GEOS_FOUND 1) 160 | list(APPEND OSMIUM_LIBRARIES ${GEOS_LIBRARY}) 161 | list(APPEND OSMIUM_INCLUDE_DIRS ${GEOS_INCLUDE_DIR}) 162 | else() 163 | message(WARNING "Osmium: GEOS library is required but not found, please install it or configure the paths.") 164 | endif() 165 | endif() 166 | 167 | #---------------------------------------------------------------------- 168 | # Component 'gdal' (alias 'ogr') 169 | if(Osmium_USE_GDAL) 170 | find_package(GDAL) 171 | 172 | list(APPEND OSMIUM_EXTRA_FIND_VARS GDAL_FOUND) 173 | if(GDAL_FOUND) 174 | list(APPEND OSMIUM_LIBRARIES ${GDAL_LIBRARIES}) 175 | list(APPEND OSMIUM_INCLUDE_DIRS ${GDAL_INCLUDE_DIRS}) 176 | else() 177 | message(WARNING "Osmium: GDAL library is required but not found, please install it or configure the paths.") 178 | endif() 179 | endif() 180 | 181 | #---------------------------------------------------------------------- 182 | # Component 'proj' 183 | if(Osmium_USE_PROJ) 184 | find_path(PROJ_INCLUDE_DIR proj_api.h) 185 | find_library(PROJ_LIBRARY NAMES proj) 186 | 187 | list(APPEND OSMIUM_EXTRA_FIND_VARS PROJ_INCLUDE_DIR PROJ_LIBRARY) 188 | if(PROJ_INCLUDE_DIR AND PROJ_LIBRARY) 189 | set(PROJ_FOUND 1) 190 | list(APPEND OSMIUM_LIBRARIES ${PROJ_LIBRARY}) 191 | list(APPEND OSMIUM_INCLUDE_DIRS ${PROJ_INCLUDE_DIR}) 192 | else() 193 | message(WARNING "Osmium: PROJ.4 library is required but not found, please install it or configure the paths.") 194 | endif() 195 | endif() 196 | 197 | #---------------------------------------------------------------------- 198 | # Component 'sparsehash' 199 | if(Osmium_USE_SPARSEHASH) 200 | find_path(SPARSEHASH_INCLUDE_DIR google/sparsetable) 201 | 202 | list(APPEND OSMIUM_EXTRA_FIND_VARS SPARSEHASH_INCLUDE_DIR) 203 | if(SPARSEHASH_INCLUDE_DIR) 204 | # Find size of sparsetable::size_type. This does not work on older 205 | # CMake versions because they can do this check only in C, not in C++. 206 | if (NOT CMAKE_VERSION VERSION_LESS 3.0) 207 | include(CheckTypeSize) 208 | set(CMAKE_REQUIRED_INCLUDES ${SPARSEHASH_INCLUDE_DIR}) 209 | set(CMAKE_EXTRA_INCLUDE_FILES "google/sparsetable") 210 | check_type_size("google::sparsetable::size_type" SPARSETABLE_SIZE_TYPE LANGUAGE CXX) 211 | set(CMAKE_EXTRA_INCLUDE_FILES) 212 | set(CMAKE_REQUIRED_INCLUDES) 213 | else() 214 | set(SPARSETABLE_SIZE_TYPE ${CMAKE_SIZEOF_VOID_P}) 215 | endif() 216 | 217 | # Sparsetable::size_type must be at least 8 bytes (64bit), otherwise 218 | # OSM object IDs will not fit. 219 | if(SPARSETABLE_SIZE_TYPE GREATER 7) 220 | set(SPARSEHASH_FOUND 1) 221 | add_definitions(-DOSMIUM_WITH_SPARSEHASH=${SPARSEHASH_FOUND}) 222 | list(APPEND OSMIUM_INCLUDE_DIRS ${SPARSEHASH_INCLUDE_DIR}) 223 | else() 224 | message(WARNING "Osmium: Disabled Google SparseHash library on 32bit system (size_type=${SPARSETABLE_SIZE_TYPE}).") 225 | endif() 226 | else() 227 | message(WARNING "Osmium: Google SparseHash library is required but not found, please install it or configure the paths.") 228 | endif() 229 | endif() 230 | 231 | #---------------------------------------------------------------------- 232 | 233 | list(REMOVE_DUPLICATES OSMIUM_INCLUDE_DIRS) 234 | 235 | if(OSMIUM_XML_LIBRARIES) 236 | list(REMOVE_DUPLICATES OSMIUM_XML_LIBRARIES) 237 | endif() 238 | 239 | if(OSMIUM_PBF_LIBRARIES) 240 | list(REMOVE_DUPLICATES OSMIUM_PBF_LIBRARIES) 241 | endif() 242 | 243 | if(OSMIUM_IO_LIBRARIES) 244 | list(REMOVE_DUPLICATES OSMIUM_IO_LIBRARIES) 245 | endif() 246 | 247 | if(OSMIUM_LIBRARIES) 248 | list(REMOVE_DUPLICATES OSMIUM_LIBRARIES) 249 | endif() 250 | 251 | #---------------------------------------------------------------------- 252 | # 253 | # Check that all required libraries are available 254 | # 255 | #---------------------------------------------------------------------- 256 | if (OSMIUM_EXTRA_FIND_VARS) 257 | list(REMOVE_DUPLICATES OSMIUM_EXTRA_FIND_VARS) 258 | endif() 259 | # Handle the QUIETLY and REQUIRED arguments and set OSMIUM_FOUND to TRUE if 260 | # all listed variables are TRUE. 261 | include(FindPackageHandleStandardArgs) 262 | find_package_handle_standard_args(Osmium REQUIRED_VARS OSMIUM_INCLUDE_DIR ${OSMIUM_EXTRA_FIND_VARS}) 263 | unset(OSMIUM_EXTRA_FIND_VARS) 264 | 265 | #---------------------------------------------------------------------- 266 | # 267 | # Add compiler flags 268 | # 269 | #---------------------------------------------------------------------- 270 | add_definitions(-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64) 271 | 272 | if(MSVC) 273 | add_definitions(-wd4996) 274 | 275 | # Disable warning C4068: "unknown pragma" because we want it to ignore 276 | # pragmas for other compilers. 277 | add_definitions(-wd4068) 278 | 279 | # Disable warning C4715: "not all control paths return a value" because 280 | # it generates too many false positives. 281 | add_definitions(-wd4715) 282 | 283 | # Disable warning C4351: new behavior: elements of array '...' will be 284 | # default initialized. The new behaviour is correct and we don't support 285 | # old compilers anyway. 286 | add_definitions(-wd4351) 287 | 288 | add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS) 289 | endif() 290 | 291 | if(APPLE) 292 | # following only available from cmake 2.8.12: 293 | # add_compile_options(-stdlib=libc++) 294 | # so using this instead: 295 | add_definitions(-stdlib=libc++) 296 | set(LDFLAGS ${LDFLAGS} -stdlib=libc++) 297 | endif() 298 | 299 | #---------------------------------------------------------------------- 300 | 301 | # This is a set of recommended warning options that can be added when compiling 302 | # libosmium code. 303 | if(MSVC) 304 | set(OSMIUM_WARNING_OPTIONS "/W3 /wd4514" CACHE STRING "Recommended warning options for libosmium") 305 | else() 306 | set(OSMIUM_WARNING_OPTIONS "-Wall -Wextra -pedantic -Wredundant-decls -Wdisabled-optimization -Wctor-dtor-privacy -Wnon-virtual-dtor -Woverloaded-virtual -Wsign-promo -Wold-style-cast" CACHE STRING "Recommended warning options for libosmium") 307 | endif() 308 | 309 | set(OSMIUM_DRACONIC_CLANG_OPTIONS "-Wdocumentation -Wunused-exception-parameter -Wmissing-declarations -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-unused-macros -Wno-exit-time-destructors -Wno-global-constructors -Wno-padded -Wno-switch-enum -Wno-missing-prototypes -Wno-weak-vtables -Wno-cast-align -Wno-float-equal") 310 | 311 | if(Osmium_DEBUG) 312 | message(STATUS "OSMIUM_XML_LIBRARIES=" ${OSMIUM_XML_LIBRARIES}) 313 | message(STATUS "OSMIUM_PBF_LIBRARIES=" ${OSMIUM_PBF_LIBRARIES}) 314 | message(STATUS "OSMIUM_IO_LIBRARIES=" ${OSMIUM_IO_LIBRARIES}) 315 | message(STATUS "OSMIUM_LIBRARIES=" ${OSMIUM_LIBRARIES}) 316 | message(STATUS "OSMIUM_INCLUDE_DIRS=" ${OSMIUM_INCLUDE_DIRS}) 317 | endif() 318 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ./nix/sources.nix; 3 | pkgs = import sources.nixpkgs { 4 | overlays = [ 5 | (_: _: { inherit sources; }) 6 | (import ./overlay.nix) 7 | ]; 8 | }; 9 | in 10 | with pkgs; 11 | 12 | stdenv.mkDerivation { 13 | name = "nix-cpp-demo"; 14 | nativeBuildInputs = [ cmake pkg-config ]; 15 | src = pkgs.lib.cleanSource ./.; 16 | 17 | # tell Cmake location of all headers 18 | cmakeFlags = [ 19 | "-DEXTERNAL_INCLUDE_DIRECTORIES=${lib.strings.makeSearchPathOutput "dev" "include" libosmium.buildInputs}" 20 | ]; 21 | 22 | buildInputs = lib.lists.concatLists [ 23 | # We want to check if dependencies exist using find_package 24 | [ 25 | libosmium.buildInputs 26 | ] 27 | # dependencies 28 | [ 29 | libosmium 30 | ] 31 | ]; 32 | 33 | installPhase = '' 34 | mkdir -p $out/bin 35 | cp bin/hello-world $out/bin 36 | ''; 37 | } 38 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "libosmium": { 3 | "branch": "master", 4 | "description": "Fast and flexible C++ library for working with OpenStreetMap data.", 5 | "homepage": "https://osmcode.org/libosmium/", 6 | "owner": "osmcode", 7 | "repo": "libosmium", 8 | "rev": "bf61abd59f3ded669da2bb98fd066ea05ec17c04", 9 | "sha256": "0pj29hihlnpz1dnjxm87qlzxzlcvyhkv5zq3x3919i55f2xyjd5s", 10 | "type": "tarball", 11 | "url": "https://github.com/osmcode/libosmium/archive/bf61abd59f3ded669da2bb98fd066ea05ec17c04.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "master", 16 | "description": "Nix Packages collection", 17 | "homepage": null, 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "2e4dec050ba7613b98043181d5ca82d1e943ef0a", 21 | "sha256": "0bcnx3izyfzl8c0k468fgf3895zkmma9k6kjw0igzlak1k53rjvn", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/2e4dec050ba7613b98043181d5ca82d1e943ef0a.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | spec.ref or ( 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" 34 | ); 35 | submodules = spec.submodules or false; 36 | submoduleArg = 37 | let 38 | nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; 39 | emptyArgWithWarning = 40 | if submodules 41 | then 42 | builtins.trace 43 | ( 44 | "The niv input \"${name}\" uses submodules " 45 | + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " 46 | + "does not support them" 47 | ) 48 | { } 49 | else { }; 50 | in 51 | if nixSupportsSubmodules 52 | then { inherit submodules; } 53 | else emptyArgWithWarning; 54 | in 55 | builtins.fetchGit 56 | ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); 57 | 58 | fetch_local = spec: spec.path; 59 | 60 | fetch_builtin-tarball = name: throw 61 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 62 | $ niv modify ${name} -a type=tarball -a builtin=true''; 63 | 64 | fetch_builtin-url = name: throw 65 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 66 | $ niv modify ${name} -a type=file -a builtin=true''; 67 | 68 | # 69 | # Various helpers 70 | # 71 | 72 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 73 | sanitizeName = name: 74 | ( 75 | concatMapStrings (s: if builtins.isList s then "-" else s) 76 | ( 77 | builtins.split "[^[:alnum:]+._?=-]+" 78 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 79 | ) 80 | ); 81 | 82 | # The set of packages used when specs are fetched using non-builtins. 83 | mkPkgs = sources: system: 84 | let 85 | sourcesNixpkgs = 86 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 87 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 88 | hasThisAsNixpkgsPath = == ./.; 89 | in 90 | if builtins.hasAttr "nixpkgs" sources 91 | then sourcesNixpkgs 92 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 93 | import { } 94 | else 95 | abort 96 | '' 97 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 98 | add a package called "nixpkgs" to your sources.json. 99 | ''; 100 | 101 | # The actual fetching function. 102 | fetch = pkgs: name: spec: 103 | 104 | if ! builtins.hasAttr "type" spec then 105 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 106 | else if spec.type == "file" then fetch_file pkgs name spec 107 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 108 | else if spec.type == "git" then fetch_git name spec 109 | else if spec.type == "local" then fetch_local spec 110 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 111 | else if spec.type == "builtin-url" then fetch_builtin-url name 112 | else 113 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 114 | 115 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 116 | # the path directly as opposed to the fetched source. 117 | replace = name: drv: 118 | let 119 | saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; 120 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 121 | in 122 | if ersatz == "" then drv else 123 | # this turns the string into an actual Nix path (for both absolute and 124 | # relative paths) 125 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 126 | 127 | # Ports of functions for older nix versions 128 | 129 | # a Nix version of mapAttrs if the built-in doesn't exist 130 | mapAttrs = builtins.mapAttrs or ( 131 | f: set: with builtins; 132 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 133 | ); 134 | 135 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 136 | range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); 137 | 138 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 139 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 140 | 141 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 142 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 143 | concatMapStrings = f: list: concatStrings (map f list); 144 | concatStrings = builtins.concatStringsSep ""; 145 | 146 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 147 | optionalAttrs = cond: as: if cond then as else { }; 148 | 149 | # fetchTarball version that is compatible between all the versions of Nix 150 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 151 | let 152 | inherit (builtins) lessThan nixVersion fetchTarball; 153 | in 154 | if lessThan nixVersion "1.12" then 155 | fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 156 | else 157 | fetchTarball attrs; 158 | 159 | # fetchurl version that is compatible between all the versions of Nix 160 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 161 | let 162 | inherit (builtins) lessThan nixVersion fetchurl; 163 | in 164 | if lessThan nixVersion "1.12" then 165 | fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 166 | else 167 | fetchurl attrs; 168 | 169 | # Create the final "sources" from the config 170 | mkSources = config: 171 | mapAttrs 172 | ( 173 | name: spec: 174 | if builtins.hasAttr "outPath" spec 175 | then 176 | abort 177 | "The values in sources.json should not have an 'outPath' attribute" 178 | else 179 | spec // { outPath = replace name (fetch config.pkgs name spec); } 180 | ) 181 | config.sources; 182 | 183 | # The "config" used by the fetchers 184 | mkConfig = 185 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 186 | , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) 187 | , system ? builtins.currentSystem 188 | , pkgs ? mkPkgs sources system 189 | }: { 190 | # The sources, i.e. the attribute set of spec name to spec 191 | inherit sources; 192 | 193 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 194 | inherit pkgs; 195 | }; 196 | 197 | in 198 | mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } 199 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/overlay.nix: -------------------------------------------------------------------------------- 1 | self: super: { 2 | libosmium = self.callPackage ./overlay/libosmium.nix { }; 3 | } 4 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/overlay/libosmium.nix: -------------------------------------------------------------------------------- 1 | { stdenv, cmake, protozero, expat, zlib, lz4, bzip2, boost, sources }: 2 | 3 | stdenv.mkDerivation { 4 | name = "libosmium"; 5 | src = sources.libosmium; 6 | 7 | nativeBuildInputs = [ cmake ]; 8 | cmakeFlags = [ 9 | "-DBUILD_BENCHMARKS=OFF" 10 | "-DBUILD_DATA_TESTS=OFF" 11 | "-DBUILD_EXAMPLES=OFF" 12 | "-DBUILD_HEADERS=OFF" 13 | "-DBUILD_TESTING=OFF" 14 | ]; 15 | buildInputs = [ 16 | protozero 17 | expat 18 | zlib 19 | lz4 20 | bzip2 21 | boost 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ---[ Include sources 2 | set(SOURCES_CPP 3 | main.cpp 4 | ) 5 | 6 | # ---[ Fetch all include headers 7 | file(GLOB_RECURSE INCLUDE_HEADERS include/*.h) 8 | 9 | # ---[ Add the executable 10 | add_executable(${PROJECT_NAME} ${SOURCES_CPP} ${INCLUDE_HEADERS}) 11 | 12 | #link executable to dependency libraries 13 | target_link_libraries ( 14 | ${PROJECT_NAME} 15 | ${OSMIUM_LIBRARIES} 16 | ) 17 | -------------------------------------------------------------------------------- /examples/cpp-libosmium/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | int main() 6 | { 7 | cout << "Hello, World!\n"; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /foo/default.nix: -------------------------------------------------------------------------------- 1 | { haskellPackages 2 | , stdenv 3 | , lib 4 | }: 5 | 6 | { 7 | buildPackage = 8 | attrs: 9 | let 10 | src = if !lib.isDerivation attrs && lib.isAttrs attrs then attrs.src else attrs; 11 | root = if !lib.isDerivation attrs && lib.isAttrs attrs then attrs.root else attrs; 12 | nubdeps = ds: lib.lists.sort (x: y: x < y) ( 13 | lib.unique ( 14 | map (d: lib.head (lib.splitString " " d)) ds 15 | ) 16 | ); 17 | spec = builtins.fromJSON (builtins.readFile (root + "/package.yaml")); 18 | commonDeps = spec.dependencies; 19 | 20 | libraryExtraDeps = 21 | lib.optionals 22 | (spec ? library && spec.library ? dependencies) 23 | spec.library.dependencies; 24 | libraryDeps = nubdeps (commonDeps ++ libraryExtraDeps); 25 | 26 | exeExtraDeps = lib.optionals (spec ? executables) ( 27 | lib.concatMap 28 | ( 29 | exe: lib.optionals 30 | (exe ? dependencies) 31 | exe.dependencies 32 | ) 33 | (builtins.attrValues spec.executables) 34 | ); 35 | exeDeps = 36 | nubdeps 37 | ( 38 | builtins.filter (x: x != spec.name) 39 | (commonDeps ++ exeExtraDeps) 40 | ); 41 | 42 | testExtraDeps = lib.optionals (spec ? tests) ( 43 | lib.concatMap 44 | ( 45 | test: lib.optionals 46 | (test ? dependencies) 47 | test.dependencies 48 | ) 49 | (builtins.attrValues spec.tests) 50 | ); 51 | testDeps = nubdeps (builtins.filter (x: x != spec.name) (commonDeps ++ testExtraDeps)); 52 | 53 | depsFor = depType: 54 | map ( 55 | d: 56 | if ! builtins.hasAttr d haskellPackages 57 | then throw "haskellPackages does not contain dependency '${d}' needed for '${depType}'" 58 | else 59 | haskellPackages.${d} 60 | ); 61 | 62 | in 63 | haskellPackages.callPackage 64 | ( 65 | { mkDerivation }: 66 | mkDerivation { 67 | pname = spec.name; 68 | inherit (spec) version; 69 | inherit src; 70 | isLibrary = builtins.hasAttr "library" spec; 71 | isExecutable = builtins.hasAttr "executables" spec; 72 | enableSeparateDataOutput = true; 73 | libraryHaskellDepends = depsFor "libraryHaskellDepends" libraryDeps; 74 | libraryToolDepends = [ haskellPackages.hpack ]; 75 | executableHaskellDepends = depsFor "executableHaskellDepends" exeDeps; 76 | testHaskellDepends = depsFor "testHaskellDepends" testDeps; 77 | prePatch = "hpack"; 78 | homepage = "https://github.com/${spec.github}#readme"; 79 | description = spec.synopsis; 80 | license = 81 | if builtins.hasAttr "license" spec && spec.license == "MIT" 82 | then lib.licenses.mit 83 | else throw "Don't know how to handle license: ${builtins.toJSON spec.license}"; 84 | } 85 | ) 86 | { }; 87 | } 88 | -------------------------------------------------------------------------------- /niv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 59 | 60 | 61 | $ niv initInitializing Creating nix/sources.nix Creating nix/sources.json Importing 'niv' ... Adding package nmattia/niv Reading sources file Writing new sources file Done: Adding package nmattia/niv Importing 'nixpkgs' ... Adding package NixOS/nixpkgs-channels Done: Adding package NixOS/nixpkgs-channelsDone: Initializing$ niv add stedolan/jqAdding package stedolan/jq Reading sources file Writing new sources fileDone: Adding package stedolan/jq 62 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./sources.nix, inNixShell ? null /* nix-shell sometimes adds this */ }: 2 | import sources.nixpkgs { 3 | overlays = [ 4 | (_: pkgs: { inherit sources; }) 5 | (_: pkgs: { termtosvg = pkgs.callPackage ./termtosvg.nix { }; }) 6 | ]; 7 | config = { }; 8 | } 9 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixpkgs": { 3 | "branch": "master", 4 | "description": "Nix Packages collection", 5 | "homepage": null, 6 | "owner": "NixOS", 7 | "repo": "nixpkgs", 8 | "rev": "41e7ce9a0a29b26e5ca97fd0a4bd85765ed8f9ac", 9 | "sha256": "1gp9qwyqwfv8x8k8rj11w7ajlb96a9njxsb5vg14dzadhn97slrc", 10 | "type": "tarball", 11 | "url": "https://github.com/NixOS/nixpkgs/archive/41e7ce9a0a29b26e5ca97fd0a4bd85765ed8f9ac.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "termtosvg": { 15 | "branch": "develop", 16 | "description": "Record terminal sessions as SVG animations", 17 | "homepage": "https://nbedos.github.io/termtosvg/", 18 | "owner": "nbedos", 19 | "repo": "termtosvg", 20 | "rev": "b97cc0132073111cec37f64c95539e869202ff99", 21 | "sha256": "1c86622sda98zm3q3dzwmm37i09yca661kn860svl191nqbh2l68", 22 | "type": "tarball", 23 | "url": "https://github.com/nbedos/termtosvg/archive/b97cc0132073111cec37f64c95539e869202ff99.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | spec.ref or ( 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" 34 | ); 35 | submodules = spec.submodules or false; 36 | submoduleArg = 37 | let 38 | nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; 39 | emptyArgWithWarning = 40 | if submodules 41 | then 42 | builtins.trace 43 | ( 44 | "The niv input \"${name}\" uses submodules " 45 | + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " 46 | + "does not support them" 47 | ) 48 | { } 49 | else { }; 50 | in 51 | if nixSupportsSubmodules 52 | then { inherit submodules; } 53 | else emptyArgWithWarning; 54 | in 55 | builtins.fetchGit 56 | ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); 57 | 58 | fetch_local = spec: spec.path; 59 | 60 | fetch_builtin-tarball = name: throw 61 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 62 | $ niv modify ${name} -a type=tarball -a builtin=true''; 63 | 64 | fetch_builtin-url = name: throw 65 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 66 | $ niv modify ${name} -a type=file -a builtin=true''; 67 | 68 | # 69 | # Various helpers 70 | # 71 | 72 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 73 | sanitizeName = name: 74 | ( 75 | concatMapStrings (s: if builtins.isList s then "-" else s) 76 | ( 77 | builtins.split "[^[:alnum:]+._?=-]+" 78 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 79 | ) 80 | ); 81 | 82 | # The set of packages used when specs are fetched using non-builtins. 83 | mkPkgs = sources: system: 84 | let 85 | sourcesNixpkgs = 86 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 87 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 88 | hasThisAsNixpkgsPath = == ./.; 89 | in 90 | if builtins.hasAttr "nixpkgs" sources 91 | then sourcesNixpkgs 92 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 93 | import { } 94 | else 95 | abort 96 | '' 97 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 98 | add a package called "nixpkgs" to your sources.json. 99 | ''; 100 | 101 | # The actual fetching function. 102 | fetch = pkgs: name: spec: 103 | 104 | if ! builtins.hasAttr "type" spec then 105 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 106 | else if spec.type == "file" then fetch_file pkgs name spec 107 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 108 | else if spec.type == "git" then fetch_git name spec 109 | else if spec.type == "local" then fetch_local spec 110 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 111 | else if spec.type == "builtin-url" then fetch_builtin-url name 112 | else 113 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 114 | 115 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 116 | # the path directly as opposed to the fetched source. 117 | replace = name: drv: 118 | let 119 | saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; 120 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 121 | in 122 | if ersatz == "" then drv else 123 | # this turns the string into an actual Nix path (for both absolute and 124 | # relative paths) 125 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 126 | 127 | # Ports of functions for older nix versions 128 | 129 | # a Nix version of mapAttrs if the built-in doesn't exist 130 | mapAttrs = builtins.mapAttrs or ( 131 | f: set: with builtins; 132 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 133 | ); 134 | 135 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 136 | range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); 137 | 138 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 139 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 140 | 141 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 142 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 143 | concatMapStrings = f: list: concatStrings (map f list); 144 | concatStrings = builtins.concatStringsSep ""; 145 | 146 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 147 | optionalAttrs = cond: as: if cond then as else { }; 148 | 149 | # fetchTarball version that is compatible between all the versions of Nix 150 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 151 | let 152 | inherit (builtins) lessThan nixVersion fetchTarball; 153 | in 154 | if lessThan nixVersion "1.12" then 155 | fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 156 | else 157 | fetchTarball attrs; 158 | 159 | # fetchurl version that is compatible between all the versions of Nix 160 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 161 | let 162 | inherit (builtins) lessThan nixVersion fetchurl; 163 | in 164 | if lessThan nixVersion "1.12" then 165 | fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) 166 | else 167 | fetchurl attrs; 168 | 169 | # Create the final "sources" from the config 170 | mkSources = config: 171 | mapAttrs 172 | ( 173 | name: spec: 174 | if builtins.hasAttr "outPath" spec 175 | then 176 | abort 177 | "The values in sources.json should not have an 'outPath' attribute" 178 | else 179 | spec // { outPath = replace name (fetch config.pkgs name spec); } 180 | ) 181 | config.sources; 182 | 183 | # The "config" used by the fetchers 184 | mkConfig = 185 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 186 | , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) 187 | , system ? builtins.currentSystem 188 | , pkgs ? mkPkgs sources system 189 | }: { 190 | # The sources, i.e. the attribute set of spec name to spec 191 | inherit sources; 192 | 193 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 194 | inherit pkgs; 195 | }; 196 | 197 | in 198 | mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } 199 | -------------------------------------------------------------------------------- /nix/termtosvg.nix: -------------------------------------------------------------------------------- 1 | { sources, lib, python3Packages }: 2 | 3 | python3Packages.buildPythonApplication { 4 | pname = "termtosvg"; 5 | version = "0.0.0"; 6 | 7 | src = sources.termtosvg; 8 | 9 | doCheck = false; 10 | 11 | propagatedBuildInputs = with python3Packages; [ lxml pyte ]; 12 | 13 | meta = with lib; { 14 | inherit (sources.termtosvg) homepage description; 15 | license = licenses.bsd3; 16 | maintainers = with maintainers; [ ma27 ]; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | { 2 | "name": "niv", 3 | "version": "0.2.22", 4 | "license": "MIT", 5 | "author": "Nicolas Mattia ", 6 | "maintainer": "Nicolas Mattia ", 7 | "copyright": "(c) 2019 Nicolas Mattia", 8 | "category": "Development", 9 | "github": "nmattia/niv", 10 | "synopsis": "Easy dependency management for Nix projects", 11 | "description": "Easy dependency management for Nix projects.", 12 | "ghc-options": [ 13 | "-Wall", 14 | "-optP-Wno-nonportable-include-path" 15 | ], 16 | "data-files": [ 17 | "nix/sources.nix" 18 | ], 19 | "extra-source-files": [ 20 | "README.md" 21 | ], 22 | "dependencies": [ 23 | "aeson >= 2", 24 | "aeson-pretty", 25 | "ansi-terminal", 26 | "base < 5", 27 | "bytestring", 28 | "directory", 29 | "file-embed", 30 | "filepath", 31 | "hashable", 32 | "http-conduit", 33 | "mtl", 34 | "optparse-applicative", 35 | "process", 36 | "profunctors", 37 | "pureMD5", 38 | "string-qq", 39 | "text", 40 | "unliftio", 41 | "unordered-containers" 42 | ], 43 | "library": { 44 | "source-dirs": [ 45 | "src" 46 | ], 47 | "dependencies": [ 48 | "aeson", 49 | "tasty", 50 | "tasty-hunit", 51 | "unordered-containers" 52 | ] 53 | }, 54 | "executables": { 55 | "niv": { 56 | "main": "Niv.main", 57 | "source-dirs": "app", 58 | "dependencies": [ 59 | "niv" 60 | ] 61 | } 62 | }, 63 | "tests": { 64 | "unit": { 65 | "main": "NivTest.main", 66 | "source-dirs": "app", 67 | "dependencies": [ 68 | "tasty", 69 | "niv" 70 | ] 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash 3 | #!nix-shell -I nixpkgs=./nix 4 | #!nix-shell -p ormolu 5 | #!nix-shell -p glibcLocales 6 | #!nix-shell --keep GITHUB_TOKEN 7 | ### 8 | ### fmt - Format Haskell files with Ormolu 9 | ### 10 | ### Usage: 11 | ### fmt [-c|--check] 12 | ### 13 | ### Options: 14 | ### -c,--check Only check formatting, don't change files 15 | 16 | set -euo pipefail 17 | 18 | help() { 19 | sed -rn 's/^### ?//;T;p' "$0" 20 | } 21 | 22 | fmt() { 23 | local mode="$1" 24 | command -v ormolu >/dev/null 2>&1 || { 25 | echo >&2 "error: ormolu not found. run this in niv's nix-shell" 26 | exit 1 27 | } 28 | 29 | needs_formatting=( ) 30 | for f in $(find . -name '*.hs') 31 | do 32 | echo "checking: $f" 33 | if ! ormolu --no-cabal --mode "$mode" "$f"; then 34 | needs_formatting+=( "$f" ) 35 | fi 36 | done 37 | 38 | if [ ${#needs_formatting[@]} -eq 0 ]; then 39 | echo All files checked for formatting 40 | else 41 | echo The following files need formatting: 42 | for i in "${needs_formatting[@]}"; do 43 | echo " - $i" 44 | done 45 | if [ "$mode" == "check" ]; then 46 | exit 1 47 | fi 48 | echo "Please run ./script/fmt" 49 | fi 50 | } 51 | 52 | if [ "$#" == "0" ]; then 53 | fmt "inplace" 54 | elif [ "$1" == "-c" ] || [ "$1" == "--check" ]; then 55 | fmt "check" 56 | elif [ "$1" == "-h" ] || [ "$1" == "--help" ]; then 57 | help 58 | exit 0 59 | else 60 | help 61 | exit 1 62 | fi 63 | -------------------------------------------------------------------------------- /script/gen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash 3 | #!nix-shell -I nixpkgs=./nix 4 | #!nix-shell -p nix 5 | #!nix-shell --keep GITHUB_TOKEN 6 | 7 | set -euo pipefail 8 | 9 | echo "Updating README" 10 | 11 | cat $(nix-build -A readme) > README.md 12 | 13 | if [ $# -gt 0 ] && [ $1 == "svg" ]; then 14 | echo "Updating niv.svg" 15 | $(nix-build -A niv-svg-gen) 16 | fi 17 | 18 | echo done 19 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash 3 | #!nix-shell -I nixpkgs=./nix 4 | #!nix-shell -p nix 5 | #!nix-shell -p nixpkgs-fmt 6 | 7 | set -euo pipefail 8 | 9 | export NIX_PATH="nixpkgs=./nix" 10 | 11 | echo "Building" 12 | 13 | nixargs=( 14 | "--no-link" 15 | "--max-jobs" "10" 16 | ) 17 | 18 | targets=( 19 | 20 | ) 21 | 22 | 23 | if [[ ! $OSTYPE =~ darwin ]]; then 24 | echo "Not testing on darwin" 25 | echo "Enabling sandbox, running all tests" 26 | nixargs+=("--sandbox") 27 | else 28 | echo "Testing on darwin" 29 | echo "Not enabling sandbox, not running integration" 30 | targets+=("-A" "niv") 31 | fi 32 | 33 | # Build and create a root 34 | nix-build ${nixargs[@]} ${targets[@]} 35 | 36 | echo "Formatting" 37 | if ! nixpkgs-fmt --check . ; then 38 | echo 39 | echo 'run `nixpkgs-fmt .` to fix this issue' 40 | exit 1 41 | fi 42 | 43 | echo "Building examples" 44 | for example in examples/*; do 45 | echo " - $(basename $example)" 46 | pushd $example 47 | nix-build ${nixargs[@]} 48 | popd 49 | done 50 | 51 | echo "all good" 52 | -------------------------------------------------------------------------------- /script/upload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -I nixpkgs=./nix 3 | #!nix-shell -i bash -p cabal-install -p nix -p curl 4 | # vim: filetype=sh 5 | # 6 | # 7 | # How To Release: 8 | # * git checkout master 9 | # * make sure changelog is up-to-date 10 | # * bump version in package.yaml 11 | # * bump version in CHANGELOG 12 | # * run ./script/gen (twice...) 13 | # * run ./script/test 14 | # * git ci -am "Release 3.14.15" 15 | # * ./script/upload --publish 16 | # * git tag v3.14.15 17 | # * git push 18 | # * git push --tags 19 | 20 | $(nix-build -A niv-cabal-upload) "$@" 21 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./nix { } }: 2 | (import ./default.nix { inherit pkgs; }).niv-devshell 3 | -------------------------------------------------------------------------------- /src/Data/Aeson/Extended.hs: -------------------------------------------------------------------------------- 1 | module Data.Aeson.Extended where 2 | 3 | import Data.Aeson (ToJSON) 4 | import qualified Data.Aeson.Encode.Pretty as AesonPretty 5 | import qualified Data.ByteString.Lazy as BL 6 | 7 | --- Aeson 8 | 9 | -- | Efficiently prettify and serialize a JSON value as a lazy 'BL.ByteString' 10 | -- and write it to a file. 11 | encodeFilePretty :: (ToJSON a) => FilePath -> a -> IO () 12 | encodeFilePretty fp = BL.writeFile fp . AesonPretty.encodePretty' config 13 | where 14 | config = 15 | AesonPretty.defConfig 16 | { AesonPretty.confTrailingNewline = True, 17 | AesonPretty.confCompare = compare 18 | } 19 | -------------------------------------------------------------------------------- /src/Data/HashMap/Strict/Extended.hs: -------------------------------------------------------------------------------- 1 | module Data.HashMap.Strict.Extended where 2 | 3 | import Control.Monad 4 | import qualified Data.HashMap.Strict as HMS 5 | import Data.Hashable (Hashable) 6 | 7 | --- HashMap 8 | 9 | forWithKeyM :: 10 | (Eq k, Hashable k, Monad m) => 11 | HMS.HashMap k v1 -> 12 | (k -> v1 -> m v2) -> 13 | m (HMS.HashMap k v2) 14 | forWithKeyM = flip mapWithKeyM 15 | 16 | forWithKeyM_ :: 17 | (Eq k, Hashable k, Monad m) => 18 | HMS.HashMap k v1 -> 19 | (k -> v1 -> m ()) -> 20 | m () 21 | forWithKeyM_ = flip mapWithKeyM_ 22 | 23 | mapWithKeyM :: 24 | (Eq k, Hashable k, Monad m) => 25 | (k -> v1 -> m v2) -> 26 | HMS.HashMap k v1 -> 27 | m (HMS.HashMap k v2) 28 | mapWithKeyM f m = do 29 | fmap mconcat $ 30 | forM (HMS.toList m) $ \(k, v) -> 31 | HMS.singleton k <$> f k v 32 | 33 | mapWithKeyM_ :: 34 | (Eq k, Hashable k, Monad m) => 35 | (k -> v1 -> m ()) -> 36 | HMS.HashMap k v1 -> 37 | m () 38 | mapWithKeyM_ f m = do 39 | forM_ (HMS.toList m) $ \(k, v) -> 40 | HMS.singleton k <$> f k v 41 | -------------------------------------------------------------------------------- /src/Data/Text/Extended.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Data.Text.Extended where 4 | 5 | import qualified Data.Text as T 6 | import Niv.Logger 7 | import System.Exit (exitFailure) 8 | import UnliftIO 9 | 10 | tshow :: (Show a) => a -> T.Text 11 | tshow = T.pack . show 12 | 13 | -- not quite the perfect place for this 14 | abort :: (MonadIO io) => T.Text -> io a 15 | abort msg = do 16 | tsay $ T.unwords [tbold $ tred "FATAL:", msg] 17 | liftIO exitFailure 18 | -------------------------------------------------------------------------------- /src/Niv/Cmd.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | 3 | module Niv.Cmd where 4 | 5 | import qualified Data.Aeson as Aeson 6 | import qualified Data.Text as T 7 | import Niv.Sources 8 | import Niv.Update 9 | import qualified Options.Applicative as Opts 10 | 11 | -- TODO: add filter 12 | data Cmd = Cmd 13 | { description :: forall a. Opts.InfoMod a, 14 | parseCmdShortcut :: T.Text -> Maybe (PackageName, Aeson.Object), 15 | parsePackageSpec :: Opts.Parser PackageSpec, 16 | updateCmd :: Update () (), 17 | name :: T.Text, 18 | -- | Some notes to print 19 | extraLogs :: Attrs -> [T.Text] 20 | } 21 | -------------------------------------------------------------------------------- /src/Niv/Git/Cmd.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE TupleSections #-} 5 | {-# LANGUAGE ViewPatterns #-} 6 | 7 | module Niv.Git.Cmd where 8 | 9 | import Control.Applicative 10 | import Control.Arrow 11 | import qualified Data.Aeson as Aeson 12 | import qualified Data.Aeson.Key as K 13 | import qualified Data.Aeson.KeyMap as KM 14 | import qualified Data.ByteString.Char8 as B8 15 | import Data.Char (isDigit) 16 | import qualified Data.HashMap.Strict as HMS 17 | import Data.Maybe 18 | import qualified Data.Text as T 19 | import Data.Text.Extended as T 20 | import Niv.Cmd 21 | import Niv.Logger 22 | import Niv.Sources 23 | import Niv.Update 24 | import qualified Options.Applicative as Opts 25 | import qualified Options.Applicative.Help.Pretty as Opts 26 | import System.Exit (ExitCode (ExitSuccess)) 27 | import System.Process (readProcessWithExitCode) 28 | 29 | gitCmd :: Cmd 30 | gitCmd = 31 | Cmd 32 | { description = describeGit, 33 | parseCmdShortcut = parseGitShortcut, 34 | parsePackageSpec = parseGitPackageSpec, 35 | updateCmd = gitUpdate', 36 | name = "git", 37 | extraLogs = gitExtraLogs 38 | } 39 | 40 | gitExtraLogs :: Attrs -> [T.Text] 41 | gitExtraLogs attrs = noteRef <> warnRefBranch <> warnRefTag 42 | where 43 | noteRef = 44 | textIf (HMS.member "ref" attrs) $ 45 | mkNote 46 | "Your source contains a `ref` attribute. Make sure your sources.nix is up-to-date and consider using a `branch` or `tag` attribute." 47 | warnRefBranch = 48 | textIf (member "ref" && member "branch") $ 49 | mkWarn 50 | "Your source contains both a `ref` and a `branch`. Niv will update the `branch` but the `ref` will be used by Nix to fetch the repo." 51 | warnRefTag = 52 | textIf (member "ref" && member "tag") $ 53 | mkWarn 54 | "Your source contains both a `ref` and a `tag`. The `ref` will be used by Nix to fetch the repo." 55 | member x = HMS.member x attrs 56 | textIf cond txt = [txt | cond] 57 | 58 | parseGitShortcut :: T.Text -> Maybe (PackageName, Aeson.Object) 59 | parseGitShortcut txt'@(T.dropWhileEnd (== '/') -> txt) = 60 | -- basic heuristics for figuring out if something is a git repo 61 | if isGitURL 62 | then case T.splitOn "/" txt of 63 | [] -> Nothing 64 | (last -> w) -> case T.stripSuffix ".git" w of 65 | Nothing -> Just (PackageName w, KM.singleton "repo" (Aeson.String txt')) 66 | Just w' -> Just (PackageName w', KM.singleton "repo" (Aeson.String txt')) 67 | else Nothing 68 | where 69 | isGitURL = 70 | ".git" 71 | `T.isSuffixOf` txt 72 | || "git@" 73 | `T.isPrefixOf` txt 74 | || "ssh://" 75 | `T.isPrefixOf` txt 76 | 77 | parseGitPackageSpec :: Opts.Parser PackageSpec 78 | parseGitPackageSpec = 79 | PackageSpec . KM.fromList 80 | <$> many (parseRepo <|> parseBranch <|> parseRev <|> parseAttr <|> parseSAttr) 81 | where 82 | parseRepo = 83 | ("repo",) . Aeson.String 84 | <$> Opts.strOption 85 | ( Opts.long "repo" 86 | <> Opts.metavar "URL" 87 | ) 88 | parseRev = 89 | ("rev",) . Aeson.String 90 | <$> Opts.strOption 91 | ( Opts.long "rev" 92 | <> Opts.metavar "SHA" 93 | ) 94 | parseBranch = 95 | ("branch",) . Aeson.String 96 | <$> Opts.strOption 97 | ( Opts.long "branch" 98 | <> Opts.short 'b' 99 | <> Opts.metavar "BRANCH" 100 | ) 101 | parseAttr = 102 | Opts.option 103 | (Opts.maybeReader parseKeyValJSON) 104 | ( Opts.long "attribute" 105 | <> Opts.short 'a' 106 | <> Opts.metavar "KEY=VAL" 107 | <> Opts.help "Set the package spec attribute to , where may be JSON." 108 | ) 109 | parseSAttr = 110 | Opts.option 111 | (Opts.maybeReader (parseKeyVal Aeson.toJSON)) 112 | ( Opts.long "string-attribute" 113 | <> Opts.short 's' 114 | <> Opts.metavar "KEY=VAL" 115 | <> Opts.help "Set the package spec attribute to ." 116 | ) 117 | parseKeyValJSON = parseKeyVal $ \x -> 118 | fromMaybe (Aeson.toJSON x) (Aeson.decodeStrict (B8.pack x)) 119 | -- Parse "key=val" into ("key", val) 120 | parseKeyVal :: 121 | -- how to convert to JSON 122 | (String -> Aeson.Value) -> 123 | String -> 124 | Maybe (K.Key, Aeson.Value) 125 | parseKeyVal toJSON str = case span (/= '=') str of 126 | (key, '=' : val) -> Just (K.fromString key, toJSON val) 127 | _ -> Nothing 128 | 129 | describeGit :: Opts.InfoMod a 130 | describeGit = 131 | mconcat 132 | [ Opts.fullDesc, 133 | Opts.progDesc "Add a git dependency. Experimental.", 134 | Opts.headerDoc $ 135 | Just $ 136 | Opts.vcat 137 | [ "Examples:", 138 | "", 139 | " niv add git git@github.com:stedolan/jq", 140 | " niv add git ssh://git@github.com/stedolan/jq --rev deadb33f", 141 | " niv add git https://github.com/stedolan/jq.git", 142 | " niv add git --repo /my/custom/repo --name custom --branch development" 143 | ] 144 | ] 145 | 146 | gitUpdate :: 147 | -- | latest rev 148 | (T.Text -> T.Text -> IO T.Text) -> 149 | -- | latest rev and default ref 150 | (T.Text -> IO (T.Text, T.Text)) -> 151 | Update () () 152 | gitUpdate latestRev' defaultBranchAndRev' = proc () -> do 153 | useOrSet "type" -< ("git" :: Box T.Text) 154 | repository <- load "repo" -< () 155 | discoverRev <+> discoverRefAndRev -< repository 156 | where 157 | discoverRefAndRev = proc repository -> do 158 | branchAndRev <- run defaultBranchAndRev' -< repository 159 | update "branch" -< fst <$> branchAndRev 160 | update "rev" -< snd <$> branchAndRev 161 | returnA -< () 162 | discoverRev = proc repository -> do 163 | branch <- load "branch" -< () 164 | rev <- run' (uncurry latestRev') -< (,) <$> repository <*> branch 165 | update "rev" -< rev 166 | returnA -< () 167 | 168 | -- | The "real" (IO) update 169 | gitUpdate' :: Update () () 170 | gitUpdate' = gitUpdate latestRev defaultBranchAndRev 171 | 172 | latestRev :: 173 | -- | the repository 174 | T.Text -> 175 | -- | the branch 176 | T.Text -> 177 | IO T.Text 178 | latestRev repo branch = do 179 | let gitArgs = ["ls-remote", repo, "refs/heads/" <> branch] 180 | sout <- runGit gitArgs 181 | case sout of 182 | ls@(_ : _ : _) -> abortTooMuchOutput gitArgs ls 183 | [l1] -> parseRev gitArgs l1 184 | [] -> abortNoOutput gitArgs 185 | where 186 | parseRev args l = maybe (abortNoRev args l) pure $ do 187 | checkRev $ T.takeWhile (/= '\t') l 188 | checkRev t = if isRev t then Just t else Nothing 189 | abortNoOutput args = 190 | abortGitFailure 191 | args 192 | $ "Git didn't produce any output. Does the branch '" <> branch <> "' exist?" 193 | abortTooMuchOutput args ls = 194 | abortGitBug args $ 195 | T.unlines $ 196 | ["Git produced too much output:"] <> map (" " <>) ls 197 | 198 | defaultBranchAndRev :: 199 | -- | the repository 200 | T.Text -> 201 | IO (T.Text, T.Text) 202 | defaultBranchAndRev repo = do 203 | sout <- runGit args 204 | case sout of 205 | (l1 : l2 : _) -> (,) <$> parseBranch l1 <*> parseRev l2 206 | _ -> 207 | abortGitBug args $ 208 | T.unlines $ 209 | [ "Could not read reference and revision from stdout:" 210 | ] 211 | <> sout 212 | where 213 | args = ["ls-remote", "--symref", repo, "HEAD"] 214 | parseBranch l = maybe (abortNoRef args l) pure $ do 215 | -- ref: refs/head/master\tHEAD -> master\tHEAD 216 | refAndSym <- T.stripPrefix "ref: refs/heads/" l 217 | let branch = T.takeWhile (/= '\t') refAndSym 218 | if T.null branch then Nothing else Just branch 219 | parseRev l = maybe (abortNoRev args l) pure $ do 220 | checkRev $ T.takeWhile (/= '\t') l 221 | checkRev t = if isRev t then Just t else Nothing 222 | 223 | abortNoRev :: [T.Text] -> T.Text -> IO a 224 | abortNoRev args l = abortGitBug args $ "Could not read revision from: " <> l 225 | 226 | abortNoRef :: [T.Text] -> T.Text -> IO a 227 | abortNoRef args l = abortGitBug args $ "Could not read reference from: " <> l 228 | 229 | -- | Run the "git" executable 230 | runGit :: [T.Text] -> IO [T.Text] 231 | runGit args = do 232 | (exitCode, sout, serr) <- readProcessWithExitCode "git" (T.unpack <$> args) "" 233 | case (exitCode, lines sout) of 234 | (ExitSuccess, ls) -> pure $ T.pack <$> ls 235 | _ -> 236 | abortGitBug args $ 237 | T.unlines 238 | [ T.unwords ["stdout:", T.pack sout], 239 | T.unwords ["stderr:", T.pack serr] 240 | ] 241 | 242 | isRev :: T.Text -> Bool 243 | isRev t = 244 | -- commit hashes are comprised of abcdef0123456789 245 | T.all (\c -> (c >= 'a' && c <= 'f') || isDigit c) t 246 | && 247 | -- commit _should_ be 40 chars long, but to be sure we pick 7 248 | T.length t >= 7 249 | 250 | abortGitFailure :: [T.Text] -> T.Text -> IO a 251 | abortGitFailure args msg = 252 | abort $ 253 | T.unlines 254 | [ "Could not read the output of 'git'.", 255 | T.unwords ("command:" : "git" : args), 256 | msg 257 | ] 258 | 259 | abortGitBug :: [T.Text] -> T.Text -> IO a 260 | abortGitBug args msg = 261 | abort $ 262 | bug $ 263 | T.unlines 264 | [ "Could not read the output of 'git'.", 265 | T.unwords ("command:" : "git" : args), 266 | msg 267 | ] 268 | -------------------------------------------------------------------------------- /src/Niv/Git/Test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Niv.Git.Test 5 | ( tests, 6 | ) 7 | where 8 | 9 | import Control.Monad 10 | import qualified Data.Aeson.KeyMap as KM 11 | import Data.Bifunctor 12 | import qualified Data.HashMap.Strict as HMS 13 | import Data.IORef 14 | import Niv.Git.Cmd 15 | import Niv.Sources 16 | import Niv.Update 17 | import qualified Test.Tasty as Tasty 18 | import Test.Tasty.HUnit ((@=?)) 19 | import qualified Test.Tasty.HUnit as Tasty 20 | 21 | tests :: [Tasty.TestTree] 22 | tests = [test_repositoryParse, test_gitUpdates] 23 | 24 | test_repositoryParse :: Tasty.TestTree 25 | test_repositoryParse = 26 | Tasty.testGroup 27 | "repository parse" 28 | [ Tasty.testCase "goo" $ 29 | parseGitShortcut "goo" @=? Nothing, 30 | Tasty.testCase "git@github.com:nmattia/niv" $ 31 | parseGitShortcut "git@github.com:nmattia/niv" 32 | @=? Just 33 | (PackageName "niv", KM.singleton "repo" "git@github.com:nmattia/niv"), 34 | Tasty.testCase "ssh://git@github.com/stedolan/jq" $ 35 | parseGitShortcut "ssh://git@github.com/stedolan/jq" 36 | @=? Just 37 | (PackageName "jq", KM.singleton "repo" "ssh://git@github.com/stedolan/jq"), 38 | Tasty.testCase "https://github.com/stedolan/jq.git" $ 39 | parseGitShortcut "https://github.com/stedolan/jq.git" 40 | @=? Just 41 | (PackageName "jq", KM.singleton "repo" "https://github.com/stedolan/jq.git"), 42 | Tasty.testCase "https://github.com/stedolan/jq" $ 43 | parseGitShortcut "https://github.com/stedolan/jq" @=? Nothing, 44 | Tasty.testCase "~/path/to/repo.git" $ 45 | parseGitShortcut "~/path/to/repo.git" 46 | @=? Just 47 | (PackageName "repo", KM.singleton "repo" "~/path/to/repo.git") 48 | ] 49 | 50 | test_gitUpdates :: Tasty.TestTree 51 | test_gitUpdates = 52 | Tasty.testGroup 53 | "updates" 54 | [ Tasty.testCase "rev is updated" test_gitUpdateRev, 55 | Tasty.testCase "git is called once" test_gitCalledOnce 56 | ] 57 | 58 | test_gitUpdateRev :: IO () 59 | test_gitUpdateRev = do 60 | interState <- evalUpdate initialState $ proc () -> 61 | gitUpdate (error "should be def") defaultBranchAndHEAD' -< () 62 | let interState' = HMS.map (first (\_ -> Free)) interState 63 | actualState <- evalUpdate interState' $ proc () -> 64 | gitUpdate latestRev' (error "should update") -< () 65 | unless ((snd <$> actualState) == expectedState) $ 66 | error $ 67 | "State mismatch: " <> show actualState 68 | where 69 | latestRev' _ _ = pure "some-other-rev" 70 | defaultBranchAndHEAD' _ = pure ("some-branch", "some-rev") 71 | initialState = 72 | HMS.fromList 73 | [("repo", (Free, "git@github.com:nmattia/niv"))] 74 | expectedState = 75 | HMS.fromList 76 | [ ("repo", "git@github.com:nmattia/niv"), 77 | ("branch", "some-branch"), 78 | ("rev", "some-other-rev"), 79 | ("type", "git") 80 | ] 81 | 82 | once1 :: (b -> IO a) -> IO (b -> IO a) 83 | once1 f = do 84 | used <- newIORef False 85 | pure $ \x -> do 86 | used' <- readIORef used 87 | if used' 88 | then error "already used" 89 | else do 90 | writeIORef used True 91 | f x 92 | 93 | once2 :: (a -> b -> IO c) -> IO (a -> b -> IO c) 94 | once2 f = do 95 | used <- newIORef False 96 | pure $ \x y -> do 97 | used' <- readIORef used 98 | if used' 99 | then error "already used" 100 | else do 101 | writeIORef used True 102 | f x y 103 | 104 | -- | This tests that we don't run the same git operations several times during 105 | -- the update 106 | test_gitCalledOnce :: IO () 107 | test_gitCalledOnce = do 108 | defaultBranchAndHEAD'' <- once1 defaultBranchAndHEAD' 109 | latestRev'' <- once2 latestRev' 110 | interState <- evalUpdate initialState $ proc () -> 111 | gitUpdate (error "should be def") defaultBranchAndHEAD'' -< () 112 | let interState' = HMS.map (first (\_ -> Free)) interState 113 | actualState <- evalUpdate interState' $ proc () -> 114 | gitUpdate latestRev'' (error "should update") -< () 115 | unless ((snd <$> actualState) == expectedState) $ 116 | error $ 117 | "State mismatch: " <> show actualState 118 | where 119 | latestRev' _ _ = pure "some-other-rev" 120 | defaultBranchAndHEAD' _ = pure ("some-branch", "some-rev") 121 | initialState = 122 | HMS.fromList 123 | [("repo", (Free, "git@github.com:nmattia/niv"))] 124 | expectedState = 125 | HMS.fromList 126 | [ ("repo", "git@github.com:nmattia/niv"), 127 | ("branch", "some-branch"), 128 | ("rev", "some-other-rev"), 129 | ("type", "git") 130 | ] 131 | -------------------------------------------------------------------------------- /src/Niv/GitHub.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module Niv.GitHub where 6 | 7 | import Control.Arrow 8 | import Data.Bool 9 | import Data.Maybe 10 | import qualified Data.Text as T 11 | import Niv.GitHub.API 12 | import Niv.Update 13 | 14 | -- | The GitHub update function 15 | -- TODO: fetchers for: 16 | -- * npm 17 | -- * hackage 18 | -- * docker 19 | -- * ... ? 20 | githubUpdate :: 21 | -- | prefetch 22 | (Bool -> T.Text -> IO T.Text) -> 23 | -- | latest revision 24 | (T.Text -> T.Text -> T.Text -> IO T.Text) -> 25 | -- | get repo 26 | (T.Text -> T.Text -> IO GithubRepo) -> 27 | Update () () 28 | githubUpdate prefetch latestRev ghRepo = proc () -> do 29 | urlTemplate <- 30 | template 31 | <<< (useOrSet "url_template" <<< completeSpec) <+> load "url_template" 32 | -< 33 | () 34 | url <- update "url" -< urlTemplate 35 | let isTarGuess u = 36 | any 37 | (`T.isSuffixOf` u) 38 | [ ".tar", 39 | ".tar.gz", 40 | ".tgz", 41 | ".tar.bz2", 42 | ".tar.xz", 43 | ".tar.zst" 44 | ] 45 | type' <- useOrSet "type" -< bool "file" "tarball" . isTarGuess <$> url :: Box T.Text 46 | let doUnpack = (== "tarball") <$> type' 47 | _sha256 <- update "sha256" <<< run (\(up, u) -> prefetch up u) -< (,) <$> doUnpack <*> url 48 | returnA -< () 49 | where 50 | completeSpec :: Update () (Box T.Text) 51 | completeSpec = proc () -> do 52 | owner <- load "owner" -< () 53 | repo <- load "repo" -< () 54 | repoInfo <- run (\(a, b) -> ghRepo a b) -< (,) <$> owner <*> repo 55 | branch <- 56 | useOrSet "branch" <<< arr (fmap $ fromMaybe "master") 57 | -< 58 | repoDefaultBranch <$> repoInfo 59 | _description <- useOrSet "description" -< repoDescription <$> repoInfo 60 | _homepage <- useOrSet "homepage" -< repoHomepage <$> repoInfo 61 | _ <- 62 | update "rev" <<< run' (\(a, b, c) -> latestRev a b c) 63 | -< 64 | (,,) <$> owner <*> repo <*> branch 65 | returnA -< pure githubURLTemplate 66 | 67 | githubURLTemplate :: T.Text 68 | githubURLTemplate = 69 | (if githubSecure then "https://" else "http://") 70 | <> githubHost 71 | <> githubPath 72 | <> "//archive/.tar.gz" 73 | -------------------------------------------------------------------------------- /src/Niv/GitHub/API.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE QuasiQuotes #-} 4 | {-# LANGUAGE ViewPatterns #-} 5 | 6 | module Niv.GitHub.API where 7 | 8 | import qualified Data.Aeson as Aeson 9 | import qualified Data.Aeson.KeyMap as KM 10 | import qualified Data.ByteString.Char8 as BS8 11 | import Data.Functor 12 | import Data.Maybe 13 | import Data.String.QQ (s) 14 | import qualified Data.Text as T 15 | import qualified Data.Text.Encoding as T 16 | import Data.Text.Extended 17 | import qualified Network.HTTP.Simple as HTTP 18 | import System.Environment (lookupEnv) 19 | import System.Exit (exitFailure) 20 | import System.IO.Unsafe (unsafePerformIO) 21 | import Text.Read (readMaybe) 22 | 23 | -- Bunch of GitHub helpers 24 | 25 | data GithubRepo = GithubRepo 26 | { repoDescription :: Maybe T.Text, 27 | repoHomepage :: Maybe T.Text, 28 | repoDefaultBranch :: Maybe T.Text 29 | } 30 | 31 | githubRepo :: T.Text -> T.Text -> IO GithubRepo 32 | githubRepo owner repo = do 33 | request <- defaultRequest ["repos", owner, repo] 34 | -- we don't use httpJSONEither because it adds an "Accept: 35 | -- application/json" header that GitHub chokes on 36 | resp0 <- HTTP.httpBS request 37 | let resp = fmap Aeson.eitherDecodeStrict resp0 38 | case (HTTP.getResponseStatusCode resp, HTTP.getResponseBody resp) of 39 | (200, Right (Aeson.Object m)) -> do 40 | let lookupText k = case KM.lookup k m of 41 | Just (Aeson.String t) -> Just t 42 | _ -> Nothing 43 | pure 44 | GithubRepo 45 | { repoDescription = lookupText "description", 46 | repoHomepage = lookupText "homepage", 47 | repoDefaultBranch = lookupText "default_branch" 48 | } 49 | (200, Right v) -> do 50 | error $ "expected object, got " <> show v 51 | (200, Left e) -> do 52 | error $ "github didn't return JSON: " <> show e 53 | _ -> abortCouldNotFetchGitHubRepo (tshow (request, resp0)) (owner, repo) 54 | 55 | -- | TODO: Error instead of T.Text? 56 | abortCouldNotFetchGitHubRepo :: T.Text -> (T.Text, T.Text) -> IO a 57 | abortCouldNotFetchGitHubRepo e (T.unpack -> owner, T.unpack -> repo) = do 58 | putStrLn $ unlines [line1, line2, T.unpack line3] 59 | exitFailure 60 | where 61 | line1 = "WARNING: Could not read from GitHub repo: " <> owner <> "/" <> repo 62 | line2 = 63 | [s| 64 | I assumed that your package was a GitHub repository. An error occurred while 65 | gathering information from the repository. Check whether your package was added 66 | correctly: 67 | 68 | niv show 69 | 70 | If not, try re-adding it: 71 | 72 | niv drop 73 | niv add 74 | 75 | Make sure the repository exists. 76 | |] 77 | line3 = T.unwords ["(Error was:", e, ")"] 78 | 79 | defaultRequest :: [T.Text] -> IO HTTP.Request 80 | defaultRequest (map T.encodeUtf8 -> parts) = do 81 | let path = T.encodeUtf8 githubPath <> BS8.intercalate "/" parts 82 | mtoken <- lookupEnv' "GITHUB_TOKEN" 83 | pure 84 | $ maybe 85 | id 86 | ( \token -> 87 | HTTP.addRequestHeader "authorization" ("token " <> BS8.pack token) 88 | ) 89 | mtoken 90 | $ HTTP.setRequestPath path 91 | $ HTTP.addRequestHeader "user-agent" "niv" 92 | $ HTTP.addRequestHeader "accept" "application/vnd.github.v3+json" 93 | $ HTTP.setRequestSecure githubSecure 94 | $ HTTP.setRequestHost (T.encodeUtf8 githubApiHost) 95 | $ HTTP.setRequestPort githubApiPort HTTP.defaultRequest 96 | 97 | -- | Get the latest revision for owner, repo and branch. 98 | -- TODO: explain no error handling 99 | githubLatestRev :: 100 | -- | owner 101 | T.Text -> 102 | -- | repo 103 | T.Text -> 104 | -- | branch 105 | T.Text -> 106 | IO T.Text 107 | githubLatestRev owner repo branch = do 108 | request <- 109 | defaultRequest ["repos", owner, repo, "commits", branch] 110 | <&> HTTP.addRequestHeader "accept" "application/vnd.github.v3.sha" 111 | resp <- HTTP.httpBS request 112 | case HTTP.getResponseStatusCode resp of 113 | 200 -> pure $ T.decodeUtf8 $ HTTP.getResponseBody resp 114 | _ -> abortCouldNotGetRev owner repo branch resp 115 | 116 | abortCouldNotGetRev :: T.Text -> T.Text -> T.Text -> HTTP.Response BS8.ByteString -> IO a 117 | abortCouldNotGetRev owner repo branch resp = abort $ T.unlines [line1, line2, line3] 118 | where 119 | line1 = 120 | T.unwords 121 | [ "Cannot get latest revision for branch", 122 | "'" <> branch <> "'", 123 | "(" <> owner <> "/" <> repo <> ")" 124 | ] 125 | line2 = "The request failed: " <> tshow resp 126 | line3 = 127 | [s| 128 | NOTE: You may want to retry with an authentication token: 129 | 130 | GITHUB_TOKEN=... niv 131 | 132 | For more information on rate-limiting, see 133 | 134 | https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting 135 | 136 | |] 137 | 138 | -- | Like lookupEnv "foo" but also looks up "NIV_foo" 139 | lookupEnv' :: String -> IO (Maybe String) 140 | lookupEnv' vn = 141 | lookupEnv vn >>= \case 142 | Just x -> pure (Just x) 143 | Nothing -> lookupEnv ("NIV_" <> vn) 144 | 145 | githubHost :: T.Text 146 | githubHost = unsafePerformIO $ do 147 | lookupEnv' "GITHUB_HOST" >>= \case 148 | Just (T.pack -> x) -> pure x 149 | Nothing -> pure "github.com" 150 | 151 | githubApiPort :: Int 152 | githubApiPort = unsafePerformIO $ do 153 | lookupEnv' "GITHUB_API_PORT" >>= \case 154 | Just (readMaybe -> Just x) -> pure x 155 | _ -> pure $ if githubSecure then 443 else 80 156 | 157 | githubApiHost :: T.Text 158 | githubApiHost = unsafePerformIO $ do 159 | lookupEnv' "GITHUB_API_HOST" >>= \case 160 | Just (T.pack -> x) -> pure x 161 | Nothing -> pure "api.github.com" 162 | 163 | -- For these two we prepend NIV_ to the variable name because the variable 164 | -- names can have different meanings, see 165 | -- https://github.com/nmattia/niv/issues/280 166 | 167 | githubSecure :: Bool 168 | githubSecure = unsafePerformIO $ do 169 | lookupEnv "NIV_GITHUB_INSECURE" >>= \case 170 | Just "" -> pure True 171 | Just _ -> pure False 172 | Nothing -> pure True 173 | 174 | githubPath :: T.Text 175 | githubPath = unsafePerformIO $ do 176 | lookupEnv "NIV_GITHUB_PATH" >>= \case 177 | Just (T.pack -> x) -> pure $ fromMaybe x (T.stripSuffix "/" x) <> "/" 178 | Nothing -> pure "/" 179 | -------------------------------------------------------------------------------- /src/Niv/GitHub/Cmd.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE PartialTypeSignatures #-} 4 | {-# LANGUAGE QuasiQuotes #-} 5 | {-# LANGUAGE TupleSections #-} 6 | {-# LANGUAGE ViewPatterns #-} 7 | 8 | module Niv.GitHub.Cmd 9 | ( githubCmd, 10 | ) 11 | where 12 | 13 | import Control.Applicative 14 | import Data.Aeson ((.=)) 15 | import qualified Data.Aeson as Aeson 16 | import qualified Data.Aeson.Key as K 17 | import qualified Data.Aeson.KeyMap as KM 18 | import Data.Bifunctor 19 | import qualified Data.ByteString.Char8 as B8 20 | import Data.Char (isAlphaNum) 21 | import Data.Maybe 22 | import Data.String.QQ (s) 23 | import qualified Data.Text as T 24 | import Data.Text.Extended 25 | import Niv.Cmd 26 | import Niv.GitHub 27 | import Niv.GitHub.API 28 | import Niv.Sources 29 | import Niv.Update 30 | import qualified Options.Applicative as Opts 31 | import qualified Options.Applicative.Help.Pretty as Opts 32 | import System.Exit (ExitCode (ExitSuccess)) 33 | import System.Process (readProcessWithExitCode) 34 | 35 | githubCmd :: Cmd 36 | githubCmd = 37 | Cmd 38 | { description = describeGitHub, 39 | parseCmdShortcut = parseAddShortcutGitHub, 40 | parsePackageSpec = parseGitHubPackageSpec, 41 | updateCmd = githubUpdate', 42 | name = "github", 43 | extraLogs = const [] 44 | -- TODO: here filter by type == tarball or file or builtin- 45 | } 46 | 47 | parseGitHubPackageSpec :: Opts.Parser PackageSpec 48 | parseGitHubPackageSpec = 49 | PackageSpec . KM.fromList 50 | <$> many parseAttribute 51 | where 52 | parseAttribute :: Opts.Parser (K.Key, Aeson.Value) 53 | parseAttribute = 54 | Opts.option 55 | (Opts.maybeReader parseKeyValJSON) 56 | ( Opts.long "attribute" 57 | <> Opts.short 'a' 58 | <> Opts.metavar "KEY=VAL" 59 | <> Opts.help "Set the package spec attribute to , where may be JSON." 60 | ) 61 | <|> Opts.option 62 | (Opts.maybeReader (parseKeyVal Aeson.toJSON)) 63 | ( Opts.long "string-attribute" 64 | <> Opts.short 's' 65 | <> Opts.metavar "KEY=VAL" 66 | <> Opts.help "Set the package spec attribute to ." 67 | ) 68 | <|> shortcutAttributes 69 | <|> ( ("url_template",) . Aeson.String 70 | <$> Opts.strOption 71 | ( Opts.long "template" 72 | <> Opts.short 't' 73 | <> Opts.metavar "URL" 74 | <> Opts.help "Used during 'update' when building URL. Occurrences of are replaced with attribute 'foo'." 75 | ) 76 | ) 77 | <|> ( ("type",) . Aeson.String 78 | <$> Opts.strOption 79 | ( Opts.long "type" 80 | <> Opts.short 'T' 81 | <> Opts.metavar "TYPE" 82 | <> Opts.help "The type of the URL target. The value can be either 'file' or 'tarball'. If not set, the value is inferred from the suffix of the URL." 83 | ) 84 | ) 85 | parseKeyValJSON = parseKeyVal $ \x -> 86 | fromMaybe (Aeson.toJSON x) (Aeson.decodeStrict (B8.pack x)) 87 | -- Parse "key=val" into ("key", val) 88 | parseKeyVal :: 89 | -- how to convert to JSON 90 | (String -> Aeson.Value) -> 91 | String -> 92 | Maybe (K.Key, Aeson.Value) 93 | parseKeyVal toJSON str = case span (/= '=') str of 94 | (key, '=' : val) -> Just (K.fromString key, toJSON val) 95 | _ -> Nothing 96 | -- Shortcuts for common attributes 97 | shortcutAttributes :: Opts.Parser (K.Key, Aeson.Value) 98 | shortcutAttributes = 99 | foldr ((<|>) . mkShortcutAttribute) empty ["branch", "owner", "rev", "version"] 100 | -- TODO: infer those shortcuts from 'Update' keys 101 | mkShortcutAttribute :: T.Text -> Opts.Parser (K.Key, Aeson.Value) 102 | mkShortcutAttribute = \case 103 | attr@(T.uncons -> Just (c, _)) -> 104 | fmap (second Aeson.String) $ 105 | (K.fromText attr,) 106 | <$> Opts.strOption 107 | ( Opts.long (T.unpack attr) 108 | <> Opts.short c 109 | <> Opts.metavar (T.unpack $ T.toUpper attr) 110 | <> Opts.help 111 | ( T.unpack $ 112 | "Equivalent to --attribute " 113 | <> attr 114 | <> "=<" 115 | <> T.toUpper attr 116 | <> ">" 117 | ) 118 | ) 119 | _ -> empty 120 | 121 | describeGitHub :: Opts.InfoMod a 122 | describeGitHub = 123 | mconcat 124 | [ Opts.fullDesc, 125 | Opts.progDesc "Add a GitHub dependency", 126 | Opts.headerDoc $ 127 | Just $ 128 | Opts.vcat 129 | [ "Examples:", 130 | "", 131 | " niv add stedolan/jq", 132 | " niv add NixOS/nixpkgs -n nixpkgs -b nixpkgs-unstable", 133 | " niv add my-package -v alpha-0.1 -t http://example.com/archive/.zip" 134 | ] 135 | ] 136 | 137 | -- parse a github shortcut of the form "owner/repo" 138 | parseAddShortcutGitHub :: T.Text -> Maybe (PackageName, Aeson.Object) 139 | parseAddShortcutGitHub str = 140 | -- parses a string "owner/repo" into package name (repo) and spec (owner + 141 | -- repo) 142 | case T.span (/= '/') str of 143 | ( owner@(T.null -> False), 144 | T.uncons -> Just ('/', repo@(T.null -> False)) 145 | ) -> 146 | Just 147 | ( PackageName repo, 148 | KM.fromList ["owner" .= owner, "repo" .= repo] 149 | ) 150 | -- XXX: this should be "Nothing" but for the time being we keep 151 | -- backwards compatibility with "niv add foo" adding "foo" as a 152 | -- package name. 153 | _ -> Just (PackageName str, KM.empty) 154 | 155 | -- | The IO (real) github update 156 | githubUpdate' :: Update () () 157 | githubUpdate' = githubUpdate nixPrefetchURL githubLatestRev githubRepo 158 | 159 | nixPrefetchURL :: Bool -> T.Text -> IO T.Text 160 | nixPrefetchURL unpack turl@(T.unpack -> url) = do 161 | (exitCode, sout, serr) <- runNixPrefetch 162 | case (exitCode, lines sout) of 163 | (ExitSuccess, l : _) -> pure $ T.pack l 164 | _ -> abortNixPrefetchExpectedOutput (T.pack <$> args) (T.pack sout) (T.pack serr) 165 | where 166 | args = (["--unpack" | unpack]) <> [url, "--name", sanitizeName basename] 167 | runNixPrefetch = readProcessWithExitCode "nix-prefetch-url" args "" 168 | sanitizeName = T.unpack . T.filter isOk 169 | basename = last $ T.splitOn "/" turl 170 | -- From the nix-prefetch-url documentation: 171 | -- Path names are alphanumeric and can include the symbols +-._?= and must 172 | -- not begin with a period. 173 | -- (note: we assume they don't begin with a period) 174 | isOk c = isAlphaNum c || T.any (c ==) "+-._?=" 175 | 176 | abortNixPrefetchExpectedOutput :: [T.Text] -> T.Text -> T.Text -> IO a 177 | abortNixPrefetchExpectedOutput args sout serr = 178 | abort $ 179 | [s| 180 | Could not read the output of 'nix-prefetch-url'. This is a bug. Please create a 181 | ticket: 182 | 183 | https://github.com/nmattia/niv/issues/new 184 | 185 | Thanks! I'll buy you a beer. 186 | |] 187 | <> T.unlines ["command: ", T.unwords ("nix-prefetch-url" : args), "stdout: ", sout, "stderr: ", serr] 188 | -------------------------------------------------------------------------------- /src/Niv/GitHub/Test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module Niv.GitHub.Test where 6 | 7 | import Control.Monad 8 | import Data.Bifunctor 9 | import qualified Data.HashMap.Strict as HMS 10 | import Data.IORef 11 | import Niv.GitHub 12 | import Niv.GitHub.API 13 | import Niv.Update 14 | 15 | test_githubInitsProperly :: IO () 16 | test_githubInitsProperly = do 17 | actualState <- evalUpdate initialState $ proc () -> 18 | githubUpdate prefetch latestRev ghRepo -< () 19 | unless ((snd <$> actualState) == expectedState) $ 20 | error $ 21 | "State mismatch: " <> show actualState 22 | where 23 | prefetch _ _ = pure "some-sha" 24 | latestRev _ _ _ = pure "some-rev" 25 | ghRepo _ _ = 26 | pure 27 | GithubRepo 28 | { repoDescription = Just "some-descr", 29 | repoHomepage = Just "some-homepage", 30 | repoDefaultBranch = Just "master" 31 | } 32 | initialState = 33 | HMS.fromList 34 | [ ("owner", (Free, "nmattia")), 35 | ("repo", (Free, "niv")) 36 | ] 37 | expectedState = 38 | HMS.fromList 39 | [ ("owner", "nmattia"), 40 | ("repo", "niv"), 41 | ("homepage", "some-homepage"), 42 | ("description", "some-descr"), 43 | ("branch", "master"), 44 | ("url", "https://github.com/nmattia/niv/archive/some-rev.tar.gz"), 45 | ("rev", "some-rev"), 46 | ("sha256", "some-sha"), 47 | ("type", "tarball"), 48 | ("url_template", "https://github.com///archive/.tar.gz") 49 | ] 50 | 51 | test_githubUpdates :: IO () 52 | test_githubUpdates = do 53 | actualState <- evalUpdate initialState $ proc () -> 54 | githubUpdate prefetch latestRev ghRepo -< () 55 | unless ((snd <$> actualState) == expectedState) $ 56 | error $ 57 | "State mismatch: " <> show actualState 58 | where 59 | prefetch _ _ = pure "new-sha" 60 | latestRev _ _ _ = pure "new-rev" 61 | ghRepo _ _ = 62 | pure 63 | GithubRepo 64 | { repoDescription = Just "some-descr", 65 | repoHomepage = Just "some-homepage", 66 | repoDefaultBranch = Just "master" 67 | } 68 | initialState = 69 | HMS.fromList 70 | [ ("owner", (Free, "nmattia")), 71 | ("repo", (Free, "niv")), 72 | ("homepage", (Free, "some-homepage")), 73 | ("description", (Free, "some-descr")), 74 | ("branch", (Free, "master")), 75 | ("url", (Free, "https://github.com/nmattia/niv/archive/some-rev.tar.gz")), 76 | ("rev", (Free, "some-rev")), 77 | ("sha256", (Free, "some-sha")), 78 | ("type", (Free, "tarball")), 79 | ("url_template", (Free, "https://github.com///archive/.tar.gz")) 80 | ] 81 | expectedState = 82 | HMS.fromList 83 | [ ("owner", "nmattia"), 84 | ("repo", "niv"), 85 | ("homepage", "some-homepage"), 86 | ("description", "some-descr"), 87 | ("branch", "master"), 88 | ("url", "https://github.com/nmattia/niv/archive/new-rev.tar.gz"), 89 | ("rev", "new-rev"), 90 | ("sha256", "new-sha"), 91 | ("type", "tarball"), 92 | ("url_template", "https://github.com///archive/.tar.gz") 93 | ] 94 | 95 | test_githubDoesntOverrideRev :: IO () 96 | test_githubDoesntOverrideRev = do 97 | actualState <- evalUpdate initialState $ proc () -> 98 | githubUpdate prefetch latestRev ghRepo -< () 99 | unless ((snd <$> actualState) == expectedState) $ 100 | error $ 101 | "State mismatch: " <> show actualState 102 | where 103 | prefetch _ _ = pure "new-sha" 104 | latestRev _ _ _ = error "shouldn't fetch rev" 105 | ghRepo _ _ = error "shouldn't fetch repo" 106 | initialState = 107 | HMS.fromList 108 | [ ("owner", (Free, "nmattia")), 109 | ("repo", (Free, "niv")), 110 | ("homepage", (Free, "some-homepage")), 111 | ("description", (Free, "some-descr")), 112 | ("branch", (Free, "master")), 113 | ("url", (Free, "https://github.com/nmattia/niv/archive/some-rev.tar.gz")), 114 | ("rev", (Locked, "custom-rev")), 115 | ("sha256", (Free, "some-sha")), 116 | ("type", (Free, "tarball")), 117 | ("url_template", (Free, "https://github.com///archive/.tar.gz")) 118 | ] 119 | expectedState = 120 | HMS.fromList 121 | [ ("owner", "nmattia"), 122 | ("repo", "niv"), 123 | ("homepage", "some-homepage"), 124 | ("description", "some-descr"), 125 | ("branch", "master"), 126 | ("url", "https://github.com/nmattia/niv/archive/custom-rev.tar.gz"), 127 | ("rev", "custom-rev"), 128 | ("sha256", "new-sha"), 129 | ("type", "tarball"), 130 | ("url_template", "https://github.com///archive/.tar.gz") 131 | ] 132 | 133 | -- TODO: HMS diff for test output 134 | test_githubURLFallback :: IO () 135 | test_githubURLFallback = do 136 | actualState <- evalUpdate initialState $ proc () -> 137 | githubUpdate prefetch latestRev ghRepo -< () 138 | unless ((snd <$> actualState) == expectedState) $ 139 | error $ 140 | "State mismatch: " <> show actualState 141 | where 142 | prefetch _ _ = pure "some-sha" 143 | latestRev _ _ _ = error "shouldn't fetch rev" 144 | ghRepo _ _ = error "shouldn't fetch repo" 145 | initialState = 146 | HMS.fromList 147 | [ ("url_template", (Free, "https://foo.com/.tar.gz")), 148 | ("baz", (Free, "tarball")) 149 | ] 150 | expectedState = 151 | HMS.fromList 152 | [ ("url_template", "https://foo.com/.tar.gz"), 153 | ("baz", "tarball"), 154 | ("url", "https://foo.com/tarball.tar.gz"), 155 | ("sha256", "some-sha"), 156 | ("type", "tarball") 157 | ] 158 | 159 | test_githubUpdatesOnce :: IO () 160 | test_githubUpdatesOnce = do 161 | ioref <- newIORef False 162 | tmpState <- evalUpdate initialState $ proc () -> 163 | githubUpdate (prefetch ioref) latestRev ghRepo -< () 164 | unless ((snd <$> tmpState) == expectedState) $ 165 | error $ 166 | "State mismatch: " <> show tmpState 167 | -- Set everything free 168 | let tmpState' = HMS.map (first (\_ -> Free)) tmpState 169 | actualState <- evalUpdate tmpState' $ proc () -> 170 | githubUpdate (prefetch ioref) latestRev ghRepo -< () 171 | unless ((snd <$> actualState) == expectedState) $ 172 | error $ 173 | "State mismatch: " <> show actualState 174 | where 175 | prefetch ioref _ _ = do 176 | readIORef ioref >>= \case 177 | False -> pure () 178 | True -> error "Prefetch should be called once!" 179 | writeIORef ioref True 180 | pure "new-sha" 181 | latestRev _ _ _ = pure "new-rev" 182 | ghRepo _ _ = 183 | pure 184 | GithubRepo 185 | { repoDescription = Just "some-descr", 186 | repoHomepage = Just "some-homepage", 187 | repoDefaultBranch = Just "master" 188 | } 189 | initialState = 190 | HMS.fromList 191 | [ ("owner", (Free, "nmattia")), 192 | ("repo", (Free, "niv")), 193 | ("homepage", (Free, "some-homepage")), 194 | ("description", (Free, "some-descr")), 195 | ("branch", (Free, "master")), 196 | ("url", (Free, "https://github.com/nmattia/niv/archive/some-rev.tar.gz")), 197 | ("rev", (Free, "some-rev")), 198 | ("sha256", (Free, "some-sha")), 199 | ("type", (Free, "tarball")), 200 | ("url_template", (Free, "https://github.com///archive/.tar.gz")) 201 | ] 202 | expectedState = 203 | HMS.fromList 204 | [ ("owner", "nmattia"), 205 | ("repo", "niv"), 206 | ("homepage", "some-homepage"), 207 | ("description", "some-descr"), 208 | ("branch", "master"), 209 | ("url", "https://github.com/nmattia/niv/archive/new-rev.tar.gz"), 210 | ("rev", "new-rev"), 211 | ("sha256", "new-sha"), 212 | ("type", "tarball"), 213 | ("url_template", "https://github.com///archive/.tar.gz") 214 | ] 215 | -------------------------------------------------------------------------------- /src/Niv/Local/Cmd.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE TupleSections #-} 5 | 6 | module Niv.Local.Cmd where 7 | 8 | import Control.Arrow 9 | import qualified Data.Aeson as Aeson 10 | import qualified Data.Aeson.Key as K 11 | import qualified Data.Aeson.KeyMap as KM 12 | import qualified Data.Text as T 13 | import Niv.Cmd 14 | import Niv.Sources 15 | import Niv.Update 16 | import qualified Options.Applicative as Opts 17 | import qualified Options.Applicative.Help.Pretty as Opts 18 | 19 | localCmd :: Cmd 20 | localCmd = 21 | Cmd 22 | { description = describeLocal, 23 | parseCmdShortcut = parseLocalShortcut, 24 | parsePackageSpec = parseLocalPackageSpec, 25 | updateCmd = proc () -> do 26 | useOrSet "type" -< ("local" :: Box T.Text) 27 | returnA -< (), 28 | name = "local", 29 | extraLogs = const [] 30 | } 31 | 32 | parseLocalShortcut :: T.Text -> Maybe (PackageName, Aeson.Object) 33 | parseLocalShortcut txt = 34 | if T.isPrefixOf "./" txt || T.isPrefixOf "/" txt 35 | then do 36 | let n = last $ T.splitOn "/" txt 37 | Just (PackageName n, KM.fromList [("path", Aeson.String txt)]) 38 | else Nothing 39 | 40 | parseLocalPackageSpec :: Opts.Parser PackageSpec 41 | parseLocalPackageSpec = PackageSpec . KM.fromList <$> parseParams 42 | where 43 | parseParams :: Opts.Parser [(K.Key, Aeson.Value)] 44 | parseParams = maybe [] pure <$> Opts.optional parsePath 45 | parsePath = 46 | ("path",) . Aeson.String 47 | <$> Opts.strOption 48 | ( Opts.long "path" 49 | <> Opts.metavar "PATH" 50 | ) 51 | 52 | describeLocal :: Opts.InfoMod a 53 | describeLocal = 54 | mconcat 55 | [ Opts.fullDesc, 56 | Opts.progDesc "Add a local dependency. Experimental.", 57 | Opts.headerDoc $ 58 | Just $ 59 | Opts.vcat 60 | [ "Examples:", 61 | "", 62 | " niv add local ./foo/bar" 63 | ] 64 | ] 65 | -------------------------------------------------------------------------------- /src/Niv/Logger.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DerivingStrategies #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | 5 | module Niv.Logger 6 | ( Colors (Always, Never), 7 | job, 8 | setColors, 9 | bug, 10 | tsay, 11 | say, 12 | twarn, 13 | mkWarn, 14 | mkNote, 15 | green, 16 | tgreen, 17 | red, 18 | tred, 19 | blue, 20 | tblue, 21 | yellow, 22 | tyellow, 23 | bold, 24 | tbold, 25 | faint, 26 | tfaint, 27 | ) 28 | where 29 | 30 | import Control.Monad 31 | import Data.List 32 | import Data.Profunctor 33 | import qualified Data.Text as T 34 | import qualified System.Console.ANSI as ANSI 35 | import System.Exit (exitFailure) 36 | import System.IO.Unsafe (unsafePerformIO) 37 | import UnliftIO 38 | 39 | -- A somewhat hacky way of deciding whether or not to use SGR codes, by writing 40 | -- and reading a global variable unsafely. 41 | -- This should be fine as long as the IORef is written right after argument 42 | -- parsing, and as long as the value is never changed. 43 | -- NOTE: this won't work in GHCi. 44 | 45 | data Colors 46 | = Always 47 | | Never 48 | deriving (Eq) 49 | 50 | colors :: IORef Colors 51 | colors = unsafePerformIO $ newIORef Always 52 | {-# NOINLINE colors #-} 53 | 54 | setColors :: Colors -> IO () 55 | setColors = writeIORef colors 56 | 57 | useColors :: Bool 58 | useColors = unsafePerformIO $ (== Always) <$> readIORef colors 59 | 60 | type S = String -> String 61 | 62 | type T = T.Text -> T.Text 63 | 64 | -- XXX: this assumes as single thread 65 | job :: (MonadUnliftIO io, MonadIO io) => String -> io () -> io () 66 | job str act = do 67 | say (bold str) 68 | indent 69 | tryAny act <* deindent >>= \case 70 | Right () -> say $ green "Done" <> ": " <> str 71 | Left e -> do 72 | -- don't wrap if the error ain't too long 73 | let showErr = do 74 | let se = show e 75 | (if length se > 40 then ":\n" else ": ") <> se 76 | say $ red "ERROR" <> showErr 77 | liftIO exitFailure 78 | where 79 | indent = void $ atomicModifyIORef jobStack (\x -> (x + 1, undefined)) 80 | deindent = void $ atomicModifyIORef jobStack (\x -> (x - 1, undefined)) 81 | 82 | jobStackSize :: (MonadIO io) => io Int 83 | jobStackSize = readIORef jobStack 84 | 85 | jobStack :: IORef Int 86 | jobStack = unsafePerformIO $ newIORef 0 87 | 88 | {-# NOINLINE jobStackSize #-} 89 | 90 | tsay :: (MonadIO io) => T.Text -> io () 91 | tsay = say . T.unpack 92 | 93 | say :: (MonadIO io) => String -> io () 94 | say msg = do 95 | stackSize <- jobStackSize 96 | let indent = replicate (stackSize * 2) ' ' 97 | -- we use `intercalate "\n"` because `unlines` prints an extra newline at 98 | -- the end 99 | liftIO $ putStrLn $ intercalate "\n" $ (indent <>) <$> lines msg 100 | 101 | mkWarn :: T.Text -> T.Text 102 | mkWarn w = tbold (tyellow "WARNING") <> ": " <> w 103 | 104 | twarn :: (MonadIO io) => T.Text -> io () 105 | twarn = tsay . mkWarn 106 | 107 | mkNote :: T.Text -> T.Text 108 | mkNote w = tbold (tblue "NOTE") <> ": " <> w 109 | 110 | color :: ANSI.Color -> String -> String 111 | color c str = 112 | if useColors 113 | then 114 | ANSI.setSGRCode [ANSI.SetConsoleIntensity ANSI.BoldIntensity] 115 | <> ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid c] 116 | <> str 117 | <> ANSI.setSGRCode [ANSI.Reset] 118 | else str 119 | 120 | colorFaint :: ANSI.Color -> String -> String 121 | colorFaint c str = 122 | if useColors 123 | then 124 | ANSI.setSGRCode [ANSI.SetConsoleIntensity ANSI.FaintIntensity] 125 | <> ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid c] 126 | <> str 127 | <> ANSI.setSGRCode [ANSI.Reset] 128 | else str 129 | 130 | green :: S 131 | green = color ANSI.Green 132 | 133 | tgreen :: T 134 | tgreen = t green 135 | 136 | yellow :: S 137 | yellow = color ANSI.Yellow 138 | 139 | tyellow :: T 140 | tyellow = t yellow 141 | 142 | blue :: S 143 | blue = color ANSI.Blue 144 | 145 | tblue :: T 146 | tblue = t blue 147 | 148 | red :: S 149 | red = color ANSI.Red 150 | 151 | tred :: T 152 | tred = t red 153 | 154 | bold :: S 155 | bold = color ANSI.White 156 | 157 | tbold :: T 158 | tbold = t bold 159 | 160 | faint :: String -> String 161 | faint = colorFaint ANSI.White 162 | 163 | tfaint :: T 164 | tfaint = t faint 165 | 166 | t :: (String -> String) -> T.Text -> T.Text 167 | t = dimap T.unpack T.pack 168 | 169 | bug :: T.Text -> T.Text 170 | bug txt = 171 | T.unlines 172 | [ txt, 173 | "This is a bug. Please create a ticket:", 174 | " https://github.com/nmattia/niv/issues/new", 175 | "Thanks! I'll buy you a beer." 176 | ] 177 | -------------------------------------------------------------------------------- /src/Niv/Sources.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DerivingStrategies #-} 2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE QuasiQuotes #-} 6 | {-# LANGUAGE TemplateHaskell #-} 7 | 8 | module Niv.Sources where 9 | 10 | import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) 11 | import qualified Data.Aeson as Aeson 12 | import qualified Data.Aeson.Extended as Aeson 13 | import qualified Data.Aeson.KeyMap as KM 14 | import Data.Bifunctor (first) 15 | import qualified Data.ByteString as B 16 | import qualified Data.ByteString.Lazy.Char8 as BL8 17 | import qualified Data.Digest.Pure.MD5 as MD5 18 | import Data.FileEmbed (embedFile) 19 | import qualified Data.HashMap.Strict as HMS 20 | import Data.Hashable (Hashable) 21 | import Data.List 22 | import Data.String.QQ (s) 23 | import qualified Data.Text as T 24 | import Data.Text.Extended 25 | import Niv.Logger 26 | import Niv.Update 27 | import qualified System.Directory as Dir 28 | import System.FilePath (()) 29 | import UnliftIO 30 | 31 | ------------------------------------------------------------------------------- 32 | -- sources.json related 33 | ------------------------------------------------------------------------------- 34 | 35 | -- | Where to find the sources.json 36 | data FindSourcesJson 37 | = -- | use the default (nix/sources.json) 38 | Auto 39 | | -- | use the specified file path 40 | AtPath FilePath 41 | 42 | data SourcesError 43 | = SourcesDoesntExist 44 | | SourceIsntJSON 45 | | SpecIsntAMap 46 | 47 | newtype Sources = Sources 48 | {unSources :: HMS.HashMap PackageName PackageSpec} 49 | deriving newtype (FromJSON, ToJSON) 50 | 51 | getSourcesEither :: FindSourcesJson -> IO (Either SourcesError Sources) 52 | getSourcesEither fsj = do 53 | Dir.doesFileExist (pathNixSourcesJson fsj) >>= \case 54 | False -> pure $ Left SourcesDoesntExist 55 | True -> 56 | Aeson.decodeFileStrict (pathNixSourcesJson fsj) >>= \case 57 | Just value -> case valueToSources value of 58 | Nothing -> pure $ Left SpecIsntAMap 59 | Just srcs -> pure $ Right srcs 60 | Nothing -> pure $ Left SourceIsntJSON 61 | where 62 | valueToSources :: Aeson.Value -> Maybe Sources 63 | valueToSources = \case 64 | Aeson.Object obj -> 65 | ( Sources . mapKeys PackageName . KM.toHashMapText 66 | <$> traverse 67 | ( \case 68 | Aeson.Object obj' -> Just (PackageSpec obj') 69 | _ -> Nothing 70 | ) 71 | obj 72 | ) 73 | _ -> Nothing 74 | mapKeys :: (Eq k2, Hashable k2) => (k1 -> k2) -> HMS.HashMap k1 v -> HMS.HashMap k2 v 75 | mapKeys f = HMS.fromList . map (first f) . HMS.toList 76 | 77 | getSources :: FindSourcesJson -> IO Sources 78 | getSources fsj = do 79 | warnIfOutdated 80 | getSourcesEither fsj 81 | >>= either 82 | ( \case 83 | SourcesDoesntExist -> (abortSourcesDoesntExist fsj) 84 | SourceIsntJSON -> (abortSourcesIsntJSON fsj) 85 | SpecIsntAMap -> (abortSpecIsntAMap fsj) 86 | ) 87 | pure 88 | 89 | setSources :: FindSourcesJson -> Sources -> IO () 90 | setSources fsj = Aeson.encodeFilePretty (pathNixSourcesJson fsj) 91 | 92 | newtype PackageName = PackageName {unPackageName :: T.Text} 93 | deriving newtype (Eq, Hashable, FromJSONKey, ToJSONKey, Show) 94 | 95 | newtype PackageSpec = PackageSpec {unPackageSpec :: Aeson.Object} 96 | deriving newtype (FromJSON, ToJSON, Show, Semigroup, Monoid) 97 | 98 | -- | Simply discards the 'Freedom' 99 | attrsToSpec :: Attrs -> PackageSpec 100 | attrsToSpec = PackageSpec . KM.fromHashMapText . fmap snd 101 | 102 | -- | @nix/sources.json@ or pointed at by 'FindSourcesJson' 103 | pathNixSourcesJson :: FindSourcesJson -> FilePath 104 | pathNixSourcesJson = \case 105 | Auto -> "nix" "sources.json" 106 | AtPath f -> f 107 | 108 | -- 109 | -- ABORT messages 110 | -- 111 | 112 | abortSourcesDoesntExist :: FindSourcesJson -> IO a 113 | abortSourcesDoesntExist fsj = abort $ T.unlines [line1, line2] 114 | where 115 | line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj) 116 | line2 = 117 | [s| 118 | The sources file does not exist! You may need to run 'niv init'. 119 | |] 120 | 121 | abortSourcesIsntJSON :: FindSourcesJson -> IO a 122 | abortSourcesIsntJSON fsj = abort $ T.unlines [line1, line2] 123 | where 124 | line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj) 125 | line2 = "The sources file should be JSON." 126 | 127 | abortSpecIsntAMap :: FindSourcesJson -> IO a 128 | abortSpecIsntAMap fsj = abort $ T.unlines [line1, line2] 129 | where 130 | line1 = "Cannot use " <> T.pack (pathNixSourcesJson fsj) 131 | line2 = 132 | [s| 133 | The package specifications in the sources file should be JSON maps from 134 | attribute name to attribute value, e.g.: 135 | { "nixpkgs": { "foo": "bar" } } 136 | |] 137 | 138 | ------------------------------------------------------------------------------- 139 | -- sources.nix related 140 | ------------------------------------------------------------------------------- 141 | 142 | -- | All the released versions of nix/sources.nix 143 | data SourcesNixVersion 144 | = V1 145 | | V2 146 | | V3 147 | | V4 148 | | V5 149 | | V6 150 | | V7 151 | | V8 152 | | V9 153 | | V10 154 | | V11 155 | | V12 156 | | V13 157 | | V14 158 | | V15 159 | | V16 160 | | V17 161 | | -- prettify derivation name 162 | -- add 'local' type of sources 163 | V18 164 | | -- add NIV_OVERRIDE_{name} 165 | V19 166 | | -- can be imported when there's no sources.json 167 | V20 168 | | -- Use the source name in fetchurl 169 | V21 170 | | -- Stop setting `ref` and use `branch` and `tag` in sources 171 | V22 172 | | -- Allow to pass custom system to bootstrap niv in pure mode 173 | V23 174 | | -- Fix NIV_OVERRIDE_{name} for sandbox 175 | V24 176 | | -- Add the ability to pass submodules to fetchGit 177 | V25 178 | | -- formatting fix 179 | V26 180 | | -- Support submodules for git repos 181 | V27 182 | | -- formatting fix 183 | -- Apply statix suggestions 184 | V28 185 | | -- Remove unnecessary recs 186 | V29 187 | deriving stock (Bounded, Enum, Eq) 188 | 189 | -- | A user friendly version 190 | sourcesVersionToText :: SourcesNixVersion -> T.Text 191 | sourcesVersionToText = \case 192 | V1 -> "1" 193 | V2 -> "2" 194 | V3 -> "3" 195 | V4 -> "4" 196 | V5 -> "5" 197 | V6 -> "6" 198 | V7 -> "7" 199 | V8 -> "8" 200 | V9 -> "9" 201 | V10 -> "10" 202 | V11 -> "11" 203 | V12 -> "12" 204 | V13 -> "13" 205 | V14 -> "14" 206 | V15 -> "15" 207 | V16 -> "16" 208 | V17 -> "17" 209 | V18 -> "18" 210 | V19 -> "19" 211 | V20 -> "20" 212 | V21 -> "21" 213 | V22 -> "22" 214 | V23 -> "23" 215 | V24 -> "24" 216 | V25 -> "25" 217 | V26 -> "26" 218 | V27 -> "27" 219 | V28 -> "28" 220 | V29 -> "29" 221 | 222 | latestVersionMD5 :: T.Text 223 | latestVersionMD5 = sourcesVersionToMD5 maxBound 224 | 225 | -- | Find a version based on the md5 of the nix/sources.nix 226 | md5ToSourcesVersion :: T.Text -> Maybe SourcesNixVersion 227 | md5ToSourcesVersion md5 = 228 | find (\snv -> sourcesVersionToMD5 snv == md5) [minBound .. maxBound] 229 | 230 | -- | The MD5 sum of a particular version 231 | sourcesVersionToMD5 :: SourcesNixVersion -> T.Text 232 | sourcesVersionToMD5 = \case 233 | V1 -> "a7d3532c70fea66ffa25d6bc7ee49ad5" 234 | V2 -> "24cc0719fa744420a04361e23a3598d0" 235 | V3 -> "e01ed051e2c416e0fc7355fc72aeee3d" 236 | V4 -> "f754fe0e661b61abdcd32cb4062f5014" 237 | V5 -> "c34523590ff7dec7bf0689f145df29d1" 238 | V6 -> "8143f1db1e209562faf80a998be4929a" 239 | V7 -> "00a02cae76d30bbef96f001cabeed96f" 240 | V8 -> "e8b860753dd7fa1fd7b805dd836eb607" 241 | V9 -> "87149616c1b3b1e5aa73178f91c20b53" 242 | V10 -> "d8625c0a03dd935e1c79f46407faa8d3" 243 | V11 -> "8a95b7d93b16f7c7515d98f49b0ec741" 244 | V12 -> "2f9629ad9a8f181ed71d2a59b454970c" 245 | V13 -> "5e23c56b92eaade4e664cb16dcac1e0a" 246 | V14 -> "b470e235e7bcbf106d243fea90b6cfc9" 247 | V15 -> "dc11af910773ec9b4e505e0f49ebcfd2" 248 | V16 -> "2d93c52cab8e960e767a79af05ca572a" 249 | V17 -> "149b8907f7b08dc1c28164dfa55c7fad" 250 | V18 -> "bc5e6aefcaa6f9e0b2155ca4f44e5a33" 251 | V19 -> "543621698065cfc6a4a7985af76df718" 252 | V20 -> "ab4263aa63ccf44b4e1510149ce14eff" 253 | V21 -> "c501eee378828f7f49828a140dbdbca3" 254 | V22 -> "935d1d2f0bf95fda977a6e3a7e548ed4" 255 | V23 -> "4111204b613ec688e2669516dd313440" 256 | V24 -> "116c2d936f1847112fef0013771dab28" 257 | V25 -> "6612caee5814670e5e4d9dd1b71b5f70" 258 | V26 -> "937bff93370a064c9000f13cec5867f9" 259 | V27 -> "8031ba9d8fbbc7401c800d0b84278ec8" 260 | V28 -> "26ed55356db7673935329210a4f8c4a5" 261 | V29 -> "a8751de841ac5e0a60f4c2db7e8bbade" 262 | 263 | -- | The MD5 sum of ./nix/sources.nix 264 | sourcesNixMD5 :: IO T.Text 265 | sourcesNixMD5 = T.pack . show . MD5.md5 <$> BL8.readFile pathNixSourcesNix 266 | 267 | -- | @nix/sources.nix@ 268 | pathNixSourcesNix :: FilePath 269 | pathNixSourcesNix = "nix" "sources.nix" 270 | 271 | warnIfOutdated :: IO () 272 | warnIfOutdated = do 273 | tryAny (BL8.readFile pathNixSourcesNix) >>= \case 274 | Left e -> 275 | twarn $ 276 | T.unlines 277 | [ T.unwords ["Could not read", T.pack pathNixSourcesNix], 278 | T.unwords [" ", "(", tshow e, ")"] 279 | ] 280 | Right content -> do 281 | case md5ToSourcesVersion (T.pack $ show $ MD5.md5 content) of 282 | -- This is a custom or newer version, we don't do anything 283 | Nothing -> pure () 284 | Just v 285 | -- The file is the latest 286 | | v == maxBound -> pure () 287 | -- The file is older than than latest 288 | | otherwise -> do 289 | tsay $ 290 | T.unlines 291 | [ T.unwords 292 | [ tbold $ tblue "INFO:", 293 | "new sources.nix available:", 294 | sourcesVersionToText v, 295 | "->", 296 | sourcesVersionToText maxBound 297 | ], 298 | " Please run 'niv init' or add the following line in the " 299 | <> T.pack pathNixSourcesNix 300 | <> " file:", 301 | " # niv: no_update" 302 | ] 303 | 304 | -- | Glue code between nix and sources.json 305 | initNixSourcesNixContent :: B.ByteString 306 | initNixSourcesNixContent = $(embedFile "nix/sources.nix") 307 | 308 | -- | Empty JSON map 309 | initNixSourcesJsonContent :: B.ByteString 310 | initNixSourcesJsonContent = "{}" 311 | -------------------------------------------------------------------------------- /src/Niv/Sources/Test.hs: -------------------------------------------------------------------------------- 1 | module Niv.Sources.Test where 2 | 3 | import qualified Data.ByteString.Lazy as BL 4 | import qualified Data.Digest.Pure.MD5 as MD5 5 | import qualified Data.Text as T 6 | import Niv.Sources 7 | import Test.Tasty.HUnit ((@=?)) 8 | 9 | -- | Ensure that the sources.nix we ship is tracked as the latest version 10 | test_shippedSourcesNixIsLatest :: IO () 11 | test_shippedSourcesNixIsLatest = 12 | latestVersionMD5 13 | @=? (T.pack . show . MD5.md5 . BL.fromStrict $ initNixSourcesNixContent) 14 | -------------------------------------------------------------------------------- /src/Niv/Test.hs: -------------------------------------------------------------------------------- 1 | module Niv.Test (tests, test) where 2 | 3 | import qualified Niv.Git.Test as Git 4 | import Niv.GitHub.Test 5 | import Niv.Sources.Test 6 | import Niv.Update.Test 7 | import qualified Test.Tasty as Tasty 8 | import qualified Test.Tasty.HUnit as Tasty 9 | 10 | test :: IO () 11 | test = Tasty.defaultMain tests 12 | 13 | tests :: Tasty.TestTree 14 | tests = 15 | Tasty.testGroup 16 | "niv" 17 | [ Tasty.testGroup 18 | "update" 19 | [ Tasty.testCase "simply runs" simplyRuns, 20 | Tasty.testCase "picks first" picksFirst, 21 | Tasty.testCase "loads" loads, 22 | Tasty.testCase "survives checks" survivesChecks, 23 | Tasty.testCase "isn't too eager" isNotTooEager, 24 | Tasty.testCase "dirty forces update" dirtyForcesUpdate, 25 | Tasty.testCase "should run when no changes" shouldNotRunWhenNoChanges, 26 | Tasty.testCase "templates expand" templatesExpand 27 | ], 28 | Tasty.testGroup 29 | "github" 30 | [ Tasty.testCase "inits properly" test_githubInitsProperly, 31 | Tasty.testCase "updates" test_githubUpdates, 32 | Tasty.testCase "updates once" test_githubUpdatesOnce, 33 | Tasty.testCase "doesn't override rev" test_githubDoesntOverrideRev, 34 | Tasty.testCase "falls back to URL" test_githubURLFallback 35 | ], 36 | Tasty.testGroup 37 | "sources.nix" 38 | [ Tasty.testCase "has latest version" test_shippedSourcesNixIsLatest 39 | ], 40 | Tasty.testGroup "git" Git.tests 41 | ] 42 | -------------------------------------------------------------------------------- /src/Niv/Update.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE DeriveFunctor #-} 3 | {-# LANGUAGE FlexibleInstances #-} 4 | {-# LANGUAGE GADTs #-} 5 | {-# LANGUAGE LambdaCase #-} 6 | {-# LANGUAGE OverloadedStrings #-} 7 | {-# LANGUAGE RankNTypes #-} 8 | {-# LANGUAGE TupleSections #-} 9 | {-# LANGUAGE ViewPatterns #-} 10 | 11 | module Niv.Update where 12 | 13 | import Control.Applicative 14 | import Control.Arrow 15 | import qualified Control.Category as Cat 16 | import Data.Aeson (FromJSON, ToJSON, Value) 17 | import qualified Data.Aeson as Aeson 18 | import qualified Data.HashMap.Strict as HMS 19 | import Data.String 20 | import qualified Data.Text as T 21 | import Niv.Logger 22 | import UnliftIO 23 | 24 | type Attrs = HMS.HashMap T.Text (Freedom, Value) 25 | 26 | data Update b c where 27 | Id :: Update a a 28 | Compose :: (Compose b c) -> Update b c 29 | Arr :: (b -> c) -> Update b c 30 | First :: Update b c -> Update (b, d) (c, d) 31 | Zero :: Update b c 32 | Plus :: Update b c -> Update b c -> Update b c 33 | Check :: (a -> Bool) -> Update (Box a) () 34 | Load :: T.Text -> Update () (Box Value) 35 | UseOrSet :: T.Text -> Update (Box Value) (Box Value) 36 | Update :: T.Text -> Update (Box Value) (Box Value) 37 | Run :: (a -> IO b) -> Update (Box a) (Box b) 38 | Template :: Update (Box T.Text) (Box T.Text) 39 | 40 | instance ArrowZero Update where 41 | zeroArrow = Zero 42 | 43 | instance ArrowPlus Update where 44 | (<+>) = Plus 45 | 46 | instance Arrow Update where 47 | arr = Arr 48 | first = First 49 | 50 | instance Cat.Category Update where 51 | id = Id 52 | f . g = Compose (Compose' f g) 53 | 54 | instance Show (Update b c) where 55 | show = \case 56 | Id -> "Id" 57 | Compose (Compose' f g) -> "(" <> show f <> " . " <> show g <> ")" 58 | Arr _f -> "Arr" 59 | First a -> "First " <> show a 60 | Zero -> "Zero" 61 | Plus l r -> "(" <> show l <> " + " <> show r <> ")" 62 | Check _ch -> "Check" 63 | Load k -> "Load " <> T.unpack k 64 | UseOrSet k -> "UseOrSet " <> T.unpack k 65 | Update k -> "Update " <> T.unpack k 66 | Run _act -> "Io" 67 | Template -> "Template" 68 | 69 | data Compose a c = forall b. Compose' (Update b c) (Update a b) 70 | 71 | -- | Run an 'Update' and return the new attributes and result. 72 | runUpdate :: Attrs -> Update () a -> IO (Attrs, a) 73 | runUpdate attrs a = boxAttrs attrs >>= flip runUpdate' a >>= feed 74 | where 75 | feed = \case 76 | UpdateReady res -> hndl res 77 | UpdateNeedMore next -> next () >>= hndl 78 | hndl = \case 79 | UpdateSuccess f v -> (,v) <$> unboxAttrs f 80 | UpdateFailed e -> error $ "Update failed: " <> T.unpack (prettyFail e) 81 | prettyFail :: UpdateFailed -> T.Text 82 | prettyFail = \case 83 | FailNoSuchKey k -> "Key could not be found: " <> k 84 | FailZero -> bug "A dead end was reached during evaluation." 85 | FailCheck -> "A check failed during update" 86 | FailTemplate tpl keys -> 87 | T.unlines 88 | [ "Could not render template " <> tpl, 89 | "with keys: " <> T.intercalate ", " keys 90 | ] 91 | 92 | execUpdate :: Attrs -> Update () a -> IO a 93 | execUpdate attrs a = snd <$> runUpdate attrs a 94 | 95 | evalUpdate :: Attrs -> Update () a -> IO Attrs 96 | evalUpdate attrs a = fst <$> runUpdate attrs a 97 | 98 | tryEvalUpdate :: Attrs -> Update () a -> IO (Either SomeException Attrs) 99 | tryEvalUpdate attrs upd = tryAny (evalUpdate attrs upd) 100 | 101 | type JSON a = (ToJSON a, FromJSON a) 102 | 103 | data UpdateFailed 104 | = FailNoSuchKey T.Text 105 | | FailZero 106 | | FailCheck 107 | | FailTemplate T.Text [T.Text] 108 | deriving (Show) 109 | 110 | data UpdateRes a b 111 | = UpdateReady (UpdateReady b) 112 | | UpdateNeedMore (a -> IO (UpdateReady b)) 113 | deriving (Functor) 114 | 115 | data UpdateReady b 116 | = UpdateSuccess BoxedAttrs b 117 | | UpdateFailed UpdateFailed 118 | deriving (Functor) 119 | 120 | runBox :: Box a -> IO a 121 | runBox = boxOp 122 | 123 | data Box a = Box 124 | { -- | Whether the value is new or was retrieved (or derived) from old 125 | -- attributes 126 | boxNew :: Bool, 127 | boxOp :: IO a 128 | } 129 | deriving (Functor) 130 | 131 | mkBox :: Box a -> IO (Box a) 132 | mkBox b = do 133 | mvar <- newMVar Nothing 134 | pure b {boxOp = singleton mvar (boxOp b)} 135 | 136 | singleton :: MVar (Maybe a) -> IO a -> IO a 137 | singleton mvar def = do 138 | modifyMVar mvar $ \case 139 | Just a -> pure (Just a, a) 140 | Nothing -> do 141 | a <- def 142 | pure (Just a, a) 143 | 144 | instance Applicative Box where 145 | pure x = Box {boxNew = False, boxOp = pure x} 146 | f <*> v = 147 | Box 148 | { boxNew = (||) (boxNew f) (boxNew v), 149 | boxOp = boxOp f <*> boxOp v 150 | } 151 | 152 | instance (Semigroup a) => Semigroup (Box a) where 153 | (<>) = liftA2 (<>) 154 | 155 | instance IsString (Box T.Text) where 156 | fromString str = Box {boxNew = False, boxOp = pure $ T.pack str} 157 | 158 | type BoxedAttrs = HMS.HashMap T.Text (Freedom, Box Value) 159 | 160 | unboxAttrs :: BoxedAttrs -> IO Attrs 161 | unboxAttrs = traverse (\(fr, v) -> (fr,) <$> runBox v) 162 | 163 | boxAttrs :: Attrs -> IO BoxedAttrs 164 | boxAttrs = 165 | mapM 166 | ( \(fr, v) -> do 167 | box <- mkBox (pure v) 168 | pure 169 | ( fr, 170 | case fr of 171 | -- TODO: explain why hacky 172 | Locked -> box {boxNew = True} -- XXX: somewhat hacky 173 | Free -> box 174 | ) 175 | ) 176 | 177 | data Freedom 178 | = Locked 179 | | Free 180 | deriving (Eq, Show) 181 | 182 | -- | Runs an update, trying to evaluate the 'Box'es as little as possible. 183 | -- This is a hairy piece of code, apologies ¯\_(ツ)_/¯ 184 | -- In most cases I just picked the first implementation that compiled 185 | runUpdate' :: BoxedAttrs -> Update a b -> IO (UpdateRes a b) 186 | runUpdate' attrs = \case 187 | Id -> pure $ UpdateNeedMore $ pure . UpdateSuccess attrs 188 | Arr f -> pure $ UpdateNeedMore $ pure . UpdateSuccess attrs . f 189 | Zero -> pure $ UpdateReady (UpdateFailed FailZero) 190 | Plus l r -> 191 | runUpdate' attrs l >>= \case 192 | UpdateReady (UpdateFailed {}) -> runUpdate' attrs r 193 | UpdateReady (UpdateSuccess f v) -> pure $ UpdateReady (UpdateSuccess f v) 194 | UpdateNeedMore next -> pure $ 195 | UpdateNeedMore $ \v -> 196 | next v >>= \case 197 | UpdateSuccess f res -> pure $ UpdateSuccess f res 198 | UpdateFailed {} -> 199 | runUpdate' attrs r >>= \case 200 | UpdateReady res -> pure res 201 | UpdateNeedMore next' -> next' v 202 | Load k -> pure $ 203 | UpdateReady $ do 204 | case HMS.lookup k attrs of 205 | Just (_, v') -> UpdateSuccess attrs v' 206 | Nothing -> UpdateFailed $ FailNoSuchKey k 207 | First a -> do 208 | runUpdate' attrs a >>= \case 209 | UpdateReady (UpdateFailed e) -> pure $ UpdateReady $ UpdateFailed e 210 | UpdateReady (UpdateSuccess fo ba) -> pure $ 211 | UpdateNeedMore $ \gtt -> do 212 | pure $ UpdateSuccess fo (ba, snd gtt) 213 | UpdateNeedMore next -> pure $ 214 | UpdateNeedMore $ \gtt -> do 215 | next (fst gtt) >>= \case 216 | UpdateFailed e -> pure $ UpdateFailed e 217 | UpdateSuccess f res -> do 218 | pure $ UpdateSuccess f (res, snd gtt) 219 | Run act -> 220 | pure 221 | ( UpdateNeedMore $ \gtt -> do 222 | box <- mkBox $ Box (boxNew gtt) (act =<< runBox gtt) 223 | pure $ UpdateSuccess attrs box 224 | ) 225 | Check ch -> 226 | pure 227 | ( UpdateNeedMore $ \gtt -> do 228 | v <- runBox gtt 229 | if ch v 230 | then pure $ UpdateSuccess attrs () 231 | else pure $ UpdateFailed FailCheck 232 | ) 233 | UseOrSet k -> pure $ case HMS.lookup k attrs of 234 | Just (Locked, v) -> UpdateReady $ UpdateSuccess attrs v 235 | Just (Free, v) -> UpdateReady $ UpdateSuccess attrs v 236 | Nothing -> UpdateNeedMore $ \gtt -> do 237 | let attrs' = HMS.singleton k (Locked, gtt) <> attrs 238 | pure $ UpdateSuccess attrs' gtt 239 | Update k -> pure $ case HMS.lookup k attrs of 240 | Just (Locked, v) -> UpdateReady $ UpdateSuccess attrs v 241 | Just (Free, v) -> UpdateNeedMore $ \gtt -> do 242 | if boxNew gtt 243 | then do 244 | v' <- boxOp v 245 | gtt' <- boxOp gtt 246 | -- Here we compare the old and new values, flagging the new one as 247 | -- "boxNew" iff they differ. 248 | -- TODO: generalize this to all boxes 249 | let gtt'' = 250 | if v' /= gtt' 251 | then gtt {boxNew = True, boxOp = pure gtt'} 252 | else gtt {boxNew = False, boxOp = pure gtt'} 253 | pure $ UpdateSuccess (HMS.insert k (Locked, gtt'') attrs) gtt'' 254 | else do 255 | pure $ UpdateSuccess attrs v 256 | Nothing -> UpdateNeedMore $ \gtt -> do 257 | pure $ UpdateSuccess (HMS.insert k (Locked, gtt) attrs) gtt 258 | Compose (Compose' f g) -> 259 | runUpdate' attrs g >>= \case 260 | UpdateReady (UpdateFailed e) -> pure $ UpdateReady $ UpdateFailed e 261 | UpdateReady (UpdateSuccess attrs' act) -> 262 | runUpdate' attrs' f >>= \case 263 | UpdateReady (UpdateFailed e) -> pure $ UpdateReady $ UpdateFailed e 264 | UpdateReady (UpdateSuccess attrs'' act') -> pure $ UpdateReady $ UpdateSuccess attrs'' act' 265 | UpdateNeedMore next -> UpdateReady <$> next act 266 | UpdateNeedMore next -> pure $ 267 | UpdateNeedMore $ \gtt -> do 268 | next gtt >>= \case 269 | UpdateFailed e -> pure $ UpdateFailed e 270 | UpdateSuccess attrs' act -> 271 | runUpdate' attrs' f >>= \case 272 | UpdateReady ready -> pure ready 273 | UpdateNeedMore next' -> next' act 274 | Template -> pure $ 275 | UpdateNeedMore $ \v -> do 276 | v' <- runBox v 277 | case renderTemplate 278 | ( \k -> 279 | decodeBox ("When rendering template " <> v') . snd 280 | <$> HMS.lookup k attrs 281 | ) 282 | v' of 283 | Nothing -> pure $ UpdateFailed $ FailTemplate v' (HMS.keys attrs) 284 | Just v'' -> pure $ UpdateSuccess attrs (v'' <* v) -- carries over v's newness 285 | 286 | decodeBox :: (FromJSON a) => T.Text -> Box Value -> Box a 287 | decodeBox msg v = v {boxOp = boxOp v >>= decodeValue msg} 288 | 289 | decodeValue :: (FromJSON a) => T.Text -> Value -> IO a 290 | decodeValue msg v = case Aeson.fromJSON v of 291 | Aeson.Success x -> pure x 292 | Aeson.Error str -> 293 | error $ T.unpack msg <> ": Could not decode: " <> show v <> ": " <> str 294 | 295 | -- | Renders the template. Returns 'Nothing' if some of the attributes are 296 | -- missing. 297 | -- renderTemplate ("foo" -> "bar") "" -> pure (Just "bar") 298 | -- renderTemplate ("foo" -> "bar") "" -> pure Nothing 299 | renderTemplate :: (T.Text -> Maybe (Box T.Text)) -> T.Text -> Maybe (Box T.Text) 300 | renderTemplate vals tpl = case T.uncons tpl of 301 | Just ('<', str) -> do 302 | case T.span (/= '>') str of 303 | (key, T.uncons -> Just ('>', rest)) -> do 304 | let v = vals key 305 | liftA2 (<>) v (renderTemplate vals rest) 306 | _ -> Nothing 307 | Just (c, str) -> fmap (T.cons c) <$> renderTemplate vals str 308 | Nothing -> Just $ pure T.empty 309 | 310 | template :: Update (Box T.Text) (Box T.Text) 311 | template = Template 312 | 313 | check :: (a -> Bool) -> Update (Box a) () 314 | check = Check 315 | 316 | load :: (FromJSON a) => T.Text -> Update () (Box a) 317 | load k = Load k >>> arr (decodeBox $ "When loading key " <> k) 318 | 319 | -- TODO: should input really be Box? 320 | useOrSet :: (JSON a) => T.Text -> Update (Box a) (Box a) 321 | useOrSet k = 322 | arr (fmap Aeson.toJSON) 323 | >>> UseOrSet k 324 | >>> arr (decodeBox $ "When trying to use or set key " <> k) 325 | 326 | update :: (JSON a) => T.Text -> Update (Box a) (Box a) 327 | update k = 328 | arr (fmap Aeson.toJSON) 329 | >>> Update k 330 | >>> arr (decodeBox $ "When updating key " <> k) 331 | 332 | run :: (a -> IO b) -> Update (Box a) (Box b) 333 | run = Run 334 | 335 | -- | Like 'run' but forces evaluation 336 | run' :: (a -> IO b) -> Update (Box a) (Box b) 337 | run' act = Run act >>> dirty 338 | 339 | dirty :: Update (Box a) (Box a) 340 | dirty = arr (\v -> v {boxNew = True}) 341 | -------------------------------------------------------------------------------- /src/Niv/Update/Test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | 5 | module Niv.Update.Test where 6 | 7 | import Control.Arrow 8 | import Control.Monad 9 | import qualified Data.HashMap.Strict as HMS 10 | import qualified Data.Text as T 11 | import Niv.Update 12 | 13 | simplyRuns :: IO () 14 | simplyRuns = 15 | void $ 16 | runUpdate attrs $ proc () -> do 17 | returnA -< () 18 | where 19 | attrs = HMS.empty 20 | 21 | picksFirst :: IO () 22 | picksFirst = do 23 | v <- 24 | execUpdate HMS.empty $ 25 | let l = proc () -> do 26 | returnA -< 2 27 | r = proc () -> do 28 | returnA -< 3 29 | in l <+> r 30 | unless (v == (2 :: Int)) (error "bad value") 31 | 32 | loads :: IO () 33 | loads = do 34 | v <- execUpdate attrs $ load "foo" 35 | v' <- runBox v 36 | unless (v' == ("bar" :: T.Text)) (error "bad value") 37 | where 38 | attrs = HMS.singleton "foo" (Locked, "bar") 39 | 40 | survivesChecks :: IO () 41 | survivesChecks = do 42 | v <- execUpdate attrs $ proc () -> do 43 | (sawLeft <+> sawRight) -< () 44 | load "res" -< () 45 | v' <- runBox v 46 | unless (v' == ("I saw right" :: T.Text)) (error "bad value") 47 | where 48 | attrs = HMS.singleton "val" (Locked, "right") 49 | sawLeft :: Update () () 50 | sawLeft = proc () -> do 51 | val <- load "val" -< () 52 | check (== "left") -< (val :: Box T.Text) 53 | useOrSet "res" -< "I saw left" :: Box T.Text 54 | returnA -< () 55 | sawRight :: Update () () 56 | sawRight = proc () -> do 57 | val <- load "val" -< () 58 | check (== "right") -< (val :: Box T.Text) 59 | useOrSet "res" -< "I saw right" :: Box T.Text 60 | returnA -< () 61 | 62 | isNotTooEager :: IO () 63 | isNotTooEager = do 64 | let f = 65 | constBox () 66 | >>> run (const $ error "IO is too eager (f)") 67 | >>> useOrSet "foo" 68 | let f1 = proc () -> do 69 | run (const $ error "IO is too eager (f1)") -< pure () 70 | useOrSet "foo" -< "foo" 71 | void (execUpdate attrs f :: IO (Box T.Text)) 72 | void (execUpdate attrs f1 :: IO (Box T.Text)) 73 | where 74 | attrs = HMS.singleton "foo" (Locked, "right") 75 | 76 | dirtyForcesUpdate :: IO () 77 | dirtyForcesUpdate = do 78 | let f = constBox ("world" :: T.Text) >>> dirty >>> update "hello" 79 | attrs' <- evalUpdate attrs f 80 | unless ((snd <$> HMS.lookup "hello" attrs') == Just "world") $ 81 | error $ 82 | "bad value for hello: " <> show attrs' 83 | where 84 | attrs = HMS.singleton "hello" (Free, "foo") 85 | 86 | shouldNotRunWhenNoChanges :: IO () 87 | shouldNotRunWhenNoChanges = do 88 | let f = proc () -> do 89 | update "hello" -< ("world" :: Box T.Text) 90 | run (\() -> error "io shouldn't be run") -< pure () 91 | attrs <- evalUpdate HMS.empty f 92 | unless ((snd <$> HMS.lookup "hello" attrs) == Just "world") $ 93 | error $ 94 | "bad value for hello: " <> show attrs 95 | let f' = proc () -> do 96 | run (\() -> error "io shouldn't be run") -< pure () 97 | update "hello" -< ("world" :: Box T.Text) 98 | attrs' <- evalUpdate HMS.empty f' 99 | unless ((snd <$> HMS.lookup "hello" attrs') == Just "world") $ 100 | error $ 101 | "bad value for hello: " <> show attrs' 102 | v3 <- execUpdate 103 | (HMS.fromList [("hello", (Free, "world")), ("bar", (Free, "baz"))]) 104 | $ proc () -> do 105 | v1 <- update "hello" -< "world" 106 | v2 <- run (\_ -> error "io shouldn't be run") -< (v1 :: Box T.Text) 107 | v3 <- update "bar" -< (v2 :: Box T.Text) 108 | returnA -< v3 109 | v3' <- runBox v3 110 | unless (v3' == "baz") $ error "bad value" 111 | 112 | templatesExpand :: IO () 113 | templatesExpand = do 114 | v3 <- execUpdate attrs $ proc () -> template -< "-" 115 | v3' <- runBox v3 116 | unless (v3' == "hello-world") $ error "bad value" 117 | where 118 | attrs = HMS.fromList [("v1", (Free, "hello")), ("v2", (Free, "world"))] 119 | 120 | constBox :: a -> Update () (Box a) 121 | constBox a = arr (const (pure a)) 122 | -------------------------------------------------------------------------------- /tests/eval/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } 2 | }: 3 | 4 | 5 | let 6 | mkTest = name: text: 7 | { 8 | ${name} = 9 | pkgs.runCommand name { nativeBuildInputs = [ pkgs.jq pkgs.nix pkgs.moreutils ]; } 10 | '' 11 | # for nix to run smoothly in multi-user install 12 | # https://github.com/NixOS/nix/issues/3258 13 | # https://github.com/cachix/install-nix-action/issues/16 14 | export NIX_STATE_DIR="$TMPDIR" 15 | export NIX_LOG_DIR="$TMPDIR" 16 | 17 | cp ${ ../../nix/sources.nix} sources.nix 18 | echo '{}' > sources.json 19 | 20 | update_sources() { 21 | cat sources.json | jq -cMe "$@" | sponge sources.json 22 | } 23 | 24 | eval_outPath() { 25 | nix eval --raw '(let sources = import ./sources.nix; in sources.'"$1"'.outPath)' 26 | } 27 | 28 | eq() { 29 | if ! [ "$1" == "$2" ]; then 30 | echo "expected" 31 | echo " '$1' == '$2'" 32 | exit 1 33 | fi 34 | } 35 | 36 | ${text} 37 | 38 | touch "$out" 39 | ''; 40 | }; 41 | in 42 | 43 | mkTest "niv-override-eval" '' 44 | 45 | update_sources '.foo = { type: "tarball", url: "foo", sha256: "whocares" }' 46 | update_sources '."ba-r" = { type: "tarball", url: "foo", sha256: "whocares" }' 47 | update_sources '."ba z" = { type: "tarball", url: "foo", sha256: "whocares" }' 48 | 49 | res="$(NIV_OVERRIDE_foo="hello" eval_outPath "foo")" 50 | eq "$res" "hello" 51 | 52 | res="$(NIV_OVERRIDE_ba_r="hello" eval_outPath "ba-r")" 53 | eq "$res" "hello" 54 | 55 | res="$(NIV_OVERRIDE_ba_z="hello" eval_outPath '"ba z"')" 56 | eq "$res" "hello" 57 | 58 | '' // mkTest "sources-json-elsewhere" 59 | '' 60 | update_sources '.foo = { type: "tarball", url: "foo", sha256: "whocares" }' 61 | 62 | mkdir other 63 | mv sources.json other 64 | 65 | # Here we test that sources.nix can be imported even if there is no 66 | # sources.json in the same directory 67 | eval_outPath() { 68 | nix eval --raw '(let sources = import ./sources.nix { sourcesFile = ./other/sources.json; } ; in sources.'"$1"'.outPath)' 69 | } 70 | 71 | res="$(NIV_OVERRIDE_foo="hello" eval_outPath "foo")" 72 | eq "$res" "hello" 73 | '' 74 | 75 | // mkTest "sanitize-source-name" 76 | '' 77 | file=$(mktemp -d)/foo%%.bar 78 | touch "$file" 79 | sha=$(nix-hash --type sha256 --flat $file) 80 | url="file://$file" 81 | 82 | update_sources '.foo = { type: "file", url: $url, sha256: $sha}' --arg url "$url" --arg sha "$sha" 83 | 84 | # we don't need to check the result, we just make sure this evaluates 85 | eval_outPath "foo" 86 | '' 87 | -------------------------------------------------------------------------------- /tests/git/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, niv }: 2 | 3 | # TODO: this doesn' test anything meaningful yet because "niv git PACKAGE" 4 | # doesn't parse yet 5 | pkgs.runCommand "git-test" 6 | { nativeBuildInputs = [ pkgs.git niv pkgs.nix pkgs.jq ]; } 7 | ( 8 | 9 | # make sure the tests run smoothly in multi-user install 10 | # https://github.com/NixOS/nix/issues/3258 11 | '' 12 | export NIX_STATE_DIR=$TMPDIR 13 | export NIX_LOG_DIR=$TMPDIR 14 | export HOME=$TMPDIR 15 | '' + # First we create a dummy git repo with one commit on master, and one commit 16 | # on "branch". 17 | '' 18 | gitdir=$(mktemp -d) 19 | pushd $gitdir > /dev/null 20 | git init . 21 | echo hello > file 22 | git config user.email "niv@foo.bar" 23 | git config user.name "Niv Niverson" 24 | git add file 25 | git commit -m "Initial commit" 26 | gitrev=$(git rev-parse HEAD) 27 | 28 | git checkout -b branch 29 | echo world >> file 30 | git add file 31 | git commit -m "second commit" 32 | gitrev2=$(git rev-parse HEAD) 33 | 34 | # reset to master as "default branch" 35 | git checkout master 36 | popd > /dev/null 37 | '' + # Then we `niv add` that repo and check some properties, like the revision 38 | # and revCount, to make sure it was imported properly, and that sources.nix 39 | # does what it's supposed to do. 40 | '' 41 | nivdir=$(mktemp -d) 42 | pushd $nivdir > /dev/null 43 | mkdir -p nix 44 | echo "{}" > nix/sources.json 45 | niv init --latest 46 | niv add git -n my-git-repo --repo file://$gitdir 47 | nivrev=$(nix --extra-experimental-features nix-command eval --json --impure --expr '(import ./nix/sources.nix).my-git-repo.rev' | jq -r) 48 | if [ ! "$gitrev" = "$nivrev" ]; then 49 | echo "Mismatched revs: $gitrev != $nivrev" 50 | exit 42 51 | fi 52 | 53 | # here we cheat a bit and use "outPath", which actually is the result of 54 | # builtins.fetchGit. 55 | nivnixrev=$(nix --extra-experimental-features nix-command eval --json --impure --expr '(import ./nix/sources.nix).my-git-repo.outPath.rev' | jq -r) 56 | if [ ! "$gitrev" = "$nivnixrev" ]; then 57 | echo "Mismatched revs: $gitrev != $nivnixrev" 58 | exit 42 59 | fi 60 | nivnixrevcount=$(nix --extra-experimental-features nix-command eval --impure --json --expr '(import ./nix/sources.nix).my-git-repo.outPath.revCount') 61 | if [ ! "1" -eq "$nivnixrevcount" ]; then 62 | echo "Mismatched revCount: 1 != $nivnixrevcount" 63 | exit 42 64 | fi 65 | 66 | niv update my-git-repo -b branch 67 | nivrev2=$(nix --extra-experimental-features nix-command eval --impure --json --expr '(import ./nix/sources.nix).my-git-repo.rev' | jq -r) 68 | if [ ! "$gitrev2" = "$nivrev2" ]; then 69 | echo "Mismatched revs: $gitrev2 != $nivrev2" 70 | exit 42 71 | fi 72 | 73 | popd > /dev/null 74 | 75 | touch $out 76 | '' 77 | ) 78 | -------------------------------------------------------------------------------- /tests/github/data/archives/571b40d3f50466d3e91c1e609d372de96d782793.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmattia/niv/dd678782cae74508d6b4824580d2b0935308011e/tests/github/data/archives/571b40d3f50466d3e91c1e609d372de96d782793.tar.gz -------------------------------------------------------------------------------- /tests/github/data/archives/a489b65a5c3a29983701069d1ce395b23d9bde64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmattia/niv/dd678782cae74508d6b4824580d2b0935308011e/tests/github/data/archives/a489b65a5c3a29983701069d1ce395b23d9bde64.tar.gz -------------------------------------------------------------------------------- /tests/github/data/archives/abc51449406ba3279c466b4d356b4ae8522ceb58.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmattia/niv/dd678782cae74508d6b4824580d2b0935308011e/tests/github/data/archives/abc51449406ba3279c466b4d356b4ae8522ceb58.tar.gz -------------------------------------------------------------------------------- /tests/github/data/repos/NixOS/nixpkgs-channels/repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 31716917, 3 | "node_id": "MDEwOlJlcG9zaXRvcnkzMTcxNjkxNw==", 4 | "name": "nixpkgs-channels", 5 | "full_name": "NixOS/nixpkgs-channels", 6 | "private": false, 7 | "owner": { 8 | "login": "NixOS", 9 | "id": 487568, 10 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ4NzU2OA==", 11 | "avatar_url": "https://avatars3.githubusercontent.com/u/487568?v=4", 12 | "gravatar_id": "", 13 | "url": "https://api.github.com/users/NixOS", 14 | "html_url": "https://github.com/NixOS", 15 | "followers_url": "https://api.github.com/users/NixOS/followers", 16 | "following_url": "https://api.github.com/users/NixOS/following{/other_user}", 17 | "gists_url": "https://api.github.com/users/NixOS/gists{/gist_id}", 18 | "starred_url": "https://api.github.com/users/NixOS/starred{/owner}{/repo}", 19 | "subscriptions_url": "https://api.github.com/users/NixOS/subscriptions", 20 | "organizations_url": "https://api.github.com/users/NixOS/orgs", 21 | "repos_url": "https://api.github.com/users/NixOS/repos", 22 | "events_url": "https://api.github.com/users/NixOS/events{/privacy}", 23 | "received_events_url": "https://api.github.com/users/NixOS/received_events", 24 | "type": "Organization", 25 | "site_admin": false 26 | }, 27 | "html_url": "https://github.com/NixOS/nixpkgs-channels", 28 | "description": "Nixpkgs/NixOS branches that track the Nixpkgs/NixOS channels", 29 | "fork": false, 30 | "url": "https://api.github.com/repos/NixOS/nixpkgs-channels", 31 | "forks_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/forks", 32 | "keys_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/keys{/key_id}", 33 | "collaborators_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/collaborators{/collaborator}", 34 | "teams_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/teams", 35 | "hooks_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/hooks", 36 | "issue_events_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/issues/events{/number}", 37 | "events_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/events", 38 | "assignees_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/assignees{/user}", 39 | "branches_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/branches{/branch}", 40 | "tags_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/tags", 41 | "blobs_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/git/blobs{/sha}", 42 | "git_tags_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/git/tags{/sha}", 43 | "git_refs_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/git/refs{/sha}", 44 | "trees_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/git/trees{/sha}", 45 | "statuses_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/statuses/{sha}", 46 | "languages_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/languages", 47 | "stargazers_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/stargazers", 48 | "contributors_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/contributors", 49 | "subscribers_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/subscribers", 50 | "subscription_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/subscription", 51 | "commits_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/commits{/sha}", 52 | "git_commits_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/git/commits{/sha}", 53 | "comments_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/comments{/number}", 54 | "issue_comment_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/issues/comments{/number}", 55 | "contents_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/contents/{+path}", 56 | "compare_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/compare/{base}...{head}", 57 | "merges_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/merges", 58 | "archive_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/{archive_format}{/ref}", 59 | "downloads_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/downloads", 60 | "issues_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/issues{/number}", 61 | "pulls_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/pulls{/number}", 62 | "milestones_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/milestones{/number}", 63 | "notifications_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/notifications{?since,all,participating}", 64 | "labels_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/labels{/name}", 65 | "releases_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/releases{/id}", 66 | "deployments_url": "https://api.github.com/repos/NixOS/nixpkgs-channels/deployments", 67 | "created_at": "2015-03-05T14:12:05Z", 68 | "updated_at": "2019-04-25T16:29:51Z", 69 | "pushed_at": "2019-04-25T20:34:53Z", 70 | "git_url": "git://github.com/NixOS/nixpkgs-channels.git", 71 | "ssh_url": "git@github.com:NixOS/nixpkgs-channels.git", 72 | "clone_url": "https://github.com/NixOS/nixpkgs-channels.git", 73 | "svn_url": "https://github.com/NixOS/nixpkgs-channels", 74 | "homepage": null, 75 | "size": 515009, 76 | "stargazers_count": 115, 77 | "watchers_count": 115, 78 | "language": "Nix", 79 | "has_issues": false, 80 | "has_projects": true, 81 | "has_downloads": true, 82 | "has_wiki": false, 83 | "has_pages": false, 84 | "forks_count": 66, 85 | "mirror_url": null, 86 | "archived": false, 87 | "disabled": false, 88 | "open_issues_count": 0, 89 | "license": { 90 | "key": "mit", 91 | "name": "MIT License", 92 | "spdx_id": "MIT", 93 | "url": "https://api.github.com/licenses/mit", 94 | "node_id": "MDc6TGljZW5zZTEz" 95 | }, 96 | "forks": 66, 97 | "open_issues": 0, 98 | "watchers": 115, 99 | "default_branch": "nixos-unstable", 100 | "organization": { 101 | "login": "NixOS", 102 | "id": 487568, 103 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ4NzU2OA==", 104 | "avatar_url": "https://avatars3.githubusercontent.com/u/487568?v=4", 105 | "gravatar_id": "", 106 | "url": "https://api.github.com/users/NixOS", 107 | "html_url": "https://github.com/NixOS", 108 | "followers_url": "https://api.github.com/users/NixOS/followers", 109 | "following_url": "https://api.github.com/users/NixOS/following{/other_user}", 110 | "gists_url": "https://api.github.com/users/NixOS/gists{/gist_id}", 111 | "starred_url": "https://api.github.com/users/NixOS/starred{/owner}{/repo}", 112 | "subscriptions_url": "https://api.github.com/users/NixOS/subscriptions", 113 | "organizations_url": "https://api.github.com/users/NixOS/orgs", 114 | "repos_url": "https://api.github.com/users/NixOS/repos", 115 | "events_url": "https://api.github.com/users/NixOS/events{/privacy}", 116 | "received_events_url": "https://api.github.com/users/NixOS/received_events", 117 | "type": "Organization", 118 | "site_admin": false 119 | }, 120 | "network_count": 66, 121 | "subscribers_count": 30 122 | } 123 | -------------------------------------------------------------------------------- /tests/github/data/repos/nmattia/niv/repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 166296150, 3 | "node_id": "MDEwOlJlcG9zaXRvcnkxNjYyOTYxNTA=", 4 | "name": "niv", 5 | "full_name": "nmattia/niv", 6 | "private": false, 7 | "owner": { 8 | "login": "nmattia", 9 | "id": 6930756, 10 | "node_id": "MDQ6VXNlcjY5MzA3NTY=", 11 | "avatar_url": "https://avatars2.githubusercontent.com/u/6930756?v=4", 12 | "gravatar_id": "", 13 | "url": "https://api.github.com/users/nmattia", 14 | "html_url": "https://github.com/nmattia", 15 | "followers_url": "https://api.github.com/users/nmattia/followers", 16 | "following_url": "https://api.github.com/users/nmattia/following{/other_user}", 17 | "gists_url": "https://api.github.com/users/nmattia/gists{/gist_id}", 18 | "starred_url": "https://api.github.com/users/nmattia/starred{/owner}{/repo}", 19 | "subscriptions_url": "https://api.github.com/users/nmattia/subscriptions", 20 | "organizations_url": "https://api.github.com/users/nmattia/orgs", 21 | "repos_url": "https://api.github.com/users/nmattia/repos", 22 | "events_url": "https://api.github.com/users/nmattia/events{/privacy}", 23 | "received_events_url": "https://api.github.com/users/nmattia/received_events", 24 | "type": "User", 25 | "site_admin": false 26 | }, 27 | "html_url": "https://github.com/nmattia/niv", 28 | "description": "Easy dependency management for Nix projects", 29 | "fork": false, 30 | "url": "https://api.github.com/repos/nmattia/niv", 31 | "forks_url": "https://api.github.com/repos/nmattia/niv/forks", 32 | "keys_url": "https://api.github.com/repos/nmattia/niv/keys{/key_id}", 33 | "collaborators_url": "https://api.github.com/repos/nmattia/niv/collaborators{/collaborator}", 34 | "teams_url": "https://api.github.com/repos/nmattia/niv/teams", 35 | "hooks_url": "https://api.github.com/repos/nmattia/niv/hooks", 36 | "issue_events_url": "https://api.github.com/repos/nmattia/niv/issues/events{/number}", 37 | "events_url": "https://api.github.com/repos/nmattia/niv/events", 38 | "assignees_url": "https://api.github.com/repos/nmattia/niv/assignees{/user}", 39 | "branches_url": "https://api.github.com/repos/nmattia/niv/branches{/branch}", 40 | "tags_url": "https://api.github.com/repos/nmattia/niv/tags", 41 | "blobs_url": "https://api.github.com/repos/nmattia/niv/git/blobs{/sha}", 42 | "git_tags_url": "https://api.github.com/repos/nmattia/niv/git/tags{/sha}", 43 | "git_refs_url": "https://api.github.com/repos/nmattia/niv/git/refs{/sha}", 44 | "trees_url": "https://api.github.com/repos/nmattia/niv/git/trees{/sha}", 45 | "statuses_url": "https://api.github.com/repos/nmattia/niv/statuses/{sha}", 46 | "languages_url": "https://api.github.com/repos/nmattia/niv/languages", 47 | "stargazers_url": "https://api.github.com/repos/nmattia/niv/stargazers", 48 | "contributors_url": "https://api.github.com/repos/nmattia/niv/contributors", 49 | "subscribers_url": "https://api.github.com/repos/nmattia/niv/subscribers", 50 | "subscription_url": "https://api.github.com/repos/nmattia/niv/subscription", 51 | "commits_url": "https://api.github.com/repos/nmattia/niv/commits{/sha}", 52 | "git_commits_url": "https://api.github.com/repos/nmattia/niv/git/commits{/sha}", 53 | "comments_url": "https://api.github.com/repos/nmattia/niv/comments{/number}", 54 | "issue_comment_url": "https://api.github.com/repos/nmattia/niv/issues/comments{/number}", 55 | "contents_url": "https://api.github.com/repos/nmattia/niv/contents/{+path}", 56 | "compare_url": "https://api.github.com/repos/nmattia/niv/compare/{base}...{head}", 57 | "merges_url": "https://api.github.com/repos/nmattia/niv/merges", 58 | "archive_url": "https://api.github.com/repos/nmattia/niv/{archive_format}{/ref}", 59 | "downloads_url": "https://api.github.com/repos/nmattia/niv/downloads", 60 | "issues_url": "https://api.github.com/repos/nmattia/niv/issues{/number}", 61 | "pulls_url": "https://api.github.com/repos/nmattia/niv/pulls{/number}", 62 | "milestones_url": "https://api.github.com/repos/nmattia/niv/milestones{/number}", 63 | "notifications_url": "https://api.github.com/repos/nmattia/niv/notifications{?since,all,participating}", 64 | "labels_url": "https://api.github.com/repos/nmattia/niv/labels{/name}", 65 | "releases_url": "https://api.github.com/repos/nmattia/niv/releases{/id}", 66 | "deployments_url": "https://api.github.com/repos/nmattia/niv/deployments", 67 | "created_at": "2019-01-17T21:00:18Z", 68 | "updated_at": "2019-04-23T09:28:07Z", 69 | "pushed_at": "2019-04-25T22:20:50Z", 70 | "git_url": "git://github.com/nmattia/niv.git", 71 | "ssh_url": "git@github.com:nmattia/niv.git", 72 | "clone_url": "https://github.com/nmattia/niv.git", 73 | "svn_url": "https://github.com/nmattia/niv", 74 | "homepage": "https://github.com/nmattia/niv", 75 | "size": 125, 76 | "stargazers_count": 84, 77 | "watchers_count": 84, 78 | "language": "Haskell", 79 | "has_issues": true, 80 | "has_projects": true, 81 | "has_downloads": true, 82 | "has_wiki": true, 83 | "has_pages": false, 84 | "forks_count": 5, 85 | "mirror_url": null, 86 | "archived": false, 87 | "disabled": false, 88 | "open_issues_count": 9, 89 | "license": null, 90 | "forks": 5, 91 | "open_issues": 9, 92 | "watchers": 84, 93 | "default_branch": "master", 94 | "network_count": 5, 95 | "subscribers_count": 9 96 | } 97 | -------------------------------------------------------------------------------- /tests/github/default.nix: -------------------------------------------------------------------------------- 1 | # Test the niv binary 2 | # 3 | # A webserver is started to mock API requests to GitHub and to serve 4 | # files. niv commands are then executed and the some elements of the 5 | # resulting sources.json are verified with jq. 6 | # 7 | # Note that niv needs to be patched to use http://localhost:3333 8 | # instead of https://github.com since it doesn't support another 9 | # GitHub url (while the GitHub API permits that). 10 | # 11 | # To mock GitHub, JSON responses of the real GitHub API have been 12 | # copied to files (in the data folder) and are used by the webserver 13 | # to serve GitHub API responses. 14 | 15 | { pkgs, niv }: 16 | 17 | let 18 | niv_HEAD = "a489b65a5c3a29983701069d1ce395b23d9bde64"; 19 | niv_HEAD- = "abc51449406ba3279c466b4d356b4ae8522ceb58"; 20 | nixpkgs-channels_HEAD = "571b40d3f50466d3e91c1e609d372de96d782793"; 21 | in 22 | pkgs.runCommand "test" 23 | { 24 | buildInputs = [ 25 | pkgs.haskellPackages.wai-app-static 26 | niv 27 | pkgs.nix 28 | pkgs.jq 29 | pkgs.netcat-gnu 30 | ]; 31 | } 32 | '' 33 | set -euo pipefail 34 | 35 | export NIV_GITHUB_HOST="localhost:3333" 36 | export NIV_GITHUB_API_HOST="localhost" 37 | export NIV_GITHUB_API_PORT="3333" 38 | export NIV_GITHUB_INSECURE="true" 39 | 40 | echo *** Starting the webserver... 41 | mkdir -p mock 42 | 43 | warp -d mock -p 3333 & 44 | 45 | while ! nc -z 127.0.0.1 3333; do 46 | echo waiting for mock server 47 | sleep 1 48 | done 49 | 50 | # We can't access /nix or /var from the sandbox 51 | export NIX_STATE_DIR=$PWD/nix/var/nix 52 | export NIX_STORE_DIR=$PWD/nix/store 53 | 54 | 55 | echo -e "\n*** niv init" 56 | 57 | ## mock API behavior: 58 | ## - niv points to HEAD 59 | ## - nixpkgs-channels points to HEAD 60 | 61 | mkdir -p mock/repos/nmattia/niv/ 62 | cp ${./data/repos/nmattia/niv/repository.json} mock/repos/nmattia/niv/index.html 63 | 64 | mkdir -p mock/repos/nmattia/niv/commits 65 | cat ${./data/repos/nmattia/niv/commits.json} | jq -j '.[0] | .sha' > mock/repos/nmattia/niv/commits/master 66 | # XXX: cat so we don't inherit the read-only permissions 67 | mkdir -p mock/nmattia/niv/archive 68 | cp ${./data/archives + "/${niv_HEAD}.tar.gz"} \ 69 | mock/nmattia/niv/archive/${niv_HEAD}.tar.gz 70 | 71 | mkdir -p mock/repos/NixOS/nixpkgs-channels 72 | cp ${./data/repos/NixOS/nixpkgs-channels/repository.json} mock/repos/NixOS/nixpkgs-channels/index.html 73 | mkdir -p mock/repos/NixOS/nixpkgs-channels/commits 74 | cat ${./data/repos/NixOS/nixpkgs-channels/commits.json} | jq -j '.[0] | .sha' > mock/repos/NixOS/nixpkgs-channels/commits/nixos-19.09 75 | mkdir -p mock/NixOS/nixpkgs-channels/archive 76 | cp ${./data/archives + "/${nixpkgs-channels_HEAD}.tar.gz"} \ 77 | mock/NixOS/nixpkgs-channels/archive/${nixpkgs-channels_HEAD}.tar.gz 78 | 79 | niv init --nixpkgs NixOS/nixpkgs-channels --nixpkgs-branch nixos-19.09 80 | niv add nmattia/niv 81 | diff -h ${./expected/niv-init.json} nix/sources.json || \ 82 | (echo "Mismatched sources.json"; \ 83 | echo "Reference: tests/expected/niv-init.json"; \ 84 | exit 1) 85 | 86 | echo "*** ok." 87 | 88 | 89 | echo -e "\n*** niv drop niv" 90 | niv drop niv 91 | echo -n "niv package has been removed: " 92 | cat nix/sources.json | jq -e '. | has("niv") | not' 93 | echo -e "*** ok." 94 | 95 | ## mock API behavior: 96 | ## - niv points to HEAD~ 97 | ## - nixpkgs-channels points to HEAD 98 | 99 | echo -e "\n*** niv add nmattia/niv" 100 | # We use the HEAD~1 commit to update it in the next step 101 | # (i.e. we use the second element of the commit array) 102 | cat ${./data/repos/nmattia/niv/commits.json} | jq -j '.[1] | .sha' > mock/repos/nmattia/niv/commits/master 103 | cp ${./data/archives + "/${niv_HEAD-}.tar.gz"} \ 104 | mock/nmattia/niv/archive/${niv_HEAD-}.tar.gz 105 | niv add nmattia/niv 106 | echo -n "niv.rev == ${niv_HEAD-} (HEAD~): " 107 | cat nix/sources.json | jq -e '.niv | .rev == "${niv_HEAD-}"' 108 | echo -e "*** ok." 109 | 110 | ## mock API behavior: 111 | ## - niv points to HEAD 112 | ## - nixpkgs-channels points to HEAD 113 | 114 | echo -e "\n*** niv update niv" 115 | cat ${./data/repos/nmattia/niv/commits.json} | jq -j '.[0] | .sha' > mock/repos/nmattia/niv/commits/master 116 | niv update niv 117 | echo -n "niv.rev == ${niv_HEAD} (HEAD): " 118 | cat nix/sources.json | jq -e '.niv | .rev == "${niv_HEAD}"' 119 | echo -e "*** ok." 120 | 121 | echo -e "\n*** niv add foo -v 1 -t 'localhost:3333/foo-v'" 122 | echo foo > mock/foo-v1 123 | niv add foo -v 1 -t 'localhost:3333/foo-v' 124 | echo -n "foo.url == localhost:3333/foo-v1: " 125 | cat nix/sources.json | jq -e '.foo | .url == "localhost:3333/foo-v1"' 126 | echo "*** ok." 127 | cp nix/sources.json $out 128 | '' 129 | -------------------------------------------------------------------------------- /tests/github/expected/niv-init.json: -------------------------------------------------------------------------------- 1 | { 2 | "niv": { 3 | "branch": "master", 4 | "description": "Easy dependency management for Nix projects", 5 | "homepage": "https://github.com/nmattia/niv", 6 | "owner": "nmattia", 7 | "repo": "niv", 8 | "rev": "a489b65a5c3a29983701069d1ce395b23d9bde64", 9 | "sha256": "18hx0pg48xh306gwz9wja8fcyl8isfkv3ja7453yj3hmy72b6scp", 10 | "type": "tarball", 11 | "url": "http://localhost:3333/nmattia/niv/archive/a489b65a5c3a29983701069d1ce395b23d9bde64.tar.gz", 12 | "url_template": "http://localhost:3333///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixos-19.09", 16 | "description": "Nixpkgs/NixOS branches that track the Nixpkgs/NixOS channels", 17 | "homepage": null, 18 | "owner": "NixOS", 19 | "repo": "nixpkgs-channels", 20 | "rev": "571b40d3f50466d3e91c1e609d372de96d782793", 21 | "sha256": "18hlxki1hbimvm1jagcf7dy5km1iakr1px472cb05y9602py7f6g", 22 | "type": "tarball", 23 | "url": "http://localhost:3333/NixOS/nixpkgs-channels/archive/571b40d3f50466d3e91c1e609d372de96d782793.tar.gz", 24 | "url_template": "http://localhost:3333///archive/.tar.gz" 25 | } 26 | } 27 | --------------------------------------------------------------------------------