packages feed

fficxx-0.7.0.1: src/FFICXX/Generate/Code/Cabal.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module FFICXX.Generate.Code.Cabal where

import Data.Aeson.Encode.Pretty (encodePretty)
import qualified Data.ByteString.Lazy as BL
import Data.List (intercalate, nub)
import Data.Text (Text)
import qualified Data.Text as T (intercalate, pack, replicate, unlines)
import qualified Data.Text.IO as TIO (writeFile)
import qualified Data.Text.Lazy as TL (toStrict)
import Data.Text.Template (substitute)
import FFICXX.Generate.Type.Cabal
  ( AddCInc (..),
    AddCSrc (..),
    BuildType (..),
    Cabal (..),
    CabalName (..),
    GeneratedCabalInfo (..),
  )
import FFICXX.Generate.Type.Class (hasProxy)
import FFICXX.Generate.Type.Module
  ( ClassImportHeader (..),
    ClassModule (..),
    PackageConfig (..),
    TemplateClassImportHeader,
    TemplateClassModule (..),
    TopLevelImportHeader (..),
  )
import FFICXX.Generate.Util (contextT)
import FFICXX.Runtime.CodeGen.Cxx (HeaderName (..))
import System.FilePath ((<.>), (</>))

cabalIndentation :: Text
cabalIndentation = T.replicate 23 " "

unlinesWithIndent = T.unlines . map (cabalIndentation <>)

-- for source distribution
genCsrcFiles ::
  (TopLevelImportHeader, [ClassModule]) ->
  [AddCInc] ->
  [AddCSrc] ->
  [String]
genCsrcFiles (tih, cmods) acincs acsrcs =
  let selfheaders' = do
        x <- cmods
        let y = cmCIH x
        return (cihSelfHeader y)
      selfheaders = nub selfheaders'
      selfcpp' = do
        x <- cmods
        let y = cmCIH x
        return (cihSelfCpp y)
      selfcpp = nub selfcpp'
      tlh = tihHeaderFileName tih <.> "h"
      tlcpp = tihHeaderFileName tih <.> "cpp"
      includeFileStrsWithCsrc =
        map (\x -> "csrc" </> x) $
          (if (null . tihFuncs) tih then map unHdrName selfheaders else tlh : (map unHdrName selfheaders))
            ++ map (\(AddCInc hdr _) -> hdr) acincs
      cppFilesWithCsrc =
        map (\x -> "csrc" </> x) $
          (if (null . tihFuncs) tih then selfcpp else tlcpp : selfcpp)
            ++ map (\(AddCSrc src _) -> src) acsrcs
   in includeFileStrsWithCsrc <> cppFilesWithCsrc

-- for library
genIncludeFiles ::
  -- | package name
  String ->
  ([ClassImportHeader], [TemplateClassImportHeader]) ->
  [AddCInc] ->
  [String]
genIncludeFiles pkgname (cih, _tcih) acincs =
  let selfheaders = map cihSelfHeader cih
      includeFileStrs = map unHdrName (selfheaders ++ map (\(AddCInc hdr _) -> HdrName hdr) acincs)
   in (pkgname <> "Type.h") : includeFileStrs

-- for library
genCppFiles ::
  (TopLevelImportHeader, [ClassModule]) ->
  [AddCSrc] ->
  [String]
genCppFiles (tih, cmods) acsrcs =
  let selfcpp' = do
        x <- cmods
        let y = cmCIH x
        return (cihSelfCpp y)
      selfcpp = nub selfcpp'
      tlcpp = tihHeaderFileName tih <.> "cpp"
      cppFileStrs =
        map (\x -> "csrc" </> x) $
          (if (null . tihFuncs) tih then selfcpp else tlcpp : selfcpp)
            ++ map (\(AddCSrc src _) -> src) acsrcs
   in cppFileStrs

-- | generate exposed module list in cabal file
genExposedModules :: String -> ([ClassModule], [TemplateClassModule]) -> [String]
genExposedModules summarymod (cmods, tmods) =
  let cmodstrs = map cmModule cmods
      rawType = map ((<> ".RawType") . cmModule) cmods
      ffi = map ((<> ".FFI") . cmModule) cmods
      interface = map ((<> ".Interface") . cmModule) cmods
      cast = map ((<> ".Cast") . cmModule) cmods
      implementation = map ((<> ".Implementation") . cmModule) cmods
      proxy =
        map ((<> ".Proxy") . cmModule)
          . filter (hasProxy . cihClass . cmCIH)
          $ cmods
      template = map ((<> ".Template") . tcmModule) tmods
      th = map ((<> ".TH") . tcmModule) tmods
   in [summarymod, summarymod <> ".Ordinary", summarymod <> ".Template", summarymod <> ".TH"]
        <> cmodstrs
        <> rawType
        <> ffi
        <> interface
        <> cast
        <> implementation
        <> proxy
        <> template
        <> th

-- | generate other modules in cabal file
genOtherModules :: [ClassModule] -> [String]
genOtherModules _cmods = [""]

-- | generate additional package dependencies.
genPkgDeps :: [CabalName] -> [String]
genPkgDeps cs =
  [ "base > 4 && < 5",
    "fficxx-runtime >= 0.7.0.1",
    "template-haskell"
  ]
    ++ map unCabalName cs

-- |
cabalTemplate :: Text
cabalTemplate =
  "Cabal-version:  3.0\n\
  \Name:                $pkgname\n\
  \Version:     $version\n\
  \Synopsis:    $synopsis\n\
  \Description:         $description\n\
  \Homepage:       $homepage\n\
  \$licenseField\n\
  \$licenseFileField\n\
  \Author:              $author\n\
  \Maintainer:  $maintainer\n\
  \Category:       $category\n\
  \Tested-with:    GHC == 9.0.2 || == 9.2.4 || == 9.4.2 \n\
  \$buildtype\n\
  \Extra-source-files:\n\
  \$extraFiles\n\
  \$csrcFiles\n\
  \\n\
  \$sourcerepository\n\
  \\n\
  \Library\n\
  \  default-language: Haskell2010\n\
  \  hs-source-dirs: src\n\
  \  ghc-options:  -Wall -funbox-strict-fields -fno-warn-unused-do-bind -fno-warn-orphans -fno-warn-unused-imports\n\
  \  ghc-prof-options: -caf-all -auto-all\n\
  \  cxx-options: $cxxOptions\n\
  \  Build-Depends: $pkgdeps\n\
  \  Exposed-Modules:\n\
  \$exposedModules\n\
  \  Other-Modules:\n\
  \$otherModules\n\
  \  extra-lib-dirs: $extralibdirs\n\
  \  extra-libraries:    $extraLibraries\n\
  \  Include-dirs:       csrc $extraincludedirs\n\
  \  pkgconfig-depends: $pkgconfigDepends\n\
  \  Install-includes:\n\
  \$includeFiles\n\
  \  Cxx-sources:\n\
  \$cppFiles\n"

-- TODO: remove all T.pack after we switch over to Text
genCabalInfo ::
  Cabal ->
  String ->
  PackageConfig ->
  -- | extra libs
  [String] ->
  -- | cxx options
  [String] ->
  GeneratedCabalInfo
genCabalInfo cabal summarymodule pkgconfig extralibs cxxopts =
  let tih = pcfg_topLevelImportHeader pkgconfig
      classmodules = pcfg_classModules pkgconfig
      cih = pcfg_classImportHeaders pkgconfig
      tmods = pcfg_templateClassModules pkgconfig
      tcih = pcfg_templateClassImportHeaders pkgconfig
      acincs = pcfg_additional_c_incs pkgconfig
      acsrcs = pcfg_additional_c_srcs pkgconfig
      extrafiles = cabal_extrafiles cabal
   in GeneratedCabalInfo
        { gci_pkgname = T.pack (unCabalName (cabal_pkgname cabal)),
          gci_version = T.pack (cabal_version cabal),
          gci_synopsis = "",
          gci_description = "",
          gci_homepage = "",
          gci_license = maybe "" T.pack (cabal_license cabal),
          gci_licenseFile = maybe "" T.pack (cabal_licensefile cabal),
          gci_author = "",
          gci_maintainer = "",
          gci_category = "",
          gci_buildtype = case cabal_buildType cabal of
            Simple ->
              "Build-Type: Simple"
            Custom deps ->
              "Build-Type: Custom\ncustom-setup\n  setup-depends: "
                <> T.pack (intercalate ", " (map unCabalName deps))
                <> "\n",
          gci_extraFiles = map T.pack extrafiles,
          gci_csrcFiles = map T.pack $ genCsrcFiles (tih, classmodules) acincs acsrcs,
          gci_sourcerepository = "",
          gci_cxxOptions = map T.pack cxxopts,
          gci_pkgdeps = map T.pack $ genPkgDeps (cabal_additional_pkgdeps cabal),
          gci_exposedModules = map T.pack $ genExposedModules summarymodule (classmodules, tmods),
          gci_otherModules = map T.pack $ genOtherModules classmodules,
          gci_extraLibDirs = map T.pack $ cabal_extralibdirs cabal,
          gci_extraLibraries = map T.pack extralibs,
          gci_extraIncludeDirs = map T.pack $ cabal_extraincludedirs cabal,
          gci_pkgconfigDepends = map T.pack $ cabal_pkg_config_depends cabal,
          gci_includeFiles = map T.pack $ genIncludeFiles (unCabalName (cabal_pkgname cabal)) (cih, tcih) acincs,
          gci_cppFiles = map T.pack $ genCppFiles (tih, classmodules) acsrcs
        }

genCabalFile :: GeneratedCabalInfo -> Text
genCabalFile GeneratedCabalInfo {..} =
  TL.toStrict $
    substitute cabalTemplate $
      contextT
        [ ("licenseField", "license: " <> gci_license),
          ("licenseFileField", "license-file: " <> gci_licenseFile),
          ("pkgname", gci_pkgname),
          ("version", gci_version),
          ("buildtype", gci_buildtype),
          ("synopsis", gci_synopsis),
          ("description", gci_description),
          ("homepage", gci_homepage),
          ("author", gci_author),
          ("maintainer", gci_maintainer),
          ("category", gci_category),
          ("sourcerepository", gci_sourcerepository),
          ("cxxOptions", T.intercalate " " gci_cxxOptions),
          ("pkgdeps", T.intercalate ", " gci_pkgdeps),
          ("extraFiles", unlinesWithIndent gci_extraFiles),
          ("csrcFiles", unlinesWithIndent gci_csrcFiles),
          ("includeFiles", unlinesWithIndent gci_includeFiles),
          ("cppFiles", unlinesWithIndent gci_cppFiles),
          ("exposedModules", unlinesWithIndent gci_exposedModules),
          ("otherModules", unlinesWithIndent gci_otherModules),
          ("extralibdirs", T.intercalate ", " gci_extraLibDirs),
          ("extraincludedirs", T.intercalate ", " gci_extraIncludeDirs),
          ("extraLibraries", T.intercalate ", " gci_extraLibraries),
          ("cabalIndentation", cabalIndentation),
          ("pkgconfigDepends", T.intercalate ", " gci_pkgconfigDepends)
        ]

-- |
buildCabalFile ::
  Cabal ->
  String ->
  PackageConfig ->
  -- | Extra libs
  [String] ->
  -- | cxx options
  [String] ->
  -- | Cabal file path
  FilePath ->
  IO ()
buildCabalFile cabal summarymodule pkgconfig extralibs cxxopts cabalfile = do
  let cinfo = genCabalInfo cabal summarymodule pkgconfig extralibs cxxopts
      txt = genCabalFile cinfo
  TIO.writeFile cabalfile txt

-- |
buildJSONFile ::
  Cabal ->
  String ->
  PackageConfig ->
  -- | Extra libs
  [String] ->
  -- | cxx options
  [String] ->
  -- | JSON file path
  FilePath ->
  IO ()
buildJSONFile cabal summarymodule pkgconfig extralibs cxxopts jsonfile = do
  let cinfo = genCabalInfo cabal summarymodule pkgconfig extralibs cxxopts
  BL.writeFile jsonfile (encodePretty cinfo)