Skip to content

Commit

Permalink
BuildTarget: Expose source paths as environment variable
Browse files Browse the repository at this point in the history
It is possible for multiple modules to share the same build target, but
the executed class/command could not determine which modules referenced
it. This limitation is addressed by introducing a new environment
variable `MODULE_SOURCE_PATHS` which will contain the source paths of
all modules that depend on the build target.
  • Loading branch information
tindzk committed Feb 26, 2020
1 parent ac088b5 commit 62d654e
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 11 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,12 @@ class = { module = "keyboard:jvm", main = "keyboard.Bundle" }

In addition to `BUILD_PATH`, we also set the environment variable `MODULE_PATH` when running classes. The module path is the root path of the Seed project which contains the referenced module. This environment variable is not set for commands since their current working directory will point to the module path.

For two equivalent examples of using code generation, please refer to these links:
It is possible for multiple modules to share a code generator. To access the source paths of all modules that depend on a generator, access the environment variable `MODULE_SOURCE_PATHS`. Its value uses the system path separator character (`java.io.File.pathSeparatorChar`).

For examples of using code generation, please refer to these links:

* [Class target](test/custom-class-target/)
* [Class target shared by multiple modules](test/custom-class-target-shared/)
* [Command target](test/custom-command-target/)

### Compiler plug-ins
Expand Down
44 changes: 42 additions & 2 deletions src/main/scala/seed/cli/BuildTarget.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,42 @@ object BuildTarget {
case _ => List()
}.distinct

val inheritedTargets = modules
val flattenedDeps = modules
.flatMap {
case util.Target.Parsed(module, Some(Left(platform))) =>
BuildConfig.collectModuleDepsBase(build, module.module, platform)
case util.Target.Parsed(_, Some(Right(_))) => List()
case util.Target.Parsed(module, None) =>
module.name +: BuildConfig.collectModuleDeps(build, module.module)
}
.flatMap(m => build(m).module.target.keys.toList.map(m -> _))

// Determine direct parents of build targets
type BuildTarget = (String, String) // module, target name
type ParentModule = String
val parentModules: Map[BuildTarget, List[ParentModule]] =
flattenedDeps
.flatMap { parent =>
BuildConfig
.allTargets(build, parent)
.flatMap {
case (m, t) => BuildConfig.platformModule(build(m).module, t)
}
.flatMap(_.moduleDeps)
.flatMap(
module =>
build(module).module.target.keys.toList
.map(target => (module, target) -> parent)
)
}
.groupBy(_._1)
.mapValues(_.map(_._2))

val inheritedTargets: List[BuildTarget] = flattenedDeps
.flatMap(
module =>
build(module).module.target.keys.toList
.map(target => (module, target))
)
.distinct

if (targets.nonEmpty)
Expand All @@ -58,6 +85,16 @@ object BuildTarget {
val modulePath = build(m).path
val target = build(m).module.target(t)

val moduleSourcePaths = parentModules((m, t)).flatMap { module =>
val targets = BuildConfig.allTargets(build, module)
targets
.flatMap {
case (m, t) => BuildConfig.platformModule(build(m).module, t)
}
.flatMap(_.sources)
.map(_.toAbsolutePath.toString)
}

target.`class` match {
case Some(c) =>
val bloopName =
Expand All @@ -68,6 +105,7 @@ object BuildTarget {
customLog,
customLog.info(_),
Some(modulePath.toAbsolutePath.toString),
moduleSourcePaths,
Some(buildPath.toAbsolutePath.toString)
)(args: _*)

Expand All @@ -83,6 +121,7 @@ object BuildTarget {
ProcessHelper.runShell(
modulePath,
cmd,
moduleSourcePaths,
buildPath.toAbsolutePath.toString,
customLog,
customLog.info(_)
Expand All @@ -96,6 +135,7 @@ object BuildTarget {
val process = ProcessHelper.runShell(
modulePath,
cmd,
moduleSourcePaths,
buildPath.toAbsolutePath.toString,
customLog,
customLog.info(_)
Expand Down
16 changes: 16 additions & 0 deletions src/main/scala/seed/process/ProcessHelper.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package seed.process

import java.io.File
import java.nio.ByteBuffer
import java.nio.file.Path

Expand Down Expand Up @@ -65,6 +66,7 @@ object ProcessHelper {
cwd: Path,
cmd: List[String],
modulePath: Option[String] = None,
moduleSourcePaths: List[String] = List(),
buildPath: Option[String] = None,
log: Log,
onStdOut: String => Unit,
Expand All @@ -84,6 +86,16 @@ object ProcessHelper {
log.debug(s"Module path: ${Ansi.italic(mp)}", detail = true)
}

pb.environment()
.put(
"MODULE_SOURCE_PATHS",
moduleSourcePaths.mkString(File.pathSeparatorChar.toString)
)
log.debug(
s"Module source paths: ${moduleSourcePaths.map(Ansi.italic).mkString(", ")}",
detail = true
)

buildPath.foreach { bp =>
pb.environment().put("BUILD_PATH", bp)
log.debug(s"Build path: ${Ansi.italic(bp)}", detail = true)
Expand Down Expand Up @@ -125,13 +137,15 @@ object ProcessHelper {
log: Log,
onStdOut: String => Unit,
modulePath: Option[String] = None,
moduleSourcePaths: List[String] = List(),
buildPath: Option[String] = None,
verbose: Boolean = true
)(args: String*): Process =
runCommand(
cwd,
List("bloop") ++ args,
modulePath,
moduleSourcePaths,
buildPath,
log,
output => onStdOut(output),
Expand All @@ -141,6 +155,7 @@ object ProcessHelper {
def runShell(
cwd: Path,
command: String,
moduleSourcePaths: List[String] = List(),
buildPath: String,
log: Log,
onStdOut: String => Unit
Expand All @@ -149,6 +164,7 @@ object ProcessHelper {
cwd,
List("/bin/sh", "-c", command),
None,
moduleSourcePaths,
Some(buildPath),
log,
onStdOut
Expand Down
35 changes: 27 additions & 8 deletions src/test/scala/seed/generation/BloopIntegrationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
def buildCustomTarget(
name: String,
expectFailure: Boolean = false
): Future[List[String]] = {
val path = Paths.get(s"test/$name")
): Future[Either[List[String], List[String]]] = {
val path = Paths.get("test", name)

val config = BuildConfig.load(path, Log.urgent).get
import config._
Expand Down Expand Up @@ -317,7 +317,7 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
if (expectFailure)
RTS.unsafeRunToFuture(uio).failed.map { _ =>
assert(lines.nonEmpty)
lines.toList
Left(lines.toList)
} else {
RTS.unsafeRunSync(uio)

Expand All @@ -327,20 +327,38 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
TestProcessHelper
.runBloop(buildPath)("run", "demo")
.map { x =>
assertEquals(x.split("\n").count(_ == "42"), 1)
Files.delete(generatedFile)
assertEquals(lines.toList, List())
List()
Right(x.split("\n").toList)
}
}
}

testAsync("Build project with custom class target") { _ =>
buildCustomTarget("custom-class-target").map(_ => ())
buildCustomTarget("custom-class-target").map(
lines => assertEquals(lines.right.get.count(_ == "42"), 1)
)
}

testAsync(
"Build project with custom class target (shared by multiple modules)"
) { _ =>
buildCustomTarget("custom-class-target-shared").map(
lines =>
assertEquals(
lines.right.get.map(_.split("test/").last),
List(
"custom-class-target-shared/template1",
"custom-class-target-shared/template2"
)
)
)
}

testAsync("Build project with custom command target") { _ =>
buildCustomTarget("custom-command-target").map { _ =>
buildCustomTarget("custom-command-target").map { lines =>
assertEquals(lines.right.get.count(_ == "42"), 1)

val path = tempPath
.resolve("custom-command-target")
.resolve(".bloop")
Expand Down Expand Up @@ -368,7 +386,8 @@ object BloopIntegrationSpec extends TestSuite[Unit] {
log =>
// Must indicate correct position
assert(
log.exists(_.contains("[2:41]: not found: value invalidIdentifier"))
log.left.get
.exists(_.contains("[2:41]: not found: value invalidIdentifier"))
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ object TestProcessHelper {
cwd,
cmd,
None,
List(),
None,
Log.urgent,
out => sb.append(out + "\n")
Expand Down
22 changes: 22 additions & 0 deletions test/custom-class-target-shared/build.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
scalaVersion = "2.13.0"

[module.utils.jvm]
sources = ["utils"]

[module.utils.target.gen-sources]
class = ["utils:jvm", "GenerateSources"]
await = true

# `template1` and `template2` share the code generator `utils:gen-sources`
[module.template1.jvm]
moduleDeps = ["utils"]
sources = ["template1"]

[module.template2.jvm]
moduleDeps = ["utils"]
sources = ["template2"]

[module.demo.jvm]
moduleDeps = ["template1", "template2"]
sources = ["demo"]
3 changes: 3 additions & 0 deletions test/custom-class-target-shared/demo/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Main {
def main(args: Array[String]): Unit = Generated.modulePaths.foreach(println)
}
16 changes: 16 additions & 0 deletions test/custom-class-target-shared/utils/GenerateSources.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import java.io.File
import java.nio.file.{Files, Paths, StandardOpenOption}

object GenerateSources {
def main(args: Array[String]): Unit = {
val modulePath = sys.env("MODULE_PATH")
val moduleSourcePaths =
sys.env("MODULE_SOURCE_PATHS").split(File.pathSeparatorChar)
val generatedPath =
Paths.get(modulePath).resolve("demo").resolve("Generated.scala")
val pathsScala = moduleSourcePaths.mkString("\"", "\", \"", "\"")
val output = s"object Generated { val modulePaths = List($pathsScala) }"

Files.write(generatedPath, output.getBytes)
}
}

0 comments on commit 62d654e

Please sign in to comment.