K/N: Move classes and functions we don't want to expose
These classes we don't want to expose to Gradle DSL users from "kotlin-native-utils" to "kotlin-native-serializer" module. Reason: "kotlin-gradle-plugin-api" depends on "kotlin-native-utils", while "kotlin-native-serializer" is used only inside of IDEA plugin.
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a4daf9f1ff
commit
9b92d38917
@@ -13,7 +13,7 @@ dependencies {
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" {}
|
||||
"test" { none() }
|
||||
}
|
||||
|
||||
standardPublicJars()
|
||||
|
||||
@@ -20,11 +20,11 @@ interface KonanVersion : Serializable {
|
||||
}
|
||||
|
||||
data class KonanVersionImpl(
|
||||
override val meta: MetaVersion = MetaVersion.DEV,
|
||||
override val major: Int,
|
||||
override val minor: Int,
|
||||
override val maintenance: Int,
|
||||
override val build: Int = -1
|
||||
override val meta: MetaVersion = MetaVersion.DEV,
|
||||
override val major: Int,
|
||||
override val minor: Int,
|
||||
override val maintenance: Int,
|
||||
override val build: Int = -1
|
||||
) : KonanVersion {
|
||||
|
||||
override fun toString(showMeta: Boolean, showBuild: Boolean) = buildString {
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.file
|
||||
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
data class File constructor(internal val javaPath: Path) {
|
||||
constructor(parent: Path, child: String) : this(parent.resolve(child))
|
||||
constructor(parent: File, child: String) : this(parent.javaPath.resolve(child))
|
||||
constructor(path: String) : this(Paths.get(path))
|
||||
constructor(parent: String, child: String) : this(Paths.get(parent, child))
|
||||
|
||||
val path: String
|
||||
get() = javaPath.toString()
|
||||
val absolutePath: String
|
||||
get() = javaPath.toAbsolutePath().toString()
|
||||
val absoluteFile: File
|
||||
get() = File(absolutePath)
|
||||
val name: String
|
||||
get() = javaPath.fileName.toString()
|
||||
val extension: String
|
||||
get() = name.substringAfterLast('.', "")
|
||||
val parent: String
|
||||
get() = javaPath.parent.toString()
|
||||
val parentFile: File
|
||||
get() = File(javaPath.parent)
|
||||
|
||||
val exists
|
||||
get() = Files.exists(javaPath)
|
||||
val isDirectory
|
||||
get() = Files.isDirectory(javaPath)
|
||||
val isFile
|
||||
get() = Files.isRegularFile(javaPath)
|
||||
val isAbsolute
|
||||
get() = javaPath.isAbsolute()
|
||||
val listFiles: List<File>
|
||||
get() = Files.newDirectoryStream(javaPath).use { stream -> stream.map { File(it) } }
|
||||
val listFilesOrEmpty: List<File>
|
||||
get() = if (exists) listFiles else emptyList()
|
||||
|
||||
fun child(name: String) = File(this, name)
|
||||
|
||||
fun copyTo(destination: File) {
|
||||
Files.copy(javaPath, destination.javaPath, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
fun recursiveCopyTo(destination: File) {
|
||||
val sourcePath = javaPath
|
||||
val destPath = destination.javaPath
|
||||
sourcePath.recursiveCopyTo(destPath)
|
||||
}
|
||||
|
||||
fun mkdirs() = Files.createDirectories(javaPath)
|
||||
fun delete() = Files.deleteIfExists(javaPath)
|
||||
fun deleteRecursively() = postorder { Files.delete(it) }
|
||||
fun deleteOnExitRecursively() = preorder { File(it).deleteOnExit() }
|
||||
|
||||
fun preorder(task: (Path) -> Unit) {
|
||||
if (!this.exists) return
|
||||
|
||||
Files.walkFileTree(javaPath, object : SimpleFileVisitor<Path>() {
|
||||
override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult {
|
||||
task(file!!)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult {
|
||||
task(dir!!)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun postorder(task: (Path) -> Unit) {
|
||||
if (!this.exists) return
|
||||
|
||||
Files.walkFileTree(javaPath, object : SimpleFileVisitor<Path>() {
|
||||
override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult {
|
||||
task(file!!)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun postVisitDirectory(dir: Path?, exc: java.io.IOException?): FileVisitResult {
|
||||
task(dir!!)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun deleteOnExit(): File {
|
||||
// Works only on the default file system,
|
||||
// but that's okay for now.
|
||||
javaPath.toFile().deleteOnExit()
|
||||
return this // Allow streaming.
|
||||
}
|
||||
|
||||
fun readBytes() = Files.readAllBytes(javaPath)
|
||||
fun writeBytes(bytes: ByteArray) = Files.write(javaPath, bytes)
|
||||
fun appendBytes(bytes: ByteArray) = Files.write(javaPath, bytes, StandardOpenOption.APPEND)
|
||||
|
||||
fun writeLines(lines: Iterable<String>) {
|
||||
Files.write(javaPath, lines)
|
||||
}
|
||||
|
||||
fun writeText(text: String): Unit = writeLines(listOf(text))
|
||||
|
||||
fun forEachLine(action: (String) -> Unit) {
|
||||
Files.lines(javaPath).use { lines ->
|
||||
lines.forEach { action(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun createAsSymlink(target: String) {
|
||||
val targetPath = Paths.get(target)
|
||||
if (Files.isSymbolicLink(this.javaPath) && Files.readSymbolicLink(javaPath) == targetPath) {
|
||||
return
|
||||
}
|
||||
Files.createSymbolicLink(this.javaPath, targetPath)
|
||||
}
|
||||
|
||||
override fun toString() = path
|
||||
|
||||
// TODO: Consider removeing these after konanazing java.util.Properties.
|
||||
fun bufferedReader() = Files.newBufferedReader(javaPath)
|
||||
|
||||
fun outputStream() = Files.newOutputStream(javaPath)
|
||||
fun printWriter() = javaPath.toFile().printWriter()
|
||||
|
||||
companion object {
|
||||
val userDir
|
||||
get() = File(System.getProperty("user.dir"))
|
||||
|
||||
val userHome
|
||||
get() = File(System.getProperty("user.home"))
|
||||
|
||||
val javaHome
|
||||
get() = File(System.getProperty("java.home"))
|
||||
val pathSeparator = java.io.File.pathSeparator
|
||||
}
|
||||
|
||||
fun readStrings() = mutableListOf<String>().also { list -> forEachLine { list.add(it) } }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val otherFile = other as? File ?: return false
|
||||
return otherFile.javaPath.toAbsolutePath() == javaPath.toAbsolutePath()
|
||||
}
|
||||
|
||||
override fun hashCode() = javaPath.toAbsolutePath().hashCode()
|
||||
}
|
||||
|
||||
fun String.File(): File = File(this)
|
||||
fun Path.File(): File = File(this)
|
||||
|
||||
fun createTempFile(name: String, suffix: String? = null) = Files.createTempFile(name, suffix).File()
|
||||
fun createTempDir(name: String): File = Files.createTempDirectory(name).File()
|
||||
|
||||
fun Path.recursiveCopyTo(destPath: Path) {
|
||||
val sourcePath = this
|
||||
Files.walk(sourcePath).forEach next@{ oldPath ->
|
||||
|
||||
val relative = sourcePath.relativize(oldPath)
|
||||
val destFs = destPath.getFileSystem()
|
||||
// We are copying files between file systems,
|
||||
// so pass the relative path through the String.
|
||||
val newPath = destFs.getPath(destPath.toString(), relative.toString())
|
||||
|
||||
// File systems don't allow replacing an existing root.
|
||||
if (newPath == newPath.getRoot()) return@next
|
||||
if (Files.isDirectory(newPath)) {
|
||||
Files.createDirectories(newPath)
|
||||
} else {
|
||||
Files.copy(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bufferedReader(errorStream: InputStream?) = BufferedReader(InputStreamReader(errorStream))
|
||||
|
||||
// stdlib `use` function adapted for AutoCloseable.
|
||||
inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R {
|
||||
var closed = false
|
||||
try {
|
||||
return block(this)
|
||||
} catch (e: Exception) {
|
||||
closed = true
|
||||
try {
|
||||
this?.close()
|
||||
} catch (closeException: Exception) {
|
||||
}
|
||||
throw e
|
||||
} finally {
|
||||
if (!closed) {
|
||||
this?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.file
|
||||
|
||||
import java.net.URI
|
||||
import java.nio.file.*
|
||||
|
||||
private val File.zipUri: URI
|
||||
get() = URI.create("jar:${this.toPath().toUri()}")
|
||||
|
||||
fun File.zipFileSystem(mutable: Boolean = false): FileSystem {
|
||||
val zipUri = this.zipUri
|
||||
val attributes = hashMapOf("create" to mutable.toString())
|
||||
return try {
|
||||
FileSystems.newFileSystem(zipUri, attributes, null)
|
||||
} catch (e: FileSystemAlreadyExistsException) {
|
||||
FileSystems.getFileSystem(zipUri)
|
||||
}
|
||||
}
|
||||
|
||||
fun FileSystem.file(file: File) = File(this.getPath(file.path))
|
||||
|
||||
fun FileSystem.file(path: String) = File(this.getPath(path))
|
||||
|
||||
private fun File.toPath() = Paths.get(this.path)
|
||||
|
||||
fun File.zipDirAs(unixFile: File) {
|
||||
unixFile.withMutableZipFileSystem {
|
||||
this.recursiveCopyTo(it.file("/"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Path.unzipTo(directory: Path) {
|
||||
val zipUri = URI.create("jar:" + this.toUri())
|
||||
FileSystems.newFileSystem(zipUri, emptyMap<String, Any?>(), null).use { zipfs ->
|
||||
val zipPath = zipfs.getPath("/")
|
||||
zipPath.recursiveCopyTo(directory)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> File.withZipFileSystem(mutable: Boolean = false, action: (FileSystem) -> T): T {
|
||||
val zipFileSystem = this.zipFileSystem(mutable)
|
||||
return try {
|
||||
action(zipFileSystem)
|
||||
} finally {
|
||||
zipFileSystem.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> File.withZipFileSystem(action: (FileSystem) -> T): T = this.withZipFileSystem(false, action)
|
||||
|
||||
fun <T> File.withMutableZipFileSystem(action: (FileSystem) -> T): T = this.withZipFileSystem(true, action)
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.library
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
const val KLIB_FILE_EXTENSION = "klib"
|
||||
const val KLIB_FILE_EXTENSION_WITH_DOT = ".$KLIB_FILE_EXTENSION"
|
||||
|
||||
const val KONAN_STDLIB_NAME = "stdlib"
|
||||
|
||||
val KONAN_COMMON_LIBS_PATH: Path
|
||||
get() = Paths.get("klib", "common")
|
||||
|
||||
val KONAN_ALL_PLATFORM_LIBS_PATH: Path
|
||||
get() = Paths.get("klib", "platform")
|
||||
|
||||
fun konanCommonLibraryPath(libraryName: String): Path = KONAN_COMMON_LIBS_PATH.resolve(libraryName)
|
||||
|
||||
fun konanSpecificPlatformLibrariesPath(platform: String): Path = KONAN_ALL_PLATFORM_LIBS_PATH.resolve(platform)
|
||||
|
||||
fun konanPlatformLibraryPath(platform: String, libraryName: String): Path = konanSpecificPlatformLibrariesPath(platform).resolve(libraryName)
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.library
|
||||
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
|
||||
interface SearchPathResolver {
|
||||
val searchRoots: List<File>
|
||||
fun resolve(givenPath: String): File
|
||||
fun defaultLinks(noStdLib: Boolean, noDefaultLibs: Boolean): List<File>
|
||||
}
|
||||
|
||||
interface SearchPathResolverWithTarget : SearchPathResolver {
|
||||
val target: KonanTarget
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.properties
|
||||
|
||||
import org.jetbrains.kotlin.konan.file.*
|
||||
|
||||
typealias Properties = java.util.Properties
|
||||
|
||||
fun File.loadProperties(): Properties {
|
||||
val properties = java.util.Properties()
|
||||
this.bufferedReader().use { reader ->
|
||||
properties.load(reader)
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
fun loadProperties(path: String): Properties = File(path).loadProperties()
|
||||
|
||||
fun File.saveProperties(properties: Properties) {
|
||||
this.outputStream().use {
|
||||
properties.store(it, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun Properties.saveToFile(file: File) = file.saveProperties(this)
|
||||
|
||||
fun Properties.propertyString(key: String, suffix: String? = null): String? = getProperty(key.suffix(suffix)) ?: this.getProperty(key)
|
||||
|
||||
/**
|
||||
* TODO: this method working with suffixes should be replaced with
|
||||
* functionality borrowed from def file parser and unified for interop tool
|
||||
* and kotlin compiler.
|
||||
*/
|
||||
fun Properties.propertyList(key: String, suffix: String? = null): List<String> {
|
||||
val value = this.getProperty(key.suffix(suffix)) ?: this.getProperty(key)
|
||||
if (value?.isBlank() == true) return emptyList()
|
||||
|
||||
return value?.split(Regex("\\s+")) ?: emptyList()
|
||||
}
|
||||
|
||||
fun Properties.hasProperty(key: String, suffix: String? = null): Boolean = this.getProperty(key.suffix(suffix)) != null
|
||||
|
||||
fun String.suffix(suf: String?): String = if (suf == null) this else "${this}.$suf"
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.util
|
||||
|
||||
import java.io.*
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLConnection
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.concurrent.*
|
||||
|
||||
internal typealias ProgressCallback = (url: String, currentBytes: Long, totalBytes: Long) -> Unit
|
||||
|
||||
internal class DependencyDownloader(
|
||||
var maxAttempts: Int = DEFAULT_MAX_ATTEMPTS,
|
||||
var attemptIntervalMs: Long = DEFAULT_ATTEMPT_INTERVAL_MS,
|
||||
customProgressCallback: ProgressCallback? = null
|
||||
) {
|
||||
|
||||
private val progressCallback = customProgressCallback ?: { url, currentBytes, totalBytes ->
|
||||
print("\rDownloading dependency: $url (${currentBytes.humanReadable}/${totalBytes.humanReadable}). ")
|
||||
}
|
||||
|
||||
val executor = ExecutorCompletionService<Unit>(Executors.newSingleThreadExecutor { r ->
|
||||
val thread = Thread(r)
|
||||
thread.name = "konan-dependency-downloader"
|
||||
thread.isDaemon = true
|
||||
|
||||
thread
|
||||
})
|
||||
|
||||
enum class ReplacingMode {
|
||||
/** Re-download the file and replace the existing one. */
|
||||
REPLACE,
|
||||
/** Throw FileAlreadyExistsException */
|
||||
THROW,
|
||||
/** Don't download the file and return the existing one*/
|
||||
RETURN_EXISTING
|
||||
}
|
||||
|
||||
class DownloadingProgress(@Volatile var currentBytes: Long) {
|
||||
fun update(readBytes: Int) {
|
||||
currentBytes += readBytes
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.checkHTTPResponse(expected: Int, originalUrl: URL = url) {
|
||||
if (responseCode != expected) {
|
||||
throw DependencyDownloaderHTTPResponseException(originalUrl, responseCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.checkHTTPResponse(originalUrl: URL, predicate: (Int) -> Boolean) {
|
||||
if (!predicate(responseCode)) {
|
||||
throw DependencyDownloaderHTTPResponseException(originalUrl, responseCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doDownload(
|
||||
originalUrl: URL,
|
||||
connection: URLConnection,
|
||||
tmpFile: File,
|
||||
currentBytes: Long,
|
||||
totalBytes: Long,
|
||||
append: Boolean
|
||||
) {
|
||||
val progress = DownloadingProgress(currentBytes)
|
||||
|
||||
// TODO: Implement multi-thread downloading.
|
||||
executor.submit {
|
||||
connection.getInputStream().use { from ->
|
||||
FileOutputStream(tmpFile, append).use { to ->
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
var read = from.read(buffer)
|
||||
while (read != -1) {
|
||||
if (Thread.interrupted()) {
|
||||
throw InterruptedException()
|
||||
}
|
||||
to.write(buffer, 0, read)
|
||||
progress.update(read)
|
||||
read = from.read(buffer)
|
||||
}
|
||||
if (progress.currentBytes != totalBytes) {
|
||||
throw EOFException("The stream closed before end of downloading.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result: Future<Unit>?
|
||||
do {
|
||||
progressCallback(originalUrl.toString(), progress.currentBytes, totalBytes)
|
||||
result = executor.poll(1, TimeUnit.SECONDS)
|
||||
} while (result == null)
|
||||
progressCallback(originalUrl.toString(), progress.currentBytes, totalBytes)
|
||||
|
||||
try {
|
||||
result.get()
|
||||
} catch (e: ExecutionException) {
|
||||
throw e.cause ?: e
|
||||
}
|
||||
}
|
||||
|
||||
private fun resumeDownload(originalUrl: URL, originalConnection: HttpURLConnection, tmpFile: File) {
|
||||
originalConnection.connect()
|
||||
val totalBytes = originalConnection.contentLengthLong
|
||||
val currentBytes = tmpFile.length()
|
||||
if (currentBytes >= totalBytes || originalConnection.getHeaderField("Accept-Ranges") != "bytes") {
|
||||
// The temporary file is bigger then expected or the server doesn't support resuming downloading.
|
||||
// Download the file from scratch.
|
||||
doDownload(originalUrl, originalConnection, tmpFile, 0, totalBytes, false)
|
||||
} else {
|
||||
originalConnection.disconnect()
|
||||
val rangeConnection = originalUrl.openConnection() as HttpURLConnection
|
||||
rangeConnection.setRequestProperty("range", "bytes=$currentBytes-")
|
||||
rangeConnection.connect()
|
||||
rangeConnection.checkHTTPResponse(originalUrl) {
|
||||
it == HttpURLConnection.HTTP_PARTIAL || it == HttpURLConnection.HTTP_OK
|
||||
}
|
||||
doDownload(originalUrl, rangeConnection, tmpFile, currentBytes, totalBytes, true)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs an attempt to download a specified file into the specified location */
|
||||
private fun tryDownload(url: URL, tmpFile: File) {
|
||||
val connection = url.openConnection()
|
||||
|
||||
(connection as? HttpURLConnection)?.checkHTTPResponse(HttpURLConnection.HTTP_OK, url)
|
||||
|
||||
if (connection is HttpURLConnection && tmpFile.exists()) {
|
||||
resumeDownload(url, connection, tmpFile)
|
||||
} else {
|
||||
connection.connect()
|
||||
val totalBytes = connection.contentLengthLong
|
||||
doDownload(url, connection, tmpFile, 0, totalBytes, false)
|
||||
}
|
||||
}
|
||||
|
||||
/** Downloads a file from [source] url to [destination]. Returns [destination]. */
|
||||
fun download(
|
||||
source: URL,
|
||||
destination: File,
|
||||
replace: ReplacingMode = ReplacingMode.RETURN_EXISTING
|
||||
): File {
|
||||
|
||||
if (destination.exists()) {
|
||||
when (replace) {
|
||||
ReplacingMode.RETURN_EXISTING -> return destination
|
||||
ReplacingMode.THROW -> throw FileAlreadyExistsException(destination)
|
||||
ReplacingMode.REPLACE -> Unit // Just continue with downloading.
|
||||
}
|
||||
}
|
||||
val tmpFile = File("${destination.canonicalPath}.$TMP_SUFFIX")
|
||||
|
||||
check(!tmpFile.isDirectory) {
|
||||
"A temporary file is a directory: ${tmpFile.canonicalPath}. Remove it and try again."
|
||||
}
|
||||
check(!destination.isDirectory) {
|
||||
"The destination file is a directory: ${destination.canonicalPath}. Remove it and try again."
|
||||
}
|
||||
|
||||
var attempt = 1
|
||||
var waitTime = 0L
|
||||
while (true) {
|
||||
try {
|
||||
tryDownload(source, tmpFile)
|
||||
break
|
||||
} catch (e: DependencyDownloaderHTTPResponseException) {
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
if (attempt >= maxAttempts) {
|
||||
throw e
|
||||
}
|
||||
attempt++
|
||||
waitTime += attemptIntervalMs
|
||||
println(
|
||||
"Cannot download a dependency: $e\n" +
|
||||
"Waiting ${waitTime.toDouble() / 1000} sec and trying again (attempt: $attempt/$maxAttempts)."
|
||||
)
|
||||
// TODO: Wait better
|
||||
Thread.sleep(waitTime)
|
||||
}
|
||||
}
|
||||
|
||||
Files.move(tmpFile.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
println("Done.")
|
||||
return destination
|
||||
}
|
||||
|
||||
private val Long.humanReadable: String
|
||||
get() {
|
||||
if (this < 0) {
|
||||
return "-"
|
||||
}
|
||||
if (this < 1024) {
|
||||
return "$this bytes"
|
||||
}
|
||||
val exp = (Math.log(this.toDouble()) / Math.log(1024.0)).toInt()
|
||||
val prefix = "kMGTPE"[exp - 1]
|
||||
return "%.1f %siB".format(this / Math.pow(1024.0, exp.toDouble()), prefix)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_MAX_ATTEMPTS = 10
|
||||
const val DEFAULT_ATTEMPT_INTERVAL_MS = 3000L
|
||||
|
||||
const val TMP_SUFFIX = "part"
|
||||
}
|
||||
}
|
||||
|
||||
class DependencyDownloaderHTTPResponseException(val url: URL, val responseCode: Int) :
|
||||
IOException("Server returned HTTP response code: $responseCode for URL: $url")
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.util
|
||||
|
||||
import org.jetbrains.kotlin.konan.file.unzipTo
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
internal class DependencyExtractor {
|
||||
private val useZip = System.getProperty("os.name").startsWith("Windows")
|
||||
|
||||
internal val archiveExtension = if (useZip) {
|
||||
"zip"
|
||||
} else {
|
||||
"tar.gz"
|
||||
}
|
||||
|
||||
private fun extractTarGz(tarGz: File, targetDirectory: File) {
|
||||
val tarProcess = ProcessBuilder().apply {
|
||||
command("tar", "-xzf", tarGz.canonicalPath)
|
||||
directory(targetDirectory)
|
||||
inheritIO()
|
||||
}.start()
|
||||
val finished = tarProcess.waitFor(extractionTimeout, extractionTimeoutUntis)
|
||||
when {
|
||||
finished && tarProcess.exitValue() != 0 ->
|
||||
throw RuntimeException(
|
||||
"Cannot extract archive with dependency: ${tarGz.canonicalPath}.\n" +
|
||||
"Tar exit code: ${tarProcess.exitValue()}."
|
||||
)
|
||||
!finished -> {
|
||||
tarProcess.destroy()
|
||||
throw RuntimeException(
|
||||
"Cannot extract archive with dependency: ${tarGz.canonicalPath}.\n" +
|
||||
"Tar process hasn't finished in ${extractionTimeoutUntis.toSeconds(extractionTimeout)} sec."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extract(archive: File, targetDirectory: File) {
|
||||
if (useZip) {
|
||||
archive.toPath().unzipTo(targetDirectory.toPath())
|
||||
} else {
|
||||
extractTarGz(archive, targetDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val extractionTimeout = 3600L
|
||||
val extractionTimeoutUntis = TimeUnit.SECONDS
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.util
|
||||
|
||||
import org.jetbrains.kotlin.konan.file.use
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
import org.jetbrains.kotlin.konan.properties.propertyList
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.RandomAccessFile
|
||||
import java.net.InetAddress
|
||||
import java.net.URL
|
||||
import java.net.UnknownHostException
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
private val Properties.dependenciesUrl: String
|
||||
get() = getProperty("dependenciesUrl")
|
||||
?: throw IllegalStateException("No such property in konan.properties: dependenciesUrl")
|
||||
|
||||
private val Properties.airplaneMode: Boolean
|
||||
get() = getProperty("airplaneMode")?.toBoolean() ?: false
|
||||
|
||||
private val Properties.downloadingAttempts: Int
|
||||
get() = getProperty("downloadingAttempts")?.toInt()
|
||||
?: DependencyDownloader.DEFAULT_MAX_ATTEMPTS
|
||||
|
||||
private val Properties.downloadingAttemptIntervalMs: Long
|
||||
get() = getProperty("downloadingAttemptPauseMs")?.toLong()
|
||||
?: DependencyDownloader.DEFAULT_ATTEMPT_INTERVAL_MS
|
||||
|
||||
private fun Properties.findCandidates(dependencies: List<String>): Map<String, List<DependencySource>> {
|
||||
val dependencyProfiles = this.propertyList("dependencyProfiles")
|
||||
return dependencies.map { dependency ->
|
||||
dependency to dependencyProfiles.flatMap { profile ->
|
||||
val candidateSpecs = propertyList("$dependency.$profile")
|
||||
if (profile == "default" && candidateSpecs.isEmpty()) {
|
||||
listOf(DependencySource.Remote.Public)
|
||||
} else {
|
||||
candidateSpecs.map { candidateSpec ->
|
||||
when (candidateSpec) {
|
||||
"remote:public" -> DependencySource.Remote.Public
|
||||
"remote:internal" -> DependencySource.Remote.Internal
|
||||
else -> DependencySource.Local(File(candidateSpec))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
sealed class DependencySource {
|
||||
data class Local(val path: File) : DependencySource()
|
||||
|
||||
sealed class Remote : DependencySource() {
|
||||
object Public : Remote()
|
||||
object Internal : Remote()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects dependencies and downloads all the missing ones into [dependenciesDirectory] from [dependenciesUrl].
|
||||
* If [airplaneMode] is true will throw a RuntimeException instead of downloading.
|
||||
*/
|
||||
class DependencyProcessor(
|
||||
dependenciesRoot: File,
|
||||
val dependenciesUrl: String,
|
||||
dependencyToCandidates: Map<String, List<DependencySource>>,
|
||||
homeDependencyCache: File = defaultDependencyCacheDir,
|
||||
val airplaneMode: Boolean = false,
|
||||
maxAttempts: Int = DependencyDownloader.DEFAULT_MAX_ATTEMPTS,
|
||||
attemptIntervalMs: Long = DependencyDownloader.DEFAULT_ATTEMPT_INTERVAL_MS,
|
||||
customProgressCallback: ProgressCallback? = null,
|
||||
val keepUnstable: Boolean = true
|
||||
) {
|
||||
|
||||
val dependenciesDirectory = dependenciesRoot.apply { mkdirs() }
|
||||
val cacheDirectory = homeDependencyCache.apply { mkdirs() }
|
||||
|
||||
val lockFile = File(cacheDirectory, ".lock").apply { if (!exists()) createNewFile() }
|
||||
|
||||
var showInfo = true
|
||||
private var isInfoShown = false
|
||||
|
||||
private val downloader = DependencyDownloader(maxAttempts, attemptIntervalMs, customProgressCallback)
|
||||
private val extractor = DependencyExtractor()
|
||||
|
||||
private val archiveExtension get() = extractor.archiveExtension
|
||||
|
||||
constructor(
|
||||
dependenciesRoot: File,
|
||||
properties: Properties,
|
||||
dependencies: List<String>,
|
||||
dependenciesUrl: String = properties.dependenciesUrl,
|
||||
keepUnstable: Boolean = true
|
||||
) : this(
|
||||
dependenciesRoot,
|
||||
dependenciesUrl,
|
||||
dependencyToCandidates = properties.findCandidates(dependencies),
|
||||
airplaneMode = properties.airplaneMode,
|
||||
maxAttempts = properties.downloadingAttempts,
|
||||
attemptIntervalMs = properties.downloadingAttemptIntervalMs,
|
||||
keepUnstable = keepUnstable
|
||||
)
|
||||
|
||||
class DependencyFile(directory: File, fileName: String) {
|
||||
val file = File(directory, fileName).apply { createNewFile() }
|
||||
private val dependencies = file.readLines().toMutableSet()
|
||||
|
||||
fun contains(dependency: String) = dependencies.contains(dependency)
|
||||
fun add(dependency: String) = dependencies.add(dependency)
|
||||
fun remove(dependency: String) = dependencies.remove(dependency)
|
||||
|
||||
fun removeAndSave(dependency: String) {
|
||||
remove(dependency)
|
||||
save()
|
||||
}
|
||||
|
||||
fun addAndSave(dependency: String) {
|
||||
add(dependency)
|
||||
save()
|
||||
}
|
||||
|
||||
fun save() {
|
||||
val writer = file.writer()
|
||||
writer.use {
|
||||
dependencies.forEach {
|
||||
writer.write(it)
|
||||
writer.write("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadDependency(dependency: String, baseUrl: String) {
|
||||
val depDir = File(dependenciesDirectory, dependency)
|
||||
val depName = depDir.name
|
||||
|
||||
val fileName = "$depName.$archiveExtension"
|
||||
val archive = cacheDirectory.resolve(fileName)
|
||||
val url = URL("$baseUrl/$fileName")
|
||||
|
||||
val extractedDependencies = DependencyFile(dependenciesDirectory, ".extracted")
|
||||
if (extractedDependencies.contains(depName) &&
|
||||
depDir.exists() &&
|
||||
depDir.isDirectory &&
|
||||
depDir.list().isNotEmpty()
|
||||
) {
|
||||
|
||||
if (!keepUnstable && depDir.list().contains(".unstable")) {
|
||||
// The downloaded version of the dependency is unstable -> redownload it.
|
||||
depDir.deleteRecursively()
|
||||
archive.delete()
|
||||
extractedDependencies.removeAndSave(dependency)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (showInfo && !isInfoShown) {
|
||||
println("Downloading native dependencies (LLVM, sysroot etc). This is a one-time action performed only on the first run of the compiler.")
|
||||
isInfoShown = true
|
||||
}
|
||||
|
||||
if (!archive.exists()) {
|
||||
if (airplaneMode) {
|
||||
throw FileNotFoundException(
|
||||
"""
|
||||
Cannot find a dependency locally: $dependency.
|
||||
Set `airplaneMode = false` in konan.properties to download it.
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
downloader.download(url, archive)
|
||||
}
|
||||
println("Extracting dependency: $archive into $dependenciesDirectory")
|
||||
extractor.extract(archive, dependenciesDirectory)
|
||||
extractedDependencies.addAndSave(depName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
val localKonanDir: File by lazy {
|
||||
File(System.getenv("KONAN_DATA_DIR") ?: (System.getProperty("user.home") + File.separator + ".konan"))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val defaultDependenciesRoot: File
|
||||
get() = localKonanDir.resolve("dependencies")
|
||||
|
||||
val defaultDependencyCacheDir: File
|
||||
get() = localKonanDir.resolve("cache")
|
||||
}
|
||||
|
||||
private val resolvedDependencies = dependencyToCandidates.map { (dependency, candidates) ->
|
||||
val candidate = candidates.asSequence().mapNotNull { candidate ->
|
||||
when (candidate) {
|
||||
is DependencySource.Local -> candidate.takeIf { it.path.exists() }
|
||||
DependencySource.Remote.Public -> candidate
|
||||
DependencySource.Remote.Internal -> candidate.takeIf { InternalServer.isAvailable }
|
||||
}
|
||||
}.firstOrNull()
|
||||
|
||||
candidate ?: error("$dependency is not available; candidates:\n${candidates.joinToString("\n")}")
|
||||
|
||||
dependency to candidate
|
||||
}.toMap()
|
||||
|
||||
private fun resolveDependency(dependency: String): File {
|
||||
val candidate = resolvedDependencies[dependency]
|
||||
return when (candidate) {
|
||||
is DependencySource.Local -> candidate.path
|
||||
is DependencySource.Remote -> File(dependenciesDirectory, dependency)
|
||||
null -> error("$dependency not declared as dependency")
|
||||
}
|
||||
}
|
||||
|
||||
fun resolveRelative(relative: String): File {
|
||||
val path = Paths.get(relative)
|
||||
if (path.isAbsolute) error("not a relative path: $relative")
|
||||
|
||||
val dependency = path.first().toString()
|
||||
return resolveDependency(dependency).let {
|
||||
if (path.nameCount > 1) {
|
||||
it.toPath().resolve(path.subpath(1, path.nameCount)).toFile()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun run() = lock.withLock {
|
||||
RandomAccessFile(lockFile, "rw").channel.lock().use {
|
||||
resolvedDependencies.forEach { (dependency, candidate) ->
|
||||
val baseUrl = when (candidate) {
|
||||
is DependencySource.Local -> null
|
||||
DependencySource.Remote.Public -> dependenciesUrl
|
||||
DependencySource.Remote.Internal -> InternalServer.url
|
||||
}
|
||||
// TODO: consider using different caches for different remotes.
|
||||
if (baseUrl != null) {
|
||||
downloadDependency(dependency, baseUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object InternalServer {
|
||||
private val host = "repo.labs.intellij.net"
|
||||
val url = "http://$host/kotlin-native"
|
||||
|
||||
private val internalDomain = "labs.intellij.net"
|
||||
|
||||
val isAvailable by lazy {
|
||||
val envKey = "KONAN_USE_INTERNAL_SERVER"
|
||||
val envValue = System.getenv(envKey)
|
||||
when (envValue) {
|
||||
"0" -> false
|
||||
"1" -> true
|
||||
null -> checkAccessible()
|
||||
else -> error("unexpected environment: $envKey=$envValue")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAccessible(): Boolean {
|
||||
if (!InetAddress.getLocalHost().canonicalHostName.endsWith(".$internalDomain")) {
|
||||
// Fast path:
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
InetAddress.getByName(host)
|
||||
true
|
||||
} catch (e: UnknownHostException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.util
|
||||
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
fun printMillisec(message: String, body: () -> Unit) {
|
||||
val msec = measureTimeMillis {
|
||||
body()
|
||||
}
|
||||
println("$message: $msec msec")
|
||||
}
|
||||
|
||||
fun profile(message: String, body: () -> Unit) = profileIf(
|
||||
System.getProperty("konan.profile")?.equals("true") ?: false,
|
||||
message, body
|
||||
)
|
||||
|
||||
fun profileIf(condition: Boolean, message: String, body: () -> Unit) =
|
||||
if (condition) printMillisec(message, body) else body()
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.konan.util
|
||||
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.util.*
|
||||
|
||||
fun defaultTargetSubstitutions(target: KonanTarget) =
|
||||
mapOf(
|
||||
"target" to target.visibleName,
|
||||
"arch" to target.architecture.visibleName,
|
||||
"family" to target.family.visibleName
|
||||
)
|
||||
|
||||
// Performs substitution similar to:
|
||||
// foo = ${foo} ${foo.${arch}} ${foo.${os}}
|
||||
fun substitute(properties: Properties, substitutions: Map<String, String>) {
|
||||
for (key in properties.stringPropertyNames()) {
|
||||
for (substitution in substitutions.values) {
|
||||
val suffix = ".$substitution"
|
||||
if (key.endsWith(suffix)) {
|
||||
val baseKey = key.removeSuffix(suffix)
|
||||
val oldValue = properties.getProperty(baseKey, "")
|
||||
val appendedValue = properties.getProperty(key, "")
|
||||
val newValue = if (oldValue != "") "$oldValue $appendedValue" else appendedValue
|
||||
properties.setProperty(baseKey, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user