[K/N] Merge :kotlin-native-shared with :native:kotlin-native-utils

* Code was moved to utils, but sources are included to the shared
until bootstrap advance.
* Fixed dependencies and set API & LV to 1.4 for the modules used with
Gradle.

Merge-request: KT-MR-9122
Merged-by: Pavel Punegov <Pavel.Punegov@jetbrains.com>
This commit is contained in:
Pavel Punegov
2023-03-10 12:57:35 +00:00
committed by Space Team
parent 633d840c88
commit aed6272107
43 changed files with 38 additions and 25 deletions
+6 -2
View File
@@ -8,11 +8,13 @@ description = "Kotlin/Native utils"
dependencies {
compileOnly(kotlinStdlib())
api(project(":kotlin-util-io"))
api(project(":kotlin-util-klib"))
api(platform(project(":kotlin-gradle-plugins-bom")))
testImplementation(commonDependency("junit:junit"))
testImplementation(kotlinStdlib())
testImplementation(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
testApiJUnit5()
}
sourceSets {
@@ -28,10 +30,12 @@ tasks {
freeCompilerArgs += "-Xsuppress-version-warnings"
}
}
withType<Test>().configureEach {
useJUnitPlatform()
}
}
// TODO: this single known external consumer of this artifact is Kotlin/Native backend,
// so publishing could be stopped after migration to monorepo
publish()
standardPublicJars()
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan
/**
* This is a common ancestor of all Kotlin/Native exceptions.
*/
open class KonanException(message: String = "", cause: Throwable? = null) : Exception(message, cause)
/**
* An error occurred during external tool invocation. Such as non-zero exit code.
*/
class KonanExternalToolFailure(message: String, val toolName: String, cause: Throwable? = null) : KonanException(message, cause)
/**
* An exception indicating a failed attempt to access some parts of Xcode (e.g. get SDK paths or version).
*/
class MissingXcodeException(message: String, cause: Throwable? = null) : KonanException(message, cause)
/**
* Native exception handling in Kotlin: terminate, wrap, etc.
* Foreign exceptionMode mode is per library option: controlled by cinterop command-line option or def file property
* than stored in klib manifest and used by compiler to generate appropriate handler.
*/
class ForeignExceptionMode {
companion object {
val manifestKey = "foreignExceptionMode"
val default = Mode.TERMINATE
fun byValue(value: String?): Mode = value?.let {
Mode.values().find { it.value == value }
?: throw IllegalArgumentException("Illegal ForeignExceptionMode $value")
} ?: default
}
enum class Mode(val value: String) {
TERMINATE("terminate"),
OBJC_WRAP("objc-wrap")
}
}
@@ -0,0 +1,28 @@
/**
* Copyright 2010-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan
fun String.parseKonanAbiVersion(): KonanAbiVersion {
return KonanAbiVersion(this.toInt())
}
data class KonanAbiVersion(val version: Int) {
companion object {
val CURRENT = KonanAbiVersion(10)
}
override fun toString() = "$version"
}
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.file.*
/**
* Creates and stores temporary compiler outputs
* If pathToTemporaryDir is given and is not empty then temporary outputs will be preserved
*/
class TempFiles(pathToTemporaryDir: String? = null) {
fun dispose() {
if (deleteOnExit) {
// Note: this can throw an exception if a file deletion is failed for some reason (e.g. OS is Windows and the file is in use).
dir.deleteRecursively()
}
}
val deleteOnExit = pathToTemporaryDir == null || pathToTemporaryDir.isEmpty()
private val dir by lazy {
if (deleteOnExit) {
createTempDir("konan_temp")
} else {
createDirForTemporaryFiles(pathToTemporaryDir!!)
}
}
private fun createDirForTemporaryFiles(path: String): File {
if (File(path).isFile) {
throw IllegalArgumentException("Given file is not a directory: $path")
}
return File(path).apply {
if (!exists) { mkdirs() }
}
}
/**
* Create file named {name}{suffix} inside temporary dir
*/
fun create(prefix: String, suffix: String = ""): File =
File(dir, "$prefix$suffix")
}
@@ -0,0 +1,138 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.exec
import org.jetbrains.kotlin.konan.KonanExternalToolFailure
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.lang.ProcessBuilder.Redirect
import java.nio.file.Files
open class Command(initialCommand: List<String>, val redirectInputFile: File? = null) {
constructor(tool: String) : this(listOf(tool))
constructor(vararg command: String) : this(command.toList<String>())
protected val command = initialCommand.toMutableList()
val argsWithExecutable: List<String> = command
val args: List<String>
get() = command.drop(1)
operator fun String.unaryPlus(): Command {
command += this
return this@Command
}
operator fun List<String>.unaryPlus(): Command {
command.addAll(this)
return this@Command
}
var logger: ((() -> String)->Unit)? = null
private var stdError: List<String> = emptyList()
fun logWith(newLogger: ((() -> String)->Unit)): Command {
logger = newLogger
return this
}
open fun runProcess(): Int {
stdError = emptyList()
val builder = ProcessBuilder(command)
builder.redirectOutput(Redirect.INHERIT)
if (redirectInputFile == null) {
builder.redirectInput(Redirect.INHERIT)
} else {
builder.redirectInput(redirectInputFile)
}
val process = builder.start()
val reader = BufferedReader(InputStreamReader(process.errorStream))
stdError = reader.readLines()
val exitCode = process.waitFor()
return exitCode
}
open fun execute() {
log()
val code = runProcess()
handleExitCode(code, stdError)
}
/**
* If withErrors is true then output from error stream will be added
*/
fun getOutputLines(withErrors: Boolean = false): List<String> =
getResult(withErrors, handleError = true).outputLines
fun getResult(withErrors: Boolean, handleError: Boolean = false): Result {
log()
val outputFile = Files.createTempFile(null, null).toFile()
outputFile.deleteOnExit()
try {
val builder = ProcessBuilder(command)
if (redirectInputFile == null) {
builder.redirectInput(Redirect.INHERIT)
} else {
builder.redirectInput(redirectInputFile)
}
builder.redirectError(Redirect.INHERIT)
builder.redirectOutput(Redirect.to(outputFile))
.redirectErrorStream(withErrors)
// Note: getting process output could be done without redirecting to temporary file,
// however this would require managing a thread to read `process.inputStream` because
// it may have limited capacity.
val process = builder.start()
val code = process.waitFor()
if (handleError) handleExitCode(code, outputFile.readLines())
return Result(code, outputFile.readLines())
} finally {
outputFile.delete()
}
}
class Result(val exitCode: Int, val outputLines: List<String>)
private fun handleExitCode(code: Int, output: List<String> = emptyList()) {
if (code != 0) throw KonanExternalToolFailure("""
The ${command[0]} command returned non-zero exit code: $code.
output:
""".trimIndent() + "\n${output.joinToString("\n")}", command[0])
// Show warnings in case of success linkage.
if (stdError.isNotEmpty()) {
stdError.joinToString("\n").also { message ->
logger?.let { it { message } } ?: println(message)
}
}
}
private fun log() {
if (logger != null) logger!! { command.joinToString(" ") }
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.file
val String.isJavaScript
get() = this.endsWith(".js")
val String.isUnixStaticLib
get() = this.endsWith(".a")
val String.isWindowsStaticLib
get() = this.endsWith(".lib")
val String.isBitcode
get() = this.endsWith(".bc")
@@ -0,0 +1,23 @@
package org.jetbrains.kotlin.konan.library
import org.jetbrains.kotlin.konan.properties.propertyList
import org.jetbrains.kotlin.library.*
const val KLIB_PROPERTY_LINKED_OPTS = "linkerOpts"
const val KLIB_PROPERTY_INCLUDED_HEADERS = "includedHeaders"
interface TargetedLibrary {
val targetList: List<String>
val includedPaths: List<String>
}
interface BitcodeLibrary : TargetedLibrary {
val bitcodePaths: List<String>
}
interface KonanLibrary : BitcodeLibrary, KotlinLibrary {
val linkerOpts: List<String>
}
val KonanLibrary.includedHeaders
get() = manifestProperties.propertyList(KLIB_PROPERTY_INCLUDED_HEADERS, escapeInQuotes = true)
@@ -0,0 +1,50 @@
/**
* Copyright 2010-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.library
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.IrKotlinLibraryLayout
import org.jetbrains.kotlin.library.KotlinLibraryLayout
import org.jetbrains.kotlin.library.MetadataKotlinLibraryLayout
interface TargetedKotlinLibraryLayout : KotlinLibraryLayout {
val target: KonanTarget?
// This is a default implementation. Can't make it an assignment.
get() = null
val targetsDir
get() = File(componentDir, "targets")
val targetDir
get() = File(targetsDir, target!!.visibleName)
val includedDir
get() = File(targetDir, "included")
}
interface BitcodeKotlinLibraryLayout : TargetedKotlinLibraryLayout, KotlinLibraryLayout {
val kotlinDir
get() = File(targetDir, "kotlin")
val nativeDir
get() = File(targetDir, "native")
// TODO: Experiment with separate bitcode files.
// Per package or per class.
val mainBitcodeFile
get() = File(kotlinDir, "program.kt.bc")
val mainBitcodeFileName
get() = mainBitcodeFile.path
}
interface KonanLibraryLayout : MetadataKotlinLibraryLayout, BitcodeKotlinLibraryLayout, IrKotlinLibraryLayout
@@ -0,0 +1,20 @@
/*
* 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 file.
*/
package org.jetbrains.kotlin.konan.library
import org.jetbrains.kotlin.library.BaseWriter
import org.jetbrains.kotlin.library.IrWriter
import org.jetbrains.kotlin.library.MetadataWriter
interface TargetedWriter {
fun addIncludedBinary(library: String)
}
interface BitcodeWriter : TargetedWriter {
fun addNativeBitcode(library: String)
}
interface KonanLibraryWriter : MetadataWriter, BaseWriter, IrWriter, BitcodeWriter
@@ -0,0 +1,92 @@
package org.jetbrains.kotlin.konan.library
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryImpl
import org.jetbrains.kotlin.konan.library.impl.createKonanLibraryComponents
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.util.DummyLogger
import org.jetbrains.kotlin.util.Logger
interface SearchPathResolverWithTarget<L: KotlinLibrary>: SearchPathResolver<L> {
val target: KonanTarget
}
fun defaultResolver(
repositories: List<String>,
target: KonanTarget,
distribution: Distribution
): SearchPathResolverWithTarget<KonanLibrary> = defaultResolver(repositories, emptyList(), target, distribution)
fun defaultResolver(
repositories: List<String>,
directLibs: List<String>,
target: KonanTarget,
distribution: Distribution,
logger: Logger = DummyLogger,
skipCurrentDir: Boolean = false
): SearchPathResolverWithTarget<KonanLibrary> = KonanLibraryProperResolver(
repositories,
directLibs,
target,
distribution.klib,
distribution.localKonanDir.absolutePath,
skipCurrentDir,
logger
)
fun resolverByName(
repositories: List<String>,
directLibs: List<String> = emptyList(),
distributionKlib: String? = null,
localKotlinDir: String? = null,
skipCurrentDir: Boolean = false,
logger: Logger
): SearchPathResolver<KotlinLibrary> =
object : KotlinLibrarySearchPathResolver<KotlinLibrary>(
repositories,
directLibs,
distributionKlib,
localKotlinDir,
skipCurrentDir,
logger
) {
override fun libraryComponentBuilder(file: File, isDefault: Boolean) = createKonanLibraryComponents(file, null, isDefault)
}
internal class KonanLibraryProperResolver(
repositories: List<String>,
directLibs: List<String>,
override val target: KonanTarget,
distributionKlib: String?,
localKonanDir: String?,
skipCurrentDir: Boolean,
override val logger: Logger
) : KotlinLibraryProperResolverWithAttributes<KonanLibrary>(
repositories, directLibs,
distributionKlib,
localKonanDir,
skipCurrentDir,
logger,
listOf(KLIB_INTEROP_IR_PROVIDER_IDENTIFIER)
), SearchPathResolverWithTarget<KonanLibrary>
{
override fun libraryComponentBuilder(file: File, isDefault: Boolean) = createKonanLibraryComponents(file, target, isDefault)
override val distPlatformHead: File?
get() = distributionKlib?.File()?.child("platform")?.child(target.visibleName)
override fun libraryMatch(candidate: KonanLibrary, unresolved: UnresolvedLibrary): Boolean {
val resolverTarget = this.target
val candidatePath = candidate.libraryFile.absolutePath
if (!candidate.targetList.contains(resolverTarget.visibleName)) {
logger.warning("skipping $candidatePath. The target doesn't match. Expected '$resolverTarget', found ${candidate.targetList}")
return false
}
return super.libraryMatch(candidate, unresolved)
}
}
@@ -0,0 +1,40 @@
/*
* 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 file.
*/
package org.jetbrains.kotlin.konan.library.impl
import org.jetbrains.kotlin.konan.library.BitcodeWriter
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.BitcodeKotlinLibraryLayout
import org.jetbrains.kotlin.konan.library.TargetedKotlinLibraryLayout
open class TargetedWriterImpl(val targetLayout: TargetedKotlinLibraryLayout) {
init {
targetLayout.targetDir.mkdirs()
targetLayout.includedDir.mkdirs()
}
fun addIncludedBinary(library: String) {
val basename = File(library).name
File(library).copyTo(File(targetLayout.includedDir, basename))
}
}
class BitcodeWriterImpl(
libraryLayout: BitcodeKotlinLibraryLayout
) : BitcodeWriter, TargetedWriterImpl(libraryLayout) {
val bitcodeLayout = libraryLayout
init {
bitcodeLayout.kotlinDir.mkdirs()
bitcodeLayout.nativeDir.mkdirs()
}
override fun addNativeBitcode(library: String) {
val basename = File(library).name
File(library).copyTo(File(bitcodeLayout.nativeDir, basename))
}
}
@@ -0,0 +1,114 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.library.impl
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.properties.Properties
import org.jetbrains.kotlin.konan.properties.loadProperties
import org.jetbrains.kotlin.konan.properties.propertyList
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.util.defaultTargetSubstitutions
import org.jetbrains.kotlin.konan.util.substitute
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.*
open class TargetedLibraryImpl(
private val access: TargetedLibraryAccess<TargetedKotlinLibraryLayout>,
private val base: BaseKotlinLibrary
) : TargetedLibrary, BaseKotlinLibrary by base {
private val target: KonanTarget? get() = access.target
override val targetList: List<String>
get() = commonizerNativeTargets?.takeIf { it.isNotEmpty() }
?: nativeTargets.takeIf { it.isNotEmpty() }
?: // TODO: We have a choice: either assume it is the CURRENT TARGET
// or a list of ALL KNOWN targets.
listOfNotNull(access.target?.visibleName)
override val manifestProperties: Properties by lazy {
val properties = base.manifestProperties
target?.let { substitute(properties, defaultTargetSubstitutions(it)) }
properties
}
override val includedPaths: List<String>
get() = access.realFiles {
it.includedDir.listFilesOrEmpty.map { it.absolutePath }
}
}
open class BitcodeLibraryImpl(
private val access: BitcodeLibraryAccess<BitcodeKotlinLibraryLayout>,
targeted: TargetedLibrary
) : BitcodeLibrary, TargetedLibrary by targeted {
override val bitcodePaths: List<String>
get() = access.realFiles { it: BitcodeKotlinLibraryLayout ->
(it.kotlinDir.listFilesOrEmpty + it.nativeDir.listFilesOrEmpty).map { it.absolutePath }
}
}
class KonanLibraryImpl(
targeted: TargetedLibraryImpl,
metadata: MetadataLibraryImpl,
ir: IrLibraryImpl,
bitcode: BitcodeLibraryImpl
) : KonanLibrary,
BaseKotlinLibrary by targeted,
MetadataLibrary by metadata,
IrLibrary by ir,
BitcodeLibrary by bitcode {
override val linkerOpts: List<String>
get() = manifestProperties.propertyList(KLIB_PROPERTY_LINKED_OPTS, escapeInQuotes = true)
}
fun createKonanLibrary(
libraryFile: File,
component: String,
target: KonanTarget? = null,
isDefault: Boolean = false
): KonanLibrary {
val baseAccess = BaseLibraryAccess<KotlinLibraryLayout>(libraryFile, component)
val targetedAccess = TargetedLibraryAccess<TargetedKotlinLibraryLayout>(libraryFile, component, target)
val metadataAccess = MetadataLibraryAccess<MetadataKotlinLibraryLayout>(libraryFile, component)
val irAccess = IrLibraryAccess<IrKotlinLibraryLayout>(libraryFile, component)
val bitcodeAccess = BitcodeLibraryAccess<BitcodeKotlinLibraryLayout>(libraryFile, component, target)
val base = BaseKotlinLibraryImpl(baseAccess, isDefault)
val targeted = TargetedLibraryImpl(targetedAccess, base)
val metadata = MetadataLibraryImpl(metadataAccess)
val ir = IrMonoliticLibraryImpl(irAccess)
val bitcode = BitcodeLibraryImpl(bitcodeAccess, targeted)
return KonanLibraryImpl(targeted, metadata, ir, bitcode)
}
fun createKonanLibraryComponents(
libraryFile: File,
target: KonanTarget? = null,
isDefault: Boolean = true
) : List<KonanLibrary> {
val baseAccess = BaseLibraryAccess<KotlinLibraryLayout>(libraryFile, null)
val base = BaseKotlinLibraryImpl(baseAccess, isDefault)
return base.componentList.map {
createKonanLibrary(libraryFile, it, target, isDefault)
}
}
@@ -0,0 +1,61 @@
package org.jetbrains.kotlin.konan.library.impl
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.*
import java.nio.file.FileSystem
open class TargetedLibraryLayoutImpl(klib: File, component: String, override val target: KonanTarget?) :
KotlinLibraryLayoutImpl(klib, component), TargetedKotlinLibraryLayout {
override val extractingToTemp: TargetedKotlinLibraryLayout by lazy {
ExtractingTargetedLibraryImpl(this)
}
override fun directlyFromZip(zipFileSystem: FileSystem): TargetedKotlinLibraryLayout =
FromZipTargetedLibraryImpl(this, zipFileSystem)
}
class BitcodeLibraryLayoutImpl(klib: File, component: String, target: KonanTarget?) :
TargetedLibraryLayoutImpl(klib, component, target), BitcodeKotlinLibraryLayout {
override val extractingToTemp: BitcodeKotlinLibraryLayout by lazy {
ExtractingBitcodeLibraryImpl(this)
}
override fun directlyFromZip(zipFileSystem: FileSystem): BitcodeKotlinLibraryLayout =
FromZipBitcodeLibraryImpl(this, zipFileSystem)
}
open class TargetedLibraryAccess<L : KotlinLibraryLayout>(klib: File, component: String, val target: KonanTarget?) :
BaseLibraryAccess<L>(klib, component) {
override val layout = TargetedLibraryLayoutImpl(klib, component, target)
}
open class BitcodeLibraryAccess<L : KotlinLibraryLayout>(klib: File, component: String, target: KonanTarget?) :
TargetedLibraryAccess<L>(klib, component, target) {
override val layout = BitcodeLibraryLayoutImpl(klib, component, target)
}
private open class FromZipTargetedLibraryImpl(zipped: TargetedLibraryLayoutImpl, zipFileSystem: FileSystem) :
FromZipBaseLibraryImpl(zipped, zipFileSystem), TargetedKotlinLibraryLayout
private class FromZipBitcodeLibraryImpl(zipped: BitcodeLibraryLayoutImpl, zipFileSystem: FileSystem) :
FromZipTargetedLibraryImpl(zipped, zipFileSystem), BitcodeKotlinLibraryLayout
open class ExtractingTargetedLibraryImpl(zipped: TargetedLibraryLayoutImpl) :
ExtractingKotlinLibraryLayout(zipped),
TargetedKotlinLibraryLayout {
override val includedDir: File by lazy { zipped.extractDir(zipped.includedDir) }
}
class ExtractingBitcodeLibraryImpl(zipped: BitcodeLibraryLayoutImpl) :
ExtractingTargetedLibraryImpl(zipped), BitcodeKotlinLibraryLayout {
override val kotlinDir: File by lazy { zipped.extractDir(zipped.kotlinDir) }
override val nativeDir: File by lazy { zipped.extractDir(zipped.nativeDir) }
}
@@ -0,0 +1,90 @@
/*
* 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 file.
*/
package org.jetbrains.kotlin.konan.library.impl
import org.jetbrains.kotlin.konan.library.BitcodeWriter
import org.jetbrains.kotlin.konan.library.KonanLibraryWriter
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.konan.properties.Properties
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.*
class KonanLibraryLayoutForWriter(
libFile: File,
unzippedDir: File,
override val target: KonanTarget
) : KonanLibraryLayout, KotlinLibraryLayoutForWriter(libFile, unzippedDir)
/**
* Requires non-null [target].
*/
class KonanLibraryWriterImpl(
moduleName: String,
versions: KotlinLibraryVersioning,
target: KonanTarget,
builtInsPlatform: BuiltInsPlatform,
nopack: Boolean = false,
shortName: String? = null,
val layout: KonanLibraryLayoutForWriter,
base: BaseWriter = BaseWriterImpl(layout, moduleName, versions, builtInsPlatform, listOf(target.visibleName), nopack, shortName),
bitcode: BitcodeWriter = BitcodeWriterImpl(layout),
metadata: MetadataWriter = MetadataWriterImpl(layout),
ir: IrWriter = IrMonoliticWriterImpl(layout)
) : BaseWriter by base, BitcodeWriter by bitcode, MetadataWriter by metadata, IrWriter by ir, KonanLibraryWriter
fun buildLibrary(
natives: List<String>,
included: List<String>,
linkDependencies: List<KonanLibrary>,
metadata: SerializedMetadata,
ir: SerializedIrModule?,
versions: KotlinLibraryVersioning,
target: KonanTarget,
output: String,
moduleName: String,
nopack: Boolean,
shortName: String?,
manifestProperties: Properties?,
dataFlowGraph: ByteArray?
): KonanLibraryLayout {
val libFile = File(output)
val unzippedDir = if (nopack) libFile else org.jetbrains.kotlin.konan.file.createTempDir("klib")
val layout = KonanLibraryLayoutForWriter(libFile, unzippedDir, target)
val library = KonanLibraryWriterImpl(
moduleName,
versions,
target,
BuiltInsPlatform.NATIVE,
nopack,
shortName,
layout
)
library.addMetadata(metadata)
if (ir != null) {
library.addIr(ir)
}
natives.forEach {
library.addNativeBitcode(it)
}
included.forEach {
library.addIncludedBinary(it)
}
manifestProperties?.let { library.addManifestAddend(it) }
library.addLinkDependencies(linkDependencies)
dataFlowGraph?.let { library.addDataFlowGraph(it) }
library.commit()
return library.layout
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2021 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.target
import org.jetbrains.kotlin.konan.util.defaultTargetSubstitutions
abstract class AbstractToolConfig(konanHome: String, userProvidedTargetName: String?, propertyOverrides: Map<String, String>) {
private val distribution = Distribution(konanHome, propertyOverrides = propertyOverrides)
private val platformManager = PlatformManager(distribution)
private val targetManager = platformManager.targetManager(userProvidedTargetName)
private val host = HostManager.host
val target = targetManager.target
protected val platform = platformManager.platform(target)
val substitutions = defaultTargetSubstitutions(target)
fun downloadDependencies() = platform.downloadDependencies()
val llvmHome = platform.absoluteLlvmHome
val sysRoot = platform.absoluteTargetSysRoot
val libclang = when (host) {
KonanTarget.MINGW_X64 -> "$llvmHome/bin/libclang.dll"
else -> "$llvmHome/lib/${System.mapLibraryName("clang")}"
}
abstract fun loadLibclang()
fun prepare() {
downloadDependencies()
loadLibclang()
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed -> in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader
import org.jetbrains.kotlin.konan.properties.Properties
import org.jetbrains.kotlin.konan.util.InternalServer
import kotlin.math.max
class AppleConfigurablesImpl(
target: KonanTarget,
properties: Properties,
baseDir: String?
) : AppleConfigurables, KonanPropertiesLoader(target, properties, baseDir) {
private val sdkDependency = this.targetSysRoot!!
private val toolchainDependency = this.targetToolchain!!
private val xcodeAddonDependency = this.additionalToolsDir!!
override val absoluteTargetSysRoot: String get() = when (val provider = xcodePartsProvider) {
is XcodePartsProvider.Local -> provider.xcode.pathToPlatformSdk(platformName())
XcodePartsProvider.InternalServer -> absolute(sdkDependency)
}
override val absoluteTargetToolchain: String get() = when (val provider = xcodePartsProvider) {
is XcodePartsProvider.Local -> provider.xcode.toolchain
XcodePartsProvider.InternalServer -> absolute(toolchainDependency)
}
override val absoluteAdditionalToolsDir: String get() = when (val provider = xcodePartsProvider) {
is XcodePartsProvider.Local -> provider.xcode.additionalTools
XcodePartsProvider.InternalServer -> absolute(additionalToolsDir)
}
override val dependencies get() = super.dependencies + when (xcodePartsProvider) {
is XcodePartsProvider.Local -> emptyList()
XcodePartsProvider.InternalServer -> listOf(sdkDependency, toolchainDependency, xcodeAddonDependency)
}
private val xcodePartsProvider by lazy {
if (InternalServer.isAvailable) {
XcodePartsProvider.InternalServer
} else {
val xcode = Xcode.findCurrent()
if (properties.getProperty("ignoreXcodeVersionCheck") != "true") {
properties.getProperty("minimalXcodeVersion")?.let { minimalXcodeVersion ->
val currentXcodeVersion = xcode.version
checkXcodeVersion(minimalXcodeVersion, currentXcodeVersion)
}
}
XcodePartsProvider.Local(xcode)
}
}
private fun checkXcodeVersion(minimalVersion: String, currentVersion: String) {
// Xcode versions contain only numbers (even betas).
// But we still split by '-' and whitespaces to take into account versions like 11.2-beta.
val minimalVersionParts = minimalVersion.split("(\\s+|\\.|-)".toRegex()).map { it.toIntOrNull() ?: 0 }
val currentVersionParts = currentVersion.split("(\\s+|\\.|-)".toRegex()).map { it.toIntOrNull() ?: 0 }
val size = max(minimalVersionParts.size, currentVersionParts.size)
for (i in 0 until size) {
val currentPart = currentVersionParts.getOrElse(i) { 0 }
val minimalPart = minimalVersionParts.getOrElse(i) { 0 }
when {
currentPart > minimalPart -> return
currentPart < minimalPart ->
error("Unsupported Xcode version $currentVersion, minimal supported version is $minimalVersion.")
}
}
}
private sealed class XcodePartsProvider {
class Local(val xcode: Xcode) : XcodePartsProvider()
object InternalServer : XcodePartsProvider()
}
}
/**
* Name of an Apple platform as in Xcode.app/Contents/Developer/Platforms.
*/
fun AppleConfigurables.platformName(): String = when (target.family) {
Family.OSX -> "MacOSX"
Family.IOS -> if (targetTriple.isSimulator) {
"iPhoneSimulator"
} else {
"iPhoneOS"
}
Family.TVOS -> if (targetTriple.isSimulator) {
"AppleTVSimulator"
} else {
"AppleTVOS"
}
Family.WATCHOS -> if (targetTriple.isSimulator) {
"WatchSimulator"
} else {
"WatchOS"
}
else -> error("Not an Apple target: $target")
}
@@ -0,0 +1,289 @@
/*
* Copyright 2010-2022 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.target
import org.jetbrains.kotlin.konan.file.File
internal object Android {
const val API = "21"
private val architectureMap = mapOf(
KonanTarget.ANDROID_X86 to "x86",
KonanTarget.ANDROID_X64 to "x86_64",
KonanTarget.ANDROID_ARM32 to "arm",
KonanTarget.ANDROID_ARM64 to "arm64"
)
fun architectureDirForTarget(target: KonanTarget) =
"android-${API}/arch-${architectureMap.getValue(target)}"
}
sealed class ClangArgs(
private val configurables: Configurables,
private val forJni: Boolean
) {
private val absoluteTargetToolchain = configurables.absoluteTargetToolchain
private val absoluteTargetSysRoot = configurables.absoluteTargetSysRoot
private val absoluteLlvmHome = configurables.absoluteLlvmHome
private val target = configurables.target
private val targetTriple = configurables.targetTriple
// TODO: Should be dropped in favor of real MSVC target.
private val argsForWindowsJni = forJni && target == KonanTarget.MINGW_X64
private val clangArgsSpecificForKonanSources : List<String>
get() {
val konanOptions = listOfNotNull(
target.architecture.name.takeIf { target != KonanTarget.WATCHOS_ARM64 },
"ARM32".takeIf { target == KonanTarget.WATCHOS_ARM64 },
target.family.name.takeIf { target.family != Family.MINGW },
"WINDOWS".takeIf { target.family == Family.MINGW },
"MACOSX".takeIf { target.family == Family.OSX },
"NO_THREADS".takeUnless { target.supportsThreads() },
"NO_EXCEPTIONS".takeUnless { target.supportsExceptions() },
"NO_MEMMEM".takeUnless { target.suportsMemMem() },
"NO_64BIT_ATOMIC".takeUnless { target.supports64BitAtomics() },
"NO_UNALIGNED_ACCESS".takeUnless { target.supportsUnalignedAccess() },
"FORBID_BUILTIN_MUL_OVERFLOW".takeUnless { target.supports64BitMulOverflow() },
"OBJC_INTEROP".takeIf { target.supportsObjcInterop() },
"HAS_FOUNDATION_FRAMEWORK".takeIf { target.hasFoundationFramework() },
"HAS_UIKIT_FRAMEWORK".takeIf { target.hasUIKitFramework() },
"REPORT_BACKTRACE_TO_IOS_CRASH_LOG".takeIf { target.supportsIosCrashLog() },
"NEED_SMALL_BINARY".takeIf { target.needSmallBinary() },
"TARGET_HAS_ADDRESS_DEPENDENCY".takeIf { target.hasAddressDependencyInMemoryModel() },
"SUPPORTS_GRAND_CENTRAL_DISPATCH".takeIf { target.supportsGrandCentralDispatch },
).map { "KONAN_$it=1" }
val otherOptions = listOfNotNull(
"USE_ELF_SYMBOLS=1".takeIf { target.binaryFormat() == BinaryFormat.ELF },
"ELFSIZE=${target.pointerBits()}".takeIf { target.binaryFormat() == BinaryFormat.ELF },
"MACHSIZE=${target.pointerBits()}".takeIf { target.binaryFormat() == BinaryFormat.MACH_O },
"__ANDROID__".takeIf { target.family == Family.ANDROID },
"USE_PE_COFF_SYMBOLS=1".takeIf { target.binaryFormat() == BinaryFormat.PE_COFF },
"UNICODE".takeIf { target.family == Family.MINGW },
"USE_WINAPI_UNWIND=1".takeIf { target.supportsWinAPIUnwind() },
"USE_GCC_UNWIND=1".takeIf { target.supportsGccUnwind() },
// Clang 11 does not support this attribute. We don't need to handle it properly,
// so just undefine it.
"NS_FORMAT_ARGUMENT(A)=".takeIf { target.family.isAppleFamily },
)
val customOptions = target.customArgsForKonanSources()
return (konanOptions + otherOptions + customOptions).map { "-D$it" }
}
private val binDir = when (HostManager.host) {
KonanTarget.LINUX_X64 -> "$absoluteTargetToolchain/bin"
KonanTarget.MINGW_X64 -> "$absoluteTargetToolchain/bin"
KonanTarget.MACOS_X64,
KonanTarget.MACOS_ARM64 -> "$absoluteTargetToolchain/usr/bin"
else -> throw TargetSupportException("Unexpected host platform")
}
// TODO: Use buildList
private val commonClangArgs: List<String> = mutableListOf<List<String>>().apply {
// Currently, MinGW toolchain contains old LLVM 8, and -fuse-ld=lld picks linker from there.
// And, unfortunately, `-fuse-ld=<absolute path>` doesn't work correctly for MSVC toolchain.
// That's why we just don't add $absoluteTargetToolchain/bin to binary search path in case of JNI compilation.
// TODO: Can be removed after MinGW sysroot update.
if (!argsForWindowsJni) {
add(listOf("-B$binDir"))
} else {
require(configurables is MingwConfigurables)
add(configurables.msvc.compilerFlags())
add(configurables.windowsKit.compilerFlags())
// Do not depend on link.exe from Visual Studio.
add(listOf("-fuse-ld=lld"))
}
add(listOf("-fno-stack-protector"))
if (configurables is GccConfigurables) {
add(listOf("--gcc-toolchain=${configurables.absoluteGccToolchain}"))
}
val targetString: String = when {
argsForWindowsJni -> "x86_64-pc-windows-msvc"
configurables is AppleConfigurables -> {
val osVersionMin = when (target) {
// Here we workaround Clang 8 limitation: macOS major version should be 10.
// So we compile runtime with version 10.16 and then override version in BitcodeCompiler.
// TODO: Fix with LLVM Update.
KonanTarget.MACOS_ARM64 -> "10.16"
else -> configurables.osVersionMin
}
targetTriple.copy(
os = "${targetTriple.os}$osVersionMin"
).toString()
}
else -> configurables.targetTriple.toString()
}
add(listOf("-target", targetString))
val hasCustomSysroot = configurables is ZephyrConfigurables
|| configurables is WasmConfigurables
|| configurables is AndroidConfigurables
|| argsForWindowsJni
if (!hasCustomSysroot) {
when (configurables) {
// isysroot and sysroot on darwin are _almost_ synonyms.
// The first one parses SDKSettings.json while second one is not.
is AppleConfigurables -> add(listOf("-isysroot", absoluteTargetSysRoot))
else -> add(listOf("--sysroot=$absoluteTargetSysRoot"))
}
}
// PIC is not required on Windows (and Clang will fail with `error: unsupported option '-fPIC'`)
if (configurables !is MingwConfigurables) {
// `-fPIC` allows us to avoid some problems when producing dynamic library.
// See KT-43502.
add(listOf("-fPIC"))
}
}.flatten()
private val specificClangArgs: List<String> = when (target) {
KonanTarget.LINUX_ARM32_HFP -> listOf(
"-mfpu=vfp", "-mfloat-abi=hard"
)
KonanTarget.IOS_ARM32, KonanTarget.WATCHOS_ARM32 -> listOf(
// Force generation of ARM instruction set instead of Thumb-2.
// It allows LLVM ARM backend to encode bigger offsets in BL instruction,
// thus allowing to generate a slightly bigger binaries.
// See KT-37368.
"-marm"
)
KonanTarget.ANDROID_ARM32, KonanTarget.ANDROID_ARM64,
KonanTarget.ANDROID_X86, KonanTarget.ANDROID_X64 -> {
val clangTarget = targetTriple.withoutVendor()
val architectureDir = Android.architectureDirForTarget(target)
val toolchainSysroot = "$absoluteTargetToolchain/sysroot"
listOf(
"-D__ANDROID_API__=${Android.API}",
"--sysroot=$absoluteTargetSysRoot/$architectureDir",
"-I$toolchainSysroot/usr/include/c++/v1",
"-I$toolchainSysroot/usr/include",
"-I$toolchainSysroot/usr/include/$clangTarget"
)
}
// By default WASM target forces `hidden` visibility which causes linkage problems.
KonanTarget.WASM32 -> listOf(
"-fno-rtti",
"-fno-exceptions",
"-fvisibility=default",
"-D_LIBCPP_ABI_VERSION=2",
"-D_LIBCPP_NO_EXCEPTIONS=1",
"-nostdinc",
"-Xclang", "-nobuiltininc",
"-Xclang", "-nostdsysteminc",
"-Xclang", "-isystem$absoluteTargetSysRoot/include/libcxx",
"-Xclang", "-isystem$absoluteTargetSysRoot/lib/libcxxabi/include",
"-Xclang", "-isystem$absoluteTargetSysRoot/include/compat",
"-Xclang", "-isystem$absoluteTargetSysRoot/include/libc"
)
is KonanTarget.ZEPHYR -> listOf(
"-fno-rtti",
"-fno-exceptions",
"-fno-asynchronous-unwind-tables",
"-fno-pie",
"-fno-pic",
"-fshort-enums",
"-nostdinc",
// TODO: make it a libGcc property?
// We need to get rid of wasm sysroot first.
"-isystem ${configurables.targetToolchain}/../lib/gcc/arm-none-eabi/7.2.1/include",
"-isystem ${configurables.targetToolchain}/../lib/gcc/arm-none-eabi/7.2.1/include-fixed",
"-isystem$absoluteTargetSysRoot/include/libcxx",
"-isystem$absoluteTargetSysRoot/include/libc"
) + (configurables as ZephyrConfigurables).constructClangArgs()
else -> emptyList()
}
val clangPaths = listOf("$absoluteLlvmHome/bin", binDir)
/**
* Clang args for Objectice-C and plain C compilation.
*/
val clangArgs: Array<String> = (commonClangArgs + specificClangArgs).toTypedArray()
/**
* Clang args for C++ compilation.
*/
val clangXXArgs: Array<String> = clangArgs + when (configurables) {
is AppleConfigurables -> arrayOf(
"-stdlib=libc++"
)
else -> emptyArray()
}
val clangArgsForKonanSources =
clangXXArgs + clangArgsSpecificForKonanSources
private val libclangSpecificArgs =
// libclang works not exactly the same way as the clang binary and
// (in particular) uses different default header search path.
// See e.g. http://lists.llvm.org/pipermail/cfe-dev/2013-November/033680.html
// We workaround the problem with -isystem flag below.
// TODO: Revise after update to LLVM 10.
listOf("-isystem", "$absoluteLlvmHome/lib/clang/${configurables.llvmVersion}/include")
/**
* libclang args for plain C and Objective-C.
*
* Note that it's different from [clangArgs].
*/
val libclangArgs: List<String> =
libclangSpecificArgs + clangArgs
/**
* libclang args for C++.
*
* Note that it's different from [clangXXArgs].
*/
val libclangXXArgs: List<String> =
libclangSpecificArgs + clangXXArgs
private val targetClangCmd
= listOf("${absoluteLlvmHome}/bin/clang") + clangArgs
private val targetClangXXCmd
= listOf("${absoluteLlvmHome}/bin/clang++") + clangXXArgs
private val targetArCmd
= listOf("${absoluteLlvmHome}/bin/llvm-ar")
fun clangC(vararg userArgs: String) = targetClangCmd + userArgs.asList()
fun clangCXX(vararg userArgs: String) = targetClangXXCmd + userArgs.asList()
fun llvmAr(vararg userArgs: String) = targetArCmd + userArgs.asList()
/**
* Should be used when compiling library for JNI.
* For example, it is used for Kotlin/Native's Clang and LLVM libraries.
*/
class Jni(configurables: Configurables) : ClangArgs(configurables, forJni = true) {
private val jdkDir by lazy {
val home = File.javaHome.absoluteFile
if (home.child("include").exists)
home.absolutePath
else
home.parentFile.absolutePath
}
val hostCompilerArgsForJni: Array<String> by lazy {
listOf("", HostManager.jniHostPlatformIncludeDir)
.map { "-I$jdkDir/include/$it" }
.toTypedArray()
}
}
/**
* Used for compiling native code that meant to be run on end-user's hardware.
* E.g., Kotlin/Native runtime and interop stubs.
*/
class Native(configurables: Configurables) : ClangArgs(configurables, forJni = false)
}
@@ -0,0 +1,141 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed -> in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.properties.*
interface RelocationModeFlags : TargetableExternalStorage {
val dynamicLibraryRelocationMode get() = targetString("dynamicLibraryRelocationMode").mode()
val staticLibraryRelocationMode get() = targetString("staticLibraryRelocationMode").mode()
val executableRelocationMode get() = targetString("executableRelocationMode").mode()
@Suppress("DEPRECATION")
private fun String?.mode(): Mode = when (this?.toLowerCase()) {
null -> Mode.DEFAULT
"pic" -> Mode.PIC
"static" -> Mode.STATIC
else -> error("Unknown relocation mode: $this")
}
enum class Mode {
PIC,
STATIC,
DEFAULT
}
}
interface ClangFlags : TargetableExternalStorage, RelocationModeFlags {
val clangFlags get() = targetList("clangFlags")
val clangNooptFlags get() = targetList("clangNooptFlags")
val clangOptFlags get() = targetList("clangOptFlags")
val clangDebugFlags get() = targetList("clangDebugFlags")
}
interface LldFlags : TargetableExternalStorage {
val lldFlags get() = targetList("lld")
}
interface Configurables : TargetableExternalStorage, RelocationModeFlags {
val target: KonanTarget
val targetTriple: TargetTriple
get() = targetString("targetTriple")
?.let(TargetTriple.Companion::fromString)
?: error("quadruple for $target is not set.")
val llvmHome get() = hostString("llvmHome")
val llvmVersion get() = hostString("llvmVersion")
val libffiDir get() = hostString("libffiDir")
val cacheableTargets get() = hostList("cacheableTargets")
val additionalCacheFlags get() = targetList("additionalCacheFlags")
// TODO: Delegate to a map?
val linkerOptimizationFlags get() = targetList("linkerOptimizationFlags")
val linkerKonanFlags get() = targetList("linkerKonanFlags")
val mimallocLinkerDependencies get() = targetList("mimallocLinkerDependencies")
val linkerNoDebugFlags get() = targetList("linkerNoDebugFlags")
val linkerDynamicFlags get() = targetList("linkerDynamicFlags")
val targetSysRoot get() = targetString("targetSysRoot")
// Notice: these ones are host-target.
val targetToolchain get() = hostTargetString("targetToolchain")
val absoluteTargetSysRoot get() = absolute(targetSysRoot)
val absoluteTargetToolchain get() = absolute(targetToolchain)
val absoluteLlvmHome get() = absolute(llvmHome)
val targetCpu get() = targetString("targetCpu")
val targetCpuFeatures get() = targetString("targetCpuFeatures")
val llvmInlineThreshold get() = targetString("llvmInlineThreshold")
val runtimeDefinitions get() = targetList("runtimeDefinitions")
}
interface ConfigurablesWithEmulator : Configurables {
val emulatorDependency get() = hostTargetString("emulatorDependency")
// TODO: We need to find a way to represent absolute path in properties.
// In case of QEMU, absolute path to dynamic linker should be specified.
val emulatorExecutable get() = hostTargetString("emulatorExecutable")
val absoluteEmulatorExecutable get() = absolute(emulatorExecutable)
}
interface AppleConfigurables : Configurables, ClangFlags {
val arch get() = targetTriple.architecture
val osVersionMin get() = targetString("osVersionMin")!!
val osVersionMinFlagLd get() = targetString("osVersionMinFlagLd")!!
val stripFlags get() = targetList("stripFlags")
val additionalToolsDir get() = hostString("additionalToolsDir")
val absoluteAdditionalToolsDir get() = absolute(additionalToolsDir)
}
interface MingwConfigurables : Configurables, ClangFlags {
val linker get() = hostTargetString("linker")!!
val absoluteLinker get() = absolute(linker)
val windowsKit: WindowsKit
val msvc: Msvc
val windowsKitParts get() = hostString("windowsKitParts")!!
val msvcParts get() = hostString("msvcParts")!!
}
interface GccConfigurables : Configurables, ClangFlags {
val gccToolchain get() = targetString("gccToolchain")
val absoluteGccToolchain get() = absolute(gccToolchain)
val libGcc get() = targetString("libGcc")!!
val dynamicLinker get() = targetString("dynamicLinker")!!
val abiSpecificLibraries get() = targetList("abiSpecificLibraries")
val crtFilesLocation get() = targetString("crtFilesLocation")!!
val linker get() = hostTargetString("linker")
val linkerHostSpecificFlags get() = hostTargetList("linkerHostSpecificFlags")
val absoluteLinker get() = absolute(linker)
val linkerGccFlags get() = targetList("linkerGccFlags")
}
interface AndroidConfigurables : Configurables, ClangFlags
interface WasmConfigurables : Configurables, ClangFlags, LldFlags
interface ZephyrConfigurables : Configurables, ClangFlags {
val boardSpecificClangFlags get() = targetList("boardSpecificClangFlags")
val targetAbi get() = targetString("targetAbi")
}
@@ -0,0 +1,23 @@
package org.jetbrains.kotlin.konan.target
fun ZephyrConfigurables.constructClangArgs(): List<String> = mutableListOf<String>().apply {
targetCpu?.let {
add("-mcpu=$it")
}
targetAbi?.let {
add("-mabi=$it")
}
addAll(boardSpecificClangFlags)
}
fun ZephyrConfigurables.constructClangCC1Args(): List<String> = mutableListOf<String>().apply {
addAll("-cc1 -emit-obj -disable-llvm-optzns -x ir -fdata-sections -ffunction-sections".split(" "))
targetCpu?.let {
addAll(listOf("-target-abi", it))
}
targetAbi?.let {
addAll(listOf("-target-cpu", it))
}
addAll(boardSpecificClangFlags)
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed -> in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.properties.*
class GccConfigurablesImpl(target: KonanTarget, properties: Properties, baseDir: String?)
: GccConfigurables, KonanPropertiesLoader(target, properties, baseDir), ConfigurablesWithEmulator {
override val dependencies: List<String>
get() = super.dependencies + listOfNotNull(emulatorDependency)
}
class AndroidConfigurablesImpl(target: KonanTarget, properties: Properties, baseDir: String?)
: AndroidConfigurables, KonanPropertiesLoader(target, properties, baseDir)
class WasmConfigurablesImpl(target: KonanTarget, properties: Properties, baseDir: String?)
: WasmConfigurables, KonanPropertiesLoader(target, properties, baseDir)
class ZephyrConfigurablesImpl(target: KonanTarget, properties: Properties, baseDir: String?)
: ZephyrConfigurables, KonanPropertiesLoader(target, properties, baseDir)
fun loadConfigurables(target: KonanTarget, properties: Properties, baseDir: String?): Configurables = when (target.family) {
Family.LINUX -> GccConfigurablesImpl(target, properties, baseDir)
Family.TVOS, Family.WATCHOS, Family.IOS, Family.OSX -> AppleConfigurablesImpl(target, properties, baseDir)
Family.ANDROID -> AndroidConfigurablesImpl(target, properties, baseDir)
Family.MINGW -> MingwConfigurablesImpl(target, properties, baseDir)
Family.WASM -> WasmConfigurablesImpl(target, properties, baseDir)
Family.ZEPHYR -> ZephyrConfigurablesImpl(target, properties, baseDir)
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed -> in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.properties
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.Configurables
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.util.ArchiveType
import org.jetbrains.kotlin.konan.util.DependencyProcessor
import java.io.File
interface TargetableExternalStorage {
fun targetString(key: String): String?
fun targetList(key: String): List<String>
fun hostString(key: String): String?
fun hostList(key: String): List<String>
fun hostTargetString(key: String): String?
fun hostTargetList(key: String): List<String>
fun absolute(value: String?): String
fun downloadDependencies()
}
abstract class KonanPropertiesLoader(override val target: KonanTarget,
val properties: Properties,
private val baseDir: String? = null,
private val host: KonanTarget = HostManager.host) : Configurables {
private val predefinedLlvmDistributions: Set<String> =
properties.propertyList("predefinedLlvmDistributions").toSet()
private val predefinedLibffiVersions: Set<String> =
properties.propertyList("predefinedLibffiVersions").toSet()
private fun getPredefinedDependencyOrNull(
dependencyName: String,
dependencyAccessor: () -> String?,
predefinedDependencies: Set<String>
): String? {
// Store into variable to avoid repeated resolve.
val dependency = dependencyAccessor()
?: error("Undefined $dependencyName!")
return when (dependency) {
in predefinedDependencies -> dependency
else -> null
}
}
private fun compilerDependencies(): List<String> = listOfNotNull(
getPredefinedDependencyOrNull("LLVM home", this::llvmHome, predefinedLlvmDistributions),
getPredefinedDependencyOrNull("libffi version", this::libffiDir, predefinedLibffiVersions)
)
open val dependencies: List<String>
get() = hostTargetList("dependencies") + compilerDependencies()
override fun downloadDependencies() {
dependencyProcessor!!.run()
}
// TODO: We may want to add caching to avoid repeated resolve.
override fun targetString(key: String): String?
= properties.targetString(key, target)
override fun targetList(key: String): List<String>
= properties.targetList(key, target)
override fun hostString(key: String): String?
= properties.hostString(key, host)
override fun hostList(key: String): List<String>
= properties.hostList(key, host)
override fun hostTargetString(key: String): String?
= properties.hostTargetString(key, target, host)
override fun hostTargetList(key: String): List<String>
= properties.hostTargetList(key, target, host)
override fun absolute(value: String?): String =
dependencyProcessor!!.resolve(value!!).absolutePath
private val dependencyProcessor by lazy {
baseDir?.let {
DependencyProcessor(
dependenciesRoot = File(baseDir),
properties = this,
archiveType = defaultArchiveTypeByHost(host)
){ url, currentBytes, totalBytes ->
print("\n(KonanProperies) Downloading dependency: $url (${currentBytes}/${totalBytes}). ")
}
}
}
}
private fun defaultArchiveTypeByHost(host: KonanTarget): ArchiveType = when (host) {
KonanTarget.LINUX_X64,
KonanTarget.MACOS_X64,
KonanTarget.MACOS_ARM64 -> ArchiveType.TAR_GZ
KonanTarget.MINGW_X64 -> ArchiveType.ZIP
else -> error("$host can't be a host platform!")
}
@@ -0,0 +1,202 @@
/*
* Copyright 2010-2022 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.target
enum class BinaryFormat {
ELF,
PE_COFF,
MACH_O
}
fun KonanTarget.binaryFormat() = when (family) {
Family.WATCHOS -> BinaryFormat.MACH_O
Family.IOS -> BinaryFormat.MACH_O
Family.TVOS -> BinaryFormat.MACH_O
Family.OSX -> BinaryFormat.MACH_O
Family.ANDROID -> BinaryFormat.ELF
Family.LINUX -> BinaryFormat.ELF
Family.MINGW -> BinaryFormat.PE_COFF
Family.WASM, Family.ZEPHYR -> null
}
fun KonanTarget.pointerBits() = when (architecture) {
Architecture.X64 -> 64
Architecture.X86 -> 32
Architecture.ARM64 -> if (this == KonanTarget.WATCHOS_ARM64) 32 else 64
Architecture.ARM32 -> 32
Architecture.MIPS32 -> 32
Architecture.MIPSEL32 -> 32
Architecture.WASM32 -> 32
}
fun KonanTarget.supportsCodeCoverage(): Boolean =
// TODO: Disabled for now, because we don't support
// coverage format from LLVM 11.
false
// this == KonanTarget.MINGW_X64 ||
// this == KonanTarget.LINUX_X64 ||
// this == KonanTarget.MACOS_X64 ||
// this == KonanTarget.IOS_X64
fun KonanTarget.supportsMimallocAllocator(): Boolean =
when(this) {
is KonanTarget.LINUX_X64 -> true
is KonanTarget.MINGW_X86 -> true
is KonanTarget.MINGW_X64 -> true
is KonanTarget.MACOS_X64 -> true
is KonanTarget.MACOS_ARM64 -> true
is KonanTarget.LINUX_ARM64 -> true
is KonanTarget.LINUX_ARM32_HFP -> true
is KonanTarget.ANDROID_X64 -> true
is KonanTarget.ANDROID_ARM64 -> true
is KonanTarget.IOS_ARM32 -> true
is KonanTarget.IOS_ARM64 -> true
is KonanTarget.IOS_X64 -> true
is KonanTarget.IOS_SIMULATOR_ARM64 -> true
is KonanTarget.WATCHOS_ARM32, is KonanTarget.WATCHOS_ARM64,
is KonanTarget.WATCHOS_SIMULATOR_ARM64, is KonanTarget.WATCHOS_X64, is KonanTarget.WATCHOS_X86,
is KonanTarget.TVOS_ARM64, is KonanTarget.TVOS_SIMULATOR_ARM64, is KonanTarget.TVOS_X64,
is KonanTarget.ANDROID_X86, is KonanTarget.ANDROID_ARM32 -> false // aren't tested.
is KonanTarget.LINUX_MIPS32, is KonanTarget.LINUX_MIPSEL32 -> false // need linking with libatomic.
is KonanTarget.WASM32, is KonanTarget.ZEPHYR -> false // likely not supported
// Funny thing is we can neither access WATCHOS_DEVICE_ARM64, nor omit it explicitly due to the
// build's quirks. Workaround by using else clause.
// TODO: Add explicit WATCHOS_DEVICE_ARM64 after compiler update.
else -> false
}
fun KonanTarget.supportsLibBacktrace(): Boolean =
this.family.isAppleFamily ||
// MIPS architectures have issues, see KT-48949
(this.family == Family.LINUX && this.architecture !in listOf(Architecture.MIPS32, Architecture.MIPSEL32)) ||
this.family == Family.ANDROID
// TODO: Add explicit WATCHOS_DEVICE_ARM64 after compiler update.
fun KonanTarget.supportsCoreSymbolication(): Boolean =
this in listOf(
KonanTarget.MACOS_X64, KonanTarget.MACOS_ARM64, KonanTarget.IOS_X64,
KonanTarget.IOS_SIMULATOR_ARM64, KonanTarget.TVOS_X64, KonanTarget.TVOS_SIMULATOR_ARM64,
KonanTarget.WATCHOS_X86, KonanTarget.WATCHOS_X64, KonanTarget.WATCHOS_SIMULATOR_ARM64
)
fun KonanTarget.supportsGccUnwind(): Boolean = family == Family.ANDROID || family == Family.LINUX || this is KonanTarget.MINGW_X86
// MINGW_X64 target does not support GCC unwind, since its sysroot contains libgcc version < 12 having misfeature, see KT-49240
fun KonanTarget.supportsWinAPIUnwind(): Boolean = this is KonanTarget.MINGW_X64
fun KonanTarget.supportsThreads(): Boolean = when(this) {
is KonanTarget.WASM32 -> false
is KonanTarget.ZEPHYR -> false
else -> true
}
fun KonanTarget.supportsExceptions(): Boolean = when(this) {
is KonanTarget.WASM32 -> false
is KonanTarget.ZEPHYR -> false
else -> true
}
fun KonanTarget.suportsMemMem(): Boolean = when (this) {
is KonanTarget.WASM32 -> false
is KonanTarget.MINGW_X86 -> false
is KonanTarget.MINGW_X64 -> false
is KonanTarget.ZEPHYR -> false
else -> true
}
fun KonanTarget.supportsObjcInterop(): Boolean = family.isAppleFamily
fun KonanTarget.hasFoundationFramework(): Boolean = family.isAppleFamily
fun KonanTarget.hasUIKitFramework(): Boolean = family == Family.IOS || family == Family.TVOS
fun KonanTarget.supports64BitMulOverflow(): Boolean = when (this) {
is KonanTarget.MINGW_X86 -> false
is KonanTarget.LINUX_ARM32_HFP -> false
is KonanTarget.LINUX_MIPS32 -> false
is KonanTarget.LINUX_MIPSEL32 -> false
is KonanTarget.WASM32 -> false
is KonanTarget.ZEPHYR -> false
is KonanTarget.ANDROID_ARM32 -> false
is KonanTarget.ANDROID_X86 -> false
else -> true
}
// TODO: Add explicit WATCHOS_DEVICE_ARM64 after compiler update.
fun KonanTarget.supportsIosCrashLog(): Boolean = when (this) {
KonanTarget.IOS_ARM32 -> true
KonanTarget.IOS_ARM64 -> true
KonanTarget.WATCHOS_ARM32 -> true
KonanTarget.WATCHOS_ARM64 -> true
KonanTarget.TVOS_ARM64 -> true
else -> false
}
/*
* While not 100% correct here, using atomic ops on iOS armv7 requires 8 byte alignment,
* and general ABI requires 4-byte alignment on 64-bit long fields as mentioned in
* https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009021-SW1
* See https://github.com/ktorio/ktor/issues/941 for the context.
* TODO: reconsider once target MIPS can do proper 64-bit load/store/CAS.
*/
fun KonanTarget.supports64BitAtomics(): Boolean = when (architecture) {
Architecture.ARM32, Architecture.WASM32, Architecture.MIPS32, Architecture.MIPSEL32 -> false
Architecture.X86, Architecture.ARM64, Architecture.X64 -> true
} && this != KonanTarget.WATCHOS_ARM64 && this != KonanTarget.WATCHOS_X86
fun KonanTarget.supportsUnalignedAccess(): Boolean = when (architecture) {
Architecture.ARM32, Architecture.WASM32, Architecture.MIPS32, Architecture.MIPSEL32 -> false
Architecture.X86, Architecture.ARM64, Architecture.X64 -> true
} && this != KonanTarget.WATCHOS_ARM64
fun KonanTarget.needSmallBinary() = when {
family == Family.WATCHOS -> true
family.isAppleFamily -> architecture == Architecture.ARM32
else -> false
}
fun KonanTarget.supportedSanitizers(): List<SanitizerKind> =
when(this) {
is KonanTarget.LINUX_X64 -> listOf(SanitizerKind.ADDRESS)
is KonanTarget.MACOS_X64 -> listOf(SanitizerKind.THREAD)
// TODO: Enable ASAN on macOS. Currently there's an incompatibility between clang frontend version and clang_rt.asan version.
// TODO: Enable TSAN on linux. Currently there's a link error between clang_rt.tsan and libstdc++.
// TODO: Consider supporting mingw.
// TODO: Support macOS arm64
else -> listOf()
}
fun KonanTarget.hasAddressDependencyInMemoryModel(): Boolean =
when (this.architecture) {
Architecture.X86, Architecture.X64, Architecture.ARM32, Architecture.ARM64 -> true
Architecture.MIPS32, Architecture.MIPSEL32, Architecture.WASM32 -> false
}
val KonanTarget.supportsGrandCentralDispatch
get() = when(family) {
Family.WATCHOS, Family.IOS, Family.TVOS, Family.OSX -> true
else -> false
}
// TODO: this is bad function. It should be replaced by capabilities functions like above
// but two affected targets are too strange, so we postpone it
fun KonanTarget.customArgsForKonanSources() = when (this) {
KonanTarget.WASM32 -> listOf(
"KONAN_NO_FFI=1",
"KONAN_INTERNAL_DLMALLOC=1",
"KONAN_INTERNAL_SNPRINTF=1",
"KONAN_INTERNAL_NOW=1",
"KONAN_NO_CTORS_SECTION=1",
"KONAN_NO_BACKTRACE=1",
"KONAN_NO_EXTERNAL_CALLS_CHECKER=1",
)
is KonanTarget.ZEPHYR -> listOf(
"KONAN_NO_FFI=1",
"KONAN_NO_MATH=1",
"KONAN_INTERNAL_SNPRINTF=1",
"KONAN_INTERNAL_NOW=1",
"KONAN_NO_CTORS_SECTION=1",
"KONAN_NO_BACKTRACE=1"
)
else -> emptyList()
}
@@ -0,0 +1,593 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import java.lang.ProcessBuilder
import java.lang.ProcessBuilder.Redirect
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.*
typealias ObjectFile = String
typealias ExecutableFile = String
enum class LinkerOutputKind {
DYNAMIC_LIBRARY,
STATIC_LIBRARY,
EXECUTABLE
}
// Here we take somewhat unexpected approach - we create the thin
// library, and then repack it during post-link phase.
// This way we ensure .a inputs are properly processed.
private fun staticGnuArCommands(ar: String, executable: ExecutableFile,
objectFiles: List<ObjectFile>, libraries: List<String>) = when {
HostManager.hostIsMingw -> {
val temp = executable.replace('/', '\\') + "__"
val arWindows = ar.replace('/', '\\')
listOf(
Command(arWindows, "-rucT", temp).apply {
+objectFiles
+libraries
},
Command("cmd", "/c").apply {
+"(echo create $executable & echo addlib ${temp} & echo save & echo end) | $arWindows -M"
},
Command("cmd", "/c", "del", "/q", temp))
}
HostManager.hostIsLinux || HostManager.hostIsMac -> listOf(
Command(ar, "cqT", executable).apply {
+objectFiles
+libraries
},
Command("/bin/sh", "-c").apply {
+"printf 'create $executable\\naddlib $executable\\nsave\\nend' | $ar -M"
})
else -> TODO("Unsupported host ${HostManager.host}")
}
// Use "clang -v -save-temps" to write linkCommand() method
// for another implementation of this class.
abstract class LinkerFlags(val configurables: Configurables) {
protected val llvmBin = "${configurables.absoluteLlvmHome}/bin"
protected val llvmLib = "${configurables.absoluteLlvmHome}/lib"
open val useCompilerDriverAsLinker: Boolean get() = false // TODO: refactor.
/**
* Returns list of commands that produces final linker output.
*/
// TODO: Number of arguments is quite big. Better to pass args via object.
abstract fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind? = null): List<Command>
/**
* Returns list of commands that link object files into a single one.
* Pre-linkage is useful for hiding dependency symbols.
*/
open fun preLinkCommands(objectFiles: List<ObjectFile>, output: ObjectFile): List<Command> =
error("Pre-link is unsupported for ${configurables.target}.")
abstract fun filterStaticLibraries(binaries: List<String>): List<String>
open fun linkStaticLibraries(binaries: List<String>): List<String> {
val libraries = filterStaticLibraries(binaries)
// Let's just pass them as absolute paths.
return libraries
}
open fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean = false): String? {
System.err.println("Can't provide $libraryName.")
return null
}
// Code coverage requires this library.
protected val profileLibrary: String? by lazy {
provideCompilerRtLibrary("profile")
}
}
class AndroidLinker(targetProperties: AndroidConfigurables)
: LinkerFlags(targetProperties), AndroidConfigurables by targetProperties {
private val clangTarget = when (val targetString = targetProperties.targetTriple.toString()) {
"arm-unknown-linux-androideabi" -> "armv7a-linux-androideabi"
else -> targetProperties.targetTriple.withoutVendor()
}
private val prefix = "$absoluteTargetToolchain/bin/${clangTarget}${Android.API}"
private val clang = if (HostManager.hostIsMingw) "$prefix-clang.cmd" else "$prefix-clang"
private val ar = "$absoluteTargetToolchain/${targetProperties.targetTriple.withoutVendor()}/bin/ar"
override val useCompilerDriverAsLinker: Boolean get() = true
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isUnixStaticLib }
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
if (kind == LinkerOutputKind.STATIC_LIBRARY)
return staticGnuArCommands(ar, executable, objectFiles, libraries)
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
val toolchainSysroot = "${absoluteTargetToolchain}/sysroot"
val architectureDir = Android.architectureDirForTarget(target)
val apiSysroot = "$absoluteTargetSysRoot/$architectureDir"
val clangTarget = targetTriple.withoutVendor()
val libDirs = listOf(
"--sysroot=$apiSysroot",
if (target == KonanTarget.ANDROID_X64) "-L$apiSysroot/usr/lib64" else "-L$apiSysroot/usr/lib",
"-L$toolchainSysroot/usr/lib/$clangTarget/${Android.API}",
"-L$toolchainSysroot/usr/lib/$clangTarget")
return listOf(Command(clang).apply {
+"-o"
+executable
when (kind) {
LinkerOutputKind.EXECUTABLE -> +listOf("-fPIE", "-pie")
LinkerOutputKind.DYNAMIC_LIBRARY -> +listOf("-fPIC", "-shared")
LinkerOutputKind.STATIC_LIBRARY -> {}
}
+"-target"
+clangTarget
+libDirs
+objectFiles
if (optimize) +linkerOptimizationFlags
if (!debug) +linkerNoDebugFlags
if (dynamic) +linkerDynamicFlags
if (dynamic) +"-Wl,-soname,${File(executable).name}"
+linkerKonanFlags
if (mimallocEnabled) +mimallocLinkerDependencies
+libraries
+linkerArgs
})
}
}
class MacOSBasedLinker(targetProperties: AppleConfigurables)
: LinkerFlags(targetProperties), AppleConfigurables by targetProperties {
private val libtool = "$absoluteTargetToolchain/usr/bin/libtool"
private val linker = "$absoluteTargetToolchain/usr/bin/ld"
private val strip = "$absoluteTargetToolchain/usr/bin/strip"
private val dsymutil = "$absoluteTargetToolchain/usr/bin/dsymutil"
private val compilerRtDir: String? by lazy {
val dir = File("$absoluteTargetToolchain/usr/lib/clang/").listFiles.firstOrNull()?.absolutePath
if (dir != null) "$dir/lib/darwin/" else null
}
override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
val prefix = when (target.family) {
Family.IOS -> "ios"
Family.WATCHOS -> "watchos"
Family.TVOS -> "tvos"
Family.OSX -> "osx"
else -> error("Target $target is unsupported")
}
// Separate libclang_rt version for simulator appeared in Xcode 12.
// We don't support Xcode versions older than 12.5 anymore, so no need to check Xcode version.
val suffix = if (targetTriple.isSimulator) {
"sim"
} else {
""
}
val dir = compilerRtDir
val mangledLibraryName = if (libraryName.isEmpty()) "" else "${libraryName}_"
val extension = if (isDynamic) "_dynamic.dylib" else ".a"
return if (dir != null) "$dir/libclang_rt.$mangledLibraryName$prefix$suffix$extension" else null
}
private val osVersionMinFlags: List<String> by lazy {
listOf(osVersionMinFlagLd, osVersionMin + ".0")
}
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isUnixStaticLib }
// Note that may break in case of 32-bit Mach-O. See KT-37368.
override fun preLinkCommands(objectFiles: List<ObjectFile>, output: ObjectFile): List<Command> =
Command(linker).apply {
+"-r"
+listOf("-arch", arch)
+listOf("-syslibroot", absoluteTargetSysRoot)
+objectFiles
+listOf("-o", output)
}.let(::listOf)
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, kind: LinkerOutputKind,
outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind == LinkerOutputKind.STATIC_LIBRARY) {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return listOf(Command(libtool).apply {
+"-static"
+listOf("-o", executable)
+objectFiles
+libraries
})
}
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
val result = mutableListOf<Command>()
result += Command(linker).apply {
+"-demangle"
+listOf("-dynamic", "-arch", arch)
+osVersionMinFlags
+listOf("-syslibroot", absoluteTargetSysRoot, "-o", executable)
+objectFiles
if (optimize) +linkerOptimizationFlags
if (!debug) +linkerNoDebugFlags
if (dynamic) +linkerDynamicFlags
+linkerKonanFlags
if (mimallocEnabled) +mimallocLinkerDependencies
if (compilerRtLibrary != null) +compilerRtLibrary!!
if (needsProfileLibrary) +profileLibrary!!
+libraries
+linkerArgs
+rpath(dynamic, sanitizer)
when (sanitizer) {
null -> {}
SanitizerKind.ADDRESS -> +provideCompilerRtLibrary("asan", isDynamic=true)!!
SanitizerKind.THREAD -> +provideCompilerRtLibrary("tsan", isDynamic=true)!!
}
}
// TODO: revise debug information handling.
if (debug) {
result += dsymUtilCommand(executable, outputDsymBundle)
if (optimize) {
result += Command(strip, *stripFlags.toTypedArray(), executable)
}
}
return result
}
private val compilerRtLibrary: String? by lazy {
provideCompilerRtLibrary("")
}
private fun rpath(dynamic: Boolean, sanitizer: SanitizerKind?): List<String> = listOfNotNull(
when (target.family) {
Family.OSX -> "@executable_path/../Frameworks"
Family.IOS,
Family.WATCHOS,
Family.TVOS -> "@executable_path/Frameworks"
else -> error(target)
},
"@loader_path/Frameworks".takeIf { dynamic },
compilerRtDir.takeIf { sanitizer != null }
).flatMap { listOf("-rpath", it) }
fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) =
object : Command(dsymutilCommand(executable, outputDsymBundle)) {
override fun runProcess(): Int =
executeCommandWithFilter(command)
}
// TODO: consider introducing a better filtering directly in Command.
private fun executeCommandWithFilter(command: List<String>): Int {
val builder = ProcessBuilder(command)
// Inherit main process output streams.
val isDsymUtil = (command[0] == dsymutil)
builder.redirectOutput(Redirect.INHERIT)
builder.redirectInput(Redirect.INHERIT)
if (!isDsymUtil)
builder.redirectError(Redirect.INHERIT)
val process = builder.start()
if (isDsymUtil) {
/**
* llvm-lto has option -alias that lets tool to know which symbol we use instead of _main,
* llvm-dsym doesn't have such a option, so we ignore annoying warning manually.
*/
val errorStream = process.errorStream
val outputStream = bufferedReader(errorStream)
while (true) {
val line = outputStream.readLine() ?: break
if (!line.contains("warning: could not find object file symbol for symbol _main"))
System.err.println(line)
}
outputStream.close()
}
val exitCode = process.waitFor()
return exitCode
}
fun dsymutilCommand(executable: ExecutableFile, outputDsymBundle: String): List<String> =
listOf(dsymutil, executable, "-o", outputDsymBundle)
fun dsymutilDryRunVerboseCommand(executable: ExecutableFile): List<String> =
listOf(dsymutil, "-dump-debug-map", executable)
}
class GccBasedLinker(targetProperties: GccConfigurables)
: LinkerFlags(targetProperties), GccConfigurables by targetProperties {
private val ar = if (HostManager.hostIsLinux) {
"$absoluteTargetToolchain/bin/ar"
} else {
"$absoluteTargetToolchain/bin/llvm-ar"
}
override val libGcc = "$absoluteTargetSysRoot/${super.libGcc}"
private val specificLibs = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" }
override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
require(!isDynamic) {
"Dynamic compiler rt librares are unsupported"
}
val targetSuffix = when (target) {
KonanTarget.LINUX_X64 -> "x86_64"
else -> error("$target is not supported.")
}
val dir = File("$absoluteLlvmHome/lib/clang/").listFiles.firstOrNull()?.absolutePath
return if (dir != null) "$dir/lib/linux/libclang_rt.$libraryName-$targetSuffix.a" else null
}
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isUnixStaticLib }
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind == LinkerOutputKind.STATIC_LIBRARY) {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return staticGnuArCommands(ar, executable, objectFiles, libraries)
}
val isMips = target == KonanTarget.LINUX_MIPS32 || target == KonanTarget.LINUX_MIPSEL32
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
val crtPrefix = "$absoluteTargetSysRoot/$crtFilesLocation"
// TODO: Can we extract more to the konan.configurables?
return listOf(Command(absoluteLinker).apply {
+"--sysroot=${absoluteTargetSysRoot}"
+"-export-dynamic"
+"-z"
+"relro"
+"--build-id"
+"--eh-frame-hdr"
+"-dynamic-linker"
+dynamicLinker
linkerHostSpecificFlags.forEach { +it }
+"-o"
+executable
if (!dynamic) +"$crtPrefix/crt1.o"
+"$crtPrefix/crti.o"
+if (dynamic) "$libGcc/crtbeginS.o" else "$libGcc/crtbegin.o"
+"-L$libGcc"
if (!isMips) +"--hash-style=gnu" // MIPS doesn't support hash-style=gnu
+specificLibs
if (optimize) +linkerOptimizationFlags
if (!debug) +linkerNoDebugFlags
if (dynamic) +linkerDynamicFlags
+objectFiles
+libraries
+linkerArgs
if (mimallocEnabled) +mimallocLinkerDependencies
// See explanation about `-u__llvm_profile_runtime` here:
// https://github.com/llvm/llvm-project/blob/21e270a479a24738d641e641115bce6af6ed360a/llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp#L930
if (needsProfileLibrary) +listOf("-u__llvm_profile_runtime", profileLibrary!!)
+linkerKonanFlags
+linkerGccFlags
+if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o"
+"$crtPrefix/crtn.o"
when (sanitizer) {
null -> {}
SanitizerKind.ADDRESS -> {
+"-lrt"
+provideCompilerRtLibrary("asan")!!
+provideCompilerRtLibrary("asan_cxx")!!
}
SanitizerKind.THREAD -> {
+"-lrt"
+provideCompilerRtLibrary("tsan")!!
+provideCompilerRtLibrary("tsan_cxx")!!
}
}
})
}
}
class MingwLinker(targetProperties: MingwConfigurables)
: LinkerFlags(targetProperties), MingwConfigurables by targetProperties {
// TODO: Maybe always use llvm-ar?
private val ar = if (HostManager.hostIsMingw) {
"$absoluteTargetToolchain/bin/ar"
} else {
"$absoluteLlvmHome/bin/llvm-ar"
}
private val clang = "$absoluteLlvmHome/bin/clang++"
override val useCompilerDriverAsLinker: Boolean get() = true
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib }
override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
require(!isDynamic) {
"Dynamic compiler rt librares are unsupported"
}
val targetSuffix = when (target) {
KonanTarget.MINGW_X64 -> "x86_64"
else -> error("$target is not supported.")
}
val dir = File("$absoluteLlvmHome/lib/clang/").listFiles.firstOrNull()?.absolutePath
return if (dir != null) "$dir/lib/windows/libclang_rt.$libraryName-$targetSuffix.a" else null
}
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
if (kind == LinkerOutputKind.STATIC_LIBRARY)
return staticGnuArCommands(ar, executable, objectFiles, libraries)
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
fun Command.constructLinkerArguments(
additionalArguments: List<String> = listOf(),
skipDefaultArguments: List<String> = listOf()
): Command = apply {
+listOf("--sysroot", absoluteTargetSysRoot)
+listOf("-target", targetTriple.toString())
+listOf("-o", executable)
+objectFiles
// --gc-sections flag may affect profiling.
// See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#drawbacks-and-limitations.
// TODO: switching to lld may help.
if (optimize && !needsProfileLibrary) {
// TODO: Can be removed after LLD update.
// See KT-48085.
if (!dynamic) {
+linkerOptimizationFlags
}
}
if (!debug) +linkerNoDebugFlags
if (dynamic) +linkerDynamicFlags
+libraries
if (needsProfileLibrary) +profileLibrary!!
+linkerArgs
+linkerKonanFlags.filterNot { it in skipDefaultArguments }
if (mimallocEnabled) +mimallocLinkerDependencies
+additionalArguments
}
return listOf(Command(clang).constructLinkerArguments(additionalArguments = listOf("-fuse-ld=$absoluteLinker")))
}
}
class WasmLinker(targetProperties: WasmConfigurables)
: LinkerFlags(targetProperties), WasmConfigurables by targetProperties {
override val useCompilerDriverAsLinker: Boolean get() = false
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isJavaScript }
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind")
require(sanitizer == null) {
"Sanitizers are unsupported"
}
val linkage = Command("$llvmBin/wasm-ld").apply {
+objectFiles
+listOf("-o", executable)
+lldFlags
}
// TODO(horsh): maybe rethink it.
val jsBindingsGeneration = object : Command() {
override fun execute() {
javaScriptLink(libraries, executable)
}
private fun javaScriptLink(jsFiles: List<String>, executable: String): String {
val linkedJavaScript = File("$executable.js")
val linkerHeader = "var konan = { libraries: [] };\n"
val linkerFooter = """|if (isBrowser()) {
| konan.moduleEntry([]);
|} else {
| konan.moduleEntry(arguments);
|}""".trimMargin()
linkedJavaScript.writeText(linkerHeader)
jsFiles.forEach {
linkedJavaScript.appendBytes(File(it).readBytes())
}
linkedJavaScript.appendBytes(linkerFooter.toByteArray())
return linkedJavaScript.name
}
}
return listOf(linkage, jsBindingsGeneration)
}
}
open class ZephyrLinker(targetProperties: ZephyrConfigurables)
: LinkerFlags(targetProperties), ZephyrConfigurables by targetProperties {
private val linker = "$absoluteTargetToolchain/bin/ld"
override val useCompilerDriverAsLinker: Boolean get() = false
override fun filterStaticLibraries(binaries: List<String>) = emptyList<String>()
override fun finalLinkCommands(objectFiles: List<ObjectFile>, executable: ExecutableFile,
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind: $kind")
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return listOf(Command(linker).apply {
+listOf("-r", "--gc-sections", "--entry", "main")
+listOf("-o", executable)
+objectFiles
+libraries
+linkerArgs
})
}
}
fun linker(configurables: Configurables): LinkerFlags =
when (configurables) {
is GccConfigurables -> GccBasedLinker(configurables)
is AppleConfigurables -> MacOSBasedLinker(configurables)
is AndroidConfigurables-> AndroidLinker(configurables)
is MingwConfigurables -> MingwLinker(configurables)
is WasmConfigurables -> WasmLinker(configurables)
is ZephyrConfigurables -> ZephyrLinker(configurables)
else -> error("Unexpected target: ${configurables.target}")
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.util.DependencyProcessor
class Platform(val configurables: Configurables)
: Configurables by configurables {
val clang: ClangArgs.Native by lazy {
ClangArgs.Native(configurables)
}
val clangForJni: ClangArgs.Jni by lazy {
ClangArgs.Jni(configurables)
}
val linker: LinkerFlags by lazy {
linker(configurables)
}
}
class PlatformManager private constructor(private val serialized: Serialized) :
HostManager(serialized.distribution, serialized.experimental), java.io.Serializable {
constructor(konanHome: String, experimental: Boolean = false) : this(Distribution(konanHome), experimental)
constructor(distribution: Distribution, experimental: Boolean = false) : this(Serialized(distribution, experimental))
private val distribution by serialized::distribution
private val loaders = enabled.map {
it to loadConfigurables(it, distribution.properties, DependencyProcessor.defaultDependenciesRoot.absolutePath)
}.toMap()
private val platforms = loaders.map {
it.key to Platform(it.value)
}.toMap()
fun platform(target: KonanTarget) = platforms.getValue(target)
val hostPlatform = platforms.getValue(host)
fun loader(target: KonanTarget) = loaders.getValue(target)
private fun writeReplace(): Any = serialized
private data class Serialized(
val distribution: Distribution,
val experimental: Boolean
) : java.io.Serializable {
companion object {
private const val serialVersionUID: Long = 0L
}
private fun readResolve(): Any = PlatformManager(this)
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2020 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.target
enum class SanitizerKind {
ADDRESS,
THREAD,
}
/**
* Suffix for [KonanTarget] name.
*
* In string interpolation use
* ```
* "… ${target}${sanitizer.targetSuffix} …"
* ```
*/
val SanitizerKind?.targetSuffix: String
get() = when (this) {
null -> ""
SanitizerKind.THREAD -> "_tsan"
SanitizerKind.ADDRESS -> "_asan"
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.properties
import org.jetbrains.kotlin.konan.target.*
fun Properties.hostString(name: String, host: KonanTarget): String?
= this.resolvablePropertyString(name, host.name)
fun Properties.hostList(name: String, host: KonanTarget): List<String>
= this.resolvablePropertyList(name, host.name)
fun Properties.targetString(name: String, target: KonanTarget): String?
= this.resolvablePropertyString(name, target.name)
fun Properties.targetList(name: String, target: KonanTarget): List<String>
= this.resolvablePropertyList(name, target.name)
fun Properties.hostTargetString(name: String, target: KonanTarget, host: KonanTarget): String?
= this.resolvablePropertyString(name, hostTargetSuffix(host, target))
fun Properties.hostTargetList(name: String, target: KonanTarget, host: KonanTarget): List<String>
= this.resolvablePropertyList(name, hostTargetSuffix(host, target))
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2021 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.target
/**
* Classical way to describe a target platform.
* Despite it's name, may contain more or less than 3 components.
*
* Example: arm64-apple-ios-simulator.
* - arm64 - target hardware.
* - apple - vendor.
* - ios - operating system.
* - simulator - environment.
*
* @see <a href="https://clang.llvm.org/docs/CrossCompilation.html#target-triple">Clang documentation for target triple</a>.
*/
data class TargetTriple(
val architecture: String,
val vendor: String,
val os: String,
val environment: String?
) {
companion object {
/**
* Parse <arch>-<vendor>-<os>-<environment?> [tripleString].
*
* TODO: Support normalization like LLVM's Triple::normalize.
*/
fun fromString(tripleString: String): TargetTriple {
val components = tripleString.split('-')
// TODO: There might be other cases (e.g. of size 2 or 5),
// but let's support only these for now.
require(components.size == 3 || components.size == 4) {
"Malformed target triple: $tripleString. Expected format: <arch>-<vendor>-<os>-<environment?>."
}
return TargetTriple(
architecture = components[0],
vendor = components[1],
os = components[2],
environment = components.getOrNull(3)
)
}
}
override fun toString(): String {
val envSuffix = environment?.let { "-$environment" }
?: ""
return "$architecture-$vendor-$os$envSuffix"
}
}
/**
* Check that given target is Apple simulator.
*/
val TargetTriple.isSimulator: Boolean
get() = environment == "simulator"
/**
* Appends version to OS part of triple.
*
* Useful for precise target specification in Clang and Swift.
*/
fun TargetTriple.withOSVersion(osVersion: String): TargetTriple =
copy(os = "${os}${osVersion}")
/**
* Triple without vendor (second) component.
*
* TODO: Actually, this method should return [TargetTriple],
* but this class is not that flexible yet.
*/
fun TargetTriple.withoutVendor(): String {
val envSuffix = environment?.let { "-$environment" }
?: ""
return "$architecture-$os$envSuffix"
}
@@ -0,0 +1,119 @@
/*
* Copyright 2010-2021 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.target
import org.jetbrains.kotlin.konan.util.InternalServer
import java.nio.file.Path
import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader
import org.jetbrains.kotlin.konan.properties.Properties
import java.nio.file.Paths
class MingwConfigurablesImpl(target: KonanTarget, properties: Properties, baseDir: String?)
: MingwConfigurables, KonanPropertiesLoader(target, properties, baseDir) {
override val windowsKit: WindowsKit by lazy {
when (windowsSdkPartsProvider) {
WindowsSdkPartsProvider.InternalServer -> createCustomWindowsKitPath(Paths.get(absolute(windowsKitParts)))
WindowsSdkPartsProvider.Local -> WindowsKit.DefaultPath
}
}
override val msvc: Msvc by lazy {
when (windowsSdkPartsProvider) {
WindowsSdkPartsProvider.InternalServer -> createCustomMsvcPath(Paths.get(absolute(msvcParts)))
WindowsSdkPartsProvider.Local -> Msvc.DefaultPath
}
}
private val windowsSdkPartsProvider by lazy {
if (InternalServer.isAvailable) {
WindowsSdkPartsProvider.InternalServer
} else {
WindowsSdkPartsProvider.Local
}
}
private val windowsHostDependencies by lazy {
when (windowsSdkPartsProvider) {
WindowsSdkPartsProvider.InternalServer -> listOf(windowsKitParts, msvcParts)
WindowsSdkPartsProvider.Local -> emptyList()
}
}
override val dependencies
get() = super.dependencies + if (HostManager.hostIsMingw) {
windowsHostDependencies
} else {
emptyList()
}
}
private fun createCustomWindowsKitPath(windowsKitParts: Path): WindowsKit.CustomPath {
return WindowsKit.CustomPath(
libraryDirectories = listOf(
windowsKitParts.resolve("Lib").resolve("ucrt").resolve("x64"),
windowsKitParts.resolve("Lib").resolve("um").resolve("x64")
),
includeDirectories = listOf(
windowsKitParts.resolve("Include").resolve("ucrt")
)
)
}
private fun createCustomMsvcPath(msvcParts: Path): Msvc.CustomPath {
return Msvc.CustomPath(
libraryDirectories = listOf(
msvcParts.resolve("lib").resolve("x64")
),
includeDirectories = listOf(
msvcParts.resolve("include")
)
)
}
sealed class Msvc {
abstract fun compilerFlags(): List<String>
object DefaultPath : Msvc() {
override fun compilerFlags(): List<String> = emptyList()
}
class CustomPath(
private val includeDirectories: List<Path>,
private val libraryDirectories: List<Path>
) : Msvc() {
// Note that this approach doesn't exclude default VS path.
// TODO: A better (but harder) way would be LIB environment variable.
override fun compilerFlags(): List<String> =
includeDirectories.flatMap { listOf("-isystem", it.toAbsolutePath().toString()) } +
libraryDirectories.flatMap { listOf("-L", it.toAbsolutePath().toString()) }
}
}
sealed class WindowsKit {
abstract fun compilerFlags(): List<String>
object DefaultPath : WindowsKit() {
override fun compilerFlags(): List<String> = emptyList()
}
class CustomPath(
private val includeDirectories: List<Path>,
private val libraryDirectories: List<Path>
) : WindowsKit() {
// Note that this approach doesn't exclude default Windows Kit path.
// TODO: A better (but harder) way would be LIB environment variable.
override fun compilerFlags(): List<String> =
includeDirectories.flatMap { listOf("-isystem", it.toAbsolutePath().toString()) } +
libraryDirectories.flatMap { listOf("-L", it.toAbsolutePath().toString()) }
}
}
private sealed class WindowsSdkPartsProvider {
object Local : WindowsSdkPartsProvider()
object InternalServer : WindowsSdkPartsProvider()
}
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.target
import org.jetbrains.kotlin.konan.KonanExternalToolFailure
import org.jetbrains.kotlin.konan.MissingXcodeException
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.File
interface Xcode {
val toolchain: String
val macosxSdk: String
val iphoneosSdk: String
val iphonesimulatorSdk: String
val version: String
val appletvosSdk: String
val appletvsimulatorSdk: String
val watchosSdk: String
val watchsimulatorSdk: String
// Xcode.app/Contents/Developer/usr
val additionalTools: String
val simulatorRuntimes: String
/**
* TODO: `toLowerCase` is deprecated and should be replaced with `lowercase`, but
* this code used in buildSrc which depends on bootstrap version of stdlib, so right version
* of this function isn't available, please replace warning suppression with right function
* when compatible version of bootstrap will be available.
*/
@Suppress("DEPRECATION")
fun pathToPlatformSdk(platformName: String): String = when (platformName.toLowerCase()) {
"macosx" -> macosxSdk
"iphoneos" -> iphoneosSdk
"iphonesimulator" -> iphonesimulatorSdk
"appletvos" -> appletvosSdk
"appletvsimulator" -> appletvsimulatorSdk
"watchos" -> watchosSdk
"watchsimulator" -> watchsimulatorSdk
else -> error("Unknown Apple platform: $platformName")
}
companion object {
// Don't cache the instance: the compiler might be executed in a Gradle daemon process,
// so current Xcode might actually change between different invocations.
@Deprecated("", ReplaceWith("this.findCurrent()"), DeprecationLevel.WARNING)
val current: Xcode
get() = findCurrent()
fun findCurrent(): Xcode = CurrentXcode()
}
}
internal class CurrentXcode : Xcode {
override val toolchain by lazy {
val ldPath = xcrun("-f", "ld") // = $toolchain/usr/bin/ld
File(ldPath).parentFile.parentFile.parentFile.absolutePath
}
override val additionalTools: String by lazy {
val bitcodeBuildToolPath = xcrun("-f", "bitcode-build-tool")
File(bitcodeBuildToolPath).parentFile.parentFile.absolutePath
}
override val simulatorRuntimes: String by lazy {
Command("/usr/bin/xcrun", "simctl", "list", "runtimes", "-j").getOutputLines().joinToString(separator = "\n")
}
override val macosxSdk by lazy { getSdkPath("macosx") }
override val iphoneosSdk by lazy { getSdkPath("iphoneos") }
override val iphonesimulatorSdk by lazy { getSdkPath("iphonesimulator") }
override val appletvosSdk by lazy { getSdkPath("appletvos") }
override val appletvsimulatorSdk by lazy { getSdkPath("appletvsimulator") }
override val watchosSdk: String by lazy { getSdkPath("watchos") }
override val watchsimulatorSdk: String by lazy { getSdkPath("watchsimulator") }
internal val xcodebuildVersion: String
get() = xcrun("xcodebuild", "-version")
.removePrefix("Xcode ")
internal val bundleVersion: String
get() = bash("""/usr/libexec/PlistBuddy "$(xcode-select -print-path)/../Info.plist" -c "Print :CFBundleShortVersionString"""")
override val version by lazy {
try {
bundleVersion
} catch (e: KonanExternalToolFailure) {
xcodebuildVersion
}
}
private fun xcrun(vararg args: String): String = try {
Command("/usr/bin/xcrun", *args).getOutputLines().first()
} catch (e: KonanExternalToolFailure) {
// TODO: we should make the message below even more clear and actionable.
// Maybe add a link to the documentation.
// See https://youtrack.jetbrains.com/issue/KT-50923.
val message = """
An error occurred during an xcrun execution. Make sure that Xcode and its command line tools are properly installed.
Failed command: /usr/bin/xcrun ${args.joinToString(" ")}
Try running this command in Terminal and fix the errors by making Xcode (and its command line tools) configuration correct.
""".trimIndent()
throw MissingXcodeException(message, e)
}
private fun bash(command: String): String = Command("/bin/bash", "-c", command).getOutputLines().joinToString("\n")
private fun getSdkPath(sdk: String) = xcrun("--sdk", sdk, "--show-sdk-path")
}
@@ -0,0 +1,191 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.util
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
import java.io.File
import java.io.StringReader
import java.util.*
class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProperties:Properties, val defHeaderLines:List<String>) {
private constructor(file0:File?, triple: Triple<Properties, Properties, List<String>>): this(file0, DefFileConfig(triple.first), triple.second, triple.third)
constructor(file:File?, substitutions: Map<String, String>) : this(file, parseDefFile(file, substitutions))
val name by lazy {
file?.nameWithoutExtension ?: ""
}
class DefFileConfig(private val properties: Properties) {
val headers by lazy {
properties.getSpaceSeparated("headers")
}
val modules by lazy {
properties.getSpaceSeparated("modules")
}
val language by lazy {
properties.getProperty("language")
}
val compilerOpts by lazy {
properties.getSpaceSeparated("compilerOpts")
}
val excludeSystemLibs by lazy {
properties.getProperty("excludeSystemLibs")?.toBoolean() ?: false
}
val excludeDependentModules by lazy {
properties.getProperty("excludeDependentModules")?.toBoolean() ?: false
}
val entryPoints by lazy {
properties.getSpaceSeparated("entryPoint")
}
val linkerOpts by lazy {
properties.getSpaceSeparated("linkerOpts")
}
val linker by lazy {
properties.getProperty("linker", "clang")
}
val excludedFunctions by lazy {
properties.getSpaceSeparated("excludedFunctions")
}
val excludedMacros by lazy {
properties.getSpaceSeparated("excludedMacros")
}
val staticLibraries by lazy {
properties.getSpaceSeparated("staticLibraries")
}
val libraryPaths by lazy {
properties.getSpaceSeparated("libraryPaths")
}
val packageName by lazy {
properties.getProperty("package")
}
/**
* Header inclusion globs.
*/
val headerFilter by lazy {
properties.getSpaceSeparated("headerFilter")
}
/**
* Header exclusion globs. Have higher priority than [headerFilter].
*/
val excludeFilter by lazy {
properties.getSpaceSeparated("excludeFilter")
}
val strictEnums by lazy {
properties.getSpaceSeparated("strictEnums")
}
val nonStrictEnums by lazy {
properties.getSpaceSeparated("nonStrictEnums")
}
val noStringConversion by lazy {
properties.getSpaceSeparated("noStringConversion")
}
val depends by lazy {
properties.getSpaceSeparated("depends")
}
val exportForwardDeclarations by lazy {
properties.getSpaceSeparated("exportForwardDeclarations")
}
val disableDesignatedInitializerChecks by lazy {
properties.getProperty("disableDesignatedInitializerChecks")?.toBoolean() ?: false
}
val foreignExceptionMode by lazy {
properties.getProperty("foreignExceptionMode")
}
val pluginName by lazy {
properties.getProperty("plugin")
}
val objcClassesIncludingCategories by lazy {
properties.getSpaceSeparated("objcClassesIncludingCategories")
}
}
}
private fun Properties.getSpaceSeparated(name: String): List<String> =
this.getProperty(name)?.let { parseSpaceSeparatedArgs(it) } ?: emptyList()
private fun parseDefFile(file: File?, substitutions: Map<String, String>): Triple<Properties, Properties, List<String>> {
val properties = Properties()
if (file == null) {
return Triple(properties, Properties(), emptyList())
}
val lines = file.readLines()
val separator = "---"
val separatorIndex = lines.indexOf(separator)
val propertyLines: List<String>
val headerLines: List<String>
if (separatorIndex != -1) {
propertyLines = lines.subList(0, separatorIndex)
headerLines = lines.subList(separatorIndex + 1, lines.size)
} else {
propertyLines = lines
headerLines = emptyList()
}
// \ isn't escaping character in quotes, so replace them with \\.
val joinedLines = propertyLines.joinToString(System.lineSeparator())
val escapedTokens = joinedLines.split('"')
val postprocessProperties = escapedTokens.mapIndexed { index, token ->
if (index % 2 != 0) {
token.replace("""\\(?=.)""".toRegex(), Regex.escapeReplacement("""\\"""))
} else {
token
}
}.joinToString("\"")
val propertiesReader = StringReader(postprocessProperties)
properties.load(propertiesReader)
// Pass unsubstituted copy of properties we have obtained from `.def`
// to compiler `-manifest`.
val manifestAddendProperties = properties.duplicate()
substitute(properties, substitutions)
return Triple(properties, manifestAddendProperties, headerLines)
}
private fun Properties.duplicate() = Properties().apply { putAll(this@duplicate) }
fun DefFile(file: File?, target: KonanTarget) = DefFile(file, defaultTargetSubstitutions(target))
@@ -0,0 +1,230 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.*
typealias ProgressCallback = (url: String, currentBytes: Long, totalBytes: Long) -> Unit
class DependencyDownloader(
var maxAttempts: Int = DEFAULT_MAX_ATTEMPTS,
var attemptIntervalMs: Long = DEFAULT_ATTEMPT_INTERVAL_MS,
customProgressCallback: ProgressCallback? = null
) {
private val progressCallback = customProgressCallback ?: TODO()/* { url, currentBytes, totalBytes ->
print("\nDownloading dependency: $url (${currentBytes.humanReadable}/${totalBytes.humanReadable}). ")
}*/
val executor = ExecutorCompletionService<Unit>(Executors.newSingleThreadExecutor(object : ThreadFactory {
override fun newThread(r: Runnable?): Thread {
val thread = Thread(r)
thread.name = "konan-dependency-downloader"
thread.isDaemon = true
return thread
}
}))
enum class ReplacingMode {
/** Redownload the file and replace the existing one. */
REPLACE,
/** Throw FileAlreadyExistsException */
THROW,
/** Don't download the file and return the existing one*/
RETURN_EXISTING
}
class HTTPResponseException(val url: URL, val responseCode: Int)
: IOException("Server returned HTTP response code: $responseCode for URL: $url")
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 HTTPResponseException(originalUrl, responseCode)
}
}
private fun HttpURLConnection.checkHTTPResponse(originalUrl: URL, predicate: (Int) -> Boolean) {
if (!predicate(responseCode)) {
throw HTTPResponseException(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
val handleException = { e: Exception ->
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)
}
while (true) {
try {
tryDownload(source, tmpFile)
break
} catch (e: HTTPResponseException) {
if (e.responseCode >= 500) {
// Retry server errors.
handleException(e)
} else {
// Do not retry client errors.
throw e
}
} catch (e: IOException) {
handleException(e)
}
}
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"
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.util
import org.jetbrains.kotlin.konan.file.unzipTo
import java.io.File
import java.util.concurrent.TimeUnit
enum class ArchiveType(val fileExtension: String) {
ZIP("zip"),
TAR_GZ("tar.gz");
companion object {
val systemDefault = if (System.getProperty("os.name").startsWith("Windows")) {
ZIP
} else {
TAR_GZ
}
}
}
class DependencyExtractor(
private val archiveType: ArchiveType
) {
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) {
when (archiveType) {
ArchiveType.ZIP -> archive.toPath().unzipTo(targetDirectory.toPath())
ArchiveType.TAR_GZ -> extractTarGz(archive, targetDirectory)
}
}
companion object {
val extractionTimeout = 3600L
val extractionTimeoutUntis = TimeUnit.SECONDS
}
}
@@ -0,0 +1,351 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.konan.util
import org.jetbrains.kotlin.konan.file.use
import org.jetbrains.kotlin.konan.properties.KonanPropertiesLoader
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()
}
private val KonanPropertiesLoader.dependenciesUrl : String get() = properties.dependenciesUrl
private val KonanPropertiesLoader.airplaneMode : Boolean get() = properties.airplaneMode
private val KonanPropertiesLoader.downloadingAttempts : Int get() = properties.downloadingAttempts
private val KonanPropertiesLoader.downloadingAttemptIntervalMs : Long get() = properties.downloadingAttemptIntervalMs
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,
private val dependenciesUrl: String,
dependencyToCandidates: Map<String, List<DependencySource>>,
homeDependencyCache: File = defaultDependencyCacheDir,
private val airplaneMode: Boolean = false,
maxAttempts: Int = DependencyDownloader.DEFAULT_MAX_ATTEMPTS,
attemptIntervalMs: Long = DependencyDownloader.DEFAULT_ATTEMPT_INTERVAL_MS,
customProgressCallback: ProgressCallback? = null,
private val keepUnstable: Boolean = true,
private val deleteArchives: Boolean = true,
private val archiveType: ArchiveType = ArchiveType.systemDefault) {
private val dependenciesDirectory by lazy {
dependenciesRoot.apply { mkdirs() }
}
private val cacheDirectory by lazy {
homeDependencyCache.apply { mkdirs() }
}
private val lockFile by lazy {
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(archiveType)
constructor(dependenciesRoot: File,
properties: KonanPropertiesLoader,
dependenciesUrl: String = properties.dependenciesUrl,
keepUnstable:Boolean = true,
archiveType: ArchiveType = ArchiveType.systemDefault,
customProgressCallback: ProgressCallback? = null) : this(
dependenciesRoot,
properties.properties,
properties.dependencies,
dependenciesUrl,
keepUnstable = keepUnstable,
archiveType = archiveType,
customProgressCallback = customProgressCallback)
constructor(dependenciesRoot: File,
properties: Properties,
dependencies: List<String>,
dependenciesUrl: String = properties.dependenciesUrl,
keepUnstable:Boolean = true,
archiveType: ArchiveType = ArchiveType.systemDefault,
customProgressCallback: ProgressCallback? = null ) : this(
dependenciesRoot,
dependenciesUrl,
dependencyToCandidates = properties.findCandidates(dependencies),
airplaneMode = properties.airplaneMode,
maxAttempts = properties.downloadingAttempts,
attemptIntervalMs = properties.downloadingAttemptIntervalMs,
keepUnstable = keepUnstable,
archiveType = archiveType,
customProgressCallback = customProgressCallback)
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.${archiveType.fileExtension}"
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)
if (deleteArchives) {
archive.delete()
}
extractedDependencies.addAndSave(depName)
}
companion object {
val localKonanDir: File
get() = 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")
val isInternalSeverAvailable: Boolean
get() = InternalServer.isAvailable
}
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")
}
}
/**
* If given [path] is relative, resolves it relative to dependecies directory.
* In case of absolute path just wraps it into a [File].
*
* Support of both relative and absolute path kinds allows to substitute predefined
* dependencies with system ones.
*
* TODO: It looks like DependencyProcessor have two split responsibilities:
* * Dependency resolving
* * Dependency downloading
* Also it is tightly tied to KonanProperties.
*/
fun resolve(path: String): File =
if (Paths.get(path).isAbsolute) File(path) else resolveRelative(path)
private 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() {
// We need a lock that can be shared between different classloaders (KT-39781).
// TODO: Rework dependencies downloading to avoid storing the lock in the system properties.
val lock = System.getProperties().computeIfAbsent("kotlin.native.dependencies.lock") {
// String literals are internalized so we create a new instance to avoid synchronization on a shared object.
java.lang.String("lock")
}
val remoteDependencies = resolvedDependencies.mapNotNull { (dependency, candidate) ->
when (candidate) {
is DependencySource.Local -> null
is DependencySource.Remote -> dependency to candidate
}
}
if (remoteDependencies.isEmpty()) { return }
synchronized(lock) {
RandomAccessFile(lockFile, "rw").channel.lock().use {
remoteDependencies.forEach { (dependency, candidate) ->
val baseUrl = when (candidate) {
DependencySource.Remote.Public -> dependenciesUrl
DependencySource.Remote.Internal -> InternalServer.url
}
// TODO: consider using different caches for different remotes.
downloadDependency(dependency, baseUrl)
}
}
}
}
}
internal object InternalServer {
private const val host = "repo.labs.intellij.net"
const val url = "https://$host/kotlin-native"
private const val internalDomain = "labs.intellij.net"
val isAvailable: Boolean get() {
val envKey = "KONAN_USE_INTERNAL_SERVER"
return when (val envValue = System.getenv(envKey)) {
null, "0" -> false
"1" -> true
"auto" -> isAccessible
else -> error("unexpected environment: $envKey=$envValue")
}
}
private val isAccessible by lazy { checkAccessible() }
private fun checkAccessible() = try {
if (!InetAddress.getLocalHost().canonicalHostName.endsWith(".$internalDomain")) {
// Fast path:
false
} else {
InetAddress.getByName(host)
true
}
} catch (e: UnknownHostException) {
false
}
}
@@ -0,0 +1,5 @@
package org.jetbrains.kotlin.konan.util
object PlatformLibsInfo {
const val namePrefix = "org.jetbrains.kotlin.native.platform."
}
@@ -0,0 +1,29 @@
package org.jetbrains.kotlin.konan.util
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.util.*
// FIXME(ddol): KLIB-REFACTORING-CLEANUP: remove the whole file!
fun defaultTargetSubstitutions(target: KonanTarget) =
mapOf<String, String>(
"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)
}
}
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2023 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.target
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Assumptions.assumeTrue
internal class CurrentXcodeTest {
companion object {
@BeforeAll
@JvmStatic
fun assumeMacOS() {
assumeTrue(HostManager.hostIsMac)
}
}
@Test
fun `Should be able to access Xcode bundle version`() {
val version = CurrentXcode().bundleVersion
assertNotEquals("", version)
}
@Test
fun `Should be able to access xcodebuild version`() {
val version = CurrentXcode().xcodebuildVersion
assertNotEquals("", version)
}
@Test
fun `Xcode bundle version version should match xcodebuild version`() {
val xcode = CurrentXcode()
val xcodebuildVersion = xcode.xcodebuildVersion
val bundleVersion = xcode.bundleVersion
val version = xcode.version
assertEquals(xcodebuildVersion, bundleVersion)
assertEquals(xcodebuildVersion, version)
}
}