Native: move samples to backend.native/tests/

This commit is contained in:
Svyatoslav Scherbina
2022-08-26 11:50:39 +02:00
committed by Space
parent b7337d2e64
commit 7bf6d64cfb
94 changed files with 94 additions and 93 deletions
@@ -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
@@ -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
@@ -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
@@ -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()
}
@@ -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
@@ -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
View File
@@ -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" "$@"
+89
View File
@@ -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>
@@ -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()
}
}
}
@@ -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 }
}
}
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -0,0 +1 @@
headers = tensorflow/c/c_api.h
@@ -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
@@ -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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -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
@@ -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(&copy);
}
static void avcodec_free_context2(AVCodecContext* ref) {
AVCodecContext* copy = ref;
avcodec_free_context(&copy);
}
static void avformat_free_context2(AVFormatContext* ref) {
AVFormatContext* copy = ref;
avformat_free_context(&copy);
}
static void swr_free2(SwrContext* ref) {
SwrContext* copy = ref;
swr_free(&copy);
}
@@ -0,0 +1,7 @@
package = sdl
headers = SDL.h
entryPoint = SDL_main
headerFilter = SDL*
linkerOpts = -lSDL2
@@ -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
}
@@ -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)
}
@@ -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)
}
@@ -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()
}
@@ -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
}
}
}
@@ -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)
}
@@ -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()
}
}
}
}
}
}
@@ -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)
}
}
@@ -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")
}
}
}
@@ -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 */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -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")
}