From e2419afb5638c600249c14d8a3ec18cb4eb2b8ea Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 30 Jun 2022 17:26:58 +0100 Subject: [PATCH 01/25] re-writing accessors transformer using the new AGP API --- dependencies.list | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle-plugin/build.gradle | 8 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../main/groovy/io/realm/gradle/Realm.groovy | 159 ------------- .../src/main/kotlin/io/realm/gradle/Realm.kt | 209 ++++++++++++++++++ .../io/realm/gradle/SimpleAGPVersion.kt | 75 +++++++ gradle-plugin/src/main/templates/Version.java | 5 - gradle-plugin/src/main/templates/Version.kt | 5 + gradle/wrapper/gradle-wrapper.properties | 2 +- library-benchmarks/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-transformer/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../io/realm/transformer/ManagedClassPool.kt | 27 +-- .../io/realm/transformer/RealmTransformer.kt | 163 ++++++++------ .../realm/transformer/build/BuildTemplate.kt | 127 +++++++---- .../io/realm/transformer/build/FullBuild.kt | 58 ++--- .../transformer/build/IncrementalBuild.kt | 151 ------------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm/kotlin-extensions/build.gradle | 7 +- .../realm-annotations-processor/build.gradle | 4 +- realm/realm-library/build.gradle | 29 +-- 25 files changed, 550 insertions(+), 511 deletions(-) delete mode 100644 gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy create mode 100644 gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt create mode 100644 gradle-plugin/src/main/kotlin/io/realm/gradle/SimpleAGPVersion.kt delete mode 100644 gradle-plugin/src/main/templates/Version.java create mode 100644 gradle-plugin/src/main/templates/Version.kt delete mode 100644 realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt diff --git a/dependencies.list b/dependencies.list index 2acac6e58f..bec3df0c61 100644 --- a/dependencies.list +++ b/dependencies.list @@ -7,10 +7,10 @@ REALM_CORE=11.14.0 MONGODB_REALM_SERVER=2022-01-17 # Common Android settings across projects -GRADLE_BUILD_TOOLS=7.1.0 +GRADLE_BUILD_TOOLS=7.4.0-alpha05 ANDROID_BUILD_TOOLS=30.0.3 -KOTLIN=1.5.31 -KOTLIN_COROUTINES=1.5.2 +KOTLIN=1.6.20 +KOTLIN_COROUTINES=1.6.20 # Common classpath dependencies gradle=7.3.3 diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..41dfb87909 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index cb66eaf55d..8f2228326a 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -78,8 +78,8 @@ dependencies { } task generateVersionClass(type: Copy) { - from 'src/main/templates/Version.java' - into 'build/generated-src/main/java/io/realm' + from 'src/main/templates/Version.kt' + into 'build/generated-src/main/kotlin/io/realm' filter(ReplaceTokens, tokens: [version: version]) outputs.upToDateWhen { false } } @@ -88,12 +88,12 @@ task generateVersionClass(type: Copy) { sourceSets { main { java { - srcDir 'build/generated-src/main/java' + srcDirs += ['build/generated-src/main/kotlin', 'src/main/kotlin'] } } } -compileJava.dependsOn generateVersionClass +compileKotlin.dependsOn generateVersionClass apply from: "${rootDir}/../mavencentral-publications.gradle" apply from: "${rootDir}/../mavencentral-publish.gradle" diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..41dfb87909 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy b/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy deleted file mode 100644 index 5675799f91..0000000000 --- a/gradle-plugin/src/main/groovy/io/realm/gradle/Realm.groovy +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2016 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.gradle - -import com.android.build.gradle.AppPlugin -import com.android.build.gradle.LibraryPlugin -import com.neenbedankt.gradle.androidapt.AndroidAptPlugin -import io.realm.transformer.RealmTransformer -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.UnknownConfigurationException - -class Realm implements Plugin { - - @Override - void apply(Project project) { - // Make sure the project is either an Android application or library - def isAndroidApp = project.plugins.withType(AppPlugin) - def isAndroidLib = project.plugins.withType(LibraryPlugin) - if (!isAndroidApp && !isAndroidLib) { - throw new GradleException("'com.android.application' or 'com.android.library' plugin required.") - } - - if (!isTransformAvailable()) { - throw new GradleException('Realm gradle plugin only supports android gradle plugin 1.5.0 or later.') - } - - def syncEnabledDefault = false - def usesAptPlugin = project.plugins.findPlugin('com.neenbedankt.android-apt') != null - def isKotlinProject = project.plugins.findPlugin('kotlin-android') != null || project.plugins.findPlugin("kotlin-multiplatform") != null - def useKotlinExtensionsDefault = isKotlinProject - def hasAnnotationProcessorConfiguration = project.getConfigurations().findByName('annotationProcessor') != null - // TODO add a parameter in 'realm' block if this should be specified by users - def preferAptOnKotlinProject = false - def dependencyConfigurationName = getDependencyConfigurationName(project) - def extension = project.extensions.create('realm', RealmPluginExtension) - extension.addPropertyListener(RealmPluginExtension.KEY_KOTLIN_EXTENSIONS_ENABLED, new RealmPluginExtension.PropertyChangedListener() { - @Override - void onChange(Boolean checked) { - setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) - } - }) - extension.addPropertyListener(RealmPluginExtension.KEY_SYNC_ENABLED, new RealmPluginExtension.PropertyChangedListener() { - @Override - void onChange(Boolean checked) { - setDependencies(project, dependencyConfigurationName, extension.syncEnabled, extension.kotlinExtensionsEnabled) - } - }) - extension.kotlinExtensionsEnabled = useKotlinExtensionsDefault - - if (shouldApplyAndroidAptPlugin(usesAptPlugin, isKotlinProject, - hasAnnotationProcessorConfiguration, preferAptOnKotlinProject)) { - project.plugins.apply(AndroidAptPlugin) - usesAptPlugin = true - } - - // Register transformer during the evaluations phase, so the Android Plugin - // is able to pick it up. The project is passed in in order to gather various - // metadata in `project.afterEvaluate { }`, but the transformer is not allowed - // to store a reference to it if we want to support the Gradle Configuration Cache. - project.android.registerTransform(new RealmTransformer(project)) - - project.dependencies.add(dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}") - if (usesAptPlugin) { - project.dependencies.add("apt", "io.realm:realm-annotations-processor:${Version.VERSION}") - project.dependencies.add("androidTestApt", "io.realm:realm-annotations-processor:${Version.VERSION}") - } else if (isKotlinProject && !preferAptOnKotlinProject) { - project.dependencies.add("kapt", "io.realm:realm-annotations-processor:${Version.VERSION}") - project.dependencies.add("kaptAndroidTest", "io.realm:realm-annotations-processor:${Version.VERSION}") - } else { - assert hasAnnotationProcessorConfiguration - project.dependencies.add("annotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") - project.dependencies.add("androidTestAnnotationProcessor", "io.realm:realm-annotations-processor:${Version.VERSION}") - } - } - - private static boolean isTransformAvailable() { - try { - Class.forName('com.android.build.api.transform.Transform') - return true - } catch (Exception ignored) { - return false - } - } - - private static String getDependencyConfigurationName(Project project) { - /* - * Dependency configuration name for android gradle plugin 3.0.0-*. - * We need to use 'api' instead of 'implementation' since user's model class - * might be using Realm's classes and annotations. - */ - def newDependencyName = "api" - def oldDependencyName = "compile" - try { - project.getConfigurations().getByName(newDependencyName) - return newDependencyName - } catch (UnknownConfigurationException ignored) { - oldDependencyName - } - } - - private static boolean shouldApplyAndroidAptPlugin(boolean usesAptPlugin, boolean isKotlinProject, - boolean hasAnnotationProcessorConfiguration, - boolean preferAptOnKotlinProject) { - if (usesAptPlugin) { - // for any projects that uses android-apt plugin already. No need to apply it twice. - return false - } - if (isKotlinProject) { - // for any Kotlin projects where user did not apply 'android-apt' plugin manually. - return preferAptOnKotlinProject && !hasAnnotationProcessorConfiguration - } - // for any Java Projects where user did not apply 'android-apt' plugin manually. - return !hasAnnotationProcessorConfiguration - } - - // This will setup the required dependencies. - // Due to how Gradle works, we have no choice but to run this code every time any of the parameters - // in the Realm extension is changed. - private static void setDependencies(Project project, String dependencyConfigurationName, boolean syncEnabled, boolean kotlinExtensionsEnabled) { - // remove libraries first - def iterator = project.getConfigurations().getByName(dependencyConfigurationName).getDependencies().iterator() - while (iterator.hasNext()) { - def item = iterator.next() - if (item.group == 'io.realm') { - if (item.name.startsWith('realm-android-library')) { - iterator.remove() - } - if (item.name.startsWith('realm-android-kotlin-extensions')) { - iterator.remove() - } - } - } - - // then add again - def syncArtifactName = "realm-android-library${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${syncArtifactName}:${Version.VERSION}") - - if (kotlinExtensionsEnabled) { - def kotlinExtArtifactName = "realm-android-kotlin-extensions${syncEnabled ? '-object-server' : ''}" - project.dependencies.add(dependencyConfigurationName, "io.realm:${kotlinExtArtifactName}:${Version.VERSION}") - } - } -} diff --git a/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt new file mode 100644 index 0000000000..0e40b869be --- /dev/null +++ b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt @@ -0,0 +1,209 @@ +package io.realm.gradle + +import com.android.build.gradle.AppPlugin +import com.android.build.gradle.LibraryPlugin +import com.neenbedankt.gradle.androidapt.AndroidAptPlugin +import io.realm.transformer.RealmTransformer +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.UnknownConfigurationException +import org.gradle.api.plugins.PluginCollection +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +val logger: Logger = LoggerFactory.getLogger("realm-logger") + +// TODO Run a Task or Visitor to collect runtimeClassPath, then serialize it +// Run another task that depends on the output of th first task inorder to desrialize the ClassPool and process each class appart +open class Realm : Plugin { + override fun apply(project: Project) { + // Make sure the project is either an Android application or library + val isAndroidApp: PluginCollection = + project.plugins.withType(AppPlugin::class.java) + val isAndroidLib: PluginCollection = + project.plugins.withType(LibraryPlugin::class.java) + + if (isAndroidApp.isEmpty() && isAndroidLib.isEmpty()) { + throw GradleException("'com.android.application' or 'com.android.library' plugin required.") + } + + checkCompatibleAGPVersion() + + var usesAptPlugin: Boolean = + project.plugins.findPlugin("com.neenbedankt.android-apt") != null + val isKotlinProject: Boolean = + project.plugins.findPlugin("kotlin-android") != null || project.plugins.findPlugin("kotlin-multiplatform") != null + val hasAnnotationProcessorConfiguration = + project.getConfigurations().findByName("annotationProcessor") != null + // TODO add a parameter in 'realm' block if this should be specified by users + val preferAptOnKotlinProject = false + val dependencyConfigurationName: String = getDependencyConfigurationName(project) + val extension = project.extensions.create("realm", RealmPluginExtension::class.java) + + extension.addPropertyListener( + RealmPluginExtension.KEY_KOTLIN_EXTENSIONS_ENABLED + ) { + setDependencies( + project, + dependencyConfigurationName, + extension.isSyncEnabled, + extension.isKotlinExtensionsEnabled + ) + } + extension.addPropertyListener( + RealmPluginExtension.KEY_SYNC_ENABLED + ) { + setDependencies( + project, + dependencyConfigurationName, + extension.isSyncEnabled, + extension.isKotlinExtensionsEnabled + ) + } + extension.isKotlinExtensionsEnabled = isKotlinProject + + if (shouldApplyAndroidAptPlugin( + usesAptPlugin, + isKotlinProject, + hasAnnotationProcessorConfiguration, + preferAptOnKotlinProject + ) + ) { + project.plugins.apply(AndroidAptPlugin::class.java) + usesAptPlugin = true + } + + RealmTransformer.register(project) + + project.dependencies.add( + dependencyConfigurationName, + "io.realm:realm-annotations:${Version.VERSION}" + ) + if (usesAptPlugin) { + project.dependencies.add( + "apt", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + project.dependencies.add( + "androidTestApt", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + } else if (isKotlinProject && !preferAptOnKotlinProject) { + project.dependencies.add( + "kapt", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + project.dependencies.add( + "kaptAndroidTest", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + } else { + assert(hasAnnotationProcessorConfiguration) + project.dependencies.add( + "annotationProcessor", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + project.dependencies.add( + "androidTestAnnotationProcessor", + "io.realm:realm-annotations-processor:${Version.VERSION}" + ) + } + } + + companion object { + + private fun checkCompatibleAGPVersion() { + val version = SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION + return when { + version >= SimpleAGPVersion(7, 4) -> { + // minimum version compatible with https://developer.android.com/studio/releases/gradle-plugin-api-updates#support_for_transformations_based_on_whole_program_analysis + logger.debug("Realm Plugin used with AGP version: ${version.major}.${version.minor}.") + } + version >= SimpleAGPVersion(4, 2) -> { + throw GradleException("Realm Plugin used with incompatible AGP version: ${version.major}.${version.minor}. You should consider using a Realm-Java v10.11.0 or lower. Or migrate your project to use AGP 7.4 or newer.") + } + else -> { + throw GradleException("Android Gradle Plugin $version is not supported") + } + } + } + + private fun getDependencyConfigurationName(project: Project): String { + /* + * Dependency configuration name for android gradle plugin 3.0.0-*. + * We need to use 'api' instead of 'implementation' since user's model class + * might be using Realm's classes and annotations. + */ + val newDependencyName = "api" + val oldDependencyName = "compile" + return try { + project.configurations.getByName(newDependencyName) + newDependencyName + } catch (ignored: UnknownConfigurationException) { + oldDependencyName + } + } + + private fun shouldApplyAndroidAptPlugin( + usesAptPlugin: Boolean, + isKotlinProject: Boolean, + hasAnnotationProcessorConfiguration: Boolean, + preferAptOnKotlinProject: Boolean + ): Boolean { + if (usesAptPlugin) { + // for any projects that uses android-apt plugin already. No need to apply it twice. + return false + } + return if (isKotlinProject) { + // for any Kotlin projects where user did not apply 'android-apt' plugin manually. + preferAptOnKotlinProject && !hasAnnotationProcessorConfiguration + } else !hasAnnotationProcessorConfiguration + + // for any Java Projects where user did not apply 'android-apt' plugin manually. + } + + // This will setup the required dependencies. + // Due to how Gradle works, we have no choice but to run this code every time any of the parameters + // in the Realm extension is changed. + private fun setDependencies( + project: Project, + dependencyConfigurationName: String, + syncEnabled: Boolean, + kotlinExtensionsEnabled: Boolean + ) { + // remove libraries first + val iterator = + project.configurations.getByName(dependencyConfigurationName).dependencies.iterator() + while (iterator.hasNext()) { + val item = iterator.next() + if (item.group == "io.realm") { + if (item.name.startsWith("realm-android-library")) { + iterator.remove() + } + if (item.name.startsWith("realm-android-kotlin-extensions")) { + iterator.remove() + } + } + } + + // then add again + val syncArtifactName = + "realm-android-library${if (syncEnabled) "-object-server" else ""}" + project.dependencies.add( + dependencyConfigurationName, + "io.realm:${syncArtifactName}:${Version.VERSION}" + ) + + if (kotlinExtensionsEnabled) { + val kotlinExtArtifactName = + "realm-android-kotlin-extensions${if (syncEnabled) "-object-server" else ""}" + project.dependencies.add( + dependencyConfigurationName, + "io.realm:${kotlinExtArtifactName}:${Version.VERSION}" + ) + } + } + + } +} \ No newline at end of file diff --git a/gradle-plugin/src/main/kotlin/io/realm/gradle/SimpleAGPVersion.kt b/gradle-plugin/src/main/kotlin/io/realm/gradle/SimpleAGPVersion.kt new file mode 100644 index 0000000000..32c1f8ae43 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/io/realm/gradle/SimpleAGPVersion.kt @@ -0,0 +1,75 @@ +package io.realm.gradle + +/* + * Copyright (C) 2022 The Dagger 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 + * + * 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. + */ + +/** + * Simple Android Gradle Plugin version class since there is no public API one. b/175816217 + */ +data class SimpleAGPVersion( + val major: Int, + val minor: Int, +) : Comparable { + + override fun compareTo(other: SimpleAGPVersion): Int { + return compareValuesBy( + this, + other, + compareBy(SimpleAGPVersion::major).thenBy(SimpleAGPVersion::minor) + ) { it } + } + + companion object { + + // TODO: Migrate to AndroidPluginVersion once it is available (b/175816217) + val ANDROID_GRADLE_PLUGIN_VERSION by lazy { + val clazz = + findClass("com.android.Version") + ?: findClass("com.android.builder.model.Version") + if (clazz != null) { + return@lazy parse( + clazz.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String + ) + } + error( + "Unable to obtain AGP version. It is likely that the AGP version being used is too old." + ) + } + + private fun parse(version: String?) = + tryParse(version) ?: error("Unable to parse AGP version: $version") + + private fun tryParse(version: String?): SimpleAGPVersion? { + if (version == null) { + return null + } + + val parts = version.split('.') + if (parts.size == 1) { + return SimpleAGPVersion(parts[0].toInt(), 0) + } + + return SimpleAGPVersion(parts[0].toInt(), parts[1].toInt()) + } + + private fun findClass(fqName: String) = + try { + Class.forName(fqName) + } catch (ex: ClassNotFoundException) { + null + } + } +} diff --git a/gradle-plugin/src/main/templates/Version.java b/gradle-plugin/src/main/templates/Version.java deleted file mode 100644 index 330285c5a5..0000000000 --- a/gradle-plugin/src/main/templates/Version.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.realm.gradle; - -public class Version { - public static final String VERSION = "@version@"; -} diff --git a/gradle-plugin/src/main/templates/Version.kt b/gradle-plugin/src/main/templates/Version.kt new file mode 100644 index 0000000000..d11046b7d2 --- /dev/null +++ b/gradle-plugin/src/main/templates/Version.kt @@ -0,0 +1,5 @@ +package io.realm.gradle; + +object Version { + const val VERSION = "@version@" +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..fcfb29d920 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-benchmarks/build.gradle b/library-benchmarks/build.gradle index 313e9729b7..95bbdf68e3 100644 --- a/library-benchmarks/build.gradle +++ b/library-benchmarks/build.gradle @@ -52,12 +52,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' } } diff --git a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..fcfb29d920 100644 --- a/library-benchmarks/gradle/wrapper/gradle-wrapper.properties +++ b/library-benchmarks/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..41dfb87909 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..fcfb29d920 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index c360c53a73..43dfd247dd 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -27,8 +27,8 @@ properties.load(new FileInputStream("${projectDir}/../dependencies.list")) def coreVersion = properties.getProperty('REALM_CORE') -sourceCompatibility = '1.8' -targetCompatibility = '1.8' +sourceCompatibility = '11' +targetCompatibility = '11' repositories { mavenLocal() @@ -100,6 +100,6 @@ java { compileKotlin { kotlinOptions { - freeCompilerArgs = ["-Xinline-classes"] + freeCompilerArgs = ["-Xinline-classes", "-Xjvm-default=enable"] } } diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..fcfb29d920 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt index ce337d9a53..aa4906cd4e 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ManagedClassPool.kt @@ -16,18 +16,21 @@ package io.realm.transformer -import com.android.build.api.transform.TransformInput import javassist.ClassPath import javassist.ClassPool +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.provider.ListProperty import java.io.Closeable +import java.io.File /** * This class is a wrapper around JavaAssists {@code ClassPool} class that allows for correct cleanup * of the resources used. */ -class ManagedClassPool(inputs: Collection, referencedInputs: Collection) : ClassPool(), Closeable { +class ManagedClassPool(inputs: ListProperty, referencedInputs: ConfigurableFileCollection) : ClassPool(), Closeable { - val pathElements: ArrayList = arrayListOf() + private val pathElements: ArrayList = arrayListOf() /** * Constructor for creating and populating the JavAssist class pool. @@ -42,24 +45,14 @@ class ManagedClassPool(inputs: Collection, referencedInputs: Col // will use a cached object and all the classes will be frozen. appendSystemPath() - inputs.forEach{ - it.directoryInputs.forEach { - pathElements.add(appendClassPath(it.file.absolutePath)) - } - - it.jarInputs.forEach { - pathElements.add(appendClassPath(it.file.absolutePath)) + inputs.get().forEach{ directory: Directory -> + directory.asFile.walk().filter(File::isDirectory).forEach { + pathElements.add(appendClassPath(it.absolutePath)) } } referencedInputs.forEach { - it.directoryInputs.forEach { - pathElements.add(appendClassPath(it.file.absolutePath)) - } - - it.jarInputs.forEach { - pathElements.add(appendClassPath(it.file.absolutePath)) - } + pathElements.add(appendClassPath(it.absolutePath)) } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index c85acccbf1..b3d81ff7b8 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -16,17 +16,29 @@ package io.realm.transformer -import com.android.build.api.transform.* +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.gradle.internal.publishing.AndroidArtifacts import io.realm.analytics.RealmAnalytics import io.realm.transformer.build.BuildTemplate import io.realm.transformer.build.FullBuild -import io.realm.transformer.build.IncrementalBuild import io.realm.transformer.ext.getBootClasspath -import javassist.CtClass +import org.gradle.api.DefaultTask import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFiles +import org.gradle.api.tasks.TaskAction import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.BufferedOutputStream import java.io.File +import java.io.FileOutputStream +import java.util.jar.JarOutputStream // Package level logger val logger: Logger = LoggerFactory.getLogger("realm-logger") @@ -43,56 +55,60 @@ data class ProjectMetaData( /** * This class implements the Transform API provided by the Android Gradle plugin. */ -class RealmTransformer(project: Project) : Transform() { - private val logger: Logger = LoggerFactory.getLogger("realm-logger") - private lateinit var metadata: ProjectMetaData +class RealmTransformer(project: Project, + private val inputs: ListProperty, + private val allJars: ListProperty, + private val referencedInputs: ConfigurableFileCollection, + private val output: RegularFileProperty) { + private var metadata: ProjectMetaData private var analytics: RealmAnalytics? = null init { // Fetch project metadata when registering the transformer, but as some of the properties // we need to read might not be initialized yet (e.g. the Android extension), we need // to wait until after the build files have been evaluated. - project.afterEvaluate { - metadata = ProjectMetaData( - // Plugin requirements - project.gradle.startParameter.isOffline, - project.getBootClasspath() - ) - - try { - this.analytics = RealmAnalytics() - this.analytics!!.calculateAnalyticsData(project) - } catch (e: Exception) { - // Analytics should never crash the build. - logger.debug("Could not calculate Realm analytics data:\n$e" ) - } - } - } - override fun getName(): String { - return "RealmTransformer" - } - - override fun getInputTypes(): Set { - return setOf(QualifiedContent.DefaultContentType.CLASSES) - } + metadata = ProjectMetaData( + // Plugin requirements + project.gradle.startParameter.isOffline, + project.getBootClasspath() + ) - override fun isIncremental(): Boolean { - return true - } + try { + this.analytics = RealmAnalytics() + this.analytics!!.calculateAnalyticsData(project) - override fun getScopes(): MutableSet { - return mutableSetOf(QualifiedContent.Scope.PROJECT) + } catch (e: Exception) { + // Analytics should never crash the build. + logger.debug("Could not calculate Realm analytics data:\n$e") + } + transform() } - override fun getReferencedScopes(): MutableSet { - // Scope.PROJECT_LOCAL_DEPS and Scope.SUB_PROJECTS_LOCAL_DEPS is only for compatibility with AGP 1.x, 2.x - return mutableSetOf( - QualifiedContent.Scope.EXTERNAL_LIBRARIES, - QualifiedContent.Scope.PROJECT_LOCAL_DEPS, - QualifiedContent.Scope.SUB_PROJECTS, - QualifiedContent.Scope.SUB_PROJECTS_LOCAL_DEPS, - QualifiedContent.Scope.TESTED_CODE - ) + companion object { + + fun register(project: Project) { + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + androidComponents.onVariants { variant -> + + val taskProvider = + project.tasks.register("${variant.name}RealmAccessorsTransformer", ModifyClassesTask::class.java) { + it.fullRuntimeClasspath.setFrom(variant.runtimeConfiguration.incoming.artifactView { c-> + c.attributes.attribute( + AndroidArtifacts.ARTIFACT_TYPE, + AndroidArtifacts.ArtifactType.CLASSES_JAR.type + ) + }.files) + } + variant.artifacts.forScope(com.android.build.api.variant.ScopedArtifacts.Scope.PROJECT) + .use(taskProvider) + .toTransform( + com.android.build.api.artifact.ScopedArtifact.CLASSES, + ModifyClassesTask::allJars, + ModifyClassesTask::allDirectories, + ModifyClassesTask::output + ) + } + } } /** @@ -103,34 +119,30 @@ class RealmTransformer(project: Project) : Transform() { * incremental build. In a full build, we can use text matching to go from a proxy class * to the model class. Something we cannot do when building incrementally. In that case * we have to deduce all information from the class at hand. - * - * @param context - * @param inputs - * @param referencedInputs - * @param outputProvider - * @param isIncremental - * @throws IOException - * @throws TransformException - * @throws InterruptedException */ - override fun transform(context: Context?, inputs: MutableCollection?, - referencedInputs: Collection?, - outputProvider: TransformOutputProvider?, isIncremental: Boolean) { - + private fun transform() { val timer = Stopwatch() timer.start("Realm Transform time") - val build: BuildTemplate = if (isIncremental) IncrementalBuild(metadata, outputProvider!!, this) - else FullBuild(metadata, outputProvider!!, this) + val jarOutput = JarOutputStream( + BufferedOutputStream( + FileOutputStream( + output.get().asFile + ) + ) + ) - build.prepareOutputClasses(inputs!!) + logger.debug("output is=${output.get().asFile.path} exists=${output.get().asFile.exists()}") + val build: BuildTemplate = FullBuild(metadata, allJars, jarOutput, this) + + build.prepareOutputClasses(inputs) timer.splitTime("Prepare output classes") if (build.hasNoOutput()) { // Abort transform as quickly as possible if no files where found for processing. - exitTransform(emptySet(), emptySet(), timer) + exitTransform(timer) return } - build.prepareReferencedClasses(referencedInputs!!) + build.prepareReferencedClasses(referencedInputs) timer.splitTime("Prepare referenced classes") build.markMediatorsAsTransformed() timer.splitTime("Mark mediators as transformed") @@ -138,13 +150,36 @@ class RealmTransformer(project: Project) : Transform() { timer.splitTime("Transform model classes") build.transformDirectAccessToModelFields() timer.splitTime("Transform references to model fields") + build.copyProcessedClasses() + timer.splitTime("Copy processed classes") build.copyResourceFiles() - timer.splitTime("Copy resource files") - exitTransform(inputs, build.getOutputModelClasses(), timer) + timer.splitTime("Copy jar files") + jarOutput.close() + exitTransform(timer) } - private fun exitTransform(inputs: Collection, outputModelClasses: Set, timer: Stopwatch) { + private fun exitTransform(timer: Stopwatch) { timer.stop() analytics?.execute() } } + +abstract class ModifyClassesTask: DefaultTask() { + @get:Classpath + abstract val fullRuntimeClasspath : ConfigurableFileCollection + + @get:InputFiles + abstract val allJars: ListProperty + + @get:InputFiles + abstract val allDirectories: ListProperty + + @get:OutputFiles + abstract val output: RegularFileProperty + + @TaskAction + fun taskAction() { + RealmTransformer(project, allDirectories, allJars, fullRuntimeClasspath, output) + } +} + diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index ef66df5139..9ed24bbd7b 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -15,17 +15,20 @@ */ package io.realm.transformer.build -import com.android.build.api.transform.DirectoryInput -import com.android.build.api.transform.Format -import com.android.build.api.transform.JarInput -import com.android.build.api.transform.Transform -import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformOutputProvider -import com.google.common.io.Files import io.realm.transformer.* import javassist.ClassPool import javassist.CtClass +import com.google.common.io.Files +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty import java.io.File +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream import java.util.regex.Pattern public const val DOT_CLASS = ".class" @@ -35,18 +38,19 @@ public const val DOT_JAR = ".jar" * Abstract class defining the structure of doing different types of builds. * */ -abstract class BuildTemplate(val metadata: ProjectMetaData, val outputProvider: TransformOutputProvider, val transform: Transform) { +abstract class BuildTemplate(private val metadata: ProjectMetaData, private val allJars: ListProperty, protected val outputProvider: JarOutputStream, val transform: RealmTransformer) { - protected lateinit var inputs: MutableCollection + protected lateinit var inputs: ListProperty protected lateinit var classPool: ManagedClassPool protected val outputClassNames: MutableSet = hashSetOf() protected val outputReferencedClassNames: MutableSet = hashSetOf() protected val outputModelClasses: ArrayList = arrayListOf() + protected val processedClasses = mutableMapOf() /** * Find all the class names available for transforms as well as all referenced classes. */ - abstract fun prepareOutputClasses(inputs: MutableCollection) + abstract fun prepareOutputClasses(inputs: ListProperty) /** * Helper method for going through all `TransformInput` and sort classes into buckets of @@ -58,9 +62,11 @@ abstract class BuildTemplate(val metadata: ProjectMetaData, val outputProvider: * @param jaFiles the set of files found in jar files. These will never be transformed. This should * already be done when creating the jar file. */ - protected abstract fun categorizeClassNames(inputs: Collection, - directoryFiles: MutableSet, - referencedFiles: MutableSet) + protected abstract fun categorizeClassNames(inputs: ListProperty, + directoryFiles: MutableSet) + + protected abstract fun categorizeClassNames(referencedInputs: ConfigurableFileCollection, + jarFiles: MutableSet) /** * Returns `true` if this build contains no relevant classes to transform. @@ -70,8 +76,8 @@ abstract class BuildTemplate(val metadata: ProjectMetaData, val outputProvider: } - fun prepareReferencedClasses(referencedInputs: Collection) { - categorizeClassNames(referencedInputs, outputReferencedClassNames, outputReferencedClassNames) // referenced files + fun prepareReferencedClasses(referencedInputs: ConfigurableFileCollection) { + categorizeClassNames(referencedInputs, outputReferencedClassNames) // referenced files // Create and populate the Javassist class pool this.classPool = ManagedClassPool(inputs, referencedInputs) @@ -97,6 +103,7 @@ abstract class BuildTemplate(val metadata: ProjectMetaData, val outputProvider: logger.debug("Proxy Mediator Classes: ${proxyMediatorClasses.joinToString(",") { it.name }}") proxyMediatorClasses.forEach { BytecodeModifier.overrideTransformedMarker(it) + processedClasses[it.name] = it } } @@ -107,53 +114,77 @@ abstract class BuildTemplate(val metadata: ProjectMetaData, val outputProvider: BytecodeModifier.addRealmAccessors(it) BytecodeModifier.addRealmProxyInterface(it, classPool) BytecodeModifier.callInjectObjectContextFromConstructors(it) + + processedClasses[it.name] = it } } abstract fun transformDirectAccessToModelFields() - fun copyResourceFiles() { - copyResourceFiles(inputs) - classPool.close(); - } + fun copyProcessedClasses() { + for ((fqname: String, clazz: CtClass) in processedClasses) { + logger.debug("Adding class: $fqname into output Jar") - private fun copyResourceFiles(inputs: MutableCollection) { - inputs.forEach { input: TransformInput -> - input.directoryInputs.forEach { directory: DirectoryInput -> - val dirPath: String = directory.file.absolutePath - directory.file.walkTopDown().forEach { file: File -> - if (file.isFile) { - if (!file.absolutePath.endsWith(DOT_CLASS)) { - logger.debug(" Copying resource file: $file") - val dest = File(getOutputFile(outputProvider, Format.DIRECTORY), file.absolutePath.substring(dirPath.length)) - dest.parentFile.mkdirs() - Files.copy(file, dest) - } - } - } - } + outputProvider.putNextEntry(JarEntry("${fqname.replace('.', '/')}.class")) + outputProvider.write(clazz.toBytecode()) + outputProvider.closeEntry() + } + } - input.jarInputs.forEach { jar: JarInput -> - logger.debug("Found JAR file: ${jar.file.absolutePath}") - val dirPath: String = jar.file.absolutePath - jar.file.walkTopDown().forEach { file: File -> - if (file.isFile) { - if (file.absolutePath.endsWith(DOT_JAR)) { - logger.debug(" Copying jar file: $file") - val dest = File(getOutputFile(outputProvider, Format.JAR), file.absolutePath.substring(dirPath.length)) - dest.parentFile.mkdirs() - Files.copy(file, dest) - } - } + fun copyResourceFiles() { + allJars.get().forEach { file -> + println("TRANSFORMER>>JarFile : " + file.asFile.absolutePath) + val jarFile = JarFile(file.asFile) + for (jarEntry: JarEntry in jarFile.entries()) { + println("TRANSFORMER>>Adding from jar ${jarEntry.name}") + outputProvider.putNextEntry(JarEntry(jarEntry.name)) + jarFile.getInputStream(jarEntry).use { + outputProvider.write(it.readBytes()) } + outputProvider.closeEntry() } + jarFile.close() } - } - protected fun getOutputFile(outputProvider: TransformOutputProvider, format: Format): File { - return outputProvider.getContentLocation("realm", transform.inputTypes, transform.scopes, format) + classPool.close() } +// private fun copyResourceFiles(inputs: ListProperty) { +// inputs.get().forEach { input: Directory -> +// input.asFile.walkTopDown().forEach { directory: File -> +// val dirPath: String = directory.absolutePath +// directory.walkTopDown().forEach { file: File -> +// if (file.isFile) { +// if (!file.absolutePath.endsWith(DOT_CLASS)) { +// val dest = File(outputProvider.asFile.get(), file.absolutePath.substring(dirPath.length)) +// logger.debug(" Copying resource file from = $file into = $dest mkdir is = ${dest.parentFile.mkdirs()}") +// Files.copy(file, dest) +// } +// } +// } +// } +// +//// input.jarInputs.forEach { jar: JarInput -> +//// logger.debug("Found JAR file: ${jar.file.absolutePath}") +//// val dirPath: String = jar.file.absolutePath +//// jar.file.walkTopDown().forEach { file: File -> +//// if (file.isFile) { +//// if (file.absolutePath.endsWith(DOT_JAR)) { +//// logger.debug(" Copying jar file: $file") +////// val dest = File(getOutputFile(outputProvider, Format.JAR), file.absolutePath.substring(dirPath.length)) +////// dest.parentFile.mkdirs() +////// Files.copy(file, dest) +//// } +//// } +//// } +//// } +// } +// } + +// protected fun getOutputFile(outputProvider: TransformOutputProvider, format: Format): File { +// return outputProvider.getContentLocation("realm", transform.inputTypes, transform.scopes, format) +// } + /** * There is no official way to get the path to android.jar for transform. * See https://code.google.com/p/android/issues/detail?id=209426 diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt index d317a69e2b..8bda056559 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/FullBuild.kt @@ -16,9 +16,6 @@ package io.realm.transformer.build -import com.android.build.api.transform.Format -import com.android.build.api.transform.TransformInput -import com.android.build.api.transform.TransformOutputProvider import io.realm.transformer.BytecodeModifier import io.realm.transformer.ProjectMetaData import io.realm.transformer.RealmTransformer @@ -26,41 +23,46 @@ import io.realm.transformer.ext.safeSubtypeOf import io.realm.transformer.logger import javassist.CtClass import javassist.CtField +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty import java.io.File import java.util.jar.JarFile +import java.util.jar.JarOutputStream -class FullBuild(metadata: ProjectMetaData, outputProvider: TransformOutputProvider, transformer: RealmTransformer) - : BuildTemplate(metadata, outputProvider, transformer) { +class FullBuild(metadata: ProjectMetaData, allJars: ListProperty, outputProvider: JarOutputStream, transformer: RealmTransformer) + : BuildTemplate(metadata, allJars, outputProvider, transformer) { private val allModelClasses: ArrayList = arrayListOf() - override fun prepareOutputClasses(inputs: MutableCollection) { + override fun prepareOutputClasses(inputs: ListProperty) { this.inputs = inputs; - categorizeClassNames(inputs, outputClassNames, outputReferencedClassNames) + categorizeClassNames(inputs, outputClassNames) logger.debug("Full build. Files being processed: ${outputClassNames.size}.") } - override fun categorizeClassNames(inputs: Collection, - directoryFiles: MutableSet, - jarFiles: MutableSet) { - inputs.forEach { - it.directoryInputs.forEach { - val dirPath: String = it.file.absolutePath - // Non-incremental build: Include all files - it.file.walkTopDown().forEach { - if (it.isFile) { - if (it.absolutePath.endsWith(DOT_CLASS)) { - val className: String = it.absolutePath - .substring(dirPath.length + 1, it.absolutePath.length - DOT_CLASS.length) - .replace(File.separatorChar, '.') - directoryFiles.add(className) - } - } + override fun categorizeClassNames(inputs: ListProperty, + directoryFiles: MutableSet) { + + inputs.get().forEach { directory -> + val dirPath: String = directory.asFile.absolutePath + directory.asFile.walk().filter(File::isFile).forEach { file -> + if (file.absolutePath.endsWith(DOT_CLASS)) { + val className: String = file.absolutePath + .substring(dirPath.length + 1, file.absolutePath.length - DOT_CLASS.length) + .replace(File.separatorChar, '.') + directoryFiles.add(className) } } + } + } - it.jarInputs.forEach { - val jarFile = JarFile(it.file) + override fun categorizeClassNames(referencedInputs: ConfigurableFileCollection, + jarFiles: MutableSet) { + + referencedInputs.forEach { + val jarFile = JarFile(it) jarFile.entries() .toList() .filter { @@ -79,7 +81,6 @@ class FullBuild(metadata: ProjectMetaData, outputProvider: TransformOutputProvid } jarFile.close() // Crash transformer if this fails } - } } override fun findModelClasses(classNames: Set): Collection { @@ -133,8 +134,11 @@ class FullBuild(metadata: ProjectMetaData, outputProvider: TransformOutputProvid logger.debug("Modifying accessors in class: $it") try { val ctClass: CtClass = classPool.getCtClass(it) + if (ctClass.isFrozen) { + ctClass.defrost() + } BytecodeModifier.useRealmAccessors(classPool, ctClass, allManagedFields) - ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath) + processedClasses[it] = ctClass } catch (e: Exception) { throw RuntimeException("Failed to transform $it.", e) } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt deleted file mode 100644 index 34e8bafc28..0000000000 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/IncrementalBuild.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2018 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.transformer.build - -import com.android.build.api.transform.Format -import com.android.build.api.transform.Status -import com.android.build.api.transform.TransformInput -import com.android.build.api.transform.TransformOutputProvider -import io.realm.annotations.RealmClass -import io.realm.transformer.BytecodeModifier -import io.realm.transformer.ProjectMetaData -import io.realm.transformer.RealmTransformer -import io.realm.transformer.ext.safeSubtypeOf -import io.realm.transformer.logger -import javassist.CtClass -import javassist.NotFoundException -import java.io.File -import java.util.jar.JarFile - -class IncrementalBuild(metadata: ProjectMetaData, outputProvider: TransformOutputProvider, transform: RealmTransformer) - : BuildTemplate(metadata, outputProvider, transform) { - - override fun prepareOutputClasses(inputs: MutableCollection) { - this.inputs = inputs; - categorizeClassNames(inputs, outputClassNames, outputReferencedClassNames) // Output files - logger.debug("Incremental build. Files being processed: ${outputClassNames.size}.") - logger.debug("Incremental files: ${outputClassNames.joinToString(",")}") - } - - override fun filterForModelClasses(outputClassNames: Set, outputReferencedClassNames: Set) { - outputModelClasses.addAll(findModelClasses(outputClassNames)) - } - - override fun transformDirectAccessToModelFields() { - // Use accessors instead of direct field access - outputClassNames.forEach { - logger.debug("Modify accessors in class: $it") - val ctClass: CtClass = classPool.getCtClass(it) - BytecodeModifier.useRealmAccessors(classPool, ctClass, null) - ctClass.writeFile(getOutputFile(outputProvider, Format.DIRECTORY).canonicalPath) - } - } - - /** - * Categorize the transform input into its two main categorizes: `directoryFiles` which are - * source files in the current project and `jarFiles` which are source files found in jars. - * - * @param inputs set of input files - * @param directoryFiles the set of files in directories getting compiled. These are candidates for the transformer. - * @param jarFiles the set of files that are possible referenced but never transformed (required by JavaAssist). - * @param isIncremental `true` if build is incremental. - */ - override fun categorizeClassNames(inputs: Collection, - directoryFiles: MutableSet, - jarFiles: MutableSet) { - inputs.forEach { - it.directoryInputs.forEach { - val dirPath: String = it.file.absolutePath - - it.changedFiles.entries.forEach { - if (it.value == Status.NOTCHANGED || it.value == Status.REMOVED) { - return@forEach - } - val filePath: String = it.key.absolutePath - if (filePath.endsWith(DOT_CLASS)) { - val className = filePath - .substring(dirPath.length + 1, filePath.length - DOT_CLASS.length) - .replace(File.separatorChar, '.') - directoryFiles.add(className) - } - } - } - - it.jarInputs.forEach { - if (it.status == Status.REMOVED) { - return@forEach - } - - val jarFile = JarFile(it.file) - jarFile.entries() - .toList() - .filter { - !it.isDirectory && it.name.endsWith(DOT_CLASS) - } - .forEach { - val path: String = it.name - // The jar might not using File.separatorChar as the path separator. So we just replace both `\` and - // `/`. It depends on how the jar file was created. - // See http://stackoverflow.com/questions/13846000/file-separators-of-path-name-of-zipentry - val className: String = path - .substring(0, path.length - DOT_CLASS.length) - .replace('/', '.') - .replace('\\', '.') - jarFiles.add(className) - } - jarFile.close() // Crash transformer if this fails - } - } - } - - override fun findModelClasses(classNames: Set): Collection { - val realmObjectProxyInterface: CtClass = classPool.get("io.realm.internal.RealmObjectProxy") - // For incremental builds we need to determine if a class is a model class file - // based on information in the file itself. This require checks that are only - // possible once we loaded the CtClass from the ClassPool and is slower - // than the approach used when doing full builds. - return classNames - // Map strings to CtClass'es. - .map { classPool.getCtClass(it) } - // Model classes either have the @RealmClass annotation directly (if implementing RealmModel) - // or their superclass has it (if extends RealmObject). The annotation processor - // will have ensured the annotation is only present in these cases. - .filter { - var result: Boolean - if (it.hasAnnotation(RealmClass::class.java)) { - result = true - } else { - try { - result = it.superclass?.hasAnnotation(RealmClass::class.java) == true - } catch (e: NotFoundException) { - // Can happen if the super class is part of the `android.jar` which might - // not have been loaded. In any case, any base class part of Android cannot - // be a Realm model class. - result = false - } - } - return@filter result - } - // Proxy classes are generated by the Realm Annotation Processor and might accidentally - // pass the above check (e.g. if the model class has the @RealmClass annotation), so - // ignore them. - .filter { !it.safeSubtypeOf(realmObjectProxyInterface) } - // Unfortunately the RealmObject base class passes all above checks, so explicitly - // ignore it. - .filter { !it.name.equals("io.realm.RealmObject") } - } -} \ No newline at end of file diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..fcfb29d920 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 4c2a713d17..85b3713867 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -73,8 +73,8 @@ android { // Required from Kotlin 1.1.2 compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 + targetCompatibility 11 + sourceCompatibility 11 } } @@ -171,4 +171,5 @@ artifacts { publishToMavenLocal.dependsOn assemble -android.registerTransform(new io.realm.transformer.RealmTransformer(project)) +io.realm.transformer.RealmTransformer.@Companion.register(project) + diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 7d6973599c..4a8c75830e 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'kotlin' apply plugin: 'maven-publish' -sourceCompatibility = '1.8' -targetCompatibility = '1.8' +sourceCompatibility = '11' +targetCompatibility = '11' def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index bb3b5ef4cc..5f178d62de 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -107,8 +107,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } packagingOptions { @@ -149,18 +149,19 @@ android { } } -android.registerTransform(new io.realm.transformer.RealmTransformer(project)) -android.registerTransform(new io.realm.buildtransformer.RealmBuildTransformer( - "base", - "io.realm.internal.annotations.ObjectServer", - [ - "io_realm_sync_permissions_ClassPermissionsRealmProxyInterface.class", - "io_realm_sync_permissions_PermissionRealmProxyInterface.class", - "io_realm_sync_permissions_PermissionUserRealmProxyInterface.class", - "io_realm_sync_permissions_RealmPermissionsRealmProxyInterface.class", - "io_realm_sync_permissions_RoleRealmProxyInterface.class" - ].toSet() -)) +io.realm.transformer.RealmTransformer.@Companion.register(project) +// TODO +//android.registerTransform(new io.realm.buildtransformer.RealmBuildTransformer( +// "base", +// "io.realm.internal.annotations.ObjectServer", +// [ +// "io_realm_sync_permissions_ClassPermissionsRealmProxyInterface.class", +// "io_realm_sync_permissions_PermissionRealmProxyInterface.class", +// "io_realm_sync_permissions_PermissionUserRealmProxyInterface.class", +// "io_realm_sync_permissions_RealmPermissionsRealmProxyInterface.class", +// "io_realm_sync_permissions_RoleRealmProxyInterface.class" +// ].toSet() +//)) project.afterEvaluate { tasks.withType(JavaCompile) { From fbb9f083844b78c32298971975c978daf584bdaf Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 30 Jun 2022 17:34:55 +0100 Subject: [PATCH 02/25] cleanup --- .../realm/transformer/build/BuildTemplate.kt | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index 9ed24bbd7b..5f39db7edf 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -149,42 +149,6 @@ abstract class BuildTemplate(private val metadata: ProjectMetaData, private val classPool.close() } -// private fun copyResourceFiles(inputs: ListProperty) { -// inputs.get().forEach { input: Directory -> -// input.asFile.walkTopDown().forEach { directory: File -> -// val dirPath: String = directory.absolutePath -// directory.walkTopDown().forEach { file: File -> -// if (file.isFile) { -// if (!file.absolutePath.endsWith(DOT_CLASS)) { -// val dest = File(outputProvider.asFile.get(), file.absolutePath.substring(dirPath.length)) -// logger.debug(" Copying resource file from = $file into = $dest mkdir is = ${dest.parentFile.mkdirs()}") -// Files.copy(file, dest) -// } -// } -// } -// } -// -//// input.jarInputs.forEach { jar: JarInput -> -//// logger.debug("Found JAR file: ${jar.file.absolutePath}") -//// val dirPath: String = jar.file.absolutePath -//// jar.file.walkTopDown().forEach { file: File -> -//// if (file.isFile) { -//// if (file.absolutePath.endsWith(DOT_JAR)) { -//// logger.debug(" Copying jar file: $file") -////// val dest = File(getOutputFile(outputProvider, Format.JAR), file.absolutePath.substring(dirPath.length)) -////// dest.parentFile.mkdirs() -////// Files.copy(file, dest) -//// } -//// } -//// } -//// } -// } -// } - -// protected fun getOutputFile(outputProvider: TransformOutputProvider, format: Format): File { -// return outputProvider.getContentLocation("realm", transform.inputTypes, transform.scopes, format) -// } - /** * There is no official way to get the path to android.jar for transform. * See https://code.google.com/p/android/issues/detail?id=209426 From 8a3c558967c10cadb51d78328a6099957ec326a8 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sun, 10 Jul 2022 16:20:33 +0100 Subject: [PATCH 03/25] Using Java 11 --- dependencies.list | 2 +- gradle-plugin/build.gradle | 10 ++++++---- library-build-transformer/build.gradle | 8 ++++---- .../gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/build.gradle | 6 ++++-- .../kotlin/io/realm/transformer/RealmTransformer.kt | 1 - realm/build.gradle | 2 +- realm/realm-annotations-processor/build.gradle | 4 ++-- 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/dependencies.list b/dependencies.list index bec3df0c61..38a5354e5a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -10,7 +10,7 @@ MONGODB_REALM_SERVER=2022-01-17 GRADLE_BUILD_TOOLS=7.4.0-alpha05 ANDROID_BUILD_TOOLS=30.0.3 KOTLIN=1.6.20 -KOTLIN_COROUTINES=1.6.20 +KOTLIN_COROUTINES=1.6.0 # Common classpath dependencies gradle=7.3.3 diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 8f2228326a..e79531945b 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -5,7 +5,7 @@ import org.gradle.api.internal.project.ProjectInternal buildscript { def properties = new Properties() properties.load(new FileInputStream("${rootDir}/../dependencies.list")) - + ext.kotlin_version = properties.get('KOTLIN') repositories { jcenter() maven { @@ -15,10 +15,12 @@ buildscript { dependencies { classpath "org.jfrog.buildinfo:build-info-extractor-gradle:${properties.get('BUILD_INFO_EXTRACTOR_GRADLE')}" classpath "io.github.gradle-nexus:publish-plugin:${properties.get("GRADLE_NEXUS_PLUGIN")}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -apply plugin: 'groovy-gradle-plugin' +apply plugin: 'kotlin' +apply plugin: 'java-gradle-plugin' apply plugin: 'maven-publish' def props = new Properties() @@ -33,8 +35,8 @@ repositories { jcenter() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = 11 +targetCompatibility = 11 group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim() diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index a931f49f93..a30fe8753d 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -41,14 +41,14 @@ dependencies { testImplementation group:'junit', name:'junit', version:'4.12' } -sourceCompatibility = '1.8' -targetCompatibility = '1.8' +sourceCompatibility = '11' +targetCompatibility = '11' compileKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions.jvmTarget = "11" } compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions.jvmTarget = "11" } def commonPom = { diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 41dfb87909..fcfb29d920 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 7da81ff129..62544413aa 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -17,8 +17,8 @@ buildscript { apply plugin: 'java' apply plugin: 'maven-publish' -sourceCompatibility = '1.8' -targetCompatibility = '1.8' +sourceCompatibility = '11' +targetCompatibility = '11' group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim() @@ -44,4 +44,6 @@ publishing { java { withSourcesJar() withJavadocJar() + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index b3d81ff7b8..b3803ec6f5 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -132,7 +132,6 @@ class RealmTransformer(project: Project, ) ) - logger.debug("output is=${output.get().asFile.path} exists=${output.get().asFile.exists()}") val build: BuildTemplate = FullBuild(metadata, allJars, jarOutput, this) build.prepareOutputClasses(inputs) diff --git a/realm/build.gradle b/realm/build.gradle index bfa2df0e05..64e7fd591a 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -37,7 +37,7 @@ allprojects { project.ext.set(key, val) } project.ext.minSdkVersion = 16 - project.ext.compileSdkVersion = 29 + project.ext.compileSdkVersion = 30 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 4a8c75830e..18ad6892db 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -65,13 +65,13 @@ java { compileKotlin { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" freeCompilerArgs += ["-XXLanguage:+InlineClasses"] } } compileTestKotlin { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } } From a9a25750fc69d7c8a7c337e1c2774d8428eb645a Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 11 Jul 2022 06:37:21 +0100 Subject: [PATCH 04/25] Silencing Javadoc --- realm-transformer/build.gradle | 4 ++- .../io/realm/analytics/AnalyticsData.kt | 3 +- .../analytics/ComputerIdentifierGenerator.kt | 2 +- .../io/realm/transformer/RealmTransformer.kt | 28 ++++++++++--------- .../realm/transformer/build/BuildTemplate.kt | 4 --- .../io/realm/transformer/ext/ProjectExt.kt | 2 +- realm/realm-library/build.gradle | 9 ++---- 7 files changed, 24 insertions(+), 28 deletions(-) diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 43dfd247dd..3c7ae7883f 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -96,10 +96,12 @@ publishing { java { withSourcesJar() withJavadocJar() + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } compileKotlin { kotlinOptions { - freeCompilerArgs = ["-Xinline-classes", "-Xjvm-default=enable"] + freeCompilerArgs = ["-Xinline-classes", "-Xjvm-default=all-compatibility"] } } diff --git a/realm-transformer/src/main/kotlin/io/realm/analytics/AnalyticsData.kt b/realm-transformer/src/main/kotlin/io/realm/analytics/AnalyticsData.kt index 505f7dd3ca..d0f7e729d0 100644 --- a/realm-transformer/src/main/kotlin/io/realm/analytics/AnalyticsData.kt +++ b/realm-transformer/src/main/kotlin/io/realm/analytics/AnalyticsData.kt @@ -20,7 +20,8 @@ import io.realm.transformer.Version import java.net.SocketException import java.security.NoSuchAlgorithmException -inline class PublicAppId(val id: String) { +@JvmInline +value class PublicAppId(val id: String) { fun anonymize(): String { val idBytes: ByteArray = id.toByteArray() return Utils.hexStringify(Utils.sha256Hash(idBytes)) diff --git a/realm-transformer/src/main/kotlin/io/realm/analytics/ComputerIdentifierGenerator.kt b/realm-transformer/src/main/kotlin/io/realm/analytics/ComputerIdentifierGenerator.kt index 8d007c825f..ba5df1e07d 100644 --- a/realm-transformer/src/main/kotlin/io/realm/analytics/ComputerIdentifierGenerator.kt +++ b/realm-transformer/src/main/kotlin/io/realm/analytics/ComputerIdentifierGenerator.kt @@ -33,7 +33,7 @@ import java.util.* class ComputerIdentifierGenerator { companion object { private const val UNKNOWN = "unknown" - private val OS: String = System.getProperty("os.name").toLowerCase() + private val OS: String = System.getProperty("os.name").lowercase(Locale.getDefault()) private val isWindows: Boolean = OS.contains("win") private val isMac: Boolean = OS.contains("mac") private val isLinux: Boolean = OS.contains("inux") diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index b3803ec6f5..db25f47da5 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -60,28 +60,30 @@ class RealmTransformer(project: Project, private val allJars: ListProperty, private val referencedInputs: ConfigurableFileCollection, private val output: RegularFileProperty) { - private var metadata: ProjectMetaData + private lateinit var metadata: ProjectMetaData private var analytics: RealmAnalytics? = null init { // Fetch project metadata when registering the transformer, but as some of the properties // we need to read might not be initialized yet (e.g. the Android extension), we need // to wait until after the build files have been evaluated. - metadata = ProjectMetaData( - // Plugin requirements - project.gradle.startParameter.isOffline, - project.getBootClasspath() - ) + project.afterEvaluate { + metadata = ProjectMetaData( + // Plugin requirements + project.gradle.startParameter.isOffline, + project.getBootClasspath() + ) - try { - this.analytics = RealmAnalytics() - this.analytics!!.calculateAnalyticsData(project) + try { + this.analytics = RealmAnalytics() + this.analytics!!.calculateAnalyticsData(project) - } catch (e: Exception) { - // Analytics should never crash the build. - logger.debug("Could not calculate Realm analytics data:\n$e") + } catch (e: Exception) { + // Analytics should never crash the build. + logger.debug("Could not calculate Realm analytics data:\n$e") + } + transform() } - transform() } companion object { diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index 5f39db7edf..bf8aa902b2 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -15,17 +15,13 @@ */ package io.realm.transformer.build -import com.android.build.api.transform.TransformOutputProvider import io.realm.transformer.* import javassist.ClassPool import javassist.CtClass -import com.google.common.io.Files import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.provider.ListProperty -import java.io.File import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt index ee533aa6db..97c2b5a9ad 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/ext/ProjectExt.kt @@ -77,7 +77,7 @@ fun Project.getAgpVersion(): String { * Returns the `bootClasspath` for this project */ fun Project.getBootClasspath(): List { - return getAndroidExtension(this).bootClasspath ?: listOf() + return getAndroidExtension(this).bootClasspath } private fun getAndroidExtension(project: Project): BaseExtension { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 5f178d62de..9b2c6a8be1 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -240,17 +240,14 @@ task sourcesJar(type: Jar) { classifier = 'sources' } -def betaTag = 'Beta:a:
This software is considered in beta phase. ' + - 'It indicates that any public interface can change without prior announcements. ' + - 'Moreover, classes, constructors, and methods annotated as beta are not ' + - 'considered at production quality, and should be used with care.
' - task javadoc(type: Javadoc) { + failOnError false dependsOn("compileObjectServerReleaseJavaWithJavac") source android.sourceSets.objectServer.java.srcDirs source android.sourceSets.main.java.srcDirs source "../../realm-annotations/src/main/java" classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + options { title = "Realm ${project.version}" memberLevel = JavadocMemberLevel.PUBLIC @@ -264,8 +261,6 @@ task javadoc(type: Javadoc) { links "http://reactivex.io/RxJava/javadoc/" links "https://developer.android.com/reference" links "https://www.javadoc.io/doc/org.mongodb/bson/${properties.getProperty('BSON_DEPENDENCY')}/" - - tags = [betaTag] } exclude '**/internal/**' exclude '**/BuildConfig.java' From 15a02d504cda6632abc8d0bbf80df430e62713f9 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Mon, 11 Jul 2022 06:47:07 +0100 Subject: [PATCH 05/25] Project already evaluated --- realm-transformer/build.gradle | 1 + .../io/realm/transformer/RealmTransformer.kt | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index 3c7ae7883f..f49d22d9e6 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -102,6 +102,7 @@ java { compileKotlin { kotlinOptions { + jvmTarget = "11" freeCompilerArgs = ["-Xinline-classes", "-Xjvm-default=all-compatibility"] } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index db25f47da5..94f230b5be 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -67,23 +67,21 @@ class RealmTransformer(project: Project, // Fetch project metadata when registering the transformer, but as some of the properties // we need to read might not be initialized yet (e.g. the Android extension), we need // to wait until after the build files have been evaluated. - project.afterEvaluate { - metadata = ProjectMetaData( - // Plugin requirements - project.gradle.startParameter.isOffline, - project.getBootClasspath() - ) + metadata = ProjectMetaData( + // Plugin requirements + project.gradle.startParameter.isOffline, + project.getBootClasspath() + ) - try { - this.analytics = RealmAnalytics() - this.analytics!!.calculateAnalyticsData(project) + try { + this.analytics = RealmAnalytics() + this.analytics!!.calculateAnalyticsData(project) - } catch (e: Exception) { - // Analytics should never crash the build. - logger.debug("Could not calculate Realm analytics data:\n$e") - } - transform() + } catch (e: Exception) { + // Analytics should never crash the build. + logger.debug("Could not calculate Realm analytics data:\n$e") } + transform() } companion object { From 207e5bcf046923551e59e9a0bea0cef833f7e58e Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Wed, 27 Jul 2022 10:07:53 +0100 Subject: [PATCH 06/25] WIP --- dependencies.list | 2 +- .../io/realm/examples/arch/CustomApplication.java | 8 ++++---- .../io/realm/examples/arch/PersonFragment.java | 3 ++- .../io/realm/examples/arch/PersonListFragment.java | 6 ++++-- .../java/io/realm/examples/arch/model/Person.java | 14 +++++++------- .../encryption/EncryptionExampleActivity.java | 2 ++ examples/gradle/wrapper/gradle-wrapper.properties | 2 +- .../realm/examples/intro/IntroExampleActivity.java | 6 +++++- examples/kotlinExample/build.gradle | 5 +++-- .../appmodules/ModulesExampleActivity.java | 14 ++++++++++++-- .../java/io/realm/examples/librarymodules/Zoo.java | 5 ++++- examples/mongoDbRealmExample/build.gradle | 4 ++-- .../realmmultiprocessexample/MyApplication.java | 6 +++++- .../io/realm/examples/rxjava/MyApplication.java | 5 ++++- .../io/realm/examples/threads/MyApplication.java | 5 ++++- .../examples/unittesting/ExampleActivity.java | 8 +++++++- .../asm/visitors/AnnotatedCodeStripVisitor.kt | 2 +- .../asm/visitors/AnnotationVisitor.kt | 2 +- realm/kotlin-extensions/build.gradle | 4 ++++ realm/realm-library/build.gradle | 4 ++++ 20 files changed, 77 insertions(+), 30 deletions(-) diff --git a/dependencies.list b/dependencies.list index 38a5354e5a..38bb5e1c6a 100644 --- a/dependencies.list +++ b/dependencies.list @@ -9,7 +9,7 @@ MONGODB_REALM_SERVER=2022-01-17 # Common Android settings across projects GRADLE_BUILD_TOOLS=7.4.0-alpha05 ANDROID_BUILD_TOOLS=30.0.3 -KOTLIN=1.6.20 +KOTLIN=1.6.21 KOTLIN_COROUTINES=1.6.0 # Common classpath dependencies diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java index b3c8333f70..c05630d53b 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java @@ -37,19 +37,19 @@ public void onCreate() { @Override public void execute(@NonNull Realm realm) { Person person = realm.createObject(Person.class); - person.setName("Makoto Yamazaki"); + person.name = "Makoto Yamazaki"; person.setAge(32); person = realm.createObject(Person.class); - person.setName("Christian Melchior"); + person.name = "Christian Melchior"; person.setAge(34); person = realm.createObject(Person.class); - person.setName("Chen Mulong"); + person.name = "Chen Mulong"; person.setAge(29); person = realm.createObject(Person.class); - person.setName("Nabil Hachicha"); + person.name = "Nabil Hachicha"; person.setAge(31); } }) diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java index 194553cae3..aaacd57267 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java @@ -64,7 +64,8 @@ public T create(@NonNull Class modelClass) { personViewModel.getPerson().observe(this, person -> { if (person != null) { // null would mean the object was deleted. - name.setText(person.getName()); +// name.setText(person.getName()); + name.setText(person.name); age.setText(String.valueOf(person.getAge())); } }); diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java index b879caced3..22dfc5a0f4 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java @@ -114,7 +114,8 @@ static class ViewHolder extends RecyclerView.ViewHolder { return; } AppCompatActivity activity = ContextUtils.findActivity(view.getContext()); - PersonFragment personFragment = PersonFragment.create(person.getName()); +// PersonFragment personFragment = PersonFragment.create(person.getName()); + PersonFragment personFragment = PersonFragment.create(person.name); activity.getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) @@ -132,7 +133,8 @@ public ViewHolder(View itemView) { public void bind(Person person) { this.person = person; - name.setText(person.getName()); +// name.setText(person.getName()); + name.setText(person.name); age.setText(String.valueOf(person.getAge())); } } diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java index fac53a7398..159558b597 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java @@ -21,17 +21,17 @@ public class Person extends RealmObject { @Index - private String name; + public String name; private int age; - public String getName() { - return name; - } +// public String getName() { +// return name; +// } - public void setName(String name) { - this.name = name; - } +// public void setName(String name) { +// this.name = name; +// } public int getAge() { return age; diff --git a/examples/encryptionExample/src/main/java/io/realm/examples/encryption/EncryptionExampleActivity.java b/examples/encryptionExample/src/main/java/io/realm/examples/encryption/EncryptionExampleActivity.java index fd5ae8d804..9665125016 100644 --- a/examples/encryptionExample/src/main/java/io/realm/examples/encryption/EncryptionExampleActivity.java +++ b/examples/encryptionExample/src/main/java/io/realm/examples/encryption/EncryptionExampleActivity.java @@ -52,6 +52,8 @@ protected void onCreate(Bundle savedInstanceState) { RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .encryptionKey(key) + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) .build(); // Start with a clean slate every time diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 41dfb87909..fcfb29d920 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java index 4660587de3..adb9c48b4b 100644 --- a/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java +++ b/examples/introExample/src/main/java/io/realm/examples/intro/IntroExampleActivity.java @@ -28,6 +28,7 @@ import io.realm.OrderedRealmCollectionChangeListener; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.Sort; import io.realm.examples.intro.model.Cat; @@ -66,7 +67,10 @@ protected void onCreate(Bundle savedInstanceState) { Realm.deleteRealm(Realm.getDefaultConfiguration()); // Create the Realm instance - realm = Realm.getDefaultInstance(); + RealmConfiguration build = new RealmConfiguration.Builder() + .allowWritesOnUiThread(true) + .allowQueriesOnUiThread(true).build(); + realm = Realm.getInstance(build); // Asynchronous queries are evaluated on a background thread, // and passed to the registered change listener when it's done. diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 3f969a9178..177b1a5a83 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -48,6 +48,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" - implementation "org.jetbrains.anko:anko-commons:0.10.4" +// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" +// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21" +// implementation "org.jetbrains.anko:anko-commons:0.10.4" } diff --git a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java index d08f35687a..16e23ed3e7 100644 --- a/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java +++ b/examples/moduleExample/app/src/main/java/io/realm/examples/appmodules/ModulesExampleActivity.java @@ -35,6 +35,7 @@ import io.realm.examples.librarymodules.model.Elephant; import io.realm.examples.librarymodules.model.Lion; import io.realm.examples.librarymodules.model.Zebra; +import io.realm.examples.librarymodules.modules.AllAnimalsModule; import io.realm.examples.librarymodules.modules.DomesticAnimalsModule; import io.realm.examples.librarymodules.modules.ZooAnimalsModule; import io.realm.exceptions.RealmException; @@ -58,20 +59,29 @@ protected void onCreate(Bundle savedInstanceState) { // The default Realm instance implicitly knows about all classes in the realmModuleAppExample Android Studio // module. This does not include the classes from the realmModuleLibraryExample AS module so a Realm using this // configuration would know about the following classes: { Cow, Pig, Snake, Spider } - RealmConfiguration defaultConfig = new RealmConfiguration.Builder().build(); + RealmConfiguration defaultConfig = new RealmConfiguration + .Builder() + .modules(Realm.getDefaultModule(), new AllAnimalsModule()) + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) + .build(); // It is possible to extend the default schema by adding additional Realm modules using modules(). This can // also be Realm modules from libraries. The below Realm contains the following classes: { Cow, Pig, Snake, // Spider, Cat, Dog } RealmConfiguration farmAnimalsConfig = new RealmConfiguration.Builder() .name("farm.realm") - .modules(Realm.getDefaultModule(), new DomesticAnimalsModule()) + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) + .modules(Realm.getDefaultModule(), new AllAnimalsModule()) .build(); // Or you can completely replace the default schema. // This Realm contains the following classes: { Elephant, Lion, Zebra, Snake, Spider } RealmConfiguration exoticAnimalsConfig = new RealmConfiguration.Builder() .name("exotic.realm") + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) .modules(new ZooAnimalsModule(), new CreepyAnimalsModule()) .build(); diff --git a/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java b/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java index b7d68f2eb6..1c0e4b5f7e 100644 --- a/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java +++ b/examples/moduleExample/library/src/main/java/io/realm/examples/librarymodules/Zoo.java @@ -35,7 +35,10 @@ public class Zoo { public Zoo() { realmConfig = new RealmConfiguration.Builder() // The app is responsible for calling `Realm.init(Context)` .name("library.zoo.realm") // So always use a unique name - .modules(new AllAnimalsModule()) // Always use explicit modules in library projects + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) + .modules(new AllAnimalsModule()) + // Always use explicit modules in library projects .build(); // Reset Realm diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle index 7ccfb65314..31791711cc 100644 --- a/examples/mongoDbRealmExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -53,8 +53,8 @@ realm { } dependencies { - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java index d078915489..7d55aa6831 100644 --- a/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java +++ b/examples/multiprocessExample/src/main/java/io/realm/examples/realmmultiprocessexample/MyApplication.java @@ -26,7 +26,11 @@ public class MyApplication extends Application { public void onCreate() { super.onCreate(); Realm.init(this); - RealmConfiguration configuration = new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build(); + RealmConfiguration configuration = new RealmConfiguration.Builder() + .deleteRealmIfMigrationNeeded() + .allowWritesOnUiThread(true) + .allowQueriesOnUiThread(true) + .build(); Realm.setDefaultConfiguration(configuration); } } diff --git a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java index 52ed89fdb1..ae98502370 100644 --- a/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java +++ b/examples/rxJavaExample/src/main/java/io/realm/examples/rxjava/MyApplication.java @@ -45,7 +45,10 @@ public class MyApplication extends Application { public void onCreate() { super.onCreate(); Realm.init(this); - RealmConfiguration config = new RealmConfiguration.Builder().build(); + RealmConfiguration config = new RealmConfiguration.Builder() + .allowWritesOnUiThread(true) + .allowQueriesOnUiThread(true) + .build(); Realm.deleteRealm(config); Realm.setDefaultConfiguration(config); createTestData(); diff --git a/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java b/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java index a043477ecf..8c18e77467 100644 --- a/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java +++ b/examples/threadExample/src/main/java/io/realm/examples/threads/MyApplication.java @@ -29,7 +29,10 @@ public void onCreate() { // Configure Realm for the application Realm.init(this); - RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build(); + RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() + .allowWritesOnUiThread(true) + .allowQueriesOnUiThread(true) + .build(); Realm.deleteRealm(realmConfiguration); // Clean slate Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default } diff --git a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java index 4b0f94e752..f1a5f58a2a 100644 --- a/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java +++ b/examples/unitTestExample/src/main/java/io/realm/examples/unittesting/ExampleActivity.java @@ -24,6 +24,7 @@ import android.widget.TextView; import io.realm.Realm; +import io.realm.RealmConfiguration; import io.realm.RealmResults; import io.realm.examples.unittesting.model.Person; @@ -43,7 +44,12 @@ protected void onCreate(Bundle savedInstanceState) { rootLayout.removeAllViews(); // Open the default Realm for the UI thread. - realm = Realm.getDefaultInstance(); + RealmConfiguration conf = new RealmConfiguration + .Builder() + .allowWritesOnUiThread(true) + .allowQueriesOnUiThread(true) + .build(); + realm = Realm.getInstance(conf); // Clean up from previous run cleanUp(); diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt index 0892d82cba..049aa489f8 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt @@ -30,7 +30,7 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, private val markedClasses: Set, private val markedMethods: Map>, private val markedFields: Map>, - classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM6, classWriter) { + classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM7_EXPERIMENTAL, classWriter) { var deleteClass: Boolean = false private lateinit var markedMethodsInClass: Set diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt index a34825b972..ee1e7252d7 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt @@ -27,7 +27,7 @@ import org.objectweb.asm.AnnotationVisitor * pass and is required for correctly identifying them in the 2nd pass before any byte code is * written. */ -class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor(Opcodes.ASM6) { +class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor(Opcodes.ASM7_EXPERIMENTAL) { val annotatedClasses: MutableSet = mutableSetOf() val annotatedMethods: MutableMap> = mutableMapOf() diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index 85b3713867..ad58de63c4 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -76,6 +76,10 @@ android { targetCompatibility 11 sourceCompatibility 11 } + + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 9b2c6a8be1..047d06134d 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -111,6 +111,10 @@ android { targetCompatibility JavaVersion.VERSION_11 } + kotlinOptions { + jvmTarget = 11 + } + packagingOptions { exclude 'META-INF/NOTICE.txt' exclude 'META-INF/LICENSE.txt' From bf97b43762772a5033e66ccc94154b46ca735696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 10 Aug 2022 10:34:47 +0200 Subject: [PATCH 07/25] Fix sdk tests and all examples --- dependencies.list | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- realm-annotations/build.gradle | 4 ++ .../main/java/io/realm/transformer/Utils.java | 2 +- .../io/realm/transformer/RealmTransformer.kt | 40 ++++++++++--------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../src/androidTest/AndroidManifest.xml | 4 +- .../testUtils/java/io/realm/TestHelper.java | 1 + .../realm/services/RemoteProcessService.java | 1 + 10 files changed, 35 insertions(+), 25 deletions(-) diff --git a/dependencies.list b/dependencies.list index 38bb5e1c6a..8199bd89d7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -7,7 +7,7 @@ REALM_CORE=11.14.0 MONGODB_REALM_SERVER=2022-01-17 # Common Android settings across projects -GRADLE_BUILD_TOOLS=7.4.0-alpha05 +GRADLE_BUILD_TOOLS=7.4.0-alpha08 ANDROID_BUILD_TOOLS=30.0.3 KOTLIN=1.6.21 KOTLIN_COROUTINES=1.6.0 diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index fcfb29d920..26e62a13f1 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fcfb29d920..26e62a13f1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 62544413aa..003357f9ff 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -47,3 +47,7 @@ java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + +javadoc { + options.encoding = 'UTF-8' +} diff --git a/realm-transformer/src/main/java/io/realm/transformer/Utils.java b/realm-transformer/src/main/java/io/realm/transformer/Utils.java index 7069bfe429..c515d8c8a5 100644 --- a/realm-transformer/src/main/java/io/realm/transformer/Utils.java +++ b/realm-transformer/src/main/java/io/realm/transformer/Utils.java @@ -29,7 +29,7 @@ public class Utils { * Encode the given string with Base64 * @param data the string to encode * @return the encoded string - * @throws UnsupportedEncodingException + * @throws UnsupportedEncodingException if string is not valid utf8 */ public static String base64Encode(String data) throws UnsupportedEncodingException { return DatatypeConverter.printBase64Binary(data.getBytes("UTF-8")); diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 94f230b5be..34c26190dd 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -89,24 +89,28 @@ class RealmTransformer(project: Project, fun register(project: Project) { val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) androidComponents.onVariants { variant -> - - val taskProvider = - project.tasks.register("${variant.name}RealmAccessorsTransformer", ModifyClassesTask::class.java) { - it.fullRuntimeClasspath.setFrom(variant.runtimeConfiguration.incoming.artifactView { c-> - c.attributes.attribute( - AndroidArtifacts.ARTIFACT_TYPE, - AndroidArtifacts.ArtifactType.CLASSES_JAR.type - ) - }.files) - } - variant.artifacts.forScope(com.android.build.api.variant.ScopedArtifacts.Scope.PROJECT) - .use(taskProvider) - .toTransform( - com.android.build.api.artifact.ScopedArtifact.CLASSES, - ModifyClassesTask::allJars, - ModifyClassesTask::allDirectories, - ModifyClassesTask::output - ) + variant.components.forEach { component -> + val taskProvider = + project.tasks.register( + "${component.name}RealmAccessorsTransformer", + ModifyClassesTask::class.java + ) { + it.fullRuntimeClasspath.setFrom(component.runtimeConfiguration.incoming.artifactView { c -> + c.attributes.attribute( + AndroidArtifacts.ARTIFACT_TYPE, + AndroidArtifacts.ArtifactType.CLASSES_JAR.type + ) + }.files) + } + component.artifacts.forScope(com.android.build.api.variant.ScopedArtifacts.Scope.PROJECT) + .use(taskProvider) + .toTransform( + com.android.build.api.artifact.ScopedArtifact.CLASSES, + ModifyClassesTask::allJars, + ModifyClassesTask::allDirectories, + ModifyClassesTask::output + ) + } } } } diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index fcfb29d920..26e62a13f1 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/realm-library/src/androidTest/AndroidManifest.xml b/realm/realm-library/src/androidTest/AndroidManifest.xml index eda3aabdc9..07f785081c 100644 --- a/realm/realm-library/src/androidTest/AndroidManifest.xml +++ b/realm/realm-library/src/androidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ - @@ -20,7 +20,7 @@ diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 88cf50ee4e..235bafdf26 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -140,6 +140,7 @@ public static RealmFieldType getColumnType(Object o) { * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row * added. */ + @Deprecated public static long addRowWithValues(Table table, long[] columnKeys, Object[] values) { long rowKey = OsObject.createRow(table); diff --git a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java index a2441c4ef5..bbf99aaf12 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteProcessService.java @@ -38,6 +38,7 @@ * Helper service for multi-processes support testing. * @deprecated use {@link RemoteTestService} instead. */ +@Deprecated public class RemoteProcessService extends Service { public abstract static class Step { From 13a9de5f4edfcf301d345303cb7b8dc01cb8ed68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 12 Aug 2022 14:34:29 +0200 Subject: [PATCH 08/25] Fix kotlin-extension tests --- realm-transformer/build.gradle | 6 +++--- .../io/realm/transformer/build/BuildTemplate.kt | 15 +++++++++++++++ realm/kotlin-extensions/build.gradle | 6 +++--- realm/realm-annotations-processor/build.gradle | 8 ++++---- .../io/realm/processor/NameConverterTests.java | 4 ++-- realm/realm-library/build.gradle | 6 +++--- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/realm-transformer/build.gradle b/realm-transformer/build.gradle index f49d22d9e6..7982fcbb5f 100644 --- a/realm-transformer/build.gradle +++ b/realm-transformer/build.gradle @@ -27,8 +27,8 @@ properties.load(new FileInputStream("${projectDir}/../dependencies.list")) def coreVersion = properties.getProperty('REALM_CORE') -sourceCompatibility = '11' -targetCompatibility = '11' +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 repositories { mavenLocal() @@ -102,7 +102,7 @@ java { compileKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = JavaVersion.VERSION_11 freeCompilerArgs = ["-Xinline-classes", "-Xjvm-default=all-compatibility"] } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index bf8aa902b2..ce3548e22e 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -22,6 +22,7 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory import org.gradle.api.file.RegularFile import org.gradle.api.provider.ListProperty +import java.io.File import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream @@ -128,6 +129,20 @@ abstract class BuildTemplate(private val metadata: ProjectMetaData, private val } fun copyResourceFiles() { + inputs.get().forEach { directory: Directory -> + val dirName = directory.asFile.absolutePath + File.separator + directory.asFile.walk().filter(File::isFile).forEach { file -> + if (!file.absolutePath.endsWith(DOT_CLASS)) { + println("TRANSFORMER>>JarFile : " + file.absolutePath) + val removePrefix = file.absolutePath.removePrefix(dirName) + println("TRANSFORMER>>JarFile : " + removePrefix) + println("TRANSFORMER>>Adding from jar $removePrefix") + outputProvider.putNextEntry(JarEntry(removePrefix)) + outputProvider.write(file.readBytes()) + outputProvider.closeEntry() + } + } + } allJars.get().forEach { file -> println("TRANSFORMER>>JarFile : " + file.asFile.absolutePath) val jarFile = JarFile(file.asFile) diff --git a/realm/kotlin-extensions/build.gradle b/realm/kotlin-extensions/build.gradle index ad58de63c4..9c96c8db5f 100644 --- a/realm/kotlin-extensions/build.gradle +++ b/realm/kotlin-extensions/build.gradle @@ -73,12 +73,12 @@ android { // Required from Kotlin 1.1.2 compileOptions { - targetCompatibility 11 - sourceCompatibility 11 + targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = 11 + jvmTarget = JavaVersion.VERSION_1_8 } } diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 18ad6892db..27775915f3 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'kotlin' apply plugin: 'maven-publish' -sourceCompatibility = '11' -targetCompatibility = '11' +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) @@ -65,13 +65,13 @@ java { compileKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = JavaVersion.VERSION_11 freeCompilerArgs += ["-XXLanguage:+InlineClasses"] } } compileTestKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = JavaVersion.VERSION_11 } } diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java index 8465d0d28c..51b0c99ee9 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java @@ -1,5 +1,7 @@ package io.realm.processor; +import static org.junit.Assert.assertEquals; + import org.junit.Test; import java.util.LinkedHashMap; @@ -10,8 +12,6 @@ import io.realm.processor.nameconverter.NameConverter; import io.realm.processor.nameconverter.PascalCaseConverter; -import static org.junit.Assert.assertEquals; - public class NameConverterTests { @Test diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 047d06134d..7912e5deab 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -107,12 +107,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = 11 + jvmTarget = JavaVersion.VERSION_1_8 } packagingOptions { From e1aeed26f818df5977ee4b5fcaa8738f5f757d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 12 Aug 2022 16:08:40 +0200 Subject: [PATCH 09/25] Fix example projects according to latest updates --- examples/coroutinesExample/build.gradle | 13 +++++++------ examples/kotlinExample/build.gradle | 9 +++++---- .../realm/examples/kotlin/KotlinExampleActivity.kt | 8 ++++++++ examples/mongoDbRealmExample/build.gradle | 3 ++- .../java/io/realm/processor/NameConverterTests.java | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/examples/coroutinesExample/build.gradle b/examples/coroutinesExample/build.gradle index 2dd3516bf7..4e909277c9 100644 --- a/examples/coroutinesExample/build.gradle +++ b/examples/coroutinesExample/build.gradle @@ -5,7 +5,8 @@ apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' android { - compileSdkVersion rootProject.sdkVersion + // androidx.lifecycle dependencies requires Android APIs 31 or later + compileSdkVersion 31 buildToolsVersion rootProject.buildTools defaultConfig { @@ -57,10 +58,10 @@ dependencies { implementation "androidx.fragment:fragment-ktx:1.2.5" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" - implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" + implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0" implementation "androidx.legacy:legacy-support-v4:1.0.0" @@ -68,7 +69,7 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "com.dropbox.mobile.store:store4:4.0.0" + implementation "com.dropbox.mobile.store:store4:4.0.5" implementation "com.google.android.material:material:1.2.1" diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 177b1a5a83..851b22e382 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -35,9 +35,10 @@ android { // This is added automatically if Kotlin is registered in the project, but Kotlin extension functions // for Realm can be excluded if needed. -realm { - kotlinExtensionsEnabled = true -} +// FIXME The automatic dependency handling is somehow ruined with the latest AGP, need to investigate +//realm { +// kotlinExtensionsEnabled = true +//} // enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ @@ -50,5 +51,5 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21" -// implementation "org.jetbrains.anko:anko-commons:0.10.4" + implementation "org.jetbrains.anko:anko-commons:0.10.4" } diff --git a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt index 3d7af8c2dc..2e810e70dd 100644 --- a/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt +++ b/examples/kotlinExample/src/main/kotlin/io/realm/examples/kotlin/KotlinExampleActivity.kt @@ -22,6 +22,7 @@ import android.util.Log import android.widget.LinearLayout import android.widget.TextView import io.realm.Realm +import io.realm.RealmConfiguration import io.realm.Sort import io.realm.examples.kotlin.model.Cat import io.realm.examples.kotlin.model.Dog @@ -46,6 +47,13 @@ class KotlinExampleActivity : Activity() { rootLayout = findViewById(R.id.container) rootLayout.removeAllViews() + Realm.setDefaultConfiguration( + RealmConfiguration.Builder() + .allowQueriesOnUiThread(true) + .allowWritesOnUiThread(true) + .build() + ) + // Open the realm for the UI thread. realm = Realm.getDefaultInstance() diff --git a/examples/mongoDbRealmExample/build.gradle b/examples/mongoDbRealmExample/build.gradle index 31791711cc..fa9bf356b1 100644 --- a/examples/mongoDbRealmExample/build.gradle +++ b/examples/mongoDbRealmExample/build.gradle @@ -5,7 +5,8 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'realm-android' android { - compileSdkVersion rootProject.sdkVersion + // androidx.lifecycle dependencies requires Android APIs 31 or later + compileSdkVersion 31 buildToolsVersion rootProject.buildTools defaultConfig { diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java index 51b0c99ee9..37b01dbb52 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java @@ -44,7 +44,7 @@ public void camelCase() { put("aHTMLFile", "aHtmlFile"); put("_HTMLFile", "htmlFile"); - // Emojiis are neither upper case nor lower case (Smiley) +// // Emojiis are neither upper case nor lower case (Smiley) put("\uD83D\uDE01", "\uD83D\uDE01"); put("m\uD83D\uDE01", "m\uD83D\uDE01"); put("M\uD83D\uDE01", "m\uD83D\uDE01"); From 134a9bd3930bec573eb72c0cc11766e6ee00f0c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 16 Aug 2022 10:24:52 +0200 Subject: [PATCH 10/25] Revert annotations JDK target update --- realm-annotations/build.gradle | 8 ++++---- .../test/java/io/realm/processor/NameConverterTests.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/realm-annotations/build.gradle b/realm-annotations/build.gradle index 003357f9ff..b397f25e72 100644 --- a/realm-annotations/build.gradle +++ b/realm-annotations/build.gradle @@ -17,8 +17,8 @@ buildscript { apply plugin: 'java' apply plugin: 'maven-publish' -sourceCompatibility = '11' -targetCompatibility = '11' +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim() @@ -44,8 +44,8 @@ publishing { java { withSourcesJar() withJavadocJar() - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } javadoc { diff --git a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java index 37b01dbb52..51b0c99ee9 100644 --- a/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java +++ b/realm/realm-annotations-processor/src/test/java/io/realm/processor/NameConverterTests.java @@ -44,7 +44,7 @@ public void camelCase() { put("aHTMLFile", "aHtmlFile"); put("_HTMLFile", "htmlFile"); -// // Emojiis are neither upper case nor lower case (Smiley) + // Emojiis are neither upper case nor lower case (Smiley) put("\uD83D\uDE01", "\uD83D\uDE01"); put("m\uD83D\uDE01", "m\uD83D\uDE01"); put("M\uD83D\uDE01", "m\uD83D\uDE01"); From df23338c97f128b3ef32fd6f61333a54a640718c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 16 Aug 2022 14:40:49 +0200 Subject: [PATCH 11/25] Fix processor test encoding issues --- realm/realm-annotations-processor/build.gradle | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index 27775915f3..7335fc39c6 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'kotlin' apply plugin: 'maven-publish' -sourceCompatibility = JavaVersion.VERSION_11 -targetCompatibility = JavaVersion.VERSION_11 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) @@ -65,13 +65,20 @@ java { compileKotlin { kotlinOptions { - jvmTarget = JavaVersion.VERSION_11 + jvmTarget = JavaVersion.VERSION_1_8 freeCompilerArgs += ["-XXLanguage:+InlineClasses"] } } compileTestKotlin { kotlinOptions { - jvmTarget = JavaVersion.VERSION_11 + jvmTarget = JavaVersion.VERSION_1_8 } } + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} +tasks.withType(Test) { + systemProperty "file.encoding", "UTF-8" +} \ No newline at end of file From 33081efd95f18c25b7cb095ab22d6c2a7bef0b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 16 Aug 2022 14:41:27 +0200 Subject: [PATCH 12/25] Ensure JNI headers are generated before native build --- realm/realm-library/build.gradle | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 7912e5deab..38651458c8 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -408,12 +408,9 @@ project.afterEvaluate { // all Java files must be compiled before native build // See https://github.com/android/ndk-samples/issues/284 - android.libraryVariants.all { anotherVariant -> - if (variant.flavorName == anotherVariant.flavorName) { - variant.externalNativeBuildProviders[0].configure { - dependsOn "compile${anotherVariant.name.capitalize()}JavaWithJavac" - } - } + variant.externalNativeBuildProviders[0].configure { + it.taskDependencies.getDependencies(it).find { it.name.startsWith("buildCMake") } + .dependsOn "compile${variant.name.capitalize()}JavaWithJavac" } // as of android gradle plugin 3.0.0-alpha5, generateJsonModel* triggers native build. Java files must be compiled before them. android.buildTypes.all { buildType -> From 3d780c816b5271f0df0cff811e310a294d4df2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 22 Aug 2022 15:12:55 +0200 Subject: [PATCH 13/25] Fix automatic dependency setup --- examples/kotlinExample/build.gradle | 8 ++--- .../src/main/kotlin/io/realm/gradle/Realm.kt | 35 +++++++------------ .../io/realm/gradle/RealmPluginExtension.java | 26 -------------- 3 files changed, 16 insertions(+), 53 deletions(-) diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 851b22e382..0785f52dd2 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -35,11 +35,9 @@ android { // This is added automatically if Kotlin is registered in the project, but Kotlin extension functions // for Realm can be excluded if needed. -// FIXME The automatic dependency handling is somehow ruined with the latest AGP, need to investigate -//realm { -// kotlinExtensionsEnabled = true -//} - +realm { + kotlinExtensionsEnabled = true +} // enable @ParametersAreNonnullByDefault annotation. See https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-1-50-is-out/ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { diff --git a/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt index 0e40b869be..8180e34c8f 100644 --- a/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt +++ b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt @@ -41,26 +41,6 @@ open class Realm : Plugin { val dependencyConfigurationName: String = getDependencyConfigurationName(project) val extension = project.extensions.create("realm", RealmPluginExtension::class.java) - extension.addPropertyListener( - RealmPluginExtension.KEY_KOTLIN_EXTENSIONS_ENABLED - ) { - setDependencies( - project, - dependencyConfigurationName, - extension.isSyncEnabled, - extension.isKotlinExtensionsEnabled - ) - } - extension.addPropertyListener( - RealmPluginExtension.KEY_SYNC_ENABLED - ) { - setDependencies( - project, - dependencyConfigurationName, - extension.isSyncEnabled, - extension.isKotlinExtensionsEnabled - ) - } extension.isKotlinExtensionsEnabled = isKotlinProject if (shouldApplyAndroidAptPlugin( @@ -109,6 +89,14 @@ open class Realm : Plugin { "io.realm:realm-annotations-processor:${Version.VERSION}" ) } + project.afterEvaluate { + setDependencies( + project, + dependencyConfigurationName, + extension.isSyncEnabled, + extension.isKotlinExtensionsEnabled + ) + } } companion object { @@ -164,8 +152,11 @@ open class Realm : Plugin { } // This will setup the required dependencies. - // Due to how Gradle works, we have no choice but to run this code every time any of the parameters - // in the Realm extension is changed. + // This must only be called once, as removal of dependencies through the iterator API will + // not propagate correctly into the IterationOrderRetainingSetElementSource which is backing + // the dependency set deep inside the DefaultDependencySet implementation, and failure to do + // so causes some internal caching in IterationOrderRetainingSetElementSource to skip + // re-adding it if it had already been there once. private fun setDependencies( project: Project, dependencyConfigurationName: String, diff --git a/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java index a83fed35f0..f2b14f4155 100644 --- a/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java +++ b/realm-transformer/src/main/java/io/realm/gradle/RealmPluginExtension.java @@ -22,13 +22,8 @@ import java.util.Map; public class RealmPluginExtension { - - public static final String KEY_SYNC_ENABLED = "syncEnabled"; - public static final String KEY_KOTLIN_EXTENSIONS_ENABLED = "kotlinExtensionsEnabled"; - private boolean syncEnabled; private boolean kotlinExtensionsEnabled; - private Map listeners = new LinkedHashMap<>(); @Input public boolean isSyncEnabled() { @@ -37,7 +32,6 @@ public boolean isSyncEnabled() { public void setSyncEnabled(boolean syncEnabled) { this.syncEnabled = syncEnabled; - notifyChange(KEY_SYNC_ENABLED, syncEnabled); } @Input @@ -47,25 +41,5 @@ public boolean isKotlinExtensionsEnabled() { public void setKotlinExtensionsEnabled(boolean kotlinExtensionsEnabled) { this.kotlinExtensionsEnabled = kotlinExtensionsEnabled; - notifyChange(KEY_KOTLIN_EXTENSIONS_ENABLED, kotlinExtensionsEnabled); - } - - public void addPropertyListener(String property, PropertyChangedListener listener) { - listeners.put(property, listener); - } - - private void notifyChange(String key, Object value) { - PropertyChangedListener listener = listeners.get(key); - if (listener != null) { - // Up to users of the API to use the correct generic type, otherwise it will crash - // at runtime. - //noinspection unchecked - listener.onChange(value); - } - } - - // Callback triggered when the extension property is changed - public interface PropertyChangedListener { - void onChange(T value); } } From f8940ced8b7722557d64d1008089e45d093d3c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 29 Aug 2022 11:12:39 +0200 Subject: [PATCH 14/25] Update to Core 12.5.1 and tests for raw queries with keyword field names (#7711) --- CHANGELOG.md | 5 ++++- .../androidTest/java/io/realm/RealmQueryTests.java | 13 +++++++++++++ realm/realm-library/src/main/cpp/realm-core | 2 +- .../java/io/realm/entities/KeywordFieldNames.java | 10 ++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 realm/realm-library/src/testUtils/java/io/realm/entities/KeywordFieldNames.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a851518a3..c226896ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,16 @@ * None ### Fixed -* None +* Now queries can point to fields with query language-reserved words like 'desc', 'sort', 'distinct', etc. Issue [#7705](https://github.com/realm/realm-java/issues/7705) ### Compatibility * File format: Generates Realms with format v22. Unsynced Realms will be upgraded from Realm Java 2.0 and later. Synced Realms can only be read and upgraded if created with Realm Java v10.0.0-BETA.1. * APIs are backwards compatible with all previous release of realm-java in the 10.6.y series. * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. +### Internal +* Update to Realm Core 12.5.1, commit: 6f6a0f415bd33cf2ced4467e36a47f7c84f0a1d7. + ## 10.11.1 (2022-07-14) diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java index 350cd7257c..55ed6bafa2 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmQueryTests.java @@ -51,6 +51,7 @@ import io.realm.entities.DictionaryAllTypes; import io.realm.entities.Dog; import io.realm.entities.IndexedFields; +import io.realm.entities.KeywordFieldNames; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.NullTypes; @@ -4082,6 +4083,18 @@ public void rawPredicate_invalidFormatOptions() { } } + @Test + public void rawPredicate_reservedKeywords() { + realm.beginTransaction(); + realm.insert(new KeywordFieldNames()); + realm.commitTransaction(); + + realm.where(KeywordFieldNames.class).rawPredicate("desc = $0", "value").findAll(); + realm.where(KeywordFieldNames.class).rawPredicate("limit = $0", "value").findAll(); + realm.where(KeywordFieldNames.class).rawPredicate("sort = $0", "value").findAll(); + realm.where(KeywordFieldNames.class).rawPredicate("distinct = $0", "value").findAll(); + } + @Test public void limit() { populateTestRealm(realm, TEST_DATA_SIZE); diff --git a/realm/realm-library/src/main/cpp/realm-core b/realm/realm-library/src/main/cpp/realm-core index 55a48c287b..6f6a0f415b 160000 --- a/realm/realm-library/src/main/cpp/realm-core +++ b/realm/realm-library/src/main/cpp/realm-core @@ -1 +1 @@ -Subproject commit 55a48c287b5e3a8ca129c257ec7e3b92bcb2a05f +Subproject commit 6f6a0f415bd33cf2ced4467e36a47f7c84f0a1d7 diff --git a/realm/realm-library/src/testUtils/java/io/realm/entities/KeywordFieldNames.java b/realm/realm-library/src/testUtils/java/io/realm/entities/KeywordFieldNames.java new file mode 100644 index 0000000000..b32a7fdedc --- /dev/null +++ b/realm/realm-library/src/testUtils/java/io/realm/entities/KeywordFieldNames.java @@ -0,0 +1,10 @@ +package io.realm.entities; + +import io.realm.RealmObject; + +public class KeywordFieldNames extends RealmObject { + public String desc; + public String sort; + public String distinct; + public String limit; +} \ No newline at end of file From 1af01b70dbeb345e884f92692230f92b8f6e7f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 30 Aug 2022 13:25:47 +0200 Subject: [PATCH 15/25] Strip object server symbols from base artifacts --- .../examples/arch/CustomApplication.java | 8 +- .../io/realm/examples/arch/model/Person.java | 12 +- examples/kotlinExample/build.gradle | 2 - library-build-transformer/build.gradle | 13 +- .../buildtransformer/RealmBuildTransformer.kt | 220 ++++++++++-------- .../asm/visitors/AnnotatedCodeStripVisitor.kt | 8 +- .../asm/visitors/AnnotationVisitor.kt | 3 +- realm/realm-library/build.gradle | 19 +- .../realm/internal/UnmanagedSubscription.java | 2 + .../objectstore/OsMutableSubscriptionSet.java | 3 +- .../internal/objectstore/OsSubscription.java | 2 +- .../objectstore/OsSubscriptionSet.java | 3 +- .../realm/mongodb/sync/SubscriptionSet.java | 1 + 13 files changed, 153 insertions(+), 143 deletions(-) diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java index c05630d53b..b3c8333f70 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/CustomApplication.java @@ -37,19 +37,19 @@ public void onCreate() { @Override public void execute(@NonNull Realm realm) { Person person = realm.createObject(Person.class); - person.name = "Makoto Yamazaki"; + person.setName("Makoto Yamazaki"); person.setAge(32); person = realm.createObject(Person.class); - person.name = "Christian Melchior"; + person.setName("Christian Melchior"); person.setAge(34); person = realm.createObject(Person.class); - person.name = "Chen Mulong"; + person.setName("Chen Mulong"); person.setAge(29); person = realm.createObject(Person.class); - person.name = "Nabil Hachicha"; + person.setName("Nabil Hachicha"); person.setAge(31); } }) diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java index 159558b597..de97eb924b 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/model/Person.java @@ -25,13 +25,13 @@ public class Person extends RealmObject { private int age; -// public String getName() { -// return name; -// } + public String getName() { + return name; + } -// public void setName(String name) { -// this.name = name; -// } + public void setName(String name) { + this.name = name; + } public int getAge() { return age; diff --git a/examples/kotlinExample/build.gradle b/examples/kotlinExample/build.gradle index 0785f52dd2..f4b29cbc0d 100644 --- a/examples/kotlinExample/build.gradle +++ b/examples/kotlinExample/build.gradle @@ -47,7 +47,5 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } dependencies { -// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}" -// implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21" implementation "org.jetbrains.anko:anko-commons:0.10.4" } diff --git a/library-build-transformer/build.gradle b/library-build-transformer/build.gradle index a30fe8753d..5ac9a2b65f 100644 --- a/library-build-transformer/build.gradle +++ b/library-build-transformer/build.gradle @@ -1,6 +1,3 @@ -group 'io.realm' -version '1.0.0' - buildscript { def properties = new Properties() properties.load(new FileInputStream("${projectDir}/../dependencies.list")) @@ -23,6 +20,7 @@ group = 'io.realm' version = file("${projectDir}/../version.txt").text.trim() apply plugin: 'kotlin' +apply plugin: 'java' apply plugin: 'maven-publish' apply from: "${rootDir}/../mavencentral-publications.gradle" @@ -30,19 +28,20 @@ repositories { google() mavenCentral() } - +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../dependencies.list")) dependencies { implementation gradleApi() - compileOnly 'com.android.tools.build:gradle:3.1.1' implementation 'org.ow2.asm:asm:6.2' implementation 'org.ow2.asm:asm-util:6.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" + compileOnly "com.android.tools.build:gradle:${properties.get("GRADLE_BUILD_TOOLS")}" testImplementation group:'junit', name:'junit', version:'4.12' } -sourceCompatibility = '11' -targetCompatibility = '11' +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 compileKotlin { kotlinOptions.jvmTarget = "11" diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt index c213b96fed..d39264033c 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt @@ -15,15 +15,28 @@ */ package io.realm.buildtransformer -import com.android.build.api.transform.* -import com.google.common.collect.ImmutableSet -import io.realm.buildtransformer.asm.ClassPoolTransformer -import io.realm.buildtransformer.ext.packageHierarchyRootDir -import io.realm.buildtransformer.ext.shouldBeDeleted +import com.android.build.api.variant.AndroidComponentsExtension +import io.realm.buildtransformer.asm.visitors.AnnotatedCodeStripVisitor import io.realm.buildtransformer.util.Stopwatch +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File +import java.io.BufferedOutputStream +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream // Type aliases for improving readability typealias ByteCodeTypeDescriptor = String @@ -39,123 +52,124 @@ val logger: Logger = LoggerFactory.getLogger("realm-build-logger") * a specific Android flavour. It is also possible to provide a list of files to delete whether or * not they have the annotation. These files will only be deleted from the defined flavour. */ -class RealmBuildTransformer(private val flavorToStrip: String, - private val annotationQualifiedName: QualifiedName, - private val specificFilesToStrip: Set = setOf()) : Transform() { - - override fun getName(): String { - return "RealmBuildTransformer" - } - - override fun isIncremental(): Boolean { - return true - } - - override fun getScopes(): MutableSet { - return mutableSetOf(QualifiedContent.Scope.PROJECT) - } - - override fun getInputTypes(): Set { - return setOf(QualifiedContent.DefaultContentType.CLASSES) +class RealmBuildTransformer( + private val annotationQualifiedName: Property, + private val input: ListProperty, + private val output: RegularFileProperty, +) { + init { + transform() } - override fun getReferencedScopes(): MutableSet { - return ImmutableSet.of() - } - - override fun transform(context: Context?, - inputs: MutableCollection?, - referencedInputs: MutableCollection?, - outputProvider: TransformOutputProvider?, - isIncremental: Boolean) { - @Suppress("DEPRECATION") - super.transform(context, inputs, referencedInputs, outputProvider, isIncremental) - - // Poor mans version of detecting variants, since the Gradle API does not allow us to - // register a transformer for only a single build variant - // https://issuetracker.google.com/issues/37072849 - val transformClasses: Boolean = context?.variantName?.startsWith(flavorToStrip.toLowerCase()) == true + private fun transform() { + val outputProvider = JarOutputStream( + BufferedOutputStream( + FileOutputStream( + output.get().asFile.absolutePath + ".tmp" + ) + ) + ) val timer = Stopwatch() timer.start("Build Transform time") - if (isIncremental) { - runIncrementalTransform(inputs!!, outputProvider!!, transformClasses) - } else { - runFullTransform(inputs!!, outputProvider!!, transformClasses) - } - timer.stop() - } - private fun runFullTransform(inputs: MutableCollection, outputProvider: TransformOutputProvider, transformClasses: Boolean) { - logger.debug("Run full transform") - val outputDir = outputProvider.getContentLocation("realmlibrarytransformer", outputTypes, scopes, Format.DIRECTORY) - val inputFiles: MutableSet = mutableSetOf() - inputs.forEach { - it.directoryInputs.forEach { - // Non-incremental build: Include all files - val dirPath: String = it.file.absolutePath - it.file.walkTopDown() - .filter { it.isFile } - .filter { it.name.endsWith(".class") } - .forEach { file -> - file.packageHierarchyRootDir = dirPath - file.shouldBeDeleted = transformClasses && specificFilesToStrip.find { file.absolutePath.endsWith(it) } != null - inputFiles.add(file) - } + val annotationDescriptor = createDescriptor(annotationQualifiedName.get()) + val metadataCollector = + io.realm.buildtransformer.asm.visitors.AnnotationVisitor(annotationDescriptor) + + forEachJarEntry { jarEntry, inputStream -> + if (jarEntry.name.endsWith(".class") ) { + inputStream.use { + val classReader = ClassReader(it) + classReader.accept(metadataCollector, 0) + } } } - - transformClassFiles(outputDir, inputFiles, transformClasses) - } - - private fun runIncrementalTransform(inputs: MutableCollection, outputProvider: TransformOutputProvider, transformClasses: Boolean) { - logger.debug("Run incremental transform") - val outputDir = outputProvider.getContentLocation("realmlibrarytransformer", outputTypes, scopes, Format.DIRECTORY) - val inputFiles: MutableSet = mutableSetOf() - inputs.forEach { - it.directoryInputs.forEach iterateDirs@{ - if (!it.file.exists()) { - return@iterateDirs // Directory was deleted + forEachJarEntry { jarEntry, inputStream -> + val bytes = inputStream.use { inputStream -> + if (jarEntry.name.endsWith(".class")) { + val writer = + ClassWriter(0) // We don't modify methods so no reason to re-calculate method frames + val classRemover = AnnotatedCodeStripVisitor( + annotationDescriptor, + metadataCollector.annotatedClasses, + metadataCollector.annotatedMethods, + metadataCollector.annotatedFields, + writer + ) + ClassReader(inputStream).accept(classRemover, 0) + if (classRemover.deleteClass) ByteArray(0) else writer.toByteArray() + } else { + inputStream.readBytes() } - val dirPath: String = it.file.absolutePath - it.changedFiles.entries - .filter { it.key.isFile } - .filter { it.key.name.endsWith(".class") } - .filterNot { it.value == Status.REMOVED } - .forEach { - val file: File = it.key - file.packageHierarchyRootDir = dirPath - file.shouldBeDeleted = transformClasses && specificFilesToStrip.find { file.absolutePath.endsWith(it) } != null - inputFiles.add(file) - } + } + if (bytes.isNotEmpty()) { + outputProvider.putNextEntry(JarEntry(jarEntry.name)) + outputProvider.write(bytes) + outputProvider.closeEntry() } } - - transformClassFiles(outputDir, inputFiles, transformClasses) + outputProvider.close() + FileInputStream( output.get().asFile.absolutePath + ".tmp" ).channel.use { input -> + FileOutputStream(this.output.asFile.get().absoluteFile).channel.use { output -> + output.transferFrom(input, 0, input.size()) + } + } + timer.stop() } - private fun transformClassFiles(outputDir: File, inputFiles: MutableSet, transformClasses: Boolean) { - val files: Set = if (transformClasses) { - val transformer = ClassPoolTransformer(annotationQualifiedName, inputFiles) - transformer.transform() - } else { - inputFiles + private fun forEachJarEntry(block: (jarEntry: JarEntry, inputStream: InputStream) -> Unit) { + val jarFiles: List = input.get().map { JarFile(it.asFile) } + jarFiles.forEach { jarFile -> + jarFile.entries().toList().map { + block(it, jarFile.getInputStream(it)) + } } + } - copyToOutput(outputDir, files) + private fun createDescriptor(qualifiedName: String): String { + return "L${qualifiedName.replace(".", "/")};" } - private fun copyToOutput(outputDir: File, files: Set) { - files.forEach { - val outputFile = File(outputDir, it.absolutePath.substring(it.packageHierarchyRootDir.length)) - if (it.shouldBeDeleted) { - if (outputFile.exists()) { - outputFile.delete() + companion object { + fun register(project: Project, flavorToStrip: String, annotationQualifiedName: QualifiedName) { + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + androidComponents.onVariants { variant -> + if (variant.name.startsWith(flavorToStrip)) { + val taskProvider = project.tasks.register( + "${variant.name}RealmBuildTransformer", + ModifyClassesTask::class.java + ) { + it.annotationQualifiedName.set(annotationQualifiedName) + } + variant.artifacts.forScope(com.android.build.api.variant.ScopedArtifacts.Scope.PROJECT) + .use(taskProvider) + .toTransform( + com.android.build.api.artifact.ScopedArtifact.CLASSES, + ModifyClassesTask::allJars, + ModifyClassesTask::allDirectories, + ModifyClassesTask::output + ) } - } else { - it.copyTo(outputFile, overwrite = true) } } } +} +abstract class ModifyClassesTask: DefaultTask() { + @get:InputFiles + abstract val allJars: ListProperty + + @get:InputFiles + abstract val allDirectories: ListProperty + @get:OutputFiles + abstract val output: RegularFileProperty + + @get:Input + abstract val annotationQualifiedName : Property + + @TaskAction + fun taskAction() { + RealmBuildTransformer(annotationQualifiedName, allJars, output) + } } diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt index 049aa489f8..64dcf85c1f 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotatedCodeStripVisitor.kt @@ -30,7 +30,7 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, private val markedClasses: Set, private val markedMethods: Map>, private val markedFields: Map>, - classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM7_EXPERIMENTAL, classWriter) { + classWriter: ClassVisitor) : ClassVisitor(Opcodes.ASM7, classWriter) { var deleteClass: Boolean = false private lateinit var markedMethodsInClass: Set @@ -51,7 +51,7 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, // Remove INNERCLASS definitions from the bytecode in the top level class. It isn't clear if // these are used by any relevant API's, but better remove them just in case. override fun visitInnerClass(name: String?, outerName: String?, innerName: String?, access: Int) { - if (!markedClasses.contains(name)) { + if (!markedClasses.contains(name) && !deleteClass) { super.visitInnerClass(name, outerName, innerName, access) } else { logger.debug("Removing inner class description: $name") @@ -59,7 +59,7 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, } override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor? { - return if (!markedFieldsInClass.contains(name)) { + return if (!markedFieldsInClass.contains(name) && !deleteClass) { super.visitField(access, name, descriptor, signature, value) } else { logger.debug("Removing field: $name") @@ -68,7 +68,7 @@ class AnnotatedCodeStripVisitor(private val annotationDescriptor: String, } override fun visitMethod(access: Int, name: ByteCodeMethodName?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor? { - return if (!markedMethodsInClass.contains(name + descriptor)) { + return if (!markedMethodsInClass.contains(name + descriptor) && !deleteClass) { super.visitMethod(access, name, descriptor, signature, exceptions) } else { logger.debug("Removing method: $name $descriptor") diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt index ee1e7252d7..43bcce4d14 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/asm/visitors/AnnotationVisitor.kt @@ -18,7 +18,6 @@ package io.realm.buildtransformer.asm.visitors; import io.realm.buildtransformer.ByteCodeMethodName import io.realm.buildtransformer.ByteCodeTypeDescriptor import io.realm.buildtransformer.FieldName -import io.realm.buildtransformer.logger import org.objectweb.asm.* import org.objectweb.asm.AnnotationVisitor @@ -27,7 +26,7 @@ import org.objectweb.asm.AnnotationVisitor * pass and is required for correctly identifying them in the 2nd pass before any byte code is * written. */ -class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor(Opcodes.ASM7_EXPERIMENTAL) { +class AnnotationVisitor(private val annotationDescriptor: String) : ClassVisitor(Opcodes.ASM7) { val annotatedClasses: MutableSet = mutableSetOf() val annotatedMethods: MutableMap> = mutableMapOf() diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index 38651458c8..f5a1be13db 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -154,18 +154,13 @@ android { } io.realm.transformer.RealmTransformer.@Companion.register(project) -// TODO -//android.registerTransform(new io.realm.buildtransformer.RealmBuildTransformer( -// "base", -// "io.realm.internal.annotations.ObjectServer", -// [ -// "io_realm_sync_permissions_ClassPermissionsRealmProxyInterface.class", -// "io_realm_sync_permissions_PermissionRealmProxyInterface.class", -// "io_realm_sync_permissions_PermissionUserRealmProxyInterface.class", -// "io_realm_sync_permissions_RealmPermissionsRealmProxyInterface.class", -// "io_realm_sync_permissions_RoleRealmProxyInterface.class" -// ].toSet() -//)) +// TODO RealmBuildTransformer only supports stripping symbols from Jars so must be applied +// after the accessor transformer (that combines all inputs into one common Jar) +io.realm.buildtransformer.RealmBuildTransformer.@Companion.register( + project, + "base", + "io.realm.internal.annotations.ObjectServer", +) project.afterEvaluate { tasks.withType(JavaCompile) { diff --git a/realm/realm-library/src/main/java/io/realm/internal/UnmanagedSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/UnmanagedSubscription.java index 3f4cee09c9..c2b8848aa0 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/UnmanagedSubscription.java +++ b/realm/realm-library/src/main/java/io/realm/internal/UnmanagedSubscription.java @@ -20,11 +20,13 @@ import javax.annotation.Nullable; import io.realm.RealmQuery; +import io.realm.internal.annotations.ObjectServer; import io.realm.mongodb.sync.Subscription; /** * Class that handles unmanaged subscriptions. Required as we need to track a realm query ptr. */ +@ObjectServer public class UnmanagedSubscription implements Subscription { private final Date createdAt; diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java index 2332c6c36c..3092a6fbe1 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java @@ -18,12 +18,13 @@ import io.realm.RealmModel; import io.realm.internal.RealmProxyMediator; import io.realm.internal.UnmanagedSubscription; +import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.mongodb.sync.MutableSubscriptionSet; import io.realm.mongodb.sync.Subscription; // TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. -//@ObjectServer +@ObjectServer public class OsMutableSubscriptionSet extends OsSubscriptionSet implements MutableSubscriptionSet { public OsMutableSubscriptionSet(long nativePtr, diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java index 67bce6ff58..a23b8c034d 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java @@ -22,7 +22,7 @@ import io.realm.mongodb.sync.Subscription; // TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. -//@ObjectServer +@ObjectServer public class OsSubscription implements NativeObject, Subscription { private static final long nativeFinalizerPtr = nativeGetFinalizerMethodPtr(); diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java index ad4561b45f..7962960934 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java @@ -31,13 +31,14 @@ import io.realm.RealmQuery; import io.realm.internal.NativeObject; import io.realm.internal.RealmProxyMediator; +import io.realm.internal.annotations.ObjectServer; import io.realm.internal.async.RealmAsyncTaskImpl; import io.realm.internal.async.RealmThreadPoolExecutor; import io.realm.mongodb.sync.Subscription; import io.realm.mongodb.sync.SubscriptionSet; // TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. -//@ObjectServer +@ObjectServer public class OsSubscriptionSet implements NativeObject, SubscriptionSet { public static final byte STATE_VALUE_UNCOMMITTED = 0; diff --git a/realm/realm-library/src/main/java/io/realm/mongodb/sync/SubscriptionSet.java b/realm/realm-library/src/main/java/io/realm/mongodb/sync/SubscriptionSet.java index ba7266190a..6930206661 100644 --- a/realm/realm-library/src/main/java/io/realm/mongodb/sync/SubscriptionSet.java +++ b/realm/realm-library/src/main/java/io/realm/mongodb/sync/SubscriptionSet.java @@ -36,6 +36,7 @@ */ @Beta @Keep +@ObjectServer public interface SubscriptionSet extends Iterable { /** From 58a791eae35952f6a19bdd5dfe6e934d44ac08a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 30 Aug 2022 14:19:08 +0200 Subject: [PATCH 16/25] Clean up --- .../main/java/io/realm/examples/arch/PersonFragment.java | 3 +-- gradle-plugin/build.gradle | 2 +- .../kotlin/io/realm/transformer/build/BuildTemplate.kt | 7 ------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java index aaacd57267..194553cae3 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonFragment.java @@ -64,8 +64,7 @@ public T create(@NonNull Class modelClass) { personViewModel.getPerson().observe(this, person -> { if (person != null) { // null would mean the object was deleted. -// name.setText(person.getName()); - name.setText(person.name); + name.setText(person.getName()); age.setText(String.valueOf(person.getAge())); } }); diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index e79531945b..4f82390038 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -90,7 +90,7 @@ task generateVersionClass(type: Copy) { sourceSets { main { java { - srcDirs += ['build/generated-src/main/kotlin', 'src/main/kotlin'] + srcDirs += ['build/generated-src/main/kotlin'] } } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt index ce3548e22e..8cf1e596c4 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/build/BuildTemplate.kt @@ -120,8 +120,6 @@ abstract class BuildTemplate(private val metadata: ProjectMetaData, private val fun copyProcessedClasses() { for ((fqname: String, clazz: CtClass) in processedClasses) { - logger.debug("Adding class: $fqname into output Jar") - outputProvider.putNextEntry(JarEntry("${fqname.replace('.', '/')}.class")) outputProvider.write(clazz.toBytecode()) outputProvider.closeEntry() @@ -133,10 +131,7 @@ abstract class BuildTemplate(private val metadata: ProjectMetaData, private val val dirName = directory.asFile.absolutePath + File.separator directory.asFile.walk().filter(File::isFile).forEach { file -> if (!file.absolutePath.endsWith(DOT_CLASS)) { - println("TRANSFORMER>>JarFile : " + file.absolutePath) val removePrefix = file.absolutePath.removePrefix(dirName) - println("TRANSFORMER>>JarFile : " + removePrefix) - println("TRANSFORMER>>Adding from jar $removePrefix") outputProvider.putNextEntry(JarEntry(removePrefix)) outputProvider.write(file.readBytes()) outputProvider.closeEntry() @@ -144,10 +139,8 @@ abstract class BuildTemplate(private val metadata: ProjectMetaData, private val } } allJars.get().forEach { file -> - println("TRANSFORMER>>JarFile : " + file.asFile.absolutePath) val jarFile = JarFile(file.asFile) for (jarEntry: JarEntry in jarFile.entries()) { - println("TRANSFORMER>>Adding from jar ${jarEntry.name}") outputProvider.putNextEntry(JarEntry(jarEntry.name)) jarFile.getInputStream(jarEntry).use { outputProvider.write(it.readBytes()) From 8f17c241864b0028169c0d8797060bfca10340de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 30 Aug 2022 14:33:03 +0200 Subject: [PATCH 17/25] Update version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 593914ca00..5d91297a1a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -10.12.0-SNAPSHOT +10.12.0-transformer-api-SNAPSHOT From eae54a1ba1136326a82eeb994dcbe4b16e95ff65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 31 Aug 2022 14:57:04 +0200 Subject: [PATCH 18/25] Add CHANGELOG entry --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb130c15eb..4740d4131a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -## 10.12.0 (YYYY-MM-DD) +## 10.12.0-transformer-api (YYYY-MM-DD) + +### Breaking Changes +* Only works with Android Gradle Plugin 7.4 or newer. (Issue [#7714](https://github.com/realm/realm-java/issues/7714)) ### Enhancements * [RealmApp] Introduced `SyncSession.RecoverOrDiscardUnsyncedChangesStrategy`, an alternative automatic client reset strategy that tries to automatically recover any unsynced data from the client, and discards any unsynced data if not possible. This is now the default client reset policy if not explicitly set in the `SyncConfiguration`. From 98c6bbe3cc18e6c0edde7c5674f2b8a4d89baa9a Mon Sep 17 00:00:00 2001 From: Sergey Gerasimenko Date: Tue, 6 Sep 2022 20:55:08 +0300 Subject: [PATCH 19/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b93b98b8cd..069b0561f6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,6 @@ contact_links: - name: General Questions and Inquiries url: https://www.mongodb.com/community/forums/tags/c/realm-sdks/58/java about: Please ask general design/architecture questions in the community forums. - - name: MongoDB Realm (Sync) Production Issues + - name: MongoDB Device Sync Production Issues url: https://support.mongodb.com/ about: Please report urgent production issues to the support portal directly. From 77225f11d876a8d6d8b18c7db4cfbc7430fd1d7b Mon Sep 17 00:00:00 2001 From: Sergey Gerasimenko Date: Tue, 6 Sep 2022 20:58:30 +0300 Subject: [PATCH 20/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 069b0561f6..07a7ca0328 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,6 @@ contact_links: - name: General Questions and Inquiries url: https://www.mongodb.com/community/forums/tags/c/realm-sdks/58/java about: Please ask general design/architecture questions in the community forums. - - name: MongoDB Device Sync Production Issues + - name: MongoDB Atals Device Sync Production Issues url: https://support.mongodb.com/ about: Please report urgent production issues to the support portal directly. From a4f034ba9e609292d38d7803326c3cd4ebd9670c Mon Sep 17 00:00:00 2001 From: Sergey Gerasimenko Date: Tue, 6 Sep 2022 20:58:45 +0300 Subject: [PATCH 21/25] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 07a7ca0328..55bfd39c93 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,6 @@ contact_links: - name: General Questions and Inquiries url: https://www.mongodb.com/community/forums/tags/c/realm-sdks/58/java about: Please ask general design/architecture questions in the community forums. - - name: MongoDB Atals Device Sync Production Issues + - name: MongoDB Atlas Device Sync Production Issues url: https://support.mongodb.com/ about: Please report urgent production issues to the support portal directly. From 143fe750cd39b639f25d6f17414c43887dea6b0a Mon Sep 17 00:00:00 2001 From: clementetb Date: Wed, 7 Sep 2022 13:53:38 +0200 Subject: [PATCH 22/25] Bump core 1.6.0 (#7717) --- CHANGELOG.md | 2 +- dependencies.list | 2 +- .../src/main/cpp/io_realm_internal_OsList.cpp | 2 +- .../src/main/cpp/io_realm_internal_OsResults.cpp | 2 +- .../src/main/cpp/io_realm_mongodb_sync_Sync.cpp | 10 +++++++++- .../src/main/cpp/observable_collection_wrapper.hpp | 12 +----------- .../src/main/cpp/observable_dictionary_wrapper.hpp | 12 +----------- realm/realm-library/src/main/cpp/realm-core | 2 +- .../java/io/realm/mongodb/ErrorCode.java | 1 + 9 files changed, 17 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c226896ae1..eb742c1387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ * Realm Studio 11.0.0-alpha.0 or above is required to open Realms created by this version. ### Internal -* Update to Realm Core 12.5.1, commit: 6f6a0f415bd33cf2ced4467e36a47f7c84f0a1d7. +* Update to Realm Core 12.6.0, commit: 5da7744b4056ad185c025bccf0924f17f73f7a91. ## 10.11.1 (2022-07-14) diff --git a/dependencies.list b/dependencies.list index 3d08953f21..a177ae9db7 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,6 +1,6 @@ # Realm Core release used by Realm Java # https://github.com/realm/realm-core/releases -REALM_CORE=12.3.0 +REALM_CORE=12.6.0 # Version of MongoDB Realm used by integration tests # See https://github.com/realm/ci/packages/147854 for available versions diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp index 568c9aae6c..065ca6d863 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsList.cpp @@ -605,7 +605,7 @@ JNIEXPORT jobject JNICALL Java_io_realm_internal_OsList_nativeGetValue(JNIEnv* e try { auto& wrapper = *reinterpret_cast(list_ptr); JavaAccessorContext context(env); - return any_cast(wrapper.collection().get(context, pos)); + return util::any_cast(wrapper.collection().get(context, pos)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp index 0ae7d8463e..a3b03bfefa 100644 --- a/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_internal_OsResults.cpp @@ -536,7 +536,7 @@ Java_io_realm_internal_OsResults_nativeGetValue(JNIEnv* env, jclass, jlong nativ try { auto& wrapper = *reinterpret_cast(native_ptr); JavaAccessorContext context(env); - return any_cast(wrapper.collection().get(context, pos)); + return util::any_cast(wrapper.collection().get(context, pos)); } CATCH_STD() diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp index 01bef7dc09..909c9b313b 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_Sync.cpp @@ -65,7 +65,15 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_Sync_nativeSimulateSyncError(J type == "realm::sync::ProtocolError" ? realm::sync::protocol_error_category() : realm::sync::client_error_category() }; - SyncSession::OnlyForTesting::handle_error(*session, {code, std::string(message), to_bool(is_fatal)}); + + SyncError sync_error( + code, + std::string(message), + to_bool(is_fatal) + ); + sync_error.server_requests_action = sync::ProtocolErrorInfo::Action::ClientReset; + + SyncSession::OnlyForTesting::handle_error(*session, sync_error); } CATCH_STD() } diff --git a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp index b73d7ccaa2..0d3a1620b7 100644 --- a/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_collection_wrapper.hpp @@ -86,21 +86,11 @@ void ObservableCollectionWrapper::start_listening(JNIEnv* env, jobject j_coll m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_collection_object); } - auto cb = [=](CollectionChangeSet const& changes, std::exception_ptr err) { + auto cb = [=](CollectionChangeSet const& changes) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - realm::jni_util::Log::e("Caught exception in collection change callback %1", e.what()); - return; - } - } - m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { local_env->CallVoidMethod( collection_obj, notify_change_listeners, diff --git a/realm/realm-library/src/main/cpp/observable_dictionary_wrapper.hpp b/realm/realm-library/src/main/cpp/observable_dictionary_wrapper.hpp index 16704fa2f3..23430fd125 100644 --- a/realm/realm-library/src/main/cpp/observable_dictionary_wrapper.hpp +++ b/realm/realm-library/src/main/cpp/observable_dictionary_wrapper.hpp @@ -70,21 +70,11 @@ void ObservableDictionaryWrapper::start_listening(JNIEnv* env, jobject j_observa m_collection_weak_ref = jni_util::JavaGlobalWeakRef(env, j_observable_map); } - auto cb = [=](DictionaryChangeSet changes, std::exception_ptr err) { + auto cb = [=](DictionaryChangeSet changes) { // OS will call all notifiers' callback in one run, so check the Java exception first!! if (env->ExceptionCheck()) return; - if (err) { - try { - std::rethrow_exception(err); - } - catch (const std::exception& e) { - realm::jni_util::Log::e("Caught exception in dictionary change callback %1", e.what()); - return; - } - } - m_collection_weak_ref.call_with_local_ref(env, [&](JNIEnv* local_env, jobject collection_obj) { bool changes_empty = changes.deletions.empty() && changes.insertions.empty() && diff --git a/realm/realm-library/src/main/cpp/realm-core b/realm/realm-library/src/main/cpp/realm-core index 6f6a0f415b..5da7744b40 160000 --- a/realm/realm-library/src/main/cpp/realm-core +++ b/realm/realm-library/src/main/cpp/realm-core @@ -1 +1 @@ -Subproject commit 6f6a0f415bd33cf2ced4467e36a47f7c84f0a1d7 +Subproject commit 5da7744b4056ad185c025bccf0924f17f73f7a91 diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java index f848addea0..02bf8275ae 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/ErrorCode.java @@ -110,6 +110,7 @@ public enum ErrorCode { SERVER_PERMISSIONS_CHANGED(Type.PROTOCOL, 228), // Server permissions for this file ident have changed since the last time it was used (IDENT) INITIAL_SYNC_NOT_COMPLETE(Type.PROTOCOL, 229), // Client tried to open a session before initial sync is complete (BIND) WRITE_NOT_ALLOWED(Type.PROTOCOL, 230), // Client attempted a write that is disallowed by permissions, or modifies an object outside the current query - requires client reset (UPLOAD) + COMPENSATING_WRITE(Type.PROTOCOL, 231), // Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change // Sync Network Client errors. // See https://github.com/realm/realm-core/blob/master/src/realm/sync/client_base.hpp#L73 From 64a7000af0d95d88bdaf8bfe4eb20fd0a6cf8875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 8 Sep 2022 11:12:38 +0200 Subject: [PATCH 23/25] Remove export compliance clause --- LICENSE | 31 ++----------------------------- README.md | 4 ---- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/LICENSE b/LICENSE index e163ae2f84..f13a843379 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,3 @@ -TABLE OF CONTENTS - -1. Apache License version 2.0 -2. Export Compliance - -1. ------------------------------------------------------------------------------- - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -180,25 +173,5 @@ TABLE OF CONTENTS incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. -2. ------------------------------------------------------------------------------- - -EXPORT COMPLIANCE - -You understand that the Software may contain cryptographic functions that may be -subject to export restrictions, and you represent and warrant that you are not -(i) located in a jurisdiction that is subject to United States economic -sanctions (“Prohibited Jurisdiction”), including Cuba, Iran, North Korea, -Sudan, Syria or the Crimea region, (ii) a person listed on any U.S. government -blacklist (to include the List of Specially Designated Nationals and Blocked -Persons or the Consolidated Sanctions List administered by the U.S. Department -of the Treasury’s Office of Foreign Assets Control, or the Denied Persons List -or Entity List administered by the U.S. Department of Commerce) -(“Sanctioned Person”), or (iii) controlled or 50% or more owned by a Sanctioned -Person. - -You agree to comply with all export, re-export and import restrictions and -regulations of the U.S. Department of Commerce or other agency or authority of -the United States or other applicable countries. You also agree not to transfer, -or authorize the transfer of, directly or indirectly, of the Software to any -Prohibited Jurisdiction, or otherwise in violation of any such restrictions or -regulations. + END OF TERMS AND CONDITIONS + \ No newline at end of file diff --git a/README.md b/README.md index 3b97135083..13b1546a9e 100644 --- a/README.md +++ b/README.md @@ -288,10 +288,6 @@ Realm Java is published under the Apache 2.0 license. Realm Core is also published under the Apache 2.0 license and is available [here](https://github.com/realm/realm-core). -**This product is not being made available to any person located in Cuba, Iran, -North Korea, Sudan, Syria or the Crimea region, or to any other person that is -not eligible to receive the product under U.S. law.** - ## Feedback **_If you use Realm and are happy with it, all we ask is that you, please consider sending out a tweet mentioning [@realm](http://twitter.com/realm) to share your thoughts!_** From b057cab924f94ba3d511956ae00fbf896392adeb Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Tue, 13 Sep 2022 15:00:41 +0200 Subject: [PATCH 24/25] Refactor EmailPasswordAuth tests to be less flaky. (#7723) --- Jenkinsfile | 7 +- .../kotlin/io/realm/EmailPasswordAuthTests.kt | 409 +++++++++--------- .../kotlin/io/realm/admin/ServerAdmin.kt | 77 ---- .../io/realm/mongodb/sync/SyncedRealmTests.kt | 18 +- .../io/realm/SyncedRealmIntegrationTests.kt | 12 +- .../testUtils/java/io/realm/TestHelper.java | 2 +- .../sync_test_server/app_config_generator.sh | 90 +++- .../auth_providers/local-userpass.json | 11 +- .../functions/canReadPartition/config.json | 6 + .../functions/canReadPartition/source.js | 12 + .../functions/canWritePartition/config.json | 6 + .../functions/canWritePartition/source.js | 12 + .../functions/confirmFunc/source.js | 36 +- .../functions/testAuthFunc/source.js | 4 +- tools/sync_test_server/start_local_server.sh | 5 +- tools/sync_test_server/start_server.sh | 5 +- 16 files changed, 381 insertions(+), 331 deletions(-) create mode 100644 tools/sync_test_server/app_template/functions/canReadPartition/config.json create mode 100644 tools/sync_test_server/app_template/functions/canReadPartition/source.js create mode 100644 tools/sync_test_server/app_template/functions/canWritePartition/config.json create mode 100644 tools/sync_test_server/app_template/functions/canWritePartition/source.js diff --git a/Jenkinsfile b/Jenkinsfile index 9ee6fd487d..967962da09 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -125,8 +125,9 @@ try { [$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'realm-kotlin-baas-aws-credentials', accessKeyVariable: 'BAAS_AWS_ACCESS_KEY_ID', secretKeyVariable: 'BAAS_AWS_SECRET_ACCESS_KEY'] ]) { def tempDir = runCommand('mktemp -d -t app_config.XXXXXXXXXX') - sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition testapp1 testapp2" - sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template flex testapp3" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition auto testapp1" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template partition email testapp2" + sh "tools/sync_test_server/app_config_generator.sh ${tempDir} tools/sync_test_server/app_template flex function testapp3" sh "docker network create ${dockerNetworkId}" mongoDbRealmContainer = mdbRealmImage.run("--network ${dockerNetworkId} -v$tempDir:/apps -e AWS_ACCESS_KEY_ID='$BAAS_AWS_ACCESS_KEY_ID' -e AWS_SECRET_ACCESS_KEY='$BAAS_AWS_SECRET_ACCESS_KEY'") mongoDbRealmCommandServerContainer = commandServerEnv.run("--network container:${mongoDbRealmContainer.id} -v$tempDir:/apps") @@ -162,7 +163,7 @@ try { sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device - sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" + sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 -memory 2048 &" try { runBuild(buildFlags, instrumentationTestTarget) } finally { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt index 887e722b63..441bd3c8e6 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/EmailPasswordAuthTests.kt @@ -32,16 +32,19 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith +import io.realm.TEST_APP_1 +import io.realm.TEST_APP_2 +import io.realm.TEST_APP_3 +import kotlin.random.Random -@RunWith(AndroidJUnit4::class) -class EmailPasswordAuthTests { +abstract class EmailPasswordAuthTests { - private val looperThread = BlockingLooperThread() - private lateinit var app: TestApp - private lateinit var admin: ServerAdmin + protected val looperThread = BlockingLooperThread() + protected lateinit var app: TestApp + protected lateinit var admin: ServerAdmin // Callback use to verify that an Illegal Argument was thrown from async methods - private val checkNullArgCallback = App.Callback { result -> + protected val checkNullArgCallback = App.Callback { result -> if (result.isSuccess) { fail() } else { @@ -51,7 +54,7 @@ class EmailPasswordAuthTests { } // Methods exposed by the EmailPasswordAuthProvider - enum class Method { + protected enum class Method { REGISTER_USER, CONFIRM_USER, RESEND_CONFIRMATION_EMAIL, @@ -60,11 +63,15 @@ class EmailPasswordAuthTests { RETRY_CUSTOM_CONFIRMATION, RESET_PASSWORD } +} + +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithAutoConfirm: EmailPasswordAuthTests() { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - app = TestApp() + app = TestApp(appName = TEST_APP_1) RealmLog.setLevel(LogLevel.DEBUG) admin = ServerAdmin(app) admin.deleteAllUsers() @@ -72,9 +79,7 @@ class EmailPasswordAuthTests { @After fun tearDown() { - if (this::app.isInitialized) { - app.close() - } + app.close() RealmLog.setLevel(LogLevel.WARN) } @@ -193,200 +198,6 @@ class EmailPasswordAuthTests { provider.confirmUserAsync("token", TestHelper.getNull(), checkNullArgCallback) } } - - @Test - fun resendConfirmationEmail() { - // We only test that the server successfully accepts the request. We have no way of knowing - // if the Email was actually sent. - // FIXME: Figure out a way to check if this actually happened. Perhaps a custom SMTP server? - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - provider.resendConfirmationEmail(email) - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmailAsync() { - // We only test that the server successfully accepts the request. We have no way of knowing - // if the Email was actually sent. - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - looperThread.runBlocking { - val provider = app.emailPassword - provider.registerUser(email, "123456") - provider.resendConfirmationEmailAsync(email) { result -> - when (result.isSuccess) { - true -> looperThread.testComplete() - false -> fail(result.error.toString()) - } - } - } - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmail_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - try { - provider.resendConfirmationEmail("foo") - fail() - } catch (error: AppException) { - assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmailAsync_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - try { - looperThread.runBlocking { - provider.resendConfirmationEmailAsync("foo") { result -> - if (result.isSuccess) { - fail() - } else { - assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) - looperThread.testComplete() - } - } - } - } finally { - admin.setAutomaticConfirmation(true) - } - } - - @Test - fun resendConfirmationEmail_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPassword - assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } - looperThread.runBlocking { - provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) - } - } - - @Test - fun retryCustomConfirmation() { - val email = "test_realm_tests_do_autoverify@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - provider.retryCustomConfirmation(email) - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_failConfirmation() { - // Only emails containing realm_tests_do_autoverify will be confirmed - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - try { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - val exception = assertFailsWith { - provider.retryCustomConfirmation(email) - } - - assertEquals("failed to confirm user test@10gen.com", exception.errorMessage) - - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmationAsync() { - val email = "test_realm_tests_do_autoverify@10gen.com" - admin.setAutomaticConfirmation(false) - try { - looperThread.runBlocking { - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - provider.retryCustomConfirmationAsync(email) { result -> - when (result.isSuccess) { - true -> looperThread.testComplete() - false -> fail(result.error.toString()) - } - } - } - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - - try { - provider.retryCustomConfirmation("foo") - fail() - } catch (error: AppException) { - assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmationAsync_invalidServerArgsThrows() { - val email = "test@10gen.com" - admin.setAutomaticConfirmation(false) - val provider = app.emailPassword - provider.registerUser(email, "123456") - admin.setCustomConfirmation(true) - try { - looperThread.runBlocking { - provider.retryCustomConfirmationAsync("foo") { result -> - if (result.isSuccess) { - fail() - } else { - assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) - looperThread.testComplete() - } - } - } - } finally { - admin.setCustomConfirmation(false) - } - } - - @Test - fun retryCustomConfirmation_invalidArgumentsThrows() { - val provider: EmailPasswordAuth = app.emailPassword - assertFailsWith { provider.retryCustomConfirmation(TestHelper.getNull()) } - looperThread.runBlocking { - provider.retryCustomConfirmationAsync(TestHelper.getNull(), checkNullArgCallback) - } - } - @Test fun sendResetPasswordEmail() { val provider = app.emailPassword @@ -470,8 +281,8 @@ class EmailPasswordAuthTests { try { looperThread.runBlocking { provider.callResetPasswordFunctionAsync(email, - "new-password", - arrayOf("say-the-magic-word", 42)) { result -> + "new-password", + arrayOf("say-the-magic-word", 42)) { result -> if (result.isSuccess) { val user = app.login(Credentials.emailPassword(email, "new-password")) user.logOut() @@ -510,9 +321,9 @@ class EmailPasswordAuthTests { try { looperThread.runBlocking { provider.callResetPasswordFunctionAsync( - email, - "new-password", - arrayOf("wrong-magic-word")) { result -> + email, + "new-password", + arrayOf("wrong-magic-word")) { result -> if (result.isSuccess) { fail() } else { @@ -639,3 +450,181 @@ class EmailPasswordAuthTests { } } +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithEmailConfirmTests: EmailPasswordAuthTests() { + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp(appName = TEST_APP_2) + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin(app) + admin.deleteAllUsers() + } + + @After + fun tearDown() { + app.close() + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + fun resendConfirmationEmail() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + // TODO: Figure out a way to check if this actually happened. Perhaps a custom SMTP server? + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.resendConfirmationEmail(email) + } + + @Test + fun resendConfirmationEmailAsync() { + // We only test that the server successfully accepts the request. We have no way of knowing + // if the Email was actually sent. + val email = "test@10gen.com" + looperThread.runBlocking { + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.resendConfirmationEmailAsync(email) { result -> + when (result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } + + @Test + fun resendConfirmationEmail_invalidServerArgsThrows() { + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + try { + provider.resendConfirmationEmail("foo") + fail() + } catch (error: AppException) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } + } + + @Test + fun resendConfirmationEmailAsync_invalidServerArgsThrows() { + val email = "test@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + looperThread.runBlocking { + provider.resendConfirmationEmailAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun resendConfirmationEmail_invalidArgumentsThrows() { + val provider: EmailPasswordAuth = app.emailPassword + assertFailsWith { provider.resendConfirmationEmail(TestHelper.getNull()) } + looperThread.runBlocking { + provider.resendConfirmationEmailAsync(TestHelper.getNull(), checkNullArgCallback) + } + } +} + +@RunWith(AndroidJUnit4::class) +class EmailPasswordAuthWithCustomFunctionConfirmTests: EmailPasswordAuthTests() { + @Before + fun setUp() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp(appName = TEST_APP_3) + RealmLog.setLevel(LogLevel.DEBUG) + admin = ServerAdmin(app) + admin.deleteAllUsers() + } + + @After + fun tearDown() { + app.close() + RealmLog.setLevel(LogLevel.WARN) + } + + @Test + fun retryCustomConfirmation() { + val email = "test_realm_pending_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.retryCustomConfirmation(email) + } + + @Test + fun retryCustomConfirmation_failConfirmation() { + // Only emails containing realm_tests_do_autoverify or @10gen.com will be confirmed + val email = "test_only_realm_pending_${Random.nextLong()}@mongodb.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + val exception = assertFailsWith { + provider.retryCustomConfirmation(email) + } + assertEquals("failed to confirm user $email", exception.errorMessage) + } + + @Test + fun retryCustomConfirmationAsync() { + // First call to register will move any email with `realm_pending` into `Pending`. Next + // call will register it. + val email = "test_realm_pending_${Random.nextLong()}@10gen.com" + looperThread.runBlocking { + val provider = app.emailPassword + provider.registerUser(email, "123456") + provider.retryCustomConfirmationAsync(email) { result -> + when (result.isSuccess) { + true -> looperThread.testComplete() + false -> fail(result.error.toString()) + } + } + } + } + + @Test + fun retryCustomConfirmation_invalidServerArgsThrows() { + val email = "test_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + try { + provider.retryCustomConfirmation("foo") + fail() + } catch (error: AppException) { + assertEquals(ErrorCode.USER_NOT_FOUND, error.errorCode) + } + } + + @Test + fun retryCustomConfirmationAsync_invalidServerArgsThrows() { + val email = "test_${Random.nextLong()}@10gen.com" + val provider = app.emailPassword + provider.registerUser(email, "123456") + looperThread.runBlocking { + provider.retryCustomConfirmationAsync("foo") { result -> + if (result.isSuccess) { + fail() + } else { + assertEquals(ErrorCode.USER_NOT_FOUND, result.error.errorCode) + looperThread.testComplete() + } + } + } + } + + @Test + fun retryCustomConfirmation_invalidArgumentsThrows() { + val provider: EmailPasswordAuth = app.emailPassword + assertFailsWith { provider.retryCustomConfirmation(TestHelper.getNull()) } + looperThread.runBlocking { + provider.retryCustomConfirmationAsync(TestHelper.getNull(), checkNullArgCallback) + } + } +} diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index cf98a2c2d1..638597ff7b 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -97,81 +97,6 @@ class ServerAdmin(private val app: App) { throw IllegalArgumentException("Could not find app: $") } - /** - * Toggle whether or not automatic confirmation of new users are enabled. - */ - fun setAutomaticConfirmation(enabled: Boolean) { - val providerId: String = getLocalUserPassProviderId() - val url = "$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId" - var request = Request.Builder() - .url(url) - .get() - val authProviderConfig = JSONObject(executeRequest(request, true)) - authProviderConfig.getJSONObject("config").apply { - put("autoConfirm", enabled) - } - // Change autoConfirm and update the provider - request = Request.Builder() - .url(url) - .patch(RequestBody.create(json, authProviderConfig.toString())) - executeRequest(request, true) - - request = Request.Builder() - .url(url) - .get() - val config = JSONObject(executeRequest(request, true)) - RealmLog.error("SetAutomaticConfirmation($enabled): ${config.toString(4)}") - waitForDeployment() - } - - private fun waitForDeployment() { - // TODO Attempt to work-around, what looks like a race condition on the server deploying - // changes to the server. Even though the /deployments endpoint report success, it seems - // like the change hasn't propagated fully. This usually surfaces as registerUser errors - // where it tries to use the customFunc instead of automatically registering. - val url = "$baseUrl/groups/$groupId/apps/$appId/deployments" - var request = Request.Builder() - .url(url) - .get() - val deployments = JSONArray(executeRequest(request, true)) - val dep = deployments[0] as JSONObject - if (dep.getString("status") != "successful") { - RealmLog.error("Failed to deploy: ${dep.toString(4)}") - } - - // Work-around for /deployments reporting success, but /register still failing. - SystemClock.sleep(5000) - } - - /** - * Toggle whether or not custom confirmation functions are enabled. - */ - fun setCustomConfirmation(enabled: Boolean) { - val providerId: String = getLocalUserPassProviderId() - val url = "$baseUrl/groups/$groupId/apps/$appId/auth_providers/$providerId" - var request = Request.Builder() - .url(url) - .get() - val authProviderConfig = JSONObject(executeRequest(request, true)) - - authProviderConfig.getJSONObject("config").apply { - put("autoConfirm", !enabled) - put("runConfirmationFunction", enabled) - } - // Change autoConfirm and update the provider - request = Request.Builder() - .url(url) - .patch(RequestBody.create(json, authProviderConfig.toString())) - executeRequest(request, true) - - request = Request.Builder() - .url(url) - .get() - val config = JSONObject(executeRequest(request, true)) - RealmLog.error("setCustomConfirmation($enabled): ${config.toString(4)}") - waitForDeployment() - } - fun enableFlexibleSync() { var request = Request.Builder() .url("$baseUrl/groups/$groupId/apps/$appId/services") @@ -212,8 +137,6 @@ class ServerAdmin(private val app: App) { } } - val JSON = MediaType.parse("application/json; charset=utf-8") - fun disableUser(user: User) { var request = Request.Builder() .url("$baseUrl/groups/$groupId/apps/$appId/users/${user.id}/disable") diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt index c085e34731..65c3469fd5 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncedRealmTests.kt @@ -462,14 +462,14 @@ class SyncedRealmTests { Assert.assertEquals(2, realm.where().count()) val nodeResults = realm.where().findAll() - Assert.assertTrue(nodeResults.any { it.treeNodeId == "node1" }) - Assert.assertTrue(nodeResults.any { it.treeNodeId == "node2" }) + assertTrue(nodeResults.any { it.treeNodeId == "node1" }) + assertTrue(nodeResults.any { it.treeNodeId == "node2" }) Assert.assertEquals(3, realm.where().count()) val leafResults = realm.where().findAll() - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) - Assert.assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf1" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf2" }) + assertTrue(leafResults.any { it.treeLeafId == "leaf3" }) } } @@ -496,10 +496,10 @@ class SyncedRealmTests { // Make sure we can synchronize changes realm1 = Realm.getInstance(config1) realm2 = Realm.getInstance(config2) - realm1.syncSession.downloadAllServerChanges() - realm2.syncSession.downloadAllServerChanges() - Assert.assertTrue(realm1.isEmpty) - Assert.assertTrue(realm2.isEmpty) + realm1.syncSession.downloadAllServerChanges(1, TimeUnit.MINUTES) + realm2.syncSession.downloadAllServerChanges(1, TimeUnit.MINUTES) + assertTrue(realm1.isEmpty) + assertTrue(realm2.isEmpty) } finally { realm1?.close() realm2?.close() diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 83c959d6e8..fbb11dec96 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -18,6 +18,7 @@ package io.realm import android.os.SystemClock import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.StringOnly @@ -184,7 +185,7 @@ class SyncedRealmIntegrationTests { .build() Realm.getInstance(configOld).use { realm -> // Create many changesets to make sure that download is "slow" - for (i in 0..999) { + for (i in 0..99) { realm.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" } @@ -206,7 +207,7 @@ class SyncedRealmIntegrationTests { Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { looperThread.closeAfterTest(realm) - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) looperThread.testComplete() } @@ -218,6 +219,7 @@ class SyncedRealmIntegrationTests { // Try an scenario where a Sync and Async race to wait for the initial remote data. @Test + @FlakyTest(detail = "Depend on the server being able to integrate changes into MongoDB fast enough before the new client downloads data.") fun waitForInitialRemoteData_getInstance_race_AsyncAndSync() = looperThread.runBlocking { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) @@ -226,7 +228,7 @@ class SyncedRealmIntegrationTests { .build() Realm.getInstance(configOld).use { realm -> // Create many changesets to make sure that download is "slow" - for (i in 0..999) { + for (i in 0..99) { realm.executeTransaction { realm -> realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" } @@ -250,7 +252,7 @@ class SyncedRealmIntegrationTests { Realm.getInstanceAsync(config, object: Realm.Callback() { override fun onSuccess(realm: Realm) { looperThread.closeAfterTest(realm) - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) looperThread.testComplete() } @@ -259,7 +261,7 @@ class SyncedRealmIntegrationTests { } }) Realm.getInstance(config).use { realm -> - assertEquals(1000, realm.where(SyncStringOnly::class.java).count()) + assertEquals(100, realm.where(SyncStringOnly::class.java).count()) } } diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 88cf50ee4e..b28fb28b95 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -357,7 +357,7 @@ public static int getRandomId() { public static String getRandomEmail() { StringBuilder sb = new StringBuilder(UUID.randomUUID().toString().toLowerCase()); sb.append('@'); - sb.append("androidtest.realm.io"); + sb.append("10gen.com"); return sb.toString(); } diff --git a/tools/sync_test_server/app_config_generator.sh b/tools/sync_test_server/app_config_generator.sh index 886b34aa3e..0e52051435 100755 --- a/tools/sync_test_server/app_config_generator.sh +++ b/tools/sync_test_server/app_config_generator.sh @@ -2,6 +2,7 @@ TARGET_APP_PATH=$1;shift TEMPLATE_APP_PATH=$1;shift SYNC_MODE=$1;shift # Must be either "partition" or "flex" +AUTH_MODE=$1;shift # Must be either "auto", "function" or "email" mkdir -p $TARGET_APP_PATH for APP_NAME in "$@" do @@ -9,10 +10,62 @@ do sed -i'.bak' 's/APP_NAME_PLACEHOLDER/'$APP_NAME'/g' $TARGET_APP_PATH/$APP_NAME/config.json done +# Setup auth config +for APP_NAME in "$@" +do + JSON="placeholder" + if [ "$AUTH_MODE" = "auto" ]; then + JSON=' + "config": { + "autoConfirm": true, + "runConfirmationFunction": false, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + if [ "$AUTH_MODE" = "function" ]; then + JSON=' + "config": { + "autoConfirm": false, + "runConfirmationFunction": true, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + if [ "$AUTH_MODE" = "email" ]; then + JSON=' + "config": { + "autoConfirm": false, + "runConfirmationFunction": false, + "confirmationFunctionName": "confirmFunc", + "emailConfirmationUrl": "http://realm.io/confirm-user", + "resetFunctionName": "resetFunc", + "resetPasswordSubject": "Reset Password", + "resetPasswordUrl": "http://realm.io/reset-password", + "runResetFunction": false + }, + ' + fi + + ESCAPED_JSON=`echo ${JSON} | tr '\n' "\\n"` + cp -r $TEMPLATE_APP_PATH $TARGET_APP_PATH/$APP_NAME + sed -i'.bak' "s#%EMAIL_AUTH_CONFIG%#$ESCAPED_JSON#g" $TARGET_APP_PATH/$APP_NAME/auth_providers/local-userpass.json +done + # Setup sync configuration for APP_NAME in "$@" do - JSON="boo" + JSON="placeholder" if [ "$SYNC_MODE" = "partition" ]; then JSON=' "sync": { @@ -22,8 +75,26 @@ do "key": "realm_id", "type": "string", "permissions": { - "read": true, - "write": true + "read": { + "%%true": { + "%function": { + "arguments": [ + "%%partition" + ], + "name": "canReadPartition" + } + } + }, + "write": { + "%%true": { + "%function": { + "arguments": [ + "%%partition" + ], + "name": "canWritePartition" + } + } + } } } } @@ -39,7 +110,18 @@ do "name", "color", "section" - ] + ], + "permissions": { + "rules": {}, + "defaultRoles": [ + { + "name": "read-write", + "applyWhen": {}, + "read": true, + "write": true + } + ] + } } ' fi diff --git a/tools/sync_test_server/app_template/auth_providers/local-userpass.json b/tools/sync_test_server/app_template/auth_providers/local-userpass.json index 76be7a9a19..69e8697893 100644 --- a/tools/sync_test_server/app_template/auth_providers/local-userpass.json +++ b/tools/sync_test_server/app_template/auth_providers/local-userpass.json @@ -2,15 +2,6 @@ "id": "60489e7df5d9bdc94de663db", "name": "local-userpass", "type": "local-userpass", - "config": { - "autoConfirm": true, - "confirmationFunctionName": "confirmFunc", - "emailConfirmationUrl": "http://realm.io/confirm-user", - "resetFunctionName": "resetFunc", - "resetPasswordSubject": "Reset Password", - "resetPasswordUrl": "http://realm.io/reset-password", - "runConfirmationFunction": false, - "runResetFunction": false - }, + %EMAIL_AUTH_CONFIG% "disabled": false } diff --git a/tools/sync_test_server/app_template/functions/canReadPartition/config.json b/tools/sync_test_server/app_template/functions/canReadPartition/config.json new file mode 100644 index 0000000000..452fce7a18 --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canReadPartition/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "60489e7df5d9bdc94de663da", + "name": "canReadPartition", + "private": false +} diff --git a/tools/sync_test_server/app_template/functions/canReadPartition/source.js b/tools/sync_test_server/app_template/functions/canReadPartition/source.js new file mode 100644 index 0000000000..3cbf1868da --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canReadPartition/source.js @@ -0,0 +1,12 @@ +/** + * Users with an email that contains `_noread_` do not have read access, + * all others do. + */ +exports = async (partition) => { + const email = context.user.data.email; + if (email != undefined) { + return(!email.includes("_noread_")); + } else { + return true; + } +} diff --git a/tools/sync_test_server/app_template/functions/canWritePartition/config.json b/tools/sync_test_server/app_template/functions/canWritePartition/config.json new file mode 100644 index 0000000000..f96e8f824a --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canWritePartition/config.json @@ -0,0 +1,6 @@ +{ + "can_evaluate": {}, + "id": "60489e7df5d9bdc94de783cf", + "name": "canWritePartition", + "private": false +} diff --git a/tools/sync_test_server/app_template/functions/canWritePartition/source.js b/tools/sync_test_server/app_template/functions/canWritePartition/source.js new file mode 100644 index 0000000000..4b0effd12d --- /dev/null +++ b/tools/sync_test_server/app_template/functions/canWritePartition/source.js @@ -0,0 +1,12 @@ +/** + * Users with an email that contains `_nowrite_` do not have write access, + * all others do. + */ +exports = async (partition) => { + const email = context.user.data.email; + if (email != undefined) { + return(!email.includes("_nowrite_")); + } else { + return true; + } +} diff --git a/tools/sync_test_server/app_template/functions/confirmFunc/source.js b/tools/sync_test_server/app_template/functions/confirmFunc/source.js index 6a3d024046..40d11dea9b 100644 --- a/tools/sync_test_server/app_template/functions/confirmFunc/source.js +++ b/tools/sync_test_server/app_template/functions/confirmFunc/source.js @@ -1,4 +1,3 @@ - /* This function will be run AFTER a user registers their username and password and is called with an object parameter @@ -38,12 +37,37 @@ The uncommented function below is just a placeholder and will result in failure. */ - - exports = ({ token, tokenId, username }) => { +exports = async ({ token, tokenId, username }) => { // process the confirm token, tokenId and username - if (username.includes("realm_tests_do_autoverify")) { + + if (username.includes("realm_verify")) { + // Automatically confirm users with `realm_verify` in their email. return { status: 'success' } + } else if (username.includes("realm_pending")) { + // This supports two versions of custom registering: + // + // 1. Emails with `realm_pending` in their email will be placed in Pending + // the first time they register and then fully confirmed when they + // retry the confirmation logic. + // 2. Emails with `only_realm_pending` in their email will be placed in + // Pending the first time they register and fail all subsequent attempts + // at retrying the confirmation logic. + const mdb = context.services.get("BackingDB"); + const collection = mdb.db("custom-auth").collection("users"); + const existing = await collection.findOne({ username: username }); + if (existing) { + if (username.includes("only_realm_pending")) { + return { status: 'fail' } + } else { + return { status: 'success' }; + } + } + await collection.insertOne({ username: username }); + return { status: 'pending' } + } else if (username.endsWith("@10gen.com") || username.includes("realm_tests_do_autoverify")) { + return { status: 'success' } + } else { + // All other emails should fail to confirm outright. + return { status: 'fail' }; } - // do not confirm the user - return { status: 'fail' }; }; diff --git a/tools/sync_test_server/app_template/functions/testAuthFunc/source.js b/tools/sync_test_server/app_template/functions/testAuthFunc/source.js index 4c1e87f25a..72700e13ac 100644 --- a/tools/sync_test_server/app_template/functions/testAuthFunc/source.js +++ b/tools/sync_test_server/app_template/functions/testAuthFunc/source.js @@ -1,7 +1,7 @@ exports = ({mail, id}) => { - // Auth function will fail for emails with a domain different to @androidtest.realm.io + // Auth function will fail for emails with a domain different to @10gen.com // or with id lower than 666 - if (!new RegExp("@androidtest.realm.io$").test(mail) || id < 666) { + if (!new RegExp("@10gen.com$").test(mail) || id < 666) { return 0; } else { // Use the users email as UID diff --git a/tools/sync_test_server/start_local_server.sh b/tools/sync_test_server/start_local_server.sh index 5a8127bc6c..f50099d090 100755 --- a/tools/sync_test_server/start_local_server.sh +++ b/tools/sync_test_server/start_local_server.sh @@ -126,8 +126,9 @@ function boot_command_server () { function generate_app_configs () { APP_CONFIG_DIR=`mktemp -d -t app_config` - $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition testapp1 testapp2 - $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex testapp3 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition auto testapp1 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition email testapp2 + $SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex function testapp3 } function import_apps () { diff --git a/tools/sync_test_server/start_server.sh b/tools/sync_test_server/start_server.sh index 14ce0e57ea..3347685516 100755 --- a/tools/sync_test_server/start_server.sh +++ b/tools/sync_test_server/start_server.sh @@ -38,8 +38,9 @@ SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" # Create app configurations APP_CONFIG_DIR=`mktemp -d -t app_config` -$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition testapp1 testapp2 -$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex testapp3 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition auto testapp1 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template partition email testapp2 +$SCRIPTPATH/app_config_generator.sh $APP_CONFIG_DIR $SCRIPTPATH/app_template flex function testapp3 # Run Stitch and Stitch CLI Docker images docker network create mongodb-realm-network From 0357c1110c591f9351806673daaae6d15cadd284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 14 Sep 2022 14:19:55 +0200 Subject: [PATCH 25/25] Updates according to review comments --- .../examples/arch/PersonListFragment.java | 4 +- .../src/main/kotlin/io/realm/gradle/Realm.kt | 47 ++----------------- gradle-plugin/src/main/templates/Version.kt | 4 +- .../buildtransformer/RealmBuildTransformer.kt | 45 +++++++++++------- .../io/realm/transformer/RealmTransformer.kt | 4 +- .../objectstore/OsMutableSubscriptionSet.java | 1 - .../internal/objectstore/OsSubscription.java | 1 - .../objectstore/OsSubscriptionSet.java | 1 - 8 files changed, 36 insertions(+), 71 deletions(-) diff --git a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java index 22dfc5a0f4..a114dfd84a 100644 --- a/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java +++ b/examples/architectureComponentsExample/src/main/java/io/realm/examples/arch/PersonListFragment.java @@ -114,7 +114,6 @@ static class ViewHolder extends RecyclerView.ViewHolder { return; } AppCompatActivity activity = ContextUtils.findActivity(view.getContext()); -// PersonFragment personFragment = PersonFragment.create(person.getName()); PersonFragment personFragment = PersonFragment.create(person.name); activity.getSupportFragmentManager() .beginTransaction() @@ -133,8 +132,7 @@ public ViewHolder(View itemView) { public void bind(Person person) { this.person = person; -// name.setText(person.getName()); - name.setText(person.name); + name.setText(person.getName()); age.setText(String.valueOf(person.getAge())); } } diff --git a/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt index 8180e34c8f..118bd77fbe 100644 --- a/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt +++ b/gradle-plugin/src/main/kotlin/io/realm/gradle/Realm.kt @@ -2,7 +2,6 @@ package io.realm.gradle import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryPlugin -import com.neenbedankt.gradle.androidapt.AndroidAptPlugin import io.realm.transformer.RealmTransformer import org.gradle.api.GradleException import org.gradle.api.Plugin @@ -15,7 +14,7 @@ import org.slf4j.LoggerFactory val logger: Logger = LoggerFactory.getLogger("realm-logger") // TODO Run a Task or Visitor to collect runtimeClassPath, then serialize it -// Run another task that depends on the output of th first task inorder to desrialize the ClassPool and process each class appart +// Run another task that depends on the output of the first task in order to deserialize the ClassPool and process each class apart open class Realm : Plugin { override fun apply(project: Project) { // Make sure the project is either an Android application or library @@ -30,29 +29,16 @@ open class Realm : Plugin { checkCompatibleAGPVersion() - var usesAptPlugin: Boolean = - project.plugins.findPlugin("com.neenbedankt.android-apt") != null val isKotlinProject: Boolean = project.plugins.findPlugin("kotlin-android") != null || project.plugins.findPlugin("kotlin-multiplatform") != null val hasAnnotationProcessorConfiguration = project.getConfigurations().findByName("annotationProcessor") != null // TODO add a parameter in 'realm' block if this should be specified by users - val preferAptOnKotlinProject = false val dependencyConfigurationName: String = getDependencyConfigurationName(project) val extension = project.extensions.create("realm", RealmPluginExtension::class.java) extension.isKotlinExtensionsEnabled = isKotlinProject - if (shouldApplyAndroidAptPlugin( - usesAptPlugin, - isKotlinProject, - hasAnnotationProcessorConfiguration, - preferAptOnKotlinProject - ) - ) { - project.plugins.apply(AndroidAptPlugin::class.java) - usesAptPlugin = true - } RealmTransformer.register(project) @@ -60,16 +46,7 @@ open class Realm : Plugin { dependencyConfigurationName, "io.realm:realm-annotations:${Version.VERSION}" ) - if (usesAptPlugin) { - project.dependencies.add( - "apt", - "io.realm:realm-annotations-processor:${Version.VERSION}" - ) - project.dependencies.add( - "androidTestApt", - "io.realm:realm-annotations-processor:${Version.VERSION}" - ) - } else if (isKotlinProject && !preferAptOnKotlinProject) { + if (isKotlinProject) { project.dependencies.add( "kapt", "io.realm:realm-annotations-processor:${Version.VERSION}" @@ -133,24 +110,6 @@ open class Realm : Plugin { } } - private fun shouldApplyAndroidAptPlugin( - usesAptPlugin: Boolean, - isKotlinProject: Boolean, - hasAnnotationProcessorConfiguration: Boolean, - preferAptOnKotlinProject: Boolean - ): Boolean { - if (usesAptPlugin) { - // for any projects that uses android-apt plugin already. No need to apply it twice. - return false - } - return if (isKotlinProject) { - // for any Kotlin projects where user did not apply 'android-apt' plugin manually. - preferAptOnKotlinProject && !hasAnnotationProcessorConfiguration - } else !hasAnnotationProcessorConfiguration - - // for any Java Projects where user did not apply 'android-apt' plugin manually. - } - // This will setup the required dependencies. // This must only be called once, as removal of dependencies through the iterator API will // not propagate correctly into the IterationOrderRetainingSetElementSource which is backing @@ -197,4 +156,4 @@ open class Realm : Plugin { } } -} \ No newline at end of file +} diff --git a/gradle-plugin/src/main/templates/Version.kt b/gradle-plugin/src/main/templates/Version.kt index d11046b7d2..3e422086e2 100644 --- a/gradle-plugin/src/main/templates/Version.kt +++ b/gradle-plugin/src/main/templates/Version.kt @@ -1,5 +1,5 @@ package io.realm.gradle; object Version { - const val VERSION = "@version@" -} \ No newline at end of file + const val VERSION = "@version@" +} diff --git a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt index d39264033c..a20a856e4c 100644 --- a/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt +++ b/library-build-transformer/src/main/kotlin/io/realm/buildtransformer/RealmBuildTransformer.kt @@ -25,7 +25,10 @@ import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFiles +import org.gradle.api.tasks.TaskAction import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.slf4j.Logger @@ -57,34 +60,39 @@ class RealmBuildTransformer( private val input: ListProperty, private val output: RegularFileProperty, ) { - init { - transform() - } - private fun transform() { + internal fun transform() { + // The AGP build infrastructure will inject the task input and outputs and location is not + // to be considered as part of the API, so we must be able to accept the current scenario + // where the input and output is actually the same JAR. Thus, we write everything to a + // temporary output to avoid truncating the input, and then write this temporary output + // back to the final output location in the end. + // See https://developer.android.com/reference/tools/gradle-api/7.4/com/android/build/api/variant/ScopedArtifactsOperation#toTransform(com.android.build.api.artifact.ScopedArtifact,kotlin.Function1,kotlin.Function1,kotlin.Function1) + // for further information. + val temporaryOutput = output.get().asFile.absolutePath + ".tmp" val outputProvider = JarOutputStream( - BufferedOutputStream( - FileOutputStream( - output.get().asFile.absolutePath + ".tmp" - ) - ) + BufferedOutputStream(FileOutputStream(temporaryOutput)) ) val timer = Stopwatch() timer.start("Build Transform time") + // The ASM infrastructure does not allow to inspect annotations of methods before visiting + // them. This prevents inspecting and stripping methods in the same pass, thus we first + // collect information about annotations and then secondly strip the annotation symbols. + // 1. Collect annotation information val annotationDescriptor = createDescriptor(annotationQualifiedName.get()) val metadataCollector = io.realm.buildtransformer.asm.visitors.AnnotationVisitor(annotationDescriptor) - forEachJarEntry { jarEntry, inputStream -> - if (jarEntry.name.endsWith(".class") ) { - inputStream.use { - val classReader = ClassReader(it) - classReader.accept(metadataCollector, 0) - } + if (jarEntry.name.endsWith(".class")) { + inputStream.use { + val classReader = ClassReader(it) + classReader.accept(metadataCollector, 0) + } } } + // 2. Strip annotated symbols forEachJarEntry { jarEntry, inputStream -> val bytes = inputStream.use { inputStream -> if (jarEntry.name.endsWith(".class")) { @@ -110,7 +118,9 @@ class RealmBuildTransformer( } } outputProvider.close() - FileInputStream( output.get().asFile.absolutePath + ".tmp" ).channel.use { input -> + // Write the temporary output to the final output location. See comment about + // temporaryOutput for the details + FileInputStream(temporaryOutput).channel.use { input -> FileOutputStream(this.output.asFile.get().absoluteFile).channel.use { output -> output.transferFrom(input, 0, input.size()) } @@ -171,5 +181,6 @@ abstract class ModifyClassesTask: DefaultTask() { @TaskAction fun taskAction() { RealmBuildTransformer(annotationQualifiedName, allJars, output) + .transform() } } diff --git a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt index 34c26190dd..5db110cd6c 100644 --- a/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt +++ b/realm-transformer/src/main/kotlin/io/realm/transformer/RealmTransformer.kt @@ -81,7 +81,6 @@ class RealmTransformer(project: Project, // Analytics should never crash the build. logger.debug("Could not calculate Realm analytics data:\n$e") } - transform() } companion object { @@ -124,7 +123,7 @@ class RealmTransformer(project: Project, * to the model class. Something we cannot do when building incrementally. In that case * we have to deduce all information from the class at hand. */ - private fun transform() { + internal fun transform() { val timer = Stopwatch() timer.start("Realm Transform time") @@ -183,6 +182,7 @@ abstract class ModifyClassesTask: DefaultTask() { @TaskAction fun taskAction() { RealmTransformer(project, allDirectories, allJars, fullRuntimeClasspath, output) + .transform() } } diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java index 3092a6fbe1..e798c1a9bf 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsMutableSubscriptionSet.java @@ -23,7 +23,6 @@ import io.realm.mongodb.sync.MutableSubscriptionSet; import io.realm.mongodb.sync.Subscription; -// TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. @ObjectServer public class OsMutableSubscriptionSet extends OsSubscriptionSet implements MutableSubscriptionSet { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java index a23b8c034d..e11b939d4f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscription.java @@ -21,7 +21,6 @@ import io.realm.internal.annotations.ObjectServer; import io.realm.mongodb.sync.Subscription; -// TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. @ObjectServer public class OsSubscription implements NativeObject, Subscription { diff --git a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java index 7962960934..5d19858818 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java +++ b/realm/realm-library/src/main/java/io/realm/internal/objectstore/OsSubscriptionSet.java @@ -37,7 +37,6 @@ import io.realm.mongodb.sync.Subscription; import io.realm.mongodb.sync.SubscriptionSet; -// TODO Adding @ObjectServer here seems to break the Realm Build Transformer. Investigate why. @ObjectServer public class OsSubscriptionSet implements NativeObject, SubscriptionSet {