Native: move samples to backend.native/tests/
This commit is contained in:
committed by
Space
parent
b7337d2e64
commit
7bf6d64cfb
@@ -0,0 +1,2 @@
|
||||
This directory contains a set of compiler tests.
|
||||
They do not demonstrate Kotlin/Native best practices, and are mostly pretty outdated.
|
||||
@@ -0,0 +1,59 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
val kotlinCompilerRepo: String? by rootProject
|
||||
kotlinCompilerRepo?.let { maven(it) }
|
||||
}
|
||||
|
||||
val kotlin_version: String by rootProject
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
val kotlinCompilerRepo: String? by rootProject
|
||||
kotlinCompilerRepo?.let { maven(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMacos = hostOs == "Mac OS X"
|
||||
val isLinux = hostOs == "Linux"
|
||||
val isWindows = hostOs.startsWith("Windows")
|
||||
|
||||
val localRepo = rootProject.file("build/.m2-local")
|
||||
|
||||
val clean by tasks.creating(Delete::class) {
|
||||
delete(localRepo)
|
||||
}
|
||||
|
||||
val buildSamplesWithPlatformLibs by tasks.creating {
|
||||
dependsOn(":csvparser:assemble")
|
||||
dependsOn(":curl:assemble")
|
||||
dependsOn(":echoServer:assemble")
|
||||
dependsOn(":globalState:assemble")
|
||||
dependsOn(":html5Canvas:assemble")
|
||||
dependsOn(":workers:assemble")
|
||||
|
||||
if (isMacos || isLinux) {
|
||||
dependsOn(":nonBlockingEchoServer:assemble")
|
||||
dependsOn(":tensorflow:assemble")
|
||||
}
|
||||
|
||||
if (isMacos) {
|
||||
dependsOn(":objc:assemble")
|
||||
dependsOn(":opengl:assemble")
|
||||
dependsOn(":uikit:assemble")
|
||||
dependsOn(":coverage:assemble")
|
||||
dependsOn(":watchos:assemble")
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
dependsOn(":win32:assemble")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
|
||||
// Create a target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("coverage")
|
||||
hostOs == "Linux" -> linuxX64("coverage")
|
||||
isMingwX64 -> mingwX64("coverage")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable(listOf(DEBUG))
|
||||
}
|
||||
binaries.getTest("DEBUG").apply {
|
||||
freeCompilerArgs += listOf("-Xlibrary-to-cover=${compilations["main"].output.classesDirs.singleFile.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val coverageMain by getting
|
||||
val coverageTest by getting
|
||||
}
|
||||
}
|
||||
|
||||
tasks.create("createCoverageReport") {
|
||||
dependsOn("coverageTest")
|
||||
|
||||
description = "Create coverage report"
|
||||
|
||||
doLast {
|
||||
val testDebugBinary = kotlin.targets["coverage"].let { it as KotlinNativeTarget }.binaries.getTest("DEBUG").outputFile
|
||||
exec {
|
||||
commandLine("llvm-profdata", "merge", "$testDebugBinary.profraw", "-o", "$testDebugBinary.profdata")
|
||||
}
|
||||
exec {
|
||||
commandLine("llvm-cov", "show", "$testDebugBinary", "-instr-profile", "$testDebugBinary.profdata")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
kotlin.code.style=official
|
||||
@@ -0,0 +1,17 @@
|
||||
fun baz(args: Array<String>) {
|
||||
if (args.size > 4) {
|
||||
println("Too many")
|
||||
} else {
|
||||
println("Fine")
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
init {
|
||||
println("A::init")
|
||||
}
|
||||
|
||||
fun f() {
|
||||
println("A::f")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun main() {
|
||||
baz(arrayOf("abc"))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CoverageTests {
|
||||
@Test
|
||||
fun testHello() {
|
||||
main()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testA() {
|
||||
val a = A()
|
||||
a.f()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("csvParser")
|
||||
hostOs == "Linux" -> linuxX64("csvParser")
|
||||
hostOs.startsWith("Windows") -> mingwX64("csvParser")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
compilations["main"].enableEndorsedLibs = true
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.csvparser.main"
|
||||
runTask?.args("--column", 4, "--count", 100, "./European_Mammals_Red_List_Nov_2009.csv")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.csvparser
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
import kotlinx.cli.*
|
||||
|
||||
fun parseLine(line: String, separator: Char) : List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val builder = StringBuilder()
|
||||
var quotes = 0
|
||||
for (ch in line) {
|
||||
when {
|
||||
ch == '\"' -> {
|
||||
quotes++
|
||||
builder.append(ch)
|
||||
}
|
||||
(ch == '\n') || (ch == '\r') -> {}
|
||||
(ch == separator) && (quotes % 2 == 0) -> {
|
||||
result.add(builder.toString())
|
||||
builder.setLength(0)
|
||||
}
|
||||
else -> builder.append(ch)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val argParser = ArgParser("csvparser")
|
||||
val fileName by argParser.argument(ArgType.String, description = "CSV file")
|
||||
val column by argParser.option(ArgType.Int, description = "Column to parse").required()
|
||||
val count by argParser.option(ArgType.Int, description = "Count of lines to parse").required()
|
||||
argParser.parse(args)
|
||||
|
||||
val file = fopen(fileName, "r")
|
||||
if (file == null) {
|
||||
perror("cannot open input file $fileName")
|
||||
return
|
||||
}
|
||||
|
||||
val keyValue = mutableMapOf<String, Int>()
|
||||
|
||||
try {
|
||||
memScoped {
|
||||
val bufferLength = 64 * 1024
|
||||
val buffer = allocArray<ByteVar>(bufferLength)
|
||||
|
||||
for (i in 1..count) {
|
||||
val nextLine = fgets(buffer, bufferLength, file)?.toKString()
|
||||
if (nextLine == null || nextLine.isEmpty()) break
|
||||
|
||||
val records = parseLine(nextLine, ',')
|
||||
val key = records[column]
|
||||
val current = keyValue[key] ?: 0
|
||||
keyValue[key] = current + 1
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
|
||||
keyValue.forEach {
|
||||
println("${it.key} -> ${it.value}")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val localRepo = rootProject.file("build/.m2-local")
|
||||
|
||||
repositories {
|
||||
maven("file://$localRepo")
|
||||
}
|
||||
|
||||
//val mingwPath = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
val mingwPath = File("C:/msys64/mingw64") // use only preinstalled verions from this path, otherwise fail with linkage errors
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("curl")
|
||||
hostOs == "Linux" -> linuxX64("curl")
|
||||
isMingwX64 -> mingwX64("curl")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.curl.main"
|
||||
if (isMingwX64) {
|
||||
// Add lib path to `libcurl` and its dependencies:
|
||||
linkerOpts("-L${mingwPath.resolve("lib")}")
|
||||
runTask?.environment("PATH" to mingwPath.resolve("bin"))
|
||||
}
|
||||
runTask?.args("https://www.jetbrains.com/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val curlMain by getting {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin.sample.native:libcurl:1.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The code snippet below is needed to make all compile tasks depend on publication of
|
||||
// "libcurl" library. So that to the time of compilation the library will already be
|
||||
// in Maven repo and will be successfully resolved as a dependency of this project.
|
||||
targets.all {
|
||||
compilations.all {
|
||||
compileKotlinTask.dependsOn(":libcurl:publish")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The following snippet is needed to give instructions for IDEA user who just imported project
|
||||
// and sees "Could not resolve..." message in IDEA console.
|
||||
gradle.buildFinished {
|
||||
val configurationName = kotlin.targets["curl"].compilations["main"].compileDependencyConfigurationName
|
||||
val configuration = project.configurations[configurationName]
|
||||
if (configuration.isCanBeResolved && configuration.state == Configuration.State.RESOLVED_WITH_FAILURES) {
|
||||
println(
|
||||
"""
|
||||
|
|
||||
|IMPORTANT:
|
||||
|The message about unresolved "libcurl" dependency likely means that "libcurl" has not been built and published to local Maven repo yet.
|
||||
|Please run "publish" task for "libcurl" sub-project and re-import Kotlin/Native samples in IDEA.
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.curl
|
||||
|
||||
import sample.libcurl.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty())
|
||||
return help()
|
||||
|
||||
val curl = CUrl(args[0])
|
||||
curl.header += {
|
||||
println("[H] $it")
|
||||
}
|
||||
|
||||
curl.body += {
|
||||
println("[B] $it")
|
||||
}
|
||||
|
||||
curl.fetch()
|
||||
curl.close()
|
||||
}
|
||||
|
||||
fun help() {
|
||||
println("ERROR: missing URL command line argument")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetPreset
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
// Add two additional presets for Raspberry Pi and Linux/ARM64.
|
||||
val raspberryPiPresets: List<KotlinNativeTargetPreset> = listOf("linuxArm32Hfp", "linuxArm64").map {
|
||||
kotlin.presets[it] as KotlinNativeTargetPreset
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create a target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("echoServer")
|
||||
hostOs == "Linux" -> linuxX64("echoServer")
|
||||
hostOs.startsWith("Windows") -> mingwX64("echoServer")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
// Create cross-targets.
|
||||
val raspberryPiTargets = raspberryPiPresets.map { preset ->
|
||||
val targetName = "echoServer${preset.name.capitalize()}"
|
||||
targetFromPreset(preset, targetName) {}
|
||||
}
|
||||
|
||||
// Configure executables for all targets.
|
||||
configure(raspberryPiTargets + listOf(hostTarget)) {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.echoserver.main"
|
||||
runTask?.args(3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val echoServerMain by getting
|
||||
raspberryPiPresets.forEach { preset ->
|
||||
val mainSourceSetName = "echoServer${preset.name.capitalize()}Main"
|
||||
getByName(mainSourceSetName).dependsOn(echoServerMain)
|
||||
}
|
||||
}
|
||||
|
||||
// Enable experimental stdlib API used by the sample.
|
||||
sourceSets.all {
|
||||
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.echoserver
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty()) {
|
||||
println("Usage: echoserver.kexe <port>")
|
||||
return
|
||||
}
|
||||
|
||||
val port = args[0].toShort()
|
||||
|
||||
// Initialize sockets in platform-dependent way.
|
||||
init_sockets()
|
||||
|
||||
memScoped {
|
||||
|
||||
val buffer = ByteArray(1024)
|
||||
val prefixBuffer = "echo: ".encodeToByteArray()
|
||||
val serverAddr = alloc<sockaddr_in>()
|
||||
|
||||
val listenFd = socket(AF_INET, SOCK_STREAM, 0)
|
||||
.ensureUnixCallResult("socket") { !it.isMinusOne() }
|
||||
|
||||
with(serverAddr) {
|
||||
memset(this.ptr, 0, sockaddr_in.size.convert())
|
||||
sin_family = AF_INET.convert()
|
||||
sin_port = posix_htons(port).convert()
|
||||
}
|
||||
|
||||
bind(listenFd, serverAddr.ptr.reinterpret(), sockaddr_in.size.convert())
|
||||
.ensureUnixCallResult("bind") { it == 0 }
|
||||
|
||||
listen(listenFd, 10)
|
||||
.ensureUnixCallResult("listen") { it == 0 }
|
||||
|
||||
val commFd = accept(listenFd, null, null)
|
||||
.ensureUnixCallResult("accept") { !it.isMinusOne() }
|
||||
|
||||
buffer.usePinned { pinned ->
|
||||
while (true) {
|
||||
val length = recv(commFd, pinned.addressOf(0), buffer.size.convert(), 0).toInt()
|
||||
.ensureUnixCallResult("read") { it >= 0 }
|
||||
|
||||
if (length == 0) {
|
||||
break
|
||||
}
|
||||
|
||||
send(commFd, prefixBuffer.refTo(0), prefixBuffer.size.convert(), 0)
|
||||
.ensureUnixCallResult("write") { it >= 0 }
|
||||
send(commFd, pinned.addressOf(0), length.convert(), 0)
|
||||
.ensureUnixCallResult("write") { it >= 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Int.ensureUnixCallResult(op: String, predicate: (Int) -> Boolean): Int {
|
||||
if (!predicate(this)) {
|
||||
throw Error("$op: ${strerror(posix_errno())!!.toKString()}")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun Long.ensureUnixCallResult(op: String, predicate: (Long) -> Boolean): Long {
|
||||
if (!predicate(this)) {
|
||||
throw Error("$op: ${strerror(posix_errno())!!.toKString()}")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun ULong.ensureUnixCallResult(op: String, predicate: (ULong) -> Boolean): ULong {
|
||||
if (!predicate(this)) {
|
||||
throw Error("$op: ${strerror(posix_errno())!!.toKString()}")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun Int.isMinusOne() = (this == -1)
|
||||
private fun Long.isMinusOne() = (this == -1L)
|
||||
private fun ULong.isMinusOne() = (this == ULong.MAX_VALUE)
|
||||
@@ -0,0 +1,27 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("globalState")
|
||||
hostOs == "Linux" -> linuxX64("globalState")
|
||||
hostOs.startsWith("Windows") -> mingwX64("globalState")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.globalstate.main"
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val global by creating
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.globalstate
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
|
||||
inline fun Int.ensureUnixCallResult(op: String, predicate: (Int) -> Boolean = { x -> x == 0} ): Int {
|
||||
if (!predicate(this)) {
|
||||
throw Error("$op: ${strerror(posix_errno())!!.toKString()}")
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
data class SharedDataMember(val double: Double)
|
||||
|
||||
data class SharedData(val string: String, val int: Int, val member: SharedDataMember)
|
||||
|
||||
// Here we access the same shared Kotlin object from multiple threads.
|
||||
val globalObject: SharedData?
|
||||
get() = sharedData.kotlinObject?.asStableRef<SharedData>()?.get()
|
||||
|
||||
fun dumpShared(prefix: String) {
|
||||
println("""
|
||||
$prefix: ${pthread_self()} x=${sharedData.x} f=${sharedData.f} s=${sharedData.string!!.toKString()}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// Arena owning all native allocs.
|
||||
val arena = Arena()
|
||||
|
||||
// Assign global data.
|
||||
sharedData.x = 239
|
||||
sharedData.f = 0.5f
|
||||
sharedData.string = "Hello Kotlin!".cstr.getPointer(arena)
|
||||
|
||||
// Here we create shared object reference,
|
||||
val stableRef = StableRef.create(SharedData("Shared", 239, SharedDataMember(2.71)))
|
||||
sharedData.kotlinObject = stableRef.asCPointer()
|
||||
dumpShared("thread1")
|
||||
println("shared is $globalObject")
|
||||
|
||||
// Start a new thread, that sees the variable.
|
||||
// memScoped is needed to pass thread's local address to pthread_create().
|
||||
memScoped {
|
||||
val thread = alloc<pthread_tVar>()
|
||||
pthread_create(thread.ptr, null, staticCFunction { argC ->
|
||||
initRuntimeIfNeeded()
|
||||
dumpShared("thread2")
|
||||
val arg = argC!!.asStableRef<SharedDataMember>()
|
||||
println("thread arg is ${arg.get()} shared is $globalObject")
|
||||
arg.dispose()
|
||||
// Workaround for compiler issue.
|
||||
null as COpaquePointer?
|
||||
}, StableRef.create(SharedDataMember(3.14)).asCPointer() ).ensureUnixCallResult("pthread_create")
|
||||
pthread_join(thread.value, null).ensureUnixCallResult("pthread_join")
|
||||
}
|
||||
|
||||
// At this moment we do not need data stored in shared data, so clean up the data
|
||||
// and free memory.
|
||||
sharedData.string = null
|
||||
stableRef.dispose()
|
||||
arena.clear()
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package = sample.globalstate
|
||||
|
||||
---
|
||||
typedef struct {
|
||||
int x;
|
||||
float f;
|
||||
char* string;
|
||||
void* kotlinObject;
|
||||
} SharedDataStruct;
|
||||
|
||||
SharedDataStruct sharedData;
|
||||
@@ -0,0 +1,21 @@
|
||||
kotlin.code.style=official
|
||||
|
||||
# Run parallel builds in Gradle:
|
||||
org.gradle.parallel=true
|
||||
org.gradle.workers.max=4
|
||||
|
||||
# Pin Kotlin version:
|
||||
# CHANGE_VERSION_WITH_RELEASE
|
||||
kotlin_version=1.6.0
|
||||
|
||||
# Sets maven path for the kotlin version other than release
|
||||
#kotlinCompilerRepo=
|
||||
|
||||
# Use custom Kotlin/Native home:
|
||||
kotlin.native.home=../../dist
|
||||
|
||||
# Increase memory for in-process compiler execution.
|
||||
org.gradle.jvmargs=-Xmx3g
|
||||
|
||||
# Disable HMPP with metadata until KT-50547 is not fixed
|
||||
kotlin.mpp.hierarchicalStructureSupport=false
|
||||
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||
distributionSha256Sum=bf8b869948901d422e9bb7d1fa61da6a6e19411baa7ad6ee929073df85d6365d
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,96 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
|
||||
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isWindows = hostOs.startsWith("Windows")
|
||||
|
||||
val packageName = "kotlinx.interop.wasm.dom"
|
||||
val jsinteropKlibFile = buildDir.resolve("klib").resolve("$packageName-jsinterop.klib")
|
||||
|
||||
kotlin {
|
||||
wasm32("html5Canvas") {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.html5canvas.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
jvm("httpServer")
|
||||
sourceSets {
|
||||
val html5CanvasMain by getting {
|
||||
dependencies {
|
||||
implementation(files(jsinteropKlibFile))
|
||||
}
|
||||
}
|
||||
val httpServerMain by getting {
|
||||
dependencies {
|
||||
implementation("io.ktor:ktor-server-netty:1.2.1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jsinterop by tasks.creating(Exec::class) {
|
||||
workingDir = projectDir
|
||||
|
||||
val ext = if (isWindows) ".bat" else ""
|
||||
val distributionPath = project.properties["kotlin.native.home"] as String?
|
||||
|
||||
if (distributionPath != null) {
|
||||
val jsinteropCommand = file(distributionPath).resolve("bin").resolve("jsinterop$ext")
|
||||
|
||||
inputs.property("jsinteropCommand", jsinteropCommand)
|
||||
inputs.property("jsinteropPackageName", packageName)
|
||||
outputs.file(jsinteropKlibFile)
|
||||
|
||||
commandLine(
|
||||
jsinteropCommand,
|
||||
"-pkg", packageName,
|
||||
"-o", jsinteropKlibFile,
|
||||
"-target", "wasm32"
|
||||
)
|
||||
} else {
|
||||
doFirst {
|
||||
// Abort build execution if the distribution path isn't specified.
|
||||
throw GradleException(
|
||||
"""
|
||||
|
|
||||
|Kotlin/Native distribution path must be specified to build the JavaScript interop.
|
||||
|Use 'kotlin.native.home' project property to specify it.
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(AbstractKotlinNativeCompile::class).all {
|
||||
dependsOn(jsinterop)
|
||||
}
|
||||
|
||||
val assemble by tasks.getting
|
||||
|
||||
// This is to run embedded HTTP server with Ktor:
|
||||
val runProgram by tasks.creating(JavaExec::class) {
|
||||
dependsOn(assemble)
|
||||
|
||||
val httpServer: KotlinJvmTarget by kotlin.targets
|
||||
val httpServerMainCompilation = httpServer.compilations["main"]
|
||||
|
||||
main = "sample.html5canvas.httpserver.HttpServer"
|
||||
classpath = files(httpServerMainCompilation.output) + httpServerMainCompilation.runtimeDependencyFiles
|
||||
args = listOf(projectDir.toString())
|
||||
}
|
||||
|
||||
tasks.withType(KotlinJvmCompile::class).all {
|
||||
runProgram.dependsOn(this)
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script src="build/bin/html5Canvas/releaseExecutable/html5Canvas.wasm.js" wasm="build/bin/html5Canvas/releaseExecutable/html5Canvas.wasm"> </script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="myCanvas" width="640" height="480" style="border:1px solid #000000;">
|
||||
</canvas>
|
||||
<p>Draw something using the mouse!</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.html5canvas
|
||||
|
||||
import kotlinx.interop.wasm.dom.*
|
||||
import kotlinx.wasm.jsinterop.*
|
||||
|
||||
fun main() {
|
||||
|
||||
val canvas = document.getElementById("myCanvas").asCanvas
|
||||
val ctx = canvas.getContext("2d")
|
||||
val rect = canvas.getBoundingClientRect()
|
||||
val rectLeft = rect.left
|
||||
val rectTop = rect.top
|
||||
|
||||
var mouseX: Int = 0
|
||||
var mouseY: Int = 0
|
||||
var draw: Boolean = false
|
||||
|
||||
document.setter("onmousemove") { arguments: ArrayList<JsValue> ->
|
||||
val event = MouseEvent(arguments[0])
|
||||
mouseX = event.getInt("clientX") - rectLeft
|
||||
mouseY = event.getInt("clientY") - rectTop
|
||||
|
||||
if (mouseX < 0) mouseX = 0
|
||||
if (mouseX > 639) mouseX = 639
|
||||
if (mouseY < 0) mouseY = 0
|
||||
if (mouseY > 479) mouseY = 479
|
||||
}
|
||||
|
||||
document.setter("onmousedown") {
|
||||
draw = true
|
||||
}
|
||||
|
||||
document.setter("onmouseup") {
|
||||
draw = false
|
||||
}
|
||||
|
||||
setInterval(10) {
|
||||
if (draw) {
|
||||
ctx.strokeStyle = "#222222"
|
||||
ctx.lineTo(mouseX, mouseY)
|
||||
ctx.stroke()
|
||||
} else {
|
||||
ctx.moveTo(mouseX, mouseY)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:JvmName("HttpServer")
|
||||
|
||||
package sample.html5canvas.httpserver
|
||||
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.content.LocalFileContent
|
||||
import io.ktor.http.content.default
|
||||
import io.ktor.http.content.files
|
||||
import io.ktor.http.content.static
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import java.io.File
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
check(args.size == 1) { "Invalid number of arguments: $args.\nExpected one argument with content root." }
|
||||
|
||||
val contentRoot = File(args[0])
|
||||
check(contentRoot.isDirectory) { "Invalid content root: $contentRoot." }
|
||||
|
||||
println(
|
||||
"""
|
||||
|
||||
IMPORTANT: Please open http://localhost:8080/ in your browser!
|
||||
|
||||
To stop embedded HTTP server use Ctrl+C (Cmd+C for Mac OS X).
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val server = embeddedServer(Netty, 8080) {
|
||||
routing {
|
||||
val wasm = "build/bin/html5Canvas/releaseExecutable/html5Canvas.wasm"
|
||||
get(wasm) {
|
||||
// TODO: ktor as of now doesn't know about 'application/wasm'.
|
||||
// The newer browsers (firefox and chrome at least) don't allow
|
||||
// 'application/octet-stream' for wasm anymore.
|
||||
// We provide the proper content type here and,
|
||||
// at the same time, put it into the ktor database.
|
||||
// Remove this whole get() clause when ktor fix is available.
|
||||
call.respond(LocalFileContent(File(wasm), ContentType("application", "wasm")))
|
||||
}
|
||||
static("/") {
|
||||
files(contentRoot)
|
||||
default("index.html")
|
||||
}
|
||||
}
|
||||
}
|
||||
server.start(wait = true)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "org.jetbrains.kotlin.sample.native"
|
||||
version = "1.0"
|
||||
|
||||
val localRepo = rootProject.file("build/.m2-local")
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven("file://$localRepo")
|
||||
}
|
||||
}
|
||||
|
||||
val cleanLocalRepo by tasks.creating(Delete::class) {
|
||||
delete(localRepo)
|
||||
}
|
||||
|
||||
val mingwPath = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
|
||||
kotlin {
|
||||
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("libcurl")
|
||||
hostOs == "Linux" -> linuxX64("libcurl")
|
||||
hostOs.startsWith("Windows") -> mingwX64("libcurl")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
compilations["main"].cinterops {
|
||||
val libcurl by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"] -> includeDirs.headerFilterOnly("/opt/local/include", "/usr/local/include")
|
||||
presets["linuxX64"] -> includeDirs.headerFilterOnly("/usr/include", "/usr/include/x86_64-linux-gnu")
|
||||
presets["mingwX64"] -> includeDirs.headerFilterOnly(mingwPath.resolve("include"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mavenPublication {
|
||||
pom {
|
||||
withXml {
|
||||
val root = asNode()
|
||||
root.appendNode("name", "libcurl interop library")
|
||||
root.appendNode("description", "A library providing interoperability with host libcurl")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable experimental stdlib API used by the sample.
|
||||
sourceSets.all {
|
||||
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.libcurl
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.size_t
|
||||
import libcurl.*
|
||||
|
||||
class CUrl(url: String) {
|
||||
private val stableRef = StableRef.create(this)
|
||||
|
||||
private val curl = curl_easy_init()
|
||||
|
||||
init {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url)
|
||||
val header = staticCFunction(::header_callback)
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header)
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, stableRef.asCPointer())
|
||||
val writeData = staticCFunction(::write_callback)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stableRef.asCPointer())
|
||||
}
|
||||
|
||||
val header = Event<String>()
|
||||
val body = Event<String>()
|
||||
|
||||
fun nobody(){
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L)
|
||||
}
|
||||
|
||||
fun fetch() {
|
||||
val res = curl_easy_perform(curl)
|
||||
if (res != CURLE_OK)
|
||||
println("curl_easy_perform() failed: ${curl_easy_strerror(res)?.toKString()}")
|
||||
}
|
||||
|
||||
fun close() {
|
||||
curl_easy_cleanup(curl)
|
||||
stableRef.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun CPointer<ByteVar>.toKString(length: Int): String {
|
||||
val bytes = this.readBytes(length)
|
||||
return bytes.decodeToString()
|
||||
}
|
||||
|
||||
fun header_callback(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
|
||||
if (buffer == null) return 0u
|
||||
if (userdata != null) {
|
||||
val header = buffer.toKString((size * nitems).toInt()).trim()
|
||||
val curl = userdata.asStableRef<CUrl>().get()
|
||||
curl.header(header)
|
||||
}
|
||||
return size * nitems
|
||||
}
|
||||
|
||||
|
||||
fun write_callback(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
|
||||
if (buffer == null) return 0u
|
||||
if (userdata != null) {
|
||||
val data = buffer.toKString((size * nitems).toInt()).trim()
|
||||
val curl = userdata.asStableRef<CUrl>().get()
|
||||
curl.body(data)
|
||||
}
|
||||
return size * nitems
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.libcurl
|
||||
|
||||
typealias EventHandler<T> = (T) -> Unit
|
||||
class Event<T : Any> {
|
||||
private var handlers = emptyList<EventHandler<T>>()
|
||||
|
||||
fun subscribe(handler: EventHandler<T>) {
|
||||
handlers += handler
|
||||
}
|
||||
|
||||
fun unsubscribe(handler: EventHandler<T>) {
|
||||
handlers -= handler
|
||||
}
|
||||
|
||||
operator fun plusAssign(handler: EventHandler<T>) = subscribe(handler)
|
||||
operator fun minusAssign(handler: EventHandler<T>) = unsubscribe(handler)
|
||||
|
||||
operator fun invoke(value: T) {
|
||||
var exception: Throwable? = null
|
||||
for (handler in handlers) {
|
||||
try {
|
||||
handler(value)
|
||||
} catch (e: Throwable) {
|
||||
exception = e
|
||||
}
|
||||
}
|
||||
exception?.let { throw it }
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
headers = curl/curl.h
|
||||
headerFilter = curl/*
|
||||
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lcurl
|
||||
linkerOpts.mingw = -lcurl
|
||||
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("nonBlockingEchoServer")
|
||||
hostOs == "Linux" -> linuxX64("nonBlockingEchoServer")
|
||||
hostOs.startsWith("Windows") -> mingwX64("nonBlockingEchoServer")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.nbechoserver.main"
|
||||
runTask?.args(3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.nbechoserver
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty()) {
|
||||
println("Usage: nonBlockingEchoServer.kexe <port>")
|
||||
return
|
||||
}
|
||||
|
||||
val port = args[0].toShort()
|
||||
|
||||
memScoped {
|
||||
|
||||
val serverAddr = alloc<sockaddr_in>()
|
||||
|
||||
val listenFd = socket(AF_INET, SOCK_STREAM, 0)
|
||||
.ensureUnixCallResult { !it.isMinusOne() }
|
||||
|
||||
with(serverAddr) {
|
||||
memset(this.ptr, 0, sockaddr_in.size.convert())
|
||||
sin_family = AF_INET.convert()
|
||||
sin_addr.s_addr = posix_htons(0).convert()
|
||||
sin_port = posix_htons(port).convert()
|
||||
}
|
||||
|
||||
bind(listenFd, serverAddr.ptr.reinterpret(), sockaddr_in.size.toUInt())
|
||||
.ensureUnixCallResult { it == 0 }
|
||||
|
||||
fcntl(listenFd, F_SETFL, O_NONBLOCK)
|
||||
.ensureUnixCallResult { it == 0 }
|
||||
|
||||
listen(listenFd, 10)
|
||||
.ensureUnixCallResult { it == 0 }
|
||||
|
||||
var connectionId = 0
|
||||
acceptClientsAndRun(listenFd) {
|
||||
memScoped {
|
||||
val bufferLength = 100uL
|
||||
val buffer = allocArray<ByteVar>(bufferLength.toLong())
|
||||
val connectionIdString = "#${++connectionId}: ".cstr
|
||||
val connectionIdBytes = connectionIdString.ptr
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
val length = read(buffer, bufferLength)
|
||||
|
||||
if (length == 0uL)
|
||||
break
|
||||
|
||||
write(connectionIdBytes, connectionIdString.size.toULong())
|
||||
write(buffer, length)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
println("I/O error occured: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WaitingFor {
|
||||
class Accept : WaitingFor()
|
||||
|
||||
class Read(val data: CArrayPointer<ByteVar>,
|
||||
val length: ULong,
|
||||
val continuation: Continuation<ULong>) : WaitingFor()
|
||||
|
||||
class Write(val data: CArrayPointer<ByteVar>,
|
||||
val length: ULong,
|
||||
val continuation: Continuation<Unit>) : WaitingFor()
|
||||
}
|
||||
|
||||
class Client(val clientFd: Int, val waitingList: MutableMap<Int, WaitingFor>) {
|
||||
suspend fun read(data: CArrayPointer<ByteVar>, dataLength: ULong): ULong {
|
||||
val length = read(clientFd, data, dataLength)
|
||||
if (length >= 0)
|
||||
return length.toULong()
|
||||
if (posix_errno() != EWOULDBLOCK)
|
||||
throw IOException(getUnixError())
|
||||
// Save continuation and suspend.
|
||||
return suspendCoroutine { continuation ->
|
||||
waitingList.put(clientFd, WaitingFor.Read(data, dataLength, continuation))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun write(data: CArrayPointer<ByteVar>, length: ULong) {
|
||||
val written = write(clientFd, data, length)
|
||||
if (written >= 0)
|
||||
return
|
||||
if (posix_errno() != EWOULDBLOCK)
|
||||
throw IOException(getUnixError())
|
||||
// Save continuation and suspend.
|
||||
return suspendCoroutine { continuation ->
|
||||
waitingList.put(clientFd, WaitingFor.Write(data, length, continuation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
|
||||
companion object : EmptyContinuation()
|
||||
override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
|
||||
}
|
||||
|
||||
fun acceptClientsAndRun(serverFd: Int, block: suspend Client.() -> Unit) {
|
||||
memScoped {
|
||||
val waitingList = mutableMapOf<Int, WaitingFor>(serverFd to WaitingFor.Accept())
|
||||
val readfds = alloc<fd_set>()
|
||||
val writefds = alloc<fd_set>()
|
||||
val errorfds = alloc<fd_set>()
|
||||
var maxfd = serverFd
|
||||
while (true) {
|
||||
posix_FD_ZERO(readfds.ptr)
|
||||
posix_FD_ZERO(writefds.ptr)
|
||||
posix_FD_ZERO(errorfds.ptr)
|
||||
for ((socketFd, watingFor) in waitingList) {
|
||||
when (watingFor) {
|
||||
is WaitingFor.Accept -> posix_FD_SET(socketFd, readfds.ptr)
|
||||
is WaitingFor.Read -> posix_FD_SET(socketFd, readfds.ptr)
|
||||
is WaitingFor.Write -> posix_FD_SET(socketFd, writefds.ptr)
|
||||
}
|
||||
posix_FD_SET(socketFd, errorfds.ptr)
|
||||
}
|
||||
pselect(maxfd + 1, readfds.ptr, writefds.ptr, errorfds.ptr, null, null)
|
||||
.ensureUnixCallResult { it >= 0 }
|
||||
loop@for (socketFd in 0..maxfd) {
|
||||
val waitingFor = waitingList[socketFd]
|
||||
val errorOccured = posix_FD_ISSET(socketFd, errorfds.ptr) != 0
|
||||
if (posix_FD_ISSET(socketFd, readfds.ptr) != 0
|
||||
|| posix_FD_ISSET(socketFd, writefds.ptr) != 0
|
||||
|| errorOccured) {
|
||||
when (waitingFor) {
|
||||
is WaitingFor.Accept -> {
|
||||
if (errorOccured)
|
||||
throw Error("Socket has been closed externally")
|
||||
|
||||
// Accept new client.
|
||||
val clientFd = accept(serverFd, null, null)
|
||||
if (clientFd.isMinusOne()) {
|
||||
if (posix_errno() != EWOULDBLOCK)
|
||||
throw Error(getUnixError())
|
||||
break@loop
|
||||
}
|
||||
fcntl(clientFd, F_SETFL, O_NONBLOCK)
|
||||
.ensureUnixCallResult { it == 0 }
|
||||
if (maxfd < clientFd)
|
||||
maxfd = clientFd
|
||||
block.startCoroutine(Client(clientFd, waitingList), EmptyContinuation)
|
||||
}
|
||||
is WaitingFor.Read -> {
|
||||
if (errorOccured)
|
||||
waitingFor.continuation.resumeWithException(IOException("Connection was closed by peer"))
|
||||
|
||||
// Resume reading operation.
|
||||
waitingList.remove(socketFd)
|
||||
val length = read(socketFd, waitingFor.data, waitingFor.length)
|
||||
if (length < 0) // Read error.
|
||||
waitingFor.continuation.resumeWithException(IOException(getUnixError()))
|
||||
waitingFor.continuation.resume(length.toULong())
|
||||
}
|
||||
is WaitingFor.Write -> {
|
||||
if (errorOccured)
|
||||
waitingFor.continuation.resumeWithException(IOException("Connection was closed by peer"))
|
||||
|
||||
// Resume writing operation.
|
||||
waitingList.remove(socketFd)
|
||||
val written = write(socketFd, waitingFor.data, waitingFor.length)
|
||||
if (written < 0) // Write error.
|
||||
waitingFor.continuation.resumeWithException(IOException(getUnixError()))
|
||||
waitingFor.continuation.resume(Unit)
|
||||
}
|
||||
else -> throw Error("No operation for $waitingFor is defined")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IOException(message: String): RuntimeException(message)
|
||||
|
||||
fun getUnixError() = strerror(posix_errno())!!.toKString()
|
||||
|
||||
inline fun Int.ensureUnixCallResult(predicate: (Int) -> Boolean): Int {
|
||||
if (!predicate(this)) {
|
||||
throw Error(getUnixError())
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun Long.ensureUnixCallResult(predicate: (Long) -> Boolean): Long {
|
||||
if (!predicate(this)) {
|
||||
throw Error(getUnixError())
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun ULong.ensureUnixCallResult(predicate: (ULong) -> Boolean): ULong {
|
||||
if (!predicate(this)) {
|
||||
throw Error(getUnixError())
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun Int.isMinusOne() = (this == -1)
|
||||
private fun Long.isMinusOne() = (this == -1L)
|
||||
private fun ULong.isMinusOne() = (this == ULong.MAX_VALUE)
|
||||
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
macosX64("objc") {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.objc.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,103 @@
|
||||
package sample.objc
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.Foundation.NSOperationQueue
|
||||
import platform.Foundation.NSThread
|
||||
import platform.darwin.dispatch_async_f
|
||||
import platform.darwin.dispatch_get_main_queue
|
||||
import platform.darwin.dispatch_sync_f
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
|
||||
dispatch_async_f(queue.underlyingQueue, StableRef.create(
|
||||
producerConsumer()
|
||||
).asCPointer(), staticCFunction { it ->
|
||||
val result = it!!.asStableRef<Pair<T, (T) -> Unit>>()
|
||||
result.get().second(result.get().first)
|
||||
result.dispose()
|
||||
})
|
||||
}
|
||||
|
||||
inline fun mainContinuation(singleShot: Boolean = true, noinline block: () -> Unit) = Continuation0(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation0()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation0()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
inline fun <T1> mainContinuation(singleShot: Boolean = true, noinline block: (T1) -> Unit) = Continuation1(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation1<T1>()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation1<T1>()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
inline fun <T1, T2> mainContinuation(singleShot: Boolean = true, noinline block: (T1, T2) -> Unit) = Continuation2(
|
||||
block, staticCFunction { invokerArg ->
|
||||
if (NSThread.isMainThread()) {
|
||||
invokerArg!!.callContinuation2<T1, T2>()
|
||||
} else {
|
||||
dispatch_sync_f(dispatch_get_main_queue(), invokerArg, staticCFunction { args ->
|
||||
args!!.callContinuation2<T1, T2>()
|
||||
})
|
||||
}
|
||||
}, singleShot)
|
||||
|
||||
// This object allows to create frozen Kotlin continuations suitable for execution on other threads/queues.
|
||||
// It takes frozen operation and any after call, and creates lambda which could be used to run operation
|
||||
// anywhere and provide result to `after` callback.
|
||||
@ThreadLocal
|
||||
object Continuator {
|
||||
val map = mutableMapOf<Any, Pair<Int, *>>()
|
||||
|
||||
fun wrap(operation: () -> Unit, after: () -> Unit): () -> Unit {
|
||||
assert(NSThread.isMainThread())
|
||||
val id = Any()
|
||||
map[id] = Pair(0, after)
|
||||
return {
|
||||
initRuntimeIfNeeded()
|
||||
operation()
|
||||
executeAsync(NSOperationQueue.mainQueue) {
|
||||
Pair(id, { id: Any -> Continuator.execute(id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <P> wrap(operation: () -> P, block: (P) -> Unit): () -> Unit {
|
||||
assert(NSThread.isMainThread())
|
||||
val id = Any()
|
||||
map[id] = Pair(1, block)
|
||||
return {
|
||||
initRuntimeIfNeeded()
|
||||
// Note, that operation here must return detachable value (for example, frozen).
|
||||
executeAsync(NSOperationQueue.mainQueue) {
|
||||
Pair(Pair(id, operation()), { it: Pair<Any, P> ->
|
||||
Continuator.execute(it.first, it.second)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun execute(id: Any) {
|
||||
val countAndBlock = map.remove(id)
|
||||
assertNotNull(countAndBlock)
|
||||
assert(countAndBlock.first == 0)
|
||||
(countAndBlock.second as Function0<Unit>)()
|
||||
}
|
||||
|
||||
fun <P> execute(id: Any, parameter: P) {
|
||||
val countAndBlock = map.remove(id)
|
||||
assertNotNull(countAndBlock)
|
||||
assert(countAndBlock.first == 1)
|
||||
(countAndBlock.second as Function1<P, Unit>)(parameter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 sample.objc
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.AppKit.*
|
||||
import platform.Contacts.CNContactStore
|
||||
import platform.Contacts.CNEntityType
|
||||
import platform.Foundation.*
|
||||
import platform.darwin.*
|
||||
import platform.posix.QOS_CLASS_BACKGROUND
|
||||
import platform.posix.memcpy
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
data class QueryResult(val json: Map<String, *>?, val error: String?)
|
||||
|
||||
private val NSData.json: Map<String, *>?
|
||||
get() = NSJSONSerialization.JSONObjectWithData(this, 0, null) as? Map<String, *>
|
||||
|
||||
fun main() {
|
||||
autoreleasepool {
|
||||
runApp()
|
||||
}
|
||||
}
|
||||
|
||||
val appDelegate = MyAppDelegate()
|
||||
|
||||
private fun runApp() {
|
||||
val app = NSApplication.sharedApplication()
|
||||
|
||||
app.delegate = appDelegate
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
|
||||
app.activateIgnoringOtherApps(true)
|
||||
|
||||
app.run()
|
||||
}
|
||||
|
||||
|
||||
class Controller : NSObject() {
|
||||
private var index = 1
|
||||
private val httpDelegate = HttpDelegate()
|
||||
|
||||
@ObjCAction
|
||||
fun onClick() {
|
||||
if (!appDelegate.canClick) {
|
||||
appDelegate.contentText.string = "Another load in progress..."
|
||||
return
|
||||
}
|
||||
|
||||
// Here we call continuator service to ensure we can access mutable state from continuation.
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND.convert(), 0),
|
||||
Continuator.wrap({ println("In queue ${dispatch_get_current_queue()}")}) {
|
||||
println("After in queue ${dispatch_get_current_queue()}: $index")
|
||||
})
|
||||
|
||||
appDelegate.canClick = false
|
||||
// Fetch URL in the background on the button click.
|
||||
httpDelegate.fetchUrl("https://jsonplaceholder.typicode.com/todos/${index++}")
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun onQuit() {
|
||||
NSApplication.sharedApplication().stop(this)
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun onRequest() {
|
||||
val addressBookRef = CNContactStore()
|
||||
addressBookRef.requestAccessForEntityType(CNEntityType.CNEntityTypeContacts, mainContinuation {
|
||||
granted, error ->
|
||||
appDelegate.contentText.string = if (granted)
|
||||
"Access granted!"
|
||||
else
|
||||
"Access denied: $error"
|
||||
})
|
||||
}
|
||||
|
||||
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
|
||||
private val asyncQueue = NSOperationQueue()
|
||||
private var receivedData: NSData? = null
|
||||
|
||||
fun fetchUrl(url: String) {
|
||||
receivedData = null
|
||||
val session = NSURLSession.sessionWithConfiguration(
|
||||
NSURLSessionConfiguration.defaultSessionConfiguration(),
|
||||
this,
|
||||
delegateQueue = asyncQueue
|
||||
)
|
||||
session.dataTaskWithURL(NSURL(string = url)).resume()
|
||||
}
|
||||
|
||||
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
|
||||
initRuntimeIfNeeded()
|
||||
receivedData = didReceiveData
|
||||
}
|
||||
|
||||
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
|
||||
initRuntimeIfNeeded()
|
||||
|
||||
executeAsync(NSOperationQueue.mainQueue) {
|
||||
val response = task.response as? NSHTTPURLResponse
|
||||
Pair(when {
|
||||
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
|
||||
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
|
||||
else -> QueryResult(receivedData?.json, null)
|
||||
}, { result: QueryResult ->
|
||||
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
|
||||
appDelegate.canClick = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
|
||||
|
||||
private val window: NSWindow
|
||||
private val controller = Controller()
|
||||
val contentText: NSText
|
||||
var canClick = true
|
||||
|
||||
init {
|
||||
val mainDisplayRect = NSScreen.mainScreen()!!.frame
|
||||
val windowRect = mainDisplayRect.useContents {
|
||||
NSMakeRect(
|
||||
origin.x + size.width * 0.25,
|
||||
origin.y + size.height * 0.25,
|
||||
size.width * 0.5,
|
||||
size.height * 0.5
|
||||
)
|
||||
}
|
||||
|
||||
val windowStyle = NSWindowStyleMaskTitled or NSWindowStyleMaskMiniaturizable or
|
||||
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
|
||||
|
||||
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
|
||||
title = "URL async fetcher"
|
||||
opaque = true
|
||||
hasShadow = true
|
||||
preferredBackingLocation = NSWindowBackingLocationVideoMemory
|
||||
hidesOnDeactivate = false
|
||||
backgroundColor = NSColor.grayColor()
|
||||
releasedWhenClosed = false
|
||||
|
||||
val delegateImpl = object : NSObject(), NSWindowDelegateProtocol {
|
||||
override fun windowShouldClose(sender: NSWindow): Boolean {
|
||||
NSApplication.sharedApplication().stop(this)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapping to autoreleasepool is a workaround for false-positive memory leak detected:
|
||||
// NSWindow.delegate setter appears to put the delegate to autorelease pool.
|
||||
// Since this code runs during top-level val initializer, it misses the autoreleasepool in [main],
|
||||
// so the object gets released too late.
|
||||
autoreleasepool {
|
||||
delegate = delegateImpl
|
||||
}
|
||||
}
|
||||
|
||||
val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply {
|
||||
title = "Click"
|
||||
target = controller
|
||||
action = NSSelectorFromString("onClick")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonPress)
|
||||
val buttonQuit = NSButton(NSMakeRect(120.0, 10.0, 100.0, 40.0)).apply {
|
||||
title = "Quit"
|
||||
target = controller
|
||||
action = NSSelectorFromString("onQuit")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonQuit)
|
||||
|
||||
val buttonRequest = NSButton(NSMakeRect(230.0, 10.0, 100.0, 40.0)).apply {
|
||||
title = "Request"
|
||||
target = controller
|
||||
action = NSSelectorFromString("onRequest")
|
||||
}
|
||||
window.contentView!!.addSubview(buttonRequest)
|
||||
|
||||
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
|
||||
string = "Press 'Click' to start fetching"
|
||||
verticallyResizable = false
|
||||
horizontallyResizable = false
|
||||
|
||||
}
|
||||
window.contentView!!.addSubview(contentText)
|
||||
}
|
||||
|
||||
override fun applicationWillFinishLaunching(notification: NSNotification) {
|
||||
window.makeKeyAndOrderFront(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
macosX64("opengl") {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.opengl.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.opengl
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.GLUT.*
|
||||
import platform.OpenGL.*
|
||||
import platform.OpenGLCommon.*
|
||||
|
||||
// Ported from http://openglsamples.sourceforge.net/projects/index.php/blog/index/
|
||||
|
||||
private var rotation: GLfloat = 0.0f
|
||||
private val rotationSpeed: GLfloat = 0.2f
|
||||
|
||||
private val windowWidth = 640
|
||||
private val windowHeight = 480
|
||||
|
||||
fun display() {
|
||||
// Clear Screen and Depth Buffer
|
||||
glClear((GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT).convert())
|
||||
glLoadIdentity()
|
||||
|
||||
// Define a viewing transformation
|
||||
gluLookAt(4.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
||||
|
||||
// Push and pop the current matrix stack.
|
||||
// This causes that translations and rotations on this matrix wont influence others.
|
||||
|
||||
glPushMatrix()
|
||||
glColor3f(1.0f, 0.0f, 0.0f)
|
||||
glTranslatef(0.0f, 0.0f, 0.0f)
|
||||
glRotatef(rotation, 0.0f, 1.0f, 0.0f)
|
||||
glRotatef(90.0f, 0.0f, 1.0f, 0.0f)
|
||||
|
||||
// Draw the teapot
|
||||
glutSolidTeapot(1.0)
|
||||
glPopMatrix()
|
||||
|
||||
|
||||
rotation += rotationSpeed
|
||||
glutSwapBuffers()
|
||||
}
|
||||
|
||||
|
||||
fun initialize() {
|
||||
// select projection matrix
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
|
||||
// set the viewport
|
||||
glViewport(0, 0, windowWidth, windowHeight)
|
||||
|
||||
// set matrix mode
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
|
||||
// reset projection matrix
|
||||
glLoadIdentity()
|
||||
val aspect = windowWidth.toDouble() / windowHeight
|
||||
|
||||
// set up a perspective projection matrix
|
||||
gluPerspective(45.0, aspect, 1.0, 500.0)
|
||||
|
||||
// specify which matrix is the current matrix
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
glShadeModel(GL_SMOOTH)
|
||||
|
||||
// specify the clear value for the depth buffer
|
||||
glClearDepth(1.0)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glDepthFunc(GL_LEQUAL)
|
||||
|
||||
// specify implementation-specific hints
|
||||
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
|
||||
|
||||
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, cValuesOf(0.1f, 0.1f, 0.1f, 1.0f))
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, cValuesOf(0.6f, 0.6f, 0.6f, 1.0f))
|
||||
glLightfv(GL_LIGHT0, GL_SPECULAR, cValuesOf(0.7f, 0.7f, 0.3f, 1.0f))
|
||||
|
||||
glEnable(GL_LIGHT0)
|
||||
glEnable(GL_COLOR_MATERIAL)
|
||||
glShadeModel(GL_SMOOTH)
|
||||
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE)
|
||||
glDepthFunc(GL_LEQUAL)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_LIGHT0)
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// initialize and run program
|
||||
memScoped {
|
||||
val argc = alloc<IntVar>().apply { value = 0 }
|
||||
glutInit(argc.ptr, null) // TODO: pass real args
|
||||
}
|
||||
|
||||
// Display Mode
|
||||
glutInitDisplayMode((GLUT_RGB or GLUT_DOUBLE or GLUT_DEPTH).convert())
|
||||
|
||||
// Set window size
|
||||
glutInitWindowSize(windowWidth, windowHeight)
|
||||
|
||||
// create Window
|
||||
glutCreateWindow("The GLUT Teapot")
|
||||
|
||||
// register Display Function
|
||||
glutDisplayFunc(staticCFunction(::display))
|
||||
|
||||
// register Idle Function
|
||||
glutIdleFunc(staticCFunction(::display))
|
||||
|
||||
initialize()
|
||||
|
||||
// run GLUT mainloop
|
||||
glutMainLoop()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMacos = hostOs == "Mac OS X"
|
||||
val isLinux = hostOs == "Linux"
|
||||
val isWindows = hostOs.startsWith("Windows")
|
||||
|
||||
/*
|
||||
* The following projects are only available for certain platforms.
|
||||
*
|
||||
* IMPORTANT: If a new sample doesn't include interop with third-party libraries,
|
||||
* add it into the 'buildSamplesWithPlatfromLibs' task in the root build.gradle.
|
||||
*/
|
||||
if (isMacos || isLinux || isWindows) {
|
||||
include(":csvparser")
|
||||
include(":curl")
|
||||
include(":echoServer")
|
||||
include(":globalState")
|
||||
include(":html5Canvas")
|
||||
include(":libcurl")
|
||||
include(":videoplayer")
|
||||
include(":workers")
|
||||
include(":coverage")
|
||||
}
|
||||
|
||||
if (isMacos || isLinux) {
|
||||
include(":nonBlockingEchoServer")
|
||||
include(":tensorflow")
|
||||
}
|
||||
|
||||
if (isMacos) {
|
||||
include(":objc")
|
||||
include(":opengl")
|
||||
include(":uikit")
|
||||
include(":watchos")
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
include(":win32")
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val kotlinNativeDataPath = System.getenv("KONAN_DATA_DIR")?.let { File(it) }
|
||||
?: File(System.getProperty("user.home")).resolve(".konan")
|
||||
|
||||
val tensorflowHome = kotlinNativeDataPath.resolve("third-party/tensorflow")
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("tensorflow")
|
||||
hostOs == "Linux" -> linuxX64("tensorflow")
|
||||
// Windows is not supported
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.tensorflow.main"
|
||||
linkerOpts("-L${tensorflowHome.resolve("lib")}", "-ltensorflow")
|
||||
runTask?.environment(
|
||||
"LD_LIBRARY_PATH" to tensorflowHome.resolve("lib"),
|
||||
"DYLD_LIBRARY_PATH" to tensorflowHome.resolve("lib")
|
||||
)
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val tensorflow by creating {
|
||||
includeDirs(tensorflowHome.resolve("include"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val downloadTensorflow by tasks.creating(Exec::class) {
|
||||
workingDir = projectDir
|
||||
commandLine("./downloadTensorflow.sh")
|
||||
}
|
||||
|
||||
val tensorflow: KotlinNativeTarget by kotlin.targets
|
||||
tasks[tensorflow.compilations["main"].cinterops["tensorflow"].interopProcessingTaskName].dependsOn(downloadTensorflow)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
KONAN_USER_DIR=${KONAN_DATA_DIR:-"$HOME/.konan"}
|
||||
TF_TARGET_DIRECTORY="$KONAN_USER_DIR/third-party/tensorflow"
|
||||
TF_TYPE="cpu" # Change to "gpu" for GPU support
|
||||
|
||||
if [ x$TARGET == x ]; then
|
||||
case "$OSTYPE" in
|
||||
darwin*) TARGET=macbook; TF_TARGET=darwin ;;
|
||||
linux*) TARGET=linux; TF_TARGET=linux ;;
|
||||
*) echo "unknown: $OSTYPE" && exit 1;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ ! -d $TF_TARGET_DIRECTORY/include/tensorflow ]; then
|
||||
echo "Installing TensorFlow into $TF_TARGET_DIRECTORY ..."
|
||||
mkdir -p $TF_TARGET_DIRECTORY
|
||||
curl -s -L "https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-${TF_TYPE}-${TF_TARGET}-x86_64-1.1.0.tar.gz" | tar -C $TF_TARGET_DIRECTORY -xz
|
||||
fi
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+1
@@ -0,0 +1 @@
|
||||
headers = tensorflow/c/c_api.h
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.tensorflow
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.size_t
|
||||
import tensorflow.*
|
||||
|
||||
typealias Status = CPointer<TF_Status>
|
||||
typealias Operation = CPointer<TF_Operation>
|
||||
typealias Tensor = CPointer<TF_Tensor>
|
||||
|
||||
val Status.isOk: Boolean get() = TF_GetCode(this) == TF_OK
|
||||
val Status.errorMessage: String get() = TF_Message(this)!!.toKString()
|
||||
fun Status.delete() = TF_DeleteStatus(this)
|
||||
fun Status.validate() {
|
||||
try {
|
||||
if (!isOk) {
|
||||
throw Error("Status is not ok: $errorMessage")
|
||||
}
|
||||
} finally {
|
||||
delete()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> statusValidated(block: (Status) -> T): T {
|
||||
val status = TF_NewStatus()!!
|
||||
val result = block(status)
|
||||
status.validate()
|
||||
return result
|
||||
}
|
||||
|
||||
fun scalarTensor(value: Int): Tensor {
|
||||
val data = nativeHeap.allocArray<IntVar>(1)
|
||||
data[0] = value
|
||||
|
||||
return TF_NewTensor(
|
||||
TF_INT32,
|
||||
/* dims = */ null,
|
||||
/* num_dims = */ 0,
|
||||
/* data = */ data,
|
||||
/* len = */ IntVar.size.convert(),
|
||||
/* deallocator = */ staticCFunction { dataToFree, _, _ -> nativeHeap.free(dataToFree!!.reinterpret<IntVar>()) },
|
||||
/* deallocator_arg = */ null
|
||||
)!!
|
||||
}
|
||||
|
||||
val Tensor.scalarIntValue: Int get() {
|
||||
if (TF_INT32 != TF_TensorType(this) || IntVar.size.convert<size_t>() != TF_TensorByteSize(this)) {
|
||||
throw Error("Tensor is not of type int.")
|
||||
}
|
||||
if (0 != TF_NumDims(this)) {
|
||||
throw Error("Tensor is not scalar.")
|
||||
}
|
||||
|
||||
return TF_TensorData(this)!!.reinterpret<IntVar>().pointed.value
|
||||
}
|
||||
|
||||
|
||||
class Graph {
|
||||
val tensorflowGraph = TF_NewGraph()!!
|
||||
|
||||
inline fun operation(type: String, name: String, initDescription: (CPointer<TF_OperationDescription>) -> Unit): Operation {
|
||||
val description = TF_NewOperation(tensorflowGraph, type, name)!!
|
||||
initDescription(description)
|
||||
return statusValidated { TF_FinishOperation(description, it)!! }
|
||||
}
|
||||
|
||||
fun constant(value: Int, name: String = "scalarIntConstant") = operation("Const", name) { description ->
|
||||
statusValidated { TF_SetAttrTensor(description, "value", scalarTensor(value), it) }
|
||||
TF_SetAttrType(description, "dtype", TF_INT32)
|
||||
}
|
||||
|
||||
fun intInput(name: String = "input") = operation("Placeholder", name) { description ->
|
||||
TF_SetAttrType(description, "dtype", TF_INT32)
|
||||
}
|
||||
|
||||
fun add(left: Operation, right: Operation, name: String = "add") = memScoped {
|
||||
val inputs = allocArray<TF_Output>(2)
|
||||
inputs[0].apply { oper = left; index = 0 }
|
||||
inputs[1].apply { oper = right; index = 0 }
|
||||
|
||||
operation("AddN", name) { description ->
|
||||
TF_AddInputList(description, inputs, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO set unique operation names
|
||||
operator fun Operation.plus(right: Operation) = add(this, right)
|
||||
|
||||
inline fun <T> withSession(block: Session.() -> T): T {
|
||||
val session = Session(this)
|
||||
try {
|
||||
return session.block()
|
||||
} finally {
|
||||
session.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Session(val graph: Graph) {
|
||||
private val inputs = mutableListOf<TF_Output>()
|
||||
private val inputValues = mutableListOf<Tensor>()
|
||||
private var outputs = mutableListOf<TF_Output>()
|
||||
private val outputValues = mutableListOf<Tensor?>()
|
||||
private val targets = listOf<Operation>()
|
||||
|
||||
private fun createNewSession(): CPointer<TF_Session> {
|
||||
val options = TF_NewSessionOptions()
|
||||
val session = statusValidated { TF_NewSession(graph.tensorflowGraph, options, it)!! }
|
||||
TF_DeleteSessionOptions(options)
|
||||
return session
|
||||
}
|
||||
|
||||
private var tensorflowSession: CPointer<TF_Session>? = createNewSession()
|
||||
|
||||
private fun clearInputValues() {
|
||||
for (inputValue in inputValues) {
|
||||
TF_DeleteTensor(inputValue)
|
||||
}
|
||||
|
||||
inputValues.clear()
|
||||
}
|
||||
|
||||
private fun clearOutputValues() {
|
||||
for (outputValue in outputValues) {
|
||||
if (outputValue != null)
|
||||
TF_DeleteTensor(outputValue)
|
||||
}
|
||||
outputValues.clear()
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
clearInputValues()
|
||||
clearOutputValues()
|
||||
clearInputs()
|
||||
clearOutputs()
|
||||
|
||||
if (tensorflowSession != null) {
|
||||
statusValidated { TF_CloseSession(tensorflowSession, it) }
|
||||
statusValidated { TF_DeleteSession(tensorflowSession, it) }
|
||||
tensorflowSession = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInputsWithValues(inputsWithValues: List<Pair<Operation, Tensor>>) {
|
||||
clearInputValues()
|
||||
clearInputs()
|
||||
for ((input, inputValue) in inputsWithValues) {
|
||||
this.inputs.add(nativeHeap.alloc<TF_Output>().apply { oper = input; index = 0 })
|
||||
inputValues.add(inputValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOutputs(outputs: List<Operation>) {
|
||||
clearOutputValues()
|
||||
clearOutputs()
|
||||
this.outputs = outputs.map { nativeHeap.alloc<TF_Output>().apply { oper = it; index = 0 } }.toMutableList()
|
||||
}
|
||||
|
||||
private fun clearOutputs() {
|
||||
this.outputs.forEach { nativeHeap.free(it) }
|
||||
this.outputs.clear()
|
||||
}
|
||||
|
||||
private fun clearInputs() {
|
||||
this.inputs.forEach { nativeHeap.free(it) }
|
||||
this.inputs.clear()
|
||||
}
|
||||
|
||||
operator fun invoke(outputs: List<Operation>, inputsWithValues: List<Pair<Operation, Tensor>> = listOf()): List<Tensor?> {
|
||||
setInputsWithValues(inputsWithValues)
|
||||
setOutputs(outputs)
|
||||
|
||||
return invoke()
|
||||
}
|
||||
|
||||
operator fun invoke(output: Operation, inputsWithValues: List<Pair<Operation, Tensor>> = listOf()) =
|
||||
invoke(listOf(output), inputsWithValues).single()!!
|
||||
|
||||
operator fun invoke(): List<Tensor?> {
|
||||
if (inputs.size != inputValues.size) {
|
||||
throw Error("Call SetInputs() before Run()")
|
||||
}
|
||||
clearOutputValues()
|
||||
|
||||
val inputsCArray = if (inputs.any()) nativeHeap.allocArray<TF_Output>(inputs.size) else null
|
||||
|
||||
inputs.forEachIndexed { i, input ->
|
||||
inputsCArray!![i].apply {
|
||||
oper = input.oper
|
||||
index = input.index
|
||||
}
|
||||
}
|
||||
|
||||
val outputsCArray = if (outputs.any()) nativeHeap.allocArray<TF_Output>(outputs.size) else null
|
||||
|
||||
outputs.forEachIndexed { i, output ->
|
||||
outputsCArray!![i].apply {
|
||||
oper = output.oper
|
||||
index = output.index
|
||||
}
|
||||
}
|
||||
|
||||
memScoped {
|
||||
val outputValuesCArray = allocArrayOfPointersTo<TF_Tensor>(outputs.map { null })
|
||||
|
||||
statusValidated {
|
||||
TF_SessionRun(tensorflowSession, null,
|
||||
inputsCArray, inputValues.toCValues(), inputs.size,
|
||||
outputsCArray, outputValuesCArray, outputs.size,
|
||||
targets.toCValues(), targets.size,
|
||||
null, it)
|
||||
}
|
||||
|
||||
for (index in outputs.indices) {
|
||||
outputValues.add(outputValuesCArray[index])
|
||||
}
|
||||
}
|
||||
|
||||
clearInputValues()
|
||||
|
||||
return outputValues
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
println("Hello, TensorFlow ${TF_Version()!!.toKString()}!")
|
||||
|
||||
val result = Graph().run {
|
||||
val input = intInput()
|
||||
|
||||
withSession { invoke(input + constant(2), inputsWithValues = listOf(input to scalarTensor(3))).scalarIntValue }
|
||||
}
|
||||
|
||||
println("3 + 2 is $result.")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
xcuserdata
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
301A7F7B2E38A0507DE50BE1 /* ViewController.kt in Resources */ = {isa = PBXBuildFile; fileRef = 4CD08237C99EE51EF77078BB /* ViewController.kt */; };
|
||||
562ACC5BA848F15EF1C8CA10 /* main.kt in Resources */ = {isa = PBXBuildFile; fileRef = FC430E849C9519096FC79E83 /* main.kt */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4CD08237C99EE51EF77078BB /* ViewController.kt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ViewController.kt; sourceTree = "<group>"; };
|
||||
6B07A266844DA0A0E37CC68F /* UIKitSample.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = UIKitSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FC430E849C9519096FC79E83 /* main.kt */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.kt; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
363F3F70F599800876E80E01 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BBFEA7F68D9D6C62F2908D66 /* src */,
|
||||
69EF9C159A26B77EFF55F942 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
63755DA7308C68AF59C587B3 /* kotlin */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC430E849C9519096FC79E83 /* main.kt */,
|
||||
4CD08237C99EE51EF77078BB /* ViewController.kt */,
|
||||
);
|
||||
path = kotlin;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
69EF9C159A26B77EFF55F942 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6B07A266844DA0A0E37CC68F /* UIKitSample.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B54757CDD36230AE01923800 /* iosMain */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
63755DA7308C68AF59C587B3 /* kotlin */,
|
||||
);
|
||||
path = iosMain;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BBFEA7F68D9D6C62F2908D66 /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B54757CDD36230AE01923800 /* iosMain */,
|
||||
);
|
||||
path = src;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
F5227B97658961C9C73A6F2E /* UIKitSample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = B1A8070BD396A498BD833DC3 /* Build configuration list for PBXNativeTarget "UIKitSample" */;
|
||||
buildPhases = (
|
||||
9147B4CF5C06D4CF77ACF3FC /* GradleCompile */,
|
||||
BE9081AF451571B84021AC67 /* Sources */,
|
||||
D848DE0D231736CAB55BD443 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = UIKitSample;
|
||||
productName = UIKitSample;
|
||||
productReference = 6B07A266844DA0A0E37CC68F /* UIKitSample.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
2F795AF76B32ABD2043FD7B6 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
TargetAttributes = {
|
||||
F5227B97658961C9C73A6F2E = {
|
||||
DevelopmentTeam = N462MKSJ7M;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 0D29F0838E0A64A05D02D666 /* Build configuration list for PBXProject "UIKitSample" */;
|
||||
compatibilityVersion = "Xcode 10.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 363F3F70F599800876E80E01;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
F5227B97658961C9C73A6F2E /* UIKitSample */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D848DE0D231736CAB55BD443 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
301A7F7B2E38A0507DE50BE1 /* ViewController.kt in Resources */,
|
||||
562ACC5BA848F15EF1C8CA10 /* main.kt in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9147B4CF5C06D4CF77ACF3FC /* GradleCompile */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = GradleCompile;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "$SRCROOT/../gradlew -p $SRCROOT/.. packForXcode\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
BE9081AF451571B84021AC67 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
30240F2C5B94778954FDDD23 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = plists/Ios/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.UIKitSample;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALID_ARCHS = arm64;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
ACEFD109F7738D0AE3383B13 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = N462MKSJ7M;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
C3420161DB9AF86DB7EEDFBE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = plists/Ios/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.UIKitSample;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALID_ARCHS = arm64;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CDD41CD31E42F4CB22754575 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = N462MKSJ7M;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
0D29F0838E0A64A05D02D666 /* Build configuration list for PBXProject "UIKitSample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CDD41CD31E42F4CB22754575 /* Debug */,
|
||||
ACEFD109F7738D0AE3383B13 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
B1A8070BD396A498BD833DC3 /* Build configuration list for PBXNativeTarget "UIKitSample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C3420161DB9AF86DB7EEDFBE /* Debug */,
|
||||
30240F2C5B94778954FDDD23 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "";
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 2F795AF76B32ABD2043FD7B6 /* Project object */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,109 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
val sdkName: String? = System.getenv("SDK_NAME")
|
||||
|
||||
enum class Target(val simulator: Boolean, val key: String) {
|
||||
WATCHOS_X86(true, "watchos"), WATCHOS_ARM64(false, "watchos"),
|
||||
IOS_X64(true, "ios"), IOS_ARM64(false, "ios")
|
||||
}
|
||||
|
||||
val target = sdkName.orEmpty().let {
|
||||
when {
|
||||
it.startsWith("iphoneos") -> Target.IOS_ARM64
|
||||
it.startsWith("iphonesimulator") -> Target.IOS_X64
|
||||
it.startsWith("watchos") -> Target.WATCHOS_ARM64
|
||||
it.startsWith("watchsimulator") -> Target.WATCHOS_X86
|
||||
else -> Target.IOS_X64
|
||||
}
|
||||
}
|
||||
|
||||
val buildType = System.getenv("CONFIGURATION")?.let {
|
||||
NativeBuildType.valueOf(it.toUpperCase())
|
||||
} ?: NativeBuildType.DEBUG
|
||||
|
||||
kotlin {
|
||||
// Declare a target.
|
||||
// We declare only one target (either arm64 or x64)
|
||||
// to workaround lack of common platform libraries
|
||||
// for both device and simulator.
|
||||
val ios = if (!target.simulator) {
|
||||
// Device.
|
||||
iosArm64("ios")
|
||||
} else {
|
||||
// Simulator.
|
||||
iosX64("ios")
|
||||
}
|
||||
|
||||
val watchos = if (!target.simulator) {
|
||||
// Device.
|
||||
watchosArm64("watchos")
|
||||
} else {
|
||||
// Simulator.
|
||||
watchosX86("watchos")
|
||||
}
|
||||
|
||||
// Declare the output program.
|
||||
ios.binaries.executable(listOf(buildType)) {
|
||||
baseName = "app"
|
||||
entryPoint = "sample.uikit.main"
|
||||
}
|
||||
|
||||
watchos.binaries.executable(listOf(buildType)) {
|
||||
baseName = "watchapp"
|
||||
}
|
||||
|
||||
// Configure dependencies.
|
||||
val appleMain by sourceSets.creating {
|
||||
dependsOn(sourceSets["commonMain"])
|
||||
}
|
||||
sourceSets["iosMain"].dependsOn(appleMain)
|
||||
sourceSets["watchosMain"].dependsOn(appleMain)
|
||||
}
|
||||
|
||||
// Create Xcode integration tasks.
|
||||
val targetBuildDir: String? = System.getenv("TARGET_BUILD_DIR")
|
||||
val executablePath: String? = System.getenv("EXECUTABLE_PATH")
|
||||
|
||||
val currentTarget = kotlin.targets[target.key] as KotlinNativeTarget
|
||||
val kotlinBinary = currentTarget.binaries.getExecutable(buildType)
|
||||
val xcodeIntegrationGroup = "Xcode integration"
|
||||
|
||||
val packForXCode = if (sdkName == null || targetBuildDir == null || executablePath == null) {
|
||||
// The build is launched not by Xcode ->
|
||||
// We cannot create a copy task and just show a meaningful error message.
|
||||
tasks.create("packForXCode").doLast {
|
||||
throw IllegalStateException("Please run the task from Xcode")
|
||||
}
|
||||
} else {
|
||||
// Otherwise copy the executable into the Xcode output directory.
|
||||
tasks.create("packForXCode", Copy::class.java) {
|
||||
dependsOn(kotlinBinary.linkTask)
|
||||
|
||||
destinationDir = file(targetBuildDir)
|
||||
|
||||
val dsymSource = kotlinBinary.outputFile.absolutePath + ".dSYM"
|
||||
val dsymDestination = File(executablePath).parentFile.name + ".dSYM"
|
||||
val oldExecName = kotlinBinary.outputFile.name
|
||||
val newExecName = File(executablePath).name
|
||||
|
||||
from(dsymSource) {
|
||||
into(dsymDestination)
|
||||
rename(oldExecName, newExecName)
|
||||
}
|
||||
|
||||
from(kotlinBinary.outputFile) {
|
||||
rename { executablePath }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,28 @@
|
||||
name: UIKitSample
|
||||
options:
|
||||
bundleIdPrefix: org.jetbrains
|
||||
settings:
|
||||
DEVELOPMENT_TEAM: N462MKSJ7M
|
||||
CODE_SIGN_IDENTITY: "iPhone Developer"
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
MARKETING_VERSION: "1.0"
|
||||
CURRENT_PROJECT_VERSION: "4"
|
||||
SDKROOT: iphoneos
|
||||
targets:
|
||||
UIKitSample:
|
||||
type: application
|
||||
platform: iOS
|
||||
deploymentTarget: "12.0"
|
||||
prebuildScripts:
|
||||
- script: cd "$SRCROOT" && ../gradlew -p .. packForXCode
|
||||
name: GradleCompile
|
||||
info:
|
||||
path: plists/Ios/Info.plist
|
||||
properties:
|
||||
sources:
|
||||
- "src/"
|
||||
settings:
|
||||
LIBRARY_SEARCH_PATHS: "$(inherited)"
|
||||
ENABLE_BITCODE: "YES"
|
||||
ONLY_ACTIVE_ARCH: "NO"
|
||||
VALID_ARCHS: "arm64"
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 sample.uikit
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.Foundation.*
|
||||
import platform.UIKit.*
|
||||
import platform.CoreGraphics.CGPointMake
|
||||
import platform.CoreGraphics.CGRectMake
|
||||
import platform.Foundation.NSCoder
|
||||
import platform.Foundation.NSSelectorFromString
|
||||
|
||||
@ExportObjCClass
|
||||
class ViewController : UIViewController {
|
||||
|
||||
@OverrideInit
|
||||
constructor() : super(nibName = null, bundle = null)
|
||||
|
||||
@OverrideInit
|
||||
constructor(coder: NSCoder) : super(coder)
|
||||
|
||||
@ObjCOutlet
|
||||
lateinit var label: UILabel
|
||||
|
||||
@ObjCOutlet
|
||||
lateinit var button: UIButton
|
||||
|
||||
var pressed = 0
|
||||
|
||||
@ObjCAction
|
||||
fun buttonPressed() {
|
||||
label.text = "Hello #${pressed++} from Konan!"
|
||||
println("Button pressed")
|
||||
}
|
||||
|
||||
override fun viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
val (width, height) = UIScreen.mainScreen.bounds.useContents {
|
||||
this.size.width to this.size.height
|
||||
}
|
||||
|
||||
val header = UIView().apply {
|
||||
backgroundColor = UIColor.lightGrayColor
|
||||
view.addSubview(this)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
|
||||
topAnchor.constraintEqualToAnchor(view.topAnchor).active = true
|
||||
widthAnchor.constraintEqualToAnchor(view.widthAnchor).active = true
|
||||
heightAnchor.constraintEqualToAnchor(view.heightAnchor).active = true
|
||||
}
|
||||
|
||||
label = UILabel().apply {
|
||||
setFrame(CGRectMake(x = 10.0, y = 10.0, width = width - 100.0, height = 40.0))
|
||||
center = CGPointMake(x = width / 2, y = 40.0 )
|
||||
textAlignment = NSTextAlignmentCenter
|
||||
text = "Press OK"
|
||||
header.addSubview(this)
|
||||
}
|
||||
|
||||
button = UIButton().apply {
|
||||
setFrame(CGRectMake(x = 10.0, y = height - 100.0, width = width - 100.0, height = 40.0))
|
||||
center = CGPointMake(x = width / 2, y = height / 2)
|
||||
backgroundColor = UIColor.blueColor
|
||||
setTitle("OK", forState = UIControlStateNormal)
|
||||
font = UIFont.fontWithName(fontName = font.fontName, size = 28.0)!!
|
||||
layer.borderWidth = 1.0
|
||||
layer.borderColor = UIColor.colorWithRed(0x47 / 255.0, 0x43 / 255.0, 0x70 / 255.0, 1.0).CGColor
|
||||
layer.masksToBounds = true
|
||||
addTarget(target = this@ViewController, action = NSSelectorFromString("buttonPressed"),
|
||||
forControlEvents = UIControlEventTouchUpInside)
|
||||
header.addSubview(this)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 sample.uikit
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.Foundation.*
|
||||
import platform.UIKit.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
memScoped {
|
||||
val argc = args.size + 1
|
||||
val argv = (arrayOf("konan") + args).map { it.cstr.ptr }.toCValues()
|
||||
|
||||
autoreleasepool {
|
||||
UIApplicationMain(argc, argv, null, NSStringFromClass(AppDelegate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate : UIResponder, UIApplicationDelegateProtocol {
|
||||
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta {}
|
||||
|
||||
@OverrideInit
|
||||
constructor() : super()
|
||||
|
||||
private var _window: UIWindow? = null
|
||||
override fun window() = _window
|
||||
override fun setWindow(window: UIWindow?) {
|
||||
_window = window
|
||||
}
|
||||
|
||||
override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
|
||||
window = UIWindow(frame = UIScreen.mainScreen.bounds)
|
||||
window!!.rootViewController = ViewController()
|
||||
window!!.makeKeyAndVisible()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
ffmpeg and SDL2 are needed for the compilation, i.e.
|
||||
|
||||
port install ffmpeg-devel
|
||||
brew install ffmpeg sdl2
|
||||
apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
|
||||
apt install libsdl2-dev
|
||||
pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-ffmpeg
|
||||
@@ -0,0 +1,56 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val mingwPath = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("videoPlayer")
|
||||
hostOs == "Linux" -> linuxX64("videoPlayer")
|
||||
hostOs.startsWith("Windows") -> mingwX64("videoPlayer")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.videoplayer.main"
|
||||
|
||||
when (preset) {
|
||||
presets["macosX64"] -> linkerOpts("-L/opt/local/lib", "-L/usr/local/lib")
|
||||
presets["linuxX64"] -> linkerOpts("-L/usr/lib/x86_64-linux-gnu", "-L/usr/lib64")
|
||||
presets["mingwX64"] -> linkerOpts("-L${mingwPath.resolve("lib")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compilations["main"].cinterops {
|
||||
val ffmpeg by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"] -> includeDirs.headerFilterOnly("/opt/local/include", "/usr/local/include")
|
||||
presets["linuxX64"] -> includeDirs.headerFilterOnly("/usr/include", "/usr/include/x86_64-linux-gnu", "/usr/include/ffmpeg")
|
||||
presets["mingwX64"] -> includeDirs(mingwPath.resolve("include"))
|
||||
}
|
||||
}
|
||||
val sdl by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"] -> includeDirs("/opt/local/include/SDL2", "/usr/local/include/SDL2")
|
||||
presets["linuxX64"] -> includeDirs("/usr/include", "/usr/include/x86_64-linux-gnu", "/usr/include/SDL2")
|
||||
presets["mingwX64"] -> includeDirs(mingwPath.resolve("include/SDL2"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compilations["main"].enableEndorsedLibs = true
|
||||
}
|
||||
|
||||
// Enable experimental stdlib API used by the sample.
|
||||
sourceSets.all {
|
||||
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package = ffmpeg
|
||||
headers = libavcodec/avcodec.h libavformat/avformat.h libavutil/pixfmt.h libavutil/opt.h \
|
||||
libswscale/swscale.h libswresample/swresample.h
|
||||
headerFilter = libavcodec/** libavformat/** libavutil/** \
|
||||
libswscale/** libswresample/**
|
||||
linkerOpts = -lavutil -lavformat -lavcodec -lswscale -lswresample
|
||||
---
|
||||
|
||||
static void av_buffer_unref2(AVBufferRef* ref) {
|
||||
AVBufferRef* copy = ref;
|
||||
av_buffer_unref(©);
|
||||
}
|
||||
|
||||
static void avcodec_free_context2(AVCodecContext* ref) {
|
||||
AVCodecContext* copy = ref;
|
||||
avcodec_free_context(©);
|
||||
}
|
||||
|
||||
static void avformat_free_context2(AVFormatContext* ref) {
|
||||
AVFormatContext* copy = ref;
|
||||
avformat_free_context(©);
|
||||
}
|
||||
|
||||
static void swr_free2(SwrContext* ref) {
|
||||
SwrContext* copy = ref;
|
||||
swr_free(©);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package = sdl
|
||||
headers = SDL.h
|
||||
entryPoint = SDL_main
|
||||
|
||||
headerFilter = SDL*
|
||||
|
||||
linkerOpts = -lSDL2
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import ffmpeg.*
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.memcpy
|
||||
|
||||
// This global variable only set to != null value in the decoding worker.
|
||||
@ThreadLocal
|
||||
private var decoder: Decoder? = null
|
||||
|
||||
data class VideoInfo(val size: Dimensions, val fps: Double)
|
||||
data class AudioInfo(val sampleRate: Int, val channels: Int)
|
||||
|
||||
data class CodecInfo(val video: VideoInfo?, val audio: AudioInfo?) {
|
||||
val hasVideo = video != null
|
||||
val hasAudio = audio != null
|
||||
}
|
||||
|
||||
class VideoFrame(val buffer: CPointer<AVBufferRef>, val lineSize: Int, val timeStamp: Double) {
|
||||
fun unref() = av_buffer_unref2(buffer)
|
||||
}
|
||||
|
||||
class AudioFrame(val buffer: CPointer<AVBufferRef>, var position: Int, val size: Int, val timeStamp: Double) {
|
||||
fun unref() = av_buffer_unref2(buffer)
|
||||
}
|
||||
|
||||
private fun Int.checkAVError() {
|
||||
if (this != 0) {
|
||||
val buffer = ByteArray(1024)
|
||||
av_strerror(this, buffer.refTo(0), buffer.size.convert())
|
||||
throw Error("AVError: ${buffer.decodeToString()}")
|
||||
}
|
||||
}
|
||||
|
||||
private val AVFormatContext.codecs: List<AVCodecContext?>
|
||||
get() = List(nb_streams.toInt()) { streams?.get(it)?.pointed?.codec?.pointed }
|
||||
|
||||
private fun AVFormatContext.streamAt(index: Int): AVStream? =
|
||||
if (index < 0) null else streams?.get(index)?.pointed
|
||||
|
||||
private fun AVStream.openCodec(tag: String): AVCodecContext {
|
||||
// Get codec context for the video stream.
|
||||
val codecContext = codec!!.pointed
|
||||
val codec = avcodec_find_decoder(codecContext.codec_id)?.pointed ?:
|
||||
throw Error("Unsupported $tag codec with id ${codecContext.codec_id}...")
|
||||
// Open codec.
|
||||
if (avcodec_open2(codecContext.ptr, codec.ptr, null) < 0)
|
||||
throw Error("Couldn't open $tag codec with id ${codecContext.codec_id}")
|
||||
return codecContext
|
||||
}
|
||||
|
||||
class AVFile(private val fileName: String) : DisposableContainer() {
|
||||
private val contextPtrPtr = arena.alloc<CPointerVar<AVFormatContext>>().ptr
|
||||
private val contextPtr: CPointer<AVFormatContext>
|
||||
|
||||
init {
|
||||
avformat_open_input(contextPtrPtr, fileName, null, null).checkAVError()
|
||||
contextPtr = contextPtrPtr.pointed.value ?: throw Error("Failed to open AV file")
|
||||
tryConstruct {
|
||||
if (avformat_find_stream_info(contextPtr, null) < 0)
|
||||
throw Error("Couldn't find stream information")
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
avformat_close_input(contextPtrPtr)
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
fun dumpFormat() = av_dump_format(contextPtr, 0, fileName, 0)
|
||||
|
||||
val context get() = contextPtr.pointed
|
||||
}
|
||||
|
||||
private fun PixelFormat.toAVPixelFormat(): AVPixelFormat? = when (this) {
|
||||
PixelFormat.RGB24 -> AV_PIX_FMT_RGB24
|
||||
PixelFormat.ARGB32 -> AV_PIX_FMT_RGB32
|
||||
PixelFormat.INVALID -> null
|
||||
}
|
||||
|
||||
private data class VideoDecoderOutput(val size: Dimensions, val avPixelFormat: AVPixelFormat)
|
||||
|
||||
// Performs data type conversion and copy to transfer data to DecoderWorker
|
||||
private fun VideoOutput.toVideoDecoderOutput(): VideoDecoderOutput? {
|
||||
val avPixelFormat = pixelFormat.toAVPixelFormat() ?: return null
|
||||
return VideoDecoderOutput(size.copy(), avPixelFormat)
|
||||
}
|
||||
|
||||
private class VideoDecoder(
|
||||
private val videoCodecContext: AVCodecContext,
|
||||
output: VideoDecoderOutput
|
||||
) : DisposableContainer() {
|
||||
private val windowSize = output.size
|
||||
private val avPixelFormat = output.avPixelFormat
|
||||
private val videoSize = Dimensions(videoCodecContext.width, videoCodecContext.height)
|
||||
private val videoFrame: AVFrame =
|
||||
disposable("av_frame_alloc", ::av_frame_alloc, ::av_frame_unref).pointed
|
||||
private val scaledVideoFrame: AVFrame =
|
||||
disposable("av_frame_alloc", ::av_frame_alloc, ::av_frame_unref).pointed
|
||||
private val softwareScalingContext: CPointer<SwsContext> = disposable(
|
||||
message = "sws_getContext",
|
||||
create = {
|
||||
sws_getContext(
|
||||
videoSize.w, videoSize.h,
|
||||
videoCodecContext.pix_fmt,
|
||||
windowSize.w, windowSize.h, avPixelFormat,
|
||||
SWS_BILINEAR, null, null, null)
|
||||
},
|
||||
dispose = ::sws_freeContext
|
||||
)
|
||||
private val scaledFrameSize = avpicture_get_size(avPixelFormat, windowSize.w, windowSize.h)
|
||||
private val buffer: UByteArray = UByteArray(scaledFrameSize)
|
||||
|
||||
private val videoQueue = Queue<VideoFrame>(100)
|
||||
|
||||
private val minVideoFrames = 5
|
||||
|
||||
init {
|
||||
avpicture_fill(scaledVideoFrame.ptr.reinterpret(), buffer.refTo(0),
|
||||
avPixelFormat, windowSize.w, windowSize.h)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
while (!videoQueue.isEmpty()) videoQueue.pop().unref()
|
||||
}
|
||||
|
||||
fun isQueueEmpty() = videoQueue.isEmpty()
|
||||
fun isQueueAlmostFull() = videoQueue.size() > videoQueue.maxSize - 5
|
||||
fun needMoreFrames() = videoQueue.size() < minVideoFrames
|
||||
fun nextFrame() = videoQueue.popOrNull()
|
||||
|
||||
fun decodeVideoPacket(packet: AVPacket, frameFinished: IntVar) {
|
||||
// Decode video frame.
|
||||
avcodec_decode_video2(videoCodecContext.ptr, videoFrame.ptr, frameFinished.ptr, packet.ptr)
|
||||
// Did we get a video frame?
|
||||
if (frameFinished.value != 0) {
|
||||
// Convert the frame from its movie format to window pixel format.
|
||||
sws_scale(softwareScalingContext, videoFrame.data,
|
||||
videoFrame.linesize, 0, videoSize.h,
|
||||
scaledVideoFrame.data, scaledVideoFrame.linesize)
|
||||
// TODO: reuse buffers!
|
||||
val buffer = av_buffer_alloc(scaledFrameSize)!!
|
||||
val ts = av_frame_get_best_effort_timestamp(videoFrame.ptr) *
|
||||
av_q2d(videoCodecContext.time_base.readValue())
|
||||
memcpy(buffer.pointed.data, scaledVideoFrame.data[0], scaledFrameSize.convert())
|
||||
videoQueue.push(VideoFrame(buffer, scaledVideoFrame.linesize[0], ts))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SampleFormat.toAVSampleFormat(): AVSampleFormat? = when (this) {
|
||||
SampleFormat.S16 -> AV_SAMPLE_FMT_S16
|
||||
SampleFormat.INVALID -> null
|
||||
}
|
||||
|
||||
private data class AudioDecoderOutput(
|
||||
val sampleRate: Int,
|
||||
val channels: Int,
|
||||
val channelLayout: Int,
|
||||
val sampleFormat: AVSampleFormat)
|
||||
|
||||
// Performs data type conversion and copy to transfer data to DecoderWorker
|
||||
private fun AudioOutput.toAudioDecoderOutput(): AudioDecoderOutput? {
|
||||
val avSampleFormat = sampleFormat.toAVSampleFormat() ?: return null
|
||||
if (channels != 2) return null // only stereo output is supported for now
|
||||
return AudioDecoderOutput(sampleRate, channels, AV_CH_LAYOUT_STEREO, avSampleFormat)
|
||||
}
|
||||
|
||||
private class AudioDecoder(
|
||||
private val audioCodecContext: AVCodecContext,
|
||||
output: AudioDecoderOutput
|
||||
): DisposableContainer() {
|
||||
private val audioFrame: AVFrame =
|
||||
disposable(create = ::av_frame_alloc, dispose = ::av_frame_unref).pointed
|
||||
private val resampledAudioFrame: AVFrame =
|
||||
disposable(create = ::av_frame_alloc, dispose = ::av_frame_unref).pointed
|
||||
private val resampleContext: CPointer<SwrContext> =
|
||||
disposable(create = ::swr_alloc, dispose = ::swr_free2)
|
||||
|
||||
private val audioQueue = Queue<AudioFrame>(100)
|
||||
|
||||
private val minAudioFrames = 2
|
||||
private val maxAudioFrames = 5
|
||||
|
||||
init {
|
||||
with(resampledAudioFrame) {
|
||||
channels = output.channels
|
||||
sample_rate = output.sampleRate
|
||||
format = output.sampleFormat
|
||||
channel_layout = output.channelLayout.convert()
|
||||
}
|
||||
|
||||
with(audioCodecContext) {
|
||||
setResampleOpt("in_channel_layout", channel_layout.convert())
|
||||
setResampleOpt("out_channel_layout", output.channelLayout)
|
||||
setResampleOpt("in_sample_rate", sample_rate)
|
||||
setResampleOpt("out_sample_rate", output.sampleRate)
|
||||
setResampleOpt("in_sample_fmt", sample_fmt)
|
||||
setResampleOpt("out_sample_fmt", output.sampleFormat)
|
||||
}
|
||||
swr_init(resampleContext)
|
||||
}
|
||||
|
||||
private fun setResampleOpt(name: String, value: Int) =
|
||||
av_opt_set_int(resampleContext, name, value.signExtend(), 0)
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
while (!audioQueue.isEmpty()) audioQueue.pop().unref()
|
||||
}
|
||||
|
||||
fun isSynced(): Boolean = audioQueue.size() < maxAudioFrames
|
||||
|
||||
fun isQueueEmpty() = audioQueue.isEmpty()
|
||||
fun isQueueAlmostFull() = audioQueue.size() > audioQueue.maxSize - 20
|
||||
fun needMoreFrames() = audioQueue.size() < minAudioFrames
|
||||
|
||||
fun nextFrame(size: Int): AudioFrame? {
|
||||
val frame = audioQueue.peek() ?: return null
|
||||
val realSize = if (frame.position + size > frame.size) frame.size - frame.position else size
|
||||
return if (frame.position + realSize == frame.size) {
|
||||
audioQueue.pop()
|
||||
} else {
|
||||
val result = AudioFrame(av_buffer_ref(frame.buffer)!!, frame.position, frame.size, frame.timeStamp)
|
||||
frame.position += realSize
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeAudioPacket(packet: AVPacket, frameFinished: IntVar) {
|
||||
while (packet.size > 0) {
|
||||
val size = avcodec_decode_audio4(audioCodecContext.ptr, audioFrame.ptr, frameFinished.ptr, packet.ptr)
|
||||
if (frameFinished.value != 0) {
|
||||
// Put audio frame to decoder's queue.
|
||||
swr_convert_frame(resampleContext, resampledAudioFrame.ptr, audioFrame.ptr).checkAVError()
|
||||
with(resampledAudioFrame) {
|
||||
val audioFrameSize = av_samples_get_buffer_size(null, channels, nb_samples, format, 1)
|
||||
val buffer = av_buffer_alloc(audioFrameSize)!!
|
||||
val ts = av_frame_get_best_effort_timestamp(audioFrame.ptr) *
|
||||
av_q2d(audioCodecContext.time_base.readValue())
|
||||
memcpy(buffer.pointed.data, data[0], audioFrameSize.convert())
|
||||
audioQueue.push(AudioFrame(buffer, 0, audioFrameSize, ts))
|
||||
}
|
||||
}
|
||||
packet.size -= size
|
||||
packet.data += size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Decoder(
|
||||
private val formatContext: CPointer<AVFormatContext>,
|
||||
private val videoStreamIndex: Int,
|
||||
private val audioStreamIndex: Int,
|
||||
private val videoCodecContext: AVCodecContext?,
|
||||
private val audioCodecContext: AVCodecContext?
|
||||
) {
|
||||
private var video: VideoDecoder? = null
|
||||
private var audio: AudioDecoder? = null
|
||||
|
||||
var noMoreFrames = false
|
||||
|
||||
fun start(videoOutput: VideoDecoderOutput?, audioOutput: AudioDecoderOutput?) {
|
||||
video = videoCodecContext?.let { ctx -> videoOutput?.let { VideoDecoder(ctx, it) } }
|
||||
audio = audioCodecContext?.let { ctx -> audioOutput?.let { AudioDecoder(ctx, it) } }
|
||||
noMoreFrames = false
|
||||
decodeIfNeeded()
|
||||
}
|
||||
|
||||
fun done() = noMoreFrames && (video?.isQueueEmpty() ?: true) && (audio?.isQueueEmpty() ?: true)
|
||||
|
||||
fun dispose() {
|
||||
video?.dispose()
|
||||
audio?.dispose()
|
||||
}
|
||||
|
||||
private fun needMoreFrames(): Boolean =
|
||||
(video?.needMoreFrames() ?: false) || (audio?.needMoreFrames() ?: false)
|
||||
|
||||
fun decodeIfNeeded() {
|
||||
if (!needMoreFrames()) return
|
||||
if (video?.isQueueAlmostFull() == true) return
|
||||
if (audio?.isQueueAlmostFull() == true) return
|
||||
memScoped {
|
||||
val packet = alloc<AVPacket>()
|
||||
val frameFinished = alloc<IntVar>()
|
||||
while (needMoreFrames() && av_read_frame(formatContext, packet.ptr) >= 0) {
|
||||
when (packet.stream_index) {
|
||||
videoStreamIndex -> video?.decodeVideoPacket(packet, frameFinished)
|
||||
audioStreamIndex -> audio?.decodeAudioPacket(packet, frameFinished)
|
||||
}
|
||||
av_packet_unref(packet.ptr)
|
||||
}
|
||||
if (needMoreFrames()) noMoreFrames = true
|
||||
}
|
||||
}
|
||||
|
||||
fun nextVideoFrame(): VideoFrame? {
|
||||
decodeIfNeeded()
|
||||
return video?.nextFrame()
|
||||
}
|
||||
|
||||
fun nextAudioFrame(size: Int): AudioFrame? {
|
||||
decodeIfNeeded()
|
||||
return audio?.nextFrame(size)
|
||||
}
|
||||
|
||||
fun audioVideoSynced() = (audio?.isSynced() ?: true) || done()
|
||||
}
|
||||
|
||||
inline class DecoderWorker(val worker: Worker) : Disposable {
|
||||
// This class must have no other state, but this worker object.
|
||||
// All the real state must be stored on the worker's side.
|
||||
constructor() : this(Worker.start())
|
||||
|
||||
override fun dispose() {
|
||||
worker.requestTermination().result
|
||||
}
|
||||
|
||||
fun initDecode(context: AVFormatContext, useVideo: Boolean = true, useAudio: Boolean = true): CodecInfo {
|
||||
// Find the first video/audio streams.
|
||||
val videoStreamIndex =
|
||||
if (useVideo) context.codecs.indexOfFirst { it?.codec_type == AVMEDIA_TYPE_VIDEO } else -1
|
||||
val audioStreamIndex =
|
||||
if (useAudio) context.codecs.indexOfFirst { it?.codec_type == AVMEDIA_TYPE_AUDIO } else -1
|
||||
|
||||
val videoStream = context.streamAt(videoStreamIndex)
|
||||
val audioStream = context.streamAt(audioStreamIndex)
|
||||
|
||||
val videoContext = videoStream?.openCodec("video")
|
||||
val audioContext = audioStream?.openCodec("audio")
|
||||
|
||||
// Extract video info.
|
||||
val video = videoContext?.run {
|
||||
VideoInfo(Dimensions(width, height), av_q2d(av_stream_get_r_frame_rate(videoStream.ptr)))
|
||||
}
|
||||
// Extract audio info.
|
||||
val audio = audioContext?.run {
|
||||
AudioInfo(sample_rate, channels)
|
||||
}
|
||||
|
||||
// Pack all state and pass it to the worker.
|
||||
worker.execute(TransferMode.SAFE, {
|
||||
Decoder(context.ptr,
|
||||
videoStreamIndex, audioStreamIndex,
|
||||
videoContext, audioContext)
|
||||
}) { decoder = it }
|
||||
return CodecInfo(video, audio)
|
||||
}
|
||||
|
||||
fun start(videoOutput: VideoOutput, audioOutput: AudioOutput) {
|
||||
worker.execute(TransferMode.SAFE,
|
||||
{ Pair(
|
||||
videoOutput.toVideoDecoderOutput(),
|
||||
audioOutput.toAudioDecoderOutput())
|
||||
}) {
|
||||
decoder?.start(it.first, it.second)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
worker.execute(TransferMode.SAFE, { null }) {
|
||||
decoder?.run {
|
||||
dispose()
|
||||
decoder = null
|
||||
}
|
||||
}.result
|
||||
}
|
||||
|
||||
fun done(): Boolean =
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.done() ?: true }.result
|
||||
|
||||
fun requestDecodeChunk() =
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.decodeIfNeeded() }.result
|
||||
|
||||
fun nextVideoFrame(): VideoFrame? =
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.nextVideoFrame() }.result
|
||||
|
||||
fun nextAudioFrame(size: Int): AudioFrame? =
|
||||
worker.execute(TransferMode.SAFE, { size }) { decoder?.nextAudioFrame(it) }.result
|
||||
|
||||
fun audioVideoSynced(): Boolean =
|
||||
worker.execute(TransferMode.SAFE, { null }) { decoder?.audioVideoSynced() ?: true }.result
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
data class Dimensions(val w: Int, val h: Int) {
|
||||
operator fun minus(other: Dimensions) = Dimensions(w - other.w, h - other.h)
|
||||
operator fun div(b: Int) = Dimensions(w / b, h / b)
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
/**
|
||||
* Disposable class manages and owns all its native resources. It allocates some resources
|
||||
* during construction and may allocate some additional resources during operation.
|
||||
* It must free all its resource once [dispose] is invoked. "Disposed" is a final state of the disposable
|
||||
* class. It is not supposed to be used after being disposed.
|
||||
*/
|
||||
interface Disposable {
|
||||
/**
|
||||
* Disposes all native resources owned by this class. This function must be invoked
|
||||
* exactly once as the last operation on the corresponding class.
|
||||
*/
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to implement [Disposable] interface. It contains an [arena] for native
|
||||
* memory allocations and a number of helper methods to simplify management of other
|
||||
* kinds of native resources.
|
||||
*
|
||||
* It is important to wrap all potentially exception-throwing code in the class constructor
|
||||
* into [tryConstruct] invocation, because, when object construction fails with exception,
|
||||
* it ensures that all the resource that were allocated so far will get freed.
|
||||
*/
|
||||
abstract class DisposableContainer : Disposable {
|
||||
val arena = Arena()
|
||||
|
||||
override fun dispose() {
|
||||
arena.clear()
|
||||
}
|
||||
|
||||
inline fun <T> tryConstruct(init: () -> T): T =
|
||||
try { init() }
|
||||
catch (e: Throwable) {
|
||||
dispose()
|
||||
throw e
|
||||
}
|
||||
|
||||
inline fun <T> disposable(
|
||||
message: String = "disposable",
|
||||
create: () -> T?,
|
||||
crossinline dispose: (T) -> Unit
|
||||
): T =
|
||||
tryConstruct {
|
||||
create()?.also {
|
||||
arena.defer { dispose(it) }
|
||||
} ?: throw Error(message)
|
||||
}
|
||||
|
||||
inline fun <T : Disposable> disposable(create: () -> T): T =
|
||||
disposable(
|
||||
create = create,
|
||||
dispose = { it.dispose() })
|
||||
|
||||
inline fun <T : CPointed> sdlDisposable(
|
||||
message: String,
|
||||
ptr: CPointer<T>?,
|
||||
crossinline dispose: (CPointer<T>) -> Unit): CPointer<T> =
|
||||
disposable(
|
||||
create = { ptr ?: throwSDLError(message) },
|
||||
dispose = dispose)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
class Queue<T>(val maxSize: Int) {
|
||||
private val array = arrayOfNulls<Any>(maxSize)
|
||||
private var head = 0
|
||||
private var tail = 0
|
||||
|
||||
fun push(element: T) {
|
||||
if ((tail + 1) % maxSize == head)
|
||||
throw Error("queue overflow: $tail $head")
|
||||
array[tail] = element
|
||||
tail = (tail + 1) % maxSize
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun pop(): T {
|
||||
if (tail == head)
|
||||
throw Error("queue underflow")
|
||||
val result = array[head] as T
|
||||
array[head] = null
|
||||
head = (head + 1) % maxSize
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun peek() : T? {
|
||||
if (isEmpty()) return null
|
||||
return array[head] as T
|
||||
}
|
||||
|
||||
fun size() = if (tail >= head) tail - head else maxSize - (head - tail)
|
||||
|
||||
fun isEmpty() = head == tail
|
||||
|
||||
fun popOrNull(): T? = if (isEmpty()) null else pop()
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import kotlin.native.concurrent.Worker
|
||||
import kotlinx.cinterop.*
|
||||
import sdl.*
|
||||
import platform.posix.memset
|
||||
import platform.posix.memcpy
|
||||
|
||||
enum class SampleFormat {
|
||||
INVALID,
|
||||
S16
|
||||
}
|
||||
|
||||
data class AudioOutput(val sampleRate: Int, val channels: Int, val sampleFormat: SampleFormat)
|
||||
|
||||
private fun SampleFormat.toSDLFormat(): SDL_AudioFormat? = when (this) {
|
||||
SampleFormat.S16 -> AUDIO_S16SYS.convert()
|
||||
SampleFormat.INVALID -> null
|
||||
}
|
||||
|
||||
class SDLAudio(private val player: VideoPlayer) : DisposableContainer() {
|
||||
private var state = State.STOPPED
|
||||
|
||||
fun start(audio: AudioOutput) {
|
||||
stop()
|
||||
val audioFormat = audio.sampleFormat.toSDLFormat() ?: return
|
||||
println("SDL Audio: Playing output with ${audio.channels} channels, ${audio.sampleRate} samples per second")
|
||||
|
||||
memScoped {
|
||||
// TODO: better mechanisms to ensure we have same output format here and in resampler of the decoder.
|
||||
val spec = alloc<SDL_AudioSpec>().apply {
|
||||
freq = audio.sampleRate
|
||||
format = audioFormat
|
||||
channels = audio.channels.convert()
|
||||
silence = 0u
|
||||
samples = 4096u
|
||||
userdata = player.worker.asCPointer()
|
||||
callback = staticCFunction(::audioCallback)
|
||||
}
|
||||
val realSpec = alloc<SDL_AudioSpec>()
|
||||
if (SDL_OpenAudio(spec.ptr, realSpec.ptr) < 0)
|
||||
throwSDLError("SDL_OpenAudio")
|
||||
// TODO: ensure real spec matches what we asked for.
|
||||
state = State.PAUSED
|
||||
resume()
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
state = state.transition(State.PLAYING, State.PAUSED) { SDL_PauseAudio(1) }
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
state = state.transition(State.PAUSED, State.PLAYING) { SDL_PauseAudio(0) }
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
pause()
|
||||
state = state.transition(State.PAUSED, State.STOPPED) { SDL_CloseAudio() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun audioCallback(userdata: COpaquePointer?, buffer: CPointer<Uint8Var>?, length: Int) {
|
||||
// This handler will be invoked in the audio thread, so reinit runtime.
|
||||
initRuntimeIfNeeded()
|
||||
val decoder = DecoderWorker(Worker.fromCPointer(userdata))
|
||||
var outPosition = 0
|
||||
while (outPosition < length) {
|
||||
val frame = decoder.nextAudioFrame(length - outPosition)
|
||||
if (frame != null) {
|
||||
val toCopy = minOf(length - outPosition, frame.size - frame.position)
|
||||
memcpy(buffer + outPosition, frame.buffer.pointed.data + frame.position, toCopy.convert())
|
||||
frame.unref()
|
||||
outPosition += toCopy
|
||||
} else {
|
||||
// println("Decoder returned nothing!")
|
||||
memset(buffer + outPosition, 0, (length - outPosition).convert())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import sdl.SDL_GetError
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun throwSDLError(name: String): Nothing =
|
||||
throw Error("SDL_$name Error: ${SDL_GetError()!!.toKString()}")
|
||||
|
||||
fun checkSDLError(name: String, result: Int) {
|
||||
if (result != 0) throwSDLError(name)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import sdl.*
|
||||
|
||||
class SDLInput(private val player: VideoPlayer) : DisposableContainer() {
|
||||
private val event = arena.alloc<SDL_Event>().ptr
|
||||
|
||||
fun check() {
|
||||
while (SDL_PollEvent(event.reinterpret()) != 0) {
|
||||
when (event.pointed.type) {
|
||||
SDL_QUIT -> player.stop()
|
||||
SDL_KEYDOWN -> {
|
||||
val keyboardEvent = event.reinterpret<SDL_KeyboardEvent>().pointed
|
||||
when (keyboardEvent.keysym.scancode) {
|
||||
SDL_SCANCODE_ESCAPE -> player.stop()
|
||||
SDL_SCANCODE_SPACE -> player.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import sdl.*
|
||||
|
||||
enum class PixelFormat {
|
||||
INVALID,
|
||||
RGB24,
|
||||
ARGB32
|
||||
}
|
||||
|
||||
data class VideoOutput(val size: Dimensions, val pixelFormat: PixelFormat)
|
||||
|
||||
class SDLVideo : DisposableContainer() {
|
||||
private val displaySize: Dimensions
|
||||
private var window: SDLRendererWindow? = null
|
||||
|
||||
init {
|
||||
disposable(
|
||||
create = { checkSDLError("Init", SDL_Init(SDL_INIT_EVERYTHING)) },
|
||||
dispose = { SDL_Quit() })
|
||||
displaySize = tryConstruct {
|
||||
memScoped {
|
||||
alloc<SDL_DisplayMode>().run {
|
||||
checkSDLError("GetCurrentDisplayMode", SDL_GetCurrentDisplayMode(0, ptr.reinterpret()))
|
||||
Dimensions(w, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
stop()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
fun start(videoSize: Dimensions) {
|
||||
stop() // To free resources from previous playbacks.
|
||||
println("SDL Video: Playing output with ${videoSize.w} x ${videoSize.h} pixels")
|
||||
window = SDLRendererWindow((displaySize - videoSize) / 2, videoSize)
|
||||
}
|
||||
|
||||
fun pixelFormat(): PixelFormat = window?.pixelFormat() ?: PixelFormat.INVALID
|
||||
|
||||
fun nextFrame(frameData: CPointer<Uint8Var>, linesize: Int) =
|
||||
window?.nextFrame(frameData, linesize)
|
||||
|
||||
fun stop() {
|
||||
window?.let {
|
||||
it.dispose()
|
||||
window = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLRendererWindow(windowPos: Dimensions, videoSize: Dimensions) : DisposableContainer() {
|
||||
private val window = sdlDisposable("CreateWindow",
|
||||
SDL_CreateWindow("VideoPlayer", windowPos.w, windowPos.h, videoSize.w, videoSize.h, SDL_WINDOW_SHOWN),
|
||||
::SDL_DestroyWindow)
|
||||
private val renderer = sdlDisposable("CreateRenderer",
|
||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC),
|
||||
::SDL_DestroyRenderer)
|
||||
private val texture = sdlDisposable("CreateTexture",
|
||||
SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), 0, videoSize.w, videoSize.h),
|
||||
::SDL_DestroyTexture)
|
||||
private val rect = sdlDisposable("calloc(SDL_Rect)",
|
||||
SDL_calloc(1, sizeOf<SDL_Rect>().convert()), ::SDL_free)
|
||||
.reinterpret<SDL_Rect>()
|
||||
|
||||
init {
|
||||
rect.pointed.apply {
|
||||
x = 0
|
||||
y = 0
|
||||
w = videoSize.w
|
||||
h = videoSize.h
|
||||
}
|
||||
}
|
||||
|
||||
fun pixelFormat(): PixelFormat = when (SDL_GetWindowPixelFormat(window)) {
|
||||
SDL_PIXELFORMAT_RGB24 -> PixelFormat.RGB24
|
||||
SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_RGB888 -> PixelFormat.ARGB32
|
||||
else -> {
|
||||
println("Pixel format ${SDL_GetWindowPixelFormat(window)} unknown")
|
||||
PixelFormat.INVALID
|
||||
}
|
||||
}
|
||||
|
||||
fun nextFrame(frameData: CPointer<Uint8Var>, linesize: Int) {
|
||||
SDL_UpdateTexture(texture, rect, frameData, linesize)
|
||||
SDL_RenderClear(renderer)
|
||||
SDL_RenderCopy(renderer, texture, rect, rect)
|
||||
SDL_RenderPresent(renderer)
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.videoplayer
|
||||
|
||||
import ffmpeg.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
import kotlinx.cli.*
|
||||
|
||||
enum class State {
|
||||
PLAYING,
|
||||
STOPPED,
|
||||
PAUSED;
|
||||
|
||||
inline fun transition(from: State, to: State, block: () -> Unit): State =
|
||||
if (this == from) {
|
||||
block()
|
||||
to
|
||||
} else this
|
||||
}
|
||||
|
||||
enum class PlayMode {
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
BOTH;
|
||||
|
||||
val useVideo: Boolean get() = this != AUDIO
|
||||
val useAudio: Boolean get() = this != VIDEO
|
||||
}
|
||||
|
||||
class VideoPlayer(private val requestedSize: Dimensions?) : DisposableContainer() {
|
||||
private val decoder = disposable { DecoderWorker() }
|
||||
private val video = disposable { SDLVideo() }
|
||||
private val audio = disposable { SDLAudio(this) }
|
||||
private val input = disposable { SDLInput(this) }
|
||||
private val now = arena.alloc<timespec>().ptr
|
||||
|
||||
private var state = State.STOPPED
|
||||
|
||||
val worker get() = decoder.worker
|
||||
var lastFrameTime = 0.0
|
||||
|
||||
fun stop() {
|
||||
state = State.STOPPED
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
when (state) {
|
||||
State.PAUSED -> {
|
||||
state = State.PLAYING
|
||||
audio.resume()
|
||||
}
|
||||
State.PLAYING -> {
|
||||
state = State.PAUSED
|
||||
audio.pause()
|
||||
}
|
||||
State.STOPPED -> throw Error("Cannot pause in stopped state")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTime(): Double {
|
||||
clock_gettime(CLOCK_MONOTONIC, now)
|
||||
return now.pointed.tv_sec + now.pointed.tv_nsec / 1e9
|
||||
}
|
||||
|
||||
fun playFile(fileName: String, mode: PlayMode) {
|
||||
println("playFile $fileName")
|
||||
val file = AVFile(fileName)
|
||||
try {
|
||||
file.dumpFormat()
|
||||
val info = decoder.initDecode(file.context, mode.useVideo, mode.useAudio)
|
||||
val videoSize = requestedSize ?: info.video?.size ?: Dimensions(400, 200)
|
||||
// Use requested video size to start SDLVideo
|
||||
info.video?.let { video.start(videoSize) }
|
||||
// Configure decoder output based on actual SDLVideo pixel format
|
||||
val videoOutput = VideoOutput(videoSize, video.pixelFormat())
|
||||
// Use fixed audio output format
|
||||
val audioOutput = AudioOutput(44100, 2, SampleFormat.S16)
|
||||
// Start decoder
|
||||
decoder.start(videoOutput, audioOutput)
|
||||
// Start SDLAudio player
|
||||
info.audio?.let { audio.start(audioOutput) }
|
||||
// Main player loop
|
||||
lastFrameTime = getTime()
|
||||
state = State.PLAYING
|
||||
decoder.requestDecodeChunk() // Fill in frame caches
|
||||
while (state != State.STOPPED) {
|
||||
// Fetch video
|
||||
info.video?.let { playVideoFrame(it) }
|
||||
// Audio is being auto-fetched by the audio thread
|
||||
// Check if there are any input
|
||||
input.check()
|
||||
// Pause support
|
||||
checkPause()
|
||||
// Inter-frame pause, may lead to broken A/V sync, think of better approach
|
||||
if (state == State.PLAYING) syncAV(info)
|
||||
if (decoder.done()) stop()
|
||||
}
|
||||
} finally {
|
||||
stop()
|
||||
audio.stop()
|
||||
video.stop()
|
||||
decoder.stop()
|
||||
file.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private fun playVideoFrame(videoInfo: VideoInfo) {
|
||||
// Fetch next frame
|
||||
val frame = decoder.nextVideoFrame() ?: return
|
||||
// Use video FPS to maintain frame rate
|
||||
val now = getTime()
|
||||
val frameDuration = 1.0 / videoInfo.fps
|
||||
val passedTime = now - lastFrameTime
|
||||
lastFrameTime += frameDuration // try to maintain perfect frame rate
|
||||
// Wait for next frame, if needed
|
||||
if (passedTime < frameDuration) {
|
||||
usleep((1000_000 * (frameDuration - passedTime)).toInt().toUInt())
|
||||
} else if (passedTime > frameDuration * 1.5){
|
||||
lastFrameTime = now // we fell behind more than half frame, reset time
|
||||
}
|
||||
// Play frame
|
||||
video.nextFrame(frame.buffer.pointed.data!!, frame.lineSize)
|
||||
frame.unref()
|
||||
}
|
||||
|
||||
private fun checkPause() {
|
||||
while (state == State.PAUSED) {
|
||||
audio.pause()
|
||||
input.check()
|
||||
usleep(1u * 1000u)
|
||||
}
|
||||
audio.resume()
|
||||
}
|
||||
|
||||
private fun syncAV(info: CodecInfo) {
|
||||
if (info.hasVideo) {
|
||||
if (info.hasAudio) {
|
||||
// Use sound for A/V sync.
|
||||
if (!decoder.audioVideoSynced()) {
|
||||
println("Resynchronizing video with audio")
|
||||
while (!decoder.audioVideoSynced() && state == State.PLAYING) {
|
||||
usleep(500)
|
||||
input.check()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For pure sound, playback is driven by demand.
|
||||
usleep(10u * 1000u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val argParser = ArgParser("videoplayer")
|
||||
val mode by argParser.option(
|
||||
ArgType.Choice<PlayMode>(), shortName = "m", description = "Play mode")
|
||||
.default(PlayMode.BOTH)
|
||||
val size by argParser.option(ArgType.Int, shortName = "s", description = "Required size of videoplayer window")
|
||||
.delimiter(",")
|
||||
val fileName by argParser.argument(ArgType.String, description = "File to play")
|
||||
argParser.parse(args)
|
||||
|
||||
av_register_all()
|
||||
val requestedSize = if (size.size != 2) {
|
||||
if (size.isNotEmpty())
|
||||
println("Size value should include width and height separated with ','.")
|
||||
null
|
||||
} else
|
||||
Dimensions(size[0], size[1])
|
||||
val player = VideoPlayer(requestedSize)
|
||||
try {
|
||||
player.playFile(fileName, mode)
|
||||
} finally {
|
||||
player.dispose()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="11134" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="11055"/>
|
||||
</dependencies>
|
||||
<scenes/>
|
||||
</document>
|
||||
@@ -0,0 +1,114 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
val sdkName: String? = System.getenv("SDK_NAME")
|
||||
|
||||
enum class Target(val simulator: Boolean, val key: String) {
|
||||
WATCHOS_X86(true, "watchos"), WATCHOS_ARM64(false, "watchos"),
|
||||
IOS_X64(true, "ios"), IOS_ARM64(false, "ios")
|
||||
}
|
||||
|
||||
val target = sdkName.orEmpty().let {
|
||||
when {
|
||||
it.startsWith("iphoneos") -> Target.IOS_ARM64
|
||||
it.startsWith("iphonesimulator") -> Target.IOS_X64
|
||||
it.startsWith("watchos") -> Target.WATCHOS_ARM64
|
||||
it.startsWith("watchsimulator") -> Target.WATCHOS_X86
|
||||
else -> Target.WATCHOS_X86
|
||||
}
|
||||
}
|
||||
|
||||
val buildType = System.getenv("CONFIGURATION")?.let {
|
||||
NativeBuildType.valueOf(it.toUpperCase())
|
||||
} ?: NativeBuildType.DEBUG
|
||||
|
||||
kotlin {
|
||||
// Declare a target.
|
||||
// We declare only one target (either arm64 or x64)
|
||||
// to workaround lack of common platform libraries
|
||||
// for both device and simulator.
|
||||
val ios = if (!target.simulator ) {
|
||||
// Device.
|
||||
iosArm64("ios")
|
||||
} else {
|
||||
// Simulator.
|
||||
iosX64("ios")
|
||||
}
|
||||
|
||||
val watchos = if (!target.simulator) {
|
||||
// Device.
|
||||
watchosArm64("watchos")
|
||||
} else {
|
||||
// Simulator.
|
||||
watchosX86("watchos")
|
||||
}
|
||||
|
||||
// Declare the output program.
|
||||
ios.binaries.executable(listOf(buildType)) {
|
||||
baseName = "app"
|
||||
}
|
||||
|
||||
watchos.binaries.executable(listOf(buildType)) {
|
||||
baseName = "watchapp"
|
||||
entryPoint = "sample.watchos.main"
|
||||
}
|
||||
|
||||
// Configure dependencies.
|
||||
val appleMain by sourceSets.creating {
|
||||
dependsOn(sourceSets["commonMain"])
|
||||
}
|
||||
|
||||
sourceSets["iosMain"].dependsOn(appleMain)
|
||||
sourceSets["watchosMain"].dependsOn(appleMain)
|
||||
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.optIn("kotlin.native.SymbolNameIsInternal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create Xcode integration tasks.
|
||||
val targetBuildDir: String? = System.getenv("TARGET_BUILD_DIR")
|
||||
val executablePath: String? = System.getenv("EXECUTABLE_PATH")
|
||||
|
||||
val currentTarget = kotlin.targets[target.key] as KotlinNativeTarget
|
||||
val kotlinBinary = currentTarget.binaries.getExecutable(buildType)
|
||||
val xcodeIntegrationGroup = "Xcode integration"
|
||||
|
||||
val packForXCode = if (sdkName == null || targetBuildDir == null || executablePath == null) {
|
||||
// The build is launched not by Xcode ->
|
||||
// We cannot create a copy task and just show a meaningful error message.
|
||||
tasks.create("packForXCode").doLast {
|
||||
throw IllegalStateException("Please run the task from Xcode")
|
||||
}
|
||||
} else {
|
||||
// Otherwise copy the executable into the Xcode output directory.
|
||||
tasks.create("packForXCode", Copy::class.java) {
|
||||
dependsOn(kotlinBinary.linkTask)
|
||||
destinationDir = file(targetBuildDir)
|
||||
val dsymSource = kotlinBinary.outputFile.absolutePath + ".dSYM"
|
||||
val dsymDestination = file(executablePath).parentFile.name + ".dSYM"
|
||||
val oldExecName = kotlinBinary.outputFile.name
|
||||
val newExecName = file(executablePath).name
|
||||
|
||||
from(dsymSource) {
|
||||
into(dsymDestination)
|
||||
rename(oldExecName, newExecName)
|
||||
}
|
||||
|
||||
from(kotlinBinary.outputFile) {
|
||||
rename { executablePath }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>controllers</key>
|
||||
<dict>
|
||||
<key>controller-AgC-eL-Hgc</key>
|
||||
<dict>
|
||||
<key>controllerClass</key>
|
||||
<string>Watchapp3InterfaceController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>root</key>
|
||||
<string>controller-AgC-eL-Hgc</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>WKAppBundleIdentifier</key>
|
||||
<string>com.jetbrains.watchapp0.watchapp3.watchkitapp</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
<key>WKExtensionDelegateClassName</key>
|
||||
<string>Watchapp3ExtensionDelegate</string>
|
||||
<key>WKWatchOnly</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,54 @@
|
||||
name: watchosSample
|
||||
options:
|
||||
bundleIdPrefix: com.jetbrains.watchapp0
|
||||
settings:
|
||||
DEVELOPMENT_TEAM: N462MKSJ7M
|
||||
CODE_SIGN_IDENTITY: "iPhone Developer"
|
||||
CODE_SIGN_STYLE: Automatic
|
||||
MARKETING_VERSION: "1.0"
|
||||
CURRENT_PROJECT_VERSION: "4"
|
||||
SDKROOT: iphoneos
|
||||
targets:
|
||||
watchapp3.watchkitapp.watchkitextension:
|
||||
type: watchkit2-extension
|
||||
platform: watchOS
|
||||
sources:
|
||||
- "plists/Ext"
|
||||
- "src/"
|
||||
prebuildScripts:
|
||||
- script: cd "$SRCROOT" && ../gradlew -p .. packForXCode
|
||||
name: GradleCompile
|
||||
info:
|
||||
path: plists/Ext/Info.plist
|
||||
properties:
|
||||
WKWatchOnly: true
|
||||
CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
WKExtensionDelegateClassName: Watchapp3ExtensionDelegate
|
||||
NSExtension:
|
||||
NSExtensionAttributes:
|
||||
WKAppBundleIdentifier: com.jetbrains.watchapp0.watchapp3.watchkitapp
|
||||
NSExtensionPointIdentifier: com.apple.watchkit
|
||||
watchapp3.watchkitapp:
|
||||
type: application.watchapp2
|
||||
platform: watchOS
|
||||
dependencies:
|
||||
- target: "watchapp3.watchkitapp.watchkitextension"
|
||||
sources:
|
||||
- "plists/App"
|
||||
- "plists/App/Interface.plist"
|
||||
info:
|
||||
path: plists/App/Info.plist
|
||||
properties:
|
||||
WKWatchKitApp: true
|
||||
CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
settings:
|
||||
ENABLE_BITCODE: "YES"
|
||||
ONLY_ACTIVE_ARCH: "YES"
|
||||
VALID_ARCHS: $(ARCHS_STANDARD)
|
||||
watchapp3:
|
||||
type: application.watchapp2-container
|
||||
platform: iOS
|
||||
dependencies:
|
||||
- target: "watchapp3.watchkitapp"
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 sample.watchos
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.Foundation.*
|
||||
import platform.WatchKit.*
|
||||
import platform.darwin.NSObject
|
||||
|
||||
// Standard entry point for WatchKit applications.
|
||||
@SymbolName("WKExtensionMain")
|
||||
external fun WKExtensionMain(argc: Int, argv: CPointer<CPointerVar<ByteVar>>)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
memScoped {
|
||||
val argc = args.size + 1
|
||||
val argv = (arrayOf("konan") + args).map { it.cstr.ptr }.toCValues()
|
||||
|
||||
autoreleasepool {
|
||||
WKExtensionMain(argc, argv.ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name of this class is mentioned in Info.plist.
|
||||
@ExportObjCClass
|
||||
@Suppress("CONFLICTING_OVERLOADS")
|
||||
class Watchapp3ExtensionDelegate : NSObject, WKExtensionDelegateProtocol {
|
||||
|
||||
@OverrideInit constructor() : super() {
|
||||
println("constructor Watchapp3ExtensionDelegate")
|
||||
}
|
||||
|
||||
override fun applicationDidFinishLaunching() {
|
||||
println("applicationDidFinishLaunching")
|
||||
}
|
||||
}
|
||||
|
||||
// Name of this class is mentioned in Interface.plist.
|
||||
@ExportObjCClass
|
||||
class Watchapp3InterfaceController : WKInterfaceController {
|
||||
@OverrideInit constructor() : super() {
|
||||
println("constructor Watchapp3InterfaceController")
|
||||
}
|
||||
|
||||
override fun didAppear() {
|
||||
println("didAppear")
|
||||
presentTextInputControllerWithSuggestions(null, WKTextInputMode.WKTextInputModeAllowAnimatedEmoji) {
|
||||
results ->
|
||||
println("printed $results")
|
||||
}
|
||||
}
|
||||
|
||||
override fun awakeWithContext(context: Any?) {
|
||||
super.awakeWithContext(context)
|
||||
println("awakeWithContext $context")
|
||||
if (context == null) {
|
||||
setTitle("Kotlin/Native sample")
|
||||
}
|
||||
}
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2E2A2A9B9037D7A4B8891638 /* Interface.plist in Resources */ = {isa = PBXBuildFile; fileRef = BA63A983DA7F116016C945B1 /* Interface.plist */; };
|
||||
465E1456E1F3939248A1AE18 /* watchapp3.watchkitapp.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = C83F8D8D7FEC0305A3C42EF0 /* watchapp3.watchkitapp.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
C413044D6EC06F630A1281B6 /* watchapp3.watchkitapp.watchkitextension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D8268FB6509819F8DF88F338 /* watchapp3.watchkitapp.watchkitextension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
50B1D2FDA42D7325321EAF92 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = CF674FA31D0C7A5F482A25CD /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 85F30E635DAD8136EC80F430;
|
||||
remoteInfo = watchapp3.watchkitapp.watchkitextension;
|
||||
};
|
||||
777F9D95E28CDA6B3DB73D90 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = CF674FA31D0C7A5F482A25CD /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = C432419F771BBC1164EFD4F4;
|
||||
remoteInfo = watchapp3.watchkitapp;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
4E503C8039A14A5EA90C75B2 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
C413044D6EC06F630A1281B6 /* watchapp3.watchkitapp.watchkitextension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
B00C7BBEEE4BC6B46F93C76A /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
465E1456E1F3939248A1AE18 /* watchapp3.watchkitapp.app in Embed Watch Content */,
|
||||
);
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
6CC853D0EA8F64CF953D56AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
70938E1058DAC99B6ACB89A1 /* watchapp3.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = watchapp3.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AA40AB18A1134B1A4F332C2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
BA63A983DA7F116016C945B1 /* Interface.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Interface.plist; sourceTree = "<group>"; };
|
||||
C83F8D8D7FEC0305A3C42EF0 /* watchapp3.watchkitapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watchapp3.watchkitapp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D8268FB6509819F8DF88F338 /* watchapp3.watchkitapp.watchkitextension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = watchapp3.watchkitapp.watchkitextension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3935909E94CECE9E5D5E428A /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
70938E1058DAC99B6ACB89A1 /* watchapp3.app */,
|
||||
C83F8D8D7FEC0305A3C42EF0 /* watchapp3.watchkitapp.app */,
|
||||
D8268FB6509819F8DF88F338 /* watchapp3.watchkitapp.watchkitextension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
ADD17C1426885B036B4D4500 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BBACCDB231253BE6BD60C83F /* App */,
|
||||
E7C757434B0E5A8C26D64165 /* Ext */,
|
||||
3935909E94CECE9E5D5E428A /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BBACCDB231253BE6BD60C83F /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6CC853D0EA8F64CF953D56AD /* Info.plist */,
|
||||
BA63A983DA7F116016C945B1 /* Interface.plist */,
|
||||
);
|
||||
name = App;
|
||||
path = plists/App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E7C757434B0E5A8C26D64165 /* Ext */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AA40AB18A1134B1A4F332C2 /* Info.plist */,
|
||||
);
|
||||
name = Ext;
|
||||
path = plists/Ext;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
85F30E635DAD8136EC80F430 /* watchapp3.watchkitapp.watchkitextension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E590E047549EA79901895875 /* Build configuration list for PBXNativeTarget "watchapp3.watchkitapp.watchkitextension" */;
|
||||
buildPhases = (
|
||||
ED480B935D153474B4BE5E66 /* GradleCompile */,
|
||||
F1E62571296D41A29073A0DC /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = watchapp3.watchkitapp.watchkitextension;
|
||||
productName = watchapp3.watchkitapp.watchkitextension;
|
||||
productReference = D8268FB6509819F8DF88F338 /* watchapp3.watchkitapp.watchkitextension.appex */;
|
||||
productType = "com.apple.product-type.watchkit2-extension";
|
||||
};
|
||||
C2A3BDB9D8B1B6F53A29A1DE /* watchapp3 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 9FC8F6F94AA3C978F239A5AC /* Build configuration list for PBXNativeTarget "watchapp3" */;
|
||||
buildPhases = (
|
||||
64396ECFF63C36B1C586BC70 /* Sources */,
|
||||
B00C7BBEEE4BC6B46F93C76A /* Embed Watch Content */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
1D2B97A46A6DF0B1A5F835FC /* PBXTargetDependency */,
|
||||
);
|
||||
name = watchapp3;
|
||||
productName = watchapp3;
|
||||
productReference = 70938E1058DAC99B6ACB89A1 /* watchapp3.app */;
|
||||
productType = "com.apple.product-type.application.watchapp2-container";
|
||||
};
|
||||
C432419F771BBC1164EFD4F4 /* watchapp3.watchkitapp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F10C79A6F81EA8DC928F5411 /* Build configuration list for PBXNativeTarget "watchapp3.watchkitapp" */;
|
||||
buildPhases = (
|
||||
5545C689631BCCAF2B3AF567 /* Sources */,
|
||||
1C155B15CD6523CC1566092A /* Resources */,
|
||||
4E503C8039A14A5EA90C75B2 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
AF975C9937185BABD014C388 /* PBXTargetDependency */,
|
||||
);
|
||||
name = watchapp3.watchkitapp;
|
||||
productName = watchapp3.watchkitapp;
|
||||
productReference = C83F8D8D7FEC0305A3C42EF0 /* watchapp3.watchkitapp.app */;
|
||||
productType = "com.apple.product-type.application.watchapp2";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
CF674FA31D0C7A5F482A25CD /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
TargetAttributes = {
|
||||
85F30E635DAD8136EC80F430 = {
|
||||
DevelopmentTeam = N462MKSJ7M;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
C2A3BDB9D8B1B6F53A29A1DE = {
|
||||
DevelopmentTeam = N462MKSJ7M;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
C432419F771BBC1164EFD4F4 = {
|
||||
DevelopmentTeam = N462MKSJ7M;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DAC5D137D023645E8075D0A0 /* Build configuration list for PBXProject "watchosSample" */;
|
||||
compatibilityVersion = "Xcode 10.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = ADD17C1426885B036B4D4500;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
C2A3BDB9D8B1B6F53A29A1DE /* watchapp3 */,
|
||||
C432419F771BBC1164EFD4F4 /* watchapp3.watchkitapp */,
|
||||
85F30E635DAD8136EC80F430 /* watchapp3.watchkitapp.watchkitextension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
1C155B15CD6523CC1566092A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2E2A2A9B9037D7A4B8891638 /* Interface.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
ED480B935D153474B4BE5E66 /* GradleCompile */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = GradleCompile;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "cd \"$SRCROOT\" && ../gradlew -p .. packForXCode";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
5545C689631BCCAF2B3AF567 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
64396ECFF63C36B1C586BC70 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F1E62571296D41A29073A0DC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
1D2B97A46A6DF0B1A5F835FC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = C432419F771BBC1164EFD4F4 /* watchapp3.watchkitapp */;
|
||||
targetProxy = 777F9D95E28CDA6B3DB73D90 /* PBXContainerItemProxy */;
|
||||
};
|
||||
AF975C9937185BABD014C388 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 85F30E635DAD8136EC80F430 /* watchapp3.watchkitapp.watchkitextension */;
|
||||
targetProxy = 50B1D2FDA42D7325321EAF92 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
42683BF037EF7F48F88A88E6 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = N462MKSJ7M;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
955A76A7B52DD667863D8504 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
964D5540C94A88A9C7443DC0 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = plists/App/Info.plist;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3.watchkitapp;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
VALID_ARCHS = "$(ARCHS_STANDARD)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
AA41209F69F829E248A0B3BC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = N462MKSJ7M;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"DEBUG=1",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
B7ECDB01F700B2FCF22D16A4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_BITCODE = YES;
|
||||
INFOPLIST_FILE = plists/App/Info.plist;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3.watchkitapp;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
VALID_ARCHS = "$(ARCHS_STANDARD)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
BE4BD7149295C555AB8E7434 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
INFOPLIST_FILE = plists/Ext/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3.watchkitapp.watchkitextension;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E5DC67A24A9D438C35262109 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
INFOPLIST_FILE = plists/Ext/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3.watchkitapp.watchkitextension;
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F4338EEF3126A85F45205463 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jetbrains.watchapp0.watchapp3;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
9FC8F6F94AA3C978F239A5AC /* Build configuration list for PBXNativeTarget "watchapp3" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F4338EEF3126A85F45205463 /* Debug */,
|
||||
955A76A7B52DD667863D8504 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "";
|
||||
};
|
||||
DAC5D137D023645E8075D0A0 /* Build configuration list for PBXProject "watchosSample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
AA41209F69F829E248A0B3BC /* Debug */,
|
||||
42683BF037EF7F48F88A88E6 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
E590E047549EA79901895875 /* Build configuration list for PBXNativeTarget "watchapp3.watchkitapp.watchkitextension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
BE4BD7149295C555AB8E7434 /* Debug */,
|
||||
E5DC67A24A9D438C35262109 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "";
|
||||
};
|
||||
F10C79A6F81EA8DC928F5411 /* Build configuration list for PBXNativeTarget "watchapp3.watchkitapp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
964D5540C94A88A9C7443DC0 /* Debug */,
|
||||
B7ECDB01F700B2FCF22D16A4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = "";
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CF674FA31D0C7A5F482A25CD /* Project object */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
mingwX64("win32") {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.win32.main"
|
||||
linkerOpts("-Wl,--subsystem,windows")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.win32
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.windows.*
|
||||
|
||||
fun main() {
|
||||
val message = StringBuilder()
|
||||
memScoped {
|
||||
val buffer = allocArray<UShortVar>(MAX_PATH)
|
||||
GetModuleFileNameW(null, buffer, MAX_PATH)
|
||||
val path = buffer.toKString().split("\\").dropLast(1).joinToString("\\")
|
||||
message.append("Я нахожусь в $path\n")
|
||||
}
|
||||
MessageBoxW(null, "Konan говорит:\nЗДРАВСТВУЙ МИР!\n$message",
|
||||
"Заголовок окна", (MB_YESNOCANCEL or MB_ICONQUESTION).convert())
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("workers")
|
||||
hostOs == "Linux" -> linuxX64("workers")
|
||||
hostOs.startsWith("Windows") -> mingwX64("workers")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.workers.main"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.workers
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
|
||||
data class WorkerArgument(val intParam: Int, val stringParam: String)
|
||||
data class WorkerResult(val intResult: Int, val stringResult: String)
|
||||
|
||||
fun main() {
|
||||
val COUNT = 5
|
||||
val workers = Array(COUNT, { _ -> Worker.start() })
|
||||
|
||||
for (attempt in 1..3) {
|
||||
val futures = Array(workers.size) { workerIndex ->
|
||||
workers[workerIndex].execute(TransferMode.SAFE, {
|
||||
WorkerArgument(workerIndex, "attempt $attempt")
|
||||
}) { input ->
|
||||
var sum = 0
|
||||
for (i in 0..input.intParam * 1000) {
|
||||
sum += i
|
||||
}
|
||||
WorkerResult(sum, input.stringParam + " result")
|
||||
}
|
||||
}
|
||||
val futureSet = futures.toSet()
|
||||
var consumed = 0
|
||||
while (consumed < futureSet.size) {
|
||||
val ready = waitForMultipleFutures(futureSet, 10000)
|
||||
ready.forEach {
|
||||
it.consume { result ->
|
||||
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
||||
consumed++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
workers.forEach {
|
||||
it.requestTermination().result
|
||||
}
|
||||
println("Workers: OK")
|
||||
}
|
||||
Reference in New Issue
Block a user