Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup processing abstraction to lay the groundwork for KSP #152

Closed
wants to merge 14 commits into from
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# 1.8.0
- TODO: Update all docs to require R2
- Support KSP
- The butterknife gradle plugin must now be used in all modules using Paris (https://github.com/JakeWharton/butterknife#library-projects), and all annotation values referencing resources must use the generated R2 class instead of R directly
- getting ahead of https://github.com/JakeWharton/butterknife/issues/1634
- R class must now be provided in all modules using Paris via a ParisConfig annotation
- as well as in test modules too that use Paris annotations
- ParisConfig annotation can no longer be used on package elements, only on class or interfaces
- Resource values used as annotation parameters must now be defined in the same module (ie the R class must not be from a different module)

- Fix
# 1.7.3 (April 13, 2021)

- Fix generated code consistency in module class (#154)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Paris lets you define and apply styles programmatically to Android views, includ
In your project's `build.gradle`:
```gradle
dependencies {
implementation 'com.airbnb.android:paris:1.7.3'
implementation 'com.airbnb.android:paris:2.0.0'
// If you're using Paris annotations.
kapt 'com.airbnb.android:paris-processor:1.7.3'
kapt 'com.airbnb.android:paris-processor:2.0.0'
}
```

Expand Down
5 changes: 5 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ Releasing
2. Release title `vX.Y.Z`
3. Paste the content from `CHANGELOG.md` as the description
7. `./gradlew clean uploadArchives --no-daemon --no-parallel`
- Use gradle properties to config release:
- SONATYPE_NEXUS_USERNAME
- SONATYPE_NEXUS_PASSWORD
- RELEASE_REPOSITORY_URL
- SNAPSHOT_REPOSITORY_URL
8. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact.
1 change: 0 additions & 1 deletion blessedDeps.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ rootProject.ext {
incapRuntime : "net.ltgt.gradle.incap:incap:$INCAP_VERSION",
incapProcessor : "net.ltgt.gradle.incap:incap-processor:$INCAP_VERSION",
junit : "junit:junit:$JUNIT_VERSION",
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION",
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION",
kotlinPoet : "com.squareup:kotlinpoet:$KOTLINPOET_VERSION",
kotlinTest : "io.kotlintest:kotlintest:$KOTLIN_TEST_VERSION",
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: "com.github.ben-manes.versions"
buildscript {
ext {
ANDROID_PLUGIN_VERSION = '4.1.3'
BUTTERKNIFE_VERSION = '10.2.1'
BUTTERKNIFE_VERSION = '10.2.3'
KOTLIN_VERSION = '1.5.21'
VERSIONS_VERSION = '0.39.0'
}
Expand All @@ -16,7 +16,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:$ANDROID_PLUGIN_VERSION"
classpath "com.android.tools.build:gradle:${ANDROID_PLUGIN_VERSION}"
classpath "com.jakewharton:butterknife-gradle-plugin:$BUTTERKNIFE_VERSION"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "com.github.ben-manes:gradle-versions-plugin:$VERSIONS_VERSION"
Expand Down
5 changes: 3 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION_NAME=1.7.3
VERSION_NAME=2.0.0-Airbnb2
GROUP=com.airbnb.android
POM_DESCRIPTION=Paris is a system for creating and applying styles to views in Android.
POM_URL=https://github.com/airbnb/paris
Expand All @@ -17,9 +17,10 @@ android.useAndroidX=true
org.gradle.parallel=true
org.gradle.incremental=true
org.gradle.configureondemand=true
org.gradle.daemon=true
kotlin.incremental=true
kapt.includeCompileClasspath=false

# Dokka fails without a larger metaspace https://github.com/Kotlin/dokka/issues/1405
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g
org.gradle.daemon=true
#org.gradle.debug=true
1 change: 0 additions & 1 deletion paris-annotations/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ sourceCompatibility = rootProject.JAVA_SOURCE_VERSION
targetCompatibility = rootProject.JAVA_TARGET_VERSION

dependencies {
implementation deps.kotlin
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@
import java.lang.annotation.Target;

/**
* Note that when using KAPT with incremental annotation processing it is recommended to only use this annotation on class or interface elements,
* not on package elements in package-info.java. This is because there is a bug where package-info is not properly reprocessed in incremental builds.
* Place this annotation on a single class or interface within your module to specify configuration options for that module.
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.PACKAGE, ElementType.TYPE})
@Target(ElementType.TYPE)
public @interface ParisConfig {

String defaultStyleNameFormat() default "";

Class<?> rClass() default Void.class;

/**
* This is an experimental gradle flag (android.namespacedRClass=true). Setting to true allows Paris to generate code compatible
* with R files that only have resources from the module the resource was declared in.
* This is an experimental gradle flag (android.namespacedRClass=true). Setting to true allows Paris to generate code compatible with R files that
* only have resources from the module the resource was declared in.
*/
boolean namespacedResourcesEnabled() default false;
}
15 changes: 10 additions & 5 deletions paris-processor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,26 @@ targetCompatibility = rootProject.JAVA_TARGET_VERSION

dependencies {
implementation project(':paris-annotations')
implementation "com.google.devtools.ksp:symbol-processing-api:1.5.21-1.0.0-beta07"
implementation "androidx.room:room-compiler-processing:2.4.0-alpha04"

implementation deps.androidAnnotations
implementation deps.kotlin

implementation deps.javaPoet
implementation deps.kotlinPoet
compileOnly deps.incapRuntime
kapt deps.incapProcessor

compileOnly files(Jvm.current().getToolsJar())

testImplementation "androidx.room:room-compiler-processing-testing:2.4.0-alpha04"
testImplementation deps.junit
testImplementation deps.kotlinTest
}

compileKotlin {
kotlinOptions.jvmTarget = "1.8"
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
freeCompilerArgs += "-Xopt-in=kotlin.contracts.ExperimentalContracts"
freeCompilerArgs += "-Xopt-in=androidx.room.compiler.processing.ExperimentalProcessingApi"
}
}
85 changes: 52 additions & 33 deletions paris-processor/src/main/java/com/airbnb/paris/processor/Format.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package com.airbnb.paris.processor

import androidx.annotation.ColorInt
import androidx.annotation.Px
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XVariableElement
import androidx.room.compiler.processing.isArray
import androidx.room.compiler.processing.isInt
import androidx.room.compiler.processing.isMethod
import com.airbnb.paris.annotations.Fraction
import com.airbnb.paris.annotations.LayoutDimension
import com.airbnb.paris.processor.framework.AndroidClassNames
import com.airbnb.paris.processor.framework.hasAnnotation
import com.airbnb.paris.processor.framework.hasAnyAnnotation
import com.airbnb.paris.processor.framework.Memoizer
import com.airbnb.paris.processor.utils.hasAnyAnnotationBySimpleName
import com.airbnb.paris.processor.utils.isBoolean
import com.airbnb.paris.processor.utils.isFieldElement
import com.airbnb.paris.processor.utils.isFloat
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement

internal class Format private constructor(
private val type: Type,
Expand Down Expand Up @@ -69,61 +79,70 @@ internal class Format private constructor(
"XmlRes"
)

fun forElement(processor: ParisProcessor, element: Element): Format {
return if (element.kind == ElementKind.FIELD) {
forField(processor, element)
} else {
forMethod(element)
fun forElement(memoizer: Memoizer, element: XElement): Format {
return when {
element.isFieldElement() -> {
forField(memoizer, element)
}
element.isMethod() -> {
forMethod(element)
}
else -> {
error("unsupported $element")
}
}
}

private fun forField(processor: ParisProcessor, element: Element): Format {
val type = element.asType()
if (processor.isView(type)) {
private fun forField(memoizer: Memoizer, element: XFieldElement): Format {
// TODO: 2/21/21 Is this assignable checking the right direction?
if (memoizer.androidViewClassTypeX.rawType.isAssignableFrom(element.type)) {
// If the field is a View then the attribute must be a style or style resource id
return Format(Type.STYLE)
}

return forEitherFieldOrMethodParameter(element)
}

private fun forMethod(element: Element): Format {
return forEitherFieldOrMethodParameter((element as ExecutableElement).parameters[0])
private fun forMethod(element: XMethodElement): Format {
val param = element.parameters.firstOrNull() ?: error("No parameter for $element")
return forEitherFieldOrMethodParameter(param)
}

private fun forEitherFieldOrMethodParameter(element: Element): Format {
private fun forEitherFieldOrMethodParameter(element: XVariableElement): Format {
// TODO Use qualified name of annotations
// TODO Check that the type of the parameters corresponds to the annotation

if (element.hasAnnotation("ColorInt")) {
if (element.hasAnnotation(ColorInt::class)) {
return Format(Type.COLOR)
}
if (element.hasAnnotation("Fraction")) {
val fraction = element.getAnnotation(Fraction::class.java)
element.getAnnotation(Fraction::class)?.value?.let { fraction ->
return Format(Type.FRACTION, fraction.base, fraction.pbase)
}
if (element.hasAnnotation("LayoutDimension")) {
if (element.hasAnnotation(LayoutDimension::class)) {
return Format(Type.LAYOUT_DIMENSION)
}
// TODO What about Sp?
if (element.hasAnnotation("Px")) {
if (element.hasAnnotation(Px::class)) {
return Format(Type.DIMENSION_PIXEL_SIZE)
}
if (element.hasAnyAnnotation(RES_ANNOTATIONS)) {
if (element.hasAnyAnnotationBySimpleName(RES_ANNOTATIONS)) {
return Format.RESOURCE_ID
}

val formatType = when (val typeString = element.asType().toString()) {
"java.lang.Boolean", "boolean" -> Type.BOOLEAN
"java.lang.CharSequence" -> Type.CHARSEQUENCE
"java.lang.CharSequence[]" -> Type.CHARSEQUENCE_ARRAY
"android.content.res.ColorStateList" -> Type.COLOR_STATE_LIST
"android.graphics.Typeface" -> Type.FONT
"android.graphics.drawable.Drawable" -> Type.DRAWABLE
"java.lang.Float", "float" -> Type.FLOAT
"java.lang.Integer", "int" -> Type.INT
"java.lang.String" -> Type.STRING
else -> throw IllegalArgumentException("Invalid type: $typeString")
val type = element.type
val typeString by lazy { type.toString() }
val formatType = when {
type.isBoolean() -> Type.BOOLEAN
type.isFloat() -> Type.FLOAT
type.isInt() -> Type.INT
// TODO: Is the different java vs kotlin version of String accounted for when processed in either language with either tool?
type.isTypeOf(CharSequence::class) -> Type.CHARSEQUENCE
type.isTypeOf(String::class) -> Type.STRING
typeString == "android.content.res.ColorStateList" -> Type.COLOR_STATE_LIST
typeString == "android.graphics.Typeface" -> Type.FONT
typeString == "android.graphics.drawable.Drawable" -> Type.DRAWABLE
type.isArray() && type.componentType.isTypeOf(CharSequence::class) -> Type.CHARSEQUENCE_ARRAY
else -> error("Invalid type: $type")
}
return Format(formatType)
}
Expand Down
Loading