packages feed

cpkg-0.1.1.0: dhall/cpkg-prelude.dhall

{- Dhall prelude imports -}
let concatMapSep = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/Text/concatMapSep
in

let concatMapText = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/Text/concatMap
in

let concatMap = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/List/concatMap
in

let map = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/List/map
in

let mapOptional = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/Optional/map
in

let not = https://raw.githubusercontent.com/dhall-lang/dhall-lang/master/Prelude/Bool/not
in

{- Imported types -}
let types = ../dhall/cpkg-types.dhall
in

let showVersion =
  concatMapSep "." Natural Natural/show
in

let maybeAppend =
  λ(a : Type) →
  λ(x : Optional a) →
  λ(xs : List a) →
    Optional/fold a x (List a) (λ(x : a) → (xs # [x])) xs
in

{- Print the architecture for use with GCC -}
let printArch =
  λ(arch : types.Arch) →
    merge
      { X64         = λ(_ : {}) → "x86_64"
      , AArch       = λ(_ : {}) → "aarch64"
      , Arm         = λ(_ : {}) → "arm"
      , RISCV64     = λ(_ : {}) → "riscv64"
      , PowerPC     = λ(_ : {}) → "powerpc"
      , PowerPC64   = λ(_ : {}) → "powerpc64"
      , PowerPC64le = λ(_ : {}) → "powerpc64le"
      , Sparc64     = λ(_ : {}) → "sparc64"
      , S390x       = λ(_ : {}) → "s390x"
      , Alpha       = λ(_ : {}) → "alpha"
      , M68k        = λ(_ : {}) → "m68k"
      , Mips        = λ(_ : {}) → "mips"
      , MipsEl      = λ(_ : {}) → "mipsel"
      , Mips64      = λ(_ : {}) → "mips64"
      , Mips64El    = λ(_ : {}) → "mips64el"
      , X86         = λ(_ : {}) → "i686"
      , SH4         = λ(_ : {}) → "sh4"
      , HPPA        = λ(_ : {}) → "hppa"
      , HPPA64      = λ(_ : {}) → "hppa64"
      }
      arch
in

let printManufacturer =
  λ(x : types.Manufacturer) →
    merge
      { Unknown = λ(_ : {}) → "unknown"
      , Apple   = λ(_ : {}) → "apple"
      , IBM     = λ(_ : {}) → "ibm"
      , PC      = λ(_ : {}) → "pc"
      }
      x
in

let printOS =
  λ(os : types.OS) →
    merge
      { FreeBSD   = λ(_ : {}) → "freebsd"
      , OpenBSD   = λ(_ : {}) → "openbsd"
      , NetBSD    = λ(_ : {}) → "netbsd"
      , Solaris   = λ(_ : {}) → "solaris"
      , Dragonfly = λ(_ : {}) → "dragonfly"
      , Linux     = λ(_ : {}) → "linux"
      , Darwin    = λ(_ : {}) → "darwin"
      , Windows   = λ(_ : {}) → "w64"
      , Redox     = λ(_ : {}) → "redox"
      , Haiku     = λ(_ : {}) → "haiku"
      , IOS       = λ(_ : {}) → "darwin"
      , AIX       = λ(_ : {}) → "aix"
      , Hurd      = λ(_ : {}) → "hurd"
      , Android   = λ(_ : {}) → "android"
      , NoOs      = λ(_ : {}) → "none"
      }
      os
in

{- Print the ABI for use with GCC -}
let printABI =
  λ(os : types.ABI) →
    merge
      { GNU       = λ(_ : {}) → "gnu"
      , GNUabi64  = λ(_ : {}) → "gnuabi64"
      , GNUeabi   = λ(_ : {}) → "gnueabi"
      , GNUeabihf = λ(_ : {}) → "gnueabihf"
      , GNUspe    = λ(_ : {}) → "gnuspe"
      , MinGw     = λ(_ : {}) → "mingw32"
      }
      os
in

{- Print target triple for use with GCC -}
let printTargetTriple =
  λ(t : types.TargetTriple) →
    "${printArch t.arch}-${printOS t.os}" ++ Optional/fold types.ABI t.abi Text (λ(abi : types.ABI) → "-${printABI abi}") ""
in

{- Print --host flag for use with ./configure scripts -}
let mkHost =
  mapOptional types.TargetTriple Text (λ(tgt : types.TargetTriple) → "--host=${printTargetTriple tgt}")
in

{- Pick executable for 'make'. Use 'gmake' on BSDs, 'make' everywhere else. -}
let makeExe =
  λ(os : types.OS) →

    let gmake = λ(_ : {}) → "gmake"
    in
    let make  = λ(_ : {}) → "make"
    in

    merge
      { FreeBSD   = gmake
      , OpenBSD   = gmake
      , NetBSD    = gmake
      , Solaris   = gmake
      , Dragonfly = gmake
      , Linux     = make
      , Darwin    = make
      , Windows   = make
      , Redox     = make
      , Haiku     = make
      , IOS       = make
      , AIX       = make
      , Hurd      = make
      , Android   = make
      , NoOs      = make -- this is bad but it's meaningless in this context
      }
      os
in

let mkExe =
  λ(x : Text) →
    types.Command.MakeExecutable { file = x }
in

let mkExes =
  map Text types.Command mkExe
in

let writeFile =
  types.Command.Write
in

let defaultEnv =
  None (List types.EnvVar)
in

let defaultCall =
  { arguments = [] : List Text
  , environment = defaultEnv
  , procDir = None Text
  }
in

let call =
  types.Command.Call
in

let symlinkBinary =
  λ(file : Text) →
    types.Command.SymlinkBinary { file = file }
in

let symlink =
  λ(tgt : Text) →
  λ(lnk : Text) →
    types.Command.Symlink { tgt = tgt, linkName = lnk }
in

let copyFile =
  λ(src : Text) →
  λ(dest : Text) →
    types.Command.CopyFile { src = src, dest = dest }
in

let symlinkBinaries =
  map Text types.Command symlinkBinary
in

{- This is to be used on the build OS -}
let isUnix =
  λ(os : types.OS) →

    let true = λ(_ : {}) → True
    in
    let false = λ(_ : {}) → False
    in

    merge
      { FreeBSD   = true
      , OpenBSD   = true
      , NetBSD    = true
      , Solaris   = true
      , Dragonfly = true
      , Linux     = true
      , Darwin    = true
      , Windows   = false
      , Redox     = false
      , Haiku     = false
      , IOS       = true
      , AIX       = true
      , Hurd      = true
      , Android   = true
      , NoOs      = false -- bad but this should never happen
      }
      os
in

{- Environment variable LDFLAGS for a given configuration -}
let mkLDFlagsGeneral =
  λ(libDirs : List Text) →
  λ(linkLibs : List Text) →
    let flag0 = concatMapSep " " Text (λ(dir : Text) → "-L${dir}") libDirs
    in
    let flag1 = concatMapText Text (λ(dir : Text) → " -l${dir}") linkLibs
    let flag2 = concatMapText Text (λ(dir : Text) → " -Wl,-rpath-link,${dir}") libDirs
    in

    { var = "LDFLAGS", value = flag0 ++ flag1 ++ flag2 }
in

let mkLDFlags =
  λ(libDirs : List Text) →
    mkLDFlagsGeneral libDirs ([] : List Text)
in

let mkLDPath =
  λ(libDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → dir) libDirs
    in

    { var = "LD_LIBRARY_PATH", value = flag }
in

let mkLDRunPath =
  λ(libDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → dir) libDirs
    in

    { var = "LD_RUN_PATH", value = flag }
in

let mkStaPath =
  λ(libDirs : List Text) →
    let flag = concatMapText Text (λ(dir : Text) → "${dir}:") libDirs
    in

    { var = "LIBRARY_PATH", value = flag ++ "/usr/local/lib:/lib:/usr/lib" }
in

{- Get the host OS from a configuration. If we are not cross-compiling, we can
   just use the build OS as detected by cpkg
   -}
let osCfg =
  λ(cfg : types.BuildVars) →
    Optional/fold types.TargetTriple cfg.targetTriple types.OS
      (λ(tgt : types.TargetTriple) → tgt.os)
        cfg.buildOS
in

{- Get the host architecture for the build, using the method above -}
let archCfg =
  λ(cfg : types.BuildVars) →
    Optional/fold types.TargetTriple cfg.targetTriple types.Arch
      (λ(tgt : types.TargetTriple) → tgt.arch)
        cfg.buildArch
in


{- Used to set the PERL5LIB variable to the right thing. This is necessary since
   we install libraries to nonstandard locations.
   -}
let mkPerlLib =
  λ(x : { libDirs : List Text, perlVersion : List Natural, cfg : types.BuildVars }) →
    let os = x.cfg.buildOS
    in
    let arch = x.cfg.buildArch
    in
    let flag = concatMapSep ":" Text (λ(dir : Text) → dir ++ "/site_perl/${showVersion x.perlVersion}/${printArch arch}-${printOS os}/") x.libDirs
    in
    let major = Optional/fold Natural (List/head Natural x.perlVersion) Text (Natural/show) ""
    in

    { var = "PERL${major}LIB", value = flag }
in

let mkIncludePath =
  λ(incls : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → dir) incls
    in

    { var = "C_INCLUDE_PATH", value = flag }
in

let mkCFlags =
  λ(cfg : types.BuildVars) →
    let flag = concatMapSep " " Text (λ(dir : Text) → "-I${dir}") cfg.includeDirs
    in
    let staFlag =
      if cfg.static
        then " -static"
        else ""
    in

    { var = "CPPFLAGS", value = flag ++ staFlag }
in

let mkPkgConfigVar =
  λ(libDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → "${dir}/pkgconfig") libDirs
    in

    { var = "PKG_CONFIG_PATH", value = flag }
in

{- This is used for GLib/GTK/some of that stuff -}
let mkXdgDataDirs =
  λ(shareDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → dir) shareDirs
    in

    { var = "XDG_DATA_DIRS", value = flag }
in

let mkPathVar =
  λ(binDirs : List Text) →
    concatMapText Text (λ(dir : Text) → "${dir}:") binDirs
in

let defaultPath =
  λ(cfg : types.BuildVars) →
    if isUnix cfg.buildOS
      then [ { var = "PATH", value = mkPathVar cfg.binDirs ++ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" } ] : List types.EnvVar
      else [] : List types.EnvVar -- FIXME: handle non-unix case
in

{- Set either LD_LIBRARY_PATH or LIBRARY_PATH depending on whether we are doing
   a static build or not
   -}
let libPath =
  λ(cfg : types.BuildVars) →
    if cfg.static
      then
        mkStaPath cfg.linkDirs
      else
        mkLDPath cfg.linkDirs
in

{- This is vaguely terrible, but it's needed for GLib for some reason -}
let mkLDPreload =
  λ(libs : List Text) →
    let flag = concatMapSep " " Text (λ(lib : Text) → lib) libs
    in

    { var = "LD_PRELOAD", value = flag }
in

{- Default environment variables for a given configuration -}
let configEnv =
  λ(linkLibs : List Text) →
  λ(cfg : types.BuildVars) →
    defaultPath cfg # [ mkLDFlagsGeneral cfg.linkDirs linkLibs
                      , mkCFlags cfg
                      , mkPkgConfigVar (cfg.shareDirs # cfg.linkDirs)
                      , libPath cfg
                      , mkLDRunPath cfg.linkDirs
                      , mkPerlLib { libDirs = cfg.linkDirs, perlVersion = [5,28,1], cfg = cfg } -- TODO: take this as a parameter
                      ]
in

let buildEnv =
  λ(cfg : types.BuildVars) →
    defaultPath cfg # [ mkPkgConfigVar (cfg.shareDirs # cfg.linkDirs)
                      , mkPerlLib { libDirs = cfg.linkDirs, perlVersion = [5,28,1], cfg = cfg } -- TODO: take this as a parameter
                      , mkLDPath cfg.linkDirs
                      ]
in

let configSome =
  λ(linkLibs : List Text) →
  λ(cfg : types.BuildVars) →
    Some (configEnv linkLibs cfg)
in

{- The most general configuration setup. You probably want to use
   defaultConfigure
   -}
let generalConfigure =
  λ(envVars : List Text → types.BuildVars → Optional (List types.EnvVar)) →
  λ(filename : Text) →
  λ(linkLibs : List Text) →
  λ(extraFlags : List Text) →
  λ(cfg : types.BuildVars) →
    let maybeHost = mkHost cfg.targetTriple
    in
    let modifyArgs = maybeAppend Text maybeHost
    in

    [ mkExe filename
    , call (defaultCall ⫽ { program = "./${filename}"
                          , arguments = modifyArgs [ "--prefix=${cfg.installDir}" ] # extraFlags
                          , environment = envVars linkLibs cfg
                          })
    ]
in

let configWithEnv =
  λ(envVars : List Text → types.BuildVars → Optional (List types.EnvVar)) →
    generalConfigure envVars "configure" ([] : List Text) ([] : List Text)
in

let configureWithFlags =
  generalConfigure configSome "configure" ([] : List Text)
in

let defaultConfigure =
  configureWithFlags ([] : List Text)
in

let configureLinkExtraLibs =
  λ(linkLibs : List Text) →
    generalConfigure configSome "configure" linkLibs ([] : List Text)
in

let mkAclocalPath =
  λ(shareDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → "${dir}/aclocal:${dir}/autoconf/autoconf") shareDirs
    in

    { var = "ACLOCAL_PATH", value = flag }
in

let configureMkExesExtraFlags =
  λ(x : { bins : List Text, extraFlags : List Text }) →
  λ(cfg : types.BuildVars) →
    mkExes x.bins
      # configureWithFlags x.extraFlags cfg
in

let configureMkExes =
  λ(bins : List Text) →
    configureMkExesExtraFlags { bins = bins, extraFlags = ([] : List Text) }
in

let buildWith =
  λ(envs : List types.EnvVar) →
  λ(cfg : types.BuildVars) →
    [ call (defaultCall ⫽ { program = makeExe cfg.buildOS
                          , arguments = [ "-j${Natural/show cfg.cpus}" ]
                          , environment =
                              Some envs
                          })
    ]
in

let defaultBuild =
  λ(cfg : types.BuildVars) →
    buildWith (buildEnv cfg) cfg
in

let installWith =
  λ(envs : List types.EnvVar) →
  λ(cfg : types.BuildVars) →
    [ call (defaultCall ⫽ { program = makeExe cfg.buildOS
                          , arguments = [ "install" ]
                          , environment =
                              Some envs
                          })
    ]
in

let defaultInstall =
  λ(cfg : types.BuildVars) →
    installWith (buildEnv cfg) cfg
in

let installWithBinaries =
  λ(bins : List Text) →
  λ(installVars : types.BuildVars) →
    defaultInstall installVars
      # (if not installVars.isCross
            then symlinkBinaries bins
            else [] : List types.Command)
in

let unbounded =
  λ(x : Text) →
    { name = x
    , bound = types.VersionBound.NoBound {=}
    }
in

let lowerBound =
  λ(pkg : { name : Text, lower : List Natural }) →
    { name = pkg.name
    , bound = types.VersionBound.Lower { lower = pkg.lower }
    }
in

let upperBound =
  λ(pkg : { name : Text, upper : List Natural }) →
    { name = pkg.name
    , bound = types.VersionBound.Upper { upper = pkg.upper }
    }
in

let defaultPackage =
  { configureCommand = defaultConfigure
  , buildCommand     = defaultBuild
  , installCommand   = defaultInstall
  , pkgBuildDeps     = [] : List types.Dep
  , pkgDeps          = [] : List types.Dep
  , pkgStream        = True
  }
in

let simplePackage =
  λ(pkg : { name : Text, version : List Natural}) →
    defaultPackage ⫽
      { pkgName = pkg.name
      , pkgVersion = pkg.version
      , pkgSubdir = "${pkg.name}-${showVersion pkg.version}"
      }
in

let makeGnuExe =
  λ(pkg : { name : Text, version : List Natural}) →
    simplePackage pkg ⫽
      { pkgUrl = "https://ftp.gnu.org/gnu/${pkg.name}/${pkg.name}-${showVersion pkg.version}.tar.xz"
      , installCommand = installWithBinaries [ "bin/${pkg.name}" ]
      }
in

let makeGnuLibrary =
  λ(pkg : { name : Text, version : List Natural}) →
    simplePackage pkg ⫽
      { pkgUrl = "https://mirrors.ocf.berkeley.edu/gnu/lib${pkg.name}/lib${pkg.name}-${showVersion pkg.version}.tar.xz"
      , pkgSubdir = "lib${pkg.name}-${showVersion pkg.version}"
      }
in

let createDir =
  λ(x : Text) →
    types.Command.CreateDirectory { dir = x }
in

let printCMakeOS =
  λ(os : types.OS) →
    merge
      { FreeBSD   = λ(_ : {}) → "BSD"
      , OpenBSD   = λ(_ : {}) → "BSD"
      , NetBSD    = λ(_ : {}) → "BSD"
      , Solaris   = λ(_ : {}) → "Solaris"
      , Dragonfly = λ(_ : {}) → "BSD"
      , Linux     = λ(_ : {}) → "Linux"
      , Darwin    = λ(_ : {}) → "Darwin"
      , Windows   = λ(_ : {}) → "Windows"
      , Redox     = λ(_ : {}) → "Redox"
      , Haiku     = λ(_ : {}) → "Haiku"
      , IOS       = λ(_ : {}) → "Darwin"
      , AIX       = λ(_ : {}) → "AIX"
      , Hurd      = λ(_ : {}) → "Hurd"
      , Android   = λ(_ : {}) → "Android"
      , NoOs      = λ(_ : {}) → "Generic"
      }
      os
in

let cmakeConfigureGeneral =
  λ(envVars : types.BuildVars → Optional (List types.EnvVar)) →
  λ(flags : List Text) →
  λ(cfg : types.BuildVars) →
    let host =
      Optional/fold types.TargetTriple cfg.targetTriple (List Text)
        (λ(tgt : types.TargetTriple) → ["-DCMAKE_C_COMPILER=${printTargetTriple tgt}-gcc", "-DCMAKE_CXX_COMPILER=${printTargetTriple tgt}-g++"])
          (["-DCMAKE_C_COMPILER=cc", "-DCMAKE_CXX_COMPILER=c++"])
    in
    let system =
      Optional/fold types.TargetTriple cfg.targetTriple (List Text)
        (λ(tgt : types.TargetTriple) → ["-DCMAKE_SYSTEM_NAME=${printCMakeOS tgt.os}"])
          ([] : List Text)
    in

    [ createDir "build"
    , call { program = "cmake"
           , arguments = [ "../", "-DCMAKE_INSTALL_PREFIX:PATH=${cfg.installDir}", "-DCMAKE_MAKE_PROGRAM=${makeExe cfg.buildOS}" ] # host # system # flags
           , environment = envVars cfg
           , procDir = Some "build"
           }
    ]
in

let cmakeEnv =
  λ(cfg : types.BuildVars) →
    [ mkPkgConfigVar cfg.shareDirs
    , { var = "CMAKE_INCLUDE_PATH", value = (mkIncludePath cfg.includeDirs).value }
    , { var = "CMAKE_LIBRARY_PATH", value = (libPath cfg).value }
    ]
      # defaultPath cfg
in

let cmakeSome =
  λ(cfg : types.BuildVars) →
    Some (cmakeEnv cfg)
in

let cmakeConfigureWithFlags =
  cmakeConfigureGeneral cmakeSome
in

let cmakeConfigure =
  cmakeConfigureWithFlags ([] : List Text)
in

let cmakeConfigureNinja =
  λ(cfg : types.BuildVars) →
    let host =
      Optional/fold types.TargetTriple cfg.targetTriple (List Text)
        (λ(tgt : types.TargetTriple) → ["-DCMAKE_C_COMPILER=${printTargetTriple tgt}-gcc", "-DCMAKE_CXX_COMPILER=${printTargetTriple tgt}-g++"])
          ([] : List Text)
    in

    let system =
      Optional/fold types.TargetTriple cfg.targetTriple (List Text)
        (λ(tgt : types.TargetTriple) → ["-DCMAKE_SYSTEM_NAME=${printCMakeOS tgt.os}"])
          ([] : List Text)
    in

    [ createDir "build"
    , call { program = "cmake"
           , arguments = [ "../", "-DCMAKE_INSTALL_PREFIX:PATH=${cfg.installDir}", "-G", "Ninja" ] # host # system
           , environment = defaultEnv
           , procDir = Some "build"
           }
    ]
in

let perlConfigure =
  λ(cfg : types.BuildVars) →

  [ call { program = "perl"
         , arguments = [ "Makefile.PL", "PREFIX=${cfg.installDir}" ]
         , environment = defaultEnv
         , procDir = None Text
         }
  ]
in

let cmakeBuild =
  λ(cfg : types.BuildVars) →
    [ call { program = "cmake"
           , arguments = [ "--build", ".", "--config", "Release", "--", "-j", Natural/show cfg.cpus ]
           , environment = defaultEnv
           , procDir = Some "build"
           }
    ]
in

let cmakeInstall =
  λ(cfg : types.BuildVars) →
    [ call { program = "cmake"
           , arguments = [ "--build", ".", "--target", "install", "--config", "Release" ]
           , environment = defaultEnv
           , procDir = Some "build"
           }
    ]
in

let cmakeInstallWithBinaries =
  λ(bins : List Text) →
  λ(installVars : types.BuildVars) →
    cmakeInstall installVars
      # symlinkBinaries bins
in

let cmakePackage =
  defaultPackage ⫽
    { configureCommand = cmakeConfigure
    , buildCommand     = cmakeBuild
    , installCommand   = cmakeInstall
    , pkgBuildDeps     = [ unbounded "cmake" ]
    }
in

let autogenConfigure =
  λ(cfg : types.BuildVars) →
    [ mkExe "autogen.sh"
    , call (defaultCall ⫽ { program = "./autogen.sh"
                          , environment = Some ([ mkAclocalPath cfg.shareDirs ]
                                                  # defaultPath cfg)
                          })
    ] # defaultConfigure cfg
in

let fullVersion =
  λ(x : { version : List Natural, patch : Natural }) →
    x.version # [x.patch]
in

let mkPyPath =
  λ(version : List Natural) →
  λ(libDirs : List Text) →
    let flag = concatMapSep ":" Text (λ(dir : Text) → "${dir}/python${showVersion version}/site-packages") libDirs
    in

    { var = "PYTHONPATH", value = flag }
in

let mkPy3Path =
  mkPyPath [3,7]
in

{- Write a cross-compilation configuration file for use with meson -}
let mesonCfgFile =
  λ(cfg : types.BuildVars) →
    let prefix =
      Optional/fold types.TargetTriple cfg.targetTriple Text
        (λ(tgt : types.TargetTriple) → "${printTargetTriple tgt}-")
          ""
    in

    "[binaries]\n" ++
    "c = '${prefix}gcc'\n" ++ -- FIXME: default to cc/c++ when no cfg.targetTriple is passed
    "cpp = '${prefix}g++'\n" ++
    "ar = '${prefix}ar'\n" ++
    "strip = '${prefix}strip'\n" ++
    "pkgconfig = 'pkg-config'\n" ++

    "[host_machine]\n" ++
    "system = '${printOS (osCfg cfg)}'\n" ++ -- TODO: printOSMeson function (w64 -> windows)
    "cpu_family = '${printArch (archCfg cfg)}'\n" ++
    "cpu = '${printArch (archCfg cfg)}'\n" ++
    "endian = 'little'" -- FIXME parse endianness in Haskell library?
in

let mesonEnv =
  λ(cfg : types.BuildVars) →
    Some [ mkPkgConfigVar cfg.linkDirs
         , { var = "PATH", value = mkPathVar cfg.binDirs ++ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
         , mkPy3Path cfg.linkDirs
         , libPath cfg
         , mkLDRunPath cfg.linkDirs
         , mkLDFlags cfg.linkDirs
         , mkCFlags cfg
         ]
in

let mesonConfigureGeneral =
  λ(envs : types.BuildVars → Optional (List types.EnvVar)) →
  λ(flags : List Text) →
  λ(cfg : types.BuildVars) →
    let crossArgs =
      if cfg.isCross
        then [ "--cross-file", "cross.txt" ]
        else [] : List Text
    in

    [ createDir "build"
    , writeFile { file = "build/cross.txt", contents = mesonCfgFile cfg }
    , call { program = "meson"
           , arguments = [ "--prefix=${cfg.installDir}", ".." ] # crossArgs # flags
           , environment = envs cfg
           , procDir = Some "build"
           }
    ]
in

let mesonConfigureWithFlags =
  mesonConfigureGeneral mesonEnv
in

let mesonConfigure =
  mesonConfigureWithFlags ([] : List Text)
in

let ninjaBuildWith =
  λ(linkLibs : List Text) →
  λ(cfg : types.BuildVars) →
    [ call (defaultCall ⫽ { program = "ninja"
                          , environment = Some [ mkPkgConfigVar cfg.linkDirs
                                               , { var = "PATH", value = mkPathVar cfg.binDirs ++ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
                                               , mkPy3Path cfg.linkDirs
                                               , libPath cfg
                                               , mkLDRunPath cfg.linkDirs
                                               , mkLDFlagsGeneral cfg.linkDirs linkLibs
                                               , mkCFlags cfg
                                               , mkPkgConfigVar cfg.linkDirs
                                               ]
                          , procDir = Some "build" }) ]
in

let ninjaBuild =
  ninjaBuildWith ([] : List Text)
in

let ninjaInstall =
  λ(cfg : types.BuildVars) →
    [ call (defaultCall ⫽ { program = "ninja"
                          , environment = Some [ mkPkgConfigVar cfg.linkDirs
                                               , { var = "PATH", value = mkPathVar cfg.binDirs ++ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
                                               , mkPy3Path cfg.linkDirs
                                               , libPath cfg
                                               , mkLDRunPath cfg.linkDirs
                                               , mkLDFlags cfg.linkDirs
                                               , mkCFlags cfg
                                               ]
                          , arguments = [ "install" ]
                          , procDir = Some "build"
                          })
    ]
in

let ninjaPackage =
  λ(x : { name : Text, version : List Natural }) →
    simplePackage x ⫽
      { configureCommand = mesonConfigure
      , buildCommand = ninjaBuild
      , installCommand = ninjaInstall
      , pkgBuildDeps = [ unbounded "meson"
                       , unbounded "ninja"
                       ]
      }
in

let copyFiles =
  map { src : Text, dest : Text } types.Command types.Command.CopyFile
in

let ninjaInstallWithPkgConfig =
  λ(fs : List { src : Text, dest : Text }) →
  λ(cfg : types.BuildVars) →
    ninjaInstall cfg # copyFiles fs
in

let doNothing =
  λ(_ : types.BuildVars) → [] : List types.Command
in

let mesonMoves =
  map Text { src : Text, dest : Text} (λ(pcFile : Text) → { src = "build/meson-private/${pcFile}", dest = "lib/pkgconfig/${pcFile}" })
in

let pythonBuild =
  λ(version : List Natural) →
  λ(cfg : types.BuildVars) →
    let major = Optional/fold Natural (List/head Natural version) Text (Natural/show) ""
    in
    let versionString = showVersion version
    in
    [ createDir "${cfg.installDir}/lib/python${versionString}/site-packages"
    , call (defaultCall ⫽ { program = "python${major}"
                          , arguments = [ "setup.py", "build" ]
                          , environment = Some ([ { var = "PYTHONPATH", value = "${cfg.installDir}/lib/python${versionString}/site-packages" }
                                                  , mkPkgConfigVar cfg.linkDirs
                                                  , libPath cfg
                                                  ] # defaultPath cfg)
                          })
    ]
in

let pythonInstall =
  λ(version : List Natural) →
  λ(cfg : types.BuildVars) →
    let major = Optional/fold Natural (List/head Natural version) Text (Natural/show) ""
    in
    let versionString = showVersion version
    in
    [ createDir "${cfg.installDir}/lib/python${versionString}/site-packages"
    , call (defaultCall ⫽ { program = "python${major}"
                          , arguments = [ "setup.py", "install", "--prefix=${cfg.installDir}", "--optimize=1" ]
                          , environment = Some ([ { var = "PYTHONPATH", value = "${cfg.installDir}/lib/python${versionString}/site-packages" }
                                                , mkPkgConfigVar cfg.linkDirs
                                                , libPath cfg
                                                ] # defaultPath cfg)
                          })
    ]
in

let pythonPackage =
  λ(pyVersion : List Natural) →
  λ(x : { name : Text, version : List Natural }) →
    let major = Optional/fold Natural (List/head Natural pyVersion) Text (Natural/show) ""
    in
    simplePackage x ⫽
      { configureCommand = doNothing
      , buildCommand = pythonBuild pyVersion
      , installCommand = pythonInstall pyVersion
      , pkgBuildDeps = [ unbounded "python${major}" ]
      }
in

let python3Build =
  pythonBuild [3,7]
in

let python3Install =
  pythonInstall [3,7]
in

let python3Package =
  pythonPackage [3,7]
in

let python2Package =
  pythonPackage [2,7]
in

let mkCCVar =
  λ(cfg : types.BuildVars) →
    Optional/fold types.TargetTriple cfg.targetTriple (List types.EnvVar)
      (λ(tgt : types.TargetTriple) → [{ var = "CC", value = "${printTargetTriple tgt}-gcc" }])
        ([] : List types.EnvVar)
in

let squishVersion =
  concatMapText Natural Natural/show
in

let mkCCArg =
  λ(cfg : types.BuildVars) →
    Optional/fold types.TargetTriple cfg.targetTriple (List Text)
      (λ(tgt : types.TargetTriple) → ["CC=${printTargetTriple tgt}-gcc"])
        ([] : List Text)
in

let preloadEnv =
  λ(_ : List Text) →
  λ(cfg : types.BuildVars) →
    Some (defaultPath cfg # [ mkLDFlags cfg.linkDirs
                            , mkCFlags cfg
                            , mkPkgConfigVar cfg.linkDirs
                            , libPath cfg
                            , mkXdgDataDirs cfg.shareDirs
                            , mkLDPreload cfg.preloadLibs
                            , mkPerlLib { libDirs = cfg.linkDirs, perlVersion = [5,28,1], cfg = cfg } -- TODO: take this as a parameter
                            ])
in

let preloadCfg =
  generalConfigure preloadEnv "configure" ([] : List Text) ([] : List Text)
in

let printEnvVar =
  λ(var : types.EnvVar) →
    "${var.var}=${var.value}"
in

{- This writes + installs a shell script that allows you to use programs
   installed via cpkg with the appropriate environment variables set, since of
   course we install in nonstandard locations
   -}
let mkPyWrapper =
  λ(version : List Natural) →
  λ(binName : Text) →
  λ(cfg : types.BuildVars) →
    let wrapperContents = "${printEnvVar (libPath cfg)} ${printEnvVar (mkPyPath version cfg.linkDirs)}:${cfg.installDir}/lib/python${showVersion version}/site-packages ${cfg.installDir}/bin/${binName} $@"
    in
    let wrapped = "wrapper/${binName}"
    in

    [ createDir "wrapper"
    , writeFile { file = wrapped, contents = wrapperContents }
    , mkExe wrapped
    , copyFile wrapped wrapped
    , symlinkBinary wrapped
    ]
in

let mkPy3Wrapper =
  mkPyWrapper [3,7]
in

let mkPy2Wrapper =
  mkPyWrapper [2,7]
in

let installWithPyWrappers =
  λ(version : List Natural) →
  λ(binNames : List Text) →
  λ(cfg : types.BuildVars) →
    pythonInstall version cfg
      # concatMap Text types.Command (λ(bin : Text) → mkPyWrapper version bin cfg) binNames
in

let installWithPy3Wrappers =
  installWithPyWrappers [3,7]
in

{- This is used to make bash scripts that wrap an executable. Since executables
   are linked against libraries installed in nonstandard places, we wrap them
   with a shell script that sets LD_LIBRARY_PATH appropriately.

   For an example use, see the emacs package.
   -}
let mkLDPathWrapper =
  λ(cfg : types.BuildVars) →
  λ(binName : Text) →
    let wrapper = "${printEnvVar (mkLDPath cfg.linkDirs)}:${cfg.installDir}/lib LD_PRELOAD='${(mkLDPreload cfg.preloadLibs).value}' ${cfg.installDir}/bin/${binName} $@"
    in
    let wrapped = "wrapper/${binName}"
    in

    [ createDir "wrapper"
    , writeFile { file = wrapped, contents = wrapper }
    , mkExe wrapped
    , copyFile wrapped wrapped
    , symlinkBinary wrapped
    ]
in

let installWithWrappers =
  λ(bins : List Text) →
  λ(cfg : types.BuildVars) →
    defaultInstall cfg #
      concatMap Text types.Command (λ(bin : Text) → mkLDPathWrapper cfg bin) bins
      -- TODO: add to PATH for e.g. PERL interpreter
in

let underscoreVersion =
  concatMapSep "_" Natural Natural/show
in

let isX64 =
  let true = λ(_ : {}) → True
  in
  let false = λ(_ : {}) → False
  in
  λ(arch : types.Arch) →
    merge
      { X64         = true
      , AArch       = false
      , Arm         = false
      , RISCV64     = false
      , PowerPC     = false
      , PowerPC64   = false
      , PowerPC64le = false
      , Sparc64     = false
      , S390x       = false
      , Alpha       = false
      , M68k        = false
      , Mips        = false
      , MipsEl      = false
      , Mips64      = false
      , Mips64El    = false
      , X86         = false
      , SH4         = false
      , HPPA        = false
      , HPPA64      = false
      }
      arch
in

{ showVersion         = showVersion
, makeGnuLibrary      = makeGnuLibrary
, makeGnuExe          = makeGnuExe
, defaultPackage      = defaultPackage
, unbounded           = unbounded
, lowerBound          = lowerBound
, upperBound          = upperBound
, makeExe             = makeExe
, printArch           = printArch
, printManufacturer   = printManufacturer
, printOS             = printOS
, printTargetTriple   = printTargetTriple
, call                = call
, mkExe               = mkExe
, mkExes              = mkExes
, createDir           = createDir
, mkHost              = mkHost
, defaultConfigure    = defaultConfigure
, defaultBuild        = defaultBuild
, defaultInstall      = defaultInstall
, cmakeConfigure      = cmakeConfigure
, cmakeConfigureGeneral = cmakeConfigureGeneral
, cmakeConfigureWithFlags = cmakeConfigureWithFlags
, cmakeBuild          = cmakeBuild
, cmakeInstall        = cmakeInstall
, cmakePackage        = cmakePackage
, autogenConfigure    = autogenConfigure
, defaultCall         = defaultCall
, defaultEnv          = defaultEnv
, maybeAppend         = maybeAppend
, mkCFlags            = mkCFlags
, mkLDFlags           = mkLDFlags
, mkLDPath            = mkLDPath
, mkLDRunPath         = mkLDRunPath
, mkStaPath           = mkStaPath
, libPath             = libPath
, mkPyPath            = mkPyPath
, mkPy3Path           = mkPy3Path
, mkIncludePath       = mkIncludePath
, isUnix              = isUnix
, defaultPath         = defaultPath
, simplePackage       = simplePackage
, symlinkBinary       = symlinkBinary
, symlink             = symlink
, symlinkBinaries     = symlinkBinaries
, installWithBinaries = installWithBinaries
, configureMkExes     = configureMkExes
, generalConfigure    = generalConfigure
, configureWithFlags  = configureWithFlags
, configureMkExesExtraFlags = configureMkExesExtraFlags
, writeFile           = writeFile
, cmakeInstallWithBinaries = cmakeInstallWithBinaries
, copyFile            = copyFile
, mkPathVar           = mkPathVar
, mkPkgConfigVar      = mkPkgConfigVar
, fullVersion         = fullVersion
, mesonConfigure      = mesonConfigure
, mesonConfigureGeneral = mesonConfigureGeneral
, mesonEnv            = mesonEnv
, mesonConfigureWithFlags = mesonConfigureWithFlags
, ninjaBuild          = ninjaBuild
, ninjaInstall        = ninjaInstall
, ninjaInstallWithPkgConfig = ninjaInstallWithPkgConfig
, ninjaPackage        = ninjaPackage
, doNothing           = doNothing
, perlConfigure       = perlConfigure
, copyFiles           = copyFiles
, mkPerlLib           = mkPerlLib
, mesonMoves          = mesonMoves
, python3Build        = python3Build
, python3Install      = python3Install
, python3Package      = python3Package
, mkLDPreload         = mkLDPreload
, configureLinkExtraLibs = configureLinkExtraLibs
, mkXdgDataDirs       = mkXdgDataDirs
, buildWith           = buildWith
, installWith         = installWith
, mkCCVar             = mkCCVar
, squishVersion       = squishVersion
, osCfg               = osCfg
, archCfg             = archCfg
, mkCCArg             = mkCCArg
, mesonCfgFile        = mesonCfgFile
, python2Package      = python2Package
, configEnv           = configEnv
, configSome          = configSome
, preloadEnv          = preloadEnv
, preloadCfg          = preloadCfg
, printEnvVar         = printEnvVar
, mkPyWrapper         = mkPyWrapper
, mkPy3Wrapper        = mkPy3Wrapper
, mkPy2Wrapper        = mkPy2Wrapper
, installWithPyWrappers = installWithPyWrappers
, installWithPy3Wrappers = installWithPy3Wrappers
, cmakeConfigureNinja = cmakeConfigureNinja
, mkLDPathWrapper     = mkLDPathWrapper
, installWithWrappers = installWithWrappers
, cmakeEnv            = cmakeEnv
, cmakeSome           = cmakeSome
, underscoreVersion   = underscoreVersion
, isX64               = isX64
, configWithEnv       = configWithEnv
, buildEnv            = buildEnv
}