blob: 18ab95cf8d06d4aff442aaabcb080294bc634a2e [file]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sdkpath provides functions for locating the Android SDK.
// These functions respect the ANDROID_HOME environment variable, and
// otherwise use the default SDK location.
package sdkpath
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
)
// AndroidHome returns the absolute path of the selected Android SDK,
// if one can be found.
func AndroidHome() (string, error) {
androidHome := os.Getenv("ANDROID_HOME")
if androidHome == "" {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
switch runtime.GOOS {
case "windows":
// See https://android.googlesource.com/platform/tools/adt/idea/+/85b4bfb7a10ad858a30ffa4003085b54f9424087/native/installer/win/setup_android_studio.nsi#100
androidHome = filepath.Join(home, "AppData", "Local", "Android", "sdk")
case "darwin":
// See https://android.googlesource.com/platform/tools/asuite/+/67e0cd9604379e9663df57f16a318d76423c0aa8/aidegen/lib/ide_util.py#88
androidHome = filepath.Join(home, "Library", "Android", "sdk")
default: // Linux, BSDs, etc.
// See LINUX_ANDROID_SDK_PATH in ide_util.py above.
androidHome = filepath.Join(home, "Android", "Sdk")
}
}
if info, err := os.Stat(androidHome); err != nil {
return "", fmt.Errorf("%w; Android SDK was not found at %s", err, androidHome)
} else if !info.IsDir() {
return "", fmt.Errorf("%s is not a directory", androidHome)
}
return androidHome, nil
}
// AndroidAPIPath returns an android SDK platform directory within the configured SDK.
// If there are multiple platforms that satisfy the minimum version requirement,
// AndroidAPIPath returns the latest one among them.
func AndroidAPIPath(api int) (string, error) {
sdk, err := AndroidHome()
if err != nil {
return "", err
}
sdkDir, err := os.Open(filepath.Join(sdk, "platforms"))
if err != nil {
return "", fmt.Errorf("failed to find android SDK platform: %w", err)
}
defer sdkDir.Close()
fis, err := sdkDir.Readdir(-1)
if err != nil {
return "", fmt.Errorf("failed to find android SDK platform (API level: %d): %w", api, err)
}
var apiPath string
var apiMajor int
var apiMinor int
for _, fi := range fis {
name := fi.Name()
major, minor, ok := parseAndroidPlatformVersion(name)
if !ok || major < api {
continue
}
p := filepath.Join(sdkDir.Name(), name)
_, err = os.Stat(filepath.Join(p, "android.jar"))
if err == nil && (major > apiMajor || major == apiMajor && minor > apiMinor) {
apiPath = p
apiMajor = major
apiMinor = minor
}
}
if apiMajor == 0 {
return "", fmt.Errorf("failed to find android SDK platform (API level: %d) in %s",
api, sdkDir.Name())
}
return apiPath, nil
}
func parseAndroidPlatformVersion(name string) (major int, minor int, ok bool) {
version, ok := strings.CutPrefix(name, "android-")
if !ok {
return 0, 0, false
}
majorString, minorString, hasMinor := strings.Cut(version, ".")
major, err := strconv.Atoi(majorString)
if err != nil {
return 0, 0, false
}
if !hasMinor {
return major, 0, true
}
minor, err = strconv.Atoi(minorString)
if err != nil {
return 0, 0, false
}
return major, minor, true
}