Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ class TemplateDetailsFragment :
}

viewModel.creatingProject.value = true
val appContext = requireContext().applicationContext
executeAsyncProvideError({
template.recipe.execute(TemplateRecipeExecutor())
template.recipe.execute(TemplateRecipeExecutor(appContext))
}) { result, err ->

viewModel.creatingProject.value = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.itsaky.androidide.utils

import android.content.Context
import org.adfa.constants.LOCAL_MAVEN_CACHES_DEST
import org.adfa.constants.LOCAL_MAVEN_REPO_ARCHIVE_ZIP_NAME
import com.blankj.utilcode.util.ResourceUtils
Expand All @@ -33,7 +34,9 @@ import java.io.InputStream
*
* @author Akash Yadav
*/
class TemplateRecipeExecutor : RecipeExecutor {
class TemplateRecipeExecutor (
override val context: Context
) : RecipeExecutor {

private val application: IDEApplication
get() = IDEApplication.instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.itsaky.androidide.templates

import android.content.Context
import java.io.File
import java.io.InputStream

Expand All @@ -27,6 +28,8 @@ import java.io.InputStream
*/
interface RecipeExecutor {

val context: Context? get() = null

/**
* Get the project template data. This is available only while creating modules in an existing project.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.itsaky.androidide.templates.impl.zip

import com.itsaky.androidide.templates.Language
import android.annotation.SuppressLint
import android.content.Context
import dalvik.system.DexClassLoader

import java.io.File
import java.io.StringWriter
import java.util.zip.ZipFile
import java.util.ServiceLoader

import org.slf4j.LoggerFactory
import io.pebbletemplates.pebble.PebbleEngine
import io.pebbletemplates.pebble.loader.StringLoader
import io.pebbletemplates.pebble.lexer.Syntax
import io.pebbletemplates.pebble.error.PebbleException
import io.pebbletemplates.pebble.extension.Extension

import com.itsaky.androidide.templates.Language
import com.itsaky.androidide.templates.ModuleTemplateData
import com.itsaky.androidide.templates.Parameter
import com.itsaky.androidide.templates.ProjectTemplateData
Expand All @@ -18,11 +24,12 @@ import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.TemplateRecipe
import com.itsaky.androidide.templates.impl.base.ProjectTemplateRecipeResultImpl
import com.itsaky.androidide.utils.Environment
import io.pebbletemplates.pebble.error.PebbleException

import org.slf4j.LoggerFactory
import org.adfa.constants.ANDROID_GRADLE_PLUGIN_VERSION
import org.adfa.constants.KOTLIN_VERSION
import org.adfa.constants.Sdk
import java.util.zip.ZipEntry

class ZipRecipeExecutor(
private val zipProvider: () -> ZipFile,
Expand Down Expand Up @@ -69,12 +76,27 @@ class ZipRecipeExecutor(
.setCommentCloseDelimiter(DELIM_COMMENT_CLOSE)
.build()

val pebbleEngine = PebbleEngine.Builder()
val builder = PebbleEngine.Builder()

val extensionsEntry = zip.getEntry(META_EXTENSION_JAR)
if (extensionsEntry != null) {
val context = executor.context
if (context == null) {
warn("Skipping $META_EXTENSION_JAR because TemplateRecipeExecutor.context is unavailable")
} else {
val extensions = loadExtensionFromArchive(zip, extensionsEntry, context)
for (ext in extensions) {
builder.extension(ext)
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

val pebbleEngine = builder.loader(StringLoader())
.strictVariables(true)
.loader(StringLoader())
.syntax(customSyntax)
.build()


val className = data.name.replace(CLASS_NAME_PATTERN, "")
val (baseIdentifiers, warnings) = metaJson.pebbleParams(data, defModule, params)
val identifiers = baseIdentifiers + (KEY_CLASS_NAME to className)
Expand Down Expand Up @@ -289,10 +311,10 @@ class ZipRecipeExecutor(
else ResolvedParam(value, false)
}

private fun resolveBoolean(raw: Boolean?, default: Boolean): ResolvedParam<Boolean> {
private fun resolveBoolean(raw: Boolean?, default: Boolean): ResolvedParam<Boolean> {
return if (raw == null) ResolvedParam(default, true)
else ResolvedParam(raw, false)
}
}
Comment thread
jomen-adfa marked this conversation as resolved.

private fun filterAndNormalizeZipEntry(
entryName: String,
Expand Down Expand Up @@ -330,4 +352,83 @@ class ZipRecipeExecutor(

private fun Exception.wrap(msg: String): RuntimeException =
RuntimeException(msg, this)

@SuppressLint("SetWorldReadable")
private fun loadExtensionFromArchive(
zip: ZipFile,
entry: ZipEntry,
context: Context,
): List<Extension> {

val tempJar = File.createTempFile("ext_", ".jar", context.codeCacheDir)

try {
zip.getInputStream(entry).use { input ->
tempJar.outputStream().use { output ->
input.copyTo(output)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (e: Exception) {
error("Failed to extract ${entry.name} to ${tempJar.absolutePath}", e)
return emptyList()
}
Comment thread
jomen-adfa marked this conversation as resolved.

try {
tempJar.setReadable(true, false)
tempJar.setWritable(false)
tempJar.setExecutable(false)
} catch (e: SecurityException) {
warn("Could not adjust permissions on ${tempJar.absolutePath} $e")
}

val optimizedDir = File(
Environment.TEMPLATES_DIR,
"$DEX_OPT_FOLDER/${basePath}"
)

if (!optimizedDir.exists() && !optimizedDir.mkdirs()) {
error("Failed to create optimized dex directory: ${optimizedDir.absolutePath}",
IllegalStateException("mkdirs() failed for ${optimizedDir.absolutePath}"))
return emptyList()
}

val classLoader = try {
DexClassLoader(
tempJar.absolutePath,
optimizedDir.absolutePath,
null,
context.classLoader
)
} catch (e: Exception) {
error("Failed to create DexClassLoader for ${entry.name}", e)
return emptyList()
}

val serviceLoader = try {
ServiceLoader.load(Extension::class.java, classLoader)
} catch (e: Throwable) {
error("ServiceLoader failed for ${entry.name}",
Exception("ServiceLoader failed", e))
return emptyList()
}

val extensions = mutableListOf<Extension>()

try {
for (ext in serviceLoader) {
try {
log.debug("Loading ${ext::class.java.name}")
extensions += ext
} catch (e: Throwable) {
error("Failed to instantiate extension from ${entry.name}",
Exception("Failed to instantiate extension", e))
}
}
} catch (e: Throwable) {
error("ServiceLoader iteration failed for ${entry.name}",
Exception("ServiceLoader iteration failed", e))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return extensions
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const val ARCHIVE_JSON = "templates.json"
const val META_FOLDER = "template"
const val META_JSON = "template.json"
const val META_THUMBNAIL = "thumb.png"
const val META_EXTENSION_JAR = "extensions.jar"

const val DEX_OPT_FOLDER = "dex_opt"

const val TEMPLATE_EXTENSION = ".peb"

Expand Down
Loading