feat(kotlin-compose): design system + 33 components + gallery_kt + e2e android emulator + scaffolder fixes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: deploy_capacitor_to_emulator
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "deploy_capacitor_to_emulator(app_dir: string, avd_name?: string, package_name?: string) -> void"
|
||||
description: "Pipeline end-to-end: build Capacitor APK + arranca AVD + instala + opcionalmente lanza la app. Valida que el AVD existe, construye el APK con capacitor_build_apk, arranca el emulador de forma idempotente, instala el APK y lanza la app si se da package_name. Imprime comando logcat sugerido al final."
|
||||
tags: [android, capacitor, emulator, deploy, launcher]
|
||||
uses_functions:
|
||||
- capacitor_build_apk_bash_pipelines
|
||||
- android_emulator_list_bash_infra
|
||||
- android_emulator_start_bash_infra
|
||||
- android_apk_install_bash_infra
|
||||
- android_logcat_bash_infra
|
||||
- adb_wsl_bash_infra
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/pipelines/deploy_capacitor_to_emulator.sh"
|
||||
params:
|
||||
- name: app_dir
|
||||
desc: "Path al directorio de la app Capacitor. Debe contener package.json y ser un proyecto web compatible con Capacitor. Puede ser relativo al cwd o absoluto."
|
||||
- name: avd_name
|
||||
desc: "Nombre del AVD Android a usar como destino de deploy. Default: Medium_Phone_API_35. Debe existir en la lista de AVDs del sistema."
|
||||
- name: package_name
|
||||
desc: "Package id Android de la app (ej: com.fnregistry.voiceguide). Opcional. Si se provee, lanza la app tras instalarla y muestra comando logcat sugerido."
|
||||
output: "Stdout con pasos de cada fase. Exit 0 = APK instalado (y lanzado si se dio package_name) en el emulador. Exit != 0 si alguna fase falló (AVD no existe, build falla, emulador no arranca, install falla)."
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Build + deploy en AVD por defecto, sin lanzar app
|
||||
bash bash/functions/pipelines/deploy_capacitor_to_emulator.sh apps/voice_guide
|
||||
|
||||
# Build + deploy + lanzar app
|
||||
bash bash/functions/pipelines/deploy_capacitor_to_emulator.sh \
|
||||
apps/voice_guide \
|
||||
Medium_Phone_API_35 \
|
||||
com.fnregistry.voiceguide
|
||||
|
||||
# Con AVD personalizado
|
||||
bash bash/functions/pipelines/deploy_capacitor_to_emulator.sh \
|
||||
apps/voice_guide \
|
||||
Pixel_7_API_34 \
|
||||
com.fnregistry.voiceguide
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- El APK se busca como `*.apk` en la raiz de `app_dir` tras el build (patron de `capacitor_build_apk`).
|
||||
- El arranque del emulador es idempotente via `android_emulator_start`: si ya hay un emulador corriendo no lanza otro.
|
||||
- El logcat no se sigue automaticamente (no bloqueante). Se imprime el comando para que el usuario lo ejecute si quiere.
|
||||
- Requiere WSL2 + Android SDK instalado en Windows (usa `adb_wsl` para resolver `adb.exe`).
|
||||
- La fase de build puede tardar varios minutos en la primera ejecucion (descarga de Gradle, Capacitor, etc.).
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy_capacitor_to_emulator — Pipeline end-to-end: build Capacitor APK + arranca AVD + instala + lanza app.
|
||||
#
|
||||
# USO:
|
||||
# deploy_capacitor_to_emulator.sh <app_dir> [avd_name] [package_name]
|
||||
#
|
||||
# ARGUMENTOS:
|
||||
# app_dir Path al directorio de la app Capacitor (debe contener package.json). Obligatorio.
|
||||
# avd_name Nombre del AVD a usar (default: Medium_Phone_API_35). Opcional.
|
||||
# package_name Package id de la app Android (ej: com.fnregistry.voiceguide).
|
||||
# Si se da, lanza la app tras instalar. Opcional.
|
||||
#
|
||||
# EJEMPLO:
|
||||
# bash deploy_capacitor_to_emulator.sh apps/voice_guide Medium_Phone_API_35 com.fnregistry.voiceguide
|
||||
#
|
||||
# REQUISITOS:
|
||||
# - Node.js + pnpm en PATH
|
||||
# - Java 17+ en PATH
|
||||
# - ANDROID_HOME seteado o ~/android-sdk/env.sh disponible
|
||||
# - Emulator Windows accesible desde WSL2 (adb_wsl)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parseo de argumentos
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
APP_DIR="${1:-}"
|
||||
AVD_NAME="${2:-Medium_Phone_API_35}"
|
||||
PACKAGE_NAME="${3:-}"
|
||||
|
||||
if [[ -z "$APP_DIR" ]]; then
|
||||
echo "[deploy_capacitor_to_emulator] ERROR: app_dir es obligatorio." >&2
|
||||
echo "USO: $0 <app_dir> [avd_name] [package_name]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolver path absoluto de APP_DIR (puede venir relativo al cwd)
|
||||
if [[ "$APP_DIR" != /* ]]; then
|
||||
APP_DIR="$(pwd)/$APP_DIR"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$APP_DIR" ]]; then
|
||||
echo "[deploy_capacitor_to_emulator] ERROR: directorio no existe: $APP_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$APP_DIR/package.json" ]]; then
|
||||
echo "[deploy_capacitor_to_emulator] ERROR: no se encontró package.json en $APP_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo " deploy_capacitor_to_emulator"
|
||||
echo "======================================================================"
|
||||
echo " app_dir : $APP_DIR"
|
||||
echo " avd_name : $AVD_NAME"
|
||||
echo " package_name : ${PACKAGE_NAME:-(no lanzar)}"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source adb_wsl para tener helpers ADB y resolver $ADB
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# shellcheck source=../infra/adb_wsl.sh
|
||||
source "$REGISTRY_ROOT/bash/functions/infra/adb_wsl.sh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fase 1: Verificar que el AVD existe
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[deploy_capacitor_to_emulator] [1/4] Verificando AVD '$AVD_NAME'..."
|
||||
|
||||
if ! bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_list.sh" | grep -qx "$AVD_NAME"; then
|
||||
echo "[deploy_capacitor_to_emulator] ERROR: AVD '$AVD_NAME' no encontrado." >&2
|
||||
echo " AVDs disponibles:" >&2
|
||||
bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_list.sh" | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[deploy_capacitor_to_emulator] AVD '$AVD_NAME' existe."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fase 2: Build APK
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "[deploy_capacitor_to_emulator] [2/4] Construyendo APK desde $APP_DIR ..."
|
||||
|
||||
bash "$REGISTRY_ROOT/bash/functions/pipelines/capacitor_build_apk.sh" "$APP_DIR"
|
||||
|
||||
# Localizar APK generado: capacitor_build_apk copia el APK a $APP_DIR/<app_name>.apk
|
||||
APK_PATH=""
|
||||
while IFS= read -r -d '' candidate; do
|
||||
APK_PATH="$candidate"
|
||||
break
|
||||
done < <(find "$APP_DIR" -maxdepth 1 -name "*.apk" -print0 2>/dev/null)
|
||||
|
||||
if [[ -z "$APK_PATH" ]]; then
|
||||
echo "[deploy_capacitor_to_emulator] ERROR: No se encontró ningún .apk en $APP_DIR tras el build." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[deploy_capacitor_to_emulator] APK localizado: $APK_PATH"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fase 3: Arrancar emulador (idempotente)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "[deploy_capacitor_to_emulator] [3/4] Arrancando emulador '$AVD_NAME' (idempotente)..."
|
||||
|
||||
bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_start.sh" "$AVD_NAME"
|
||||
|
||||
echo "[deploy_capacitor_to_emulator] Emulador listo."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fase 4: Instalar APK (y lanzar si se dio package_name)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "[deploy_capacitor_to_emulator] [4/4] Instalando APK..."
|
||||
|
||||
# Source android_apk_install para usar su función
|
||||
# shellcheck source=../infra/android_apk_install.sh
|
||||
source "$REGISTRY_ROOT/bash/functions/infra/android_apk_install.sh"
|
||||
|
||||
android_apk_install "$APK_PATH" "$PACKAGE_NAME"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Diagnóstico no-bloqueante: hint logcat
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [[ -n "$PACKAGE_NAME" ]]; then
|
||||
echo ""
|
||||
echo "[deploy_capacitor_to_emulator] Para seguir los logs de la app en tiempo real:"
|
||||
echo " bash $REGISTRY_ROOT/bash/functions/infra/android_logcat.sh --package '$PACKAGE_NAME'"
|
||||
echo ""
|
||||
echo " O los últimos 50 mensajes:"
|
||||
echo " bash $REGISTRY_ROOT/bash/functions/infra/android_logcat.sh --package '$PACKAGE_NAME' --lines 50"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resumen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo " DEPLOY COMPLETADO"
|
||||
echo "======================================================================"
|
||||
echo " APK instalado : $APK_PATH"
|
||||
echo " Emulador : $AVD_NAME"
|
||||
if [[ -n "$PACKAGE_NAME" ]]; then
|
||||
echo " App lanzada : $PACKAGE_NAME"
|
||||
fi
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: init_cpp_app
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "0.1.0"
|
||||
purity: impure
|
||||
signature: "init_cpp_app(name: string, [--project <p>] [--domain <d>] [--desc <s>] [--tags <csv>]) -> void"
|
||||
description: "Scaffolder estandar de apps C++ del registry. Genera main.cpp + CMakeLists.txt + app.md siguiendo el patron canonico (cfg.about/log/panels, sin app_menubar manual, dockspace via framework), registra la app en cpp/CMakeLists.txt, inicializa repo Gitea dataforge/<name> y ejecuta fn index."
|
||||
tags: [cpp, imgui, scaffold, pipeline, bash, launcher]
|
||||
uses_functions:
|
||||
- ensure_repo_synced_bash_infra
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: name
|
||||
desc: "nombre de la app (snake_case). Sera el id en registry.db y el repo dataforge/<name>"
|
||||
- name: "--project"
|
||||
desc: "proyecto bajo projects/ donde crear la app (opcional). Si se omite va a cpp/apps/<name>/"
|
||||
- name: "--domain"
|
||||
desc: "dominio del registry (default: tools)"
|
||||
- name: "--desc"
|
||||
desc: "descripcion breve (frontmatter description + cfg.about/cfg.title)"
|
||||
- name: "--tags"
|
||||
desc: "tags CSV adicionales para el frontmatter (siempre se anade 'imgui')"
|
||||
output: "estructura completa de la app + entry registrada en cpp/CMakeLists.txt + repo Gitea + fn index"
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/pipelines/init_cpp_app.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# App suelta en cpp/apps/<name>/
|
||||
fn run init_cpp_app my_tool --desc "Herramienta para X"
|
||||
|
||||
# App dentro de un proyecto
|
||||
fn run init_cpp_app finance_panel --project budget --desc "Panel de finanzas" --tags "finance,dashboard"
|
||||
```
|
||||
|
||||
## Que genera
|
||||
|
||||
```
|
||||
<dir>/
|
||||
main.cpp # Plantilla canonica: panels[] + cfg.about + cfg.log + run_app(cfg, render)
|
||||
CMakeLists.txt # add_imgui_app(<name> main.cpp)
|
||||
app.md # Frontmatter completo (lang:cpp, framework:imgui, dir_path, repo_url)
|
||||
```
|
||||
|
||||
Y ademas:
|
||||
|
||||
- Registra `add_subdirectory(apps/<name>)` (o el bloque `_DIR` para projects) en `cpp/CMakeLists.txt`.
|
||||
- Crea repo Gitea `dataforge/<name>` con master + commit inicial via `ensure_repo_synced_bash_infra` (requiere `GITEA_URL` y `GITEA_TOKEN`).
|
||||
- Ejecuta `fn index` para registrar la app en `registry.db`.
|
||||
|
||||
## Plantilla `main.cpp`
|
||||
|
||||
La plantilla cumple `cpp/PATTERNS.md`:
|
||||
|
||||
- NO llama `app_menubar` manual (lo dibuja el framework).
|
||||
- NO llama `DockSpaceOverViewport` (auto_dockspace=true por defecto).
|
||||
- Declara `panels[]` con un panel "Main" toggleable.
|
||||
- Setea `cfg.about` (window About) y `cfg.log` (logger + ventana Logs).
|
||||
|
||||
## Despues de crear
|
||||
|
||||
1. Editar `app.md` y completar `uses_functions` cuando la app consuma funciones del registry.
|
||||
2. Anadir las funciones del registry al `CMakeLists.txt` como paths absolutos: `${CMAKE_SOURCE_DIR}/functions/<dom>/<func>.cpp`.
|
||||
3. Build: `cd cpp && cmake --build build --target <name> -j`.
|
||||
Executable
+211
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env bash
|
||||
# init_cpp_app — Scaffolder estandar de apps C++ del registry.
|
||||
#
|
||||
# Genera la estructura canonica (main.cpp, CMakeLists.txt, app.md), registra
|
||||
# la app en cpp/CMakeLists.txt, inicializa git + repo Gitea dataforge/<name>,
|
||||
# y ejecuta fn index. La plantilla cumple cpp/PATTERNS.md y .claude/rules/cpp_apps.md.
|
||||
#
|
||||
# Uso:
|
||||
# init_cpp_app <name> [--project <p>] [--domain <d>] [--desc "..."] [--tags "a,b"]
|
||||
#
|
||||
# Por defecto domain=tools, sin proyecto (cpp/apps/<name>/).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Carga helpers del registry
|
||||
FN_ROOT="${FN_REGISTRY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
# shellcheck source=/dev/null
|
||||
source "$FN_ROOT/bash/functions/infra/ensure_repo_synced.sh"
|
||||
|
||||
init_cpp_app() {
|
||||
local name=""
|
||||
local project=""
|
||||
local domain="tools"
|
||||
local desc=""
|
||||
local tags=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project) project="$2"; shift 2 ;;
|
||||
--domain) domain="$2"; shift 2 ;;
|
||||
--desc) desc="$2"; shift 2 ;;
|
||||
--tags) tags="$2"; shift 2 ;;
|
||||
-*) echo "init_cpp_app: flag desconocido: $1" >&2; return 2 ;;
|
||||
*) if [[ -z "$name" ]]; then name="$1"; else
|
||||
echo "init_cpp_app: argumento extra: $1" >&2; return 2
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "init_cpp_app: se requiere <name>" >&2
|
||||
echo "Uso: init_cpp_app <name> [--project <p>] [--domain <d>] [--desc \"...\"] [--tags \"a,b\"]" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
[[ -z "$desc" ]] && desc="App C++ del registry"
|
||||
|
||||
# Resolver dir destino
|
||||
local rel_dir abs_dir
|
||||
if [[ -n "$project" ]]; then
|
||||
if [[ ! -f "$FN_ROOT/projects/$project/project.md" ]]; then
|
||||
echo "init_cpp_app: proyecto '$project' no existe (falta projects/$project/project.md)" >&2
|
||||
return 1
|
||||
fi
|
||||
rel_dir="projects/$project/apps/$name"
|
||||
else
|
||||
rel_dir="cpp/apps/$name"
|
||||
fi
|
||||
abs_dir="$FN_ROOT/$rel_dir"
|
||||
|
||||
if [[ -e "$abs_dir" ]]; then
|
||||
echo "init_cpp_app: $rel_dir ya existe" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$abs_dir"
|
||||
|
||||
# ---------- main.cpp ----------
|
||||
cat > "$abs_dir/main.cpp" <<EOF
|
||||
#include <imgui.h>
|
||||
#include "framework/app_base.h"
|
||||
#include "core/icons_tabler.h"
|
||||
#include "core/logger.h"
|
||||
|
||||
// Toggles de paneles (visibles desde el menu View del menubar canonico)
|
||||
static bool g_show_main = true;
|
||||
|
||||
static void draw_main() {
|
||||
if (!ImGui::Begin(TI_HOME " Main", &g_show_main)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
ImGui::TextUnformatted("Hello from $name");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
static void render() {
|
||||
// El framework dibuja menubar (View/Layouts/Settings/About) y un
|
||||
// DockSpaceOverViewport central (auto_dockspace=true por defecto).
|
||||
// Aqui solo se dibujan los paneles propios de la app.
|
||||
if (g_show_main) draw_main();
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
static fn_ui::PanelToggle panels[] = {
|
||||
{ "Main", nullptr, &g_show_main },
|
||||
};
|
||||
|
||||
fn::AppConfig cfg;
|
||||
cfg.title = "$name — $desc";
|
||||
cfg.about = { "$name", "0.1.0", "$desc" };
|
||||
cfg.log = { "$name.log", 1 };
|
||||
cfg.panels = panels;
|
||||
cfg.panel_count = sizeof(panels) / sizeof(panels[0]);
|
||||
|
||||
return fn::run_app(cfg, render);
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---------- CMakeLists.txt ----------
|
||||
cat > "$abs_dir/CMakeLists.txt" <<EOF
|
||||
add_imgui_app($name
|
||||
main.cpp
|
||||
)
|
||||
target_include_directories($name PRIVATE \${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties($name PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
EOF
|
||||
|
||||
# ---------- app.md ----------
|
||||
local repo_url="https://gitea.organic-machine.com/dataforge/$name"
|
||||
local tags_yaml="[imgui]"
|
||||
if [[ -n "$tags" ]]; then
|
||||
# Convierte "a,b,c" -> "[a, b, c]"
|
||||
tags_yaml="[$(echo "$tags" | sed 's/,/, /g'), imgui]"
|
||||
fi
|
||||
|
||||
cat > "$abs_dir/app.md" <<EOF
|
||||
---
|
||||
name: $name
|
||||
lang: cpp
|
||||
domain: $domain
|
||||
description: "$desc"
|
||||
tags: $tags_yaml
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
dir_path: "$rel_dir"
|
||||
repo_url: "$repo_url"
|
||||
---
|
||||
|
||||
# $name
|
||||
|
||||
$desc
|
||||
|
||||
## Build
|
||||
|
||||
\`\`\`bash
|
||||
cd cpp && cmake --build build --target $name -j
|
||||
\`\`\`
|
||||
|
||||
## Run
|
||||
|
||||
\`\`\`bash
|
||||
./cpp/build/$name
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# ---------- Registrar en cpp/CMakeLists.txt ----------
|
||||
local cpp_cmake="$FN_ROOT/cpp/CMakeLists.txt"
|
||||
if ! grep -q "# --- $name ---" "$cpp_cmake"; then
|
||||
if [[ -n "$project" ]]; then
|
||||
local upper
|
||||
upper="$(echo "$name" | tr '[:lower:]' '[:upper:]')"
|
||||
cat >> "$cpp_cmake" <<EOF
|
||||
|
||||
# --- $name (lives in projects/$project/apps/) ---
|
||||
set(_${upper}_DIR \${CMAKE_SOURCE_DIR}/../projects/$project/apps/$name)
|
||||
if(EXISTS \${_${upper}_DIR}/CMakeLists.txt)
|
||||
add_subdirectory(\${_${upper}_DIR} \${CMAKE_BINARY_DIR}/apps/$name)
|
||||
endif()
|
||||
EOF
|
||||
else
|
||||
cat >> "$cpp_cmake" <<EOF
|
||||
|
||||
# --- $name ---
|
||||
if(EXISTS \${CMAKE_CURRENT_SOURCE_DIR}/apps/$name/CMakeLists.txt)
|
||||
add_subdirectory(apps/$name)
|
||||
endif()
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------- Git + Gitea ----------
|
||||
if [[ -n "${GITEA_URL:-}" && -n "${GITEA_TOKEN:-}" ]]; then
|
||||
ensure_repo_synced "$abs_dir" "dataforge" "$name" "master" "feat: scaffold $name via init_cpp_app" || \
|
||||
echo "init_cpp_app: warning — ensure_repo_synced fallo, repo no creado" >&2
|
||||
else
|
||||
echo "init_cpp_app: GITEA_URL/GITEA_TOKEN no seteados, omitiendo creacion de repo Gitea" >&2
|
||||
(cd "$abs_dir" && git init -b master >/dev/null 2>&1 || git init >/dev/null 2>&1)
|
||||
fi
|
||||
|
||||
# ---------- fn index ----------
|
||||
if [[ -x "$FN_ROOT/fn" ]]; then
|
||||
(cd "$FN_ROOT" && ./fn index >/dev/null 2>&1) || \
|
||||
echo "init_cpp_app: warning — fn index fallo" >&2
|
||||
fi
|
||||
|
||||
echo "init_cpp_app: $rel_dir creada"
|
||||
echo " - Build: cd $FN_ROOT/cpp && cmake --build build --target $name -j"
|
||||
echo " - app.md: $rel_dir/app.md (rellena uses_functions cuando uses funciones del registry)"
|
||||
}
|
||||
|
||||
# Permitir invocacion directa via 'fn run init_cpp_app ...'
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
init_cpp_app "$@"
|
||||
fi
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
name: init_kotlin_app
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "init_kotlin_app(name: string, [--project <p>] [--desc <s>] [--tags <csv>] [--package <id>]) -> void"
|
||||
description: "Scaffolder canonico de app Android Kotlin Compose con FnTheme + Roborazzi tests + e2e_checks declarados. Mirror exacto del patron init_cpp_app para el stack Kotlin."
|
||||
tags: [android, kotlin, compose, scaffolder, launcher]
|
||||
uses_functions:
|
||||
- ensure_repo_synced_bash_infra
|
||||
- gradle_run_bash_infra
|
||||
- gradle_assemble_debug_bash_infra
|
||||
- gradle_unit_test_bash_infra
|
||||
- gradle_screenshot_test_bash_infra
|
||||
- fn_theme_kt_ui
|
||||
- fn_tokens_kt_ui
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: name
|
||||
desc: "nombre de la app en snake_case. Sera el id en registry.db y el repo dataforge/<name>"
|
||||
- name: "--project"
|
||||
desc: "proyecto bajo projects/ donde crear la app (opcional). Si se omite va a apps/<name>/. El project.md debe existir"
|
||||
- name: "--desc"
|
||||
desc: "descripcion breve para el frontmatter app.md (default: 'App Android Kotlin Compose')"
|
||||
- name: "--tags"
|
||||
desc: "tags CSV adicionales para el frontmatter (siempre se anaden kotlin, compose, android)"
|
||||
- name: "--package"
|
||||
desc: "application id Android (default: com.fnregistry.<name>). Ej: com.aurgi.scanner"
|
||||
output: "Stdout con pasos y archivos creados. Exit 0 = scaffold completo y ready para build. Exit !=0 si falla validacion (nombre invalido, destino existe, proyecto inexistente) o git init."
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/pipelines/init_kotlin_app.sh"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# App suelta en apps/<name>/
|
||||
fn run init_kotlin_app my_scanner --desc "Escaner de documentos" --package "com.aurgi.scanner"
|
||||
|
||||
# App dentro de un proyecto
|
||||
fn run init_kotlin_app expense_tracker --project budget --desc "Tracker de gastos" --tags "finance,mobile"
|
||||
|
||||
# Con package id personalizado
|
||||
fn run init_kotlin_app pos_terminal --package "com.aurgi.pos" --desc "Terminal de punto de venta"
|
||||
```
|
||||
|
||||
## Que genera
|
||||
|
||||
```
|
||||
apps/<name>/ (o projects/<p>/apps/<name>/)
|
||||
├── settings.gradle.kts # rootProject + include(:app) + composite build ui
|
||||
├── build.gradle.kts # top-level (apply false)
|
||||
├── app/build.gradle.kts # Compose + Material3 + Roborazzi + tests
|
||||
├── gradle.properties
|
||||
├── gradlew # stub ejecutable
|
||||
├── gradle/wrapper/gradle-wrapper.properties # Gradle 8.6
|
||||
├── app/
|
||||
│ └── src/
|
||||
│ ├── main/
|
||||
│ │ ├── AndroidManifest.xml # activity con LAUNCHER intent
|
||||
│ │ ├── kotlin/<pkg_path>/
|
||||
│ │ │ └── MainActivity.kt # FnTheme + Surface + Text("<name> ready")
|
||||
│ │ └── res/values/strings.xml
|
||||
│ ├── test/kotlin/<pkg_path>/
|
||||
│ │ └── ExampleScreenshotTest.kt # Roborazzi: captura FnTheme + Surface
|
||||
│ └── androidTest/kotlin/<pkg_path>/
|
||||
│ └── MainActivityTest.kt # Compose ui-test: assertIsDisplayed("<name> ready")
|
||||
├── app.md # frontmatter registry (lang:kt, framework:compose)
|
||||
├── .gitignore
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Composite build
|
||||
|
||||
La app apunta via `includeBuild` a `kotlin/functions/ui` del registry (FnTheme + FnTokens).
|
||||
El path relativo se calcula automaticamente:
|
||||
|
||||
| Ubicacion | Path al composite |
|
||||
|---|---|
|
||||
| `apps/<name>/` | `../../kotlin/functions/ui` |
|
||||
| `projects/<p>/apps/<name>/` | `../../../../kotlin/functions/ui` |
|
||||
|
||||
La dependencia se declara como `implementation("fn.compose:ui")` en `app/build.gradle.kts`.
|
||||
|
||||
## e2e_checks generados
|
||||
|
||||
| id | cmd | timeout |
|
||||
|---|---|---|
|
||||
| `unit` | `fn run gradle_unit_test_bash_infra <dir>` | 240s |
|
||||
| `screenshot` | `fn run gradle_screenshot_test_bash_infra <dir>` | 240s |
|
||||
| `build` | `fn run gradle_assemble_debug_bash_infra <dir>` | 360s |
|
||||
| `emu_start` | `fn run android_emulator_start_bash_infra Medium_Phone_API_35` | 240s |
|
||||
| `instrumented` | `fn run gradle_instrumented_test_bash_infra <dir>` | 600s |
|
||||
| `emu_stop` | `fn run android_emulator_stop_bash_infra` | 30s (warning) |
|
||||
|
||||
## Despues de crear
|
||||
|
||||
1. Completar `uses_functions` en `app.md` cuando la app consuma funciones adicionales del registry.
|
||||
2. Para tests Roborazzi: los snapshots se generan en `app/src/test/snapshots/images/` (gitignored).
|
||||
Para actualizar golden images: `./gradlew recordRoborazziDebug`.
|
||||
3. Para tests instrumentados se necesita un AVD creado con `avdmanager create avd -n Medium_Phone_API_35 ...`.
|
||||
|
||||
## Notas
|
||||
|
||||
- `fn_theme_kt_ui` y `fn_tokens_kt_ui` se referencian en `uses_functions` del `app.md` generado
|
||||
aunque todavia no esten indexados en registry.db — son los modulos del composite build.
|
||||
- `ensure_repo_synced_bash_infra` requiere `GITEA_URL` y `GITEA_TOKEN`. Sin ellos se hace
|
||||
`git init + git commit` local y se avisa al usuario.
|
||||
- Si `--project <p>` se especifica, se ejecuta `fn index` al final para registrar la nueva app.
|
||||
Executable
+531
@@ -0,0 +1,531 @@
|
||||
#!/usr/bin/env bash
|
||||
# init_kotlin_app — Scaffolder canonico de apps Android Kotlin Compose del registry.
|
||||
#
|
||||
# Genera la estructura canonica (MainActivity.kt, build.gradle.kts, app.md,
|
||||
# Roborazzi screenshot tests), apuntando al composite build kotlin/functions/ui
|
||||
# para FnTheme + FnTokens, inicializa git + repo Gitea dataforge/<name>.
|
||||
#
|
||||
# Uso:
|
||||
# init_kotlin_app <name> [--project <p>] [--desc "..."] [--tags "a,b"] [--package <com.foo.bar>]
|
||||
#
|
||||
# Por defecto sin proyecto (apps/<name>/), package = com.fnregistry.<name>.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Carga helpers del registry
|
||||
FN_ROOT="${FN_REGISTRY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
# shellcheck source=/dev/null
|
||||
source "$FN_ROOT/bash/functions/infra/ensure_repo_synced.sh"
|
||||
|
||||
init_kotlin_app() {
|
||||
local name=""
|
||||
local project=""
|
||||
local desc=""
|
||||
local tags=""
|
||||
local pkg_id=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project) project="$2"; shift 2 ;;
|
||||
--desc) desc="$2"; shift 2 ;;
|
||||
--tags) tags="$2"; shift 2 ;;
|
||||
--package) pkg_id="$2"; shift 2 ;;
|
||||
-*) echo "init_kotlin_app: flag desconocido: $1" >&2; return 2 ;;
|
||||
*) if [[ -z "$name" ]]; then name="$1"; else
|
||||
echo "init_kotlin_app: argumento extra: $1" >&2; return 2
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "init_kotlin_app: se requiere <name>" >&2
|
||||
echo "Uso: init_kotlin_app <name> [--project <p>] [--desc \"...\"] [--tags \"a,b\"] [--package <com.foo.bar>]" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# Validar snake_case
|
||||
if [[ ! "$name" =~ ^[a-z][a-z0-9_]*$ ]]; then
|
||||
echo "init_kotlin_app: nombre '$name' debe ser snake_case (solo letras minusculas, digitos y _)" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
[[ -z "$desc" ]] && desc="App Android Kotlin Compose"
|
||||
[[ -z "$pkg_id" ]] && pkg_id="com.fnregistry.$name"
|
||||
|
||||
# Resolver dir destino
|
||||
local rel_dir abs_dir
|
||||
if [[ -n "$project" ]]; then
|
||||
if [[ ! -f "$FN_ROOT/projects/$project/project.md" ]]; then
|
||||
echo "init_kotlin_app: proyecto '$project' no existe (falta projects/$project/project.md)" >&2
|
||||
return 1
|
||||
fi
|
||||
rel_dir="projects/$project/apps/$name"
|
||||
else
|
||||
rel_dir="apps/$name"
|
||||
fi
|
||||
abs_dir="$FN_ROOT/$rel_dir"
|
||||
|
||||
if [[ -e "$abs_dir" ]]; then
|
||||
echo "init_kotlin_app: $rel_dir ya existe" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Convertir package id a path (com.fnregistry.my_app -> com/fnregistry/my_app)
|
||||
local pkg_path
|
||||
pkg_path="$(echo "$pkg_id" | tr '.' '/')"
|
||||
|
||||
# Calcular path relativo al composite build de kotlin/functions/ui
|
||||
# Desde apps/<name>/ -> ../../kotlin/functions/ui
|
||||
# Desde projects/<p>/apps/<n> -> ../../../../kotlin/functions/ui
|
||||
local ui_rel_path
|
||||
if [[ -n "$project" ]]; then
|
||||
ui_rel_path="../../../../kotlin/functions/ui"
|
||||
else
|
||||
ui_rel_path="../../kotlin/functions/ui"
|
||||
fi
|
||||
|
||||
# ---- Crear estructura de directorios ----
|
||||
mkdir -p "$abs_dir/app/src/main/kotlin/$pkg_path"
|
||||
mkdir -p "$abs_dir/app/src/main/res/values"
|
||||
mkdir -p "$abs_dir/app/src/test/kotlin/$pkg_path"
|
||||
mkdir -p "$abs_dir/app/src/androidTest/kotlin/$pkg_path"
|
||||
mkdir -p "$abs_dir/gradle/wrapper"
|
||||
|
||||
echo "init_kotlin_app: creando $rel_dir ..."
|
||||
|
||||
# ---- settings.gradle.kts ----
|
||||
cat > "$abs_dir/settings.gradle.kts" <<EOF
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "$name"
|
||||
include(":app")
|
||||
|
||||
// Composite build: FnTheme + FnTokens desde el registry
|
||||
includeBuild("$ui_rel_path") {
|
||||
dependencySubstitution {
|
||||
substitute(module("fn.compose:ui")).using(project(":"))
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- build.gradle.kts (raiz) ----
|
||||
cat > "$abs_dir/build.gradle.kts" <<'EOF'
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.4.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- app/build.gradle.kts ----
|
||||
cat > "$abs_dir/app/build.gradle.kts" <<EOF
|
||||
plugins {
|
||||
id("com.android.application") version "8.4.0"
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22"
|
||||
id("io.github.takahirom.roborazzi") version "1.20.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "$pkg_id"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "$pkg_id"
|
||||
minSdk = 24
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.8"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
all {
|
||||
it.systemProperty("robolectric.graphicsMode", "NATIVE")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.activity:activity-compose:1.8.2")
|
||||
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
// FnTheme + FnTokens via composite build
|
||||
implementation("fn.compose:ui")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.robolectric:robolectric:4.11.1")
|
||||
testImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
testImplementation("io.github.takahirom.roborazzi:roborazzi:1.20.0")
|
||||
testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.20.0")
|
||||
testImplementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:1.20.0")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2024.02.00"))
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- gradle.properties ----
|
||||
cat > "$abs_dir/gradle.properties" <<'EOF'
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
EOF
|
||||
|
||||
# ---- gradle/wrapper/gradle-wrapper.properties ----
|
||||
cat > "$abs_dir/gradle/wrapper/gradle-wrapper.properties" <<'EOF'
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
EOF
|
||||
|
||||
# ---- gradlew + wrapper jar (vendored real wrapper) ----
|
||||
local tmpl_wrapper="$FN_ROOT/bash/functions/pipelines/templates/kotlin/wrapper"
|
||||
if [[ -f "$tmpl_wrapper/gradlew" && -f "$tmpl_wrapper/gradle-wrapper.jar" ]]; then
|
||||
cp "$tmpl_wrapper/gradlew" "$abs_dir/gradlew"
|
||||
cp "$tmpl_wrapper/gradle-wrapper.jar" "$abs_dir/gradle/wrapper/gradle-wrapper.jar"
|
||||
chmod +x "$abs_dir/gradlew"
|
||||
else
|
||||
echo "init_kotlin_app: WARN templates/kotlin/wrapper missing, fallback gradlew stub"
|
||||
cat > "$abs_dir/gradlew" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "gradlew stub — install gradle wrapper or replace with real one" >&2
|
||||
exit 2
|
||||
EOF
|
||||
chmod +x "$abs_dir/gradlew"
|
||||
fi
|
||||
|
||||
# ---- local.properties (Android SDK location, gitignored, per-machine) ----
|
||||
local sdk_path="${ANDROID_SDK_DIR:-$HOME/android-sdk}"
|
||||
if [[ ! -d "$sdk_path" ]] && [[ -d "$HOME/Android/Sdk" ]]; then
|
||||
sdk_path="$HOME/Android/Sdk"
|
||||
fi
|
||||
cat > "$abs_dir/local.properties" <<EOF
|
||||
# Auto-generated by init_kotlin_app. Per-machine, gitignored.
|
||||
sdk.dir=$sdk_path
|
||||
EOF
|
||||
|
||||
# ---- AndroidManifest.xml ----
|
||||
cat > "$abs_dir/app/src/main/AndroidManifest.xml" <<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
EOF
|
||||
|
||||
# ---- res/values/strings.xml ----
|
||||
cat > "$abs_dir/app/src/main/res/values/strings.xml" <<EOF
|
||||
<resources>
|
||||
<string name="app_name">$name</string>
|
||||
</resources>
|
||||
EOF
|
||||
|
||||
# ---- MainActivity.kt ----
|
||||
cat > "$abs_dir/app/src/main/kotlin/$pkg_path/MainActivity.kt" <<EOF
|
||||
package $pkg_id
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import fn.compose.theme.FnTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
FnTheme {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
Text("$name ready")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- ExampleScreenshotTest.kt (Roborazzi) ----
|
||||
cat > "$abs_dir/app/src/test/kotlin/$pkg_path/ExampleScreenshotTest.kt" <<EOF
|
||||
package $pkg_id
|
||||
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import fn.compose.theme.FnTheme
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class ExampleScreenshotTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun screenshotFnThemeSurface() {
|
||||
composeTestRule.setContent {
|
||||
FnTheme {
|
||||
Surface {
|
||||
Text("$name screenshot")
|
||||
}
|
||||
}
|
||||
}
|
||||
composeTestRule.onRoot().captureRoboImage("src/test/snapshots/images/${name}_smoke.png")
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- MainActivityTest.kt (instrumented) ----
|
||||
cat > "$abs_dir/app/src/androidTest/kotlin/$pkg_path/MainActivityTest.kt" <<EOF
|
||||
package $pkg_id
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<MainActivity>()
|
||||
|
||||
@Test
|
||||
fun appLaunchesAndShowsReadyText() {
|
||||
composeTestRule
|
||||
.onNodeWithText("$name ready")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# ---- app.md frontmatter ----
|
||||
local repo_url="https://gitea.organic-machine.com/dataforge/$name"
|
||||
local tags_yaml="[kotlin, compose, android]"
|
||||
if [[ -n "$tags" ]]; then
|
||||
tags_yaml="[$(echo "$tags" | sed 's/,/, /g'), kotlin, compose, android]"
|
||||
fi
|
||||
|
||||
cat > "$abs_dir/app.md" <<EOF
|
||||
---
|
||||
name: $name
|
||||
domain: tools
|
||||
description: "$desc"
|
||||
tags: $tags_yaml
|
||||
lang: kt
|
||||
framework: compose
|
||||
entry_point: "app/src/main/kotlin/$pkg_path/MainActivity.kt"
|
||||
dir_path: "$rel_dir"
|
||||
repo_url: "$repo_url"
|
||||
uses_functions:
|
||||
- fn_theme_kt_ui
|
||||
- fn_tokens_kt_ui
|
||||
uses_types: []
|
||||
e2e_checks:
|
||||
- id: unit
|
||||
cmd: "fn run gradle_unit_test_bash_infra $rel_dir"
|
||||
timeout_s: 240
|
||||
- id: screenshot
|
||||
cmd: "fn run gradle_screenshot_test_bash_infra $rel_dir"
|
||||
timeout_s: 240
|
||||
- id: build
|
||||
cmd: "fn run gradle_assemble_debug_bash_infra $rel_dir"
|
||||
timeout_s: 360
|
||||
- id: emu_start
|
||||
cmd: "fn run android_emulator_start_bash_infra Medium_Phone_API_35"
|
||||
timeout_s: 240
|
||||
- id: instrumented
|
||||
cmd: "fn run gradle_instrumented_test_bash_infra $rel_dir"
|
||||
timeout_s: 600
|
||||
- id: emu_stop
|
||||
cmd: "fn run android_emulator_stop_bash_infra"
|
||||
severity: warning
|
||||
timeout_s: 30
|
||||
---
|
||||
|
||||
# $name
|
||||
|
||||
$desc
|
||||
|
||||
## Build
|
||||
|
||||
\`\`\`bash
|
||||
fn run gradle_assemble_debug_bash_infra $rel_dir
|
||||
\`\`\`
|
||||
|
||||
## Tests unitarios + Roborazzi screenshots
|
||||
|
||||
\`\`\`bash
|
||||
fn run gradle_unit_test_bash_infra $rel_dir
|
||||
fn run gradle_screenshot_test_bash_infra $rel_dir
|
||||
\`\`\`
|
||||
|
||||
## Tests instrumentados (requiere emulador)
|
||||
|
||||
\`\`\`bash
|
||||
fn run android_emulator_start_bash_infra Medium_Phone_API_35
|
||||
fn run gradle_instrumented_test_bash_infra $rel_dir
|
||||
fn run android_emulator_stop_bash_infra
|
||||
\`\`\`
|
||||
|
||||
## Package
|
||||
|
||||
\`$pkg_id\`
|
||||
|
||||
## FnTheme
|
||||
|
||||
La app usa FnTheme y FnTokens via composite build en \`kotlin/functions/ui\`.
|
||||
Para cambiar colores o tipografia, editar el modulo del registry.
|
||||
EOF
|
||||
|
||||
# ---- .gitignore ----
|
||||
cat > "$abs_dir/.gitignore" <<'EOF'
|
||||
.gradle/
|
||||
build/
|
||||
local.properties
|
||||
*.iml
|
||||
.idea/
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
*.aab
|
||||
# NOTE: app/src/test/snapshots/ is committed (Roborazzi goldens are test refs).
|
||||
EOF
|
||||
|
||||
# ---- README.md ----
|
||||
cat > "$abs_dir/README.md" <<EOF
|
||||
# $name
|
||||
|
||||
$desc
|
||||
|
||||
Generado con \`init_kotlin_app\` del registry fn_registry.
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Android SDK 34
|
||||
- JDK 17
|
||||
- Gradle 8.6 (via wrapper)
|
||||
- \`kotlin/functions/ui\` modulo del registry (composite build)
|
||||
|
||||
## Build rapido
|
||||
|
||||
\`\`\`bash
|
||||
fn run gradle_assemble_debug_bash_infra $rel_dir
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# ---- Git + Gitea ----
|
||||
if [[ -n "${GITEA_URL:-}" && -n "${GITEA_TOKEN:-}" ]]; then
|
||||
ensure_repo_synced "$abs_dir" "dataforge" "$name" "master" \
|
||||
"feat: scaffold $name via init_kotlin_app" || \
|
||||
echo "init_kotlin_app: warning — ensure_repo_synced fallo, repo no creado" >&2
|
||||
else
|
||||
echo "init_kotlin_app: GITEA_URL/GITEA_TOKEN no seteados, omitiendo creacion de repo Gitea" >&2
|
||||
(cd "$abs_dir" && git init -b master >/dev/null 2>&1 || git init >/dev/null 2>&1 \
|
||||
&& git add -A \
|
||||
&& git commit -m "feat: scaffold $name via init_kotlin_app" --quiet)
|
||||
fi
|
||||
|
||||
# ---- fn index si hay proyecto ----
|
||||
if [[ -n "$project" && -x "$FN_ROOT/fn" ]]; then
|
||||
(cd "$FN_ROOT" && ./fn index >/dev/null 2>&1) || \
|
||||
echo "init_kotlin_app: warning — fn index fallo" >&2
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "init_kotlin_app: $rel_dir creada"
|
||||
echo ""
|
||||
echo " Archivos:"
|
||||
echo " $rel_dir/settings.gradle.kts"
|
||||
echo " $rel_dir/app/build.gradle.kts"
|
||||
echo " $rel_dir/app/src/main/kotlin/$pkg_path/MainActivity.kt"
|
||||
echo " $rel_dir/app/src/test/kotlin/$pkg_path/ExampleScreenshotTest.kt"
|
||||
echo " $rel_dir/app/src/androidTest/kotlin/$pkg_path/MainActivityTest.kt"
|
||||
echo " $rel_dir/app.md"
|
||||
echo ""
|
||||
echo " Pasos siguientes:"
|
||||
echo " fn run gradle_unit_test_bash_infra $rel_dir"
|
||||
echo " fn run gradle_screenshot_test_bash_infra $rel_dir"
|
||||
echo " fn run gradle_assemble_debug_bash_infra $rel_dir"
|
||||
echo ""
|
||||
echo " Para tests instrumentados (emulador):"
|
||||
echo " fn run android_emulator_start_bash_infra Medium_Phone_API_35"
|
||||
echo " fn run gradle_instrumented_test_bash_infra $rel_dir"
|
||||
echo " fn run android_emulator_stop_bash_infra"
|
||||
echo ""
|
||||
echo " Package: $pkg_id"
|
||||
echo " FnTheme composite: $ui_rel_path"
|
||||
}
|
||||
|
||||
# Permitir invocacion directa via 'fn run init_kotlin_app ...'
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
init_kotlin_app "$@"
|
||||
fi
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: run_kotlin_app_tests
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "run_kotlin_app_tests(project_dir: string, avd_name?: string, --skip-emulator?, --no-stop?) -> int"
|
||||
description: "Pipeline e2e completo de testing app Kotlin: unit JVM + screenshot Roborazzi + build APK + instrumented Compose en emulador."
|
||||
tags: [android, kotlin, compose, test, e2e, launcher]
|
||||
uses_functions:
|
||||
- gradle_unit_test_bash_infra
|
||||
- gradle_screenshot_test_bash_infra
|
||||
- gradle_assemble_debug_bash_infra
|
||||
- gradle_instrumented_test_bash_infra
|
||||
- android_emulator_list_bash_infra
|
||||
- android_emulator_start_bash_infra
|
||||
- android_emulator_stop_bash_infra
|
||||
- adb_wsl_bash_infra
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/pipelines/run_kotlin_app_tests.sh"
|
||||
params:
|
||||
- name: project_dir
|
||||
desc: "Raiz del proyecto Android. Debe contener gradlew. Puede ser relativo al cwd o absoluto."
|
||||
- name: avd_name
|
||||
desc: "AVD para instrumented tests. Default: Medium_Phone_API_35. Debe existir en la lista de AVDs del sistema. Ignorado si se pasa --skip-emulator."
|
||||
- name: --skip-emulator
|
||||
desc: "Saltar instrumented tests. El pipeline solo ejecuta unit tests, screenshot tests y build APK, luego sale con exit 0."
|
||||
- name: --no-stop
|
||||
desc: "No parar el emulador al finalizar los instrumented tests. Util en desarrollo iterativo para no esperar el arranque en la siguiente ejecucion."
|
||||
output: "Stdout con tabla resumen de cada step (nombre, OK/FAIL/SKIP, tiempo). Exit 0 = todos los tests pasan. Exit codes: 1=unit tests fallaron, 2=screenshot tests fallaron, 3=build APK fallado, 4=emulador no encontrado o no arranca, 5=instrumented tests fallaron."
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Suite completa con AVD por defecto
|
||||
bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_kotlin_app
|
||||
|
||||
# Suite completa con AVD especifico
|
||||
bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_kotlin_app Pixel_7_API_34
|
||||
|
||||
# Solo tests JVM (unit + screenshot + build), sin emulador
|
||||
bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_kotlin_app --skip-emulator
|
||||
|
||||
# Suite completa, dejar emulador corriendo al final
|
||||
bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_kotlin_app Medium_Phone_API_35 --no-stop
|
||||
|
||||
# Desde el launcher (fn run)
|
||||
fn run run_kotlin_app_tests apps/my_kotlin_app
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- Fail-fast: si un paso falla, el pipeline imprime el resumen parcial y sale con el exit code del paso fallido. No continua al siguiente.
|
||||
- El orden de los flags tras `project_dir` es libre: `avd_name` es el primer argumento no-flag; `--skip-emulator` y `--no-stop` pueden aparecer en cualquier posicion.
|
||||
- El arranque del emulador usa `android_emulator_start` que es idempotente: si ya hay un emulador corriendo con ese AVD, no lanza otro.
|
||||
- `adb_wsl` se sourcea para resolver `$ADB` apuntando a `adb.exe` en Windows desde WSL2.
|
||||
- El paso `emulator_stop` se registra como SKIP en la tabla resumen cuando se pasa `--no-stop`.
|
||||
- Requiere WSL2 + Android SDK instalado en Windows. `ANDROID_HOME` o `~/android-sdk/env.sh` deben estar disponibles.
|
||||
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env bash
|
||||
# run_kotlin_app_tests — Pipeline e2e completo de testing app Kotlin:
|
||||
# unit JVM + screenshot Roborazzi + build APK + instrumented Compose en emulador.
|
||||
#
|
||||
# USO:
|
||||
# bash run_kotlin_app_tests.sh <project_dir> [avd_name] [--skip-emulator] [--no-stop]
|
||||
#
|
||||
# ARGUMENTOS:
|
||||
# project_dir Raiz del proyecto Android (debe contener gradlew). Obligatorio.
|
||||
# avd_name Nombre del AVD para instrumented tests. Default: Medium_Phone_API_35.
|
||||
# --skip-emulator Saltar instrumented tests (solo unit + screenshot + build).
|
||||
# --no-stop No parar el emulador al finalizar (util en desarrollo iterativo).
|
||||
#
|
||||
# EXIT CODES:
|
||||
# 0 Todos los tests pasan.
|
||||
# 1 Unit tests fallaron.
|
||||
# 2 Screenshot tests fallaron.
|
||||
# 3 Build APK fallado.
|
||||
# 4 AVD no encontrado.
|
||||
# 5 Instrumented tests fallaron.
|
||||
#
|
||||
# EJEMPLO:
|
||||
# bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_app
|
||||
# bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_app Pixel_7_API_34
|
||||
# bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_app --skip-emulator
|
||||
# bash bash/functions/pipelines/run_kotlin_app_tests.sh apps/my_app Medium_Phone_API_35 --no-stop
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parseo de argumentos
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PROJECT_DIR="${1:-}"
|
||||
AVD_NAME="Medium_Phone_API_35"
|
||||
SKIP_EMULATOR=false
|
||||
NO_STOP=false
|
||||
|
||||
# Primer argumento posicional es project_dir; el resto puede ser flags o avd_name.
|
||||
shift || true
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--skip-emulator) SKIP_EMULATOR=true ;;
|
||||
--no-stop) NO_STOP=true ;;
|
||||
--*)
|
||||
echo "[run_kotlin_app_tests] ERROR: flag desconocido: $arg" >&2
|
||||
echo "USO: $0 <project_dir> [avd_name] [--skip-emulator] [--no-stop]" >&2
|
||||
exit 1
|
||||
;;
|
||||
*) AVD_NAME="$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$PROJECT_DIR" ]]; then
|
||||
echo "[run_kotlin_app_tests] ERROR: project_dir es obligatorio." >&2
|
||||
echo "USO: $0 <project_dir> [avd_name] [--skip-emulator] [--no-stop]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolver path absoluto
|
||||
if [[ "$PROJECT_DIR" != /* ]]; then
|
||||
PROJECT_DIR="$(pwd)/$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Validacion inicial
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [[ ! -f "$PROJECT_DIR/gradlew" ]]; then
|
||||
echo "[run_kotlin_app_tests] ERROR: no se encontro gradlew en $PROJECT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo " run_kotlin_app_tests"
|
||||
echo "======================================================================"
|
||||
echo " project_dir : $PROJECT_DIR"
|
||||
echo " avd_name : $AVD_NAME"
|
||||
echo " skip_emulator : $SKIP_EMULATOR"
|
||||
echo " no_stop : $NO_STOP"
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tabla de resultados acumulada
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Arrays paralelos: nombre, status, tiempo
|
||||
STEP_NAMES=()
|
||||
STEP_STATUS=()
|
||||
STEP_TIMES=()
|
||||
|
||||
record_step() {
|
||||
local name="$1" status="$2" elapsed="$3"
|
||||
STEP_NAMES+=("$name")
|
||||
STEP_STATUS+=("$status")
|
||||
STEP_TIMES+=("${elapsed}s")
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo " RESUMEN"
|
||||
printf " %-30s %-6s %s\n" "STEP" "STATUS" "TIEMPO"
|
||||
echo " ------------------------------ ------ ------"
|
||||
for i in "${!STEP_NAMES[@]}"; do
|
||||
printf " %-30s %-6s %s\n" "${STEP_NAMES[$i]}" "${STEP_STATUS[$i]}" "${STEP_TIMES[$i]}"
|
||||
done
|
||||
echo "======================================================================"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paso 1: Unit tests (JVM)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[run_kotlin_app_tests] [1/4] Unit tests (JVM)..."
|
||||
T_START=$SECONDS
|
||||
if bash "$REGISTRY_ROOT/bash/functions/infra/gradle_unit_test.sh" "$PROJECT_DIR"; then
|
||||
record_step "unit_tests" "OK" $((SECONDS - T_START))
|
||||
echo "[run_kotlin_app_tests] Unit tests: OK"
|
||||
else
|
||||
record_step "unit_tests" "FAIL" $((SECONDS - T_START))
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] ERROR: unit tests fallaron." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paso 2: Screenshot tests (Roborazzi, JVM)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "[run_kotlin_app_tests] [2/4] Screenshot tests (Roborazzi, JVM)..."
|
||||
T_START=$SECONDS
|
||||
if bash "$REGISTRY_ROOT/bash/functions/infra/gradle_screenshot_test.sh" "$PROJECT_DIR"; then
|
||||
record_step "screenshot_tests" "OK" $((SECONDS - T_START))
|
||||
echo "[run_kotlin_app_tests] Screenshot tests: OK"
|
||||
else
|
||||
record_step "screenshot_tests" "FAIL" $((SECONDS - T_START))
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] ERROR: screenshot tests fallaron." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paso 3: Build APK debug
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "[run_kotlin_app_tests] [3/4] Build APK debug..."
|
||||
T_START=$SECONDS
|
||||
if bash "$REGISTRY_ROOT/bash/functions/infra/gradle_assemble_debug.sh" "$PROJECT_DIR"; then
|
||||
record_step "assemble_debug" "OK" $((SECONDS - T_START))
|
||||
echo "[run_kotlin_app_tests] Build APK: OK"
|
||||
else
|
||||
record_step "assemble_debug" "FAIL" $((SECONDS - T_START))
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] ERROR: build APK fallado." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Paso 4: Instrumented tests (emulador)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [[ "$SKIP_EMULATOR" == "true" ]]; then
|
||||
record_step "instrumented_tests" "SKIP" 0
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] --skip-emulator activo. Pipeline completo (sin instrumented)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[run_kotlin_app_tests] [4/4] Instrumented tests en emulador '$AVD_NAME'..."
|
||||
|
||||
# Source adb_wsl para resolver $ADB y helpers de dispositivo
|
||||
# shellcheck source=../infra/adb_wsl.sh
|
||||
source "$REGISTRY_ROOT/bash/functions/infra/adb_wsl.sh"
|
||||
|
||||
# Verificar que el AVD existe
|
||||
echo "[run_kotlin_app_tests] Verificando AVD '$AVD_NAME'..."
|
||||
if ! bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_list.sh" | grep -qx "$AVD_NAME"; then
|
||||
record_step "emulator_check" "FAIL" 0
|
||||
record_step "instrumented_tests" "SKIP" 0
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] ERROR: AVD '$AVD_NAME' no encontrado." >&2
|
||||
echo " AVDs disponibles:" >&2
|
||||
bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_list.sh" | sed 's/^/ /' >&2
|
||||
exit 4
|
||||
fi
|
||||
record_step "emulator_check" "OK" 0
|
||||
|
||||
# Arrancar emulador (idempotente)
|
||||
echo "[run_kotlin_app_tests] Arrancando emulador '$AVD_NAME' (idempotente)..."
|
||||
T_START=$SECONDS
|
||||
if ! bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_start.sh" "$AVD_NAME"; then
|
||||
record_step "emulator_start" "FAIL" $((SECONDS - T_START))
|
||||
print_summary
|
||||
echo "[run_kotlin_app_tests] ERROR: no se pudo arrancar el emulador." >&2
|
||||
exit 4
|
||||
fi
|
||||
record_step "emulator_start" "OK" $((SECONDS - T_START))
|
||||
|
||||
# Correr instrumented tests
|
||||
echo "[run_kotlin_app_tests] Corriendo instrumented tests..."
|
||||
T_START=$SECONDS
|
||||
INSTRUMENTED_EXIT=0
|
||||
bash "$REGISTRY_ROOT/bash/functions/infra/gradle_instrumented_test.sh" "$PROJECT_DIR" || INSTRUMENTED_EXIT=$?
|
||||
|
||||
if [[ $INSTRUMENTED_EXIT -eq 0 ]]; then
|
||||
record_step "instrumented_tests" "OK" $((SECONDS - T_START))
|
||||
echo "[run_kotlin_app_tests] Instrumented tests: OK"
|
||||
else
|
||||
record_step "instrumented_tests" "FAIL" $((SECONDS - T_START))
|
||||
fi
|
||||
|
||||
# Parar emulador (salvo --no-stop)
|
||||
if [[ "$NO_STOP" == "false" ]]; then
|
||||
echo "[run_kotlin_app_tests] Parando emulador..."
|
||||
T_START=$SECONDS
|
||||
bash "$REGISTRY_ROOT/bash/functions/infra/android_emulator_stop.sh" || true
|
||||
record_step "emulator_stop" "OK" $((SECONDS - T_START))
|
||||
else
|
||||
record_step "emulator_stop" "SKIP" 0
|
||||
echo "[run_kotlin_app_tests] --no-stop activo: emulador sigue corriendo."
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if [[ $INSTRUMENTED_EXIT -ne 0 ]]; then
|
||||
echo "[run_kotlin_app_tests] ERROR: instrumented tests fallaron." >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "[run_kotlin_app_tests] Pipeline e2e completado con exito."
|
||||
exit 0
|
||||
Binary file not shown.
+251
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Reference in New Issue
Block a user