module Distribution.Nixpkgs.Nodejs.OptimizedNixOutput
( convertLockfile
, mkPackageSet
) where
import Protolude
import qualified Data.Map as M
import qualified Data.Text as T
import Data.Fix (Fix(Fix))
import qualified Data.MultiKeyedMap as MKM
import qualified Data.List as List
import qualified Data.List.NonEmpty as NE
import Nix.Expr (NExpr, ($=), (==>), (!.), (@@))
import Nix.Expr.Additions (($$=), (!!.))
import qualified Nix.Expr as N
import qualified Nix.Expr.Additions as NA
import qualified Yarn.Lock.Types as YLT
import qualified Distribution.Nixpkgs.Nodejs.ResolveLockfile as Res
import Distribution.Nixpkgs.Nodejs.Utils (packageKeyToSymbol)
newtype NSym = NSym { unNSym :: Text }
deriving (IsString, Ord, Eq)
newtype NVar = NVar NSym
deriving (IsString)
data AStrVal = V NVar
| T Text
antiquote :: [AStrVal] -> NExpr
antiquote vals = Fix . N.NStr . N.DoubleQuoted
$ flip map vals $ \case
T t -> N.Plain t
V (NVar (NSym t)) -> N.Antiquoted $ N.mkSym t
data Registry = Registry
{ registrySym :: NSym
, registryBuilder :: NVar -> NVar -> [AStrVal]
}
data Git = Git
{ gitUrl :: Text
, gitRev :: Text }
data PkgRef
= PkgRef Text
| PkgDefFile (PkgData (Either Text Registry))
| PkgDefGit (PkgData Git)
data PkgData a = PkgData
{ pkgDataName :: Text
, pkgDataVersion :: Text
, pkgDataUpstream :: a
, pkgDataHashSum :: Text
, pkgDataDependencies :: [Text]
}
registries :: [(Text, Registry)]
registries =
[ ( yarnP
, Registry "yarn"
$ \n v -> [T yarnP, V n, T "/-/", V n, T "-", V v, T ".tgz"] )
]
where
yarnP = "https://registry.yarnpkg.com/"
shortcuts :: M.Map [NSym] NSym
shortcuts = M.fromList
[ (["self"], "s")
, (["registries", "yarn"], "y")
, (["nodeFilePackage"], "f")
, (["nodeGitPackage"], "g")
, (["identityRegistry"], "ir")
]
recognizeRegistry :: Text -> Maybe Registry
recognizeRegistry fileUrl = snd <$> filterRegistry fileUrl
where
filterRegistry url = find (\reg -> fst reg `T.isPrefixOf` url) registries
convertLockfile :: Res.ResolvedLockfile -> M.Map Text PkgRef
convertLockfile = M.fromList . foldMap convert . MKM.toList
where
convert :: (NE.NonEmpty YLT.PackageKey, (Res.Resolved YLT.Package))
-> [(Text, PkgRef)]
convert (keys, Res.Resolved{ hashSum, resolved=pkg }) = let
defName = NE.head $ maximumBy (comparing length) $ NE.group $ NE.sort $ fmap YLT.name keys
defSym = packageKeyToSymbol $ YLT.PackageKey
{ YLT.name = defName
, YLT.npmVersionSpec = YLT.version pkg }
pkgDataGeneric upstream = PkgData
{ pkgDataName = defName
, pkgDataVersion = YLT.version pkg
, pkgDataUpstream = upstream
, pkgDataHashSum = hashSum
, pkgDataDependencies = map packageKeyToSymbol
$ YLT.dependencies pkg <> YLT.optionalDependencies pkg
}
def = case YLT.remote pkg of
YLT.FileRemote{fileUrl} ->
PkgDefFile $ pkgDataGeneric $ note fileUrl $ recognizeRegistry fileUrl
YLT.GitRemote{gitRepoUrl, gitRev} ->
PkgDefGit $ pkgDataGeneric $ Git gitRepoUrl gitRev
refNames = List.delete defSym $ toList $ NE.nub
$ fmap packageKeyToSymbol keys
in (defSym, def) : map (\rn -> (rn, PkgRef defSym)) refNames
mkPackageSet :: M.Map Text PkgRef -> NExpr
mkPackageSet packages =
NA.simpleParamSet ["fetchurl", "fetchgit"]
==> N.Param "self" ==> N.Param "super"
==> N.mkLets
( [ "registries" $= N.mkNonRecSet (fmap (mkRegistry . snd) registries)
, "sanitizePackageName" $= sanitizePackageName
, "nodeFilePackage" $= buildPkgFn
, "nodeGitPackage" $= buildPkgGitFn
, "identityRegistry" $= NA.multiParam ["url", "_", "_"] "url" ]
<> fmap mkShortcut (M.toList shortcuts) )
(N.mkNonRecSet (map mkPkg $ M.toAscList packages))
where
mkRegistry (Registry{..}) = unNSym registrySym $=
(N.Param "n" ==> N.Param "v" ==> antiquote (registryBuilder "n" "v"))
concatNSyms :: [NSym] -> NExpr
concatNSyms [] = panic "non-empty shortcut syms!"
concatNSyms (l:ls) = foldl (!.) (N.mkSym $ unNSym l) (fmap unNSym ls)
mkShortcut :: ([NSym], NSym) -> N.Binding NExpr
mkShortcut (nSyms, short) = unNSym short $= concatNSyms nSyms
shorten :: [NSym] -> NExpr
shorten s = maybe (concatNSyms s) (N.mkSym . unNSym) $ M.lookup s shortcuts
sanitizePackageName :: NExpr
sanitizePackageName = "builtins" !. "replaceStrings"
@@ N.mkList [N.mkStr "@", N.mkStr "/"]
@@ N.mkList [N.mkStr "-", N.mkStr "-"]
buildPkgFnGeneric :: [Text] -> NExpr -> NExpr
buildPkgFnGeneric additionalArguments srcNExpr =
NA.multiParam (["name", "version"] <> additionalArguments <> ["deps"])
$ ("super" !!. "_buildNodePackage") @@ N.mkNonRecSet
[ "name" $= ("sanitizePackageName" @@ "name")
, N.inherit $ map N.StaticKey ["version"]
, "src" $= srcNExpr
, "nodeBuildInputs" $= "deps" ]
buildPkgFn :: NExpr
buildPkgFn =
buildPkgFnGeneric ["registry", "sha1"]
("fetchurl" @@ N.mkNonRecSet
[ "url" $= ("registry" @@ "name" @@ "version")
, N.inherit $ [N.StaticKey "sha1"] ])
buildPkgGitFn :: NExpr
buildPkgGitFn =
buildPkgFnGeneric ["url", "rev", "sha256"]
("fetchgit" @@ N.mkNonRecSet
[ N.inherit $ map N.StaticKey ["url", "rev", "sha256"] ])
mkDefGeneric :: PkgData a -> NSym -> [NExpr] -> NExpr
mkDefGeneric PkgData{..} buildFnSym additionalArguments =
foldl' (@@) (shorten [buildFnSym])
$ [ N.mkStr pkgDataName
, N.mkStr pkgDataVersion ]
<> additionalArguments <>
[ N.mkList $ map (N.mkSym selfSym !!.) pkgDataDependencies ]
mkPkg :: (Text, PkgRef) -> N.Binding NExpr
mkPkg (key, pkgRef) = key $$= case pkgRef of
PkgRef t -> N.mkSym selfSym !!. t
PkgDefFile pd@PkgData{pkgDataUpstream, pkgDataHashSum} ->
mkDefGeneric pd "nodeFilePackage"
[ either (\url -> shorten ["identityRegistry"] @@ N.mkStr url )
(\reg -> shorten ["registries", registrySym reg])
pkgDataUpstream
, N.mkStr pkgDataHashSum ]
PkgDefGit pd@PkgData{pkgDataUpstream = Git{..}, pkgDataHashSum} ->
mkDefGeneric pd "nodeGitPackage"
[ N.mkStr gitUrl, N.mkStr gitRev, N.mkStr pkgDataHashSum ]
selfSym :: Text
selfSym = "s"