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

Caching for Osgi-bundles. #6

Closed
wants to merge 3 commits into from
Closed
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: 4 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ object Library {
// Versions
val bndVersion = "2.1.0"
val specs2Version = "1.14"
val armVersion = "1.3"

// Libraries
val bndLib = "biz.aQute.bnd" % "bndlib" % bndVersion
val specs2 = "org.specs2" %% "specs2" % specs2Version
val arm = "com.jsuereth" %% "scala-arm" % armVersion
}

object Dependencies {
Expand All @@ -17,6 +19,7 @@ object Dependencies {

val sbtOsgi = List(
bndLib,
specs2 % "test"
specs2 % "test",
arm
)
}
79 changes: 55 additions & 24 deletions src/main/scala/com/typesafe/sbt/osgi/Osgi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,72 @@ import aQute.bnd.osgi.Constants._
import java.util.Properties
import sbt._
import sbt.Keys._
import resource._
import scala.collection.JavaConversions._
import java.io.{ FileInputStream, FileOutputStream }

private object Osgi {

def bundleTask(
headers: OsgiManifestHeaders,
additionalHeaders: Map[String, String],
fullClasspath: Seq[Attributed[File]],
artifactPath: File,
resourceDirectories: Seq[File],
embeddedJars: Seq[File],
streams: TaskStreams): File = {
val builder = new Builder
builder.setClasspath(fullClasspath map (_.data) toArray)
builder.setProperties(headersToProperties(headers, additionalHeaders))
includeResourceProperty(resourceDirectories.filter(_.exists), embeddedJars) foreach (dirs =>
builder.setProperty(INCLUDERESOURCE, dirs)
)
bundleClasspathProperty(embeddedJars) foreach (jars =>
builder.setProperty(BUNDLE_CLASSPATH, jars)
)
// Write to a temporary file to prevent trying to simultaneously read from and write to the
// same jar file in exportJars mode (which causes a NullPointerException).
val tmpArtifactPath = file(artifactPath.absolutePath + ".tmp")
// builder.build is not thread-safe because it uses a static SimpleDateFormat. This ensures
// that all calls to builder.build are serialized.
val jar = synchronized {
builder.build
streams: TaskStreams,
target: File): File = {

//this is a cache file generated to check that the bnd parameters have not changed
val manifest = target / "manifest.xml"

val props = headersToProperties(headers, additionalHeaders)
val oldProps = new Properties()

//loads the previous set of headers into oldProps if we have a manifest.xml
if (manifest.exists) managed(new FileInputStream(manifest)) foreach oldProps.load
//saves the new properties into manifest.xml if they are different from the previous properties.
if (!oldProps.equals(props)) managed(new FileOutputStream(manifest)) foreach (props.store(_, ""))

//A helper function that turns a File object f into a flat array of Files, expanding f into its child files if f is a directory. If f has directories within it
// it expands those recursively.
def expandClasspath(f: File): Array[File] = if (f.isDirectory) f.listFiles() flatMap expandClasspath else Array(f)

//FileFunction.cached produces a function that takes a Set[File] and runs the function if the files in that set have changed at all, producing another Set[File].
// if the contents of the input set have not changed, the cached function returns the previously generated set of files. cachedFunction checks if the lastModified
// date of the input files has changed, and if the output set already exists or not. It runs the generator function if either the input set last modified dates are
// off or the generated files do not exist.
val cachedFunction = FileFunction.cached(target / "package-cache", FilesInfo.lastModified, FilesInfo.exists) {
(changes: Set[File]) ⇒
val builder = new Builder
builder.setClasspath(fullClasspath map (_.data) toArray)
builder.setProperties(props)
includeResourceProperty(resourceDirectories filter (_.exists), embeddedJars) foreach (dirs ⇒
builder.setProperty(INCLUDE_RESOURCE, dirs)
)
bundleClasspathProperty(embeddedJars) foreach (jars ⇒
builder.setProperty(BUNDLE_CLASSPATH, jars)
)


// Write to a temporary file to prevent trying to simultaneously read from and write to the
// same jar file in exportJars mode (which causes a NullPointerException).
val tmpArtifactPath = file(artifactPath.absolutePath + ".tmp")

// builder.build is not thread-safe because it uses a static SimpleDateFormat. This ensures
// that all calls to builder.build are serialized
val jar = synchronized {
builder.build
}

val log = streams.log
builder.getWarnings foreach (s => log.warn(s"bnd: $s"))
builder.getErrors foreach (s => log.error(s"bnd: $s"))
jar.write(tmpArtifactPath)
IO.move(tmpArtifactPath, artifactPath)
Set(artifactPath)
}
val log = streams.log
builder.getWarnings.foreach(s => log.warn(s"bnd: $s"))
builder.getErrors.foreach(s => log.error(s"bnd: $s"))
jar.write(tmpArtifactPath)
IO.move(tmpArtifactPath, artifactPath)
artifactPath

cachedFunction((fullClasspath flatMap (a ⇒ expandClasspath(a.data)) toSet) ++ resourceDirectories.toSet ++ embeddedJars.toSet + manifest).head
}

def headersToProperties(headers: OsgiManifestHeaders, additionalHeaders: Map[String, String]): Properties = {
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/com/typesafe/sbt/osgi/SbtOsgi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ object SbtOsgi extends Plugin {
artifactPath in (Compile, packageBin),
resourceDirectories in Compile,
embeddedJars,
streams
streams,
target
) map Osgi.bundleTask,
manifestHeaders <<= (
bundleActivator,
Expand Down