#!/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='' 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="${versionNumbers[0]}" fi if [[ -n "${versionNumbers[1]}" ]]; then minorVersionString="${versionNumbers[1]}" fi if [[ -n "${versionNumbers[2]}" ]]; then microVersionString="${versionNumbers[2]}" 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="${versionNumbers[3]#rc}" fi ( cat <<'EOF' Terms and Conditions This is the Android Software Development Kit License Agreement EOF ) > "$targetFile" echo " $typeDetails $majorVersionString $minorVersionString $microVersionString $previewVersionString $displayName " >> $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" "$sdkVersionInt$compileSdkPreview" "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 < "${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 < "$projectDir/local.properties" ( cat < "$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 "$@"