Skip to content

Commit d61a625

Browse files
committed
Merge pull request #1 from pvlugter/sbt-javaagent
Initial version of sbt-javaagent
2 parents 062679b + 8852bb6 commit d61a625

File tree

46 files changed

+524
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+524
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target
2+
project/project

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2016 Lightbend Inc. [https://www.lightbend.com]
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,63 @@
11
# sbt-javaagent
2-
sbt plugin for adding java agents to projects
2+
3+
This sbt plugin adds Java agents to projects in a general way. It can enable agents in [sbt-native-packager] dists, as compile-time dependencies, in forked run, or in forked tests.
4+
5+
## Plugin dependency
6+
7+
Add the plugin to your `project/plugins.sbt`:
8+
9+
```scala
10+
addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % "0.1.0")
11+
```
12+
13+
## Java agent
14+
15+
To add a Java agent to an [sbt-native-packager] distribution, enable the `JavaAgent` plugin on a project that also has `JavaAppPackaging` enabled, and then add the agent dependency using the `javaAgents` setting. For example:
16+
17+
```scala
18+
lazy val distProject = project
19+
.in(file("somewhere"))
20+
.enablePlugins(JavaAgent, JavaAppPackaging)
21+
.settings(
22+
javaAgents += "com.example" % "agent" % "1.2.3"
23+
)
24+
```
25+
26+
This will automatically resolve the agent module, bundle the agent artifact in the distribution, and add a `-javaagent` option to the start script.
27+
28+
> **Note**: sbt-javaagent has a dynamic dependency on [sbt-native-packager]. You need to add sbt-native-packager separately.
29+
30+
## Scopes
31+
32+
By default, sbt-javaagent will only add an agent to distributions. Agents can be optionally enabled for compile, run, or test.
33+
34+
The following scopes are supported:
35+
36+
* **dist** — bundle the agent in production distributions and add a `-javaagent` option to start scripts
37+
* **compile** — add the agent as a `provided` dependency so that it's available on the compile classpath
38+
* **run** — automatically fork the run and add a `-javaagent` option
39+
* **test** — automatically fork tests and add a `-javaagent` option
40+
41+
The plugin can derive these scopes from module configurations.
42+
43+
For example, to add an agent to *compile*, to build against an API provided by an agent, use the `compile` or `provided` configuration:
44+
45+
```scala
46+
javaAgents += "com.example" % "agent" % "1.2.3" % "compile"
47+
```
48+
49+
Marking a dependency for *compile* will also automatically enable the agent for *run* as well.
50+
51+
To enable for run or tests, use the `runtime` or `test` configurations.
52+
53+
Multiple configurations can be specified. For example, the following will enable both *compile* and *test* (and implicitly *run*):
54+
55+
```scala
56+
javaAgents += "com.example" % "agent" % "1.2.3" % "compile;test"
57+
```
58+
59+
Note that in this case, the agent dependency is actually added under the `provided` configuration, so that a project can compile against the agent and then have the agent provided at runtime using a `-javaagent` option.
60+
61+
If the *compile* scope is not enabled, then the agent dependency is put under a special `javaagent` configuration so that it doesn't appear as a regular library dependency or on build classpaths.
62+
63+
[sbt-native-packager]: https://github.com/sbt/sbt-native-packager

build.sbt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright © 2016 Lightbend, Inc. <http://www.lightbend.com>
3+
*/
4+
5+
lazy val `sbt-javaagent` = project in file(".")
6+
7+
sbtPlugin := true
8+
9+
name := "sbt-javaagent"
10+
organization := "com.lightbend.sbt"
11+
12+
// dependencies
13+
val packagerVersion = "1.0.6"
14+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % packagerVersion % "provided")
15+
16+
// compile settings
17+
scalacOptions ++= Seq("-encoding", "UTF-8", "-target:jvm-1.6", "-unchecked", "-deprecation", "-feature")
18+
javacOptions ++= Seq("-encoding", "UTF-8", "-source", "1.6", "-target", "1.6")
19+
20+
// test agent
21+
lazy val maxwell = project
22+
.in(file("maxwell"))
23+
.settings(
24+
name := "maxwell",
25+
organization := "sbt.javaagent.test",
26+
autoScalaLibrary := false,
27+
crossPaths := false,
28+
packageOptions += Package.ManifestAttributes("Premain-Class" -> "maxwell.Maxwell"),
29+
publish := ()
30+
)
31+
32+
// test settings
33+
scriptedSettings
34+
scriptedLaunchOpts ++= Seq(
35+
"-Dproject.version=" + version.value,
36+
"-Dpackager.version=" + packagerVersion
37+
)
38+
scriptedDependencies := {
39+
(publishLocal in maxwell).value
40+
publishLocal.value
41+
}
42+
test in Test := {
43+
(test in Test).value
44+
ScriptedPlugin.scripted.toTask("").value
45+
}
46+
47+
// publish settings
48+
publishMavenStyle := false
49+
bintrayOrganization := Some("lightbend")
50+
bintrayRepository := "sbt-plugin-releases"
51+
bintrayPackage := "sbt-javaagent"
52+
bintrayReleaseOnPublish := false
53+
licenses += "Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0.html")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package maxwell;
2+
3+
import java.lang.instrument.Instrumentation;
4+
5+
public class Maxwell {
6+
public static void premain(String agentArgs, Instrumentation instrumentation) {
7+
System.out.println("Agent 86");
8+
}
9+
}

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=0.13.11

project/plugins.sbt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright © 2016 Lightbend, Inc. <http://www.lightbend.com>
3+
*/
4+
5+
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.3")
6+
addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0")
7+
8+
libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright © 2016 Lightbend, Inc. <http://www.lightbend.com>
3+
*/
4+
5+
package com.lightbend.sbt.javaagent
6+
7+
import sbt._
8+
import sbt.Keys._
9+
10+
/**
11+
* Plugin for adding Java agents to projects in a general way.
12+
* Supports agents as compile-time dependencies, in forked tests, in forked run,
13+
* and in `sbt-native-packager` dists through the `JavaAgentPackaging` plugin.
14+
*/
15+
object JavaAgent extends AutoPlugin {
16+
17+
object JavaAgentKeys {
18+
val javaAgents = settingKey[Seq[AgentModule]]("Java agent modules enabled for this project.")
19+
val resolvedJavaAgents = taskKey[Seq[ResolvedAgent]]("Java agent modules with resolved artifacts.")
20+
}
21+
22+
import JavaAgentKeys._
23+
24+
val autoImport = JavaAgentKeys
25+
26+
val AgentConfig = config("javaagent").hide
27+
28+
case class AgentScope(compile: Boolean = false, test: Boolean = false, run: Boolean = false, dist: Boolean = true)
29+
30+
case class AgentModule(name: String, module: ModuleID, scope: AgentScope)
31+
32+
case class ResolvedAgent(agent: AgentModule, artifact: File)
33+
34+
object AgentModule {
35+
import scala.language.implicitConversions
36+
implicit def moduleToAgentModule(module: ModuleID): AgentModule = JavaAgent(module)
37+
}
38+
39+
/**
40+
* Create an agent module from a module dependency.
41+
* Scope is also derived from the given module configuration.
42+
*/
43+
def apply(module: ModuleID, name: String = null, scope: AgentScope = AgentScope()): AgentModule = {
44+
val agentName = Option(name).getOrElse(module.name)
45+
val confs = module.configurations.toSeq.flatMap(_.split(";"))
46+
val inCompile = scope.compile || confs.contains(Compile.name) || confs.contains(Provided.name)
47+
val inRun = scope.run || inCompile || confs.contains(Runtime.name)
48+
val inTest = scope.test || confs.contains(Test.name)
49+
val inDist = scope.dist
50+
val configuration = if (inCompile) Provided else AgentConfig
51+
val reconfiguredModule = module.copy(configurations = Some(configuration.name))
52+
val configuredScope = AgentScope(compile = inCompile, test = inTest, run = inRun, dist = inDist)
53+
AgentModule(agentName, reconfiguredModule, configuredScope)
54+
}
55+
56+
override def requires = plugins.JvmPlugin
57+
58+
override def projectSettings = Seq(
59+
javaAgents := Seq.empty,
60+
ivyConfigurations += AgentConfig,
61+
libraryDependencies ++= javaAgents.value.map(_.module),
62+
resolvedJavaAgents := resolveAgents.value,
63+
fork in run := enableFork(fork in run, _.scope.run).value,
64+
fork in Test := enableFork(fork in Test, _.scope.test).value,
65+
javaOptions in run ++= agentOptions(_.agent.scope.run).value,
66+
javaOptions in Test ++= agentOptions(_.agent.scope.test).value
67+
)
68+
69+
private def resolveAgents = Def.task[Seq[ResolvedAgent]] {
70+
javaAgents.value flatMap { agent =>
71+
update.value.matching(exactModuleFilter(agent.module)).headOption map {
72+
jar => ResolvedAgent(agent, jar)
73+
}
74+
}
75+
}
76+
77+
private def exactModuleFilter(module: ModuleID) = new ModuleFilter {
78+
def apply(m: ModuleID) = {
79+
m.organization == module.organization &&
80+
m.name == module.name &&
81+
m.revision == module.revision
82+
}
83+
}
84+
85+
private def enableFork(forkKey: SettingKey[Boolean], enabled: AgentModule => Boolean) = Def.setting[Boolean] {
86+
forkKey.value || javaAgents.value.exists(enabled)
87+
}
88+
89+
private def agentOptions(enabled: ResolvedAgent => Boolean) = Def.task[Seq[String]] {
90+
resolvedJavaAgents.value filter enabled map { agent =>
91+
"-javaagent:" + agent.artifact.absolutePath
92+
}
93+
}
94+
95+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright © 2016 Lightbend, Inc. <http://www.lightbend.com>
3+
*/
4+
5+
package com.lightbend.sbt.javaagent
6+
7+
import sbt._
8+
import sbt.Keys._
9+
import com.typesafe.sbt.packager.archetypes.JavaAppPackaging.autoImport.bashScriptExtraDefines
10+
import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport.Universal
11+
import java.io.File
12+
13+
/**
14+
* Plugin for adding Java agents to `sbt-native-packager` distributions.
15+
*/
16+
object JavaAgentPackaging extends AutoPlugin {
17+
import JavaAgent.JavaAgentKeys._
18+
19+
override def trigger = allRequirements
20+
21+
override def requires = JavaAgent && PluginRef("com.typesafe.sbt.packager.archetypes.JavaAppPackaging")
22+
23+
override def projectSettings = Seq(
24+
mappings in Universal ++= agentMappings.value,
25+
bashScriptExtraDefines ++= agentScriptOptions.value
26+
)
27+
28+
private def agentMappings = Def.task[Seq[(File, String)]] {
29+
resolvedJavaAgents.value filter (_.agent.scope.dist) map { resolved =>
30+
resolved.artifact -> (Project.normalizeModuleID(resolved.agent.name) + File.separator + resolved.artifact.name)
31+
}
32+
}
33+
34+
private def agentScriptOptions = Def.task[Seq[String]] {
35+
agentMappings.value map {
36+
case (_, path) => s"""addJava "-javaagent:$${app_home}/../${normalizePath(path)}" """
37+
}
38+
}
39+
40+
private def normalizePath(path: String, separator: Char = File.separatorChar): String = {
41+
if (separator == '/') path else path.replace(separator, '/')
42+
}
43+
44+
/**
45+
* Reflectively load an auto plugin if it's on the classpath, otherwise
46+
* return an empty plugin that won't trigger all-requirements loading.
47+
* See also: https://github.com/sbt/sbt/issues/2538
48+
*/
49+
private def PluginRef(className: String): AutoPlugin = {
50+
try {
51+
val name = if (className.endsWith("$")) className else (className + "$")
52+
Class.forName(name).asInstanceOf[Class[AutoPlugin]]
53+
.getDeclaredField("MODULE$").get(null) match {
54+
case plugin: AutoPlugin => plugin
55+
case _ => throw new RuntimeException(s"[$name] is not an AutoPlugin object")
56+
}
57+
} catch {
58+
case _: Throwable => NoPlugin
59+
}
60+
}
61+
62+
object NoPlugin extends AutoPlugin
63+
64+
}

src/sbt-test/agent/compile/build.sbt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lazy val agentCompile = project in file(".") enablePlugins (JavaAgent, JavaAppPackaging)
2+
3+
javaAgents += "sbt.javaagent.test" % "maxwell" % sys.props("project.version") % "compile"

0 commit comments

Comments
 (0)