Skip to content

Commit

Permalink
Improve SBT caching during code generation (#1499)
Browse files Browse the repository at this point in the history
  • Loading branch information
majk-p authored May 18, 2024
1 parent b96f736 commit 1b40612
Show file tree
Hide file tree
Showing 19 changed files with 93 additions and 58 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ We apologize for the inconvenience.
* `smithy4sUpdateLSPConfig`: Replace `imports` with `sources` to be more in line with idiomatic smithy-build config in https://github.com/disneystreaming/smithy4s/pull/1518 (see https://github.com/disneystreaming/smithy4s/issues/1459)
* Update smithy: 1.45.0 to 1.49.0 (binary breaking) in https://github.com/disneystreaming/smithy4s/pull/1485
* Rendered type aliases are now sorted alphabetically in https://github.com/disneystreaming/smithy4s/pull/1523
* Adds handlers construct to facilitate the decoupling of operation implementations in https://github.com/disneystreaming/smithy4s/pull/1522
* Add handlers construct to facilitate the decoupling of operation implementations in https://github.com/disneystreaming/smithy4s/pull/1522
* Improve cache in code generation (sbt) in https://github.com/disneystreaming/smithy4s/pull/1499

# 0.18.18

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
> run

# This new type was not flattened because it has range constraints
$ exists smithy_output/com/amazonaws/dynamodb/ListTablesInputLimit.scala
$ exists smithy_output/smithy4s/com/amazonaws/dynamodb/ListTablesInputLimit.scala

# Flattened and removed by the projection transformer
-$ exists smithy_output/com/amazonaws/dynamodb/Long.scala
-$ exists smithy_output/smithy4s/com/amazonaws/dynamodb/Long.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# check if smithy4sCodegen works
> smithy4sCodegen
$ exists target/scala-2.13/src_managed/main/scala/com/amazonaws/dynamodb/AttributeValue.scala
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/com/amazonaws/dynamodb/AttributeValue.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# check if smithy4sCodegen works
> show p1/smithy4sAllExternalDependencies
> p1/compile
$ exists p1/smithy_output/aws/iam/ActionPermissionDescription.scala
$ exists p1/smithy_output/smithy4s/example/ObjectService.scala
$ exists p1/smithy_output/smithy4s/aws/iam/ActionPermissionDescription.scala
$ exists p1/smithy_output/smithy4s/smithy4s/example/ObjectService.scala

> p2/compile
$ exists p2/smithy_output/aws/iam/ActionPermissionDescription.scala
$ exists p2/smithy_output/smithy4s/example/ObjectService.scala
-$ exists p2/smithy_output/smithy4s/toexclude/StructureToExclude.scala
$ exists p2/smithy_output/smithy4s/aws/iam/ActionPermissionDescription.scala
$ exists p2/smithy_output/smithy4s/smithy4s/example/ObjectService.scala
-$ exists p2/smithy_output/smithy4s/smithy4s/toexclude/StructureToExclude.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# check if smithy4sCodegen works
> compile
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/example/ObjectService.scala
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/smithy4s/example/ObjectService.scala
$ exists target/scala-2.13/resource_managed/main/smithy4s.example.ObjectService.json

# check if code can run, this can reveal runtime issues
# such as initialization errors
> run
$ copy-file example-added.smithy src/main/smithy/example-added.smithy
> compile
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/example/Added.scala
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/smithy4s/example/Added.scala

# ensuring that removing existing files removes their outputs
$ delete src/main/smithy/example.smithy
-> compile

> smithy4sUpdateLSPConfig
> checkSmithyBuild
> checkSmithyBuild
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# check if smithy4sCodegen works
> p1/compile
$ exists p1/smithy_output/aws/iam/ActionPermissionDescription.scala
$ exists p1/smithy_output/smithy4s/aws/iam/ActionPermissionDescription.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# check if main sources are generated
> compile
$ exists target/scala-2.13/src_managed/main/scala/example/ExampleStruct.scala
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/example/ExampleStruct.scala

# check if test sources are generated
> Test/compile
$ exists target/scala-2.13/src_managed/test/scala/testexample/TestStruct.scala
$ exists target/scala-2.13/src_managed/test/scala/smithy4s/testexample/TestStruct.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# check if smithy4sCodegen works with libraries that were built with Smithy4s
> show bar/smithy4sAllExternalDependencies
> compile
$ exists foo/target/scala-2.13/src_managed/main/scala/foo/Lambda.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/foo/Lambda.scala
$ exists foo/target/scala-2.13/src_managed/main/scala/smithy4s/foo/Lambda.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/smithy4s/foo/Lambda.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
> foo/publishLocal
> upstream/publishLocal
> bar/compile
$ exists bar/target/scala-2.13/src_managed/main/scala/bar/Bar.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/foo/Foo.scala
$ exists bar/target/scala-2.13/src_managed/main/scala/smithy4s/bar/Bar.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/smithy4s/foo/Foo.scala

# check if code can run, this can reveal runtime issues# such as initialization errors
> bar/run
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# check if smithy4sCodegen works in multimodule contexts
> compile
$ exists bar/target/scala-2.13/src_managed/main/scala/bar/Bar.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/foo/Foo.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/foodir/FooDir.scala
$ exists foo/target/scala-2.13/src_managed/main/scala/foo/Foo.scala
$ exists foo/target/scala-2.13/src_managed/main/scala/foodir/FooDir.scala

$ exists bar/target/scala-2.13/src_managed/main/scala/smithy4s/bar/Bar.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/smithy4s/foo/Foo.scala
$ absent bar/target/scala-2.13/src_managed/main/scala/smithy4s/foodir/FooDir.scala
$ exists foo/target/scala-2.13/src_managed/main/scala/smithy4s/foo/Foo.scala
$ exists foo/target/scala-2.13/src_managed/main/scala/smithy4s/foodir/FooDir.scala
# check if code can run, this can reveal runtime issues# such as initialization errors
> bar/run

Expand All @@ -14,4 +13,4 @@ $ copy-file a.scala foo/src/main/scala/a.scala
> bar/run

> smithy4sUpdateLSPConfig
> checkSmithyBuild
> checkSmithyBuild
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# check if smithy4sCodegen works
> compile

$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/errors/BadRequest.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/errors/InternalServerError.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/errors/ErrorService.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/errors/package.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/smithy4s/errors/BadRequest.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/smithy4s/errors/InternalServerError.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/smithy4s/errors/ErrorService.scala
$ exists target/scala-3.3.0/src_managed/main/scala/smithy4s/smithy4s/errors/package.scala

# check if code can run
> run
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# check if smithy4sCodegen works
> compile
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/example/ObjectService.scala
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/smithy4s/example/ObjectService.scala
$ exists target/scala-2.13/resource_managed/main/smithy4s.example.ObjectService.json
> checkOpenApi
> checkOpenApi
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# check if smithy4sCodegen works
> compile
$ exists target/scala-2.13/src_managed/main/scala/smithy/rules
$ exists target/scala-2.13/src_managed/main/scala/smithy4s/smithy/rules
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,3 @@ $ exists target/scala-2.13/resource_managed/main/META-INF/smithy/generated-metad
> ++3.3.0 root/compile
$ exists target/scala-3.3.0/src_managed/main/smithy/generated-metadata.smithy
$ exists target/scala-3.3.0/resource_managed/main/META-INF/smithy/generated-metadata.smithy

# ensure metadata file is re-generated after deleting
$ delete target/scala-2.13/src_managed/main/smithy/generated-metadata.smithy
$ delete target/scala-3.3.0/resource_managed/main/META-INF/smithy/generated-metadata.smithy
> ++3.3.0 root/compile
$ exists target/scala-3.3.0/src_managed/main/smithy/generated-metadata.smithy
$ exists target/scala-3.3.0/resource_managed/main/META-INF/smithy/generated-metadata.smithy
36 changes: 23 additions & 13 deletions modules/codegen-plugin/src/smithy4s/codegen/JsonConverters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,30 @@ private[smithy4s] object JsonConverters {
// This serialises a path by providing a hash of the content it points to.
// Because the hash is part of the Json, this allows SBT to detect when a file
// changes and invalidate its relevant caches, leading to a call to Smithy4s' code generator.
implicit val pathFormat: JsonFormat[os.Path] =
BasicJsonProtocol.projectFormat[os.Path, HashFileInfo](
implicit val pathRefFormat: JsonFormat[PathRef] =
BasicJsonProtocol.projectFormat[PathRef, HashFileInfo](
p => {
if (os.isFile(p)) FileInfo.hash(p.toIO)
if (os.isFile(p.underlying)) FileInfo.hash(p.underlying.toIO)
else
// If the path is a directory, we get the hashes of all files
// then hash the concatenation of the hash's bytes.
FileInfo.hash(
p.toIO,
p.underlying.toIO,
Hash(
os.walk(p)
os.walk(p.underlying)
.map(_.toIO)
.map(Hash(_))
.foldLeft(Array.emptyByteArray)(_ ++ _)
)
)
},
hash => os.Path(hash.file)
hash => PathRef(os.Path(hash.file))
)

implicit val pathFormat: JsonFormat[os.Path] =
BasicJsonProtocol.projectFormat[os.Path, String](
p => p.toString,
str => os.Path(str)
)

implicit val fileTypeFormat: JsonFormat[FileType] =
Expand All @@ -61,11 +67,15 @@ private[smithy4s] object JsonConverters {
)

// format: off
type GenTarget = List[os.Path] :*: os.Path :*: os.Path :*: Set[FileType] :*: Boolean:*: Option[Set[String]] :*: Option[Set[String]] :*: List[String] :*: List[String] :*: List[String] :*: List[os.Path] :*: Option[os.Path] :*: LNil
type GenTarget = List[PathRef] :*: os.Path :*: os.Path :*: Set[FileType] :*: Boolean:*: Option[Set[String]] :*: Option[Set[String]] :*: List[String] :*: List[String] :*: List[String] :*: List[PathRef] :*: Option[PathRef] :*: LNil
// format: on

// `output` and `resourceOutput` are intentionally serialized as paths
// instead of PathRefs. This is to avoid hashing the directories that are generated by smithy4s anyway
// See https://github.com/disneystreaming/smithy4s/issues/1495 for reference on this decision
implicit val codegenArgsIso = LList.iso[CodegenArgs, GenTarget](
{ ca: CodegenArgs =>
("specs", ca.specs) :*:
("specs", ca.specs.map(PathRef(_))) :*:
("output", ca.output) :*:
("resourceOutput", ca.resourceOutput) :*:
("skip", ca.skip) :*:
Expand All @@ -75,8 +85,8 @@ private[smithy4s] object JsonConverters {
("repositories", ca.repositories) :*:
("dependencies", ca.dependencies) :*:
("transformers", ca.transformers) :*:
("localJars", ca.localJars) :*:
("smithyBuild", ca.smithyBuild) :*:
("localJars", ca.localJars.map(PathRef(_))) :*:
("smithyBuild", ca.smithyBuild.map(PathRef(_))) :*:
LNil
},
{
Expand All @@ -93,7 +103,7 @@ private[smithy4s] object JsonConverters {
(_, localJars) :*:
(_, smithyBuild) :*: LNil =>
CodegenArgs(
specs,
specs.map(_.underlying),
output,
resourceOutput,
skip,
Expand All @@ -103,8 +113,8 @@ private[smithy4s] object JsonConverters {
repositories,
dependencies,
transformers,
localJars,
smithyBuild
localJars.map(_.underlying),
smithyBuild.map(_.underlying)
)
}
)
Expand Down
19 changes: 19 additions & 0 deletions modules/codegen-plugin/src/smithy4s/codegen/PathRef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2021-2024 Disney Streaming
*
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://disneystreaming.github.io/TOST-1.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package smithy4s.codegen

final case class PathRef(underlying: os.Path)
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,15 @@ object Smithy4sCodegenPlugin extends AutoPlugin {
(inputDirs ++ generatedFiles)
.filter(_.exists())
.toList
val outputPath = (conf / smithy4sOutputDir).value
val outputPath = (conf / smithy4sOutputDir).value / "smithy4s"
val resourceOutputPath = (conf / smithy4sResourceDir).value
val allowedNamespaces =
(conf / smithy4sAllowedNamespaces).?.value.map(_.toSet)
val excludedNamespaces =
(conf / smithy4sExcludedNamespaces).?.value.map(_.toSet)
val localJars =
(conf / smithy4sAllDependenciesAsJars).value.map(os.Path(_)).toList
(conf / smithy4sAllDependenciesAsJars).value.toList.sorted
.map(p => os.Path(p))
val res =
(conf / resolvers).value.toList.collect { case m: MavenRepository =>
m.root
Expand All @@ -425,12 +426,17 @@ object Smithy4sCodegenPlugin extends AutoPlugin {
val skipResources: Set[FileType] =
if ((conf / smithy4sSmithyLibrary).value) Set.empty
else Set(FileType.Resource)

val skipSet = skipResources

val filePaths = inputFiles.map(_.getAbsolutePath())

val specs = filePaths.sorted.map(p => os.Path(p)).toList

val smithyBuildValue = (conf / smithyBuild).value.map(os.Path(_))

val codegenArgs = CodegenArgs(
filePaths.map(os.Path(_)).toList,
specs,
output = os.Path(outputPath),
resourceOutput = os.Path(resourceOutputPath),
skip = skipSet,
Expand All @@ -453,11 +459,13 @@ object Smithy4sCodegenPlugin extends AutoPlugin {
s.cacheStoreFactory.make("output")
) { case ((inputChanged, args), outputs) =>
if (inputChanged || outputs.isEmpty) {
s.log.debug("Regenerating managed sources")
val resPaths = smithy4s.codegen.Codegen
.generateToDisk(args)
.toList
resPaths.map(path => new File(path.toString))
} else {
s.log.debug("Using cached version of outputs")
outputs.getOrElse(Seq.empty)
}
}
Expand Down
1 change: 1 addition & 0 deletions modules/codegen/src/smithy4s/codegen/CodegenArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ final case class CodegenArgs(
def skipOpenapi: Boolean = skip(FileType.Openapi)
def skipResources: Boolean = skip(FileType.Resource)
def skipProto: Boolean = skip(FileType.Proto)

}

sealed abstract class FileType(val name: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ trait Smithy4sModule extends ScalaModule {
val specFiles = (smithy4sGeneratedSmithyFiles() ++ smithy4sInputDirs())
.map(_.path)
.filter(os.exists(_))
.toList

val scalaOutput = smithy4sOutputDir().path
val resourcesOutput = smithy4sResourceOutputDir().path
Expand All @@ -209,10 +210,13 @@ trait Smithy4sModule extends ScalaModule {
val smithyBuildFile = smithyBuild().map(_.path)

val allLocalJars =
smithy4sAllDependenciesAsJars().map(_.path).iterator.to(List)
smithy4sAllDependenciesAsJars()
.map(_.path)
.iterator
.to(List)

val args = CodegenArgs(
specs = specFiles.toList,
specs = specFiles,
output = scalaOutput,
resourceOutput = resourcesOutput,
skip = skipSet,
Expand Down

0 comments on commit 1b40612

Please sign in to comment.