diff --git a/cabal2nix/src/Distribution/Nixpkgs/Haskell/Derivation.hs b/cabal2nix/src/Distribution/Nixpkgs/Haskell/Derivation.hs index c8c85ef8..68589bd1 100644 --- a/cabal2nix/src/Distribution/Nixpkgs/Haskell/Derivation.hs +++ b/cabal2nix/src/Distribution/Nixpkgs/Haskell/Derivation.hs @@ -9,6 +9,7 @@ module Distribution.Nixpkgs.Haskell.Derivation , cabalFlags, runHaddock, jailbreak, doCheck, doBenchmark, testFlags, testTargets, hyperlinkSource , enableLibraryProfiling, enableExecutableProfiling, phaseOverrides, editedCabalFile, metaSection , dependencies, setupDepends, benchmarkDepends, enableSeparateDataOutput, extraAttributes + , subLibraryDepends, subLibraryDependencies ) where @@ -50,6 +51,8 @@ data Derivation = MkDerivation , _executableDepends :: BuildInfo , _testDepends :: BuildInfo , _benchmarkDepends :: BuildInfo + , _subLibraryDepends :: Map String BuildInfo + , _subLibraryDependencies :: Map String [String] , _configureFlags :: Set String , _cabalFlags :: FlagAssignment , _runHaddock :: Bool @@ -83,6 +86,8 @@ nullDerivation = MkDerivation , _executableDepends = error "undefined Derivation.executableDepends" , _testDepends = error "undefined Derivation.testDepends" , _benchmarkDepends = error "undefined Derivation.benchmarkDepends" + , _subLibraryDepends = mempty + , _subLibraryDependencies = mempty , _configureFlags = error "undefined Derivation.configureFlags" , _cabalFlags = error "undefined Derivation.cabalFlags" , _runHaddock = error "undefined Derivation.runHaddock" @@ -102,7 +107,18 @@ nullDerivation = MkDerivation makeLenses ''Derivation -makeLensesFor [("_setupDepends", "dependencies"), ("_libraryDepends", "dependencies"), ("_executableDepends", "dependencies"), ("_testDepends", "dependencies"), ("_benchmarkDepends", "dependencies")] ''Derivation +-- | Traversal over all 'BuildInfo' values in a 'Derivation', including +-- the setup, library, executable, test, benchmark, and sub-library depends. +dependencies :: Traversal' Derivation BuildInfo +dependencies f drv = + (\s l e t b sl -> drv { _setupDepends = s, _libraryDepends = l, _executableDepends = e + , _testDepends = t, _benchmarkDepends = b, _subLibraryDepends = sl }) + <$> f (_setupDepends drv) + <*> f (_libraryDepends drv) + <*> f (_executableDepends drv) + <*> f (_testDepends drv) + <*> f (_benchmarkDepends drv) + <*> traverse f (_subLibraryDepends drv) instance Package Derivation where packageId = view pkgid @@ -110,7 +126,7 @@ instance Package Derivation where instance NFData Derivation instance Pretty Derivation where - pPrint drv@MkDerivation {..} = funargs (map text ("mkDerivation" : toAscList inputs)) $$ vcat + pPrint drv@MkDerivation {_subLibraryDepends = subLibDeps, _subLibraryDependencies = subLibConsumerDeps, ..} = funargs (map text ("mkDerivation" : toAscList inputs)) $$ vcat [ text "mkDerivation" <+> lbrace , nest 2 $ vcat [ attr "pname" $ doubleQuotes $ pPrint (packageName _pkgid) @@ -124,10 +140,12 @@ instance Pretty Derivation where , boolattr "isExecutable" (not _isLibrary || _isExecutable) _isExecutable , boolattr "enableSeparateDataOutput" _enableSeparateDataOutput _enableSeparateDataOutput , onlyIf (_setupDepends /= mempty) $ pPrintBuildInfo "setup" _setupDepends - , onlyIf (_libraryDepends /= mempty) $ pPrintBuildInfo "library" _libraryDepends + , onlyIf (mergedLibraryDepends /= mempty) $ pPrintBuildInfo "library" mergedLibraryDepends + , onlyIf (not (Map.null subLibDeps)) $ pPrintSubLibraryDepends subLibDeps , onlyIf (_executableDepends /= mempty) $ pPrintBuildInfo "executable" _executableDepends , onlyIf (_testDepends /= mempty) $ pPrintBuildInfo "test" _testDepends , onlyIf (_benchmarkDepends /= mempty) $ pPrintBuildInfo "benchmark" _benchmarkDepends + , onlyIf (not (Map.null subLibConsumerDeps)) $ pPrintSubLibraryDependencies subLibConsumerDeps , boolattr "enableLibraryProfiling" _enableLibraryProfiling _enableLibraryProfiling , boolattr "enableExecutableProfiling" _enableExecutableProfiling _enableExecutableProfiling , boolattr "doHaddock" (not _runHaddock) _runHaddock @@ -152,8 +170,39 @@ instance Pretty Derivation where Just derivKind' -> Set.fromList [derivKindFunction derivKind' | not isHackagePackage] ] + -- For backwards compatibility, libraryHaskellDepends is emitted as the + -- union of the main library deps and all sub-library deps. New consumers + -- should prefer the separate subLibraryDepends attrset. + mergedLibraryDepends = _libraryDepends `mappend` foldMap id (Map.elems subLibDeps) + renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text (unFlagName f) | (f, enable) <- unFlagAssignment _cabalFlags ] ++ map text (toAscList _configureFlags) isHackagePackage = "mirror://hackage/" `isPrefixOf` derivUrl _src postUnpack = string $ "sourceRoot+=/" ++ _subpath ++ "; echo source root reset to $sourceRoot" + +pPrintSubLibraryDepends :: Map String BuildInfo -> Doc +pPrintSubLibraryDepends libs = vcat + [ text "subLibraryDepends" <+> equals <+> lbrace + , nest 2 $ vcat entries + , rbrace <> semi + ] + where + entries = [ vcat [ text (show name) <+> equals <+> lbrace + , nest 2 $ pPrintBuildInfo "" bi + , rbrace <> semi + ] + | (name, bi) <- Map.toAscList libs + , bi /= mempty + ] + +pPrintSubLibraryDependencies :: Map String [String] -> Doc +pPrintSubLibraryDependencies deps = vcat + [ text "subLibraryDependencies" <+> equals <+> lbrace + , nest 2 $ vcat entries + , rbrace <> semi + ] + where + entries = [ listattrDoc (show pkgName) empty (map (doubleQuotes . text) subLibs) + | (pkgName, subLibs) <- Map.toAscList deps + ] diff --git a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal.hs b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal.hs index 91c9a254..9d6403f9 100644 --- a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal.hs +++ b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal.hs @@ -9,8 +9,10 @@ module Distribution.Nixpkgs.Haskell.FromCabal import Control.Lens import Data.Maybe +import qualified Data.Map as Map import Data.Set ( Set ) import qualified Data.Set as Set +import qualified Distribution.Compat.NonEmptySet as NES import Distribution.Compiler import Distribution.Nixpkgs.Haskell import qualified Distribution.Nixpkgs.Haskell as Nix @@ -29,6 +31,7 @@ import Distribution.System import Distribution.Types.PackageVersionConstraint import Distribution.Text ( display ) import Distribution.Types.ComponentRequestedSpec as Cabal +import Distribution.Types.LibraryVisibility #if !MIN_VERSION_Cabal(3,8,1) import Distribution.Types.ExeDependency as Cabal import Distribution.Types.LegacyExeDependency as Cabal @@ -124,10 +127,22 @@ fromPackageDescription haskellResolver nixpkgsResolver missingDeps flags Package & isExecutable .~ not (null executables) & extraFunctionArgs .~ mempty & extraAttributes .~ mempty - & libraryDepends .~ foldMap (convertBuildInfo . libBuildInfo) (maybeToList library ++ subLibraries) + & libraryDepends .~ foldMap (convertBuildInfo . libBuildInfo) (maybeToList library) + & subLibraryDepends .~ Map.fromList + [ (unUnqualComponentName n, convertBuildInfo (libBuildInfo l)) + | l <- subLibraries + , LSubLibName n <- [libName l] + ] & executableDepends .~ mconcat (map (convertBuildInfo . buildInfo) executables) & testDepends .~ mconcat (map (convertBuildInfo . testBuildInfo) testSuites) & benchmarkDepends .~ mconcat (map (convertBuildInfo . benchmarkBuildInfo) benchmarks) + & subLibraryDependencies .~ collectSubLibDeps + ( concatMap (targetBuildDepends . libBuildInfo) (maybeToList library) + ++ concatMap (targetBuildDepends . libBuildInfo) subLibraries + ++ concatMap (targetBuildDepends . buildInfo) executables + ++ concatMap (targetBuildDepends . testBuildInfo) testSuites + ++ concatMap (targetBuildDepends . benchmarkBuildInfo) benchmarks + ) & Nix.setupDepends .~ maybe mempty convertSetupBuildInfo setupBuildInfo & configureFlags .~ mempty & cabalFlags .~ flags @@ -201,13 +216,31 @@ fromPackageDescription haskellResolver nixpkgsResolver missingDeps flags Package | otherwise = resolveInNixpkgs i internalLibNames :: [PackageName] - internalLibNames = [ unqualComponentNameToPackageName n | LSubLibName n <- libName <$> subLibraries ] + internalLibNames = [ unqualComponentNameToPackageName n + | l <- subLibraries + , LSubLibName n <- [libName l] + , libVisibility l /= LibraryVisibilityPublic + ] doHaddockPhase :: Bool doHaddockPhase | not (null internalLibNames) = False | Just l <- library = not (null (exposedModules l)) | otherwise = True + -- | Collect sub-library qualifiers from dependencies on external packages. + -- For each external dependency that specifies sub-library names (not just + -- LMainLibName), record which sub-libraries are being depended upon. + collectSubLibDeps :: [Dependency] -> Map.Map String [String] + collectSubLibDeps deps = Map.fromListWith (\a b -> Set.toAscList (Set.fromList (a ++ b))) + [ (unPackageName x, subLibNames) + | Dependency x _ libs <- deps + , x `notElem` internalLibNames + , x /= pkgName package -- exclude self-referential sub-library deps + , let subLibNames = [ unUnqualComponentName n | LSubLibName n <- NES.toList libs ] + , not (null subLibNames) + ] + pkgName (PackageIdentifier n _) = n + convertBuildInfo :: Cabal.BuildInfo -> Nix.BuildInfo convertBuildInfo Cabal.BuildInfo {..} | not buildable = mempty convertBuildInfo Cabal.BuildInfo {..} = mempty diff --git a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs index a59aafcd..1df8e8c0 100644 --- a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs +++ b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs @@ -3,6 +3,7 @@ module Distribution.Nixpkgs.Haskell.FromCabal.Normalize ( normalize ) where import Control.Lens +import qualified Data.Map as Map import qualified Data.Set as Set import Data.String import Distribution.Nixpkgs.Haskell @@ -17,6 +18,7 @@ normalize drv = drv & over executableDepends (normalizeBuildInfo (packageName drv)) & over testDepends (normalizeBuildInfo (packageName drv)) & over benchmarkDepends (normalizeBuildInfo (packageName drv)) + & over subLibraryDepends (Map.map (normalizeBuildInfo (packageName drv))) & over metaSection normalizeMeta & jailbreak %~ (&& (packageName drv /= "jailbreak-cabal")) diff --git a/cabal2nix/test/golden-test-cases/haddock-library.nix.golden b/cabal2nix/test/golden-test-cases/haddock-library.nix.golden index 4ab0fbf9..648ed5bd 100644 --- a/cabal2nix/test/golden-test-cases/haddock-library.nix.golden +++ b/cabal2nix/test/golden-test-cases/haddock-library.nix.golden @@ -6,6 +6,11 @@ mkDerivation { version = "1.4.5"; sha256 = "deadbeef"; libraryHaskellDepends = [ base bytestring deepseq transformers ]; + subLibraryDepends = { + "attoparsec" = { + HaskellDepends = [ base bytestring deepseq ]; + }; + }; testHaskellDepends = [ base base-compat bytestring deepseq hspec QuickCheck transformers ]; diff --git a/cabal2nix/test/golden-test-cases/sub-libraries.cabal b/cabal2nix/test/golden-test-cases/sub-libraries.cabal new file mode 100644 index 00000000..feed366f --- /dev/null +++ b/cabal2nix/test/golden-test-cases/sub-libraries.cabal @@ -0,0 +1,28 @@ +cabal-version: 3.0 +name: sub-libraries +version: 0.1.0.0 +synopsis: Test case for sub-library support +license: BSD-3-Clause +build-type: Simple + +library + exposed-modules: MyLib + build-depends: base >=4.14 + default-language: Haskell2010 + +library public-sub + visibility: public + exposed-modules: PublicSub + build-depends: base, deepseq + default-language: Haskell2010 + +library private-sub + visibility: private + exposed-modules: PrivateSub + build-depends: base, containers + default-language: Haskell2010 + +executable demo + main-is: Main.hs + build-depends: base, sub-libraries, sub-libraries:public-sub + default-language: Haskell2010 diff --git a/cabal2nix/test/golden-test-cases/sub-libraries.nix.golden b/cabal2nix/test/golden-test-cases/sub-libraries.nix.golden new file mode 100644 index 00000000..bbc8d1c2 --- /dev/null +++ b/cabal2nix/test/golden-test-cases/sub-libraries.nix.golden @@ -0,0 +1,22 @@ +{ mkDerivation, base, containers, deepseq, lib }: +mkDerivation { + pname = "sub-libraries"; + version = "0.1.0.0"; + sha256 = "deadbeef"; + isLibrary = true; + isExecutable = true; + libraryHaskellDepends = [ base containers deepseq ]; + subLibraryDepends = { + "private-sub" = { + HaskellDepends = [ base containers ]; + }; + "public-sub" = { + HaskellDepends = [ base deepseq ]; + }; + }; + executableHaskellDepends = [ base ]; + doHaddock = false; + description = "Test case for sub-library support"; + license = lib.meta.getLicenseFromSpdxId "BSD-3-Clause"; + mainProgram = "demo"; +}