Skip to content

Releases: pfranccino/android-gradle-analyzer

v1.7.0

08 Jun 16:44
e2c4373

Choose a tag to compare

Added

  • Salida JSON estructurada en las 4 herramientas (gradle-analyzer, gradle-externals, gradle-impact, gradle-sanity): nuevo --format json —y opción JSON en el selector de formato y en la exportación del menú— que escribe un .json con schema_version estable, pensado para que una skill o script lo consuma y lo formatee. El JSON de internas respeta el foco y --depth (el árbol enraizado); externas incluye el cono de llamadores por nivel (caller_cone)

Fixed

  • El resumen en pantalla (y los exports) ignoraba el foco. run_internal generaba los archivos con el foco pero armaba el resumen sin él, así que en el menú "mostraba todo" el grafo. Ahora el resumen respeta el foco igual que los archivos
  • Timer "congelado" con el motor dinámico/auto. El spinner y la barra seguían sys.stdout, que el análisis redirige a un buffer para capturar logs; sus frames se iban al buffer y el timer no avanzaba en pantalla. Ahora fijan su Console al stdout real
  • Prompt de profundidad simplificado: quedan "Todas (hasta las hojas)" y "1 (solo directas)"; se quitan las opciones fijas "2" y "3" (para una profundidad puntual está --depth N por CLI)

v1.6.0

08 Jun 15:51
da23777

Choose a tag to compare

Changed

  • Dependencias internas ahora es un árbol enraizado en el módulo elegido. Al enfocar un módulo (ej. customer:customer-account-recovery) la salida muestra solo lo que ese módulo usa, recursivamente (clausura hacia abajo), en vez de incluir a sus llamadores y volcar toda la lista de dependencias de app. "Quién me llama" pasa a ser exclusivamente la función de Llamadas externas. Como el conjunto es cerrado bajo "depende de", la vista nunca arrastra módulos ajenos al foco
  • El reporte ASCII de internas se renderiza como árbol anidado real (antes era plano de un nivel), con dedup de subárboles repetidos () y corte de ciclos
  • Llamadas externas ahora es simétrico a internas: el cono transitivo de llamadores. Antes mostraba solo los llamadores directos (1 salto); ahora responde "de qué parte del proyecto te llaman" recursivamente. Una sola resolución del grafo deriva los llamadores directos (con scope, como antes) y el cono transitivo por niveles; el reporte agrega la sección "Cadena de llamadores"
  • Sanidad enfocada muestra ambas direcciones. Al enfocar un módulo (o un directorio), el reporte agrega la sección "Dependencias del foco — ambas direcciones": para cada módulo del foco, quién lo llama (Ca) y a quién llama (Ce), medido sobre el grafo completo. Las métricas Ca/Ce/I siguen calculándose en el contexto del proyecto entero

Added

  • Flag --depth N|all en gradle-analyzer y gradle-externals (y selector de profundidad en el menú): limita cuántos niveles se recorren — las "internas de sus internas" hacia abajo, y el cono de llamadores hacia arriba (default: all)

Fixed

  • Motor dinámico: explosión de dependencias por flavor/buildType. El init script recorría todas las configuraciones, incluidas las resolvables que AGP deriva (*CompileClasspath/*RuntimeClasspath), que heredan las deps de los buckets declarables y reportaban la misma arista una vez por variante (en un proyecto con 4 flavors, ×5 por dependencia). Ahora se inspeccionan solo configuraciones declarables (cfg.canBeResolved descarta las resolvables), con red de seguridad en _normalize_raw
  • Barra de progreso clavada en 0% con motor dinámico/auto. El motor dinámico hace todo el trabajo en una sola llamada bloqueante a Gradle antes de poder reportar progreso, así que la barra determinada se quedaba en 0/N durante los minutos de configuración y saltaba a 100% al final. Para dinámico/auto ahora se usa un spinner indeterminado y un aviso de que Gradle puede tardar varios minutos

v1.5.0

08 Jun 01:29
88b9a96

Choose a tag to compare

Added

  • Foco + contexto completo en las 4 funciones: ahora se puede apuntar gradle-analyzer, gradle-sanity, gradle-externals y gradle-impact a un módulo, un subárbol o el proyecto entero, y el análisis se hace siempre en relación al proyecto completo. El grafo de dependencias se construye desde la raíz (nombres canónicos) y la salida se centra en lo que se elige. Antes, apuntar a una subcarpeta analizaba ese subárbol de forma aislada y distorsionaba las métricas.
    • Sanidad acepta --focus MODULE[,MODULE] (y un selector de módulo en el menú): el reporte y el score se centran en el módulo elegido, pero Ca/Ce/I se miden en el contexto del proyecto completo. El acoplamiento entrante (Ca) ahora cuenta a los llamadores externos al subárbol (ej. app); antes los perdía e inflaba la inestabilidad I (y con ella SDP, fan-out y la detección de lógica mal ubicada)
    • Dependencias internas enfocadas muestran el módulo "en contexto": lo que usa (downstream transitivo) y quién lo llama (llamadores directos)
    • Llamadas externas e impacto ahora funcionan también si se apunta a una subcarpeta: detectan la raíz y encuentran los llamadores/dependientes externos al subárbol
    • Nuevo helper compute_scope(base_path) -> (raíz, known_modules, focus_modules) que unifica la detección de raíz y foco para las 4 funciones. Nuevos tests: TestComputeScope, TestSanityFocusInContext, vista enfocada con llamadores y subcarpeta en externas/impacto

Fixed

  • "No detecta nada" en proyectos con include multilínea: el parser de settings.gradle(.kts) leía línea por línea y descartaba toda línea sin la palabra include, por lo que el formato Kotlin DSL moderno —include(":app",":core",)— devolvía 0 módulos. Y como el settings.gradle sí existía pero quedaba vacío, el análisis no caía al escaneo por carpetas y reportaba cero dependencias. Ahora _extract_includes une statements lógicos (paréntesis abiertos + coma de continuación Groovy) y elimina comentarios antes de matchear. Cubre: include() multilínea con y sin coma final, listas Groovy multilínea, listOf(...).forEach { include(it) }, varios módulos por include(...), semicolons, CRLF/tabs e include (...) con espacio
  • Falsos positivos en la detección de módulos: ahora la detección exige una llamada real a include (la palabra seguida de ( o comilla), no un substring. Con esto se evita contar como módulos: dependencias dentro de bloques comentados /* ... */, identificadores como val includedFeatures = ..., includeBuild, la palabra include embebida en un string (ej. rootProject.name = "include-this"), y strings de sentencias vecinas pegadas con ; en la misma línea (ej. include(":a"); rootProject.name = "x" o un project(...).projectDir = file(...) a continuación)
  • Nombres canónicos al analizar un subárbol: al apuntar gradle-analyzer a una subcarpeta del proyecto (ej. customer/) los módulos se nombraban relativos a esa carpeta (customer-account-recovery) en vez de su nombre completo (customer:customer-account-recovery). Como las dependencias declaradas usan el nombre completo (project(":customer:...")), los edges internos del subárbol se descartaban y el grafo salía vacío ("no detecta nada"). Ahora los módulos se nombran siempre relativos a la raíz del proyecto (donde está settings.gradle) y las dependencias se resuelven desde la raíz (ver "foco + contexto completo" en Added)
  • Fallback robusto cuando settings.gradle no declara módulos parseables: parse_settings_modules devuelve None (en vez de lista vacía) para que los tres analizadores (gradle-analyzer, gradle-impact, gradle-externals) caigan al escaneo por carpetas en lugar de reportar "0 módulos" silenciosamente
  • Nueva clase de test TestExtractIncludesFormats (24 casos) sobre los formatos Kotlin DSL y Groovy de settings.gradle: include multilínea, listas, forEach, comentarios (línea/bloque/inline), ;, condicionales if, interpolación no resoluble, BOM, CRLF, y un settings a escala de monorepo con include("...") con paréntesis, sin : inicial y anidamiento profundo

Changed

  • Documentación: README reescrito y reducido (~270 líneas) con salida real de los CLIs y un diagrama Mermaid que GitHub renderiza en vivo. El detalle (referencia de comandos, métricas de sanidad, motores, configuración, CI, internals y troubleshooting) se movió al Wiki como única fuente de verdad. EXAMPLES.md actualizado a los CLIs actuales con salida real y reproducible contra tests/fixtures/

Removed

  • Imágenes docs/preview.svg y docs/preview-menu.svg (maquetas dibujadas a mano y desactualizadas; reemplazadas por salida real y Mermaid en vivo en el README)

v1.4.1

08 Jun 00:15
bb190f5

Choose a tag to compare

Fixed

  • "No detecta nada" en proyectos con include multilínea: el parser de settings.gradle(.kts) leía línea por línea y descartaba toda línea sin la palabra include, por lo que el formato Kotlin DSL moderno —include(":app",":core",)— devolvía 0 módulos. Y como el settings.gradle sí existía pero quedaba vacío, el análisis no caía al escaneo por carpetas y reportaba cero dependencias. Ahora _extract_includes une statements lógicos (paréntesis abiertos + coma de continuación Groovy) y elimina comentarios antes de matchear. Cubre: include() multilínea con y sin coma final, listas Groovy multilínea, listOf(...).forEach { include(it) }, varios módulos por include(...), semicolons, CRLF/tabs e include (...) con espacio
  • Falsos positivos en la detección de módulos: dependencias dentro de bloques comentados /* ... */ se contaban como módulos, e identificadores como val includedFeatures = ... también (matcheo por substring include). Ahora se usa \binclude\b, que excluye además includeBuild de forma natural
  • Fallback robusto cuando settings.gradle no declara módulos parseables: parse_settings_modules devuelve None (en vez de lista vacía) para que los tres analizadores (gradle-analyzer, gradle-impact, gradle-externals) caigan al escaneo por carpetas en lugar de reportar "0 módulos" silenciosamente
  • Nueva clase de test TestExtractIncludesFormats (13 casos) sobre los formatos Kotlin DSL y Groovy de settings.gradle, incluyendo los antifalsos-positivos y un settings a escala de monorepo con include("...") con paréntesis, sin : inicial y anidamiento profundo

Changed

  • Documentación: README reescrito y reducido (~270 líneas) con salida real de los CLIs y un diagrama Mermaid que GitHub renderiza en vivo. El detalle (referencia de comandos, métricas de sanidad, motores, configuración, CI, internals y troubleshooting) se movió al Wiki como única fuente de verdad. EXAMPLES.md actualizado a los CLIs actuales con salida real y reproducible contra tests/fixtures/

Removed

  • Imágenes docs/preview.svg y docs/preview-menu.svg (maquetas dibujadas a mano y desactualizadas; reemplazadas por salida real y Mermaid en vivo en el README)

v1.4.0

07 Jun 05:20
f8bd77a

Choose a tag to compare

Added

  • Motor dinámico de extracción de dependencias (--engine dynamic): ejecuta gradlew -I <init script> y lee las dependencias project(...) que Gradle resuelve del modelo de proyecto, en vez de parsear texto. Precisión total: soporta Version Catalogs, variables, accessors type-safe y convention plugins sin actualizar el parser. Extrae dependencias declaradas directas por configuración (no resuelve el grafo transitivo), por lo que no necesita red ni compilar
  • Modo --engine auto: usa el motor dinámico si hay wrapper de Gradle (gradlew) y la corrida tiene éxito; si no está disponible o falla, cae al motor estático con una advertencia. Mismo patrón de fallback que tree-sitter/regex
  • Nuevo módulo dependency_engine.py con StaticEngine / DynamicEngine / AutoEngine detrás de una interfaz común resolve(project_root, modules, known_modules). Los cuatro análisis (gradle-analyzer, gradle-impact, gradle-externals, gradle-sanity) aceptan --engine static|dynamic|auto (default static), configurable también por analyzer.yml (engine: por sección)
  • El menú interactivo incluye un paso de selección de motor en cada análisis
  • Nueva clase de test TestDependencyEngine* (18 tests): paridad del estático, normalización/filtrado del JSON dinámico, detección del wrapper y fallback de auto

Changed

  • Los tres analizadores (gradle_analyzer, gradle_impact, external_callers) y gradle_sanity delegan ahora la extracción de dependencias en dependency_engine en lugar de llamar a parse_gradle_file_scoped directamente. El motor estático reproduce el comportamiento previo byte a byte (sin regresión: 89 tests existentes en verde)

Security

  • El motor dinámico ejecuta el build del proyecto analizado (settings, plugins, convention plugins). Es opt-in (static sigue siendo el default) y solo debe usarse sobre repos de confianza. El motor estático solo lee texto y es seguro para repos no confiables

v1.3.2

07 Jun 04:25
df01a2e

Choose a tag to compare

Fixed

  • Falsos positivos en .gradle.kts sin tree-sitter: el fallback de regex para Kotlin DSL no eliminaba comentarios, por lo que dependencias comentadas (// implementation(project(":x")) o bloques /* ... */) se reportaban como llamadas reales en el análisis de dependencias y en la búsqueda de llamadas externas. Ahora el fallback aplica _strip_comments, consistente con el path Groovy (_preprocess_groovy) y el de accessors. Afecta solo a la instalación por defecto (sin el extra kts); con tree-sitter-kotlin instalado el comportamiento ya era correcto
  • Nueva clase de test TestKtsRegexFallback: fuerza el camino sin tree-sitter (parchando _parse_kts_project_calls) para cubrir esta regresión siempre. Los tests previos de comentarios en KTS se saltaban cuando tree-sitter no estaba instalado — justo el escenario donde vivía el bug

v1.3.1

07 Jun 03:50

Choose a tag to compare

Changed

  • Distribución por PyPI: la herramienta ahora se instala con pipx install android-gradle-analyzer (antes solo git+https://...). El install desde git+ se mantiene documentado como vía de desarrollo
  • README y CONTRIBUTING actualizados: el flujo de desarrollo usa pip install -e ".[kts,yaml]" y el de release ya no crea el tag a mano (lo hace el CI)

Added

  • Publicación automática a PyPI vía Trusted Publishers (OIDC) en release.yml: el job publish-pypi sube el paquete sin tokens ni contraseñas, gateado a que el job release haya creado un tag nuevo (no publica en pushes sin bump de versión)

Removed

  • setup.sh y requirements.txt: pyproject.toml queda como fuente única de dependencias

v1.3.0

06 Jun 18:06

Choose a tag to compare

Added

  • Detector de "lógica compartida mal ubicada" en gradle-sanity: identifica un módulo hoja (feature o app — I alto, en la punta del grafo de dependencias) del que sin embargo otros módulos dependen (Ca por encima del límite). Suele indicar código común atrapado en un feature en vez de estar en core/shared. Se basa solo en las métricas que el tool ya calcula (I y Ca) — no depende de nombres de módulo ni de plugins, y core/common quedan excluidos automáticamente por tener I bajo (su Ca alto es esperado)
  • Advisory por default: con leaf_penalty/app_penalty en 0 el detector solo aparece en el reporte y en el JSON (coupling_issues) sin afectar el score → cero regresión para proyectos existentes. Subir las penalizaciones activa el gate en CI
  • Nueva config coupling_limits (leaf_instability, leaf_max_ca, leaf_penalty, app_max_ca, app_penalty) — funciona sin analyzer_config.json usando los defaults internos
  • El punto de entrada (app) se detecta por el plugin com.android.application (única señal de plugin confiable; com.android.library no distingue rol). coupling_overrides permite forzar ("app"/"leaf") o excluir ("ignore") módulos puntuales cuando la métrica se equivoca
  • Nueva clase de test TestLeafCoupling y fixture leaf_coupling

Known limitations

  • La detección del plugin de app no resuelve alias(libs.plugins.android.application) (requiere leer el version catalog); solo el id("com.android.application") literal. El detector funciona igual sin esa señal — solo pierde el umbral más estricto del punto de entrada

v1.2.2

28 May 02:03

Choose a tag to compare

Added

  • Parser tree-sitter para .gradle.kts: cuando tree-sitter-kotlin está instalado (pip install "android-gradle-analyzer[kts]"), los archivos KTS se parsean con un AST real en lugar de regex. Maneja correctamente: dependencias multilínea, comentarios de línea // y de bloque /* */, y parámetros nombrados project(path = ":module"). El fallback a regex es automático si la librería no está disponible
  • Preprocesador Groovy para archivos .gradle: antes de aplicar regex, se eliminan comentarios // y /* */ y se colapsan declaraciones multilínea (e.g. implementation(\n project(":core")\n)). Elimina falsos negativos por comentarios y falsos positivos desde dependencias comentadas
  • Nueva función _strip_comments (interna): limpia comentarios sin colapso de paréntesis — usada en el path KTS para los accessors projects.*
  • Nuevos fixtures de test: kts_multiline, kts_commented, groovy_multiline
  • Nuevas clases de test: TestPreprocessGroovy y TestKtsTreeSitter (se saltan automáticamente si tree-sitter-kotlin no está instalado)
  • Nueva dependencia opcional kts: pip install "android-gradle-analyzer[kts]" instala tree-sitter>=0.22 y tree-sitter-kotlin>=0.3

v1.2.1

27 May 02:38

Choose a tag to compare

Changed

  • pyproject.toml: requires-python actualizado de >=3.8 a >=3.10. El código usa sintaxis PEP 604 (str | None, list[str]) que requiere Python 3.10 o superior. Antes el paquete se instalaba en 3.8/3.9 pero crashaba al primer import — ahora pip se niega antes con un mensaje claro
  • README: badge de Python actualizado a 3.10+ (antes mentía con 3.7+)

Added

  • README: badges de Tests (estado del último workflow) y Release (última versión publicada) vía shields.io