0 | module Pack.Database.Types
  1 |
  2 | import Core.Name.Namespace
  3 | import Data.List1
  4 | import Data.List.Elem
  5 | import Data.SortedMap
  6 | import Data.String
  7 | import Derive.Prelude
  8 | import Idris.Package.Types
  9 | import Pack.Core.Types
 10 |
 11 | %default total
 12 | %language ElabReflection
 13 |
 14 | --------------------------------------------------------------------------------
 15 | --          MetaCommits
 16 | --------------------------------------------------------------------------------
 17 |
 18 | ||| A git commit hash or tag, or a meta commit: The latest commit of a branch.
 19 | public export
 20 | data MetaCommit : Type where
 21 |   MC     : Commit -> MetaCommit
 22 |   Latest : Branch -> MetaCommit
 23 |   Fetch  : Branch -> MetaCommit
 24 |
 25 | public export
 26 | FromString MetaCommit where
 27 |   fromString s = case forget $ split (':' ==) s of
 28 |     ["latest",branch]       => Latest $ MkBranch branch
 29 |     ["fetch-latest",branch] => Fetch $ MkBranch branch
 30 |     _                       => MC $ MkCommit s
 31 |
 32 | export
 33 | Interpolation MetaCommit where
 34 |   interpolate (Latest b) = "latest:\{b}"
 35 |   interpolate (Fetch b)  = "fetch-latest:\{b}"
 36 |   interpolate (MC c)     = "\{c}"
 37 |
 38 | export
 39 | toLatest : MetaCommit -> MetaCommit
 40 | toLatest (MC x) = Latest (MkBranch x.value)
 41 | toLatest x      = x
 42 |
 43 | ||| Strategy for resolving `MetaCommit`s
 44 | public export
 45 | data FetchMethod : Type where
 46 |   ||| Update only meta commits, which don't have a cached entry in the
 47 |   ||| "commits" directory. This is the default.
 48 |   MissingOnly  : FetchMethod
 49 |
 50 |   ||| Update all custom commits. This is the strategy used with the `fetch`
 51 |   ||| command.
 52 |   All          : FetchMethod
 53 |
 54 |   ||| Remove the "commits" directory, thus forcing all meta commits to
 55 |   ||| be resolved again. This is the strategy used with the `switch latest`
 56 |   ||| command
 57 |   ClearCommits : FetchMethod
 58 |
 59 | %runElab derive "FetchMethod" [Show,Eq,Ord]
 60 |
 61 | --------------------------------------------------------------------------------
 62 | --          Core Packages
 63 | --------------------------------------------------------------------------------
 64 |
 65 | ||| Core packages bundled with the Idris compiler
 66 | public export
 67 | data CorePkg =
 68 |     Prelude
 69 |   | Base
 70 |   | Contrib
 71 |   | Linear
 72 |   | Network
 73 |   | Test
 74 |   | Papers
 75 |   | IdrisApi
 76 |
 77 | ||| The list of core packages.
 78 | public export
 79 | corePkgs : List CorePkg
 80 | corePkgs = [Prelude, Base, Contrib, Linear, Network, Test, Papers, IdrisApi]
 81 |
 82 | export
 83 | Interpolation CorePkg where
 84 |   interpolate Prelude  = "prelude"
 85 |   interpolate Base     = "base"
 86 |   interpolate Contrib  = "contrib"
 87 |   interpolate Linear   = "linear"
 88 |   interpolate Network  = "network"
 89 |   interpolate Test     = "test"
 90 |   interpolate Papers   = "papers"
 91 |   interpolate IdrisApi = "idris2"
 92 |
 93 | export
 94 | Cast CorePkg Body where
 95 |   cast Prelude  = "prelude"
 96 |   cast Base     = "base"
 97 |   cast Contrib  = "contrib"
 98 |   cast Linear   = "linear"
 99 |   cast Network  = "network"
100 |   cast Test     = "test"
101 |   cast Papers   = "papers"
102 |   cast IdrisApi = "idris2"
103 |
104 | export %inline
105 | Cast CorePkg (Path Rel) where
106 |   cast c = PRel [< cast c]
107 |
108 | ||| Package name of a core package.
109 | export
110 | corePkgName : CorePkg -> PkgName
111 | corePkgName = MkPkgName . interpolate
112 |
113 | ||| `.ipkg` file name corrsponding to a core package.
114 | export
115 | coreIpkgFile : CorePkg -> Body
116 | coreIpkgFile IdrisApi = "idris2api.ipkg"
117 | coreIpkgFile c        = cast c <+> ".ipkg"
118 |
119 | ||| Relative path to the `.ipkg` file corrsponding to a core package
120 | ||| (in the Idris2 project).
121 | export
122 | coreIpkgPath : CorePkg -> File Rel
123 | coreIpkgPath IdrisApi = MkF neutral "idris2api.ipkg"
124 | coreIpkgPath c        = MkF (neutral /> "libs" //> c) (coreIpkgFile c)
125 |
126 | ||| Try to convert a string to a core package.
127 | export
128 | readCorePkg : String -> Maybe CorePkg
129 | readCorePkg "prelude" = Just Prelude
130 | readCorePkg "base"    = Just Base
131 | readCorePkg "contrib" = Just Contrib
132 | readCorePkg "linear"  = Just Linear
133 | readCorePkg "network" = Just Network
134 | readCorePkg "test"    = Just Test
135 | readCorePkg "papers"  = Just Papers
136 | readCorePkg "idris2"  = Just IdrisApi
137 | readCorePkg _         = Nothing
138 |
139 | ||| True, if the given string corresponds to one of the core packges.
140 | export
141 | isCorePkg : String -> Bool
142 | isCorePkg = isJust . readCorePkg
143 |
144 | --------------------------------------------------------------------------------
145 | --          Packages
146 | --------------------------------------------------------------------------------
147 |
148 | ||| Description of a Git or local Idris package in the
149 | ||| package database.
150 | |||
151 | ||| Note: This does not contain the package name, as it
152 | ||| will be paired with its name in a `SortedMap`.
153 | public export
154 | data Package_ : (c : Type) -> Type where
155 |   ||| A Git repository, given as the package's URL,
156 |   ||| commit (hash or tag), and name of `.ipkg` file to use.
157 |   ||| `pkgPath` should be set to `True` for executables which need
158 |   ||| access to the `IDRIS2_PACKAGE_PATH`: The list of directories
159 |   ||| where Idris packages are installed.
160 |   Git :  (url      : URL)
161 |       -> (commit   : c)
162 |       -> (ipkg     : File Rel)
163 |       -> (pkgPath  : Bool)
164 |       -> (testIpkg : Maybe (File Rel))
165 |       -> (notice   : Maybe String)
166 |       -> Package_ c
167 |
168 |   ||| A local Idris project given as an absolute path to a local
169 |   ||| directory, and `.ipkg` file to use.
170 |   ||| `pkgPath` should be set to `True` for executable which need
171 |   ||| access to the `IDRIS2_PACKAGE_PATH`: The list of directories
172 |   ||| where Idris packages are installed.
173 |   Local  :  (dir      : Path Abs)
174 |          -> (ipkg     : File Rel)
175 |          -> (pkgPath  : Bool)
176 |          -> (testIpkg : Maybe (File Rel))
177 |          -> Package_ c
178 |
179 |   ||| A core package of the Idris2 project
180 |   Core   : (core : CorePkg) -> Package_ c
181 |
182 | export
183 | Functor Package_ where
184 |   map f (Git u c i p t n) = Git u (f c) i p t n
185 |   map f (Local d i p t) = Local d i p t
186 |   map f (Core c)        = Core c
187 |
188 | export
189 | traverse : Applicative f => (URL -> a -> f b) -> Package_ a -> f (Package_ b)
190 | traverse g (Git u c i p t n) = (\c' => Git u c' i p t n) <$> g u c
191 | traverse _ (Local d i p t)    = pure $ Local d i p t
192 | traverse _ (Core c)           = pure $ Core c
193 |
194 | ||| An alias for `Package_ Commit`: A package description with
195 | ||| meta commits already resolved.
196 | public export
197 | 0 Package : Type
198 | Package = Package_ Commit
199 |
200 | ||| An alias for `Package_ MetaCommit`: A package description where
201 | ||| the commit might still contain meta information.
202 | public export
203 | 0 UserPackage : Type
204 | UserPackage = Package_ MetaCommit
205 |
206 | ||| Proof that a package is a core package
207 | public export
208 | data IsCore : Package -> Type where
209 |   ItIsCore : IsCore (Core c)
210 |
211 | export
212 | Uninhabited (IsCore $ Local {}) where
213 |   uninhabited _ impossible
214 |
215 | export
216 | Uninhabited (IsCore $ Git {}) where
217 |   uninhabited _ impossible
218 |
219 | ||| Decides, if the given package represents
220 | ||| one of the core packages (`base`, `prelude`, etc.)
221 | export
222 | isCore : (p : Package) -> Dec (IsCore p)
223 | isCore (Core {})  = Yes ItIsCore
224 | isCore (Git {})   = No absurd
225 | isCore (Local {}) = No absurd
226 |
227 | ||| Proof that a package is a local package
228 | public export
229 | data IsLocal : Package -> Type where
230 |   ItIsLocal : IsLocal (Local {})
231 |
232 | export
233 | Uninhabited (IsLocal $ Core {}) where
234 |   uninhabited _ impossible
235 |
236 | export
237 | Uninhabited (IsLocal $ Git {}) where
238 |   uninhabited _ impossible
239 |
240 | ||| Decides, if the given package represents
241 | ||| a local package.
242 | export
243 | isLocal : (p : Package) -> Dec (IsLocal p)
244 | isLocal (Core {})  = No absurd
245 | isLocal (Git {})   = No absurd
246 | isLocal (Local {}) = Yes ItIsLocal
247 |
248 | ||| Proof that a package is a Git package
249 | public export
250 | data IsGit : Package -> Type where
251 |   ItIsGit : IsGit (Git {})
252 |
253 | export
254 | Uninhabited (IsGit $ Core {}) where
255 |   uninhabited _ impossible
256 |
257 | export
258 | Uninhabited (IsGit $ Local {}) where
259 |   uninhabited _ impossible
260 |
261 | ||| Decides, if the given package represents
262 | ||| a Git package.
263 | export
264 | isGit : (p : Package) -> Dec (IsGit p)
265 | isGit (Core {})  = No absurd
266 | isGit (Git {})   = Yes ItIsGit
267 | isGit (Local {}) = No absurd
268 |
269 | ||| True, if the given application needs access to the
270 | ||| folders where Idris package are installed.
271 | export
272 | usePackagePath : Package_ c -> Bool
273 | usePackagePath (Git _ _ _ pp _ _) = pp
274 | usePackagePath (Local _ _ pp _) = pp
275 | usePackagePath (Core _)         = False
276 |
277 | ||| Absolute path to the `.ipkg` file of a package.
278 | export
279 | ipkg : (dir : Path Abs) -> Package -> File Abs
280 | ipkg dir (Git _ _ i _ _ _) = toAbsFile dir i
281 | ipkg dir (Local _ i _ _) = toAbsFile dir i
282 | ipkg dir (Core c)        = toAbsFile dir (coreIpkgPath c)
283 |
284 | --------------------------------------------------------------------------------
285 | --          Resolved Packages
286 | --------------------------------------------------------------------------------
287 |
288 | ||| Installation status of an Idris package.
289 | public export
290 | data PkgStatus : Package -> Type where
291 |   Missing     : Hash -> PkgStatus p
292 |   Installed   : Hash -> (withDocs : Bool) -> PkgStatus p
293 |
294 | export
295 | hash : PkgStatus p -> Hash
296 | hash (Missing h)     = h
297 | hash (Installed h _) = h
298 |
299 | ||| A resolved library, which was cloned from a Git repo
300 | ||| or looked up in the local file system. This comes with
301 | ||| a fully parsed `PkgDesc` (representing the `.ipkg` file).
302 | public export
303 | record ResolvedLib t where
304 |   constructor RL
305 |   pkg     : Package
306 |   hash    : Hash
307 |   name    : PkgName
308 |   desc    : Desc t
309 |   status  : PkgStatus pkg
310 |   deps    : List (DPair Package PkgStatus)
311 |
312 | namespace ResolvedLib
313 |   ||| Extracts the package name from a resolved library.
314 |   export %inline
315 |   nameStr : ResolvedLib t -> String
316 |   nameStr = value . name
317 |
318 |   ||| Change the type-level tag of a resolved library.
319 |   export %inline
320 |   reTag : ResolvedLib s -> Desc t -> ResolvedLib t
321 |   reTag rl d = {desc := d} rl
322 |
323 |   ||| Extracts the dependencies of a resolved library.
324 |   export
325 |   dependencies : ResolvedLib t -> List PkgName
326 |   dependencies rp = dependencies rp.desc
327 |
328 |   ||| Check if the given library is installed
329 |   export
330 |   isInstalled : ResolvedLib t -> Bool
331 |   isInstalled rl = case rl.status of
332 |     Missing _ => False
333 |     _         => True
334 |
335 | namespace AppStatus
336 |   ||| Installation status of an Idris app.
337 |   public export
338 |   data AppStatus : Package -> Type where
339 |     ||| The app has not been compiled and is therfore missing
340 |     Missing      :  Hash -> AppStatus p
341 |
342 |     ||| The app has been built but is not on the `PATH`.
343 |     Installed    :  Hash -> AppStatus p
344 |
345 |     ||| The app has been built and a wrapper script has been added
346 |     ||| to `$PACK_DIR/bin`, so it should be on the `PATH`.
347 |     BinInstalled :  Hash -> AppStatus p
348 |
349 | ||| A resolved application, which was cloned from a Git repo
350 | ||| or looked up in the local file system. This comes with
351 | ||| a fully parsed `PkgDesc` (representing the `.ipkg` file).
352 | public export
353 | record ResolvedApp t where
354 |   constructor RA
355 |   pkg     : Package
356 |   hash    : Hash
357 |   name    : PkgName
358 |   desc    : Desc t
359 |   status  : AppStatus pkg
360 |   exec    : Body
361 |   deps    : List (DPair Package PkgStatus)
362 |
363 | namespace ResolveApp
364 |   ||| Extracts the package name from a resolved application.
365 |   export %inline
366 |   nameStr : ResolvedApp t -> String
367 |   nameStr = value . name
368 |
369 |   ||| Extracts the dependencies of a resolved application.
370 |   export
371 |   dependencies : ResolvedApp t -> List PkgName
372 |   dependencies rp = dependencies rp.desc
373 |
374 |   ||| Change the type-level tag of a resolved application.
375 |   export %inline
376 |   reTag : ResolvedApp s -> Desc t -> ResolvedApp t
377 |   reTag rl d = {desc := d} rl
378 |
379 |   ||| True, if the given application needs access to the
380 |   ||| folders where Idris package are installed.
381 |   export %inline
382 |   usePackagePath : ResolvedApp t -> Bool
383 |   usePackagePath = usePackagePath . pkg
384 |
385 | ||| Either a resolved library or application tagged with the given tag.
386 | ||| This is to be used in build plans, so applications come with the
387 | ||| additional info whether we want to install a wrapper script or not.
388 | public export
389 | data LibOrApp : (t,s : PkgDesc -> Type) -> Type where
390 |   Lib : ResolvedLib t -> LibOrApp t s
391 |   App : (withWrapperScript : Bool) -> ResolvedApp s -> LibOrApp t s
392 |
393 | namespace LibOrApp
394 |   ||| Extract the dependencies of a resolved library or application.
395 |   export
396 |   dependencies : LibOrApp t s -> List PkgName
397 |   dependencies (Lib x)   = dependencies x
398 |   dependencies (App _ x) = dependencies x
399 |
400 |   ||| Extract the package of a resolved library or application.
401 |   export
402 |   pkg : LibOrApp t s -> Package
403 |   pkg (Lib x)   = x.pkg
404 |   pkg (App _ x) = x.pkg
405 |
406 |   ||| Extract the description of a resolved library or application.
407 |   export
408 |   desc : LibOrApp t t -> Desc t
409 |   desc (Lib x)   = x.desc
410 |   desc (App _ x) = x.desc
411 |
412 |   ||| Extract the package name of a resolved library or application.
413 |   export
414 |   name : LibOrApp t s -> PkgName
415 |   name (Lib x)   = x.name
416 |   name (App _ x) = x.name
417 |
418 |   ||| Try to extract the package name of a resolved library.
419 |   export
420 |   libName : LibOrApp t s -> Maybe PkgName
421 |   libName (Lib x)   = Just x.name
422 |   libName (App _ _) = Nothing
423 |
424 | --------------------------------------------------------------------------------
425 | --          Package Database
426 | --------------------------------------------------------------------------------
427 |
428 | ||| DB used for building packages. This includes
429 | ||| the Idris commit to use, together with a curated list of
430 | ||| known packages.
431 | public export
432 | record DB_ c where
433 |   constructor MkDB
434 |   idrisURL     : URL
435 |   idrisCommit  : c
436 |   idrisVersion : PkgVersion
437 |   packages     : SortedMap PkgName (Package_ c)
438 |
439 | export
440 | Functor DB_ where
441 |   map f db = {
442 |       idrisCommit $= f
443 |     , packages    $= map (map f)
444 |     } db
445 |
446 | public export
447 | 0 DB : Type
448 | DB = DB_ Commit
449 |
450 | public export
451 | 0 MetaDB : Type
452 | MetaDB = DB_ MetaCommit
453 |
454 | ||| Sets the package version.
455 | export %inline
456 | setVersion : PkgVersion -> DB -> DB
457 | setVersion v = {idrisVersion := v}
458 |
459 | ||| Effectfully convert package descriptions in a DB
460 | export
461 | traverseDB :
462 |      {auto _ : Applicative f}
463 |   -> (URL -> a -> f b)
464 |   -> DB_ a
465 |   -> f (DB_ b)
466 | traverseDB g db =
467 |   let ic   := g db.idrisURL db.idrisCommit
468 |       pkgs := traverse (traverse g) db.packages
469 |    in [| adj ic pkgs |]
470 |     where
471 |       adj : b -> SortedMap PkgName (Package_ b) -> DB_ b
472 |       adj ic cb = {idrisCommit := ic, packages := cb} db
473 |
474 | tomlBool : Bool -> String
475 | tomlBool True  = "true"
476 | tomlBool False = "false"
477 |
478 | testPath : Maybe (File Rel) -> Maybe String
479 | testPath = map (\x => "test        = \{quote x}")
480 |
481 | notice : Maybe String -> Maybe String
482 | notice = map (\x =>   "notice      = \{quote x}")
483 |
484 | -- we need to print `Git` packages as `"github"` at
485 | -- least for the time being for reasons of compatibility
486 | printPair : (PkgName,Package) -> List String
487 | printPair (x, Git url commit ipkg pp t n) =
488 |   [ "[db.\{x}]"
489 |   , "type        = \"github\""
490 |   , "url         = \{quote url}"
491 |   , "commit      = \{quote commit}"
492 |   , "ipkg        = \{quote ipkg}"
493 |   , "packagePath = \{tomlBool pp}"
494 |   ] ++ (catMaybes [testPath t, notice n])
495 |
496 | printPair (x, Local dir ipkg pp t) =
497 |   [ "[db.\{x}]"
498 |   , "type        = \"local\""
499 |   , "path        = \{quote dir}"
500 |   , "ipkg        = \{quote ipkg}"
501 |   , "packagePath = \{tomlBool pp}"
502 |   ] ++ (catMaybes [testPath t])
503 |
504 | printPair (x, Core c) =
505 |   [ "[db.\{x}]"
506 |   , "type        = \"core\""
507 |   ]
508 |
509 | ||| Convert a package collection to a valid TOML string.
510 | export
511 | printDB : DB -> String
512 | printDB (MkDB u c v db) =
513 |   let header := """
514 |         [idris2]
515 |         url     = "\{u}"
516 |         version = "\{v}"
517 |         commit  = "\{c}"
518 |         """
519 |    in unlines $ header :: (SortedMap.toList db >>= \p => "" :: printPair p)
520 |
521 | --------------------------------------------------------------------------------
522 | --          Tests
523 | --------------------------------------------------------------------------------
524 |
525 | -- make sure no core package was forgotten
526 | 0 corePkgsTest : (c : CorePkg) -> Elem c Types.corePkgs
527 | corePkgsTest Prelude  = %search
528 | corePkgsTest Base     = %search
529 | corePkgsTest Contrib  = %search
530 | corePkgsTest Linear   = %search
531 | corePkgsTest Network  = %search
532 | corePkgsTest Test     = %search
533 | corePkgsTest Papers   = %search
534 | corePkgsTest IdrisApi = %search
535 |
536 | -- all core packages should be parsable from their
537 | -- interpolation string
538 | 0 corePkgRoundTrip : (c : CorePkg) -> readCorePkg (interpolate c) === Just c
539 | corePkgRoundTrip Prelude  = Refl
540 | corePkgRoundTrip Base     = Refl
541 | corePkgRoundTrip Contrib  = Refl
542 | corePkgRoundTrip Linear   = Refl
543 | corePkgRoundTrip Network  = Refl
544 | corePkgRoundTrip Test     = Refl
545 | corePkgRoundTrip Papers   = Refl
546 | corePkgRoundTrip IdrisApi = Refl
547 |