Skip to content

Commit cdd3d80

Browse files
Implement nested packages (#382)
Co-authored-by: Sönke Hahn <[email protected]>
1 parent b7ed1b6 commit cdd3d80

File tree

12 files changed

+491
-306
lines changed

12 files changed

+491
-306
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ node_modules/
44
/website/dist/
55
/website/public/install.sh
66
/website/public/nix-installer*
7+
/ts/internal/nixpkgs
78
/ts/nixpkgs.ts
89
/.golden/*/actual

.golden/adds_a_description_attribute_from_the_nix_package/golden

-10
This file was deleted.

.golden/extracts_top-level_derivations/golden

-7
This file was deleted.

.golden/generates_nice_JSDoc_comments/golden

-18
This file was deleted.

.golden/ignores_attributes_that_are_marked_broken/golden

-7
This file was deleted.

.golden/ignores_attributes_that_throw/golden

-7
This file was deleted.

.golden/ignores_things_that_are_not_derivations/golden

-7
This file was deleted.

.golden/removes_conflicts_due_to_sanitization/golden

-7
This file was deleted.

.golden/sanitizes_package_names/golden

-27
This file was deleted.

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
provides a nice way to bundle up more complex project modifications into a
1010
single declaration. It also allows to use `Plugin`s from other sources,
1111
including third-party libraries.
12+
- Expose some useful nested nix packages in the garn `nixpkgs.ts` package.
1213
- Add `garn.javascript.vite`, a `Plugin` that adds fields for bundling vite projects into a `Package`.
1314

1415
## v0.0.15

src/Garn/CodeGen.hs

+131-64
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
{-# LANGUAGE QuasiQuotes #-}
2+
{-# LANGUAGE ViewPatterns #-}
23

34
module Garn.CodeGen
45
( Garn.CodeGen.run,
5-
fromToplevelDerivation,
6+
PkgInfo (Derivation, Collection, description, path),
7+
scanPackages,
8+
writePkgFiles,
69
)
710
where
811

12+
import Control.Exception (IOException, catch)
13+
import Control.Monad (forM_)
914
import Cradle (StdoutUntrimmed (..), run)
1015
import Data.Aeson (FromJSON, eitherDecode, toJSON)
1116
import Data.Aeson.Text (encodeToLazyText)
17+
import Data.Char (isDigit)
1218
import Data.Functor ((<&>))
1319
import Data.List (intercalate)
1420
import Data.Map (Map, toAscList)
@@ -19,69 +25,115 @@ import Data.String.Interpolate (i)
1925
import Data.String.Interpolate.Util (unindent)
2026
import GHC.Generics (Generic)
2127
import Garn.Common (currentSystem, nixpkgsInput)
28+
import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive)
2229
import WithCli (withCli)
2330

31+
-- A nix expression that specifies which packages to include in the final
32+
-- collection.
33+
--
34+
-- All top-level derivations included by default, but everything else excluded.
35+
-- This can be overridden with the given attribute set.
36+
pkgSpec :: String
37+
pkgSpec =
38+
[i|
39+
{
40+
haskellPackages = {};
41+
nimPackages = {};
42+
nodePackages = { "@antora/cli" = false; };
43+
phpPackages = {};
44+
python2Packages = {};
45+
python3Packages = {};
46+
rubyPackages = {};
47+
rustPackages = {};
48+
}
49+
|]
50+
51+
pkgs :: String -> String
52+
pkgs system =
53+
[i|
54+
import (builtins.getFlake "#{nixpkgsInput}") {
55+
system = "#{system}";
56+
config.allowAliases = false;
57+
}
58+
|]
59+
2460
run :: IO ()
2561
run = withCli $ do
2662
system <- currentSystem
27-
let varName = "pkgs"
28-
nixpkgsExpression =
29-
[i|
30-
import (builtins.getFlake "#{nixpkgsInput}") {
31-
system = "#{system}";
32-
config.allowAliases = false;
33-
}
34-
|]
35-
code <- fromToplevelDerivation "." varName nixpkgsExpression
36-
writeFile "ts/nixpkgs.ts" code
63+
let outDir = "ts/internal/nixpkgs"
64+
removeDirectoryRecursive outDir `catch` \(_ :: IOException) -> pure ()
65+
pkgs <- scanPackages system (pkgs system) pkgSpec
66+
writePkgFiles outDir "../.." pkgs
67+
writeFile "ts/nixpkgs.ts" "export * from \"./internal/nixpkgs/mod.ts\";\n"
68+
69+
writePkgFiles :: String -> String -> Map String PkgInfo -> IO ()
70+
writePkgFiles modulePath garnLibRoot (Map.mapKeys sanitize -> pkgs) = do
71+
createDirectoryIfMissing True modulePath
72+
let code =
73+
unindent
74+
[i|
75+
import { mkPackage } from "#{garnLibRoot}/package.ts";
76+
import { nixRaw } from "#{garnLibRoot}/nix.ts";
77+
78+
|]
79+
<> pkgsString pkgs
80+
writeFile (modulePath <> "/mod.ts") code
81+
forM_ (Map.assocs pkgs) $ \(name :: String, pkgInfo :: PkgInfo) -> do
82+
case pkgInfo of
83+
Derivation {} -> pure ()
84+
Collection {subPkgs} -> writePkgFiles (modulePath <> "/" <> name) (garnLibRoot <> "/..") subPkgs
3785

38-
fromToplevelDerivation :: String -> String -> String -> IO String
39-
fromToplevelDerivation garnLibRoot varName rootExpr = do
40-
system :: String <- do
41-
StdoutUntrimmed json <- Cradle.run "nix" nixArgs (words "eval --impure --json --expr builtins.currentSystem")
42-
pure $ either error id $ eitherDecode (cs json)
86+
scanPackages :: String -> String -> String -> IO (Map String PkgInfo)
87+
scanPackages system pkgs pkgSpec = do
4388
StdoutUntrimmed json <- Cradle.run "nix" nixArgs "eval" (".#lib." <> system) "--json" "--apply" nixExpr
44-
pkgs :: Map String PkgInfo <- case eitherDecode (cs json) of
89+
case eitherDecode (cs json) of
4590
Right pkgs -> pure pkgs
4691
Left e -> error (e <> " in " <> cs json)
47-
let sanitizedPkgs = Map.mapKeys sanitize pkgs
48-
pure $
49-
unindent
50-
[i|
51-
import { mkPackage } from "#{garnLibRoot}/package.ts";
52-
import { nixRaw } from "#{garnLibRoot}/nix.ts";
53-
54-
|]
55-
<> pkgsString varName sanitizedPkgs
5692
where
5793
nixExpr =
5894
[i|
59-
lib :
60-
let mk = name: value: {
61-
attribute = name;
62-
description = if value ? meta.description
63-
then value.meta.description
64-
else null;
65-
};
66-
isNotBroken = value:
67-
let broken = (builtins.tryEval (value.meta.broken or false));
68-
in broken.success && !broken.value;
69-
doesNotThrow = value : (builtins.tryEval value).success;
70-
filterAttrs = lib.attrsets.filterAttrs
71-
(name: value:
72-
doesNotThrow value
73-
&& lib.isDerivation value
74-
&& isNotBroken value);
75-
in
76-
(lib.mapAttrs mk
77-
(filterAttrs (#{rootExpr}))
78-
)
95+
lib:
96+
let
97+
scan = path: pkgs: pkgSpec:
98+
if pkgSpec == false then null
99+
else filterNulls (lib.mapAttrs (k: v:
100+
let
101+
newPath = "${path}.${k}";
102+
in
103+
if pkgSpec ? ${k}
104+
then mkCollection (scan newPath v (pkgSpec.${k}))
105+
else if isWorkingDerivation v then mkDerivation newPath v
106+
else null
107+
) pkgs);
108+
mkCollection = subPkgs:
109+
if subPkgs == null then null
110+
else {
111+
tag = "Collection";
112+
inherit subPkgs;
113+
};
114+
mkDerivation = path: pkg: {
115+
tag = "Derivation";
116+
inherit path;
117+
description = if pkg ? meta.description
118+
then pkg.meta.description
119+
else null;
120+
};
121+
filterNulls = lib.filterAttrs (k: v: v != null);
122+
isWorkingDerivation = value:
123+
(builtins.tryEval value).success &&
124+
(let
125+
evalResult = (builtins.tryEval (value.meta.broken or false));
126+
in
127+
evalResult.success &&
128+
!evalResult.value &&
129+
lib.isDerivation value);
130+
in
131+
scan "pkgs" (#{pkgs}) (#{pkgSpec})
79132
|]
80133

81-
data PkgInfo = PkgInfo
82-
{ description :: Maybe String,
83-
attribute :: String
84-
}
134+
data PkgInfo
135+
= Derivation {description :: Maybe String, path :: String}
136+
| Collection {subPkgs :: Map String PkgInfo}
85137
deriving stock (Eq, Show, Generic)
86138
deriving anyclass (FromJSON)
87139

@@ -96,28 +148,40 @@ pkgDoc pkgInfo = case description pkgInfo of
96148
*/
97149
|]
98150

99-
formatPkg :: String -> (String, PkgInfo) -> String
100-
formatPkg varName (name, pkgInfo) =
101-
let escapedDoc = encodeToLazyText . toJSON $ fromMaybe "" $ description pkgInfo
102-
in pkgDoc pkgInfo
103-
<> unindent
104-
[i|
105-
export const #{name} = mkPackage(
106-
nixRaw`#{varName}.#{attribute pkgInfo}`,
107-
#{escapedDoc},
108-
);
109-
|]
151+
formatPkg :: (String, PkgInfo) -> String
152+
formatPkg (name, pkgInfo) = do
153+
case pkgInfo of
154+
Derivation {description, path} ->
155+
let escapedDoc = encodeToLazyText . toJSON $ fromMaybe "" description
156+
in pkgDoc pkgInfo
157+
<> unindent
158+
[i|
159+
export const #{name} = mkPackage(
160+
nixRaw`#{path}`,
161+
#{escapedDoc},
162+
);
163+
|]
164+
Collection _ ->
165+
unindent
166+
[i|
167+
export * as #{name} from "./#{name}/mod.ts";
168+
|]
110169

111-
pkgsString :: String -> Map String PkgInfo -> String
112-
pkgsString varName pkgs =
113-
intercalate "\n" $ formatPkg varName <$> toAscList pkgs
170+
pkgsString :: Map String PkgInfo -> String
171+
pkgsString pkgs =
172+
intercalate "\n" $ formatPkg <$> toAscList pkgs
114173

115174
sanitize :: String -> String
116175
sanitize str
176+
| isDigit $ head str = sanitize $ "_" <> str
117177
| str `elem` tsKeywords = str <> "_"
118178
| otherwise =
119179
str <&> \case
180+
'+' -> '_'
120181
'-' -> '_'
182+
'.' -> '_'
183+
'/' -> '_'
184+
'@' -> '_'
121185
x -> x
122186

123187
tsKeywords :: [String]
@@ -145,8 +209,10 @@ tsKeywords =
145209
"import",
146210
"in",
147211
"instanceOf",
212+
"interface",
148213
"new",
149214
"null",
215+
"private",
150216
"return",
151217
"super",
152218
"switch",
@@ -155,6 +221,7 @@ tsKeywords =
155221
"true",
156222
"try",
157223
"typeOf",
224+
"typeof",
158225
"var",
159226
"void",
160227
"while",

0 commit comments

Comments
 (0)