Merge "Move JvmOverloads workaround for constructors to Psi model" into metalava-main
diff --git a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiEnvironmentManager.kt b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiEnvironmentManager.kt
index 2a38904..d21f12d 100644
--- a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiEnvironmentManager.kt
+++ b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiEnvironmentManager.kt
@@ -135,6 +135,7 @@
modelOptions: ModelOptions,
allowReadingComments: Boolean,
jdkHome: File?,
+ projectDescription: File?,
): SourceParser {
return PsiSourceParser(
psiEnvironmentManager = this,
@@ -145,6 +146,7 @@
useK2Uast = modelOptions[PsiModelOptions.useK2Uast],
allowReadingComments = allowReadingComments,
jdkHome = jdkHome,
+ projectDescription = projectDescription,
)
}
diff --git a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiSourceParser.kt b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiSourceParser.kt
index a9d059e..211dd33 100644
--- a/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiSourceParser.kt
+++ b/metalava-model-psi/src/main/java/com/android/tools/metalava/model/psi/PsiSourceParser.kt
@@ -65,6 +65,7 @@
private val useK2Uast: Boolean = false,
private val allowReadingComments: Boolean,
private val jdkHome: File? = null,
+ private val projectDescription: File? = null,
) : SourceParser {
override fun getClassResolver(classPath: List<File>): ClassResolver {
@@ -109,16 +110,22 @@
val rootDir = sourceSet.sourcePath.firstOrNull() ?: File("").canonicalFile
- if (commonSourceSet.sources.isNotEmpty()) {
- configureUastEnvironmentForKMP(
- config,
- sourceSet.sources,
- commonSourceSet.sources,
- classpath,
- rootDir
- )
- } else {
- configureUastEnvironment(config, sourceSet.sourcePath, classpath, rootDir)
+ when {
+ projectDescription != null -> {
+ configureUastEnvironmentFromProjectDescription(config, projectDescription)
+ }
+ commonSourceSet.sources.isNotEmpty() -> {
+ configureUastEnvironmentForKMP(
+ config,
+ sourceSet.sources,
+ commonSourceSet.sources,
+ classpath,
+ rootDir
+ )
+ }
+ else -> {
+ configureUastEnvironment(config, sourceSet.sourcePath, classpath, rootDir)
+ }
}
// K1 UAST: loading of JDK (via compiler config, i.e., only for FE1.0), when using JDK9+
jdkHome?.let {
@@ -233,7 +240,11 @@
for (dep in classpath) {
// TODO: what other kinds of dependencies?
if (dep.extension !in SUPPORTED_CLASSPATH_EXT) continue
- appendLine(" <classpath ${dep.extension}=\"${dep.absolutePath}\" />")
+ if (dep.extension == "klib") {
+ appendLine(" <klib file=\"${dep.absolutePath}\" />")
+ } else {
+ appendLine(" <classpath ${dep.extension}=\"${dep.absolutePath}\" />")
+ }
}
}
@@ -294,6 +305,13 @@
}
projectXml.writeText(description)
+ configureUastEnvironmentFromProjectDescription(config, projectXml)
+ }
+
+ private fun configureUastEnvironmentFromProjectDescription(
+ config: UastEnvironment.Configuration,
+ projectDescription: File,
+ ) {
val lintClient = MetalavaCliClient()
// This will parse the description of Lint's project model and populate the module structure
// inside the given Lint client. We will use it to set up the project structure that
@@ -303,7 +321,7 @@
// There are a couple of limitations that force use fall into this long steps:
// * Lint Project creation is not exposed at all. Only project.xml parsing is available.
// * UastEnvironment Module simply reuses existing Lint Project model.
- computeMetadata(lintClient, projectXml)
+ computeMetadata(lintClient, projectDescription)
config.addModules(
lintClient.knownProjects.map { lintProject ->
lintProject.kotlinLanguageLevel = kotlinLanguageLevel
diff --git a/metalava-model-source/src/main/java/com/android/tools/metalava/model/source/EnvironmentManager.kt b/metalava-model-source/src/main/java/com/android/tools/metalava/model/source/EnvironmentManager.kt
index 8a911fa..32eab54 100644
--- a/metalava-model-source/src/main/java/com/android/tools/metalava/model/source/EnvironmentManager.kt
+++ b/metalava-model-source/src/main/java/com/android/tools/metalava/model/source/EnvironmentManager.kt
@@ -41,6 +41,7 @@
* @param kotlinLanguageLevel the kotlin language level as a string, e.g. 1.8, etc.
* @param modelOptions a set of model specific options provided by the caller.
* @param jdkHome the optional path to the jdk home directory.
+ * @param projectDescription Lint project model that can describe project structures in detail.
*/
fun createSourceParser(
reporter: Reporter,
@@ -50,6 +51,7 @@
modelOptions: ModelOptions = ModelOptions.empty,
allowReadingComments: Boolean = true,
jdkHome: File? = null,
+ projectDescription: File? = null,
): SourceParser
}
diff --git a/metalava-model-turbine/src/main/java/com/android/tools/metalava/model/turbine/TurbineEnvironmentManager.kt b/metalava-model-turbine/src/main/java/com/android/tools/metalava/model/turbine/TurbineEnvironmentManager.kt
index f555dd6..f8853b7 100644
--- a/metalava-model-turbine/src/main/java/com/android/tools/metalava/model/turbine/TurbineEnvironmentManager.kt
+++ b/metalava-model-turbine/src/main/java/com/android/tools/metalava/model/turbine/TurbineEnvironmentManager.kt
@@ -34,6 +34,7 @@
modelOptions: ModelOptions,
allowReadingComments: Boolean,
jdkHome: File?,
+ projectDescription: File?,
): SourceParser {
return TurbineSourceParser(reporter, annotationManager, allowReadingComments)
}
diff --git a/metalava-testing/src/main/java/com/android/tools/metalava/testing/TestUtils.kt b/metalava-testing/src/main/java/com/android/tools/metalava/testing/TestUtils.kt
index 6b673aa..8534574 100644
--- a/metalava-testing/src/main/java/com/android/tools/metalava/testing/TestUtils.kt
+++ b/metalava-testing/src/main/java/com/android/tools/metalava/testing/TestUtils.kt
@@ -29,6 +29,10 @@
return TestFiles.source(to, source.trimIndent())
}
+fun xml(to: String, @Language("XML") source: String): TestFile {
+ return TestFiles.xml(to, source.trimIndent())
+}
+
fun java(to: String, @Language("JAVA") source: String): TestFile {
return TestFiles.java(to, source.trimIndent())
}
diff --git a/metalava/src/main/java/com/android/tools/metalava/Driver.kt b/metalava/src/main/java/com/android/tools/metalava/Driver.kt
index 3063b33..15bf58a 100644
--- a/metalava/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/metalava/src/main/java/com/android/tools/metalava/Driver.kt
@@ -154,6 +154,7 @@
modelOptions = modelOptions,
allowReadingComments = options.allowReadingComments,
jdkHome = options.jdkHome,
+ projectDescription = options.projectDescription,
)
val signatureFileCache = options.signatureFileCache
diff --git a/metalava/src/main/java/com/android/tools/metalava/Options.kt b/metalava/src/main/java/com/android/tools/metalava/Options.kt
index b6d7f7b..df7b7a9 100644
--- a/metalava/src/main/java/com/android/tools/metalava/Options.kt
+++ b/metalava/src/main/java/com/android/tools/metalava/Options.kt
@@ -195,8 +195,8 @@
const val ARG_SDK_JAR_ROOT = "--sdk-extensions-root"
const val ARG_SDK_INFO_FILE = "--sdk-extensions-info"
const val ARG_USE_K2_UAST = "--Xuse-k2-uast"
+const val ARG_PROJECT = "--project"
const val ARG_SOURCE_MODEL_PROVIDER = "--source-model-provider"
-
const val ARG_CONFIG_FILE = "--config-file"
class Options(
@@ -320,6 +320,9 @@
/** All source files to parse */
var sources: List<File> = mutableSources
+ /** Lint project description that describes project's module structure in details */
+ var projectDescription: File? = null
+
val configFiles by
option(
ARG_CONFIG_FILE,
@@ -890,6 +893,9 @@
compileSdkVersion = getValue(args, ++index)
}
ARG_USE_K2_UAST -> useK2Uast = true
+ ARG_PROJECT -> {
+ projectDescription = stringToExistingFile(getValue(args, ++index))
+ }
ARG_SDK_JAR_ROOT -> {
sdkJarRoot = stringToExistingDir(getValue(args, ++index))
}
@@ -1235,6 +1241,8 @@
"One or more directories or jars (separated by " +
"`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " +
"source files",
+ "$ARG_PROJECT <xmlfile>",
+ "Project description written in XML according to Lint's project model.",
"$ARG_MERGE_QUALIFIER_ANNOTATIONS <file>",
"An external annotations file to merge and overlay " +
"the sources, or a directory of such files. Should be used for annotations intended for " +
diff --git a/metalava/src/test/java/com/android/tools/metalava/DriverTest.kt b/metalava/src/test/java/com/android/tools/metalava/DriverTest.kt
index 5c35f5b..a361abc 100644
--- a/metalava/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/metalava/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -534,8 +534,10 @@
sourceFiles: Array<TestFile> = emptyArray(),
/** The common source files to pass to the analyzer */
commonSourceFiles: Array<TestFile> = emptyArray(),
+ /** Lint project description */
+ projectDescription: TestFile? = null,
/** [ARG_REPEAT_ERRORS_MAX] */
- repeatErrorsMax: Int = 0
+ repeatErrorsMax: Int = 0,
) {
// Ensure different API clients don't interfere with each other
try {
@@ -615,6 +617,8 @@
)
}
+ val projectDescriptionFile = projectDescription?.createFile(project)
+
val apiClassResolutionArgs =
arrayOf(ARG_API_CLASS_RESOLUTION, apiClassResolution.optionValue)
@@ -1062,7 +1066,6 @@
*validateNullabilityFromListArgs,
format.outputFlags(),
*apiClassResolutionArgs,
- *sourceList,
*extraArguments,
*errorMessageApiLintArgs,
*errorMessageCheckCompatibilityReleasedArgs,
@@ -1071,9 +1074,17 @@
*apiLintArgs,
) +
buildList {
- if (commonSourcePath != null) {
- add(ARG_COMMON_SOURCE_PATH)
- add(commonSourcePath)
+ if (projectDescriptionFile != null) {
+ add(ARG_PROJECT)
+ add(projectDescriptionFile.absolutePath)
+ // When project description is provided,
+ // skip listing (common) sources
+ } else {
+ addAll(sourceList)
+ if (commonSourcePath != null) {
+ add(ARG_COMMON_SOURCE_PATH)
+ add(commonSourcePath)
+ }
}
}
.toTypedArray()
diff --git a/metalava/src/test/java/com/android/tools/metalava/MainCommandTest.kt b/metalava/src/test/java/com/android/tools/metalava/MainCommandTest.kt
index fb8cda4..200fc24 100644
--- a/metalava/src/test/java/com/android/tools/metalava/MainCommandTest.kt
+++ b/metalava/src/test/java/com/android/tools/metalava/MainCommandTest.kt
@@ -121,6 +121,8 @@
--classpath <paths>
One or more directories or jars (separated by `:`) containing classes that
should be on the classpath when parsing the source files
+--project <xmlfile>
+ Project description written in XML according to Lint's project model.
--merge-qualifier-annotations <file>
An external annotations file to merge and overlay the sources, or a
directory of such files. Should be used for annotations intended for
diff --git a/metalava/src/test/java/com/android/tools/metalava/ProjectDescriptionTest.kt b/metalava/src/test/java/com/android/tools/metalava/ProjectDescriptionTest.kt
new file mode 100644
index 0000000..ba34a18
--- /dev/null
+++ b/metalava/src/test/java/com/android/tools/metalava/ProjectDescriptionTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.android.tools.metalava
+
+import com.android.tools.metalava.model.provider.Capability
+import com.android.tools.metalava.model.testing.RequiresCapabilities
+import com.android.tools.metalava.testing.java
+import com.android.tools.metalava.testing.kotlin
+import com.android.tools.metalava.testing.xml
+import org.junit.Test
+
+@RequiresCapabilities(Capability.KOTLIN)
+class ProjectDescriptionTest : DriverTest() {
+
+ @Test
+ fun `conflict declarations`() {
+ // Example from b/364480872
+ // Conflict declarations in Foo.java and Foo.kt are intentional.
+ // project.xml will use "androidMain" as root so that it can discard one in jvmMain.
+ check(
+ commonSourceFiles =
+ arrayOf(
+ kotlin(
+ "commonMain/src/some/common/Bogus.kt",
+ """
+ // bogus file to trigger multi-folder structure
+ package some.common
+
+ class Bogus
+ """
+ )
+ ),
+ sourceFiles =
+ arrayOf(
+ kotlin(
+ "androidMain/src/some/pkg/Foo.kt",
+ """
+ package some.pkg
+
+ class Foo {
+ companion object {
+ @JvmStatic
+ public fun foo(x: String): String {
+ return x
+ }
+ }
+ }
+ """
+ ),
+ java(
+ "androidMain/src/test/Bar.java",
+ """
+ package test;
+
+ import some.pkg.Foo;
+
+ public class Bar {
+ public String bar(String x) {
+ return Foo.foo(x);
+ }
+ }
+ """
+ ),
+ java(
+ "jvmMain/src/some/pkg/Foo.java",
+ """
+ package some.pkg;
+
+ public class Foo {
+ public static String foo(String x) {
+ return x;
+ }
+ }
+ """
+ ),
+ ),
+ projectDescription =
+ xml(
+ "project.xml",
+ """
+ <project>
+ <module name="app" android="true" library="false">
+ <src file="androidMain/src/some/pkg/Foo.kt" />
+ <src file="androidMain/src/test/Bar.java" />
+ </module>
+ </project>
+ """
+ ),
+ api =
+ """
+ package some.pkg {
+ public final class Foo {
+ ctor public Foo();
+ field public static final some.pkg.Foo.Companion Companion;
+ }
+ public static final class Foo.Companion {
+ method public String foo(String x);
+ }
+ }
+ package test {
+ public class Bar {
+ ctor public Bar();
+ method public String! bar(String!);
+ }
+ }
+ """
+ )
+ }
+}