diff --git a/README.md b/README.md index 068d704..b6e18fb 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,8 @@ projectSettings = { ### Language packages (module defaults) -Add Python/R/Julia libraries that get appended to the module defaults. The built-in defaults are: +Add Python/R/Julia libraries. These are appended to the built-in defaults, which +always apply (see `settings.langPackageDefaults` in `modules/module/settings/lang-packages.nix`): - Python: `duckdb`, `polars` - R: `arrow`, `broom`, `data_table`, `janitor`, `styler` @@ -214,7 +215,8 @@ projectSettings = { }; ``` -Use `lib.mkForce` to replace rather than append: +Use `lib.mkForce` to replace other `lang_packages` assignments; the built-in defaults still apply. +To exclude defaults, override `catPkgs.` instead: ```nix projectSettings = { @@ -337,5 +339,5 @@ cats.nix ───────────────► config.cats │ - **Overlays** (`overlays/`) inject dependency package sets into nixpkgs (`rpkgs`, `baseRPackages`, `basePythonPackages`, plugins). Top-level `rWrapper` and `quarto` are compatibility conveniences, but `pkgs.rpkgs` is the canonical R surface for downstream configuration. - **cat-packages.nix** is the single source of truth for per-category packages. Each list is gated by its cat toggle. - **deps.nix** wires `catPkgs` into the wrapper's runtime PATH. -- **settings.lang_packages** holds the shared defaults used by local outputs and downstream module consumers. +- **settings.lang_packages** holds project-specific language libraries appended to the built-in `settings.langPackageDefaults`, used by local outputs and downstream module consumers. - **flake.nix** exports `lib.eval`, `lib.mkWrapper`, `lib.devShellPackages`, and `lib.shellHook` as the canonical downstream helpers, and builds `packages.default` and `devShells.default` from the same module config. diff --git a/flake.nix b/flake.nix index 3d3543e..7406da2 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,6 @@ url = "github:jmbuhr/cmp-pandoc-references"; flake = false; }; - "plugins-bloocky" = { url = "github:atiladefreitas/bloocky"; flake = false; @@ -39,6 +38,7 @@ url = "github:atiladefreitas/dooing"; flake = false; }; + }; outputs = { @@ -216,10 +216,10 @@ (builtins.length (self.lib.devShellPackages defaultConfig) > 0) ]; overrideAssertions = [ - (!(overrideConfig.cats.r or false)) + (overrideConfig.cats.r or false) (builtins.length overrideNix == 1) ((builtins.head overrideNix) == "alejandra") - (builtins.match ".*R RHOME.*" overrideShellHook == null) + (builtins.match ".*R RHOME.*" overrideShellHook != null) ]; in pkgs.runCommand "check-downstream-overrides" { @@ -241,6 +241,36 @@ touch $out ''; + # Downstream modules assign settings.lang_packages. plainly. The + # module system replaces same-option defaults, so built-in defaults must + # live in langPackageDefaults and be composed at the consumption sites. + # This check fails if someone moves defaults back into lang_packages: + # then a downstream assignment would silently drop lintr etc. + append-semantics = let + downstreamCfg = (self.lib.eval { + inherit pkgs; + modules = [ + { + cats.r = true; + settings.lang_packages.r = [ pkgs.rpkgs.rPackages.fixest ]; + } + ]; + }).config; + rWrapperPkg = builtins.head downstreamCfg.catPkgs.r; + in + pkgs.runCommand "check-lang-packages-append" { } '' + script=$(readlink -f "${rWrapperPkg}/bin/R") + grep -q "r-lintr-" "$script" || { + echo "default R packages were dropped by a downstream lang_packages assignment" >&2 + exit 1 + } + grep -q "r-fixest-" "$script" || { + echo "downstream lang_packages.r additions were not appended" >&2 + exit 1 + } + echo "lang_packages append semantics OK" > $out + ''; + smoke-test = pkgs.runCommand "smoke-test" {} '' # The Nix build sandbox has a read-only HOME; point XDG dirs at a # writable location so vim.lsp/shaDa can write state headlessly. diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index dbdba3f..a2a1400 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -9,14 +9,17 @@ let # NOTE: Package list expressions are lazily evaluated, and derivations are # not built until needed, so keep side-effecting expressions out of these # lists. - maybe = cat: pkgsList: - lib.optionals (config.cats.${cat} or false) pkgsList; - rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + maybe = cat: pkgsList: lib.optionals (config.cats.${cat} or false) pkgsList; + rPackages = + (pkgs.baseRPackages or [ ]) + ++ config.settings.langPackageDefaults.r + ++ config.settings.lang_packages.r; rWrapperPackages = rPackages; quartoPkg = - if config.cats.r or false - then pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } - else pkgs.quarto; + if config.cats.r or false then + pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } + else + pkgs.quarto; in { options.catPkgs = lib.mkOption { @@ -25,83 +28,107 @@ in }; config.catPkgs = { - always = maybe "always" (with pkgs; [ - ripgrep - ]); + always = maybe "always" ( + with pkgs; + [ + ripgrep + ] + ); clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); - external = maybe "external" (with pkgs; [ - nodejs - perl - ruby - shfmt - sqlfluff - tree-sitter - ]); + external = maybe "external" ( + with pkgs; + [ + nodejs + perl + ruby + shfmt + sqlfluff + tree-sitter + ] + ); julia = maybe "julia" [ - (pkgs.julia-bin.withPackages config.settings.lang_packages.julia) + (pkgs.julia-bin.withPackages ( + config.settings.langPackageDefaults.julia ++ config.settings.lang_packages.julia + )) ]; lua = maybe "lua" (with pkgs; [ lua-language-server ]); - markdown = maybe "markdown" (with pkgs; [ - python3Packages.pylatexenc - quartoPkg - zk - marksman - texlab - imagemagick - harper - ]); + markdown = maybe "markdown" ( + with pkgs; + [ + python3Packages.pylatexenc + quartoPkg + zk + marksman + texlab + imagemagick + harper + ] + ); - nix = maybe "nix" (with pkgs; [ - alejandra - nix-doc - nixd - ]); + nix = maybe "nix" ( + with pkgs; + [ + alejandra + nix-doc + nixd + ] + ); - optional = maybe "optional" (with pkgs; [ - bat - broot - devenv - dust - fd - fzf - gawk - gh - git - hunspell - hunspellDicts.de-at - hunspellDicts.en-us - ispell - jq - just - lazygit - man - ncdu - pigz - poppler - ripgrep - tokei - wget - yq - ]); + optional = maybe "optional" ( + with pkgs; + [ + bat + broot + devenv + dust + fd + fzf + gawk + gh + git + hunspell + hunspellDicts.de-at + hunspellDicts.en-us + ispell + jq + just + lazygit + man + ncdu + pigz + poppler + ripgrep + tokei + wget + yq + ] + ); - python = maybe "python" (let - python_packages_fn = - if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python - else _: config.settings.lang_packages.python; - python_with_packages = pkgs.python3.withPackages python_packages_fn; - in - with pkgs; [ + python = maybe "python" ( + let + python_packages_fn = + if pkgs ? basePythonPackages then + ps: + pkgs.basePythonPackages ps + ++ config.settings.langPackageDefaults.python + ++ config.settings.lang_packages.python + else + _: config.settings.langPackageDefaults.python ++ config.settings.lang_packages.python; + python_with_packages = pkgs.python3.withPackages python_packages_fn; + in + with pkgs; + [ python_with_packages ruff basedpyright uv - ]); + ] + ); r = maybe "r" [ (pkgs.rpkgs.rWrapper.override { packages = rWrapperPackages; }) diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 75ac273..0575cf0 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -14,6 +14,15 @@ # R.nvim v1.x owns its cache and temporary directories and exports # RNVIM_COMPLDIR/RNVIM_TMPDIR during setup. Do not inject literal `$PWD` # values into the wrapper environment. + # + # ponytail: with R_LIBS_USER unset-or-empty, loading compiled namespaces + # (S7 via btw) segfaults on macOS for bare-terminal launches outside the + # devShell hook. Any non-empty value avoids it; this store path exists, + # contains no libraries, and stays inert. The devShell hook overrides it + # with $PWD/.r-libs. Upgrade path: teach wlib to emit expandable defaults. + (lib.mkIf (config.cats.r or false) { + R_LIBS_USER = "${pkgs.rpkgs.rWrapper}"; + }) ]; # Environment variables with defaults (can be overridden by user) diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index 2db1515..4638e5e 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -5,7 +5,10 @@ ... }: let - rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + rPackages = + (pkgs.baseRPackages or [ ]) + ++ config.settings.langPackageDefaults.r + ++ config.settings.lang_packages.r; rWrapperPkg = pkgs.rpkgs.rWrapper.override { packages = rPackages ++ [pkgs.nvimcom]; }; in { diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index 3be7a9a..c172f8c 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -5,53 +5,75 @@ ... }: { - options.settings.lang_packages = lib.mkOption { - type = lib.types.submodule { - options = { - python = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - description = "Additional Python-related packages appended to the python spec (overlay defaults remain)."; - }; - r = lib.mkOption { - type = lib.types.listOf lib.types.package; - default = [ ]; - description = "Additional R-related packages appended to the r spec (overlay defaults remain)."; - }; - julia = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Additional Julia packages (names) passed to julia-bin.withPackages."; + options.settings = { + # Built-in language libraries composed into every language spec. + # Consumers extend via settings.lang_packages, which is APPENDED to these. + langPackageDefaults = lib.mkOption { + type = lib.types.submodule { + options = { + python = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + }; + r = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + }; + julia = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; }; }; + default = { }; + }; + + lang_packages = lib.mkOption { + type = lib.types.submodule { + options = { + python = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + description = "Additional Python-related packages appended to the python spec (overlay defaults remain)."; + }; + r = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + description = "Additional R-related packages appended to the r spec (overlay defaults remain)."; + }; + julia = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Additional Julia packages (names) passed to julia-bin.withPackages."; + }; + }; + }; + default = { }; + description = '' + Project-specific language libraries. Appended to settings.langPackageDefaults + in each language spec's runtime packages. + ''; }; - default = { }; - description = '' - Language-specific package defaults and downstream overrides appended to each - language spec's runtime packages. - ''; }; - config.settings.lang_packages = { - python = lib.mkDefault (with pkgs.python3Packages; [ + config.settings.langPackageDefaults = { + python = with pkgs.python3Packages; [ duckdb polars - ]); - r = lib.mkDefault ( - (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - # vscDebugger is not on CRAN/Bioconductor, so it is not available in - # pkgs.rpkgs.rPackages. Install it manually in your R library if you - # want to use the nvim-dap R adapter (see plugin/26_dap.lua). - # vscDebugger - lintr - ]) - ); - julia = lib.mkDefault [ + ]; + r = with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + # vscDebugger is not on CRAN/Bioconductor, so it is not available in + # pkgs.rpkgs.rPackages. Install it manually in your R library if you + # want to use the nvim-dap R adapter (see plugin/26_dap.lua). + # vscDebugger + lintr + ]; + julia = [ "DataFramesMeta" "QuackIO" ];