diff --git a/lib/default.nix b/lib/default.nix index 5d491e5c82..3d3d4f3e50 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -603,4 +603,6 @@ in { __toJSON (__attrNames (lib.filterAttrs (_: v: __length v > 1) ( builtins.groupBy (x: if __typeOf x == "set" then x.name or "noname" else "notset") x))) }"; + + types = import ./types.nix { inherit lib; }; } diff --git a/lib/types.nix b/lib/types.nix new file mode 100644 index 0000000000..5d030182bd --- /dev/null +++ b/lib/types.nix @@ -0,0 +1,47 @@ +{ lib }: + +with lib; + +rec { + # This is just like listOf, except that it filters out all null elements. + listOfFilteringNulls = elemType: types.listOf elemType // { + # Mostly copied from nixpkgs/lib/types.nix + merge = loc: defs: + map (x: x.value) (filter (x: x ? value && x.value != null) (concatLists (imap1 + (n: def: + if isList def.value then + imap1 + (m: def': + (mergeDefinitions + (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) + elemType + [{ inherit (def) file; value = def'; }] + ).optionalValue + ) + def.value + else + throw "The option value `${showOption loc}` in `${def.file}` is not a list.") + defs))); + }; + + # dealing with str is a bit annoying especially with `nullOr str` as that apparently defaults to "" + # instead of null :shrug:. This then messes with our option inheritance logic. + # Hence we have a uniqueStr type that ensures multiple identically defined options are collapsed + # without raising an error. And a way to fetch default options that will retain `null` if the + # option is not defined or "". + getDefaultOrNull = def: key: if def ? ${key} && def.${key} != "" then def.${key} else null; + + mergeUniqueOption = locs: defs: + let + mergeOneOption = loc: defs': + # we ignore "" as optionalString, will default to "". + let defs = filter (x: x.value != "") defs'; in + if defs == [ ] then null + else if length defs != 1 then + throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}; with values `${concatStringsSep "', `" (map (x: x.value) defs)}'." + else (head defs).value; + in + mergeOneOption locs (lists.unique defs); + + uniqueStr = types.str // { merge = mergeUniqueOption; }; +} diff --git a/modules/component-options.nix b/modules/component-options.nix new file mode 100644 index 0000000000..ad5783399d --- /dev/null +++ b/modules/component-options.nix @@ -0,0 +1,166 @@ +{ lib, haskellLib, ... }: +{ + options = { + buildable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + + configureFlags = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = []; + }; + + setupBuildFlags = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = []; + }; + + testFlags = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = []; + }; + + setupInstallFlags = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = []; + }; + + setupHaddockFlags = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = []; + }; + + doExactConfig = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + doCheck = lib.mkOption { + type = lib.types.bool; + default = true; + }; + + doCrossCheck = lib.mkOption { + description = "Run doCheck also in cross compilation settings. This can be tricky as the test logic must know how to run the tests on the target."; + type = lib.types.bool; + default = false; + }; + + doHaddock = lib.mkOption { + description = "Enable building of the Haddock documentation from the annotated Haskell source code."; + type = lib.types.bool; + default = true; + }; + + doHoogle = lib.mkOption { + description = "Also build a hoogle index."; + type = lib.types.bool; + default = true; + }; + + doHyperlinkSource = lib.mkOption { + description = "Link documentation to the source code."; + type = lib.types.bool; + default = true; + }; + + doQuickjump = lib.mkOption { + description = "Generate an index for interactive documentation navigation."; + type = lib.types.bool; + default = true; + }; + + doCoverage = lib.mkOption { + description = "Enable production of test coverage reports."; + type = lib.types.bool; + default = false; + }; + + dontPatchELF = lib.mkOption { + description = "If set, the patchelf command is not used to remove unnecessary RPATH entries. Only applies to Linux."; + type = lib.types.bool; + default = true; + }; + + dontStrip = lib.mkOption { + description = "If set, libraries and executables are not stripped."; + type = lib.types.bool; + default = true; + }; + + enableDeadCodeElimination = lib.mkOption { + description = "If set, enables split sections for link-time dead-code stripping. Only applies to Linux"; + type = lib.types.bool; + default = true; + }; + + enableStatic = lib.mkOption { + description = "If set, enables building static libraries and executables."; + type = lib.types.bool; + default = true; + }; + + enableShared = lib.mkOption { + description = "If set, enables building shared libraries."; + type = lib.types.bool; + default = true; + }; + + configureAllComponents = lib.mkOption { + description = "If set all the components in the package are configured (useful for cabal-doctest)."; + type = lib.types.bool; + default = false; + }; + + shellHook = lib.mkOption { + description = "Hook to run when entering a shell"; + type = lib.types.unspecified; # Can be either a string or a function + default = ""; + }; + + enableLibraryProfiling = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + enableSeparateDataOutput = lib.mkOption { + type = lib.types.bool; + default = true; + }; + + enableProfiling = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + profilingDetail = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = "default"; + }; + + keepConfigFiles = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Keep component configFiles in the store in a `configFiles` output"; + }; + + keepGhc = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Keep component wrapped ghc in the store in a `ghc` output"; + }; + + keepSource = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Keep component source in the store in a `source` output"; + }; + + writeHieFiles = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Write component `.hie` files in the store in a `hie` output"; + }; + }; +} diff --git a/modules/component.nix b/modules/component.nix new file mode 100644 index 0000000000..384ce10c61 --- /dev/null +++ b/modules/component.nix @@ -0,0 +1,121 @@ +{ lib, haskellLib, ... }: + +let + inherit (lib) types; + inherit (haskellLib.types) listOfFilteringNulls; + +in +{ + imports = [ + ./component-options.nix + ./package-options.nix + ]; + + options = { + plugins = lib.mkOption { + type = types.listOf (types.submodule { + options = { + library = lib.mkOption { + type = types.unspecified; + }; + + moduleName = lib.mkOption { + type = types.str; + }; + + args = lib.mkOption { + type = types.listOf types.str; + default = [ ]; + }; + }; + }); + + default = [ ]; + }; + + depends = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + libs = lib.mkOption { + type = listOfFilteringNulls (types.either (types.nullOr types.package) (listOfFilteringNulls types.package)); + default = [ ]; + }; + + frameworks = lib.mkOption { + type = listOfFilteringNulls types.package; + default = [ ]; + }; + + pkgconfig = lib.mkOption { + type = types.listOf (listOfFilteringNulls types.package); + default = [ ]; + }; + + build-tools = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + modules = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + asmSources = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + cmmSources = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + cSources = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + cxxSources = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + jsSources = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + hsSourceDirs = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ "." ]; + }; + + includeDirs = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + includes = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + mainPath = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + extraSrcFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; + }; + + platforms = lib.mkOption { + type = types.nullOr (listOfFilteringNulls types.unspecified); + default = null; + }; + }; +} diff --git a/modules/package-options.nix b/modules/package-options.nix new file mode 100644 index 0000000000..4f4a84b80a --- /dev/null +++ b/modules/package-options.nix @@ -0,0 +1,108 @@ +{ lib, haskellLib, ... }: +{ + options = { + preUnpack = lib.mkOption { + type = lib.types.nullOr lib.types.lines; + default = null; + }; + + postUnpack = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + prePatch = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + postPatch = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + preConfigure = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + postConfigure = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + preBuild = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + postBuild = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + preCheck = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + # Wrapper for test executable run in checkPhase + testWrapper = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = [ ]; + description = "A command to run for executing tests in checkPhase, which takes the original test command as its arguments."; + example = "echo"; + }; + + postCheck = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + preInstall = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + postInstall = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + preHaddock = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + postHaddock = lib.mkOption { + type = lib.types.nullOr haskellLib.types.uniqueStr; + default = null; + }; + + hardeningDisable = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = [ ]; + }; + + ghcOptions = lib.mkOption { + type = haskellLib.types.listOfFilteringNulls lib.types.str; + default = [ ]; + }; + + contentAddressed = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Build content addressed derivation, requires Nix to have experimental feature + `ca-derivations` enabled. + ''; + }; + + planned = lib.mkOption { + description = "Set to true by `plan-to-nix` for any component that was included in the `plan.json` file."; + # This is here so that (rather than in componentOptions) so it can be set project wide for stack projects + type = lib.types.bool; + default = false; + }; + }; +} diff --git a/modules/package.nix b/modules/package.nix index 01820bf5d8..f6f02af319 100644 --- a/modules/package.nix +++ b/modules/package.nix @@ -1,342 +1,268 @@ -# package descriptions in hackage will look like: -# { system, compiler, flags, pkgs, hsPkgs, pkgconfPkgs }: -# { flags = { flag1 = false; flags2 = true; ... }; -# package = { specVersion = "X.Y"; identifier = { name = "..."; version = "a.b.c.d"; }; -# license = "..."; copyright = "..."; maintainer = "..."; author = "..."; -# homepage = "..."; url = "..."; synopsis = "..."; description = "..."; -# buildType = "Simple"; # or Custom, Autoconf, ... -# }; -# components = { -# "..." = { depends = [ (hsPkgs.base) ... ]; }; -# exes = { "..." = { depends = ... }; -# "..." = { depends = ... }; }; -# tests = { "..." = { depends = ... }; ... }; -# }; - -{ parentConfig, mod_args, listOfFilteringNulls, componentOptions, packageOptions }: { lib, config, pkgs, haskellLib, ... }: -with lib; -with types; - # Work around issue that can cause _lots_ of files to be copied into the store. # See https://github.com/NixOS/nixpkgs/pull/64691 let + inherit (haskellLib.types) listOfFilteringNulls; + inherit (lib) types; + path = types.path // { check = x: types.path.check (x.origSrc or x); }; - componentType = submodule { - # add the shared componentOptions - options = (packageOptions config) // { - buildable = mkOption { - type = bool; - default = true; - }; - plugins = mkOption { - type = listOf (submodule { - options = { - library = mkOption { - type = unspecified; - }; - moduleName = mkOption { - type = str; - }; - args = mkOption { - type = listOf str; - default = []; - }; - }; - }); - default = []; - }; - depends = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - libs = mkOption { - type = listOfFilteringNulls (either (nullOr package) (listOfFilteringNulls package)); - default = []; - }; - frameworks = mkOption { - type = listOfFilteringNulls package; - default = []; - }; - pkgconfig = mkOption { - type = listOf (listOfFilteringNulls package); - default = []; - }; - build-tools = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - modules = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - asmSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cmmSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - cxxSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - jsSources = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - hsSourceDirs = mkOption { - type = listOfFilteringNulls unspecified; - default = ["."]; - }; - includeDirs = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - includes = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - mainPath = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - extraSrcFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; - }; - platforms = mkOption { - type = nullOr (listOfFilteringNulls unspecified); - default = null; - }; - }; - }; + componentType = types.submodule [ + ./component.nix + { _module.args = { inherit haskellLib; }; } + # pass down common options as default values + ({ lib, options, ... }: lib.mkDefault (lib.filterAttrs (n: _v: builtins.hasAttr n options) config)) + ]; + +in +{ + imports = [ + ./component-options.nix + ./package-options.nix + ]; -in { # This is how the Nix expressions generated by *-to-nix receive # their flags argument. - config._module.args = mod_args // { flags = config.flags; }; + config._module.args = { inherit (config) flags; }; - options = (packageOptions parentConfig) // { - # TODO: Add descriptions to everything. - flags = mkOption { - type = attrsOf bool; + # TODO: Add descriptions to everything. + options = { + flags = lib.mkOption { + type = types.attrsOf types.bool; }; + package = { - specVersion = mkOption { - type = str; + specVersion = lib.mkOption { + type = types.str; }; - identifier.name = mkOption { - type = str; + identifier.name = lib.mkOption { + type = types.str; }; - identifier.version = mkOption { - type = str; + identifier.version = lib.mkOption { + type = types.str; }; - license = mkOption { - type = str; + license = lib.mkOption { + type = types.str; }; - copyright = mkOption { - type = str; + copyright = lib.mkOption { + type = types.str; }; - maintainer = mkOption { - type = str; + maintainer = lib.mkOption { + type = types.str; }; - author = mkOption { - type = str; + author = lib.mkOption { + type = types.str; }; - homepage = mkOption { - type = str; + homepage = lib.mkOption { + type = types.str; }; - url = mkOption { - type = str; + url = lib.mkOption { + type = types.str; }; - synopsis = mkOption { - type = str; + synopsis = lib.mkOption { + type = types.str; }; - description = mkOption { - type = str; + description = lib.mkOption { + type = types.str; }; - buildType = mkOption { - type = str; + buildType = lib.mkOption { + type = types.str; }; - setup-depends = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + setup-depends = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - detailLevel = mkOption { - type = str; + detailLevel = lib.mkOption { + type = types.str; default = "MinimalDetails"; }; - licenseFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + licenseFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - dataDir = mkOption { - type = str; + dataDir = lib.mkOption { + type = types.str; default = ""; }; - dataFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + dataFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - extraSrcFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + extraSrcFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - extraTmpFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + extraTmpFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - extraDocFiles = mkOption { - type = listOfFilteringNulls unspecified; - default = []; + extraDocFiles = lib.mkOption { + type = listOfFilteringNulls types.unspecified; + default = [ ]; }; - cleanHpack = mkOption { - type = bool; + cleanHpack = lib.mkOption { + type = types.bool; default = false; }; - isLocal = mkOption { - type = bool; + isLocal = lib.mkOption { + type = types.bool; default = false; }; - isProject = mkOption { - type = bool; + isProject = lib.mkOption { + type = types.bool; default = false; }; - buildable = mkOption { - type = bool; + buildable = lib.mkOption { + type = types.bool; default = true; }; }; components = { - setup = mkOption { - type = nullOr componentType; + setup = lib.mkOption { + type = types.nullOr componentType; default = { - depends = []; - libs = []; - frameworks = []; + depends = [ ]; + libs = [ ]; + frameworks = [ ]; doExactConfig = false; # We have to set hsSourceDirs or cleanCabalComponent will # include everything (and as a result all the components of # the package will depend on everything in the package). # TODO find a better way - hsSourceDirs = ["setup-src"]; - includeDirs = []; - asmSources = []; - cSources = []; - cmmSources = []; - cxxSources = []; - jsSources = []; + hsSourceDirs = [ "setup-src" ]; + includeDirs = [ ]; + asmSources = [ ]; + cSources = [ ]; + cmmSources = [ ]; + cxxSources = [ ]; + jsSources = [ ]; extraSrcFiles = [ "Setup.hs" "Setup.lhs" ]; platforms = null; }; }; - library = mkOption { - type = nullOr componentType; + + library = lib.mkOption { + type = types.nullOr componentType; default = null; }; - sublibs = mkOption { - type = attrsOf componentType; - default = {}; + + sublibs = lib.mkOption { + type = types.attrsOf componentType; + default = { }; }; - foreignlibs = mkOption { - type = attrsOf componentType; - default = {}; + + foreignlibs = lib.mkOption { + type = types.attrsOf componentType; + default = { }; }; - exes = mkOption { - type = attrsOf componentType; - default = {}; + + exes = lib.mkOption { + type = types.attrsOf componentType; + default = { }; }; - tests = mkOption { - type = attrsOf componentType; - default = {}; + + tests = lib.mkOption { + type = types.attrsOf componentType; + default = { }; }; - benchmarks = mkOption { - type = attrsOf componentType; - default = {}; + + benchmarks = lib.mkOption { + type = types.attrsOf componentType; + default = { }; }; }; - name = mkOption { - type = str; + name = lib.mkOption { + type = types.str; default = "${config.package.identifier.name}-${config.package.identifier.version}"; defaultText = "\${config.package.identifier.name}-\${config.package.identifier.version}"; }; - sha256 = mkOption { - type = nullOr str; + + sha256 = lib.mkOption { + type = types.nullOr types.str; default = null; }; - src = mkOption { - type = either path package; - default = pkgs.fetchurl { url = "mirror://hackage/${config.name}.tar.gz"; inherit (config) sha256; }; - defaultText = "pkgs.fetchurl { url = \"mirror://hackage/\${config.name}.tar.gz\"; inherit (config) sha256; };"; + + src = lib.mkOption { + type = types.either path types.package; + default = + pkgs.fetchurl { + url = "mirror://hackage/${config.name}.tar.gz"; + inherit (config) sha256; + }; + defaultText = '' + pkgs.fetchurl { + url = "mirror://hackage/$'{config.name}.tar.gz"; + inherit (config) sha256; + }; + ''; # Make sure paths have a context so they will be included in the derivation # inputs for the component derivations. Without this sandbox builds fail # cannot see the input and fail with the error: # do not know how to unpack source archive /nix/store/... apply = v: - let storeDirMatch = __match "(${__storeDir}/[^/]+).*" v; - in if isString v && __getContext v == {} && storeDirMatch != null - then __appendContext v { ${__head storeDirMatch} = { path = true; }; } - else v; + let storeDirMatch = builtins.match "(${builtins.storeDir}/[^/]+).*" v; + in if builtins.isString v && builtins.getContext v == { } && storeDirMatch != null + then builtins.appendContext v { ${builtins.head storeDirMatch} = { path = true; }; } + else v; }; - package-description-override = mkOption { - type = nullOr str; + + package-description-override = lib.mkOption { + type = types.nullOr types.str; default = null; description = "Cabal file to use instead of the one shipped inside the package source distribution."; }; - cabal-generator = mkOption { - type = nullOr str; + + cabal-generator = lib.mkOption { + type = types.nullOr types.str; default = null; }; - revision = mkOption { - type = nullOr int; + + revision = lib.mkOption { + type = types.nullOr types.int; default = null; }; - revisionSha256 = mkOption { - type = nullOr str; + + revisionSha256 = lib.mkOption { + type = types.nullOr types.str; default = null; }; - patches = mkOption { - type = listOf (either unspecified path); - default = []; + + patches = lib.mkOption { + type = types.listOf (types.either types.unspecified path); + default = [ ]; }; + # This used to be `components.all` but it has been added back as `allComponent` to # to avoid confusion. It is not mapped by `builder/hspkg-builder.nix` to anything # you can build. Instead it is used internally when `configureAllComponents` # is set or for tests whe on `cabal-doctest` is in the `setup-depends` of the package. - allComponent = mkOption { + allComponent = lib.mkOption { type = componentType; apply = all: all // { # TODO: Should this check for the entire component @@ -359,14 +285,17 @@ in { # returning true if any is true. config.allComponent = let allComps = haskellLib.getAllComponents config; - in lib.mkMerge ( - builtins.map (c: - # Exclude attributes that are likely to have conflicting definitions - # (a common use case for `all` is in `shellFor` and it only has an - # install phase). - builtins.removeAttrs c ["preCheck" "postCheck" "keepConfigFiles" "keepGhc" "keepSource"] - ) (lib.filter (c: c.buildable && c.planned) allComps) - ) // { + in lib.mkMerge + ( + builtins.map + (c: + # Exclude attributes that are likely to have conflicting definitions + # (a common use case for `all` is in `shellFor` and it only has an + # install phase). + builtins.removeAttrs c [ "preCheck" "postCheck" "keepConfigFiles" "keepGhc" "keepSource" ] + ) + (lib.filter (c: c.buildable && c.planned) allComps) + ) // { # If any one of the components needs us to keep one of these # then keep it for the `all` component keepConfigFiles = lib.foldl' (x: comp: x || comp.keepConfigFiles) false allComps; diff --git a/modules/plan.nix b/modules/plan.nix index 9dcfd1d436..014f413706 100644 --- a/modules/plan.nix +++ b/modules/plan.nix @@ -1,305 +1,36 @@ -# The plan (that is, a package set description like an LTS set or a -# plan.nix (derived from plan.json)) will produce a structure that -# looks like, which is stored in config.plan.pkg-def: -# -# { packages = { "package" = { revision = hackageConfigs.$package.$version.revisions.default; -# flags = { flag1 = true; flag2 = false; ... }; }; -# ... }; -# compiler = { version = "X.Y.Z"; nix-name ="ghcXYZ"; -# # packages that come bundled with the compiler -# packages = { "bytestring" = "a.b.c.d"; ... }; }; -# } - { lib, config, pkgs, pkgconfPkgs, haskellLib, ... }: with lib; with types; let - # dealing with str is a bit annoying especially with `nullOr str` as that apparently defaults to "" - # instead of null :shrug:. This then messes with our option inheritance logic. - # Hence we have a uniqueStr type that ensures multiple identically defined options are collapsed - # without raising an error. And a way to fetch default options that will retain `null` if the - # option is not defined or "". - getDefaultOrNull = def: key: if def ? ${key} && def.${key} != "" then def.${key} else null; - mergeUniqueOption = locs: defs: let - mergeOneOption = loc: defs': - # we ignore "" as optionalString, will default to "". - let defs = filter (x: x.value != "") defs'; in - if defs == [] then null - else if length defs != 1 then - throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}; with values `${concatStringsSep "', `" (map (x: x.value) defs)}'." - else (head defs).value; - in mergeOneOption locs (lists.unique defs); - uniqueStr = str // { merge = mergeUniqueOption; }; - - # This is just like listOf, except that it filters out all null elements. - listOfFilteringNulls = elemType: listOf elemType // { - # Mostly copied from nixpkgs/lib/types.nix - merge = loc: defs: - map (x: x.value) (filter (x: x ? value && x.value != null) (concatLists (imap1 (n: def: - if isList def.value then - imap1 (m: def': - (mergeDefinitions - (loc ++ ["[definition ${toString n}-entry ${toString m}]"]) - elemType - [{ inherit (def) file; value = def'; }] - ).optionalValue - ) def.value - else - throw "The option value `${showOption loc}` in `${def.file}` is not a list.") defs))); - }; - - componentOptions = def: { - buildable = mkOption { - type = bool; - default = true; - }; - configureFlags = mkOption { - type = listOfFilteringNulls str; - default = (def.configureFlags or []); - }; - setupBuildFlags = mkOption { - type = listOfFilteringNulls str; - default = (def.setupBuildFlags or []); - }; - testFlags = mkOption { - type = listOfFilteringNulls str; - default = (def.testFlags or []); - }; - setupInstallFlags = mkOption { - type = listOfFilteringNulls str; - default = (def.setupInstallFlags or []); - }; - setupHaddockFlags = mkOption { - type = listOfFilteringNulls str; - default = (def.setupHaddockFlags or []); - }; - doExactConfig = mkOption { - type = bool; - default = (def.doExactConfig or false); - }; - doCheck = mkOption { - type = bool; - default = (def.doCheck or true); - }; - doCrossCheck = mkOption { - description = "Run doCheck also in cross compilation settings. This can be tricky as the test logic must know how to run the tests on the target."; - type = bool; - default = (def.doCrossCheck or false); - }; - doHaddock = mkOption { - description = "Enable building of the Haddock documentation from the annotated Haskell source code."; - type = bool; - default = (def.doHaddock or true); - }; - doHoogle = mkOption { - description = "Also build a hoogle index."; - type = bool; - default = (def.doHoogle or true); - }; - doHyperlinkSource = mkOption { - description = "Link documentation to the source code."; - type = bool; - default = (def.doHyperlinkSource or true); - }; - doQuickjump = mkOption { - description = "Generate an index for interactive documentation navigation."; - type = bool; - default = (def.doQuickjump or true); - }; - doCoverage = mkOption { - description = "Enable production of test coverage reports."; - type = bool; - default = (def.doCoverage or false); - }; - dontPatchELF = mkOption { - description = "If set, the patchelf command is not used to remove unnecessary RPATH entries. Only applies to Linux."; - type = bool; - default = (def.dontPatchELF or true); - }; - dontStrip = mkOption { - description = "If set, libraries and executables are not stripped."; - type = bool; - default = (def.dontStrip or true); - }; - enableDeadCodeElimination = mkOption { - description = "If set, enables split sections for link-time dead-code stripping. Only applies to Linux"; - type = bool; - default = (def.enableDeadCodeElimination or true); - }; - enableStatic = mkOption { - description = "If set, enables building static libraries and executables."; - type = bool; - default = (def.enableStatic or true); - }; - enableShared = mkOption { - description = "If set, enables building shared libraries."; - type = bool; - default = (def.enableShared or true); - }; - configureAllComponents = mkOption { - description = "If set all the components in the package are configured (useful for cabal-doctest)."; - type = bool; - default = false; - }; - shellHook = mkOption { - description = "Hook to run when entering a shell"; - type = unspecified; # Can be either a string or a function - default = (def.shellHook or ""); - }; - enableLibraryProfiling = mkOption { - type = bool; - default = (def.enableLibraryProfiling or false); - }; - - enableSeparateDataOutput = mkOption { - type = bool; - default = (def.enableSeparateDataOutput or true); - }; - - enableProfiling = mkOption { - type = bool; - default = (def.enableProfiling or false); - }; - - profilingDetail = mkOption { - type = nullOr uniqueStr; - default = (def.profilingDetail or "default"); - }; - - keepConfigFiles = mkOption { - type = bool; - default = (def.keepConfigFiles or false); - description = "Keep component configFiles in the store in a `configFiles` output"; - }; - - keepGhc = mkOption { - type = bool; - default = (def.keepGhc or false); - description = "Keep component wrapped ghc in the store in a `ghc` output"; - }; - - keepSource = mkOption { - type = bool; - default = (def.keepSource or false); - description = "Keep component source in the store in a `source` output"; - }; - - writeHieFiles = mkOption { - type = bool; - default = (def.writeHieFiles or false); - description = "Write component `.hie` files in the store in a `hie` output"; - }; - }; - packageOptions = def: componentOptions def // { - preUnpack = mkOption { - type = nullOr lines; - default = (def.preUnpack or null); - }; - postUnpack = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postUnpack"; - }; - prePatch = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "prePatch"; - }; - postPatch = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postPatch"; - }; - preConfigure = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "preConfigure"; - }; - postConfigure = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postConfigure"; - }; - preBuild = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "preBuild"; - }; - postBuild = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postBuild"; - }; - preCheck = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "preCheck"; - }; - # Wrapper for test executable run in checkPhase - testWrapper = mkOption { - type = listOfFilteringNulls str; - default = def.testWrapper or []; - description = "A command to run for executing tests in checkPhase, which takes the original test command as its arguments."; - example = "echo"; - }; - postCheck = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postCheck"; - }; - preInstall = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "preInstall"; - }; - postInstall = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postInstall"; - }; - preHaddock = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "preHaddock"; - }; - postHaddock = mkOption { - type = nullOr uniqueStr; - default = getDefaultOrNull def "postHaddock"; - }; - hardeningDisable = mkOption { - type = listOfFilteringNulls str; - default = (def.hardeningDisable or []); - }; - ghcOptions = mkOption { - type = listOfFilteringNulls str; - default = def.ghcOptions or []; - }; - contentAddressed = mkOption { - type = bool; - default = (def.contentAddressed or false); - description = '' - Build content addressed derivation, requires Nix to have experimental feature - `ca-derivations` enabled. - ''; - }; - planned = mkOption { - description = "Set to true by `plan-to-nix` for any component that was included in the `plan.json` file."; - # This is here so that (rather than in componentOptions) so it can be set project wide for stack projects - type = bool; - default = def.planned or false; - }; - }; - + package = submodule [ + { + _module.args = { + inherit pkgs pkgconfPkgs haskellLib; + inherit (config) hsPkgs errorHandler; + inherit (config.cabal) system compiler; + }; + } + ./package.nix + # pass down common options as default values + ({ lib, options, ... }: lib.mkDefault (lib.filterAttrs (n: _v: builtins.hasAttr n options) config)) + ]; + +in +{ + imports = [ + ./component-options.nix + ./package-options.nix + ]; -in { # Global options. These are passed down to the package level, and from there to the # component level, unless specifically overridden. Depending on the flag flags are # combined or replaced. We seed the package Options with an empty set forcing the # default values. - options = (packageOptions {}) // { - + options = { packages = mkOption { - type = - let mod_args = { - inherit pkgs pkgconfPkgs haskellLib; - inherit (config) hsPkgs errorHandler; - inherit (config.cabal) system compiler; - }; in - attrsOf (submodule (import ./package.nix { - inherit mod_args listOfFilteringNulls; - inherit componentOptions packageOptions; - parentConfig = config; - })); + type = attrsOf package; }; compiler = {