Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foreign Library Stanza #518

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,16 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
| `reexported-modules` | · | | |
| `signatures` | · | | |

#### Foreign Library

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
| `type` | `type` | | |
| `lib-version-info` | `lib-version-info` | | |
| `options` | `options` | | [https://cabal.readthedocs.io/en/3.4/cabal-package.html#pkg-field-foreign-library-options](Foreign Library Options) |
| `other-modules` | · | All modules in `source-dirs` less `main` less any modules mentioned in `when` | |
| `generated-other-modules` | | | Added to `other-modules` and `autogen-modules`. Since `0.23.0`.

#### Executable fields

| Hpack | Cabal | Default | Notes |
Expand Down
104 changes: 101 additions & 3 deletions src/Hpack/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ module Hpack.Config (
, Section(..)
, Library(..)
, Executable(..)
, ForeignLibrary(..)
, Conditional(..)
, Cond(..)
, Flag(..)
Expand Down Expand Up @@ -156,6 +157,7 @@ package name version = Package {
, packageCustomSetup = Nothing
, packageLibrary = Nothing
, packageInternalLibraries = mempty
, packageForeignLibraries = mempty
, packageExecutables = mempty
, packageTests = mempty
, packageBenchmarks = mempty
Expand All @@ -165,6 +167,7 @@ package name version = Package {
renamePackage :: String -> Package -> Package
renamePackage name p@Package{..} = p {
packageName = name
, packageForeignLibraries = fmap (renameDependencies packageName name) packageForeignLibraries
, packageExecutables = fmap (renameDependencies packageName name) packageExecutables
, packageTests = fmap (renameDependencies packageName name) packageTests
, packageBenchmarks = fmap (renameDependencies packageName name) packageBenchmarks
Expand All @@ -183,6 +186,7 @@ renameDependencies old new sect@Section{..} = sect {sectionDependencies = (Depen
packageDependencies :: Package -> [(String, DependencyInfo)]
packageDependencies Package{..} = nub . sortBy (comparing (lexicographically . fst)) $
(concatMap deps packageExecutables)
++ (concatMap deps packageForeignLibraries)
++ (concatMap deps packageTests)
++ (concatMap deps packageBenchmarks)
++ maybe [] deps packageLibrary
Expand Down Expand Up @@ -226,6 +230,27 @@ instance Semigroup LibrarySection where
, librarySectionSignatures = librarySectionSignatures a <> librarySectionSignatures b
}

data ForeignLibrarySection = ForeignLibrarySection {
foreignLibrarySectionType :: Last String
, foreignLibrarySectionLibVersionInfo :: Last String
, foreignLibrarySectionOptions :: Maybe (List String)
, foreignLibrarySectionOtherModules :: Maybe (List Module)
, foreignLibrarySectionGeneratedOtherModules :: Maybe (List Module)
} deriving (Eq, Show, Generic, FromValue)

instance Monoid ForeignLibrarySection where
mempty = ForeignLibrarySection mempty mempty Nothing Nothing Nothing
mappend = (<>)

instance Semigroup ForeignLibrarySection where
a <> b = ForeignLibrarySection {
foreignLibrarySectionType = foreignLibrarySectionType a <> foreignLibrarySectionType b
, foreignLibrarySectionLibVersionInfo = foreignLibrarySectionLibVersionInfo a <> foreignLibrarySectionLibVersionInfo b
, foreignLibrarySectionOptions = foreignLibrarySectionOptions a <> foreignLibrarySectionOptions b
, foreignLibrarySectionOtherModules = foreignLibrarySectionOtherModules a <> foreignLibrarySectionOtherModules b
, foreignLibrarySectionGeneratedOtherModules = foreignLibrarySectionGeneratedOtherModules a <> foreignLibrarySectionGeneratedOtherModules b
}

data ExecutableSection = ExecutableSection {
executableSectionMain :: Alias 'True "main-is" (Last FilePath)
, executableSectionOtherModules :: Maybe (List Module)
Expand Down Expand Up @@ -548,10 +573,12 @@ type SectionConfigWithDefaults cSources cxxSources jsSources a = Product Default

type PackageConfigWithDefaults cSources cxxSources jsSources = PackageConfig_
(SectionConfigWithDefaults cSources cxxSources jsSources LibrarySection)
(SectionConfigWithDefaults cSources cxxSources jsSources ForeignLibrarySection)
(SectionConfigWithDefaults cSources cxxSources jsSources ExecutableSection)

type PackageConfig cSources cxxSources jsSources = PackageConfig_
(WithCommonOptions cSources cxxSources jsSources LibrarySection)
(WithCommonOptions cSources cxxSources jsSources ForeignLibrarySection)
(WithCommonOptions cSources cxxSources jsSources ExecutableSection)

data PackageVersion = PackageVersion {unPackageVersion :: String}
Expand All @@ -562,7 +589,7 @@ instance FromValue PackageVersion where
String s -> return (T.unpack s)
_ -> typeMismatch "Number or String" v

data PackageConfig_ library executable = PackageConfig {
data PackageConfig_ library foreignLib executable = PackageConfig {
packageConfigName :: Maybe String
, packageConfigVersion :: Maybe PackageVersion
, packageConfigSynopsis :: Maybe String
Expand All @@ -588,6 +615,8 @@ data PackageConfig_ library executable = PackageConfig {
, packageConfigCustomSetup :: Maybe CustomSetupSection
, packageConfigLibrary :: Maybe library
, packageConfigInternalLibraries :: Maybe (Map String library)
, packageConfigForeignLibraries :: Maybe (Map String foreignLib)
, packageConfigForeignLibrary :: Maybe foreignLib
, packageConfigExecutable :: Maybe executable
, packageConfigExecutables :: Maybe (Map String executable)
, packageConfigTests :: Maybe (Map String executable)
Expand Down Expand Up @@ -616,13 +645,17 @@ traversePackageConfig :: Traversal PackageConfig
traversePackageConfig t p@PackageConfig{..} = do
library <- traverse (traverseWithCommonOptions t) packageConfigLibrary
internalLibraries <- traverseNamedConfigs t packageConfigInternalLibraries
foreignLibrary <- traverse (traverseWithCommonOptions t) packageConfigForeignLibrary
foreignLibraries <- traverseNamedConfigs t packageConfigForeignLibraries
executable <- traverse (traverseWithCommonOptions t) packageConfigExecutable
executables <- traverseNamedConfigs t packageConfigExecutables
tests <- traverseNamedConfigs t packageConfigTests
benchmarks <- traverseNamedConfigs t packageConfigBenchmarks
return p {
packageConfigLibrary = library
, packageConfigInternalLibraries = internalLibraries
, packageConfigForeignLibrary = foreignLibrary
, packageConfigForeignLibraries = foreignLibraries
, packageConfigExecutable = executable
, packageConfigExecutables = executables
, packageConfigTests = tests
Expand Down Expand Up @@ -705,6 +738,7 @@ addPathsModuleToGeneratedModules pkg cabalVersion
| otherwise = pkg {
packageLibrary = fmap mapLibrary <$> packageLibrary pkg
, packageInternalLibraries = fmap mapLibrary <$> packageInternalLibraries pkg
, packageForeignLibraries = fmap mapForeignLibrary <$> packageForeignLibraries pkg
, packageExecutables = fmap mapExecutable <$> packageExecutables pkg
, packageTests = fmap mapExecutable <$> packageTests pkg
, packageBenchmarks = fmap mapExecutable <$> packageBenchmarks pkg
Expand All @@ -721,6 +755,15 @@ addPathsModuleToGeneratedModules pkg cabalVersion
where
generatedModules = libraryGeneratedModules lib

mapForeignLibrary :: ForeignLibrary -> ForeignLibrary
mapForeignLibrary foreignLibrary
| pathsModule `elem` foreignLibraryOtherModules foreignLibrary = foreignLibrary {
foreignLibraryGeneratedModules = if pathsModule `elem` generatedModules then generatedModules else pathsModule : generatedModules
}
| otherwise = foreignLibrary
where
generatedModules = foreignLibraryGeneratedModules foreignLibrary

mapExecutable :: Executable -> Executable
mapExecutable executable
| pathsModule `elem` executableOtherModules executable = executable {
Expand Down Expand Up @@ -777,6 +820,7 @@ determineCabalVersion inferredLicense pkg@Package{..} = (
packageCabalVersion
, packageLibrary >>= libraryCabalVersion
, internalLibsCabalVersion packageInternalLibraries
, foreignLibsCabalVersion packageForeignLibraries
, executablesCabalVersion packageExecutables
, executablesCabalVersion packageTests
, executablesCabalVersion packageBenchmarks
Expand Down Expand Up @@ -811,12 +855,24 @@ determineCabalVersion inferredLicense pkg@Package{..} = (
executablesCabalVersion :: Map String (Section Executable) -> Maybe Version
executablesCabalVersion = foldr max Nothing . map executableCabalVersion . Map.elems

foreignLibsCabalVersion :: Map String (Section ForeignLibrary) -> Maybe Version
foreignLibsCabalVersion = foldr max Nothing . map foreignLibCabalVersion . Map.elems

foreignLibCabalVersion :: Section ForeignLibrary -> Maybe Version
foreignLibCabalVersion sect = maximum [
makeVersion [2,0] <$ guard (foreignLibraryHasGeneratedModules sect)
, sectionCabalVersion (concatMap getForeignLibraryModules) sect
]

executableCabalVersion :: Section Executable -> Maybe Version
executableCabalVersion sect = maximum [
makeVersion [2,0] <$ guard (executableHasGeneratedModules sect)
, sectionCabalVersion (concatMap getExecutableModules) sect
]

foreignLibraryHasGeneratedModules :: Section ForeignLibrary -> Bool
foreignLibraryHasGeneratedModules = any (not . null . foreignLibraryGeneratedModules)

executableHasGeneratedModules :: Section Executable -> Bool
executableHasGeneratedModules = any (not . null . executableGeneratedModules)

Expand Down Expand Up @@ -957,6 +1013,7 @@ data Package = Package {
, packageCustomSetup :: Maybe CustomSetup
, packageLibrary :: Maybe (Section Library)
, packageInternalLibraries :: Map String (Section Library)
, packageForeignLibraries :: Map String (Section ForeignLibrary)
, packageExecutables :: Map String (Section Executable)
, packageTests :: Map String (Section Executable)
, packageBenchmarks :: Map String (Section Executable)
Expand All @@ -977,6 +1034,14 @@ data Library = Library {
, librarySignatures :: [String]
} deriving (Eq, Show)

data ForeignLibrary = ForeignLibrary {
foreignLibraryType :: Maybe String
, foreignLibraryLibVersionInfo :: Maybe String
, foreignLibraryOptions :: Maybe [String]
, foreignLibraryOtherModules :: [Module]
, foreignLibraryGeneratedModules :: [Module]
} deriving (Eq, Show)

data Executable = Executable {
executableMain :: Maybe FilePath
, executableOtherModules :: [Module]
Expand Down Expand Up @@ -1095,13 +1160,17 @@ expandSectionDefaults
expandSectionDefaults formatYamlParseError userDataDir dir p@PackageConfig{..} = do
library <- traverse (expandDefaults formatYamlParseError userDataDir dir) packageConfigLibrary
internalLibraries <- traverse (traverse (expandDefaults formatYamlParseError userDataDir dir)) packageConfigInternalLibraries
foreignLibrary <- traverse (expandDefaults formatYamlParseError userDataDir dir) packageConfigForeignLibrary
foreignLibraries <- traverse (traverse (expandDefaults formatYamlParseError userDataDir dir)) packageConfigForeignLibraries
executable <- traverse (expandDefaults formatYamlParseError userDataDir dir) packageConfigExecutable
executables <- traverse (traverse (expandDefaults formatYamlParseError userDataDir dir)) packageConfigExecutables
tests <- traverse (traverse (expandDefaults formatYamlParseError userDataDir dir)) packageConfigTests
benchmarks <- traverse (traverse (expandDefaults formatYamlParseError userDataDir dir)) packageConfigBenchmarks
return p{
packageConfigLibrary = library
, packageConfigInternalLibraries = internalLibraries
, packageConfigForeignLibrary = foreignLibrary
, packageConfigForeignLibraries = foreignLibraries
, packageConfigExecutable = executable
, packageConfigExecutables = executables
, packageConfigTests = tests
Expand Down Expand Up @@ -1158,25 +1227,28 @@ type GlobalOptions = CommonOptions CSources CxxSources JsSources Empty

toPackage_ :: MonadIO m => FilePath -> Product GlobalOptions (PackageConfig CSources CxxSources JsSources) -> Warnings m (Package, String)
toPackage_ dir (Product g PackageConfig{..}) = do
foreignLibraryMap <- toExecutableMap packageName_ packageConfigForeignLibraries packageConfigForeignLibrary
executableMap <- toExecutableMap packageName_ packageConfigExecutables packageConfigExecutable
let
globalVerbatim = commonOptionsVerbatim g
globalOptions = g {commonOptionsVerbatim = Nothing}

executableNames = maybe [] Map.keys executableMap
componentNames = maybe [] Map.keys executableMap ++ maybe [] Map.keys foreignLibraryMap

toSect :: (Monad m, Monoid a) => WithCommonOptions CSources CxxSources JsSources a -> Warnings m (Section a)
toSect = toSection packageName_ executableNames . first ((mempty <$ globalOptions) <>)
toSect = toSection packageName_ componentNames . first ((mempty <$ globalOptions) <>)

toSections :: (Monad m, Monoid a) => Maybe (Map String (WithCommonOptions CSources CxxSources JsSources a)) -> Warnings m (Map String (Section a))
toSections = maybe (return mempty) (traverse toSect)

toLib = liftIO . toLibrary dir packageName_
toExecutables = toSections >=> traverse (liftIO . toExecutable dir packageName_)
toForeignLibraries = toSections >=> traverse (liftIO . toForeignLibrary dir packageName_)

mLibrary <- traverse (toSect >=> toLib) packageConfigLibrary
internalLibraries <- toSections packageConfigInternalLibraries >>= traverse toLib

foreignLibraries <- toForeignLibraries foreignLibraryMap
executables <- toExecutables executableMap
tests <- toExecutables packageConfigTests
benchmarks <- toExecutables packageConfigBenchmarks
Expand All @@ -1186,6 +1258,7 @@ toPackage_ dir (Product g PackageConfig{..}) = do
missingSourceDirs <- liftIO $ nub . sort <$> filterM (fmap not <$> doesDirectoryExist . (dir </>)) (
maybe [] sectionSourceDirs mLibrary
++ concatMap sectionSourceDirs internalLibraries
++ concatMap sectionSourceDirs foreignLibraries
++ concatMap sectionSourceDirs executables
++ concatMap sectionSourceDirs tests
++ concatMap sectionSourceDirs benchmarks
Expand Down Expand Up @@ -1242,6 +1315,7 @@ toPackage_ dir (Product g PackageConfig{..}) = do
, packageCustomSetup = mCustomSetup
, packageLibrary = mLibrary
, packageInternalLibraries = internalLibraries
, packageForeignLibraries = foreignLibraries
, packageExecutables = executables
, packageTests = tests
, packageBenchmarks = benchmarks
Expand Down Expand Up @@ -1351,6 +1425,9 @@ getMentionedLibraryModules (LibrarySection _ _ exposedModules generatedExposedMo
getLibraryModules :: Library -> [Module]
getLibraryModules Library{..} = libraryExposedModules ++ libraryOtherModules

getForeignLibraryModules :: ForeignLibrary -> [Module]
getForeignLibraryModules ForeignLibrary{..} = foreignLibraryOtherModules

getExecutableModules :: Executable -> [Module]
getExecutableModules Executable{..} = executableOtherModules

Expand Down Expand Up @@ -1427,10 +1504,31 @@ fromLibrarySectionPlain LibrarySection{..} = Library {
, librarySignatures = fromMaybeList librarySectionSignatures
}

getMentionedForeignLibraryModules :: ForeignLibrarySection -> [Module]
getMentionedForeignLibraryModules (ForeignLibrarySection _ _ _ otherModules generatedModules)=
fromMaybeList (otherModules <> generatedModules)

getMentionedExecutableModules :: ExecutableSection -> [Module]
getMentionedExecutableModules (ExecutableSection (Alias (Last main)) otherModules generatedModules)=
maybe id (:) (toModule . Path.fromFilePath <$> main) $ fromMaybeList (otherModules <> generatedModules)

toForeignLibrary :: FilePath -> String -> Section ForeignLibrarySection -> IO (Section ForeignLibrary)
toForeignLibrary dir packageName_ =
inferModules dir packageName_ getMentionedForeignLibraryModules getForeignLibraryModules fromForeignLibrarySection (fromForeignLibrarySection [])
where
fromForeignLibrarySection :: [Module] -> [Module] -> ForeignLibrarySection -> ForeignLibrary
fromForeignLibrarySection pathsModule inferableModules ForeignLibrarySection{..} =
(ForeignLibrary
(getLast foreignLibrarySectionType)
(getLast foreignLibrarySectionLibVersionInfo)
(fromList <$> foreignLibrarySectionOptions)
(otherModules ++ generatedModules)
generatedModules
)
where
otherModules = maybe (inferableModules ++ pathsModule) fromList foreignLibrarySectionOtherModules
generatedModules = maybe [] fromList foreignLibrarySectionGeneratedOtherModules

toExecutable :: FilePath -> String -> Section ExecutableSection -> IO (Section Executable)
toExecutable dir packageName_ =
inferModules dir packageName_ getMentionedExecutableModules getExecutableModules fromExecutableSection (fromExecutableSection [])
Expand Down
25 changes: 25 additions & 0 deletions src/Hpack/Render.hs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ renderPackageWith settings headerFieldsAlignment existingFieldOrder sectionsFiel
, map renderFlag packageFlags
, library
, renderInternalLibraries packageInternalLibraries
, renderForeignLibraries packageForeignLibraries
, renderExecutables packageExecutables
, renderTests packageTests
, renderBenchmarks packageBenchmarks
Expand Down Expand Up @@ -162,6 +163,13 @@ renderInternalLibrary :: (String, Section Library) -> Element
renderInternalLibrary (name, sect) =
Stanza ("library " ++ name) (renderLibrarySection sect)

renderForeignLibraries :: Map String (Section ForeignLibrary) -> [Element]
renderForeignLibraries = map renderForeignLibrary . Map.toList

renderForeignLibrary :: (String, Section ForeignLibrary) -> Element
renderForeignLibrary (name, sect) =
Stanza ("foreign-library " ++ name) (renderForeignLibrarySection [] sect)

renderExecutables :: Map String (Section Executable) -> [Element]
renderExecutables = map renderExecutable . Map.toList

Expand Down Expand Up @@ -195,6 +203,19 @@ renderExecutableFields Executable{..} = mainIs ++ [otherModules, generatedModule
otherModules = renderOtherModules executableOtherModules
generatedModules = renderGeneratedModules executableGeneratedModules

renderForeignLibrarySection :: [Element] -> Section ForeignLibrary -> [Element]
renderForeignLibrarySection extraFields = renderSection renderForeignLibraryFields extraFields

renderForeignLibraryFields :: ForeignLibrary -> [Element]
renderForeignLibraryFields ForeignLibrary{..} =
typeField ++ libVersionInfo ++ options ++ [otherModules, generatedModules]
where
typeField = maybe [] (return . Field "type" . Literal) foreignLibraryType
libVersionInfo = maybe [] (return . Field "lib-version-info" . Literal) foreignLibraryLibVersionInfo
options = maybe [] (\opts -> [renderForeignLibOptions opts]) foreignLibraryOptions
otherModules = renderOtherModules foreignLibraryOtherModules
generatedModules = renderGeneratedModules foreignLibraryGeneratedModules

renderCustomSetup :: CustomSetup -> Element
renderCustomSetup CustomSetup{..} =
Stanza "custom-setup" $ renderDependencies "setup-depends" customSetupDependencies
Expand Down Expand Up @@ -304,6 +325,10 @@ renderDirectories name = Field name . LineSeparatedList . replaceDots
"." -> "./"
_ -> xs


renderForeignLibOptions :: [String] -> Element
renderForeignLibOptions = Field "options" . LineSeparatedList

renderExposedModules :: [Module] -> Element
renderExposedModules = Field "exposed-modules" . LineSeparatedList . map unModule

Expand Down
Loading