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

Update all the things and fix/finish the worker implementation #25

Merged
merged 3 commits into from
Sep 11, 2024
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
5 changes: 5 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ common --noenable_bzlmod

common:ci --color=yes

test --test_output=errors

build --experimental_worker_multiplex_sandboxing
build --experimental_worker_cancellation

build --java_language_version="21"
build --java_runtime_version="remotejdk_21"
build --tool_java_language_version="21"
Expand Down
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ default_java_toolchain(
name = "repository_default_toolchain_21",
configuration = DEFAULT_TOOLCHAIN_CONFIGURATION,
java_runtime = "@rules_java//toolchains:remotejdk_21",
javac_supports_worker_multiplex_sandboxing = True,
# some of the default options make scala compilation fail in the test package
misc = [opt for opt in DEFAULT_JAVACOPTS if not opt.startswith("-Xep")],
source_version = "21",
Expand Down
18 changes: 9 additions & 9 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# rules_java
http_archive(
name = "rules_java",
sha256 = "647bb31c0d51882549def6f67ee9078df697043406ed4a5144bbdf3b17f91e33",
sha256 = "41131de4417de70b9597e6ebd515168ed0ba843a325dc54a81b92d7af9a7b3ea",
urls = [
"https://github.com/bazelbuild/rules_java/releases/download/7.8.0/rules_java-7.8.0.tar.gz",
"https://github.com/bazelbuild/rules_java/releases/download/7.9.0/rules_java-7.9.0.tar.gz",
],
)

Expand Down Expand Up @@ -89,11 +89,11 @@ load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
protobuf_deps()

# higherkindness/rules_scala
rules_scala_annex_version = "c8c4345e3f354753ed4ae7830618467ab59262c6"
rules_scala_annex_version = "f23c16037db66efb541dbbf5e17e6604886c85ff"

http_archive(
name = "rules_scala_annex",
integrity = "sha256-pmES8mOOeirB5woNYT2w97+5+C0Bt8ghrj9lHBKkMy8=",
integrity = "sha256-b/cPeh6J1Mq63u6fSWdEHAKL/kWfPhZcNL7m9If7PWM=",
strip_prefix = "rules_scala-{}".format(rules_scala_annex_version),
type = "zip",
url = "https://github.com/lucidsoftware/rules_scala/archive/{}.zip".format(rules_scala_annex_version),
Expand Down Expand Up @@ -154,19 +154,19 @@ load_env_vars(
####################################################################

## For tests
play_version = "2.7" # This doesn't actually matter, since we're not using the default compilers rules_play_routes provides
play_version = "3.0" # This doesn't actually matter, since we're not using the default compilers rules_play_routes provides

rules_play_routes_version = "8dbe5ee4359c30cfb7d368fed9b2df59c9665eb1"
rules_play_routes_version = "22a30c6d2d315e532b4e1963bb9e8a167c470545"

http_archive(
name = "io_bazel_rules_play_routes",
sha256 = "d93e6d53440a53da4c33f78736b8b78c9a1e84623bcccd6a1cbff55e1c318c97",
name = "rules_play_routes",
sha256 = "cc9e431be031f775da1610341dd4429ff27e7c1e191f8a3018e8b39da4ca00f1",
strip_prefix = "rules_play_routes-{}".format(rules_play_routes_version),
type = "zip",
url = "https://github.com/lucidsoftware/rules_play_routes/archive/{}.zip".format(rules_play_routes_version),
)

load("@io_bazel_rules_play_routes//:workspace.bzl", "play_routes_repositories")
load("@rules_play_routes//:workspace.bzl", "play_routes_repositories")

play_routes_repositories(play_version)

Expand Down
3 changes: 3 additions & 0 deletions play-routes-compiler/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ scala_library(
tags = ["maven_coordinates=com.lucidchart:" + artifact_id + ":{pom_version}"],
visibility = ["//visibility:public"],
deps = [
"//third_party/bazel/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/jarhelper",
"@play_routes_compiler_cli_maven//:com_github_scopt_scopt_3",
"@play_routes_compiler_cli_maven//:org_playframework_play_routes_compiler_3",
"@rules_scala_annex//src/main/scala/higherkindness/rules_scala/common/error",
"@rules_scala_annex//src/main/scala/higherkindness/rules_scala/common/sandbox",
"@rules_scala_annex//src/main/scala/higherkindness/rules_scala/common/worker",
],
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,95 @@
package rulesplayroutes.routes

import com.google.devtools.build.buildjar.jarhelper.JarCreator
import higherkindness.rules_scala.common.error.AnnexWorkerError
import higherkindness.rules_scala.common.worker.WorkerMain
import higherkindness.rules_scala.common.sandbox.SandboxUtil
import java.io.{File, PrintStream}
import java.nio.file.{Files, Paths}
import java.nio.file.{Files, Path, Paths}
import play.routes.compiler._
import play.routes.compiler.RoutesCompiler.RoutesCompilerTask
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters._
import scala.io.Source
import scala.util.{Try, Success, Failure}
import scopt.Read
import scala.Console._
import scopt.OptionParser

object CommandLinePlayRoutesCompiler extends WorkerMain[Unit] {
implicit val listPathRead: Read[List[Path]] = Read.reads { commaList =>
commaList.split(',').view.map(Paths.get(_)).toList
}

case class Config(
sources: Seq[File] = Seq.empty[File],
generatedDirectory: File = new File("."),
additionalImports: Seq[String] = Seq.empty[String],
routesGenerator: RoutesGenerator = InjectedRoutesGenerator,
generateReverseRouter: Boolean = false,
namespaceReverserRouter: Boolean = false,
generateForwardsRouter: Boolean = true
var sources: List[Path] = List.empty[Path],
var outputDirectory: Path = Paths.get("."),
var outputSrcJar: Path = Paths.get("."),
var additionalImports: List[String] = List.empty[String],
var routesGenerator: RoutesGenerator = InjectedRoutesGenerator,
var generateReverseRouter: Boolean = false,
var namespaceReverserRouter: Boolean = false,
var generateForwardsRouter: Boolean = true
)

val parser = new OptionParser[Config]("scopt") {
def parser(workDir: Path) = new OptionParser[Config]("scopt") {
head("Command Line Play Routes Compiler", "0.1")

arg[File]("<outputDirectory>").required().action { (value, config) =>
config.copy(generatedDirectory = value)
arg[Path]("<outputDirectory>").required().action { (outputDirectory, config) =>
config.outputDirectory = SandboxUtil.getSandboxPath(workDir, outputDirectory)

Choose a reason for hiding this comment

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

Nit: I'm not a huge fan of the mutability here. What's wrong with .copy? If we're trying to micro-optimize, are the benefits really worth it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Speed is the reason. I do think it's worth it because that many object copies is not very fast.

Also I'm not too worried about it as the only uses of this object are in this file, it's very short, and reasoning about correctness is much easier than a larger codebase.

config
}.text("directory to output compiled routes to")

arg[Seq[File]]("<source1>,<source2>...").unbounded().required().action { (value, config) =>
config.copy(sources = value)
arg[Path]("<outputSrcJar>").required().action { (outputSrcJar, config) =>
config.outputSrcJar = SandboxUtil.getSandboxPath(workDir, outputSrcJar)
config
}.text("file to output srcjar containing compiled routes to")

arg[List[Path]]("<source1>,<source2>...").unbounded().required().action { (sources, config) =>
config.sources = sources.map(SandboxUtil.getSandboxPath(workDir, _))
config
}.text("routes to compile")

opt[String]('i', "routesImport").valueName("<import>").unbounded().action { (value, config) =>
config.copy(additionalImports = config.additionalImports ++ Seq(value))
opt[String]('i', "routesImport").valueName("<import>").unbounded().action { (routesImport, config) =>
config.additionalImports = config.additionalImports ++ List(routesImport)
config
}.text("Imports for the router")

opt[String]('g', "routesGenerator").valueName("<generator>").maxOccurs(1).action { (value, config) =>
config.copy(routesGenerator = {
opt[String]('g', "routesGenerator").valueName("<generator>").maxOccurs(1).action { (routesGeneratorClassString, config) =>
config.routesGenerator = {
try {
val name = if value.endsWith("$") then value else value + "$"
val name = if (routesGeneratorClassString.endsWith("$")) {
routesGeneratorClassString
} else {
routesGeneratorClassString + "$"
}
val clazz = java.lang.Class.forName(name, true, getClass.getClassLoader)
clazz.getField("MODULE$").get(null).asInstanceOf[RoutesGenerator]
} catch {
case e: Exception => {
throw new Exception(
s"Could not instantiate a routes generator from the given class: ${value}",
s"Could not instantiate a routes generator from the given class: ${routesGeneratorClassString}",
e,
)
}
}
})
}
config
}.text("The full class of the routes generator, e.g., play.routes.compiler.InjectedRoutesGenerator")

opt[Unit]('r', "generateReverseRouter").maxOccurs(1).action { (value, config) =>
config.copy(generateReverseRouter = true)
config.generateReverseRouter = true
config
}.text("Whether the reverse router should be generated. Setting to false may reduce compile times if it's not needed.")

opt[Unit]('n', "namespaceReverserRouter").maxOccurs(1).action { (value, config) =>
config.copy(namespaceReverserRouter = true)
config.namespaceReverserRouter = true
config
}.text("Whether the reverse router should be namespaced. Useful if you have many routers that use the same actions.")

opt[Boolean]('f', "generateForwardsRouter").maxOccurs(1).action { (value, config) =>
config.copy(generateForwardsRouter = value)
config.generateForwardsRouter = value
config
}.text("Whether the forwards router should be generated. Setting this to false should allow us to only generate reverse routes for a project")
}

Expand All @@ -78,43 +104,63 @@ object CommandLinePlayRoutesCompiler extends WorkerMain[Unit] {
/**
* Do Play Routes compilation and return true if things succeeded, otherwise return false.
*/
def compilePlayRoutes(config: Config): Boolean = {
config.sources.forall { file =>
private def compilePlayRoutes(config: Config, out: PrintStream): Try[Unit] = Try {
config.sources.foreach { path =>
RoutesCompiler.compile(
RoutesCompilerTask(
file,
path.toFile,
config.additionalImports,
config.generateForwardsRouter,
config.generateReverseRouter,
config.generateReverseRouter,
config.namespaceReverserRouter
),
config.routesGenerator,
config.generatedDirectory
config.outputDirectory.toFile(),
) match {
case Right(generatedFiles) =>
generatedFiles.foreach { f =>
stripHeader(f.getPath)
}
true
case Left(errors) =>
Console.err.println(s"${RESET}${RED}Play Routes Compilation Error:${RESET} Failed to compile routes for ${file}. Errors: ${errors}")
false
throw new Exception(
s"${RESET}${RED}Play Routes Compilation Error:${RESET} Failed to compile routes for ${path}. Errors: ${errors}"
)
}
}
}

def generateJar(config: Config): Unit = {
val jarCreator = new JarCreator(config.outputSrcJar)
jarCreator.addDirectory(config.outputDirectory)
jarCreator.setCompression(false)
jarCreator.setNormalize(true)
jarCreator.setVerbose(false)
jarCreator.execute()
}

/**
* Read any args passed in via files, so we can pass them to the arg parser
*/
private def readArgsFromArgFiles(args: Array[String]): List[String] = {
val builder = new ListBuffer[String]()
args.foreach {
case arg if arg.startsWith("@") => builder.addAll(Files.readAllLines(Paths.get(arg.tail)).asScala)
case arg => builder.addOne(arg)
}
builder.result()
}

override def init(args: Option[Array[String]]): Unit = ()

protected def work(ctx: Unit, args: Array[String], out: PrintStream): Unit = {
val isSuccess = parser.parse(args, Config())
.map(compilePlayRoutes)
.getOrElse(false)
protected def work(ctx: Unit, args: Array[String], out: PrintStream, workDir: Path, verbosity: Int): Unit = {
val config = parser(workDir).parse(
readArgsFromArgFiles(args), Config()
).getOrElse(throw new AnnexWorkerError(1))

if (isSuccess) {
System.exit(0)
} else {
System.exit(1)
compilePlayRoutes(config, out) match {
case Success(_) => generateJar(config)
case Failure(e) => throw new AnnexWorkerError(1, "Failed to compile play routes", e)
}
}
}
Loading