@@ -16,13 +16,20 @@ import org.jetbrains.kotlin.analysis.project.structure.builder.KtModuleBuilder
1616import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule
1717import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule
1818import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule
19- import org.jetbrains.kotlin.config.*
19+ import org.jetbrains.kotlin.config.AnalysisFlags
20+ import org.jetbrains.kotlin.config.ApiVersion
21+ import org.jetbrains.kotlin.config.LanguageVersion
22+ import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
2023import org.jetbrains.kotlin.platform.CommonPlatforms
2124import org.jetbrains.kotlin.platform.js.JsPlatforms
2225import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
2326import org.jetbrains.kotlin.platform.konan.NativePlatforms
2427import org.jetbrains.kotlin.platform.wasm.WasmPlatforms
2528import java.io.File
29+ import java.io.IOException
30+ import java.nio.file.*
31+ import java.nio.file.attribute.BasicFileAttributes
32+ import kotlin.io.path.extension
2633
2734internal fun Platform.toTargetPlatform () = when (this ) {
2835 Platform .wasm -> WasmPlatforms .unspecifiedWasmPlatform
@@ -113,10 +120,19 @@ internal fun createAnalysisSession(
113120 getLanguageVersionSettings(sourceSet.languageVersion, sourceSet.apiVersion)
114121 platform = targetPlatform
115122 moduleName = " <module ${sourceSet.displayName} >"
116- if (isSampleProject)
117- addSourceRoots(sourceSet.samples.map { it.toPath() })
118- else
119- addSourceRoots(sourceSet.sourceRoots.map { it.toPath() })
123+
124+ // can be removed after https://youtrack.jetbrains.com/issue/KT-81107 is implemented (see #4266)
125+ // here we mimic the logic, which happens inside AA during building KaModule, but we follow symlinks
126+ // https://github.com/JetBrains/kotlin/blob/dcd24449718cba21bd86428e5cddb9b25e5612af/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/builder/KaSourceModuleBuilder.kt#L80
127+ if (isSampleProject) {
128+ sourceSet.samples.forEach { root ->
129+ addSourceRoots(collectSourceFilePaths(root.toPath()))
130+ }
131+ } else {
132+ sourceSet.sourceRoots.forEach { root ->
133+ addSourceRoots(collectSourceFilePaths(root.toPath()))
134+ }
135+ }
120136 addModuleDependencies(
121137 sourceSet,
122138 )
@@ -171,3 +187,48 @@ internal fun topologicalSortByDependantSourceSets(
171187 sourceSets.forEach(::dfs)
172188 return result
173189}
190+
191+ // copied from AA: https://github.com/JetBrains/kotlin/blob/dcd24449718cba21bd86428e5cddb9b25e5612af/analysis/analysis-api-standalone/src/org/jetbrains/kotlin/analysis/project/structure/impl/KaModuleUtils.kt#L60-L110
192+ // with a fix for following symlinks
193+
194+ private fun collectSourceFilePaths (root : Path ): List <Path > {
195+ // NB: [Files#walk] throws an exception if there is an issue during IO.
196+ // With [Files#walkFileTree] with a custom visitor, we can take control of exception handling.
197+ val result = mutableListOf<Path >()
198+ Files .walkFileTree(
199+ /* start = */ root,
200+ /* options = */ setOf (FileVisitOption .FOLLOW_LINKS ), // <-- THIS IS THE FIX
201+ /* maxDepth = */ Int .MAX_VALUE ,
202+ /* visitor = */ object : SimpleFileVisitor <Path >() {
203+ override fun preVisitDirectory (dir : Path , attrs : BasicFileAttributes ): FileVisitResult {
204+ return if (Files .isReadable(dir))
205+ FileVisitResult .CONTINUE
206+ else
207+ FileVisitResult .SKIP_SUBTREE
208+ }
209+
210+ override fun visitFile (file : Path , attrs : BasicFileAttributes ): FileVisitResult {
211+ if (! Files .isRegularFile(file) || ! Files .isReadable(file))
212+ return FileVisitResult .CONTINUE
213+ if (file.hasSuitableExtensionToAnalyse()) {
214+ result.add(file)
215+ }
216+ return FileVisitResult .CONTINUE
217+ }
218+
219+ override fun visitFileFailed (file : Path , exc : IOException ): FileVisitResult {
220+ // TODO: report or log [IOException]?
221+ // NB: this intentionally swallows the exception, hence fail-safe.
222+ // Skipping subtree doesn't make any sense, since this is not a directory.
223+ // Skipping sibling may drop valid file paths afterward, so we just continue.
224+ return FileVisitResult .CONTINUE
225+ }
226+ }
227+ )
228+ return result
229+ }
230+
231+ private fun Path.hasSuitableExtensionToAnalyse (): Boolean {
232+ val extension = extension
233+ return extension == " kt" || extension == " kts" || extension == " java"
234+ }
0 commit comments