cmd/gomobile: clean up work directory after environment errors

buildEnvInit creates its work directory before calling envInit. If an error occurs afterward, buildEnvInit returns without giving the caller a cleanup function, leaving the directory behind.

Defer cleanupFn and invoke it whenever buildEnvInit returns an error. This covers future error paths and preserves -work behavior because cleanupFn already retains and prints the work directory when requested.

Add a regression test using an NDK whose compiler is missing.

Fixes golang/go#80901

Change-Id: I8557b11364769e95ac37353018b2123f9fc85e74
Reviewed-on: https://go-review.googlesource.com/c/mobile/+/816100
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Hajime Hoshi <hajimehoshi@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Hajime Hoshi <hajimehoshi@gmail.com>
diff --git a/cmd/gomobile/env.go b/cmd/gomobile/env.go
index b05ad04..3562ad5 100644
--- a/cmd/gomobile/env.go
+++ b/cmd/gomobile/env.go
@@ -140,6 +140,11 @@
 		}
 		removeAll(tmpdir)
 	}
+	defer func() {
+		if err != nil {
+			cleanupFn()
+		}
+	}()
 	if buildN {
 		tmpdir = "$WORK"
 		cleanupFn = func() {}
diff --git a/cmd/gomobile/env_test.go b/cmd/gomobile/env_test.go
index ad4c040..e23b9f3 100644
--- a/cmd/gomobile/env_test.go
+++ b/cmd/gomobile/env_test.go
@@ -5,12 +5,60 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
 	"testing"
 )
 
+func TestBuildEnvInitCleanup(t *testing.T) {
+	// Prepare an incomplete NDK without a compiler so that environment
+	// initialization fails after creating the work directory.
+	ndkRoot := t.TempDir()
+	metaDir := filepath.Join(ndkRoot, "meta")
+	if err := os.Mkdir(metaDir, 0755); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.WriteFile(filepath.Join(metaDir, "platforms.json"), []byte(`{"min":16,"max":32}`), 0644); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.WriteFile(filepath.Join(metaDir, "abis.json"), []byte(`{}`), 0644); err != nil {
+		t.Fatal(err)
+	}
+	t.Setenv("ANDROID_NDK_HOME", ndkRoot)
+
+	oldBuildAndroidAPI := buildAndroidAPI
+	oldBuildN := buildN
+	oldBuildWork := buildWork
+	oldTmpdir := tmpdir
+	t.Cleanup(func() {
+		buildAndroidAPI = oldBuildAndroidAPI
+		buildN = oldBuildN
+		buildWork = oldBuildWork
+		tmpdir = oldTmpdir
+	})
+	buildAndroidAPI = minAndroidAPI
+	buildN = false
+	buildWork = false
+
+	cleanup, err := buildEnvInit()
+	workDir := tmpdir
+	t.Cleanup(func() { os.RemoveAll(workDir) })
+	if err == nil {
+		if cleanup != nil {
+			cleanup()
+		}
+		t.Fatal("buildEnvInit succeeded with an NDK missing its compiler")
+	}
+	if cleanup != nil {
+		t.Fatal("buildEnvInit returned a cleanup function with an error")
+	}
+	if _, err := os.Stat(tmpdir); !errors.Is(err, os.ErrNotExist) {
+		t.Errorf("work directory still exists after buildEnvInit failed: %v", err)
+	}
+}
+
 func TestNdkRoot(t *testing.T) {
 	home, err := os.MkdirTemp("", "gomobile-test-")
 	if err != nil {