blob: 442ebaa13a67ce10337e19291bbe165bd356dc52 [file]
#!/bin/bash
set -e
set -m
# This is a wrapper script that runs the specific version of Android Studio that is recommended for developing in this repository.
# (This serves a similar purpose to gradlew)
# Get the property value by a properties file
function getProperyValue() {
getProperyValueAbs ${scriptDir}/$1 $2
}
function getProperyValueAbs() {
# Use --no-messages to suppress error messages about non-existant files
echo "$(grep --no-messages "$2[[:space:]]*=[[:space:]]*" ${1} | sed 's/[^=]*=[[:space:]]*//')"
}
# Get the studio version corresponding to the gradle version defined in gradle.properties
function fetchStudioUrl() {
local cache_file=.studio_version_cache
local studioVersion="$(echo "$(getProperyValue ManagedProvisioningGradleProject/gradle/libs.versions.toml android-studio)" | sed 's/"//g')"
local cachedVersion="$(getProperyValue $cache_file cached_studio_version)"
if [ ! "$studioVersion" == "$cachedVersion" ]; then
local content="$(curl -L https://developer.android.com/studio/archive.html)"
local iframe_url="$(echo $content | egrep -o 'iframe src="[^"]+"' | cut -d '"' -f 2)"
content="$(curl -L $iframe_url)"
if [ "$osName" == "mac" ]; then
content="$(echo $content | egrep -o "Android Studio [^0-9]+$studioVersion.+?section")"
else
content="$(echo $content | grep -Po "Android Studio [^0-9]+$studioVersion.+?section")"
fi
local mac_url
if [ "$arch" == "arm" ]; then
mac_url="$(echo $content | egrep -o '"[^"]+mac_arm.zip"' | cut -d '"' -f 2)"
else
mac_url="$(echo $content | egrep -o '"[^"]+mac.zip"' | cut -d '"' -f 2)"
fi
mac_url="$(echo $mac_url | cut -d " " -f 1)"
local linux_url="$(echo $content | egrep -o '"[^"]+linux[^"]*"' | cut -d '"' -f 2 | cut -d " " -f 1)"
linux_url="$(echo $linux_url | cut -d " " -f 1)"
echo cached_studio_version=$studioVersion > ${scriptDir}/$cache_file
echo mac=$mac_url >> ${scriptDir}/$cache_file
echo linux=$linux_url >> ${scriptDir}/$cache_file
fi
studioUrl="https://dl.google.com/dl/android/studio/$(getProperyValue $cache_file $osName | egrep -o 'ide-zips/.*')"
}
# Escape sequence to print bold colors
RED='\033[1;31m'
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
# Escape sequence to clear formatting
NC='\033[0m'
printError() {
local logMessage="$1"
echo -e "${RED}ERROR:${NC} ${logMessage}"
}
printWarning() {
local logMessage="$1"
echo -e "${YELLOW}WARNING:${NC} ${logMessage}"
}
printInfo() {
local logMessage="$1"
echo -e "${GREEN}INFO:${NC} ${logMessage}"
}
acceptsLicenseAgreement="false"
runStudio="true"
downloadStudioZip="true"
cleanProjectFiles="false"
scriptDir="$(cd $(dirname $0) && pwd)"
projectDir=$scriptDir/ManagedProvisioningGradleProject
androidBuildTop="$(cd "${scriptDir}/../../../../"; pwd)"
gradleOutDir="${androidBuildTop}/out/gradle"
usingDefaultSettingsDir='true'
androidStudioSettingsDir="${gradleOutDir}/AndroidStudio"
# Where to put the generated idea.properties file relative to $androidStudioSettingsDir, used for
# STUDIO_PROPERTIES
ideaPropRelPath="bin/idea.properties"
# TODO(b/249826650): Maybe we shouldn't write to ~/.AndroidStudio* named directories. Android Studio
# searches for these directories and tries to use them when opening for the first time elsewhere.
# See:
# - Implementation details: http://shortn/_Q0gp64FPj3
# - Screenshot: http://screen/4sCQBhNyVTMPPf3.png
studioHomeDir="${HOME}/.AndroidStudioSystemUI"
studioSetupDir="${studioHomeDir}/bin"
function getOsName() {
local unameOutput="$(uname)"
local osName=""
if [ "${unameOutput}" == "Linux" ]; then
osName="linux"
else
osName="mac"
fi
echo "${osName}"
}
osName="$(getOsName)"
arch="$(uname -p)"
studioUrl=
# If empty string, don't update the SDK. Otherwise, the string indicates the method used for
# fetching the artifacts needed to generate the SDK. Currently, either 'adb' or 'soong'
updateSdk=''
copySdkSourceDir=''
function setupBuildSrcSymlinks() {
# Builtbots can't write to the source dirs, and there is no gradle option to overwrite the .gradle
# location of buildSrc, so we use symlinks. The dirs must be created before running the build.
#
# Alternatively, we could migrate from buildSrc/ to a composite build to avoid needing the
# symlinks. See: http://go/gh/gradle/gradle/issues/2472#issuecomment-315376378
cd "${scriptDir}/ManagedProvisioningGradleProject/buildSrc"
mkdir -p $(readlink .gradle)
mkdir -p $(readlink build)
cd - > /dev/null
}
# Used to keep track of whether we confirmed adb root works. We only want to check once.
adbChecked='false'
function assertHasAdbRoot() {
if [[ "${adbChecked}" == 'false' && "${updateSdk}" == 'adb' ]]; then
adb root || {
printError 'adb root failed. You must have a rooted device attached to use "--update-sdk adb".'
if [ "${osName}" != "mac" ]; then
echo 'NOTE: On Linux, you can use "--update-sdk soong" to compile the SDK from source.'
fi
exit 1
}
adb wait-for-device
adbChecked='true'
fi
}
function getUpstreamGitBranch() {
echo "$(git -C "${androidBuildTop}/.repo/manifests" for-each-ref --format='%(upstream:short)' refs/heads | sed 's/origin\///')"
}
function toUpperCase() {
local dashedName="$1"
IFS='-'
local words=($dashedName)
unset IFS
local upperCaseName=''
for word in "${words[@]}"; do
upperCaseName="${upperCaseName}$(echo "$word" | sed 's/[a-z]/\U&/')"
done
echo "$upperCaseName"
}
# Find the name of the branch (e.g. udc-dev) and change it to camel-case (e.g. UdcDev)
# (e.g. udc-dev)
branchName="$(getUpstreamGitBranch)"
# (e.g. UdcDev)
upperCaseBranchName="$(toUpperCase "$branchName")"
sdkVersionInt="$(getProperyValue ManagedProvisioningGradleProject/gradle.properties TARGET_SDK)"
# Use the first 4 chars of the SHA of $ANDROID_BUILD_TOP as a unique ID for this Android tree.
# We will use this to associate this tree with the generated SDK so that all platform SDK can share
# the same SDK home. This is just for preventing collisions if someone has the same branch checked
# out twice. Otherwise, we'd just use $upperCaseBranchName
androidTreeUniqueId="$(echo "$androidBuildTop" | shasum -a 256 | head -c 4)"
# ---- ---- ---- ----
# The following variables are to correspond with the Android Gradle DSL property of the same name:
# Public docs: http://go/android-reference/tools/gradle-api/8.0/com/android/build/api/dsl/SettingsExtension#compileSdkPreview()
# Parsing code: http://go/aocs/android/platform/superproject/+/studio-main:tools/base/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dsl/CommonExtensionImpl.kt
compileSdkPreview="${upperCaseBranchName}ForTree${androidTreeUniqueId}"
compileSdkVersion="android-${compileSdkPreview}"
# ---- ---- ---- ----
prebuiltsDir="${androidBuildTop}/prebuilts"
function getLegacySdkDir() {
if [ "${osName}" == "mac" ]; then
echo "${prebuiltsDir}/fullsdk-darwin"
else
echo "${prebuiltsDir}/fullsdk-linux"
fi
}
function getLegacySdkDir2() {
if [ "${osName}" == "mac" ]; then
echo "${studioHomeDir}/fullsdk-darwin"
else
echo "${studioHomeDir}/fullsdk-linux"
fi
}
function getSdkDir() {
echo "${gradleOutDir}/MockSdk"
}
sdkDir="$(getSdkDir)"
platformDir="$sdkDir/platforms/$compileSdkVersion"
function printHelpAndExit() {
local message=(
'Usage: studiow [OPTION...]'
'\n'
'\n -y, --accept-license-agreement'
'\n --update-only'
'\n --no-download'
'\n --update-sdk [ARTIFACT SOURCE (adb, soong, or copy)]'
'\n --project-dir'
'\n --settings-dir'
'\n --clean'
'\n --uninstall'
)
local fmt_cmd=(fmt -w 100 --split-only)
if [ "$osName" == "mac" ]; then
fmt_cmd=(fmt -w 100)
fi
printf "$(printf %s "${message[@]}")" | ${fmt_cmd[@]}
exit 1
}
function parseOptions() {
while :; do
case "$1" in
-y|--accept-license-agreement)
acceptsLicenseAgreement="true"
;;
--update-only)
runStudio="false"
;;
--no-download)
downloadStudioZip="false"
runStudio="false"
;;
--update-sdk)
if [[ -n "$2" && "$2" != -* ]]; then
updateSdk="$2"
shift
fi
case "$updateSdk" in
adb)
;;
soong)
if [[ "${osName}" == "mac" ]]; then
printError 'MacOS does not support soong builds'
exit 1
elif [[ ! -f "$androidBuildTop/build/soong/soong_ui.bash" ]]; then
printError 'You must have a full platform branch to compile the SDK. Minimal checkouts (e.g. *-sysui-studio-dev) do not support soong builds.'
exit 1
fi
;;
copy)
if [[ -n "$2" && "$2" != -* ]]; then
copySdkSourceDir="$2"
shift
else
printError 'You must specify a directoy to copy the SDK from'
echo ''
echo 'Usage: ./studiow --update-sdk copy [DIR]'
echo ''
echo 'Directory can be local or remote in the form of [user@]host:[path].'
echo ''
echo 'For example, to copy the SDK from a remote host, run the following:'
echo ''
echo ' ./studiow --update-sdk copy example.corp.google.com:/path/to/out/gradle/MockSdk/platforms/android-31'
echo ''
exit 1
fi
;;
*)
if [[ -z "${updateSdk}" ]]; then
printError 'You must specify artifact source when using --update-sdk.'
else
printError "Unknown SDK artifact source: $updateSdk"
fi
echo ''
echo 'Usage: ./studiow --update-sdk [ARTIFACT SOURCE]'
echo ''
echo 'Available options are:'
echo ' adb: Pull the artifacts from attached device (default)'
echo ' soong: Build the artifacts using soong (recommended)'
echo ' copy: Copy android.jar and framework.aidl from the given directory'
echo ''
echo 'NOTE: soong option can only be used on Linux with a full checkout'
exit 1
;;
esac
;;
--pull-sdk)
printWarning "--pull-sdk is deprecated. It is equivalent to '--update-sdk adb'"
updateSdk='adb'
;;
--project-dir)
shift
projectDir="$1"
;;
--settings-dir)
shift
usingDefaultSettingsDir='false'
if [[ -z "$1" ]] ; then
echo "--settings-dir expects a directory. Usage: --settings-dir [dir name]"
exit 1
elif [[ ! -d "$1" ]] ; then
echo "Invalid --settings-dir: $1 does not exist or is not a directory"
exit 1
fi
androidStudioSettingsDir="$(cd "$1"; pwd)"
;;
--clean)
cleanProjectFiles="true"
;;
--uninstall)
uninstallAndroidStudio="true"
;;
-h|--help|-?)
printHelpAndExit
;;
*)
if [ -z "$1" ]; then
# If $1 is an empty string, it means we reached the end of the passed arguments
break
else
echo "Unknown option: $1"
exit
fi
esac
shift
done
}
# $1 - string to print
# $2 - default, either 'y' or 'n'
function yesNo() {
local question="$1"
local defaultResponse="${2:-y}"
local yesNoPrompt=''
if [[ "${defaultResponse::1}" =~ [yY] ]]; then
yesNoPrompt='[Y/n]'
else
yesNoPrompt='[y/N]'
fi
read -r -n 1 -p "$question ${yesNoPrompt}? " -s reply
if [ -z "${reply}" ]; then
# Replace the empty string with the default response
reply="${defaultResponse::1}"
fi
# Print the response so there is no confusion
echo "${reply::1}"
case "${reply::1}" in
[yY])
true
;;
*)
false
;;
esac
}
function downloadFile() {
fromUrl="$1"
destPath="$2"
tempPath="${destPath}.tmp"
if [ -f "${destPath}" ]; then
if yesNo "File already exists. Do you want to delete and re-download?"; then
rm "${destPath}"
fi
fi
if [ -f "${destPath}" ]; then
echo "Using existing file from ${destPath}"
else
echo "Downloading ${fromUrl} to ${destPath}"
curl "${fromUrl}" > "${tempPath}"
mv "${tempPath}" "${destPath}"
fi
}
function findStudioMacAppPath() {
echo "$(find "${studioUnzippedPath}" -type d -depth 1 -name "Android Studio*.app")"
}
function getLicensePath() {
if [ "${osName}" == "mac" ]; then
appPath="$(findStudioMacAppPath)"
echo "${appPath}/Contents/Resources/LICENSE.txt"
else
echo "${studioUnzippedPath}/android-studio/LICENSE.txt"
fi
}
function checkLicenseAgreement() {
# TODO: Is there a more official way to check that the user accepts the license?
licenseAcceptedPath="${studioUnzippedPath}/STUDIOW_LICENSE_ACCEPTED"
if [ ! -f "${licenseAcceptedPath}" ]; then
if [ "${acceptsLicenseAgreement}" == "true" ]; then
touch "${licenseAcceptedPath}"
else
if yesNo "Do you accept the license agreement at $(getLicensePath)"; then
touch "${licenseAcceptedPath}"
else
exit 1
fi
fi
fi
}
# Inserts a snippet into the studio.sh launch script that prevents it from running if
# STUDIO_LAUNCHED_WITH_WRAPPER is not set. This is to prevent people from launching studio.sh
# directly, which can result in build errors. This only works on Linux. Unfortunately, there is no
# equivalent for Mac OS.
#
# Inputs:
# osName - Either mac or linux
# studioPath - Path to the studio.sh script on Linux
function updateStudioLaunchScript() {
# Only Linux uses the studio.sh script
if [ "${osName}" == "mac" ]; then
return
fi
# If studio.sh already contains 'STUDIO_LAUNCHED_WITH_WRAPPER', don't do anything
grep -qF 'STUDIO_LAUNCHED_WITH_WRAPPER' "$studioPath" && return
local tmpStudioSh="$(mktemp)"
# Find the first line OS_TYPE, and use this as our insertion point.
local insertionPoint="$(grep --line-number OS_TYPE "$studioPath" | head -n 1 | cut -d ':' -f 1)"
# Dump everything from line 0 through $insertionPoint-1 into the tmp file
head -n "$(("$insertionPoint"-1))" "$studioPath" > "$tmpStudioSh"
# Insert a conditional to prevent launching studiow when STUDIO_LAUNCHED_WITH_WRAPPER is unset
( cat <<'EOF'
if [ -z "$STUDIO_LAUNCHED_WITH_WRAPPER" ]; then
message 'This installation of Android Studio must be launched using the studiow script found in $ANDROID_BUILD_TOP/packages/apps/ManagedProvisioning/studio-dev/studiow.'
exit 1
fi
EOF
) >> "$tmpStudioSh"
# Dump everything from $insertionPoint until the end studio.sh into the tmp file
tail -n +"$insertionPoint" "$studioPath" >> "$tmpStudioSh"
# Ensure that the tmp file has the same permissions as the original studio.sh
chmod --reference="$studioPath" "$tmpStudioSh"
echo "Inserting the following snippet into studio.sh to prevent launching it outside of studiow:"
diff "$studioPath" "$tmpStudioSh" || :
mv "$tmpStudioSh" "$studioPath"
}
# Creates an idea.properties file where all the configs are specific to this checkout of the Android
# tree and not shared between different branches.
#
# Inputs:
# scriptDir - The path to this script (studiow)
# androidStudioSettingsDir - The common dir for Android Studio settings per checkout
# studioPropertiesFile - Where to put the generated idea.properties file, used for STUDIO_PROPERTIES
function updateIdeaProperties() {
local ideaPropRefFile="${scriptDir}/development/studio/idea.properties"
mkdir -p "$(dirname "$studioPropertiesFile")"
echo "SYSUI_STUDIO_SETTINGS_DIR=$androidStudioSettingsDir" > "$studioPropertiesFile"
chmod 640 "$studioPropertiesFile"
cat "${ideaPropRefFile}" >> "$studioPropertiesFile"
}
# Creates the DO_NOT_RUN_ANDROID_STUDIO_FROM_HERE file
#
# Inputs:
# studioSetupDir - Path to Android Studio bin dir. Assumes the dir already exists.
function createWarningTextFile() {
( cat <<'EOF'
The installations of Android Studio found in this directory MUST be launched using the studiow
script found in:
$ANDROID_BUILD_TOP/packages/apps/ManagedProvisioning/studio-dev/studiow
Do NOT launch Android Studio by running studio.sh (on Linux) or by opening Android Studio.app (on
MacOS). Otherwise, you won't be able to work on multiple branches of Android simultaneously.
EOF
) > "${studioSetupDir}/DO_NOT_RUN_ANDROID_STUDIO_FROM_HERE"
}
function updateStudio() {
# skip if already up-to-date
if stat "${studioUnzippedPath}" >/dev/null 2>/dev/null; then
# already up-to-date
createWarningTextFile
return
fi
mkdir -p "${studioSetupDir}"
downloadFile "${studioUrl}" "${studioZipPath}"
echo
echo "Unzipping"
if [[ $studioZipPath = *.zip ]]; then
unzip "${studioZipPath}" -d "${studioUnzippedPath}"
elif [[ $studioZipPath = *.tar.gz ]]; then
mkdir -p $studioUnzippedPath
tar -xf $studioZipPath -C $studioUnzippedPath
fi
createWarningTextFile
}
# ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED environment variable prevents Studio from showing IDE
# inspection warnings for nullability issues, if the context is deprecated
# This environment variable is consumed by InteroperabilityDetector.kt
function runStudioLinux() {
studioPath="${studioUnzippedPath}/android-studio/bin/studio.sh"
updateStudioLaunchScript
echo "$studioPath &"
env LAUNCHED_VIA_STUDIOW='true' \
STUDIO_PROPERTIES="${studioPropertiesFile}" \
STUDIO_VM_OPTIONS="${scriptDir}/development/studio/studio.vmoptions" \
ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED="true" \
"${studioPath}" "${projectDir}" &>/dev/null &
}
function runStudioMac() {
appPath="$(findStudioMacAppPath)"
echo "open ${appPath}"
env STUDIO_PROPERTIES="${studioPropertiesFile}" \
STUDIO_VM_OPTIONS="${scriptDir}/development/studio/studio.vmoptions" \
ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED="true" \
open -a "${appPath}" "${projectDir}"
}
function runStudio() {
local studioPropertiesFile="${androidStudioSettingsDir}/${ideaPropRelPath}"
updateIdeaProperties
# Export an env var so the gradle script can check that Android Studio was launched using this
# script. Launching Android Studio and then opening the Gradle project after-the-fact IS
# UNSUPPORTED. Android Studio needs to be lunched using studiow so that it can validate settings
# and update the build environment.
export STUDIO_LAUNCHED_WITH_WRAPPER='true'
if [ "${osName}" == "mac" ]; then
runStudioMac
else
runStudioLinux
fi
}
function runCleanProjectFiles() {
local projects=(
//external/setupcompat
//external/setupdesign
//frameworks/base
//frameworks/libs/systemui
//packages/apps/Launcher3
//platform_testing
//vendor/unbundled_google/libraries
//vendor/unbundled_google/packages/NexusLauncher
//packages/apps/ManagedProvisioning
)
local gitPath
for gitPath in "${projects[@]}"; do
cleanProjectFiles "$gitPath"
done
removeDirIfExists "${gradleOutDir}/build" || :
}
function cleanProjectFiles() {
local projectPath="$1"
local gitPath="${androidBuildTop}/${gitPath:1}"
local cleanPreview="$(git -C "$gitPath" clean --dry-run --force -X -d .)"
if [[ -z "$cleanPreview" ]]; then
echo "$projectPath already clean. Nothing to do."
else
echo "$projectPath cleaning:"
echo "$cleanPreview"
if yesNo 'Do you want to delete these files?' 'n'; then
git -C "$gitPath" clean --force -X -d .
else
echo "Clean operation cancelled."
fi
fi
}
function removeDirIfExists() {
local dir="$1"
if [[ -z "${dir}" ]] ; then
echo 'script error: removeDirIfExists expects 1 arg'
exit 1
fi
if [[ -d "${dir}" ]] ; then
if yesNo "Remove ${dir}?" 'n'; then
rm -rf "${dir}"
fi
return 0
fi
return 1
}
function runUninstallAndroidStudio() {
if ! yesNo 'This will remove the local Android Studio installation, local SDK, and project gradle files. Proceed?'; then
echo "Uninstall operation cancelled."
return
fi
removeDirIfExists "$studioHomeDir" || echo "Android Studio installation not found."
removeDirIfExists "$(getLegacySdkDir)" || :
removeDirIfExists "$(getLegacySdkDir2)" || :
removeDirIfExists "${gradleOutDir}" || :
runCleanProjectFiles
}
function adbGetProp() {
local prop="$1"
echo "$(adb shell getprop $prop 2>/dev/null || true)"
}
function askToUpdateSdkUsingAdb() {
echo ''
if yesNo "Update SDK using adb?"; then
updateSdk="adb"
fi
}
function checkSdkNeedsUpdate() {
printInfo "Android SDK Location: $sdkDir"
local localSdkTime="$(getProperyValueAbs $platformDir/build.prop ro.system.build.date.utc)"
local localSdkFingerprint="$(getProperyValueAbs $platformDir/build.prop ro.system.build.fingerprint)"
local utcTimeOneWeekAgo="$(expr $(date +%s) - 604800)"
if [[ -z "$localSdkFingerprint" ]]; then
localSdkFingerprint='<Unknown>'
fi
local sdkGenSrc="$(getProperyValueAbs $platformDir/build.prop ro.sdk.gensrc)"
sdkGenSrc="${sdkGenSrc:-adb}"
printInfo "Android SDK Fingerprint: $localSdkFingerprint"
printInfo "Android SDK was last updated using \"--update-sdk ${sdkGenSrc}\""
if [[ -z "$localSdkTime" ]]; then
printWarning 'Could not determine age of Android SDK. This means your SDK is corrupt or out of date.'
askToUpdateSdkUsingAdb
elif [[ "$utcTimeOneWeekAgo" -gt "$localSdkTime" ]]; then
printWarning 'Android SDK is more than 7 days old.'
askToUpdateSdkUsingAdb
else
local serial="$(adb get-serialno 2> /dev/null)"
if [[ -z "$serial" ]]; then
local noDeviceMessage='Skipping adb build check. No devices/emulators found.'
if [[ "$sdkGenSrc" == 'adb' ]]; then
printWarning "$noDeviceMessage"
else
printInfo "$noDeviceMessage"
fi
else
local adbSdkBuildTime="$(adbGetProp ro.system.build.date.utc)"
if [ "${updateSdk}" != "true" ]; then
if [ $adbSdkBuildTime -gt $localSdkTime ]; then
printWarning "Attached device has newer SDK."
askToUpdateSdkUsingAdb
fi
fi
fi
fi
}
function createPackageXml() {
local targetFile="$1"
local localPackage="$2"
local buildToolsVersion="$3"
local typeDetails="$4"
local displayName="$5"
# Split X.Y.Z-rcN into an array of X, Y, Z, and rcN
IFS='.-'
local versionNumbers=($buildToolsVersion)
unset IFS
local majorVersionString=""
local minorVersionString=""
local microVersionString=""
local previewVersionString=""
if [[ -n "${versionNumbers[0]}" ]]; then
majorVersionString="<major>${versionNumbers[0]}</major>"
fi
if [[ -n "${versionNumbers[1]}" ]]; then
minorVersionString="<minor>${versionNumbers[1]}</minor>"
fi
if [[ -n "${versionNumbers[2]}" ]]; then
microVersionString="<micro>${versionNumbers[2]}</micro>"
fi
# preview version includes rc if it's set, e.g. rc1. It could also be an empty string if it's not
# a preview version at all
if [[ -n "${versionNumbers[3]}" ]]; then
# Remove rc from the preview version number
previewVersionString="<preview>${versionNumbers[3]#rc}</preview>"
fi
( cat <<'EOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:repository xmlns:ns2="http://schemas.android.com/repository/android/common/02" xmlns:ns3="http://schemas.android.com/repository/android/common/01" xmlns:ns4="http://schemas.android.com/repository/android/generic/01" xmlns:ns5="http://schemas.android.com/repository/android/generic/02" xmlns:ns6="http://schemas.android.com/sdk/android/repo/addon2/01" xmlns:ns7="http://schemas.android.com/sdk/android/repo/addon2/02" xmlns:ns8="http://schemas.android.com/sdk/android/repo/addon2/03" xmlns:ns9="http://schemas.android.com/sdk/android/repo/repository2/01" xmlns:ns10="http://schemas.android.com/sdk/android/repo/repository2/02" xmlns:ns11="http://schemas.android.com/sdk/android/repo/repository2/03" xmlns:ns12="http://schemas.android.com/sdk/android/repo/sys-img2/03" xmlns:ns13="http://schemas.android.com/sdk/android/repo/sys-img2/02" xmlns:ns14="http://schemas.android.com/sdk/android/repo/sys-img2/01"><license id="android-sdk-license" type="text">Terms and Conditions
This is the Android Software Development Kit License Agreement
</license>
EOF
) > "$targetFile"
echo "<localPackage path=\"$localPackage\" obsolete=\"false\">
<type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ns11:platformDetailsType\">$typeDetails</type-details>
<revision>
$majorVersionString
$minorVersionString
$microVersionString
$previewVersionString
</revision>
<display-name>$displayName</display-name>
<uses-license ref=\"android-sdk-license\"/>
</localPackage>
</ns2:repository>
" >> $targetFile
}
function appendLineIfNotExists() {
grep -qxF "$1" $2 || echo "$1" >> $2
}
function setupMockSdk() {
rm -rf $sdkDir
mkdir -p $sdkDir
local toolsName
if [ "${osName}" == "mac" ]; then
toolsName="darwin"
else
toolsName="linux"
fi
# e.g. 33.0.1 OR 34.0.0-rc1
# In Gradle, there's a space before 'rc', but when packaged in Android/Sdk, there's a dash.
# To work with that, replace ' ' with '-' for usage in the script
local buildToolsVersion="$(getProperyValue ManagedProvisioningGradleProject/gradle.properties BUILD_TOOLS_VERSION | sed 's/ /-/')"
local adbRelPath="out/host/$toolsName-x86/bin/adb"
local compiledAdb="$androidBuildTop/$adbRelPath"
# platform tools
if [ ! -f $compiledAdb ]; then
# Check if adb exists in path (don't exit if `which` fails)
compiledAdb="$(which adb || true)"
if [ ! -f $compiledAdb ]; then
printError "Could not find adb at $adbRelPath or in the environment path"
echo "Did you build system image once?"
exit 1
fi
fi
printInfo "Using adb binary found at $compiledAdb"
mkdir $sdkDir/platform-tools
ln -s $compiledAdb $sdkDir/platform-tools/adb
createPackageXml $sdkDir/platform-tools/package.xml "platform-tools" "$buildToolsVersion" "" "Android SDK Platform-Tools"
# Setup build tools
mkdir -p $sdkDir/build-tools
cp -r $prebuiltsDir/sdk/tools/$toolsName/bin $sdkDir/build-tools/$buildToolsVersion
cp -r $prebuiltsDir/sdk/tools/$toolsName/lib $sdkDir/build-tools/$buildToolsVersion/lib
cp -r $prebuiltsDir/sdk/tools/$toolsName/lib64 $sdkDir/build-tools/$buildToolsVersion/lib64
ln -s $prebuiltsDir/sdk/renderscript $sdkDir/build-tools/$buildToolsVersion/renderscript
# All tools are now validated by studio, so we need to collect them all
cp -r $prebuiltsDir/sdk/tools/lib/* $sdkDir/build-tools/$buildToolsVersion/lib
cp $prebuiltsDir/sdk/tools/dx $sdkDir/build-tools/$buildToolsVersion
cp -r $prebuiltsDir/build-tools/$toolsName-x86/bin/* $sdkDir/build-tools/$buildToolsVersion/
cp -r $prebuiltsDir/build-tools/$toolsName-x86/lib64/* $sdkDir/build-tools/$buildToolsVersion/lib64/
createPackageXml $sdkDir/build-tools/$buildToolsVersion/package.xml "build-tools;$buildToolsVersion" "$buildToolsVersion" "" "Android SDK Build-Tools $buildToolsVersion"
# Setup platforms
mkdir -p $platformDir
createPackageXml $platformDir/package.xml "platforms;$compileSdkVersion" "1" "<api-level>$sdkVersionInt</api-level><codename>$compileSdkPreview</codename><layoutlib api=\"15\" />" "Android SDK Platform $upperCaseBranchName for SysUiStudio (tree=$androidBuildTop, branch=$branchName, treeId=$androidTreeUniqueId)"
prebuiltAndroidJar=$prebuiltsDir/sdk/current/system/android.jar
# Setup test and optional packages
mkdir $platformDir/optional
cp -r $prebuiltsDir/sdk/current/test/*.*.jar $platformDir/optional/
list=
for f in $platformDir/optional/*.jar; do
filename=$(basename -- "$f")
libname="${filename%.*}"
[ ! -z "$list" ] && list="$list,"
br=$'\n'
list="$list$br{ \"name\": \"$libname\", \"jar\": \"$filename\", \"manifest\": false }"
done
echo "[$list]" > $platformDir/optional/optional.json
}
function createPlatformSdk() {
assertHasAdbRoot
echo "Setting up SDK from scratch"
setupMockSdk
echo "Updating private apis sdk"
local android_jar_outfile="$platformDir/android.jar"
local framework_aidl="$platformDir/framework.aidl"
rm -rf $android_jar_outfile
local buildPropFile="$platformDir/build.prop"
local buildDate=
local buildFingerprint=
if [[ "$updateSdk" == 'adb' ]]; then
local tempFiles="$(mktemp -d)"
buildDate="$(adbGetProp ro.system.build.date.utc)"
buildFingerprint="$(adbGetProp ro.system.build.fingerprint)"
printInfo "Pulling SDK artifacts from device $buildFingerprint using adb"
adb pull /system/framework/framework-res.apk $tempFiles/
adb pull /system/framework/framework.jar $tempFiles/
adb pull /system/framework/framework-location.jar $tempFiles/
local dexList="--dex $tempFiles/framework.jar"
dexList="$dexList --dex $tempFiles/framework-location.jar"
local apexJars=($(adb shell ls /apex/*/javalib/*.jar))
for f in "${apexJars[@]}"
do
local fileBasename="$(basename $f)"
if [[ ! $f = *@* ]] && [[ "$fileBasename" != service-* ]]; then
local target="$tempFiles/$(basename $f)"
adb pull $f $target
dexList="$dexList --dex $target"
fi
done
cp $prebuiltsDir/sdk/current/public/framework.aidl $framework_aidl
java -jar $scriptDir/StubGenerator/StubGenerator.jar -o $android_jar_outfile \
$dexList \
--zip $tempFiles/framework-res.apk \
--zip $prebuiltAndroidJar \
--aidl $framework_aidl
echo "Removing temp files"
rm -rf $tempFiles
cp $prebuiltsDir/sdk/current/module-lib/core-for-system-modules.jar "$platformDir"
elif [[ "$updateSdk" == 'soong' ]]; then
printInfo "Building SDK artifacts using soong"
# TODO(b/251871740): Replace these steps with the output of a soong target
#
# ---- begin ----
#
local frameworks_deps=(
out/soong/.intermediates/build/soong/java/core-libraries/legacy.core.platform.api.stubs/android_common/turbine-combined/legacy.core.platform.api.stubs.jar
out/soong/.intermediates/libcore/core-lambda-stubs-for-system-modules/android_common/turbine-combined/core-lambda-stubs-for-system-modules.jar
out/soong/.intermediates/libcore/core-generated-annotation-stubs/android_common/turbine-combined/core-generated-annotation-stubs.jar
out/soong/.intermediates/frameworks/base/framework/android_common/turbine-combined/framework.jar
out/soong/.intermediates/frameworks/base/ext/android_common/turbine-combined/ext.jar
out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/package-res.apk
out/soong/.intermediates/tools/metalava/private-stub-annotations-jar/android_common/turbine-combined/private-stub-annotations-jar.jar
)
cd $androidBuildTop
. ./build/envsetup.sh
if [[ -n "$TARGET_PRODUCT" && -n "$TARGET_BUILD_VARIANT" ]]; then
if [[ -n "$TARGET_RELEASE" ]]; then
lunch "$TARGET_PRODUCT-$TARGET_RELEASE-$TARGET_BUILD_VARIANT"
else
lunch "$TARGET_PRODUCT-$TARGET_BUILD_VARIANT"
fi
else
lunch aosp_arm64-trunk_staging-userdebug
fi
m merge_zips sdkparcelables ${frameworks_deps[@]}
echo "Updating private apis sdk"
merge_zips --ignore-duplicates $android_jar_outfile ${frameworks_deps[@]}
sdkparcelables "$android_jar_outfile" "$framework_aidl"
# TODO(b/259594098): sdkparcelables output aidl file doesn't include all the interfaces we need
appendLineIfNotExists "interface android.app.IApplicationThread;" "$framework_aidl"
appendLineIfNotExists "interface android.view.IRecentsAnimationController;" "$framework_aidl"
appendLineIfNotExists "interface android.view.IRecentsAnimationRunner;" "$framework_aidl"
appendLineIfNotExists "interface android.view.IRemoteAnimationRunner;" "$framework_aidl"
appendLineIfNotExists "interface android.window.IOnBackInvokedCallback;" "$framework_aidl"
cp $prebuiltsDir/sdk/current/module-lib/core-for-system-modules.jar "$platformDir"
#
# ---- end ----
#
buildDate="$(date +%s)"
buildFingerprint="$(cat $ANDROID_PRODUCT_OUT/build_fingerprint.txt)"
printInfo "If you'd like to use this SDK on your laptop too, run ./studiow --update-sdk copy $(hostname):$platformDir"
elif [[ "${updateSdk}" == 'copy' ]]; then
scp "${copySdkSourceDir}"/{framework.aidl,android.jar,build.prop,core-for-system-modules.jar} "$platformDir/."
# Copy the build date and fingerprint from build.prop, then delete the file
buildDate="$(getProperyValueAbs $buildPropFile ro.system.build.date.utc)"
buildFingerprint="$(getProperyValueAbs $buildPropFile ro.system.build.fingerprint)"
rm "$buildPropFile"
else
printError "Internal error. Unknown SDK update source: $updateSdk"
exit 1
fi
{
echo "ro.system.build.version.sdk=$sdkVersionInt"
echo "ro.build.version.codename=$compileSdkPreview"
echo "ro.system.build.date.utc=$buildDate"
echo "ro.system.build.fingerprint=$buildFingerprint"
echo "ro.sdk.gensrc=$updateSdk"
} > "$buildPropFile"
cp "${scriptDir}/development/sdk/sdk.properties" "${platformDir}"
( cat <<EOF
Pkg.Desc=Android SDK Platform $upperCaseBranchName
Pkg.UserSrc=false
Platform.Version=$compileSdkPreview
Platform.CodeName=$compileSdkPreview
Pkg.Revision=1
AndroidVersion.ApiLevel=$sdkVersionInt
AndroidVersion.CodeName=$compileSdkPreview
AndroidVersion.ExtensionLevel=5
AndroidVersion.IsBaseSdk=true
Layoutlib.Api=15
Layoutlib.Revision=1
Platform.MinToolsRev=22
EOF
) > "${platformDir}/source.properties"
echo "Generating sdk data"
# Sympolic link does not work here with android
mkdir -p $platformDir/data
cp -r $androidBuildTop/frameworks/base/core/res/res $platformDir/data/
mv $platformDir/data/res/values/public-final.xml $platformDir/data/res/values/public.xml
echo "Removing build cache"
rm -rf "${scriptDir}/ManagedProvisioningGradleProject/.build-cache"
echo "Accepting license"
cp -r ${prebuiltsDir}/cmdline-tools $sdkDir/
yes | $sdkDir/cmdline-tools/tools/bin/sdkmanager --licenses >/dev/null
rm -rf $sdkDir/cmdline-tools
echo "Linking sources"
ln -s $androidBuildTop/frameworks/base/core/java $platformDir/sources
echo "Done"
}
function copyFileIfAbsent() {
SRC=$1
DEST=$2
if [ ! -f $DEST ]; then
mkdir -p $(dirname $DEST)
cp $SRC $DEST
fi
}
# On mac, a newer JDK must be installed manually.
function checkJdkVersion() {
if [ "${osName}" == "mac" ]; then
local javaHome
javaHome="$(/usr/libexec/java_home -v 17)"
if [[ "$?" -eq 0 && -n "$javaHome" ]]; then
return 0
else
printError "Compatible JDK not found. studiow requires JDK 17 to run Android's command-line tools for generating the SDK. Install the JDK using 'mule install jdk19' and re-run this script."
exit
fi
fi
}
function updateLocalGradleProperties() {
export ANDROID_HOME="${sdkDir}"
( cat <<EOF
sdk.dir=${sdkDir}
EOF
) > "$projectDir/local.properties"
( cat <<EOF
compile.sdk.preview=${compileSdkPreview}
compile.sdk.version=${compileSdkVersion}
EOF
) > "$projectDir/studiow.properties"
}
function updateStudioConfig() {
local legacy_config_dir="${studioHomeDir}/config"
local config_dir="${androidStudioSettingsDir}/config"
if [[ "${usingDefaultSettingsDir}" == 'true' && -d "$legacy_config_dir" ]]; then
local message=(
'\n-------------------------------------------------------------------------------------\n\n'
"${YELLOW}WARNING:${NC} You have config files stored in ~/.AndroidStudioSystemUI/config "
'which will NOT be used by this instance of sysui-studio. '
'To support multi-branch development, '
"the config directory has moved to \$TOP${config_dir#"$androidBuildTop"}"
'\n\n'
'Each tree now has its own config directory. This means you may need to change your '
'settings for Android Studio multiple times (per branch) if, for example, you want to '
'change the default keymap.'
'\n\n'
'Your existing configs have NOT been automatically moved or deleted for the following '
'reasons:'
'\n\n'
' 1) You may want to inspect your old settings\n'
' 2) The old config may still be used by branches on revisions predate this change.'
'\n\n'
'For more info, see http://b/249826650'
'\n\n'
'To make this warning go away, delete ~/.AndroidStudioSystemUI/config'
'\n\n'
'To launch Android Studio with the old config, run the following:'
'\n'
'\n studiow --settings-dir ~/.AndroidStudioSystemUI'
'\n'
'\nYou can then export your old settings using File > Manage IDE Settings > Export Settings'
'\n'
'\n-------------------------------------------------------------------------------------\n\n'
)
local fmt_cmd=(fmt -w 100 --split-only)
if [ "$osName" == "mac" ]; then
fmt_cmd=(fmt -w 100)
fi
printf "$(printf %s "${message[@]}")" | ${fmt_cmd[@]}
fi
mkdir -p "${config_dir}"
# Disable update checks
copyFileIfAbsent $scriptDir/development/studio/updates.xml "${androidStudioSettingsDir}/updates.xml"
# Disable instant run
copyFileIfAbsent $scriptDir/development/studio/instant-run.xml $config_dir/options/instant-run.xml
# Copy android code style
copyFileIfAbsent $scriptDir/development/studio/AndroidStyle.xml $config_dir/codestyles/AndroidStyle.xml
copyFileIfAbsent $scriptDir/development/studio/code.style.schemes.xml $config_dir/options/code.style.schemes.xml
# Disable notification to update gradle
copyFileIfAbsent $scriptDir/development/studio/notifications.xml $config_dir/options/notifications.xml
# Disable dialog that asks to trust the project
local trustedPathsFile="$config_dir/options/trusted-paths.xml"
if [ ! -f $trustedPathsFile ]; then
sed "s|__PROJECT_DIR_PLACEHOLDER__|$projectDir|" $scriptDir/development/studio/trusted-paths.xml > $trustedPathsFile
fi
# Disable dialog that asks whether to use Studio SDK or Project SDK
local jdkPathsFile="$config_dir/options/jdk.table.xml"
if [ ! -f $jdkPathsFile ]; then
sed "s|__SDK_DIR_PLACEHOLDER__|$sdkDir|g;s|__SDK_VERSION_NAME_PLACEHOLDER__|$compileSdkVersion|g;" $scriptDir/development/studio/jdk.table.xml > $jdkPathsFile
fi
}
function main() {
parseOptions "$@"
if [ "${uninstallAndroidStudio}" == "true" ]; then
runUninstallAndroidStudio
exit
fi
if [ "${cleanProjectFiles}" == "true" ]; then
runCleanProjectFiles
exit
fi
assertHasAdbRoot
setupBuildSrcSymlinks
if [ "${downloadStudioZip}" == "true" ]; then
fetchStudioUrl
studioDestName="$(basename ${studioUrl})"
studioZipPath="${studioSetupDir}/${studioDestName}"
studioUnzippedPath="$(echo ${studioZipPath} | sed 's/\.zip$//' | sed 's/\.tar\.gz$//')"
fi
# Checks if a platform SDK already exists. If it doesn't, automatically adjust the flags so that
# an SDK is generated by pulling files off the device.
if [[ -z "$updateSdk" ]]; then
if [[ ! -f $platformDir/android.jar ]]; then
printInfo "Android SDK not found. Automatically appending '--update-sdk adb' to the argument list."
updateSdk='adb'
else
checkSdkNeedsUpdate
fi
fi
checkJdkVersion
if [ -n "${updateSdk}" ]; then
createPlatformSdk
updateLocalGradleProperties
fi
if [ "${downloadStudioZip}" == "true" ]; then
updateStudio
createDesktopEntry
fi
if [ "${runStudio}" == "true" ]; then
checkLicenseAgreement
updateLocalGradleProperties
updateStudioConfig
runStudio
fi
}
function createDesktopEntry() {
studiow_path="${scriptDir}/studiow"
studiow_icon="${studioUnzippedPath}/android-studio/bin/studio.png"
targetDir="${HOME}/.local/share/applications"
mkdir -p "$targetDir"
cat "${scriptDir}/MPStudio.desktop" | \
sed "s|%STUDIOW_PATH%|${studiow_path}|g" | \
sed "s|%ANDROID_TOP%|${androidBuildTop}|g" | \
sed "s|%STUDIOW_ICON%|${studiow_icon}|g" > "${targetDir}/MPStudio.desktop"
}
main "$@"