Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18bdfc7bfd | |||
| 27ae829a1e |
@@ -6,9 +6,7 @@
|
||||
scan_secrets_in_dirty() {
|
||||
local repo_dir="${1:-.}"
|
||||
|
||||
# Accept both regular repos (.git is a directory) and worktrees (.git is a
|
||||
# file containing "gitdir: ..." pointer).
|
||||
if [[ ! -d "$repo_dir/.git" && ! -f "$repo_dir/.git" ]]; then
|
||||
if [[ ! -d "$repo_dir/.git" ]]; then
|
||||
echo "scan_secrets_in_dirty: '$repo_dir' no es un repo git" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -17,9 +17,7 @@ git_hook_audit_app_drift() {
|
||||
echo "ERROR: repo_dir required" >&2
|
||||
return 2
|
||||
fi
|
||||
# Accept both regular repos (.git is a directory) and worktrees (.git is a
|
||||
# file containing "gitdir: ..." pointer).
|
||||
if [[ ! -d "$repo_dir/.git" && ! -f "$repo_dir/.git" ]]; then
|
||||
if [[ ! -d "$repo_dir/.git" ]]; then
|
||||
echo "ERROR: $repo_dir is not a git repo" >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
@@ -3,7 +3,7 @@ name: gradle_run
|
||||
kind: function
|
||||
lang: bash
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "gradle_run(project_dir: string, task...: string) -> int"
|
||||
description: "Wrapper canonico para invocar gradlew Android en WSL2 con JDK 17 + ANDROID_HOME validados."
|
||||
@@ -24,7 +24,7 @@ tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "bash/functions/infra/gradle_run.sh"
|
||||
notes: "Las demas funciones gradle_* lo sourcean. Reutiliza patron de adb_wsl_bash_infra para ser source-able+ejecutable. Cubre tanto SDK Linux (~/Android/Sdk via install_android_sdk) como SDK Windows (/mnt/c/...) montado en WSL."
|
||||
notes: "Las demas funciones gradle_* lo sourcean. Reutiliza patron de adb_wsl_bash_infra para ser source-able+ejecutable. Cubre SDK Linux en $HOME/android-sdk (install_android_sdk_bash_infra), $HOME/Android/Sdk (Android Studio), y SDK Windows (/mnt/c/...) montado en WSL."
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
@@ -50,10 +50,12 @@ Si no esta fijado en el entorno, busca en orden:
|
||||
Si ninguno existe → error en stderr y `return 1`.
|
||||
|
||||
### ANDROID_HOME
|
||||
Si no esta fijado:
|
||||
1. Intenta `$HOME/Android/Sdk` (SDK Linux via `install_android_sdk_bash_infra`)
|
||||
2. Si no existe, intenta `$ANDROID_SDK_WIN` (SDK Windows montado en `/mnt/c/...`)
|
||||
3. Si ninguno, lo deja vacio — gradle mostrara el error adecuado para builds JVM puros
|
||||
Si no esta fijado, busca en orden (requiere que el directorio tenga `platform-tools/`):
|
||||
1. `$HOME/android-sdk` — default de `install_android_sdk_bash_infra` (lowercase)
|
||||
2. `$HOME/Android/Sdk` — default de Android Studio en Linux
|
||||
3. `$ANDROID_SDK_WIN` (o `/mnt/c/Users/$USER/AppData/Local/Android/Sdk`) — SDK Windows montado en WSL2
|
||||
|
||||
Si ninguno existe con `platform-tools/`, lo deja vacio — gradle mostrara el error adecuado para builds JVM puros
|
||||
|
||||
## Exit codes
|
||||
|
||||
@@ -69,4 +71,8 @@ Si no esta fijado:
|
||||
Source-able y ejecutable directo. Al sourcear, el caller importa la funcion `gradle_run` sin ejecutarla. Al ejecutar directamente, delega `"$@"` a `gradle_run`.
|
||||
|
||||
No exporta `JAVA_HOME`/`ANDROID_HOME` al entorno del shell padre — los variables se pasan solo al subshell de gradlew para evitar contaminar el entorno.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-05-15) — ANDROID_HOME detection order: prioriza `$HOME/android-sdk` (install_android_sdk default) sobre `$HOME/Android/Sdk`; requiere platform-tools/ presente; anade WSL2 Windows path como fallback explicito (issue 0076)
|
||||
---
|
||||
|
||||
@@ -44,16 +44,25 @@ gradle_run() {
|
||||
fi
|
||||
|
||||
# ---- Resolver ANDROID_HOME ---------------------------------------------
|
||||
# Orden de busqueda (de mas probable a menos para entorno Linux/WSL2):
|
||||
# 1. $HOME/android-sdk — instalado por install_android_sdk_bash_infra (default)
|
||||
# 2. $HOME/Android/Sdk — ruta de Android Studio en Linux
|
||||
# 3. $ANDROID_SDK_WIN — SDK Windows montado en WSL2 via /mnt/c/...
|
||||
# Solo se acepta un candidato si tiene platform-tools/, no solo el directorio raiz.
|
||||
local android_home="${ANDROID_HOME:-}"
|
||||
if [[ -z "$android_home" ]]; then
|
||||
local _default_linux="$HOME/Android/Sdk"
|
||||
if [[ -d "$_default_linux" ]]; then
|
||||
android_home="$_default_linux"
|
||||
elif [[ -n "${ANDROID_SDK_WIN:-}" && -d "${ANDROID_SDK_WIN}" ]]; then
|
||||
# SDK Windows montado en WSL via /mnt/c/...
|
||||
android_home="${ANDROID_SDK_WIN}"
|
||||
fi
|
||||
unset _default_linux
|
||||
local _sdk_candidates=(
|
||||
"$HOME/android-sdk"
|
||||
"$HOME/Android/Sdk"
|
||||
"${ANDROID_SDK_WIN:-/mnt/c/Users/$USER/AppData/Local/Android/Sdk}"
|
||||
)
|
||||
for _candidate in "${_sdk_candidates[@]}"; do
|
||||
if [[ -d "$_candidate" && -d "$_candidate/platform-tools" ]]; then
|
||||
android_home="$_candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
unset _sdk_candidates _candidate
|
||||
fi
|
||||
|
||||
# ANDROID_HOME puede quedar vacio si no hay SDK instalado; gradle mostrara
|
||||
|
||||
+1
-58
@@ -194,67 +194,10 @@ func buildGoCommand(fn *registry.Function, registryRoot, absPath string, args []
|
||||
}
|
||||
|
||||
|
||||
// bashFunctionName returns the name of the top-level function defined in the
|
||||
// script that matches the file basename (e.g. "gradle_unit_test" for
|
||||
// "gradle_unit_test.sh"), or "" if no such function is found.
|
||||
//
|
||||
// Library-style bash scripts define a function `<basename>()` or
|
||||
// `function <basename>` at the top level but do not call it. When executed
|
||||
// directly with `bash <script> args...` the function is defined but never
|
||||
// invoked, so no output is produced. Detecting this pattern lets the
|
||||
// dispatcher source the script and call the function explicitly.
|
||||
func bashFunctionName(path, content string) string {
|
||||
base := strings.TrimSuffix(filepath.Base(path), ".sh")
|
||||
// Match "basename()" or "basename (){" at the start of a line.
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
if strings.HasPrefix(trimmed, base+"()") ||
|
||||
strings.HasPrefix(trimmed, base+" ()") {
|
||||
return base
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func buildBashCommand(absPath string, args []string) (*exec.Cmd, error) {
|
||||
dir := filepath.Dir(absPath)
|
||||
|
||||
content := ""
|
||||
if data, err := os.ReadFile(absPath); err == nil {
|
||||
content = string(data)
|
||||
}
|
||||
|
||||
// Case 1: script has the self-executing guard — run directly.
|
||||
// The guard pattern is: if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
// (many scripts also use BASH_SOURCE[0] only for SCRIPT_DIR — we must
|
||||
// match the == "$0" comparison specifically to avoid false positives).
|
||||
hasSelfGuard := strings.Contains(content, `BASH_SOURCE[0]}" == "$0"`) ||
|
||||
strings.Contains(content, `BASH_SOURCE[0]}" = "$0"`)
|
||||
if hasSelfGuard {
|
||||
cmdArgs := append([]string{absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = dir
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Case 2: library-style script — defines a function `<basename>()` at
|
||||
// the top level but never calls it. Source the script and call the
|
||||
// function explicitly so stdout/stderr reach the caller.
|
||||
//
|
||||
// bash -c 'source "$1"; shift; fn_name "$@"' -- <script> [args...]
|
||||
if fnName := bashFunctionName(absPath, content); fnName != "" {
|
||||
inline := fmt.Sprintf(`source "$1"; shift; %s "$@"`, fnName)
|
||||
cmdArgs := append([]string{"-c", inline, "--", absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = dir
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Case 3: top-level pipeline script — executes code directly without a
|
||||
// wrapping function (e.g. propose_capability_groups.sh). Run as-is.
|
||||
cmdArgs := append([]string{absPath}, args...)
|
||||
cmd := exec.Command("bash", cmdArgs...)
|
||||
cmd.Dir = dir
|
||||
cmd.Dir = filepath.Dir(absPath)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBashFunctionName verifies detection of library-style bash scripts
|
||||
// that define a function matching the file basename.
|
||||
func TestBashFunctionName(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
filename string
|
||||
content string
|
||||
wantFn string
|
||||
}{
|
||||
{
|
||||
name: "library-style: defines matching function",
|
||||
filename: "gradle_unit_test.sh",
|
||||
content: `#!/usr/bin/env bash
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/gradle_run.sh"
|
||||
|
||||
gradle_unit_test() {
|
||||
local project_dir="$1"
|
||||
echo "running tests in $project_dir"
|
||||
}
|
||||
`,
|
||||
wantFn: "gradle_unit_test",
|
||||
},
|
||||
{
|
||||
name: "library-style: defines matching function with space before parens",
|
||||
filename: "port_kill.sh",
|
||||
content: `#!/usr/bin/env bash
|
||||
port_kill () {
|
||||
echo "killing port $1"
|
||||
}
|
||||
`,
|
||||
wantFn: "port_kill",
|
||||
},
|
||||
{
|
||||
name: "top-level pipeline: no matching function",
|
||||
filename: "propose_capability_groups.sh",
|
||||
content: `#!/usr/bin/env bash
|
||||
# inline pipeline logic
|
||||
find_root() { echo "root"; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
shift
|
||||
done
|
||||
`,
|
||||
wantFn: "",
|
||||
},
|
||||
{
|
||||
name: "self-executing (has guard): no separate function detection needed",
|
||||
filename: "rsync_deploy.sh",
|
||||
content: `#!/usr/bin/env bash
|
||||
rsync_deploy() {
|
||||
echo "deploying"
|
||||
}
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
rsync_deploy "$@"
|
||||
fi
|
||||
`,
|
||||
// bashFunctionName should still return the function name — the
|
||||
// caller (buildBashCommand) decides which path to take based on
|
||||
// the guard, not this helper.
|
||||
wantFn: "rsync_deploy",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := bashFunctionName("/tmp/"+tc.filename, tc.content)
|
||||
if got != tc.wantFn {
|
||||
t.Errorf("bashFunctionName(%q) = %q, want %q", tc.filename, got, tc.wantFn)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildBashCommand_LibraryStyle verifies that a library-style bash script
|
||||
// (defines a function but has no self-invocation guard) is run by sourcing
|
||||
// the script and calling the function — producing real stdout output.
|
||||
func TestBuildBashCommand_LibraryStyle(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Write a library-style script: defines a function, no guard.
|
||||
scriptPath := filepath.Join(dir, "say_hello.sh")
|
||||
scriptContent := `#!/usr/bin/env bash
|
||||
say_hello() {
|
||||
echo "hello from $1"
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(scriptPath, []byte(scriptContent), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd, err := buildBashCommand(scriptPath, []string{"world"})
|
||||
if err != nil {
|
||||
t.Fatalf("buildBashCommand error: %v", err)
|
||||
}
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output() error: %v", err)
|
||||
}
|
||||
|
||||
got := strings.TrimSpace(string(out))
|
||||
want := "hello from world"
|
||||
if got != want {
|
||||
t.Errorf("output = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildBashCommand_SelfGuard verifies that a script with the BASH_SOURCE
|
||||
// guard is run directly (not via source+call) and still produces output.
|
||||
func TestBuildBashCommand_SelfGuard(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
scriptPath := filepath.Join(dir, "say_hi.sh")
|
||||
scriptContent := `#!/usr/bin/env bash
|
||||
say_hi() {
|
||||
echo "hi from $1"
|
||||
}
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
say_hi "$@"
|
||||
fi
|
||||
`
|
||||
if err := os.WriteFile(scriptPath, []byte(scriptContent), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd, err := buildBashCommand(scriptPath, []string{"guard"})
|
||||
if err != nil {
|
||||
t.Fatalf("buildBashCommand error: %v", err)
|
||||
}
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output() error: %v", err)
|
||||
}
|
||||
|
||||
got := strings.TrimSpace(string(out))
|
||||
want := "hi from guard"
|
||||
if got != want {
|
||||
t.Errorf("output = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildBashCommand_TopLevelPipeline verifies that a pipeline script that
|
||||
// runs top-level code directly (no function, no guard) produces output.
|
||||
func TestBuildBashCommand_TopLevelPipeline(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
scriptPath := filepath.Join(dir, "my_pipeline.sh")
|
||||
scriptContent := `#!/usr/bin/env bash
|
||||
echo "pipeline ran with $# args: $@"
|
||||
`
|
||||
if err := os.WriteFile(scriptPath, []byte(scriptContent), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd, err := buildBashCommand(scriptPath, []string{"a", "b"})
|
||||
if err != nil {
|
||||
t.Fatalf("buildBashCommand error: %v", err)
|
||||
}
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output() error: %v", err)
|
||||
}
|
||||
|
||||
got := strings.TrimSpace(string(out))
|
||||
want := "pipeline ran with 2 args: a b"
|
||||
if got != want {
|
||||
t.Errorf("output = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: 0076
|
||||
title: gradle_run no detecta SDK en $HOME/android-sdk (donde lo deja install_android_sdk)
|
||||
status: pending
|
||||
status: done
|
||||
priority: medium
|
||||
created: 2026-05-10
|
||||
related_functions: [gradle_run_bash_infra, install_android_sdk_bash_infra]
|
||||
|
||||
Reference in New Issue
Block a user