diff --git a/build.sbt b/build.sbt index 186dedca2..60c12f45f 100644 --- a/build.sbt +++ b/build.sbt @@ -471,6 +471,7 @@ lazy val codegenPlugin = (projectMatrix in file("modules/codegen-plugin")) Compile / unmanagedSources / excludeFilter := { f => Glob("**/sbt-test/**").matches(f.toPath) }, + libraryDependencies += Dependencies.MunitV1.diff.value, publishLocal := { // make sure that core and codegen are published before the // plugin is published diff --git a/modules/codegen-plugin/src/sbt-test/codegen-plugin/defaults/test b/modules/codegen-plugin/src/sbt-test/codegen-plugin/defaults/test index a967ff4bc..049493127 100644 --- a/modules/codegen-plugin/src/sbt-test/codegen-plugin/defaults/test +++ b/modules/codegen-plugin/src/sbt-test/codegen-plugin/defaults/test @@ -1,4 +1,5 @@ # check if smithy4sCodegen works +> set logLevel := Level.Debug > compile $ exists target/scala-2.13/src_managed/main/smithy4s/smithy4s/example/ObjectService.scala $ exists target/scala-2.13/resource_managed/main/smithy4s.example.ObjectService.json diff --git a/modules/codegen-plugin/src/smithy4s/codegen/CachedTask.scala b/modules/codegen-plugin/src/smithy4s/codegen/CachedTask.scala new file mode 100644 index 000000000..c634d6ccf --- /dev/null +++ b/modules/codegen-plugin/src/smithy4s/codegen/CachedTask.scala @@ -0,0 +1,83 @@ +/* + * 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 + +import munit.diff.Diff +import sbt._ +import sbt.util.CacheImplicits._ +import sbt.util.CacheStore +import sbt.util.Logger +import sjsonnew._ +import sjsonnew.support.murmurhash.Hasher +import sjsonnew.support.scalajson.unsafe.Converter +import sjsonnew.support.scalajson.unsafe.{PrettyPrinter => prettify} + +import scala.util.Try + +private[codegen] object CachedTask { + + // This implementation is inspired by sbt.util.Tracked.inputChanged + // The main difference is that when the values don't match, the difference is calculated + // using munit-diff and recorded to debug log + def inputChanged[I: JsonFormat, O](store: CacheStore, logger: Logger)( + f: (Boolean, I) => O + ): I => O = { in => + def debug(str: String): Unit = logger.debug(s"[smithy4s] $str") + + val previousValue = Try(store.read[ValueAndHash[I]]()).toOption + val newValueHash = hash(in) + store.write[ValueAndHash[I]]((in, newValueHash)) + + previousValue match { + case None => + debug("Could not read previous inputs value from cache.") + f(true, in) + + case Some((oldValue, previousHash)) => + (toJson(oldValue), toJson(in)) match { + case (Some(oldArgs), Some(newArgs)) if !oldArgs.equals(newArgs) => + val diff = new Diff(prettify(oldArgs), prettify(newArgs)) + val report = diff.createReport( + "Arguments changed between smithy4s codegen invocations, diff:", + printObtainedAsStripMargin = false + ) + debug(report) + f(true, in) + + case (_, _) if (previousHash != newValueHash) => + debug( + "Codegen arguments didn't change, but their hashes didn't match. " + + "This means file change on paths provided as codegen arguments." + ) + f(true, in) + + case _ => + debug("Input didn't change between codegen invocations") + f(false, in) + } + + } + } + + private type ValueAndHash[I] = (I, Int) + + private def toJson[I: JsonFormat](args: I) = + Converter.toJson(args).toOption + + private def hash[I: JsonFormat](in: I) = + Hasher.hash(in).toOption.getOrElse(-1) +} diff --git a/modules/codegen-plugin/src/smithy4s/codegen/Smithy4sCodegenPlugin.scala b/modules/codegen-plugin/src/smithy4s/codegen/Smithy4sCodegenPlugin.scala index f6010e11c..b275f0146 100644 --- a/modules/codegen-plugin/src/smithy4s/codegen/Smithy4sCodegenPlugin.scala +++ b/modules/codegen-plugin/src/smithy4s/codegen/Smithy4sCodegenPlugin.scala @@ -451,21 +451,24 @@ object Smithy4sCodegenPlugin extends AutoPlugin { ) val cached = - Tracked.inputChanged[CodegenArgs, Seq[File]]( - s.cacheStoreFactory.make("input") + CachedTask.inputChanged[CodegenArgs, Seq[File]]( + s.cacheStoreFactory.make("input"), + s.log ) { Function.untupled { Tracked.lastOutput[(Boolean, CodegenArgs), Seq[File]]( s.cacheStoreFactory.make("output") ) { case ((inputChanged, args), outputs) => if (inputChanged || outputs.isEmpty) { - s.log.debug("Regenerating managed sources") + s.log.debug(s"[smithy4s] Input changed: $inputChanged") + s.log.debug(s"[smithy4s] Outputs empty: ${outputs.isEmpty}") + s.log.debug("[smithy4s] Sources will be regenerated") val resPaths = smithy4s.codegen.Codegen .generateToDisk(args) .toList resPaths.map(path => new File(path.toString)) } else { - s.log.debug("Using cached version of outputs") + s.log.debug("[smithy4s] Using cached version of outputs") outputs.getOrElse(Seq.empty) } } @@ -474,4 +477,5 @@ object Smithy4sCodegenPlugin extends AutoPlugin { cached(codegenArgs) } + } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index af081abe7..232b368e4 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -139,7 +139,7 @@ object Dependencies { ) } - class MunitCross(munitVersion: String) { + class MunitCross(val munitVersion: String) { val core: Def.Initialize[ModuleID] = Def.setting("org.scalameta" %%% "munit" % munitVersion) val scalacheck: Def.Initialize[ModuleID] = @@ -147,6 +147,10 @@ object Dependencies { } object Munit extends MunitCross("0.7.29") object MunitMilestone extends MunitCross("1.0.0-M6") + object MunitV1 extends MunitCross("1.0.0") { + val diff: Def.Initialize[ModuleID] = + Def.setting("org.scalameta" %%% "munit-diff" % munitVersion) + } val Scalacheck = new { val scalacheckVersion = "1.16.0"