├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 phil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nix for devs 2 | 3 | This is collection of recipes focused on [`nix-shell`](ttp://nixos.org/nixpkgs/manual/#how-to-create-ad-hoc-environments-for-nix-shell) to make setting up project environments easy. 4 | Pragmatism and low project impact are prioritized: 5 | it's not a goal to change a nodejs project to use nix instead of npm, 6 | but it is a goal to run npm projects without having to install any OS dependencies (including nodejs itself!). 7 | `nix-ops` and other super-cool nix tech are completely out of scope. 8 | 9 | Every solution should work today. 10 | If something is broken, [open an issue!](https://github.com/uniphil/nix-for-devs/issues/new). 11 | If there is a better solution that will be supported by nix soon, we wait until it's released before mentioning it here (but don't hesitate to open a tracking issue!). 12 | 13 | Maybe in the best case, this document can be a kind of gateway to nix and nixos. 14 | I keep telling my developer friends about the cool things I do with nix, but I have to add huge warnings about how much time it's taken me to get to my current level of productivity. 15 | I'm annoyingly aware of the amount of trivia I'm carying in my head (though I still feel 16 | like a beginner!), and I constantly go back to old projects to look up what I did last time to fix problems. 17 | I frequently find myself running into hard-to-google issues. 18 | So, I'm finally writing it all down, for myself as much as for my friends :) 19 | 20 | 21 | ## Work-in-progress 22 | 23 | Current state: rough sketches and todos. Pull requests welcome! 24 | 25 | 26 | ## `nix-shell` 27 | 28 | TODO 29 | 30 | 31 | ### Run stuff without installing anything 32 | 33 | Stuff I used to do with docker. TODO :) 34 | 35 | 36 | ## Node.js 37 | 38 | `shell.nix` 39 | 40 | ```nix 41 | with import {}; 42 | 43 | stdenv.mkDerivation { 44 | name = "node"; 45 | buildInputs = [ 46 | nodejs 47 | ]; 48 | shellHook = '' 49 | export PATH="$PWD/node_modules/.bin/:$PATH" 50 | ''; 51 | } 52 | ``` 53 | 54 | The `nodejs` build input sets up `node` and `npm` in the shell. 55 | The shell hook adds the `.bin/` folder to your `PATH`, so you can install node command-line tools like `grunt` with `npm` without the `-g` flag. 56 | Finally, it automatically installs any dependencies found in `package.json`, if one exists. 57 | If you prefer to run `npm install` manually inside the shell, just delete that line from `shellHook`. 58 | 59 | ### `npm run <...>` shortcut 60 | 61 | It's handy to have an alias `run` for `npm run`, which makes the following two commands equivalent: 62 | 63 | ```bash 64 | $ npm run watch:test 65 | $ run watch:test 66 | ``` 67 | 68 | You can add this to `shellHook`: 69 | 70 | ```bash 71 | alias run='npm run' 72 | ``` 73 | 74 | ### Newer nodejs 75 | 76 | `shell.nix` 77 | 78 | ```nix 79 | with import {}; 80 | 81 | stdenv.mkDerivation { 82 | name = "node"; 83 | buildInputs = [ 84 | nodejs-8_x 85 | ]; 86 | shellHook = '' 87 | export PATH="$PWD/node_modules/.bin/:$PATH" 88 | ''; 89 | } 90 | ``` 91 | 92 | Node versions are published as -_x, so etg., `nodejs-7_x` and `nodejs-6_x` are also valid. 93 | 94 | ### View available npm scripts 95 | 96 | I use `jq` to quickly get a summary of all scripts defined in a package's `package.json` file: 97 | 98 | `shell.nix` 99 | 100 | ```nix 101 | with import {}; 102 | 103 | stdenv.mkDerivation { 104 | name = "node"; 105 | buildInputs = [ 106 | jq 107 | nodejs 108 | ]; 109 | shellHook = '' 110 | export PATH="$PWD/node_modules/.bin/:$PATH" 111 | alias scripts='jq ".scripts" package.json' 112 | ''; 113 | } 114 | ``` 115 | 116 | This sets up an alias called `scripts`. Example output: 117 | 118 | ```bash 119 | $ scripts 120 | { 121 | "build": "babel --ignore __tests__,story.js -d lib/ src/", 122 | "lint": "eslint *.js src/ storybook/", 123 | "start": "start-storybook -p 9001 -c ./storybook/web", 124 | "test": "jest --roots src --silent", 125 | "test:watch": "jest --roots src --watch" 126 | } 127 | ``` 128 | 129 | ### React native 130 | 131 | `shell.nix` 132 | 133 | ```nix 134 | with import {}; 135 | 136 | let 137 | jdk = openjdk; 138 | node = nodejs-8_x; 139 | sdk = androidenv.androidsdk { 140 | platformVersions = [ "23" ]; 141 | abiVersions = [ "x86" ]; 142 | useGoogleAPIs = true; 143 | useExtraSupportLibs = false; 144 | useGooglePlayServices = false; 145 | }; 146 | unpatched-sdk = 147 | let version = "3859397"; 148 | in stdenv.mkDerivation { 149 | name = "unpatched-sdk"; 150 | src = fetchzip { 151 | url = "https://dl.google.com/android/repository/sdk-tools-linux-${version}.zip"; 152 | sha256 = "03vh2w1i7sjgsh91bpw40ryhwmz46rv8b9mp7xzg89zs18021plr"; 153 | }; 154 | installPhase = '' 155 | mkdir -p $out 156 | cp -r * $out/ 157 | ''; 158 | dontPatchELF = true; 159 | }; 160 | run-android = pkgs.buildFHSUserEnv { 161 | name = "run-android"; 162 | targetPkgs = (pkgs: [ 163 | node 164 | ]); 165 | profile = '' 166 | export JAVA_HOME=${jdk.home} 167 | export ANDROID_HOME=$PWD/.android 168 | export PATH=$PWD/node_modules/.bin:$PATH 169 | ''; 170 | runScript = "react-native run-android"; 171 | }; 172 | in 173 | stdenv.mkDerivation { 174 | name = "react-native-android"; 175 | nativeBuildInputs = [ 176 | run-android 177 | ]; 178 | buildInputs = [ 179 | coreutils 180 | node 181 | sdk 182 | unpatched-sdk 183 | ]; 184 | shellHook = '' 185 | export JAVA_HOME=${jdk} 186 | export ANDROID_HOME=$PWD/.android/sdk 187 | export PATH="$ANDROID_HOME/bin:$PWD/node_modules/.bin:$PATH" 188 | 189 | if ! test -d .android ; then 190 | echo doing hacky setup stuff: 191 | 192 | echo "=> pull the sdk out of the nix store and into a writeable directory" 193 | mkdir -p .android/sdk 194 | cp -r ${unpatched-sdk}/* .android/sdk/ 195 | 196 | echo "=> don't track the sdk directory" 197 | echo .android/ >> .gitignore 198 | 199 | echo "=> get react-native-cli in here" 200 | npm install --no-save react-native-cli 201 | 202 | echo "=> set up react-native plugins... need an FHS env for... reasons." 203 | cd .android/sdk 204 | $PWD/bin/sdkmanager --update 205 | echo "=> installing platform stuff (you'll need to accept a license in a second)..." 206 | $PWD/bin/sdkmanager "platforms;android-23" "build-tools;23.0.1" "add-ons;addon-google_apis-google-23" 207 | cd ../../ 208 | fi; 209 | ''; 210 | } 211 | ``` 212 | 213 | This one is super-hacky and requires usage instructions :( 214 | also probably only works on linux :/ 215 | 216 | #### 1. Enter the environment 217 | 218 | ```bash 219 | $ nix-shell 220 | [...lots of console spam the first time] 221 | [nix-shell:]$ 222 | ``` 223 | 224 | #### 2. If you don't have an avd set up, make one 225 | 226 | ```bash 227 | [nix-shell:]$ android create avd -t android-23 -b x86 -d "Nexus 5" -n nexus 228 | ``` 229 | 230 | That command seeme to create slightly screwy avds. I ran `$ android avd` and then hit `edit` and `save` without any changes on mine, which seems to fix it :/ 231 | 232 | #### 3. Start the JS server and emulator 233 | 234 | Both commands block: either background then (add `&` at the end) to run in the same terminal, or open a terminal for each 235 | 236 | ```bash 237 | [nix-shell:]$ npm start 238 | [nix-shell:]$ emulator -avd nexus 239 | ``` 240 | 241 | 242 | #### 4. Run it! 243 | 244 | ```bash 245 | [nix-shell:]$ run-android 246 | ``` 247 | 248 | Note that this `run-android` is provided by [`shell.nix`](./shell.nix), and wraps the call to `react-native-cli`'s `react-native run-android` command in the FHS environment so that the build works. 249 | 250 | 251 | ## Python 252 | 253 | `shell.nix` 254 | 255 | ```nix 256 | with import {}; 257 | with pkgs.python27Packages; 258 | 259 | stdenv.mkDerivation { 260 | name = "python"; 261 | 262 | buildInputs = [ 263 | pip 264 | python27Full 265 | virtualenv 266 | ]; 267 | 268 | shellHook = '' 269 | SOURCE_DATE_EPOCH=$(date +%s) # so that we can use python wheels 270 | YELLOW='\033[1;33m' 271 | NC="$(printf '\033[0m')" 272 | 273 | echo -e "''${YELLOW}Creating python environment...''${NC}" 274 | virtualenv --no-setuptools venv > /dev/null 275 | export PATH=$PWD/venv/bin:$PATH > /dev/null 276 | pip install -r requirements.txt > /dev/null 277 | ''; 278 | } 279 | ``` 280 | 281 | This expression sets up a python 2 environment, and installs any dependencies from `requirements.txt` with `pip` in a `virtualenv`. 282 | 283 | ### OS library dependencies 284 | 285 | There is a `LD_LIBRARY_PATH` attribute for nix packages that can help python dependencies find the libraries they may need to complete installation. 286 | You generally need to format two pieces of information together: the path to the dependency in the nix store, and `/lib`. 287 | 288 | Usually this means you just need `LD_LIBRARY_PATH=${}/lib`. 289 | However, sometimes nix packages divide up their outputs, with the `/lib` folder and its contents at their own path in the nix store. _Usually_, the output with `lib//` is called `out`, and can be used like `LD_LIBRARY_PATH=${.out}/lib`. 290 | 291 | If you need to put more than on dependency into `LD_LIBRARY_PATH`, separate them with a colon `:`, like for `$PATH`. 292 | 293 | #### `geos` and `gdal` 294 | 295 | `shell.nix` 296 | 297 | ```nix 298 | with import {}; 299 | with pkgs.python27Packages; 300 | 301 | stdenv.mkDerivation { 302 | name = "python"; 303 | 304 | buildInputs = [ 305 | gdal 306 | geos 307 | pip 308 | python27Full 309 | virtualenv 310 | ]; 311 | 312 | LD_LIBRARY_PATH="${geos}/lib:${gdal}/lib"; 313 | 314 | shellHook = '' 315 | SOURCE_DATE_EPOCH=$(date +%s) # so that we can use python wheels 316 | YELLOW='\033[1;33m' 317 | NC="$(printf '\033[0m')" 318 | 319 | echo -e "''${YELLOW}Creating python environment...''${NC}" 320 | virtualenv --no-setuptools venv > /dev/null 321 | export PATH=$PWD/venv/bin:$PATH > /dev/null 322 | pip install -r requirements.txt > /dev/null 323 | ''; 324 | } 325 | ``` 326 | 327 | 328 | #### zlib 329 | 330 | TODO 331 | 332 | 333 | #### sqlite3 334 | 335 | TODO 336 | 337 | --- 338 | 339 | also, http://nixos.org/nixpkgs/manual/#using-python 340 | 341 | 342 | ## Rust 343 | 344 | TODO: mozilla nix overlay for nightly 345 | 346 | `shell.nix` 347 | 348 | ```nix 349 | with import {}; 350 | 351 | stdenv.mkDerivation { 352 | name = "rust"; 353 | buildInputs = [ 354 | rustChannels.nightly.cargo 355 | rustChannels.nightly.rust 356 | ]; 357 | } 358 | ``` 359 | 360 | ### OpenSSL 361 | 362 | `shell.nix` 363 | 364 | ```nix 365 | with import {}; 366 | 367 | stdenv.mkDerivation { 368 | name = "rust"; 369 | buildInputs = [ 370 | openssl 371 | rustChannels.nightly.cargo 372 | rustChannels.nightly.rust 373 | ]; 374 | shellHook = '' 375 | export OPENSSL_DIR="${openssl.dev}" 376 | export OPENSSL_LIB_DIR="${openssl.out}/lib" 377 | ''; 378 | } 379 | ``` 380 | 381 | 382 | ### diesel 383 | 384 | `shell.nix` 385 | 386 | ```nix 387 | with import {}; 388 | 389 | stdenv.mkDerivation { 390 | name = "rust"; 391 | buildInputs = [ 392 | postgresql 393 | rustChannels.nightly.cargo 394 | rustChannels.nightly.rust 395 | ]; 396 | shellHook = '' 397 | export OPENSSL_DIR="${openssl.dev}" 398 | export OPENSSL_LIB_DIR="${openssl.out}/lib" 399 | export PATH="$PWD/bin:$PATH" 400 | export DATABASE_URL="postgres://postgres@localhost/db" 401 | if ! type diesel > /dev/null 2> /dev/null; then 402 | cargo install diesel_cli --no-default-features --features postgres --root $PWD 403 | fi 404 | diesel setup 405 | ''; 406 | } 407 | ``` 408 | 409 | TODO: talk about running postgres in a nix-shell, and don't hard-code `DATABASE_URL` in such an ugly way 410 | 411 | `cargo.toml` 412 | 413 | ```toml 414 | [dependencies.diesel] 415 | version = "0.11" 416 | features = ["postgres"] 417 | 418 | [dependencies.diesel_codegen] 419 | version = "0.11" 420 | features = ["postgres"] 421 | ``` 422 | 423 | `.gitignore` 424 | 425 | ``` 426 | bin/ 427 | ``` 428 | --------------------------------------------------------------------------------