Skip to content

NDK support#871

Closed
jomen-adfa wants to merge 3 commits intostagefrom
task/ADFA-2179
Closed

NDK support#871
jomen-adfa wants to merge 3 commits intostagefrom
task/ADFA-2179

Conversation

@jomen-adfa
Copy link
Contributor

Adding NDK support

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough
  • Features Added

    • Automated NDK installation: post-install step invokes NDK extraction from an NDK tarball (Environment.NDK_TAR_XZ / Environment.NDK_DIR) using a ProcessBuilder-driven bash/tar command; extraction is timed and logged, and archive removal is attempted after extraction.
    • NDK Activity template: new ndkActivityProject template providing Kotlin/Java activity sources, native C++ source (native-lib.cpp), and CMakeLists to scaffold an NDK-based activity.
    • NDK Gradle script generators: ndkBuildGradleSrcKts(...) and ndkBuildGradleSrcGroovy(...) to emit complete NDK-enabled build.gradle content (KTS/Groovy) with signing, ndkVersion, abiFilters, and cppFlags support.
    • NdkModuleTemplateBuilder and utilities: new builder to place native sources under src/main/cpp, helpers to write C/C++ and CMake files, and convenience defaultAppModuleWithNdk(...) function to assemble an app module with NDK settings.
    • Template registration: ndkActivityProject added to the template provider list.
    • Environment constants: added NDK_TAR_XZ and NDK_DIR to Environment and initialize NDK_DIR in Environment.init.
    • UI / resources: new template tag TEMPLATE_NDK_ACTIVITY and string resource template_ndk ("NDK Activity").
    • Minor API change: AndroidModuleTemplateBuilder made open for inheritance.
    • Asset installer UX tweak: progress snapshot now short-circuits to a static "Post install processing in progress...." message when percent >= 99.99.
    • Asset size updates: expected sizes for ANDROID_SDK_ZIP updated in BundledAssetsInstaller and SplitAssetsInstaller (values increased significantly).
  • Files touched / highlights

    • app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt — adds installNdk(archiveFile, outputDir) flow using a bash -c tar -xJf extraction via Termux path, logs timing and deletion outcome.
    • app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt — progress snapshot rendering change.
    • templates-api & templates-impl — new NDK template builders, Gradle generators, template implementation and source providers (ndkActivity, ndkSources, ndkExtensions).
    • common/src/main/java/.../Environment.java — NDK_TAR_XZ and NDK_DIR fields and initialization.
    • resources & idetooltips — new string and tooltip tag for NDK Activity.
  • Risks & Best-Practice Violations

    1. Shell injection risk (High)
      • archiveFile.absolutePath and outputDir.absolutePath are interpolated into a bash -c string. Unescaped or attacker-controlled paths could enable command injection.
      • Recommendation: avoid shell interpolation; pass command/args directly to ProcessBuilder (List) or properly escape/validate paths.
    2. Strong dependency on Termux binaries (High)
      • Extraction relies on Termux bin path for bash/tar. If Termux or expected binaries are missing or path differs, installation fails.
      • Recommendation: add platform-agnostic fallback extraction (Java-based extraction or bundled extractor) and clearer error messages.
    3. Cleanup treated as success criterion (Medium)
      • Archive deletion failures make install return false even if extraction succeeded.
      • Recommendation: decouple cleanup outcome from extraction success; report deletion failures as warnings.
    4. Missing post-extraction validation (Medium)
      • Only process exit code is checked; code does not verify expected NDK directory layout or presence of required files.
      • Recommendation: validate expected artifacts (e.g., toolchains, cmake dirs) before reporting success.
    5. Large expected SDK size changes (Medium)
      • Updated ANDROID_SDK_ZIP sizes increased considerably; may affect progress computation, storage checks, or telemetry.
      • Recommendation: verify sizes against actual artifacts and adjust any dependent logic/UX.
    6. Platform-specific flags and behavior (Medium)
      • Uses bash and tar with --no-same-owner; behavior may vary across environments and Android/Termux versions.
      • Recommendation: detect environment capabilities and adapt or document requirements.
    7. Limited exception handling and logging granularity (Low–Medium)
      • Process/IO operations lack granular exception handling; more detailed error reporting would aid debugging.
      • Recommendation: add targeted try/catch, surface failures clearly, and avoid swallowing exceptions.
    8. Conditional template availability UX (Low)
      • ndkActivityProject is only exposed when Environment.NDK_DIR exists; users may not discover the template without guidance.
      • Recommendation: surface an explanatory message or enable template with a prompt to install NDK.
  • Other notes

    • New public fields (Environment.NDK_TAR_XZ, Environment.NDK_DIR) and new template APIs expand the public surface — review for API stability.
    • Review focus areas: security of shell command construction, Termux/platform dependency handling, correctness of updated expected sizes, and post-extraction validation and cleanup semantics.

Walkthrough

Adds NDK support across installers, environment, templates, and UI: NDK tar constant and directory, installer extraction/logging for NDK, Gradle NDK build generators, an NDK module/template (activity, native sources, CMake), UI strings/tag, and updated SDK asset sizes.

Changes

Cohort / File(s) Summary
Asset progress UI
app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt
Snapshot rendering now returns a static "Post install processing in progress...." message when percent ≥ 99.99; otherwise preserves per-entry statuses + detail block.
NDK installer & logging
app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt
Added installNdk(archiveFile, outputDir) invoked from postInstall(): uses ProcessBuilder/bash to extract tar -xJf, measures duration, logs debug/error, and attempts archive deletion.
Asset size updates
app/src/main/java/com/itsaky/androidide/assets/BundledAssetsInstaller.kt, app/src/main/java/com/itsaky/androidide/assets/SplitAssetsInstaller.kt
Updated expected sizes for ANDROID_SDK_ZIP entries.
Environment additions
common/src/main/java/com/itsaky/androidide/utils/Environment.java
Added NDK_TAR_XZ constant and NDK_DIR File; initialize NDK_DIR = new File(ANDROID_HOME, "ndk") in init().
Template API surface
templates-api/src/main/java/com/itsaky/androidide/templates/base/AndroidModuleTemplateBuilder.kt, .../modules/android/buildGradle.kt, .../base/ndkExtensions.kt
Made AndroidModuleTemplateBuilder open; added ndkBuildGradleSrcKts() and ndkBuildGradleSrcGroovy(); introduced NdkModuleTemplateBuilder, utilities to write native/CMake sources, and defaultAppModuleWithNdk() helper.
Template implementations
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/TemplateProviderImpl.kt, .../ndkActivity/ndkActivityTemplate.kt, .../ndkActivity/ndkSources.kt
Registered and added ndkActivityProject() template with recipe wiring; added Kotlin/Java activity templates, C++ JNI source, and CMakeLists generators.
UI integration
idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt, resources/src/main/res/values/strings.xml
Added TEMPLATE_NDK_ACTIVITY tag and template_ndk string resource ("NDK Activity").

Sequence Diagram(s)

sequenceDiagram
    participant BaseInstaller as BaseAssetsInstaller
    participant Logger
    participant FS as FileSystem
    participant Shell as ProcessBuilder

    BaseInstaller->>Logger: debug "starting NDK installation"
    BaseInstaller->>FS: check archiveFile exists (Environment.NDK_TAR_XZ)
    FS-->>BaseInstaller: exists / not exists

    alt archive exists
        BaseInstaller->>Shell: spawn bash -c "tar -xJf <archive> -C <output> --no-same-owner"
        Shell->>FS: extract files into NDK_DIR
        FS-->>Shell: extraction result
        Shell-->>BaseInstaller: exit code & output
        BaseInstaller->>Logger: log extraction duration & result
        BaseInstaller->>FS: attempt delete archive
        FS-->>BaseInstaller: deletion success/failure
        BaseInstaller->>Logger: log cleanup status
    else archive missing
        BaseInstaller->>Logger: debug "NDK archive missing"
    end
Loading
sequenceDiagram
    participant TemplateProvider
    participant NdkBuilder as NdkModuleTemplateBuilder
    participant RecipeExec as RecipeExecutor
    participant Writer as SourceWriter
    participant FS as FileSystem

    TemplateProvider->>NdkBuilder: request ndkActivityProject()
    NdkBuilder->>RecipeExec: execute recipe
    RecipeExec->>NdkBuilder: call buildGradle() (KTS/Groovy)
    NdkBuilder->>Writer: write C++ source (native-lib.cpp)
    Writer-->>FS: create src/main/cpp/native-lib.cpp
    NdkBuilder->>Writer: write CMakeLists.txt
    Writer-->>FS: create CMakeLists.txt
    NdkBuilder->>RecipeExec: write activity sources/layout/resources
    RecipeExec-->>FS: files created
    NdkBuilder-->>TemplateProvider: return ProjectTemplate
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • itsaky-adfa
  • jatezzz

🐰
NDK tar hops into place,
Templates hum, native code in space,
CMake, JNI, and Gradle sing,
A rabbit cheers for every spring! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'NDK support' directly reflects the main objective of this pull request, which adds comprehensive NDK functionality across multiple modules including installation, templates, and environment configuration.
Description check ✅ Passed The description 'Adding NDK support' is concise and directly related to the changeset, which introduces NDK installation flows, template builders, and environment variables for NDK functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt`:
- Around line 48-51: The process.waitFor() call in BaseAssetsInstaller can block
indefinitely; change it to use process.waitFor(timeout, TimeUnit.SECONDS) with a
reasonable timeout (e.g., 60s), handle the boolean timedOut result by calling
process.destroy() and if necessary process.destroyForcibly(), capture/handle
InterruptedException, and set exitCode/result appropriately; also ensure you
drain both process.inputStream and process.errorStream (or read them
asynchronously) to avoid stream blocking when the process is running.

In
`@templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt`:
- Around line 604-609: The Groovy build script unconditionally appends
composeConfigGroovy() causing compose config to be present in non-compose
modules; update the template in buildGradle.kt to call composeConfigGroovy()
only when isComposeModule is true (mirror the conditional logic used for the
buildFeatures block), i.e., guard the composeConfigGroovy() invocation using the
isComposeModule flag so that compose-specific configuration is emitted only for
compose modules.
- Around line 494-499: The file unconditionally injects composeConfigKts()
causing Compose options to be added for non-Compose NDK modules; update the
template to call composeConfigKts() only when isComposeModule is true (mirror
the conditional used in buildGradleSrcKts). Locate the buildFeatures block and
the composeConfigKts() invocation and wrap or conditionally render
composeConfigKts() based on the isComposeModule flag so compose-specific
settings (kotlinCompilerExtensionVersion, packaging rules, dependency
resolution) are only added for modules where isComposeModule is true.
- Around line 460-462: The ndk block currently emits invalid Kotlin DSL by
writing abiFilters += "arm64-v8a","x86_64"; change the generation so abiFilters
is given a Kotlin list: produce abiFilters +=
listOf(${abiFilters.joinToString(",") { "\"$it\"" }}) (or use
abiFilters.addAll(listOf(...))) so the emitted code uses listOf(...) instead of
a comma-separated string list; update the template in buildGradle.kt around the
ndk { abiFilters ... } section accordingly.

In
`@templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt`:
- Around line 50-67: The current RecipeExecutor.buildGradle() passes empty
strings/lists for ndkVersion, abiFilters, and cppFlags which produces invalid
Gradle snippets; update buildGradle() to forward nullable values (ndkVersion,
abiFilters, cppFlags) without coercing to ""/emptyList or else compute
conditional content so ndkBuildGradleSrcKts and ndkBuildGradleSrcGroovy can omit
their NDK blocks when values are null/empty; specifically, stop using ndkVersion
?: "", abiFilters ?: emptyList(), cppFlags ?: "" and either call
ndkBuildGradleSrcKts/ndkBuildGradleSrcGroovy with nullable params or prebuild
the NDK-specific strings only when values are present, then call
save(gradleContent, buildGradleFile()).

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt`:
- Around line 29-31: Update the NDK ABI filters in ndkActivityTemplate.kt so
emulators are supported: in the block defining ndkVersion, abiFilters, and
cppFlags (symbols: ndkVersion, abiFilters, cppFlags) add "x86_64" to the
abiFilters list (e.g., listOf("arm64-v8a", "x86_64")) while keeping the existing
ndkVersion and cppFlags values unchanged.

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt`:
- Around line 17-30: The Kotlin template uses Java syntax; update MainActivity
to valid Kotlin by removing the `public` modifier, keep _binding:
ActivityMainBinding? and the binding getter as-is, replace the Java static block
with a companion object containing an init that calls
System.loadLibrary("native-lib"), and convert the native declaration to Kotlin
external syntax by replacing `public native String stringFromJNI();` with an
external function declaration like `external fun stringFromJNI(): String`;
ensure these changes reference MainActivity, _binding, binding, companion
object/init, System.loadLibrary, and stringFromJNI.
🧹 Nitpick comments (8)
common/src/main/java/com/itsaky/androidide/utils/Environment.java (1)

115-116: Consider making NDK_TAR_XZ final for consistency.

Other string constants in this class (like PROJECTS_FOLDER, DEFAULT_ROOT, etc.) are declared as public static final. For consistency and to prevent accidental reassignment, NDK_TAR_XZ should also be final.

Suggested fix
-	public static String NDK_TAR_XZ = "ndk-cmake.tar.xz";
+	public static final String NDK_TAR_XZ = "ndk-cmake.tar.xz";
app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt (2)

21-24: Consider handling the return value of installNdk.

The return value is currently ignored. While NDK installation may be optional, logging the outcome would help with troubleshooting if users expect NDK features to work.

Suggested fix
-        installNdk(
+        val ndkInstalled = installNdk(
             File(Environment.ANDROID_HOME, Environment.NDK_TAR_XZ),
             Environment.ANDROID_HOME
         )
+        logger.debug("NDK installation result: {}", if (ndkInstalled) "success" else "skipped or failed")

54-72: Confusing return semantics when deletion fails after successful extraction.

The function returns false if the archive was extracted successfully but couldn't be deleted (lines 62-64) or wasn't found (line 67). This conflates extraction success with cleanup success. Consider returning true for successful extraction regardless of cleanup, or separating the concerns.

Suggested fix
         return if (exitCode == 0) {
             logger.debug("Extraction of ${archiveFile.absolutePath} successful took ${elapsed}ms : $result")
 
-            if (archiveFile.exists()) {
-                val deleted = archiveFile.delete()
-                if (deleted) {
-                    logger.debug("${archiveFile.absolutePath} deleted successfully.")
-                } else {
-                    logger.debug("Failed to delete ${archiveFile.absolutePath}.")
-                }
-                deleted
-            } else {
-                logger.debug("Archive file not found for deletion.")
-                false
+            // Attempt cleanup but don't fail extraction success
+            if (archiveFile.exists() && !archiveFile.delete()) {
+                logger.warn("Failed to delete ${archiveFile.absolutePath} after extraction")
             }
+            true
         } else {
             logger.error("Extraction failed with code $exitCode: $result")
             false
         }
app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt (1)

179-181: Use threshold comparison instead of floating-point equality.

Comparing percent == 100.0 with exact equality can be unreliable due to floating-point precision. While this specific calculation may work, a threshold comparison is safer and more robust.

Suggested fix
                     val snapshot =
-                        if (percent == 100.0) {
+                        if (percent >= 99.99) {
                             "Post install processing in progress...."
                         } else {
templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt (1)

396-507: Consider extracting common template logic to reduce duplication.

Both NDK build functions share ~90% of their logic with the existing buildGradleSrcKts and buildGradleSrcGroovy functions. The only additions are the NDK-specific blocks (ndk { abiFilters }, externalNativeBuild, ndkVersion). Consider refactoring to compose these templates from smaller, reusable pieces to improve maintainability.

Also applies to: 509-615

templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt (2)

28-37: Unused writer parameter in writeCpp.

The writer: SourceWriter parameter is never used in this function. This appears to be dead code that should be removed.

♻️ Proposed fix
-    inline fun writeCpp(
-        writer: SourceWriter,
-        fileName: String,
-        crossinline cppSrcProvider: () -> String
-    ) {
+    inline fun writeCpp(
+        fileName: String,
+        crossinline cppSrcProvider: () -> String
+    ) {
         val src = cppSrcProvider()
         if (src.isNotBlank() && fileName.isNotBlank()) {
             executor.save(src, srcNativeFilePath(SrcSet.Main, fileName))
         }
     }

39-48: Remove unused writer parameter from writeCMakeList and writeCpp.

Both functions have an unused writer: SourceWriter parameter that should be removed. This also affects the callers in ndkActivityTemplate.kt which will need to be updated to remove the this argument being passed.

♻️ Proposed changes

Function signatures:

 inline fun writeCpp(
-    writer: SourceWriter,
     fileName: String,
     crossinline cppSrcProvider: () -> String
 )

 inline fun writeCMakeList(
-    writer: SourceWriter,
     fileName: String,
     crossinline cmakeSrcProvider: () -> String
 )

Callers in ndkActivityTemplate.kt:

-writeCpp(this, fileName = "native-lib.cpp") { ndkCpp() }
+writeCpp(fileName = "native-lib.cpp") { ndkCpp() }
-writeCMakeList(this, fileName = "CMakeLists.txt") { ndkCMakeLists() }
+writeCMakeList(fileName = "CMakeLists.txt") { ndkCMakeLists() }
templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkSources.kt (1)

41-43: Kotlin style: Use property assignment syntax instead of setText.

Kotlin's Android extensions and view binding support property syntax for setters.

♻️ Proposed fix
         // Call JNI method and display result
-        binding.textView.setText(stringFromJNI());
+        binding.textView.text = stringFromJNI()
     }

@jomen-adfa jomen-adfa requested review from a team and davidschachterADFA January 23, 2026 13:33
)
}

private fun installNdk(archiveFile: File, outputDir: File): Boolean {
Copy link
Collaborator

@jatezzz jatezzz Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jomen-adfa I have a question about this process: does it run on the main thread or is it called from a coroutine? Since it processes files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jatezzz It's called from assetsinstallationhelper after the doinstalls are done, either for debug or release. This needs to be done in postInstall since it needs both android-sdk and bootstrap packages to be already extracted for tar and xz to run without potential issues for the moment so we can release and get feedback. This would be a bit temporary since in the near future we could be putting the ndk package in a plugin and additionally if we do decide to adapt tar.xz packaging then we'd do the extraction in our code using apache commons packages and not delegated to tar and xz utilities in termux.

private val cppFlags: String? = null
) : AndroidModuleTemplateBuilder() {

fun srcNativeFilePath(srcSet: SrcSet, fileName: String): File {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jomen-adfa Could you move the handling of coroutines to a lower level to avoid errors in the high-level caller?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all methods in this file, including the I/O dispatcher or the most appropriate one in each case.

@jomen-adfa jomen-adfa requested a review from jatezzz January 23, 2026 14:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt`:
- Around line 178-191: The snapshot logic currently uses percent >= 99.99 to
show the "Post install processing..." message; instead, change it to check that
all entries are finished by inspecting entryStatusMap (e.g.
entryStatusMap.values.all { it == STATUS_FINISHED }) so the per-entry status
list is only hidden when every entry is STATUS_FINISHED; update the condition
around the snapshot variable (and any related branches that produce the "Post
install processing in progress...." text) to use this all-finished check rather
than the percent threshold so approximate/zero expectedSize won’t prematurely
hide entry statuses.

In `@app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt`:
- Around line 49-56: The current code reads process.inputStream with
process.inputStream.bufferedReader().use { it.readText() } before calling
process.waitFor, which can block indefinitely; instead start draining both
stdout and stderr concurrently (e.g., spawn threads or use Executors to read
process.inputStream and process.errorStream into strings or StringBuilders)
immediately after processBuilder.start(), then call process.waitFor(2,
TimeUnit.MINUTES) and set exitCode based on the completed flag (and call
process.destroyForcibly() on timeout); finally collect the drained output into
result and include stderr if needed. Ensure you keep references to the started
threads/futures so you can await their completion (or cancel) after waitFor, and
update variables result and exitCode accordingly.

In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt`:
- Around line 28-31: The ndkActivity template currently hardcodes ndkVersion =
"29.0.14206865" in the call to defaultAppModuleWithNdk; change it to use
BuildConfig.NDK_VERSION instead (i.e., set ndkVersion = BuildConfig.NDK_VERSION)
so the template aligns with the canonical NDK version; update any necessary
imports or references so BuildConfig is accessible from ndkActivityTemplate.kt
and verify the ndkVersion parameter of defaultAppModuleWithNdk receives
BuildConfig.NDK_VERSION.
♻️ Duplicate comments (1)
templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt (1)

50-64: Avoid empty NDK defaults in generated Gradle (still present).

At Line 54–63, nulls are coerced into empty strings/lists, which can emit invalid NDK config (e.g., ndkVersion = "", empty abiFilters). This mirrors the earlier review feedback; consider requiring non-null values or omitting the NDK blocks when values are missing.

Comment on lines 178 to 191
val snapshot =
buildString {
entryStatusMap.forEach { (entry, status) ->
appendLine("$entry ${if (status == STATUS_FINISHED) "✓" else ""}")
if (percent >= 99.99) {
"Post install processing in progress...."
} else {
buildString {
entryStatusMap.forEach { (entry, status) ->
appendLine("$entry ${if (status == STATUS_FINISHED) "✓" else ""}")
}
appendLine("--------------------")
appendLine("Progress: ${formatPercent(percent)}")
appendLine("Installed: ${formatBytes(installedSize)} / ${formatBytes(totalSize)}")
appendLine("Remaining storage: ${formatBytes(freeStorage)}")
}
appendLine("--------------------")
appendLine("Progress: ${formatPercent(percent)}")
appendLine("Installed: ${formatBytes(installedSize)} / ${formatBytes(totalSize)}")
appendLine("Remaining storage: ${formatBytes(freeStorage)}")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Gate the “post install” message on completion, not percent.

If any expectedSize is approximate/zero, percent can reach 99.99 before all entries are finished, which hides the per‑entry status prematurely. Consider switching the condition to “all entries finished” for a more accurate status.

🔧 Suggested adjustment
-                    val snapshot =
-                        if (percent >= 99.99) {
+                    val allFinished =
+                        entryStatusMap.size == expectedEntries.size &&
+                            entryStatusMap.values.all { it == STATUS_FINISHED }
+
+                    val snapshot =
+                        if (allFinished) {
                             "Post install processing in progress...."
                         } else {
                             buildString {
                                 entryStatusMap.forEach { (entry, status) ->
                                     appendLine("$entry ${if (status == STATUS_FINISHED) "✓" else ""}")
                                 }
                                 appendLine("--------------------")
                                 appendLine("Progress: ${formatPercent(percent)}")
                                 appendLine("Installed: ${formatBytes(installedSize)} / ${formatBytes(totalSize)}")
                                 appendLine("Remaining storage: ${formatBytes(freeStorage)}")
                             }
                         }
🤖 Prompt for AI Agents
In `@app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt`
around lines 178 - 191, The snapshot logic currently uses percent >= 99.99 to
show the "Post install processing..." message; instead, change it to check that
all entries are finished by inspecting entryStatusMap (e.g.
entryStatusMap.values.all { it == STATUS_FINISHED }) so the per-entry status
list is only hidden when every entry is STATUS_FINISHED; update the condition
around the snapshot variable (and any related branches that produce the "Post
install processing in progress...." text) to use this all-finished check rather
than the percent threshold so approximate/zero expectedSize won’t prematurely
hide entry statuses.

Comment on lines +49 to +56
val process = processBuilder.start()

result = process.inputStream.bufferedReader().use { it.readText() }
val completed = process.waitFor(2, TimeUnit.MINUTES)
exitCode = if (completed) process.exitValue() else {
process.destroyForcibly()
-1
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Timeout is ineffective because output is read before waitFor.

readText() blocks until the process exits, so a hung tar extraction can still block indefinitely (and can deadlock if output buffers fill). Drain output concurrently, then apply the timeout.

🔧 Suggested fix (drain output concurrently)
 import java.util.concurrent.TimeUnit
+import kotlin.concurrent.thread
 ...
             val process = processBuilder.start()
 
-            result = process.inputStream.bufferedReader().use { it.readText() }
-            val completed = process.waitFor(2, TimeUnit.MINUTES)
-            exitCode = if (completed) process.exitValue() else {
-                process.destroyForcibly()
-                -1
-            }
+            val output = StringBuilder()
+            val reader = thread(start = true, name = "ndk-extract-output") {
+                process.inputStream.bufferedReader().useLines { lines ->
+                    lines.forEach { output.appendLine(it) }
+                }
+            }
+
+            val completed = process.waitFor(2, TimeUnit.MINUTES)
+            if (!completed) {
+                process.destroyForcibly()
+            }
+            reader.join()
+            result = output.toString()
+            exitCode = if (completed) process.exitValue() else -1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val process = processBuilder.start()
result = process.inputStream.bufferedReader().use { it.readText() }
val completed = process.waitFor(2, TimeUnit.MINUTES)
exitCode = if (completed) process.exitValue() else {
process.destroyForcibly()
-1
}
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
val process = processBuilder.start()
val output = StringBuilder()
val reader = thread(start = true, name = "ndk-extract-output") {
process.inputStream.bufferedReader().useLines { lines ->
lines.forEach { output.appendLine(it) }
}
}
val completed = process.waitFor(2, TimeUnit.MINUTES)
if (!completed) {
process.destroyForcibly()
}
reader.join()
result = output.toString()
exitCode = if (completed) process.exitValue() else -1
🤖 Prompt for AI Agents
In `@app/src/main/java/com/itsaky/androidide/assets/BaseAssetsInstaller.kt` around
lines 49 - 56, The current code reads process.inputStream with
process.inputStream.bufferedReader().use { it.readText() } before calling
process.waitFor, which can block indefinitely; instead start draining both
stdout and stderr concurrently (e.g., spawn threads or use Executors to read
process.inputStream and process.errorStream into strings or StringBuilders)
immediately after processBuilder.start(), then call process.waitFor(2,
TimeUnit.MINUTES) and set exitCode based on the completed flag (and call
process.destroyForcibly() on timeout); finally collect the drained output into
result and include stderr if needed. Ensure you keep references to the started
threads/futures so you can await their completion (or cancel) after waitFor, and
update variables result and exitCode accordingly.

Comment on lines +28 to +31
defaultAppModuleWithNdk(
ndkVersion = "29.0.14206865",
abiFilters = listOf("arm64-v8a"),
cppFlags = "-std=c++17"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the Environment.java file
fd -type f -name "Environment.java"

Repository: appdevforall/CodeOnTheGo

Length of output: 238


🏁 Script executed:

# Search for NDK version related constants and patterns
rg -n "NDK_(TAR|VERSION|DIR|REVISION|BUILD)" --type java

Repository: appdevforall/CodeOnTheGo

Length of output: 409


🏁 Script executed:

# Check the actual template file for context
fd -type f -name "ndkActivityTemplate.kt"

Repository: appdevforall/CodeOnTheGo

Length of output: 238


🏁 Script executed:

# Search for ndkVersion usages across the entire codebase
rg -n "ndkVersion\s*=" --type kotlin --type java

Repository: appdevforall/CodeOnTheGo

Length of output: 833


🏁 Script executed:

# Find BuildConfig to understand NDK_VERSION
fd -type f -name "BuildConfig.kt" -o -name "BuildConfig.java"

Repository: appdevforall/CodeOnTheGo

Length of output: 238


🏁 Script executed:

# Check the content of buildGradle.kt to understand the ndkVersion parameter
cat -n templates-api/src/main/java/com/itsaky/androidide/templates/base/modules/android/buildGradle.kt | sed -n '465,480p'

Repository: appdevforall/CodeOnTheGo

Length of output: 497


🏁 Script executed:

# View the full ndkActivityTemplate.kt file to understand context
cat -n templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 2630


🏁 Script executed:

# Find BuildConfig files
fd -name "BuildConfig*"

Repository: appdevforall/CodeOnTheGo

Length of output: 298


🏁 Script executed:

# Search for NDK_VERSION in BuildConfig files
rg -n "NDK_VERSION" --type kotlin

Repository: appdevforall/CodeOnTheGo

Length of output: 820


🏁 Script executed:

# Check if there's version info in gradle files or properties
fd -name "gradle.properties" -o -name "build.gradle.kts" | head -5

Repository: appdevforall/CodeOnTheGo

Length of output: 298


🏁 Script executed:

# Look at the actual gradle.properties or main build file for NDK version
rg -n "29\.0\.14206865"

Repository: appdevforall/CodeOnTheGo

Length of output: 213


🏁 Script executed:

# Check the defaultAppModuleWithNdk function signature
rg -A 10 "fun defaultAppModuleWithNdk" --type kotlin

Repository: appdevforall/CodeOnTheGo

Length of output: 50


🏁 Script executed:

# Look at BuildConfig.kt to understand the full context
cat -n composite-builds/build-logic/common/src/main/java/com/itsaky/androidide/build/config/BuildConfig.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 1908


🏁 Script executed:

# Find the defaultAppModuleWithNdk function definition
rg -B 5 -A 15 "fun defaultAppModuleWithNdk"

Repository: appdevforall/CodeOnTheGo

Length of output: 50


🏁 Script executed:

# Check what file this function is in
fd -name "*.kt" | xargs rg -l "defaultAppModuleWithNdk"

Repository: appdevforall/CodeOnTheGo

Length of output: 498


🏁 Script executed:

# Read the ndkExtensions.kt file to see the function definition
cat templates-api/src/main/java/com/itsaky/androidide/templates/base/ndkExtensions.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 3405


Use BuildConfig.NDK_VERSION instead of hardcoding the NDK version.

Line 29 hardcodes ndkVersion = "29.0.14206865", but the canonical version defined in BuildConfig.NDK_VERSION is "28.1.13356709". This mismatch is inconsistent with other modules (termux-app, libjdwp, shizuku-manager) which correctly reference BuildConfig.NDK_VERSION. Update the template to derive the version from the build configuration to keep the template aligned with the packaged NDK and prevent Gradle sync failures.

🤖 Prompt for AI Agents
In
`@templates-impl/src/main/java/com/itsaky/androidide/templates/impl/ndkActivity/ndkActivityTemplate.kt`
around lines 28 - 31, The ndkActivity template currently hardcodes ndkVersion =
"29.0.14206865" in the call to defaultAppModuleWithNdk; change it to use
BuildConfig.NDK_VERSION instead (i.e., set ndkVersion = BuildConfig.NDK_VERSION)
so the template aligns with the canonical NDK version; update any necessary
imports or references so BuildConfig is accessible from ndkActivityTemplate.kt
and verify the ndkVersion parameter of defaultAppModuleWithNdk receives
BuildConfig.NDK_VERSION.

@jomen-adfa jomen-adfa closed this Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants