Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ captures
.idea/deploymentTargetDropDown.xml
.idea/misc.xml
.idea/androidTestResultsUserPreferences.xml
.idea/appInsightsSettings.xml
.idea/migrations.xml
gradle.xml
*.iml

Expand Down
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions app-catalog/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
id 'dagger.hilt.android.plugin'
id 'kotlin-kapt'
}

android {
namespace 'com.google.android.catalog.app'
compileSdk 33
compileSdk 34

defaultConfig {
applicationId "com.google.android.catalog.app"
minSdk 21
targetSdk 33
targetSdk 34
versionCode 1
versionName "1.0"

Expand All @@ -42,11 +42,11 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}

buildFeatures {
Expand Down Expand Up @@ -75,7 +75,7 @@ dependencies {

// Required by CASA to generate the wiring logic
implementation libs.hilt.android
kapt libs.hilt.compiler
ksp libs.hilt.compiler

// Include all available samples dynamically importend in settings.gradle
gradle.ext.samples.each {
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ subprojects {
kotlinOptions {
// Treat all Kotlin warnings as errors
allWarningsAsErrors = true
// Set JVM target to 1.8
jvmTarget = "1.8"
// Set JVM target to 17
jvmTarget = "17"
// Allow use of @OptIn
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
// Enable default methods in interfaces
Expand Down
2 changes: 1 addition & 1 deletion framework/annotations/api/current.api
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Signature format: 4.0
package com.google.android.catalog.framework.annotations {

@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Sample {
@dagger.hilt.GeneratesRootInput @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Sample {
method public abstract String description();
method public abstract String documentation();
method public abstract String name();
Expand Down
1 change: 1 addition & 0 deletions framework/annotations/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ affectedTestConfiguration {

dependencies {
api 'javax.inject:javax.inject:1'
implementation libs.hilt.core
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.google.android.catalog.framework.annotations

import dagger.hilt.GeneratesRootInput

/**
* Add this annotation to a parameterless `@Compose` function, fragment or activity classes to
* create a new sample entry-point that will be automatically included in the Catalog app.
Expand All @@ -32,6 +34,7 @@ package com.google.android.catalog.framework.annotations
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@GeneratesRootInput
annotation class Sample(
val name: String,
val description: String,
Expand Down
8 changes: 4 additions & 4 deletions framework/base/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ mavenPublishing {
android {
namespace 'com.google.android.catalog.framework.base'

compileSdk 33
compileSdk 34

defaultConfig {
minSdk 21
targetSdk 33
targetSdk 34
resConfigs 'en'

testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17

kotlin {
kotlinOptions {
Expand Down
6 changes: 3 additions & 3 deletions framework/processor/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ package com.google.android.catalog.framework.processor {
method public java.util.List<com.google.devtools.ksp.symbol.KSAnnotated> process(com.google.devtools.ksp.processing.Resolver resolver);
}

public final class SampleProcessorKt {
}

public final class SampleProcessorProvider implements com.google.devtools.ksp.processing.SymbolProcessorProvider {
ctor public SampleProcessorProvider();
method public com.google.devtools.ksp.processing.SymbolProcessor create(com.google.devtools.ksp.processing.SymbolProcessorEnvironment environment);
}

public final class SampleVisitorKt {
}

}

2 changes: 1 addition & 1 deletion framework/processor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mavenPublishing {

compileKotlin {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,16 @@

package com.google.android.catalog.framework.processor

import androidx.annotation.RequiresApi
import com.google.android.catalog.framework.annotations.Sample
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.containingFile
import com.google.devtools.ksp.getAllSuperTypes
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.isAnnotationPresent
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.FileLocation
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.validate
Expand All @@ -50,10 +44,7 @@ class SampleProcessor(
private val options: Map<String, String>
) : SymbolProcessor {

private val classes = mutableListOf<KSClassDeclaration>()
private val functions = mutableListOf<KSFunctionDeclaration>()

private val visitor = SampleVisitor(classes, functions)
private val visitor = SampleVisitor(logger, codeGenerator)

override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(Sample::class.java.name)
Expand All @@ -73,139 +64,4 @@ class SampleProcessor(

return emptyList()
}

override fun finish() {
functions.forEach { declaration ->
require(
declaration.annotations.any { it.shortName.asString() == "Composable" } &&
declaration.parameters.isEmpty()
) {
"@Sample must be a in a Composable function with empty parameters"
}

createModule(
functionSample = declaration,
target = "targetComposable { ${declaration.toFullPath()}() }"
)
}
classes.forEach { declaration ->
val target = declaration.getAllSuperTypes().firstNotNullOfOrNull {
val className = it.declaration.qualifiedName?.asString().orEmpty()
logger.warn(className)
when (className) {
"android.app.Activity" -> {
"targetActivity<${declaration.toFullPath()}>()"
}

"androidx.fragment.app.Fragment" -> {
"targetFragment<${declaration.toFullPath()}>()"
}

else -> null
}
}
requireNotNull(target) {
"@Sample only supports Fragments, Activities or Composable functions"
}

createModule(
functionSample = declaration,
target = target
)
}
}

@OptIn(KspExperimental::class)
private fun createModule(functionSample: KSDeclaration, target: String) {
val filePath = getRelativeFilePath(functionSample)
val packageName = functionSample.packageName.asString()
val sampleFile = functionSample.simpleName.asString()
val sample = functionSample.getAnnotationsByType(Sample::class).first()
val sampleSource = sample.sourcePath.ifBlank { filePath }
val minSDK = functionSample.getAnnotationsByType(RequiresApi::class).minOfOrNull {
it.value
} ?: 0

val file = codeGenerator.createNewFile(
dependencies = Dependencies(
aggregating = true,
sources = (functions + classes).map { it.containingFile!! }.toTypedArray()
),
packageName = packageName,
fileName = "${sampleFile}Module"
)
file.use { stream ->
stream.write(
sampleTemplate(
sampleFile = sampleFile,
samplePackage = packageName,
sampleName = sample.name,
sampleDescription = sample.description,
sampleTags = sample.tags,
sampleDocs = sample.documentation,
sampleSource = sampleSource,
samplePath = filePath.substringBefore("/src"),
sampleOwners = sample.owners,
sampleTarget = target,
sampleMinSdk = minSDK,
sampleRoute = "$sampleSource-$sampleFile",
).toByteArray()
)
}
}

private fun KSDeclaration.toFullPath() =
"${packageName.asString()}.${simpleName.asString()}"

private fun getRelativeFilePath(declaration: KSDeclaration): String {
val path = (declaration.location as? FileLocation)?.filePath.orEmpty()
return path.substringAfterLast("/samples/")
}
}

private fun sampleTemplate(
sampleFile: String,
samplePackage: String,
sampleName: String,
sampleDescription: String,
sampleTags: Array<String>,
sampleDocs: String,
sampleSource: String,
samplePath: String,
sampleOwners: Array<String>,
sampleTarget: String,
sampleMinSdk: Int,
sampleRoute: String,
) = """
package $samplePackage

import com.google.android.catalog.framework.base.*

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet

@Module
@InstallIn(SingletonComponent::class)
class ${sampleFile}Module {

@Provides
@IntoSet
fun provide${sampleFile}Sample(): CatalogSample {
return CatalogSample(
"$sampleName",
"$sampleDescription",
listOf(${sampleTags.joinToString(",") { "\"$it\"" }}),
"$sampleDocs",
"$sampleSource",
"$samplePath",
listOf(${sampleOwners.joinToString(",") { "\"$it\"" }}),
$sampleTarget,
$sampleMinSdk,
"$sampleRoute",
)
}
}
""".trimIndent()
Loading