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 @@ -142,7 +142,11 @@ class TemplateListFragment :
updateSpanCount()

if (warnings.isNotEmpty()) {
requireActivity().flashError(warnings.joinToString("\n"))
requireActivity().flashError(
warnings.joinToString(System.lineSeparator()) { w ->
requireContext().getString(w.resId, *w.args.toTypedArray())
}
)
Comment thread
jomen-adfa marked this conversation as resolved.
}
}
}
31 changes: 31 additions & 0 deletions resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1284,4 +1284,35 @@
<string name="merge_conflicts">Merge Conflicts</string>
<string name="abort_merge">Abort Merge</string>
<string name="confirm_abort_merge">Are you sure you want to abort the current merge? All conflict resolutions will be discarded.</string>

<!-- Templates -->
<string name="template_exec_info_basepath">Starting project creation for %1$s</string>
<string name="template_exec_warn_identifiers">Identifier warnings: %1$s</string>
<string name="template_exec_warn_suspicious_entry">Skipping suspicious template entry outside project dir: %1$s</string>
<string name="template_exec_info_processing">Processing template %1$s</string>
<string name="template_exec_error_read_fail">Failed to read template %1$s</string>
<string name="template_exec_error_parse_line">Pebble parse error in %1$s at line %2$d: %3$s</string>
<string name="template_exec_error_parse">Unexpected Pebble parse error in %1$s</string>
<string name="template_exec_error_evaluate_line">Pebble evaluation error in %1$s at line %2$d: %3$s</string>
<string name="template_exec_error_evaluate">Unexpected Pebble evaluation error in %1$s %2$s</string>
<string name="template_exec_error_write">Failed writing output file: %1$s %2$s</string>
<string name="template_exec_error_copy">Failed copying binary entry: %1$s %2$s</string>
<string name="template_exec_error_process">Failed to process template entry: %1$s %2$s</string>
Comment thread
jomen-adfa marked this conversation as resolved.

<string name="template_exec_warn_map_appname">Missing appName, defaulted to %1$s</string>
<string name="template_exec_warn_map_pkgname">Missing packageName, defaulted to %1$s</string>
<string name="template_exec_warn_map_location">Missing saveLocation, defaulted to %1$s</string>
<string name="template_exec_warn_map_agp">Missing agpVersion, defaulted to %1$s</string>
<string name="template_exec_warn_map_kotlin">Missing kotlinVersion, defaulted to %1$s</string>
<string name="template_exec_warn_map_gradle">Missing gradleVersion, defaulted to %1$s</string>
<string name="template_exec_warn_map_compilesdk">Missing compileSdk, defaulted to %1$s</string>
<string name="template_exec_warn_map_targetsdk">Missing targetSdk, defaulted to %1$s</string>
<string name="template_exec_warn_map_javasource_compat">Missing javaSourceCompat, defaulted to %1$s</string>
<string name="template_exec_warn_map_javatarget_compat">Missing javaTargetCompat, defaulted to %1$s</string>
<string name="template_exec_warn_map_javatarget">Missing javaTarget, defaulted to %1$s</string>

<string name="template_read_error_archive_json">%1$s does not contain %2$s</string>
<string name="template_read_error_template_load">Failed to load template at %1$s error: %2$s</string>
<string name="template_read_error_archive_load">Failed to load template archive %1$s error: %2$s</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.itsaky.androidide.templates.impl
import com.google.auto.service.AutoService
import com.google.common.collect.ImmutableList
import com.itsaky.androidide.templates.ITemplateProvider
import com.itsaky.androidide.templates.R
import com.itsaky.androidide.templates.Template
import com.itsaky.androidide.templates.impl.zip.ZipRecipeExecutor
import com.itsaky.androidide.templates.impl.zip.ZipTemplateReader
Expand All @@ -44,7 +45,7 @@ class TemplateProviderImpl : ITemplateProvider {
}

private val templates = mutableMapOf<String, Template<*>>()
val warnings: MutableList<String> = mutableListOf()
val warnings: MutableList<TemplateWarning> = mutableListOf()

init {
reload()
Expand All @@ -64,7 +65,9 @@ class TemplateProviderImpl : ITemplateProvider {
templates[t.templateId] = t
}
} catch (e: Exception) {
warnings.add("Failed to load template archive $zipFile error: ${e.message}")
warnings.add(TemplateWarning(
R.string.template_read_error_archive_load,
listOf(zipFile, e.message)))
log.error("Failed to load template from archive: $zipFile", e)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.itsaky.androidide.templates.impl

data class TemplateWarning(
val resId: Int,
val args: List<Any?>
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.itsaky.androidide.templates.impl.zip

import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.StringRes
import dalvik.system.DexClassLoader

import java.io.File
Expand All @@ -22,6 +23,7 @@ import com.itsaky.androidide.templates.ProjectTemplateData
import com.itsaky.androidide.templates.ProjectTemplateRecipeResult
import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.TemplateRecipe
import com.itsaky.androidide.templates.impl.R
import com.itsaky.androidide.templates.impl.base.ProjectTemplateRecipeResultImpl
import com.itsaky.androidide.utils.Environment

Expand Down Expand Up @@ -50,8 +52,9 @@ class ZipRecipeExecutor(
override fun execute(
executor: RecipeExecutor
): ProjectTemplateRecipeResult {
val ctx = requireNotNull(executor.context) { "context null" }

info("Starting project creation for $basePath")
info(ctx, R.string.template_exec_info_basepath, basePath)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

val projectDir = data.projectDir
if (projectDir.exists()) {
Expand Down Expand Up @@ -80,14 +83,9 @@ class ZipRecipeExecutor(

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)
}
val extensions = loadExtensionFromArchive(zip, extensionsEntry, ctx)
for (ext in extensions) {
builder.extension(ext)
}
}

Expand All @@ -98,11 +96,11 @@ class ZipRecipeExecutor(


val className = data.name.replace(CLASS_NAME_PATTERN, "")
val (baseIdentifiers, warnings) = metaJson.pebbleParams(data, defModule, params)
val (baseIdentifiers, warnings) = metaJson.pebbleParams(ctx, data, defModule, params)
val identifiers = baseIdentifiers + (KEY_CLASS_NAME to className)

if (warnings.isNotEmpty()) {
warn("Identifier warnings: ${warnings.joinToString(System.lineSeparator())}")
warn(ctx, R.string.template_exec_warn_identifiers, warnings.joinToString(System.lineSeparator()))
}

val packageName =
Expand Down Expand Up @@ -130,7 +128,7 @@ class ZipRecipeExecutor(
val outFile = File(projectDir, relativePath.removeSuffix(TEMPLATE_EXTENSION)).canonicalFile

if (!outFile.toPath().startsWith(projectRoot.toPath())) {
warn("Skipping suspicious template entry outside project dir: ${entry.name}")
warn(ctx, R.string.template_exec_warn_suspicious_entry, entry.name)
continue
}

Expand All @@ -141,41 +139,49 @@ class ZipRecipeExecutor(
outFile.parentFile?.mkdirs()

if (entry.name.endsWith(TEMPLATE_EXTENSION)) {
info("Processing template ${entry.name}")
info(ctx, R.string.template_exec_info_processing, entry.name)
val content = try {
zip.getInputStream(entry).bufferedReader().use { it.readText() }
} catch (e: Exception) {
throw e.wrap("Failed to read template ${entry.name}")
throw e.wrap(ctx, R.string.template_exec_error_read_fail, entry.name)
}

val template = try {
pebbleEngine.getTemplate(content)
} catch (e: PebbleException) {
throw e.wrap(
"Pebble parse error in ${entry.name} at line ${e.lineNumber}: ${e.message}"
ctx, R.string.template_exec_error_parse_line,
entry.name, e.lineNumber, e.message
)
} catch (e: Exception) {
throw e.wrap("Unexpected Pebble parse error in ${entry.name}")
throw e.wrap(ctx, R.string.template_exec_error_parse, entry.name)
}

val writer = StringWriter()
val rendered = try {
template.evaluate(writer, identifiers)
} catch (e: PebbleException) {
error(
"Pebble evaluation error in ${entry.name} at line ${e.lineNumber}: ${e.message}", e
ctx, R.string.template_exec_error_evaluate_line,
entry.name, e.lineNumber, e.message
)
null
} catch (e: Exception) {
error("Unexpected Pebble evaluation error in ${entry.name}", e)
error(
ctx, R.string.template_exec_error_evaluate,
entry.name, e.toString()
)
null
}
if (rendered == null) continue

try {
outFile.writeText(writer.toString(), Charsets.UTF_8)
} catch (e: Exception) {
error("Failed writing output file: ${outFile.absolutePath}", e)
error(
ctx, R.string.template_exec_error_write,
outFile.absolutePath, e.toString()
)
}

} else {
Expand All @@ -186,11 +192,17 @@ class ZipRecipeExecutor(
}
}
} catch (e: Exception) {
error("Failed copying binary entry: ${entry.name}", e)
error(
ctx, R.string.template_exec_error_write,
entry.name, e.toString()
)
Comment thread
jomen-adfa marked this conversation as resolved.
}
}
} catch (e: Exception) {
error("Failed to process template entry: ${entry.name}", e)
error(
ctx, R.string.template_exec_error_process,
entry.name, e.toString()
)
}
}
}
Expand Down Expand Up @@ -234,6 +246,7 @@ class ZipRecipeExecutor(
minSdk?.api?.toString() ?: ""

private fun TemplateJson.pebbleParams(
ctx: Context,
data: ProjectTemplateData,
defModule: ModuleTemplateData,
params: MutableMap<String, Parameter<*>>
Expand All @@ -242,41 +255,52 @@ class ZipRecipeExecutor(
val warnings = mutableListOf<String>()

val appName = resolveString(parameters?.required?.appName?.identifier, KEY_APP_NAME)
if (appName.usedDefault) warnings += "Missing 'appName', defaulted to $KEY_APP_NAME"
if (appName.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_appname,
KEY_APP_NAME)

val packageName = resolveString(parameters?.required?.packageName?.identifier, KEY_PACKAGE_NAME)
if (packageName.usedDefault) warnings += "Missing 'packageName', defaulted to $KEY_PACKAGE_NAME"
if (packageName.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_pkgname,
KEY_PACKAGE_NAME)

val saveLocation = resolveString(parameters?.required?.saveLocation?.identifier, KEY_SAVE_LOCATION)
if (saveLocation.usedDefault) warnings += "Missing 'saveLocation', defaulted to $KEY_SAVE_LOCATION"
if (saveLocation.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_location,
KEY_SAVE_LOCATION)

val language = resolveString(parameters?.optional?.language?.identifier, KEY_LANGUAGE)

val minSdk = resolveString(parameters?.optional?.minsdk?.identifier, KEY_MIN_SDK)

val agpVersion = resolveString(system?.agpVersion?.identifier, KEY_AGP_VERSION)
if (agpVersion.usedDefault) warnings += "Missing 'agpVersion', defaulted to $KEY_AGP_VERSION"
if (agpVersion.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_agp,
KEY_AGP_VERSION)

val kotlinVersion = resolveString(system?.kotlinVersion?.identifier, KEY_KOTLIN_VERSION)
if (kotlinVersion.usedDefault) warnings += "Missing 'kotlinVersion', defaulted to $KEY_KOTLIN_VERSION"
if (kotlinVersion.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_kotlin,
KEY_KOTLIN_VERSION)

val gradleVersion = resolveString(system?.gradleVersion?.identifier, KEY_GRADLE_VERSION)
if (gradleVersion.usedDefault) warnings += "Missing 'gradleVersion', defaulted to $KEY_GRADLE_VERSION"
if (gradleVersion.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_gradle,
KEY_GRADLE_VERSION)

val compileSdk = resolveString(system?.compileSdk?.identifier, KEY_COMPILE_SDK)
if (compileSdk.usedDefault) warnings += "Missing 'compileSdk', defaulted to $KEY_COMPILE_SDK"
if (compileSdk.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_compilesdk,
KEY_COMPILE_SDK)

val targetSdk = resolveString(system?.targetSdk?.identifier, KEY_TARGET_SDK)
if (targetSdk.usedDefault) warnings += "Missing 'targetSdk', defaulted to $KEY_TARGET_SDK"
if (targetSdk.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_targetsdk,
KEY_TARGET_SDK)

val javaSourceCompat = resolveString(system?.javaSourceCompat?.identifier, KEY_JAVA_SOURCE_COMPAT)
if (javaSourceCompat.usedDefault) warnings += "Missing 'javaSourceCompat', defaulted to $KEY_JAVA_SOURCE_COMPAT"
if (javaSourceCompat.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_javasource_compat,
KEY_JAVA_SOURCE_COMPAT)

val javaTargetCompat = resolveString(system?.javaTargetCompat?.identifier, KEY_JAVA_TARGET_COMPAT)
if (javaTargetCompat.usedDefault) warnings += "Missing 'javaTargetCompat', defaulted to $KEY_JAVA_TARGET_COMPAT"
if (javaTargetCompat.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_javatarget_compat,
KEY_JAVA_TARGET_COMPAT)

val javaTarget = resolveString(system?.javaTarget?.identifier, KEY_JAVA_TARGET)
if (javaTarget.usedDefault) warnings += "Missing 'javaTarget', defaulted to $KEY_JAVA_TARGET"
if (javaTarget.usedDefault) warnings += ctx.getString(R.string.template_exec_warn_map_javatarget,
KEY_JAVA_TARGET)

val baseMap = mapOf(
appName.value to data.name,
Expand Down Expand Up @@ -341,17 +365,52 @@ class ZipRecipeExecutor(
log.warn(msg)
}

private fun warn(
context: Context,
@StringRes resId: Int,
vararg args: Any?
) {
val msg = context.getString(resId, *args)
warn(msg)
}

private fun info(msg: String) {
log.info(msg)
}

private fun info(
context: Context,
@StringRes resId: Int,
vararg args: Any?
) {
val msg = context.getString(resId, *args)
info(msg)
}


private fun error(msg: String, e: Exception) {
hasErrorsWarnings = true
log.error(msg, e)
}

private fun Exception.wrap(msg: String): RuntimeException =
RuntimeException(msg, this)
private fun error(
context: Context,
@StringRes resId: Int,
vararg args: Any?
) {
hasErrorsWarnings = true
val msg = context.getString(resId, *args)
log.error(msg)
}

private fun Exception.wrap(
context: Context,
@StringRes resId: Int,
vararg args: Any?
): RuntimeException {
val msg = context.getString(resId, *args)
return RuntimeException(msg, this)
}

@SuppressLint("SetWorldReadable")
private fun loadExtensionFromArchive(
Expand Down
Loading
Loading