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

First draft of the mill plugin for smithy4s #450

Merged
merged 16 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ jobs:
with:
java-version: temurin@17

- name: Get mill version
run: echo "MILL_VERSION=$(cat .mill-version)" >> $GITHUB_ENV

- name: Setup Mill
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
uses: jodersky/setup-mill@master
with:
mill-version: ${{ env.MILL_VERSION }}

- name: Run tests
run: |
sbt test_$BUILD_KEY \
Expand Down
1 change: 1 addition & 0 deletions .mill-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.10.7
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
44 changes: 43 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Global / licenses := Seq(
sonatypeCredentialHost := "s01.oss.sonatype.org"

ThisBuild / version := {
if (!sys.env.contains("CI")) "dev"
if (!sys.env.contains("CI")) "dev-SNAPSHOT"
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
else (ThisBuild / version).value
}

Expand All @@ -46,6 +46,7 @@ lazy val root = project
lazy val allModules = Seq(
core.projectRefs,
codegen.projectRefs,
millCodegenPlugin.projectRefs,
json.projectRefs,
example.projectRefs,
tests.projectRefs,
Expand Down Expand Up @@ -383,6 +384,47 @@ lazy val codegenPlugin = (projectMatrix in file("modules/codegen-plugin"))
scriptedBufferLog := false
)

/**
* Mill plugin to run codegen
*/
lazy val millCodegenPlugin = projectMatrix
.in(file("modules/mill-codegen-plugin"))
.enablePlugins(BuildInfoPlugin)
.jvmPlatform(
scalaVersions = List(Scala213),
simpleJVMLayout
)
.settings(
name := "mill-codegen-plugin",
buildInfoKeys := Seq[BuildInfoKey](version),
buildInfoPackage := "smithy4s.codegen.mill",
libraryDependencies ++= Seq(
"com.lihaoyi" %% "mill-scalalib" % millVersion,
"com.lihaoyi" %% "mill-main" % millVersion,
"com.lihaoyi" %% "mill-main-api" % millVersion,
"com.lihaoyi" %% "mill-main-testkit" % millVersion % Test
),
publishLocal := {
// make sure that core and codegen are published before the
// plugin is published
// this allows running `scripted` alone
val _ = List(
// for the code being built
(core.jvm(Scala213) / publishLocal).value,
(dynamic.jvm(Scala213) / publishLocal).value,
(codegen.jvm(Scala213) / publishLocal).value,
// dependency of codegen
(openapi.jvm(Scala213) / publishLocal).value,

// for mill
(protocol.jvm(autoScalaLibrary = false) / publishLocal).value
)
publishLocal.value
},
libraryDependencies ++= munitDeps.value
)
.dependsOn(codegen)

lazy val decline = (projectMatrix in file("modules/decline"))
.settings(
name := "decline",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ object Smithy4sCodegenPlugin extends AutoPlugin {

val smithy4sSmithyLibrary =
settingKey[Boolean](
"Sets whether this project should be use as a Smithy library by packaging the Smithy specs in the resulting jar"
"Sets whether this project should be used as a Smithy library by packaging the Smithy specs in the resulting jar"
)

val Smithy4s =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2021-2022 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.mill

import coursier.maven.MavenRepository
import mill._
import mill.api.PathRef
import mill.define.{Source, Sources}
import mill.scalalib._
import smithy4s.codegen.{CodegenArgs, Codegen => Smithy4s, FileType}
import smithy4s.codegen.mill.BuildInfo

trait Smithy4sModule extends ScalaModule {

/** Input directory for .smithy files */
protected def smithy4sInputDir: Source = T.source {
PathRef(millSourcePath / "smithy")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed from os.pwd

}

protected def smithy4sOutputDir: T[PathRef] = T {
PathRef(T.ctx().dest / "scala")
}

protected def smithy4sResourceOutputDir: T[PathRef] = T {
PathRef(T.ctx().dest / "resources")
}
protected def generateOpenApiSpecs: T[Boolean] = true

def smithy4sAllowedNamespaces: T[Option[Set[String]]] = None

def smithy4sExcludedNamespaces: T[Option[Set[String]]] = None

def smithy4sCodegenDependencies: T[List[String]] = List.empty[String]

def smithy4sLocalJars: T[List[os.Path]] = T {
T.traverse(moduleDeps)(_.jar)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

.map(_.toList.map(_.path))
}

def smithy4sModelTransformers: T[List[String]] = List.empty[String]

def smithy4sRepositories: T[List[String]] = repositoriesTask().toList
.collect { case repository: MavenRepository =>
repository.root
}

def smithy4sVersion: T[String] = BuildInfo.version
def smithy4sSmithyLibrary: T[Boolean] = true

def smithy4sCodegen: T[(PathRef, PathRef)] = T {

val specFiles = if (os.exists(smithy4sInputDir().path)) {
os.walk(smithy4sInputDir().path, skip = _.ext != "smithy")
} else Seq.empty

val scalaOutput = smithy4sOutputDir().path
val resourcesOutput = smithy4sResourceOutputDir().path

val skipResources: Set[FileType] =
if (smithy4sSmithyLibrary()) Set.empty
else Set(FileType.Resource)

val skipOpenApi: Set[FileType] =
if (generateOpenApiSpecs()) Set.empty
else Set(FileType.Openapi)

val skipSet = skipResources ++ skipOpenApi

val args = CodegenArgs(
specs = specFiles.toList,
output = scalaOutput,
resourceOutput = resourcesOutput,
skip = skipSet,
discoverModels = true,
allowedNS = smithy4sAllowedNamespaces(),
excludedNS = smithy4sExcludedNamespaces(),
repositories = smithy4sRepositories(),
dependencies = smithy4sCodegenDependencies(),
transformers = smithy4sModelTransformers(),
localJars = smithy4sLocalJars()
)
Smithy4s.processSpecs(args)
(PathRef(scalaOutput), PathRef(resourcesOutput))
}

override def generatedSources: T[Seq[PathRef]] = T {
val (scalaOutput, _) = smithy4sCodegen()
scalaOutput +: super.generatedSources()
}

override def resources: Sources = T.sources {
super.resources() :+ smithy4sResourceOutputDir()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package smithy4s.codegen.mill

import mill.testkit.MillTestKit
import mill.scalalib._
import mill._
import munit.Location
import sourcecode.FullName

class Smithy4sModuleSpec extends munit.FunSuite {
object testKit extends MillTestKit

val coreDep =
ivy"com.disneystreaming.smithy4s::smithy4s-core:${smithy4s.codegen.BuildInfo.version}"

object foo extends testKit.BaseModule with Smithy4sModule {
override def scalaVersion = "2.13.8"
override def ivyDeps = Agg(coreDep)
}

object bar extends testKit.BaseModule with Smithy4sModule {
override def moduleDeps = Seq(foo)
override def scalaVersion = "2.13.8"
override def ivyDeps = Agg(coreDep)
}

test("codegen runs") {
val ev = testKit.staticTestEvaluator(foo)(FullName("codegen-runs"))
os.write.over(
foo.millSourcePath / "smithy" / "foo.smithy",
s"""|$$version: "2"
|
|namespace basic
|
|string MyNewString""".stripMargin,
createFolders = true
)

compileWorks(foo, ev)
checkFileExist(
ev.outPath / "smithy4sOutputDir.dest" / "scala" / "basic" / "MyNewString.scala",
shouldExist = true
)
}

test("multi-module codegen works") {
daddykotex marked this conversation as resolved.
Show resolved Hide resolved
val fooEv = testKit.staticTestEvaluator(foo)(FullName("multi-module-foo"))
val barEv = testKit.staticTestEvaluator(foo)(FullName("multi-module-bar"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the implicit FullName here is rather pointless

I'd rather have the evaluator for foo to write in foo

in this example if I don't provide it, it writes to barEv and if you have multiple tests that use an evaluator with the same name, they write to the same out directory.

I think this is more a criticism of https://github.com/com-lihaoyi/mill/pull/1893/files rather than this PR though

Copy link
Contributor

@Baccata Baccata Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is more a criticism of https://github.com/com-lihaoyi/mill/pull/1893/files rather than this PR though

UX can be improved as we learn more by using it. MiMa has not been set on this module, specifically to be able to iterate.

os.write.over(
foo.millSourcePath / "smithy" / "foo.smithy",
s"""|$$version: "2.0"
|
|namespace foo
|
|structure Foo {
| a: Integer
|}""".stripMargin,
createFolders = true
)

os.write.over(
bar.millSourcePath / "smithy" / "bar.smithy",
s"""|$$version: "2.0"
|
|namespace bar
|
|use foo#Foo
|
|// Checking that Foo can be found by virtue of the bar project depending on the foo project
|structure Bar {
| foo: Foo
|}""".stripMargin,
createFolders = true
)

os.write.over(
bar.millSourcePath / "src" / "Test.scala",
s"""|package bar
|
|import foo._
|
|object BarTest {
|
| def main(args: Array[String]): Unit = println(Bar(Some(Foo(Some(1)))))
|
|}""".stripMargin,
createFolders = true
)

compileWorks(foo, fooEv)
checkFileExist(
fooEv.outPath / "smithy4sOutputDir.dest" / "scala" / "foo" / "Foo.scala",
shouldExist = true
)

compileWorks(bar, barEv)
checkFileExist(
barEv.outPath / "smithy4sOutputDir.dest" / "scala" / "foo" / "Foo.scala",
shouldExist = false
)
checkFileExist(
barEv.outPath / "smithy4sOutputDir.dest" / "scala" / "bar" / "Bar.scala",
shouldExist = true
)
}

private def compileWorks(
sm: ScalaModule,
testEvaluator: testKit.TestEvaluator
)(implicit loc: Location) = {
val result = testEvaluator(sm.compile).map(_._1)
assertEquals(result.isRight, true)
}

private def checkFileExist(path: os.Path, shouldExist: Boolean) = {
if (!os.exists(path) && shouldExist) {
sys.error(s"${path} file not found")
}
if (os.exists(path) && !shouldExist) {
sys.error(s"${path} file should not exist")
}
}
}
2 changes: 2 additions & 0 deletions project/Smithy4sPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ object Smithy4sPlugin extends AutoPlugin {
val Scala213 = "2.13.8"
val Scala3 = "3.2.0"

val millVersion = IO.readLines(file(".") / ".mill-version").head.trim()

implicit class ProjectMatrixOps(val pm: ProjectMatrix) extends AnyVal {
def http4sJvmPlatform(
scalaVersions: Seq[String],
Expand Down