Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DecodedLibraryWithDeps #1374

Merged
merged 1 commit into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 41 additions & 39 deletions core/src/main/scala/org/bykn/bosatsu/library/Command.scala
Original file line number Diff line number Diff line change
Expand Up @@ -597,62 +597,64 @@ object Command {
libFromCas(dep)
}
.map { fetchedLibsOpt =>
val fetchedDeps = fetchedLibsOpt.flatMap {
fetchedLibsOpt.flatMap {
case None => Nil
case Some(dep) =>
// we will find the transitivies by walking them
dep.arg.publicDependencies.toList ::: dep.arg.privateDependencies.toList
(dep.arg.publicDependencies.toList ::: dep.arg.privateDependencies.toList)
.filterNot { dep =>
nextFetched.contains((dep.name, versionOf(dep)))
}
}

fetchedDeps.filterNot { dep =>
nextFetched.contains((dep.name, versionOf(dep)))
}
}

nextBatchF.map((nextFetched, _))
}

def showFetchState(fs: FetchState): F[Doc] = {
val depStr = if (fs.size == 1) "dependency" else "dependencies"
val header = Doc.text(s"fetched ${fs.size} transitive ${depStr}.")

val resultDoc = header + Doc.line + Doc.intercalate(
Doc.hardLine,
fs.toList.map { case ((n, v), hashes) =>
val sortedHashes = hashes.toList.sortBy(_._1.toIdent)
val hashDoc = Doc.intercalate(
Doc.comma + Doc.line,
sortedHashes.map { case (wh, msg) =>
val ident = wh.toIdent
msg match {
case Right(true) => Doc.text(show"fetched $ident")
case Right(false) => Doc.text(show"cached $ident")
case Left(err) =>
Doc.text(show"failed: $ident ${err.getMessage}")
}
}
)

Doc.text(show"$n $v:") + (Doc.line + hashDoc).nested(4).grouped
}
)

val success = fs.forall { case (_, dl) =>
dl.forall { case (_, res) => res.isRight }
}
if (success) moduleIOMonad.pure(resultDoc)
else
moduleIOMonad.raiseError(
CliException("failed to fetch", err = resultDoc)
)
}

moduleIOMonad
.tailRecM((SortedMap.empty: FetchState, deps)) { case (fetched, deps) =>
step(fetched, deps).map {
case (state, Nil) => Right(state)
case next => Left(next)
}
}
.flatMap { fs =>
val depStr = if (fs.size == 1) "dependency" else "dependencies"
val header = Doc.text(s"fetched ${fs.size} transitive ${depStr}.")

val resultDoc = header + Doc.line + Doc.intercalate(
Doc.hardLine,
fs.toList.map { case ((n, v), hashes) =>
val sortedHashes = hashes.toList.sortBy(_._1.toIdent)
val hashDoc = Doc.intercalate(
Doc.comma + Doc.line,
sortedHashes.map { case (wh, msg) =>
val ident = wh.toIdent
msg match {
case Right(true) => Doc.text(show"fetched $ident")
case Right(false) => Doc.text(show"cached $ident")
case Left(err) =>
Doc.text(show"failed: $ident ${err.getMessage}")
}
}
)

Doc.text(show"$n $v:") + (Doc.line + hashDoc).nested(4).grouped
}
)

val success = fs.forall { case (_, dl) =>
dl.forall { case (_, res) => res.isRight }
}
if (success) moduleIOMonad.pure(resultDoc)
else
moduleIOMonad.raiseError(
CliException("failed to fetch", err = resultDoc)
)
}
.flatMap(showFetchState(_))
}
}
}
112 changes: 103 additions & 9 deletions core/src/main/scala/org/bykn/bosatsu/library/DecodedLibrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package org.bykn.bosatsu.library

import _root_.bosatsu.{TypedAst => proto}
import cats.MonadError
import cats.data.StateT
import cats.syntax.all._
import org.bykn.bosatsu.hashing.{Hashed, HashValue}
import org.bykn.bosatsu.hashing.{Algo, Hashed, HashValue}
import org.bykn.bosatsu.tool.CliException
import org.bykn.bosatsu.{Package, PackageName, PackageMap, ProtoConverter}
import scala.collection.immutable.{SortedMap, SortedSet}
import org.typelevel.paiges.Doc

case class DecodedLibrary[A](
name: Name,
version: Version,
hashValue: HashValue[A],
protoLib: proto.Library,
interfaces: List[Package.Interface],
Expand All @@ -18,7 +23,7 @@ case class DecodedLibrary[A](
}

object DecodedLibrary {
def decode[F[_], A](
def decode[F[_], A: Algo](
protoLib: Hashed[A, proto.Library]
)(implicit F: MonadError[F, Throwable]): F[DecodedLibrary[A]] =
F.fromTry(
Expand All @@ -27,14 +32,103 @@ object DecodedLibrary {
protoLib.arg.exportedIfaces,
protoLib.arg.internalPackages
)
).map { case (ifs, impls) =>
).flatMap { case (ifs, impls) =>
// TODO: should verify somewhere that all the package names are distinct, but since this is presumed to be
// a good library maybe that's a waste
DecodedLibrary[A](
protoLib.hash,
protoLib.arg,
ifs,
PackageMap(impls.iterator.map(pack => (pack.name, pack)).to(SortedMap))
)

protoLib.arg.descriptor.flatMap(_.version) match {
case Some(protoV) =>
F.pure(
DecodedLibrary[A](
Name(protoLib.arg.name),
Version.fromProto(protoV),
protoLib.hash,
protoLib.arg,
ifs,
PackageMap(
impls.iterator.map(pack => (pack.name, pack)).to(SortedMap)
)
)
)
case None =>
F.raiseError(
CliException(
"missing version",
Doc.text(
show"while decoding library ${protoLib.arg.name} with hash ${protoLib.hash.toIdent} has missing version."
)
)
)
}
}
}

case class DecodedLibraryWithDeps[A](
lib: DecodedLibrary[A],
deps: SortedMap[(Name, Version), DecodedLibraryWithDeps[A]]
) {
def name: Name = lib.name
def version: Version = lib.version
}

object DecodedLibraryWithDeps {
def decodeAll[F[_]](
protoLib: Hashed[Algo.Blake3, proto.Library]
)(load: proto.LibDependency => F[Hashed[Algo.Blake3, proto.Library]])(implicit
F: MonadError[F, Throwable]
): F[DecodedLibraryWithDeps[Algo.Blake3]] = {
type Key = (Name, Version)
type Value = DecodedLibraryWithDeps[Algo.Blake3]
type S = Map[Key, Value]
type Cached[T] = StateT[F, S, T]

val getS: Cached[S] = StateT.get

def get(k: Key): Cached[Option[Value]] =
getS.map(_.get(k))

def decodeAndStore(
protoLib: Hashed[Algo.Blake3, proto.Library]
): Cached[Value] =
for {
root <- StateT.liftF(DecodedLibrary.decode[F, Algo.Blake3](protoLib))
deps <-
(protoLib.arg.privateDependencies.toList ::: protoLib.arg.publicDependencies.toList)
.traverse(fetchDep(_))
depMap = deps.iterator
.map(dec => (dec.name, dec.version) -> dec)
.to(SortedMap)
result = DecodedLibraryWithDeps(root, depMap)
_ <- StateT.modify[F, S](
_.updated((root.name, root.version), result)
)
} yield result

def fetchDep(dep: proto.LibDependency): Cached[Value] = {
val fV = dep.desc.flatMap(_.version) match {
case Some(v) => F.pure(Version.fromProto(v))
case None =>
F.raiseError[Version](
CliException(
"missing version",
Doc.text(
show"while loading dependency ${dep.name} with hashes=${dep.desc.toList.flatMap(_.hashes).mkString(",")}: missing version."
)
)
)
}

for {
v <- StateT.liftF[F, S, Version](fV)
cached <- get((Name(dep.name), v))
res <- cached match {
case Some(d) => StateT.pure[F, S, Value](d)
case None =>
StateT.liftF(load(dep)).flatMap(decodeAndStore(_))
}
} yield res
}

decodeAndStore(protoLib).runA(Map.empty)
}
}
Loading