Skip to content

Commit

Permalink
✨ 试玩 kotlin ksp
Browse files Browse the repository at this point in the history
  • Loading branch information
ChunMengLu committed May 28, 2024
1 parent 1c7b1c1 commit 0ab2212
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 4 deletions.
17 changes: 14 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
apply plugin: "java-library"
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm' version '1.9.24' apply false
}
apply from: "${rootProject.projectDir}/gradle/publish-maven.gradle"

ext {
javaVersion = JavaVersion.VERSION_1_8
springBootVersion = "2.7.18"
lombokVersion = "1.18.30"
lombokVersion = "1.18.32"
googleAutoVersion = "1.1.1"
compileTestingVersion = '0.21.0'
inCapVersion = "1.0.0"
kotlinVersion = "1.9.24"
}

group = GROUPID
version = VERSION

allprojects {
projects {
apply plugin: 'java'
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
Expand All @@ -27,6 +31,7 @@ tasks.withType(JavaCompile) {
dependencies {
api "org.springframework.boot:spring-boot-configuration-processor:$springBootVersion"
api "org.springframework.boot:spring-boot-autoconfigure-processor:$springBootVersion"
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.24-1.0.20")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
compileOnly "com.google.auto.service:auto-service:$googleAutoVersion"
Expand All @@ -35,6 +40,12 @@ dependencies {
annotationProcessor "net.ltgt.gradle.incap:incap-processor:$inCapVersion"
testImplementation "com.google.testing.compile:compile-testing:$compileTestingVersion"
testImplementation "net.ltgt.gradle.incap:incap:$inCapVersion"
testImplementation 'dev.zacsweers.kctfork:ksp:0.4.1'
testImplementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-scripting-compiler:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-scripting-compiler-impl:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-scripting-jvm:$kotlinVersion"
}

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class AutoServiceProcessor extends AbstractMicaProcessor {
/**
* AutoService 注解名
*/
private static final String AUTO_SERVICE_NAME = net.dreamlu.mica.auto.annotation.AutoService.class.getName();
public static final String AUTO_SERVICE_NAME = net.dreamlu.mica.auto.annotation.AutoService.class.getName();
/**
* spi 服务集合,key 接口 -> value 实现列表
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 ([email protected] & www.dreamlu.net).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 net.dreamlu.mica.auto.service;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.devtools.ksp.processing.*;
import com.google.devtools.ksp.symbol.*;
import kotlin.Pair;
import kotlin.sequences.Sequence;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
* ksp AutoService 处理器
*
* @author L.cm
*/
public class AutoServiceSymbolProcessor implements SymbolProcessor {
private final SymbolProcessorEnvironment environment;
private final CodeGenerator codeGenerator;
private final KSPLogger logger;

/**
* Maps the class names of service provider interfaces to the class names of the concrete classes
* which implement them plus their KSFile (for incremental processing).
* <p>
* For example,
* ```
* "com.google.apphosting.LocalRpcService" -> "com.google.apphosting.datastore.LocalDatastoreService"
* ```
*/
private final Multimap<String, Pair<String, KSFile>> providers = HashMultimap.create();

private final boolean verify;
private final boolean verbose;

public AutoServiceSymbolProcessor(SymbolProcessorEnvironment environment) {
this.environment = environment;
this.codeGenerator = environment.getCodeGenerator();
this.logger = environment.getLogger();
this.verify = getOrDefault(environment, "autoserviceKsp.verify");
this.verbose = getOrDefault(environment, "autoserviceKsp.verbose");
}

@NotNull
@Override
public List<KSAnnotated> process(@NotNull Resolver resolver) {
KSName autoServiceKsName = resolver.getKSNameFromString(AutoServiceProcessor.AUTO_SERVICE_NAME);
KSClassDeclaration classDeclaration = resolver.getClassDeclarationByName(autoServiceKsName);
if (classDeclaration == null) {
String message = "@AutoService type not found on the classpath, skipping processing.";
if (verbose) {
logger.warn(message, null);
} else {
logger.info(message, null);
}
return Collections.emptyList();
}
KSType autoServiceType = classDeclaration.asType(Collections.emptyList());
Sequence<KSAnnotated> annotatedSequence = resolver.getSymbolsWithAnnotation(AutoServiceProcessor.AUTO_SERVICE_NAME, false);
Iterator<KSAnnotated> it = annotatedSequence.iterator();
if (it.hasNext()) {
KSAnnotated ksAnnotated = it.next();
if (ksAnnotated instanceof KSClassDeclaration) {
System.out.println(ksAnnotated);
}
System.out.println(ksAnnotated);
}
return Collections.emptyList();
}

private void log(String message) {
if (verbose) {
logger.logging(message, null);
}
}

private static boolean getOrDefault(SymbolProcessorEnvironment environment, String key) {
Map<String, String> options = environment.getOptions();
String value = options.get(key);
return value == null || "false".equals(value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2019-2029, Dreamlu 卢春梦 ([email protected] & www.dreamlu.net).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 net.dreamlu.mica.auto.service;

import com.google.auto.service.AutoService;
import com.google.devtools.ksp.processing.SymbolProcessor;
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
import com.google.devtools.ksp.processing.SymbolProcessorProvider;
import org.jetbrains.annotations.NotNull;

/**
* AutoService SymbolProcessorProvider
*
* @author L.cm
*/
@AutoService(SymbolProcessorProvider.class)
public class AutoServiceSymbolProcessorProvider implements SymbolProcessorProvider {

@NotNull
@Override
public SymbolProcessor create(@NotNull SymbolProcessorEnvironment environment) {
return new AutoServiceSymbolProcessor(environment);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package net.dreamlu.mica.auto.service;

import com.tschuchort.compiletesting.CompilationResult;
import com.tschuchort.compiletesting.KotlinCompilation;
import com.tschuchort.compiletesting.KotlinCompilation.ExitCode;
import com.tschuchort.compiletesting.KspKt;
import com.tschuchort.compiletesting.SourceFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import static com.google.common.truth.Truth.assertThat;

@RunWith(Parameterized.class)
public class AutoServiceSymbolProcessorTest {
private final boolean incremental;

public AutoServiceSymbolProcessorTest(boolean incremental) {
this.incremental = incremental;
}

@Parameters(name = "incremental={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{{false}, {true}});
}

@Test
public void smokeTest() {
SourceFile source = SourceFile.Companion.kotlin("CustomCallable.kt",
"package test;\n" +
"import net.dreamlu.mica.auto.annotation.AutoService;\n" +
"import java.util.concurrent.Callable;\n" +
"\n" +
"@AutoService(Callable.class)\n" +
"class CustomCallable : Callable<String> {\n" +
" override fun call(): String = \"Hello world!\"\n" +
"}", true);

KotlinCompilation compilation = new KotlinCompilation();
compilation.setSources(Collections.singletonList(source));
compilation.setInheritClassPath(true);
KspKt.setSymbolProcessorProviders(compilation, Collections.singletonList(new AutoServiceSymbolProcessorProvider()));
KspKt.setKspIncremental(compilation, incremental);

CompilationResult result = compilation.compile();
String messages = result.getMessages();
System.out.println(messages);

assertThat(result.getExitCode()).isEqualTo(ExitCode.OK);
File generatedSourcesDir = KspKt.getKspSourcesDir(compilation);
File generatedFile = new File(generatedSourcesDir, "resources/META-INF/services/java.util.concurrent.Callable");
assertThat(generatedFile.exists()).isTrue();
}

@Test
public void smokeTestForJava() {
SourceFile source = SourceFile.Companion.java("CustomCallable.java",
"package test;\n" +
"import net.dreamlu.mica.auto.annotation.AutoService;\n" +
"import java.util.concurrent.Callable;\n" +
"\n" +
"@AutoService(Callable.class)\n" +
"public class CustomCallable implements Callable<String> {\n" +
" @Override public String call() { return \"Hello world!\"; }\n" +
"}", false);

KotlinCompilation compilation = new KotlinCompilation();
compilation.setSources(Collections.singletonList(source));
compilation.setInheritClassPath(true);
KspKt.setSymbolProcessorProviders(compilation, Collections.singletonList(new AutoServiceSymbolProcessorProvider()));
KspKt.setKspIncremental(compilation, incremental);

CompilationResult result = compilation.compile();
assertThat(result.getExitCode()).isEqualTo(ExitCode.OK);

File generatedSourcesDir = KspKt.getKspSourcesDir(compilation);
File generatedFile = new File(generatedSourcesDir, "resources/META-INF/services/java.util.concurrent.Callable");
assertThat(generatedFile.exists()).isTrue();
}

@Test
public void errorOnNoServiceInterfacesProvided() {
SourceFile source = SourceFile.Companion.kotlin("CustomCallable.kt",
"package test;\n" +
"import net.dreamlu.mica.auto.annotation.AutoService;\n" +
"import java.util.concurrent.Callable;\n" +
"\n" +
"@AutoService\n" +
"class CustomCallable : Callable<String> {\n" +
" override fun call(): String = \"Hello world!\"\n" +
"}", false);

KotlinCompilation compilation = new KotlinCompilation();
compilation.setSources(Collections.singletonList(source));
compilation.setInheritClassPath(true);
KspKt.setSymbolProcessorProviders(compilation, Collections.singletonList(new AutoServiceSymbolProcessorProvider()));
KspKt.setKspIncremental(compilation, incremental);

CompilationResult result = compilation.compile();
assertThat(result.getExitCode()).isEqualTo(ExitCode.COMPILATION_ERROR);

assertThat(result.getMessages())
.contains("No service interfaces specified by @AutoService annotation!\n" +
"You can provide them in annotation parameters: @AutoService(YourService::class)");
}
}

0 comments on commit 0ab2212

Please sign in to comment.