Skip to content

Instantly share code, notes, and snippets.

@hyperfekt
Created May 7, 2020 12:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyperfekt/9b5b36afe86c176d5c144bfd67699eee to your computer and use it in GitHub Desktop.
Save hyperfekt/9b5b36afe86c176d5c144bfd67699eee to your computer and use it in GitHub Desktop.
# This is the entry point of the library, and badly needs documentation.
# TODO: currently single out derivations prepend the PWD to the path
# TODO: make sure that filters for "base" are airtight
# TODO: document the sh*t out of these functions
{ pkgs
, ghc-version ? "ghc864"
, ghcWithPackages ? pkgs.haskell.packages.${ghc-version}.ghcWithPackages
, haskellPackages ? pkgs.haskell.packages.${ghc-version}
}:
with pkgs;
with (callPackage ./build.nix {});
with (callPackage ./files.nix {});
with (callPackage ./ghci.nix {});
with (callPackage ./lib.nix {});
with (callPackage ./modules.nix {});
with (callPackage ./module-spec.nix {});
with (callPackage ./package-spec.nix {});
with rec
{
hpack = callPackage ./hpack.nix { inherit pkgDescriptionsFromPath; };
# Derivation that creates a binary in a 'bin' folder.
executable = packageFile:
let
specs = specsFromPackageFile packageFile;
spec =
if pkgs.lib.length specs == 1
then pkgs.lib.head specs
else abort "'executable' can only be called on a single executable";
exe =
if spec.packageIsExe
then buildAsExecutable spec
else abort "'executable' called on a library";
in exe.out;
# Build a package spec as resp. a library and an executable
buildAsLibrary = pkgSpec:
buildLibrary ghcWith (libraryModSpecs pkgSpec);
buildAsExecutable = pkgSpec:
let
moduleSpec = executableMainModSpec pkgSpec;
name = pkgSpec.packageName;
mainModName = pkgSpec.packageMainModule;
drv = linkMainModule { inherit moduleSpec name ghcWith mainModName; };
in
{ out = drv.out;
exe_path = "${drv.out}/${drv.relExePath}";
};
ghcWith = deps: ghcWithPackages
(ps: map (p: ps.${p}) deps);
# Normal build (libs, exes)
inferBuild = packageFile:
mkPackages (specsFromPackageFile packageFile);
mkPackages = pkgSpecs: writeText "build.json"
( builtins.toJSON
( builtins.map
(pkgSpec:
if pkgSpec.packageIsExe
then
{ build_type = "executable";
result = buildAsExecutable pkgSpec;
}
else
{ build_type = "library";
result = buildAsLibrary pkgSpec;
}
) pkgSpecs
)
);
# GHCi build (libs, exes)
inferGhci = packageFile:
mkPackagesGhci (specsFromPackageFile packageFile);
mkPackagesGhci = pkgSpecs: writeText "hpack-ghci-json"
( builtins.toJSON (
builtins.map
(pkgSpec:
let
drv =
if pkgSpec.packageIsExe
then ghciWithMain ghcWith (executableMainModSpec pkgSpec)
else ghciWithModules ghcWith (libraryModSpecs pkgSpec)
;
in
{ build_type = "ghci"; # TODO: need to record the name somewhere
result = "${drv.out}/bin/ghci-with-files";
}
) pkgSpecs
));
# How to build resp. libraries and executables
libraryModSpecs = pkgSpec:
let
moduleSpecFold' = modSpecFoldFromPackageSpec pkgSpec;
modNames = pkgs.lib.concatMap listModulesInDir pkgSpec.packageSourceDirs;
fld = moduleSpecFold' modSpecs';
modSpecs' = foldDAG fld modNames;
modSpecs = builtins.attrValues modSpecs';
in modSpecs;
executableMainModSpec = pkgSpec:
let
moduleSpecFold' = modSpecFoldFromPackageSpec pkgSpec;
mainModName = pkgSpec.packageMain;
mainModSpec =
let
fld = moduleSpecFold' modSpecs;
modSpecs = foldDAG fld [mainModName];
in modSpecs.${mainModName};
in mainModSpec;
# Get a list of package descriptions from a path
# This can be
# - a path, relative or absolute, to a directory that contains either a
# package.yaml or a package.nix
# - a path, relative or absolute, to a file with either .nix or .yaml or
# .yml extension
pkgDescriptionsFromPath =
with rec
{
pkgDescriptionsFromFile = packageFile:
with rec
{
basename = builtins.baseNameOf packageFile;
components = pkgs.lib.strings.splitString "." basename;
ext =
if pkgs.lib.length components <= 1
then abort ("File " ++ packageFile ++ " does not have an extension")
else pkgs.lib.last components;
fromNix = [(import packageFile)];
fromHPack = hpack.pkgDescriptionsFromHPack packageFile;
};
if ext == "nix" then fromNix
else if ext == "yaml" then fromHPack
else if ext == "yml" then fromHPack
else abort ("Unknown extension " ++ ext ++ " of file " ++ packagePath);
pkgDescriptionsFromDir = packageDir:
with rec
{ dirContent = builtins.readDir packageDir;
hasPackageYaml = builtins.hasAttr "package.yaml" dirContent;
hasPackageNix = builtins.hasAttr "package.nix" dirContent;
};
if hasPackageYaml && hasPackageNix
then abort "Found both package.yaml and package.nix in ${packageDir}"
else if ! (hasPackageYaml || hasPackageNix)
then abort "Couldn't find package.yaml or package.nix in ${packageDir}"
else if hasPackageYaml
then pkgDescriptionsFromFile
"${builtins.toString packageDir}/package.yaml"
else pkgDescriptionsFromFile
"${builtins.toString packageDir}/package.nix";
};
packagePath:
with { pathType = pkgs.lib.pathType packagePath ; } ;
if pathType == "directory"
then pkgDescriptionsFromDir packagePath
else if pathType == "regular"
then pkgDescriptionsFromFile packagePath
else abort "Don't know how to load package path of type ${pathType}";
specsFromPackageFile = packagePath:
map mkPackageSpec (pkgDescriptionsFromPath packagePath);
buildHoogle = packagePath:
let
concatUnion = lists:
let
sets = map (l: pkgs.lib.genAttrs l (_: null)) lists;
union = pkgs.lib.foldAttrs (n: a: null) {} sets;
in
builtins.attrNames union;
allDeps = concatUnion (map (spec: spec.packageDependencies {}) (specsFromPackageFile packagePath));
drv = haskellPackages.hoogleLocal { packages = map (p: haskellPackages.${p}) allDeps; };
in
writeText "hoogle-json"
( builtins.toJSON
{ build_type = "hoogle";
result = {
exe_path = "${drv.out}/bin/hoogle";
};
}
);
buildHieBios = packagePath: haskellPath:
with lib;
let
isPrefix = xxs: xs: length xs == 0 || length xxs > 0 && head xs == head xxs && isPrefix (tail xxs) (tail xs);
components = strings.splitString "/";
hasSource = spec: any (isPrefix (components haskellPath)) (map components spec.packageSourceDirs) || any hasSource spec.packagePackages;
containingPackage = findFirst hasSource (abort "Couldn't find package containing ${haskellPath} in ${builtins.unsafeDiscardStringContext packagePath} and the packages it references") (specsFromPackageFile packagePath);
modspecs = if containingPackage.packageIsExe then [ (executableMainModSpec containingPackage) ] else libraryModSpecs containingPackage;
deps = allTransitiveDeps modspecs;
dirs = allTransitiveDirectories modspecs;
exts = allTransitiveExtensions modspecs;
ghc = ghcWith deps;
db = "${ghc}/lib/ghc-${ghc.version}/package.conf.d";
flags = [ "-no-global-package-db" "-package-db ${db}" ] ++ (map (p: "-p ${p}") deps) ++ (map (d: "-i ${d}") dirs) ++ (map (e: "-X${e}") exts);
in
writeText "hie-bios-json" (
builtins.toJSON {
build_type = "hie-bios";
result = {
hie-bios_flags = flags;
};
}
);
};
{
inherit
inferBuild
inferGhci
buildAsExecutable
buildAsLibrary
executable
buildHoogle
buildHieBios
;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment