From 84a8c759805b66f8d291942c824f4c1dfbdf0c09 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Sun, 14 Apr 2024 22:15:58 +0200 Subject: [PATCH] Replace nio with multiplatform okio --- core/build.gradle.kts | 6 +- .../main/kotlin/io/sellmair/okay/OkCache.kt | 38 ++---- .../kotlin/io/sellmair/okay/OkCacheRecord.kt | 2 +- .../kotlin/io/sellmair/okay/OkCacheRestore.kt | 16 ++- .../kotlin/io/sellmair/okay/OkCacheStore.kt | 25 ++-- .../main/kotlin/io/sellmair/okay/OkContext.kt | 6 +- .../io/sellmair/okay/OkCoroutineDescriptor.kt | 2 +- .../io/sellmair/okay/OkModuleContext.kt | 28 ++-- .../kotlin/io/sellmair/okay/clean/okClean.kt | 4 +- .../okay/dependency/OkDependenciesFile.kt | 2 +- .../dependency/OkDependencyDeclaration.kt | 2 +- .../okay/dependency/parseDependenciesFile.kt | 10 +- .../io/sellmair/okay/input/OkInputFile.kt | 12 +- .../okay/input/OkInputFileCollection.kt | 2 +- .../io/sellmair/okay/io/OkFileCollection.kt | 11 +- .../main/kotlin/io/sellmair/okay/io/OkPath.kt | 56 -------- .../kotlin/io/sellmair/okay/io/copyFile.kt | 9 +- .../kotlin/io/sellmair/okay/io/ioState.kt | 20 +-- .../io/sellmair/okay/kotlin/kotlinClean.kt | 4 +- .../io/sellmair/okay/kotlin/kotlinCompile.kt | 36 +++--- .../okay/kotlin/kotlinCompileDependencies.kt | 2 +- .../kotlinCompileRuntimeDependencies.kt | 2 +- .../io/sellmair/okay/kotlin/kotlinJar.kt | 10 +- .../io/sellmair/okay/kotlin/kotlinPackage.kt | 29 ++--- .../io/sellmair/okay/kotlin/kotlinRun.kt | 20 +-- .../okay/maven/mavenResolveDependencies.kt | 2 +- .../okay/maven/mavenResolveDependency.kt | 10 +- .../sellmair/okay/output/OkOutputDirectory.kt | 4 +- .../io/sellmair/okay/output/OkOutputFile.kt | 5 +- .../main/kotlin/io/sellmair/okay/utils/log.kt | 2 +- .../kotlin/io/sellmair/okay/zip/zipFiles.kt | 13 +- .../okay/tests/integrationTests/CacheTest.kt | 1 + .../MavenResolveDependenciesTest.kt | 3 +- .../tests/unitTests/OkFileCollectionTest.kt | 48 +++++++ .../okay/tests/unitTests/OkPathTest.kt | 27 ++-- .../okay/utils/OkTestCoroutineCacheHook.kt | 2 +- okay-fs/build.gradle.kts | 10 +- .../kotlin/io/sellmair/okay/OkHash.kt | 51 +++++--- .../kotlin/io/sellmair/okay/fs/OkFs.kt | 121 +++++++++++++----- .../kotlin/io/sellmair/okay/fs/OkPath.kt | 67 +++++++++- .../kotlin/io/sellmair/okay/fs/OkFs.jvm.kt | 16 +++ .../kotlin/io/sellmair/okay/fs/OkFs.native.kt | 8 ++ 42 files changed, 440 insertions(+), 304 deletions(-) delete mode 100644 core/src/main/kotlin/io/sellmair/okay/io/OkPath.kt create mode 100644 core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkFileCollectionTest.kt rename {core/src/main => okay-fs/src/commonMain}/kotlin/io/sellmair/okay/OkHash.kt (65%) create mode 100644 okay-fs/src/jvmMain/kotlin/io/sellmair/okay/fs/OkFs.jvm.kt create mode 100644 okay-fs/src/nativeMain/kotlin/io/sellmair/okay/fs/OkFs.native.kt diff --git a/core/build.gradle.kts b/core/build.gradle.kts index cc9f3bd..a617693 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -47,15 +47,16 @@ tasks.register("install") { tasks.register("buildTestProject") { dependsOn("install") - workingDir = file("testProject") + workingDir = rootDir.resolve("samples/ktorServer") classpath = kotlin.target.compilations["main"].runtimeDependencyFiles - args = listOf("build") + args = listOf("pkg") mainClass = "io.sellmair.okay.OkMain" } dependencies { implementation(project(":okay-fs")) + implementation("io.ktor:ktor-client-cio-jvm:2.3.9") implementation("io.ktor:ktor-client-cio:2.3.9") implementation("io.ktor:ktor-client-core:2.3.9") implementation("org.apache.maven:maven-model:3.9.6") @@ -65,7 +66,6 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.6.3") implementation("org.slf4j:slf4j-api:2.0.12") implementation("org.slf4j:slf4j-jdk14:2.0.12") - implementation("io.ktor:ktor-client-cio-jvm:2.3.9") testImplementation(kotlin("test-junit5")) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") diff --git a/core/src/main/kotlin/io/sellmair/okay/OkCache.kt b/core/src/main/kotlin/io/sellmair/okay/OkCache.kt index 4a02d3e..a8c8f51 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkCache.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkCache.kt @@ -1,18 +1,12 @@ package io.sellmair.okay +import io.sellmair.okay.fs.* import io.sellmair.okay.serialization.format import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.encodeToByteArray -import java.io.ObjectInputStream -import java.io.ObjectOutputStream -import java.nio.file.Path -import kotlin.io.path.createDirectories -import kotlin.io.path.inputStream -import kotlin.io.path.isRegularFile -import kotlin.io.path.outputStream /** * The dispatcher used to read/write anything to the cache. @@ -39,16 +33,15 @@ internal val OkContext.cacheBlobsDirectory @OptIn(ExperimentalSerializationApi::class) internal suspend fun OkContext.readCacheRecord(key: OkHash): OkCacheRecord? = withOkContext(okCacheDispatcher) { val file = cacheEntriesDirectory.resolve(key.value) - if (!file.system().isRegularFile()) return@withOkContext null - ObjectInputStream(file.system().inputStream()).use { stream -> - format.decodeFromByteArray(stream.readAllBytes()) - }/* - We should not read entries that have been written during this exact session. - Otherwise, the UP-TO-DATE checks have a problem: - Yes, the recently stored cache entry is UP-TO-DATE (because it was just stored), - but it might not be UP-TO-DATE from the perspective of the previously stored - coroutine as the dependencies (or outputs) might have changed. - */ + if (!file.isRegularFile()) return@withOkContext null + format.decodeFromByteArray(file.readAll()) + /* + We should not read entries that have been written during this exact session. + Otherwise, the UP-TO-DATE checks have a problem: + Yes, the recently stored cache entry is UP-TO-DATE (because it was just stored), + but it might not be UP-TO-DATE from the perspective of the previously stored + coroutine as the dependencies (or outputs) might have changed. + */ .takeUnless { it.session == currentOkSessionId() } } @@ -57,14 +50,11 @@ internal suspend fun OkContext.readCacheRecord(key: OkHash): OkCacheRecord? = wi */ @OptIn(ExperimentalSerializationApi::class) @OkUnsafe("Consider using ") -internal suspend fun OkContext.storeCacheRecord(value: OkCacheRecord): Path = withOkContext(okCacheDispatcher) { - cacheEntriesDirectory.system().createDirectories() - - val file = cacheEntriesDirectory.resolve(value.inputHash.value).system() - ObjectOutputStream(file.outputStream().buffered()).use { stream -> - stream.write(format.encodeToByteArray(value)) - } +internal suspend fun OkContext.storeCacheRecord(value: OkCacheRecord): OkPath = withOkContext(okCacheDispatcher) { + cacheEntriesDirectory.createDirectories() + val file = cacheEntriesDirectory.resolve(value.inputHash.value) + file.write(format.encodeToByteArray(value)) file } diff --git a/core/src/main/kotlin/io/sellmair/okay/OkCacheRecord.kt b/core/src/main/kotlin/io/sellmair/okay/OkCacheRecord.kt index 6384dae..25fc900 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkCacheRecord.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkCacheRecord.kt @@ -1,7 +1,7 @@ package io.sellmair.okay +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.input.OkInput -import io.sellmair.okay.io.OkPath import io.sellmair.okay.output.OkOutput import io.sellmair.okay.serialization.Base64Serializer import kotlinx.serialization.Contextual diff --git a/core/src/main/kotlin/io/sellmair/okay/OkCacheRestore.kt b/core/src/main/kotlin/io/sellmair/okay/OkCacheRestore.kt index 4b53906..c49b06f 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkCacheRestore.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkCacheRestore.kt @@ -2,6 +2,10 @@ package io.sellmair.okay +import io.sellmair.okay.fs.copyTo +import io.sellmair.okay.fs.createParentDirectories +import io.sellmair.okay.fs.deleteRecursively +import io.sellmair.okay.fs.isRegularFile import io.sellmair.okay.output.OkOutputDirectory import io.sellmair.okay.output.OkOutputs import io.sellmair.okay.utils.* @@ -16,8 +20,8 @@ internal data class OkCacheHit( ) : OkCacheResult() { override fun toString(): String { return "CacheHit(" + - "upToDate=${upToDate.joinToString { it.descriptor.id }}, " + - "restored=${restored.joinToString { it.descriptor.id }})" + "upToDate=${upToDate.joinToString { it.descriptor.id }}, " + + "restored=${restored.joinToString { it.descriptor.id }})" } } @@ -93,13 +97,13 @@ private fun OkContext.restoreFilesFromCache( ) { entry.output.withClosure { output -> if (output is OkOutputs) output.values else emptyList() } .filterIsInstance() - .forEach { outputDirectory -> outputDirectory.path.system().deleteRecursively() } + .forEach { outputDirectory -> outputDirectory.path.deleteRecursively() } entry.outputFiles.orEmpty().forEach { (path, hash) -> - val blob = cacheBlobsDirectory.resolve(hash.value).system() + val blob = cacheBlobsDirectory.resolve(hash.value) if (blob.isRegularFile()) { - path.system().createParentDirectories() - blob.copyTo(path.system(), true) + path.createParentDirectories() + blob.copyTo(path) } } } diff --git a/core/src/main/kotlin/io/sellmair/okay/OkCacheStore.kt b/core/src/main/kotlin/io/sellmair/okay/OkCacheStore.kt index 45b9451..3abc6df 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkCacheStore.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkCacheStore.kt @@ -1,5 +1,6 @@ package io.sellmair.okay +import io.sellmair.okay.fs.* import io.sellmair.okay.input.OkInput import io.sellmair.okay.io.regularFileStateHash import io.sellmair.okay.output.OkOutput @@ -9,13 +10,8 @@ import io.sellmair.okay.output.OkOutputs import io.sellmair.okay.serialization.format import kotlinx.coroutines.Dispatchers import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializer -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.encodeToStream import java.nio.file.FileAlreadyExistsException -import java.nio.file.Path -import kotlin.io.path.* +import kotlin.io.path.ExperimentalPathApi /** @@ -37,23 +33,22 @@ suspend fun OkContext.storeCachedCoroutine( serializer: KSerializer, dependencies: Set ): OkCacheRecord = withOkContext(Dispatchers.IO) { - cacheBlobsDirectory.system().createDirectories() + cacheBlobsDirectory.createDirectories() val outputFiles = output.walkFiles() .toList() .mapNotNull { path -> - if (!path.exists() || !path.isRegularFile()) return@mapNotNull null + if (!path.isRegularFile()) return@mapNotNull null val fileCacheKey = path.regularFileStateHash() - val blobFile = cacheBlobsDirectory.resolve(fileCacheKey.value).system() + val blobFile = cacheBlobsDirectory.resolve(fileCacheKey.value) try { - blobFile.createFile() - path.copyTo(blobFile, true) + path.copyTo(blobFile) } catch (t: FileAlreadyExistsException) { /* File exists, no need to store it */ } - path.ok() to fileCacheKey + path to fileCacheKey } .toMap() @@ -74,12 +69,12 @@ suspend fun OkContext.storeCachedCoroutine( } @OptIn(ExperimentalPathApi::class) -fun OkOutput.walkFiles(): Sequence { +fun OkOutput.walkFiles(): Sequence { return sequence { when (this@walkFiles) { is OkOutputs -> yieldAll(values.flatMap { it.walkFiles() }) - is OkOutputDirectory -> yieldAll(path.system().walk()) - is OkOutputFile -> yield(path.system()) + is OkOutputDirectory -> yieldAll(path.listRecursively()) + is OkOutputFile -> yield(path) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/OkContext.kt b/core/src/main/kotlin/io/sellmair/okay/OkContext.kt index e91d075..cd749e4 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkContext.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkContext.kt @@ -3,8 +3,10 @@ package io.sellmair.okay -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.absolutePathString import kotlinx.coroutines.* +import okio.Path.Companion.toPath import java.nio.file.Path import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -29,8 +31,6 @@ interface OkContext { val cs: CoroutineScope val ctx: OkContext get() = this - - fun Path.ok(): OkPath = path(this) } internal operator fun OkContext.plus(element: CoroutineContext.Element): OkContext { diff --git a/core/src/main/kotlin/io/sellmair/okay/OkCoroutineDescriptor.kt b/core/src/main/kotlin/io/sellmair/okay/OkCoroutineDescriptor.kt index b344c3d..fa6dee2 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkCoroutineDescriptor.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkCoroutineDescriptor.kt @@ -1,7 +1,7 @@ package io.sellmair.okay import io.sellmair.okay.input.OkInput -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import kotlinx.serialization.Serializable import kotlin.reflect.typeOf diff --git a/core/src/main/kotlin/io/sellmair/okay/OkModuleContext.kt b/core/src/main/kotlin/io/sellmair/okay/OkModuleContext.kt index 5dc85a5..38ff503 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkModuleContext.kt +++ b/core/src/main/kotlin/io/sellmair/okay/OkModuleContext.kt @@ -1,38 +1,30 @@ package io.sellmair.okay -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkFs +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.absolutePathString +import okio.Path.Companion.toPath import java.nio.file.Path import kotlin.coroutines.CoroutineContext import kotlin.io.path.Path -import kotlin.io.path.name -import kotlin.io.path.relativeTo +import kotlin.io.path.pathString -@OptIn(OkUnsafe::class) -fun OkContext.path(value: String): OkPath { - val root = cs.coroutineContext[OkRoot]?.path?.toString() ?: "" - return OkPath(root, value) -} - -@OptIn(OkUnsafe::class) fun OkContext.rootPath() = path("") fun OkContext.modulePath(): OkPath { return cs.coroutineContext[OkModuleContext]?.path ?: rootPath() } -fun OkContext.moduleName() = modulePath().system().toAbsolutePath() - .normalize().name +fun OkContext.moduleName() = modulePath().absolutePathString().toPath(true).name fun OkContext.modulePath(path: String): OkPath { return modulePath().resolve(path) } - -@OptIn(OkUnsafe::class) -fun OkContext.path(path: Path): OkPath { - val root = cs.coroutineContext[OkRoot]?.path ?: Path("") - return OkPath(root.toString(), path.relativeTo(root).toString()) +fun OkContext.path(value: String): OkPath { + val root = cs.coroutineContext[OkRoot]?.path?.toString() ?: "" + return OkFs(root).path(value) } internal suspend fun OkContext.withOkModule(path: OkPath, block: suspend OkContext.() -> T): T { @@ -56,4 +48,4 @@ internal class OkRoot( override val key: CoroutineContext.Key<*> = Key companion object Key : CoroutineContext.Key -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/io/sellmair/okay/clean/okClean.kt b/core/src/main/kotlin/io/sellmair/okay/clean/okClean.kt index fdc351a..bd0cfc7 100644 --- a/core/src/main/kotlin/io/sellmair/okay/clean/okClean.kt +++ b/core/src/main/kotlin/io/sellmair/okay/clean/okClean.kt @@ -1,16 +1,16 @@ package io.sellmair.okay.clean import io.sellmair.okay.OkContext +import io.sellmair.okay.fs.deleteRecursively import io.sellmair.okay.okExtensions import io.sellmair.okay.path import io.sellmair.okay.utils.log import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.deleteRecursively @OptIn(ExperimentalPathApi::class) suspend fun OkContext.okClean() { log("Cleaning .okay") - path(".okay").system().deleteRecursively() + path(".okay").deleteRecursively() okExtensions { extension -> extension.clean(this) diff --git a/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependenciesFile.kt b/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependenciesFile.kt index 399588b..4f8b8f3 100644 --- a/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependenciesFile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependenciesFile.kt @@ -1,6 +1,6 @@ package io.sellmair.okay.dependency -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath data class OkDependenciesFile( val module: OkPath, diff --git a/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependencyDeclaration.kt b/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependencyDeclaration.kt index 043b2d9..03b5dad 100644 --- a/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependencyDeclaration.kt +++ b/core/src/main/kotlin/io/sellmair/okay/dependency/OkDependencyDeclaration.kt @@ -1,7 +1,7 @@ package io.sellmair.okay.dependency import io.sellmair.okay.OkContext -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.path data class OkDependencyDeclaration( diff --git a/core/src/main/kotlin/io/sellmair/okay/dependency/parseDependenciesFile.kt b/core/src/main/kotlin/io/sellmair/okay/dependency/parseDependenciesFile.kt index a60229e..131a41a 100644 --- a/core/src/main/kotlin/io/sellmair/okay/dependency/parseDependenciesFile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/dependency/parseDependenciesFile.kt @@ -1,6 +1,8 @@ package io.sellmair.okay.dependency import io.sellmair.okay.* +import io.sellmair.okay.fs.isRegularFile +import io.sellmair.okay.fs.readText import io.sellmair.okay.input.asInput import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* @@ -15,11 +17,9 @@ suspend fun OkContext.parseDependenciesFile(): OkDependenciesFile? { describeCoroutine("parseLibsFile"), input = dependenciesFile.asInput() ) task@{ - val file = dependenciesFile.system() - if (!file.exists()) return@task null - val parsedObject = file.inputStream().buffered().use { inputStream -> - Json.decodeFromStream(inputStream) - }.jsonObject + if (!dependenciesFile.isRegularFile()) return@task null + val parsedObject =Json.decodeFromString(dependenciesFile.readText()) + .jsonObject val declarations = parsedObject.keys.map { dependencyKey -> OkDependencyDeclaration( diff --git a/core/src/main/kotlin/io/sellmair/okay/input/OkInputFile.kt b/core/src/main/kotlin/io/sellmair/okay/input/OkInputFile.kt index 7a7a4dc..316d361 100644 --- a/core/src/main/kotlin/io/sellmair/okay/input/OkInputFile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/input/OkInputFile.kt @@ -1,20 +1,20 @@ package io.sellmair.okay.input -import io.sellmair.okay.* -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.OkContext +import io.sellmair.okay.OkHash +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.isDirectory import io.sellmair.okay.io.directoryStateHash import io.sellmair.okay.io.regularFileStateHash import kotlinx.serialization.Serializable -import kotlin.io.path.isDirectory fun OkPath.asInput(): OkInputFile = OkInputFile(this) @Serializable data class OkInputFile(val path: OkPath) : OkInput { override suspend fun currentHash(ctx: OkContext): OkHash { - val systemPath = path.system() - return if (systemPath.isDirectory()) systemPath.directoryStateHash() - else systemPath.regularFileStateHash() + return if (path.isDirectory()) path.directoryStateHash() + else path.regularFileStateHash() } } diff --git a/core/src/main/kotlin/io/sellmair/okay/input/OkInputFileCollection.kt b/core/src/main/kotlin/io/sellmair/okay/input/OkInputFileCollection.kt index 4e349b4..c0cd1c3 100644 --- a/core/src/main/kotlin/io/sellmair/okay/input/OkInputFileCollection.kt +++ b/core/src/main/kotlin/io/sellmair/okay/input/OkInputFileCollection.kt @@ -15,7 +15,7 @@ data class OkInputFileCollection(val files: OkFileCollection) : OkInput { override suspend fun currentHash(ctx: OkContext): OkHash { return hash { files.resolve(ctx).forEach { path -> - push(path.system().regularFileStateHash()) + push(path.regularFileStateHash()) } } } diff --git a/core/src/main/kotlin/io/sellmair/okay/io/OkFileCollection.kt b/core/src/main/kotlin/io/sellmair/okay/io/OkFileCollection.kt index 6a99d61..41ae1d1 100644 --- a/core/src/main/kotlin/io/sellmair/okay/io/OkFileCollection.kt +++ b/core/src/main/kotlin/io/sellmair/okay/io/OkFileCollection.kt @@ -3,10 +3,11 @@ package io.sellmair.okay.io import io.sellmair.okay.OkContext +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.isRegularFile +import io.sellmair.okay.fs.listRecursively import java.io.Serializable -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.isRegularFile -import kotlin.io.path.walk +import kotlin.io.path.* /** * Represents a collection of regular/existing files @@ -25,14 +26,12 @@ fun OkFileCollection.withExtension(extension: String): OkFileCollection { } @kotlinx.serialization.Serializable -@OptIn(ExperimentalPathApi::class) internal data class OkWalkFileCollection( private val root: OkPath ) : OkFileCollection, Serializable { override suspend fun resolve(ctx: OkContext): List = with(ctx) { - root.system().walk() + root.listRecursively() .filter { it.isRegularFile() } - .map { it.ok() } .toList() } } diff --git a/core/src/main/kotlin/io/sellmair/okay/io/OkPath.kt b/core/src/main/kotlin/io/sellmair/okay/io/OkPath.kt deleted file mode 100644 index 3fe7ac5..0000000 --- a/core/src/main/kotlin/io/sellmair/okay/io/OkPath.kt +++ /dev/null @@ -1,56 +0,0 @@ -package io.sellmair.okay.io - -import io.sellmair.okay.OkUnsafe -import java.io.Serializable -import java.nio.file.Path -import kotlin.io.path.Path -import kotlin.io.path.extension - -@kotlinx.serialization.Serializable -class OkPath @OkUnsafe constructor( - private val root: String, - @property:OkUnsafe - internal val value: String -) : Serializable { - - @OptIn(OkUnsafe::class) - override fun toString(): String { - return value - } - - @OptIn(OkUnsafe::class) - fun resolve(other: String): OkPath { - return OkPath(root, Path(value).resolve(other).toString()) - } - - @OptIn(OkUnsafe::class) - fun resolve(other: OkPath): OkPath { - require(other.root == this.root) - return OkPath(root, Path(value).resolve(other.value).toString()) - } - - @OptIn(OkUnsafe::class) - val extension: String get() = Path(value).extension - - @OptIn(OkUnsafe::class) - fun system(): Path { - return Path(root).resolve(value) - } - - @OptIn(OkUnsafe::class) - override fun equals(other: Any?): Boolean { - if (other === this) return true - if (other !is OkPath) return false - if (other.root != this.root) return false - if (other.value != this.value) return false - return true - } - - @OptIn(OkUnsafe::class) - override fun hashCode(): Int { - var result = root.hashCode() - result = 31 * result + value.hashCode() - return result - } -} - diff --git a/core/src/main/kotlin/io/sellmair/okay/io/copyFile.kt b/core/src/main/kotlin/io/sellmair/okay/io/copyFile.kt index 98a2f23..8fefba6 100644 --- a/core/src/main/kotlin/io/sellmair/okay/io/copyFile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/io/copyFile.kt @@ -1,10 +1,13 @@ package io.sellmair.okay.io -import io.sellmair.okay.* +import io.sellmair.okay.OkContext +import io.sellmair.okay.cachedCoroutine +import io.sellmair.okay.describeCoroutine +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.copyTo import io.sellmair.okay.input.asInput import io.sellmair.okay.output.asOutput import kotlinx.serialization.serializer -import kotlin.io.path.copyTo suspend fun OkContext.copyFile(from: OkPath, to: OkPath): OkPath { return cachedCoroutine( @@ -13,6 +16,6 @@ suspend fun OkContext.copyFile(from: OkPath, to: OkPath): OkPath { output = to.asOutput(), serializer = serializer() ) { - from.system().copyTo(to.system(), true).ok() + from.copyTo(to) } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/io/ioState.kt b/core/src/main/kotlin/io/sellmair/okay/io/ioState.kt index 3a876fd..86fdebe 100644 --- a/core/src/main/kotlin/io/sellmair/okay/io/ioState.kt +++ b/core/src/main/kotlin/io/sellmair/okay/io/ioState.kt @@ -1,18 +1,17 @@ package io.sellmair.okay.io import io.sellmair.okay.OkHash +import io.sellmair.okay.fs.* import io.sellmair.okay.hash import io.sellmair.okay.okCacheDispatcher import kotlinx.coroutines.withContext -import java.nio.file.Path -import kotlin.io.path.* -internal suspend fun Path.directoryStateHash(): OkHash = withContext(okCacheDispatcher) { +internal suspend fun OkPath.directoryStateHash(): OkHash = withContext(okCacheDispatcher) { hash { push(absolutePathString()) if (isDirectory()) { - listDirectoryEntries().map { entry -> + list().map { entry -> if (entry.isDirectory()) { push(entry.directoryStateHash()) } else if (entry.isRegularFile()) { @@ -23,20 +22,13 @@ internal suspend fun Path.directoryStateHash(): OkHash = withContext(okCacheDisp } } -internal suspend fun Path.regularFileStateHash(): OkHash = withContext(okCacheDispatcher) { +internal suspend fun OkPath.regularFileStateHash(): OkHash = withContext(okCacheDispatcher) { hash { push(absolutePathString()) - push(if (exists()) 1 else 0) + push(if (isRegularFile()) 1 else 0) if (isRegularFile()) { - val buffer = ByteArray(2048) - inputStream().buffered().use { input -> - while (true) { - val read = input.read(buffer) - if (read < 0) break - push(buffer, 0, read) - } - } + push(source()) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinClean.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinClean.kt index 2c90209..b7da2a9 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinClean.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinClean.kt @@ -7,9 +7,9 @@ import io.sellmair.okay.OkCoroutineDescriptor.Verbosity.Info import io.sellmair.okay.clean.OkCleanExtension import io.sellmair.okay.dependency.moduleOrNull import io.sellmair.okay.dependency.parseDependenciesFile +import io.sellmair.okay.fs.deleteRecursively import io.sellmair.okay.utils.log import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.deleteRecursively internal class KotlinCleanExtension : OkCleanExtension { override suspend fun clean(ctx: OkContext) { @@ -27,6 +27,6 @@ internal suspend fun OkContext.kotlinClean() { }.awaitAll() log("Cleaning Kotlin") - modulePath("build").system().deleteRecursively() + modulePath("build").deleteRecursively() } } diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompile.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompile.kt index 3712411..fa432f2 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompile.kt @@ -4,35 +4,34 @@ package io.sellmair.okay.kotlin import io.sellmair.okay.* import io.sellmair.okay.OkCoroutineDescriptor.Verbosity.Info -import io.sellmair.okay.input.OkInput -import io.sellmair.okay.input.OkInputs +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.absolutePathString +import io.sellmair.okay.fs.createDirectories +import io.sellmair.okay.fs.deleteRecursively import io.sellmair.okay.input.asInput import io.sellmair.okay.input.plus import io.sellmair.okay.io.OkFileCollection -import io.sellmair.okay.io.OkPath import io.sellmair.okay.io.walk import io.sellmair.okay.io.withExtension import io.sellmair.okay.maven.mavenResolveCompileDependencies -import io.sellmair.okay.output.OkOutput import io.sellmair.okay.output.OkOutputDirectory -import io.sellmair.okay.output.OkOutputs import io.sellmair.okay.utils.log -import kotlinx.serialization.serializer +import okio.Path.Companion.toPath +import org.jetbrains.kotlin.cli.common.CLITool import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.compilerRunner.toArgumentStrings import org.jetbrains.kotlin.incremental.classpathAsList import org.jetbrains.kotlin.incremental.destinationAsFile -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.absolutePathString -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteRecursively +import java.io.File +import kotlin.io.path.* suspend fun OkContext.kotlinCompile(): OkPath { val kotlinSources = modulePath("src").walk().withExtension("kt") val dependencies = mavenResolveCompileDependencies() + - kotlinCompileDependencies() + kotlinCompileDependencies() return kotlinCompile(kotlinSources, dependencies, modulePath("build/classes")) } @@ -46,24 +45,23 @@ suspend fun OkContext.kotlinCompile( return cachedCoroutine( describeCoroutine("kotlinCompile", verbosity = Info), input = sources.asInput() + - dependencies.map { it.asInput() }.asInput(), + dependencies.map { it.asInput() }.asInput(), output = OkOutputDirectory(outputDirectory), serializer = OkPath.serializer() ) { log("Compiling Kotlin") - outputDirectory.system().deleteRecursively() - outputDirectory.system().createDirectories() + outputDirectory.deleteRecursively() + outputDirectory.createDirectories() val args = K2JVMCompilerArguments() args.noStdlib = true args.moduleName = moduleName() - args.classpathAsList = dependencies.map { it.system().toFile() } - args.freeArgs += sources.resolve(ctx).map { it.system().absolutePathString() } - args.destinationAsFile = outputDirectory.system().toFile() - - K2JVMCompiler.main(args.toArgumentStrings().toTypedArray()) + args.classpathAsList = dependencies.map { File(it.absolutePathString()) } + args.freeArgs += sources.resolve(ctx).map { it.absolutePathString() } + args.destinationAsFile = File(outputDirectory.absolutePathString()) + K2JVMCompiler().exec(System.err, MessageRenderer.GRADLE_STYLE, *args.toArgumentStrings().toTypedArray()) outputDirectory } } diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileDependencies.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileDependencies.kt index b78a2b9..8cb6086 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileDependencies.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileDependencies.kt @@ -5,7 +5,7 @@ import io.sellmair.okay.async import io.sellmair.okay.awaitAll import io.sellmair.okay.dependency.compileDependenciesClosure import io.sellmair.okay.dependency.moduleOrNull -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.withOkModule suspend fun OkContext.kotlinCompileDependencies(): List { diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileRuntimeDependencies.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileRuntimeDependencies.kt index 4466dee..affd2d8 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileRuntimeDependencies.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinCompileRuntimeDependencies.kt @@ -5,7 +5,7 @@ import io.sellmair.okay.async import io.sellmair.okay.awaitAll import io.sellmair.okay.dependency.moduleOrNull import io.sellmair.okay.dependency.runtimeDependenciesClosure -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.withOkModule suspend fun OkContext.kotlinCompileRuntimeDependencies(): List { diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinJar.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinJar.kt index 6d2a752..5d71f04 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinJar.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinJar.kt @@ -4,22 +4,20 @@ package io.sellmair.okay.kotlin import io.sellmair.okay.OkAsync import io.sellmair.okay.OkContext -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.listRecursively import io.sellmair.okay.moduleName import io.sellmair.okay.modulePath import io.sellmair.okay.zip.zipFiles import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.relativeTo -import kotlin.io.path.walk suspend fun OkContext.kotlinJar( jarManifestAttributes: Map> = emptyMap(), ): OkPath { val outputDir = kotlinCompile() - val files = outputDir.system().walk().associate { file -> - val relativePath = file.relativeTo(outputDir.system()) - relativePath.toString() to file.ok() + val files = outputDir.listRecursively().associateBy { file -> + file.relativeTo(outputDir) } val manifest = buildString { diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinPackage.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinPackage.kt index 94a98e2..885af60 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinPackage.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinPackage.kt @@ -4,11 +4,11 @@ import io.sellmair.okay.* import io.sellmair.okay.OkCoroutineDescriptor.Verbosity.Info import io.sellmair.okay.dependency.moduleOrNull import io.sellmair.okay.dependency.runtimeDependenciesClosure +import io.sellmair.okay.fs.* import io.sellmair.okay.input.OkInput import io.sellmair.okay.input.OkInputFile import io.sellmair.okay.input.OkInputProperty import io.sellmair.okay.input.OkInputs -import io.sellmair.okay.io.OkPath import io.sellmair.okay.io.copyFile import io.sellmair.okay.maven.mavenResolveRuntimeDependencies import io.sellmair.okay.output.OkOutput @@ -33,7 +33,7 @@ suspend fun OkContext.kotlinPackage(): OkPath { val classPath = async { (mavenRuntimeDependencies.await() + moduleDependencies.await()).map { path -> - path.system().relativeTo(packageDir.system()) + path.relativeTo(packageDir) }.joinToString(" ") } @@ -48,7 +48,7 @@ suspend fun OkContext.kotlinPackage(): OkPath { ) ) - copyFile(jarFile, packageDir.resolve(jarFile.system().name)) + copyFile(jarFile, packageDir.resolve(jarFile.name)) log("Packaged Application in '$ansiGreen$packageDir$ansiReset'") packageDir } @@ -66,8 +66,8 @@ suspend fun OkContext.packageModuleDependencies(packageDir: OkPath): List async { val fromFile = dependencyModuleJar.await() - val targetFile = packageDir.resolve("libs").resolve(fromFile.system().name) - targetFile.system().createParentDirectories() + val targetFile = packageDir.resolve("libs").resolve(fromFile.name) + targetFile.createParentDirectories() copyFile(fromFile, targetFile) } }.awaitAll() @@ -77,8 +77,8 @@ suspend fun OkContext.packageModuleDependencies(packageDir: OkPath): List { val runtimeDependencies = mavenResolveRuntimeDependencies() val destinationFiles = runtimeDependencies.map { file -> - packageDir.system().resolve("libs/${file.system().name}") - }.map { it.ok() } + packageDir.resolve("libs/${file.name}") + } return cachedCoroutine( describeCoroutine("copyMavenRuntimeDependencies"), @@ -86,9 +86,9 @@ suspend fun OkContext.packageMavenRuntimeDependencies(packageDir: OkPath): List< output = OkOutput.none(), ) { runtimeDependencies.forEach { file -> - val targetFile = packageDir.system().resolve("libs/${file.system().name}") + val targetFile = packageDir.resolve("libs/${file.name}") targetFile.createParentDirectories() - file.system().copyTo(targetFile, true) + file.copyTo(targetFile) } destinationFiles @@ -109,14 +109,9 @@ suspend fun OkContext.packageStartScript(packageDir: OkPath): OkPath { input = OkInputProperty("scriptContent", scriptContent), output = OkOutputFile(scriptFile) ) { - scriptFile.system().createParentDirectories() - scriptFile.system().writeText(scriptContent) - - runCatching { - scriptFile.system().setPosixFilePermissions( - scriptFile.system().getPosixFilePermissions() + PosixFilePermission.OWNER_EXECUTE - ) - } + scriptFile.createParentDirectories() + scriptFile.writeText(scriptContent) + scriptFile.setIsExecutable(true) scriptFile } diff --git a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinRun.kt b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinRun.kt index 97f70cf..9b60ab4 100644 --- a/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinRun.kt +++ b/core/src/main/kotlin/io/sellmair/okay/kotlin/kotlinRun.kt @@ -2,17 +2,18 @@ package io.sellmair.okay.kotlin import io.sellmair.okay.* import io.sellmair.okay.OkCoroutineDescriptor.Verbosity.Info +import io.sellmair.okay.fs.absolutePathString +import io.sellmair.okay.fs.isRegularFile +import io.sellmair.okay.fs.readText import io.sellmair.okay.input.OkInputFile import io.sellmair.okay.maven.mavenResolveRuntimeDependencies import io.sellmair.okay.utils.log import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* -import org.jetbrains.kotlin.konan.file.use import java.net.URLClassLoader import java.util.* import kotlin.concurrent.thread -import kotlin.io.path.exists -import kotlin.io.path.inputStream +import kotlin.io.path.Path suspend fun OkContext.kotlinRun(target: String? = null, arguments: List = emptyList()): Thread = withOkStack( @@ -28,9 +29,9 @@ suspend fun OkContext.kotlinRun(target: String? = null, arguments: List val loader = URLClassLoader.newInstance( - mavenDependencies.await().map { it.system().toUri().toURL() }.toTypedArray() + - moduleDependencies.await().map { it.system().toUri().toURL() } + - compiled.await().system().toUri().toURL() + mavenDependencies.await().map { Path(it.absolutePathString()).toUri().toURL() }.toTypedArray() + + moduleDependencies.await().map { Path(it.absolutePathString()).toUri().toURL() } + + Path(compiled.await().absolutePathString()).toUri().toURL() ) log("run: $className.$functionName()") @@ -77,13 +78,12 @@ internal suspend fun OkContext.parseKotlinRunOptions(target: String? = null): Ko describeCoroutine("parseKotlinRunOptions"), input = OkInputFile(runConfigurationFile) ) coroutine@{ - if (!runConfigurationFile.system().exists()) { + if (!runConfigurationFile.isRegularFile()) { return@coroutine null } - val parsed = runConfigurationFile.system().inputStream().buffered().use { inputStream -> - Json.decodeFromStream(inputStream) - } + val parsed = Json.decodeFromString(runConfigurationFile.readText()) + if (target == null && parsed is JsonObject) { return@coroutine parsed.toKotlinRunOptions() diff --git a/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependencies.kt b/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependencies.kt index 5385d8b..69d81d4 100644 --- a/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependencies.kt +++ b/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependencies.kt @@ -5,7 +5,7 @@ import io.sellmair.okay.OkCoroutineDescriptor.Verbosity.Info import io.sellmair.okay.dependency.compileDependenciesClosure import io.sellmair.okay.dependency.runtimeDependenciesClosure import io.sellmair.okay.input.OkInput -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.output.OkOutput diff --git a/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependency.kt b/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependency.kt index c206316..50bff65 100644 --- a/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependency.kt +++ b/core/src/main/kotlin/io/sellmair/okay/maven/mavenResolveDependency.kt @@ -6,7 +6,9 @@ import io.sellmair.okay.cachedCoroutine import io.sellmair.okay.describeRootCoroutine import io.sellmair.okay.input.OkInput import io.sellmair.okay.input.OkInputString -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath +import io.sellmair.okay.fs.createParentDirectories +import io.sellmair.okay.fs.sink import io.sellmair.okay.utils.ansiGreen import io.sellmair.okay.utils.ansiReset import io.sellmair.okay.utils.log @@ -35,7 +37,7 @@ suspend fun OkContext.mavenResolveDependency( ) { log("Downloading '$ansiGreen$mavenCoordinates$ansiReset'") - outputFile.system().createParentDirectories() + outputFile.createParentDirectories() val urlString = buildString { append("https://repo1.maven.org/maven2/") // repo @@ -48,8 +50,8 @@ suspend fun OkContext.mavenResolveDependency( mavenResolvePom(mavenCoordinates) URI.create(urlString).toURL().openStream().use { inputStream -> - outputFile.system().outputStream().use { outputStream -> - inputStream.copyTo(outputStream) + outputFile.sink().outputStream().use { out -> + inputStream.copyTo(out) } } diff --git a/core/src/main/kotlin/io/sellmair/okay/output/OkOutputDirectory.kt b/core/src/main/kotlin/io/sellmair/okay/output/OkOutputDirectory.kt index eeeeba8..9ff04cd 100644 --- a/core/src/main/kotlin/io/sellmair/okay/output/OkOutputDirectory.kt +++ b/core/src/main/kotlin/io/sellmair/okay/output/OkOutputDirectory.kt @@ -2,13 +2,13 @@ package io.sellmair.okay.output import io.sellmair.okay.OkContext import io.sellmair.okay.OkHash -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.io.directoryStateHash import kotlinx.serialization.Serializable @Serializable data class OkOutputDirectory(val path: OkPath) : OkOutput { override suspend fun currentHash(ctx: OkContext): OkHash { - return path.system().directoryStateHash() + return path.directoryStateHash() } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/output/OkOutputFile.kt b/core/src/main/kotlin/io/sellmair/okay/output/OkOutputFile.kt index a3f1452..bfd4e44 100644 --- a/core/src/main/kotlin/io/sellmair/okay/output/OkOutputFile.kt +++ b/core/src/main/kotlin/io/sellmair/okay/output/OkOutputFile.kt @@ -2,9 +2,8 @@ package io.sellmair.okay.output import io.sellmair.okay.OkContext import io.sellmair.okay.OkHash -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import io.sellmair.okay.io.regularFileStateHash -import kotlinx.serialization.Serializer import java.io.Serializable fun OkPath.asOutput(): OkOutputFile = OkOutputFile(this) @@ -12,6 +11,6 @@ fun OkPath.asOutput(): OkOutputFile = OkOutputFile(this) @kotlinx.serialization.Serializable data class OkOutputFile(val path: OkPath) : OkOutput, Serializable { override suspend fun currentHash(ctx: OkContext): OkHash { - return path.system().regularFileStateHash() + return path.regularFileStateHash() } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/utils/log.kt b/core/src/main/kotlin/io/sellmair/okay/utils/log.kt index 1092666..ab445d9 100644 --- a/core/src/main/kotlin/io/sellmair/okay/utils/log.kt +++ b/core/src/main/kotlin/io/sellmair/okay/utils/log.kt @@ -15,7 +15,7 @@ suspend fun log(value: String) { val stack = currentCoroutineContext().okStack val lastFrame = stack.lastOrNull { it.verbosity >= Verbosity.Info } ?: return val title = lastFrame.title - val module = lastFrame.module.toString().ifBlank { "" } + val module = if (lastFrame.module.parent == null) "" else lastFrame.module.toString() val logger = currentCoroutineContext()[OkLogger] ?: OkStdLogger logger.log(module, title, value) diff --git a/core/src/main/kotlin/io/sellmair/okay/zip/zipFiles.kt b/core/src/main/kotlin/io/sellmair/okay/zip/zipFiles.kt index 523d309..76e21e8 100644 --- a/core/src/main/kotlin/io/sellmair/okay/zip/zipFiles.kt +++ b/core/src/main/kotlin/io/sellmair/okay/zip/zipFiles.kt @@ -1,10 +1,10 @@ package io.sellmair.okay.zip import io.sellmair.okay.* +import io.sellmair.okay.fs.* import io.sellmair.okay.input.OkInputFile import io.sellmair.okay.input.asInput import io.sellmair.okay.input.plus -import io.sellmair.okay.io.OkPath import io.sellmair.okay.output.OkOutputFile import java.nio.file.Files import java.util.zip.ZipEntry @@ -37,14 +37,17 @@ suspend fun OkContext.zipFiles( input = files.values.map { OkInputFile(it) }.asInput() + layoutInputHash.asInput(), output = OkOutputFile(zipFile) ) { - zipFile.system().createParentDirectories() - ZipOutputStream(zipFile.system().outputStream().buffered()).use { out -> + zipFile.createParentDirectories() + + ZipOutputStream(zipFile.sink().outputStream().buffered()).use { out -> files.forEach { (name, file) -> - val sanitizedName = if (file.system().isDirectory() && !name.endsWith("/")) name + "/" + val sanitizedName = if (file.isDirectory() && !name.endsWith("/")) name + "/" else name out.putNextEntry(ZipEntry(sanitizedName)) - Files.copy(file.system(), out) + if (file.isRegularFile()) { + file.source { source -> source.inputStream().copyTo(out) } + } out.closeEntry() } diff --git a/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/CacheTest.kt b/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/CacheTest.kt index 0b0eec3..f972698 100644 --- a/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/CacheTest.kt +++ b/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/CacheTest.kt @@ -66,6 +66,7 @@ class CacheTest { assertCacheUpToDate(rootPath(), "kotlinCompile") } + println("Deleted 'new.kt'") newFile.deleteIfExists() runOkTest(OkRoot(tempDir)) { kotlinCompile() diff --git a/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/MavenResolveDependenciesTest.kt b/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/MavenResolveDependenciesTest.kt index 7f0e744..8f73aa7 100644 --- a/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/MavenResolveDependenciesTest.kt +++ b/core/src/test/kotlin/io/sellmair/okay/tests/integrationTests/MavenResolveDependenciesTest.kt @@ -4,6 +4,7 @@ package io.sellmair.okay.tests.integrationTests import io.sellmair.okay.OkRoot import io.sellmair.okay.async +import io.sellmair.okay.fs.isRegularFile import io.sellmair.okay.io.walk import io.sellmair.okay.maven.mavenResolveCompileDependencies import io.sellmair.okay.maven.mavenResolveRuntimeDependencies @@ -44,7 +45,7 @@ class MavenResolveDependenciesTest { runOkTest(OkRoot(projectDir)) { mavenResolveCompileDependencies() val expectedCoroutinesJar = path(".okay/libs/maven/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.7.3.jar") - if (!expectedCoroutinesJar.system().exists()) { + if (!expectedCoroutinesJar.isRegularFile()) { val actualLibraries = path(".okay/libs/maven").walk().resolve(ctx).joinToString("\n") fail("Missing 'kotlinx-coroutines-core-jvm.jar'. Found:\n$actualLibraries") } diff --git a/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkFileCollectionTest.kt b/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkFileCollectionTest.kt new file mode 100644 index 0000000..4eaa3d2 --- /dev/null +++ b/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkFileCollectionTest.kt @@ -0,0 +1,48 @@ +package io.sellmair.okay.tests.unitTests + +import io.sellmair.okay.OkRoot +import io.sellmair.okay.fs.absolutePathString +import io.sellmair.okay.io.walk +import io.sellmair.okay.io.withExtension +import io.sellmair.okay.path +import io.sellmair.okay.utils.runOkTest +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertEquals + +class OkFileCollectionTest { + + @TempDir + lateinit var tempDir: Path + + @Test + fun `test - filtered file collection`() { + val aKt = tempDir.resolve("foo/bar/a.kt") + val bKt = tempDir.resolve("foo/bar/b.kt") + val cKt = tempDir.resolve("foo/c.kt") + val dKt = tempDir.resolve("d.kt") + + val aTxt = tempDir.resolve("foo/bar/a.txt") + val bTxt = tempDir.resolve("b.txt") + + tempDir.resolve("foo/bar").createDirectories() + aKt.writeText("class A") + bKt.writeText("class B") + cKt.writeText("class C") + dKt.writeText("class D") + aTxt.writeText("a") + bTxt.writeText("b") + + runOkTest(OkRoot(tempDir)) { + val resolved = path("").walk().withExtension("kt").resolve(ctx).toList() + assertEquals( + listOf(dKt, aKt, bKt, cKt).map { it.absolutePathString() }, resolved.map { it.absolutePathString() } + ) + } + + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkPathTest.kt b/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkPathTest.kt index 446e7d7..8fc5eaf 100644 --- a/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkPathTest.kt +++ b/core/src/test/kotlin/io/sellmair/okay/tests/unitTests/OkPathTest.kt @@ -1,30 +1,39 @@ package io.sellmair.okay.tests.unitTests import io.sellmair.okay.OkRoot +import io.sellmair.okay.fs.absolutePathString +import io.sellmair.okay.moduleName import io.sellmair.okay.path import io.sellmair.okay.utils.runOkTest +import io.sellmair.okay.withOkModule import org.junit.jupiter.api.Test +import java.io.File import kotlin.io.path.Path +import kotlin.io.path.absolutePathString import kotlin.test.assertEquals class OkPathTest { @Test fun `test - conversion to system path`() = runOkTest { val okFoo = path("foo") - assertEquals(Path("foo"), okFoo.system()) - assertEquals(okFoo, okFoo.system().ok()) + assertEquals(Path("foo").absolutePathString(), okFoo.absolutePathString()) } @Test - fun `test - conversion to system path - with custom root`() = runOkTest(OkRoot(Path("path/to/project"))) { - val okFoo = path("foo") - assertEquals(Path("path/to/project/foo"), okFoo.system()) - assertEquals(okFoo, okFoo.system().ok()) + fun `test - moduleName`() { + runOkTest { + assertEquals("okay", moduleName()) + assertEquals(File(".").canonicalFile.name, moduleName()) + } } @Test - fun `test - absolutePath`() = runOkTest(OkRoot(Path("path/to/project"))) { - val okFoo = path("/my/absolute/path") - assertEquals(Path("/my/absolute/path"), okFoo.system()) + fun `test - moduleName - nested`() { + runOkTest(OkRoot(Path("some/root"))) { + assertEquals("root", moduleName()) + withOkModule(path("myModule")) { + assertEquals("myModule", moduleName()) + } + } } } \ No newline at end of file diff --git a/core/src/test/kotlin/io/sellmair/okay/utils/OkTestCoroutineCacheHook.kt b/core/src/test/kotlin/io/sellmair/okay/utils/OkTestCoroutineCacheHook.kt index eb25422..62a5044 100644 --- a/core/src/test/kotlin/io/sellmair/okay/utils/OkTestCoroutineCacheHook.kt +++ b/core/src/test/kotlin/io/sellmair/okay/utils/OkTestCoroutineCacheHook.kt @@ -4,7 +4,7 @@ import io.sellmair.okay.* import io.sellmair.okay.OkCacheMiss import io.sellmair.okay.OkCacheResult import io.sellmair.okay.OkCoroutineCacheHook -import io.sellmair.okay.io.OkPath +import io.sellmair.okay.fs.OkPath import kotlinx.coroutines.currentCoroutineContext import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock diff --git a/okay-fs/build.gradle.kts b/okay-fs/build.gradle.kts index 3e74074..9286e3e 100644 --- a/okay-fs/build.gradle.kts +++ b/okay-fs/build.gradle.kts @@ -13,12 +13,12 @@ kotlin { macosArm64() linuxArm64() linuxX64() - + sourceSets.commonMain.dependencies { - implementation("com.squareup.okio:okio:3.9.0") + api("com.squareup.okio:okio:3.9.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3") } - + sourceSets.commonTest.dependencies { implementation(kotlin("test")) implementation("com.squareup.okio:okio-fakefilesystem:3.9.0") @@ -34,6 +34,6 @@ tasks.withType().configureEach { this.showStackTraces = true this.exceptionFormat = TestExceptionFormat.FULL } - - + + } \ No newline at end of file diff --git a/core/src/main/kotlin/io/sellmair/okay/OkHash.kt b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/OkHash.kt similarity index 65% rename from core/src/main/kotlin/io/sellmair/okay/OkHash.kt rename to okay-fs/src/commonMain/kotlin/io/sellmair/okay/OkHash.kt index 51b9ca4..d2f6409 100644 --- a/core/src/main/kotlin/io/sellmair/okay/OkHash.kt +++ b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/OkHash.kt @@ -1,7 +1,8 @@ package io.sellmair.okay -import io.sellmair.okay.io.OkPath -import java.security.MessageDigest +import io.sellmair.okay.fs.OkPath +import okio.* +import okio.HashingSink.Companion.sha256 import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi @@ -11,7 +12,7 @@ fun OkHash(hash: ByteArray): OkHash { } @kotlinx.serialization.Serializable -data class OkHash(val value: String) { +data class OkHash(val value: String) { override fun toString(): String { return value.take(6) } @@ -22,19 +23,26 @@ fun HashBuilder(): HashBuilder = HashBuilderImpl() interface HashBuilder { fun push(value: String) + fun push(value: ByteArray) + fun push(value: ByteArray, offset: Int, length: Int) + fun push(value: Boolean) + fun push(value: Byte) + fun push(value: OkHash) + fun push(value: OkPath) + + fun push(value: Source) + fun build(): OkHash } fun hash(value: ByteArray): OkHash { - val sha265 = MessageDigest.getInstance("SHA-256") - sha265.update(value) - return OkHash(sha265.digest()) + return hash { push(value) } } fun hash(value: String): OkHash { @@ -53,39 +61,50 @@ fun Iterable.hash(): OkHash = hash { forEach { value -> push(value) } } -class HashBuilderImpl( - private val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") -) : HashBuilder { +class HashBuilderImpl : HashBuilder { + + private val hashingSink = sha256(blackholeSink()) + + private val buffer = sha256(hashingSink).buffer() override fun push(value: String) { - messageDigest.update(value.encodeToByteArray()) + buffer.writeUtf8(value) } override fun push(value: ByteArray) { - messageDigest.update(value) + buffer.write(value) } override fun push(value: ByteArray, offset: Int, length: Int) { - messageDigest.update(value, offset, length) + buffer.write(value, offset, length) } override fun push(value: Boolean) { - messageDigest.update(if (value) 1 else 0) + buffer.writeByte(if (value) 1 else 0) } override fun push(value: Byte) { - messageDigest.update(value) + buffer.writeByte(value.toInt()) } override fun push(value: OkHash) { - push(value.value) + buffer.writeUtf8(value.value) } override fun push(value: OkPath) { push(value.toString()) } + override fun push(value: Source) { + try { + buffer.writeAll(value) + } finally { + value.close() + } + } + override fun build(): OkHash { - return OkHash(messageDigest.digest()) + buffer.close() + return OkHash(hashingSink.hash.base64Url()) } } diff --git a/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkFs.kt b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkFs.kt index 4d0e871..a0075fd 100644 --- a/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkFs.kt +++ b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkFs.kt @@ -1,92 +1,147 @@ package io.sellmair.okay.fs -import okio.FileSystem -import okio.Path +import okio.* import okio.Path.Companion.toPath -import okio.SYSTEM fun OkFs(root: String): OkFs { return OkFsImpl(root.toPath()) } sealed interface OkFs { - + fun path(path: String): OkPath = OkPath(path.toPath(), this) - + fun exists(path: OkPath): Boolean - + fun isRegularFile(path: OkPath): Boolean - + fun isDirectory(path: OkPath): Boolean - + fun absolutePath(path: OkPath): String - + fun createDirectories(path: OkPath) - + fun delete(path: OkPath) - + fun deleteRecursively(path: OkPath) - + fun write(path: OkPath, data: ByteArray) - -} -internal val OkFs.impl get() = when(this) { - is OkFsImpl -> this + fun list(path: OkPath): List + + fun listOrNull(path: OkPath): List? + + fun listRecursively(path: OkPath): Sequence + + fun copy(from: OkPath, to: OkPath) + + fun source(path: OkPath): BufferedSource + + fun sink(path: OkPath): BufferedSink + + fun setIsExecutable(path: OkPath, isExecutable: Boolean) } +internal val OkFs.impl + get() = when (this) { + is OkFsImpl -> this + } + internal class OkFsImpl( - val root: Path, private val delegate: FileSystem = FileSystem.SYSTEM -): OkFs { - + val root: Path, private val fs: FileSystem = FileSystem.SYSTEM +) : OkFs { + private val OkPath.resolved: Path get() = root.resolve(relative) - + override fun exists(path: OkPath): Boolean { - return delegate.exists(path.resolved) + return fs.exists(path.resolved) } override fun isRegularFile(path: OkPath): Boolean { - return delegate.metadataOrNull(path.resolved)?.isRegularFile == true + return fs.metadataOrNull(path.resolved)?.isRegularFile == true } override fun isDirectory(path: OkPath): Boolean { - return delegate.metadataOrNull(path.resolved)?.isDirectory == true + return fs.metadataOrNull(path.resolved)?.isDirectory == true } override fun absolutePath(path: OkPath): String { - return path.resolved.toString() + return fs.canonicalize(".".toPath()).resolve(path.resolved).normalized().toString() } override fun createDirectories(path: OkPath) { - delegate.createDirectories(path.resolved, mustCreate = true) + fs.createDirectories(path.resolved, mustCreate = false) } override fun delete(path: OkPath) { - delegate.delete(path.resolved, mustExist = false) + fs.delete(path.resolved, mustExist = false) } override fun deleteRecursively(path: OkPath) { - delegate.deleteRecursively(path.resolved, mustExist = false) + fs.deleteRecursively(path.resolved, mustExist = false) } override fun write(path: OkPath, data: ByteArray) { - delegate.write(path.resolved, mustCreate = false) { + fs.write(path.resolved, mustCreate = false) { this.write(data) } } + override fun list(path: OkPath): List { + return fs.list(path.resolved).map { child -> OkPath(child.relativeTo(root), this) } + } + + override fun listOrNull(path: OkPath): List? { + return fs.listOrNull(path.resolved)?.map { child -> OkPath(child.relativeTo(root), this) } + } + + override fun listRecursively(path: OkPath): Sequence { + return fs.listRecursively(path.resolved, true).map { child -> + // https://github.com/square/okio/issues/1468 + if (root == ".".toPath()) OkPath(child, this) + else OkPath(child.relativeTo(root), this) + }.emptyOnError() + } + + override fun copy(from: OkPath, to: OkPath) { + require(from.fs == to.fs) { "Expected paths fo of the same file system " } + fs.copy(from.resolved, to.resolved) + } + + override fun source(path: OkPath): BufferedSource { + return fs.source(path.resolved).buffer() + } + + override fun sink(path: OkPath): BufferedSink { + return fs.sink(path.resolved, false).buffer() + } + + override fun setIsExecutable(path: OkPath, isExecutable: Boolean) { + return fs.setIsExecutable(path.resolved, isExecutable) + } + override fun equals(other: Any?): Boolean { - if(this === other) return true - if(other !is OkFsImpl) return false - if(this.root != other.root) return false - if(delegate::class != other.delegate::class) return false + if (this === other) return true + if (other !is OkFsImpl) return false + if (this.root != other.root) return false + if (fs::class != other.fs::class) return false return true } override fun hashCode(): Int { var hash = root.hashCode() - hash = 31 * hash + delegate::class.hashCode() + hash = 31 * hash + fs::class.hashCode() return hash } } + +expect fun FileSystem.setIsExecutable(path: Path, isExecutable: Boolean) + +private fun Sequence.emptyOnError() = sequence { + try { + yieldAll(this@emptyOnError) + } catch (t: Throwable) { + // sorry folks. + } +} \ No newline at end of file diff --git a/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkPath.kt b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkPath.kt index 8b294f8..5e41841 100644 --- a/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkPath.kt +++ b/okay-fs/src/commonMain/kotlin/io/sellmair/okay/fs/OkPath.kt @@ -1,7 +1,10 @@ package io.sellmair.okay.fs import kotlinx.serialization.Serializable +import okio.BufferedSink +import okio.BufferedSource import okio.Path +import okio.use @Serializable(OkPathSerializer::class) class OkPath internal constructor( @@ -15,6 +18,19 @@ class OkPath internal constructor( val parent: OkPath? get() = relative.parent?.let { parentPath -> OkPath(parentPath, fs) } + val extension: String + get() = relative.segments.lastOrNull() + ?.split(".")?.lastOrNull().orEmpty() + + val name: String + get() = relative.name + + fun resolve(child: String): OkPath = OkPath(relative.resolve(child), fs) + + fun relativeTo(other: OkPath): String { + return relative.relativeTo(other.relative).toString() + } + override fun toString(): String { return relative.toString() } @@ -40,6 +56,55 @@ fun OkPath.isDirectory(): Boolean = fs.isDirectory(this) fun OkPath.createDirectories() = fs.createDirectories(this) +fun OkPath.createParentDirectories() { + parent?.createDirectories() +} + fun OkPath.delete() = fs.delete(this) -fun OkPath.write(data: ByteArray) = fs.write(this, data) \ No newline at end of file +fun OkPath.deleteRecursively() = fs.deleteRecursively(this) + +fun OkPath.write(data: ByteArray) = fs.write(this, data) + +fun OkPath.writeText(value: String) = sink().use { sink -> + sink.writeUtf8(value) +} + +fun OkPath.readAll(): ByteArray { + return source().use { it.readByteArray() } +} + +fun OkPath.readText(): String { + return source().use { it.readUtf8() } +} + +fun OkPath.list(): List = fs.list(this) + +fun OkPath.listOrNull(): List? = fs.listOrNull(this) + +fun OkPath.listRecursively(): Sequence = fs.listRecursively(this) + +fun OkPath.absolutePathString(): String = fs.absolutePath(this) + +fun OkPath.setIsExecutable(isExecutable: Boolean) = fs.setIsExecutable(this, isExecutable) + +fun OkPath.copyTo(other: OkPath): OkPath { + fs.copy(this, other) + return other +} + +fun OkPath.source(): BufferedSource { + return fs.source(this) +} + +fun OkPath.source(use: (sink: BufferedSource) -> Unit) { + source().use(use) +} + +fun OkPath.sink(): BufferedSink { + return fs.sink(this) +} + +fun OkPath.sink(use: (sink: BufferedSink) -> Unit) { + sink().use(use) +} \ No newline at end of file diff --git a/okay-fs/src/jvmMain/kotlin/io/sellmair/okay/fs/OkFs.jvm.kt b/okay-fs/src/jvmMain/kotlin/io/sellmair/okay/fs/OkFs.jvm.kt new file mode 100644 index 0000000..6e4906b --- /dev/null +++ b/okay-fs/src/jvmMain/kotlin/io/sellmair/okay/fs/OkFs.jvm.kt @@ -0,0 +1,16 @@ +package io.sellmair.okay.fs + +import okio.FileSystem +import okio.Path +import java.nio.file.attribute.PosixFilePermission +import kotlin.io.path.getPosixFilePermissions +import kotlin.io.path.setPosixFilePermissions + +actual fun FileSystem.setIsExecutable(path: Path, isExecutable: Boolean) { + runCatching { + path.toNioPath().setPosixFilePermissions( + if (isExecutable) path.toNioPath().getPosixFilePermissions() + PosixFilePermission.OWNER_EXECUTE + else path.toNioPath().getPosixFilePermissions() - PosixFilePermission.OWNER_EXECUTE + ) + } +} \ No newline at end of file diff --git a/okay-fs/src/nativeMain/kotlin/io/sellmair/okay/fs/OkFs.native.kt b/okay-fs/src/nativeMain/kotlin/io/sellmair/okay/fs/OkFs.native.kt new file mode 100644 index 0000000..f42b03c --- /dev/null +++ b/okay-fs/src/nativeMain/kotlin/io/sellmair/okay/fs/OkFs.native.kt @@ -0,0 +1,8 @@ +package io.sellmair.okay.fs + +import okio.FileSystem +import platform.posix.chmod + +actual fun FileSystem.setIsExecutable(path: okio.Path, isExecutable: Boolean) { + // TODO +} \ No newline at end of file