├── modules
├── programs
│ ├── git
│ │ └── tests
│ │ │ ├── enabled
│ │ │ ├── .gitattributes
│ │ │ ├── gitignore
│ │ │ └── default.nix
│ │ │ ├── default.nix
│ │ │ └── settings
│ │ │ ├── .gitattributes
│ │ │ ├── gitignore
│ │ │ ├── git
│ │ │ └── config
│ │ │ └── default.nix
│ ├── treefmt.nix
│ ├── project-manager.nix
│ ├── just.nix
│ ├── direnv.nix
│ └── mercurial.nix
├── files
│ └── tests
│ │ ├── simple
│ │ ├── meh
│ │ └── default.nix
│ │ └── default.nix
├── services
│ ├── flakehub
│ │ ├── tests
│ │ │ ├── default.nix
│ │ │ └── enabled
│ │ │ │ ├── default.nix
│ │ │ │ └── .github
│ │ │ │ └── workflows
│ │ │ │ └── flakehub-publish.yml
│ │ └── default.nix
│ ├── renovate
│ │ ├── tests
│ │ │ ├── default.nix
│ │ │ └── simple
│ │ │ │ ├── renovate.json
│ │ │ │ └── default.nix
│ │ └── default.nix
│ ├── flakestry.nix
│ ├── garnix.nix
│ ├── github.nix
│ └── nix-ci.nix
├── misc
│ ├── editorconfig
│ │ ├── tests
│ │ │ ├── default.nix
│ │ │ └── simple
│ │ │ │ ├── editorconfig
│ │ │ │ └── default.nix
│ │ └── default.nix
│ ├── version.nix
│ ├── xdg.nix
│ └── news.nix
├── lib
│ ├── types
│ │ ├── tests
│ │ │ ├── default.nix
│ │ │ ├── dag-merge-result.txt
│ │ │ ├── dag-submodule.nix
│ │ │ ├── dag-merge.nix
│ │ │ └── gvariant-merge.nix
│ │ ├── dag.nix
│ │ └── default.nix
│ ├── shell.nix
│ ├── stdlib-extended.nix
│ ├── default.nix
│ ├── strings.nix
│ ├── generators.nix
│ ├── path.nix
│ ├── dag.nix
│ └── gvariant.nix
├── all-modules.nix
├── modules.nix
├── default.nix
└── lib-bash
│ └── activation-init.bash
├── docs
├── manual
│ ├── 3rd-party
│ │ └── collections.md
│ ├── options.md
│ ├── faq.md
│ ├── faq
│ │ ├── session-variables.md
│ │ ├── ca-desrt-dconf.md
│ │ ├── unstable.md
│ │ ├── change-package-module.md
│ │ └── collision.md
│ ├── usage
│ │ ├── updating.md
│ │ ├── rollbacks.md
│ │ ├── dotfiles.md
│ │ └── configuration.md
│ ├── introduction
│ │ └── devShell.md
│ ├── quick-start.md
│ ├── manual.md
│ ├── quick-start
│ │ ├── new-project.md
│ │ └── existing-project.md
│ ├── 3rd-party.md
│ ├── nix-flakes.md
│ ├── preface.md
│ ├── writing-modules.md
│ ├── contributing.md
│ ├── nix-flakes
│ │ ├── prerequisites.md
│ │ └── standalone.md
│ ├── contributing
│ │ ├── getting-started.md
│ │ └── news.md
│ ├── installation.md
│ ├── introduction.md
│ ├── usage.md
│ └── manpage-urls.json
├── project-configuration-nix-footer.5
├── highlight-style.css
├── release-notes
│ ├── rl-0.md
│ └── release-notes.md
├── options.html
├── project-configuration-nix-header.5
├── html-open-tool.nix
├── project-manager-manual.nix
├── static
│ ├── tomorrow-night.min.css
│ └── tomorrow.min.css
└── default.nix
├── .github
├── renovate.json
├── workflows
│ ├── flakestry-publish.yml
│ ├── switch-pm-generation.yml
│ ├── flakehub-publish.yml
│ └── pages.yml
└── settings.yml
├── .config
├── mustache.yaml
└── project
│ ├── README.md
│ └── github-pages.nix
├── release.nix
├── nix
├── lib
│ ├── default.nix
│ ├── default-configuration.nix
│ └── configuration.nix
└── schemas.nix
├── nix-ci.nix
├── .vale.ini
├── testing
├── big-test.nix
├── asserts.nix
├── stubs.nix
├── darwinScrubList.nix
├── flake.nix
└── default.nix
├── .cache
└── vale
│ ├── Vocab
│ └── project-manager
│ │ └── accept.txt
│ └── config
│ └── vocabularies
│ └── project-manager
│ └── accept.txt
├── .editorconfig
├── templates
├── default.nix
└── default
│ ├── flake.nix
│ └── .config
│ └── project
│ └── default.nix
├── .gitattributes
├── garnix.yaml
├── LICENSE
└── project-manager
├── build-news.nix
├── default.nix
├── completion.zsh
└── completion.fish
/modules/programs/git/tests/enabled/.gitattributes:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/modules/files/tests/simple/meh:
--------------------------------------------------------------------------------
1 | This is a very simple file.
2 |
--------------------------------------------------------------------------------
/modules/files/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | files-simple = ./simple/default.nix;
3 | }
4 |
--------------------------------------------------------------------------------
/modules/services/flakehub/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | flakehub-enabled = ./enabled;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/manual/3rd-party/collections.md:
--------------------------------------------------------------------------------
1 | # Module Collections {#sec-3rd-party-module-collections}
2 |
--------------------------------------------------------------------------------
/docs/project-configuration-nix-footer.5:
--------------------------------------------------------------------------------
1 | .SH "AUTHORS"
2 | .PP
3 | Project Manager contributors
4 |
--------------------------------------------------------------------------------
/modules/services/renovate/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | renovate-simple = ./simple/default.nix;
3 | }
4 |
--------------------------------------------------------------------------------
/modules/misc/editorconfig/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | editorconfig-simple = ./simple/default.nix;
3 | }
4 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | git-enabled = ./enabled;
3 | git-settings = ./settings;
4 | }
5 |
--------------------------------------------------------------------------------
/docs/highlight-style.css:
--------------------------------------------------------------------------------
1 | pre {
2 | padding: 0;
3 | }
4 |
5 | pre code.hljs {
6 | border: none;
7 | margin: 0;
8 | }
9 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/enabled/gitignore:
--------------------------------------------------------------------------------
1 | /.gitattributes
2 | /.gitignore
3 | /.local/state/project-manager/
4 | /.local/state/nix/profiles/
5 |
--------------------------------------------------------------------------------
/docs/release-notes/rl-0.md:
--------------------------------------------------------------------------------
1 | # Release 0 {#sec-release-0}
2 |
3 | This is the current unstable branch and the information in this section
4 | is therefore not final.
5 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/settings/.gitattributes:
--------------------------------------------------------------------------------
1 | *.bin -diff important=50
2 | *.pdf diff=pdf
3 | /some-file linguist-generated
4 | meh important=100
5 | other !unset
6 |
--------------------------------------------------------------------------------
/modules/services/renovate/tests/simple/renovate.json:
--------------------------------------------------------------------------------
1 | {"$schema":"https://docs.renovatebot.com/renovate-schema.json","extends":["config:recommended"],"nix":{"enabled":true}}
--------------------------------------------------------------------------------
/modules/programs/git/tests/settings/gitignore:
--------------------------------------------------------------------------------
1 | /.gitattributes
2 | /.gitignore
3 | /.local/state/project-manager/
4 | /.local/state/nix/profiles/
5 | /.locally-generated
6 | /.cache
7 |
--------------------------------------------------------------------------------
/docs/manual/options.md:
--------------------------------------------------------------------------------
1 | # Project Manager Configuration Options {#ch-options}
2 |
3 | ```{=include=} options
4 | id-prefix: opt-
5 | list-id: project-manager-options
6 | source: @OPTIONS_JSON@
7 | ```
8 |
--------------------------------------------------------------------------------
/modules/lib/types/tests/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | lib-types-dag-submodule = ./dag-submodule.nix;
3 | lib-types-dag-merge = ./dag-merge.nix;
4 |
5 | lib-types-gvariant-merge = ./gvariant-merge.nix;
6 | }
7 |
--------------------------------------------------------------------------------
/docs/manual/faq.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions (FAQ) {#ch-faq}
2 |
3 | ```{=include=} sections
4 | faq/collision.md
5 | faq/session-variables.md
6 | faq/ca-desrt-dconf.md
7 | faq/unstable.md
8 | faq/change-package-module.md
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/release-notes/release-notes.md:
--------------------------------------------------------------------------------
1 | # Release Notes {#ch-release-notes}
2 |
3 | This section lists the release notes for stable versions of Project Manager
4 | and the current unstable version.
5 |
6 | ```{=include=} chapters
7 | rl-0.md
8 | ```
9 |
--------------------------------------------------------------------------------
/modules/files/tests/simple/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | project.file."meh".text = ''
3 | This is a very simple file.
4 | '';
5 |
6 | nmt.script = ''
7 | assertFileExists "project-files/meh"
8 | assertFileContent "project-files/meh" ${./meh}
9 | '';
10 | }
11 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/settings/git/config:
--------------------------------------------------------------------------------
1 | ; This file was generated by Project Manager.
2 | [include]
3 | path = "~/some-extra/git/config.inc"
4 |
5 | ; This file was generated by Project Manager.
6 | [includeIf "gitdir:~/src/dir"]
7 | path = "~/some-extra/git/conditional.inc"
8 |
--------------------------------------------------------------------------------
/docs/manual/faq/session-variables.md:
--------------------------------------------------------------------------------
1 |
2 | # Why are the session variables not set? {#_why_are_the_session_variables_not_set}
3 |
4 | Project Manager is only able to set session variables automatically in derivations that extend the ones provided by Project Manager.
5 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {"$schema":"https://docs.renovatebot.com/renovate-schema.json","extends":["config:recommended"],"labels":["automated"],"lockFileMaintenance":{"automerge":true,"enabled":true},"nix":{"enabled":true},"packageRules":[{"automerge":true,"matchCurrentVersion":"!/^0/","matchUpdateTypes":["minor","patch"]}]}
2 |
--------------------------------------------------------------------------------
/.config/mustache.yaml:
--------------------------------------------------------------------------------
1 | {
2 | "project":
3 | {
4 | "name": "project-manager",
5 | "summary": "Home Manager, but for repos.",
6 | "description": "A configuration for managing flake-based projects.",
7 | "repo": "sellout/project-manager",
8 | },
9 | "type": { "name": "nix" },
10 | }
11 |
--------------------------------------------------------------------------------
/modules/misc/editorconfig/tests/simple/editorconfig:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | root=true
3 |
4 | [*]
5 | charset=utf-8
6 | end_of_line=lf
7 | indent_size=2
8 | indent_style=space
9 | insert_final_newline=true
10 | max_line_width=78
11 | trim_trailing_whitespace=true
12 |
13 | [*.rs]
14 | indent_size=4
15 |
--------------------------------------------------------------------------------
/modules/services/renovate/tests/simple/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | ## With no additional settings, we get a bare-bones Renovate config.
3 | services.renovate.enable = true;
4 |
5 | nmt.script = ''
6 | assertFileExists "project-files/renovate.json"
7 | assertFileContent "project-files/renovate.json" ${./renovate.json}
8 | '';
9 | }
10 |
--------------------------------------------------------------------------------
/release.nix:
--------------------------------------------------------------------------------
1 | let
2 | ## This follows Semantic Versioning 2.0.0 (https://semver.org/)
3 | version = {
4 | major = 0;
5 | minor = 7;
6 | patch = 0;
7 | };
8 | in {
9 | inherit version;
10 | release = (
11 | with version; "${toString major}.${toString minor}.${toString patch}"
12 | );
13 | isReleaseBranch = true;
14 | }
15 |
--------------------------------------------------------------------------------
/nix/lib/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | flake-utils,
3 | pkgsFor,
4 | self,
5 | treefmt-nix,
6 | }: let
7 | configuration = import ./configuration.nix {
8 | inherit flake-utils pkgsFor self treefmt-nix;
9 | };
10 | in {
11 | inherit configuration;
12 |
13 | defaultConfiguration =
14 | import ./default-configuration.nix {inherit configuration;};
15 | }
16 |
--------------------------------------------------------------------------------
/nix-ci.nix:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | { allow-import-from-derivation = false; auto-retry = true; build-logs = true; cachix = { name = "sellout"; public-key = "sellout.cachix.org-1:v37cTpWBEycnYxSPAgSQ57Wiqd3wjljni2aC0Xry1DE="; }; deploy = { }; doNotBuild = [ "devShells.x86_64-linux.lax-checks" ]; enable = true; fail-fast = false; impure = false; test = { }; }
3 |
--------------------------------------------------------------------------------
/.vale.ini:
--------------------------------------------------------------------------------
1 | ; This file was generated by Project Manager.
2 | MinAlertLevel=suggestion
3 | Packages=Microsoft
4 | StylesPath=.cache/vale
5 | Vocab=project-manager
6 |
7 | [*]
8 | BasedOnStyles=Vale, Microsoft
9 | Microsoft.Dashes=NO
10 | Microsoft.GeneralURL=NO
11 | Microsoft.Headings=NO
12 | Microsoft.Quotes=NO
13 | Microsoft.Ranges=NO
14 | Microsoft.Vocab=NO
15 | Microsoft.We=NO
16 |
--------------------------------------------------------------------------------
/docs/manual/usage/updating.md:
--------------------------------------------------------------------------------
1 | # Updating {#sec-updating}
2 |
3 | If you have installed Project Manager using the Nix channel method then
4 | updating Project Manager is done by first updating the channel. You can
5 | then switch to the updated Project Manager environment.
6 |
7 | ```shell
8 | $ nix-channel --update
9 | …
10 | unpacking channels...
11 | $ project-manager switch
12 | ```
13 |
--------------------------------------------------------------------------------
/testing/big-test.nix:
--------------------------------------------------------------------------------
1 | {lib, ...}: {
2 | options.test.enableBig = lib.mkOption {
3 | type = lib.types.bool;
4 | default = true;
5 | description = ''
6 | Whether to enable "big" tests. These are tests that require
7 | more resources than typical tests. For example, tests that depend on large
8 | packages or tests that take long to run.
9 | '';
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/modules/lib/types/tests/dag-merge-result.txt:
--------------------------------------------------------------------------------
1 | before:before
2 | merged:left,middle,middle,right
3 | between:between
4 | after:after
5 | list-anywhere-0:list-anywhere-0
6 | list-before-0:list-before-0,sneaky-merge
7 | list-before-1:list-before-1
8 | list-anywhere-1:list-anywhere-1
9 | inside-list:inside-list
10 | list-after-0:list-after-0
11 | list-after-1:list-after-1
12 | list-anywhere-2:list-anywhere-2
13 |
--------------------------------------------------------------------------------
/.cache/vale/Vocab/project-manager/accept.txt:
--------------------------------------------------------------------------------
1 | Hacktoberfest
2 | direnv
3 | formatter
4 | garnix
5 | [Nn]ix
6 | Pfeil
7 | ShellCheck
8 | alejandra
9 | babelfish
10 | [Bb]oolean
11 | composable
12 | DBus
13 | dconf
14 | declutter
15 | decluttered
16 | decluttering
17 | devenv
18 | devShell
19 | Dhall
20 | formatters
21 | NixOS
22 | Nixpkgs
23 | NMT
24 | sandboxed
25 | subcommands
26 | systemd
27 | treefmt
28 | unsandboxed
29 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | root=true
3 |
4 | [*]
5 | binary_next_line=true
6 | charset=utf-8
7 | end_of_line=lf
8 | indent_size=2
9 | indent_style=space
10 | insert_final_newline=true
11 | space_redirects=true
12 | switch_case_indent=true
13 | trim_trailing_whitespace=true
14 |
15 | [*.{diff,patch}]
16 | trim_trailing_whitespace=false
17 |
18 | [*.{el,lisp}]
19 | indent_size=unset
20 |
--------------------------------------------------------------------------------
/templates/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | default = {
3 | description = "A simple flake with project configuration";
4 | path = ./default;
5 | welcomeText = ''
6 | You now have a flake with a centralized project configuration!
7 |
8 | 1. edit the project configuration in “.config/project/default.nix”.
9 | 2. use `nix run .#project-manager -- switch` to populate the repo.
10 | '';
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/modules/lib/shell.nix:
--------------------------------------------------------------------------------
1 | {lib}: rec {
2 | # Produces a Bourne shell like variable export statement.
3 | export = n: v: ''export ${n}="${toString v}"'';
4 |
5 | # Given an attribute set containing shell variable names and their
6 | # assignment, this function produces a string containing an export
7 | # statement for each set entry.
8 | exportAll = vars: lib.concatStringsSep "\n" (lib.mapAttrsToList export vars);
9 | }
10 |
--------------------------------------------------------------------------------
/.cache/vale/config/vocabularies/project-manager/accept.txt:
--------------------------------------------------------------------------------
1 | Hacktoberfest
2 | direnv
3 | formatter
4 | garnix
5 | [Nn]ix
6 | Pfeil
7 | ShellCheck
8 | alejandra
9 | babelfish
10 | [Bb]oolean
11 | composable
12 | DBus
13 | dconf
14 | declutter
15 | decluttered
16 | decluttering
17 | devenv
18 | devShell
19 | Dhall
20 | formatters
21 | NixOS
22 | Nixpkgs
23 | NMT
24 | sandboxed
25 | subcommands
26 | systemd
27 | treefmt
28 | unsandboxed
29 |
--------------------------------------------------------------------------------
/modules/services/flakehub/tests/enabled/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | services = {
3 | flakehub = {
4 | enable = true;
5 | name = "sellout/example";
6 | };
7 | github.enable = true;
8 | };
9 |
10 | nmt.script = ''
11 | assertFileExists "project-files/.github/workflows/flakehub-publish.yml"
12 | assertFileContent "project-files/.github/workflows/flakehub-publish.yml" \
13 | ${./.github/workflows/flakehub-publish.yml}
14 | '';
15 | }
16 |
--------------------------------------------------------------------------------
/modules/lib/stdlib-extended.nix:
--------------------------------------------------------------------------------
1 | # Just a convenience function that returns the given Nixpkgs standard
2 | # library extended with the HM library.
3 | nixpkgsLib: let
4 | mkPmLib = import ./.;
5 | in
6 | nixpkgsLib.extend (self: super: {
7 | pm = mkPmLib {lib = self;};
8 |
9 | # For forward compatibility.
10 | literalExpression = super.literalExpression or super.literalExample;
11 | literalDocBook = super.literalDocBook or super.literalExample;
12 | })
13 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/enabled/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | programs.git.enable = true;
3 |
4 | nmt.script = ''
5 | assertFileExists project-files/.gitattributes
6 | assertFileContent project-files/.gitattributes ${./.gitattributes}
7 | assertFileExists project-files/.gitignore
8 | ## NB: We remove the leading “.” from the filename so that it doesn’t
9 | ## actually ignore things.
10 | assertFileContent project-files/.gitignore ${./gitignore}
11 | '';
12 | }
13 |
--------------------------------------------------------------------------------
/docs/manual/introduction/devShell.md:
--------------------------------------------------------------------------------
1 | # the `project-manager` devShell {#sec-project-manager-devShell}
2 |
3 | One of the most powerful combinations is `self.projectConfigurations.${system}.devShells.project-manager`. This provides a shell that has a PATH and other environment that have been produced by the Project Manager configuration. If you use this environment, you can often avoid producing files into your working tree, as the executables available here may be modified to look for their files directly in the Nix store.
4 |
--------------------------------------------------------------------------------
/docs/manual/quick-start.md:
--------------------------------------------------------------------------------
1 | # Quick Start {#ch-quick-start}
2 |
3 | It’s not necessary to install Project Manager to use it for a project. The easiest way to try it out is on a new project, but it’s also intended to be easy to integrate it with an existing project
4 |
5 | **NB**: If you have installed Project Manager already, then `nix run github:sellout/project-manager --` can be replaced with `project-manager` in any commands.
6 |
7 | ```{=include=} sections
8 | quick-start/new-project.md
9 | quick-start/existing-project.md
10 | ```
11 |
--------------------------------------------------------------------------------
/docs/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Redirecting…
4 |
5 |
6 |
9 |
10 |
11 | Redirecting…
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/manual/manual.md:
--------------------------------------------------------------------------------
1 | # Project Manager Manual {#project-manager-manual}
2 |
3 | ## Version 0.7.0 (unstable)
4 |
5 | ```{=include=} preface
6 | preface.md
7 | ```
8 |
9 | ```{=include=} parts
10 | introduction.md
11 | quick-start.md
12 | installation.md
13 | usage.md
14 | nix-flakes.md
15 | writing-modules.md
16 | contributing.md
17 | 3rd-party.md
18 | faq.md
19 | ```
20 |
21 | ```{=include=} appendix html:into-file=//options.xhtml
22 | options.md
23 | ```
24 |
25 | ```{=include=} appendix html:into-file=//release-notes.xhtml
26 | release-notes/release-notes.md
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/manual/quick-start/new-project.md:
--------------------------------------------------------------------------------
1 | # New Project {#sec-new-project}
2 |
3 | To create a new project from scratch, run the following command :
4 |
5 | ```bash
6 | nix run github:sellout/project-manager -- init my-new-project --switch
7 | ```
8 |
9 | This will create a new directory called “my-new-project” in the current directory, create a flake and project configuration (.config/project/default.nix) in that directory, and populate it with files as dictated by the project configuration.
10 |
11 | See [§Configuration](#sec-usage-configuration) for where to go from here.
12 |
--------------------------------------------------------------------------------
/docs/manual/3rd-party.md:
--------------------------------------------------------------------------------
1 | # Third-Party Tools and Extensions {#ch-3rd-party}
2 |
3 | Here is a collection of tools and extensions that relate to Project
4 | Manager. Note, these are maintained outside the regular Project Manager
5 | flow so quality and support may vary wildly. If you encounter problems
6 | then please raise them in the corresponding project, not as issues in
7 | the Project Manager tracker.
8 |
9 | If you have made something interesting related to Project Manager then you
10 | are encouraged to create a PR that expands this chapter.
11 |
12 | ```{=include=} sections
13 | 3rd-party/collections.md
14 | ```
15 |
--------------------------------------------------------------------------------
/modules/lib/default.nix:
--------------------------------------------------------------------------------
1 | {lib}: rec {
2 | dag = import ./dag.nix {inherit lib;};
3 |
4 | # assertions = import ./assertions.nix { inherit lib; };
5 |
6 | # booleans = import ./booleans.nix { inherit lib; };
7 | generators = import ./generators.nix {inherit lib;};
8 | gvariant = import ./gvariant.nix {inherit lib;};
9 | # maintainers = import ./maintainers.nix;
10 | path = import ./path.nix {inherit lib;};
11 | strings = import ./strings.nix {inherit lib;};
12 | types = import ./types {inherit gvariant lib;};
13 |
14 | shell = import ./shell.nix {inherit lib;};
15 | # zsh = import ./zsh.nix { inherit lib; };
16 | }
17 |
--------------------------------------------------------------------------------
/docs/manual/nix-flakes.md:
--------------------------------------------------------------------------------
1 | # Nix Flakes {#ch-nix-flakes}
2 |
3 | Project Manager is compatible with [Nix
4 | Flakes](https://wiki.nixos.org/wiki/Flakes). But please be aware that this
5 | support is still experimental and may change in backwards
6 | incompatible ways.
7 |
8 | Just like in the standard installation you can use the Project Manager
9 | flake in one way:
10 |
11 | 1. Using the standalone `project-manager` tool. See
12 | [Standalone setup](#sec-flakes-standalone) for instructions on how
13 | to perform this installation.
14 |
15 | ```{=include=} sections
16 | nix-flakes/prerequisites.md
17 | nix-flakes/standalone.md
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/manual/preface.md:
--------------------------------------------------------------------------------
1 | # Preface {#preface}
2 |
3 | This manual will eventually describe how to install, use, and extend Project
4 | Manager.
5 |
6 | If you encounter problems then please reach out on [the Matrix room](https://matrix.to/#/%23project-manager:matrix.org).
7 | If your problem is caused by a bug in Project Manager then it should
8 | be reported on the
9 | [Project Manager issue tracker](https://github.com/nix-community/project-manager/issues).
10 |
11 | :::{.note}
12 | Commands prefixed with `$ sudo` have to be run as root, either
13 | requiring to login as root user or temporarily switching to it using
14 | `sudo` for example.
15 | :::
16 |
--------------------------------------------------------------------------------
/modules/misc/editorconfig/tests/simple/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | editorconfig = {
3 | enable = true;
4 | settings = {
5 | "*" = {
6 | charset = "utf-8";
7 | end_of_line = "lf";
8 | trim_trailing_whitespace = true;
9 | insert_final_newline = true;
10 | max_line_width = 78;
11 | indent_style = "space";
12 | indent_size = 2;
13 | };
14 | "*.rs" = {
15 | indent_size = 4;
16 | };
17 | };
18 | };
19 |
20 | nmt.script = ''
21 | assertFileExists "project-files/.editorconfig"
22 | assertFileContent "project-files/.editorconfig" ${./editorconfig}
23 | '';
24 | }
25 |
--------------------------------------------------------------------------------
/nix/lib/default-configuration.nix:
--------------------------------------------------------------------------------
1 | ## Takes the same arguments as `configuration`, but defaults to loading
2 | ## configuration from `$PROJECT_ROOT/.config/project` and
3 | ## `$PROJECT_ROOT/.config/project/user`.
4 | {configuration}: {
5 | self,
6 | modules ? [],
7 | ...
8 | } @ args: let
9 | projectConfig = "${self}/.config/project";
10 | userConfig = "${projectConfig}/user";
11 | in
12 | configuration
13 | (args
14 | // {
15 | modules =
16 | modules
17 | ++ [projectConfig]
18 | ++ (
19 | if builtins.pathExists userConfig
20 | then [userConfig]
21 | else []
22 | );
23 | })
24 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.cache/vale/Vocab/project-manager/accept.txt linguist-generated
2 | /.cache/vale/config/vocabularies/project-manager/accept.txt linguist-generated
3 | /.editorconfig linguist-generated
4 | /.gitattributes linguist-generated
5 | /.github/renovate.json linguist-generated
6 | /.github/settings.yml linguist-generated
7 | /.github/workflows/flakehub-publish.yml linguist-generated
8 | /.github/workflows/flakestry-publish.yml linguist-generated
9 | /.github/workflows/pages.yml linguist-generated
10 | /.github/workflows/switch-pm-generation.yml linguist-generated
11 | /.vale.ini linguist-generated
12 | /garnix.yaml linguist-generated
13 | /nix-ci.nix linguist-generated
14 |
--------------------------------------------------------------------------------
/docs/manual/writing-modules.md:
--------------------------------------------------------------------------------
1 | # Writing Project Manager Modules {#ch-writing-modules}
2 |
3 | The module system in Project Manager is based entirely on the NixOS module
4 | system so we will here only highlight aspects that are specific for Project
5 | Manager. For information about the module system as such please refer to
6 | the [Writing NixOS
7 | Modules](https://nixos.org/nixos/manual/index.html#sec-writing-modules)
8 | chapter of the NixOS manual.
9 |
10 | Overall the basic option types are the same in Project Manager as Home Manager. There are some extra options provided in the file-type submodule.
11 |
12 | ```{=include=} sections
13 | writing-modules/types.md
14 | ```
15 |
--------------------------------------------------------------------------------
/.github/workflows/flakestry-publish.yml:
--------------------------------------------------------------------------------
1 | name: "Publish a flake to flakestry"
2 | on:
3 | push:
4 | tags:
5 | - "v?[0-9]+.[0-9]+.[0-9]+"
6 | - "v?[0-9]+.[0-9]+"
7 | workflow_dispatch:
8 | inputs:
9 | tag:
10 | description: "The existing tag to publish"
11 | type: "string"
12 | required: true
13 | jobs:
14 | publish-flake:
15 | runs-on: ubuntu-24.04
16 | permissions:
17 | id-token: "write"
18 | contents: "read"
19 | steps:
20 | - uses: flakestry/flakestry-publish@main
21 | with:
22 | version: "${{ inputs.tag || github.ref_name }}"
23 |
--------------------------------------------------------------------------------
/.github/workflows/switch-pm-generation.yml:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | {"jobs":{"switch":{"if":"github.head_ref == 'renovate/lock-file-maintenance'","runs-on":"ubuntu-24.04","steps":[{"uses":"actions/checkout@v6","with":{"ref":"${{ github.event.pull_request.head.ref }}}","repository":"${{ github.event.pull_request.head.repo.full_name }}","token":"${{ secrets.PROJECT_MANAGER_TOKEN }}"}},{"uses":"cachix/install-nix-action@v31"},{"run":"nix develop .#project-manager --command project-manager kitchen-sink"},{"name":"commit changes","uses":"EndBug/add-and-commit@v9","with":{"add":"--all","default_author":"github_actions","message":"Switch Project Manager generation"}}]}},"name":"Project Manager","on":{"pull_request":{}}}
3 |
--------------------------------------------------------------------------------
/docs/project-configuration-nix-header.5:
--------------------------------------------------------------------------------
1 | .TH "PROJECT-CONFIGURATION\&.NIX" "5" "01/01/1980" "Project Manager"
2 | .\" disable hyphenation
3 | .nh
4 | .\" disable justification (adjust text to left margin only)
5 | .ad l
6 | .\" enable line breaks after slashes
7 | .cflags 4 /
8 | .SH "NAME"
9 | \fIproject\-configuration\&.nix\fP \- Project Manager configuration specification
10 | .SH "DESCRIPTION"
11 | .sp
12 | The file ~/\&.config/project/default\&.nix contains the declarative specification of your Project Manager configuration\&. The command \fBproject\-manager\fR takes this file and realises the user environment configuration specified therein\&.
13 | .SH "OPTIONS"
14 | .PP
15 | You can use the following options in
16 | project\-configuration\&.nix:
17 | .PP
18 |
--------------------------------------------------------------------------------
/docs/manual/faq/ca-desrt-dconf.md:
--------------------------------------------------------------------------------
1 |
2 | # Why do I get an error message about `ca.desrt.dconf` or `dconf.service`? {#_why_do_i_get_an_error_message_about_literal_ca_desrt_dconf_literal_or_literal_dconf_service_literal}
3 |
4 | You are most likely trying to configure something that uses dconf but
5 | the DBus session isn’t aware of the dconf service. The full error you
6 | might get is
7 |
8 | error: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name ca.desrt.dconf was not provided by any .service files
9 |
10 | or
11 |
12 | error: GDBus.Error:org.freedesktop.systemd1.NoSuchUnit: Unit dconf.service not found.
13 |
14 | The solution on NixOS is to add
15 |
16 | ```nix
17 | programs.dconf.enable = true;
18 | ```
19 |
20 | to your system configuration.
21 |
--------------------------------------------------------------------------------
/garnix.yaml:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | {"builds":[{"exclude":["devShells.aarch64-darwin.lax-checks","devShells.aarch64-linux.lax-checks","devShells.i686-linux.lax-checks","devShells.x86_64-linux.lax-checks","checks.*.formatter-22_11","checks.*.formatter-23_05","checks.*.formatter-23_11","checks.*.formatter-24_05","checks.*.formatter-24_11","checks.*.shellcheck-22_11","checks.*.shellcheck-23_05","checks.*.shellcheck-23_11","checks.*.shellcheck-24_05","checks.*.shellcheck-24_11","checks.aarch64-linux.formatter","checks.i686-linux.formatter","checks.x86_64-linux.formatter","checks.aarch64-linux.shellcheck","checks.i686-linux.shellcheck","checks.x86_64-linux.shellcheck","checks.aarch64-linux.vale","checks.i686-linux.vale","checks.x86_64-linux.vale","*.x86_64-darwin","*.x86_64-darwin.*","*.x86_64-darwin-example","*.x86_64-linux.*"],"include":["*.*","*.*.*"]}],"servers":[]}
3 |
--------------------------------------------------------------------------------
/.github/workflows/flakehub-publish.yml:
--------------------------------------------------------------------------------
1 | name: "Publish tags to FlakeHub"
2 | on:
3 | push:
4 | tags:
5 | - "v?[0-9]+.[0-9]+.[0-9]+*"
6 | workflow_dispatch:
7 | inputs:
8 | tag:
9 | description: "The existing tag to publish to FlakeHub"
10 | type: "string"
11 | required: true
12 | jobs:
13 | flakehub-publish:
14 | runs-on: "ubuntu-24.04"
15 | permissions:
16 | id-token: "write"
17 | contents: "read"
18 | steps:
19 | - uses: "actions/checkout@v6"
20 | with:
21 | ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
22 | - uses: "DeterminateSystems/nix-installer-action@main"
23 | - uses: "DeterminateSystems/flakehub-push@main"
24 | with:
25 | visibility: "public"
26 | name: "sellout/project-manager"
27 | tag: "${{ inputs.tag }}"
28 |
--------------------------------------------------------------------------------
/modules/lib/strings.nix:
--------------------------------------------------------------------------------
1 | {lib}: let
2 | inherit
3 | (lib)
4 | genList
5 | length
6 | lowerChars
7 | replaceStrings
8 | stringToCharacters
9 | upperChars
10 | ;
11 | in {
12 | # Figures out a valid Nix store name for the given path.
13 | storeFileName = path: let
14 | # All characters that are considered safe. Note "-" is not
15 | # included to avoid "-" followed by digit being interpreted as a
16 | # version.
17 | safeChars =
18 | ["+" "." "_" "?" "="]
19 | ++ lowerChars
20 | ++ upperChars
21 | ++ stringToCharacters "0123456789";
22 |
23 | empties = l: genList (x: "") (length l);
24 |
25 | unsafeInName =
26 | stringToCharacters (replaceStrings safeChars (empties safeChars) path);
27 |
28 | safeName = replaceStrings unsafeInName (empties unsafeInName) path;
29 | in
30 | "pm_" + safeName;
31 | }
32 |
--------------------------------------------------------------------------------
/.config/project/README.md:
--------------------------------------------------------------------------------
1 | # Example project configuration
2 |
3 | This isn’t only an example, but the actual project configuration used by this repository. See [the top-level README](../README.md) for more about what Project Manager is and how to use it.
4 |
5 | The key file here is [default.nix](./default.nix), which is explicitly imported by [the top-level flake.nix](../../flake.nix:). Everything else in this directory is imported by default.nix. default.nix has the general project metadata, and the other files contain configuration for various other programs and services that this repository relies on.
6 |
7 | This organization isn’t required. All these files could be combined, or perhaps just one or two large configurations extracted from default.nix. default.nix itself can be anywhere in your repository and named anything, so long as it’s referenced correctly in `projectConfigurations` in flake.nix.
8 |
--------------------------------------------------------------------------------
/modules/services/flakehub/tests/enabled/.github/workflows/flakehub-publish.yml:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | name: "Publish tags to FlakeHub"
3 | on:
4 | push:
5 | tags:
6 | - "v?[0-9]+.[0-9]+.[0-9]+*"
7 | workflow_dispatch:
8 | inputs:
9 | tag:
10 | description: "The existing tag to publish to FlakeHub"
11 | type: "string"
12 | required: true
13 | jobs:
14 | flakehub-publish:
15 | runs-on: "ubuntu-24.04"
16 | permissions:
17 | id-token: "write"
18 | contents: "read"
19 | steps:
20 | - uses: "actions/checkout@v6"
21 | with:
22 | ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
23 | - uses: "DeterminateSystems/nix-installer-action@main"
24 | - uses: "DeterminateSystems/flakehub-push@main"
25 | with:
26 | visibility: "unlisted"
27 | name: "sellout/example"
28 | tag: "${{ inputs.tag }}"
29 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | {"concurrency":{"cancel-in-progress":false,"group":"pages"},"jobs":{"build":{"runs-on":"ubuntu-24.04","steps":[{"name":"Checkout","uses":"actions/checkout@v6"},{"name":"Setup Pages","uses":"actions/configure-pages@v4"},{"uses":"cachix/install-nix-action@v24","with":{"extra_nix_config":"extra-trusted-public-keys = cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=\nextra-substituters = https://cache.garnix.io\n"}},{"run":"nix build .#docs-html\ncp -r result/share/doc/project-manager ./_site\n"},{"name":"Upload artifact","uses":"actions/upload-pages-artifact@v3"}]},"deploy":{"environment":{"name":"github-pages","url":"${{ steps.deployment.outputs.page_url }}"},"needs":"build","runs-on":"ubuntu-24.04","steps":[{"id":"deployment","name":"Deploy to GitHub Pages","uses":"actions/deploy-pages@v4"}]}},"name":"Deploy generated docs to Pages","on":{"push":{"branches":["main"]},"workflow_dispatch":{}},"permissions":{"contents":"read","id-token":"write","pages":"write"}}
3 |
--------------------------------------------------------------------------------
/docs/manual/faq/unstable.md:
--------------------------------------------------------------------------------
1 |
2 | # How do I install packages from Nixpkgs unstable? {#_how_do_i_install_packages_from_nixpkgs_unstable}
3 |
4 | If you are using a stable version of Nixpkgs but would like to install
5 | some particular packages from Nixpkgs unstable – or some other channel
6 | – then you can import the unstable Nixpkgs and refer to its packages
7 | within your configuration. Something like
8 |
9 | ```nix
10 | { pkgs, config, ... }:
11 |
12 | let
13 |
14 | pkgsUnstable = import {};
15 |
16 | in
17 |
18 | {
19 | project.devPackages = [
20 | pkgsUnstable.foo
21 | ];
22 |
23 | # …
24 | }
25 | ```
26 |
27 | should work provided you have a Nix channel called `nixpkgs-unstable`.
28 |
29 | You can add the `nixpkgs-unstable` channel by running
30 |
31 | ```shell
32 | $ nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
33 | $ nix-channel --update
34 | ```
35 |
36 | Note, the package won’t be affected by any package overrides,
37 | overlays, etc.
38 |
--------------------------------------------------------------------------------
/nix/lib/configuration.nix:
--------------------------------------------------------------------------------
1 | {
2 | flake-utils,
3 | pkgsFor,
4 | self,
5 | treefmt-nix,
6 | }: let
7 | project-manager = self;
8 | in
9 | {
10 | self,
11 | modules ? [],
12 | pkgs,
13 | lib ? pkgs.lib,
14 | extraSpecialArgs ? {},
15 | check ? true,
16 | supportedSystems ? flake-utils.lib.defaultSystems,
17 | }:
18 | import ../../modules {
19 | inherit check extraSpecialArgs lib pkgs;
20 | configuration = {
21 | imports =
22 | modules
23 | ++ [
24 | {
25 | _module.args = {
26 | inherit
27 | project-manager
28 | self
29 | supportedSystems
30 | treefmt-nix
31 | ;
32 | ## The pkgs used by Project Manager itself, also used in modules
33 | ## in certain cases.
34 | pmPkgs = pkgsFor pkgs.system;
35 | };
36 | programs.project-manager.path = toString ../.;
37 | }
38 | ];
39 | };
40 | modules = builtins.attrValues project-manager.projectModules;
41 | }
42 |
--------------------------------------------------------------------------------
/modules/services/renovate/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | ...
5 | }: let
6 | cfg = config.services.renovate;
7 | in {
8 | meta.maintainers = [lib.maintainers.sellout];
9 |
10 | options.services.renovate = {
11 | enable = lib.mkEnableOption "Renovate";
12 |
13 | settings = lib.mkOption {
14 | type = lib.types.attrs;
15 | default = {};
16 | description = ''
17 | Configuration written to {file}`$PROJECT_ROOT/renovate.json`.
18 | See for documentation.
19 | '';
20 | };
21 | };
22 |
23 | config = lib.mkIf cfg.enable (let
24 | ## Renovate looks in multiple places for the configuration, so, when
25 | ## possible, we hide it in a directory that will already exist.
26 | location =
27 | if config.services.github.enable
28 | then ".github"
29 | else ".";
30 | in {
31 | project.file."${location}/renovate.json".text = lib.pm.generators.toJSON {} ({
32 | "$schema" = "https://docs.renovatebot.com/renovate-schema.json";
33 | extends = ["config:recommended"];
34 | nix.enabled = true;
35 | }
36 | // cfg.settings);
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/modules/programs/git/tests/settings/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | programs.git = {
3 | enable = true;
4 | attributes = {
5 | "/some-file".linguist-generated = true;
6 | "*.pdf".diff = "pdf";
7 | meh.important = 100;
8 | "*.bin" = {
9 | diff = false;
10 | important = 50;
11 | };
12 | other.unset = null;
13 | };
14 |
15 | includes = [
16 | {path = "~/some-extra/git/config.inc";}
17 | {
18 | path = "~/some-extra/git/conditional.inc";
19 | condition = "gitdir:~/src/dir";
20 | }
21 | ];
22 |
23 | ignores = [
24 | "/.locally-generated"
25 | "/.cache"
26 | ];
27 | };
28 |
29 | ## NB: We remove the leading “.” from the filename so that it doesn’t
30 | ## actually ignore things.
31 | nmt.script = ''
32 | assertFileExists "project-files/.git/config"
33 | assertFileContent "project-files/.git/config" ${./git/config}
34 | assertFileExists "project-files/.gitattributes"
35 | assertFileContent "project-files/.gitattributes" ${./.gitattributes}
36 | assertFileExists "project-files/.gitignore"
37 | assertFileContent "project-files/.gitignore" ${./gitignore}
38 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2023 Home Manager contributors
4 | Copyright (c) 2023 Project Manager contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/docs/html-open-tool.nix:
--------------------------------------------------------------------------------
1 | {
2 | writeShellScriptBin,
3 | makeDesktopItem,
4 | symlinkJoin,
5 | }: {
6 | html,
7 | pathName ? "project-manager",
8 | projectName ? pathName,
9 | name ? "${pathName}-help",
10 | }: let
11 | helpScript = writeShellScriptBin name ''
12 | set -euo pipefail
13 | if [[ ! -v BROWSER || -z $BROWSER ]]; then
14 | for candidate in xdg-open open w3m; do
15 | BROWSER="$(type -P $candidate || true)"
16 | if [[ -x $BROWSER ]]; then
17 | break;
18 | fi
19 | done
20 | fi
21 | if [[ ! -v BROWSER || -z $BROWSER ]]; then
22 | echo "$0: unable to start a web browser; please set \$BROWSER"
23 | exit 1
24 | else
25 | exec "$BROWSER" "${html}/share/doc/${pathName}/index.xhtml"
26 | fi
27 | '';
28 |
29 | desktopItem = makeDesktopItem {
30 | name = "${pathName}-manual";
31 | desktopName = "${projectName} Manual";
32 | genericName = "View ${projectName} documentation in a web browser";
33 | icon = "nix-snowflake";
34 | exec = "${helpScript}/bin/${name}";
35 | categories = ["System"];
36 | };
37 | in
38 | symlinkJoin {
39 | inherit name;
40 | paths = [helpScript desktopItem];
41 | }
42 |
--------------------------------------------------------------------------------
/modules/all-modules.nix:
--------------------------------------------------------------------------------
1 | {
2 | modules,
3 | pkgs,
4 | # Note, this should be "the standard library" + PM extensions.
5 | lib,
6 | # Whether to enable module type checking.
7 | check ? true,
8 | }: let
9 | ## This includes modules from upstream projects, like nixpkgs.
10 | allModules =
11 | modules
12 | ++ [
13 | ## FIXME: nixpkgs should expose these via flake outputs (and all of its
14 | ## modules) so we could instead have
15 | ## > nixpkgs.nixosModules.assertions
16 | ## > nixpkgs.nixosModules.lib
17 | ## > nixpkgs.nixosModules.meta
18 | ## or possibly even re-export them in _our_ `projectModules`.
19 | ## See NixOS/nixpkgs#???)
20 | (pkgs.path + "/nixos/modules/misc/assertions.nix")
21 | (pkgs.path + "/nixos/modules/misc/lib.nix")
22 | (pkgs.path + "/nixos/modules/misc/meta.nix")
23 | ];
24 | in
25 | allModules
26 | ++ [
27 | ({...}: {
28 | config = {
29 | _module.args.baseModules = allModules;
30 | _module.args.pkgsPath = lib.mkDefault pkgs.path;
31 | _module.args.pkgs = lib.mkDefault pkgs;
32 | _module.check = check;
33 | lib = lib.pm;
34 | };
35 | })
36 | ]
37 |
--------------------------------------------------------------------------------
/modules/lib/types/tests/dag-submodule.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | inherit
8 | (lib)
9 | concatStringsSep
10 | pm
11 | mkOption
12 | types
13 | ;
14 |
15 | dag = lib.pm.dag;
16 |
17 | result = let
18 | sorted = dag.topoSort config.tested.dag;
19 | data = map (e: "${e.name}:${e.data.name}") sorted.result;
20 | in
21 | concatStringsSep "\n" data + "\n";
22 | in {
23 | options.tested.dag = mkOption {
24 | type = pm.types.dagOf (
25 | types.submodule (
26 | {dagName, ...}: {
27 | options.name = mkOption {type = types.str;};
28 | config.name = "dn-${dagName}";
29 | }
30 | )
31 | );
32 | };
33 |
34 | config = {
35 | tested.dag = {
36 | after = {};
37 | before = dag.entryBefore ["after"] {};
38 | between = dag.entryBetween ["after"] ["before"] {};
39 | };
40 |
41 | project.file."result.txt".text = result;
42 |
43 | nmt.script = ''
44 | assertFileContent \
45 | project-files/result.txt \
46 | ${pkgs.writeText "result.txt" ''
47 | before:dn-before
48 | between:dn-between
49 | after:dn-after
50 | ''}
51 | '';
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/docs/manual/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing {#ch-contributing}
2 |
3 | Contributions to Project Manager are very welcome. To make the process as
4 | smooth as possible for both you and the Project Manager maintainers we
5 | provide some guidelines that we ask you to follow. See [Getting
6 | started](#sec-contrib-getting-started) for information on how to set up
7 | a suitable development environment and [Guidelines](#sec-guidelines) for
8 | the actual guidelines.
9 |
10 | This text is mainly directed at those who would like to make code
11 | contributions to Project Manager. If you just want to report a bug then
12 | first look among the already [open
13 | issues](https://github.com/sellout/project-manager/issues), if you
14 | find one matching yours then feel free to comment on it to add any
15 | additional information you may have. If no matching issue exists then go
16 | to the [new
17 | issue](https://github.com/sellout/project-manager/issues/new) page
18 | and write a description of your problem. Include as much information as
19 | you can, ideally also include relevant excerpts from your Project Manager
20 | configuration.
21 |
22 | ```{=include=} sections
23 | contributing/getting-started.md
24 | contributing/guidelines.md
25 | contributing/news.md
26 | contributing/tests.md
27 | ```
28 |
--------------------------------------------------------------------------------
/modules/services/flakestry.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | cfg = config.services.flakestry;
8 | in {
9 | meta.maintainers = [lib.maintainers.sellout];
10 |
11 | options.services.flakestry = {
12 | enable = lib.mkEnableOption "Flakestry";
13 | };
14 |
15 | config = lib.mkIf cfg.enable {
16 | ## Written as `lines` instead of an attr set to make it easier to compare
17 | ## against https://flakestry.dev/publish.
18 | services.github.workflow."flakestry-publish.yml".text = ''
19 | name: "Publish a flake to flakestry"
20 | on:
21 | push:
22 | tags:
23 | - "v?[0-9]+.[0-9]+.[0-9]+"
24 | - "v?[0-9]+.[0-9]+"
25 | workflow_dispatch:
26 | inputs:
27 | tag:
28 | description: "The existing tag to publish"
29 | type: "string"
30 | required: true
31 | jobs:
32 | publish-flake:
33 | runs-on: ubuntu-24.04
34 | permissions:
35 | id-token: "write"
36 | contents: "read"
37 | steps:
38 | - uses: flakestry/flakestry-publish@main
39 | with:
40 | version: "''${{ inputs.tag || github.ref_name }}"
41 | '';
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/docs/manual/usage/rollbacks.md:
--------------------------------------------------------------------------------
1 | # Rollbacks {#sec-usage-rollbacks}
2 |
3 | While the `project-manager` tool doesn’t explicitly support rollbacks at
4 | the moment it’s relatively easy to perform one manually. The steps to
5 | do so are
6 |
7 | 1. Run `project-manager generations` to determine which generation you
8 | wish to rollback to:
9 |
10 | ```shell
11 | $ project-manager generations
12 | 2018-01-04 11:56 : id 765 -> /nix/store/kahm1rxk77mnvd2l8pfvd4jkkffk5ijk-project-manager-generation
13 | 2018-01-03 10:29 : id 764 -> /nix/store/2wsmsliqr5yynqkdyjzb1y57pr5q2lsj-project-manager-generation
14 | 2018-01-01 12:21 : id 763 -> /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-project-manager-generation
15 | 2017-12-29 21:03 : id 762 -> /nix/store/6c0k1r03fxckql4vgqcn9ccb616ynb94-project-manager-generation
16 | 2017-12-25 18:51 : id 761 -> /nix/store/czc5y6vi1rvnkfv83cs3rn84jarcgsgh-project-manager-generation
17 | …
18 | ```
19 |
20 | 2. Copy the Nix store path of the generation you chose, for example,
21 |
22 | /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-project-manager-generation
23 |
24 | for generation 763.
25 |
26 | 3. Run the `activate` script inside the copied store path:
27 |
28 | ```shell
29 | $ /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-project-manager-generation/activate
30 | Starting Project Manager activation
31 | …
32 | ```
33 |
--------------------------------------------------------------------------------
/modules/modules.nix:
--------------------------------------------------------------------------------
1 | {
2 | files = ./files;
3 | # ./misc/dconf.nix
4 | # ./misc/debug.nix
5 | editorconfig = ./misc/editorconfig;
6 | news = ./misc/news.nix;
7 | # ./misc/specialisation.nix
8 | version = ./misc/version.nix;
9 | # ./misc/vte.nix
10 | xdg = ./misc/xdg.nix;
11 | # ./programs/bash.nix
12 | # ./programs/darcs.nix
13 | direnv = ./programs/direnv.nix;
14 | # ./programs/emacs.nix
15 | # ./programs/fish.nix
16 | # ./programs/gh.nix
17 | # ./programs/gh-dash.nix
18 | # ./programs/git-cliff.nix
19 | # ./programs/git-credential-oauth.nix
20 | git = ./programs/git;
21 | just = ./programs/just.nix;
22 | mercurial = ./programs/mercurial.nix;
23 | # ./programs/neovim.nix
24 | # ./programs/nushell.nix
25 | project-manager = ./programs/project-manager.nix;
26 | # ./programs/pylint.nix
27 | shellcheck = ./programs/shellcheck.nix;
28 | treefmt = ./programs/treefmt.nix;
29 | vale = ./programs/vale.nix;
30 | # ./programs/vim.nix
31 | # ./programs/vscode.nix
32 | # ./programs/zellij.nix
33 | # ./programs/zsh.nix
34 | project-environment = ./project-environment.nix;
35 | flakehub = ./services/flakehub;
36 | flakestry = ./services/flakestry.nix;
37 | garnix = ./services/garnix.nix;
38 | github = ./services/github.nix;
39 | # ./services/git-sync.nix
40 | # ./services/lorri.nix
41 | nix-cx = ./services/nix-ci.nix;
42 | renovate = ./services/renovate;
43 | }
44 | # // optional useNixpkgsModule ./misc/nixpkgs.nix
45 | # // optional (!useNixpkgsModule) ./misc/nixpkgs-disabled.nix
46 |
47 |
--------------------------------------------------------------------------------
/docs/manual/nix-flakes/prerequisites.md:
--------------------------------------------------------------------------------
1 | # Prerequisites {#sec-flakes-prerequisites}
2 |
3 | - Install Nix 2.4 or later, or have it in `nix-shell`.
4 |
5 | - Enable experimental features `nix-command` and `flakes`.
6 |
7 | - When using NixOS, add the following to your `configuration.nix`
8 | and rebuild your system.
9 |
10 | ```nix
11 | nix = {
12 | package = pkgs.nixFlakes;
13 | extraOptions = ''
14 | experimental-features = nix-command flakes
15 | '';
16 | };
17 | ```
18 |
19 | - If you aren’t using NixOS, add the following to `nix.conf`
20 | (located at `~/.config/nix/` or `/etc/nix/nix.conf`).
21 |
22 | ```bash
23 | experimental-features = nix-command flakes
24 | ```
25 |
26 | You may need to restart the Nix daemon with, for example,
27 | `sudo systemctl restart nix-daemon.service`.
28 |
29 | - You can also enable flakes on a per-command basis with
30 | the following extra flags to `nix` and `project-manager`:
31 |
32 | ```shell
33 | $ nix --extra-experimental-features "nix-command flakes"
34 | $ project-manager --extra-experimental-features "nix-command flakes"
35 | ```
36 |
37 | - Prepare your Project Manager configuration (`.config/project/default.nix`).
38 |
39 | Unlike the channel-based setup, `.config/project/default.nix` will be evaluated when
40 | the flake is built, so it must be present before bootstrap of Project
41 | Manager from the flake. See [Configuration Example](#sec-usage-configuration) for
42 | introduction about writing a Project Manager configuration.
43 |
--------------------------------------------------------------------------------
/modules/lib/generators.nix:
--------------------------------------------------------------------------------
1 | ## This provides the generators provided by Nixpkgs with two changes:
2 | ##
3 | ## 1. when possible, it adds a “generated by Project Manager” comment and
4 | ## 2. when necessary, it adds a trailing newline, to make it a valid POSIX file.
5 | {lib}: let
6 | message = "This file was generated by Project Manager.";
7 | in {
8 | toDhall = args: v:
9 | ''
10 | --| ${message}
11 | ''
12 | + lib.generators.toDhall args v;
13 | toGitINI = v:
14 | ''
15 | ; ${message}
16 | ''
17 | + lib.generators.toGitINI v;
18 | toINI = args: v:
19 | ''
20 | ; ${message}
21 | ''
22 | + lib.generators.toINI args v;
23 | toINIWithGlobalSection = args: v:
24 | ''
25 | ; ${message}
26 | ''
27 | + lib.generators.toINIWithGlobalSection args v;
28 | ## NB: JSON doesn't support comments, but we re-export `toJSON` here for
29 | ## consistency.
30 | toJSON = args: v: lib.generators.toJSON args v + "\n";
31 | toLua = {indent, ...} @ args: v:
32 | ''
33 | ${indent}-- ${message}
34 | ''
35 | + lib.generators.toLua args v;
36 | toPlist = args: v:
37 | ''
38 |
39 | ''
40 | + lib.generators.toPlist args v;
41 | toPretty = args: v:
42 | ''
43 | # ${message}
44 | ''
45 | + lib.generators.toPretty args v
46 | + "\n";
47 | toTOML = args: v:
48 | ''
49 | # ${message}
50 | ''
51 | + lib.generators.toINIWithGlobalSection args v;
52 | toYAML = options: v:
53 | ''
54 | # ${message}
55 | ''
56 | + lib.generators.toYAML options v
57 | + "\n";
58 | }
59 |
--------------------------------------------------------------------------------
/docs/manual/usage/dotfiles.md:
--------------------------------------------------------------------------------
1 | # Keeping your project root safe from harm {#sec-usage-dotfiles}
2 |
3 | To configure programs and services Project Manager must write various things to your project directory. Project Manager will attempt to detect collisions with existing files, but this can be difficult. Whereas Home Manager can rely on symlinks to identify files that have been produced during activation, Project Manager can’t always do this. Files with [“repository” persistence](#opt-project.file._name_.minimum-persistence) must be either hard links or copies (if the project is on a different volume than the Nix store). Project Manager must assume these files can be overwritten. This is why it’s important to ensure that changes are stashed or committed before running `project-manager switch`.
4 |
5 | If there are collisions between existing files and files with `worktree` persistence, these will cause Project Manager to terminate before changing _any_ files. (Files with “store” persistence don’t exist in the working tree and thus can never prevent switching.)
6 |
7 | For example, suppose you have a wonderful, painstakingly created `$PROJECT_ROOT/.git/config` and add
8 |
9 | ```nix
10 | {
11 | # …
12 |
13 | programs.git = {
14 | enable = true;
15 | userName = "Jane Doe";
16 | userEmail = "jane.doe@example.org";
17 | };
18 |
19 | # …
20 | }
21 | ```
22 |
23 | to your configuration. Attempting to switch to the generation will then result in
24 |
25 | ```shell
26 | $ project-manager switch
27 | …
28 | Activating checkLinkTargets
29 | Existing file '/…/.git/config' is in the way
30 | Please move the above files and try again
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/manual/contributing/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting started {#sec-contrib-getting-started}
2 |
3 |
4 |
5 | If you haven’t forked Project Manager yet, then you need to do that
6 | first. Have a look at GitHub's [Fork a
7 | repo](https://help.github.com/articles/fork-a-repo/) for instructions on
8 | how to do this.
9 |
10 |
11 |
12 | Once you have a fork of Project Manager you should create a branch starting
13 | at the most recent `master` branch. Give your branch a reasonably
14 | descriptive name. Commit your changes to this branch and when you are
15 | happy with the result and it fulfills [Guidelines](#sec-guidelines) then
16 | push the branch to GitHub and [create a pull
17 | request](https://help.github.com/articles/creating-a-pull-request/).
18 |
19 | Assuming your clone is at `$HOME/devel/project-manager` then you can make
20 | the `project-manager` command use it by either
21 |
22 | 1. overriding the default path by using the `-I` command line option:
23 |
24 | ```shell
25 | $ project-manager -I project-manager=$HOME/devel/project-manager
26 | ```
27 |
28 | or, if using [flakes](#sec-flakes-standalone):
29 |
30 | ```shell
31 | $ project-manager --override-input project-manager ~/devel/project-manager
32 | ```
33 |
34 | or
35 |
36 | 2. changing the default path by ensuring your configuration includes
37 |
38 | ```nix
39 | programs.project-manager.enable = true;
40 | programs.project-manager.path = "$HOME/devel/project-manager";
41 | ```
42 |
43 | and running `project-manager switch` to activate the change.
44 | Afterward, `project-manager build` and `project-manager switch` will use
45 | your cloned repository.
46 |
47 | The first option is good if you only temporarily want to use your clone.
48 |
--------------------------------------------------------------------------------
/docs/manual/installation.md:
--------------------------------------------------------------------------------
1 | # Installing Project Manager {#ch-installation}
2 |
3 | **NB**: Project Manager doesn’t need to be installed outside of the projects it’s used in. In most projects that use Project Manager, `nix develop` (or maybe `nix develop .#project-manager`) should put you into a shell that makes `project-manager` available to you. The exception to this is when you are [adding Project Manager to a project](#ch-quick-start).
4 |
5 | That said, it can be useful to have Project Manager installed more widely. Currently this can only be done via a flake. This snippet includes examples for nix-darwin, Home Manager, and NixOS. You don’t need to include all of them.
6 |
7 | ```nix
8 | {
9 | outputs = {home-manager, nix-darwin, nixpkgs, project-manager, self ...}: let
10 | system = "x86_64-linux"; # or another system
11 | pkgs = import nixpkgs {
12 | inherit system;
13 | overlays = [project-manager.overlays.default];
14 | };
15 | {
16 | darwinConfigurations."host" = nix-darwin.lib.homeManagerConfiguration {
17 | inherit pkgs;
18 | modules = [
19 | ({pkgs, ...}: {
20 | environment.systemPackages = [pkgs.project-manager];
21 | })
22 | ];
23 | };
24 |
25 | homeConfigurations."user@host" = home-manager.lib.homeManagerConfiguration {
26 | inherit pkgs;
27 | modules = [
28 | ({pkgs, ...}: {
29 | home.packages = [pkgs.project-manager];
30 | })
31 | ];
32 | };
33 |
34 | nixosConfigurations."host" = nixpkgs.lib.nixosSystem {
35 | inherit pkgs system;
36 | modules = [
37 | ({pkgs, ...}: {
38 | environment.systemPackages = [pkgs.project-manager];
39 | })
40 | ];
41 | };
42 | };
43 |
44 | inputs.project-manager.url = "github:sellout/project-manager";
45 | }
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/project-manager-manual.nix:
--------------------------------------------------------------------------------
1 | {
2 | stdenv,
3 | lib,
4 | documentation-highlighter,
5 | revision,
6 | project-manager-options,
7 | nixos-render-docs,
8 | }: let
9 | outputPath = "share/doc/project-manager";
10 | in
11 | stdenv.mkDerivation {
12 | name = "project-manager-manual";
13 |
14 | nativeBuildInputs = [nixos-render-docs];
15 |
16 | src = ./manual;
17 |
18 | buildPhase = ''
19 | mkdir -p out/{highlightjs,media}
20 |
21 | cp -t out/highlightjs \
22 | ${documentation-highlighter}/highlight.pack.js \
23 | ${documentation-highlighter}/LICENSE \
24 | ${documentation-highlighter}/mono-blue.css \
25 | ${documentation-highlighter}/loader.js
26 |
27 | substituteInPlace ./options.md \
28 | --subst-var-by \
29 | OPTIONS_JSON \
30 | ${project-manager-options.project-manager}/share/doc/nixos/options.json
31 |
32 | cp ${./options.html} out/options.html
33 |
34 | cp ${./static/style.css} out/style.css
35 |
36 | cp -r ${./release-notes} release-notes
37 |
38 | nixos-render-docs manual html \
39 | --manpage-urls ./manpage-urls.json \
40 | --revision ${lib.trivial.revisionWithDefault revision} \
41 | --stylesheet style.css \
42 | --script highlightjs/highlight.pack.js \
43 | --script highlightjs/loader.js \
44 | --toc-depth 1 \
45 | --section-toc-depth 1 \
46 | manual.md \
47 | out/index.xhtml
48 | '';
49 |
50 | installPhase = ''
51 | dest="$out/${outputPath}"
52 | mkdir -p "$(dirname "$dest")"
53 | mv out "$dest"
54 |
55 | mkdir -p $out/nix-support/
56 | echo "doc manual $dest index.html" >> $out/nix-support/hydra-build-products
57 | '';
58 |
59 | meta = {maintainers = [lib.maintainers.considerate];};
60 | }
61 |
--------------------------------------------------------------------------------
/modules/lib/path.nix:
--------------------------------------------------------------------------------
1 | {lib}: {
2 | ## This isn't in Nixpkgs 23.05, but should be in the next release.
3 | subpath.components = path: let
4 | parts = builtins.split "/+(\\./+)*" path;
5 | partCount = lib.length parts / 2 + 1;
6 | skipStart =
7 | if lib.head parts == "."
8 | then 1
9 | else 0;
10 | skipEnd =
11 | if lib.last parts == "." || lib.last parts == ""
12 | then 1
13 | else 0;
14 | componentCount = partCount - skipEnd - skipStart;
15 | in
16 | if path == "."
17 | then []
18 | else
19 | lib.genList (
20 | index:
21 | lib.elemAt parts ((skipStart + index) * 2)
22 | )
23 | componentCount;
24 |
25 | ## Find a path to `dest` from `source`. `source` must be a directory, not a
26 | ## file.
27 | ##
28 | ## TODO: This should normalize. Right now it always uses ../ up to the common
29 | ## parent then rebuilds the entire path. E.g.,
30 | ## `route ./foo/bar/baz ./foo/bar/zab` is `../../../foo/bar/zab`, but it
31 | ## could be `../zab`.
32 | routeFromDir = sourceDir: destPath:
33 | lib.concatStringsSep
34 | "/"
35 | (map (_: "..") (lib.tail (lib.pm.path.subpath.components sourceDir)))
36 | + "/"
37 | + destPath;
38 |
39 | ## Find a path to `dest` from `source`. `source` must be a directory, not a
40 | ## file.
41 | ##
42 | ## TODO: This should normalize. Right now it always uses ../ up to the common
43 | ## parent then rebuilds the entire path. E.g.,
44 | ## `route ./foo/bar/baz ./foo/bar/zab` is `../../../foo/bar/zab`, but it
45 | ## could be `../zab`.
46 | routeFromFile = sourceFile: destPath:
47 | lib.concatStringsSep
48 | "/"
49 | (map (_: "..") (lib.tail (lib.init (lib.pm.path.subpath.components sourceFile))))
50 | + "/"
51 | + destPath;
52 | }
53 |
--------------------------------------------------------------------------------
/modules/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | configuration,
3 | modules,
4 | pkgs,
5 | lib ? pkgs.lib,
6 | # Whether to check that each option has a matching declaration.
7 | check ? true,
8 | # Extra arguments passed to specialArgs.
9 | extraSpecialArgs ? {},
10 | }: let
11 | collectFailed = cfg:
12 | map (x: x.message) (lib.filter (x: !x.assertion) cfg.assertions);
13 |
14 | showWarnings = res: let
15 | f = w: x: builtins.trace "[1;31mwarning: ${w}[0m" x;
16 | in
17 | lib.fold f res res.config.warnings;
18 |
19 | extendedLib = import ./lib/stdlib-extended.nix lib;
20 |
21 | pmModules = import ./all-modules.nix {
22 | inherit check modules pkgs;
23 | lib = extendedLib;
24 | };
25 |
26 | rawModule = extendedLib.evalModules {
27 | modules = [configuration] ++ pmModules;
28 | specialArgs =
29 | {
30 | modulesPath = builtins.toString ./.;
31 | }
32 | // extraSpecialArgs;
33 | };
34 |
35 | module = showWarnings (
36 | let
37 | failed = collectFailed rawModule.config;
38 | failedStr = lib.concatStringsSep "\n" (map (x: "- ${x}") failed);
39 | in
40 | if failed == []
41 | then rawModule
42 | else throw "\nFailed assertions:\n${failedStr}"
43 | );
44 | in {
45 | inherit (module) options config;
46 |
47 | inherit
48 | (module.config.project)
49 | packages
50 | checks
51 | sandboxedChecks
52 | unsandboxedChecks
53 | devShells
54 | formatter
55 | cleanRepositoryPersisted
56 | cleanRepositoryPersistedExcept
57 | filterRepositoryPersisted
58 | filterRepositoryPersistedExcept
59 | ;
60 |
61 | newsDisplay = rawModule.config.news.display;
62 | newsEntries = lib.sort (a: b: a.time > b.time) (
63 | lib.filter (a: a.condition) rawModule.config.news.entries
64 | );
65 |
66 | inherit (module._module.args) pkgs;
67 | }
68 |
--------------------------------------------------------------------------------
/project-manager/build-news.nix:
--------------------------------------------------------------------------------
1 | # Used by the project-manager tool to present news to the user. The content of
2 | # this file is considered internal and the exported fields may change without
3 | # warning.
4 | {
5 | newsJsonFile,
6 | newsReadIdsFile ? null,
7 | }: let
8 | inherit
9 | (builtins)
10 | concatStringsSep
11 | filter
12 | hasAttr
13 | isString
14 | length
15 | readFile
16 | replaceStrings
17 | sort
18 | split
19 | ;
20 |
21 | newsJson = builtins.fromJSON (builtins.readFile newsJsonFile);
22 |
23 | # Sorted and relevant entries.
24 | relevantEntries =
25 | sort (a: b: a.time > b.time) (filter (e: e.condition) newsJson.entries);
26 |
27 | newsReadIds =
28 | if newsReadIdsFile == null
29 | then {}
30 | else let
31 | ids = filter isString (split "\n" (readFile newsReadIdsFile));
32 | in
33 | builtins.listToAttrs (map (id: {
34 | name = id;
35 | value = null;
36 | })
37 | ids);
38 |
39 | newsIsRead = entry: hasAttr entry.id newsReadIds;
40 |
41 | newsUnread = let
42 | pred = entry: entry.condition && !newsIsRead entry;
43 | in
44 | filter pred relevantEntries;
45 |
46 | prettyTime = t: replaceStrings ["T" "+00:00"] [" " ""] t;
47 |
48 | layoutNews = entries: let
49 | mkTextEntry = entry: let
50 | flag =
51 | if newsIsRead entry
52 | then "read"
53 | else "unread";
54 | in ''
55 | * ${prettyTime entry.time} [${flag}]
56 |
57 | ${replaceStrings ["\n"] ["\n "] entry.message}
58 | '';
59 | in
60 | concatStringsSep "\n\n" (map mkTextEntry entries);
61 | in {
62 | meta = {
63 | numUnread = length newsUnread;
64 | display = newsJson.display;
65 | ids = concatStringsSep "\n" (map (e: e.id) newsJson.entries);
66 | };
67 | news = {
68 | all = layoutNews relevantEntries;
69 | unread = layoutNews newsUnread;
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/modules/programs/treefmt.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | pmPkgs,
6 | self,
7 | treefmt-nix,
8 | ...
9 | }: let
10 | cfg = config.programs.treefmt;
11 | in {
12 | meta.maintainers = [lib.maintainers.sellout];
13 |
14 | options.programs.treefmt = {
15 | enable = lib.mkEnableOption "treefmt";
16 |
17 | package = lib.mkPackageOption pkgs "treefmt" {};
18 |
19 | projectRootFile = lib.mkOption {
20 | type = lib.types.str;
21 | default = "flake.nix";
22 | description = ''
23 | The file to use to identify the root of the project for formatting.
24 | '';
25 | };
26 |
27 | programs = lib.mkOption {
28 | type = lib.types.attrsOf lib.types.attrs;
29 | default = {};
30 | description = ''
31 | Configuration for treefmt formatters. See
32 | https://github.com/numtide/treefmt-nix#configuration for details.
33 | '';
34 | };
35 |
36 | settings = lib.mkOption {
37 | type = lib.types.attrsOf lib.types.attrs;
38 | default = {};
39 | description = ''
40 | Settings for treefmt. See
41 | https://github.com/numtide/treefmt-nix#configuration for details.
42 | '';
43 | };
44 | };
45 |
46 | config = lib.mkIf cfg.enable (let
47 | newExcludes =
48 | lib.mapAttrsToList (k: v: v.target)
49 | (lib.filterAttrs (k: v: v.persistence == "repository")
50 | config.project.file);
51 | format = treefmt-nix.lib.evalModule pmPkgs {
52 | inherit (cfg) package projectRootFile programs;
53 | settings =
54 | cfg.settings
55 | // {
56 | global.excludes =
57 | (cfg.settings.global.excludes or []) ++ newExcludes;
58 | };
59 | };
60 | in {
61 | project = {
62 | checks.formatter = format.config.build.check self;
63 | formatter = format.config.build.wrapper;
64 | devPackages = [format.config.build.wrapper];
65 | };
66 | });
67 | }
68 |
--------------------------------------------------------------------------------
/docs/manual/faq/change-package-module.md:
--------------------------------------------------------------------------------
1 |
2 | # How do I change the package used by a module? {#_how_do_i_change_the_package_used_by_a_module}
3 |
4 | By default Project Manager will install the package provided by your chosen
5 | `nixpkgs` channel but occasionally you might end up needing to change
6 | this package. This can typically be done in two ways.
7 |
8 | 1. If the module provides a `package` option, such as
9 | `programs.beets.package`, then this is the recommended way to
10 | perform the change. For example,
11 |
12 | ```nix
13 | programs.beets.package = pkgs.beets.override { pluginOverrides = { beatport.enable = false; }; };
14 | ```
15 |
16 | See [Nix pill 17](https://nixos.org/guides/nix-pills/nixpkgs-overriding-packages.html)
17 | for more information on package overrides. Alternatively, if you want
18 | to use the `beets` package from Nixpkgs unstable, then a configuration like
19 |
20 | ```nix
21 | { pkgs, config, ... }:
22 |
23 | let
24 |
25 | pkgsUnstable = import {};
26 |
27 | in
28 |
29 | {
30 | programs.beets.package = pkgsUnstable.beets;
31 |
32 | # …
33 | }
34 | ```
35 |
36 | should work OK.
37 |
38 | 2. If no `package` option is available then you can typically change
39 | the relevant package using an
40 | [overlay](https://nixos.org/nixpkgs/manual/#chap-overlays).
41 |
42 | For example, if you want to use the `programs.skim` module but use
43 | the `skim` package from Nixpkgs unstable, then a configuration like
44 |
45 | ```nix
46 | { pkgs, config, ... }:
47 |
48 | let
49 |
50 | pkgsUnstable = import {};
51 |
52 | in
53 |
54 | {
55 | programs.skim.enable = true;
56 |
57 | nixpkgs.overlays = [
58 | (self: super: {
59 | skim = pkgsUnstable.skim;
60 | })
61 | ];
62 |
63 | # …
64 | }
65 | ```
66 |
67 | should work OK.
68 |
--------------------------------------------------------------------------------
/docs/manual/nix-flakes/standalone.md:
--------------------------------------------------------------------------------
1 | # Standalone setup {#sec-flakes-standalone}
2 |
3 | To prepare an initial Project Manager configuration for your logged in
4 | user, you can run the Project Manager `init` command directly from its
5 | flake.
6 |
7 | For example, to generate and activate a basic configuration run the command
8 |
9 | ```shell
10 | $ nix run github:sellout/project-manager -- init --switch
11 | ```
12 |
13 | This will generate a `flake.nix` and a `default.nix` file in
14 | `./.config/project`, creating the directory if it doesn’t exist.
15 |
16 | If you omit the `--switch` option then the activation won’t happen.
17 | This is useful if you want to inspect and edit the configuration before
18 | activating it.
19 |
20 | ```shell
21 | $ nix run github:sellout/project-manager -- init
22 | $ # Edit files in ~/.config/project-manager
23 | $ nix run github:sellout/project-manager -- init --switch
24 | ```
25 |
26 | After the initial activation has completed successfully then building
27 | and activating your flake-based configuration is as simple as
28 |
29 | ```shell
30 | $ project-manager switch
31 | ```
32 |
33 | It’s possible to override the default configuration directory, if you
34 | want. For example,
35 |
36 | ```shell
37 | $ nix run github:sellout/project-manager -- init --switch ~/hmconf
38 | $ # And after the initial activation.
39 | $ project-manager switch --flake ~/hmconf
40 | ```
41 |
42 | ::: {.note}
43 | The flake inputs aren’t automatically updated by Project Manager. You need
44 | to use the standard `nix flake update` command for that.
45 |
46 | If you only want to update a single flake input, then the command
47 | `nix flake lock --update-input ` can be used.
48 |
49 | You can also pass flake-related options such as `--recreate-lock-file`
50 | or `--update-input ` to `project-manager` when building or
51 | switching, and these options will be forwarded to `nix build`. See the
52 | [NixOS Wiki page](https://wiki.nixos.org/wiki/Flakes) for details.
53 | :::
54 |
--------------------------------------------------------------------------------
/modules/misc/editorconfig/default.nix:
--------------------------------------------------------------------------------
1 | ### This configures basic cross-editor formatting.
2 | ###
3 | ### See https://editorconfig.org/ for more info, and to see if your editor
4 | ### requires a plugin to take advantage of it.
5 | {
6 | config,
7 | lib,
8 | pkgs,
9 | ...
10 | }:
11 | with lib; let
12 | cfg = config.editorconfig;
13 |
14 | iniFormat = pkgs.formats.ini {};
15 | in {
16 | meta.maintainers = with maintainers; [sellout];
17 |
18 | options.editorconfig = {
19 | enable =
20 | mkEnableOption "EditorConfig project configuration file";
21 |
22 | settings = mkOption {
23 | type = iniFormat.type;
24 | default = {};
25 | description = ''
26 | Configuration written to {file}`$PROJECT_ROOT/.editorconfig`.
27 | `root = true` is automatically added to the file,
28 | it must not be added here.
29 | See for documentation.
30 | '';
31 | example = lib.literalMD ''
32 | {
33 | "*" = {
34 | charset = "utf-8";
35 | end_of_line = "lf";
36 | trim_trailing_whitespace = true;
37 | insert_final_newline = true;
38 | max_line_width = 78;
39 | indent_style = "space";
40 | indent_size = 4;
41 | };
42 | };
43 | '';
44 | };
45 | };
46 |
47 | config = mkIf (cfg.enable && cfg.settings != {}) {
48 | programs.vale.excludes = mkIf (config.project.file.".editorconfig".persistence != "store") [
49 | "./${config.project.file.".editorconfig".target}"
50 | ];
51 |
52 | project.file.".editorconfig" = {
53 | ## TODO: This needs to be committed so that it affects the format `check`.
54 | ## However, it should really be linked into there from the store
55 | ## instead, and this should be “worktree” so that editors can find
56 | ## it.
57 | minimum-persistence = "repository";
58 | text = lib.pm.generators.toTOML {} {
59 | globalSection = {root = true;};
60 | sections = cfg.settings;
61 | };
62 | };
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/docs/static/tomorrow-night.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: Tomorrow Night
3 | Author: Chris Kempson (http://chriskempson.com)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.09.0
7 | */
8 | pre code.hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 1em;
12 | }
13 | code.hljs {
14 | padding: 3px 5px;
15 | }
16 | .hljs {
17 | color: #ccc;
18 | background: #2d2d2d;
19 | }
20 | .hljs ::selection,
21 | .hljs::selection {
22 | background-color: #515151;
23 | color: #ccc;
24 | }
25 | .hljs-comment {
26 | color: #999;
27 | }
28 | .hljs-tag {
29 | color: #b4b7b4;
30 | }
31 | .hljs-operator,
32 | .hljs-punctuation,
33 | .hljs-subst {
34 | color: #ccc;
35 | }
36 | .hljs-operator {
37 | opacity: 0.7;
38 | }
39 | .hljs-bullet,
40 | .hljs-deletion,
41 | .hljs-name,
42 | .hljs-selector-tag,
43 | .hljs-template-variable,
44 | .hljs-variable {
45 | color: #f2777a;
46 | }
47 | .hljs-attr,
48 | .hljs-link,
49 | .hljs-literal,
50 | .hljs-number,
51 | .hljs-symbol,
52 | .hljs-variable.constant_ {
53 | color: #f99157;
54 | }
55 | .hljs-class .hljs-title,
56 | .hljs-title,
57 | .hljs-title.class_ {
58 | color: #fc6;
59 | }
60 | .hljs-strong {
61 | font-weight: 700;
62 | color: #fc6;
63 | }
64 | .hljs-addition,
65 | .hljs-code,
66 | .hljs-string,
67 | .hljs-title.class_.inherited__ {
68 | color: #9c9;
69 | }
70 | .hljs-built_in,
71 | .hljs-doctag,
72 | .hljs-keyword.hljs-atrule,
73 | .hljs-quote,
74 | .hljs-regexp {
75 | color: #6cc;
76 | }
77 | .hljs-attribute,
78 | .hljs-function .hljs-title,
79 | .hljs-section,
80 | .hljs-title.function_,
81 | .ruby .hljs-property {
82 | color: #69c;
83 | }
84 | .diff .hljs-meta,
85 | .hljs-keyword,
86 | .hljs-template-tag,
87 | .hljs-type {
88 | color: #c9c;
89 | }
90 | .hljs-emphasis {
91 | color: #c9c;
92 | font-style: italic;
93 | }
94 | .hljs-meta,
95 | .hljs-meta .hljs-keyword,
96 | .hljs-meta .hljs-string {
97 | color: #a3685a;
98 | }
99 | .hljs-meta .hljs-keyword,
100 | .hljs-meta-keyword {
101 | font-weight: 700;
102 | }
103 |
--------------------------------------------------------------------------------
/docs/manual/faq/collision.md:
--------------------------------------------------------------------------------
1 |
2 | # Why is there a collision error when switching generation? {#_why_is_there_a_collision_error_when_switching_generation}
3 |
4 | Project Manager currently installs packages into the user environment,
5 | precisely as if the packages were installed through `nix-env --install`.
6 | This means that you will get a collision error if your Project Manager
7 | configuration attempts to install a package that you already have
8 | installed manually, that is, packages that shows up when you run
9 | `nix-env --query`.
10 |
11 | For example, imagine you have the `hello` package installed in your
12 | environment
13 |
14 | ```shell
15 | $ nix-env --query
16 | hello-2.10
17 | ```
18 |
19 | and your Project Manager configuration contains
20 |
21 | ```nix
22 | project.devPackages = [ pkgs.hello ];
23 | ```
24 |
25 | Then attempting to switch to this configuration will result in an error
26 | similar to
27 |
28 | ```shell
29 | $ project-manager switch
30 | these derivations will be built:
31 | /nix/store/xg69wsnd1rp8xgs9qfsjal017nf0ldhm-project-manager-path.drv
32 | […]
33 | Activating installPackages
34 | replacing old ‘project-manager-path’
35 | installing ‘project-manager-path’
36 | building path(s) ‘/nix/store/b5c0asjz9f06l52l9812w6k39ifr49jj-project-environment’
37 | Wide character in die at /nix/store/64jc9gd2rkbgdb4yjx3nrgc91bpjj5ky-buildenv.pl line 79.
38 | collision between ‘/nix/store/fmwa4axzghz11cnln5absh31nbhs9lq1-project-manager-path/bin/hello’ and ‘/nix/store/c2wyl8b9p4afivpcz8jplc9kis8rj36d-hello-2.10/bin/hello’; use ‘nix-env --set-flag priority NUMBER PKGNAME’ to change the priority of one of the conflicting packages
39 | builder for ‘/nix/store/b37x3s7pzxbasfqhaca5dqbf3pjjw0ip-project-environment.drv’ failed with exit code 2
40 | error: build of ‘/nix/store/b37x3s7pzxbasfqhaca5dqbf3pjjw0ip-project-environment.drv’ failed
41 | ```
42 |
43 | The solution is typically to uninstall the package from the environment
44 | using `nix-env --uninstall` and reattempt the Project Manager generation
45 | switch.
46 |
47 | You could also opt to uninstall _all_ of the packages from your profile
48 | with `nix-env --uninstall '*'`.
49 |
--------------------------------------------------------------------------------
/modules/programs/project-manager.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | project-manager,
6 | ...
7 | }:
8 | with lib; let
9 | cfg = config.programs.project-manager;
10 | in {
11 | meta.maintainers = [maintainers.sellout];
12 |
13 | options = {
14 | programs.project-manager = {
15 | enable = mkEnableOption "Project Manager";
16 |
17 | package = mkOption {
18 | type = types.package;
19 | default = project-manager.packages.${pkgs.system}.project-manager;
20 | defaultText = "the instance used to build this configuration";
21 | description = ''
22 | The current Package Manager package.
23 | '';
24 | };
25 |
26 | path = mkOption {
27 | type = types.nullOr types.str;
28 | default = null;
29 | example = "$PROJECT_ROOT/devel/project-manager";
30 | description = ''
31 | The default path to use for Project Manager. When
32 | `null`, {file}`$PROJECT_ROOT/.config/nixpkgs/project-manager`
33 | will be attempted.
34 | '';
35 | };
36 |
37 | automaticallyExpireGenerations = mkOption {
38 | type = types.nullOr types.str;
39 | default = "now";
40 | example = "-30 days";
41 | description = ''
42 | If this is non-`null`, Project Manager will remove links to old
43 | generations during activation. This is akin to running
44 | `project-manager expire-generations {var}duration`. The default value
45 | is intended to leave no extra generations around, but a short value
46 | would allow for rollbacks. Be careful, though, as generations
47 | introduce GC roots, so it could result in a much larger Nix store.
48 | '';
49 | };
50 | };
51 | };
52 |
53 | config = mkIf cfg.enable {
54 | project.devPackages = [cfg.package];
55 |
56 | project.activation.automaticallyExpireGenerations =
57 | mkIf (cfg.automaticallyExpireGenerations != null)
58 | (pm.dag.entryAfter ["linkGeneration"] ''
59 | pm_expireGenerations ${lib.escapeShellArg cfg.automaticallyExpireGenerations}
60 | '');
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/modules/lib/types/dag.nix:
--------------------------------------------------------------------------------
1 | {lib}: let
2 | inherit (lib) defaultFunctor pm mkIf mkOrder mkOption mkOptionType types;
3 |
4 | dagEntryOf = elemType: let
5 | submoduleType = types.submodule ({name, ...}: {
6 | options = {
7 | data = mkOption {type = elemType;};
8 | after = mkOption {type = with types; listOf str;};
9 | before = mkOption {type = with types; listOf str;};
10 | };
11 | config = mkIf (elemType.name == "submodule") {
12 | data._module.args.dagName = name;
13 | };
14 | });
15 | maybeConvert = def:
16 | if pm.dag.isEntry def.value
17 | then def.value
18 | else
19 | pm.dag.entryAnywhere (
20 | if def ? priority
21 | then mkOrder def.priority def.value
22 | else def.value
23 | );
24 | in
25 | mkOptionType {
26 | name = "dagEntryOf";
27 | description = "DAG entry of ${elemType.description}";
28 | # leave the checking to the submodule type
29 | merge = loc: defs:
30 | submoduleType.merge loc (map (def: {
31 | inherit (def) file;
32 | value = maybeConvert def;
33 | })
34 | defs);
35 | };
36 | in rec {
37 | # A directed acyclic graph of some inner type.
38 | #
39 | # Note, if the element type is a submodule then the `name` argument
40 | # will always be set to the string "data" since it picks up the
41 | # internal structure of the DAG values. To give access to the
42 | # "actual" attribute name a new submodule argument is provided with
43 | # the name `dagName`.
44 | dagOf = elemType: let
45 | attrEquivalent = types.attrsOf (dagEntryOf elemType);
46 | in
47 | mkOptionType rec {
48 | name = "dagOf";
49 | description = "DAG of ${elemType.description}";
50 | inherit (attrEquivalent) check merge emptyValue;
51 | getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]);
52 | getSubModules = elemType.getSubModules;
53 | substSubModules = m: dagOf (elemType.substSubModules m);
54 | functor = (defaultFunctor name) // {wrapped = elemType;};
55 | nestedTypes.elemType = elemType;
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/docs/static/tomorrow.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: Tomorrow
3 | Author: Chris Kempson (http://chriskempson.com)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.09.0
7 | */
8 | pre code.hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 1em;
12 | }
13 | code.hljs {
14 | padding: 3px 5px;
15 | }
16 | .hljs {
17 | color: #4d4d4c;
18 | background: #fff;
19 | }
20 | .hljs ::selection,
21 | .hljs::selection {
22 | background-color: #d6d6d6;
23 | color: #4d4d4c;
24 | }
25 | .hljs-comment {
26 | color: #8e908c;
27 | }
28 | .hljs-tag {
29 | color: #969896;
30 | }
31 | .hljs-operator,
32 | .hljs-punctuation,
33 | .hljs-subst {
34 | color: #4d4d4c;
35 | }
36 | .hljs-operator {
37 | opacity: 0.7;
38 | }
39 | .hljs-bullet,
40 | .hljs-deletion,
41 | .hljs-name,
42 | .hljs-selector-tag,
43 | .hljs-template-variable,
44 | .hljs-variable {
45 | color: #c82829;
46 | }
47 | .hljs-attr,
48 | .hljs-link,
49 | .hljs-literal,
50 | .hljs-number,
51 | .hljs-symbol,
52 | .hljs-variable.constant_ {
53 | color: #f5871f;
54 | }
55 | .hljs-class .hljs-title,
56 | .hljs-title,
57 | .hljs-title.class_ {
58 | color: #eab700;
59 | }
60 | .hljs-strong {
61 | font-weight: 700;
62 | color: #eab700;
63 | }
64 | .hljs-addition,
65 | .hljs-code,
66 | .hljs-string,
67 | .hljs-title.class_.inherited__ {
68 | color: #718c00;
69 | }
70 | .hljs-built_in,
71 | .hljs-doctag,
72 | .hljs-keyword.hljs-atrule,
73 | .hljs-quote,
74 | .hljs-regexp {
75 | color: #3e999f;
76 | }
77 | .hljs-attribute,
78 | .hljs-function .hljs-title,
79 | .hljs-section,
80 | .hljs-title.function_,
81 | .ruby .hljs-property {
82 | color: #4271ae;
83 | }
84 | .diff .hljs-meta,
85 | .hljs-keyword,
86 | .hljs-template-tag,
87 | .hljs-type {
88 | color: #8959a8;
89 | }
90 | .hljs-emphasis {
91 | color: #8959a8;
92 | font-style: italic;
93 | }
94 | .hljs-meta,
95 | .hljs-meta .hljs-keyword,
96 | .hljs-meta .hljs-string {
97 | color: #a3685a;
98 | }
99 | .hljs-meta .hljs-keyword,
100 | .hljs-meta-keyword {
101 | font-weight: 700;
102 | }
103 |
--------------------------------------------------------------------------------
/modules/lib/types/tests/dag-merge.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | ...
5 | }: let
6 | inherit
7 | (lib)
8 | concatStringsSep
9 | pm
10 | mkIf
11 | mkMerge
12 | mkBefore
13 | mkAfter
14 | mkOption
15 | types
16 | ;
17 |
18 | dag = lib.pm.dag;
19 |
20 | result = let
21 | sorted = dag.topoSort config.tested.dag;
22 | data = map (e: "${e.name}:${e.data}") sorted.result;
23 | in
24 | concatStringsSep "\n" data + "\n";
25 | in {
26 | options.tested.dag = mkOption {type = pm.types.dagOf types.commas;};
27 |
28 | config = {
29 | tested.dag = mkMerge [
30 | (mkIf false {never = "never";})
31 | {never2 = mkIf false "never2";}
32 | {
33 | after = mkMerge [
34 | "after"
35 | (mkIf false "neither")
36 | ];
37 | }
38 | {before = dag.entryBefore ["after"] (mkIf true "before");}
39 | {
40 | between = mkIf true (dag.entryBetween ["after"] ["before"] "between");
41 | }
42 | {merged = dag.entryBefore ["between"] "middle";}
43 | {merged = mkBefore "left";}
44 | {merged = dag.entryBetween ["after"] ["before"] (mkAfter "right");}
45 | {
46 | merged = dag.entryBefore ["between"] "middle";
47 | }
48 |
49 | # Some tests of list entries.
50 | (dag.entriesAnywhere "list-anywhere" [
51 | "list-anywhere-0"
52 | "list-anywhere-1"
53 | "list-anywhere-2"
54 | ])
55 | {inside-list = dag.entryAfter ["list-anywhere-1"] "inside-list";}
56 | (
57 | dag.entriesBefore "list-before"
58 | ["list-anywhere-1"]
59 | [
60 | "list-before-0"
61 | "list-before-1"
62 | ]
63 | )
64 | (
65 | dag.entriesAfter "list-after"
66 | ["list-before-0"]
67 | [
68 | "list-after-0"
69 | "list-after-1"
70 | ]
71 | )
72 | (dag.entriesAnywhere "list-empty" [])
73 | {"list-before-0" = mkAfter "sneaky-merge";}
74 | ];
75 |
76 | project.file."result.txt".text = result;
77 |
78 | nmt.script = ''
79 | assertFileContent \
80 | project-files/result.txt \
81 | ${./dag-merge-result.txt}
82 | '';
83 | };
84 | }
85 |
--------------------------------------------------------------------------------
/docs/manual/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction to Project Manager {#ch-introduction}
2 |
3 | Project Manager is a [Nix](https://nix.dev/)-powered tool for reproducible management of the contents of project directories.
4 | This includes programs, configuration files, environment variables and, well… arbitrary files.
5 | The following example snippet of Nix code:
6 |
7 | ```nix
8 | programs.git = {
9 | enable = true;
10 | userEmail = "joe@example.org";
11 | userName = "joe";
12 | };
13 | ```
14 |
15 | would make available to a user the `git` executable and man pages and a configuration file `~/.config/git/config`:
16 |
17 | ```ini
18 | [user]
19 | email = "joe@example.org"
20 | name = "joe"
21 | ```
22 |
23 | Since Project Manager is implemented in Nix, it provides several benefits:
24 |
25 | - Contents are reproducible — a project will be the exact same every time it’s built, unless of course, an intentional change is made.
26 | This also means you can have the exact same project on different hosts.
27 | - Significantly faster and more powerful than various backup strategies.
28 | - Unlike "dot files" repositories, Project Manager supports specifying programs, as well as their configurations.
29 | - Supported by , so that you don't have to build from source.
30 | - If you do want to build some programs from source, there is hardly a tool more useful than Nix for that, and the build instructions can be neatly integrated in your Project Manager usage.
31 | - Infinitely composable, so that values in different configuration files and build instructions can share a source of truth.
32 | - Connects you with the [most extensive](https://repology.org/repositories/statistics/total) and [most up-to-date](https://repology.org/repositories/statistics/newest) software package repository on earth, [Nixpkgs](https://github.com/NixOS/nixpkgs).
33 |
34 | Project Manager produces two things
35 |
36 | 1. Nix expressions to be used in flakes and
37 | 2. files in your project’s working tree.
38 |
39 | Either of these can be used individually, but are designed to work together.
40 |
41 | The former are accessed via `self.projectConfigurations.${system}` in your flake, while the latter are created by running `project-manager switch`.
42 |
43 | ```{=include=} sections
44 | introduction/devShell.md
45 | ```
46 |
--------------------------------------------------------------------------------
/modules/programs/just.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | cfg = config.programs.just;
8 |
9 | projectDirectory = config.project.projectDirectory;
10 |
11 | fileContents =
12 | (import ../lib/file-type.nix {
13 | inherit projectDirectory lib pkgs;
14 | commit-by-default = config.project.commit-by-default;
15 | })
16 | .fileContents;
17 | in {
18 | meta.maintainers = [lib.maintainers.sellout];
19 |
20 | options.programs.just = {
21 | enable = lib.mkEnableOption "[just](https://github.com/casey/just)";
22 |
23 | package = lib.mkPackageOption pkgs "just" {};
24 |
25 | wrapProgram = lib.mkOption {
26 | type = lib.types.nullOr lib.types.bool;
27 | default = null;
28 | apply = p:
29 | if p == null
30 | then config.project.wrapPrograms
31 | else p;
32 |
33 | description = ''
34 | Whether to wrap the executable to work without a config file in the
35 | worktree or to produce the config file. If null, this falls back to
36 | {var}`config.project.wrapPrograms`.
37 | '';
38 | };
39 |
40 | recipes = lib.mkOption {
41 | type = fileContents "programs.just" "" projectDirectory "recipes";
42 | description = ''
43 | The justfile.
44 | '';
45 | };
46 | };
47 |
48 | config = lib.mkIf cfg.enable (let
49 | wrapped =
50 | if cfg.wrapProgram
51 | then let
52 | flags = [
53 | "--justfile '${config.project.file."justfile".storePath}'"
54 | "--working-directory '${config.project.projectDirectory}'"
55 | ];
56 | in
57 | cfg.package.overrideAttrs (old: {
58 | nativeBuildInputs = old.nativeBuildInputs ++ [pkgs.makeWrapper];
59 |
60 | postInstall =
61 | old.postInstall
62 | + ''
63 | wrapProgram "$out/bin/just" \
64 | --add-flags "${lib.concatStringsSep " " flags}"
65 | '';
66 | })
67 | else cfg.package;
68 | in {
69 | project = {
70 | file."justfile" =
71 | cfg.recipes
72 | // {
73 | minimum-persistence = lib.mkIf cfg.wrapProgram "store";
74 | target = "justfile";
75 | };
76 |
77 | packages = [wrapped];
78 | };
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/testing/asserts.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | ...
5 | }: let
6 | inherit (lib) concatStringsSep mkOption types;
7 | in {
8 | options.test.asserts = {
9 | warnings = {
10 | enable = mkOption {
11 | type = types.bool;
12 | default = true;
13 | description = "Whether warning asserts are enabled.";
14 | };
15 |
16 | expected = mkOption {
17 | type = types.listOf types.str;
18 | default = [];
19 | description = ''
20 | List of expected warnings.
21 | '';
22 | };
23 | };
24 |
25 | assertions = {
26 | enable = mkOption {
27 | type = types.bool;
28 | default = true;
29 | description = "Whether assertion asserts are enabled.";
30 | };
31 |
32 | expected = mkOption {
33 | type = types.listOf types.str;
34 | default = [];
35 | description = ''
36 | List of expected assertions.
37 | '';
38 | };
39 | };
40 | };
41 |
42 | config = lib.mkMerge [
43 | (lib.mkIf config.test.asserts.warnings.enable {
44 | project.file = {
45 | "asserts/warnings.actual".text =
46 | concatStringsSep ''
47 |
48 | --
49 | ''
50 | config.warnings;
51 | "asserts/warnings.expected".text =
52 | concatStringsSep ''
53 |
54 | --
55 | ''
56 | config.test.asserts.warnings.expected;
57 | };
58 |
59 | nmt.script = ''
60 | assertFileContent \
61 | project-files/asserts/warnings.actual \
62 | "$TESTED/project-files/asserts/warnings.expected"
63 | '';
64 | })
65 |
66 | (lib.mkIf config.test.asserts.assertions.enable {
67 | project.file = {
68 | "asserts/assertions.actual".text = concatStringsSep ''
69 |
70 | --
71 | '' (map (x: x.message) (lib.filter (x: !x.assertion) config.assertions));
72 | "asserts/assertions.expected".text =
73 | concatStringsSep ''
74 |
75 | --
76 | ''
77 | config.test.asserts.assertions.expected;
78 | };
79 |
80 | nmt.script = ''
81 | assertFileContent \
82 | project-files/asserts/assertions.actual \
83 | "$TESTED/project-files/asserts/assertions.expected"
84 | '';
85 | })
86 | ];
87 | }
88 |
--------------------------------------------------------------------------------
/docs/manual/usage.md:
--------------------------------------------------------------------------------
1 | # Using Project Manager {#ch-usage}
2 |
3 | Your use of Project Manager is centered around the configuration file,
4 | which is typically found at `$PROJECT_ROOT/.config/project/default.nix` in the
5 | standard installation or `$PROJECT_ROOT/flake.nix` in a Nix
6 | flake based installation.
7 |
8 | This configuration file can be _built_ and _activated_.
9 |
10 | Building a configuration produces a directory in the Nix store that
11 | has all files and programs that should be available in your project
12 | directory and Nix project profile, respectively. The build step also checks
13 | that the configuration is valid and it will fail with an error if you,
14 | for example, assign a value to an option that doesn’t exist or assign a
15 | value of the wrong type. Some modules also have custom assertions that
16 | perform more detailed, module specific, checks.
17 |
18 | Concretely, if your configuration has
19 |
20 | ```nix
21 | programs.git.enable = "yes";
22 | ```
23 |
24 | then building it, for example using `project-manager build`, will result in
25 | an error message saying something like
26 |
27 | ```console
28 | $ project-manager build
29 | error: A definition for option `programs.git.enable' is not of type `boolean'. Definition values:
30 | - In `/.../.config/project/default.nix': "yes"
31 | (use '--show-trace' to show detailed location information)
32 | ```
33 |
34 | The message indicates that you must give a Boolean value for this
35 | option, that is, either `true` or `false`. The documentation of each
36 | option will state the expected type, for
37 | [programs.git.enable](#opt-programs.git.enable) you will see "Type: boolean". You
38 | there also find information about the default value and a description of
39 | the option. You can find the complete option documentation in
40 | [Project Manager Configuration Options](#ch-options) or directly in the terminal by running
41 |
42 | ```shell
43 | man project-configuration.nix
44 | ```
45 |
46 | Once a configuration is successfully built, it can be activated. The
47 | activation performs the steps necessary to make the files, programs, and
48 | services available in your user environment. The `project-manager switch`
49 | command performs a combined build and activation.
50 |
51 | ```{=include=} sections
52 | usage/configuration.md
53 | usage/rollbacks.md
54 | usage/dotfiles.md
55 | usage/updating.md
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/manual/contributing/news.md:
--------------------------------------------------------------------------------
1 | # News {#sec-news}
2 |
3 | Project Manager includes a system for presenting news to the user. When
4 | making a change you, therefore, have the option to also include an
5 | associated news entry. In general, a news entry should only be added for
6 | truly noteworthy news. For example, a bug fix or new option doesn’t need a news entry.
7 |
8 | If you do have a change worthy of a news entry then please add one in
9 | [`news.nix`](https://github.com/sellout/project-manager/blob/main/modules/misc/news.nix)
10 | but you should follow some basic guidelines:
11 |
12 |
13 |
14 | - The entry timestamp should be in ISO-8601 format having \"+00:00\"
15 | as time zone. For example, \"2017-09-13T17:10:14+00:00\". A suitable
16 | timestamp can be produced by the command
17 |
18 | ```shell
19 | $ date --iso-8601=second --universal
20 | ```
21 |
22 |
23 |
24 | - The entry condition should be as specific as possible. For example,
25 | if you are changing or deprecating a specific option then you could
26 | restrict the news to those users who actually use this option.
27 |
28 | - Wrap the news message so that it will fit in the typical terminal,
29 | that is, at most 80 characters wide. Ideally a bit less.
30 |
31 | - Unlike commit messages, news will be read without any connection to
32 | the Project Manager source code. It’s therefore important to make the
33 | message understandable in isolation and to those who don’t have
34 | knowledge of the Project Manager internals. To this end it should be
35 | written in more descriptive, prose like way.
36 |
37 | - If you refer to an option then write its full attribute path. That
38 | is, instead of writing
39 |
40 | The option 'foo' has been deprecated, please use 'bar' instead.
41 |
42 | it should read
43 |
44 | The option 'services.myservice.foo' has been deprecated, please
45 | use 'services.myservice.bar' instead.
46 |
47 | - A new module, say `foo.nix`, should always include a news entry that
48 | has a message similar to
49 |
50 | A new module is available: 'services.foo'.
51 |
52 | If the module is platform specific, for example, a service module using
53 | systemd, then a condition like
54 |
55 | ```nix
56 | condition = hostPlatform.isLinux;
57 | ```
58 |
59 | should be added. If you contribute a module then you don't need to
60 | add this entry, the merger will create an entry for you.
61 |
--------------------------------------------------------------------------------
/project-manager/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | runStrictCommand,
3 | lib,
4 | bash-strict-mode,
5 | callPackage,
6 | coreutils,
7 | findutils,
8 | gettext,
9 | gnused,
10 | jq,
11 | less,
12 | ncurses,
13 | nix,
14 | release,
15 | unixtools,
16 | # used for pkgs.path for nixos-option
17 | pkgs,
18 | }: let
19 | nixos-option =
20 | pkgs.nixos-option
21 | or (callPackage
22 | (pkgs.path + "/nixos/modules/installer/tools/nixos-option") {});
23 | in
24 | runStrictCommand "project-manager" {
25 | preferLocalBuild = true;
26 | buildInputs = [bash-strict-mode];
27 | nativeBuildInputs = [gettext];
28 | meta = with lib; {
29 | mainProgram = "project-manager";
30 | description = "A project environment configurator";
31 | maintainers = [maintainers.sellout];
32 | platforms = platforms.unix;
33 | license = licenses.mit;
34 | };
35 | } ''
36 | install -v -D -m755 ${./project-manager} $out/bin/project-manager
37 |
38 | substituteInPlace $out/bin/project-manager \
39 | --subst-var-by DEP_PATH "${
40 | lib.makeBinPath [
41 | bash-strict-mode
42 | coreutils
43 | findutils
44 | gettext
45 | gnused
46 | jq
47 | less
48 | ncurses
49 | nix
50 | nixos-option
51 | unixtools.hostname
52 | ]
53 | }" \
54 | --subst-var-by OUT "$out" \
55 | --subst-var-by VERSION "${release}"
56 |
57 | ## See NixOS/nixpkgs#247410.
58 | (set +u; patchShebangs --host $out/bin/project-manager)
59 |
60 | install -D -m755 ${./completion.bash} \
61 | $out/share/bash-completion/completions/project-manager
62 | install -D -m755 ${./completion.zsh} \
63 | $out/share/zsh/site-functions/_project-manager
64 | install -D -m755 ${./completion.fish} \
65 | $out/share/fish/vendor_completions.d/project-manager.fish
66 |
67 | install -D -m755 ${../lib/bash/project-manager.bash} \
68 | "$out/share/bash/project-manager.bash"
69 |
70 | substituteInPlace $out/share/bash/project-manager.bash \
71 | --subst-var-by FLAKE_TEMPLATE '${../templates/default/flake.nix}' \
72 | --subst-var-by CONFIG_TEMPLATE '${../templates/default/.config/project/default.nix}'
73 |
74 | # for path in {./po}/*.po; do
75 | # lang="''${path##*/}"
76 | # lang="''${lang%%.*}"
77 | # mkdir -p "$out/share/locale/$lang/LC_MESSAGES"
78 | # msgfmt -o "$out/share/locale/$lang/LC_MESSAGES/project-manager.mo" "$path"
79 | # done
80 | ''
81 |
--------------------------------------------------------------------------------
/.config/project/github-pages.nix:
--------------------------------------------------------------------------------
1 | {lib, ...}: let
2 | defaultBranch = "main";
3 | in {
4 | services.github = {
5 | settings.pages = {
6 | build_type = "workflow";
7 | source.branch = defaultBranch;
8 | };
9 | workflow."pages.yml".text = lib.pm.generators.toYAML {} {
10 | name = "Deploy generated docs to Pages";
11 | on = {
12 | ## Runs on pushes targeting the default branch
13 | push.branches = [defaultBranch];
14 | ## Allows you to run this workflow manually from the Actions tab
15 | workflow_dispatch = {};
16 | };
17 |
18 | ## Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
19 | permissions = {
20 | contents = "read";
21 | pages = "write";
22 | id-token = "write";
23 | };
24 |
25 | ## Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
26 | ## However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
27 | concurrency = {
28 | group = "pages";
29 | cancel-in-progress = false;
30 | };
31 |
32 | jobs = {
33 | build = {
34 | runs-on = "ubuntu-24.04";
35 | steps = [
36 | {
37 | name = "Checkout";
38 | uses = "actions/checkout@v6";
39 | }
40 | {
41 | name = "Setup Pages";
42 | uses = "actions/configure-pages@v4";
43 | }
44 | {
45 | uses = "cachix/install-nix-action@v24";
46 | "with".extra_nix_config = ''
47 | extra-trusted-public-keys = cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=
48 | extra-substituters = https://cache.garnix.io
49 | '';
50 | }
51 | {
52 | run = ''
53 | nix build .#docs-html
54 | cp -r result/share/doc/project-manager ./_site
55 | '';
56 | }
57 | {
58 | name = "Upload artifact";
59 | uses = "actions/upload-pages-artifact@v3";
60 | }
61 | ];
62 | };
63 | deploy = {
64 | environment = {
65 | name = "github-pages";
66 | url = "\${{ steps.deployment.outputs.page_url }}";
67 | };
68 | runs-on = "ubuntu-24.04";
69 | needs = "build";
70 | steps = [
71 | {
72 | name = "Deploy to GitHub Pages";
73 | id = "deployment";
74 | uses = "actions/deploy-pages@v4";
75 | }
76 | ];
77 | };
78 | };
79 | };
80 | };
81 | }
82 |
--------------------------------------------------------------------------------
/modules/misc/version.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | ...
5 | }:
6 | with lib; let
7 | releaseInfo = import ../../release.nix;
8 | in {
9 | options = {
10 | project.stateVersion = mkOption {
11 | type = types.ints.between 0 releaseInfo.version.major;
12 | description = ''
13 | It is occasionally necessary for Project Manager to change configuration
14 | defaults in a way that is incompatible with stateful data. This could,
15 | for example, include switching the default data format or location of a
16 | file.
17 |
18 | The *state version* indicates which default settings are in effect and
19 | will therefore help avoid breaking program configurations. Switching to
20 | a higher state version typically requires performing some manual steps,
21 | such as data conversion or moving files.
22 |
23 | The state version corresponds to the major version component of the
24 | Project Manager release where those defaults were specified. The value
25 | can be set to any Project Manager release up to and including this one.
26 | '';
27 | };
28 |
29 | project.version = {
30 | full = mkOption {
31 | internal = true;
32 | readOnly = true;
33 | type = types.str;
34 | default = let
35 | inherit (config.project.version) release revision;
36 | suffix =
37 | optionalString (revision != null) "+${substring 0 8 revision}";
38 | in "${release}${suffix}";
39 | example = "0.3.0+213a0629";
40 | description = "The full Project Manager version.";
41 | };
42 |
43 | release = mkOption {
44 | internal = true;
45 | readOnly = true;
46 | type = types.str;
47 | default = releaseInfo.release;
48 | example = "0.3.0";
49 | description = "The Project Manager release.";
50 | };
51 |
52 | isReleaseBranch = mkOption {
53 | internal = true;
54 | readOnly = true;
55 | type = types.bool;
56 | default = releaseInfo.isReleaseBranch;
57 | description = ''
58 | Whether the Project Manager version is from a versioned
59 | release branch.
60 | '';
61 | };
62 |
63 | revision = mkOption {
64 | internal = true;
65 | type = types.nullOr types.str;
66 | default = let
67 | gitRepo = "${toString ./../..}/.git";
68 | in
69 | if pathIsGitRepo gitRepo
70 | then commitIdFromGitRepo gitRepo
71 | else null;
72 | description = ''
73 | The Git revision from which this Project Manager configuration was
74 | built.
75 | '';
76 | };
77 | };
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/docs/manual/manpage-urls.json:
--------------------------------------------------------------------------------
1 | {
2 | "gnunet.conf(5)": "https://docs.gnunet.org/users/configuration.html",
3 | "mpd(1)": "https://mpd.readthedocs.io/en/latest/mpd.1.html",
4 | "mpd.conf(5)": "https://mpd.readthedocs.io/en/latest/mpd.conf.5.html",
5 | "nix.conf(5)": "https://nixos.org/manual/nix/stable/command-ref/conf-file.html",
6 |
7 | "journald.conf(5)": "https://www.freedesktop.org/software/systemd/man/journald.conf.html",
8 | "logind.conf(5)": "https://www.freedesktop.org/software/systemd/man/logind.conf.html",
9 | "networkd.conf(5)": "https://www.freedesktop.org/software/systemd/man/networkd.conf.html",
10 | "systemd.automount(5)": "https://www.freedesktop.org/software/systemd/man/systemd.automount.html",
11 | "systemd.exec(5)": "https://www.freedesktop.org/software/systemd/man/systemd.exec.html",
12 | "systemd.link(5)": "https://www.freedesktop.org/software/systemd/man/systemd.link.html",
13 | "systemd.mount(5)": "https://www.freedesktop.org/software/systemd/man/systemd.mount.html",
14 | "systemd.netdev(5)": "https://www.freedesktop.org/software/systemd/man/systemd.netdev.html",
15 | "systemd.network(5)": "https://www.freedesktop.org/software/systemd/man/systemd.network.html",
16 | "systemd.nspawn(5)": "https://www.freedesktop.org/software/systemd/man/systemd.nspawn.html",
17 | "systemd.path(5)": "https://www.freedesktop.org/software/systemd/man/systemd.path.html",
18 | "systemd.resource-control(5)": "https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html",
19 | "systemd.scope(5)": "https://www.freedesktop.org/software/systemd/man/systemd.scope.html",
20 | "systemd.service(5)": "https://www.freedesktop.org/software/systemd/man/systemd.service.html",
21 | "systemd.slice(5)": "https://www.freedesktop.org/software/systemd/man/systemd.slice.html",
22 | "systemd.socket(5)": "https://www.freedesktop.org/software/systemd/man/systemd.socket.html",
23 | "systemd.timer(5)": "https://www.freedesktop.org/software/systemd/man/systemd.timer.html",
24 | "systemd.unit(5)": "https://www.freedesktop.org/software/systemd/man/systemd.unit.html",
25 | "systemd-system.conf(5)": "https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html",
26 | "systemd-user.conf(5)": "https://www.freedesktop.org/software/systemd/man/systemd-user.conf.html",
27 | "timesyncd.conf(5)": "https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html",
28 | "tmpfiles.d(5)": "https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html",
29 | "systemd.time(7)": "https://www.freedesktop.org/software/systemd/man/systemd.time.html",
30 | "systemd-fstab-generator(8)": "https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html",
31 | "systemd-networkd-wait-online.service(8)": "https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html"
32 | }
33 |
--------------------------------------------------------------------------------
/project-manager/completion.zsh:
--------------------------------------------------------------------------------
1 | #compdef project-manager
2 |
3 | local state ret=1
4 |
5 | _arguments \
6 | '-I[search path]:PATH:_files -/' \
7 | '--cores[cores]:NUM:()' \
8 | '--debug[debug]' \
9 | '--impure[impure]' \
10 | '--keep-failed[keep failed]' \
11 | '--keep-going[keep going]' \
12 | '--version[version]' \
13 | '(-h --help)'{--help,-h}'[help]' \
14 | '(-v --verbose)'{--verbose,-v}'[verbose]' \
15 | '(-n --dry-run)'{--dry-run,-n}'[dry run]' \
16 | '(-f --file)'{--file,-f}'[configuration file]:FILE:_files' \
17 | '(-j --max-jobs)'{--max-jobs,-j}'[max jobs]:NUM:()' \
18 | '--option[option]:NAME VALUE:()' \
19 | '--builders[builders]:SPEC:()' \
20 | '(-L --print-build-logs)'{--print-build-logs,-L}'[print build logs]' \
21 | '--show-trace[show trace]' \
22 | '--override-input[override flake input]:NAME VALUE:()' \
23 | '--update-input[update flake input]:NAME:()' \
24 | '--experimental-features[set experimental Nix features]:VALUE:()' \
25 | '--extra-experimental-features:[append to experimental Nix features]:VALUE:()' \
26 | '1: :->cmds' \
27 | '*:: :->args' && ret=0
28 |
29 | case "$state" in
30 | cmds)
31 | _values 'command' \
32 | 'help[help]' \
33 | 'edit[edit]' \
34 | 'option[inspect option]' \
35 | 'build[build]' \
36 | 'init[init]' \
37 | 'switch[switch]' \
38 | 'generations[list generations]' \
39 | 'remove-generations[remove generations]' \
40 | 'expire-generations[expire generations]' \
41 | 'packages[managed packages]' \
42 | 'fmt[format]' \
43 | 'news[read the news]' \
44 | 'uninstall[uninstall]' && ret=0
45 | ;;
46 | args)
47 | case $line[1] in
48 | remove-generations)
49 | _values 'generations' \
50 | $(project-manager generations | cut -d ' ' -f 5) && ret=0
51 | ;;
52 | build|switch)
53 | _arguments \
54 | '--cores[cores]:NUM:()' \
55 | '--debug[debug]' \
56 | '--impure[impure]' \
57 | '--keep-failed[keep failed]' \
58 | '--keep-going[keep going]' \
59 | '--max-jobs[max jobs]:NUM:()' \
60 | '--no-out-link[no out link]' \
61 | '--no-substitute[no substitute]' \
62 | '--option[option]:NAME VALUE:()' \
63 | '--show-trace[show trace]' \
64 | '--substitute[substitute]' \
65 | '--builders[builders]:SPEC:()' \
66 | '--refresh[refresh]' \
67 | '--override-input[override flake input]:NAME VALUE:()' \
68 | '--update-input[update flake input]:NAME:()' \
69 | '--experimental-features[set experimental Nix features]:VALUE:()' \
70 | '--extra-experimental-features:[append to experimental Nix features]:VALUE:()'
71 | ;;
72 | init)
73 | _arguments \
74 | '--switch[switch]' \
75 | ':PATH:_files -/'
76 | ;;
77 | esac
78 | esac
79 |
80 | return ret
81 |
--------------------------------------------------------------------------------
/modules/lib-bash/activation-init.bash:
--------------------------------------------------------------------------------
1 | function setupVars() {
2 | declare -r stateHome="$PROJECT_ROOT/.local/state"
3 | declare -r userNixStateDir="$stateHome/nix"
4 | declare -r pmGcrootsDir="$stateHome/project-manager/gcroots"
5 |
6 | mkdir -p "$userNixStateDir/profiles"
7 | declare -r profilesDir="$userNixStateDir/profiles"
8 |
9 | declare -gr genProfilePath="$profilesDir/project-manager"
10 | declare -gr newGenPath="@GENERATION_DIR@"
11 | declare -gr newGenGcPath="$pmGcrootsDir/current-project"
12 |
13 | declare greatestGenNum
14 | greatestGenNum=$(
15 | nix profile history --profile "$genProfilePath" \
16 | | tail -2 \
17 | | sed -E 's/.*m([[:digit:]]+).*/\1/' \
18 | | head -1
19 | )
20 |
21 | if [[ -n $greatestGenNum ]]; then
22 | declare -gr oldGenNum=$greatestGenNum
23 | declare -gr newGenNum=$((oldGenNum + 1))
24 | else
25 | declare -gr newGenNum=1
26 | fi
27 |
28 | if [[ -e $genProfilePath ]]; then
29 | declare -g oldGenPath
30 | oldGenPath="$(readlink -e "$genProfilePath")"
31 | fi
32 |
33 | "${VERBOSE_RUN[@]}" _i "Checking oldGenNum and oldGenPath for consistency"
34 | if [[ -v oldGenNum && ! -v oldGenPath ||
35 | ! -v oldGenNum && -v oldGenPath ]]; then
36 | _i $'The previous generation number and path are in conflict! These\nmust be either both empty or both set but are now set to\n\n \'%s\' and \'%s\'\n\nIf you don\'t mind losing previous profile generations then\nthe easiest solution is probably to run\n\n rm %s/project-manager*\n rm %s/current-project\n\nand trying project-manager switch again. Good luck!' \
37 | "${oldGenNum-}" "${oldGenPath-}" \
38 | "$profilesDir" "$pmGcrootsDir"
39 | exit 1
40 | fi
41 | }
42 |
43 | function checkProjectDirectory() {
44 | local expectedProject
45 | expectedProject=$(readlink --canonicalize-missing "$1")
46 |
47 | if ! [[ $PROJECT_ROOT -ef $expectedProject ]]; then
48 | _iError 'Error: PROJECT_ROOT is set to "%s" but we expect "%s"' "$PROJECT_ROOT" "$expectedProject"
49 | exit 1
50 | fi
51 | }
52 |
53 | pm_setVerboseAndDryRun
54 |
55 | _i "Starting Project Manager activation"
56 |
57 | # Verify that we can connect to the Nix store and/or daemon. This will
58 | # also create the necessary directories in profiles and gcroots.
59 | "${VERBOSE_RUN[@]}" _i "Validating Nix installation"
60 | nix-build --expr '{}' --no-out-link --quiet
61 |
62 | setupVars
63 |
64 | if [[ -v VERBOSE ]]; then
65 | _i 'Using Nix version: %s' "$(nix --version)"
66 | fi
67 |
68 | "${VERBOSE_RUN[@]}" _i "Activation variables:"
69 | if [[ -v oldGenNum ]]; then
70 | "${VERBOSE_ECHO}" " oldGenNum=$oldGenNum"
71 | "${VERBOSE_ECHO}" " oldGenPath=$oldGenPath"
72 | else
73 | "${VERBOSE_ECHO}" " oldGenNum undefined (first run?)"
74 | "${VERBOSE_ECHO}" " oldGenPath undefined (first run?)"
75 | fi
76 | "${VERBOSE_ECHO}" " newGenPath=$newGenPath"
77 | "${VERBOSE_ECHO}" " newGenNum=$newGenNum"
78 | "${VERBOSE_ECHO}" " genProfilePath=$genProfilePath"
79 | "${VERBOSE_ECHO}" " newGenGcPath=$newGenGcPath"
80 |
--------------------------------------------------------------------------------
/modules/lib/types/tests/gvariant-merge.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | inherit
8 | (lib)
9 | concatStringsSep
10 | pm
11 | mapAttrsToList
12 | mkMerge
13 | mkOption
14 | types
15 | ;
16 | in {
17 | options.examples = mkOption {type = types.attrsOf pm.types.gvariant;};
18 |
19 | config = {
20 | examples = with pm.gvariant;
21 | mkMerge [
22 | {bool = true;}
23 | {bool = true;}
24 |
25 | {float = 3.14;}
26 |
27 | {int = -42;}
28 | {int = -42;}
29 |
30 | {uint32 = mkUint32 42;}
31 | {uint32 = mkUint32 42;}
32 |
33 | {int16 = mkInt16 (-42);}
34 | {int16 = mkInt16 (-42);}
35 |
36 | {uint16 = mkUint16 42;}
37 | {uint16 = mkUint16 42;}
38 |
39 | {int64 = mkInt64 (-42);}
40 | {int64 = mkInt64 (-42);}
41 |
42 | {uint64 = mkUint64 42;}
43 | {uint64 = mkUint64 42;}
44 |
45 | {array1 = ["one"];}
46 | {array1 = mkArray type.string ["two"];}
47 | {array2 = mkArray type.uint32 [1];}
48 | {array2 = mkArray type.uint32 [2];}
49 |
50 | {emptyArray1 = [];}
51 | {emptyArray2 = mkEmptyArray type.uint32;}
52 |
53 | {string = "foo";}
54 | {string = "foo";}
55 | {
56 | escapedString = ''
57 | '\
58 | '';
59 | }
60 |
61 | {
62 | tuple = mkTuple [
63 | 1
64 | ["foo"]
65 | ];
66 | }
67 |
68 | {maybe1 = mkNothing type.string;}
69 | {maybe2 = mkJust (mkUint32 4);}
70 |
71 | {variant1 = mkVariant "foo";}
72 | {variant2 = mkVariant 42;}
73 |
74 | {
75 | dictionaryEntry = mkDictionaryEntry [
76 | 1
77 | ["foo"]
78 | ];
79 | }
80 | ];
81 |
82 | project.file."result.txt".text = let
83 | mkLine = n: v: "${n} = ${toString (pm.gvariant.mkValue v)}";
84 | result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples);
85 | in
86 | result + "\n";
87 |
88 | nmt.script = ''
89 | assertFileContent \
90 | project-files/result.txt \
91 | ${pkgs.writeText "expected.txt" ''
92 | array1 = @as ['one','two']
93 | array2 = @au [1,2]
94 | bool = true
95 | dictionaryEntry = @{ias} {1,@as ['foo']}
96 | emptyArray1 = @as []
97 | emptyArray2 = @au []
98 | escapedString = '\'\\\n'
99 | float = 3.140000
100 | int = -42
101 | int16 = @n -42
102 | int64 = @x -42
103 | maybe1 = @ms nothing
104 | maybe2 = just @u 4
105 | string = 'foo'
106 | tuple = @(ias) (1,@as ['foo'])
107 | uint16 = @q 42
108 | uint32 = @u 42
109 | uint64 = @t 42
110 | variant1 = @v <'foo'>
111 | variant2 = @v <42>
112 | ''}
113 | '';
114 | };
115 | }
116 |
--------------------------------------------------------------------------------
/modules/programs/direnv.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }:
7 | with lib; let
8 | cfg = config.programs.direnv;
9 |
10 | projectDirectory = config.project.projectDirectory;
11 |
12 | fileContents =
13 | (import ../lib/file-type.nix {
14 | inherit projectDirectory lib pkgs;
15 | commit-by-default = config.project.commit-by-default;
16 | })
17 | .fileContents;
18 | in {
19 | meta.maintainers = [maintainers.sellout];
20 |
21 | options.programs.direnv = {
22 | enable = mkEnableOption "direnv, the environment switcher";
23 |
24 | package = mkOption {
25 | type = types.package;
26 | default = pkgs.direnv;
27 | defaultText = lib.literalMD "pkgs.direnv";
28 | description = ''
29 | Direnv package to install.
30 | '';
31 | };
32 |
33 | auto-allow = mkOption {
34 | type = types.bool;
35 | default = false;
36 | description = ''
37 | Whether running project-manager will implicitly run `direnv allow` for
38 | the user.
39 | '';
40 | };
41 |
42 | commit-envrc = mkOption {
43 | type = types.bool;
44 | default = true;
45 | description = ''
46 | Whether .envrc should be committed to the repo or only available
47 | locally. If this is false, then users will have to run
48 | `project-manager switch` before direnv will work.
49 | '';
50 | };
51 |
52 | envrc = mkOption {
53 | type = lib.types.lines;
54 | description = ''
55 | The contents of the .envrc file.
56 | '';
57 | };
58 |
59 | layoutDir = mkOption {
60 | type = lib.types.nullOr lib.types.str;
61 | default = "${config.xdg.cacheDir}/direnv";
62 | description = ''
63 | Where to store direnv‘s cache. If null, this won’t be set.
64 | '';
65 | };
66 | };
67 |
68 | config = mkIf cfg.enable {
69 | project.devPackages = [cfg.package];
70 |
71 | programs.git.ignores = ["/${cfg.layoutDir}/"];
72 | programs.mercurial.ignoresRooted = ["${cfg.layoutDir}/**"];
73 | programs.vale.excludes = mkIf (config.project.file.".envrc".persistence != "store") [
74 | "./${config.project.file.".envrc".target}"
75 | ];
76 |
77 | project = {
78 | activation.direnv = mkIf cfg.auto-allow (pm.dag.entryAfter ["onFilesChange"] ''
79 | ${pkgs.direnv}/bin/direnv allow
80 | '');
81 |
82 | file.".envrc" = {
83 | text =
84 | ''
85 | # This file was generated by Project Manager.
86 | ''
87 | + (
88 | if cfg.layoutDir == null
89 | then ""
90 | else ''
91 | direnv_layout_dir="$PWD/${cfg.layoutDir}"
92 | ''
93 | )
94 | + cfg.envrc;
95 | minimum-persistence = "worktree";
96 | ## See direnv/direnv#1160.
97 | broken-symlink = true;
98 | commit-by-default = cfg.commit-envrc;
99 | };
100 | };
101 | };
102 | }
103 |
--------------------------------------------------------------------------------
/docs/manual/usage/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration Example {#sec-usage-configuration}
2 |
3 | A fresh install of Project Manager will generate a minimal `$PROJECT_ROOT/.config/project/default.nix` file containing something like
4 |
5 | ```nix
6 | { config, pkgs, ... }:
7 |
8 | {
9 | # This value determines the Project Manager release that your
10 | # configuration is compatible with. This helps avoid breakage
11 | # when a new Project Manager release introduces backwards
12 | # incompatible changes.
13 | #
14 | # You can update Project Manager without changing this value. See
15 | # the Project Manager release notes for a list of state version
16 | # changes in each release.
17 | home.stateVersion = 0;
18 |
19 | # Let Project Manager install and manage itself.
20 | programs.project-manager.enable = true;
21 | }
22 | ```
23 |
24 | You can use this as a base for your further configurations.
25 |
26 | ::: {.note}
27 | If you aren’t very familiar with the Nix language and NixOS modules
28 | then it’s encouraged to start with small and simple changes. As you
29 | learn you can gradually grow the configuration with confidence.
30 | :::
31 |
32 | As an example, let us expand the initial configuration file to also
33 | install the `htop` and `fortune` packages, install Emacs with a few extra
34 | packages available, and enable the user gpg-agent service.
35 |
36 | To satisfy the above setup we should elaborate the `project.nix` file as
37 | follows:
38 |
39 | ```nix
40 | { config, pkgs, ... }:
41 |
42 | {
43 | # Packages that should be installed to the project profile.
44 | project.devPackages = [
45 | pkgs.htop
46 | pkgs.fortune
47 | ];
48 |
49 | # This value determines the Project Manager release that your
50 | # configuration is compatible with. This helps avoid breakage
51 | # when a new Project Manager release introduces backwards
52 | # incompatible changes.
53 | #
54 | # You can update Project Manager without changing this value. See
55 | # the Project Manager release notes for a list of state version
56 | # changes in each release.
57 | project.stateVersion = 0;
58 |
59 | # Let Project Manager install and manage itself.
60 | programs.project-manager.enable = true;
61 |
62 | programs.emacs = {
63 | enable = true;
64 | extraPackages = epkgs: [
65 | epkgs.nix-mode
66 | epkgs.magit
67 | ];
68 | };
69 |
70 | services.gpg-agent = {
71 | enable = true;
72 | defaultCacheTtl = 1800;
73 | enableSshSupport = true;
74 | };
75 | }
76 | ```
77 |
78 | - Nixpkgs packages can be installed to the development shell using
79 | [project.devPackages](#opt-project.devPackages).
80 |
81 | - The option names of a program module typically start with
82 | `programs.`.
83 |
84 | - Similarly, for a service module, the names start with
85 | `services.`. Note in some cases a package has both
86 | programs _and_ service options – Emacs is such an example.
87 |
88 | To activate this configuration you can run
89 |
90 | ```shell
91 | project-manager switch
92 | ```
93 |
94 | or if you aren’t feeling so lucky,
95 |
96 | ```shell
97 | project-manager build
98 | ```
99 |
100 | which will create a `result` link to a directory containing an
101 | activation script and the generated project directory files.
102 |
--------------------------------------------------------------------------------
/templates/default/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "{{project.summary}}";
3 |
4 | nixConfig = {
5 | registries = false;
6 | sandbox = "relaxed";
7 | };
8 |
9 | outputs = {
10 | flake-utils,
11 | nixpkgs,
12 | project-manager,
13 | self,
14 | }:
15 | flake-utils.lib.eachDefaultSystem (system: let
16 | pkgs = import nixpkgs {inherit system;};
17 | in {
18 | ## This is the line that sets up Project Manager for your project. It
19 | ## expects to find the configuration in .config/project/default.nix, which
20 | ## is a Nix module.
21 | projectConfigurations = project-manager.lib.defaultConfiguration {
22 | inherit pkgs self;
23 |
24 | ## Specify any additional project configuration modules here, for
25 | ## example, ones from another flake.
26 | modules = [
27 | # some-other-input.projectModules.someModule
28 | ];
29 |
30 | ## Optionally use `extraSpecialArgs` to pass through arguments to
31 | ## project.nix.
32 | };
33 |
34 | devShells =
35 | ## Project Manager provides some devShells, but not the default. The
36 | ## `project-manager` devShell contains the packages included in the
37 | ## project configuration (.config/project/default.nix).
38 | self.projectConfigurations.${system}.devShells
39 | // {
40 | ## An easy way to create a default devShell is to override
41 | ## `devShells.project-manager` and add the inputs from your other
42 | ## derivations. This should have all the build dependencies for your
43 | ## packages & checks, but also the packages from the project
44 | ## configuration.
45 | default =
46 | self.projectConfigurations.${system}.devShells.project-manager.overrideAttrs
47 | (old: {
48 | inputsFrom =
49 | old.inputsFrom
50 | or []
51 | ++ builtins.attrValues self.checks.${system} or {}
52 | ++ builtins.attrValues self.packages.${system} or {};
53 | });
54 | };
55 |
56 | ## If you’ve configured a formatter via your project configuration, this
57 | ## enables it for the flake – `nix fmt` will run this formatter across the
58 | ## project.
59 | formatter = self.projectConfigurations.${system}.formatter;
60 |
61 | ## Project Manager provides a number of its own checks – ones to ensure
62 | ## any generated files are up-to-date, ones that ensure the formatter has
63 | ## been run, etc. Project modules can also add their own checks – e.g.,
64 | ## the Vale module adds a style check for English prose.
65 | checks = self.projectConfigurations.${system}.checks;
66 | });
67 |
68 | inputs = {
69 | flake-utils.url = "github:numtide/flake-utils";
70 |
71 | nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
72 |
73 | project-manager = {
74 | url = "github:sellout/project-manager";
75 | ## It’s generally not helpful to set Project Manager’s nixpkgs to match
76 | ## your own. Project Manager distinguishes between the Nixpkgs it uses
77 | ## internally and the Nixpkgs used for your configuration. That allows
78 | ## Project Manager to support many different releases of Nixpkgs on a
79 | ## single develoment path.
80 | # inputs.nixpkgs.follows = "nixpkgs";
81 | };
82 | };
83 | }
84 |
--------------------------------------------------------------------------------
/modules/programs/mercurial.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }:
7 | with lib; let
8 | cfg = config.programs.mercurial;
9 |
10 | iniFormat = pkgs.formats.ini {};
11 | in {
12 | meta.maintainers = [lib.maintainers.sellout];
13 |
14 | options = {
15 | programs.mercurial = {
16 | enable = mkEnableOption "Mercurial";
17 |
18 | package = mkOption {
19 | type = types.package;
20 | default = pkgs.mercurial;
21 | defaultText = lib.literalMD "pkgs.mercurial";
22 | description = "Mercurial package to install.";
23 | };
24 |
25 | aliases = mkOption {
26 | type = types.attrsOf types.anything;
27 | default = {};
28 | description = "Mercurial aliases to define.";
29 | };
30 |
31 | extraConfig = mkOption {
32 | type = types.either (types.attrsOf types.anything) types.lines;
33 | default = {};
34 | description = "Additional configuration to add.";
35 | };
36 |
37 | iniContent = mkOption {
38 | type = iniFormat.type;
39 | internal = true;
40 | };
41 |
42 | ignores = mkOption {
43 | type = types.listOf types.str;
44 | default = [];
45 | example = ["*~" "*.swp"];
46 | description = ''
47 | List of globs for files to be globally ignored.
48 | '';
49 | };
50 |
51 | ignoresRegexp = mkOption {
52 | type = types.listOf types.str;
53 | default = [];
54 | example = ["^.*~$" "^.*\\.swp$"];
55 | description = ''
56 | List of regular expressions for files to be globally ignored.
57 | '';
58 | };
59 |
60 | ignoresRooted = mkOption {
61 | type = types.listOf types.str;
62 | default = [];
63 | example = ["^.*~$" "^.*\\.swp$"];
64 | description = ''
65 | Similar to programs.mercurial.ignores, but these are rooted.
66 | '';
67 | };
68 | };
69 | };
70 |
71 | config = mkIf cfg.enable (mkMerge [
72 | {
73 | project.devPackages = [cfg.package];
74 |
75 | project.file.".hg/hgrc" = {
76 | persistence = "store";
77 | source = iniFormat.generate "hgrc" cfg.iniContent;
78 | };
79 |
80 | project.file.".hgignore" = {
81 | persistence = "store";
82 | text =
83 | ''
84 | syntax: glob
85 | ''
86 | + concatStringsSep "\n" (mapAttrsToList (n: v: v.target)
87 | (filterAttrs (n: v: v.persistence != "repository") config.project.file)
88 | ++ cfg.ignores)
89 | + "\n"
90 | + ''
91 | syntax: regexp
92 | ''
93 | + concatStringsSep "\n" cfg.ignoresRegexp
94 | + "\n"
95 | + ''
96 | syntax: rootglob
97 | ''
98 | + concatStringsSep "\n" cfg.ignoresRooted
99 | + "\n";
100 | };
101 | }
102 |
103 | (mkIf (cfg.aliases != {}) {
104 | programs.mercurial.iniContent.alias = cfg.aliases;
105 | })
106 |
107 | (mkIf (lib.isAttrs cfg.extraConfig) {
108 | programs.mercurial.iniContent = cfg.extraConfig;
109 | })
110 |
111 | (mkIf (lib.isString cfg.extraConfig) {
112 | project.file.".hg/hgrc" = {
113 | persistence = "store";
114 | text = cfg.extraConfig;
115 | };
116 | })
117 | ]);
118 | }
119 |
--------------------------------------------------------------------------------
/testing/stubs.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | inherit (lib) mkOption types;
8 |
9 | stubType = types.submodule (
10 | {name, ...}: {
11 | options = {
12 | name = mkOption {
13 | type = types.str;
14 | default = "dummy";
15 | description = "The stub package name.";
16 | };
17 |
18 | outPath = mkOption {
19 | type = types.nullOr types.str;
20 | default = "@${name}@";
21 | defaultText = lib.literalExpression ''"@''${name}@"'';
22 | };
23 |
24 | version = mkOption {
25 | type = types.nullOr types.str;
26 | default = null;
27 | defaultText = lib.literalExpression "pkgs.\${name}.version or null";
28 | };
29 |
30 | buildScript = mkOption {
31 | type = types.str;
32 | default = defaultBuildScript;
33 | };
34 |
35 | extraAttrs = mkOption {
36 | type = types.attrsOf types.anything;
37 | default = {};
38 | };
39 | };
40 | }
41 | );
42 |
43 | defaultBuildScript = "mkdir $out";
44 |
45 | dummyPackage = pkgs.runCommandLocal "dummy" {meta.mainProgram = "dummy";} defaultBuildScript;
46 |
47 | mkStubPackage = {
48 | name ? "dummy",
49 | outPath ? null,
50 | version ? null,
51 | buildScript ? defaultBuildScript,
52 | extraAttrs ? {},
53 | }: let
54 | pkg =
55 | if name == "dummy" && buildScript == defaultBuildScript
56 | then dummyPackage
57 | else
58 | pkgs.runCommandLocal name {
59 | pname = name;
60 | meta.mainProgram = name;
61 | }
62 | buildScript;
63 |
64 | stubbedPkg =
65 | pkg
66 | // lib.optionalAttrs (outPath != null) {
67 | inherit outPath;
68 |
69 | # Prevent getOutput from descending into outputs
70 | outputSpecified = true;
71 |
72 | # Allow the original package to be used in derivation inputs
73 | __spliced = {
74 | buildHost = pkg;
75 | hostTarget = pkg;
76 | };
77 | }
78 | // lib.optionalAttrs (version != null) {inherit version;}
79 | // extraAttrs;
80 | in
81 | stubbedPkg;
82 | in {
83 | options.test = {
84 | stubs = mkOption {
85 | type = types.attrsOf stubType;
86 | default = {};
87 | description = "Package attributes that should be replaced by a stub package.";
88 | };
89 |
90 | stubOverlays = mkOption {
91 | type = types.anything;
92 | default = [];
93 | internal = true;
94 | };
95 |
96 | unstubs = mkOption {
97 | type = types.listOf types.anything;
98 | default = [];
99 | };
100 | };
101 |
102 | config = {
103 | lib.test.mkStubPackage = mkStubPackage;
104 |
105 | test.stubOverlays =
106 | []
107 | ++ lib.optional (config.test.stubs != {}) (
108 | self: super:
109 | lib.mapAttrs (
110 | n: v:
111 | builtins.traceVerbose "${n} - stubbed" (
112 | mkStubPackage (
113 | v
114 | // lib.optionalAttrs (v.version == null) {
115 | version = super.${n}.version or null;
116 | }
117 | )
118 | )
119 | )
120 | config.test.stubs
121 | )
122 | ++ config.test.unstubs;
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/modules/misc/xdg.nix:
--------------------------------------------------------------------------------
1 | ## This module implements a pseudo-XDG file-system layout. It’s XDG base
2 | ## directories, but using `$PROJECT_ROOT` instead of `$HOME`.
3 | {
4 | config,
5 | lib,
6 | pkgs,
7 | ...
8 | }: let
9 | cfg = config.xdg;
10 |
11 | fileType =
12 | (import ../lib/file-type.nix {
13 | inherit lib pkgs;
14 | inherit (config.project) commit-by-default projectDirectory;
15 | })
16 | .fileType;
17 | in {
18 | meta.maintainers = [lib.maintainers.sellout];
19 |
20 | options.xdg = {
21 | cacheDir = lib.mkOption {
22 | type = lib.types.str;
23 | default = ".cache";
24 | description = ''
25 | Path to directory holding application caches, relative to PROJECT_ROOT.
26 | '';
27 | };
28 |
29 | ## TODO: Maybe add a project option as to whether non-store persisted files
30 | ## should be placed in `xdg.cacheDir` when possible. Otherwise it
31 | ## would default to their standard locations. But we then need to be
32 | ## able to indicate in `fileType` whether a file is relocatable, and
33 | ## make sure the target & store paths are kept up-to-date.
34 | cacheFile = lib.mkOption {
35 | type = fileType "xdg.cacheFile" "{var}`xdg.cacheDir`" cfg.cacheDir;
36 | default = {};
37 | description = ''
38 | Attribute set of files to link into the project's XDG
39 | cache directory.
40 |
41 | This should be used whenever files need to be included in the worktree,
42 | but we can control where they are located. It removes clutter from the
43 | worktree.
44 | '';
45 | };
46 |
47 | configDir = lib.mkOption {
48 | type = lib.types.str;
49 | default = ".config";
50 | description = ''
51 | Path to directory holding application configurations, relative to
52 | PROJECT_ROOT.
53 | '';
54 | };
55 |
56 | configFile = lib.mkOption {
57 | type = fileType "xdg.configFile" "{var}`xdg.configDir`" cfg.configDir;
58 | default = {};
59 | description = ''
60 | Attribute set of files to link into the project's XDG
61 | configuration directory.
62 | '';
63 | };
64 |
65 | dataDir = lib.mkOption {
66 | type = lib.types.str;
67 | default = ".local/share";
68 | description = ''
69 | Path to directory holding application data, relative to PROJECT_ROOT.
70 | '';
71 | };
72 |
73 | dataFile = lib.mkOption {
74 | type =
75 | fileType "xdg.dataFile" "xdg.dataDir" cfg.dataDir;
76 | default = {};
77 | description = ''
78 | Attribute set of files to link into the project's XDG
79 | data directory.
80 | '';
81 | };
82 |
83 | stateDir = lib.mkOption {
84 | type = lib.types.str;
85 | default = ".local/state";
86 | description = ''
87 | Path to directory holding application states, relative to PROJECT_ROOT.
88 | '';
89 | };
90 | };
91 |
92 | config = {
93 | project.file = lib.mkMerge [
94 | (lib.mapAttrs'
95 | (name: file: lib.nameValuePair "${cfg.cacheDir}/${name}" file)
96 | cfg.cacheFile)
97 | (lib.mapAttrs'
98 | (name: file: lib.nameValuePair "${cfg.configDir}/${name}" file)
99 | cfg.configFile)
100 | (lib.mapAttrs'
101 | (name: file: lib.nameValuePair "${cfg.dataDir}/${name}" file)
102 | cfg.dataFile)
103 | ];
104 | };
105 | }
106 |
--------------------------------------------------------------------------------
/docs/manual/quick-start/existing-project.md:
--------------------------------------------------------------------------------
1 | # Integrate With an Existing Project {#sec-existing-project}
2 |
3 | **WARNING**: `project-manager switch` (but no other subcommands) may overwrite files that already exist in your working tree. You should ensure that everything is committed or stashed before integrating Project Manager with an existing project.
4 |
5 | Start by creating a basic project configuration by running this from within your project directory:
6 |
7 | ```bash
8 | nix run github:sellout/project-manager
9 | ```
10 |
11 | Note that this is similar to the command used by a new project. The differences are that
12 |
13 | 1. we’re not providing a directory for it to create, so it will use the current directory and
14 | 2. we’re not providing `--switch` to avoid it immediately creating the new configuration.
15 |
16 | If your project is already flake-based, this _won’t_ overwrite the existing flake, so you will need to modify your flake to provide Project Manager integration.
17 |
18 | At a minimum, you need to have
19 |
20 | ```nix
21 | {
22 | outputs = {nixpkgs, project-manager, self}: let
23 | system = "x86_64-linux"; # or another system
24 | pkgs = import nixpkgs {inherit system};
25 | in {
26 | projectConfigurations.${system} =
27 | project-manager.lib.defaultConfiguration {inherit pkgs self};
28 | }
29 |
30 | inputs = {
31 | # NB: This version doesn’t have to be current, Project Manager supports many
32 | # older Nixpkgs releases as well as unstable (as much as is possible).
33 | nixpkgs.url = "github:NixOS/nixpkgs/release-24.11";
34 | project-manager.url = "github:sellout/project-manager";
35 | }
36 | }
37 | ```
38 |
39 | But project manager offers many attributes to integrate with other parts of your flake. For example., you may also include
40 |
41 | ```nix
42 | {
43 | outputs = {nixpkgs, project-manager, self}: let
44 | system = "x86_64-linux"; # or another system
45 | pkgs = import nixpkgs {inherit system};
46 | in {
47 | # …
48 |
49 | devShells.${system} = let
50 | self.projectConfigurations.${system}.devShells
51 | // {
52 | # You can user `overrideAttrs` on this to extend this shell.
53 | default =
54 | self.projectConfigurations.${system}.devShells.project-manager;
55 | };
56 |
57 | checks.${system} =
58 | # This provides a check that files generated by Project Manager are
59 | # up-to-date, as well as checks for other modules you may have
60 | # configured (a formatter, linters, etc.)
61 | self.projectConfigurations.${system}.checks
62 | // {
63 | # Any additional checks
64 | }
65 |
66 | # If your formatter is configured by Project Manager, which is especially
67 | # useful when using meta-formatters like treefmt-nix.
68 | # (https://github.com/numtide/treefmt-nix#readme)
69 | formatter.${system} = self.projectConfigurations.${system}.formatter;
70 | }
71 | ```
72 |
73 | If you have all of your non-Project Manager related changes stashed, you can see how the default configuration affects your project by running
74 |
75 | ```nix
76 | nix run github:sellout/project-manager -- switch
77 | ```
78 |
79 | You can now see what changes are reflected in your VCS. The most important thing is to check for modified files. For anything that has been modified you can either disable that module in .config/project/default.nix or adjust the [configuration](#ch-options) there to match what exists in your repository.
80 |
81 | Other files may have also been created. You can similarly disable these modules if you want to.
82 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | # This file was generated by Project Manager.
2 | {"branches":[{"name":"main","protection":{"allow_force_pushes":false,"enforce_admins":true,"required_linear_history":false,"required_pull_request_reviews":null,"required_status_checks":{"contexts":["All Garnix checks","build checks.x86_64-linux.formatter","build checks.x86_64-linux.project-manager-files","build checks.x86_64-linux.shellcheck","build checks.x86_64-linux.vale","build devShells.x86_64-linux.default","build devShells.x86_64-linux.project-manager","build packages.x86_64-linux.default","build packages.x86_64-linux.docs-html","build packages.x86_64-linux.docs-json","build packages.x86_64-linux.docs-manpages","build packages.x86_64-linux.project-manager","build checks.x86_64-linux.formatter-22_11","build checks.x86_64-linux.project-manager-files-22_11","build checks.x86_64-linux.shellcheck-22_11","build checks.x86_64-linux.vale-22_11","build checks.x86_64-linux.formatter-23_05","build checks.x86_64-linux.project-manager-files-23_05","build checks.x86_64-linux.shellcheck-23_05","build checks.x86_64-linux.vale-23_05","build checks.x86_64-linux.formatter-23_11","build checks.x86_64-linux.project-manager-files-23_11","build checks.x86_64-linux.shellcheck-23_11","build checks.x86_64-linux.vale-23_11","build checks.x86_64-linux.formatter-24_05","build checks.x86_64-linux.project-manager-files-24_05","build checks.x86_64-linux.shellcheck-24_05","build checks.x86_64-linux.vale-24_05","build checks.x86_64-linux.formatter-24_11","build checks.x86_64-linux.project-manager-files-24_11","build checks.x86_64-linux.shellcheck-24_11","build checks.x86_64-linux.vale-24_11","build checks.x86_64-linux.formatter-25_05","build checks.x86_64-linux.project-manager-files-25_05","build checks.x86_64-linux.shellcheck-25_05","build checks.x86_64-linux.vale-25_05","build checks.x86_64-linux.formatter-25_11","build checks.x86_64-linux.project-manager-files-25_11","build checks.x86_64-linux.shellcheck-25_11","build checks.x86_64-linux.vale-25_11"],"strict":false},"restrictions":null}}],"labels":[{"color":"#666666","description":"Created automatically by some service or process","name":"automated"},{"color":"#d73a4a","description":"Something isn’t working","name":"bug"},{"color":"#333333","description":"Updates or other changes to dependencies","name":"dependencies"},{"color":"#0075ca","description":"Improvements or additions to documentation","name":"documentation"},{"color":"#a2eeef","description":"New feature or request","name":"enhancement"},{"color":"#7057ff","description":"Good for newcomers","name":"good first issue"},{"color":"#000000","description":"Issues you want contributors to help with.","name":"hacktoberfest"},{"color":"#ff7518","description":"Indicates acceptance for Hacktoberfest criteria, even if not merged yet.","name":"hacktoberfest-accepted"},{"color":"#008672","description":"Extra attention is needed","name":"help wanted"},{"color":"#333333","description":"Unaccepted contributions that haven’t been closed for some reason.","name":"invalid"},{"color":"#d876e3","description":"Further information is requested","name":"question"},{"color":"#ffc0cb","description":"Topic created in bad faith. Services like Hacktoberfest use this to identify bad actors.","name":"spam"},{"color":"#d4af37","description":"Work prioritized by a sponsor","name":"sponsored"}],"pages":{"build_type":"workflow","source":{"branch":"main"}},"repository":{"allow_merge_commit":true,"allow_rebase_merge":false,"allow_squash_merge":false,"default_branch":"main","delete_branch_on_merge":true,"description":"Home Manager, but for repos.","enable_automated_security_fixes":true,"enable_vulnerability_alerts":true,"has_downloads":false,"has_issues":true,"has_projects":true,"has_wiki":true,"homepage":"https://sellout.github.io/project-manager","merge_commit_message":"PR_BODY","merge_commit_title":"PR_TITLE","name":"project-manager","private":false,"topics":"hacktoberfest, development, nix-flakes"}}
3 |
--------------------------------------------------------------------------------
/modules/services/flakehub/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | cfg = config.services.flakehub;
8 | in {
9 | meta.maintainers = [lib.maintainers.sellout];
10 |
11 | options.services.flakehub = {
12 | enable = lib.mkEnableOption "FlakeHub";
13 |
14 | package = lib.mkPackageOption pkgs "FlakeHub" {
15 | default = ["fh"];
16 | };
17 |
18 | mode = lib.mkOption {
19 | type =
20 | lib.types.either (lib.types.enum ["tagged"])
21 | (lib.types.attrsOf lib.types.str);
22 | default = "tagged";
23 | example.rolling = "main";
24 | description = ''
25 | This is either the string “tagged” or an attrset with the key “rolling”
26 | whose value names the branch to watch for rolling changes.
27 | '';
28 | };
29 |
30 | name = lib.mkOption {
31 | description = ''
32 | The name to publish as on FlakeHub. This defaults to “owner/repo”.
33 | '';
34 | type = lib.types.str;
35 | # default = {}; # TODO: Infer from the upstream / origin (default?) remote
36 | };
37 |
38 | visibility = lib.mkOption {
39 | description = ''
40 | “unlisted” means that your flake can be accessed by Nix but only appears
41 | on the website if you directly navigate to it. “public” means that your
42 | flake can be found by searching.
43 | '';
44 | type = lib.types.enum ["unlisted" "public"];
45 | default = "unlisted";
46 | };
47 | };
48 |
49 | config = lib.mkIf cfg.enable {
50 | ## Written as `lines` instead of an attr set to make it easier to compare
51 | ## against https://flakehub.com/new, which this mimics.
52 | services.github.workflow."flakehub-publish.yml" = {
53 | text =
54 | if cfg.mode ? rolling
55 | then ''
56 | name: "Publish every Git push to ${cfg.mode.rolling} to FlakeHub"
57 | on:
58 | push:
59 | branches:
60 | - "${cfg.mode.rolling}"
61 | jobs:
62 | flakehub-publish:
63 | runs-on: "ubuntu-24.04"
64 | permissions:
65 | id-token: "write"
66 | contents: "read"
67 | steps:
68 | - uses: "actions/checkout@v6"
69 | - uses: "DeterminateSystems/nix-installer-action@main"
70 | - uses: "DeterminateSystems/flakehub-push@main"
71 | with:
72 | name: "${cfg.name}"
73 | rolling: true
74 | visibility: "${cfg.visibility}"
75 | ''
76 | else ''
77 | name: "Publish tags to FlakeHub"
78 | on:
79 | push:
80 | tags:
81 | - "v?[0-9]+.[0-9]+.[0-9]+*"
82 | workflow_dispatch:
83 | inputs:
84 | tag:
85 | description: "The existing tag to publish to FlakeHub"
86 | type: "string"
87 | required: true
88 | jobs:
89 | flakehub-publish:
90 | runs-on: "ubuntu-24.04"
91 | permissions:
92 | id-token: "write"
93 | contents: "read"
94 | steps:
95 | - uses: "actions/checkout@v6"
96 | with:
97 | ref: "''${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || ''' }}"
98 | - uses: "DeterminateSystems/nix-installer-action@main"
99 | - uses: "DeterminateSystems/flakehub-push@main"
100 | with:
101 | visibility: "${cfg.visibility}"
102 | name: "${cfg.name}"
103 | tag: "''${{ inputs.tag }}"
104 | '';
105 | };
106 | };
107 | }
108 |
--------------------------------------------------------------------------------
/nix/schemas.nix:
--------------------------------------------------------------------------------
1 | {flake-schemas}: let
2 | ## Stolen from DeterminateSystems/flake-schemas, which should export it in its
3 | ## lib.
4 | checkDerivation = drv: drv.type or null == "derivation" && drv ? drvPath;
5 | in {
6 | projectConfigurations = {
7 | version = 1;
8 | doc = ''
9 | The `projectConfigurations` flake output defines project configurations.
10 | '';
11 | inventory = output:
12 | flake-schemas.lib.mkChildren (builtins.mapAttrs (system: project: let
13 | forSystems = [system];
14 | in {
15 | inherit forSystems;
16 | what = "Project Manager configuration for this flake’s project";
17 | children = {
18 | packages = flake-schemas.lib.mkChildren (builtins.mapAttrs (packageName: package: {
19 | inherit forSystems;
20 | shortDescription = package.meta.description or "";
21 | derivation = package;
22 | evalChecks.isDerivation = checkDerivation package;
23 | what = "package";
24 | isFlakeCheck = false;
25 | })
26 | project.packages);
27 | checks = flake-schemas.lib.mkChildren (builtins.mapAttrs (checkName: check: {
28 | inherit forSystems;
29 | shortDescription = check.meta.description or "";
30 | derivation = check;
31 | evalChecks.isDerivation = checkDerivation check;
32 | what = "CI test";
33 | isFlakeCheck = true;
34 | })
35 | project.checks);
36 | devShells = flake-schemas.lib.mkChildren (builtins.mapAttrs (shellName: shell: {
37 | inherit forSystems;
38 | shortDescription = shell.meta.description or "";
39 | derivation = shell;
40 | evalChecks.isDerivation = checkDerivation shell;
41 | what = "development environment";
42 | isFlakeCheck = false;
43 | })
44 | project.devShells);
45 | formatter = {
46 | inherit forSystems;
47 | shortDescription = project.formatter.meta.description or "";
48 | derivation = project.formatter;
49 | evalChecks.isDerivation = checkDerivation project.formatter;
50 | what = "Nix code formatter";
51 | isFlakeCheck = false;
52 | };
53 | filterRepositoryPersistedExcept = {
54 | inherit forSystems;
55 | shortDescription = project.options.project.filterRepositoryPersistedExcept.description;
56 | what = "source filter";
57 | isFlakeCheck = false;
58 | };
59 | filterRepositoryPersisted = {
60 | inherit forSystems;
61 | shortDescription = project.options.project.filterRepositoryPersisted.description;
62 | what = "source filter";
63 | isFlakeCheck = false;
64 | };
65 | cleanRepositoryPersistedExcept = {
66 | inherit forSystems;
67 | shortDescription = project.options.project.cleanRepositoryPersistedExcept.description;
68 | what = "source filter";
69 | isFlakeCheck = false;
70 | };
71 | cleanRepositoryPersisted = {
72 | inherit forSystems;
73 | shortDescription = project.options.project.cleanRepositoryPersisted.description;
74 | what = "source filter";
75 | isFlakeCheck = false;
76 | };
77 | };
78 | })
79 | output);
80 | };
81 |
82 | projectModules = {
83 | version = 1;
84 | doc = ''
85 | Defines “project modules” analogous to `nixosModules` or
86 | `homeModules`, but scoped to a single project (often some VCS repo).
87 | '';
88 | inventory = output:
89 | flake-schemas.lib.mkChildren (builtins.mapAttrs (moduleName: module: {
90 | what = "Project Manager module";
91 | })
92 | output);
93 | };
94 | }
95 |
--------------------------------------------------------------------------------
/testing/darwinScrubList.nix:
--------------------------------------------------------------------------------
1 | {
2 | lib,
3 | scrubDerivation,
4 | }: let
5 | # List of packages that need to be scrubbed on Darwin
6 | # Packages are scrubbed on Linux and expected in test output
7 | packagesToScrub = [
8 | # keep-sorted start case=no numeric=yes
9 | "aerc"
10 | "aerospace"
11 | "aichat"
12 | "alacritty"
13 | "alot"
14 | "antidote"
15 | "aria2"
16 | "atuin"
17 | "autojump"
18 | "bacon"
19 | "bash"
20 | "bash-completion"
21 | "bash-preexec"
22 | "bashInteractive"
23 | "bat"
24 | "borgmatic"
25 | "bottom"
26 | "broot"
27 | "browserpass"
28 | "btop"
29 | "carapace"
30 | "cava"
31 | "clock-rs"
32 | "cmus"
33 | "codex"
34 | "comodoro"
35 | "darcs"
36 | "delta"
37 | "dircolors"
38 | "direnv"
39 | "earthly"
40 | "element-desktop"
41 | "emacs"
42 | "espanso"
43 | "eza"
44 | "fastfetch"
45 | "feh"
46 | "fzf"
47 | "gallery-dl"
48 | "gh"
49 | "gh-dash"
50 | "ghostty"
51 | "git"
52 | "git-cliff"
53 | "git-credential-oauth"
54 | "git-lfs"
55 | "git-worktree-switcher"
56 | "gitMinimal"
57 | "gnupg"
58 | "go"
59 | "gradle"
60 | "granted"
61 | "helix"
62 | "hello"
63 | "himalaya"
64 | "hjson-go"
65 | "htop"
66 | "hyfetch"
67 | "i3status"
68 | "inori"
69 | "irssi"
70 | "isync"
71 | "jankyborders"
72 | "joplin-desktop"
73 | "jqp"
74 | "jujutsu"
75 | "k9s"
76 | "kakoune"
77 | "khal"
78 | "khard"
79 | "kitty"
80 | "kubecolor"
81 | "kubeswitch"
82 | "lapce"
83 | "lazydocker"
84 | "lazygit"
85 | "ledger"
86 | "less"
87 | "lesspipe"
88 | "lf"
89 | "lieer"
90 | "lsd"
91 | "mbsync"
92 | "meli"
93 | "mergiraf"
94 | "micro"
95 | "mise"
96 | "mpv"
97 | "msmtp"
98 | "mu"
99 | "mujmap"
100 | "mullvad-vpn"
101 | "ncmpcpp"
102 | "ne"
103 | "neomutt"
104 | "neovide"
105 | "neovim"
106 | "newsboat"
107 | "nh"
108 | "nheko"
109 | "nix"
110 | "nix-direnv"
111 | "nix-index"
112 | "nix-init"
113 | "nix-your-shell"
114 | "notmuch"
115 | "npth"
116 | "nushell"
117 | "nyxt"
118 | "oh-my-posh"
119 | "ollama"
120 | "onlyoffice-desktopeditors"
121 | "opencode"
122 | "openstackclient"
123 | "papis"
124 | "patdiff"
125 | "pay-respects"
126 | "pet"
127 | "pistol"
128 | "pls"
129 | "poetry"
130 | "powerline-go"
131 | "pubs"
132 | "pyenv"
133 | "qcal"
134 | "qutebrowser"
135 | "ranger"
136 | "rio"
137 | "ripgrep"
138 | "ruff"
139 | "sage"
140 | "sapling"
141 | "sbt"
142 | "scmpuff"
143 | "senpai"
144 | "sftpman"
145 | "sioyek"
146 | "skhd"
147 | "sm64ex"
148 | "smug"
149 | "spotify-player"
150 | "starship"
151 | "superfile"
152 | "taskwarrior"
153 | "tealdeer"
154 | "texlive"
155 | "thefuck"
156 | "thunderbird"
157 | "tmate"
158 | "tmux"
159 | "topgrade"
160 | "translate-shell"
161 | "tray-tui"
162 | "vifm"
163 | "vim-vint"
164 | "vimPlugins"
165 | "visidata"
166 | "vscode"
167 | "wallust"
168 | "watson"
169 | "wezterm"
170 | "yazi"
171 | "yq-go"
172 | "yubikey-agent"
173 | "zed-editor"
174 | "zellij"
175 | "zk"
176 | "zoxide"
177 | "zplug"
178 | "zsh"
179 | # keep-sorted end
180 | ];
181 |
182 | # Create an overlay that scrubs packages in the scrublist
183 | packageScrubOverlay = self: super:
184 | lib.mapAttrs (
185 | name: value:
186 | if lib.elem name packagesToScrub
187 | then
188 | # Apply scrubbing to this specific package
189 | scrubDerivation name value
190 | else value
191 | )
192 | super;
193 | in
194 | self: super:
195 | packageScrubOverlay self super
196 | // {
197 | buildPackages = super.buildPackages.extend packageScrubOverlay;
198 | }
199 |
--------------------------------------------------------------------------------
/modules/misc/news.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }:
7 | with lib; let
8 | cfg = config.news;
9 |
10 | hostPlatform = pkgs.stdenv.hostPlatform;
11 |
12 | entryModule = types.submodule ({config, ...}: {
13 | options = {
14 | id = mkOption {
15 | internal = true;
16 | type = types.str;
17 | description = ''
18 | A unique entry identifier. By default it is a base16
19 | formatted hash of the entry message.
20 | '';
21 | };
22 |
23 | time = mkOption {
24 | internal = true;
25 | type = types.str;
26 | example = "2017-07-10T21:55:04+00:00";
27 | description = ''
28 | News entry time stamp in ISO-8601 format. Must be in UTC
29 | (ending in '+00:00').
30 | '';
31 | };
32 |
33 | condition = mkOption {
34 | internal = true;
35 | default = true;
36 | description = "Whether the news entry should be active.";
37 | };
38 |
39 | message = mkOption {
40 | internal = true;
41 | type = types.str;
42 | description = "The news entry content.";
43 | };
44 | };
45 |
46 | config = {
47 | id = mkDefault (builtins.hashString "sha256" config.message);
48 | };
49 | });
50 | in {
51 | meta.maintainers = [
52 | maintainers.sellout
53 | maintainers.rycee
54 | ];
55 |
56 | options = {
57 | news = {
58 | display = mkOption {
59 | type = types.enum ["silent" "notify" "show"];
60 | default = "notify";
61 | description = ''
62 | How unread and relevant news should be presented when
63 | running {command}`project-manager build` and
64 | {command}`project-manager switch`.
65 |
66 | The options are
67 |
68 | `silent`
69 | : Do not print anything during build or switch. The
70 | {command}`project-manager news` command still
71 | works for viewing the entries.
72 |
73 | `notify`
74 | : The number of unread and relevant news entries will be
75 | printed to standard output. The {command}`project-manager
76 | news` command can later be used to view the entries.
77 |
78 | `show`
79 | : A pager showing unread news entries is opened.
80 | '';
81 | };
82 |
83 | entries = mkOption {
84 | internal = true;
85 | type = types.listOf entryModule;
86 | default = [];
87 | description = "News entries.";
88 | };
89 |
90 | json = {
91 | output = mkOption {
92 | internal = true;
93 | type = types.package;
94 | description = "The generated JSON file package.";
95 | };
96 | };
97 | };
98 | };
99 |
100 | config = {
101 | news.json.output = pkgs.writeText "pm-news.json" (builtins.toJSON {
102 | inherit (cfg) display entries;
103 | });
104 |
105 | # Add news entries in chronological order (i.e., latest time
106 | # should be at the bottom of the list). The time should be
107 | # formatted as given in the output of
108 | #
109 | # date --iso-8601=second --universal
110 | #
111 | # On darwin (or BSD like systems) use
112 | #
113 | # date -u +'%Y-%m-%dT%H:%M:%S+00:00'
114 | news.entries = [
115 | {
116 | time = "2023-07-25T07:16:09+00:00";
117 | condition = hostPlatform.isDarwin;
118 | message = ''
119 | A new module is available: 'services.git-sync'.
120 | '';
121 | }
122 |
123 | {
124 | time = "2023-08-15T15:45:45+00:00";
125 | message = ''
126 | A new module is available: 'programs.xplr'.
127 | '';
128 | }
129 |
130 | {
131 | time = "2023-08-16T15:43:30+00:00";
132 | message = ''
133 | A new module is available: 'programs.pqiv'.
134 | '';
135 | }
136 |
137 | {
138 | time = "2023-08-22T16:06:52+00:00";
139 | message = ''
140 | A new module is available: 'programs.qcal'.
141 | '';
142 | }
143 | ];
144 | };
145 | }
146 |
--------------------------------------------------------------------------------
/modules/services/garnix.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | flaky,
4 | lib,
5 | pkgs,
6 | self,
7 | supportedSystems,
8 | ...
9 | }:
10 | with lib; let
11 | cfg = config.services.garnix;
12 |
13 | isUniversalAttr = value: lib.elem "*.*" value && lib.elem "*.*.*" value;
14 |
15 | buildOptions.options = {
16 | exclude = lib.mkOption {
17 | type = lib.pm.types.globList isUniversalAttr;
18 | default = [];
19 | description = ''
20 | A list of attributes with wildcards that garnix should skip.
21 | '';
22 | };
23 | include = lib.mkOption {
24 | type = lib.pm.types.globList isUniversalAttr;
25 | default = [];
26 | description = ''
27 | A list of attributes with wildcards that garnix should not skip.
28 | '';
29 | };
30 | };
31 | serverOptions.options = {
32 | configuration = lib.mkOption {
33 | type = lib.types.str;
34 | description = ''
35 | The name of the `nixosConfiguration` to deploy.
36 | '';
37 | };
38 | branch = lib.mkOption {
39 | type = lib.types.nullOr lib.types.str;
40 | default = null;
41 | description = ''
42 | This is set to either the branch name for an `on-branch` deployment, or
43 | `null` for `on-pull-request` deployment.
44 | '';
45 | };
46 | };
47 | in {
48 | meta.maintainers = [maintainers.sellout];
49 |
50 | options.services.garnix = {
51 | enable = mkEnableOption "Garnix CI configuration";
52 |
53 | builds = mkOption {
54 | type = lib.types.attrsOf (lib.types.submodule buildOptions);
55 | default = {};
56 | description = ''
57 | Configuration written to {file}`$PROJECT_ROOT/garnix.yaml`.
58 | See for documentation.
59 |
60 | The structure here is different from the YAML. Instead of a “branch”
61 | field, this makes the branch names the keys of the attrSet, with `"*"`
62 | meaning “all branches”.
63 | '';
64 | example = lib.literalMD ''
65 | {
66 | "*" = {
67 | exclude = ["homeConfigurations.*"];
68 | include = [
69 | "*.x86_64-linux.*"
70 | "packages.aarch64-darwin.*"
71 | "defaultPackage.x86_64-linux"
72 | "devShell.x86_64-linux"
73 | ];
74 | };
75 | main.include = ["packages.*.release"];
76 | }
77 | '';
78 | };
79 |
80 | servers = mkOption {
81 | type = lib.types.listOf (lib.types.submodule serverOptions);
82 | default = [];
83 | description = ''
84 | Configuration written to {file}`$PROJECT_ROOT/garnix.yaml`.
85 | See and for documentation.
86 | '';
87 | example = lib.literalMD ''
88 | [
89 | {
90 | configuration = "example";
91 | branch = "main";
92 | }
93 | {configuration = "test";}
94 | ];
95 | '';
96 | };
97 | };
98 |
99 | config = mkIf (cfg.enable) {
100 | project.file."garnix.yaml".text = lib.pm.generators.toYAML {} {
101 | builds = lib.mapAttrsToList (branch: value:
102 | if branch == "*"
103 | then value
104 | else value // {inherit branch;})
105 | cfg.builds;
106 | servers =
107 | map ({
108 | configuration,
109 | branch,
110 | }: {
111 | inherit configuration;
112 | deployment =
113 | if branch == null
114 | then {type = "on-pull-request";}
115 | else {
116 | inherit branch;
117 | type = "on-branch";
118 | };
119 | })
120 | cfg.servers;
121 | };
122 |
123 | ## Can’t build un-sandboxed derivations on Garnix (see garnix-io/issues#33)
124 | services.garnix.builds."*" = {
125 | exclude = flaky.lib.forGarnixSystems supportedSystems (sys:
126 | map
127 | (name: "checks.${sys}.${name}")
128 | (builtins.attrNames
129 | self.projectConfigurations.${sys}.unsandboxedChecks)
130 | ++ ["devShells.${sys}.lax-checks"]);
131 | };
132 | };
133 | }
134 |
--------------------------------------------------------------------------------
/project-manager/completion.fish:
--------------------------------------------------------------------------------
1 | #!/bin/env fish
2 | ##################################################
3 |
4 | # « project-manager » command-line fish completion
5 | #
6 | # © 2021 "Ariel AxionL"
7 | #
8 | # MIT License
9 | #
10 |
11 | ##################################################
12 |
13 | ### Functions
14 | function __project_manager_generations --description "Get all generations"
15 | for i in (project-manager generations)
16 | set -l split (string split " " $i)
17 | set -l gen_id $split[5]
18 | set -l gen_datetime $split[1..2]
19 | set -l gen_hash (string match -r '\w{32}' $i)
20 | echo $gen_id\t$gen_datetime $gen_hash
21 | end
22 | end
23 |
24 |
25 | ### SubCommands
26 | complete -c project-manager -n "__fish_use_subcommand" -f -a "help" -d "Print project-manager help"
27 | complete -c project-manager -n "__fish_use_subcommand" -f -a "edit" -d "Open the project configuration in $EDITOR"
28 | complete -c project-manager -n "__fish_use_subcommand" -f -a "build" -d "Build configuration into result directory"
29 | complete -c project-manager -n "__fish_use_subcommand" -f -a "switch" -d "Build and activate configuration"
30 | complete -c project-manager -n "__fish_use_subcommand" -f -a "generations" -d "List all project environment generations"
31 | complete -c project-manager -n "__fish_use_subcommand" -f -a "packages" -d "List all packages installed in project-manager-path"
32 | complete -c project-manager -n "__fish_use_subcommand" -f -a "fmt" -d "Run the configured formatter. This is usually much faster than running ‘nix fmt’ because it avoids evaluating the flake."
33 | complete -c project-manager -n "__fish_use_subcommand" -f -a "news" -d "Show news entries in a pager"
34 | complete -c project-manager -n "__fish_use_subcommand" -f -a "uninstall" -d "Remove Project Manager"
35 |
36 | complete -c project-manager -n "__fish_use_subcommand" -x -a "remove-generations" -d "Remove indicated generations"
37 | complete -c project-manager -n "__fish_seen_subcommand_from remove-generations" -f -ka '(__project_manager_generations)'
38 |
39 | complete -c project-manager -n "__fish_use_subcommand" -x -a "expire-generations" -d "Remove generations older than TIMESTAMP"
40 |
41 | ### Options
42 | complete -c project-manager -F -s f -l "file" -d "The project configuration file"
43 | complete -c project-manager -F -s I -d "Add a path to the Nix expression search path"
44 | complete -c project-manager -F -l "flake" -d "Use Project Manager configuration at specified flake-uri"
45 | complete -c project-manager -f -s v -l "verbose" -d "Verbose output"
46 | complete -c project-manager -f -s n -l "dry-run" -d "Do a dry run, only prints what actions would be taken"
47 | complete -c project-manager -f -s h -l "help" -d "Print this help"
48 | complete -c project-manager -f -s h -l "version" -d "Print the Project Manager version"
49 |
50 | complete -c project-manager -x -l "arg" -d "Override inputs passed to project-manager.nix"
51 | complete -c project-manager -x -l "argstr" -d "Like --arg but the value is a string"
52 | complete -c project-manager -x -l "cores" -d "Threads per job (e.g. -j argument to make)"
53 | complete -c project-manager -x -l "debug"
54 | complete -c project-manager -x -l "impure"
55 | complete -c project-manager -f -l "keep-failed" -d "Keep temporary directory used by failed builds"
56 | complete -c project-manager -f -l "keep-going" -d "Keep going in case of failed builds"
57 | complete -c project-manager -x -s j -l "max-jobs" -d "Max number of build jobs in parallel"
58 | complete -c project-manager -x -l "option" -d "Set Nix configuration option"
59 | complete -c project-manager -x -l "builders" -d "Remote builders"
60 | complete -c project-manager -f -s L -l "print-build-logs" -d "Print full build logs on standard error"
61 | complete -c project-manager -f -l "show-trace" -d "Print stack trace of evaluation errors"
62 | complete -c project-manager -f -l "substitute"
63 | complete -c project-manager -f -l "no-substitute"
64 | complete -c project-manager -f -l "no-out-link"
65 | complete -c project-manager -f -l "update-input"
66 | complete -c project-manager -f -l "override-input"
67 | complete -c project-manager -f -l "experimental-features"
68 | complete -c project-manager -f -l "extra-experimental-features"
69 | complete -c project-manager -f -l "refresh" -d "Consider all previously downloaded files out-of-date"
70 |
--------------------------------------------------------------------------------
/modules/lib/dag.nix:
--------------------------------------------------------------------------------
1 | # A generalization of Nixpkgs's `strings-with-deps.nix`.
2 | #
3 | # The main differences from the Nixpkgs version are
4 | #
5 | # - not specific to strings, i.e., any payload is OK,
6 | #
7 | # - the addition of the function `entryBefore` indicating a "wanted
8 | # by" relationship.
9 | {lib}: let
10 | inherit (lib) all filterAttrs head pm mapAttrs length tail toposort;
11 | in {
12 | empty = {};
13 |
14 | isEntry = e: e ? data && e ? after && e ? before;
15 | isDag = dag:
16 | builtins.isAttrs dag && all pm.dag.isEntry (builtins.attrValues dag);
17 |
18 | # Takes an attribute set containing entries built by entryAnywhere,
19 | # entryAfter, and entryBefore to a topologically sorted list of
20 | # entries.
21 | #
22 | # Internally this function uses the `toposort` function in
23 | # `` and its value is accordingly.
24 | #
25 | # Specifically, the result on success is
26 | #
27 | # { result = [ { name = ?; data = ?; } … ] }
28 | #
29 | # For example
30 | #
31 | # nix-repl> topoSort {
32 | # a = entryAnywhere "1";
33 | # b = entryAfter [ "a" "c" ] "2";
34 | # c = entryBefore [ "d" ] "3";
35 | # d = entryBefore [ "e" ] "4";
36 | # e = entryAnywhere "5";
37 | # } == {
38 | # result = [
39 | # { data = "1"; name = "a"; }
40 | # { data = "3"; name = "c"; }
41 | # { data = "2"; name = "b"; }
42 | # { data = "4"; name = "d"; }
43 | # { data = "5"; name = "e"; }
44 | # ];
45 | # }
46 | # true
47 | #
48 | # And the result on error is
49 | #
50 | # {
51 | # cycle = [ { after = ?; name = ?; data = ? } … ];
52 | # loops = [ { after = ?; name = ?; data = ? } … ];
53 | # }
54 | #
55 | # For example
56 | #
57 | # nix-repl> topoSort {
58 | # a = entryAnywhere "1";
59 | # b = entryAfter [ "a" "c" ] "2";
60 | # c = entryAfter [ "d" ] "3";
61 | # d = entryAfter [ "b" ] "4";
62 | # e = entryAnywhere "5";
63 | # } == {
64 | # cycle = [
65 | # { after = [ "a" "c" ]; data = "2"; name = "b"; }
66 | # { after = [ "d" ]; data = "3"; name = "c"; }
67 | # { after = [ "b" ]; data = "4"; name = "d"; }
68 | # ];
69 | # loops = [
70 | # { after = [ "a" "c" ]; data = "2"; name = "b"; }
71 | # ];
72 | # }
73 | # true
74 | topoSort = dag: let
75 | dagBefore = dag: name:
76 | builtins.attrNames
77 | (filterAttrs (n: v: builtins.elem name v.before) dag);
78 | normalizedDag =
79 | mapAttrs (n: v: {
80 | name = n;
81 | data = v.data;
82 | after = v.after ++ dagBefore dag n;
83 | })
84 | dag;
85 | before = a: b: builtins.elem a.name b.after;
86 | sorted = toposort before (builtins.attrValues normalizedDag);
87 | in
88 | if sorted ? result
89 | then {
90 | result = map (v: {inherit (v) name data;}) sorted.result;
91 | }
92 | else sorted;
93 |
94 | # Applies a function to each element of the given DAG.
95 | map = f: mapAttrs (n: v: v // {data = f n v.data;});
96 |
97 | entryBetween = before: after: data: {inherit data before after;};
98 |
99 | # Create a DAG entry with no particular dependency information.
100 | entryAnywhere = pm.dag.entryBetween [] [];
101 |
102 | entryAfter = pm.dag.entryBetween [];
103 | entryBefore = before: pm.dag.entryBetween before [];
104 |
105 | # Given a list of entries, this function places them in order within the DAG.
106 | # Each entry is labeled "${tag}-${entry index}" and other DAG entries can be
107 | # added with 'before' or 'after' referring these indexed entries.
108 | #
109 | # The entries as a whole can be given a relation to other DAG nodes. All
110 | # generated nodes are then placed before or after those dependencies.
111 | entriesBetween = tag: let
112 | go = i: before: after: entries: let
113 | name = "${tag}-${toString i}";
114 | i' = i + 1;
115 | in
116 | if entries == []
117 | then pm.dag.empty
118 | else if length entries == 1
119 | then {
120 | "${name}" = pm.dag.entryBetween before after (head entries);
121 | }
122 | else
123 | {
124 | "${name}" = pm.dag.entryAfter after (head entries);
125 | }
126 | // go (i + 1) before [name] (tail entries);
127 | in
128 | go 0;
129 |
130 | entriesAnywhere = tag: pm.dag.entriesBetween tag [] [];
131 | entriesAfter = tag: pm.dag.entriesBetween tag [];
132 | entriesBefore = tag: before: pm.dag.entriesBetween tag before [];
133 | }
134 |
--------------------------------------------------------------------------------
/templates/default/.config/project/default.nix:
--------------------------------------------------------------------------------
1 | ### Welcome to your project configuration!
2 | ###
3 | ### This is a Nix module (https://nixos.wiki/wiki/NixOS_modules) that is
4 | ### intended to produce both flake outputs and generated files in your project.
5 | ###
6 | ### All available options for this file are listed in
7 | ### https://sellout.github.io/project-manager/options.xhtml
8 | {
9 | lib,
10 | pkgs,
11 | ...
12 | }: {
13 | project = {
14 | name = "{{project.name}}";
15 | summary = "{{project.summary}}";
16 | ## NB: This follows the same structure as nixpkgs maintainers, so it can
17 | ## contain references to that, like
18 | ## `authors = [lib.maintainers.sellout];`.
19 | authors = ["{{project.author}}"];
20 | license = "{{project.license}}";
21 | ## The `project.devPackages` option allows you to install Nix packages into
22 | ## your environment.
23 | devPackages = [
24 | # # Adds the 'hello' command to your environment. It prints a friendly
25 | # # "Hello, world!" when run.
26 | # pkgs.hello
27 |
28 | # # It is sometimes useful to fine-tune packages, for example, by applying
29 | # # overrides. You can do that directly here, just don't forget the
30 | # # parentheses. Maybe you want to install Nerd Fonts with a limited number of
31 | # # fonts?
32 | # (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; })
33 |
34 | # # You can also create simple shell scripts directly inside your
35 | # # configuration. For example, this adds a command 'my-hello' to your
36 | # # environment:
37 | # (pkgs.writeShellScriptBin "my-hello" ''
38 | # echo "Hello, \${config.project.username}!"
39 | # '')
40 |
41 | ## language servers
42 | pkgs.nil # Nix
43 | pkgs.nodePackages.bash-language-server
44 | ];
45 |
46 | # Project Manager is pretty good at managing dotfiles. Many are handled
47 | # through modules for specific programs or services, but you can also create
48 | # them directly. The primary way to manage plain files is through
49 | # 'project.file'.
50 | file = {
51 | # # Building this configuration will create a copy of 'dotfiles/screenrc' in
52 | # # the Nix store. Activating the configuration will then make '~/.screenrc' a
53 | # # symlink to the Nix store copy.
54 | # ".screenrc".source = dotfiles/screenrc;
55 |
56 | # # You can also set the file content immediately.
57 | # ".gradle/gradle.properties".text = ''
58 | # org.gradle.console=verbose
59 | # org.gradle.daemon.idletimeout=3600000
60 | # '';
61 | };
62 |
63 | # You can also manage environment variables but you will have to manually
64 | # source
65 | #
66 | # ~/.nix-profile/etc/profile.d/pm-session-vars.sh
67 | #
68 | # or
69 | #
70 | # /etc/profiles/per-user/$USER/etc/profile.d/pm-session-vars.sh
71 | #
72 | # if you don't want to manage your shell through Project Manager.
73 | sessionVariables = {
74 | # EDITOR = "emacs";
75 | };
76 |
77 | # This value determines the Project Manager release that your configuration
78 | # is compatible with. This helps avoid breakage when a new Project Manager
79 | # release introduces backwards incompatible changes.
80 | #
81 | # You should not change this value, even if you update Project Manager. If
82 | # you do want to update the value, then make sure to first check the Project
83 | # Manager release notes.
84 | stateVersion = "0"; # Please read the comment before changing.
85 | };
86 |
87 | programs = {
88 | ## Direnv is a common tool for setting up a project-specific environment
89 | ## whenever you enter a directory in your project. Project Manager can
90 | ## create the .envrc file for you.
91 | direnv = {
92 | enable = true;
93 | envrc = ''
94 | use flake
95 | '';
96 | };
97 |
98 | ## FIXME: This should be enabled automatically if the configuration
99 | ## determines it’s in a Git repository. Correspondingly, Mercurial or
100 | ## other VCSes should be enabled automatically if the configuration
101 | ## determines it’s in one of their repositories.
102 | git.enable = true;
103 |
104 | ## Let Project Manager install and manage itself.
105 | project-manager.enable = true;
106 |
107 | ## Treefmt is a meta-formatter – it runs other formatters for many languages
108 | ## across your repo. Enabling it makes it the formatter that Project Manager
109 | ## will set in your flake.
110 | treefmt = {
111 | enable = true;
112 | ## Turn on a Nix formatter. See
113 | ## https://github.com/numtide/treefmt-nix#supported-programs for a list of
114 | ## other supported formatters.
115 | programs.alejandra.enable = true;
116 | };
117 | };
118 |
119 | services = {
120 | github.enable = true;
121 | renovate.enable = true;
122 | };
123 | }
124 |
--------------------------------------------------------------------------------
/modules/services/github.nix:
--------------------------------------------------------------------------------
1 | {
2 | config,
3 | lib,
4 | pkgs,
5 | ...
6 | }: let
7 | cfg = config.services.github;
8 |
9 | projectDirectory = config.project.projectDirectory;
10 |
11 | fileType =
12 | (import ../lib/file-type.nix {
13 | inherit projectDirectory lib pkgs;
14 | commit-by-default = config.project.commit-by-default;
15 | })
16 | .fileType;
17 | in {
18 | meta.maintainers = [lib.maintainers.sellout];
19 |
20 | options.services.github = {
21 | enable = lib.mkEnableOption "GitHub";
22 |
23 | settings = lib.mkOption {
24 | description = ''
25 | Declarative GitHub settings, as provided by [Probot’s Settings
26 | app](https://probot.github.io/apps/settings/).
27 | '';
28 | type = lib.types.nullOr (lib.types.submodule {
29 | freeformType = lib.types.attrs;
30 | options = {
31 | branches = lib.mkOption {
32 | type = lib.types.attrsOf (lib.types.attrsOf lib.types.anything);
33 | default = {};
34 | description = ''
35 | Labels for issues & PRs.
36 | '';
37 | };
38 | labels = lib.mkOption {
39 | type = lib.types.attrsOf (lib.types.attrsOf lib.types.anything);
40 | default = {};
41 | description = ''
42 | Branch-specific settings.
43 | '';
44 | };
45 | repository = lib.mkOption {
46 | description = ''
47 | Settings that affect the entire repository.
48 | '';
49 | type = lib.types.submodule {
50 | freeformType = lib.types.anything;
51 | options.topics = lib.mkOption {
52 | default = [];
53 | type = lib.types.listOf lib.types.str;
54 | description = ''
55 | A list of GitHub “topics”.
56 | '';
57 | };
58 | };
59 | };
60 | };
61 | });
62 | default = null;
63 | };
64 |
65 | workflow = lib.mkOption {
66 | description = ''
67 | Attribute set of GitHub workflows.
68 | '';
69 | default = {};
70 | type =
71 | fileType
72 | "services.github.workflow"
73 | ""
74 | (projectDirectory + "/.github/workflows");
75 | };
76 | };
77 |
78 | config = lib.mkIf cfg.enable (let
79 | ## Have users provide a list of topics, then convert it into the comma-
80 | ## separated string expected by settings.yml.
81 | concatTopics = settings:
82 | if settings ? repository && settings.repository ? topics
83 | then
84 | settings
85 | // {
86 | repository =
87 | settings.repository
88 | // {topics = lib.concatStringsSep ", " settings.repository.topics;};
89 | }
90 | else settings;
91 |
92 | ## Coverts a structure like `{foo = {x = y;}; bar = {x = z;};}` to one like
93 | ## `[{name = "foo"; x = y;} {name = "bar"; x = z;}]`.
94 | attrsToNamedList = lib.mapAttrsToList (k: v:
95 | if v ? name
96 | then v
97 | else v // {name = k;});
98 |
99 | ## settings.yaml expects a list of branches, but that doesn’t have the most
100 | ## useful merge semantics. So we have an attrSet of branches, with the key
101 | ## being used as the name, if the name isn’t otherwise set.
102 | restructureBranches = settings:
103 | if settings ? branches
104 | then settings // {branches = attrsToNamedList settings.branches;}
105 | else settings;
106 |
107 | restructureLabels = settings:
108 | if settings ? labels
109 | then settings // {labels = attrsToNamedList settings.labels;}
110 | else settings;
111 |
112 | generatedAndCommitted =
113 | lib.filterAttrs (n: v: v.persistence == "repository") config.project.file;
114 | in {
115 | programs.git = {
116 | ## TODO: This probably isn’t right – just because the user users git
117 | ## doesn’t mean they want it managed by Nix – but we _do_ want to
118 | ## manage .gitattributes here. Maybe we can just cause the text to
119 | ## be appended?
120 | enable = true;
121 |
122 | attributes =
123 | lib.mapAttrs'
124 | (_: v: {
125 | name = "/" + v.target;
126 | value.linguist-generated = true;
127 | })
128 | generatedAndCommitted;
129 | };
130 |
131 | project.file =
132 | {
133 | ## This should always have at least one `linguist-generated` entry (for
134 | ## .gitattributes itself), so we always commit.
135 | ".gitattributes".commit-by-default = true;
136 | ".github/settings.yml" =
137 | lib.mkIf (cfg.settings != null)
138 | {
139 | text =
140 | lib.pm.generators.toYAML {}
141 | (restructureBranches (restructureLabels (concatTopics cfg.settings)));
142 | };
143 | }
144 | // cfg.workflow;
145 | });
146 | }
147 |
--------------------------------------------------------------------------------
/modules/lib/types/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | lib,
3 | gvariant ? import ../gvariant.nix {inherit lib;},
4 | }: let
5 | inherit
6 | (lib)
7 | all
8 | concatMap
9 | foldl'
10 | getFiles
11 | getValues
12 | head
13 | isFunction
14 | literalExpression
15 | mergeAttrs
16 | mergeDefaultOption
17 | mergeOneOption
18 | mkOption
19 | mkOptionType
20 | showFiles
21 | showOption
22 | types
23 | ;
24 |
25 | typesDag = import ./dag.nix {inherit lib;};
26 |
27 | # Needed since the type is called gvariant and its merge attribute
28 | # must refer back to the type.
29 | gvar = gvariant;
30 | in rec {
31 | inherit (typesDag) dagOf;
32 |
33 | ## A list of “[globs](https://en.wikipedia.org/wiki/Glob_(programming))”. It
34 | ## takes a predicate that returns `true` if a value of this type matches every
35 | ## possible value (a “universal match”, e.g., a list like `["*"]` for standard
36 | ## Unix-like globs). Universal matches are handled specially. They replace any
37 | ## definition that has a lower priority than them, but are replaced by all
38 | ## definitions that have a higher priority than them.
39 | globList = isUniversal:
40 | lib.types.listOf lib.types.str
41 | // {
42 | merge = _loc: defs:
43 | lib.foldr (def: prev:
44 | if isUniversal def || isUniversal prev
45 | then def
46 | else def ++ prev)
47 | []
48 | (lib.getValues defs);
49 | };
50 |
51 | selectorFunction = mkOptionType {
52 | name = "selectorFunction";
53 | description =
54 | "Function that takes an attribute set and returns a list"
55 | + " containing a selection of the values of the input set";
56 | check = isFunction;
57 | merge = _loc: defs: as: concatMap (select: select as) (getValues defs);
58 | };
59 |
60 | overlayFunction = mkOptionType {
61 | name = "overlayFunction";
62 | description =
63 | "An overlay function, takes self and super and returns"
64 | + " an attribute set overriding the desired attributes.";
65 | check = isFunction;
66 | merge = _loc: defs: self: super:
67 | foldl' (res: def: mergeAttrs res (def.value self super)) {} defs;
68 | };
69 |
70 | fontType = types.submodule {
71 | options = {
72 | package = mkOption {
73 | type = types.nullOr types.package;
74 | default = null;
75 | example = literalExpression "pkgs.dejavu_fonts";
76 | description = ''
77 | Package providing the font. This package will be installed
78 | to your profile. If `null` then the font
79 | is assumed to already be available in your profile.
80 | '';
81 | };
82 |
83 | name = mkOption {
84 | type = types.str;
85 | example = "DejaVu Sans";
86 | description = ''
87 | The family name of the font within the package.
88 | '';
89 | };
90 |
91 | size = mkOption {
92 | type = types.nullOr types.number;
93 | default = null;
94 | example = "8";
95 | description = ''
96 | The size of the font.
97 | '';
98 | };
99 | };
100 | };
101 |
102 | gvariant = mkOptionType rec {
103 | name = "gvariant";
104 | description = "GVariant value";
105 | check = v: gvar.mkValue v != null;
106 | merge = loc: defs: let
107 | vdefs = map (d:
108 | d
109 | // {
110 | value =
111 | if gvar.isGVariant d.value
112 | then d.value
113 | else gvar.mkValue d.value;
114 | })
115 | defs;
116 | vals = map (d: d.value) vdefs;
117 | defTypes = map (x: x.type) vals;
118 | sameOrNull = x: y:
119 | if x == y
120 | then y
121 | else null;
122 | # A bit naive to just check the first entry…
123 | sharedDefType = foldl' sameOrNull (head defTypes) defTypes;
124 | allChecked = all (x: check x) vals;
125 | in
126 | if sharedDefType == null
127 | then
128 | throw ("Cannot merge definitions of `${showOption loc}' with"
129 | + " mismatched GVariant types given in"
130 | + " ${showFiles (getFiles defs)}.")
131 | else if gvar.isArray sharedDefType && allChecked
132 | then
133 | gvar.mkValue ((types.listOf gvariant).merge loc
134 | (map (d: d // {value = d.value.value;}) vdefs))
135 | // {
136 | type = sharedDefType;
137 | }
138 | else if gvar.isTuple sharedDefType && allChecked
139 | then mergeOneOption loc defs
140 | else if gvar.isMaybe sharedDefType && allChecked
141 | then mergeOneOption loc defs
142 | else if gvar.isDictionaryEntry sharedDefType && allChecked
143 | then mergeOneOption loc defs
144 | else if gvar.type.variant == sharedDefType && allChecked
145 | then mergeOneOption loc defs
146 | else if gvar.type.string == sharedDefType && allChecked
147 | then types.str.merge loc defs
148 | else if gvar.type.double == sharedDefType && allChecked
149 | then types.float.merge loc defs
150 | else mergeDefaultOption loc defs;
151 | };
152 | }
153 |
--------------------------------------------------------------------------------
/modules/lib/gvariant.nix:
--------------------------------------------------------------------------------
1 | # A partial and basic implementation of GVariant formatted strings.
2 | #
3 | # Note, this API is not considered fully stable and it might therefore
4 | # change in backwards incompatible ways without prior notice.
5 | {lib}: let
6 | inherit
7 | (lib)
8 | concatMapStringsSep
9 | concatStrings
10 | escape
11 | hasPrefix
12 | head
13 | replaceStrings
14 | ;
15 |
16 | mkPrimitive = t: v: {
17 | _type = "gvariant";
18 | type = t;
19 | value = v;
20 | __toString = self: "@${self.type} ${toString self.value}";
21 | };
22 |
23 | type = {
24 | arrayOf = t: "a${t}";
25 | maybeOf = t: "m${t}";
26 | tupleOf = ts: "(${concatStrings ts})";
27 | dictionaryEntryOf = ts: "{${concatStrings ts}}";
28 | string = "s";
29 | boolean = "b";
30 | uchar = "y";
31 | int16 = "n";
32 | uint16 = "q";
33 | int32 = "i";
34 | uint32 = "u";
35 | int64 = "x";
36 | uint64 = "t";
37 | double = "d";
38 | variant = "v";
39 | };
40 |
41 | # Returns the GVariant type of a given Nix value. If no type can be
42 | # found for the value then the empty string is returned.
43 | typeOf = v:
44 | with type;
45 | if builtins.isBool v
46 | then boolean
47 | else if builtins.isInt v
48 | then int32
49 | else if builtins.isFloat v
50 | then double
51 | else if builtins.isString v
52 | then string
53 | else if builtins.isList v
54 | then let
55 | elemType = elemTypeOf v;
56 | in
57 | if elemType == ""
58 | then ""
59 | else arrayOf elemType
60 | else if builtins.isAttrs v && v ? type
61 | then v.type
62 | else "";
63 |
64 | elemTypeOf = vs:
65 | if builtins.isList vs
66 | then
67 | if vs == []
68 | then ""
69 | else typeOf (head vs)
70 | else "";
71 |
72 | mkMaybe = elemType: elem:
73 | mkPrimitive (type.maybeOf elemType) elem
74 | // {
75 | __toString = self:
76 | if self.value == null
77 | then "@${self.type} nothing"
78 | else "just ${toString self.value}";
79 | };
80 | in rec {
81 | inherit type typeOf;
82 |
83 | isGVariant = v: v._type or "" == "gvariant";
84 |
85 | isArray = hasPrefix "a";
86 | isDictionaryEntry = hasPrefix "{";
87 | isMaybe = hasPrefix "m";
88 | isTuple = hasPrefix "(";
89 |
90 | # Returns the GVariant value that most closely matches the given Nix
91 | # value. If no GVariant value can be found then `null` is returned.
92 |
93 | mkValue = v:
94 | if builtins.isBool v
95 | then mkBoolean v
96 | else if builtins.isInt v
97 | then mkInt32 v
98 | else if builtins.isFloat v
99 | then mkDouble v
100 | else if builtins.isString v
101 | then mkString v
102 | else if builtins.isList v
103 | then
104 | if v == []
105 | then mkArray type.string []
106 | else mkArray (elemTypeOf v) v
107 | else if builtins.isAttrs v && (v._type or "") == "gvariant"
108 | then v
109 | else null;
110 |
111 | mkArray = elemType: elems:
112 | mkPrimitive (type.arrayOf elemType) (map mkValue elems)
113 | // {
114 | __toString = self: "@${self.type} [${concatMapStringsSep "," toString self.value}]";
115 | };
116 |
117 | mkEmptyArray = elemType: mkArray elemType [];
118 |
119 | mkVariant = elem: let
120 | gvarElem = mkValue elem;
121 | in
122 | mkPrimitive type.variant gvarElem
123 | // {
124 | __toString = self: "@${self.type} <${toString self.value}>";
125 | };
126 |
127 | mkDictionaryEntry = elems: let
128 | gvarElems = map mkValue elems;
129 | dictionaryType = type.dictionaryEntryOf (map (e: e.type) gvarElems);
130 | in
131 | mkPrimitive dictionaryType gvarElems
132 | // {
133 | __toString = self: "@${self.type} {${concatMapStringsSep "," toString self.value}}";
134 | };
135 |
136 | mkNothing = elemType: mkMaybe elemType null;
137 |
138 | mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;
139 |
140 | mkTuple = elems: let
141 | gvarElems = map mkValue elems;
142 | tupleType = type.tupleOf (map (e: e.type) gvarElems);
143 | in
144 | mkPrimitive tupleType gvarElems
145 | // {
146 | __toString = self: "@${self.type} (${concatMapStringsSep "," toString self.value})";
147 | };
148 |
149 | mkBoolean = v:
150 | mkPrimitive type.boolean v
151 | // {
152 | __toString = self:
153 | if self.value
154 | then "true"
155 | else "false";
156 | };
157 |
158 | mkString = v: let
159 | sanitize = s: replaceStrings ["\n"] ["\\n"] (escape ["'" "\\"] s);
160 | in
161 | mkPrimitive type.string v
162 | // {
163 | __toString = self: "'${sanitize self.value}'";
164 | };
165 |
166 | mkObjectpath = v:
167 | mkPrimitive type.string v
168 | // {
169 | __toString = self: "objectpath '${escape ["'"] self.value}'";
170 | };
171 |
172 | mkUchar = mkPrimitive type.uchar;
173 |
174 | mkInt16 = mkPrimitive type.int16;
175 |
176 | mkUint16 = mkPrimitive type.uint16;
177 |
178 | mkInt32 = v:
179 | mkPrimitive type.int32 v
180 | // {
181 | __toString = self: toString self.value;
182 | };
183 |
184 | mkUint32 = mkPrimitive type.uint32;
185 |
186 | mkInt64 = mkPrimitive type.int64;
187 |
188 | mkUint64 = mkPrimitive type.uint64;
189 |
190 | mkDouble = v:
191 | mkPrimitive type.double v
192 | // {
193 | __toString = self: toString self.value;
194 | };
195 | }
196 |
--------------------------------------------------------------------------------
/testing/flake.nix:
--------------------------------------------------------------------------------
1 | # This is an internal Nix Flake intended for use when running tests.
2 | #
3 | # You can build all tests or specific tests by running
4 | #
5 | # nix build --reference-lock-file flake.lock ./testing#test-all
6 | # nix build --reference-lock-file flake.lock ./testing#test-alacritty-empty-settings
7 | #
8 | # in the Home Manager project root directory.
9 | #
10 | # Similarly for integration tests
11 | #
12 | # nix build --reference-lock-file flake.lock ./testing#integration-test-all
13 | # nix build --reference-lock-file flake.lock ./testing#integration-test-standalone-standard-basics
14 | {
15 | description = "Tests of Home Manager for Nix";
16 |
17 | outputs = {
18 | flaky,
19 | nixpkgs,
20 | self,
21 | systems,
22 | }: let
23 | supportedSystems = import systems;
24 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
25 | forCI = nixpkgs.lib.genAttrs supportedSystems;
26 |
27 | testChunks = system: let
28 | pkgs = nixpkgs.legacyPackages.${system};
29 | inherit (pkgs) lib;
30 |
31 | # Create chunked test packages for better CI parallelization
32 | tests = import ./. {
33 | inherit pkgs;
34 | pmPkgs = pkgs.appendOverlays [flaky.overlays.default];
35 | # Disable big tests since this is only used for CI
36 | enableBig = false;
37 | };
38 | allTests = lib.attrNames tests.build;
39 | # Remove 'all' from the test list as it's a meta-package
40 | filteredTests = lib.filter (name: name != "all") allTests;
41 | # NOTE: Just a starting value, we can tweak this to find a good value.
42 | targetTestsPerChunk = 50;
43 | numChunks = lib.max 1 (
44 | builtins.ceil ((builtins.length filteredTests) / (targetTestsPerChunk * 1.0))
45 | );
46 | chunkSize = builtins.ceil ((builtins.length filteredTests) / (numChunks * 1.0));
47 |
48 | makeChunk = chunkNum: testList: let
49 | start = (chunkNum - 1) * chunkSize;
50 | end = lib.min (start + chunkSize) (builtins.length testList);
51 | chunkTests = lib.sublist start (end - start) testList;
52 | chunkAttrs = lib.genAttrs chunkTests (name: tests.build.${name});
53 | in
54 | pkgs.symlinkJoin {
55 | name = "test-chunk-${toString chunkNum}";
56 | paths = lib.attrValues chunkAttrs;
57 | passthru.tests = chunkTests;
58 | };
59 | in
60 | lib.listToAttrs (
61 | lib.genList (
62 | i: lib.nameValuePair "test-chunk-${toString (i + 1)}" (makeChunk (i + 1) filteredTests)
63 | )
64 | numChunks
65 | );
66 |
67 | integrationTests = system: let
68 | pkgs = nixpkgs.legacyPackages.${system};
69 | inherit (pkgs) lib;
70 | in
71 | lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux (
72 | # let
73 | # tests = import ./integration { inherit pkgs lib; };
74 | # renameTestPkg = n: v: lib.nameValuePair "integration-${n}" v;
75 | # in
76 | # lib.mapAttrs' renameTestPkg (lib.removeAttrs tests [ "all" ])
77 | {}
78 | );
79 |
80 | # Test group definitions
81 | buildTests = system: let
82 | pkgs = nixpkgs.legacyPackages.${system};
83 | tests = import ./. {
84 | inherit pkgs;
85 | pmPkgs = pkgs.appendOverlays [flaky.overlays.default];
86 | };
87 | renameTestPkg = n: nixpkgs.lib.nameValuePair "test-${n}";
88 | in
89 | nixpkgs.lib.mapAttrs' renameTestPkg tests.build;
90 |
91 | buildTestsNoBig = system: let
92 | pkgs = nixpkgs.legacyPackages.${system};
93 | tests = import ./. {
94 | inherit pkgs;
95 | pmPkgs = pkgs.appendOverlays [flaky.overlays.default];
96 | enableBig = false;
97 | };
98 | in {
99 | test-all-no-big = tests.build.all;
100 | };
101 |
102 | buildTestsNoBigIfd = system: let
103 | pkgs = nixpkgs.legacyPackages.${system};
104 | tests = import ./. {
105 | inherit pkgs;
106 | pmPkgs = pkgs.appendOverlays [flaky.overlays.default];
107 | enableBig = false;
108 | };
109 | in {
110 | test-all-no-big-ifd = tests.build.all;
111 | };
112 |
113 | integrationTestPackages = system:
114 | # let
115 | # pkgs = nixpkgs.legacyPackages.${system};
116 | # inherit (pkgs) lib;
117 | # tests = import ./integration { inherit pkgs lib; };
118 | # renameTestPkg = n: lib.nameValuePair "integration-test-${n}";
119 | # in
120 | # lib.mapAttrs' renameTestPkg tests;
121 | {};
122 | in {
123 | # TODO: increase buildbot testing scope
124 | buildbot = forCI (
125 | system: let
126 | allIntegrationTests = integrationTests system;
127 | workingIntegrationTests =
128 | nixpkgs.lib.filterAttrs (
129 | name: _:
130 | nixpkgs.lib.elem name [
131 | "integration-nixos-basics"
132 | "integration-nixos-legacy-profile-management"
133 | ]
134 | )
135 | allIntegrationTests;
136 | in
137 | (testChunks system) // workingIntegrationTests
138 | );
139 |
140 | devShells = forAllSystems (
141 | system: let
142 | pkgs = nixpkgs.legacyPackages.${system};
143 | tests = import ./. {
144 | inherit pkgs;
145 | pmPkgs = pkgs.appendOverlays [flaky.overlays.default];
146 | };
147 | in
148 | tests.run
149 | );
150 |
151 | packages = forAllSystems (
152 | system:
153 | (buildTests system)
154 | // (integrationTestPackages system)
155 | // (buildTestsNoBig system)
156 | // (buildTestsNoBigIfd system)
157 | // (testChunks system)
158 | // (integrationTests system)
159 | );
160 | };
161 |
162 | inputs = {
163 | flaky.url = "github:sellout/flaky";
164 |
165 | nixpkgs.follows = "flaky/nixpkgs";
166 | systems.follows = "flaky/systems";
167 | };
168 | }
169 |
--------------------------------------------------------------------------------
/modules/services/nix-ci.nix:
--------------------------------------------------------------------------------
1 | ## TODO: Ideally this would live in some Nix CI flake, produced via the original
2 | ## codec, which I would imagine is defined by Autodocodec somewhere, but
3 | ## for now, it’s manualy.
4 | {
5 | config,
6 | lib,
7 | self,
8 | ...
9 | }: let
10 | cfg = config.services.nix-ci;
11 | in {
12 | options.services.nix-ci = let
13 | t = lib.types;
14 | in {
15 | ## FIXME: Try to recover my deleted version of this from Time Machine
16 | enable = lib.mkOption {
17 | type = t.nullOr t.bool;
18 | default = null;
19 | example = false;
20 | description = ''
21 | [Nix CI](https://nix-ci.com/) is enabled via
22 | [GitHub](https://github.com/). This option controls the (optional)
23 | nix-ci.nix file that it uses for configuration. The default (`null`) is
24 | to not generate a file at all. If explicitly set to `false`, it will
25 | generate the file, but set `enable = false` in the file, which
26 | _disables_ Nix CI, even if it’s enabled via GitHub. This is particularly
27 | useful if you have Nix CI enabled for your entire org, but want to
28 | disable it on some repositories.
29 | '';
30 | };
31 |
32 | systems = lib.mkOption {
33 | type = t.nullOr (t.listOf t.str);
34 | default = null;
35 | description = ''
36 | Only build for these systems. By default these are computed based on
37 | which workers are available for the repository.
38 | '';
39 | };
40 |
41 | onlyBuild = lib.mkOption {
42 | type = t.nullOr (t.listOf t.str);
43 | default = null;
44 | description = ''
45 | Only build these attributes.
46 | '';
47 | };
48 |
49 | doNotBuild = lib.mkOption {
50 | type = t.listOf t.str;
51 | default = [];
52 | description = ''
53 | Exclude these attributes.
54 | '';
55 | };
56 |
57 | git-ssh-key = lib.mkOption {
58 | type = t.nullOr (t.attrsOf t.str);
59 | default = null;
60 | description = ''
61 | SSH key to use for [Git](https://git-scm.com/) cloning.
62 |
63 | By default no SSH key is used so Nix will fail to clone private
64 | dependencies.
65 |
66 | See [the relevant Nix CI
67 | documentation](https://nix-ci.com/documentation/git-ssh-key) for more.
68 | '';
69 | };
70 |
71 | timeout = lib.mkOption {
72 | type = t.nullOr t.numbers.nonnegative;
73 | default = null;
74 | description = ''
75 | Maximum timeout, in seconds. Note that the actual timeout may depend on
76 | the workers’ configuration.
77 | '';
78 | };
79 |
80 | cache = lib.mkOption {
81 | type = t.nullOr (t.attrsOf t.str);
82 | default = null;
83 | description = ''
84 | SSH cache configuration.
85 |
86 | In order to push to the cache as well, the repository needs to have an
87 | `SSH_CACHE_PRIVATE_KEY` secret.
88 | '';
89 | };
90 |
91 | cachix = lib.mkOption {
92 | type = t.nullOr (t.attrsOf t.str);
93 | default = null;
94 | description = ''
95 | [Cachix](https://www.cachix.org/) configuration.
96 |
97 | In order to push to the cache as well, the repository needs to have a
98 | `CACHIX_AUTH_TOKEN` or `CACHIX_SIGNING_KEY` secret.
99 |
100 | See [the relevant Nix CI
101 | documentation](https://nix-ci.com/documentation/cachix) for more.
102 | '';
103 | };
104 |
105 | allow-import-from-derivation = lib.mkOption {
106 | type = t.bool;
107 | default = true;
108 | example = false;
109 | description = ''
110 | Show with `--allow-import-from-derivation`.
111 | '';
112 | };
113 |
114 | impure = lib.mkOption {
115 | type = t.bool;
116 | default = false;
117 | example = true;
118 | description = ''
119 | Build with `--impure`.
120 | '';
121 | };
122 |
123 | build-logs = lib.mkOption {
124 | type = t.bool;
125 | default = true;
126 | example = false;
127 | description = ''
128 | Build with `--print-build-logs`.
129 | '';
130 | };
131 |
132 | fail-fast = lib.mkOption {
133 | type = t.bool;
134 | default = true;
135 | example = false;
136 | description = ''
137 | Cancel the rest of a suite once one job fails.
138 | '';
139 | };
140 |
141 | auto-retry = lib.mkOption {
142 | type = t.bool;
143 | default = true;
144 | example = false;
145 | description = ''
146 | Automatically retry every individual failed run in a suite once.
147 | '';
148 | };
149 |
150 | test = lib.mkOption {
151 | type = t.attrsOf t.attrs;
152 | default = {};
153 | description = ''
154 | Test configurations.
155 |
156 | See [the relevant Nix CI
157 | documentation](https://nix-ci.com/documentation/test) for more.
158 | '';
159 | };
160 |
161 | deploy = lib.mkOption {
162 | type = t.attrsOf t.attrs;
163 | default = {};
164 | description = ''
165 | Deploy configurations.
166 |
167 | See [the relevant Nix CI
168 | documentation](https://nix-ci.com/documentation/deploy) for more.
169 | '';
170 | };
171 | };
172 |
173 | config = lib.mkIf (cfg.enable != null) {
174 | project.file."nix-ci.nix".text =
175 | lib.pm.generators.toPretty {multiline = false;}
176 | ## TODO: Elide the other default values for a smaller/cleaner file.
177 | (lib.filterAttrs (_: v: v != null) cfg);
178 |
179 | ## TODO: This duplicates logic in garnix.nix. The common functionality
180 | ## should be extracted, and maybe located near where
181 | ## `unsandboxedChecks` or `lax-checks` are defined.
182 | services.nix-ci.doNotBuild = lib.concatMap (sys:
183 | map (name: "checks.${sys}.${name}")
184 | (builtins.attrNames self.projectConfigurations.${sys}.unsandboxedChecks)
185 | ++ ["devShells.${sys}.lax-checks"])
186 | ["x86_64-linux"];
187 | };
188 | }
189 |
--------------------------------------------------------------------------------
/testing/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | pkgs ? import {},
3 | pmPkgs,
4 | enableBig ? true,
5 | }: let
6 | lib = import ../modules/lib/stdlib-extended.nix pkgs.lib;
7 |
8 | releaseInfo = import ../release.nix;
9 |
10 | nmtSrc = fetchTarball {
11 | url = "https://git.sr.ht/~rycee/nmt/archive/v0.5.1.tar.gz";
12 | sha256 = "0qhn7nnwdwzh910ss78ga2d00v42b0lspfd7ybl61mpfgz3lmdcj";
13 | };
14 |
15 | # Recursively replace each derivation in the given attribute set with the same
16 | # derivation but with the `outPath` attribute set to the string
17 | # `"@package-name@"`. This allows the tests to refer to derivations through
18 | # their values without establishing an actual dependency on the derivation
19 | # output.
20 | scrubDerivation = name: value: let
21 | scrubbedValue = scrubDerivations value;
22 |
23 | newDrvAttrs = {
24 | buildScript = abort "no build allowed";
25 |
26 | outPath = builtins.traceVerbose "${name} - got out path" "@${lib.getName value}@";
27 |
28 | # Prevent getOutput from descending into outputs
29 | outputSpecified = true;
30 |
31 | # Allow the original package to be used in derivation inputs
32 | __spliced = {
33 | buildHost = value;
34 | hostTarget = value;
35 | };
36 | };
37 | in
38 | if lib.isAttrs value
39 | then
40 | if lib.isDerivation value
41 | then scrubbedValue // newDrvAttrs
42 | else scrubbedValue
43 | else value;
44 | scrubDerivations = attrs: lib.mapAttrs scrubDerivation attrs;
45 |
46 | # Globally unscrub a few selected packages that are used by a wide selection of tests.
47 | whitelist = let
48 | inner = self: super: {
49 | inherit
50 | (pkgs)
51 | coreutils
52 | jq
53 | desktop-file-utils
54 | diffutils
55 | findutils
56 | glibcLocales
57 | gettext
58 | gnugrep
59 | gnused
60 | shared-mime-info
61 | emptyDirectory
62 | # Needed by pretty much all tests that have anything to do with fish.
63 | babelfish
64 | fish
65 | ;
66 |
67 | xorg = super.xorg.overrideScope (self: super: {inherit (pkgs.xorg) lndir;});
68 | };
69 |
70 | outer = self: super:
71 | inner self super
72 | // {
73 | buildPackages = super.buildPackages.extend inner;
74 | };
75 | in
76 | outer;
77 |
78 | # TODO: figure out stdenv stubbing so we don't have to do this
79 | darwinScrublist = import ./darwinScrublist.nix {inherit lib scrubDerivation;};
80 |
81 | scrubbedPkgs =
82 | # TODO: fix darwin stdenv stubbing
83 | if isDarwin
84 | then let
85 | rawPkgs = lib.makeExtensible (final: pkgs);
86 | in
87 | builtins.traceVerbose "eval scrubbed darwin nixpkgs" (rawPkgs.extend darwinScrublist)
88 | else let
89 | rawScrubbedPkgs = lib.makeExtensible (final: scrubDerivations pkgs);
90 | in
91 | builtins.traceVerbose "eval scrubbed nixpkgs" (rawScrubbedPkgs.extend whitelist);
92 |
93 | modules =
94 | import ../modules/all-modules.nix {
95 | inherit lib pkgs;
96 | check = false;
97 | modules = lib.attrValues (import ../modules/modules.nix);
98 | }
99 | ++ [
100 | (
101 | {config, ...}: {
102 | _module.args = {
103 | inherit pmPkgs;
104 | # Prevent the nixpkgs module from working. We want to minimize the number
105 | # of evaluations of Nixpkgs.
106 | pkgsPath = abort "pkgs path is unavailable in tests";
107 | realPkgs = pkgs;
108 | pkgs = let
109 | overlays =
110 | config.test.stubOverlays;
111 | # ++ lib.optionals (
112 | # config.nixpkgs.overlays != null && config.nixpkgs.overlays != [ ]
113 | # ) config.nixpkgs.overlays;
114 | stubbedPkgs =
115 | if overlays == []
116 | then scrubbedPkgs
117 | else builtins.traceVerbose "eval overlayed nixpkgs" (lib.foldr (o: p: p.extend o) scrubbedPkgs overlays);
118 | in
119 | lib.mkImageMediaOverride stubbedPkgs;
120 | };
121 |
122 | ## The minimal configuration needed by a project.
123 | project = {
124 | name = lib.mkDefault "test";
125 | ## By default, tests run with the current `stateVersion`. It can be
126 | ## set in tests to ensure the behavior is consistent with previous
127 | ## states.
128 | # stateVersion = lib.mkDefault releaseInfo.version.major;
129 | };
130 |
131 | # Avoid including documentation since this will cause
132 | # unnecessary rebuilds of the tests.
133 | manual.manpages.enable = lib.mkDefault false;
134 |
135 | imports = [
136 | ./asserts.nix
137 | ./big-test.nix
138 | ./stubs.nix
139 | ];
140 |
141 | test.enableBig = enableBig;
142 | }
143 | )
144 | ];
145 |
146 | isDarwin = pkgs.stdenv.hostPlatform.isDarwin;
147 | isLinux = pkgs.stdenv.hostPlatform.isLinux;
148 | in
149 | import nmtSrc {
150 | inherit lib pkgs modules;
151 | testedAttrPath = ["project" "packages" "activation"];
152 | tests =
153 | builtins.foldl'
154 | (
155 | a: b:
156 | a
157 | // (
158 | let
159 | imported = import (lib.path.append b "tests");
160 | in
161 | if lib.isFunction imported
162 | then imported {inherit lib pkgs;}
163 | else imported
164 | )
165 | )
166 | {}
167 | (
168 | [
169 | # keep-sorted start
170 | ../modules/files
171 | # ../modules/lib/generators
172 | ../modules/lib/types
173 | # ../modules/project-environment
174 | # keep-sorted end
175 | ]
176 | ++ (
177 | lib.concatMap
178 | (
179 | dir:
180 | lib.pipe dir [
181 | builtins.readDir
182 | (lib.filterAttrs (_path: kind: kind == "directory"))
183 | (lib.mapAttrsToList (path: _kind: lib.path.append dir path))
184 | ]
185 | )
186 | [
187 | # keep-sorted start
188 | ../modules/misc
189 | ../modules/programs
190 | ../modules/services
191 | # keep-sorted end
192 | ]
193 | )
194 | );
195 | }
196 |
--------------------------------------------------------------------------------
/docs/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | pkgs,
3 | # Note, this should be "the standard library" + PM extensions.
4 | lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib,
5 | self,
6 | release,
7 | isReleaseBranch,
8 | }: let
9 | # Recursively replace each derivation in the given attribute set
10 | # with the same derivation but with the `outPath` attribute set to
11 | # the string `"\${pkgs.attribute.path}"`. This allows the
12 | # documentation to refer to derivations through their values without
13 | # establishing an actual dependency on the derivation output.
14 | #
15 | # This is not perfect, but it seems to cover a vast majority of use
16 | # cases.
17 | #
18 | # Caveat: even if the package is reached by a different means, the
19 | # path above will be shown and not e.g.
20 | # `${config.services.foo.package}`.
21 | scrubDerivations = prefixPath: attrs: let
22 | scrubDerivation = name: value: let
23 | pkgAttrName = prefixPath + "." + name;
24 | in
25 | if lib.isAttrs value
26 | then
27 | scrubDerivations pkgAttrName value
28 | // lib.optionalAttrs (lib.isDerivation value) {
29 | outPath = "\${${pkgAttrName}}";
30 | }
31 | else value;
32 | in
33 | lib.mapAttrs scrubDerivation attrs;
34 |
35 | # Make sure the used package is scrubbed to avoid actually
36 | # instantiating derivations.
37 | scrubbedPkgsModule = {
38 | imports = [
39 | {
40 | _module.args = {
41 | pkgs = lib.mkForce (scrubDerivations "pkgs" pkgs);
42 | pkgs_i686 = lib.mkForce {};
43 | };
44 | }
45 | ];
46 | };
47 |
48 | dontCheckDefinitions = {_module.check = false;};
49 |
50 | gitHubDeclaration = user: repo: urlRef: subpath: {
51 | url = "https://github.com/${user}/${repo}/blob/${urlRef}/${subpath}";
52 | name = "<${repo}/${subpath}>";
53 | };
54 |
55 | pmPath = toString ./..;
56 |
57 | buildOptionsDocs = args @ {
58 | modules,
59 | includeModuleSystemOptions ? true,
60 | ...
61 | }: let
62 | options =
63 | (lib.evalModules {
64 | inherit modules;
65 | class = "projectManager";
66 | })
67 | .options;
68 | in
69 | pkgs.buildPackages.nixosOptionsDoc ({
70 | options =
71 | if includeModuleSystemOptions
72 | then options
73 | else builtins.removeAttrs options ["_module"];
74 | transformOptions = opt:
75 | opt
76 | // {
77 | # Clean up declaration sites to not refer to the Project Manager
78 | # source tree.
79 | declarations = map (decl:
80 | if lib.hasPrefix pmPath (toString decl)
81 | then
82 | gitHubDeclaration "sellout" "project-manager"
83 | (
84 | if isReleaseBranch
85 | then "v${release}"
86 | else "master"
87 | )
88 | (lib.removePrefix "/" (lib.removePrefix pmPath (toString decl)))
89 | else if decl == "lib/modules.nix"
90 | then
91 | # TODO: handle this in a better way (may require upstream
92 | # changes to nixpkgs)
93 | gitHubDeclaration "NixOS" "nixpkgs" "master" decl
94 | else decl)
95 | opt.declarations;
96 | };
97 | }
98 | // builtins.removeAttrs args ["modules" "includeModuleSystemOptions"]);
99 |
100 | pmOptionsDocs = buildOptionsDocs {
101 | modules =
102 | import ../modules/all-modules.nix {
103 | inherit lib pkgs;
104 | check = false;
105 | modules = builtins.attrValues self.projectModules;
106 | }
107 | ++ [scrubbedPkgsModule];
108 | variablelistId = "project-manager-options";
109 | };
110 |
111 | release-config = import ../release.nix;
112 | revision = "release-${release-config.release}";
113 | # Generate the `man project-configuration.nix` package
114 | project-configuration-manual =
115 | pkgs.runCommand "project-configuration-reference-manpage" {
116 | nativeBuildInputs = [pkgs.buildPackages.installShellFiles pkgs.nixos-render-docs];
117 | allowedReferences = ["out"];
118 | } ''
119 | # Generate manpages.
120 | mkdir -p $out/share/man/man5
121 | mkdir -p $out/share/man/man1
122 | nixos-render-docs -j $NIX_BUILD_CORES options manpage \
123 | --revision ${revision} \
124 | --header ${./project-configuration-nix-header.5} \
125 | --footer ${./project-configuration-nix-footer.5} \
126 | ${pmOptionsDocs.optionsJSON}/share/doc/nixos/options.json \
127 | $out/share/man/man5/project-configuration.nix.5
128 | cp ${./project-manager.1} $out/share/man/man1/project-manager.1
129 | '';
130 | # Generate the HTML manual pages
131 | project-manager-manual = pkgs.callPackage ./project-manager-manual.nix {
132 | project-manager-options = {
133 | project-manager = pmOptionsDocs.optionsJSON;
134 | };
135 | inherit revision;
136 | };
137 | html = project-manager-manual;
138 | htmlOpenTool = pkgs.callPackage ./html-open-tool.nix {} {inherit html;};
139 | in {
140 | options = {
141 | # TODO: Use `pmOptionsDocs.optionsJSON` directly once upstream
142 | # `nixosOptionsDoc` is more customizable.
143 | json =
144 | pkgs.runCommand "options.json" {
145 | meta.description = "List of Project Manager options in JSON format";
146 | } ''
147 | mkdir -p $out/{share/doc,nix-support}
148 | cp -a ${pmOptionsDocs.optionsJSON}/share/doc/nixos $out/share/doc/project-manager
149 | substitute \
150 | ${pmOptionsDocs.optionsJSON}/nix-support/hydra-build-products \
151 | $out/nix-support/hydra-build-products \
152 | --replace \
153 | '${pmOptionsDocs.optionsJSON}/share/doc/nixos' \
154 | "$out/share/doc/project-manager"
155 | '';
156 | };
157 |
158 | manPages = project-configuration-manual;
159 |
160 | manual = {inherit html htmlOpenTool;};
161 |
162 | # Unstable, mainly for CI.
163 | jsonModuleMaintainers = pkgs.writeText "pm-module-maintainers.json" (let
164 | result = lib.evalModules {
165 | modules =
166 | import ../modules/all-modules.nix {
167 | inherit lib pkgs;
168 | check = false;
169 | modules = builtins.attrValues self.projectModules;
170 | }
171 | ++ [scrubbedPkgsModule];
172 | class = "projectManager";
173 | };
174 | in
175 | builtins.toJSON result.config.meta.maintainers);
176 | }
177 |
--------------------------------------------------------------------------------