Moved basic classes from konan/utils to util-io

This commit is contained in:
Alexander Gorshenev
2019-05-31 16:50:01 +03:00
committed by alexander-gorshenev
parent f5190f195d
commit d1390365de
9 changed files with 25 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
description = "Kotlin/Native utils"
dependencies {
compile(kotlinStdlib())
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
publish()
standardPublicJars()
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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
import java.io.Serializable
interface KonanVersion : Serializable {
val meta: MetaVersion
val major: Int
val minor: Int
val maintenance: Int
val build: Int
fun toString(showMeta: Boolean, showBuild: Boolean): String
companion object {
// major.minor.patch-meta-build where patch, meta and build are optional.
private val versionPattern = "(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-(\\p{Alpha}\\p{Alnum}*))?(?:-(\\d+))?".toRegex()
fun fromString(version: String): KonanVersion {
val (major, minor, maintenance, metaString, build) =
versionPattern.matchEntire(version)?.destructured
?: throw IllegalArgumentException("Cannot parse Kotlin/Native version: $version")
return KonanVersionImpl(
MetaVersion.findAppropriate(metaString),
major.toInt(),
minor.toInt(),
maintenance.toIntOrNull() ?: 0,
build.toIntOrNull() ?: -1
)
}
}
}
fun String.parseKonanVersion() = KonanVersion.fromString(this)
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
) : KonanVersion {
override fun toString(showMeta: Boolean, showBuild: Boolean) = buildString {
append(major)
append('.')
append(minor)
if (maintenance != 0) {
append('.')
append(maintenance)
}
if (showMeta) {
append('-')
append(meta.metaString)
}
if (showBuild && build != -1) {
append('-')
append(build)
}
}
private val isRelease: Boolean
get() = meta == MetaVersion.RELEASE
private val versionString by lazy { toString(!isRelease, !isRelease) }
override fun toString() = versionString
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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
/**
* https://en.wikipedia.org/wiki/Software_versioning
* scheme major.minor[.build[.revision]].
*/
enum class MetaVersion(val metaString: String) {
DEV("dev"),
EAP("eap"),
ALPHA("alpha"),
BETA("beta"),
RC1("rc1"),
RC2("rc2"),
RELEASE("release");
companion object {
fun findAppropriate(metaString: String): MetaVersion {
return MetaVersion.values().find { it.metaString.equals(metaString, ignoreCase = true) }
?: if (metaString.isBlank()) RELEASE else error("Unknown meta version: $metaString")
}
}
}
@@ -0,0 +1,221 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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 org.jetbrains.kotlin.konan.util.removeSuffixIfPresent
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.io.RandomAccessFile
import java.lang.Exception
import java.net.URI
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
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().removeSuffixIfPresent("/") // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8153248
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 map(mode: FileChannel.MapMode = FileChannel.MapMode.READ_ONLY,
start: Long = 0, size: Long = -1): MappedByteBuffer {
val file = RandomAccessFile(path,
if (mode == FileChannel.MapMode.READ_ONLY) "r" else "rw")
val fileSize = if (mode == FileChannel.MapMode.READ_ONLY)
file.length() else size.also { assert(size != -1L) }
return file.channel.map(mode, start, fileSize) // Shall we .also { file.close() }?
}
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
val separator = java.io.File.separator
}
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()
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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)
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.*
import org.jetbrains.kotlin.konan.util.parseSpaceSeparatedArgs
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, escapeInQuotes: Boolean = false): List<String> {
val value = this.getProperty(key.suffix(suffix)) ?: this.getProperty(key)
if (value?.isBlank() == true) return emptyList()
return if (escapeInQuotes) value?.let { parseSpaceSeparatedArgs(it) } ?: emptyList()
else 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"
fun Properties.keepOnlyDefaultProfiles() {
val DEPENDENCY_PROFILES_KEY = "dependencyProfiles"
val dependencyProfiles = this.getProperty(DEPENDENCY_PROFILES_KEY)
if (dependencyProfiles != "default alt")
error("unexpected $DEPENDENCY_PROFILES_KEY value: expected 'default alt', got '$dependencyProfiles'")
// Force build to use only 'default' profile:
this.setProperty(DEPENDENCY_PROFILES_KEY, "default")
// TODO: it actually affects only resolution made in :dependencies,
// that's why we assume that 'default' profile comes first (and check this above).
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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
import org.jetbrains.kotlin.konan.file.*
import java.lang.StringBuilder
fun <T> printMillisec(message: String, body: () -> T): T {
var result: T? = null
val msec = measureTimeMillis{
result = body()
}
println("$message: $msec msec")
return result!!
}
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()
fun nTabs(amount: Int): String {
return String.format("%1$-${(amount+1)*4}s", "")
}
fun String.prefixIfNot(prefix: String) =
if (this.startsWith(prefix)) this else "$prefix$this"
fun String.prefixBaseNameIfNot(prefix: String): String {
val file = File(this).absoluteFile
val name = file.name
val directory = file.parent
return "$directory/${name.prefixIfNot(prefix)}"
}
fun String.suffixIfNot(suffix: String) =
if (this.endsWith(suffix)) this else "$this$suffix"
fun String.removeSuffixIfPresent(suffix: String) =
if (this.endsWith(suffix)) this.dropLast(suffix.length) else this
fun <T> Lazy<T>.getValueOrNull(): T? = if (isInitialized()) value else null
fun parseSpaceSeparatedArgs(argsString: String): List<String> {
val parsedArgs = mutableListOf<String>()
var inQuotes = false
var currentCharSequence = StringBuilder()
fun saveArg() {
if (!currentCharSequence.isEmpty()) {
parsedArgs.add(currentCharSequence.toString())
currentCharSequence = StringBuilder()
}
}
argsString.forEach { char ->
if (char == '"') {
inQuotes = !inQuotes
// Save value which was in quotes.
if (!inQuotes) {
saveArg()
}
} else if (char == ' ' && !inQuotes) {
// Space is separator.
saveArg()
} else {
currentCharSequence.append(char)
}
}
if (inQuotes) {
error("No close-quote was found in $currentCharSequence.")
}
saveArg()
return parsedArgs
}