Skip to content

Commit

Permalink
WIP: Kotlin support
Browse files Browse the repository at this point in the history
Signed-off-by: Patryk Wrobel <[email protected]>
  • Loading branch information
pwrobeldev committed Jan 21, 2025
1 parent 6519c60 commit e8dac33
Show file tree
Hide file tree
Showing 56 changed files with 2,613 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The general form of the command is::
gluecodium_add_generate_command(
<target> # The target to add custom build rules for.
[GENERATORS <generator> ...] # The list of source code generators.
# Known value are: cpp, swift, android, dart
# Known value are: cpp, swift, android, android-kotlin, dart
# Usually at least two generators needs to be defined
# the one for native code and another for platform,
# for example android;cpp or swift;cpp
Expand Down
16 changes: 16 additions & 0 deletions cmake/modules/gluecodium/gluecodium/KnownOptionalProperties.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ _gluecodium_define_target_property(
"This property is initialized by the value of the GLUECODIUM_JAVA_INTERNAL_PACKAGE_DEFAULT variable if it is set when the function gluecodium_add_generate_command is called."
)

_gluecodium_define_target_property(
GLUECODIUM_KOTLIN_PACKAGE
BRIEF_DOCS "The base Kotlin package to use for generated Kotlin sources"
FULL_DOCS
"The base Kotlin package to use for generated Kotlin sources, for example \"com.my_company\"."
"This property is initialized by the value of the GLUECODIUM_KOTLIN_PACKAGE_DEFAULT variable if it is set when the function gluecodium_add_generate_command is called."
)

_gluecodium_define_target_property(
GLUECODIUM_KOTLIN_INTERNAL_PACKAGE
BRIEF_DOCS "The package to use for internal Kotlin code"
FULL_DOCS
"The subpackage to use for internal Kotlin code. This value is appended with separator '.' to a value passed with GLUECODIUM_KOTLIN_PACKAGE"
"This property is initialized by the value of the GLUECODIUM_KOTLIN_INTERNAL_PACKAGE_DEFAULT variable if it is set when the function gluecodium_add_generate_command is called."
)

_gluecodium_define_target_property(
GLUECODIUM_JAVA_NAMERULES
BRIEF_DOCS "The path to a file with name rules for Java"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ function(gluecodium_get_target_include_directories _target)
"$<BUILD_INTERFACE:${_output_${_source_set_lower}_dir}/android/jni>")
endif()

if(android-kotlin IN_LIST _generators)
list(APPEND _result_list_public
"$<BUILD_INTERFACE:${_output_${_source_set_lower}_dir}/android-kotlin/jni>")
endif()

if(dart IN_LIST _generators)
list(APPEND _result_list_public
"$<BUILD_INTERFACE:${_output_${_source_set_lower}_dir}/dart/ffi>")
Expand All @@ -85,7 +90,7 @@ function(gluecodium_get_target_include_directories _target)

endforeach()

if(android IN_LIST _generators)
if((android IN_LIST _generators) OR (android-kotlin IN_LIST _generators))
# If we're not crosscompiling, we need to manually add JNI includes.
if(NOT CMAKE_CROSSCOMPILING)
find_package(JNI REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ macro(gluecodium_init_variables_with_united_file_paths name_suffix)
set(GLUECODIUM_GENERATED_cpp_${_source_group} cpp/${name_suffix}_${_source_group}_glue.cpp)
set(GLUECODIUM_GENERATED_jni_${_source_group}
android/${name_suffix}_${_source_group}_jniglue.cpp)
set(GLUECODIUM_GENERATED_jni_kotlin_${_source_group}
android-kotlin/${name_suffix}_${_source_group}_jniglue.cpp)
set(GLUECODIUM_GENERATED_cbridge_${_source_group}
cbridge/${name_suffix}_${_source_group}_cglue.cpp)
set(GLUECODIUM_GENERATED_cbridge_header_${_source_group}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ function(gluecodium_list_generated_files _target)
list(APPEND _android_generated_files "${_unity_dir}/${GLUECODIUM_GENERATED_jni_${_group}}")
endif()

if(android-kotlin IN_LIST _generators)
list(APPEND _android_kotlin_generated_files "${_unity_dir}/${GLUECODIUM_GENERATED_jni_kotlin_${_group}}")
endif()

if(swift IN_LIST _generators)
list(APPEND _cbridge_generated_files
"${_unity_dir}/${GLUECODIUM_GENERATED_cbridge_${_group}}")
Expand All @@ -76,11 +80,12 @@ function(gluecodium_list_generated_files _target)
set(${_args_OUTPUT_ALL}
${_cpp_generated_files} ${_android_generated_files} ${_cbridge_generated_files}
${_cbridge_headers_generated_files} ${_swift_generated_files} ${_dart_generated_files}
${_android_kotlin_generated_files}
PARENT_SCOPE)
endif()

if(_args_OUTPUT_CPP)
set(${_args_OUTPUT_CPP} ${_cpp_generated_files} ${_android_generated_files}
set(${_args_OUTPUT_CPP} ${_cpp_generated_files} ${_android_generated_files} ${_android_kotlin_generated_files}
${_cbridge_generated_files} ${_dart_generated_files} PARENT_SCOPE)
endif()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function(gluecodium_read_required_properties _target)
message(FATAL_ERROR "Specified target '${_target}' doesn't exist")
endif()

set(GLUECODIUM_SUPPORTED_GENERATORS cpp android swift dart)
set(GLUECODIUM_SUPPORTED_GENERATORS cpp android android-kotlin swift dart)

function(_read_required_property result _target property_name)
get_target_property(_property_value ${_target} ${property_name})
Expand Down
8 changes: 8 additions & 0 deletions cmake/modules/gluecodium/gluecodium/details/runGenerate.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ function(_prepare_gluecodium_config_file file_path)
_append_list_paths_option(input GLUECODIUM_LIME_SOURCES)
_append_list_paths_option(auxinput GLUECODIUM_LIME_SOURCES_AUX)

_append_option(kotlinpackage GLUECODIUM_KOTLIN_PACKAGE)
_append_option(kotlinintpackage GLUECODIUM_KOTLIN_INTERNAL_PACKAGE)
_append_option(javapackage GLUECODIUM_JAVA_PACKAGE)
_append_option(intpackage GLUECODIUM_JAVA_INTERNAL_PACKAGE)
_append_option(javanonnullannotation GLUECODIUM_JAVA_NONNULL_ANNOTATION)
Expand Down Expand Up @@ -251,6 +253,12 @@ function(_collect_all_files_in_single_compilation_units)
_include_all(jni "android/jni/*_Conversion.h" "android/jni/*.cpp")
endif()

if(android-kotlin IN_LIST GLUECODIUM_GENERATORS)
# Include all conversion headers first, so all later generic conversions relying on
# specialization have all these defined
_include_all(jni_kotlin "android-kotlin/jni/*_Conversion.h" "android-kotlin/jni/*.cpp")
endif()

if(swift IN_LIST GLUECODIUM_GENERATORS)
_include_all(cbridge "cbridge/*.cpp")
# Collect all includes to be used in the modulemap
Expand Down
14 changes: 13 additions & 1 deletion functional-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,19 @@ ext {
minSdkVersion = 24
cmakeVersion = '3.19.0+'

buildRoot = "${rootProject.projectDir}/build-android"
isAndroidKotlinBuild = {
return project.hasProperty("android-kotlin")
}

getAndroidBuildVariant = {
if (isAndroidKotlinBuild()) {
return "android-kotlin"
} else {
return "android"
}
}

buildRoot = "${rootProject.projectDir}/build-${getAndroidBuildVariant()}"
buildNativeRoot = "${buildRoot}/cpp"
generatedDir = "${buildRoot}/generated"

Expand Down
4 changes: 3 additions & 1 deletion functional-tests/functional/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ feature(BuiltinTypes cpp android swift dart SOURCES
input/lime/StaticIntMethods.lime
)

feature(Classes cpp android swift dart SOURCES
feature(Classes cpp android android-kotlin swift dart SOURCES
input/src/cpp/Instances.h
input/src/cpp/Instances.cpp
input/src/cpp/InstancesFactory.cpp
Expand Down Expand Up @@ -537,6 +537,8 @@ set_target_properties(functional_bindings PROPERTIES
GLUECODIUM_JAVA_INTERNAL_PACKAGE lorem.ipsum
GLUECODIUM_JAVA_NONNULL_ANNOTATION androidx.annotation.NonNull
GLUECODIUM_JAVA_NULLABLE_ANNOTATION androidx.annotation.Nullable
GLUECODIUM_KOTLIN_PACKAGE com.here.android
GLUECODIUM_KOTLIN_INTERNAL_PACKAGE lorem.ipsum
GLUECODIUM_CPP_INTERNAL_NAMESPACE "lorem_ipsum::test"
GLUECODIUM_CBRIDGE_INTERNAL_PREFIX "libfunctional_"
GLUECODIUM_DART_LIBRARY_NAME "functional"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016-2025 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package com.here.android

import android.annotation.SuppressLint
import android.app.Application
import android.util.Log
import com.here.android.lorem.ipsum.NativeBase
import com.here.gluecodium.test.functional.BuildConfig
import org.powermock.reflect.Whitebox
import org.robolectric.shadows.ShadowLog
import java.io.File

class RobolectricApplication : Application() {
private val TAG: String = RobolectricApplication::class.java.getSimpleName()

override fun onCreate() {
super.onCreate()

if (isFirstTime) {
isFirstTime = false

loadNativeLibraries();
Runtime.getRuntime().addShutdownHook(Thread{
try {
Whitebox.invokeMethod(NativeBase::class.java, "cleanUpQueue")
} catch (e: Exception) {
e.printStackTrace()
}
})
}
}

/**
* Separate method to load libraries in case you want to subclass and can't load in onCreate().
*/
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadNativeLibraries() {
val appLibraryPath: java.io.File = java.io.File(BuildConfig.NATIVE_LIB_HOST_DIR)

Log.d(TAG, "loadNativeLibraries: Using app library path: " + appLibraryPath)

val files = appLibraryPath.listFiles {
dir, name -> name.contains(".so") || name.endsWith(".dylib")
}

for (sharedObject in files) {
Log.i(TAG, "loadNativeLibraries: Loading app library '" + sharedObject.getName() + "'...")
System.load(sharedObject.getAbsolutePath())
}
}

companion object {
private var isFirstTime: Boolean = true

init {
ShadowLog.stream = System.out // Android logcat output.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2016-2025 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/
package com.here.android.test

import com.here.android.RobolectricApplication
import org.junit.Assert.assertEquals
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(application = RobolectricApplication::class)
class InstancesTest {
@org.junit.Test
fun setSameTypeInstances() {
val instanceOne: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
val instanceTwo: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
val nestedInstanceOne: NestedInstantiableOne = InstancesFactory.createNestedInstantiableOne()
instanceOne.setStringValue(INSTANCE_ONE_STRING)
instanceTwo.setStringValue(INSTANCE_TWO_STRING)

nestedInstanceOne.setSameTypeInstances(instanceOne, instanceTwo)

val resultOne: SimpleInstantiableOne = nestedInstanceOne.getInstanceOne()
val resultTwo: SimpleInstantiableOne = nestedInstanceOne.getInstanceTwo()
assertEquals(INSTANCE_ONE_STRING, resultOne.getStringValue())
assertEquals(INSTANCE_TWO_STRING, resultTwo.getStringValue())
}

@org.junit.Test
fun setSameTypeInstances_identicalInstances() {
val instanceOne: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
val nestedInstanceOne: NestedInstantiableOne = InstancesFactory.createNestedInstantiableOne()
instanceOne.setStringValue(INSTANCE_ONE_STRING)

nestedInstanceOne.setSameTypeInstances(instanceOne, instanceOne)

val resultOne: SimpleInstantiableOne = nestedInstanceOne.getInstanceOne()
val resultTwo: SimpleInstantiableOne = nestedInstanceOne.getInstanceTwo()
assertEquals(INSTANCE_ONE_STRING, resultOne.getStringValue())
assertEquals(INSTANCE_ONE_STRING, resultTwo.getStringValue())
}

@org.junit.Test
fun setMultipleTypeInstances() {
val simpleInstanceOne: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
val otherInstanceOne: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
val simpleInstanceTwo: SimpleInstantiableTwo = InstancesFactory.createSimpleInstantiableTwo()
val nested: NestedInstantiableOne = InstancesFactory.createNestedInstantiableOne()
val nestedInstantiableTwo: NestedInstantiableTwo = InstancesFactory.createNestedInstantiableTwo()
simpleInstanceOne.setStringValue(INSTANCE_ONE_STRING)
simpleInstanceTwo.setStringValue(INSTANCE_TWO_STRING)
otherInstanceOne.setStringValue(INSTANCE_OTHER_STRING)
nested.setSameTypeInstances(simpleInstanceOne, otherInstanceOne)

nestedInstantiableTwo.setMultipleTypeInstances(simpleInstanceOne, simpleInstanceTwo, nested)

assertEquals(INSTANCE_ONE_STRING, nestedInstantiableTwo.getInstantiableOne().getStringValue())
assertEquals(INSTANCE_TWO_STRING, nestedInstantiableTwo.getInstantiableTwo().getStringValue())
val nestedInstantiable: NestedInstantiableOne = nestedInstantiableTwo.getNestedInstantiable()
assertEquals(INSTANCE_ONE_STRING, nestedInstantiable.getInstanceOne().getStringValue())
assertEquals(INSTANCE_OTHER_STRING, nestedInstantiable.getInstanceTwo().getStringValue())
}

@org.junit.Test
fun setSelfInstantiable() {
val simpleOne: SimpleInstantiableOne = InstancesFactory.createSimpleInstantiableOne()
simpleOne.setStringValue(INSTANCE_ONE_STRING)

val simpleTwo: SimpleInstantiableTwo = InstancesFactory.createSimpleInstantiableTwo()
simpleTwo.setStringValue(INSTANCE_TWO_STRING)

val nestedOne: NestedInstantiableOne = InstancesFactory.createNestedInstantiableOne()
val nestedTwo: NestedInstantiableTwo = InstancesFactory.createNestedInstantiableTwo()
nestedTwo.setMultipleTypeInstances(simpleOne, simpleTwo, nestedOne)
nestedTwo.setSelfInstantiable(nestedTwo)

val selfInstantiable: NestedInstantiableTwo = nestedTwo.getSelfInstantiable()
assertEquals(INSTANCE_ONE_STRING, selfInstantiable.getInstantiableOne().getStringValue())
assertEquals(INSTANCE_TWO_STRING, selfInstantiable.getInstantiableTwo().getStringValue())
}

companion object {
private const val INSTANCE_ONE_STRING: String = "simpleInstanceOne"
private const val INSTANCE_TWO_STRING: String = "simpleInstanceTwo"
private const val INSTANCE_OTHER_STRING: String = "simpleInstanceOther"
}
}
Loading

0 comments on commit e8dac33

Please sign in to comment.