From e68f133c7fb6da2e3c4a454c635da0c18f50f979 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 2 Dec 2010 20:22:18 -0500 Subject: [PATCH] {publish,deliver}{,-local} and package tasks --- main/ClasspathProject.scala | 70 ++++++++++++++++++++++--- main/DefaultProject.scala | 67 +++++++++++++++++++----- main/MultiProject.scala | 1 + main/Package.scala | 100 ++++++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 22 deletions(-) create mode 100644 main/Package.scala diff --git a/main/ClasspathProject.scala b/main/ClasspathProject.scala index 86b118ee1..2c68d2219 100644 --- a/main/ClasspathProject.scala +++ b/main/ClasspathProject.scala @@ -8,9 +8,9 @@ package sbt import TaskExtra._ import ClasspathProject._ import java.io.File - import Path._ + import Path._ import Types._ - import scala.xml.NodeSeq + import scala.xml.{Node => XNode,NodeSeq} import scala.collection.mutable.{LinkedHashMap, LinkedHashSet} trait ClasspathProject @@ -55,7 +55,6 @@ trait BasicClasspathProject extends ClasspathProject val unmanagedBase: Task[File] def cacheDirectory: File - val updateConfig: Task[UpdateConfiguration] lazy val ivySbt: Task[IvySbt] = @@ -84,9 +83,26 @@ trait BasicClasspathProject extends ClasspathProject lazy val update = (ivyModule, updateConfig) map cachedUpdate(cacheDirectory / "update", configurationMap) } - -trait DefaultClasspathProject extends BasicClasspathProject with Project +trait PublishProject extends BasicClasspathProject { + val publishConfig: Task[PublishConfiguration] + val publishLocalConfig: Task[PublishConfiguration] + val makePomConfig: Task[MakePomConfiguration] + def packageToPublish: Seq[Task[_]] + + def publishMavenStyle = true + def deliverDepends = if(publishMavenStyle) makePom :: Nil else packageToPublish + + lazy val makePom = (ivyModule, makePomConfig) map(IvyActions.makePom) dependsOn( packageToPublish : _*) + lazy val deliver = (ivyModule, publishConfig) map cachedPublish(cacheDirectory / "deliver")(IvyActions.deliver) dependsOn(deliverDepends : _*) + lazy val deliverLocal = (ivyModule, publishLocalConfig) map cachedPublish(cacheDirectory / "deliver-local")(IvyActions.deliver) dependsOn(deliverDepends : _*) + lazy val publish = (ivyModule, publishConfig) map cachedPublish(cacheDirectory / "publish")(IvyActions.publish) dependsOn(deliver) + lazy val publishLocal = (ivyModule, publishLocalConfig) map cachedPublish(cacheDirectory / "publish-local")(IvyActions.publish) dependsOn(deliverLocal) +} + +trait DefaultClasspathProject extends BasicClasspathProject with PublishProject with Project +{ + def outputDirectory: Path def projectID: ModuleID def baseResolvers: Seq[Resolver] lazy val resolvers: Task[Seq[Resolver]] = task { baseResolvers } @@ -119,6 +135,10 @@ trait DefaultClasspathProject extends BasicClasspathProject with Project def defaultConfiguration: Option[Configuration] = None def ivyScala: Option[IvyScala] = None def ivyValidate: Boolean = false + def moduleID = normalizedName + + def pomFile: File + def publishTo: Resolver = error("Repository for publishing is not specified.") lazy val internalDependencyClasspath: Classpath = internalDependencies(this) @@ -127,6 +147,10 @@ trait DefaultClasspathProject extends BasicClasspathProject with Project lazy val moduleSettings: Task[ModuleSettings] = task { new InlineConfiguration(projectID, libraryDependencies, ivyXML, configurations, defaultConfiguration, ivyScala, ivyValidate) } + + lazy val publishConfig = task { publishConfiguration( publishPatterns(outputDirectory), resolverName = publishTo.name ) } + lazy val publishLocalConfig = task { publishConfiguration( publishPatterns(outputDirectory, true) ) } + lazy val makePomConfig = task { makePomConfiguration(pomFile) } } trait MultiClasspathProject extends DefaultClasspathProject { @@ -134,6 +158,8 @@ trait MultiClasspathProject extends DefaultClasspathProject def name: String def organization: String def version: String + def pomFile: File = outputDirectory / (moduleID + "-" + version + ".pom") + def artifacts: Seq[Artifact] = Nil def projectDependencies: Seq[ModuleID] = resolvedDependencies(this) collect { case (p: DefaultClasspathProject, conf) => p.projectID.copy(configurations = conf) } @@ -143,7 +169,7 @@ trait MultiClasspathProject extends DefaultClasspathProject new RawRepository(new ProjectResolver("inter-project", m)) } - override def projectID = ModuleID(organization, name, version) + override def projectID = ModuleID(organization, moduleID, version).cross(true).artifacts(artifacts.toSeq : _*) override def libraryDependencies: Seq[ModuleID] = super.libraryDependencies ++ projectDependencies override lazy val resolvers: Task[Seq[Resolver]] = projectResolver map { _ +: baseResolvers } @@ -274,10 +300,29 @@ object ClasspathProject case _ => Configurations.Default } + def makePomConfiguration(file: File, configurations: Option[Iterable[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true) = + new MakePomConfiguration(file, configurations, extra, process, filterRepositories) + + def publishConfiguration(patterns: PublishPatterns, resolverName: String = "local", status: String = "release", logging: UpdateLogging.Value = UpdateLogging.DownloadOnly) = + new PublishConfiguration(patterns, status, resolverName, None, logging) + + def publishPatterns(outputPath: Path, publishIvy: Boolean = false): PublishPatterns = + { + val deliverPattern = (outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath + val srcArtifactPatterns: Seq[String] = + { + val pathPatterns = + (outputPath / "[artifact]-[revision]-[type](-[classifier]).[ext]") :: + (outputPath / "[artifact]-[revision](-[classifier]).[ext]") :: + Nil + pathPatterns.map(_.absolutePath) + } + new PublishPatterns( if(publishIvy) Some(deliverPattern) else None, srcArtifactPatterns) + } + import Cache._ import Types._ - import CacheIvy.{classpathFormat, updateIC} - + import CacheIvy.{classpathFormat, publishIC, updateIC} def cachedUpdate(cacheFile: File, configMap: Map[String, Configuration]): (IvySbt#Module :+: UpdateConfiguration :+: HNil) => Map[Configuration, Seq[File]] = { case module :+: config :+: HNil => @@ -293,4 +338,13 @@ object ClasspathProject classpaths map { case (key, value) => (confMap(key), value) } toMap; } + // can't cache deliver/publish easily since files involved are hidden behind patterns. publish will be difficult to verify target-side anyway + def cachedPublish(cacheFile: File)(g: (IvySbt#Module, PublishConfiguration) => Unit): (IvySbt#Module :+: PublishConfiguration :+: HNil) => Unit = + { case module :+: config :+: HNil => + /* implicit val publishCache = publishIC + val f = cached(cacheFile) { (conf: IvyConfiguration, settings: ModuleSettings, config: PublishConfiguration) =>*/ + g(module, config) + /*} + f(module.owner.configuration :+: module.moduleSettings :+: config :+: HNil)*/ + } } \ No newline at end of file diff --git a/main/DefaultProject.scala b/main/DefaultProject.scala index 616bc19dd..57d8e9a3d 100644 --- a/main/DefaultProject.scala +++ b/main/DefaultProject.scala @@ -22,10 +22,10 @@ class DefaultProject(val info: ProjectInfo) extends BasicProject } trait IntegrationTest extends BasicProject { - override def productsTask(conf: Configuration): Task[Seq[Attributed[File]]] = + override def directoryProductsTask(conf: Configuration): Task[Seq[Attributed[File]]] = conf match { case ITestConfig => makeProducts(integrationTestCompile.compile, integrationTestCompile.compileInputs, name, "it-") - case _ => super.productsTask(conf) + case _ => super.directoryProductsTask(conf) } override def configurations: Seq[Configuration] = super.configurations :+ Configurations.IntegrationTest @@ -39,6 +39,9 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with // easier to demo for now override def organization = "org.example" override def version = "1.0" + def artifactID = normalizedName + override def artifacts: Seq[Artifact] = Artifact(artifactID) :: pomArtifact + def pomArtifact = (if(publishMavenStyle) Artifact(artifactID, "pom", "pom") :: Nil else Nil) override def watchPaths: PathFinder = (info.projectDirectory: Path) * sourceFilter +++ descendents("src","*") @@ -46,6 +49,7 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with def scalacOptions: Seq[String] = Nil def consoleOptions: Seq[String] = scalacOptions def initialCommands: String = "" + def maximumErrors: Int = 100 def outputDirectory = "target": Path def cacheDirectory = outputDirectory / "cache" @@ -53,21 +57,53 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with def testResources = descendents("src" / "test" / "resources" ###, "*") def classesDirectory(configuration: Configuration): File = - configuration match { - case CompileConfig => outputDirectory / "classes" - case c => outputDirectory / (c.name + "-classes") - } + outputDirectory / (configString(configuration, "", "-") + "classes") - lazy val products: Classpath = TaskMap(productsTask) + def packageToPublish: Seq[Task[_]] = configurations map packages.apply - // TODO: include resources, perhaps handle jars v. directories - def productsTask(conf: Configuration): Task[Seq[Attributed[File]]] = + lazy val products: Classpath = directoryProducts //TaskMap(productsTask) + + lazy val directoryProducts = TaskMap(directoryProductsTask) + lazy val packages = TaskMap(packageTask) + lazy val pkgMainClass = TaskMap(mainClassTask) + lazy val jarPath = TaskMap(jarPathTask) + + lazy val `package` = packages(CompileConfig) + lazy val testPackage = packages(TestConfig) + + def directoryProductsTask(conf: Configuration): Task[Seq[Attributed[File]]] = conf match { case CompileConfig | DefaultConfig => makeProducts(compile.compile, compile.compileInputs, name, "") case TestConfig => makeProducts(testCompile.compile, testCompile.compileInputs, name, "test-") case x => task { Nil } } + def mainClassesTask(conf: Configuration): Task[Seq[String]] = conf match { + case CompileConfig => discoveredMainClasses + case TestConfig => test.testDiscoveredMainClasses + case _ => task { Nil } + } + def mainClassTask(conf: Configuration): Task[Option[String]] = mainClassesTask(conf) map { classes => SelectMainClass(None, classes) } + def configString(conf: Configuration, pre: String, post: String): String = conf match { + case CompileConfig | DefaultConfig => "" + case _ => pre + conf.name + post + } + + def jarPathTask(conf: Configuration): Task[File] = task { outputDirectory / jarName(conf) } + def jarName(conf: Configuration): String = artifactID + "-" + version + configString(conf, "-", "") + ".jar" + + def packageConfigTask(conf: Configuration): Task[Package.Configuration] = + pkgMainClass(conf) :^: directoryProductsTask(conf) :^: jarPath(conf) :^: KNil map { case main :+: in :+: jar :+: HNil => + val srcs = data(in) flatMap { dir => descendents(dir ###, "*").xx } + new Package.Configuration(srcs, jar, main.map(Package.MainClass.apply).toList) + } + + def packageTask(conf: Configuration): Task[Seq[Attributed[File]]] = + streams :^: packageConfigTask(conf) :^: KNil map { case s :+: config :+: HNil => + Package(config, cacheDirectory / conf.name / "package", s.log) + List(config.jar) + } + lazy val buildScalaVersions: Task[String] = task { info.app.scalaProvider.version }//cross(MultiProject.ScalaVersion)(info.app.scalaProvider.version) lazy val buildScalaInstance: Task[ScalaInstance] = buildScalaVersions map { version => ScalaInstance(version, info.app.scalaProvider) } @@ -78,9 +114,6 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with lazy val discoveredMainClasses: Task[Seq[String]] = discoverMain map { _ collect { case (definition, discovered) if(discovered.hasMain) => definition.name } } - lazy val pkgMainClass: Task[Option[String]] = - discoveredMainClasses map { classes => SelectMainClass(None, classes) } - lazy val runner: Task[ScalaRun] = buildScalaInstance map { si => new Run(si) } @@ -108,7 +141,7 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with lazy val clean = task { IO.delete(outputDirectory) } - // lazy val doc, test-only, test-quick, test-failed, publish(-local), deliver(-local), make-pom, package-*, javap, copy-resources + // lazy val test-only, test-quick, test-failed, package-src, package-test, package-doc, javap lazy val set = input map { in => val Seq(name, value) = in.splitArgs.take(2) @@ -138,7 +171,7 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with val classpath = classes +: data(cp) val analysis = analysisMap(cp) val cache = cacheDirectory / "compile" / configuration.toString - Compile.inputs(classpath, sources.getFiles.toSeq, classes, scalacOptions, javacOptions, allBases.getFiles.toSeq, analysis, cache, 100)(compilers, log) + Compile.inputs(classpath, sources.getFiles.toSeq, classes, scalacOptions, javacOptions, allBases.getFiles.toSeq, analysis, cache, maximumErrors)(compilers, log) } } @@ -239,3 +272,9 @@ class TestTasks(val prefix: Option[String], val project: ClasspathProject with P } lazy val test = (project.streams, executeTests) map { case s :+: results :+: HNil => Test.showResults(s.log, results) } } +/*class PackageTasks +/* def zipTask(sources: PathFinder, outputDirectory: Path, zipName: => String): Task = + zipTask(sources, outputDirectory / zipName) + def zipTask(sources: PathFinder, zipPath: => Path): Task = + fileTask("zip", zipPath from sources) { FileUtilities.zip(sources.get, zipPath, false, log) }*/ +}*/ \ No newline at end of file diff --git a/main/MultiProject.scala b/main/MultiProject.scala index 5a117577f..a1347cabb 100644 --- a/main/MultiProject.scala +++ b/main/MultiProject.scala @@ -167,6 +167,7 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named def settings: Settings = Settings.empty def name: String = info.name getOrElse error("'name' not overridden") + def normalizedName: String = StringUtilities.normalize(name) def base = info.projectDirectory def outputRootPath = base / "target" diff --git a/main/Package.scala b/main/Package.scala new file mode 100644 index 000000000..81b6de24f --- /dev/null +++ b/main/Package.scala @@ -0,0 +1,100 @@ +/* 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, existsInputCache} + import FilesInfo.lastModified + import lastModified.infosInputCache + +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 m2 into m1 (mutating m1 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) + { + import conf._ + val manifest = new Manifest + val main = manifest.getMainAttributes + for(option <- 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) + } + } + + 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 makeJar(sources: Seq[(File, String)], jar: File, manifest: Manifest) + { + println("Packaging...") + IO.delete(jar) + IO.jar(sources, jar, manifest) + println("Done packaging.") + } + + 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 +} \ No newline at end of file