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!);
+                  }
+                }
+                """
+        )
+    }
+}