diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index ad7e4866c..3e45f790b 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -80,21 +80,24 @@ final class IvySbt(configuration: IvyConfiguration) def withModule[T](f: (Ivy,DefaultModuleDescriptor,String) => T): T = withIvy[T] { ivy => f(ivy, moduleDescriptor, defaultConfig) } - import moduleConfiguration._ private lazy val (moduleDescriptor: DefaultModuleDescriptor, defaultConfig: String) = { val (baseModule, baseConfiguration) = - if(isUnconfigured) - autodetectDependencies(IvySbt.toID(module)) - else - configureModule - ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration)) - baseModule.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String,String]].put("m", "m") + moduleConfiguration match + { + case ic: InlineConfiguration => configureInline(ic) + case ec: EmptyConfiguration => configureEmpty(ec.module) + case pc: PomConfiguration => readPom(pc.file, pc.validate) + case ifc: IvyFileConfiguration => readIvyFile(ifc.file, ifc.validate) + } + moduleConfiguration.ivyScala.foreach(IvyScala.checkModule(baseModule, baseConfiguration)) + baseModule.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String,String]].put("e", "http://ant.apache.org/ivy/extra") (baseModule, baseConfiguration) } - private def configureModule = + private def configureInline(ic: InlineConfiguration) = { - val moduleID = newConfiguredModuleID + import ic._ + val moduleID = newConfiguredModuleID(module, configurations) val defaultConf = defaultConfiguration getOrElse Configurations.config(ModuleDescriptor.DEFAULT_CONFIGURATION) log.debug("Using inline dependencies specified in Scala" + (if(ivyXML.isEmpty) "." else " and XML.")) @@ -105,7 +108,7 @@ final class IvySbt(configuration: IvyConfiguration) IvySbt.addMainArtifact(moduleID) (moduleID, parser.getDefaultConf) } - private def newConfiguredModuleID = + private def newConfiguredModuleID(module: ModuleID, configurations: Iterable[Configuration]) = { val mod = new DefaultModuleDescriptor(IvySbt.toID(module), "release", null, false) mod.setLastModified(System.currentTimeMillis) @@ -114,13 +117,13 @@ final class IvySbt(configuration: IvyConfiguration) } /** Parses the given Maven pom 'pomFile'.*/ - private def readPom(pomFile: File) = + private def readPom(pomFile: File, validate: Boolean) = { val md = PomModuleDescriptorParser.getInstance.parseDescriptor(settings, toURL(pomFile), validate) (IvySbt.toDefaultModuleDescriptor(md), "compile") } /** Parses the given Ivy file 'ivyFile'.*/ - private def readIvyFile(ivyFile: File) = + private def readIvyFile(ivyFile: File, validate: Boolean) = { val url = toURL(ivyFile) val parser = new CustomXmlParser.CustomParser(settings) @@ -131,30 +134,14 @@ final class IvySbt(configuration: IvyConfiguration) (IvySbt.toDefaultModuleDescriptor(md), parser.getDefaultConf) } private def toURL(file: File) = file.toURI.toURL - /** Called to determine dependencies when the dependency manager is SbtManager and no inline dependencies (Scala or XML) - * are defined. It will try to read from pom.xml first and then ivy.xml if pom.xml is not found. If neither is found, - * Ivy is configured with defaults.*/ - private def autodetectDependencies(module: ModuleRevisionId) = + private def configureEmpty(module: ModuleID) = { - log.debug("Autodetecting dependencies.") - val defaultPOMFile = IvySbt.defaultPOM(paths.baseDirectory) - if(defaultPOMFile.canRead) - readPom(defaultPOMFile) - else - { - val defaultIvy = IvySbt.defaultIvyFile(paths.baseDirectory) - if(defaultIvy.canRead) - readIvyFile(defaultIvy) - else - { - val defaultConf = ModuleDescriptor.DEFAULT_CONFIGURATION - log.warn("No dependency configuration found, using defaults.") - val moduleID = DefaultModuleDescriptor.newDefaultInstance(module) - IvySbt.addMainArtifact(moduleID) - IvySbt.addDefaultArtifact(defaultConf, moduleID) - (moduleID, defaultConf) - } - } + val defaultConf = ModuleDescriptor.DEFAULT_CONFIGURATION + val moduleID = new DefaultModuleDescriptor(IvySbt.toID(module), "release", null, false) + moduleID.setLastModified(System.currentTimeMillis) + moduleID.addConfiguration(IvySbt.toIvyConfiguration(Configurations.Default)) + IvySbt.addMainArtifact(moduleID) + (moduleID, defaultConf) } } } @@ -165,9 +152,9 @@ private object IvySbt val DefaultIvyFilename = "ivy.xml" val DefaultMavenFilename = "pom.xml" - private def defaultIvyFile(project: File) = new File(project, DefaultIvyFilename) - private def defaultIvyConfiguration(project: File) = new File(project, DefaultIvyConfigFilename) - private def defaultPOM(project: File) = new File(project, DefaultMavenFilename) + def defaultIvyFile(project: File) = new File(project, DefaultIvyFilename) + def defaultIvyConfiguration(project: File) = new File(project, DefaultIvyConfigFilename) + def defaultPOM(project: File) = new File(project, DefaultMavenFilename) /** Sets the resolvers for 'settings' to 'resolvers'. This is done by creating a new chain and making it the default. */ private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], log: IvyLogger) @@ -190,7 +177,7 @@ private object IvySbt manager.setChangingPattern(".*-SNAPSHOT"); settings.setDefaultRepositoryCacheManager(manager) } - private def toIvyConfiguration(configuration: Configuration) = + def toIvyConfiguration(configuration: Configuration) = { import org.apache.ivy.core.module.descriptor.{Configuration => IvyConfig} import IvyConfig.Visibility._ @@ -207,10 +194,10 @@ private object IvySbt moduleID.check() } /** Converts the given sbt module id into an Ivy ModuleRevisionId.*/ - private[xsbt] def toID(m: ModuleID) = + def toID(m: ModuleID) = { import m._ - ModuleRevisionId.newInstance(organization, name, revision) + ModuleRevisionId.newInstance(organization, name, revision, javaMap(extraAttributes)) } private def toIvyArtifact(moduleID: ModuleDescriptor, a: Artifact, configurations: Iterable[String]): MDArtifact = { @@ -218,7 +205,19 @@ private object IvySbt configurations.foreach(artifact.addConfiguration) artifact } - private def extra(artifact: Artifact) = artifact.classifier.map(c => javaMap("m:classifier" -> c)).getOrElse(null) + private def extra(artifact: Artifact) = + { + val ea = artifact.classifier match { case Some(c) => artifact.extra("e:classifier" -> c); case None => artifact } + javaMap(artifact.extraAttributes) + } + private def javaMap(map: Map[String,String]) = + if(map.isEmpty) null + else + { + val wrap = scala.collection.jcl.Map(new java.util.HashMap[String,String]) + wrap ++= map + wrap.underlying + } private object javaMap { diff --git a/ivy/IvyConfigurations.scala b/ivy/IvyConfigurations.scala index 39b7948be..6bf0115e5 100644 --- a/ivy/IvyConfigurations.scala +++ b/ivy/IvyConfigurations.scala @@ -9,17 +9,21 @@ import scala.xml.NodeSeq final class IvyPaths(val baseDirectory: File, val cacheDirectory: Option[File]) extends NotNull final class IvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val log: IvyLogger) extends NotNull -final class ModuleConfiguration(val module: ModuleID, val dependencies: Iterable[ModuleID], val ivyXML: NodeSeq, - val configurations: Iterable[Configuration], val defaultConfiguration: Option[Configuration], val ivyScala: Option[IvyScala], - val artifacts: Iterable[Artifact], val validate: Boolean) extends NotNull +sealed trait ModuleConfiguration extends NotNull { - def isUnconfigured = dependencies.isEmpty && ivyXML.isEmpty && configurations.isEmpty && - defaultConfiguration.isEmpty && artifacts.isEmpty + def validate: Boolean + def ivyScala: Option[IvyScala] } -object ModuleConfiguration +final class IvyFileConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration +final class PomConfiguration(val file: File, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration +final class InlineConfiguration(val module: ModuleID, val dependencies: Iterable[ModuleID], val ivyXML: NodeSeq, + val configurations: Iterable[Configuration], val defaultConfiguration: Option[Configuration], val ivyScala: Option[IvyScala], + val artifacts: Iterable[Artifact], val validate: Boolean) extends ModuleConfiguration +final class EmptyConfiguration(val module: ModuleID, val ivyScala: Option[IvyScala], val validate: Boolean) extends ModuleConfiguration +object InlineConfiguration { def apply(module: ModuleID, dependencies: Iterable[ModuleID], artifacts: Iterable[Artifact]) = - new ModuleConfiguration(module, dependencies, NodeSeq.Empty, Nil, None, None, artifacts, false) + new InlineConfiguration(module, dependencies, NodeSeq.Empty, Nil, None, None, artifacts, false) def configurations(explicitConfigurations: Iterable[Configuration], defaultConfiguration: Option[Configuration]) = if(explicitConfigurations.isEmpty) { @@ -32,4 +36,25 @@ object ModuleConfiguration } else explicitConfigurations +} +object ModuleConfiguration +{ + def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: IvyLogger) = + { + log.debug("Autodetecting dependencies.") + val defaultPOMFile = IvySbt.defaultPOM(baseDirectory) + if(defaultPOMFile.canRead) + new PomConfiguration(defaultPOMFile, ivyScala, validate) + else + { + val defaultIvy = IvySbt.defaultIvyFile(baseDirectory) + if(defaultIvy.canRead) + new IvyFileConfiguration(defaultIvy, ivyScala, validate) + else + { + log.warn("No dependency configuration found, using defaults.") + new EmptyConfiguration(module, ivyScala, validate) + } + } + } } \ No newline at end of file diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 5b8312cc9..f62266cb1 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -9,16 +9,17 @@ import scala.xml.NodeSeq import org.apache.ivy.plugins.resolver.IBiblioResolver import org.apache.ivy.util.url.CredentialsStore -final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String], isChanging: Boolean, isTransitive: Boolean, explicitArtifacts: Seq[Artifact]) extends NotNull +final case class ModuleID(organization: String, name: String, revision: String, configurations: Option[String], isChanging: Boolean, isTransitive: Boolean, explicitArtifacts: Seq[Artifact], extraAttributes: Map[String,String]) extends NotNull { override def toString = organization + ":" + name + ":" + revision // () required for chaining def notTransitive() = intransitive() - def intransitive() = ModuleID(organization, name, revision, configurations, isChanging, false, explicitArtifacts) - def changing() = ModuleID(organization, name, revision, configurations, true, isTransitive, explicitArtifacts) + def intransitive() = ModuleID(organization, name, revision, configurations, isChanging, false, explicitArtifacts, extraAttributes) + def changing() = ModuleID(organization, name, revision, configurations, true, isTransitive, explicitArtifacts, extraAttributes) def from(url: String) = artifacts(Artifact(name, new URL(url))) def classifier(c: String) = artifacts(Artifact(name, c)) - def artifacts(newArtifacts: Artifact*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, newArtifacts ++ explicitArtifacts) + def artifacts(newArtifacts: Artifact*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, newArtifacts ++ explicitArtifacts, extraAttributes) + def extra(attributes: (String,String)*) = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, explicitArtifacts, extraAttributes ++ attributes) } object ModuleID { @@ -27,6 +28,8 @@ object ModuleID ModuleID(organization, name, revision, configurations, false, true) def apply(organization: String, name: String, revision: String, configurations: Option[String], isChanging: Boolean, isTransitive: Boolean): ModuleID = ModuleID(organization, name, revision, configurations, isChanging, isTransitive, Nil) + def apply(organization: String, name: String, revision: String, configurations: Option[String], isChanging: Boolean, isTransitive: Boolean, explicitArtifacts: Seq[Artifact]): ModuleID = + ModuleID(organization, name, revision, configurations, isChanging, isTransitive, explicitArtifacts, Map.empty) } sealed trait Resolver extends NotNull { @@ -302,13 +305,19 @@ final case class Configuration(name: String, description: String, isPublic: Bool override def toString = name } -final case class Artifact(name: String, `type`: String, extension: String, classifier: Option[String], configurations: Iterable[Configuration], url: Option[URL]) extends NotNull +final case class Artifact(name: String, `type`: String, extension: String, classifier: Option[String], configurations: Iterable[Configuration], url: Option[URL], extraAttributes: Map[String,String]) extends NotNull +{ + def extra(attributes: (String,String)*) = Artifact(name, `type`, extension, classifier, configurations, url, extraAttributes ++ attributes) +} object Artifact { def apply(name: String): Artifact = Artifact(name, defaultType, defaultExtension, None, Nil, None) + def apply(name: String, extra: Map[String,String]): Artifact = Artifact(name, defaultType, defaultExtension, None, Nil, None, extra) def apply(name: String, classifier: String): Artifact = Artifact(name, defaultType, defaultExtension, Some(classifier), Nil, None) def apply(name: String, `type`: String, extension: String): Artifact = Artifact(name, `type`, extension, None, Nil, None) def apply(name: String, url: URL): Artifact =Artifact(name, extract(url, defaultType), extract(url, defaultExtension), None, Nil, Some(url)) + def apply(name: String, `type`: String, extension: String, classifier: Option[String], configurations: Iterable[Configuration], url: Option[URL]): Artifact = + Artifact(name, `type`, extension, classifier, configurations, url, Map.empty) val defaultExtension = "jar" val defaultType = "jar" def extract(url: URL, default: String): String = extract(url.toString, default) diff --git a/launch/Boot.scala b/launch/Boot.scala index 05b94ebc5..2de628dee 100644 --- a/launch/Boot.scala +++ b/launch/Boot.scala @@ -11,6 +11,7 @@ object Boot { def main(args: Array[String]) { + System.setProperty("scala.home", "") // avoid errors from mixing Scala versions in the same JVM CheckProxy() try { Launch(args) } catch diff --git a/launch/BootConfiguration.scala b/launch/BootConfiguration.scala index 9527f5d3a..3c6d0da31 100644 --- a/launch/BootConfiguration.scala +++ b/launch/BootConfiguration.scala @@ -41,6 +41,8 @@ private object BootConfiguration /** The class name prefix used to hide the sbt launcher classes from sbt and the project definition. * Note that access to xsbti classes are allowed.*/ val SbtBootPackage = "xsbt.boot." + /** The prefix for JLine resources.*/ + val JLinePackagePath = "jline/" /** The loader will check that these classes can be loaded and will assume that their presence indicates * sbt and its dependencies have been downloaded.*/ val TestLoadSbtClasses = "xsbt.Main" :: "org.apache.ivy.Ivy" :: Nil @@ -66,7 +68,7 @@ private object BootConfiguration /** The Ivy pattern to use for retrieving sbt and its dependencies. It is relative to the directory * containing all jars for the requested version of scala. */ - def sbtRetrievePattern(sbtVersion: String) = sbtDirectoryName(sbtVersion) + "/[conf]/[artifact]-[revision].[ext]" + def sbtRetrievePattern(sbtVersion: String) = sbtDirectoryName(sbtVersion) + "(/[component])/[artifact]-[revision].[ext]" /** The Ivy pattern to use for resolving sbt and its dependencies from the Google code project.*/ def sbtResolverPattern(scalaVersion: String) = sbtRootBase + "[revision]/[type]s/[artifact].[ext]" /** The name of the directory to retrieve sbt and its dependencies to.*/ diff --git a/launch/FilteredLoader.scala b/launch/FilteredLoader.scala index 5dbcfe1e9..bfad526d4 100644 --- a/launch/FilteredLoader.scala +++ b/launch/FilteredLoader.scala @@ -3,7 +3,7 @@ */ package xsbt.boot -import BootConfiguration.{IvyPackage, SbtBootPackage, ScalaPackage} +import BootConfiguration.{IvyPackage, JLinePackagePath, SbtBootPackage, ScalaPackage} /** A custom class loader to ensure the main part of sbt doesn't load any Scala or * Ivy classes from the jar containing the loader. */ @@ -12,11 +12,13 @@ private[boot] final class BootFilteredLoader(parent: ClassLoader) extends ClassL @throws(classOf[ClassNotFoundException]) override final def loadClass(className: String, resolve: Boolean): Class[_] = { + // note that we allow xsbti.* and jline.* if(className.startsWith(ScalaPackage) || className.startsWith(IvyPackage) || className.startsWith(SbtBootPackage)) throw new ClassNotFoundException(className) else super.loadClass(className, resolve) } - override def getResources(name: String) = null - override def getResource(name: String) = null + override def getResources(name: String) = if(includeResource(name)) super.getResources(name) else null + override def getResource(name: String) = if(includeResource(name)) super.getResource(name) else null + def includeResource(name: String) = name.startsWith(JLinePackagePath) } \ No newline at end of file diff --git a/launch/Launch.scala b/launch/Launch.scala index e5cff462d..20d710e3b 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -9,7 +9,7 @@ // satisfies requests from the main sbt for different versions of Scala for use in the build. import java.io.{File, FileFilter} -import java.net.URLClassLoader +import java.net.{URL, URLClassLoader} import xsbti.{Exit => IExit, Launcher, MainResult, Reboot, SbtConfiguration, SbtMain} @@ -18,19 +18,19 @@ import BootConfiguration._ import UpdateTarget.{UpdateScala, UpdateSbt} -class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher with NotNull +class Launch(val BaseDirectory: File, mainClassName: String) extends Launcher with NotNull { - def this(projectRootDirectory: File) = this(projectRootDirectory, SbtMainClass) + def this(baseDirectory: File) = this(baseDirectory, SbtMainClass) def this() = this(new File(".")) import Launch._ final def boot(args: Array[String]) { - System.setProperty("scala.home", "") // avoid errors from mixing Scala versions in the same JVM checkAndLoad(args) match { - case e: Exit => System.exit(e.code) + case e: IExit => System.exit(e.code) case r: Reboot => boot(r.arguments()) + case x => println("Unknown result (" + x.getClass + "): " + x); System.exit(1) } } def checkAndLoad(args: Array[String]): MainResult = @@ -67,6 +67,7 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher def scalaVersion = definitionScalaVersion def sbtVersion = useSbtVersion def launcher: Launcher = Launch.this + def baseDirectory = BaseDirectory } run(sbtLoader, mainClassName, configuration) } @@ -83,7 +84,7 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher main.run(configuration) } - final val ProjectDirectory = new File(projectRootDirectory, ProjectDirectoryName) + final val ProjectDirectory = new File(BaseDirectory, ProjectDirectoryName) final val BootDirectory = new File(ProjectDirectory, BootDirectoryName) final val PropertiesFile = new File(ProjectDirectory, BuildPropertiesName) @@ -134,19 +135,21 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher { val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion)) val mainComponentLocation = componentLocation(sbtVersion, MainSbtComponentID, scalaVersion) - val sbtLoader = newSbtLoader(mainComponentLocation, parentLoader) + val nonComponentLocation = getSbtHome(sbtVersion, scalaVersion) + val directories = Seq(mainComponentLocation, nonComponentLocation) + val sbtLoader = newSbtLoader(directories, parentLoader) if(needsUpdate(sbtLoader, TestLoadSbtClasses)) { (new Update(baseDirectory, sbtVersion, scalaVersion))(UpdateSbt) - val sbtLoader = newSbtLoader(mainComponentLocation, parentLoader) + val sbtLoader = newSbtLoader(directories, parentLoader) failIfMissing(sbtLoader, TestLoadSbtClasses, "sbt " + sbtVersion) sbtLoader } else sbtLoader } - private def newScalaLoader(dir: File) = newLoader(dir, new BootFilteredLoader(getClass.getClassLoader)) - private def newSbtLoader(dir: File, parentLoader: ClassLoader) = newLoader(dir, parentLoader) + private def newScalaLoader(dir: File) = newLoader(Seq(dir), new BootFilteredLoader(getClass.getClassLoader)) + private def newSbtLoader(directories: Seq[File], parentLoader: ClassLoader) = newLoader(directories, parentLoader) } private object Launch { @@ -171,8 +174,8 @@ private object Launch } catch { case e: ClassNotFoundException => ifFailure } } - private def newLoader(directory: File, parent: ClassLoader) = new URLClassLoader(getJars(directory), parent) - private def getJars(directory: File) = wrapNull(directory.listFiles(JarFilter)).map(_.toURI.toURL) + private def newLoader(directories: Seq[File], parent: ClassLoader) = new URLClassLoader(getJars(directories), parent) + private def getJars(directories: Seq[File]): Array[URL] = directories.flatMap(directory => wrapNull(directory.listFiles(JarFilter))).map(_.toURI.toURL).toArray private def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a } private object JarFilter extends FileFilter diff --git a/launch/interface/src/main/java/xsbti/Launcher.java b/launch/interface/src/main/java/xsbti/Launcher.java index fb7925aeb..d853cff31 100644 --- a/launch/interface/src/main/java/xsbti/Launcher.java +++ b/launch/interface/src/main/java/xsbti/Launcher.java @@ -13,6 +13,7 @@ public interface Launcher extends ScalaProvider, SbtProvider public ClassLoader update(String scalaVersion, String sbtVersion); public MainResult run(ClassLoader sbtLoader, String mainClassName, SbtConfiguration configuration); + public File BaseDirectory(); public File ProjectDirectory(); public File BootDirectory(); public File PropertiesFile(); diff --git a/launch/interface/src/main/java/xsbti/SbtConfiguration.java b/launch/interface/src/main/java/xsbti/SbtConfiguration.java index d7d6e1909..16705e695 100644 --- a/launch/interface/src/main/java/xsbti/SbtConfiguration.java +++ b/launch/interface/src/main/java/xsbti/SbtConfiguration.java @@ -5,5 +5,6 @@ public interface SbtConfiguration public String[] arguments(); public String scalaVersion(); public String sbtVersion(); + public java.io.File baseDirectory(); public Launcher launcher(); } \ No newline at end of file diff --git a/main/Main.scala b/main/Main.scala new file mode 100644 index 000000000..313a35ba4 --- /dev/null +++ b/main/Main.scala @@ -0,0 +1,14 @@ +package xsbt + +import xsbti.{Exit => IExit, MainResult, SbtConfiguration, SbtMain} + +class Main extends xsbti.SbtMain +{ + def run(configuration: SbtConfiguration): MainResult = + { + println(configuration.arguments.mkString("\n")) + Exit(0) + } +} + +final case class Exit(code: Int) extends IExit \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index 2b57548c4..1ff55a8fa 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,7 +1,7 @@ #Project properties -#Fri Aug 28 10:19:53 EDT 2009 +#Tue Sep 08 15:55:04 EDT 2009 project.organization=org.scala-tools.sbt project.name=xsbt -sbt.version=0.5.3-p1 -project.version=0.7 +sbt.version=0.5.3-p4 +project.version=0.7.0_13 scala.version=2.7.5 diff --git a/project/build/LauncherProguard.scala b/project/build/LauncherProguard.scala new file mode 100644 index 000000000..4868e7465 --- /dev/null +++ b/project/build/LauncherProguard.scala @@ -0,0 +1,45 @@ +import sbt._ +import java.io.File + +trait ProguardLaunch extends ProguardProject +{ + override def basicOptions = super.basicOptions ++ Seq(keepJLine) + def outputJar = rootProject.outputPath / ("xsbt-launch-" + version + ".jar") + override def keepClasses = + "org.apache.ivy.plugins.resolver.URLResolver" :: + "org.apache.ivy.plugins.resolver.IBiblioResolver" :: + "xsbti.**" :: + Nil + override def mapInJars(inJars: Seq[File]) = + { + val inputJar = jarPath.asFile.getAbsolutePath + val runtimeClasspath = runClasspath.get.map(_.asFile).toList + val jlineJars = runtimeClasspath.filter(isJLineJar) + // pull out Ivy in order to exclude resources inside + val (ivyJars, notIvy) = inJars.partition(_.getName.startsWith("ivy")) + val otherJars = notIvy.filter(jar => !isJarX(jar, "scala-compiler")) + + log.debug("proguard configuration ivy jar location: " + ivyJars.mkString(", ")) + + (withJar(ivyJars.toSeq, "Ivy") + "(!META-INF/**,!fr/**,!**/antlib.xml,!**/*.png)") :: + (withJar(jlineJars, "JLine") + "(!META-INF/**)" ) :: + otherJars.map( _.getAbsolutePath + "(!META-INF/**,!*.properties)").toList + } + + private def withJar[T](files: Seq[File], name: String) = files.firstOption.getOrElse(error(name + " not present (try running update)")).getAbsolutePath + private def isJLineJar(file: File) = isJarX(file, "jline") + private def isJarX(file: File, x: String) = + { + val name = file.getName + name.startsWith(x) && name.endsWith(".jar") + } + // class body declaration for proguard that keeps all public members + private val allPublic = " {\n public * ;\n}" + + private val keepJLine = + """ + |-keep public class jline.** { + | public protected *; + |} + |""".stripMargin +} diff --git a/project/build/ProguardProject.scala b/project/build/ProguardProject.scala new file mode 100644 index 000000000..d4440380d --- /dev/null +++ b/project/build/ProguardProject.scala @@ -0,0 +1,80 @@ +package sbt + +import java.io.File + +object ProguardProject +{ + val ProguardDescription = "Produces the final compacted jar that contains only the minimum classes needed using proguard." + val WriteProguardDescription = "Creates the configuration file to use with proguard." +} +import ProguardProject._ +trait ProguardProject extends BasicScalaProject +{ + def rawJarPath: Path + def rawPackage: Task + + def proguardConfigurationPath: Path = outputPath / "proguard.pro" + def outputJar: Path + def rootProjectDirectory = rootProject.info.projectPath + + val toolsConfig = config("tools") + val proguardJar = "net.sf.proguard" % "proguard" % "4.3" % "tools->default" + + lazy val proguard = proguardAction + def proguardAction = proguardTask dependsOn(writeProguardConfiguration) describedAs(ProguardDescription) + lazy val writeProguardConfiguration = writeProguardConfigurationAction + def writeProguardConfigurationAction = writeProguardConfigurationTask dependsOn rawPackage describedAs WriteProguardDescription + + def basicOptions: Seq[String] = + Seq( + "-dontoptimize", + "-dontobfuscate", + "-dontnote", + "-dontwarn", + "-ignorewarnings") + def keepClasses: Seq[String] = Nil + def mapInJars(inJars: Seq[File]): Seq[String] = inJars.map(_.getAbsolutePath) + def mapLibraryJars(libJars: Seq[File]): Seq[String] = libJars.map(_.getAbsolutePath) + + def template(inJars: Seq[String], libraryJars: Seq[String], outJars: String, options: Seq[String], mainClass: Option[String], keepClasses: Seq[String]) = + { + val keepMain = + """-keep public class %s { + | public static void main(java.lang.String[]); + |}""" + + val lines = + options ++ + keepClasses.map("-keep public class " + _ + " {\n public * ;\n}") ++ + inJars.map("-injars " + _) ++ + Seq("-injars " + rawJarPath.absolutePath, + "-outjars " + outJars) ++ + libraryJars.map("-libraryjars " + _) ++ + mainClass.map(main => keepMain.stripMargin.format(main)).toList + lines.mkString("\n") + } + + private def proguardTask = + task + { + FileUtilities.clean(outputJar :: Nil, log) + val proguardClasspathString = Path.makeString(managedClasspath(toolsConfig).get) + val configFile = proguardConfigurationPath.asFile.getAbsolutePath + val exitValue = Process("java", List("-Xmx128M", "-cp", proguardClasspathString, "proguard.ProGuard", "@" + configFile)) ! log + if(exitValue == 0) None else Some("Proguard failed with nonzero exit code (" + exitValue + ")") + } + private def writeProguardConfigurationTask = + task + { + val externalDependencies = (mainCompileConditional.analysis.allExternals).map(_.getAbsoluteFile).filter(_.getName.endsWith(".jar")) + log.debug("proguard configuration external dependencies: \n\t" + externalDependencies.mkString("\n\t")) + // partition jars from the external jar dependencies of this project by whether they are located in the project directory + // if they are, they are specified with -injars, otherwise they are specified with -libraryjars + val (externalJars, libraryJars) = externalDependencies.toList.partition(jar => Path.relativize(rootProjectDirectory, jar).isDefined) + log.debug("proguard configuration library jars locations: " + libraryJars.mkString(", ")) + + val proguardConfiguration = template(mapInJars(externalJars), mapLibraryJars(libraryJars), outputJar.absolutePath, basicOptions, getMainClass(false), keepClasses) + log.debug("Proguard configuration written to " + proguardConfigurationPath) + FileUtilities.write(proguardConfigurationPath.asFile, proguardConfiguration, log) + } +} \ No newline at end of file diff --git a/project/build/XSbt.scala b/project/build/XSbt.scala index fc00e466a..2b5ec0ed0 100644 --- a/project/build/XSbt.scala +++ b/project/build/XSbt.scala @@ -4,7 +4,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) { /* Subproject declarations*/ - val launchInterfaceSub = project(launchPath / "interface", "Launcher Interface", new InterfaceProject(_)) + val launchInterfaceSub = project(launchPath / "interface", "Launcher Interface", new LaunchInterfaceProject(_)) val launchSub = project(launchPath, "Launcher", new LaunchProject(_), launchInterfaceSub) val interfaceSub = project("interface", "Interface", new InterfaceProject(_)) @@ -17,7 +17,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) val ivySub = project("ivy", "Ivy", new IvyProject(_), interfaceSub) val logSub = project(utilPath / "log", "Logging", new Base(_), interfaceSub) - val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface Src", new CompilerInterfaceProject(_), interfaceSub) + val compileInterfaceSub = project(compilePath / "interface", "Compiler Interface", new CompilerInterfaceProject(_), interfaceSub) val taskSub = project(tasksPath, "Tasks", new TaskProject(_), controlSub, collectionSub) val cacheSub = project(cachePath, "Cache", new CacheProject(_), taskSub, ioSub) @@ -26,21 +26,45 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) launchInterfaceSub, interfaceSub, ivySub, ioSub, classpathSub, compileInterfaceSub) val stdTaskSub = project(tasksPath / "standard", "Standard Tasks", new StandardTaskProject(_), trackingSub, compilerSub) - /* Multi-subproject paths */ + val mainSub = project("main", "Main", new Base(_), stdTaskSub) + val distSub = project("dist", "Distribution", new DistProject(_)) + /* Multi-subproject paths */ def cachePath = path("cache") def tasksPath = path("tasks") def launchPath = path("launch") def utilPath = path("util") def compilePath = path("compile") + class DistProject(info: ProjectInfo) extends Base(info) with ManagedBase + { + lazy val interDependencies = (XSbt.this.dependencies.toList -- List(distSub, launchSub, launchInterfaceSub, interfaceSub, compileInterfaceSub)) flatMap { + case b: Base => b :: Nil; case _ => Nil + } + override def dependencies = interfaceSub :: compileInterfaceSub :: interDependencies + lazy val dist = increment dependsOn(publishLocal) + override def artifactID = "xsbt" + } + + def increment = task { + val Array(keep, inc) = projectVersion.value.toString.split("_") + projectVersion() = Version.fromString(keep + "_" + (inc.toInt + 1)).right.get + log.info("Version is now " + projectVersion.value) + None + } + + def compilerInterfaceClasspath = compileInterfaceSub.projectClasspath(Configurations.Test) + //run in parallel override def parallelExecution = true /* Subproject configurations*/ - class LaunchProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestDependencies + class LaunchProject(info: ProjectInfo) extends Base(info) with TestWithIO with TestDependencies with ProguardLaunch { + val jline = "jline" % "jline" % "0.9.94" val ivy = "org.apache.ivy" % "ivy" % "2.0.0" + def rawJarPath = jarPath + lazy val rawPackage = packageTask(packagePaths +++ launchInterfaceSub.packagePaths, rawJarPath, packageOptions).dependsOn(compile) // to test the retrieving and loading of the main sbt, we package and publish the test classes to the local repository override def defaultMainArtifact = Artifact(idWithVersion) override def projectID = ModuleID(organization, idWithVersion, "test-" + version) @@ -68,7 +92,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) // these compilation options are useful for debugging caches and task composition //override def compileOptions = super.compileOptions ++ List(Unchecked,ExplainTypes, CompileOption("-Xlog-implicits")) } - class Base(info: ProjectInfo) extends DefaultProject(info) with ManagedBase + class Base(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with Component { override def scratch = true override def consoleClasspath = testClasspath @@ -85,15 +109,21 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) { val ivy = "org.apache.ivy" % "ivy" % "2.0.0" } - class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with TestWithLog + class InterfaceProject(info: ProjectInfo) extends DefaultProject(info) with ManagedBase with TestWithLog with Component { // ensure that interfaces are only Java sources and that they cannot reference Scala classes override def mainSources = descendents(mainSourceRoots, "*.java") override def compileOrder = CompileOrder.JavaThenScala + override def componentID: Option[String] = Some("xsbti") + } + class LaunchInterfaceProject(info: ProjectInfo) extends InterfaceProject(info) + { + override def componentID = None } class CompilerInterfaceProject(info: ProjectInfo) extends Base(info) with SourceProject with TestWithIO with TestWithLog { def xTestClasspath = projectClasspath(Configurations.Test) + override def componentID = Some("compiler-interface-src") } trait TestWithIO extends BasicScalaProject { @@ -106,7 +136,6 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) override def testCompileAction = super.testCompileAction dependsOn(logSub.compile) override def testClasspath = super.testClasspath +++ logSub.compileClasspath } - def compilerInterfaceClasspath = compileInterfaceSub.projectClasspath(Configurations.Test) } trait SourceProject extends BasicScalaProject @@ -122,4 +151,9 @@ trait ManagedBase extends BasicScalaProject override def useDefaultConfigurations = false val defaultConf = Configurations.Default val testConf = Configurations.Test +} +trait Component extends DefaultProject +{ + override def projectID = componentID match { case Some(id) => super.projectID extra("e:component" -> id); case None => super.projectID } + def componentID: Option[String] = None } \ No newline at end of file