├── .envrc ├── .github ├── dependabot.yml ├── settings.yml └── workflows │ └── nix.yml ├── .gitignore ├── .goreleaser.yml ├── .vscode └── tasks.json ├── LICENSE ├── README.md ├── benchmark ├── README.md ├── devshell-nix.nix ├── devshell-toml.nix ├── devshell-toml.toml └── nixpkgs-mkshell.nix ├── default.nix ├── devshell.toml ├── docs ├── .gitignore ├── book.toml ├── default.nix ├── src │ ├── 99_todo.md │ ├── SUMMARY.md │ ├── ci.md │ ├── env.md │ ├── extending.md │ ├── flake-app.md │ ├── getting_started.md │ ├── intro.md │ └── modules_schema.md └── theme │ ├── index.hbs │ ├── pagetoc.css │ └── pagetoc.js ├── extra ├── git │ └── hooks.nix ├── language │ ├── c.nix │ ├── go.nix │ ├── hare.nix │ ├── perl.nix │ ├── ruby.nix │ └── rust.nix ├── locale.nix └── services │ └── postgres.nix ├── flake-module.nix ├── flake.lock ├── flake.lock.nix ├── flake.nix ├── modules ├── back-compat.nix ├── commands.nix ├── default.nix ├── devshell.nix ├── env.nix ├── eval-args.nix ├── modules-docs.nix ├── modules.nix └── services.nix ├── nix ├── ansi.nix ├── importTOML.nix ├── mkNakedShell.nix ├── nixpkgs.nix ├── source.nix ├── strOrPackage.nix └── writeDefaultShellScript.nix ├── overlay.nix ├── renovate.json ├── shell.nix ├── templates ├── flake-parts │ ├── .envrc │ ├── .gitignore │ ├── flake.lock │ └── flake.nix ├── gettingStartedExample │ ├── .envrc │ ├── .gitignore │ ├── devshell.toml │ ├── flake.lock │ ├── flake.nix │ └── readme.md └── toml │ ├── .envrc │ ├── .gitignore │ ├── devshell.toml │ ├── flake.lock │ ├── flake.nix │ └── shell.nix └── tests ├── assert.sh ├── core ├── commands.nix ├── devshell.nix ├── env.nix └── modules-docs.nix ├── default.nix └── extra ├── git.hooks.nix ├── language.c.nix ├── language.hare.nix ├── language.perl.nix ├── language.rust.nix ├── locale.nix └── services.postgres.nix /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ^ added for shellcheck and file-type detection 3 | 4 | # Watch all of these files for change 5 | watch_dir modules 6 | watch_dir nix 7 | watch_file devshell.toml 8 | 9 | # Store the shell symlink in the direnv layout directory 10 | out_link="$(direnv_layout_dir)"/devshell 11 | 12 | # Build the devshell environment 13 | nix-build shell.nix --out-link "$out_link" 14 | 15 | # Load the devshell 16 | # shellcheck disable=SC1091 17 | source "$out_link/env.bash" 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | # See https://developer.github.com/v3/repos/#edit for all available settings. 3 | 4 | # The name of the repository. Changing this will rename the repository 5 | name: devshell 6 | 7 | # A short description of the repository that will show up on GitHub 8 | description: Per project developer environments 9 | 10 | # A URL with more information about the repository 11 | homepage: "https://numtide.github.io/devshell/" 12 | 13 | # A comma-separated list of topics to set on the repository 14 | topics: "toml, nix, direnv" 15 | 16 | # Either `true` to make the repository private, or `false` to make it public. 17 | private: false 18 | 19 | # Either `true` to enable issues for this repository, `false` to disable them. 20 | has_issues: true 21 | 22 | # Either `true` to enable projects for this repository, or `false` to disable them. 23 | # If projects are disabled for the organization, passing `true` will cause an API error. 24 | has_projects: false 25 | 26 | # Either `true` to enable the wiki for this repository, `false` to disable it. 27 | has_wiki: true 28 | 29 | # Either `true` to enable downloads for this repository, `false` to disable them. 30 | has_downloads: false 31 | 32 | # Updates the default branch for this repository. 33 | default_branch: main 34 | 35 | # Either `true` to allow squash-merging pull requests, or `false` to prevent 36 | # squash-merging. 37 | allow_squash_merge: true 38 | 39 | # Either `true` to allow merging pull requests with a merge commit, or `false` 40 | # to prevent merging pull requests with merge commits. 41 | allow_merge_commit: true 42 | 43 | # Either `true` to allow rebase-merging pull requests, or `false` to prevent 44 | # rebase-merging. 45 | allow_rebase_merge: true 46 | 47 | # Either `true` to enable automatic deletion of branches on merge, or `false` to disable 48 | delete_branch_on_merge: true 49 | 50 | # Either `true` to enable automated security fixes, or `false` to disable 51 | # automated security fixes. 52 | enable_automated_security_fixes: true 53 | 54 | # Either `true` to enable vulnerability alerts, or `false` to disable 55 | # vulnerability alerts. 56 | enable_vulnerability_alerts: true 57 | 58 | # Labels: define labels for Issues and Pull Requests 59 | # 60 | labels: 61 | # NOTE: leave that up to the https://github.com/numtide/.github repo 62 | # - name: bug 63 | # color: CC0000 64 | # description: An issue with the system 🐛. 65 | 66 | # - name: feature 67 | # # If including a `#`, make sure to wrap it with quotes! 68 | # color: '#336699' 69 | # description: New functionality. 70 | 71 | # - name: Help Wanted 72 | # # Provide a new name to rename an existing label 73 | # new_name: first-timers-only 74 | 75 | # Milestones: define milestones for Issues and Pull Requests 76 | milestones: 77 | # - title: milestone-title 78 | # description: milestone-description 79 | # # The state of the milestone. Either `open` or `closed` 80 | # state: open 81 | 82 | # Collaborators: give specific users access to this repository. 83 | # See https://docs.github.com/en/rest/reference/repos#add-a-repository-collaborator for available options 84 | collaborators: 85 | # - username: xxx 86 | # Note: `permission` is only valid on organization-owned repositories. 87 | # The permission to grant the collaborator. Can be one of: 88 | # * `pull` - can pull, but not push to or administer this repository. 89 | # * `push` - can pull and push, but not administer this repository. 90 | # * `admin` - can pull, push and administer this repository. 91 | # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions. 92 | # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access. 93 | # permission: push 94 | 95 | # See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options 96 | teams: 97 | - name: network 98 | # The permission to grant the team. Can be one of: 99 | # * `pull` - can pull, but not push to or administer this repository. 100 | # * `push` - can pull and push, but not administer this repository. 101 | # * `admin` - can pull, push and administer this repository. 102 | # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions. 103 | # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access. 104 | permission: maintain 105 | 106 | branches: 107 | - name: main 108 | # https://docs.github.com/en/rest/reference/repos#update-branch-protection 109 | # Branch Protection settings. Set to null to disable 110 | protection: 111 | # Required. Require at least one approving review on a pull request, before merging. Set to null to disable. 112 | required_pull_request_reviews: 113 | # # The number of approvals required. (1-6) 114 | # required_approving_review_count: 1 115 | # # Dismiss approved reviews automatically when a new commit is pushed. 116 | # dismiss_stale_reviews: true 117 | # # Blocks merge until code owners have reviewed. 118 | # require_code_owner_reviews: true 119 | # # Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories. 120 | # dismissal_restrictions: 121 | # users: [] 122 | # teams: [] 123 | # Required. Require status checks to pass before merging. Set to null to disable 124 | required_status_checks: 125 | # Required. Require branches to be up to date before merging. 126 | strict: true 127 | # Required. The list of status checks to require in order to merge into this branch 128 | contexts: [ ] 129 | # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. 130 | enforce_admins: false 131 | # Disabled for bors to work 132 | required_linear_history: false 133 | # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. 134 | restrictions: 135 | apps: [ ] 136 | users: [] 137 | teams: [] 138 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - staging 7 | - trying 8 | pull_request: 9 | workflow_dispatch: 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | os: [ ubuntu-20.04, ubuntu-22.04, macos-11, macos-12 ] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: cachix/install-nix-action@v30 19 | - uses: cachix/cachix-action@v15 20 | with: 21 | name: numtide 22 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 23 | - run: | 24 | export PRJ_ROOT=$PWD 25 | $(./shell.nix)/entrypoint --pure bash -c "echo OK" 26 | - run: nix-shell --run "echo OK" 27 | - run: nix-build 28 | flakes: 29 | strategy: 30 | matrix: 31 | os: [ ubuntu-20.04, ubuntu-22.04, macos-11, macos-12 ] 32 | runs-on: ${{ matrix.os }} 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | # Nix Flakes doesn't work on shallow clones 37 | fetch-depth: 0 38 | - uses: cachix/install-nix-action@v30 39 | - uses: cachix/cachix-action@v15 40 | with: 41 | name: numtide 42 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 43 | - run: nix flake check 44 | - name: Run devshell entry sanity checks 45 | run: | 46 | nix develop -c echo OK 47 | for tmpl in ./templates/*; do 48 | if ! [ -d "$tmpl" ]; then 49 | continue 50 | fi 51 | nix develop --override-input devshell . "$tmpl" -c echo OK 52 | done 53 | - name: Run nix flake archive 54 | run: nix flake archive 55 | docs: 56 | strategy: 57 | matrix: 58 | os: [ ubuntu-20.04 ] 59 | runs-on: ${{ matrix.os }} 60 | steps: 61 | - uses: actions/checkout@v4 62 | - uses: cachix/install-nix-action@v30 63 | - uses: cachix/cachix-action@v15 64 | with: 65 | name: numtide 66 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 67 | - run: | 68 | nix-build -A docs 69 | cp -r "$(readlink ./result)" book 70 | - name: Deploy to GitHub Pages 71 | if: github.ref == 'refs/heads/main' 72 | uses: crazy-max/ghaction-github-pages@v4 73 | with: 74 | target_branch: gh-pages 75 | build_dir: book 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # go 2 | /devshell/devshell 3 | 4 | # mdbook 5 | /result 6 | /book 7 | 8 | # goreleaser 9 | /dist 10 | 11 | # direnv 12 | /.direnv 13 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | - bash -c "cd devshell && go mod download" 6 | builds: 7 | - env: 8 | - CGO_ENABLED=0 9 | mod_timestamp: '{{ .CommitTimestamp }}' 10 | dir: devshell 11 | goos: 12 | - darwin 13 | - freebsd 14 | - linux 15 | goarch: 16 | - '386' 17 | - amd64 18 | - arm 19 | - arm64 20 | ignore: 21 | - goos: darwin 22 | goarch: '386' 23 | signs: 24 | - artifacts: checksum 25 | args: 26 | # if you are using this is a GitHub action or some other automated pipeline, you 27 | # need to pass the batch flag to indicate its not interactive. 28 | - "--batch" 29 | - "--local-user" 30 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 31 | - "--output" 32 | - "${signature}" 33 | - "--detach-sign" 34 | - "${artifact}" 35 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build & Serve docs", 8 | "type": "shell", 9 | "command": "docs/serve.sh", 10 | "problemMatcher": [] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Numtide and contributors 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 | # devshell - like virtualenv, but for all the languages 2 | 3 |

STATUS: unstable

4 | 5 | [![Devshell Dev Environment](https://img.shields.io/badge/nix-devshell-blue?logo=NixOS&labelColor=ccc)](https://github.com/numtide/devshell) [![Support room on Matrix](https://img.shields.io/matrix/devshell:numtide.com.svg?label=%23devshell%3Anumtide.com&logo=matrix&server_fqdn=matrix.numtide.com)](https://matrix.to/#/#devshell:numtide.com) 6 | 7 | The goal of this project is to simplify per-project developer environments. 8 | 9 | Imagine, a new employee joins the company, or somebody transfers teams, or 10 | somebody wants to contribute to one of your Open Source projects. It 11 | should take them 10 minutes to clone the repo and get all of the development 12 | dependencies. 13 | 14 | ## Documentation 15 | 16 | See [docs](https://numtide.github.io/devshell/) ([docs source](docs)) 17 | 18 | ## Features 19 | 20 | ### Compatible 21 | 22 | Keep it compatible with: 23 | 24 | * nix-shell 25 | * direnv 26 | * nix flakes 27 | 28 | ### Clean environment 29 | 30 | `pkgs.stdenv.mkDerivation` and `pkgs.mkShell` build on top of the 31 | `pkgs.stdenv` which introduces all sort of dependencies. Each added package, 32 | like the `pkgs.go` in the "Story time!" section has the potential of adding 33 | new environment variables, which then need to be unset. The `stdenv` itself 34 | contains either GCC or Clang which makes it hard to select a specific C 35 | compiler. 36 | 37 | This is why `mkShell` builds its environment from a `builtins.derivation`. 38 | 39 | direnv loads will change from: 40 | 41 | ```sh 42 | direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_BUILD_CORES +NIX_BUILD_TOP +NIX_CC +NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_INDENT_MAKE +NIX_LDFLAGS +NIX_STORE +NM +OBJCOPY +OBJDUMP +RANLIB +READELF +RUSTC +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +TEMP +TEMPDIR +TMP +TMPDIR +buildInputs +buildPhase +builder +builtDependencies +cargo_bins_jq_filter +cargo_build_options +cargo_options +cargo_release +cargo_test_options +cargoconfig +checkPhase +configureFlags +configurePhase +cratePaths +crate_sources +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +docPhase +dontAddDisableDepTrack +dontUseCmakeConfigure +installPhase +name +nativeBuildInputs +out +outputs +patches +preInstallPhases +propagatedBuildInputs +propagatedNativeBuildInputs +remapPathPrefix +shell +src +stdenv +strictDeps +system +version ~PATH 43 | ``` 44 | 45 | to: 46 | 47 | ```sh 48 | direnv: export +DEVSHELL_DIR +PRJ_DATA_DIR +PRJ_ROOT +IN_NIX_SHELL +NIXPKGS_PATH ~PATH 49 | ``` 50 | 51 | There are new environment variables useful to support the day-to-day 52 | activities: 53 | 54 | * `DEVSHELL_DIR`: contains all the programs. 55 | * `PRJ_ROOT`: points to the project root. 56 | * `PRJ_DATA_DIR`: points to `$PRJ_ROOT/.data` by default. Is used to store runtime data. 57 | * `NIXPKGS_PATH`: path to `nixpkgs` source. 58 | 59 | ### Common utilities 60 | 61 | The shell comes pre-loaded with some utility functions. I'm not 100% sure if 62 | those are useful yet: 63 | 64 | * `menu` - list all the programs available 65 | 66 | ### MOTD 67 | 68 | When entering a random project, it's useful to get a quick view of what 69 | commands are available. 70 | 71 | When running `nix-shell` or `nix develop`, `mkShell` prints a welcome message: 72 | 73 | ```sh 74 | 🔨 Welcome to devshell 75 | 76 | [[general commands]] 77 | 78 | hello - prints hello 79 | menu - prints this menu 80 | 81 | [formatters] 82 | 83 | nixpkgs-fmt - Nix code formatter for nixpkgs 84 | 85 | [linters] 86 | 87 | golangci-lint - golang linter 88 | 89 | [utilites] 90 | 91 | hub - github utility 92 | 93 | [devshell]$ 94 | ``` 95 | 96 | ### Configurable with a TOML file 97 | 98 | You might be passionate about Nix, but people on the team might be afraid of 99 | that non-mainstream technology. So let them write TOML instead. It should 100 | handle 80% of the use-cases and falling back on Nix is always possible. 101 | 102 | ### Bash completion by default 103 | 104 | Life is not complete otherwise. Huhu. 105 | 106 | Packages that contain bash completions will automatically be loaded by 107 | `mkShell` in `nix-shell` or `nix develop` modes. 108 | 109 | ### Capture development dependencies in CI 110 | 111 | With a CI + Binary cache setup, one often wants to be able to capture all the 112 | build inputs of a `shell.nix`. With `mkShell` capturing all of the 113 | development dependencies is as easy as: 114 | 115 | ```sh 116 | nix-build shell.nix | cachix push 117 | ``` 118 | 119 | ### Runnable as a Nix application 120 | 121 | Devshells are runnable (via `nix run`). This makes it possible to run commands defined in your devshell without entering a `nix-shell` or `nix develop` session: 122 | 123 | ```sh 124 | nix run '.#' -- 125 | ``` 126 | 127 | This project itself exposes a Nix application; you can try it out with: 128 | 129 | ```sh 130 | nix run 'github:numtide/devshell' -- hello 131 | ``` 132 | 133 | See [here](docs/src/flake-app.md) for more details. 134 | 135 | ## TODO 136 | 137 | A lot of things! 138 | 139 | * **Documentation** 140 | * Explain how all of this works and all the use-cases. 141 | * **Testing** 142 | * Write integration tests for all of the use-cases. 143 | * **Lazy dependencies** 144 | * This requires some coordination with the repository structure. To keep the 145 | dev closure small, it would be nice to be able to load some of the 146 | dependencies on demand. 147 | * **Doctor / nix version check** 148 | * Not everything can be nicely sandboxed. Is it possible to get a fast doctor 149 | script that checks that everything is in good shape? 150 | * **Support other shells** 151 | * What? Not everyone is using bash? Right now, support is already available in 152 | direnv mode. 153 | 154 | ## Contributing 155 | 156 | ### Docs 157 | 158 | 1. Change files in `docs/` 159 | 1. Run `nix run .#docs` 160 | 1. Open [`docs`](http://localhost:3000/index.html) 161 | 162 | ### Benchmark 163 | 164 | 1. See [benchmark/README.md](./benchmark/README.md) 165 | 1. Run `nix run .#bench` 166 | 167 | ## Commercial support 168 | 169 | Looking for help or customization? 170 | 171 | Get in touch with Numtide to get a quote. We make it easy for companies to 172 | work with Open Source projects: 173 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking devshell evaluation 2 | 3 | devshell is built on top of nix, and the nixpkgs module system, which can take 4 | quite a while to evaluate. 5 | 6 | ## Hyperfine 7 | 8 | Command: 9 | 10 | ```console 11 | nix run .#bench 12 | ``` 13 | 14 | Output: 15 | 16 | ```console 17 | Benchmark 1: nix-instantiate ../shell.nix 18 | Time (mean ± σ): 568.2 ms ± 18.0 ms [User: 486.2 ms, System: 81.1 ms] 19 | Range (min … max): 544.5 ms … 596.0 ms 10 runs 20 | 21 | Benchmark 2: nix-instantiate ./devshell-nix.nix 22 | Time (mean ± σ): 189.6 ms ± 11.8 ms [User: 150.1 ms, System: 38.6 ms] 23 | Range (min … max): 177.8 ms … 221.0 ms 13 runs 24 | 25 | Benchmark 3: nix-instantiate ./devshell-toml.nix 26 | Time (mean ± σ): 194.0 ms ± 7.4 ms [User: 155.1 ms, System: 38.8 ms] 27 | Range (min … max): 181.4 ms … 214.5 ms 15 runs 28 | 29 | Benchmark 4: nix-instantiate ./nixpkgs-mkshell.nix 30 | Time (mean ± σ): 148.9 ms ± 4.7 ms [User: 118.3 ms, System: 30.3 ms] 31 | Range (min … max): 143.7 ms … 164.6 ms 20 runs 32 | 33 | Summary 34 | nix-instantiate ./nixpkgs-mkshell.nix ran 35 | 1.27 ± 0.09 times faster than nix-instantiate ./devshell-nix.nix 36 | 1.30 ± 0.06 times faster than nix-instantiate ./devshell-toml.nix 37 | 3.82 ± 0.17 times faster than nix-instantiate ../shell.nix 38 | ``` 39 | 40 | ## Nix stats 41 | 42 | ### repo shell 43 | 44 | Command: 45 | 46 | ```console 47 | NIX_SHOW_STATS=1 nix-instantiate ../shell.nix 2>&1 48 | ``` 49 | 50 | Output: 51 | 52 | ```console 53 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 54 | /nix/store/6vha60nh201fd5m36nphysmsrvvk0zq0-devshell.drv 55 | { 56 | "cpuTime": 0.42238301038742065, 57 | "envs": { 58 | "bytes": 17234184, 59 | "elements": 879269, 60 | "number": 637502 61 | }, 62 | "gc": { 63 | "heapSize": 402915328, 64 | "totalBytes": 116416656 65 | }, 66 | "list": { 67 | "bytes": 2528832, 68 | "concats": 28933, 69 | "elements": 316104 70 | }, 71 | "nrAvoided": 869773, 72 | "nrFunctionCalls": 574722, 73 | "nrLookups": 387457, 74 | "nrOpUpdateValuesCopied": 2237754, 75 | "nrOpUpdates": 54186, 76 | "nrPrimOpCalls": 405710, 77 | "nrThunks": 958406, 78 | "sets": { 79 | "bytes": 46573200, 80 | "elements": 2771910, 81 | "number": 138915 82 | }, 83 | "sizes": { 84 | "Attr": 16, 85 | "Bindings": 16, 86 | "Env": 16, 87 | "Value": 24 88 | }, 89 | "symbols": { 90 | "bytes": 551230, 91 | "number": 48261 92 | }, 93 | "values": { 94 | "bytes": 28690080, 95 | "number": 1195420 96 | } 97 | } 98 | ``` 99 | 100 | ### devshell-nix 101 | 102 | Command: 103 | 104 | ```console 105 | NIX_SHOW_STATS=1 nix-instantiate ./devshell-nix.nix 2>&1 106 | ``` 107 | 108 | Output: 109 | 110 | ```console 111 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 112 | /nix/store/6zlkfp88d1ic0zyw49kb8srnqbwz5277-devshell.drv 113 | { 114 | "cpuTime": 0.17254799604415894, 115 | "envs": { 116 | "bytes": 3515536, 117 | "elements": 175074, 118 | "number": 132184 119 | }, 120 | "gc": { 121 | "heapSize": 402915328, 122 | "totalBytes": 39903680 123 | }, 124 | "list": { 125 | "bytes": 580176, 126 | "concats": 3499, 127 | "elements": 72522 128 | }, 129 | "nrAvoided": 192068, 130 | "nrFunctionCalls": 116933, 131 | "nrLookups": 56485, 132 | "nrOpUpdateValuesCopied": 1160535, 133 | "nrOpUpdates": 7873, 134 | "nrPrimOpCalls": 99486, 135 | "nrThunks": 274189, 136 | "sets": { 137 | "bytes": 22358832, 138 | "elements": 1364423, 139 | "number": 33004 140 | }, 141 | "sizes": { 142 | "Attr": 16, 143 | "Bindings": 16, 144 | "Env": 16, 145 | "Value": 24 146 | }, 147 | "symbols": { 148 | "bytes": 222375, 149 | "number": 23097 150 | }, 151 | "values": { 152 | "bytes": 8141064, 153 | "number": 339211 154 | } 155 | } 156 | ``` 157 | 158 | ### devshell-toml 159 | 160 | Command: 161 | 162 | ```console 163 | NIX_SHOW_STATS=1 nix-instantiate ./devshell-toml.nix 2>&1 164 | ``` 165 | 166 | Output: 167 | 168 | ```console 169 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 170 | /nix/store/6zlkfp88d1ic0zyw49kb8srnqbwz5277-devshell.drv 171 | { 172 | "cpuTime": 0.14970900118350983, 173 | "envs": { 174 | "bytes": 3515888, 175 | "elements": 175092, 176 | "number": 132197 177 | }, 178 | "gc": { 179 | "heapSize": 402915328, 180 | "totalBytes": 39907952 181 | }, 182 | "list": { 183 | "bytes": 580248, 184 | "concats": 3498, 185 | "elements": 72531 186 | }, 187 | "nrAvoided": 192084, 188 | "nrFunctionCalls": 116941, 189 | "nrLookups": 56497, 190 | "nrOpUpdateValuesCopied": 1160541, 191 | "nrOpUpdates": 7874, 192 | "nrPrimOpCalls": 99494, 193 | "nrThunks": 274209, 194 | "sets": { 195 | "bytes": 22359328, 196 | "elements": 1364444, 197 | "number": 33014 198 | }, 199 | "sizes": { 200 | "Attr": 16, 201 | "Bindings": 16, 202 | "Env": 16, 203 | "Value": 24 204 | }, 205 | "symbols": { 206 | "bytes": 222404, 207 | "number": 23100 208 | }, 209 | "values": { 210 | "bytes": 8141856, 211 | "number": 339244 212 | } 213 | } 214 | ``` 215 | 216 | ### nixpkgs-mkshell 217 | 218 | Command: 219 | 220 | ```console 221 | NIX_SHOW_STATS=1 nix-instantiate ./nixpkgs-mkshell.nix 2>&1 222 | ``` 223 | 224 | Output: 225 | 226 | ```console 227 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 228 | /nix/store/53c78xjnkv3f7c87cwly5hgys1kbdjqv-nix-shell.drv 229 | { 230 | "cpuTime": 0.11669100075960159, 231 | "envs": { 232 | "bytes": 2552672, 233 | "elements": 126138, 234 | "number": 96473 235 | }, 236 | "gc": { 237 | "heapSize": 402915328, 238 | "totalBytes": 34785856te ../shell.nix 2>&1 239 | ``` 240 | }, 241 | "list": { 242 | "bytes": 457816, 243 | "concats": 1927, 244 | "elements": 57227 245 | }, 246 | "nrAvoided": 148098, 247 | "nrFunctionCalls": 85099, 248 | "nrLookups": 35864, 249 | "nrOpUpdateValuesCopied": 1078888, 250 | "nrOpUpdates": 5237, 251 | "nrPrimOpCalls": 79444, 252 | "nrThunks": 230270, 253 | "sets": { 254 | "bytes": 20572560, 255 | "elements": 1261476, 256 | "number": 24309 257 | }, 258 | "sizes": { 259 | "Attr": 16, 260 | "Bindings": 16, 261 | "Env": 16, 262 | "Value": 24 263 | }, 264 | "symbols": { 265 | "bytes": 218655, 266 | "number": 22549 267 | }, 268 | "values": { 269 | "bytes": 6839184, 270 | "number": 284966 271 | } 272 | } 273 | ``` 274 | -------------------------------------------------------------------------------- /benchmark/devshell-nix.nix: -------------------------------------------------------------------------------- 1 | { 2 | system ? builtins.currentSystem, 3 | }: 4 | let 5 | devshell = import ../. { inherit system; }; 6 | in 7 | devshell.mkShell { } 8 | -------------------------------------------------------------------------------- /benchmark/devshell-toml.nix: -------------------------------------------------------------------------------- 1 | { 2 | system ? builtins.currentSystem, 3 | }: 4 | let 5 | devshell = import ../. { inherit system; }; 6 | in 7 | devshell.fromTOML ./devshell-toml.toml 8 | -------------------------------------------------------------------------------- /benchmark/devshell-toml.toml: -------------------------------------------------------------------------------- 1 | # Empty TOML 2 | [devshell] 3 | -------------------------------------------------------------------------------- /benchmark/nixpkgs-mkshell.nix: -------------------------------------------------------------------------------- 1 | { 2 | system ? builtins.currentSystem, 3 | }: 4 | let 5 | pkgs = import (import ../nix/nixpkgs.nix) { inherit system; }; 6 | in 7 | pkgs.mkShell { } 8 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | system ? builtins.currentSystem, 3 | inputs ? import ./flake.lock.nix { }, 4 | nixpkgs ? import inputs.nixpkgs { 5 | inherit system; 6 | # Makes the config pure as well. See /top-level/impure.nix: 7 | config = { }; 8 | overlays = [ ]; 9 | }, 10 | }: 11 | let 12 | # Build a list of all the files, imported as Nix code, from a directory. 13 | importTree = 14 | dir: 15 | let 16 | data = builtins.readDir dir; 17 | op = 18 | sum: name: 19 | let 20 | path = "${dir}/${name}"; 21 | type = data.${name}; 22 | in 23 | sum 24 | ++ ( 25 | if type == "regular" then 26 | [ path ] 27 | # assume it's a directory 28 | else 29 | importTree path 30 | ); 31 | in 32 | builtins.foldl' op [ ] (builtins.attrNames data); 33 | in 34 | rec { 35 | # Folder that contains all the extra modules 36 | extraModulesPath = toString ./extra; 37 | 38 | # Alias for backward compatibility. 39 | extraModulesDir = extraModulesPath; 40 | 41 | # Get the modules documentation from an empty evaluation 42 | modules-docs = 43 | (eval { 44 | configuration = { 45 | # Load all of the extra modules so they appear in the docs 46 | imports = importTree extraModulesPath; 47 | }; 48 | }).config.modules-docs; 49 | 50 | # Docs 51 | docs = nixpkgs.callPackage ./docs { inherit modules-docs; }; 52 | 53 | # Tests 54 | tests = import ./tests { 55 | inherit system; 56 | inputs = null; 57 | pkgs = nixpkgs; 58 | }; 59 | 60 | # Evaluate the devshell module 61 | eval = import ./modules nixpkgs; 62 | 63 | importTOML = import ./nix/importTOML.nix; 64 | 65 | # Build the devshell from a TOML declaration. 66 | fromTOML = path: mkShell (importTOML path); 67 | 68 | # A utility to build a "naked" nix-shell environment that doesn't contain 69 | # all of the default environment variables. This is mostly for internal use. 70 | mkNakedShell = nixpkgs.callPackage ./nix/mkNakedShell.nix { }; 71 | 72 | # A developer shell that works in all scenarios 73 | # 74 | # * nix-build 75 | # * nix-shell 76 | # * flake app 77 | # * direnv integration 78 | # * setup hook for derivation or hercules ci effect 79 | mkShell = configuration: (eval { inherit configuration; }).shell; 80 | } 81 | -------------------------------------------------------------------------------- /devshell.toml: -------------------------------------------------------------------------------- 1 | imports = [ 2 | "language.go" 3 | ] 4 | 5 | [devshell] 6 | # This is the name of your environment. It should usually map to the project 7 | # name. 8 | name = "devshell" 9 | 10 | # Message Of The Day (MOTD) is displayed when entering the environment with an 11 | # interactive shell. By default it will show the project name. 12 | # 13 | # motd = "" 14 | 15 | # Add packages from nixpkgs here. Use `nix search nixpkgs ` to find the 16 | # package that you need. 17 | # 18 | # NOTE: don't forget to put commas between items! :) 19 | packages = [ 20 | "diffutils", # used by golangci-lint 21 | "goreleaser", 22 | "mdbook", 23 | "mdsh", 24 | "webfs", 25 | "hyperfine", 26 | ] 27 | 28 | # Expose all the dependencies from a package to the environment. 29 | packagesFrom = [ 30 | "direnv" 31 | ] 32 | 33 | # Declare commands that are available in the environment. 34 | [[commands]] 35 | help = "prints hello" 36 | name = "hello" 37 | command = "echo hello" 38 | 39 | [[commands]] 40 | package = "nixpkgs-fmt" 41 | category = "formatters" 42 | 43 | [[commands]] 44 | help = "github utility" 45 | name = "hub" 46 | package = "gitAndTools.hub" 47 | category = "utilites" 48 | 49 | [[commands]] 50 | help = "golang linter" 51 | package = "golangci-lint" 52 | category = "linters" 53 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["zimbatm"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "devshell" 7 | 8 | [output.html] 9 | # Show github links in top bar 10 | git-repository-url = "https://github.com/numtide/devshell" 11 | edit-url-template = "https://github.com/numtide/devshell/edit/main/docs/{path}" 12 | additional-css = ["theme/pagetoc.css"] 13 | additional-js = ["theme/pagetoc.js"] -------------------------------------------------------------------------------- /docs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mdbook, 3 | modules-docs, 4 | stdenv, 5 | lib, 6 | }: 7 | with lib; 8 | stdenv.mkDerivation { 9 | name = "devshell-docs"; 10 | buildInputs = [ mdbook ]; 11 | src = 12 | let 13 | fs = lib.fileset; 14 | in 15 | fs.toSource { 16 | root = ./.; 17 | fileset = fs.unions [ 18 | (fs.fileFilter (file: file.hasExt "md") ./src) 19 | ./book.toml 20 | ./theme 21 | ]; 22 | }; 23 | 24 | buildPhase = '' 25 | cp ${modules-docs.markdown} src/modules_schema.md 26 | mdbook build 27 | ''; 28 | 29 | installPhase = '' 30 | mv book $out 31 | ''; 32 | } 33 | -------------------------------------------------------------------------------- /docs/src/99_todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * How to add a new dependency 4 | * Using with CI 5 | * Extending devshell 6 | * Contributing 7 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Intro](./intro.md) 4 | - [Getting started](./getting_started.md) 5 | - [Continuous Integration setup](./ci.md) 6 | - [Extending devshell](./extending.md) 7 | - [devshell.toml schema](./modules_schema.md) 8 | - [env vars](./env.md) 9 | - [TODO](./99_todo.md) 10 | 11 | -------------------------------------------------------------------------------- /docs/src/ci.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration setup (CI) 2 | 3 | Traditionally, the CI build environment has to be kept in sync with the 4 | project. If the project needs `make` to build, the CI has to be configured to 5 | have it available. This can become quite tricky whenever a version requirement 6 | changes. 7 | 8 | With devshell, the only dependency is Nix. Once the devshell is built, all the 9 | dependencies are loaded into scope and automatically are in sync with the 10 | current code checkout. 11 | 12 | ## General approach 13 | 14 | The only dependency we need installed in the CI environment is Nix. 15 | 16 | Assuming that the `shell.nix` file exists, the general approach is to build it 17 | with nix to get back the entrypoint script. And then executed that script with 18 | the commands. 19 | 20 | For example, let's say that `make` is being used to build the project. 21 | 22 | The `devshell.toml` would have it as part of its commands: 23 | ```toml 24 | [[commands]] 25 | package = "gnumake" 26 | ``` 27 | 28 | All the CI has to do, is this: `nix-shell --run "$(nix-build shell.nix)/entrypoint make"`. 29 | 30 | 1. `$(nix-build shell.nix)/entrypoint` outputs a path to the entrypoint script 31 | 1. `nix-shell --run` sets the required environment variables for the entrypoint script to work. 32 | 2. The entrypoint script is executed with `make` as an argument. It loads the 33 | environment. 34 | 3. Finally make is executed in the context of the project environment, with 35 | all the same dependencies as the developer's. 36 | 37 | ## Hercules CI 38 | 39 | [Hercules CI](https://hercules-ci.com) is a Nix-based continuous integration and deployment service. 40 | 41 | ### Build 42 | 43 | If you haven't packaged your project with Nix or if a check can't run in the Nix sandbox, you can run it as an [effect](https://docs.hercules-ci.com/hercules-ci/effects/). 44 | 45 | `ci.nix` 46 | ``` 47 | let 48 | shell = import ./shell.nix {}; 49 | pkgs = shell.pkgs; 50 | effectsSrc = 51 | builtins.fetchTarball "https://github.com/hercules-ci/hercules-ci-effects/archive/COMMIT_HASH.tar.gz"; 52 | inherit (import effectsSrc { inherit pkgs; }) effects; 53 | in 54 | { 55 | inherit shell; 56 | build = effects.mkEffect { 57 | src = ./.; 58 | effectScript = '' 59 | go build 60 | ''; 61 | inputs = [ 62 | shell.hook 63 | ]; 64 | }; 65 | } 66 | ``` 67 | 68 | Replace COMMIT_HASH by the latest git sha from [`hercules-ci-effects`](https://github.com/hercules-ci/hercules-ci-effects/commit/master), 69 | or, if you prefer, you can bring `effects` into scope [using another pinning method](https://docs.hercules-ci.com/hercules-ci-effects/guide/import-or-pin/). 70 | 71 | ### Run locally 72 | 73 | The [`hci` command](https://docs.hercules-ci.com/hercules-ci-agent/hci/) is available in `nixos-21.05` and `nixos-unstable`. 74 | 75 | `devshell.toml` 76 | ``` 77 | [[commands]] 78 | package = "hci" 79 | ``` 80 | 81 | Use [`hci effect run`](https://docs.hercules-ci.com/hercules-ci-agent/hci/). Following the previous example: 82 | 83 | ```console 84 | hci effect run build --no-token 85 | ``` 86 | 87 | ### Shell only 88 | 89 | To build the shell itself on `x86_64-linux`: 90 | 91 | `ci.nix` 92 | ``` 93 | { 94 | shell = import ./shell.nix {}; 95 | 96 | # ... any extra Nix packages you want to build; perhaps 97 | # pkgs = import ./default.nix {} // { recurseForDerivations = true; }; 98 | } 99 | ``` 100 | 101 | ### `system` 102 | 103 | If you build for [multiple systems](https://docs.hercules-ci.com/hercules-ci/guides/multi-platform/), pass `system`: 104 | 105 | ``` 106 | import ./shell.nix { inherit system; }; 107 | ``` 108 | 109 | ## GitHub Actions 110 | 111 | Add the following file to your project. Replace the `` 112 | part with whatever is needed to build the project. 113 | 114 | `.github/workflows/devshell.yml` 115 | ```yaml 116 | name: devshell 117 | on: 118 | push: 119 | branches: 120 | - master 121 | pull_request: 122 | workflow_dispatch: 123 | jobs: 124 | build: 125 | strategy: 126 | matrix: 127 | os: [ ubuntu-20.04, macos-latest ] 128 | runs-on: ${{ matrix.os }} 129 | steps: 130 | - uses: actions/checkout@v2 131 | - uses: cachix/install-nix-action@v12 132 | - uses: cachix/cachix-action@v8 133 | with: 134 | name: "" 135 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 136 | - run: | 137 | source "$(nix-build shell.nix)" 138 | 139 | ``` 140 | 141 | ## TODO 142 | 143 | Add more CI-specific examples. 144 | 145 | -------------------------------------------------------------------------------- /docs/src/env.md: -------------------------------------------------------------------------------- 1 | # Env vars 2 | 3 | This section describes a few environment variables that can influence the 4 | behaviour of devshell. 5 | 6 | ## `DEVSHELL_NO_MOTD=1` 7 | 8 | When that variable is set, devshell will not display the menu that is executed 9 | on entrypoint. 10 | -------------------------------------------------------------------------------- /docs/src/extending.md: -------------------------------------------------------------------------------- 1 | # Extending devshell 2 | 3 | When the base modules that are provided by devshell are not enough, it is 4 | possible to extend it. 5 | 6 | ## Extra modules 7 | 8 | All the `devshell.toml` schema options that are `Declared in:` the `extra/` 9 | folder in the schema documentation are loaded on demand. 10 | 11 | In order to load an extra module, use the `` in the import section. For 12 | example to make the `locale` options available, import `locale`: 13 | 14 | `devshell.toml`: 15 | ```toml 16 | imports = ["locale"] 17 | ``` 18 | 19 | Make sure to add this at the first statement in the file. 20 | 21 | Now that the module has been loaded, the `devshell.toml` understands the 22 | `locale` prefix: 23 | 24 | ```toml 25 | imports = ["locale"] 26 | 27 | [locale] 28 | lang = "en_US.UTF-8" 29 | ``` 30 | 31 | From a nix flake you would import it like 32 | 33 | ```nix 34 | devshell.mkShell ({ extraModulesPath, ... }: { 35 | imports = ["${extraModulesPath}/locale.nix"]; 36 | }) 37 | ``` 38 | 39 | ## Building your own modules 40 | 41 | Building your own modules requires to understand the Nix language. If 42 | this is too complicated, please reach out to the issue tracker and describe 43 | your use-case. We want to be able to support a wide variety of development 44 | scenario. 45 | 46 | In the same way as previously introduced, devshell will also load files that 47 | are relative to the `devshell.toml`. For example: 48 | 49 | ```toml 50 | imports = ["mymodule.nix"] 51 | ``` 52 | 53 | Will load the `mymodule.nix` file in the project repository and extend the 54 | `devshell.toml` schema accordingly. 55 | -------------------------------------------------------------------------------- /docs/src/flake-app.md: -------------------------------------------------------------------------------- 1 | # Using a devshell as a Nix package 2 | 3 | Devshells can be treated as executable packages. This allows running commands inside a devshell's environment without having to enter it first via `nix-shell` or `nix develop`. 4 | 5 | Each devshell in a flake can be executed using nix run: 6 | ```sh 7 | nix run '.#devShells..' -- 8 | ``` 9 | 10 | To simplify this command further, re-expose the devshell under `packages..`. This allows running it like this: 11 | 12 | ```sh 13 | nix run '.#' -- 14 | ``` 15 | 16 | For example, given the following `flake.nix`: 17 | 18 | ```nix 19 | { 20 | inputs.devshell.url = "github:numtide/devshell"; 21 | inputs.flake-utils.url = "github:numtide/flake-utils"; 22 | 23 | outputs = { self, flake-utils, devshell, nixpkgs }: 24 | flake-utils.lib.eachDefaultSystem (system: { 25 | packages.devshell = self.outputs.devShells.${system}.default; 26 | 27 | devShells.default = 28 | let 29 | pkgs = import nixpkgs { 30 | inherit system; 31 | 32 | overlays = [ devshell.overlays.default ]; 33 | }; 34 | in 35 | pkgs.devshell.mkShell ({ config, ... }: { 36 | commands = [ 37 | { 38 | name = "greet"; 39 | command = '' 40 | printf -- 'Hello, %s!\n' "''${1:-world}" 41 | ''; 42 | } 43 | ]; 44 | }); 45 | }); 46 | } 47 | ``` 48 | 49 | You can execute your devshell's `greet` command like this: 50 | 51 | ```console 52 | $ nix run '.#devshell' -- greet myself 53 | Hello, myself! 54 | ``` 55 | 56 | ## Setting `PRJ_ROOT` 57 | 58 | By default, the `PRJ_ROOT` environment variable is set to the value of the 59 | `PWD` environment variable. You can override this by defining `PRJ_ROOT` in 60 | `nix run`'s environment: 61 | 62 | ```sh 63 | PRJ_ROOT=/some/where/else nix run '.#' -- 64 | ``` 65 | 66 | You can also use the `--prj-root` option: 67 | 68 | ```sh 69 | nix run '.#' -- --prj-root /yet/another/path -- 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/src/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | This project has a single dependency; Nix. It will be used to pull in all 4 | other dependencies. It can be installed by following the instructions 5 | over there: https://nixos.org/download.html#nix-quick-install 6 | 7 | Now that's done, got to your project root and create an empty `devshell.toml`. 8 | 9 | There are different ways to load that config depending on your preferences: 10 | 11 | ### Nix (non-flake) 12 | 13 | Add another file called `shell.nix` with the following content. This file will 14 | contain some nix code. Don't worry about the details. 15 | 16 | ```nix 17 | { system ? builtins.currentSystem }: 18 | let 19 | src = fetchTarball "https://github.com/numtide/devshell/archive/main.tar.gz"; 20 | devshell = import src { inherit system; }; 21 | in 22 | devshell.fromTOML ./devshell.toml 23 | ``` 24 | 25 | > NOTE: it's probably a good idea to pin the dependency by replacing `main` with a git commit ID. 26 | 27 | Now you can enter the developer shell for the project: 28 | 29 | ```console 30 | $ nix-shell 31 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 32 | these 4 derivations will be built: 33 | /nix/store/nvbfq9h68r63k5jkfnbimny3b35sx0fs-devshell-bashrc.drv 34 | /nix/store/ppfyf9zv023an8477hcbjlj0rbyvmwq7-devshell.env.drv 35 | /nix/store/8027cgy3xcinb59aaynh899q953dnzms-devshell-bin.drv 36 | /nix/store/w33zl180ni880p18sls5ykih88zkmkqk-devshell.drv 37 | building '/nix/store/nvbfq9h68r63k5jkfnbimny3b35sx0fs-devshell-bashrc.drv'... 38 | building '/nix/store/ppfyf9zv023an8477hcbjlj0rbyvmwq7-devshell-env.drv'... 39 | created 1 symlinks in user environment 40 | building '/nix/store/8027cgy3xcinb59aaynh899q953dnzms-devshell-bin.drv'... 41 | building '/nix/store/w33zl180ni880p18sls5ykih88zkmkqk-devshell.drv'... 42 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 43 | 🔨 Welcome to devshell 44 | 45 | [general commands] 46 | 47 | menu - prints this menu 48 | 49 | [devshell]$ 50 | ``` 51 | 52 | ### Nix Flakes 53 | For users of nix flakes, a default template is provided to get you up and 54 | running. 55 | 56 | ```sh 57 | nix flake new -t "github:numtide/devshell" project/ 58 | 59 | cd project/ 60 | 61 | # enter the shell 62 | nix develop # or `direnv allow` if you want to use direnv 63 | ``` 64 | 65 | Find `templates/gettingStartedExample` in this repository for a working example of the additional configuration below: `env`, `packages`, and `serviceGroups`. 66 | 67 | ## Adding environment variables 68 | 69 | Environment variables that are specific to the project can be added with the 70 | `[[env]]` declaration. Each environment variable is an entry in an array, and 71 | will be set in the order that they are declared. 72 | 73 | Eg: 74 | 75 | ```toml 76 | [[env]] 77 | name = "GO111MODULE" 78 | value = "on" 79 | ``` 80 | 81 | There are different ways to set the environment variables. Look at the schema 82 | to find all the ways. But in short: 83 | * Use the `value` key to set a fixed env var. 84 | * Use the `eval` key to evaluate the value. This is useful when one env var 85 | depends on the value of another. 86 | * Use the `prefix` key to prepend a path to an environment variable that uses 87 | the path separator. Like `PATH`. 88 | 89 | ## Adding new commands 90 | 91 | Devshell also supports adding new commands to the environment. Those are 92 | displayed on devshell entry so that the user knows what commands are available 93 | to them. 94 | 95 | In order to bring in new dependencies, you can either add them to 96 | `devshell.packages` or as an entry in the `[[commands]]` list (see [TOML docs](https://toml.io/en/v1.0.0#array-of-tables)). Commands are also added to the 97 | menu so they might be preferable for discoverability. 98 | 99 | As an exercise, add the following snippet to your `devshell.toml`: 100 | 101 | ```toml 102 | [[commands]] 103 | package = "go" 104 | ``` 105 | 106 | Then re-enter the shell with `nix-shell`/`nix develop`/`direnv reload`. You should see something like this: 107 | 108 | ```console 109 | $ nix-shell 110 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 111 | these 4 derivations will be built: 112 | /nix/store/nvbfq9h68r63k5jkfnbimny3b35sx0fs-devshell-bashrc.drv 113 | /nix/store/ppfyf9zv023an8477hcbjlj0rbyvmwq7-devshell.env.drv 114 | /nix/store/8027cgy3xcinb59aaynh899q953dnzms-devshell-bin.drv 115 | /nix/store/w33zl180ni880p18sls5ykih88zkmkqk-devshell.drv 116 | building '/nix/store/nvbfq9h68r63k5jkfnbimny3b35sx0fs-devshell-bashrc.drv'... 117 | building '/nix/store/ppfyf9zv023an8477hcbjlj0rbyvmwq7-devshell-env.drv'... 118 | created 1 symlinks in user environment 119 | building '/nix/store/8027cgy3xcinb59aaynh899q953dnzms-devshell-bin.drv'... 120 | building '/nix/store/w33zl180ni880p18sls5ykih88zkmkqk-devshell.drv'... 121 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 122 | 🔨 Welcome to devshell 123 | 124 | [general commands] 125 | 126 | menu - prints this menu 127 | go - The Go Programming language 128 | 129 | [devshell]$ 130 | ``` 131 | 132 | Now the `go` program is available in the environment and can be used to 133 | develop Go programs. This can easily be adapted to any language. 134 | 135 | Similarly, you could also add go to the packages list, in which case it would 136 | not appear in the menu: 137 | 138 | ```toml 139 | [devshell] 140 | packages = [ 141 | "go" 142 | ] 143 | ``` 144 | 145 | ### Finding packages 146 | 147 | Check out the [Nix package repository](https://search.nixos.org/packages). 148 | 149 | Note that it is also possible to **use specific versions** for some packages - e.g. for NodeJS, [search the repo](https://search.nixos.org/packages?type=packages&query=nodejs) & use like this: 150 | ```toml 151 | [[commands]] 152 | package = "nodejs-17_x" # https://search.nixos.org/packages?type=packages&query=nodejs 153 | name = "node" 154 | help = "NodeJS" 155 | ``` 156 | 157 | Or another example: 158 | ```toml 159 | [devshell] 160 | packages = [ 161 | "python27", # 2.7 162 | "python311", # 3.11 163 | ] 164 | ``` 165 | 166 | 167 | ## Adding background services 168 | 169 | Many projects need background services to be running during development or to 170 | run tests (e.g. a database, caching server, webserver, ...). This is supported 171 | in devshell through the concept of service groups. 172 | 173 | A service group defines a collection of long-running processes that can be 174 | started and stopped. 175 | 176 | An example service group could be configured like this: 177 | ```toml 178 | [serviceGroups.database] 179 | description = "Runs a database in the backgroup" 180 | [serviceGroups.database.services.postgres] 181 | command = "postgres" 182 | [serviceGroups.database.services.memcached] 183 | command = "memcached" 184 | ``` 185 | 186 | This will add two commands to the devshell: `database:start` and 187 | `database:stop`. `database:start` starts the defined services in the `database` 188 | group in the foreground and shows their output. `database:stop` can be executed 189 | in a different shell to stop the processes (or press Ctrl-C in the main shell). 190 | 191 | ## Wrapping up 192 | 193 | **devshell** is extensible in many different ways. In the next chapters we will 194 | discuss the various ways in which it can be adapted to your project's needs. 195 | -------------------------------------------------------------------------------- /docs/src/intro.md: -------------------------------------------------------------------------------- 1 | # Introducing Devshell: like virtualenv, for every language 2 | 3 | **STATUS: unstable** 4 | 5 | It should not take more than 10 minutes from the time you clone a repo and can 6 | start contributing. 7 | 8 | Unfortunately, an unbounded amount of time is usually spent installing build 9 | dependencies on the system. If you are lucky, it's a pure $LANG project and 10 | all it takes is to install that language and its dedicated package manager. On 11 | bigger projects it's quite common to need more than one language to be 12 | installed. The side-effect of that is that it creates silos withing companies, 13 | and less contributors in the open-source world. 14 | 15 | It should be possible to run a single command that loads and makes those 16 | dependencies available to the developer. 17 | 18 | And it should keep the scope of these dependencies at the project level, just 19 | like virtualenv. 20 | 21 | These are the goals of this project. 22 | -------------------------------------------------------------------------------- /docs/src/modules_schema.md: -------------------------------------------------------------------------------- 1 | ## Options 2 | 3 | ### `commands` 4 | 5 | Add commands to the environment. 6 | 7 | **Type**: 8 | 9 | ```console 10 | list of (submodule) 11 | ``` 12 | 13 | **Default value**: 14 | 15 | ```nix 16 | [ ] 17 | ``` 18 | 19 | **Example value**: 20 | 21 | ```nix 22 | [ 23 | { 24 | help = "print hello"; 25 | name = "hello"; 26 | command = "echo hello"; 27 | } 28 | 29 | { 30 | package = "nixpkgs-fmt"; 31 | category = "formatter"; 32 | } 33 | ] 34 | ``` 35 | 36 | **Declared in**: 37 | 38 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 39 | 40 | ### `commands.*.package` 41 | 42 | Used to bring in a specific package. This package will be added to the 43 | environment. 44 | 45 | **Type**: 46 | 47 | ```console 48 | null or (package or string convertible to it) 49 | ``` 50 | 51 | **Default value**: 52 | 53 | ```nix 54 | null 55 | ``` 56 | 57 | **Declared in**: 58 | 59 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 60 | 61 | ### `commands.*.category` 62 | 63 | Set a free text category under which this command is grouped 64 | and shown in the help menu. 65 | 66 | **Type**: 67 | 68 | ```console 69 | string 70 | ``` 71 | 72 | **Default value**: 73 | 74 | ```nix 75 | "[general commands]" 76 | ``` 77 | 78 | **Declared in**: 79 | 80 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 81 | 82 | ### `commands.*.command` 83 | 84 | If defined, it will add a script with the name of the command, and the 85 | content of this value. 86 | 87 | By default it generates a bash script, unless a different shebang is 88 | provided. 89 | 90 | **Type**: 91 | 92 | ```console 93 | null or string 94 | ``` 95 | 96 | **Default value**: 97 | 98 | ```nix 99 | null 100 | ``` 101 | 102 | **Example value**: 103 | 104 | ```nix 105 | '' 106 | #!/usr/bin/env python 107 | print("Hello") 108 | '' 109 | ``` 110 | 111 | **Declared in**: 112 | 113 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 114 | 115 | ### `commands.*.help` 116 | 117 | Describes what the command does in one line of text. 118 | 119 | **Type**: 120 | 121 | ```console 122 | null or string 123 | ``` 124 | 125 | **Default value**: 126 | 127 | ```nix 128 | null 129 | ``` 130 | 131 | **Declared in**: 132 | 133 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 134 | 135 | ### `commands.*.name` 136 | 137 | Name of this command. Defaults to attribute name in commands. 138 | 139 | **Type**: 140 | 141 | ```console 142 | null or string 143 | ``` 144 | 145 | **Default value**: 146 | 147 | ```nix 148 | null 149 | ``` 150 | 151 | **Declared in**: 152 | 153 | - [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix) 154 | 155 | ### `devshell.packages` 156 | 157 | The set of packages to appear in the project environment. 158 | 159 | Those packages come from and can be 160 | searched by going to 161 | 162 | **Type**: 163 | 164 | ```console 165 | list of (package or string convertible to it) 166 | ``` 167 | 168 | **Default value**: 169 | 170 | ```nix 171 | [ ] 172 | ``` 173 | 174 | **Declared in**: 175 | 176 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 177 | 178 | ### `devshell.packagesFrom` 179 | 180 | Add all the build dependencies from the listed packages to the 181 | environment. 182 | 183 | **Type**: 184 | 185 | ```console 186 | list of (package or string convertible to it) 187 | ``` 188 | 189 | **Default value**: 190 | 191 | ```nix 192 | [ ] 193 | ``` 194 | 195 | **Declared in**: 196 | 197 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 198 | 199 | ### `devshell.interactive..deps` 200 | 201 | A list of other steps that this one depends on. 202 | 203 | **Type**: 204 | 205 | ```console 206 | list of string 207 | ``` 208 | 209 | **Default value**: 210 | 211 | ```nix 212 | [ ] 213 | ``` 214 | 215 | **Declared in**: 216 | 217 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 218 | 219 | ### `devshell.interactive..text` 220 | 221 | Script to run. 222 | 223 | **Type**: 224 | 225 | ```console 226 | string 227 | ``` 228 | 229 | **Declared in**: 230 | 231 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 232 | 233 | ### `devshell.load_profiles` 234 | 235 | Whether to enable load etc/profiles.d/*.sh in the shell. 236 | **Type**: 237 | 238 | ```console 239 | boolean 240 | ``` 241 | 242 | **Default value**: 243 | 244 | ```nix 245 | false 246 | ``` 247 | 248 | **Example value**: 249 | 250 | ```nix 251 | true 252 | ``` 253 | 254 | **Declared in**: 255 | 256 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 257 | 258 | ### `devshell.meta` 259 | 260 | Metadata, such as 'meta.description'. Can be useful as metadata for downstream tooling. 261 | 262 | **Type**: 263 | 264 | ```console 265 | attribute set of anything 266 | ``` 267 | 268 | **Default value**: 269 | 270 | ```nix 271 | { } 272 | ``` 273 | 274 | **Declared in**: 275 | 276 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 277 | 278 | ### `devshell.motd` 279 | 280 | Message Of The Day. 281 | 282 | This is the welcome message that is being printed when the user opens 283 | the shell. 284 | 285 | You may use any valid ansi color from the 8-bit ansi color table. For example, to use a green color you would use something like {106}. You may also use {bold}, {italic}, {underline}. Use {reset} to turn off all attributes. 286 | 287 | **Type**: 288 | 289 | ```console 290 | string 291 | ``` 292 | 293 | **Default value**: 294 | 295 | ```nix 296 | '' 297 | {202}🔨 Welcome to devshell{reset} 298 | $(type -p menu &>/dev/null && menu) 299 | '' 300 | ``` 301 | 302 | **Declared in**: 303 | 304 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 305 | 306 | ### `devshell.name` 307 | 308 | Name of the shell environment. It usually maps to the project name. 309 | 310 | **Type**: 311 | 312 | ```console 313 | string 314 | ``` 315 | 316 | **Default value**: 317 | 318 | ```nix 319 | "devshell" 320 | ``` 321 | 322 | **Declared in**: 323 | 324 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 325 | 326 | ### `devshell.prj_root_fallback` 327 | 328 | If IN_NIX_SHELL is nonempty, or DIRENV_IN_ENVRC is set to '1', then 329 | PRJ_ROOT is set to the value of PWD. 330 | 331 | This option specifies the path to use as the value of PRJ_ROOT in case 332 | IN_NIX_SHELL is empty or unset and DIRENV_IN_ENVRC is any value other 333 | than '1'. 334 | 335 | Set this to null to force PRJ_ROOT to be defined at runtime (except if 336 | IN_NIX_SHELL or DIRENV_IN_ENVRC are defined as described above). 337 | 338 | Otherwise, you can set this to a string representing the desired 339 | default path, or to a submodule of the same type valid in the 'env' 340 | options list (except that the 'name' field is ignored). 341 | 342 | **Type**: 343 | 344 | ```console 345 | null or ((submodule) or non-empty string convertible to it) 346 | ``` 347 | 348 | **Default value**: 349 | 350 | ```nix 351 | { 352 | eval = "$PWD"; 353 | } 354 | ``` 355 | 356 | **Example value**: 357 | 358 | ```nix 359 | { 360 | # Use the top-level directory of the working tree 361 | eval = "$(git rev-parse --show-toplevel)"; 362 | }; 363 | ``` 364 | 365 | **Declared in**: 366 | 367 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 368 | 369 | ### `devshell.prj_root_fallback.eval` 370 | 371 | Like value but not evaluated by Bash. This allows to inject other 372 | variable names or even commands using the `$()` notation. 373 | 374 | **Type**: 375 | 376 | ```console 377 | null or string 378 | ``` 379 | 380 | **Default value**: 381 | 382 | ```nix 383 | null 384 | ``` 385 | 386 | **Example value**: 387 | 388 | ```nix 389 | "$OTHER_VAR" 390 | ``` 391 | 392 | **Declared in**: 393 | 394 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 395 | 396 | ### `devshell.prj_root_fallback.name` 397 | 398 | Name of the environment variable 399 | **Type**: 400 | 401 | ```console 402 | string 403 | ``` 404 | 405 | **Declared in**: 406 | 407 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 408 | 409 | ### `devshell.prj_root_fallback.prefix` 410 | 411 | Prepend to PATH-like environment variables. 412 | 413 | For example name = "PATH"; prefix = "bin"; will expand the path of 414 | ./bin and prepend it to the PATH, separated by ':'. 415 | 416 | **Type**: 417 | 418 | ```console 419 | null or string 420 | ``` 421 | 422 | **Default value**: 423 | 424 | ```nix 425 | null 426 | ``` 427 | 428 | **Example value**: 429 | 430 | ```nix 431 | "bin" 432 | ``` 433 | 434 | **Declared in**: 435 | 436 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 437 | 438 | ### `devshell.prj_root_fallback.unset` 439 | 440 | Whether to enable unsets the variable. 441 | **Type**: 442 | 443 | ```console 444 | boolean 445 | ``` 446 | 447 | **Default value**: 448 | 449 | ```nix 450 | false 451 | ``` 452 | 453 | **Example value**: 454 | 455 | ```nix 456 | true 457 | ``` 458 | 459 | **Declared in**: 460 | 461 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 462 | 463 | ### `devshell.prj_root_fallback.value` 464 | 465 | Shell-escaped value to set 466 | **Type**: 467 | 468 | ```console 469 | null or string or signed integer or boolean or package 470 | ``` 471 | 472 | **Default value**: 473 | 474 | ```nix 475 | null 476 | ``` 477 | 478 | **Declared in**: 479 | 480 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 481 | 482 | ### `devshell.startup..deps` 483 | 484 | A list of other steps that this one depends on. 485 | 486 | **Type**: 487 | 488 | ```console 489 | list of string 490 | ``` 491 | 492 | **Default value**: 493 | 494 | ```nix 495 | [ ] 496 | ``` 497 | 498 | **Declared in**: 499 | 500 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 501 | 502 | ### `devshell.startup..text` 503 | 504 | Script to run. 505 | 506 | **Type**: 507 | 508 | ```console 509 | string 510 | ``` 511 | 512 | **Declared in**: 513 | 514 | - [modules/devshell.nix](https://github.com/numtide/devshell/tree/main/modules/devshell.nix) 515 | 516 | ### `env` 517 | 518 | Add environment variables to the shell. 519 | 520 | **Type**: 521 | 522 | ```console 523 | list of (submodule) 524 | ``` 525 | 526 | **Default value**: 527 | 528 | ```nix 529 | [ ] 530 | ``` 531 | 532 | **Example value**: 533 | 534 | ```nix 535 | [ 536 | { 537 | name = "HTTP_PORT"; 538 | value = 8080; 539 | } 540 | { 541 | name = "PATH"; 542 | prefix = "bin"; 543 | } 544 | { 545 | name = "XDG_CACHE_DIR"; 546 | eval = "$PRJ_ROOT/.cache"; 547 | } 548 | { 549 | name = "CARGO_HOME"; 550 | unset = true; 551 | } 552 | ] 553 | ``` 554 | 555 | **Declared in**: 556 | 557 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 558 | 559 | ### `env.*.eval` 560 | 561 | Like value but not evaluated by Bash. This allows to inject other 562 | variable names or even commands using the `$()` notation. 563 | 564 | **Type**: 565 | 566 | ```console 567 | null or string 568 | ``` 569 | 570 | **Default value**: 571 | 572 | ```nix 573 | null 574 | ``` 575 | 576 | **Example value**: 577 | 578 | ```nix 579 | "$OTHER_VAR" 580 | ``` 581 | 582 | **Declared in**: 583 | 584 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 585 | 586 | ### `env.*.name` 587 | 588 | Name of the environment variable 589 | **Type**: 590 | 591 | ```console 592 | string 593 | ``` 594 | 595 | **Declared in**: 596 | 597 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 598 | 599 | ### `env.*.prefix` 600 | 601 | Prepend to PATH-like environment variables. 602 | 603 | For example name = "PATH"; prefix = "bin"; will expand the path of 604 | ./bin and prepend it to the PATH, separated by ':'. 605 | 606 | **Type**: 607 | 608 | ```console 609 | null or string 610 | ``` 611 | 612 | **Default value**: 613 | 614 | ```nix 615 | null 616 | ``` 617 | 618 | **Example value**: 619 | 620 | ```nix 621 | "bin" 622 | ``` 623 | 624 | **Declared in**: 625 | 626 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 627 | 628 | ### `env.*.unset` 629 | 630 | Whether to enable unsets the variable. 631 | **Type**: 632 | 633 | ```console 634 | boolean 635 | ``` 636 | 637 | **Default value**: 638 | 639 | ```nix 640 | false 641 | ``` 642 | 643 | **Example value**: 644 | 645 | ```nix 646 | true 647 | ``` 648 | 649 | **Declared in**: 650 | 651 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 652 | 653 | ### `env.*.value` 654 | 655 | Shell-escaped value to set 656 | **Type**: 657 | 658 | ```console 659 | null or string or signed integer or boolean or package 660 | ``` 661 | 662 | **Default value**: 663 | 664 | ```nix 665 | null 666 | ``` 667 | 668 | **Declared in**: 669 | 670 | - [modules/env.nix](https://github.com/numtide/devshell/tree/main/modules/env.nix) 671 | 672 | ### `extra.locale.package` 673 | 674 | Set the glibc locale package that will be used on Linux 675 | **Type**: 676 | 677 | ```console 678 | package 679 | ``` 680 | 681 | **Default value**: 682 | 683 | ```nix 684 | "pkgs.glibcLocales" 685 | ``` 686 | 687 | **Declared in**: 688 | 689 | - [extra/locale.nix](https://github.com/numtide/devshell/tree/main/extra/locale.nix) 690 | 691 | ### `extra.locale.lang` 692 | 693 | Set the language of the project 694 | **Type**: 695 | 696 | ```console 697 | null or string 698 | ``` 699 | 700 | **Default value**: 701 | 702 | ```nix 703 | null 704 | ``` 705 | 706 | **Example value**: 707 | 708 | ```nix 709 | "en_GB.UTF-8" 710 | ``` 711 | 712 | **Declared in**: 713 | 714 | - [extra/locale.nix](https://github.com/numtide/devshell/tree/main/extra/locale.nix) 715 | 716 | ### `git.hooks.enable` 717 | 718 | Whether to enable install .git/hooks on shell entry. 719 | **Type**: 720 | 721 | ```console 722 | boolean 723 | ``` 724 | 725 | **Default value**: 726 | 727 | ```nix 728 | false 729 | ``` 730 | 731 | **Example value**: 732 | 733 | ```nix 734 | true 735 | ``` 736 | 737 | **Declared in**: 738 | 739 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 740 | 741 | ### `git.hooks.applypatch-msg.text` 742 | 743 | Text of the script to install 744 | **Type**: 745 | 746 | ```console 747 | string 748 | ``` 749 | 750 | **Default value**: 751 | 752 | ```nix 753 | "" 754 | ``` 755 | 756 | **Declared in**: 757 | 758 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 759 | 760 | ### `git.hooks.commit-msg.text` 761 | 762 | Text of the script to install 763 | **Type**: 764 | 765 | ```console 766 | string 767 | ``` 768 | 769 | **Default value**: 770 | 771 | ```nix 772 | "" 773 | ``` 774 | 775 | **Declared in**: 776 | 777 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 778 | 779 | ### `git.hooks.fsmonitor-watchman.text` 780 | 781 | Text of the script to install 782 | **Type**: 783 | 784 | ```console 785 | string 786 | ``` 787 | 788 | **Default value**: 789 | 790 | ```nix 791 | "" 792 | ``` 793 | 794 | **Declared in**: 795 | 796 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 797 | 798 | ### `git.hooks.post-update.text` 799 | 800 | Text of the script to install 801 | **Type**: 802 | 803 | ```console 804 | string 805 | ``` 806 | 807 | **Default value**: 808 | 809 | ```nix 810 | "" 811 | ``` 812 | 813 | **Declared in**: 814 | 815 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 816 | 817 | ### `git.hooks.pre-applypatch.text` 818 | 819 | Text of the script to install 820 | **Type**: 821 | 822 | ```console 823 | string 824 | ``` 825 | 826 | **Default value**: 827 | 828 | ```nix 829 | "" 830 | ``` 831 | 832 | **Declared in**: 833 | 834 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 835 | 836 | ### `git.hooks.pre-commit.text` 837 | 838 | Text of the script to install 839 | **Type**: 840 | 841 | ```console 842 | string 843 | ``` 844 | 845 | **Default value**: 846 | 847 | ```nix 848 | "" 849 | ``` 850 | 851 | **Declared in**: 852 | 853 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 854 | 855 | ### `git.hooks.pre-merge-commit.text` 856 | 857 | Text of the script to install 858 | **Type**: 859 | 860 | ```console 861 | string 862 | ``` 863 | 864 | **Default value**: 865 | 866 | ```nix 867 | "" 868 | ``` 869 | 870 | **Declared in**: 871 | 872 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 873 | 874 | ### `git.hooks.pre-push.text` 875 | 876 | Text of the script to install 877 | **Type**: 878 | 879 | ```console 880 | string 881 | ``` 882 | 883 | **Default value**: 884 | 885 | ```nix 886 | "" 887 | ``` 888 | 889 | **Declared in**: 890 | 891 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 892 | 893 | ### `git.hooks.pre-rebase.text` 894 | 895 | Text of the script to install 896 | **Type**: 897 | 898 | ```console 899 | string 900 | ``` 901 | 902 | **Default value**: 903 | 904 | ```nix 905 | "" 906 | ``` 907 | 908 | **Declared in**: 909 | 910 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 911 | 912 | ### `git.hooks.prepare-commit-msg.text` 913 | 914 | Text of the script to install 915 | **Type**: 916 | 917 | ```console 918 | string 919 | ``` 920 | 921 | **Default value**: 922 | 923 | ```nix 924 | "" 925 | ``` 926 | 927 | **Declared in**: 928 | 929 | - [extra/git/hooks.nix](https://github.com/numtide/devshell/tree/main/extra/git/hooks.nix) 930 | 931 | ### `language.c.compiler` 932 | 933 | Which C compiler to use 934 | **Type**: 935 | 936 | ```console 937 | package or string convertible to it 938 | ``` 939 | 940 | **Default value**: 941 | 942 | ```nix 943 | "pkgs.clang" 944 | ``` 945 | 946 | **Declared in**: 947 | 948 | - [extra/language/c.nix](https://github.com/numtide/devshell/tree/main/extra/language/c.nix) 949 | 950 | ### `language.c.includes` 951 | 952 | C dependencies from nixpkgs 953 | **Type**: 954 | 955 | ```console 956 | list of (package or string convertible to it) 957 | ``` 958 | 959 | **Default value**: 960 | 961 | ```nix 962 | [ ] 963 | ``` 964 | 965 | **Declared in**: 966 | 967 | - [extra/language/c.nix](https://github.com/numtide/devshell/tree/main/extra/language/c.nix) 968 | 969 | ### `language.c.libraries` 970 | 971 | Use this when another language dependens on a dynamic library 972 | **Type**: 973 | 974 | ```console 975 | list of (package or string convertible to it) 976 | ``` 977 | 978 | **Default value**: 979 | 980 | ```nix 981 | [ ] 982 | ``` 983 | 984 | **Declared in**: 985 | 986 | - [extra/language/c.nix](https://github.com/numtide/devshell/tree/main/extra/language/c.nix) 987 | 988 | ### `language.go.package` 989 | 990 | Which go package to use 991 | **Type**: 992 | 993 | ```console 994 | package or string convertible to it 995 | ``` 996 | 997 | **Default value**: 998 | 999 | ```nix 1000 | 1001 | ``` 1002 | 1003 | **Example value**: 1004 | 1005 | ```nix 1006 | pkgs.go 1007 | ``` 1008 | 1009 | **Declared in**: 1010 | 1011 | - [extra/language/go.nix](https://github.com/numtide/devshell/tree/main/extra/language/go.nix) 1012 | 1013 | ### `language.go.GO111MODULE` 1014 | 1015 | Enable Go modules 1016 | **Type**: 1017 | 1018 | ```console 1019 | one of "on", "off", "auto" 1020 | ``` 1021 | 1022 | **Default value**: 1023 | 1024 | ```nix 1025 | "on" 1026 | ``` 1027 | 1028 | **Declared in**: 1029 | 1030 | - [extra/language/go.nix](https://github.com/numtide/devshell/tree/main/extra/language/go.nix) 1031 | 1032 | ### `language.perl.package` 1033 | 1034 | Which Perl package to use 1035 | **Type**: 1036 | 1037 | ```console 1038 | package or string convertible to it 1039 | ``` 1040 | 1041 | **Default value**: 1042 | 1043 | ```nix 1044 | 1045 | ``` 1046 | 1047 | **Example value**: 1048 | 1049 | ```nix 1050 | pkgs.perl538 1051 | ``` 1052 | 1053 | **Declared in**: 1054 | 1055 | - [extra/language/perl.nix](https://github.com/numtide/devshell/tree/main/extra/language/perl.nix) 1056 | 1057 | ### `language.perl.extraPackages` 1058 | 1059 | List of extra packages (coming from perl5XXPackages) to add 1060 | **Type**: 1061 | 1062 | ```console 1063 | list of (package or string convertible to it) 1064 | ``` 1065 | 1066 | **Default value**: 1067 | 1068 | ```nix 1069 | [ ] 1070 | ``` 1071 | 1072 | **Example value**: 1073 | 1074 | ```nix 1075 | [ perl538Packages.FileNext ] 1076 | ``` 1077 | 1078 | **Declared in**: 1079 | 1080 | - [extra/language/perl.nix](https://github.com/numtide/devshell/tree/main/extra/language/perl.nix) 1081 | 1082 | ### `language.perl.libraryPaths` 1083 | 1084 | List of paths to add to PERL5LIB 1085 | **Type**: 1086 | 1087 | ```console 1088 | list of string 1089 | ``` 1090 | 1091 | **Default value**: 1092 | 1093 | ```nix 1094 | [ ] 1095 | ``` 1096 | 1097 | **Example value**: 1098 | 1099 | ```nix 1100 | [ ./lib ] 1101 | ``` 1102 | 1103 | **Declared in**: 1104 | 1105 | - [extra/language/perl.nix](https://github.com/numtide/devshell/tree/main/extra/language/perl.nix) 1106 | 1107 | ### `language.ruby.package` 1108 | 1109 | Ruby version used by your project 1110 | **Type**: 1111 | 1112 | ```console 1113 | package or string convertible to it 1114 | ``` 1115 | 1116 | **Default value**: 1117 | 1118 | ```nix 1119 | "pkgs.ruby_3_2" 1120 | ``` 1121 | 1122 | **Declared in**: 1123 | 1124 | - [extra/language/ruby.nix](https://github.com/numtide/devshell/tree/main/extra/language/ruby.nix) 1125 | 1126 | ### `language.ruby.nativeDeps` 1127 | 1128 | Use this when your gems depend on a dynamic library 1129 | **Type**: 1130 | 1131 | ```console 1132 | list of (package or string convertible to it) 1133 | ``` 1134 | 1135 | **Default value**: 1136 | 1137 | ```nix 1138 | [ ] 1139 | ``` 1140 | 1141 | **Declared in**: 1142 | 1143 | - [extra/language/ruby.nix](https://github.com/numtide/devshell/tree/main/extra/language/ruby.nix) 1144 | 1145 | ### `language.rust.enableDefaultToolchain` 1146 | 1147 | Enable the default rust toolchain coming from nixpkgs 1148 | **Type**: 1149 | 1150 | ```console 1151 | boolean 1152 | ``` 1153 | 1154 | **Default value**: 1155 | 1156 | ```nix 1157 | "true" 1158 | ``` 1159 | 1160 | **Declared in**: 1161 | 1162 | - [extra/language/rust.nix](https://github.com/numtide/devshell/tree/main/extra/language/rust.nix) 1163 | 1164 | ### `language.rust.packageSet` 1165 | 1166 | Which rust package set to use 1167 | **Type**: 1168 | 1169 | ```console 1170 | attribute set 1171 | ``` 1172 | 1173 | **Default value**: 1174 | 1175 | ```nix 1176 | "pkgs.rustPlatform" 1177 | ``` 1178 | 1179 | **Declared in**: 1180 | 1181 | - [extra/language/rust.nix](https://github.com/numtide/devshell/tree/main/extra/language/rust.nix) 1182 | 1183 | ### `language.rust.tools` 1184 | 1185 | Which rust tools to pull from the platform package set 1186 | **Type**: 1187 | 1188 | ```console 1189 | list of string 1190 | ``` 1191 | 1192 | **Default value**: 1193 | 1194 | ```nix 1195 | [ 1196 | "rustc" 1197 | "cargo" 1198 | "clippy" 1199 | "rustfmt" 1200 | ] 1201 | ``` 1202 | 1203 | **Declared in**: 1204 | 1205 | - [extra/language/rust.nix](https://github.com/numtide/devshell/tree/main/extra/language/rust.nix) 1206 | 1207 | ### `serviceGroups` 1208 | 1209 | Add services to the environment. Services can be used to group long-running processes. 1210 | 1211 | **Type**: 1212 | 1213 | ```console 1214 | attribute set of (submodule) 1215 | ``` 1216 | 1217 | **Default value**: 1218 | 1219 | ```nix 1220 | { } 1221 | ``` 1222 | 1223 | **Declared in**: 1224 | 1225 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1226 | 1227 | ### `serviceGroups..description` 1228 | 1229 | Short description of the service group, shown in generated commands 1230 | 1231 | **Type**: 1232 | 1233 | ```console 1234 | null or string 1235 | ``` 1236 | 1237 | **Default value**: 1238 | 1239 | ```nix 1240 | null 1241 | ``` 1242 | 1243 | **Declared in**: 1244 | 1245 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1246 | 1247 | ### `serviceGroups..name` 1248 | 1249 | Name of the service group. Defaults to attribute name in groups. 1250 | 1251 | **Type**: 1252 | 1253 | ```console 1254 | null or string 1255 | ``` 1256 | 1257 | **Default value**: 1258 | 1259 | ```nix 1260 | null 1261 | ``` 1262 | 1263 | **Declared in**: 1264 | 1265 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1266 | 1267 | ### `serviceGroups..services` 1268 | 1269 | Attrset of services that should be run in this group. 1270 | 1271 | **Type**: 1272 | 1273 | ```console 1274 | attribute set of (submodule) 1275 | ``` 1276 | 1277 | **Default value**: 1278 | 1279 | ```nix 1280 | { } 1281 | ``` 1282 | 1283 | **Declared in**: 1284 | 1285 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1286 | 1287 | ### `serviceGroups..services..command` 1288 | 1289 | Command to execute. 1290 | 1291 | **Type**: 1292 | 1293 | ```console 1294 | string 1295 | ``` 1296 | 1297 | **Declared in**: 1298 | 1299 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1300 | 1301 | ### `serviceGroups..services..name` 1302 | 1303 | Name of this service. Defaults to attribute name in group services. 1304 | 1305 | **Type**: 1306 | 1307 | ```console 1308 | null or string 1309 | ``` 1310 | 1311 | **Default value**: 1312 | 1313 | ```nix 1314 | null 1315 | ``` 1316 | 1317 | **Declared in**: 1318 | 1319 | - [modules/services.nix](https://github.com/numtide/devshell/tree/main/modules/services.nix) 1320 | 1321 | ### `services.postgres.package` 1322 | 1323 | Which version of postgres to use 1324 | **Type**: 1325 | 1326 | ```console 1327 | package or string convertible to it 1328 | ``` 1329 | 1330 | **Default value**: 1331 | 1332 | ```nix 1333 | "pkgs.postgresql" 1334 | ``` 1335 | 1336 | **Declared in**: 1337 | 1338 | - [extra/services/postgres.nix](https://github.com/numtide/devshell/tree/main/extra/services/postgres.nix) 1339 | 1340 | ### `services.postgres.createUserDB` 1341 | 1342 | Create a database named like current user on startup. 1343 | This option only makes sense when `setupPostgresOnStartup` is true. 1344 | 1345 | **Type**: 1346 | 1347 | ```console 1348 | boolean 1349 | ``` 1350 | 1351 | **Default value**: 1352 | 1353 | ```nix 1354 | true 1355 | ``` 1356 | 1357 | **Declared in**: 1358 | 1359 | - [extra/services/postgres.nix](https://github.com/numtide/devshell/tree/main/extra/services/postgres.nix) 1360 | 1361 | ### `services.postgres.initdbArgs` 1362 | 1363 | Additional arguments passed to `initdb` during data dir 1364 | initialisation. 1365 | 1366 | **Type**: 1367 | 1368 | ```console 1369 | list of string 1370 | ``` 1371 | 1372 | **Default value**: 1373 | 1374 | ```nix 1375 | [ 1376 | "--no-locale" 1377 | ] 1378 | ``` 1379 | 1380 | **Example value**: 1381 | 1382 | ```nix 1383 | [ 1384 | "--data-checksums" 1385 | "--allow-group-access" 1386 | ] 1387 | ``` 1388 | 1389 | **Declared in**: 1390 | 1391 | - [extra/services/postgres.nix](https://github.com/numtide/devshell/tree/main/extra/services/postgres.nix) 1392 | 1393 | ### `services.postgres.setupPostgresOnStartup` 1394 | 1395 | Whether to enable call setup-postgres on startup. 1396 | **Type**: 1397 | 1398 | ```console 1399 | boolean 1400 | ``` 1401 | 1402 | **Default value**: 1403 | 1404 | ```nix 1405 | false 1406 | ``` 1407 | 1408 | **Example value**: 1409 | 1410 | ```nix 1411 | true 1412 | ``` 1413 | 1414 | **Declared in**: 1415 | 1416 | - [extra/services/postgres.nix](https://github.com/numtide/devshell/tree/main/extra/services/postgres.nix) 1417 | 1418 | ## Extra options 1419 | 1420 | ### `_module.args` 1421 | 1422 | Additional arguments passed to each module in addition to ones 1423 | like `lib`, `config`, 1424 | and `pkgs`, `modulesPath`. 1425 | 1426 | This option is also available to all submodules. Submodules do not 1427 | inherit args from their parent module, nor do they provide args to 1428 | their parent module or sibling submodules. The sole exception to 1429 | this is the argument `name` which is provided by 1430 | parent modules to a submodule and contains the attribute name 1431 | the submodule is bound to, or a unique generated name if it is 1432 | not bound to an attribute. 1433 | 1434 | Some arguments are already passed by default, of which the 1435 | following *cannot* be changed with this option: 1436 | - {var}`lib`: The nixpkgs library. 1437 | - {var}`config`: The results of all options after merging the values from all modules together. 1438 | - {var}`options`: The options declared in all modules. 1439 | - {var}`specialArgs`: The `specialArgs` argument passed to `evalModules`. 1440 | - All attributes of {var}`specialArgs` 1441 | 1442 | Whereas option values can generally depend on other option values 1443 | thanks to laziness, this does not apply to `imports`, which 1444 | must be computed statically before anything else. 1445 | 1446 | For this reason, callers of the module system can provide `specialArgs` 1447 | which are available during import resolution. 1448 | 1449 | For NixOS, `specialArgs` includes 1450 | {var}`modulesPath`, which allows you to import 1451 | extra modules from the nixpkgs package tree without having to 1452 | somehow make the module aware of the location of the 1453 | `nixpkgs` or NixOS directories. 1454 | ``` 1455 | { modulesPath, ... }: { 1456 | imports = [ 1457 | (modulesPath + "/profiles/minimal.nix") 1458 | ]; 1459 | } 1460 | ``` 1461 | 1462 | For NixOS, the default value for this option includes at least this argument: 1463 | - {var}`pkgs`: The nixpkgs package set according to 1464 | the {option}`nixpkgs.pkgs` option. 1465 | 1466 | **Type**: 1467 | 1468 | ```console 1469 | lazy attribute set of raw value 1470 | ``` 1471 | 1472 | **Declared in**: 1473 | 1474 | - [lib/modules.nix]() 1475 | -------------------------------------------------------------------------------- /docs/theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | 16 | {{> head}} 17 | 18 | 19 | 20 | 21 | 22 | {{#if favicon_svg}} 23 | 24 | {{/if}} 25 | {{#if favicon_png}} 26 | 27 | {{/if}} 28 | 29 | 30 | 31 | {{#if print_enable}} 32 | 33 | {{/if}} 34 | 35 | 36 | 37 | {{#if copy_fonts}} 38 | 39 | {{/if}} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{#each additional_css}} 48 | 49 | {{/each}} 50 | 51 | {{#if mathjax_support}} 52 | 53 | 54 | {{/if}} 55 | 56 | 57 |
58 | 59 | 63 | 64 | 65 | 79 | 80 | 81 | 92 | 93 | 94 | 95 | 96 | 110 | 111 | 119 | 120 | 121 | 141 | 142 |
143 | 144 |
145 | {{> header}} 146 | 147 | 190 | 191 | {{#if search_enabled}} 192 | 202 | {{/if}} 203 | 204 | 205 | 212 | 213 |
214 |
215 |
216 | {{{ content }}} 217 |
218 |
219 | 220 |
221 |
222 | 223 | 239 |
240 |
241 | 242 | 255 | 256 |
257 | 258 | {{#if live_reload_endpoint}} 259 | 260 | 275 | {{/if}} 276 | 277 | {{#if google_analytics}} 278 | 279 | 294 | {{/if}} 295 | 296 | {{#if playground_line_numbers}} 297 | 300 | {{/if}} 301 | 302 | {{#if playground_copyable}} 303 | 306 | {{/if}} 307 | 308 | {{#if playground_js}} 309 | 310 | 311 | 312 | 313 | 314 | {{/if}} 315 | 316 | {{#if search_js}} 317 | 318 | 319 | 320 | {{/if}} 321 | 322 | 323 | 324 | 325 | 326 | 327 | {{#each additional_js}} 328 | 329 | {{/each}} 330 | 331 | {{#if is_print}} 332 | {{#if mathjax_support}} 333 | 340 | {{else}} 341 | 346 | {{/if}} 347 | {{/if}} 348 | 349 |
350 | 351 | -------------------------------------------------------------------------------- /docs/theme/pagetoc.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --toc-width: 310px; 3 | --center-content-toc-shift: calc(-1 * var(--toc-width) / 2); 4 | } 5 | 6 | .nav-chapters { 7 | /* adjust width of buttons that bring to the previous or the next page */ 8 | min-width: 50px; 9 | } 10 | 11 | .previous { 12 | /* 13 | adjust the space between the left sidebar or the left side of the screen 14 | and the button that leads to the previous page 15 | */ 16 | margin-left: var(--page-padding); 17 | } 18 | 19 | @media only screen { 20 | main { 21 | display: flex; 22 | } 23 | 24 | @media (max-width: 1219px) { 25 | .sidebar-hidden .sidetoc { 26 | display: none; 27 | } 28 | } 29 | 30 | @media (max-width: 1499px) { 31 | .sidebar-visible .sidetoc { 32 | display: none; 33 | } 34 | } 35 | 36 | @media (1220px <= width <= 1519px) { 37 | .sidebar-hidden main { 38 | position: relative; 39 | left: var(--center-content-toc-shift); 40 | } 41 | } 42 | 43 | @media (1500px <= width <= 1820px) { 44 | .sidebar-visible main { 45 | position: relative; 46 | left: var(--center-content-toc-shift); 47 | } 48 | } 49 | 50 | .content-wrap { 51 | overflow-y: auto; 52 | width: 100%; 53 | } 54 | 55 | .sidetoc { 56 | margin-top: 20px; 57 | margin-left: 10px; 58 | margin-right: auto; 59 | } 60 | .pagetoc { 61 | position: fixed; 62 | /* adjust TOC width */ 63 | width: var(--toc-width); 64 | height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); 65 | overflow: auto; 66 | } 67 | .pagetoc a { 68 | border-left: 1px solid var(--sidebar-bg); 69 | color: var(--fg) !important; 70 | display: block; 71 | padding-bottom: 5px; 72 | padding-top: 5px; 73 | padding-left: 10px; 74 | text-align: left; 75 | text-decoration: none; 76 | } 77 | .pagetoc a:hover, 78 | .pagetoc a.active { 79 | background: var(--sidebar-bg); 80 | color: var(--sidebar-fg) !important; 81 | } 82 | .pagetoc .active { 83 | background: var(--sidebar-bg); 84 | color: var(--sidebar-fg); 85 | } 86 | .pagetoc .pagetoc-H2 { 87 | padding-left: 20px; 88 | } 89 | .pagetoc .pagetoc-H3 { 90 | padding-left: 40px; 91 | } 92 | .pagetoc .pagetoc-H4 { 93 | padding-left: 60px; 94 | } 95 | } 96 | 97 | @media print { 98 | .sidetoc { 99 | display: none; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/theme/pagetoc.js: -------------------------------------------------------------------------------- 1 | function forEach(elems, fun) { 2 | Array.prototype.forEach.call(elems, fun); 3 | } 4 | 5 | function getPagetoc(){ 6 | return document.getElementsByClassName("pagetoc")[0] 7 | } 8 | 9 | function getPagetocElems() { 10 | return getPagetoc().children; 11 | } 12 | 13 | function getHeaders(){ 14 | return document.getElementsByClassName("header") 15 | } 16 | 17 | // Un-active everything when you click it 18 | function forPagetocElem(fun) { 19 | forEach(getPagetocElems(), fun); 20 | } 21 | 22 | function getRect(element) { 23 | return element.getBoundingClientRect(); 24 | } 25 | 26 | function overflowTop(container, element) { 27 | return getRect(container).top - getRect(element).top; 28 | } 29 | 30 | function overflowBottom(container, element) { 31 | return getRect(container).bottom - getRect(element).bottom; 32 | } 33 | 34 | var activeHref = location.href; 35 | 36 | var updateFunction = function (elem = undefined) { 37 | var id = elem; 38 | 39 | if (!id && location.href != activeHref) { 40 | activeHref = location.href; 41 | forPagetocElem(function (el) { 42 | if (el.href === activeHref) { 43 | id = el; 44 | } 45 | }); 46 | } 47 | 48 | if (!id) { 49 | var elements = getHeaders(); 50 | let margin = window.innerHeight / 3; 51 | 52 | forEach(elements, function (el, i, arr) { 53 | if (!id && getRect(el).top >= 0) { 54 | if (getRect(el).top < margin) { 55 | id = el; 56 | } else { 57 | id = arr[Math.max(0, i - 1)]; 58 | } 59 | } 60 | // a very long last section 61 | // its heading is over the screen 62 | if (!id && i == arr.length - 1) { 63 | id = el 64 | } 65 | }); 66 | } 67 | 68 | forPagetocElem(function (el) { 69 | el.classList.remove("active"); 70 | }); 71 | 72 | if (!id) return; 73 | 74 | forPagetocElem(function (el) { 75 | if (id.href.localeCompare(el.href) == 0) { 76 | el.classList.add("active"); 77 | let pagetoc = getPagetoc(); 78 | if (overflowTop(pagetoc, el) > 0) { 79 | pagetoc.scrollTop = el.offsetTop; 80 | } 81 | if (overflowBottom(pagetoc, el) < 0) { 82 | pagetoc.scrollTop -= overflowBottom(pagetoc, el); 83 | } 84 | } 85 | }); 86 | }; 87 | 88 | let elements = getHeaders(); 89 | 90 | if (elements.length > 1) { 91 | // Populate sidebar on load 92 | window.addEventListener("load", function () { 93 | var pagetoc = getPagetoc(); 94 | var elements = getHeaders(); 95 | forEach(elements, function (el) { 96 | var link = document.createElement("a"); 97 | link.appendChild(document.createTextNode(el.text)); 98 | link.href = el.hash; 99 | link.classList.add("pagetoc-" + el.parentElement.tagName); 100 | pagetoc.appendChild(link); 101 | link.onclick = function () { 102 | updateFunction(link); 103 | }; 104 | }); 105 | updateFunction(); 106 | }); 107 | 108 | // Handle active elements on scroll 109 | window.addEventListener("scroll", function () { 110 | updateFunction(); 111 | }); 112 | } else { 113 | document.getElementsByClassName("sidetoc")[0].remove(); 114 | } 115 | -------------------------------------------------------------------------------- /extra/git/hooks.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; 8 | let 9 | cfg = config.git.hooks; 10 | 11 | # These are all the options available for a git hook. 12 | hookOptions = desc: { 13 | text = mkOption { 14 | description = "Text of the script to install"; 15 | default = ""; 16 | type = types.str; 17 | }; 18 | }; 19 | 20 | # All of the hook types supported by this module. 21 | allHooks = filterAttrs (k: v: k != "enable") cfg; 22 | 23 | # Only keep all the hooks that have a value set. 24 | hooksWithData = filterAttrs (k: v: v.text != "") allHooks; 25 | 26 | # Shims for all the hooks that this module supports. The shims cause git 27 | # hooks to be ignored: 28 | # 29 | # 1. Outside of the devshell, or 30 | # 2. When the current devshell doesn't define/enable *any* git hooks, or 31 | # 3. When the current devshell doesn't define/enable the specific git hook 32 | # in question. 33 | # 34 | # The idea here is to support scenarios like switching between multiple git 35 | # worktrees without having to reinstall the hook symlinks. Instead, the hook 36 | # shims read the correct "real" shim (directory) from DEVSHELL_GIT_HOOKS_DIR, 37 | # which points to the directory containing git hooks for the current 38 | # devshell. 39 | hookShimsDir = pkgs.runCommand "git.hook.shims" { } '' 40 | mkdir -p $out/bin 41 | 42 | ${lib.concatMapStringsSep "\n" (k: '' 43 | cat <<'WRAPPER' > $out/bin/${k} 44 | #!${pkgs.bash}/bin/bash 45 | set -euo pipefail 46 | 47 | if [[ -z "''${DEVSHELL_DIR:-}" ]]; then 48 | echo "${k}: ignoring git hook outside of devshell"; >&2 49 | exit; 50 | elif [[ -z "''${DEVSHELL_GIT_HOOKS_DIR:-}" ]]; then 51 | echo "${k}: git hooks are not activated in this environment"; >&2 52 | exit; 53 | elif ! [[ -x "''${DEVSHELL_GIT_HOOKS_DIR}/bin/${k}" ]]; then 54 | echo "${k}: the ${k} git hook is not activated in this environment"; >&2 55 | exit; 56 | fi 57 | 58 | exec "''${DEVSHELL_GIT_HOOKS_DIR}/bin/${k}" "$@" 59 | WRAPPER 60 | 61 | # Mark as executable 62 | chmod +x "$out/bin/${k}" 63 | '') (builtins.attrNames allHooks)} 64 | ''; 65 | 66 | # A collection of all the git hooks in the /bin folder 67 | hooksDir = 68 | let 69 | mkHookScript = k: hook: pkgs.writeShellScriptBin k hook.text; 70 | in 71 | pkgs.buildEnv { 72 | name = "git.hooks"; 73 | paths = mapAttrsToList mkHookScript hooksWithData; 74 | }; 75 | 76 | # Execute this script to update the project's git hooks 77 | install-git-hooks = pkgs.writeShellScriptBin "install-git-hooks" '' 78 | set -euo pipefail 79 | shopt -s nullglob 80 | 81 | log() { 82 | echo "[git.hooks] $*" >&2 83 | } 84 | 85 | update=0 86 | has_update() { 87 | if [[ $update == 0 ]]; then 88 | log "found updates" 89 | update=1 90 | fi 91 | } 92 | 93 | git_path_absolute() { 94 | ${pkgs.gitMinimal}/bin/git rev-parse --path-format=absolute "$@" 95 | } 96 | 97 | # Add `readlink -f` for macOS 98 | export PATH=${pkgs.coreutils}/bin:$PATH 99 | 100 | # Find the git dir 101 | git_work_tree=$(${pkgs.gitMinimal}/bin/git rev-parse --show-toplevel || true) 102 | if [[ $git_work_tree == "" ]]; then 103 | log "skipping as we can't find any .git folder, we are probably not in a git repository" >&2 104 | exit 105 | fi 106 | 107 | # Respect GIT_COMMON_DIR on git clients that support it 108 | git_dir=$(git_path_absolute --git-common-dir 2>/dev/null) || git_dir=$(git_path_absolute --git-dir) 109 | 110 | source_hook_dir=${hookShimsDir}/bin 111 | 112 | # Respect setups that define core.hooksPath 113 | target_hook_dir=$(git_path_absolute --git-path hooks/ 2>/dev/null) || target_hook_dir=$git_dir/hooks 114 | 115 | # Just in case it doesn't exist 116 | mkdir -pv "$target_hook_dir" 117 | 118 | # Iterate over all the hooks enabled for this environment 119 | for name in ${toString (attrNames hooksWithData)}; do 120 | # Resolve all the symlinks 121 | src_hook=$(readlink -f "$source_hook_dir/$name" || true) 122 | dst_hook=$(readlink -f "$target_hook_dir/$name" || true) 123 | 124 | # If the hook hasn't changed, skip 125 | if [[ "$src_hook" == "$dst_hook" ]]; then 126 | continue 127 | # If there is a new source hook, install 128 | elif [[ -f "$src_hook" ]]; then 129 | has_update 130 | ln -sfv "$src_hook" "$target_hook_dir/$name" 131 | fi 132 | done 133 | if [[ $update != 0 ]]; then 134 | log "done" 135 | fi 136 | ''; 137 | in 138 | { 139 | options.git.hooks = { 140 | enable = mkEnableOption "install .git/hooks on shell entry"; 141 | 142 | # TODO: add proper description for each hook. 143 | applypatch-msg = hookOptions ""; 144 | commit-msg = hookOptions ""; 145 | fsmonitor-watchman = hookOptions ""; 146 | post-update = hookOptions ""; 147 | pre-applypatch = hookOptions ""; 148 | pre-commit = hookOptions ""; 149 | pre-merge-commit = hookOptions ""; 150 | prepare-commit-msg = hookOptions ""; 151 | pre-push = hookOptions ""; 152 | pre-rebase = hookOptions ""; 153 | 154 | # Those are server-side hooks and probably don't make sense to have here? 155 | # post-receive = hookOptions ""; 156 | # pre-receive = hookOptions ""; 157 | # update = hookOptions ""; 158 | }; 159 | 160 | config.devshell = optionalAttrs cfg.enable { 161 | packages = [ install-git-hooks ]; 162 | 163 | startup.install-git-hooks.text = " 164 | $DEVSHELL_DIR/bin/install-git-hooks 165 | "; 166 | }; 167 | 168 | config.env = optional cfg.enable { 169 | name = "DEVSHELL_GIT_HOOKS_DIR"; 170 | value = hooksDir; 171 | }; 172 | } 173 | -------------------------------------------------------------------------------- /extra/language/c.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | cfg = config.language.c; 9 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 10 | 11 | hasLibraries = lib.length cfg.libraries > 0; 12 | hasIncludes = lib.length cfg.includes > 0; 13 | in 14 | with lib; 15 | { 16 | options.language.c = { 17 | libraries = mkOption { 18 | type = types.listOf strOrPackage; 19 | default = [ ]; 20 | description = "Use this when another language dependens on a dynamic library"; 21 | }; 22 | 23 | includes = mkOption { 24 | type = types.listOf strOrPackage; 25 | default = [ ]; 26 | description = "C dependencies from nixpkgs"; 27 | }; 28 | 29 | compiler = mkOption { 30 | type = strOrPackage; 31 | default = pkgs.clang; 32 | defaultText = "pkgs.clang"; 33 | description = "Which C compiler to use"; 34 | }; 35 | }; 36 | 37 | config = { 38 | devshell.packages = 39 | [ cfg.compiler ] 40 | ++ (lib.optionals hasLibraries (map lib.getLib cfg.libraries)) 41 | ++ 42 | # Assume we want pkg-config, because it's good 43 | (lib.optionals hasIncludes ([ pkgs.pkg-config ] ++ (map lib.getDev cfg.includes))); 44 | 45 | env = 46 | (lib.optionals hasLibraries [ 47 | { 48 | name = "LD_LIBRARY_PATH"; 49 | prefix = "$DEVSHELL_DIR/lib"; 50 | } 51 | { 52 | name = "LDFLAGS"; 53 | eval = "-L$DEVSHELL_DIR/lib"; 54 | } 55 | ]) 56 | ++ lib.optionals hasIncludes [ 57 | { 58 | name = "C_INCLUDE_PATH"; 59 | prefix = "$DEVSHELL_DIR/include"; 60 | } 61 | { 62 | name = "PKG_CONFIG_PATH"; 63 | prefix = "$DEVSHELL_DIR/lib/pkgconfig"; 64 | } 65 | ]; 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /extra/language/go.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | cfg = config.language.go; 9 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 10 | in 11 | with lib; 12 | { 13 | options.language.go = { 14 | GO111MODULE = mkOption { 15 | type = types.enum [ 16 | "on" 17 | "off" 18 | "auto" 19 | ]; 20 | default = "on"; 21 | description = "Enable Go modules"; 22 | }; 23 | 24 | package = mkOption { 25 | type = strOrPackage; 26 | default = pkgs.go; 27 | example = literalExpression "pkgs.go"; 28 | description = "Which go package to use"; 29 | }; 30 | }; 31 | 32 | config = { 33 | env = [ 34 | { 35 | name = "GO111MODULE"; 36 | value = cfg.GO111MODULE; 37 | } 38 | ]; 39 | 40 | devshell.packages = [ cfg.package ]; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /extra/language/hare.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | cfg = config.language.hare; 9 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 10 | makeHareFullPath = 11 | userHareLibs: 12 | let 13 | allHareThirdPartyLibs = builtins.attrValues (pkgs.hareThirdParty.packages pkgs); 14 | propagatedLibs = lib.unique ( 15 | builtins.foldl' ( 16 | acc: userLib: acc ++ (lib.intersectLists userLib.propagatedBuildInputs allHareThirdPartyLibs) 17 | ) [ ] userHareLibs 18 | ); 19 | in 20 | lib.makeSearchPath "src/hare/third-party" (userHareLibs ++ propagatedLibs); 21 | in 22 | with lib; 23 | { 24 | options.language.hare = { 25 | thirdPartyLibs = mkOption { 26 | type = types.listOf strOrPackage; 27 | default = [ ]; 28 | example = literalExpression "[ hareThirdParty.hare-compress ]"; 29 | description = "List of extra packages (coming from hareThirdParty) to add"; 30 | }; 31 | vendoredLibs = mkOption { 32 | type = types.listOf types.str; 33 | default = [ ]; 34 | example = literalExpression "[ ./vendor/lib ]"; 35 | description = "List of paths to add to HAREPATH"; 36 | }; 37 | package = mkOption { 38 | type = strOrPackage; 39 | default = pkgs.hare; 40 | example = literalExpression "pkgs.hare"; 41 | description = "Which Hare package to use"; 42 | }; 43 | }; 44 | 45 | config = { 46 | env = [ 47 | { 48 | name = "HAREPATH"; 49 | value = lib.makeSearchPath "src/hare/stdlib" [ cfg.package ]; 50 | } 51 | (mkIf (cfg.thirdPartyLibs != [ ]) { 52 | name = "HAREPATH"; 53 | prefix = makeHareFullPath cfg.thirdPartyLibs; 54 | }) 55 | (mkIf (cfg.vendoredLibs != [ ]) { 56 | name = "HAREPATH"; 57 | prefix = concatStringsSep ":" cfg.vendoredLibs; 58 | }) 59 | ]; 60 | devshell.packages = [ cfg.package ]; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /extra/language/perl.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | 8 | let 9 | cfg = config.language.perl; 10 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 11 | 12 | in 13 | with lib; 14 | { 15 | options.language.perl = { 16 | extraPackages = mkOption { 17 | type = types.listOf strOrPackage; 18 | default = [ ]; 19 | example = literalExpression "[ perl538Packages.FileNext ]"; 20 | description = "List of extra packages (coming from perl5XXPackages) to add"; 21 | }; 22 | libraryPaths = mkOption { 23 | type = types.listOf types.str; 24 | default = [ ]; 25 | example = literalExpression "[ ./lib ]"; 26 | description = "List of paths to add to PERL5LIB"; 27 | }; 28 | package = mkOption { 29 | type = strOrPackage; 30 | default = pkgs.perl; 31 | example = literalExpression "pkgs.perl538"; 32 | description = "Which Perl package to use"; 33 | }; 34 | }; 35 | 36 | config = { 37 | env = [ 38 | (mkIf (cfg.extraPackages != [ ]) { 39 | name = "PERL5LIB"; 40 | prefix = pkgs.perlPackages.makeFullPerlPath cfg.extraPackages; 41 | }) 42 | (mkIf (cfg.libraryPaths != [ ]) { 43 | name = "PERL5LIB"; 44 | prefix = concatStringsSep ":" cfg.libraryPaths; 45 | }) 46 | ]; 47 | devshell.packages = [ cfg.package ] ++ cfg.extraPackages; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /extra/language/ruby.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | cfg = config.language.ruby; 9 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 10 | in 11 | with lib; 12 | { 13 | imports = [ ./c.nix ]; 14 | options.language.ruby = { 15 | nativeDeps = mkOption { 16 | type = types.listOf strOrPackage; 17 | default = [ ]; 18 | description = "Use this when your gems depend on a dynamic library"; 19 | }; 20 | package = mkOption { 21 | type = strOrPackage; 22 | default = pkgs.ruby_3_2; 23 | defaultText = "pkgs.ruby_3_2"; 24 | description = "Ruby version used by your project"; 25 | }; 26 | }; 27 | 28 | config = { 29 | language.c = { 30 | compiler = pkgs.gcc; # Lots of gems don't compile properly with clang 31 | libraries = cfg.nativeDeps; 32 | includes = cfg.nativeDeps; 33 | }; 34 | devshell.packages = with pkgs; [ 35 | cfg.package 36 | # Used by mkmf, the standard gem build tool 37 | (lowPrio binutils) 38 | file 39 | findutils 40 | gnumake 41 | ]; 42 | env = [ 43 | { 44 | name = "CC"; 45 | value = "cc"; 46 | } 47 | { 48 | name = "CPP"; 49 | value = "cpp"; 50 | } 51 | { 52 | name = "CXX"; 53 | value = "c++"; 54 | } 55 | { 56 | name = "GEM_HOME"; 57 | eval = "$PRJ_DATA_DIR/ruby/bundle/$(ruby -e 'puts RUBY_VERSION')"; 58 | } 59 | { 60 | name = "PATH"; 61 | prefix = "$GEM_HOME/bin"; 62 | } 63 | ]; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /extra/language/rust.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | let 8 | cfg = config.language.rust; 9 | in 10 | with lib; 11 | { 12 | options.language.rust = { 13 | packageSet = mkOption { 14 | # FIXME: how to make the selection possible in TOML? 15 | type = types.attrs; 16 | default = pkgs.rustPackages; 17 | defaultText = "pkgs.rustPlatform"; 18 | description = "Which rust package set to use"; 19 | }; 20 | tools = mkOption { 21 | type = types.listOf types.str; 22 | default = [ 23 | "rustc" 24 | "cargo" 25 | "clippy" 26 | "rustfmt" 27 | ]; 28 | description = "Which rust tools to pull from the platform package set"; 29 | }; 30 | enableDefaultToolchain = mkOption { 31 | type = types.bool; 32 | default = true; 33 | defaultText = "true"; 34 | description = "Enable the default rust toolchain coming from nixpkgs"; 35 | }; 36 | }; 37 | 38 | config = { 39 | devshell.packages = 40 | if cfg.enableDefaultToolchain then (map (tool: cfg.packageSet.${tool}) cfg.tools) else [ ]; 41 | env = 42 | [ 43 | { 44 | # On darwin for example enables finding of libiconv 45 | name = "LIBRARY_PATH"; 46 | # append in case it needs to be modified 47 | eval = "$DEVSHELL_DIR/lib"; 48 | } 49 | { 50 | # some *-sys crates require additional includes 51 | name = "CFLAGS"; 52 | # append in case it needs to be modified 53 | eval = "\"-I $DEVSHELL_DIR/include ${lib.optionalString pkgs.stdenv.isDarwin "-iframework $DEVSHELL_DIR/Library/Frameworks"}\""; 54 | } 55 | ] 56 | ++ lib.optionals pkgs.stdenv.isDarwin [ 57 | { 58 | # On darwin for example required for some *-sys crate compilation 59 | name = "RUSTFLAGS"; 60 | # append in case it needs to be modified 61 | eval = "\"-L framework=$DEVSHELL_DIR/Library/Frameworks\""; 62 | } 63 | { 64 | # rustdoc uses a different set of flags 65 | name = "RUSTDOCFLAGS"; 66 | # append in case it needs to be modified 67 | eval = "\"-L framework=$DEVSHELL_DIR/Library/Frameworks\""; 68 | } 69 | { 70 | name = "PATH"; 71 | prefix = 72 | let 73 | inherit (pkgs) xcbuild; 74 | in 75 | lib.makeBinPath [ 76 | xcbuild 77 | "${xcbuild}/Toolchains/XcodeDefault.xctoolchain" 78 | ]; 79 | } 80 | ] 81 | # fenix provides '.rust-src' in the 'complete' toolchain configuration 82 | ++ lib.optionals (cfg.enableDefaultToolchain && cfg.packageSet ? rust-src) [ 83 | { 84 | # rust-analyzer may use this to quicker find the rust source 85 | name = "RUST_SRC_PATH"; 86 | value = "${cfg.packageSet.rust-src}/lib/rustlib/src/rust/library"; 87 | } 88 | ]; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /extra/locale.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | config, 5 | ... 6 | }: 7 | with lib; 8 | let 9 | cfg = config.extra.locale; 10 | in 11 | { 12 | options.extra.locale = { 13 | lang = mkOption { 14 | type = types.nullOr types.str; 15 | default = null; 16 | description = "Set the language of the project"; 17 | example = "en_GB.UTF-8"; 18 | }; 19 | 20 | package = mkOption { 21 | type = types.package; 22 | description = "Set the glibc locale package that will be used on Linux"; 23 | default = pkgs.glibcLocales; 24 | defaultText = "pkgs.glibcLocales"; 25 | }; 26 | }; 27 | config.env = 28 | lib.optional pkgs.stdenv.isLinux { 29 | name = "LOCALE_ARCHIVE"; 30 | value = "${cfg.package}/lib/locale/locale-archive"; 31 | } 32 | ++ lib.optionals (cfg.lang != null) [ 33 | { 34 | name = "LANG"; 35 | value = cfg.lang; 36 | } 37 | { 38 | name = "LC_ALL"; 39 | value = cfg.lang; 40 | } 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /extra/services/postgres.nix: -------------------------------------------------------------------------------- 1 | # This module automatically configures postgres when the user enters the 2 | # devshell. 3 | # 4 | # To start the server, invoke `postgres` in one devshell. Then start a second 5 | # devshell to run the clients. 6 | { 7 | lib, 8 | pkgs, 9 | config, 10 | ... 11 | }: 12 | with lib; 13 | let 14 | # Because we want to be able to push pure JSON-like data into the 15 | # environment. 16 | strOrPackage = import ../../nix/strOrPackage.nix { inherit lib pkgs; }; 17 | 18 | cfg = config.services.postgres; 19 | createDB = optionalString cfg.createUserDB '' 20 | echo "CREATE DATABASE ''${USER:-$(id -nu)};" | postgres --single -E postgres 21 | ''; 22 | 23 | setup-postgres = pkgs.writeShellScriptBin "setup-postgres" '' 24 | set -euo pipefail 25 | export PATH=${cfg.package}/bin:${pkgs.coreutils}/bin 26 | 27 | # Abort if the data dir already exists 28 | [[ ! -d "$PGDATA" ]] || exit 0 29 | 30 | initdb ${concatStringsSep " " cfg.initdbArgs} 31 | 32 | cat >> "$PGDATA/postgresql.conf" <= 5 && lockFile.version <= 7 then 118 | allNodes.${lockFile.root}.inputs 119 | else 120 | throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}"; 121 | 122 | in 123 | result 124 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "devshell"; 3 | # To update all inputs: 4 | # nix flake update 5 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | 7 | outputs = 8 | { self, nixpkgs }: 9 | let 10 | systems = [ 11 | "aarch64-darwin" 12 | "aarch64-linux" 13 | "riscv64-linux" 14 | "x86_64-darwin" 15 | "x86_64-linux" 16 | ]; 17 | 18 | eachSystem = 19 | f: 20 | nixpkgs.lib.genAttrs systems ( 21 | system: 22 | f rec { 23 | inherit system; 24 | pkgs = nixpkgs.legacyPackages.${system}; 25 | devshell = import ./. { nixpkgs = pkgs; }; 26 | } 27 | ); 28 | in 29 | { 30 | packages = eachSystem ( 31 | { 32 | pkgs, 33 | system, 34 | devshell, 35 | }: 36 | { 37 | docs = pkgs.writeShellApplication { 38 | name = "docs"; 39 | meta.description = ''Run mdBook server at http://localhost:3000''; 40 | runtimeInputs = [ pkgs.mdbook ]; 41 | text = '' 42 | cd docs 43 | cp ${devshell.modules-docs.markdown} src/modules_schema.md 44 | mdbook serve 45 | ''; 46 | }; 47 | bench = pkgs.writeShellApplication { 48 | name = "benchmark"; 49 | meta.description = ''Run benchmark''; 50 | runtimeInputs = [ pkgs.hyperfine ]; 51 | text = '' 52 | cd benchmark 53 | hyperfine -w 3 \ 54 | 'nix-instantiate ../shell.nix' \ 55 | 'nix-instantiate ./devshell-nix.nix' \ 56 | 'nix-instantiate ./devshell-toml.nix' \ 57 | 'nix-instantiate ./nixpkgs-mkshell.nix' 58 | ''; 59 | }; 60 | # expose devshell as an executable package 61 | default = self.devShells.${system}.default; 62 | } 63 | ); 64 | 65 | devShells = eachSystem ( 66 | { devshell, ... }: 67 | { 68 | default = devshell.fromTOML ./devshell.toml; 69 | } 70 | ); 71 | 72 | legacyPackages = eachSystem ( 73 | { system, pkgs, ... }: 74 | import self { 75 | inherit system; 76 | inputs = null; 77 | nixpkgs = pkgs; 78 | } 79 | ); 80 | 81 | checks = eachSystem ( 82 | { pkgs, ... }: 83 | with pkgs.lib; 84 | pipe (import ./tests { inherit pkgs; }) [ 85 | (collect isDerivation) 86 | (map (x: { 87 | name = x.name or x.pname; 88 | value = x; 89 | })) 90 | listToAttrs 91 | ] 92 | ); 93 | 94 | formatter = eachSystem ({ pkgs, ... }: pkgs.nixfmt-rfc-style); 95 | 96 | # Import this overlay into your instance of nixpkgs 97 | overlays.default = import ./overlay.nix; 98 | 99 | templates = rec { 100 | toml = { 101 | path = ./templates/toml; 102 | description = "nix flake new my-project -t github:numtide/devshell"; 103 | }; 104 | flake-parts = { 105 | path = ./templates/flake-parts; 106 | description = "nix flake new my-project -t github:numtide/devshell#flake-parts"; 107 | }; 108 | default = toml; 109 | }; 110 | 111 | lib.importTOML = import ./nix/importTOML.nix; 112 | 113 | flakeModule = ./flake-module.nix; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /modules/back-compat.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | config, 5 | ... 6 | }: 7 | # Avoid breaking back-compat for now. 8 | let 9 | # Because we want to be able to push pure JSON-like data into the 10 | # environment. 11 | strOrPackage = import ../nix/strOrPackage.nix { inherit lib pkgs; }; 12 | in 13 | with lib; 14 | { 15 | options = { 16 | bash.extra = mkOption { 17 | internal = true; 18 | type = types.lines; 19 | default = ""; 20 | }; 21 | 22 | bash.interactive = mkOption { 23 | internal = true; 24 | type = types.lines; 25 | default = ""; 26 | }; 27 | 28 | motd = mkOption { 29 | internal = true; 30 | type = types.nullOr types.str; 31 | default = null; 32 | }; 33 | 34 | name = mkOption { 35 | internal = true; 36 | type = types.nullOr types.str; 37 | default = null; 38 | }; 39 | 40 | packages = mkOption { 41 | internal = true; 42 | type = types.listOf strOrPackage; 43 | default = [ ]; 44 | }; 45 | 46 | packagesFrom = mkOption { 47 | internal = true; 48 | type = types.listOf strOrPackage; 49 | default = [ ]; 50 | }; 51 | }; 52 | 53 | # Copy the values over to the devshell module 54 | config.devshell = 55 | { 56 | packages = config.packages; 57 | packagesFrom = config.packagesFrom; 58 | startup.bash_extra = noDepEntry config.bash.extra; 59 | interactive.bash_interactive = noDepEntry config.bash.interactive; 60 | } 61 | // (lib.optionalAttrs (config.motd != null) { motd = config.motd; }) 62 | // (lib.optionalAttrs (config.name != null) { name = config.name; }); 63 | } 64 | -------------------------------------------------------------------------------- /modules/commands.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; 8 | let 9 | ansi = import ../nix/ansi.nix; 10 | 11 | # Because we want to be able to push pure JSON-like data into the 12 | # environment. 13 | strOrPackage = import ../nix/strOrPackage.nix { inherit lib pkgs; }; 14 | 15 | writeDefaultShellScript = import ../nix/writeDefaultShellScript.nix { 16 | inherit (pkgs) lib writeTextFile bash; 17 | }; 18 | 19 | pad = str: num: if num > 0 then pad "${str} " (num - 1) else str; 20 | 21 | # Fallback to the package pname if the name is unset 22 | resolveName = 23 | cmd: 24 | if cmd.name == null then 25 | cmd.package.pname or (builtins.parseDrvName cmd.package.name).name 26 | else 27 | cmd.name; 28 | 29 | # Fill in default options for a command. 30 | commandToPackage = 31 | cmd: 32 | assert lib.assertMsg (cmd.command == null || cmd.name != cmd.command) 33 | "[[commands]]: ${toString cmd.name} cannot be set to both the `name` and the `command` attributes. Did you mean to use the `package` attribute?"; 34 | assert lib.assertMsg ( 35 | cmd.package != null || (cmd.command != null && cmd.command != "") 36 | ) "[[commands]]: ${resolveName cmd} expected either a command or package attribute."; 37 | if cmd.package == null then 38 | writeDefaultShellScript { 39 | name = cmd.name; 40 | text = cmd.command; 41 | binPrefix = true; 42 | } 43 | else 44 | cmd.package; 45 | 46 | commandsToMenu = 47 | cmds: 48 | let 49 | cleanName = 50 | { name, package, ... }@cmd: 51 | assert lib.assertMsg ( 52 | cmd.name != null || cmd.package != null 53 | ) "[[commands]]: some command is missing both a `name` or `package` attribute."; 54 | let 55 | name = resolveName cmd; 56 | 57 | help = if cmd.help == null then cmd.package.meta.description or "" else cmd.help; 58 | in 59 | cmd // { inherit name help; }; 60 | 61 | commands = map cleanName cmds; 62 | 63 | commandLengths = map ({ name, ... }: builtins.stringLength name) commands; 64 | 65 | maxCommandLength = builtins.foldl' (max: v: if v > max then v else max) 0 commandLengths; 66 | 67 | commandCategories = lib.unique ( 68 | (zipAttrsWithNames [ "category" ] (name: vs: vs) commands).category 69 | ); 70 | 71 | commandByCategoriesSorted = builtins.attrValues ( 72 | lib.genAttrs commandCategories ( 73 | category: 74 | lib.nameValuePair category ( 75 | builtins.sort (a: b: a.name < b.name) (builtins.filter (x: x.category == category) commands) 76 | ) 77 | ) 78 | ); 79 | 80 | opCat = 81 | kv: 82 | let 83 | category = kv.name; 84 | cmd = kv.value; 85 | opCmd = 86 | { name, help, ... }: 87 | let 88 | len = maxCommandLength - (builtins.stringLength name); 89 | in 90 | if help == null || help == "" then " ${name}" else " ${pad name len} - ${help}"; 91 | in 92 | "\n${ansi.bold}[${category}]${ansi.reset}\n\n" + builtins.concatStringsSep "\n" (map opCmd cmd); 93 | in 94 | builtins.concatStringsSep "\n" (map opCat commandByCategoriesSorted) + "\n"; 95 | 96 | # These are all the options available for the commands. 97 | commandOptions = { 98 | name = mkOption { 99 | type = types.nullOr types.str; 100 | default = null; 101 | description = '' 102 | Name of this command. Defaults to attribute name in commands. 103 | ''; 104 | }; 105 | 106 | category = mkOption { 107 | type = types.str; 108 | default = "[general commands]"; 109 | description = '' 110 | Set a free text category under which this command is grouped 111 | and shown in the help menu. 112 | ''; 113 | }; 114 | 115 | help = mkOption { 116 | type = types.nullOr types.str; 117 | default = null; 118 | description = '' 119 | Describes what the command does in one line of text. 120 | ''; 121 | }; 122 | 123 | command = mkOption { 124 | type = types.nullOr types.str; 125 | default = null; 126 | description = '' 127 | If defined, it will add a script with the name of the command, and the 128 | content of this value. 129 | 130 | By default it generates a bash script, unless a different shebang is 131 | provided. 132 | ''; 133 | example = '' 134 | #!/usr/bin/env python 135 | print("Hello") 136 | ''; 137 | }; 138 | 139 | package = mkOption { 140 | type = types.nullOr strOrPackage; 141 | default = null; 142 | description = '' 143 | Used to bring in a specific package. This package will be added to the 144 | environment. 145 | ''; 146 | }; 147 | }; 148 | in 149 | { 150 | options.commands = mkOption { 151 | type = types.listOf (types.submodule { options = commandOptions; }); 152 | default = [ ]; 153 | description = '' 154 | Add commands to the environment. 155 | ''; 156 | example = literalExpression '' 157 | [ 158 | { 159 | help = "print hello"; 160 | name = "hello"; 161 | command = "echo hello"; 162 | } 163 | 164 | { 165 | package = "nixpkgs-fmt"; 166 | category = "formatter"; 167 | } 168 | ] 169 | ''; 170 | }; 171 | 172 | config.commands = [ 173 | { 174 | help = "prints this menu"; 175 | name = "menu"; 176 | command = '' 177 | cat <<'DEVSHELL_MENU' 178 | ${commandsToMenu config.commands} 179 | DEVSHELL_MENU 180 | ''; 181 | } 182 | ]; 183 | 184 | # Add the commands to the devshell packages. Either as wrapper scripts, or 185 | # the whole package. 186 | config.devshell.packages = map commandToPackage config.commands; 187 | # config.devshell.motd = "$(motd)"; 188 | } 189 | -------------------------------------------------------------------------------- /modules/default.nix: -------------------------------------------------------------------------------- 1 | # Evaluate the devshell environment 2 | nixpkgs: 3 | { 4 | configuration, 5 | lib ? nixpkgs.lib, 6 | extraSpecialArgs ? { }, 7 | }: 8 | let 9 | module = lib.evalModules ( 10 | import ./eval-args.nix { 11 | inherit lib extraSpecialArgs; 12 | pkgs = nixpkgs; 13 | modules = [ configuration ]; 14 | } 15 | ); 16 | in 17 | { 18 | inherit (module) config options; 19 | 20 | shell = module.config.devshell.shell; 21 | } 22 | -------------------------------------------------------------------------------- /modules/devshell.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | options, 6 | ... 7 | }: 8 | with lib; 9 | let 10 | cfg = config.devshell; 11 | sanitizedName = strings.sanitizeDerivationName cfg.name; 12 | 13 | ansi = import ../nix/ansi.nix; 14 | 15 | bashBin = "${cfg.bashPackage}/bin"; 16 | bashPath = "${cfg.bashPackage}/bin/bash"; 17 | 18 | # Because we want to be able to push pure JSON-like data into the 19 | # environment. 20 | strOrPackage = import ../nix/strOrPackage.nix { inherit lib pkgs; }; 21 | 22 | # Use this to define a flake app for the environment. 23 | mkFlakeApp = bin: { 24 | type = "app"; 25 | program = "${bin}"; 26 | }; 27 | 28 | mkSetupHook = 29 | rc: 30 | pkgs.stdenvNoCC.mkDerivation { 31 | name = "devshell-setup-hook"; 32 | setupHook = pkgs.writeText "devshell-setup-hook.sh" '' 33 | source ${rc} 34 | ''; 35 | dontUnpack = true; 36 | dontBuild = true; 37 | dontInstall = true; 38 | }; 39 | 40 | mkNakedShell = pkgs.callPackage ../nix/mkNakedShell.nix { }; 41 | 42 | addAttributeName = 43 | prefix: 44 | mapAttrs ( 45 | k: v: 46 | v 47 | // { 48 | text = '' 49 | #### ${prefix}.${k} 50 | ${v.text} 51 | ''; 52 | } 53 | ); 54 | 55 | entryOptions = { 56 | text = mkOption { 57 | type = types.str; 58 | description = '' 59 | Script to run. 60 | ''; 61 | }; 62 | 63 | deps = mkOption { 64 | type = types.listOf types.str; 65 | default = [ ]; 66 | description = '' 67 | A list of other steps that this one depends on. 68 | ''; 69 | }; 70 | }; 71 | 72 | # Write a bash profile to load 73 | envBash = pkgs.writeText "devshell-env.bash" '' 74 | if [[ -n ''${IN_NIX_SHELL:-} || ''${DIRENV_IN_ENVRC:-} = 1 ]]; then 75 | # We know that PWD is always the current directory in these contexts 76 | PRJ_ROOT=$PWD 77 | elif [[ -z ''${PRJ_ROOT:-} ]]; then 78 | ${lib.optionalString (cfg.prj_root_fallback != null) cfg.prj_root_fallback} 79 | 80 | if [[ -z "''${PRJ_ROOT:-}" ]]; then 81 | echo "ERROR: please set the PRJ_ROOT env var to point to the project root" >&2 82 | return 1 83 | fi 84 | fi 85 | 86 | export PRJ_ROOT 87 | 88 | # Expose the folder that contains the assembled environment. 89 | export DEVSHELL_DIR=@DEVSHELL_DIR@ 90 | 91 | # Prepend the PATH with the devshell dir and bash 92 | PATH=''${PATH%:/path-not-set} 93 | PATH=''${PATH#${bashBin}:} 94 | export PATH=$DEVSHELL_DIR/bin:${bashBin}:$PATH 95 | 96 | ${cfg.startup_env} 97 | 98 | ${textClosureMap id (addAttributeName "startup" cfg.startup) (attrNames cfg.startup)} 99 | 100 | # Interactive sessions 101 | if [[ $- == *i* ]]; then 102 | 103 | ${textClosureMap id (addAttributeName "interactive" cfg.interactive) (attrNames cfg.interactive)} 104 | 105 | fi # Interactive session 106 | ''; 107 | 108 | # This is our entrypoint script. 109 | entrypoint = pkgs.writeScript "${cfg.name}-entrypoint" '' 110 | #!${bashPath} 111 | # Script that sets-up the environment. Can be both sourced or invoked. 112 | 113 | export DEVSHELL_DIR=@DEVSHELL_DIR@ 114 | 115 | # If the file is sourced, skip all of the rest and just source the env 116 | # script. 117 | if (return 0) &>/dev/null; then 118 | source "$DEVSHELL_DIR/env.bash" 119 | return 120 | fi 121 | 122 | # Be strict! 123 | set -euo pipefail 124 | 125 | while (( "$#" > 0 )); do 126 | case "$1" in 127 | -h|--help) 128 | help=1 129 | ;; 130 | --pure) 131 | pure=1 132 | ;; 133 | --prj-root) 134 | if (( "$#" < 2 )); then 135 | echo 1>&2 '${cfg.name}: missing required argument to --prj-root' 136 | exit 1 137 | fi 138 | 139 | PRJ_ROOT="$2" 140 | 141 | shift 142 | ;; 143 | --env-bin) 144 | if (( "$#" < 2 )); then 145 | echo 1>&2 '${cfg.name}: missing required argument to --env-bin' 146 | exit 1 147 | fi 148 | 149 | env_bin="$2" 150 | 151 | shift 152 | ;; 153 | --) 154 | shift 155 | break 156 | ;; 157 | *) 158 | break 159 | ;; 160 | esac 161 | 162 | shift 163 | done 164 | 165 | if [[ -n "''${help:-}" ]]; then 166 | cat < [...] # run a command in the environment 171 | 172 | Options: 173 | * --pure : execute the script in a clean environment 174 | * --prj-root : set the project root (\$PRJ_ROOT) 175 | * --env-bin : path to the env executable (default: /usr/bin/env) 176 | USAGE 177 | exit 178 | fi 179 | 180 | if (( "$#" == 0 )); then 181 | # Start an interactive shell 182 | set -- ${lib.escapeShellArg bashPath} --rcfile "$DEVSHELL_DIR/env.bash" --noprofile 183 | fi 184 | 185 | if [[ -n "''${pure:-}" ]]; then 186 | # re-execute the script in a clean environment. 187 | # note that the `--` in between `"$0"` and `"$@"` will immediately 188 | # short-circuit options processing on the second pass through this 189 | # script, in case we get something like: 190 | # --pure -- --pure 191 | set -- "''${env_bin:-/usr/bin/env}" -i -- ''${HOME:+"HOME=''${HOME:-}"} ''${PRJ_ROOT:+"PRJ_ROOT=''${PRJ_ROOT:-}"} "$0" -- "$@" 192 | else 193 | # Start a script 194 | source "$DEVSHELL_DIR/env.bash" 195 | fi 196 | 197 | exec -- "$@" 198 | ''; 199 | 200 | # Builds the DEVSHELL_DIR with all the dependencies 201 | devshell_dir = pkgs.buildEnv rec { 202 | name = "${sanitizedName}-dir"; 203 | paths = cfg.packages; 204 | postBuild = '' 205 | substitute ${envBash} $out/env.bash --subst-var-by DEVSHELL_DIR $out 206 | substitute ${entrypoint} $out/entrypoint --subst-var-by DEVSHELL_DIR $out 207 | chmod +x $out/entrypoint 208 | 209 | mainProgram="${meta.mainProgram}" 210 | # ensure mainProgram doesn't collide 211 | if [ -e "$out/bin/$mainProgram" ]; then 212 | echo "Warning: Cannot create entry point for this devshell at '\$out/bin/$mainProgram' because an executable with that name already exists." >&2 213 | echo "Set meta.mainProgram to something else than '$mainProgram'." >&2 214 | else 215 | # if $out/bin is a single symlink, transform it into a directory tree 216 | # (buildEnv does that when there is only one package in the environment) 217 | if [ -L "$out/bin" ]; then 218 | mv "$out/bin" bin-tmp 219 | mkdir "$out/bin" 220 | ln -s bin-tmp/* "$out/bin/" 221 | fi 222 | ln -s $out/entrypoint "$out/bin/$mainProgram" 223 | fi 224 | ''; 225 | meta.mainProgram = config.meta.mainProgram or sanitizedName; 226 | }; 227 | 228 | # Returns a list of all the input derivation ... for a derivation. 229 | inputsOf = 230 | drv: 231 | filter lib.isDerivation ( 232 | (drv.buildInputs or [ ]) 233 | ++ (drv.nativeBuildInputs or [ ]) 234 | ++ (drv.propagatedBuildInputs or [ ]) 235 | ++ (drv.propagatedNativeBuildInputs or [ ]) 236 | ); 237 | 238 | in 239 | { 240 | options.devshell = { 241 | bashPackage = mkOption { 242 | internal = true; 243 | type = strOrPackage; 244 | default = pkgs.bashInteractive; 245 | defaultText = "pkgs.bashInteractive"; 246 | description = "Version of bash to use in the project"; 247 | }; 248 | 249 | package = mkOption { 250 | internal = true; 251 | type = types.package; 252 | description = '' 253 | This package contains the DEVSHELL_DIR 254 | ''; 255 | }; 256 | 257 | startup = mkOption { 258 | type = types.attrsOf (types.submodule { options = entryOptions; }); 259 | default = { }; 260 | internal = true; 261 | description = '' 262 | A list of scripts to execute on startup. 263 | ''; 264 | }; 265 | 266 | startup_env = mkOption { 267 | type = types.str; 268 | default = ""; 269 | internal = true; 270 | description = '' 271 | Please ignore. Used by the env module. 272 | ''; 273 | }; 274 | 275 | interactive = mkOption { 276 | type = types.attrsOf (types.submodule { options = entryOptions; }); 277 | default = { }; 278 | internal = true; 279 | description = '' 280 | A list of scripts to execute on interactive startups. 281 | ''; 282 | }; 283 | 284 | # TODO: rename motd to something better. 285 | motd = mkOption { 286 | type = types.str; 287 | default = '' 288 | {202}🔨 Welcome to ${cfg.name}{reset} 289 | $(type -p menu &>/dev/null && menu) 290 | ''; 291 | apply = replaceStrings (map (key: "{${key}}") (attrNames ansi)) (attrValues ansi); 292 | description = '' 293 | Message Of The Day. 294 | 295 | This is the welcome message that is being printed when the user opens 296 | the shell. 297 | 298 | You may use any valid ansi color from the 8-bit ansi color table. For example, to use a green color you would use something like {106}. You may also use {bold}, {italic}, {underline}. Use {reset} to turn off all attributes. 299 | ''; 300 | }; 301 | 302 | load_profiles = mkEnableOption "load etc/profiles.d/*.sh in the shell"; 303 | 304 | name = mkOption { 305 | type = types.str; 306 | default = "devshell"; 307 | description = '' 308 | Name of the shell environment. It usually maps to the project name. 309 | ''; 310 | }; 311 | 312 | meta = mkOption { 313 | type = types.attrsOf types.anything; 314 | default = { }; 315 | description = '' 316 | Metadata, such as 'meta.description'. Can be useful as metadata for downstream tooling. 317 | ''; 318 | }; 319 | 320 | packages = mkOption { 321 | type = types.listOf strOrPackage; 322 | default = [ ]; 323 | description = '' 324 | The set of packages to appear in the project environment. 325 | 326 | Those packages come from and can be 327 | searched by going to 328 | ''; 329 | }; 330 | 331 | packagesFrom = mkOption { 332 | type = types.listOf strOrPackage; 333 | default = [ ]; 334 | description = '' 335 | Add all the build dependencies from the listed packages to the 336 | environment. 337 | ''; 338 | }; 339 | 340 | shell = mkOption { 341 | internal = true; 342 | type = types.package; 343 | description = "TODO"; 344 | }; 345 | 346 | prj_root_fallback = mkOption { 347 | type = 348 | let 349 | envType = options.env.type.nestedTypes.elemType; 350 | coerceFunc = value: { inherit value; }; 351 | in 352 | types.nullOr (types.coercedTo types.nonEmptyStr coerceFunc envType); 353 | apply = x: if x == null then x else x // { name = "PRJ_ROOT"; }; 354 | default = { 355 | eval = "$PWD"; 356 | }; 357 | example = lib.literalExpression '' 358 | { 359 | # Use the top-level directory of the working tree 360 | eval = "$(git rev-parse --show-toplevel)"; 361 | }; 362 | ''; 363 | description = '' 364 | If IN_NIX_SHELL is nonempty, or DIRENV_IN_ENVRC is set to '1', then 365 | PRJ_ROOT is set to the value of PWD. 366 | 367 | This option specifies the path to use as the value of PRJ_ROOT in case 368 | IN_NIX_SHELL is empty or unset and DIRENV_IN_ENVRC is any value other 369 | than '1'. 370 | 371 | Set this to null to force PRJ_ROOT to be defined at runtime (except if 372 | IN_NIX_SHELL or DIRENV_IN_ENVRC are defined as described above). 373 | 374 | Otherwise, you can set this to a string representing the desired 375 | default path, or to a submodule of the same type valid in the 'env' 376 | options list (except that the 'name' field is ignored). 377 | ''; 378 | }; 379 | }; 380 | 381 | config.devshell = { 382 | package = devshell_dir; 383 | 384 | packages = foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] cfg.packagesFrom; 385 | 386 | startup = 387 | { 388 | motd = noDepEntry '' 389 | __devshell-motd() { 390 | cat < 0 81 | ) "[[environ]]: ${name} expected one of (value|eval|prefix|unset) to be set."; 82 | assert assertMsg ((length vals) < 2) 83 | "[[environ]]: ${name} expected only one of (value|eval|prefix|unset) to be set. Not ${toString vals}"; 84 | assert assertMsg ( 85 | !(name == "PATH" && valType == "value") 86 | ) "[[environ]]: ${name} should not override the value. Use 'prefix' instead."; 87 | if valType == "value" then 88 | "export ${name}=${escapeShellArg (toString value)}" 89 | else if valType == "eval" then 90 | "export ${name}=${eval}" 91 | else if valType == "prefix" then 92 | ''export ${name}=$(${pkgs.coreutils}/bin/realpath --canonicalize-missing "${prefix}")''${${name}+:''${${name}}}'' 93 | else if valType == "unset" then 94 | ''unset ${name}'' 95 | else 96 | throw "BUG in the env.nix module. This should never be reached."; 97 | in 98 | { 99 | options.env = mkOption { 100 | type = types.listOf (types.submodule { options = envOptions; }); 101 | default = [ ]; 102 | description = '' 103 | Add environment variables to the shell. 104 | ''; 105 | example = literalExpression '' 106 | [ 107 | { 108 | name = "HTTP_PORT"; 109 | value = 8080; 110 | } 111 | { 112 | name = "PATH"; 113 | prefix = "bin"; 114 | } 115 | { 116 | name = "XDG_CACHE_DIR"; 117 | eval = "$PRJ_ROOT/.cache"; 118 | } 119 | { 120 | name = "CARGO_HOME"; 121 | unset = true; 122 | } 123 | ] 124 | ''; 125 | }; 126 | 127 | config = { 128 | # Default env 129 | env = lib.mkBefore [ 130 | # Expose the path to nixpkgs 131 | { 132 | name = "NIXPKGS_PATH"; 133 | value = toString pkgs.path; 134 | } 135 | 136 | # This is used by bash-completions to find new completions on demand 137 | { 138 | name = "XDG_DATA_DIRS"; 139 | eval = ''$DEVSHELL_DIR/share:''${XDG_DATA_DIRS:-/usr/local/share:/usr/share}''; 140 | } 141 | 142 | # A per-project data directory for runtime information. 143 | { 144 | name = "PRJ_DATA_DIR"; 145 | eval = "\${PRJ_DATA_DIR:-$PRJ_ROOT/.data}"; 146 | } 147 | ]; 148 | 149 | devshell.startup_env = concatStringsSep "\n" config.env; 150 | }; 151 | } 152 | -------------------------------------------------------------------------------- /modules/eval-args.nix: -------------------------------------------------------------------------------- 1 | # Arguments for `lib.evalModules` or `types.submoduleWith`. 2 | { 3 | pkgs, 4 | lib, 5 | modules ? [ ], 6 | extraSpecialArgs ? { }, 7 | }: 8 | let 9 | devenvModules = import ./modules.nix { inherit lib pkgs; }; 10 | in 11 | { 12 | modules = (lib.toList modules) ++ devenvModules; 13 | specialArgs = { 14 | modulesPath = builtins.toString ./.; 15 | extraModulesPath = builtins.toString ../extra; 16 | } // extraSpecialArgs; 17 | } 18 | -------------------------------------------------------------------------------- /modules/modules-docs.nix: -------------------------------------------------------------------------------- 1 | # MIT - Copyright (c) 2017-2019 Robert Helgesson and Home Manager contributors. 2 | # 3 | # This is an adapted version of the original https://gitlab.com/rycee/nmd/ 4 | { 5 | lib, 6 | pkgs, 7 | options, 8 | config, 9 | modulesPath, 10 | ... 11 | }: 12 | with lib; 13 | let 14 | cfg = config.modules-docs; 15 | 16 | # Generate some meta data for a list of packages. This is what 17 | # `relatedPackages` option of `mkOption` lib/options.nix influences. 18 | # 19 | # Each element of `relatedPackages` can be either 20 | # - a string: that will be interpreted as an attribute name from `pkgs`, 21 | # - a list: that will be interpreted as an attribute path from `pkgs`, 22 | # - an attrset: that can specify `name`, `path`, `package`, `comment` 23 | # (either of `name`, `path` is required, the rest are optional). 24 | mkRelatedPackages = 25 | let 26 | unpack = 27 | p: 28 | if isString p then 29 | { name = p; } 30 | else if isList p then 31 | { path = p; } 32 | else 33 | p; 34 | 35 | repack = 36 | args: 37 | let 38 | name = args.name or (concatStringsSep "." args.path); 39 | path = args.path or [ args.name ]; 40 | pkg = 41 | args.package or ( 42 | let 43 | bail = throw "Invalid package attribute path '${toString path}'"; 44 | in 45 | attrByPath path bail pkgs 46 | ); 47 | in 48 | { 49 | attrName = name; 50 | packageName = pkg.meta.name; 51 | available = pkg.meta.available; 52 | } 53 | // optionalAttrs (pkg.meta ? description) { inherit (pkg.meta) description; } 54 | // optionalAttrs (pkg.meta ? longDescription) { inherit (pkg.meta) longDescription; } 55 | // optionalAttrs (args ? comment) { inherit (args) comment; }; 56 | in 57 | map (p: repack (unpack p)); 58 | 59 | # Transforms a module path into a (path, url) tuple where path is relative 60 | # to the repo root, and URL points to an online view of the module. 61 | mkDeclaration = 62 | let 63 | rootsWithPrefixes = map (p: p // { prefix = "${toString p.path}/"; }) cfg.roots; 64 | in 65 | decl: 66 | let 67 | root = lib.findFirst (x: lib.hasPrefix x.prefix decl) null rootsWithPrefixes; 68 | in 69 | if root == null then 70 | # We need to strip references to /nix/store/* from the options or 71 | # else the build will fail. 72 | { 73 | path = removePrefix "${builtins.storeDir}/" decl; 74 | url = ""; 75 | } 76 | else 77 | rec { 78 | path = removePrefix root.prefix decl; 79 | url = "${root.url}/tree/${root.branch}/${path}"; 80 | }; 81 | 82 | # Sort modules and put "enable" and "package" declarations first. 83 | moduleDocCompare = 84 | a: b: 85 | let 86 | isEnable = lib.hasPrefix "enable"; 87 | isPackage = lib.hasPrefix "package"; 88 | compareWithPrio = pred: cmp: splitByAndCompare pred compare cmp; 89 | moduleCmp = compareWithPrio isEnable (compareWithPrio isPackage compare); 90 | in 91 | compareLists moduleCmp a.loc b.loc < 0; 92 | 93 | # Replace functions by the string 94 | substFunction = 95 | x: 96 | if builtins.isAttrs x then 97 | mapAttrs (name: substFunction) x 98 | else if builtins.isList x then 99 | map substFunction x 100 | else if isFunction x then 101 | "" 102 | else 103 | x; 104 | 105 | cleanUpOption = 106 | opt: 107 | let 108 | applyOnAttr = n: f: optionalAttrs (hasAttr n opt) { ${n} = f opt.${n}; }; 109 | in 110 | opt 111 | // applyOnAttr "declarations" (map mkDeclaration) 112 | // applyOnAttr "example" substFunction 113 | // applyOnAttr "default" substFunction 114 | // applyOnAttr "type" substFunction 115 | // applyOnAttr "relatedPackages" mkRelatedPackages; 116 | 117 | optionsDocs = map cleanUpOption ( 118 | sort moduleDocCompare (filter (opt: opt.visible && !opt.internal) (optionAttrSetToDocList options)) 119 | ); 120 | 121 | # TODO: display values like TOML instead. 122 | toMarkdown = 123 | optionsDocs: 124 | let 125 | optionsDocsPartitioned = partition (opt: head opt.loc != "_module") optionsDocs; 126 | 127 | # TODO: handle opt.relatedPackages. What is it for? 128 | optToMd = 129 | opt: 130 | let 131 | heading = lib.showOption opt.loc; 132 | in 133 | '' 134 | ### `${heading}` 135 | 136 | '' 137 | + (lib.optionalString opt.internal "\n**internal**\n") 138 | + opt.description 139 | + '' 140 | 141 | **Type**: 142 | 143 | ```console 144 | ${opt.type} 145 | ``` 146 | '' 147 | + (lib.optionalString (opt ? default && opt.default != null) '' 148 | 149 | **Default value**: 150 | 151 | ```nix 152 | ${removeSuffix "\n" opt.default.text} 153 | ``` 154 | '') 155 | + (lib.optionalString (opt ? example) '' 156 | 157 | **Example value**: 158 | 159 | ```nix 160 | ${removeSuffix "\n" opt.example.text} 161 | ``` 162 | '') 163 | + '' 164 | 165 | **Declared in**: 166 | 167 | '' 168 | + (lib.concatStringsSep "\n" (map (decl: "- [${decl.path}](${decl.url})") opt.declarations)) 169 | + "\n"; 170 | doc = [ 171 | "## Options\n" 172 | (concatStringsSep "\n" (map optToMd optionsDocsPartitioned.right)) 173 | "## Extra options\n" 174 | (concatStringsSep "\n" (map optToMd optionsDocsPartitioned.wrong)) 175 | ]; 176 | in 177 | concatStringsSep "\n" doc; 178 | in 179 | { 180 | options.modules-docs = { 181 | roots = mkOption { 182 | internal = true; 183 | type = types.listOf types.attrs; 184 | description = '' 185 | Add to this list for each new module root. The attr should have path, 186 | url and branch attributes (TODO: convert to submodule). 187 | ''; 188 | }; 189 | 190 | data = mkOption { 191 | visible = false; 192 | type = types.listOf types.attrs; 193 | description = '' 194 | Contains a list of each module option, nicely split out for 195 | consumption. 196 | ''; 197 | }; 198 | 199 | markdown = mkOption { 200 | visible = false; 201 | type = types.package; 202 | description = '' 203 | Modules documentation rendered to markdown. 204 | ''; 205 | }; 206 | }; 207 | 208 | config.modules-docs = { 209 | data = optionsDocs; 210 | markdown = pkgs.writeText "modules-docs.md" (toMarkdown optionsDocs); 211 | }; 212 | } 213 | -------------------------------------------------------------------------------- /modules/modules.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib }: 2 | let 3 | modules = [ 4 | ./back-compat.nix 5 | ./commands.nix 6 | ./devshell.nix 7 | ./env.nix 8 | ./modules-docs.nix 9 | { 10 | # Configure modules-docs 11 | config.modules-docs.roots = [ 12 | { 13 | url = "https://github.com/numtide/devshell"; 14 | path = toString ../.; 15 | branch = "main"; 16 | } 17 | ]; 18 | } 19 | ./services.nix 20 | ]; 21 | 22 | pkgsModule = 23 | { config, ... }: 24 | { 25 | config = { 26 | _module.args.baseModules = modules; 27 | _module.args.pkgsPath = lib.mkDefault pkgs.path; 28 | _module.args.pkgs = lib.mkDefault pkgs; 29 | }; 30 | }; 31 | in 32 | [ pkgsModule ] ++ modules 33 | -------------------------------------------------------------------------------- /modules/services.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; 8 | let 9 | serviceOptions = { 10 | name = mkOption { 11 | type = types.nullOr types.str; 12 | default = null; 13 | description = '' 14 | Name of this service. Defaults to attribute name in group services. 15 | ''; 16 | }; 17 | command = mkOption { 18 | type = types.str; 19 | description = '' 20 | Command to execute. 21 | ''; 22 | }; 23 | }; 24 | groupOptions = { 25 | name = mkOption { 26 | type = types.nullOr types.str; 27 | default = null; 28 | description = '' 29 | Name of the service group. Defaults to attribute name in groups. 30 | ''; 31 | }; 32 | description = mkOption { 33 | type = types.nullOr types.str; 34 | default = null; 35 | description = '' 36 | Short description of the service group, shown in generated commands 37 | ''; 38 | }; 39 | services = mkOption { 40 | type = types.attrsOf (types.submodule { options = serviceOptions; }); 41 | default = { }; 42 | description = '' 43 | Attrset of services that should be run in this group. 44 | ''; 45 | }; 46 | }; 47 | groupToProcfile = 48 | name: g: 49 | pkgs.writeText "Procfile.${name}" ( 50 | concatLines ( 51 | mapAttrsToList (sName: s: "${if s.name == null then sName else s.name}: ${s.command}") g.services 52 | ) 53 | ); 54 | groupToCommands = 55 | gName: g: 56 | let 57 | procfile = groupToProcfile gName g; 58 | description = if g.description == null then gName else g.description; 59 | in 60 | [ 61 | { 62 | name = "${gName}:start"; 63 | category = "service groups"; 64 | help = "Start ${description} services"; 65 | command = 66 | (pkgs.writeShellScript "${gName}-services-start" '' 67 | if [ -e "$PRJ_DATA_DIR/pids/${gName}.pid" ]; then 68 | echo "Already running, refusing to start" 69 | exit 1 70 | fi 71 | mkdir -p "$PRJ_DATA_DIR/pids/" 72 | ${pkgs.honcho}/bin/honcho start -f ${procfile} -d "$PRJ_ROOT" & 73 | pid=$! 74 | echo $pid > "$PRJ_DATA_DIR/pids/${gName}.pid" 75 | on_stop() { 76 | if ps -p $pid > /dev/null; then 77 | kill -TERM $pid 78 | fi 79 | rm "$PRJ_DATA_DIR/pids/${gName}.pid" 80 | wait $pid 81 | } 82 | trap "on_stop" SIGINT SIGTERM SIGHUP EXIT 83 | wait $pid 84 | '').outPath; 85 | } 86 | { 87 | name = "${gName}:stop"; 88 | category = "service groups"; 89 | help = "Stop ${description} services"; 90 | command = 91 | (pkgs.writeShellScript "${gName}-services-stop" '' 92 | if [ -e "$PRJ_DATA_DIR/pids/${gName}.pid" ]; then 93 | pid=$(cat "$PRJ_DATA_DIR/pids/${gName}.pid") 94 | kill -TERM $pid 95 | rm "$PRJ_DATA_DIR/pids/${gName}.pid" 96 | fi 97 | '').outPath; 98 | } 99 | ]; 100 | in 101 | { 102 | options.serviceGroups = mkOption { 103 | type = types.attrsOf (types.submodule { options = groupOptions; }); 104 | default = { }; 105 | description = '' 106 | Add services to the environment. Services can be used to group long-running processes. 107 | ''; 108 | }; 109 | 110 | config.commands = foldl (l: r: l ++ r) [ ] ( 111 | mapAttrsToList ( 112 | gName: g: groupToCommands (if g.name == null then gName else g.name) g 113 | ) config.serviceGroups 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /nix/ansi.nix: -------------------------------------------------------------------------------- 1 | # Ansi escape codes 2 | let 3 | # Nix strings only support \t, \r and \n as escape codes, so actually store 4 | # the literal escape "ESC" code. 5 | inherit (builtins) foldl' genList; 6 | esc = ""; 7 | in 8 | { 9 | reset = "${esc}[0m"; 10 | bold = "${esc}[1m"; 11 | italic = "${esc}[3m"; 12 | underline = "${esc}[4m"; 13 | } 14 | // (foldl' (x: y: x // { "${toString y}" = "${esc}[38;5;${toString y}m"; }) { } ( 15 | genList (x: x) 256 16 | )) 17 | -------------------------------------------------------------------------------- /nix/importTOML.nix: -------------------------------------------------------------------------------- 1 | let 2 | importTOML = 3 | file: 4 | # Return a module that gets lib as an argument 5 | { lib, ... }: 6 | let 7 | dir = toString (builtins.dirOf file); 8 | data = builtins.fromTOML (builtins.readFile file); 9 | 10 | extraModulesPath = toString ../extra; 11 | extraModules = builtins.readDir extraModulesPath; 12 | 13 | importModule = 14 | str: 15 | let 16 | repoFile = "${dir}/${str}"; 17 | extraFile = "${extraModulesPath}/${builtins.replaceStrings [ "." ] [ "/" ] str}.nix"; 18 | in 19 | # First try to import from the user's repository 20 | if lib.hasPrefix "./" str || lib.hasSuffix ".nix" str || lib.hasSuffix ".toml" str then 21 | (if lib.hasSuffix ".toml" str then importTOML repoFile else import repoFile) 22 | # Then fallback on the extra modules 23 | else 24 | import extraFile; 25 | in 26 | { 27 | _file = file; 28 | imports = map importModule (data.imports or [ ]); 29 | config = builtins.removeAttrs data [ "imports" ]; 30 | }; 31 | in 32 | importTOML 33 | -------------------------------------------------------------------------------- /nix/mkNakedShell.nix: -------------------------------------------------------------------------------- 1 | { 2 | bashInteractive, 3 | coreutils, 4 | stdenv, 5 | writeTextFile, 6 | }: 7 | let 8 | bashPath = "${bashInteractive}/bin/bash"; 9 | nakedStdenv = writeTextFile { 10 | name = "naked-stdenv"; 11 | destination = "/setup"; 12 | text = '' 13 | # Fix for `nix develop` 14 | : ''${outputs:=out} 15 | 16 | runHook() { 17 | eval "$shellHook" 18 | unset runHook 19 | } 20 | ''; 21 | }; 22 | in 23 | { 24 | name, 25 | # A path to a buildEnv that will be loaded by the shell. 26 | # We assume that the buildEnv contains an ./env.bash script. 27 | profile, 28 | meta ? { }, 29 | passthru ? { }, 30 | }: 31 | (derivation { 32 | inherit name; 33 | 34 | system = stdenv.hostPlatform.system; 35 | 36 | # `nix develop` actually checks and uses builder. And it must be bash. 37 | builder = bashPath; 38 | 39 | # Bring in the dependencies on `nix-build` 40 | args = [ 41 | "-ec" 42 | "${coreutils}/bin/ln -s ${profile} $out; exit 0" 43 | ]; 44 | 45 | # $stdenv/setup is loaded by nix-shell during startup. 46 | # https://github.com/nixos/nix/blob/377345e26f1ac4bbc87bb21debcc52a1d03230aa/src/nix-build/nix-build.cc#L429-L432 47 | stdenv = nakedStdenv; 48 | 49 | # The shellHook is loaded directly by `nix develop`. But nix-shell 50 | # requires that other trampoline. 51 | shellHook = '' 52 | # Remove all the unnecessary noise that is set by the build env 53 | unset NIX_BUILD_TOP NIX_BUILD_CORES NIX_STORE 54 | unset TEMP TEMPDIR TMP TMPDIR 55 | # $name variable is preserved to keep it compatible with pure shell https://github.com/sindresorhus/pure/blob/47c0c881f0e7cfdb5eaccd335f52ad17b897c060/pure.zsh#L235 56 | unset builder out shellHook stdenv system 57 | # Flakes stuff 58 | unset dontAddDisableDepTrack outputs 59 | 60 | # For `nix develop`. We get /noshell on Linux and /sbin/nologin on macOS. 61 | if [[ "$SHELL" == "/noshell" || "$SHELL" == "/sbin/nologin" ]]; then 62 | export SHELL=${bashPath} 63 | fi 64 | 65 | # Load the environment 66 | source "${profile}/env.bash" 67 | ''; 68 | }) 69 | // { 70 | inherit meta passthru; 71 | } 72 | // passthru 73 | -------------------------------------------------------------------------------- /nix/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | # nixpkgs is only used for development. Don't add it to the flake.lock. 3 | gitRev = "2c2a09678ce2ce4125591ac4fe2f7dfaec7a609c"; 4 | in 5 | builtins.fetchTarball { 6 | url = "https://github.com/NixOS/nixpkgs/archive/${gitRev}.tar.gz"; 7 | sha256 = "1pkz5bq8f5p9kxkq3142lrrq1592d7zdi75fqzrf02cl1xy2cwvn"; 8 | } 9 | -------------------------------------------------------------------------------- /nix/source.nix: -------------------------------------------------------------------------------- 1 | # A standalone source filtering library 2 | let 3 | inherit (builtins) 4 | any 5 | isFunction 6 | isString 7 | isPath 8 | map 9 | stringLength 10 | substring 11 | ; 12 | 13 | # Copied from the nixpkgs stdlib 14 | hasSuffix = 15 | # Suffix to check for 16 | suffix: 17 | # Input string 18 | content: 19 | let 20 | lenContent = stringLength content; 21 | lenSuffix = stringLength suffix; 22 | in 23 | lenContent >= lenSuffix && substring (lenContent - lenSuffix) lenContent content == suffix; 24 | 25 | # If an argument to allow or deny is a path, transform it to a matcher. 26 | # 27 | # This probably needs more work, I don't think that it works on sub-folders. 28 | toMatcher = 29 | f: 30 | let 31 | path_ = toString f; 32 | in 33 | if isFunction f then f else (path: type: path_ == toString path); 34 | in 35 | { 36 | # Match paths with the given extension 37 | matchExt = 38 | ext: path: type: 39 | (hasSuffix ".${ext}" path); 40 | 41 | # A proper filter 42 | filter = 43 | { 44 | path, 45 | name ? "source", 46 | allow ? [ ], 47 | deny ? [ ], 48 | }: 49 | let 50 | allow_ = builtins.map toMatcher allow; 51 | deny_ = builtins.map toMatcher deny; 52 | in 53 | builtins.path { 54 | inherit name path; 55 | filter = 56 | path: type: (builtins.any (f: f path type) allow_) && (!builtins.any (f: f path type) deny_); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /nix/strOrPackage.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs }: 2 | with lib; 3 | let 4 | resolveKey = 5 | key: 6 | let 7 | attrs = builtins.filter builtins.isString (builtins.split "\\." key); 8 | op = sum: attr: sum.${attr} or (throw "package \"${key}\" not found"); 9 | in 10 | builtins.foldl' op pkgs attrs; 11 | in 12 | # Because we want to be able to push pure JSON-like data into the environment. 13 | types.coercedTo types.str resolveKey types.package 14 | -------------------------------------------------------------------------------- /nix/writeDefaultShellScript.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | writeTextFile, 4 | bash, 5 | }: 6 | 7 | /* 8 | Similar to writeShellScript, except that a default shebang can be provided 9 | 10 | Either the script already has a shebang, or one will be provided for it. 11 | */ 12 | { 13 | name, 14 | text, 15 | defaultShebang ? "#!${bash}/bin/bash\nset -euo pipefail\n", 16 | checkPhase ? null, 17 | binPrefix ? false, 18 | }: 19 | let 20 | script = if lib.hasPrefix "#!" text then text else "${defaultShebang}\n${text}"; 21 | in 22 | writeTextFile ( 23 | { 24 | inherit name; 25 | text = script; 26 | executable = true; 27 | } 28 | // (lib.optionalAttrs (checkPhase != null) { inherit checkPhase; }) 29 | // (lib.optionalAttrs binPrefix { destination = "/bin/${name}"; }) 30 | ) 31 | -------------------------------------------------------------------------------- /overlay.nix: -------------------------------------------------------------------------------- 1 | # Import this overlay in your project to add devshell 2 | final: prev: { devshell = import ./. { nixpkgs = final; }; } 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-build 2 | # Used to test the shell 3 | { 4 | system ? builtins.currentSystem, 5 | }: 6 | let 7 | devshell = import ./. { inherit system; }; 8 | in 9 | devshell.fromTOML ./devshell.toml 10 | -------------------------------------------------------------------------------- /templates/flake-parts/.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ^ added for shellcheck and file-type detection 3 | if [[ $(type -t use_flake) != function ]]; then 4 | echo "ERROR: use_flake function missing." 5 | echo "Please update direnv to v2.30.0 or later." 6 | exit 1 7 | fi 8 | use flake -------------------------------------------------------------------------------- /templates/flake-parts/.gitignore: -------------------------------------------------------------------------------- 1 | /.direnv/ -------------------------------------------------------------------------------- /templates/flake-parts/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs", 6 | "systems": "systems" 7 | }, 8 | "locked": { 9 | "lastModified": 1692793255, 10 | "narHash": "sha256-yVyj0AE280JkccDHuG1XO9oGxN6bW8ksr/xttXcXzK0=", 11 | "owner": "numtide", 12 | "repo": "devshell", 13 | "rev": "2aa26972b951bc05c3632d4e5ae683cb6771a7c6", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "numtide", 18 | "repo": "devshell", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-parts": { 23 | "inputs": { 24 | "nixpkgs-lib": "nixpkgs-lib" 25 | }, 26 | "locked": { 27 | "lastModified": 1693611461, 28 | "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=", 29 | "owner": "hercules-ci", 30 | "repo": "flake-parts", 31 | "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "hercules-ci", 36 | "repo": "flake-parts", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1677383253, 43 | "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "NixOS", 51 | "ref": "nixpkgs-unstable", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs-lib": { 57 | "locked": { 58 | "dir": "lib", 59 | "lastModified": 1693471703, 60 | "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=", 61 | "owner": "NixOS", 62 | "repo": "nixpkgs", 63 | "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "dir": "lib", 68 | "owner": "NixOS", 69 | "ref": "nixos-unstable", 70 | "repo": "nixpkgs", 71 | "type": "github" 72 | } 73 | }, 74 | "nixpkgs_2": { 75 | "locked": { 76 | "lastModified": 1693626178, 77 | "narHash": "sha256-Rpiy6lIOu4zny8tfGuIeN1ji9eSz9nPmm9yBhh/4IOM=", 78 | "owner": "NixOS", 79 | "repo": "nixpkgs", 80 | "rev": "bfb7dfec93f3b5d7274db109f2990bc889861caf", 81 | "type": "github" 82 | }, 83 | "original": { 84 | "id": "nixpkgs", 85 | "type": "indirect" 86 | } 87 | }, 88 | "root": { 89 | "inputs": { 90 | "devshell": "devshell", 91 | "flake-parts": "flake-parts", 92 | "nixpkgs": "nixpkgs_2" 93 | } 94 | }, 95 | "systems": { 96 | "locked": { 97 | "lastModified": 1681028828, 98 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 99 | "owner": "nix-systems", 100 | "repo": "default", 101 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 102 | "type": "github" 103 | }, 104 | "original": { 105 | "owner": "nix-systems", 106 | "repo": "default", 107 | "type": "github" 108 | } 109 | } 110 | }, 111 | "root": "root", 112 | "version": 7 113 | } 114 | -------------------------------------------------------------------------------- /templates/flake-parts/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "virtual environments"; 3 | 4 | inputs.devshell.url = "github:numtide/devshell"; 5 | inputs.flake-parts.url = "github:hercules-ci/flake-parts"; 6 | 7 | outputs = 8 | inputs@{ 9 | self, 10 | flake-parts, 11 | devshell, 12 | nixpkgs, 13 | }: 14 | flake-parts.lib.mkFlake { inherit inputs; } { 15 | imports = [ devshell.flakeModule ]; 16 | 17 | systems = [ 18 | "aarch64-darwin" 19 | "aarch64-linux" 20 | "i686-linux" 21 | "x86_64-darwin" 22 | "x86_64-linux" 23 | ]; 24 | 25 | perSystem = 26 | { pkgs, ... }: 27 | { 28 | devshells.default = ( 29 | { extraModulesPath, ... }@args: 30 | { 31 | # `extraModulesPath` provides access to additional modules that are 32 | # not included in the standard devshell modules list. 33 | # 34 | # Please see https://numtide.github.io/devshell/extending.html for 35 | # documentation on consuming extra modules, and see 36 | # https://github.com/numtide/devshell/tree/main/extra for the 37 | # extra modules that are currently available. 38 | imports = [ "${extraModulesPath}/git/hooks.nix" ]; 39 | 40 | git.hooks.enable = false; 41 | git.hooks.pre-commit.text = '' 42 | echo 1>&2 'time to implement a pre-commit hook!' 43 | exit 1 44 | ''; 45 | } 46 | ); 47 | }; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /templates/gettingStartedExample/.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ^ added for shellcheck and file-type detection 3 | 4 | # Watch & reload direnv on change 5 | watch_file devshell.toml 6 | 7 | if [[ $(type -t use_flake) != function ]]; then 8 | echo "ERROR: use_flake function missing." 9 | echo "Please update direnv to v2.30.0 or later." 10 | exit 1 11 | fi 12 | use flake -------------------------------------------------------------------------------- /templates/gettingStartedExample/.gitignore: -------------------------------------------------------------------------------- 1 | /.direnv/ 2 | /pgdata/* 3 | /.data/ 4 | -------------------------------------------------------------------------------- /templates/gettingStartedExample/devshell.toml: -------------------------------------------------------------------------------- 1 | # https://numtide.github.io/devshell 2 | # https://numtide.github.io/devshell/getting_started.html 3 | [[commands]] 4 | package = "hello" 5 | 6 | [[commands]] 7 | package = "go" 8 | 9 | [[commands]] 10 | package = "nodejs_20" 11 | 12 | [[commands]] 13 | package = "python311" 14 | 15 | [[env]] 16 | name = "GO111MODULE" 17 | value = "on" 18 | 19 | [devshell] 20 | packages = [ 21 | "postgresql_15", 22 | "memcached", 23 | ] 24 | 25 | [[commands]] 26 | name = "initPostgres" 27 | help = "Initialize the Postgres database" 28 | command = """\ 29 | initdb pgdata; \ 30 | chmod -R 700 pgdata; \ 31 | echo -e "Use the devshell command 'database:start'" 32 | """ 33 | 34 | [serviceGroups.database] 35 | description = "Runs a database in the background" 36 | [serviceGroups.database.services.postgres] 37 | command = "postgres -D ./pgdata" 38 | [serviceGroups.database.services.memcached] 39 | command = "memcached" 40 | -------------------------------------------------------------------------------- /templates/gettingStartedExample/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs", 6 | "systems": "systems" 7 | }, 8 | "locked": { 9 | "lastModified": 1692793255, 10 | "narHash": "sha256-yVyj0AE280JkccDHuG1XO9oGxN6bW8ksr/xttXcXzK0=", 11 | "owner": "numtide", 12 | "repo": "devshell", 13 | "rev": "2aa26972b951bc05c3632d4e5ae683cb6771a7c6", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "numtide", 18 | "repo": "devshell", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-utils": { 23 | "inputs": { 24 | "systems": "systems_2" 25 | }, 26 | "locked": { 27 | "lastModified": 1692799911, 28 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "flake-utils", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1677383253, 43 | "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "NixOS", 51 | "ref": "nixpkgs-unstable", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs_2": { 57 | "locked": { 58 | "lastModified": 1693563790, 59 | "narHash": "sha256-qUx+8lQSCiPXbwWBwRHynHuhQT+6I7kEuDFFNQ6RSPU=", 60 | "owner": "NixOS", 61 | "repo": "nixpkgs", 62 | "rev": "fd8ad63083882605e8a7f659be6c9509e6e28d28", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "id": "nixpkgs", 67 | "type": "indirect" 68 | } 69 | }, 70 | "root": { 71 | "inputs": { 72 | "devshell": "devshell", 73 | "flake-utils": "flake-utils", 74 | "nixpkgs": "nixpkgs_2" 75 | } 76 | }, 77 | "systems": { 78 | "locked": { 79 | "lastModified": 1681028828, 80 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 81 | "owner": "nix-systems", 82 | "repo": "default", 83 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "nix-systems", 88 | "repo": "default", 89 | "type": "github" 90 | } 91 | }, 92 | "systems_2": { 93 | "locked": { 94 | "lastModified": 1681028828, 95 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 96 | "owner": "nix-systems", 97 | "repo": "default", 98 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 99 | "type": "github" 100 | }, 101 | "original": { 102 | "owner": "nix-systems", 103 | "repo": "default", 104 | "type": "github" 105 | } 106 | } 107 | }, 108 | "root": "root", 109 | "version": 7 110 | } 111 | -------------------------------------------------------------------------------- /templates/gettingStartedExample/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "getting started example"; 3 | 4 | inputs.devshell.url = "github:numtide/devshell"; 5 | inputs.flake-utils.url = "github:numtide/flake-utils"; 6 | 7 | outputs = 8 | { 9 | self, 10 | flake-utils, 11 | devshell, 12 | nixpkgs, 13 | }: 14 | flake-utils.lib.eachDefaultSystem (system: { 15 | devShell = 16 | let 17 | pkgs = import nixpkgs { 18 | inherit system; 19 | 20 | overlays = [ devshell.overlays.default ]; 21 | }; 22 | in 23 | pkgs.devshell.mkShell { imports = [ (pkgs.devshell.importTOML ./devshell.toml) ]; }; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /templates/gettingStartedExample/readme.md: -------------------------------------------------------------------------------- 1 | 2 | [Getting Started](https://numtide.github.io/devshell/getting_started.html) 3 | 4 | This example will run Postgres and Memcached natively on your machine. 5 | 6 | You may have to be a member of the `postgres` group to allow Postgres to start. Otherwise, Postgres may be unable to create its lockfile. 7 | 8 | First create the Postgres database with the devshell command `initPostgres`. This is defined in the TOML. It uses Postgres command `initdb`. 9 | 10 | Then the devshell command `database:start`. 11 | -------------------------------------------------------------------------------- /templates/toml/.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ^ added for shellcheck and file-type detection 3 | 4 | # Watch & reload direnv on change 5 | watch_file devshell.toml 6 | 7 | if [[ $(type -t use_flake) != function ]]; then 8 | echo "ERROR: use_flake function missing." 9 | echo "Please update direnv to v2.30.0 or later." 10 | exit 1 11 | fi 12 | use flake -------------------------------------------------------------------------------- /templates/toml/.gitignore: -------------------------------------------------------------------------------- 1 | /.direnv/ -------------------------------------------------------------------------------- /templates/toml/devshell.toml: -------------------------------------------------------------------------------- 1 | # https://numtide.github.io/devshell 2 | [[commands]] 3 | package = "hello" 4 | -------------------------------------------------------------------------------- /templates/toml/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs", 6 | "systems": "systems" 7 | }, 8 | "locked": { 9 | "lastModified": 1692793255, 10 | "narHash": "sha256-yVyj0AE280JkccDHuG1XO9oGxN6bW8ksr/xttXcXzK0=", 11 | "owner": "numtide", 12 | "repo": "devshell", 13 | "rev": "2aa26972b951bc05c3632d4e5ae683cb6771a7c6", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "numtide", 18 | "repo": "devshell", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-utils": { 23 | "inputs": { 24 | "systems": "systems_2" 25 | }, 26 | "locked": { 27 | "lastModified": 1692799911, 28 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "flake-utils", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1677383253, 43 | "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "NixOS", 51 | "ref": "nixpkgs-unstable", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs_2": { 57 | "locked": { 58 | "lastModified": 1693563790, 59 | "narHash": "sha256-qUx+8lQSCiPXbwWBwRHynHuhQT+6I7kEuDFFNQ6RSPU=", 60 | "owner": "NixOS", 61 | "repo": "nixpkgs", 62 | "rev": "fd8ad63083882605e8a7f659be6c9509e6e28d28", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "id": "nixpkgs", 67 | "type": "indirect" 68 | } 69 | }, 70 | "root": { 71 | "inputs": { 72 | "devshell": "devshell", 73 | "flake-utils": "flake-utils", 74 | "nixpkgs": "nixpkgs_2" 75 | } 76 | }, 77 | "systems": { 78 | "locked": { 79 | "lastModified": 1681028828, 80 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 81 | "owner": "nix-systems", 82 | "repo": "default", 83 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "nix-systems", 88 | "repo": "default", 89 | "type": "github" 90 | } 91 | }, 92 | "systems_2": { 93 | "locked": { 94 | "lastModified": 1681028828, 95 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 96 | "owner": "nix-systems", 97 | "repo": "default", 98 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 99 | "type": "github" 100 | }, 101 | "original": { 102 | "owner": "nix-systems", 103 | "repo": "default", 104 | "type": "github" 105 | } 106 | } 107 | }, 108 | "root": "root", 109 | "version": 7 110 | } 111 | -------------------------------------------------------------------------------- /templates/toml/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "virtual environments"; 3 | 4 | inputs.devshell.url = "github:numtide/devshell"; 5 | inputs.flake-utils.url = "github:numtide/flake-utils"; 6 | 7 | inputs.flake-compat = { 8 | url = "github:edolstra/flake-compat"; 9 | flake = false; 10 | }; 11 | 12 | outputs = 13 | { 14 | self, 15 | flake-utils, 16 | devshell, 17 | nixpkgs, 18 | ... 19 | }: 20 | flake-utils.lib.eachDefaultSystem (system: { 21 | devShells.default = 22 | let 23 | pkgs = import nixpkgs { 24 | inherit system; 25 | 26 | overlays = [ devshell.overlays.default ]; 27 | }; 28 | in 29 | pkgs.devshell.mkShell { imports = [ (pkgs.devshell.importTOML ./devshell.toml) ]; }; 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /templates/toml/shell.nix: -------------------------------------------------------------------------------- 1 | # Use `builtins.getFlake` if available 2 | if builtins ? getFlake then 3 | let 4 | scheme = if builtins.pathExists ./.git then "git+file" else "path"; 5 | in 6 | (builtins.getFlake "${scheme}://${toString ./.}").devShells.${builtins.currentSystem}.default 7 | 8 | # Otherwise we'll use the flake-compat shim 9 | else 10 | (import ( 11 | let 12 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 13 | in 14 | fetchTarball { 15 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 16 | sha256 = lock.nodes.flake-compat.locked.narHash; 17 | } 18 | ) { src = ./.; }).shellNix 19 | -------------------------------------------------------------------------------- /tests/assert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copied from https://github.com/orangemug/bash-assert/blob/28a08c136196bd97d9e3724400aa9a07cd0e7da7/assert.sh 3 | # License: The MIT License (MIT) 4 | # Copyright (c) 2015 Jamie Blair 5 | 6 | __assert () 7 | { 8 | E_PARAM_ERR=98 9 | E_ASSERT_FAILED=99 10 | 11 | lineno=`caller 1` 12 | 13 | if [[ $# < 2 || $# > 4 ]] 14 | then 15 | num=`expr $# - 1` 16 | >&2 echo "ERR: assert require 1, 2 or 3 params, got $num" 17 | return $E_PARAM_ERR 18 | fi 19 | 20 | if [ $# -eq 2 ]; then 21 | cmd="check exit code: $?" 22 | 23 | if [ "$?" -eq 0 ] 24 | then 25 | success="true" 26 | else 27 | success="false" 28 | fi 29 | elif [ $# -eq 3 ]; then 30 | cmd="\"$2\" -eq \"$4\"" 31 | 32 | if [ "$2" "$3" ] 33 | then 34 | success="true" 35 | else 36 | success="false" 37 | fi 38 | else 39 | cmd="\"$2\" $3 \"$4\"" 40 | 41 | if [ "$2" "$3" "$4" ] 42 | then 43 | success="true" 44 | else 45 | success="false" 46 | fi 47 | fi 48 | 49 | if [ "$success" != "$1" ] 50 | then 51 | >&2 echo "Assertion failed: \"$cmd\"" 52 | >&2 echo "File \"$0\", line $lineno" 53 | return $E_ASSERT_FAILED 54 | fi 55 | } 56 | 57 | assert() { 58 | __assert "true" "$@"; 59 | return $? 60 | } 61 | 62 | assert_fail() { 63 | __assert "false" "$@"; 64 | return $? 65 | } 66 | -------------------------------------------------------------------------------- /tests/core/commands.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic devshell usage 8 | commands-1 = 9 | let 10 | shell = devshell.mkShell { 11 | devshell.name = "commands-1"; 12 | commands = [ 13 | { 14 | name = "bash-script"; 15 | category = "hello"; 16 | help = "Prints hello-bash"; 17 | command = '' 18 | echo "hello-bash" 19 | ''; 20 | } 21 | { 22 | name = "python-script"; 23 | category = "hello"; 24 | help = "Prints hello-python"; 25 | command = '' 26 | #!/usr/bin/env python3 27 | print("hello-python") 28 | ''; 29 | } 30 | { package = "git"; } 31 | ]; 32 | }; 33 | in 34 | runTest "devshell-1" { } '' 35 | # Load the devshell 36 | source ${shell}/env.bash 37 | 38 | menu 39 | 40 | # Checks that all the commands are available 41 | type -p bash-script 42 | type -p python-script 43 | type -p git 44 | 45 | assert "$(bash-script)" == hello-bash 46 | 47 | # Check that the shebang is correct. We can't execute it inside of the 48 | # sandbox because /usr/bin/env doesn't exist. 49 | # 50 | # Ideally it would be rewritten with patchShebang. 51 | assert "$(head -n1 "$(type -p python-script)")" == "#!/usr/bin/env python3" 52 | ''; 53 | } 54 | -------------------------------------------------------------------------------- /tests/core/devshell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic devshell package usage 8 | devshell-packages-1 = 9 | let 10 | shell = devshell.mkShell { 11 | devshell.name = "devshell-1"; 12 | devshell.packages = [ pkgs.git ]; 13 | devshell.packagesFrom = [ 14 | (pkgs.hello.overrideAttrs { 15 | buildInputs = [ 16 | null 17 | pkgs.cowsay 18 | ]; 19 | }) 20 | ]; 21 | }; 22 | in 23 | runTest "devshell-1" { } '' 24 | # Load the devshell 25 | source ${shell}/env.bash 26 | 27 | # Sets an environment variable that points to the buildEnv 28 | assert -n "$DEVSHELL_DIR" 29 | 30 | # Points PRJ_ROOT to the project root 31 | assert "$PWD" == "$PRJ_ROOT" 32 | 33 | # Adds packages to the PATH 34 | type -p git 35 | 36 | # Adds packages from packagesFrom to the PATH 37 | type -p cowsay 38 | ''; 39 | 40 | # Only load profiles 41 | devshell-load-profiles-1 = 42 | let 43 | fakeProfile = pkgs.writeTextFile { 44 | name = "fake_profile.sh"; 45 | destination = "/etc/profile.d/fake_profile.sh"; 46 | text = '' 47 | export FAKE_PROFILE=1 48 | ''; 49 | }; 50 | 51 | shell = devshell.mkShell { 52 | devshell.name = "devshell-1"; 53 | devshell.load_profiles = true; 54 | devshell.packages = [ fakeProfile ]; 55 | }; 56 | in 57 | runTest "devshell-load-profiles-1" { } '' 58 | # Load the devshell 59 | source ${shell}/env.bash 60 | 61 | # Check that the profile got loaded 62 | assert "$FAKE_PROFILE" == "1" 63 | ''; 64 | 65 | # Devshell entrypoint script features 66 | devshell-entrypoint-1 = 67 | let 68 | shell = devshell.mkShell { 69 | devshell.name = "devshell-entrypoint-1"; 70 | devshell.packages = [ pkgs.git ]; 71 | 72 | # Force PRJ_ROOT to be defined by caller (possibly via `--prj-root`). 73 | devshell.prj_root_fallback = null; 74 | }; 75 | in 76 | runTest "devshell-entrypoint-1" { } '' 77 | entrypoint_clean() { 78 | env -u IN_NIX_SHELL -u PRJ_ROOT ${shell}/entrypoint "$@" 79 | } 80 | 81 | # No packages in PATH 82 | ! type -p git 83 | 84 | # Exits badly if PRJ_ROOT isn't set, or if we cannot assume PRJ_ROOT 85 | # should be PWD. 86 | ! msg="$(entrypoint_clean /bin/sh -c 'exit 0' 2>&1)" 87 | assert "$msg" == 'ERROR: please set the PRJ_ROOT env var to point to the project root' 88 | 89 | # Succeeds with --prj-root set 90 | entrypoint_clean --prj-root . /bin/sh -c 'exit 0' 91 | 92 | # Packages available through entrypoint 93 | entrypoint_clean --prj-root . /bin/sh -c 'type -p git' 94 | 95 | # Packages available through entrypoint in pure mode 96 | entrypoint_clean --pure --env-bin env --prj-root . /bin/sh -c 'type -p git' 97 | ''; 98 | 99 | # Use devshell as executable 100 | devshell-executable-1 = 101 | let 102 | shell = devshell.mkShell { 103 | devshell.name = "devshell-executable-1"; 104 | devshell.packages = [ pkgs.hello ]; 105 | }; 106 | in 107 | runTest "devshell-executable-1" { } '' 108 | # Devshell is executable 109 | assert -x ${pkgs.lib.getExe shell} 110 | 111 | # Packages inside the devshell are executable 112 | ${pkgs.lib.getExe shell} hello 113 | ''; 114 | } 115 | -------------------------------------------------------------------------------- /tests/core/env.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Test the environment variables 8 | env-1 = 9 | let 10 | shell = devshell.mkShell { 11 | devshell.name = "devshell-env-1"; 12 | env = [ 13 | { 14 | name = "HTTP_PORT"; 15 | value = 8080; 16 | } 17 | { 18 | name = "PATH"; 19 | prefix = "bin"; 20 | } 21 | { 22 | name = "XDG_CACHE_DIR"; 23 | eval = "$PRJ_ROOT/$(echo .cache)"; 24 | } 25 | { 26 | name = "CARGO_HOME"; 27 | unset = true; 28 | } 29 | ]; 30 | }; 31 | in 32 | runTest "devshell-env-1" { } '' 33 | export CARGO_HOME=woot 34 | unset XDG_DATA_DIRS 35 | 36 | # Load the devshell 37 | source ${shell}/env.bash 38 | 39 | # NIXPKGS_PATH is being set 40 | assert "$NIXPKGS_PATH" == "${toString pkgs.path}" 41 | 42 | assert "$XDG_DATA_DIRS" == "$DEVSHELL_DIR/share:/usr/local/share:/usr/share" 43 | 44 | assert "$HTTP_PORT" == 8080 45 | 46 | assert "''${CARGO_HOME-not set}" == "not set" 47 | 48 | # PATH is prefixed with an expanded bin folder 49 | [[ $PATH == $PWD/bin:* ]] 50 | 51 | # Eval 52 | assert "$XDG_CACHE_DIR" == "$PWD/.cache" 53 | ''; 54 | } 55 | -------------------------------------------------------------------------------- /tests/core/modules-docs.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | modules-docs-1 = 8 | let 9 | shell = devshell.mkShell { devshell.name = "modules-docs"; }; 10 | in 11 | runTest "modules-docs-1" { } '' 12 | # The Markdown gets generated and is a derivation 13 | [[ ${toString shell.config.modules-docs.markdown} == /nix/store/* ]] 14 | 15 | echo "Markdown has been generated" 16 | ''; 17 | } 18 | -------------------------------------------------------------------------------- /tests/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | system ? builtins.currentSystem, 3 | inputs ? import ../flake.lock.nix { }, 4 | pkgs ? import inputs.nixpkgs { 5 | inherit system; 6 | # Makes the config pure as well. See /top-level/impure.nix: 7 | config = { }; 8 | overlays = [ ]; 9 | }, 10 | }: 11 | let 12 | devshell = import ../. { 13 | inputs = null; 14 | nixpkgs = pkgs; 15 | }; 16 | runTest = 17 | name: attrs: script: 18 | pkgs.runCommand name attrs '' 19 | source ${./assert.sh} 20 | 21 | # Needed by devshell 22 | export PRJ_ROOT=$PWD 23 | 24 | ${script} 25 | 26 | touch $out 27 | ''; 28 | 29 | # Arguments to pass to each test file 30 | attrs = { 31 | inherit pkgs devshell runTest; 32 | }; 33 | 34 | # Attrs marked with this attribute are recursed into by nix-build 35 | recursive = { 36 | recurseForDerivations = true; 37 | }; 38 | 39 | loadDir = 40 | testPrefix: dir: 41 | let 42 | data = builtins.readDir dir; 43 | op = 44 | sum: name: 45 | let 46 | path = "${dir}/${name}"; 47 | type = data.${name}; 48 | # Nix doesn't recurse into attrs that have dots in them... 49 | attr = builtins.replaceStrings [ "." ] [ "-" ] (pkgs.lib.removeSuffix ".nix" name); 50 | 51 | args = attrs // { 52 | # Customize runTest 53 | runTest = name: runTest "${testPrefix}.${attr}.${name}"; 54 | }; 55 | in 56 | assert type == "regular"; 57 | sum // { "${attr}" = recursive // (import path args); }; 58 | in 59 | builtins.foldl' op recursive (builtins.attrNames data); 60 | in 61 | { 62 | recurseForDerivations = true; 63 | core = loadDir "tests.core" (toString ./core); 64 | extra = loadDir "tests.extra" (toString ./extra); 65 | } 66 | -------------------------------------------------------------------------------- /tests/extra/git.hooks.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic git.hooks module tests 8 | git-hooks-1 = 9 | let 10 | shell1 = devshell.mkShell { 11 | imports = [ ../../extra/git/hooks.nix ]; 12 | devshell.name = "git-hooks-1a"; 13 | git.hooks.enable = true; 14 | git.hooks.pre-commit.text = '' 15 | #!${pkgs.bash}/bin/bash 16 | echo "PRE-COMMIT" 17 | ''; 18 | }; 19 | 20 | shell2 = devshell.mkShell { 21 | imports = [ ../../extra/git/hooks.nix ]; 22 | devshell.name = "git-hooks-1b"; 23 | git.hooks.enable = true; 24 | }; 25 | 26 | shell3 = devshell.mkShell { devshell.name = "git-hooks-1c"; }; 27 | 28 | shell4 = devshell.mkShell { 29 | imports = [ ../../extra/git/hooks.nix ]; 30 | devshell.name = "git-hooks-1d"; 31 | git.hooks.enable = true; 32 | git.hooks.pre-commit.text = '' 33 | #!${pkgs.bash}/bin/bash 34 | echo "PRE-COMMIT-OF-ANOTHER-COLOR" 35 | ''; 36 | git.hooks.pre-rebase.text = '' 37 | #!${pkgs.bash}/bin/bash 38 | echo "NOPE" 39 | exit 1 40 | ''; 41 | }; 42 | in 43 | runTest "git-hooks-1" { nativeBuildInputs = [ pkgs.git ]; } '' 44 | mkdir worktree-1 45 | 46 | cd worktree-1 47 | 48 | git init -b git-hook-test 49 | 50 | # Set up fake config values in order to make a commit 51 | git config user.email test@ing.123 52 | git config user.name "Test User" 53 | 54 | # Make a commit in order to add worktrees 55 | git commit --allow-empty -m init 56 | 57 | git_dir=$(${pkgs.gitMinimal}/bin/git rev-parse --absolute-git-dir) 58 | git_hooks_path=$(git rev-parse --path-format=absolute --git-path hooks/ 2>/dev/null) \ 59 | || git_hooks_path="''${git_dir}/hooks" 60 | 61 | git_pre_commit_hook="''${git_hooks_path}/pre-commit" 62 | 63 | # The hook doesn't exist yet 64 | assert_fail -L "$git_pre_commit_hook" 65 | 66 | # Load the devshell 67 | source ${shell1}/env.bash 68 | 69 | # The hook has been installed 70 | assert -L "$git_pre_commit_hook" 71 | 72 | # The hook outputs what we want 73 | assert "$("$git_pre_commit_hook")" == "PRE-COMMIT" 74 | 75 | # Load the new config 76 | source ${shell2}/env.bash 77 | 78 | # This specific hook should complain that it is not activated 79 | assert "$("$git_pre_commit_hook")" == "pre-commit: the pre-commit git hook is not activated in this environment" 80 | 81 | # Load a config with no hooks defined 82 | # NOTE need to unset the hooks dir environment variable as this profile 83 | # does not enable git hooks and therefore does not (re)set the variable 84 | unset DEVSHELL_GIT_HOOKS_DIR 85 | source ${shell3}/env.bash 86 | 87 | # The hook should complain that *no* hooks are activated 88 | assert "$("$git_pre_commit_hook")" == "pre-commit: git hooks are not activated in this environment" 89 | 90 | git worktree add ../worktree-2 91 | 92 | cd ../worktree-2 93 | 94 | # Now source initial profile 95 | source ${shell1}/env.bash 96 | 97 | # The hook has been reinstalled 98 | assert -L "$git_pre_commit_hook" 99 | 100 | # The hook outputs what we want 101 | assert "$("$git_pre_commit_hook")" == "PRE-COMMIT" 102 | 103 | # Stash current pre-commit hook link path for later testing 104 | git_pre_commit_real="$(readlink -f "$git_pre_commit_hook")" 105 | 106 | # Added by shell4 107 | git_pre_rebase_hook="''${git_hooks_path}/pre-rebase" 108 | 109 | # Only shell4 has this hook 110 | assert_fail -L "$git_pre_rebase_hook" 111 | 112 | # Now source the profile that defines a pre-rebase hook 113 | source ${shell4}/env.bash 114 | 115 | # Pre-rebase hook should now exist 116 | assert -L "$git_pre_rebase_hook" 117 | 118 | # Stash pre-rebase hook link path for later testing 119 | git_pre_rebase_real="$(readlink -f "$git_pre_rebase_hook")" 120 | 121 | # The hook outputs what we want 122 | assert "$("$git_pre_commit_hook")" == "PRE-COMMIT-OF-ANOTHER-COLOR" 123 | 124 | # Pre-commit link should not have changed 125 | assert "$git_pre_commit_real" = "$(readlink -f "$git_pre_commit_hook")" 126 | 127 | # Switch back to profile without pre-rebase hook 128 | source ${shell1}/env.bash 129 | 130 | # Pre-rebase link should not have changed 131 | assert "$git_pre_rebase_real" = "$(readlink -f "$git_pre_rebase_hook")" 132 | ''; 133 | } 134 | -------------------------------------------------------------------------------- /tests/extra/language.c.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic test 8 | language-c-1 = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/language/c.nix ]; 12 | devshell.name = "devshell-1"; 13 | }; 14 | in 15 | runTest "language-c-1" { } '' 16 | # Load the devshell 17 | source ${shell}/env.bash 18 | 19 | 20 | # Has a C compiler 21 | type -p clang 22 | ''; 23 | # Test good LD_LIBRARY_PATH value 24 | language-c-2 = 25 | let 26 | shell = devshell.mkShell { 27 | imports = [ ../../extra/language/c.nix ]; 28 | devshell.name = "devshell-2"; 29 | language.c.libraries = [ pkgs.openssl ]; 30 | }; 31 | in 32 | runTest "language-c-2" { } '' 33 | # Load the devshell 34 | source ${shell}/env.bash 35 | 36 | # LD_LIBRARY_PATH is evaluated correctly 37 | [[ ! "$LD_LIBRARY_PATH" =~ "DEVSHELL_DIR" ]] 38 | ''; 39 | } 40 | -------------------------------------------------------------------------------- /tests/extra/language.hare.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | pkgs.lib.optionalAttrs (!pkgs.hostPlatform.isDarwin) { 7 | # Basic test 8 | language-hare-1 = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/language/hare.nix ]; 12 | devshell.name = "devshell-1"; 13 | }; 14 | in 15 | runTest "language-hare-1" { } '' 16 | # Load the devshell 17 | source ${shell}/env.bash 18 | 19 | # Has a Hare binary 20 | type -p hare 21 | ''; 22 | # Test good HAREPATH value 23 | language-hare-2 = 24 | let 25 | shell = devshell.mkShell { 26 | imports = [ ../../extra/language/hare.nix ]; 27 | devshell.name = "devshell-2"; 28 | language.hare = { 29 | thirdPartyLibs = [ pkgs.hareThirdParty.hare-png ]; 30 | vendoredLibs = [ "./vendor/lib" ]; 31 | }; 32 | }; 33 | in 34 | runTest "language-hare-2" { } '' 35 | # Load the devshell 36 | source ${shell}/env.bash 37 | 38 | die() { 39 | printf -- '%s\n' "''${*}" 2>&1 40 | printf -- 'HAREPATH: `%s`\n' ''${HAREPATH//:/ } 2>&1 41 | exit 1 42 | } 43 | 44 | # Check for HAREPATH being set 45 | [[ -n "$HAREPATH" ]] || die "HAREPATH not set" 46 | 47 | # Check for the stdlib being included in HAREPATH 48 | [[ "$HAREPATH" =~ "src/hare/stdlib" ]] || die 'HAREPATH lacks `stdlib`' 49 | 50 | # Check for hare-compress being included in HAREPATH (propagatedBuildInputs of hare-png) 51 | [[ "$HAREPATH" =~ /nix/store/[a-z0-9]{32}-hare-compress-.*/src/hare/third-party ]] \ 52 | || die 'HAREPATH lacks `hare-compress`' 53 | 54 | # Check for hare-png being included in HAREPATH 55 | [[ "$HAREPATH" =~ /nix/store/[a-z0-9]{32}-hare-png-.*/src/hare/third-party ]] \ 56 | || die 'HAREPATH lacks `hare-png`' 57 | 58 | # Check for ./vendor/lib being included in HAREPATH 59 | [[ "$HAREPATH" =~ $PWD/vendor/lib ]] || die "HAREPATH lacks \`$PWD/vendor/lib\`" 60 | ''; 61 | } 62 | -------------------------------------------------------------------------------- /tests/extra/language.perl.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic test 8 | simple = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/language/perl.nix ]; 12 | devshell.name = "language-perl-simple"; 13 | }; 14 | in 15 | runTest "simple" { } '' 16 | # Load the devshell 17 | source ${shell}/env.bash 18 | 19 | # Has a Perl interpreter 20 | type -p perl 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /tests/extra/language.rust.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic test 8 | simple = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/language/rust.nix ]; 12 | devshell.name = "language-rust-simple"; 13 | }; 14 | in 15 | runTest "simple" { } '' 16 | # Load the devshell 17 | source ${shell}/env.bash 18 | 19 | # Has a rust compiler 20 | type -p rustc 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /tests/extra/locale.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic test 8 | simple = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/locale.nix ]; 12 | devshell.name = "locale-simple"; 13 | }; 14 | in 15 | runTest "simple" { } '' 16 | # Assume that LOCAL_ARCHIVE is not set before 17 | assert -z "$LOCALE_ARCHIVE" 18 | 19 | # Load the devshell 20 | source ${shell}/env.bash 21 | 22 | # Sets LOCALE_ARCHIVE 23 | if [[ $OSTYPE == linux-gnu ]]; then 24 | assert -n "$LOCALE_ARCHIVE" 25 | else 26 | assert -z "$LOCALE_ARCHIVE" 27 | fi 28 | ''; 29 | } 30 | -------------------------------------------------------------------------------- /tests/extra/services.postgres.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | devshell, 4 | runTest, 5 | }: 6 | { 7 | # Basic test 8 | simple = 9 | let 10 | shell = devshell.mkShell { 11 | imports = [ ../../extra/services/postgres.nix ]; 12 | devshell.name = "services-postgres-example"; 13 | }; 14 | in 15 | runTest "simple" { } '' 16 | # Load the devshell 17 | source ${shell}/env.bash 18 | 19 | # Has postgres 20 | type -p postgres 21 | 22 | # Has a setup script 23 | setup-postgres 24 | 25 | # Second call is idempotent and should succeed as well 26 | setup-postgres 27 | 28 | # Start postgres in the background 29 | pg_ctl start 30 | trap "pg_ctl stop" EXIT 31 | 32 | # Test that the DB is up and running 33 | i=0 34 | while ! pg_isready; do 35 | if [[ $i -gt 10 ]]; then 36 | echo "could not connect to postgres" 37 | exit 1 38 | fi 39 | ((i++)) 40 | sleep 0.2 41 | echo "x" 42 | done 43 | echo OK 44 | ''; 45 | } 46 | --------------------------------------------------------------------------------