2011-12-19 08:43:08 +01:00
|
|
|
/* sbt -- Simple Build Tool
|
|
|
|
|
* Copyright 2011 Sanjin Sehic
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package sbt
|
|
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.net.URI
|
|
|
|
|
|
|
|
|
|
import BuildLoader.ResolveInfo
|
|
|
|
|
import RichURI.fromURI
|
|
|
|
|
|
|
|
|
|
object Resolvers
|
|
|
|
|
{
|
|
|
|
|
type Resolver = BuildLoader.Resolver
|
|
|
|
|
|
|
|
|
|
val local: Resolver = (info: ResolveInfo) => {
|
|
|
|
|
val uri = info.uri
|
2012-02-01 22:26:18 +01:00
|
|
|
val from = new File(uri)
|
|
|
|
|
val to = uniqueSubdirectoryFor(uri, in = info.staging)
|
|
|
|
|
|
2012-02-14 04:01:05 +01:00
|
|
|
if (from.isDirectory) Some {() => if(from.canWrite) from else creates(to) {IO.copyDirectory(from, to)}}
|
2012-02-01 22:26:18 +01:00
|
|
|
else None
|
2011-12-19 08:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val remote: Resolver = (info: ResolveInfo) => {
|
2012-02-01 22:26:18 +01:00
|
|
|
val url = info.uri.toURL
|
|
|
|
|
val to = uniqueSubdirectoryFor(info.uri, in = info.staging)
|
2011-12-19 08:43:08 +01:00
|
|
|
|
2012-02-01 22:26:18 +01:00
|
|
|
Some {() => creates(to) {IO.unzipURL(url, to)}}
|
2011-12-19 08:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
2011-12-19 15:28:26 +01:00
|
|
|
val subversion: Resolver = (info: ResolveInfo) => {
|
|
|
|
|
def normalized(uri: URI) = uri.copy(scheme = "svn")
|
|
|
|
|
|
2012-02-01 22:26:18 +01:00
|
|
|
val uri = info.uri.withoutMarkerScheme
|
|
|
|
|
val localCopy = uniqueSubdirectoryFor(normalized(uri), in = info.staging)
|
2012-02-07 11:06:09 +01:00
|
|
|
val from = uri.withoutFragment.toASCIIString
|
|
|
|
|
val to = localCopy.getAbsolutePath
|
2012-02-01 22:26:18 +01:00
|
|
|
|
|
|
|
|
if (uri.hasFragment) {
|
|
|
|
|
val revision = uri.getFragment
|
|
|
|
|
Some {
|
|
|
|
|
() => creates(localCopy) {
|
2012-02-12 17:11:44 +01:00
|
|
|
run("svn", "checkout", "-q", "-r", revision, from, to)
|
2012-02-01 22:26:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
Some {
|
|
|
|
|
() => creates(localCopy) {
|
2012-02-12 17:11:44 +01:00
|
|
|
run("svn", "checkout", "-q", from, to)
|
2011-12-19 15:28:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-19 14:41:56 +01:00
|
|
|
val mercurial: Resolver = new DistributedVCS
|
|
|
|
|
{
|
|
|
|
|
override val scheme = "hg"
|
|
|
|
|
|
2012-02-12 17:11:44 +01:00
|
|
|
override def clone(from: String, to: File)
|
|
|
|
|
{
|
|
|
|
|
run("hg", "clone", "-q", from, to.getAbsolutePath)
|
2012-02-07 11:16:44 +01:00
|
|
|
}
|
2011-12-19 14:41:56 +01:00
|
|
|
|
|
|
|
|
override def checkout(branch: String, in: File)
|
|
|
|
|
{
|
2012-02-12 17:11:44 +01:00
|
|
|
run(Some(in), "hg", "checkout", "-q", branch)
|
2011-12-19 14:41:56 +01:00
|
|
|
}
|
|
|
|
|
}.toResolver
|
|
|
|
|
|
|
|
|
|
val git: Resolver = new DistributedVCS
|
|
|
|
|
{
|
|
|
|
|
override val scheme = "git"
|
|
|
|
|
|
2012-02-07 11:16:44 +01:00
|
|
|
override def clone(from: String, to: File)
|
|
|
|
|
{
|
2012-02-12 17:11:44 +01:00
|
|
|
run("git", "clone", from, to.getAbsolutePath)
|
2012-02-07 11:16:44 +01:00
|
|
|
}
|
2011-12-19 08:43:08 +01:00
|
|
|
|
2011-12-19 14:41:56 +01:00
|
|
|
override def checkout(branch: String, in: File)
|
2011-12-19 08:43:08 +01:00
|
|
|
{
|
2012-02-12 17:11:44 +01:00
|
|
|
run(Some(in), "git", "checkout", "-q", branch)
|
2011-12-19 08:43:08 +01:00
|
|
|
}
|
2011-12-19 14:41:56 +01:00
|
|
|
}.toResolver
|
2011-12-19 08:43:08 +01:00
|
|
|
|
2011-12-19 14:41:56 +01:00
|
|
|
abstract class DistributedVCS
|
|
|
|
|
{
|
|
|
|
|
val scheme: String
|
Optimize retrieving from git repositories
Instead of cloning from a remote git repository for each branch,
revision or tag separately, the git resolver locally clones only once
the remote git repository and then creates further local clones from
this local copy of the remote repository.
First, optimization, of course, is execution speed, because cloning
local repository is much faster than remote repository. Furthermore,
because git uses hard-linking when a clone of local repository is
created, the second optimization is in space consumption.
For example, if we have one project that uses
https://github.com/harrah/xsbt.git#v0.11.1 and second project that
uses https://github.com/harrah/xsbt.git#v0.11.2, in previous git
resolver implementation it would require two separate clones of the
remote git repository at https://github.com/harrah/xsbt.git. But, the
new git resolver requires only one clone of the remote git repository
and two local clones which take no space because of hard-linking.
2011-12-19 10:43:42 +01:00
|
|
|
|
2012-02-07 11:16:44 +01:00
|
|
|
def clone(from: String, to: File)
|
Optimize retrieving from git repositories
Instead of cloning from a remote git repository for each branch,
revision or tag separately, the git resolver locally clones only once
the remote git repository and then creates further local clones from
this local copy of the remote repository.
First, optimization, of course, is execution speed, because cloning
local repository is much faster than remote repository. Furthermore,
because git uses hard-linking when a clone of local repository is
created, the second optimization is in space consumption.
For example, if we have one project that uses
https://github.com/harrah/xsbt.git#v0.11.1 and second project that
uses https://github.com/harrah/xsbt.git#v0.11.2, in previous git
resolver implementation it would require two separate clones of the
remote git repository at https://github.com/harrah/xsbt.git. But, the
new git resolver requires only one clone of the remote git repository
and two local clones which take no space because of hard-linking.
2011-12-19 10:43:42 +01:00
|
|
|
|
2011-12-19 14:41:56 +01:00
|
|
|
def checkout(branch: String, in: File)
|
|
|
|
|
|
|
|
|
|
def toResolver: Resolver = (info: ResolveInfo) => {
|
|
|
|
|
val uri = info.uri.withoutMarkerScheme
|
2012-02-12 17:11:44 +01:00
|
|
|
val localCopy = uniqueSubdirectoryFor(normalized(uri), in = info.staging)
|
2012-02-07 11:16:44 +01:00
|
|
|
val from = uri.withoutFragment.toASCIIString
|
2012-02-01 22:26:18 +01:00
|
|
|
|
|
|
|
|
if (uri.hasFragment) {
|
|
|
|
|
val branch = uri.getFragment
|
|
|
|
|
Some {
|
2012-02-12 17:11:44 +01:00
|
|
|
() => creates(localCopy) {
|
|
|
|
|
clone(from, to = localCopy)
|
|
|
|
|
checkout(branch, in = localCopy)
|
|
|
|
|
}
|
2012-02-01 22:26:18 +01:00
|
|
|
}
|
2012-02-07 11:16:44 +01:00
|
|
|
} else Some {() => creates(localCopy) {clone(from, to = localCopy)}}
|
2011-12-19 14:41:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def normalized(uri: URI) = uri.copy(scheme = scheme)
|
2011-12-19 08:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private lazy val onWindows = {
|
|
|
|
|
val os = System.getenv("OSTYPE")
|
|
|
|
|
val isCygwin = (os != null) && os.toLowerCase.contains("cygwin")
|
|
|
|
|
val isWindows = System.getProperty("os.name", "").toLowerCase.contains("windows")
|
|
|
|
|
isWindows && !isCygwin
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-12 17:11:44 +01:00
|
|
|
def run(command: String*)
|
|
|
|
|
{
|
|
|
|
|
run(None, command: _*)
|
|
|
|
|
}
|
2012-02-06 18:03:37 +01:00
|
|
|
|
2012-02-12 17:11:44 +01:00
|
|
|
def run(cwd: Option[File], command: String*)
|
|
|
|
|
{
|
|
|
|
|
val result = Process(
|
|
|
|
|
if (onWindows) "cmd" +: "/c" +: command
|
|
|
|
|
else command,
|
|
|
|
|
cwd
|
|
|
|
|
) !;
|
|
|
|
|
if (result != 0)
|
|
|
|
|
error("Nonzero exit code (" + result + "): " + command.mkString(" "))
|
|
|
|
|
}
|
2011-12-19 08:43:08 +01:00
|
|
|
|
|
|
|
|
def creates(file: File)(f: => Unit) =
|
|
|
|
|
{
|
2012-02-07 11:16:44 +01:00
|
|
|
if (!file.exists)
|
|
|
|
|
try {
|
2011-12-19 08:43:08 +01:00
|
|
|
f
|
2012-02-07 11:16:44 +01:00
|
|
|
} catch {
|
|
|
|
|
case e =>
|
|
|
|
|
IO.delete(file)
|
|
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
file
|
2011-12-19 08:43:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def uniqueSubdirectoryFor(uri: URI, in: File) = new File(in, Hash.halfHashString(uri.toASCIIString))
|
|
|
|
|
}
|