From eaed0b81cb15c8d6106203d3fc878438148a036b Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 13 Jul 2022 00:29:41 -0400 Subject: [PATCH 01/12] Support creating files with a more flexible API --- .../devtools/ksp/processing/CodeGenerator.kt | 43 +++++++++++++++ .../devtools/ksp/processing/FileType.kt | 8 +++ .../devtools/ksp/symbol/KSAnnotation.kt | 5 ++ .../ksp/processing/impl/CodeGeneratorImpl.kt | 53 +++++++++++++------ 4 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt index 241e1f0613..961bc3702d 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt @@ -63,6 +63,38 @@ interface CodeGenerator { extensionName: String = "kt" ): OutputStream + /** + * Creates a file which is managed by [CodeGenerator] + * + * Sources of corresponding [KSNode]s which are obtained directly from [Resolver] need to be specified. + * Namely, the containing files of those [KSNode]s who are obtained from: + * * [Resolver.getAllFiles] + * * [Resolver.getSymbolsWithAnnotation] + * * [Resolver.getClassDeclarationByName] + * + * Instead of requiring processors to specify all source files which are relevant in generating the given output, + * KSP traces dependencies automatically and only needs to know those sources that only processors know what they + * are for. If a [KSFile] is indirectly obtained through other [KSNode]s, it hasn't to be specified for the given + * output, even if its contents contribute to the generation of the output. + * + * For example, a processor generates an output `O` after reading class `A` in `A.kt` and class `B` in `B.kt`, + * where `A` extends `B`. The processor got `A` by [Resolver.getSymbolsWithAnnotation] and then got `B` by + * [KSClassDeclaration.superTypes] from `A`. Because the inclusion of `B` is due to `A`, `B.kt` needn't to be + * specified in [dependencies] for `O`. Note that specifying `B.kt` in this case doesn't hurt, it is only unnecessary. + * + * @param dependencies are [KSFile]s from which this output is built. Only those that are obtained directly + * from [Resolver] are required. + * @param path corresponds to the relative path of the generated file; includes the full file name + * @param fileType determines the target directory to store the file + * @return OutputStream for writing into files. + * @see [CodeGenerator] for more details. + */ + fun createNewFile( + dependencies: Dependencies, + path: String, + fileType: FileType + ): OutputStream + /** * Associate [sources] to an output file. * @@ -76,6 +108,17 @@ interface CodeGenerator { */ fun associate(sources: List, packageName: String, fileName: String, extensionName: String = "kt") + /** + * Associate [sources] to an output file. + * + * @param sources are [KSFile]s from which this output is built. Only those that are obtained directly + * from [Resolver] are required. + * @param path corresponds to the relative path of the generated file; includes the full file name + * @param fileType determines the target directory where the file should exist + * @see [CodeGenerator] for more details. + */ + fun associate(sources: List, path: String, fileType: FileType) + /** * Associate [classes] to an output file. * diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt new file mode 100644 index 0000000000..86c9945ad2 --- /dev/null +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt @@ -0,0 +1,8 @@ +package com.google.devtools.ksp.processing + +enum class FileType { + CLASS, + JAVA_SOURCE, + KOTLIN_SOURCE, + RESOURCE +} diff --git a/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt b/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt index d65656c8cc..65b2ab7baf 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt @@ -46,4 +46,9 @@ interface KSAnnotation : KSNode { * Use site target of the annotation. Could be null if no annotation use site target is specified. */ val useSiteTarget: AnnotationUseSiteTarget? + + /** + * Returns the fully qualified name of the annotation, if available + */ + fun resolveQualifiedName(): KSName? } diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 6bf1941dfa..bd98721b01 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -20,6 +20,7 @@ package com.google.devtools.ksp.processing.impl import com.google.devtools.ksp.NoSourceFile import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.FileType import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile import java.io.File @@ -54,13 +55,25 @@ class CodeGeneratorImpl( fun pathOf(packageName: String, fileName: String, extensionName: String): String { val packageDirs = if (packageName != "") "${packageName.split(".").joinToString(separator)}$separator" else "" val extension = if (extensionName != "") ".$extensionName" else "" - val typeRoot = when (extensionName) { - "class" -> classDir - "java" -> javaDir - "kt" -> kotlinDir - else -> resourcesDir - }.path - return "$typeRoot$separator$packageDirs$fileName$extension" + return "$packageDirs$fileName$extension" + } + + fun extensionToType(extensionName: String): FileType { + return when (extensionName) { + "class" -> FileType.CLASS + "java" -> FileType.JAVA_SOURCE + "kt" -> FileType.KOTLIN_SOURCE + else -> FileType.RESOURCE + } + } + + fun baseDirOf(fileType: FileType): File { + return when (fileType) { + FileType.CLASS -> classDir + FileType.JAVA_SOURCE -> javaDir + FileType.KOTLIN_SOURCE-> kotlinDir + FileType.RESOURCE -> resourcesDir + } } override fun createNewFile( @@ -69,8 +82,15 @@ class CodeGeneratorImpl( fileName: String, extensionName: String ): OutputStream { - val path = pathOf(packageName, fileName, extensionName) - val file = File(path) + return createNewFile(dependencies, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) + } + + override fun createNewFile( + dependencies: Dependencies, + path: String, + fileType: FileType + ): OutputStream { + val file = File(baseDirOf(fileType), path) if (path in fileMap) { throw FileAlreadyExistsException(file) } @@ -89,14 +109,17 @@ class CodeGeneratorImpl( dependencies.originatingFiles } } - associate(sources, path) + associate(sources, file) fileOutputStreamMap[path] = fileMap[path]!!.outputStream() return fileOutputStreamMap[path]!! } override fun associate(sources: List, packageName: String, fileName: String, extensionName: String) { - val path = pathOf(packageName, fileName, extensionName) - associate(sources, path) + associate(sources, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) + } + + override fun associate(sources: List, path: String, fileType: FileType) { + associate(sources, File(baseDirOf(fileType), path)) } override fun associateWithClasses( @@ -109,14 +132,14 @@ class CodeGeneratorImpl( val files = classes.map { it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString()) } - associate(files, path) + associate(files, File(path)) } - private fun associate(sources: List, outputPath: String) { + private fun associate(sources: List, outputPath: File) { if (!isIncremental) return - val output = File(outputPath).relativeTo(projectBase) + val output = outputPath.relativeTo(projectBase) sources.forEach { source -> sourceToOutputs.getOrPut(File(source.filePath).relativeTo(projectBase)) { mutableSetOf() }.add(output) } From 4cb564fbe562e348b60d7e07efe6eae2208941e1 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 13 Jul 2022 00:52:32 -0400 Subject: [PATCH 02/12] Add test --- .../processing/impl/CodeGeneratorImplTest.kt | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt diff --git a/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt new file mode 100644 index 0000000000..c1681e527b --- /dev/null +++ b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt @@ -0,0 +1,96 @@ +package com.google.devtools.ksp.processing.impl + +import com.google.devtools.ksp.AnyChanges +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.FileType +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.io.File +import java.nio.file.Files + +class CodeGeneratorImplTest { + + lateinit var codeGenerator: CodeGenerator + lateinit var baseDir: File + + @Before + fun setup() { + baseDir = Files.createTempDirectory("project").toFile() + val classesDir = File(baseDir, "classes") + classesDir.mkdir() + val javaDir = File(baseDir, "java") + javaDir.mkdir() + val kotlinDir = File(baseDir, "kotlin") + kotlinDir.mkdir() + val resourcesDir = File(baseDir, "resources") + resourcesDir.mkdir() + codeGenerator = CodeGeneratorImpl( + classesDir, + javaDir, + kotlinDir, + resourcesDir, + baseDir, + AnyChanges(baseDir), + emptyList(), + true + ) + } + + @Test + fun testCreatingAFile() { + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a.b.c", "Test", "java") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a.b.c", "Test", "kt") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a.b.c", "Test", "class") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a.b.c", "Test", "") + + val files = codeGenerator.generatedFile.toList() + Assert.assertEquals(File(baseDir, "java/a/b/c/Test.java"), files[0]) + Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) + Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) + Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + } + + @Test + fun testCreatingAFileWithSlash() { + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c", "Test", "java") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c", "Test", "kt") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c", "Test", "class") + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c", "Test", "") + + val files = codeGenerator.generatedFile.toList() + Assert.assertEquals(File(baseDir, "java/a/b/c/Test.java"), files[0]) + Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) + Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) + Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + } + + @Test + fun testCreatingAFileWithPath() { + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.java", FileType.JAVA_SOURCE) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.kt", FileType.KOTLIN_SOURCE) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.class", FileType.CLASS) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test", FileType.RESOURCE) + + val files = codeGenerator.generatedFile.toList() + Assert.assertEquals(File(baseDir, "java/a/b/c/Test.java"), files[0]) + Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) + Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) + Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + } + + @Test + fun testCreatingAFileWithPathAndDots() { + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.java", FileType.JAVA_SOURCE) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.kt", FileType.KOTLIN_SOURCE) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.class", FileType.CLASS) + codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test", FileType.RESOURCE) + + val files = codeGenerator.generatedFile.toList() + Assert.assertEquals(File(baseDir, "java/a/b/c/dir.with.dot/Test.java"), files[0]) + Assert.assertEquals(File(baseDir, "kotlin/a/b/c/dir.with.dot/Test.kt"), files[1]) + Assert.assertEquals(File(baseDir, "classes/a/b/c/dir.with.dot/Test.class"), files[2]) + Assert.assertEquals(File(baseDir, "resources/a/b/c/dir.with.dot/Test"), files[3]) + } +} From afd9699c7f59644ea4d39769bdc76ca492312911 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 13 Jul 2022 12:58:20 -0400 Subject: [PATCH 03/12] Add space before arrow --- .../google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index bd98721b01..9bf7807e4d 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -71,7 +71,7 @@ class CodeGeneratorImpl( return when (fileType) { FileType.CLASS -> classDir FileType.JAVA_SOURCE -> javaDir - FileType.KOTLIN_SOURCE-> kotlinDir + FileType.KOTLIN_SOURCE -> kotlinDir FileType.RESOURCE -> resourcesDir } } From 87f66b265b2a12f931b2ecd5b0bf3f31c29ecf51 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 13 Jul 2022 13:27:05 -0400 Subject: [PATCH 04/12] Remove accidental addition and add javadoc for FileType --- .../kotlin/com/google/devtools/ksp/processing/FileType.kt | 4 ++++ .../kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt index 86c9945ad2..d8316fab72 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt @@ -1,5 +1,9 @@ package com.google.devtools.ksp.processing +/** + * Used to determine where files should be stored when being + * created through the [CodeGenerator]. + */ enum class FileType { CLASS, JAVA_SOURCE, diff --git a/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt b/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt index 65b2ab7baf..d65656c8cc 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSAnnotation.kt @@ -46,9 +46,4 @@ interface KSAnnotation : KSNode { * Use site target of the annotation. Could be null if no annotation use site target is specified. */ val useSiteTarget: AnnotationUseSiteTarget? - - /** - * Returns the fully qualified name of the annotation, if available - */ - fun resolveQualifiedName(): KSName? } From 9a6bd2d49813c52cf8df355d4a5179b9cf0df48e Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Fri, 29 Jul 2022 22:37:46 -0400 Subject: [PATCH 05/12] Use extension and prevent escaping the target base directory with .. --- .../devtools/ksp/processing/CodeGenerator.kt | 4 +- .../ksp/processing/impl/CodeGeneratorImpl.kt | 65 ++++++++++++------- .../processing/impl/CodeGeneratorImplTest.kt | 27 +++++--- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt index 961bc3702d..6c2ed998ca 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt @@ -89,10 +89,10 @@ interface CodeGenerator { * @return OutputStream for writing into files. * @see [CodeGenerator] for more details. */ - fun createNewFile( + fun createNewFileByPath( dependencies: Dependencies, path: String, - fileType: FileType + extensionName: String = "kt" ): OutputStream /** diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 9bf7807e4d..2e65afdda7 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -25,7 +25,9 @@ import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile import java.io.File import java.io.FileOutputStream +import java.io.IOException import java.io.OutputStream +import java.nio.file.Path class CodeGeneratorImpl( private val classDir: File, @@ -85,15 +87,41 @@ class CodeGeneratorImpl( return createNewFile(dependencies, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) } - override fun createNewFile( - dependencies: Dependencies, - path: String, - fileType: FileType - ): OutputStream { - val file = File(baseDirOf(fileType), path) + override fun createNewFileByPath(dependencies: Dependencies, path: String, extensionName: String): OutputStream { + val extension = if (extensionName != "") ".$extensionName" else "" + return createNewFile(dependencies, path + extension, extensionToType(extensionName)) + } + + override fun associate(sources: List, packageName: String, fileName: String, extensionName: String) { + associate(sources, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) + } + + override fun associate(sources: List, path: String, fileType: FileType) { + associate(sources, File(baseDirOf(fileType), path)) + } + + override fun associateWithClasses( + classes: List, + packageName: String, + fileName: String, + extensionName: String + ) { + val path = pathOf(packageName, fileName, extensionName) + val files = classes.map { + it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString()) + } + associate(files, File(path)) + } + + private fun createNewFile(dependencies: Dependencies, path: String, fileType: FileType): OutputStream { + val baseDir = baseDirOf(fileType) + val file = File(baseDir, path) if (path in fileMap) { throw FileAlreadyExistsException(file) } + if (!isWithinBaseDir(baseDir, file)) { + throw IllegalStateException("requested path is outside the bounds of the required directory") + } val parentFile = file.parentFile if (!parentFile.exists() && !parentFile.mkdirs()) { throw IllegalStateException("failed to make parent directories.") @@ -114,25 +142,14 @@ class CodeGeneratorImpl( return fileOutputStreamMap[path]!! } - override fun associate(sources: List, packageName: String, fileName: String, extensionName: String) { - associate(sources, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) - } - - override fun associate(sources: List, path: String, fileType: FileType) { - associate(sources, File(baseDirOf(fileType), path)) - } - - override fun associateWithClasses( - classes: List, - packageName: String, - fileName: String, - extensionName: String - ) { - val path = pathOf(packageName, fileName, extensionName) - val files = classes.map { - it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString()) + private fun isWithinBaseDir(baseDir: File, file: File): Boolean { + val base = baseDir.toPath().normalize() + return try { + val relativePath = file.toPath().normalize() + relativePath.startsWith(base) + } catch (e: IOException) { + false } - associate(files, File(path)) } private fun associate(sources: List, outputPath: File) { diff --git a/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt index c1681e527b..52676a3e3e 100644 --- a/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt +++ b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt @@ -3,7 +3,6 @@ package com.google.devtools.ksp.processing.impl import com.google.devtools.ksp.AnyChanges import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies -import com.google.devtools.ksp.processing.FileType import org.junit.Assert import org.junit.Before import org.junit.Test @@ -68,10 +67,10 @@ class CodeGeneratorImplTest { @Test fun testCreatingAFileWithPath() { - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.java", FileType.JAVA_SOURCE) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.kt", FileType.KOTLIN_SOURCE) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test.class", FileType.CLASS) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/Test", FileType.RESOURCE) + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/Test", "java") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/Test") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/Test", "class") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/Test", "") val files = codeGenerator.generatedFile.toList() Assert.assertEquals(File(baseDir, "java/a/b/c/Test.java"), files[0]) @@ -82,10 +81,10 @@ class CodeGeneratorImplTest { @Test fun testCreatingAFileWithPathAndDots() { - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.java", FileType.JAVA_SOURCE) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.kt", FileType.KOTLIN_SOURCE) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test.class", FileType.CLASS) - codeGenerator.createNewFile(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test", FileType.RESOURCE) + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test", "java") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test", "class") + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "a/b/c/dir.with.dot/Test", "") val files = codeGenerator.generatedFile.toList() Assert.assertEquals(File(baseDir, "java/a/b/c/dir.with.dot/Test.java"), files[0]) @@ -93,4 +92,14 @@ class CodeGeneratorImplTest { Assert.assertEquals(File(baseDir, "classes/a/b/c/dir.with.dot/Test.class"), files[2]) Assert.assertEquals(File(baseDir, "resources/a/b/c/dir.with.dot/Test"), files[3]) } + + @Test + fun testCreatingAFileByPathWithInvalidPath() { + try { + codeGenerator.createNewFileByPath(Dependencies.ALL_FILES, "../../b/c/Test", "java") + Assert.fail() + } catch (e: java.lang.IllegalStateException) { + Assert.assertEquals(e.message, "requested path is outside the bounds of the required directory") + } + } } From 9cb52bd4e760e6f1c87f78cad6e80d7fc0f97abc Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Sat, 30 Jul 2022 20:57:56 -0400 Subject: [PATCH 06/12] Fix tests --- .../ksp/processing/impl/CodeGeneratorImpl.kt | 15 ++++++----- .../processing/impl/CodeGeneratorImplTest.kt | 27 +++++++++++++++++-- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 2e65afdda7..200039165c 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -116,18 +116,19 @@ class CodeGeneratorImpl( private fun createNewFile(dependencies: Dependencies, path: String, fileType: FileType): OutputStream { val baseDir = baseDirOf(fileType) val file = File(baseDir, path) - if (path in fileMap) { - throw FileAlreadyExistsException(file) - } if (!isWithinBaseDir(baseDir, file)) { throw IllegalStateException("requested path is outside the bounds of the required directory") } + val absolutePath = file.absolutePath + if (absolutePath in fileMap) { + throw FileAlreadyExistsException(file) + } val parentFile = file.parentFile if (!parentFile.exists() && !parentFile.mkdirs()) { throw IllegalStateException("failed to make parent directories.") } file.writeText("") - fileMap[path] = file + fileMap[absolutePath] = file val sources = if (dependencies.isAllSources) { allSources + anyChangesWildcard } else { @@ -138,8 +139,8 @@ class CodeGeneratorImpl( } } associate(sources, file) - fileOutputStreamMap[path] = fileMap[path]!!.outputStream() - return fileOutputStreamMap[path]!! + fileOutputStreamMap[absolutePath] = fileMap[absolutePath]!!.outputStream() + return fileOutputStreamMap[absolutePath]!! } private fun isWithinBaseDir(baseDir: File, file: File): Boolean { @@ -163,7 +164,7 @@ class CodeGeneratorImpl( } val outputs: Set - get() = fileMap.keys.mapTo(mutableSetOf()) { File(it).relativeTo(projectBase) } + get() = fileMap.values.toMutableSet() override val generatedFile: Collection get() = fileOutputStreamMap.keys.map { fileMap[it]!! } diff --git a/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt index 52676a3e3e..cd958f979a 100644 --- a/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt +++ b/common-util/src/test/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImplTest.kt @@ -1,7 +1,6 @@ package com.google.devtools.ksp.processing.impl import com.google.devtools.ksp.AnyChanges -import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies import org.junit.Assert import org.junit.Before @@ -11,7 +10,7 @@ import java.nio.file.Files class CodeGeneratorImplTest { - lateinit var codeGenerator: CodeGenerator + lateinit var codeGenerator: CodeGeneratorImpl lateinit var baseDir: File @Before @@ -49,6 +48,12 @@ class CodeGeneratorImplTest { Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + + try { + codeGenerator.outputs + } catch (e: Exception) { + Assert.fail("Failed to get outputs: ${e.message}") + } } @Test @@ -63,6 +68,12 @@ class CodeGeneratorImplTest { Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + + try { + codeGenerator.outputs + } catch (e: Exception) { + Assert.fail("Failed to get outputs: ${e.message}") + } } @Test @@ -77,6 +88,12 @@ class CodeGeneratorImplTest { Assert.assertEquals(File(baseDir, "kotlin/a/b/c/Test.kt"), files[1]) Assert.assertEquals(File(baseDir, "classes/a/b/c/Test.class"), files[2]) Assert.assertEquals(File(baseDir, "resources/a/b/c/Test"), files[3]) + + try { + codeGenerator.outputs + } catch (e: Exception) { + Assert.fail("Failed to get outputs: ${e.message}") + } } @Test @@ -91,6 +108,12 @@ class CodeGeneratorImplTest { Assert.assertEquals(File(baseDir, "kotlin/a/b/c/dir.with.dot/Test.kt"), files[1]) Assert.assertEquals(File(baseDir, "classes/a/b/c/dir.with.dot/Test.class"), files[2]) Assert.assertEquals(File(baseDir, "resources/a/b/c/dir.with.dot/Test"), files[3]) + + try { + codeGenerator.outputs + } catch (e: Exception) { + Assert.fail("Failed to get outputs: ${e.message}") + } } @Test From 802e34ce8b9427b6bbf4f2e7a162897188ba5375 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Sat, 30 Jul 2022 21:39:27 -0400 Subject: [PATCH 07/12] Fix tests --- .../google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 200039165c..5413a7b11b 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -164,7 +164,7 @@ class CodeGeneratorImpl( } val outputs: Set - get() = fileMap.values.toMutableSet() + get() = fileMap.values.mapTo(mutableSetOf()) { it.relativeTo(projectBase) } override val generatedFile: Collection get() = fileOutputStreamMap.keys.map { fileMap[it]!! } From d5ec223ebbc59c94eebda77fb1faafe7f8ced475 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Sat, 30 Jul 2022 23:59:48 -0400 Subject: [PATCH 08/12] Change associate api to use extension --- .../devtools/ksp/processing/CodeGenerator.kt | 2 +- .../ksp/processing/impl/CodeGeneratorImpl.kt | 52 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt index 6c2ed998ca..bcacc2ab90 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt @@ -117,7 +117,7 @@ interface CodeGenerator { * @param fileType determines the target directory where the file should exist * @see [CodeGenerator] for more details. */ - fun associate(sources: List, path: String, fileType: FileType) + fun associate(sources: List, path: String, extensionName: String = "kt") /** * Associate [classes] to an output file. diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 5413a7b11b..41519dd220 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -60,24 +60,6 @@ class CodeGeneratorImpl( return "$packageDirs$fileName$extension" } - fun extensionToType(extensionName: String): FileType { - return when (extensionName) { - "class" -> FileType.CLASS - "java" -> FileType.JAVA_SOURCE - "kt" -> FileType.KOTLIN_SOURCE - else -> FileType.RESOURCE - } - } - - fun baseDirOf(fileType: FileType): File { - return when (fileType) { - FileType.CLASS -> classDir - FileType.JAVA_SOURCE -> javaDir - FileType.KOTLIN_SOURCE -> kotlinDir - FileType.RESOURCE -> resourcesDir - } - } - override fun createNewFile( dependencies: Dependencies, packageName: String, @@ -96,8 +78,9 @@ class CodeGeneratorImpl( associate(sources, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) } - override fun associate(sources: List, path: String, fileType: FileType) { - associate(sources, File(baseDirOf(fileType), path)) + override fun associate(sources: List, path: String, extensionName: String) { + val extension = if (extensionName != "") ".$extensionName" else "" + associate(sources, path + extension, extensionToType(extensionName)) } override fun associateWithClasses( @@ -110,7 +93,25 @@ class CodeGeneratorImpl( val files = classes.map { it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString()) } - associate(files, File(path)) + associate(files, path, extensionToType(extensionName)) + } + + private fun extensionToType(extensionName: String): FileType { + return when (extensionName) { + "class" -> FileType.CLASS + "java" -> FileType.JAVA_SOURCE + "kt" -> FileType.KOTLIN_SOURCE + else -> FileType.RESOURCE + } + } + + private fun baseDirOf(fileType: FileType): File { + return when (fileType) { + FileType.CLASS -> classDir + FileType.JAVA_SOURCE -> javaDir + FileType.KOTLIN_SOURCE -> kotlinDir + FileType.RESOURCE -> resourcesDir + } } private fun createNewFile(dependencies: Dependencies, path: String, fileType: FileType): OutputStream { @@ -153,6 +154,15 @@ class CodeGeneratorImpl( } } + private fun associate(sources: List, path: String, fileType: FileType) { + val baseDir = baseDirOf(fileType) + val file = File(baseDir, path) + if (!isWithinBaseDir(baseDir, file)) { + throw IllegalStateException("requested path is outside the bounds of the required directory") + } + associate(sources, file) + } + private fun associate(sources: List, outputPath: File) { if (!isIncremental) return From 3652cb6bae31b578eb12c50b10ee64165a32b1f0 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 10 Aug 2022 20:51:57 -0400 Subject: [PATCH 09/12] Remove FileType --- .../devtools/ksp/processing/FileType.kt | 12 ------ .../ksp/processing/impl/CodeGeneratorImpl.kt | 37 ++++++------------- 2 files changed, 12 insertions(+), 37 deletions(-) delete mode 100644 api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt deleted file mode 100644 index d8316fab72..0000000000 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/FileType.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.google.devtools.ksp.processing - -/** - * Used to determine where files should be stored when being - * created through the [CodeGenerator]. - */ -enum class FileType { - CLASS, - JAVA_SOURCE, - KOTLIN_SOURCE, - RESOURCE -} diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 41519dd220..0a727b9d1a 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -20,14 +20,12 @@ package com.google.devtools.ksp.processing.impl import com.google.devtools.ksp.NoSourceFile import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies -import com.google.devtools.ksp.processing.FileType import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSFile import java.io.File import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream -import java.nio.file.Path class CodeGeneratorImpl( private val classDir: File, @@ -66,21 +64,21 @@ class CodeGeneratorImpl( fileName: String, extensionName: String ): OutputStream { - return createNewFile(dependencies, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) + return createNewFile(dependencies, pathOf(packageName, fileName, extensionName), extensionToDirectory(extensionName)) } override fun createNewFileByPath(dependencies: Dependencies, path: String, extensionName: String): OutputStream { val extension = if (extensionName != "") ".$extensionName" else "" - return createNewFile(dependencies, path + extension, extensionToType(extensionName)) + return createNewFile(dependencies, path + extension, extensionToDirectory(extensionName)) } override fun associate(sources: List, packageName: String, fileName: String, extensionName: String) { - associate(sources, pathOf(packageName, fileName, extensionName), extensionToType(extensionName)) + associate(sources, pathOf(packageName, fileName, extensionName), extensionToDirectory(extensionName)) } override fun associate(sources: List, path: String, extensionName: String) { val extension = if (extensionName != "") ".$extensionName" else "" - associate(sources, path + extension, extensionToType(extensionName)) + associate(sources, path + extension, extensionToDirectory(extensionName)) } override fun associateWithClasses( @@ -93,29 +91,19 @@ class CodeGeneratorImpl( val files = classes.map { it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString()) } - associate(files, path, extensionToType(extensionName)) + associate(files, path, extensionToDirectory(extensionName)) } - private fun extensionToType(extensionName: String): FileType { + private fun extensionToDirectory(extensionName: String): File { return when (extensionName) { - "class" -> FileType.CLASS - "java" -> FileType.JAVA_SOURCE - "kt" -> FileType.KOTLIN_SOURCE - else -> FileType.RESOURCE + "class" -> classDir + "java" -> javaDir + "kt" -> kotlinDir + else -> resourcesDir } } - private fun baseDirOf(fileType: FileType): File { - return when (fileType) { - FileType.CLASS -> classDir - FileType.JAVA_SOURCE -> javaDir - FileType.KOTLIN_SOURCE -> kotlinDir - FileType.RESOURCE -> resourcesDir - } - } - - private fun createNewFile(dependencies: Dependencies, path: String, fileType: FileType): OutputStream { - val baseDir = baseDirOf(fileType) + private fun createNewFile(dependencies: Dependencies, path: String, baseDir: File): OutputStream { val file = File(baseDir, path) if (!isWithinBaseDir(baseDir, file)) { throw IllegalStateException("requested path is outside the bounds of the required directory") @@ -154,8 +142,7 @@ class CodeGeneratorImpl( } } - private fun associate(sources: List, path: String, fileType: FileType) { - val baseDir = baseDirOf(fileType) + private fun associate(sources: List, path: String, baseDir: File) { val file = File(baseDir, path) if (!isWithinBaseDir(baseDir, file)) { throw IllegalStateException("requested path is outside the bounds of the required directory") From 8f38668277832fe02687709177d0513807ba1ebb Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 10 Aug 2022 21:22:03 -0400 Subject: [PATCH 10/12] Fix test --- .../kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt | 2 +- .../google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt index bcacc2ab90..fd5bc5b1b5 100644 --- a/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt +++ b/api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt @@ -117,7 +117,7 @@ interface CodeGenerator { * @param fileType determines the target directory where the file should exist * @see [CodeGenerator] for more details. */ - fun associate(sources: List, path: String, extensionName: String = "kt") + fun associateByPath(sources: List, path: String, extensionName: String = "kt") /** * Associate [classes] to an output file. diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 0a727b9d1a..1b2de3c4e9 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -76,7 +76,7 @@ class CodeGeneratorImpl( associate(sources, pathOf(packageName, fileName, extensionName), extensionToDirectory(extensionName)) } - override fun associate(sources: List, path: String, extensionName: String) { + override fun associateByPath(sources: List, path: String, extensionName: String) { val extension = if (extensionName != "") ".$extensionName" else "" associate(sources, path + extension, extensionToDirectory(extensionName)) } From dbefb20695b4a33ae2ba32ad794d2f7ede7f9034 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Wed, 10 Aug 2022 22:34:52 -0400 Subject: [PATCH 11/12] Fix line length --- .../devtools/ksp/processing/impl/CodeGeneratorImpl.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt index 1b2de3c4e9..f9293bc6c0 100644 --- a/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt +++ b/common-util/src/main/kotlin/com/google/devtools/ksp/processing/impl/CodeGeneratorImpl.kt @@ -64,7 +64,11 @@ class CodeGeneratorImpl( fileName: String, extensionName: String ): OutputStream { - return createNewFile(dependencies, pathOf(packageName, fileName, extensionName), extensionToDirectory(extensionName)) + return createNewFile( + dependencies, + pathOf(packageName, fileName, extensionName), + extensionToDirectory(extensionName) + ) } override fun createNewFileByPath(dependencies: Dependencies, path: String, extensionName: String): OutputStream { From c541217a8a7ac101c269e6c6e1dd0bb7664b5c38 Mon Sep 17 00:00:00 2001 From: jameskleeh Date: Thu, 11 Aug 2022 23:12:22 -0400 Subject: [PATCH 12/12] Update api --- api/api.base | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/api.base b/api/api.base index 1b94cdb95b..60c2a8db3d 100644 --- a/api/api.base +++ b/api/api.base @@ -59,8 +59,10 @@ package com.google.devtools.ksp.processing { public interface CodeGenerator { method public void associate(@NonNull java.util.List sources, @NonNull String packageName, @NonNull String fileName, @NonNull String extensionName = "kt"); + method public void associateByPath(@NonNull java.util.List sources, @NonNull String path, @NonNull String extensionName = "kt"); method public void associateWithClasses(@NonNull java.util.List classes, @NonNull String packageName, @NonNull String fileName, @NonNull String extensionName = "kt"); method @NonNull public java.io.OutputStream createNewFile(@NonNull com.google.devtools.ksp.processing.Dependencies dependencies, @NonNull String packageName, @NonNull String fileName, @NonNull String extensionName = "kt"); + method @NonNull public java.io.OutputStream createNewFileByPath(@NonNull com.google.devtools.ksp.processing.Dependencies dependencies, @NonNull String path, @NonNull String extensionName = "kt"); method @NonNull public java.util.Collection getGeneratedFile(); property @NonNull public abstract java.util.Collection generatedFile; }