From 2b83114eafed403d28ca5a560092f64c612488c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Wed, 24 Nov 2021 15:19:22 +0100 Subject: [PATCH] Support multiple setting sets per file (#1156) --- .../codegen/CalibanSourceGenerator.scala | 64 +++++++++++-------- .../codegen/gen-client-task/build.sbt | 8 +++ .../gen-client-task/project/Version.scala | 7 ++ .../project/gitlab-schema.graphql | 0 .../gen-client-task/project/plugins.sbt | 5 ++ .../schema-to-check-name-uniqueness.graphql | 0 .../src/sbt-test/codegen/gen-client-task/test | 34 ++++++++++ .../codegen/gen-client-task/verify.sh | 9 +++ .../sbt-test/codegen/test-compile/build.sbt | 20 +++--- .../src/sbt-test/codegen/test-compile/test | 35 +--------- vuepress/docs/docs/client.md | 16 ++++- vuepress/docs/docs/schema.md | 3 + 12 files changed, 130 insertions(+), 71 deletions(-) create mode 100644 codegen-sbt/src/sbt-test/codegen/gen-client-task/build.sbt create mode 100644 codegen-sbt/src/sbt-test/codegen/gen-client-task/project/Version.scala rename codegen-sbt/src/sbt-test/codegen/{test-compile => gen-client-task}/project/gitlab-schema.graphql (100%) create mode 100644 codegen-sbt/src/sbt-test/codegen/gen-client-task/project/plugins.sbt rename codegen-sbt/src/sbt-test/codegen/{test-compile => gen-client-task}/project/schema-to-check-name-uniqueness.graphql (100%) create mode 100644 codegen-sbt/src/sbt-test/codegen/gen-client-task/test create mode 100644 codegen-sbt/src/sbt-test/codegen/gen-client-task/verify.sh diff --git a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala index e6b76b369e..024e06cb97 100644 --- a/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala +++ b/codegen-sbt/src/main/scala/caliban/codegen/CalibanSourceGenerator.scala @@ -2,7 +2,7 @@ package caliban.codegen import _root_.caliban.tools._ import sbt._ -import sjsonnew.IsoLList.Aux +import sjsonnew.IsoLList import zio.blocking.Blocking import java.io.File @@ -22,11 +22,11 @@ object CalibanSourceGenerator { fileSettings: Seq[CalibanFileSettings], urlSettings: Seq[CalibanUrlSettings] ): TrackedSettings = { - val allSettings: Seq[CalibanSettings] = sources.toList.map(collectSettingsFor(fileSettings, _)) ++ urlSettings + val allSettings: Seq[CalibanSettings] = sources.toList.flatMap(collectSettingsFor(fileSettings, _)) ++ urlSettings TrackedSettings(allSettings.map(_.toString)) } - implicit val analysisIso: Aux[TrackedSettings, Seq[String] :*: LNil] = + implicit val analysisIso: IsoLList.Aux[TrackedSettings, Seq[String] :*: LNil] = LList.iso[TrackedSettings, Seq[String] :*: LNil]( { case TrackedSettings(arguments) => ("args", arguments) :*: LNil }, { case (_, args) :*: LNil => TrackedSettings(args) } @@ -45,17 +45,19 @@ object CalibanSourceGenerator { interimPath.getParent.resolve(scalaName).toFile } - def collectSettingsFor(fileSettings: Seq[CalibanFileSettings], source: File): CalibanFileSettings = { + def collectSettingsFor(fileSettings: Seq[CalibanFileSettings], source: File): Seq[CalibanFileSettings] = { // Supply a default packageName. // If we do not, `src_managed.main.caliban-codegen-sbt` will be used, // which is not only terrible, but invalid. val defaults: CalibanCommonSettings = CalibanCommonSettings.empty.copy(packageName = Some("caliban")) - CalibanFileSettings( - file = source, - settings = fileSettings.collect { case needle if source.toPath.endsWith(needle.file.toPath) => needle } - .foldLeft[CalibanCommonSettings](defaults) { case (acc, next) => acc.combine(next.settings) } - ) + val matchingSettingSets = fileSettings.collect { + case needle if source.toPath.endsWith(needle.file.toPath) => needle.settings + }.map(defaults.combine(_)) + + val finalSettingSets = if (matchingSettingSets.isEmpty) List(defaults) else matchingSettingSets + + finalSettingSets.map(settings => CalibanFileSettings(file = source, settings = settings)) } def apply( @@ -94,25 +96,31 @@ object CalibanSourceGenerator { files <- Codegen.generate(opts, settings.genType).asSomeError } yield files - Runtime.default - .unsafeRun( - for { - fromFiles <- ZIO.foreach(sources.toList)(source => - generateFileSource(source, collectSettingsFor(fileSettings, source).settings).catchAll { - case Some(reason) => - putStrLn(reason.toString) *> putStrLn(reason.getStackTrace.mkString("\n")).as(List.empty) - case None => ZIO.succeed(List.empty) - } - ) - fromUrls <- ZIO.foreach(urlSettings)(setting => - generateUrlSource(setting.url, setting.settings).catchAll { - case Some(reason) => - putStrLn(reason.toString) *> putStrLn(reason.getStackTrace.mkString("\n")).as(List.empty) - case None => ZIO.succeed(List.empty) - } - ) - } yield (fromFiles ++ fromUrls).flatten - ) + val generateFromFiles = ZIO + .foreach(sources.toList) { source => + ZIO + .collectAll( + collectSettingsFor(fileSettings, source).map(s => generateFileSource(source, s.settings)) + ) + .catchAll { + case Some(reason) => + putStrLn(reason.toString) *> putStrLn(reason.getStackTrace.mkString("\n")).as(List.empty) + case None => ZIO.succeed(List.empty) + } + .map(_.flatten) + } + + val generateFromURLs = ZIO.foreach(urlSettings)(setting => + generateUrlSource(setting.url, setting.settings).catchAll { + case Some(reason) => + putStrLn(reason.toString) *> putStrLn(reason.getStackTrace.mkString("\n")).as(List.empty) + case None => ZIO.succeed(List.empty) + } + ) + + Runtime.default.unsafeRun { + ZIO.mapN(generateFromFiles, generateFromURLs)((_ ++ _)).map(_.flatten) + } } // NB: This is heavily inspired by the caching technique from eed3si9n's sbt-scalaxb plugin diff --git a/codegen-sbt/src/sbt-test/codegen/gen-client-task/build.sbt b/codegen-sbt/src/sbt-test/codegen/gen-client-task/build.sbt new file mode 100644 index 0000000000..cc00604cec --- /dev/null +++ b/codegen-sbt/src/sbt-test/codegen/gen-client-task/build.sbt @@ -0,0 +1,8 @@ +lazy val root = project + .in(file(".")) + .enablePlugins(CodegenPlugin) // Intentionally maintain the deprecated name + .settings( + libraryDependencies ++= Seq( + "com.github.ghostdogpr" %% "caliban-client" % Version.pluginVersion + ) + ) diff --git a/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/Version.scala b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/Version.scala new file mode 100644 index 0000000000..3a210ef70e --- /dev/null +++ b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/Version.scala @@ -0,0 +1,7 @@ +object Version { + def pluginVersion: String = + sys.props.get("plugin.version") match { + case Some(x) => x + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) + }} diff --git a/codegen-sbt/src/sbt-test/codegen/test-compile/project/gitlab-schema.graphql b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/gitlab-schema.graphql similarity index 100% rename from codegen-sbt/src/sbt-test/codegen/test-compile/project/gitlab-schema.graphql rename to codegen-sbt/src/sbt-test/codegen/gen-client-task/project/gitlab-schema.graphql diff --git a/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/plugins.sbt b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/plugins.sbt new file mode 100644 index 0000000000..056ee4818b --- /dev/null +++ b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/plugins.sbt @@ -0,0 +1,5 @@ +sys.props.get("plugin.version") match { + case Some(x) => addSbtPlugin("com.github.ghostdogpr" % "caliban-codegen-sbt" % x) + case _ => sys.error("""|The system property 'plugin.version' is not defined. + |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) +} diff --git a/codegen-sbt/src/sbt-test/codegen/test-compile/project/schema-to-check-name-uniqueness.graphql b/codegen-sbt/src/sbt-test/codegen/gen-client-task/project/schema-to-check-name-uniqueness.graphql similarity index 100% rename from codegen-sbt/src/sbt-test/codegen/test-compile/project/schema-to-check-name-uniqueness.graphql rename to codegen-sbt/src/sbt-test/codegen/gen-client-task/project/schema-to-check-name-uniqueness.graphql diff --git a/codegen-sbt/src/sbt-test/codegen/gen-client-task/test b/codegen-sbt/src/sbt-test/codegen/gen-client-task/test new file mode 100644 index 0000000000..e23c536dee --- /dev/null +++ b/codegen-sbt/src/sbt-test/codegen/gen-client-task/test @@ -0,0 +1,34 @@ +$ absent src/main/scala/client/ClientNameUniqueness.scala +$ absent src/main/scala/client/ClientGitLab.scala +$ absent app/com/caliban/client/ClientPlayFramework.scala +$ absent play23/com/caliban/client/ClientPlayFramework.scala + +$ mkdir src/main/scala +$ mkdir src/main/scala/client +$ mkdir app/com/caliban/client +$ mkdir play23/com/caliban/client + +> calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/client/ClientNameUniqueness.scala --packageName client +$ exists src/main/scala/client/ClientNameUniqueness.scala + +> calibanGenClient project/schema-to-check-name-uniqueness.graphql app/com/caliban/client/ClientPlayFramework.scala --packageName client +$ exists app/com/caliban/client/ClientPlayFramework.scala +$ exec sh verify.sh ClientPlayFramework ./app/com/caliban/client/ClientPlayFramework.scala + +> calibanGenClient project/schema-to-check-name-uniqueness.graphql play23/com/caliban/client/ClientPlayFramework.scala --packageName client +$ exists play23/com/caliban/client/ClientPlayFramework.scala +$ exec sh verify.sh ClientPlayFramework ./play23/com/caliban/client/ClientPlayFramework.scala + +$ mkdir src/main/scala/genview +$ mkdir src/main/scala/genview/client + +> calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/genview/client/ClientNameUniqueness.scala --packageName genview.client --genView true +$ exists src/main/scala/genview/client/ClientNameUniqueness.scala +$ exec sh verify.sh StarshipView ./src/main/scala/genview/client/ClientNameUniqueness.scala +> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true --splitFiles true --enableFmt false +$ exists src/main/scala/genview/client/package.scala +$ exec sh verify.sh ProjectID ./src/main/scala/genview/client/package.scala +$ exists src/main/scala/genview/client/Project.scala +$ exec sh verify.sh ProjectView ./src/main/scala/genview/client/Project.scala +$ exec sh verify.sh ProjectViewArgs ./src/main/scala/genview/client/Project.scala +$ exec sh verify.sh ProjectViewSelectionArgs ./src/main/scala/genview/client/Project.scala diff --git a/codegen-sbt/src/sbt-test/codegen/gen-client-task/verify.sh b/codegen-sbt/src/sbt-test/codegen/gen-client-task/verify.sh new file mode 100644 index 0000000000..fba774cc8f --- /dev/null +++ b/codegen-sbt/src/sbt-test/codegen/gen-client-task/verify.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if grep -q "$1" "$2"; then + echo "$1 exists in $2" + exit 0 +else + echo "$1 is missing in $2" + exit 1 +fi \ No newline at end of file diff --git a/codegen-sbt/src/sbt-test/codegen/test-compile/build.sbt b/codegen-sbt/src/sbt-test/codegen/test-compile/build.sbt index 45575928b4..d30512c198 100644 --- a/codegen-sbt/src/sbt-test/codegen/test-compile/build.sbt +++ b/codegen-sbt/src/sbt-test/codegen/test-compile/build.sbt @@ -1,20 +1,24 @@ +import _root_.caliban.tools.Codegen + lazy val root = project .in(file(".")) - .enablePlugins(CodegenPlugin) // Intentionally maintain the deprecated name + .enablePlugins(CalibanPlugin) .settings( libraryDependencies ++= Seq( "com.github.ghostdogpr" %% "caliban-client" % Version.pluginVersion ), Compile / caliban / calibanSettings ++= Seq( - calibanSetting(file("src/main/graphql/schema.graphql"))( // Explicitly constrain to disambiguate - cs => - cs.clientName("Client") + calibanSetting(file("src/main/graphql/schema.graphql"))( // Explicitly constrain to disambiguate + _.clientName("Client") + ), + // Another entry for the same file, which will cause another generator to run + calibanSetting(file("src/main/graphql/schema.graphql"))( + _.genType(Codegen.GenType.Schema) + .scalarMapping("Json" -> "String") + .effect("scala.util.Try") ), calibanSetting(file("src/main/graphql/genview/schema.graphql"))( - cs => - cs.clientName("Client") - .packageName("genview") - .genView(true) + _.clientName("Client").packageName("genview").genView(true) ) ) ) diff --git a/codegen-sbt/src/sbt-test/codegen/test-compile/test b/codegen-sbt/src/sbt-test/codegen/test-compile/test index fd9615afd2..67e9fe2e9b 100644 --- a/codegen-sbt/src/sbt-test/codegen/test-compile/test +++ b/codegen-sbt/src/sbt-test/codegen/test-compile/test @@ -1,39 +1,6 @@ -$ absent src/main/scala/client/ClientNameUniqueness.scala -$ absent src/main/scala/client/ClientGitLab.scala -$ absent app/com/caliban/client/ClientPlayFramework.scala -$ absent play23/com/caliban/client/ClientPlayFramework.scala - -$ mkdir src/main/scala -$ mkdir src/main/scala/client -$ mkdir app/com/caliban/client -$ mkdir play23/com/caliban/client - -> calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/client/ClientNameUniqueness.scala --packageName client -$ exists src/main/scala/client/ClientNameUniqueness.scala - -> calibanGenClient project/schema-to-check-name-uniqueness.graphql app/com/caliban/client/ClientPlayFramework.scala --packageName client -$ exists app/com/caliban/client/ClientPlayFramework.scala -$ exec sh verify.sh ClientPlayFramework ./app/com/caliban/client/ClientPlayFramework.scala - -> calibanGenClient project/schema-to-check-name-uniqueness.graphql play23/com/caliban/client/ClientPlayFramework.scala --packageName client -$ exists play23/com/caliban/client/ClientPlayFramework.scala -$ exec sh verify.sh ClientPlayFramework ./play23/com/caliban/client/ClientPlayFramework.scala - -$ mkdir src/main/scala/genview -$ mkdir src/main/scala/genview/client - -> calibanGenClient project/schema-to-check-name-uniqueness.graphql src/main/scala/genview/client/ClientNameUniqueness.scala --packageName genview.client --genView true -$ exists src/main/scala/genview/client/ClientNameUniqueness.scala -$ exec sh verify.sh StarshipView ./src/main/scala/genview/client/ClientNameUniqueness.scala -> calibanGenClient project/gitlab-schema.graphql src/main/scala/genview/client/ClientGitLab.scala --packageName genview.client --genView true --splitFiles true --enableFmt false -$ exists src/main/scala/genview/client/package.scala -$ exec sh verify.sh ProjectID ./src/main/scala/genview/client/package.scala -$ exists src/main/scala/genview/client/Project.scala -$ exec sh verify.sh ProjectView ./src/main/scala/genview/client/Project.scala -$ exec sh verify.sh ProjectViewArgs ./src/main/scala/genview/client/Project.scala -$ exec sh verify.sh ProjectViewSelectionArgs ./src/main/scala/genview/client/Project.scala > compile $ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/caliban/Client.scala $ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.scala +$ exists target/scala-2.12/src_managed/main/caliban-codegen-sbt/caliban/schema.scala $ exec sh verify.sh CharacterView target/scala-2.12/src_managed/main/caliban-codegen-sbt/genview/Client.scala diff --git a/vuepress/docs/docs/client.md b/vuepress/docs/docs/client.md index 82bd02ce67..ee61abc394 100644 --- a/vuepress/docs/docs/client.md +++ b/vuepress/docs/docs/client.md @@ -27,7 +27,7 @@ Caliban provides two sbt plugins to generate your client(s) code. The first one, named `CalibanPlugin`, allows you to generate the client code from a schema file or from a server URL. -The second one, named `CompileTimeCalibanPlugin`, allows you to generate the client code from your server code. +The second one, named `CompileTimeCalibanPlugin`, allows you to generate the client code from your server code. This second "meta" plugin is actually made of two "concrete" plugins, `CompileTimeCalibanServerPlugin` and `CompileTimeCalibanClientPlugin`, that you'll both need to configure in your project to be able to generate you Caliban client code from your Caliban server code. @@ -69,6 +69,20 @@ In order to supply more configuration options to the code generator, you can use ) ``` +The path where the generator will look for schemas can be customized by overriding the `calibanSources` settings: + +```scala +Compile / caliban / calibanSources := file("caliban") +``` + +If you want to cherry-pick certain files yourself, you can override that as well with an explicit `caliban / sources` entry: + +```scala +Compile / caliban / sources := List(file("caliban") / "Service.graphql") +``` + +For every entry in `calibanSettings` for the same file, a separate client (or [schema](schema.md#code-generation), depending on the entry's `genType` value) will be generated. + #### From a server URL The `calibanSetting` function also permits generating clients for supplied `url`'s: diff --git a/vuepress/docs/docs/schema.md b/vuepress/docs/docs/schema.md index 630e1cf126..eb97abbfbb 100644 --- a/vuepress/docs/docs/schema.md +++ b/vuepress/docs/docs/schema.md @@ -292,6 +292,9 @@ You can also indicate that the effect type is abstract via `--abstractEffectType If you want to force a mapping between a GraphQL type and a Scala class (such as scalars), you can use the `--scalarMappings` option. Also you can add additional imports by providing `--imports` option. +Since Caliban 1.3.0, you can generate schemas using an sbt `sourceGenerator`, which means your schemas will be generated every time you compile (or when you import your build into [Metals](https://scalameta.org/metals/)). +This can be configured with the same settings as [the client generators](client.md#code-generation), but you have to specify `.genType(Codegen.GenType.Schema)` in the `calibanSettings` entry for a given file. + ## Building Schemas by hand Sometimes for whatever reason schema generation fails. This can happen if your schema has co-recursive types and Magnolia is unable