/* sbt -- Simple Build Tool * Copyright 2010 Mark Harrah */ package sbt import Predef.{conforms => _, _} import java.io.File import java.util.jar.{Attributes, Manifest} import collection.JavaConversions._ import Types.:+: import Path._ import sbinary.{DefaultProtocol,Format} import DefaultProtocol.{FileFormat, immutableMapFormat, StringFormat, UnitFormat} import Cache.{defaultEquiv, hConsCache, hNilCache, streamFormat, wrapIn} import Tracked.{inputChanged, outputChanged} import FileInfo.exists import FilesInfo.lastModified sealed trait PackageOption object Package { final case class JarManifest(m: Manifest) extends PackageOption { assert(m != null) } final case class MainClass(mainClassName: String) extends PackageOption final case class ManifestAttributes(attributes: (Attributes.Name, String)*) extends PackageOption def ManifestAttributes(attributes: (String, String)*): ManifestAttributes = { val converted = for( (name,value) <- attributes ) yield (new Attributes.Name(name), value) new ManifestAttributes(converted : _*) } def mergeAttributes(a1: Attributes, a2: Attributes) = a1 ++= a2 // merges `mergeManifest` into `manifest` (mutating `manifest` in the process) def mergeManifests(manifest: Manifest, mergeManifest: Manifest) { mergeAttributes(manifest.getMainAttributes, mergeManifest.getMainAttributes) val entryMap = asScalaMap(manifest.getEntries) for((key, value) <- mergeManifest.getEntries) { entryMap.get(key) match { case Some(attributes) => mergeAttributes(attributes, value) case None => entryMap put (key, value) } } } final class Configuration(val sources: Seq[(File, String)], val jar: File, val options: Seq[PackageOption]) def apply(conf: Configuration, cacheFile: File, log: Logger) { val manifest = new Manifest val main = manifest.getMainAttributes for(option <- conf.options) { option match { case JarManifest(mergeManifest) => mergeManifests(manifest, mergeManifest) case MainClass(mainClassName) => main.put(Attributes.Name.MAIN_CLASS, mainClassName) case ManifestAttributes(attributes @ _*) => main ++= attributes case _ => log.warn("Ignored unknown package option " + option) } } setVersion(main) val cachedMakeJar = inputChanged(cacheFile / "inputs") { (inChanged, inputs: Map[File, String] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil) => val sources :+: _ :+: manifest :+: HNil = inputs outputChanged(cacheFile / "output") { (outChanged, jar: PlainFileInfo) => if(inChanged || outChanged) makeJar(sources.toSeq, jar.file, manifest, log) else log.debug("Jar uptodate: " + jar.file) } } val map = conf.sources.toMap val inputs = map :+: lastModified(map.keySet.toSet) :+: manifest :+: HNil cachedMakeJar(inputs)(() => exists(conf.jar)) } def setVersion(main: Attributes) { val version = Attributes.Name.MANIFEST_VERSION if(main.getValue(version) eq null) main.put(version, "1.0") } def addSpecManifestAttributes(name: String, version: String, orgName: String): PackageOption = { import Attributes.Name._ val attribKeys = Seq(SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR) val attribVals = Seq(name, version, orgName) ManifestAttributes(attribKeys zip attribVals : _*) } def addImplManifestAttributes(name: String, version: String, homepage: Option[java.net.URL], org: String, orgName: String): PackageOption = { import Attributes.Name._ val attribKeys = Seq(IMPLEMENTATION_TITLE, IMPLEMENTATION_VERSION, IMPLEMENTATION_VENDOR, IMPLEMENTATION_VENDOR_ID) val attribVals = Seq(name, version, orgName, org) ManifestAttributes((attribKeys zip attribVals) ++ { homepage map(h => (IMPLEMENTATION_URL, h.toString)) } : _*) } def makeJar(sources: Seq[(File, String)], jar: File, manifest: Manifest, log: Logger) { log.info("Packaging " + jar.getAbsolutePath + " ...") IO.delete(jar) log.debug(sourcesDebugString(sources)) IO.jar(sources, jar, manifest) log.info("Done packaging.") } def sourcesDebugString(sources: Seq[(File, String)]): String = "Input file mappings:\n\t" + (sources map { case (f,s) => s + "\n\t " + f} mkString("\n\t") ) implicit def manifestEquiv: Equiv[Manifest] = defaultEquiv implicit def manifestFormat: Format[Manifest] = streamFormat( _ write _, in => new Manifest(in)) implicit def stringMapEquiv: Equiv[Map[File, String]] = defaultEquiv }