diff --git a/src/main/kotlin/com/beust/kobalt/BuildScript.kt b/src/main/kotlin/com/beust/kobalt/BuildScript.kt index b40718de..585dc995 100644 --- a/src/main/kotlin/com/beust/kobalt/BuildScript.kt +++ b/src/main/kotlin/com/beust/kobalt/BuildScript.kt @@ -29,10 +29,30 @@ fun plugins(vararg dependencies : String) { } } +data class HostInfo(val url: String, var keyUsername: String? = null, var keyPassword: String? = null) { + fun hasAuth() : Boolean { + return (! keyUsername.isNullOrBlank()) && (! keyPassword.isNullOrBlank()) + } +} + @Directive fun repos(vararg repos : String) { + repos.forEach { Kobalt.addRepo(HostInfo(it)) } +} + +@Directive +fun repos(vararg repos : HostInfo) { repos.forEach { Kobalt.addRepo(it) } } +class HostConfig(var keyUsername: String? = null, var keyPassword: String? = null) + +@Directive +fun authRepo(url: String, init: HostConfig.() -> Unit) : HostInfo { + val r = HostConfig() + r.init() + return HostInfo(url, r.keyUsername, r.keyPassword) +} + @Directive fun glob(g: String) : IFileSpec.Glob = IFileSpec.Glob(g) diff --git a/src/main/kotlin/com/beust/kobalt/Main.kt b/src/main/kotlin/com/beust/kobalt/Main.kt index c8e974b3..a446cc99 100644 --- a/src/main/kotlin/com/beust/kobalt/Main.kt +++ b/src/main/kotlin/com/beust/kobalt/Main.kt @@ -67,7 +67,7 @@ private class Main @Inject constructor( private fun addReposFromContributors(project: Project?) = pluginInfo.repoContributors.forEach { it.reposFor(project).forEach { - Kobalt.addRepo(it.toString()) + Kobalt.addRepo(it) } } diff --git a/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt b/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt index ad06d139..2b40cf75 100644 --- a/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt +++ b/src/main/kotlin/com/beust/kobalt/ResolveDependency.kt @@ -1,7 +1,9 @@ package com.beust.kobalt -import com.beust.kobalt.maven.* import com.beust.kobalt.api.IClasspathDependency +import com.beust.kobalt.maven.MavenId +import com.beust.kobalt.maven.RepoFinder +import com.beust.kobalt.maven.SimpleDep import com.beust.kobalt.maven.dependency.MavenDependency import com.beust.kobalt.misc.Node import com.beust.kobalt.misc.log @@ -29,7 +31,7 @@ class ResolveDependency @Inject constructor(val repoFinder: RepoFinder) { val repoResult = repoFinder.findCorrectRepo(id) val simpleDep = SimpleDep(MavenId(id)) - val url = repoResult.repoUrl + simpleDep.toJarFile(repoResult) + val url = repoResult.repoHostInfo.url + simpleDep.toJarFile(repoResult) AsciiArt.logBox(listOf(id, url).map { " $it" }, {s -> println(s) }) display(root.children) diff --git a/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt b/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt index 693a6777..6057e0ed 100644 --- a/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt +++ b/src/main/kotlin/com/beust/kobalt/api/IRepoContributor.kt @@ -1,6 +1,6 @@ package com.beust.kobalt.api -import java.net.URI +import com.beust.kobalt.HostInfo /** * Plugins that add their own repos. @@ -11,6 +11,6 @@ interface IRepoContributor : IContributor { * before the build file gets parsed (so we don't have any projects yet) and after the * build file has been parsed (then it gets called once for each project discovered). */ - fun reposFor(project: Project?) : List + fun reposFor(project: Project?) : List } diff --git a/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt b/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt index b09567fc..14089070 100644 --- a/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt +++ b/src/main/kotlin/com/beust/kobalt/api/Kobalt.kt @@ -2,6 +2,7 @@ package com.beust.kobalt.api import com.beust.kobalt.Args import com.beust.kobalt.Plugins +import com.beust.kobalt.HostInfo import com.beust.kobalt.misc.MainModule import com.google.inject.Guice import com.google.inject.Injector @@ -23,9 +24,11 @@ public class Kobalt { "https://jcenter.bintray.com/" ) - val repos = HashSet(DEFAULT_REPOS) + val repos = HashSet(DEFAULT_REPOS.map { HostInfo(it) }) - fun addRepo(repo: String) = repos.add(if (repo.endsWith("/")) repo else repo + "/") + fun addRepo(repo: HostInfo) = repos.add( + if (repo.url.endsWith("/")) repo + else repo.copy(url = (repo.url + "/"))) private val PROPERTY_KOBALT_VERSION = "kobalt.version" private val KOBALT_PROPERTIES = "kobalt.properties" diff --git a/src/main/kotlin/com/beust/kobalt/internal/build/ParsedBuildFile.kt b/src/main/kotlin/com/beust/kobalt/internal/build/ParsedBuildFile.kt index 7e6df00d..4182981d 100644 --- a/src/main/kotlin/com/beust/kobalt/internal/build/ParsedBuildFile.kt +++ b/src/main/kotlin/com/beust/kobalt/internal/build/ParsedBuildFile.kt @@ -3,9 +3,6 @@ package com.beust.kobalt.internal.build import com.beust.kobalt.Plugins import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project -import com.beust.kobalt.internal.build.VersionFile -import com.beust.kobalt.internal.build.BuildFile -import com.beust.kobalt.internal.build.BuildScriptUtil import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.countChar @@ -41,17 +38,20 @@ class ParsedBuildFile(val buildFile: BuildFile, val context: KobaltContext, val private fun parseBuildFile() { var parenCount = 0 + var current: ArrayList? = null buildFile.path.toFile().forEachLine(Charset.defaultCharset()) { line -> - var current: ArrayList? = null var index = line.indexOf("plugins(") - if (index >= 0) { - current = pluginList - } else { - index = line.indexOf("repos(") + if (current == null) { if (index >= 0) { - current = repos + current = pluginList + } else { + index = line.indexOf("repos(") + if (index >= 0) { + current = repos + } } } + if (parenCount > 0 || current != null) { if (index == -1) index = 0 with(line.substring(index)) { @@ -63,6 +63,10 @@ class ParsedBuildFile(val buildFile: BuildFile, val context: KobaltContext, val } } + if (parenCount == 0) { + current = null + } + /** * If the current line matches one of the profile, turns the declaration into * val profile = true, otherwise return the same line diff --git a/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt b/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt index 4f121b40..3f0585a6 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/ArtifactFetcher.kt @@ -1,5 +1,6 @@ package com.beust.kobalt.maven +import com.beust.kobalt.HostInfo import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.log import com.beust.kobalt.misc.warn @@ -19,49 +20,50 @@ import javax.inject.Singleton @Singleton class DownloadManager @Inject constructor(val factory: ArtifactFetcher.IFactory) { - class Key(val url: String, val fileName: String, val executor: ExecutorService) { + class Key(val hostInfo: HostInfo, val fileName: String, val executor: ExecutorService) { override fun equals(other: Any?): Boolean { - return (other as Key).url == url + return (other as Key).hostInfo.url == hostInfo.url } override fun hashCode(): Int { - return url.hashCode() + return hostInfo.url.hashCode() } } private val CACHE : LoadingCache> = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: Key): Future { - return key.executor.submit(factory.create(key.url, key.fileName)) + return key.executor.submit(factory.create(key.hostInfo, key.fileName)) } }) - fun download(url: String, fileName: String, executor: ExecutorService) - : Future = CACHE.get(Key(url, fileName, executor)) + fun download(hostInfo: HostInfo, fileName: String, executor: ExecutorService) + : Future = CACHE.get(Key(hostInfo, fileName, executor)) } /** * Fetches an artifact (a file in a Maven repo, .jar, -javadoc.jar, ...) to the given local file. */ -class ArtifactFetcher @Inject constructor(@Assisted("url") val url: String, +class ArtifactFetcher @Inject constructor(@Assisted("hostInfo") val hostInfo: HostInfo, @Assisted("fileName") val fileName: String, - val files: KFiles, val urlFactory: Kurl.IFactory) : Callable { + val files: KFiles) : Callable { interface IFactory { - fun create(@Assisted("url") url: String, @Assisted("fileName") fileName: String) : ArtifactFetcher + fun create(@Assisted("hostInfo") hostInfo: HostInfo, @Assisted("fileName") fileName: String) : ArtifactFetcher } override fun call() : File { - val k = urlFactory.create(url + ".md5") + val k = Kurl(hostInfo.copy(url = hostInfo.url + ".md5")) val remoteMd5 = if (k.exists) k.string.trim(' ', '\t', '\n').substring(0, 32) else null val tmpFile = Paths.get(fileName + ".tmp") val file = Paths.get(fileName) + val url = hostInfo.url if (! Files.exists(file)) { with(tmpFile.toFile()) { parentFile.mkdirs() - urlFactory.create(url).toFile(this) + Kurl(hostInfo).toFile(this) } log(2, "Done downloading, renaming $tmpFile to $file") Files.move(tmpFile, file, StandardCopyOption.REPLACE_EXISTING) diff --git a/src/main/kotlin/com/beust/kobalt/maven/Kurl.kt b/src/main/kotlin/com/beust/kobalt/maven/Kurl.kt index 0d13b8b5..c91061a9 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/Kurl.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/Kurl.kt @@ -1,30 +1,45 @@ package com.beust.kobalt.maven +import com.beust.kobalt.HostInfo import com.beust.kobalt.maven.dependency.FileDependency -import com.google.inject.assistedinject.Assisted import java.io.* import java.net.HttpURLConnection import java.net.URL import java.net.URLConnection -import javax.inject.Inject +import java.util.* /** * Abstracts a URL so that it works transparently on either http:// or file:// */ -class Kurl @Inject constructor(@Assisted val url: String) { - val connection : URLConnection by lazy { - URL(url).openConnection() - } +class Kurl(val hostInfo: HostInfo) { +// constructor(url: String) : this(HostInfo(url)) - interface IFactory { - fun create(url: String) : Kurl + val connection : URLConnection + get() { + val result = URL(hostInfo.url).openConnection() + if (hostInfo.hasAuth()) { + val userPass = hostInfo.keyUsername + ":" + hostInfo.keyPassword + val basicAuth = "Basic " + String(Base64.getEncoder().encode(userPass.toByteArray())) + result.setRequestProperty("Authorization", basicAuth) + } + return result + } + + val inputStream : InputStream by lazy { + connection.inputStream } val exists : Boolean get() { + if (hostInfo.url.contains("localhost")) { + println("DONOTCOMMIT") + } + val url = hostInfo.url val result = if (connection is HttpURLConnection) { - (connection as HttpURLConnection).responseCode == 200 + val responseCode = (connection as HttpURLConnection).responseCode + checkResponseCode(responseCode) + responseCode == 200 } else if (url.startsWith(FileDependency.PREFIX_FILE)) { val fileName = url.substring(FileDependency.PREFIX_FILE.length) File(fileName).exists() @@ -34,11 +49,22 @@ class Kurl @Inject constructor(@Assisted val url: String) { return result } + private fun checkResponseCode(responseCode: Int) { + if (responseCode == 401) { + if (hostInfo.hasAuth()) { + error("Bad credentials supplied for ${hostInfo.url}") + } else { + error("This repo requires authentication: ${hostInfo.url}") + } + } + + } + /** The Kotlin compiler is about 17M and downloading it with the default buffer size takes forever */ private val estimatedSize: Int - get() = if (url.contains("kotlin-compiler")) 18000000 else 1000000 + get() = if (hostInfo.url.contains("kotlin-compiler")) 18000000 else 1000000 - fun toOutputStream(os: OutputStream, progress: (Long) -> Unit) = copy(connection.inputStream, os, progress) + fun toOutputStream(os: OutputStream, progress: (Long) -> Unit) = copy(inputStream, os, progress) fun toFile(file: File, progress: (Long) -> Unit = {}) = FileOutputStream(file).use { toOutputStream(it, progress) @@ -71,14 +97,12 @@ class Kurl @Inject constructor(@Assisted val url: String) { val string: String get() { val sb = StringBuilder() - connection.inputStream.use { inputStream -> - val reader = BufferedReader(InputStreamReader(inputStream)) + val reader = BufferedReader(InputStreamReader(inputStream)) - var line: String? = reader.readLine() - while (line != null) { - sb.append(line).append('\n') - line = reader.readLine() - } + var line: String? = reader.readLine() + while (line != null) { + sb.append(line).append('\n') + line = reader.readLine() } return sb.toString() diff --git a/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt index ffcb725b..82b506ed 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/RepoFinder.kt @@ -1,5 +1,6 @@ package com.beust.kobalt.maven +import com.beust.kobalt.HostInfo import com.beust.kobalt.api.Kobalt import com.beust.kobalt.misc.KobaltExecutors import com.beust.kobalt.misc.Strings @@ -8,6 +9,7 @@ import com.beust.kobalt.misc.warn import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache +import kotlinx.dom.parseXml import java.io.File import java.util.concurrent.Callable import java.util.concurrent.ExecutorCompletionService @@ -15,19 +17,18 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathFactory -import kotlinx.dom.parseXml /** * Find the repo that contains the given dependency among a list of repos. Searches are performed in parallel and * cached so we never make a network call for the same dependency more than once. */ -public class RepoFinder @Inject constructor(val urlFactory: Kurl.IFactory, val executors: KobaltExecutors) { +public class RepoFinder @Inject constructor(val executors: KobaltExecutors) { public fun findCorrectRepo(id: String): RepoResult { return FOUND_REPOS.get(id) } - data class RepoResult(val repoUrl: String, val found: Boolean, val version: String, val hasJar: Boolean = true, - val snapshotVersion: String = "") + data class RepoResult(val repoHostInfo: HostInfo, val found: Boolean, val version: String, + val hasJar: Boolean = true, val snapshotVersion: String = "") private val FOUND_REPOS: LoadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader() { @@ -50,14 +51,14 @@ public class RepoFinder @Inject constructor(val urlFactory: Kurl.IFactory, val e val result = cs.take().get(2000, TimeUnit.MILLISECONDS) log(2, "Result for repo #$i: $result") if (result.found) { - log(2, "Located $id in ${result.repoUrl}") + log(2, "Located $id in ${result.repoHostInfo.url}") return result } } catch(ex: Exception) { warn("Error: $ex") } } - return RepoResult("", false, id) + return RepoResult(HostInfo(""), false, id) } finally { executor.shutdownNow() } @@ -66,8 +67,9 @@ public class RepoFinder @Inject constructor(val urlFactory: Kurl.IFactory, val e /** * Execute a single HTTP request to one repo. */ - inner class RepoFinderCallable(val id: String, val repoUrl: String) : Callable { + inner class RepoFinderCallable(val id: String, val repo: HostInfo) : Callable { override fun call(): RepoResult { + val repoUrl = repo.url log(2, "Checking $repoUrl for $id") val mavenId = MavenId(id) @@ -78,9 +80,9 @@ public class RepoFinder @Inject constructor(val urlFactory: Kurl.IFactory, val e val ud = UnversionedDep(groupId, artifactId) val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false), repoUrl) if (foundVersion != null) { - return RepoResult(repoUrl, true, foundVersion) + return RepoResult(repo, true, foundVersion) } else { - return RepoResult(repoUrl, false, "") + return RepoResult(repo, false, "") } } else { val version = mavenId.version @@ -88,26 +90,27 @@ public class RepoFinder @Inject constructor(val urlFactory: Kurl.IFactory, val e val dep = SimpleDep(mavenId) val snapshotVersion = findSnapshotVersion(dep.toMetadataXmlPath(false), repoUrl) if (snapshotVersion != null) { - return RepoResult(repoUrl, true, version, true /* hasJar, potential bug here */, + return RepoResult(repo, true, version, true /* hasJar, potential bug here */, snapshotVersion) } else { - return RepoResult(repoUrl, false, "") + return RepoResult(repo, false, "") } } else { val dep = SimpleDep(mavenId) // Try to find the jar file - val urlJar = repoUrl + dep.toJarFile(dep.version) - val hasJar = urlFactory.create(urlJar).exists + val urlJar = repo.copy(url = repo.url + dep.toJarFile(dep.version)) + val hasJar = Kurl(urlJar).exists val found = if (! hasJar) { // No jar, try to find the directory - val url = repoUrl + File(dep.toJarFile(dep.version)).parentFile.path.replace("\\", "/") - urlFactory.create(url).exists + val url = repo.copy(url = repoUrl + + File(dep.toJarFile(dep.version)).parentFile.path.replace("\\", "/")) + Kurl(url).exists } else { true } log(2, "Result for $repoUrl for $id: $found") - return RepoResult(repoUrl, found, dep.version, hasJar) + return RepoResult(repo, found, dep.version, hasJar) } } } diff --git a/src/main/kotlin/com/beust/kobalt/maven/dependency/MavenDependency.kt b/src/main/kotlin/com/beust/kobalt/maven/dependency/MavenDependency.kt index 62c778e4..5d932162 100644 --- a/src/main/kotlin/com/beust/kobalt/maven/dependency/MavenDependency.kt +++ b/src/main/kotlin/com/beust/kobalt/maven/dependency/MavenDependency.kt @@ -1,5 +1,6 @@ package com.beust.kobalt.maven.dependency +import com.beust.kobalt.HostInfo import com.beust.kobalt.KobaltException import com.beust.kobalt.api.IClasspathDependency import com.beust.kobalt.api.Kobalt @@ -36,12 +37,13 @@ public class MavenDependency @Inject constructor(mavenId: MavenId, if (repoResult.found) { jarFile = if (repoResult.hasJar) { - downloadManager.download(repoResult.repoUrl + toJarFile(repoResult), jar.absolutePath, executor) + downloadManager.download(HostInfo(url = repoResult.repoHostInfo.url + toJarFile(repoResult)), + jar.absolutePath, executor) } else { CompletedFuture(File("nonexistentFile")) // will be filtered out } - pomFile = downloadManager.download(repoResult.repoUrl + toPomFile(repoResult), pom.absolutePath, - executor) + pomFile = downloadManager.download(HostInfo(url = repoResult.repoHostInfo.url + toPomFile(repoResult)), + pom.absolutePath, executor) } else { throw KobaltException("Couldn't resolve ${mavenId.toId}") } diff --git a/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt b/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt index 5c1fbf8e..02bdea1e 100644 --- a/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt +++ b/src/main/kotlin/com/beust/kobalt/misc/MainModule.kt @@ -3,7 +3,10 @@ package com.beust.kobalt.misc import com.beust.kobalt.Args import com.beust.kobalt.internal.PluginInfo import com.beust.kobalt.internal.build.BuildFileCompiler -import com.beust.kobalt.maven.* +import com.beust.kobalt.maven.ArtifactFetcher +import com.beust.kobalt.maven.LocalRepo +import com.beust.kobalt.maven.Pom +import com.beust.kobalt.maven.PomGenerator import com.beust.kobalt.plugin.publish.JCenterApi import com.google.inject.* import com.google.inject.assistedinject.FactoryModuleBuilder @@ -35,7 +38,6 @@ public open class MainModule(val args: Args) : AbstractModule() { JCenterApi.IFactory::class.java, Pom.IFactory::class.java, BuildFileCompiler.IFactory::class.java, - Kurl.IFactory::class.java, ArtifactFetcher.IFactory::class.java) .forEach { install(builder.build(it))