Skip to content

Commit

Permalink
implement deeplink gradle plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielittner committed Oct 6, 2023
1 parent cdcc3a0 commit bf2fe2e
Show file tree
Hide file tree
Showing 13 changed files with 825 additions and 71 deletions.
165 changes: 165 additions & 0 deletions deeplinks-plugin/api/deeplinks-plugin.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,73 @@
public final class com/freeletics/khonshu/deeplinks/plugin/Configuration {
public static final field Companion Lcom/freeletics/khonshu/deeplinks/plugin/Configuration$Companion;
public synthetic fun <init> (ILjava/util/List;Ljava/util/List;Ljava/util/Map;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/util/List;Ljava/util/List;Ljava/util/Map;)V
public synthetic fun <init> (Ljava/util/List;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/util/List;
public final fun component3 ()Ljava/util/Map;
public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/Map;)Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;
public static synthetic fun copy$default (Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;Ljava/util/List;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;
public fun equals (Ljava/lang/Object;)Z
public final fun getDeepLinks ()Ljava/util/Map;
public final fun getPlaceholders ()Ljava/util/List;
public final fun getPrefixes ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class com/freeletics/khonshu/deeplinks/plugin/Configuration$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/freeletics/khonshu/deeplinks/plugin/Configuration$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/freeletics/khonshu/deeplinks/plugin/Configuration;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Configuration$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/DeepLink {
public static final field Companion Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink$Companion;
public synthetic fun <init> (ILjava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
public synthetic fun <init> (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/util/List;
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Ljava/util/List;
public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;
public static synthetic fun copy$default (Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;
public fun equals (Ljava/lang/Object;)Z
public final fun getExampleQueries ()Ljava/util/List;
public final fun getPatterns ()Ljava/util/List;
public final fun getPlaceholders ()Ljava/util/List;
public final fun getPrefixes ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class com/freeletics/khonshu/deeplinks/plugin/DeepLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/freeletics/khonshu/deeplinks/plugin/DeepLink;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/DeepLink$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public abstract class com/freeletics/khonshu/deeplinks/plugin/DeeplinksManifestConfiguratorTask : org/gradle/api/DefaultTask {
public fun <init> ()V
public abstract fun getDeeplinksConfigurationFile ()Lorg/gradle/api/file/RegularFileProperty;
Expand All @@ -12,3 +82,98 @@ public abstract class com/freeletics/khonshu/deeplinks/plugin/DeeplinksPlugin :
public fun apply (Lorg/gradle/api/Project;)V
}

public final class com/freeletics/khonshu/deeplinks/plugin/Pattern {
public static final field Companion Lcom/freeletics/khonshu/deeplinks/plugin/Pattern$Companion;
public static final synthetic fun box-impl (Ljava/lang/String;)Lcom/freeletics/khonshu/deeplinks/plugin/Pattern;
public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z
public final fun getValue ()Ljava/lang/String;
public fun hashCode ()I
public static fun hashCode-impl (Ljava/lang/String;)I
public static final fun replacePlaceholders-impl (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String;
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/lang/String;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Pattern$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/freeletics/khonshu/deeplinks/plugin/Pattern$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize-Ur6tXXg (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize-njSo7Ko (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Pattern$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Placeholder {
public static final field Companion Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder$Companion;
public synthetic fun <init> (ILjava/lang/String;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/util/List;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;
public static synthetic fun copy$default (Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;
public fun equals (Ljava/lang/Object;)Z
public final fun getExampleValues ()Ljava/util/List;
public final fun getKey ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class com/freeletics/khonshu/deeplinks/plugin/Placeholder$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/freeletics/khonshu/deeplinks/plugin/Placeholder;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Placeholder$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Prefix {
public static final field Companion Lcom/freeletics/khonshu/deeplinks/plugin/Prefix$Companion;
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;ZLkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Z)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Z
public final fun copy (Ljava/lang/String;Ljava/lang/String;Z)Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;
public static synthetic fun copy$default (Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;
public fun equals (Ljava/lang/Object;)Z
public final fun getAutoVerified ()Z
public final fun getHost ()Ljava/lang/String;
public final fun getScheme ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class com/freeletics/khonshu/deeplinks/plugin/Prefix$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/freeletics/khonshu/deeplinks/plugin/Prefix$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/freeletics/khonshu/deeplinks/plugin/Prefix;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/freeletics/khonshu/deeplinks/plugin/Prefix$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

7 changes: 6 additions & 1 deletion deeplinks-plugin/deeplinks-plugin.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
plugins {
alias(libs.plugins.fgp.gradle)
// alias(libs.plugins.fgp.publish)
alias(libs.plugins.fgp.publish)
alias(libs.plugins.kotlin.serialization)
}

dependencies {
implementation(libs.toml)
implementation(libs.serialization)
implementation(projects.navigation)

compileOnly(libs.android.gradle.api)

testImplementation(libs.junit)
Expand Down
8 changes: 5 additions & 3 deletions deeplinks-plugin/rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
-keepattributes Signature,Exceptions,*Annotation*,InnerClasses,PermittedSubclasses,EnclosingMethod,Deprecated,SourceFile,LineNumberTable

# Keep your public API so that it's callable from scripts
-keep class com.freeletics.khonshu.deeplinks.plugin.*Extension { *; }
-keep class com.freeletics.khonshu.deeplinks.plugin.*Plugin { *; }
-keep class com.freeletics.khonshu.deeplinks.plugin.*Task { *; }
-keep class com.freeletics.khonshu.deeplinks.plugin.** { public *; }
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
-keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * {
synthetic <init>(...);
}

# No need to obfuscate class names
-dontobfuscate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.freeletics.khonshu.deeplinks.plugin

import com.freeletics.khonshu.navigation.deeplinks.DeepLinkHandler
import com.freeletics.khonshu.navigation.deeplinks.replacePlaceholders
import com.freeletics.khonshu.navigation.internal.InternalNavigationApi
import kotlinx.serialization.Serializable

@Serializable
public data class Configuration(
val prefixes: List<Prefix> = emptyList(),
val placeholders: List<Placeholder> = emptyList(),
val deepLinks: Map<String, DeepLink>,
)

@Serializable
public data class DeepLink(
val patterns: List<Pattern>,
val prefixes: List<Prefix> = emptyList(), // use global if empty
val placeholders: List<Placeholder> = emptyList(), // use global if empty or if key not found
val exampleQueries: List<String> = emptyList(),
)

@Serializable
@JvmInline
public value class Pattern(
public val value: String,
) {
init {
// for validation purposes
DeepLinkHandler.Pattern(value)
}

@OptIn(InternalNavigationApi::class)
public fun replacePlaceholders(replacement: (String) -> String): String {
return DeepLinkHandler.Pattern(value).replacePlaceholders(replacement)
}
}

@Serializable
public data class Prefix(
val scheme: String,
val host: String,
val autoVerified: Boolean,
) {
init {
// for validation purposes
DeepLinkHandler.Prefix("$scheme://$host")
}
}

@Serializable
public data class Placeholder(
val key: String,
val exampleValues: List<String>,
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.freeletics.khonshu.deeplinks.plugin

import com.freeletics.khonshu.navigation.deeplinks.replacePlaceholders
import com.freeletics.khonshu.navigation.internal.InternalNavigationApi
import java.io.File
import net.peanuuutz.tomlkt.Toml

private const val PLACEHOLDER = "<!-- DEEPLINK INTENT FILTERS -->"

internal fun configure(
configurationFile: File,
inputManifestFile: File,
outputManifestFile: File,
) {
val manifest = inputManifestFile.readLines().toMutableList()
val placeholderIndex = manifest.indexOfFirst { it.contains(PLACEHOLDER) }
check(placeholderIndex >= 0) {
"Did not find $PLACEHOLDER in given manifest ${inputManifestFile.absolutePath}"
}

val configuration = Toml.decodeFromString(Configuration.serializer(), configurationFile.readText())
val indentation = manifest[placeholderIndex].takeWhile { it == ' ' }
manifest[placeholderIndex] = intentFiltersFromConfig(configuration, indentation)

outputManifestFile.writeText(manifest.joinToString(separator = "\n"))
}

private fun intentFiltersFromConfig(configuration: Configuration, indentation: String): String {
val builder = IntentFilterBuilder(indentation)

val deepLinksWithGlobalPrefixes = configuration.deepLinks.values.filter { it.prefixes.isEmpty() }
if (deepLinksWithGlobalPrefixes.isNotEmpty()) {
check(configuration.prefixes.isNotEmpty()) {
"Configuration contains deep links without a prefix but has no global prefixes"
}

configuration.prefixes.forEach { prefix ->
builder.appendIntentFilter(prefix, deepLinksWithGlobalPrefixes)
}
}

configuration.deepLinks.values.filter { it.prefixes.isNotEmpty() }.forEach {
it.prefixes.forEach { prefix ->
builder.appendIntentFilter(prefix, listOf(it))
}
}

return builder.toString()
}

private fun IntentFilterBuilder.appendIntentFilter(prefix: Prefix, deepLinks: List<DeepLink>) {
start(prefix.autoVerified)
appendAction("android.intent.action.VIEW")
appendCategory("android.intent.category.DEFAULT")
appendCategory("android.intent.category.BROWSABLE")

deepLinks.forEach { deepLink ->
deepLink.patterns.forEach { pattern ->
appendData(prefix.scheme, prefix.host, pattern)
}
}
end()
}

private class IntentFilterBuilder(
private val indentation: String,
) {
private val builder = StringBuilder()

fun start(autoVerify: Boolean) {
builder.appendLine("$indentation<intent-filter android:autoVerify=\"${autoVerify}\">")
}

fun appendAction(action: String) {
builder.appendLine("$indentation <action android:name=\"${action}\" />")
}

fun appendCategory(category: String) {
builder.appendLine("$indentation <category android:name=\"${category}\" />")
}

@OptIn(InternalNavigationApi::class)
fun appendData(scheme: String, host: String, pattern: Pattern) {
val pathPattern = pattern.replacePlaceholders { ".*" }
builder.appendLine("$indentation <data")
builder.appendLine("$indentation android:scheme=\"${scheme}\"")
builder.appendLine("$indentation android:host=\"${host}\"")
builder.appendLine("$indentation android:pathPattern=\"/${pathPattern}\"")
builder.appendLine("$indentation />")
}

fun end() {
builder.appendLine("$indentation</intent-filter>")
}

override fun toString(): String {
return builder.toString().trimEnd()
}
}
Loading

0 comments on commit bf2fe2e

Please sign in to comment.