Updates to Ivy component and getting launcher component working.

This commit is contained in:
Mark Harrah 2009-09-08 23:13:30 -04:00
parent 72ce84933d
commit 76e81409df
14 changed files with 296 additions and 80 deletions

View File

@ -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
{

View File

@ -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)
}
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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.*/

View File

@ -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)
}

View File

@ -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

View File

@ -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();

View File

@ -5,5 +5,6 @@ public interface SbtConfiguration
public String[] arguments();
public String scalaVersion();
public String sbtVersion();
public java.io.File baseDirectory();
public Launcher launcher();
}

14
main/Main.scala Normal file
View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}