Skip to content

Android Studio cannot index Skip modules without explicit includeBuild in project's settings.gradle.kts #627

@m-t-a-p

Description

@m-t-a-p

Problem

When using Skip with Android Studio, the generated Kotlin project (skipstone) is included as a composite build via includeBuild. However, the path to skipstone is only known at Gradle configuration time — it's computed dynamically from the Skip plugin's output directory.

Android Studio requires a static, pre-configured settings.gradle.kts to properly index composite builds. When the skipstone path is computed dynamically, AS fails to index Skip modules, resulting in:

  • All Skip-generated types showing as unresolved (red) in the Android app module
  • No code completion for Skip module APIs
  • No navigation to Skip module source
  • The IDE being effectively unusable for the Android side of a Skip project

Root Cause

The skip-build-plugin generates the Gradle project configuration dynamically. When Skip's Gradle plugin is applied, the skipstone includeBuild path is added at runtime. This works for command-line Gradle builds but not for Android Studio's indexing, which requires the composite build structure to be statically declared in settings.gradle.kts before any plugin resolution occurs.

Workaround

We worked around this by maintaining a custom static settings.gradle.kts in the Android project that:

  1. Resolves the skipstone path from Xcode's DerivedData (always authoritative since the project is built from Xcode)
  2. Reads version configuration from skipstone's auto-generated settings.gradle.kts
  3. Calls includeBuild(skipstonePath) statically so AS can index it
val skipstonePath = run {
    val derivedDataDir = file("${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/")
    derivedDataDir.listFiles()
        ?.filter { it.name.startsWith("WhatWatt-") }
        ?.maxByOrNull { it.lastModified() }
        ?.resolve("Build/Intermediates.noindex/BuildToolPluginIntermediaries/whatwatthybrid.output/WhatWatt/skipstone")
        ?.takeIf { it.exists() }
        ?: error("Skipstone not found in DerivedData — build the project from Xcode first")
}.path

This workaround has some rough edges — though we mitigate them:

  • It hardcodes the DerivedData path structure (which may change between Xcode versions)
  • Version information is read directly from skipstone's own auto-generated settings.gradle.kts at sync time, so it stays in sync without manual duplication
  • Multiple WhatWatt DerivedData entries are handled by picking the most recently modified one, which is always the active build

Suggested Fix

The ideal fix would be for Skip to either:

  1. Generate a static includeBuild entry — write a stable, path-resolved includeBuild(...) call into the Android project's settings.gradle.kts after transpilation, so AS can see it without dynamic evaluation.

  2. Document the includeBuild requirement — clarify in the Skip docs that AS users need to manually add the includeBuild path and explain how to find it.

Environment

  • Skip: 1.7.0
  • Xcode: 26.2 (Build 17C52)
  • Android Studio: 2025.2 (Meerkat)
  • Gradle: 8.14.3
  • Android Gradle Plugin: 8.13.0
  • Kotlin: 2.2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions