From e7ebafa8a241deeee51fd414187ded017d4932d6 Mon Sep 17 00:00:00 2001 From: sternenseemann Date: Tue, 23 Sep 2025 22:36:40 +0200 Subject: [PATCH 1/2] FromCabal.Normalize: use toNixName in normalizeBuildInfo --- .../src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs index 9b4050490..a59aafcd4 100644 --- a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs +++ b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Normalize.hs @@ -6,6 +6,7 @@ import Control.Lens import qualified Data.Set as Set import Data.String import Distribution.Nixpkgs.Haskell +import Distribution.Nixpkgs.Haskell.FromCabal.Name (toNixName) import Distribution.Nixpkgs.Meta import Distribution.Package import Language.Nix hiding ( quote ) @@ -21,8 +22,8 @@ normalize drv = drv normalizeBuildInfo :: PackageName -> BuildInfo -> BuildInfo normalizeBuildInfo pname bi = bi - & haskell %~ Set.filter (\b -> view localName b /= fromString (unPackageName pname)) - & tool %~ Set.filter (\b -> view localName b /= fromString (unPackageName pname)) + & haskell %~ Set.filter (\b -> view localName b /= toNixName pname) + & tool %~ Set.filter (\b -> view localName b /= toNixName pname) normalizeMeta :: Meta -> Meta normalizeMeta meta = meta From cdaaaedf33c05e72d3e66b3134889d646a556aef Mon Sep 17 00:00:00 2001 From: sternenseemann Date: Tue, 23 Sep 2025 21:54:01 +0200 Subject: [PATCH 2/2] FromCabal.Name: prefix pkg names with _ if they can't be identifiers While we can quote attribute names, we can't use quoting to use arbitrary strings as function arguments in Nix unfortunately. This means we couldn't previously generate expressions for packages that depend on packages that start with numbers or clash with keywords. To fix this, we simply prefix the offending packages with an underscore. This mirrors an existing Nixpkgs convention (see e.g. pkgs._2bwm). This mapping work for all Hackage packages and is easily reversible by just removing the underscore. We can never introduce ambiguity this way since Cabal doesn't allow underscores in package names. Cabal packages not conforming to Hackage's restrictions won't work yet. For this, we would need to implement some kind of encoding. Resolves #163. Resolves #164. --- cabal2nix/hackage2nix/Main.hs | 7 +- .../Nixpkgs/Haskell/FromCabal/Name.hs | 77 ++++++++++++++++++- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/cabal2nix/hackage2nix/Main.hs b/cabal2nix/hackage2nix/Main.hs index 370f3324c..231c0f1e8 100644 --- a/cabal2nix/hackage2nix/Main.hs +++ b/cabal2nix/hackage2nix/Main.hs @@ -81,12 +81,7 @@ main = do config <- sconcat <$> mapM (\file -> readConfiguration (nixpkgsRepository file)) configFiles nixpkgs <- readNixpkgPackageMap nixpkgsRepository (Just "{ config = { allowAliases = false; }; }") preferredVersions <- readPreferredVersions (fromMaybe (hackageRepository "preferred-versions") preferredVersionsFile) - let fixup = Map.delete "acme-everything" -- TODO: https://github.com/NixOS/cabal2nix/issues/164 - . Map.delete "type" -- TODO: https://github.com/NixOS/cabal2nix/issues/163 - . Map.delete "control-invariants" -- TODO: depends on "assert" - . Map.delete "ConcurrentUtils" -- TODO: depends on "assert" - . Map.delete "with" -- TODO: https://github.com/NixOS/cabal2nix/issues/164 - . over (at "hermes") (fmap (set (contains "1.3.4.3") False)) -- TODO: https://github.com/haskell/hackage-server/issues/436 + let fixup = over (at "hermes") (fmap (set (contains "1.3.4.3") False)) -- TODO: https://github.com/haskell/hackage-server/issues/436 hackage <- fixup <$> readHackage hackageRepository let hackagePackagesFile :: FilePath diff --git a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Name.hs b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Name.hs index 8212e3582..c97aa1f65 100644 --- a/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Name.hs +++ b/cabal2nix/src/Distribution/Nixpkgs/Haskell/FromCabal/Name.hs @@ -8,10 +8,81 @@ import Distribution.Package import Distribution.Text import Language.Nix --- | Map Cabal names to Nix attribute names. +-- | Map Cabal names to Nix identifiers that don't need to be quoted. +-- +-- Currently this only supports 'PackageName's that consist of nothing but ASCII +-- characters (as needs to be the case with all Hackage packages). +-- Cabal package names are not changed if they already are a Nix identifier +-- that doesn't need quoting (with some notable exceptions). If they would need +-- quoting, they are prefixed with an underscore. +-- +-- >>> toNixName $ mkPackageName "cabal2nix" +-- Identifier "cabal2nix" +-- >>> toNixName $ mkPackageName "4Blocks" +-- Identifier "_4Blocks" +-- >>> toNixName $ mkPackageName "assert" +-- Identifier "_assert" +-- +-- Package names that clash with attribute names that have a special meaning +-- to the Nix evaluator are also prefixed (e.g. +-- [@type@ is evaluated eagerly]((https://github.com/NixOS/cabal2nix/issues/163)). +-- +-- The mapping is intended to be reversible, but this isn't implemented by +-- @cabal2nix@ yet (and untested). It also should not be considered +-- stable yet, in particular the following may be changed: +-- +-- - Future versions of @cabal2nix@ may prefix more 'PackageName's. +-- - The mapping may be extended to support all possible 'PackageName's. +-- +-- See also: +-- +-- - [Cabal documentation on the package name field](https://cabal.readthedocs.io/en/stable/cabal-package-description-file.html#pkg-field-name) +-- - "Language.Nix.Identifier" +-- - [Nix documentation on identifiers](https://nix.dev/manual/nix/2.30/language/identifiers.html#identifier) toNixName :: PackageName -> Identifier -toNixName "" = error "toNixName: invalid empty package name" -toNixName n = fromString (unPackageName n) +toNixName n = fromString $ + case unPackageName n of + "" -> error "toNixName: BUG: received empty package name" + '_':_ -> error "toNixName: BUG: PackageName starts with an underscore, but shouldn't" + name + -- From the Cabal documentation: + -- + -- A valid package name comprises an alphanumeric ‘word’; or two or more + -- such words separated by a hyphen character (-). A word cannot be + -- comprised only of the digits 0 to 9. + -- + -- Cabal also latin unicode characters while Hackage enforces that package + -- names are ASCII. + -- + -- If the package name comes from Hackage, the set of legal characters + -- ([a-zA-Z0-9-]) is a subset of those permissible as a Nix identifier + -- without quoting ([a-zA-Z0-9_'-]). The main difference are the rules + -- governing what may go where. In the following cases a Hackage package + -- name is not a simple identifier and 'needsQuoting' returns True: + -- + -- - if the first “word” of the package name starts with a number, e.g. 4Blocks. + -- - if the package name is the same one of the 'nixKeywords'. + -- + -- If we prefix these strings with an underscore, they no longer need quoting. + -- Because Cabal 'PackageName's may not contain underscores this mapped name + -- can never clash. (Reversing the mapping is very simple at the moment as + -- a result.) + -- + -- We additionally prefix perfectly usable identifiers like type and + -- recurseForDerivations if they have special meaning to the Nix evaluator + -- (or Hydra etc.) since it may cause evaluation failures if we expose a + -- package under haskellPackages instead of whatever value(s) Nix may + -- expect. + -- + -- TODO: Add mapping for non-ASCII 'PackageName's, using __ prefix (?) + | needsQuoting name || name `elem` haveSpecialSemantics -> '_':name + | otherwise -> name + where + -- Special attributes that affect the behavior of the Nix evaluator in some way. + -- See https://github.com/NixOS/cabal2nix/issues/163. + -- We can ignore underscore prefixed attrs like __toString, __functor. + -- Only type is the name of a real package at the moment. + haveSpecialSemantics = [ "type", "outPath", "recurseForDerivations" ] -- | Map library names specified in Cabal files to Nix package identifiers. --