|
| 1 | +{haskellLib, pkgs}: |
| 2 | +{callProjectResults, selectedCompiler}: |
| 3 | +let |
| 4 | + # Read the plan.json file `plan-nix` derivation |
| 5 | + plan-json = builtins.fromJSON ( |
| 6 | + builtins.unsafeDiscardStringContext ( |
| 7 | + builtins.readFile (callProjectResults.projectNix + "/plan.json"))); |
| 8 | + # All the units in the plan indexed by unit ID. |
| 9 | + by-id = pkgs.lib.listToAttrs (map (x: { name = x.id; value = x; }) plan-json.install-plan); |
| 10 | + # Find the names of all the pre-existing packages used by a list of dependencies |
| 11 | + # (includes transitive dependencies) |
| 12 | + lookupPreExisting = depends: |
| 13 | + pkgs.lib.concatMap (d: builtins.attrNames pre-existing-depends.${d}) depends; |
| 14 | + pre-existing-depends = |
| 15 | + pkgs.lib.listToAttrs (map (p: { |
| 16 | + name = p.id; |
| 17 | + value = pkgs.lib.optionalAttrs (p.type == "pre-existing") { ${p.pkg-name} = null; } // |
| 18 | + pkgs.lib.listToAttrs ( |
| 19 | + map (dname: { name = dname; value = null; }) (lookupPreExisting (p.depends or p.components.lib.depends))); |
| 20 | + }) plan-json.install-plan); |
| 21 | + # Lookup a dependency in `hsPkgs` |
| 22 | + lookupDependency = hsPkgs: d: |
| 23 | + pkgs.lib.optional (by-id.${d}.type != "pre-existing") ( |
| 24 | + if by-id.${d}.component-name or "lib" == "lib" |
| 25 | + then hsPkgs.${d} or hsPkgs."${by-id.${d}.pkg-name}-${by-id.${d}.pkg-version}" or hsPkgs.${by-id.${d}.pkg-name} |
| 26 | + else hsPkgs.${d}.components.sublibs.${pkgs.lib.removePrefix "lib:" by-id.${d}.component-name}); |
| 27 | + # Lookup an executable dependency in `hsPkgs.pkgsBuildBuild` |
| 28 | + lookupExeDependency = hsPkgs: d: |
| 29 | + # Try to lookup by ID, but if that fails use the name (currently a different plan is used by pkgsBuildBuild when cross compiling) |
| 30 | + (hsPkgs.pkgsBuildBuild.${d} or hsPkgs.pkgsBuildBuild.${by-id.${d}.pkg-name}).components.exes.${pkgs.lib.removePrefix "exe:" by-id.${d}.component-name}; |
| 31 | + # Populate `depends`, `pre-existing` and `build-tools` |
| 32 | + lookupDependencies = hsPkgs: depends: exe-depends: { |
| 33 | + depends = pkgs.lib.concatMap (lookupDependency hsPkgs) depends; |
| 34 | + pre-existing = lookupPreExisting depends; |
| 35 | + build-tools = map (lookupExeDependency hsPkgs) exe-depends; |
| 36 | + }; |
| 37 | + # Calculate the packages for a component |
| 38 | + getComponents = cabal2nixComponents: hsPkgs: p: |
| 39 | + let |
| 40 | + components = p.components or { ${p.component-name or "lib"} = { inherit (p) depends; exe-depends = p.exe-depends or []; }; }; |
| 41 | + # Other than the `lib` and `setup` components, component names |
| 42 | + # have a prefix based on their type. |
| 43 | + componentsWithPrefix = collectionName: prefix: |
| 44 | + pkgs.lib.listToAttrs (pkgs.lib.concatLists (pkgs.lib.mapAttrsToList (n: c: |
| 45 | + pkgs.lib.optional (pkgs.lib.hasPrefix "${prefix}:" n) ( |
| 46 | + let |
| 47 | + name = pkgs.lib.removePrefix "${prefix}:" n; |
| 48 | + value = (if cabal2nixComponents == null then {} else cabal2nixComponents.${collectionName}.${name}) // { |
| 49 | + buildable = true; |
| 50 | + } // lookupDependencies hsPkgs c.depends c.exe-depends; |
| 51 | + in { inherit name value; } |
| 52 | + )) components)); |
| 53 | + in |
| 54 | + pkgs.lib.mapAttrs componentsWithPrefix haskellLib.componentPrefix |
| 55 | + // pkgs.lib.optionalAttrs (components ? lib) { |
| 56 | + library = (if cabal2nixComponents == null then {} else cabal2nixComponents.library) // { |
| 57 | + buildable = true; |
| 58 | + } // lookupDependencies hsPkgs components.lib.depends components.lib.exe-depends; |
| 59 | + } // pkgs.lib.optionalAttrs (components ? setup) { |
| 60 | + setup = { |
| 61 | + buildable = true; |
| 62 | + } // lookupDependencies hsPkgs.pkgsBuildBuild (components.setup.depends or []) (components.setup.exe-depends or []); |
| 63 | + }; |
| 64 | + nixFilesDir = callProjectResults.projectNix + callProjectResults.src.origSubDir or ""; |
| 65 | +in { |
| 66 | + # This replaces the `plan-nix/default.nix` |
| 67 | + pkgs = (hackage: { |
| 68 | + packages = pkgs.lib.listToAttrs ( |
| 69 | + # Include entries for the `pre-existing` packages, but leave them as `null` |
| 70 | + pkgs.lib.concatMap (p: |
| 71 | + pkgs.lib.optional (p.type == "pre-existing") { |
| 72 | + name = p.id; |
| 73 | + value.revision = null; |
| 74 | + }) plan-json.install-plan |
| 75 | + # The other packages that are not part of the project itself. |
| 76 | + ++ pkgs.lib.concatMap (p: |
| 77 | + pkgs.lib.optional (p.type == "configured" && (p.style == "global" || p.style == "inplace") ) { |
| 78 | + name = p.id; |
| 79 | + value.revision = |
| 80 | + {hsPkgs, ...}@args: |
| 81 | + let |
| 82 | + # Read the output of `Cabal2Nix.hs`. We need it for information not |
| 83 | + # in the `plan.json` file. |
| 84 | + cabal2nix = ( |
| 85 | + if builtins.pathExists (nixFilesDir + "/cabal-files/${p.pkg-name}.nix") |
| 86 | + then import (nixFilesDir + "/cabal-files/${p.pkg-name}.nix") |
| 87 | + else if builtins.pathExists (nixFilesDir + "/.plan.nix/${p.pkg-name}.nix") |
| 88 | + then import (nixFilesDir + "/.plan.nix/${p.pkg-name}.nix") |
| 89 | + else (((hackage.${p.pkg-name}).${p.pkg-version}).revisions).default) (args // { hsPkgs = {}; }); |
| 90 | + in pkgs.lib.optionalAttrs (p ? pkg-src-sha256) { |
| 91 | + sha256 = p.pkg-src-sha256; |
| 92 | + } // pkgs.lib.optionalAttrs (p.pkg-src.type or "" == "source-repo") { |
| 93 | + # Replace the source repository packages with versions created when |
| 94 | + # parsing the `cabal.project` file. |
| 95 | + src = pkgs.lib.lists.elemAt callProjectResults.sourceRepos (pkgs.lib.strings.toInt p.pkg-src.source-repo.location) + "/${p.pkg-src.source-repo.subdir}"; |
| 96 | + } // pkgs.lib.optionalAttrs (cabal2nix ? package-description-override && p.pkg-version == cabal2nix.package.identifier.version) { |
| 97 | + # Use the `.cabal` file from the `Cabal2Nix` if it for the matching |
| 98 | + # version of the package (the one in the plan). |
| 99 | + inherit (cabal2nix) package-description-override; |
| 100 | + } // { |
| 101 | + flags = p.flags; # Use the flags from `plan.json` |
| 102 | + components = getComponents cabal2nix.components hsPkgs p; |
| 103 | + package = cabal2nix.package // { |
| 104 | + identifier = { name = p.pkg-name; version = p.pkg-version; id = p.id; }; |
| 105 | + isProject = false; |
| 106 | + setup-depends = []; # The correct setup depends will be in `components.setup.depends` |
| 107 | + }; |
| 108 | + }; |
| 109 | + }) plan-json.install-plan); |
| 110 | + compiler = { |
| 111 | + inherit (selectedCompiler) version; |
| 112 | + }; |
| 113 | + }); |
| 114 | + # Packages in the project (those that are both configure and local) |
| 115 | + extras = (_hackage: { |
| 116 | + packages = pkgs.lib.listToAttrs ( |
| 117 | + pkgs.lib.concatMap (p: |
| 118 | + pkgs.lib.optional (p.type == "configured" && p.style == "local") { |
| 119 | + name = p.id; |
| 120 | + value = |
| 121 | + {hsPkgs, ...}@args: |
| 122 | + let cabal2nix = import (nixFilesDir + "/.plan.nix/${p.pkg-name}.nix") (args // { hsPkgs = {}; }); |
| 123 | + in pkgs.lib.optionalAttrs (p ? pkg-src-sha256) { |
| 124 | + sha256 = p.pkg-src-sha256; |
| 125 | + } // pkgs.lib.optionalAttrs (p.pkg-src.type or "" == "local" && cabal2nix ? cabal-generator) { |
| 126 | + inherit (cabal2nix) cabal-generator; |
| 127 | + } // pkgs.lib.optionalAttrs (p.pkg-src.type or "" == "local") { |
| 128 | + # Find the `src` location based on `p.pkg-src.path` |
| 129 | + src = if pkgs.lib.hasPrefix "/" p.pkg-src.path |
| 130 | + then p.pkg-src.path # Absolute path |
| 131 | + else haskellLib.appendSubDir { |
| 132 | + # Relative to the project path |
| 133 | + inherit (callProjectResults) src; |
| 134 | + subDir = pkgs.lib.removePrefix "./" (pkgs.lib.removePrefix "/" (pkgs.lib.removeSuffix "/." (pkgs.lib.removeSuffix "/." ( |
| 135 | + if pkgs.lib.hasPrefix ".${callProjectResults.src.origSubDir or ""}/" (p.pkg-src.path + "/") |
| 136 | + then pkgs.lib.removePrefix ".${callProjectResults.src.origSubDir or ""}" p.pkg-src.path |
| 137 | + else throw "Unexpected path ${p.pkg-src.path} expected it to start with .${callProjectResults.src.origSubDir or ""}")))); |
| 138 | + includeSiblings = true; # Filtering sibling dirs of the package dir is done in the |
| 139 | + # component builder so that relative paths can be used to |
| 140 | + # reference project directories not in the package subDir. |
| 141 | + }; |
| 142 | + } // { |
| 143 | + flags = p.flags; # Use the flags from `plan.json` |
| 144 | + components = getComponents cabal2nix.components hsPkgs p; |
| 145 | + package = cabal2nix.package // { |
| 146 | + identifier = { name = p.pkg-name; version = p.pkg-version; id = p.id; }; |
| 147 | + isProject = true; |
| 148 | + setup-depends = []; # The correct setup depends will be in `components.setup.depends` |
| 149 | + }; |
| 150 | + }; |
| 151 | + }) plan-json.install-plan); |
| 152 | + }); |
| 153 | + modules = [ |
| 154 | + { inherit plan-json; } |
| 155 | + (import ../modules/install-plan/non-reinstallable.nix) |
| 156 | + (import ../modules/install-plan/override-package-by-name.nix) |
| 157 | + (import ../modules/install-plan/planned.nix { inherit getComponents; }) |
| 158 | + (import ../modules/install-plan/redirect.nix) |
| 159 | + ]; |
| 160 | +} |
0 commit comments