1#!/bin/bash 2set -e 3set -m 4 5# This is a wrapper script that runs the specific version of Android Studio that is recommended for developing in this repository. 6# (This serves a similar purpose to gradlew) 7 8# Get the property value by a properties file 9function getProperyValue() { 10 getProperyValueAbs ${scriptDir}/$1 $2 11} 12 13function getProperyValueAbs() { 14 # Use --no-messages to suppress error messages about non-existant files 15 echo "$(grep --no-messages "$2[[:space:]]*=[[:space:]]*" ${1} | sed 's/[^=]*=[[:space:]]*//')" 16} 17 18# Get the studio version corresponding to the gradle version defined in gradle.properties 19function fetchStudioUrl() { 20 local cache_file=.studio_version_cache 21 local studioVersion="$(echo "$(getProperyValue ManagedProvisioningGradleProject/gradle/libs.versions.toml android-studio)" | sed 's/"//g')" 22 local cachedVersion="$(getProperyValue $cache_file cached_studio_version)" 23 24 if [ ! "$studioVersion" == "$cachedVersion" ]; then 25 local content="$(curl -L https://developer.android.com/studio/archive.html)" 26 local iframe_url="$(echo $content | egrep -o 'iframe src="[^"]+"' | cut -d '"' -f 2)" 27 content="$(curl -L $iframe_url)" 28 29 if [ "$osName" == "mac" ]; then 30 content="$(echo $content | egrep -o "Android Studio [^0-9]+$studioVersion.+?section")" 31 else 32 content="$(echo $content | grep -Po "Android Studio [^0-9]+$studioVersion.+?section")" 33 fi 34 35 local mac_url 36 if [ "$arch" == "arm" ]; then 37 mac_url="$(echo $content | egrep -o '"[^"]+mac_arm.zip"' | cut -d '"' -f 2)" 38 else 39 mac_url="$(echo $content | egrep -o '"[^"]+mac.zip"' | cut -d '"' -f 2)" 40 fi 41 mac_url="$(echo $mac_url | cut -d " " -f 1)" 42 43 local linux_url="$(echo $content | egrep -o '"[^"]+linux[^"]*"' | cut -d '"' -f 2 | cut -d " " -f 1)" 44 linux_url="$(echo $linux_url | cut -d " " -f 1)" 45 46 echo cached_studio_version=$studioVersion > ${scriptDir}/$cache_file 47 echo mac=$mac_url >> ${scriptDir}/$cache_file 48 echo linux=$linux_url >> ${scriptDir}/$cache_file 49 fi 50 studioUrl="https://dl.google.com/dl/android/studio/$(getProperyValue $cache_file $osName | egrep -o 'ide-zips/.*')" 51} 52 53# Escape sequence to print bold colors 54RED='\033[1;31m' 55GREEN='\033[1;32m' 56YELLOW='\033[1;33m' 57# Escape sequence to clear formatting 58NC='\033[0m' 59 60printError() { 61 local logMessage="$1" 62 echo -e "${RED}ERROR:${NC} ${logMessage}" 63} 64 65printWarning() { 66 local logMessage="$1" 67 echo -e "${YELLOW}WARNING:${NC} ${logMessage}" 68} 69 70printInfo() { 71 local logMessage="$1" 72 echo -e "${GREEN}INFO:${NC} ${logMessage}" 73} 74 75 76acceptsLicenseAgreement="false" 77runStudio="true" 78downloadStudioZip="true" 79cleanProjectFiles="false" 80scriptDir="$(cd $(dirname $0) && pwd)" 81projectDir=$scriptDir/ManagedProvisioningGradleProject 82androidBuildTop="$(cd "${scriptDir}/../../../../"; pwd)" 83 84gradleOutDir="${androidBuildTop}/out/gradle" 85usingDefaultSettingsDir='true' 86androidStudioSettingsDir="${gradleOutDir}/AndroidStudio" 87# Where to put the generated idea.properties file relative to $androidStudioSettingsDir, used for 88# STUDIO_PROPERTIES 89ideaPropRelPath="bin/idea.properties" 90 91# TODO(b/249826650): Maybe we shouldn't write to ~/.AndroidStudio* named directories. Android Studio 92# searches for these directories and tries to use them when opening for the first time elsewhere. 93# See: 94# - Implementation details: http://shortn/_Q0gp64FPj3 95# - Screenshot: http://screen/4sCQBhNyVTMPPf3.png 96studioHomeDir="${HOME}/.AndroidStudioSystemUI" 97studioSetupDir="${studioHomeDir}/bin" 98function getOsName() { 99 local unameOutput="$(uname)" 100 local osName="" 101 if [ "${unameOutput}" == "Linux" ]; then 102 osName="linux" 103 else 104 osName="mac" 105 fi 106 echo "${osName}" 107} 108osName="$(getOsName)" 109arch="$(uname -p)" 110studioUrl= 111# If empty string, don't update the SDK. Otherwise, the string indicates the method used for 112# fetching the artifacts needed to generate the SDK. Currently, either 'adb' or 'soong' 113updateSdk='' 114copySdkSourceDir='' 115 116function setupBuildSrcSymlinks() { 117 # Builtbots can't write to the source dirs, and there is no gradle option to overwrite the .gradle 118 # location of buildSrc, so we use symlinks. The dirs must be created before running the build. 119 # 120 # Alternatively, we could migrate from buildSrc/ to a composite build to avoid needing the 121 # symlinks. See: http://go/gh/gradle/gradle/issues/2472#issuecomment-315376378 122 cd "${scriptDir}/ManagedProvisioningGradleProject/buildSrc" 123 mkdir -p $(readlink .gradle) 124 mkdir -p $(readlink build) 125 cd - > /dev/null 126} 127 128# Used to keep track of whether we confirmed adb root works. We only want to check once. 129adbChecked='false' 130function assertHasAdbRoot() { 131 if [[ "${adbChecked}" == 'false' && "${updateSdk}" == 'adb' ]]; then 132 adb root || { 133 printError 'adb root failed. You must have a rooted device attached to use "--update-sdk adb".' 134 if [ "${osName}" != "mac" ]; then 135 echo 'NOTE: On Linux, you can use "--update-sdk soong" to compile the SDK from source.' 136 fi 137 exit 1 138 } 139 adb wait-for-device 140 adbChecked='true' 141 fi 142} 143 144function getUpstreamGitBranch() { 145 echo "$(git -C "${androidBuildTop}/.repo/manifests" for-each-ref --format='%(upstream:short)' refs/heads | sed 's/origin\///')" 146} 147function toUpperCase() { 148 local dashedName="$1" 149 IFS='-' 150 local words=($dashedName) 151 unset IFS 152 local upperCaseName='' 153 for word in "${words[@]}"; do 154 upperCaseName="${upperCaseName}$(echo "$word" | sed 's/[a-z]/\U&/')" 155 done 156 echo "$upperCaseName" 157} 158# Find the name of the branch (e.g. udc-dev) and change it to camel-case (e.g. UdcDev) 159# (e.g. udc-dev) 160branchName="$(getUpstreamGitBranch)" 161# (e.g. UdcDev) 162upperCaseBranchName="$(toUpperCase "$branchName")" 163 164sdkVersionInt="$(getProperyValue ManagedProvisioningGradleProject/gradle.properties TARGET_SDK)" 165# Use the first 4 chars of the SHA of $ANDROID_BUILD_TOP as a unique ID for this Android tree. 166# We will use this to associate this tree with the generated SDK so that all platform SDK can share 167# the same SDK home. This is just for preventing collisions if someone has the same branch checked 168# out twice. Otherwise, we'd just use $upperCaseBranchName 169androidTreeUniqueId="$(echo "$androidBuildTop" | shasum -a 256 | head -c 4)" 170 171# ---- ---- ---- ---- 172# The following variables are to correspond with the Android Gradle DSL property of the same name: 173# Public docs: http://go/android-reference/tools/gradle-api/8.0/com/android/build/api/dsl/SettingsExtension#compileSdkPreview() 174# 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 175compileSdkPreview="${upperCaseBranchName}ForTree${androidTreeUniqueId}" 176compileSdkVersion="android-${compileSdkPreview}" 177# ---- ---- ---- ---- 178 179prebuiltsDir="${androidBuildTop}/prebuilts" 180function getLegacySdkDir() { 181 if [ "${osName}" == "mac" ]; then 182 echo "${prebuiltsDir}/fullsdk-darwin" 183 else 184 echo "${prebuiltsDir}/fullsdk-linux" 185 fi 186} 187function getLegacySdkDir2() { 188 if [ "${osName}" == "mac" ]; then 189 echo "${studioHomeDir}/fullsdk-darwin" 190 else 191 echo "${studioHomeDir}/fullsdk-linux" 192 fi 193} 194function getSdkDir() { 195 echo "${gradleOutDir}/MockSdk" 196} 197sdkDir="$(getSdkDir)" 198platformDir="$sdkDir/platforms/$compileSdkVersion" 199 200function printHelpAndExit() { 201 local message=( 202 'Usage: studiow [OPTION...]' 203 '\n' 204 '\n -y, --accept-license-agreement' 205 '\n --update-only' 206 '\n --no-download' 207 '\n --update-sdk [ARTIFACT SOURCE (adb, soong, or copy)]' 208 '\n --project-dir' 209 '\n --settings-dir' 210 '\n --clean' 211 '\n --uninstall' 212 ) 213 local fmt_cmd=(fmt -w 100 --split-only) 214 if [ "$osName" == "mac" ]; then 215 fmt_cmd=(fmt -w 100) 216 fi 217 printf "$(printf %s "${message[@]}")" | ${fmt_cmd[@]} 218 exit 1 219} 220 221 222function parseOptions() { 223 while :; do 224 case "$1" in 225 -y|--accept-license-agreement) 226 acceptsLicenseAgreement="true" 227 ;; 228 --update-only) 229 runStudio="false" 230 ;; 231 --no-download) 232 downloadStudioZip="false" 233 runStudio="false" 234 ;; 235 --update-sdk) 236 if [[ -n "$2" && "$2" != -* ]]; then 237 updateSdk="$2" 238 shift 239 fi 240 case "$updateSdk" in 241 adb) 242 ;; 243 soong) 244 if [[ "${osName}" == "mac" ]]; then 245 printError 'MacOS does not support soong builds' 246 exit 1 247 elif [[ ! -f "$androidBuildTop/build/soong/soong_ui.bash" ]]; then 248 printError 'You must have a full platform branch to compile the SDK. Minimal checkouts (e.g. *-sysui-studio-dev) do not support soong builds.' 249 exit 1 250 fi 251 ;; 252 copy) 253 if [[ -n "$2" && "$2" != -* ]]; then 254 copySdkSourceDir="$2" 255 shift 256 else 257 printError 'You must specify a directoy to copy the SDK from' 258 echo '' 259 echo 'Usage: ./studiow --update-sdk copy [DIR]' 260 echo '' 261 echo 'Directory can be local or remote in the form of [user@]host:[path].' 262 echo '' 263 echo 'For example, to copy the SDK from a remote host, run the following:' 264 echo '' 265 echo ' ./studiow --update-sdk copy example.corp.google.com:/path/to/out/gradle/MockSdk/platforms/android-31' 266 echo '' 267 exit 1 268 fi 269 ;; 270 *) 271 if [[ -z "${updateSdk}" ]]; then 272 printError 'You must specify artifact source when using --update-sdk.' 273 else 274 printError "Unknown SDK artifact source: $updateSdk" 275 fi 276 echo '' 277 echo 'Usage: ./studiow --update-sdk [ARTIFACT SOURCE]' 278 echo '' 279 echo 'Available options are:' 280 echo ' adb: Pull the artifacts from attached device (default)' 281 echo ' soong: Build the artifacts using soong (recommended)' 282 echo ' copy: Copy android.jar and framework.aidl from the given directory' 283 echo '' 284 echo 'NOTE: soong option can only be used on Linux with a full checkout' 285 exit 1 286 ;; 287 esac 288 ;; 289 --pull-sdk) 290 printWarning "--pull-sdk is deprecated. It is equivalent to '--update-sdk adb'" 291 updateSdk='adb' 292 ;; 293 --project-dir) 294 shift 295 projectDir="$1" 296 ;; 297 --settings-dir) 298 shift 299 usingDefaultSettingsDir='false' 300 if [[ -z "$1" ]] ; then 301 echo "--settings-dir expects a directory. Usage: --settings-dir [dir name]" 302 exit 1 303 elif [[ ! -d "$1" ]] ; then 304 echo "Invalid --settings-dir: $1 does not exist or is not a directory" 305 exit 1 306 fi 307 androidStudioSettingsDir="$(cd "$1"; pwd)" 308 ;; 309 --clean) 310 cleanProjectFiles="true" 311 ;; 312 --uninstall) 313 uninstallAndroidStudio="true" 314 ;; 315 -h|--help|-?) 316 printHelpAndExit 317 ;; 318 *) 319 if [ -z "$1" ]; then 320 # If $1 is an empty string, it means we reached the end of the passed arguments 321 break 322 else 323 echo "Unknown option: $1" 324 exit 325 fi 326 esac 327 328 shift 329 done 330} 331 332# $1 - string to print 333# $2 - default, either 'y' or 'n' 334function yesNo() { 335 local question="$1" 336 local defaultResponse="${2:-y}" 337 local yesNoPrompt='' 338 339 if [[ "${defaultResponse::1}" =~ [yY] ]]; then 340 yesNoPrompt='[Y/n]' 341 else 342 yesNoPrompt='[y/N]' 343 fi 344 345 read -r -n 1 -p "$question ${yesNoPrompt}? " -s reply 346 if [ -z "${reply}" ]; then 347 # Replace the empty string with the default response 348 reply="${defaultResponse::1}" 349 fi 350 351 # Print the response so there is no confusion 352 echo "${reply::1}" 353 354 case "${reply::1}" in 355 [yY]) 356 true 357 ;; 358 *) 359 false 360 ;; 361 esac 362} 363 364function downloadFile() { 365 fromUrl="$1" 366 destPath="$2" 367 tempPath="${destPath}.tmp" 368 if [ -f "${destPath}" ]; then 369 if yesNo "File already exists. Do you want to delete and re-download?"; then 370 rm "${destPath}" 371 fi 372 fi 373 374 if [ -f "${destPath}" ]; then 375 echo "Using existing file from ${destPath}" 376 else 377 echo "Downloading ${fromUrl} to ${destPath}" 378 curl "${fromUrl}" > "${tempPath}" 379 mv "${tempPath}" "${destPath}" 380 fi 381} 382 383function findStudioMacAppPath() { 384 echo "$(find "${studioUnzippedPath}" -type d -depth 1 -name "Android Studio*.app")" 385} 386 387function getLicensePath() { 388 if [ "${osName}" == "mac" ]; then 389 appPath="$(findStudioMacAppPath)" 390 echo "${appPath}/Contents/Resources/LICENSE.txt" 391 else 392 echo "${studioUnzippedPath}/android-studio/LICENSE.txt" 393 fi 394} 395 396function checkLicenseAgreement() { 397 # TODO: Is there a more official way to check that the user accepts the license? 398 399 licenseAcceptedPath="${studioUnzippedPath}/STUDIOW_LICENSE_ACCEPTED" 400 401 if [ ! -f "${licenseAcceptedPath}" ]; then 402 if [ "${acceptsLicenseAgreement}" == "true" ]; then 403 touch "${licenseAcceptedPath}" 404 else 405 if yesNo "Do you accept the license agreement at $(getLicensePath)"; then 406 touch "${licenseAcceptedPath}" 407 else 408 exit 1 409 fi 410 fi 411 fi 412} 413 414# Inserts a snippet into the studio.sh launch script that prevents it from running if 415# STUDIO_LAUNCHED_WITH_WRAPPER is not set. This is to prevent people from launching studio.sh 416# directly, which can result in build errors. This only works on Linux. Unfortunately, there is no 417# equivalent for Mac OS. 418# 419# Inputs: 420# osName - Either mac or linux 421# studioPath - Path to the studio.sh script on Linux 422function updateStudioLaunchScript() { 423 # Only Linux uses the studio.sh script 424 if [ "${osName}" == "mac" ]; then 425 return 426 fi 427 # If studio.sh already contains 'STUDIO_LAUNCHED_WITH_WRAPPER', don't do anything 428 grep -qF 'STUDIO_LAUNCHED_WITH_WRAPPER' "$studioPath" && return 429 430 local tmpStudioSh="$(mktemp)" 431 432 # Find the first line OS_TYPE, and use this as our insertion point. 433 local insertionPoint="$(grep --line-number OS_TYPE "$studioPath" | head -n 1 | cut -d ':' -f 1)" 434 435 # Dump everything from line 0 through $insertionPoint-1 into the tmp file 436 head -n "$(("$insertionPoint"-1))" "$studioPath" > "$tmpStudioSh" 437 438 # Insert a conditional to prevent launching studiow when STUDIO_LAUNCHED_WITH_WRAPPER is unset 439 ( cat <<'EOF' 440if [ -z "$STUDIO_LAUNCHED_WITH_WRAPPER" ]; then 441 message 'This installation of Android Studio must be launched using the studiow script found in $ANDROID_BUILD_TOP/packages/apps/ManagedProvisioning/studio-dev/studiow.' 442 exit 1 443fi 444EOF 445) >> "$tmpStudioSh" 446 447 # Dump everything from $insertionPoint until the end studio.sh into the tmp file 448 tail -n +"$insertionPoint" "$studioPath" >> "$tmpStudioSh" 449 450 # Ensure that the tmp file has the same permissions as the original studio.sh 451 chmod --reference="$studioPath" "$tmpStudioSh" 452 453 echo "Inserting the following snippet into studio.sh to prevent launching it outside of studiow:" 454 diff "$studioPath" "$tmpStudioSh" || : 455 mv "$tmpStudioSh" "$studioPath" 456} 457 458# Creates an idea.properties file where all the configs are specific to this checkout of the Android 459# tree and not shared between different branches. 460# 461# Inputs: 462# scriptDir - The path to this script (studiow) 463# androidStudioSettingsDir - The common dir for Android Studio settings per checkout 464# studioPropertiesFile - Where to put the generated idea.properties file, used for STUDIO_PROPERTIES 465function updateIdeaProperties() { 466 local ideaPropRefFile="${scriptDir}/development/studio/idea.properties" 467 mkdir -p "$(dirname "$studioPropertiesFile")" 468 echo "SYSUI_STUDIO_SETTINGS_DIR=$androidStudioSettingsDir" > "$studioPropertiesFile" 469 chmod 640 "$studioPropertiesFile" 470 cat "${ideaPropRefFile}" >> "$studioPropertiesFile" 471} 472 473# Creates the DO_NOT_RUN_ANDROID_STUDIO_FROM_HERE file 474# 475# Inputs: 476# studioSetupDir - Path to Android Studio bin dir. Assumes the dir already exists. 477function createWarningTextFile() { 478 ( cat <<'EOF' 479The installations of Android Studio found in this directory MUST be launched using the studiow 480script found in: 481$ANDROID_BUILD_TOP/packages/apps/ManagedProvisioning/studio-dev/studiow 482 483Do NOT launch Android Studio by running studio.sh (on Linux) or by opening Android Studio.app (on 484MacOS). Otherwise, you won't be able to work on multiple branches of Android simultaneously. 485EOF 486) > "${studioSetupDir}/DO_NOT_RUN_ANDROID_STUDIO_FROM_HERE" 487} 488 489function updateStudio() { 490 491 # skip if already up-to-date 492 if stat "${studioUnzippedPath}" >/dev/null 2>/dev/null; then 493 # already up-to-date 494 createWarningTextFile 495 return 496 fi 497 498 mkdir -p "${studioSetupDir}" 499 downloadFile "${studioUrl}" "${studioZipPath}" 500 echo 501 502 echo "Unzipping" 503 if [[ $studioZipPath = *.zip ]]; then 504 unzip "${studioZipPath}" -d "${studioUnzippedPath}" 505 elif [[ $studioZipPath = *.tar.gz ]]; then 506 mkdir -p $studioUnzippedPath 507 tar -xf $studioZipPath -C $studioUnzippedPath 508 fi 509 510 createWarningTextFile 511} 512 513# ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED environment variable prevents Studio from showing IDE 514# inspection warnings for nullability issues, if the context is deprecated 515# This environment variable is consumed by InteroperabilityDetector.kt 516 517function runStudioLinux() { 518 studioPath="${studioUnzippedPath}/android-studio/bin/studio.sh" 519 updateStudioLaunchScript 520 echo "$studioPath &" 521 env LAUNCHED_VIA_STUDIOW='true' \ 522 STUDIO_PROPERTIES="${studioPropertiesFile}" \ 523 STUDIO_VM_OPTIONS="${scriptDir}/development/studio/studio.vmoptions" \ 524 ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED="true" \ 525 "${studioPath}" "${projectDir}" &>/dev/null & 526} 527 528function runStudioMac() { 529 appPath="$(findStudioMacAppPath)" 530 echo "open ${appPath}" 531 env STUDIO_PROPERTIES="${studioPropertiesFile}" \ 532 STUDIO_VM_OPTIONS="${scriptDir}/development/studio/studio.vmoptions" \ 533 ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED="true" \ 534 open -a "${appPath}" "${projectDir}" 535} 536 537function runStudio() { 538 local studioPropertiesFile="${androidStudioSettingsDir}/${ideaPropRelPath}" 539 updateIdeaProperties 540 # Export an env var so the gradle script can check that Android Studio was launched using this 541 # script. Launching Android Studio and then opening the Gradle project after-the-fact IS 542 # UNSUPPORTED. Android Studio needs to be lunched using studiow so that it can validate settings 543 # and update the build environment. 544 export STUDIO_LAUNCHED_WITH_WRAPPER='true' 545 if [ "${osName}" == "mac" ]; then 546 runStudioMac 547 else 548 runStudioLinux 549 fi 550} 551 552function runCleanProjectFiles() { 553 local projects=( 554 //external/setupcompat 555 //external/setupdesign 556 //frameworks/base 557 //frameworks/libs/systemui 558 //packages/apps/Launcher3 559 //platform_testing 560 //vendor/unbundled_google/libraries 561 //vendor/unbundled_google/packages/NexusLauncher 562 //packages/apps/ManagedProvisioning 563 ) 564 local gitPath 565 for gitPath in "${projects[@]}"; do 566 cleanProjectFiles "$gitPath" 567 done 568 removeDirIfExists "${gradleOutDir}/build" || : 569} 570 571function cleanProjectFiles() { 572 local projectPath="$1" 573 local gitPath="${androidBuildTop}/${gitPath:1}" 574 local cleanPreview="$(git -C "$gitPath" clean --dry-run --force -X -d .)" 575 if [[ -z "$cleanPreview" ]]; then 576 echo "$projectPath already clean. Nothing to do." 577 else 578 echo "$projectPath cleaning:" 579 echo "$cleanPreview" 580 if yesNo 'Do you want to delete these files?' 'n'; then 581 git -C "$gitPath" clean --force -X -d . 582 else 583 echo "Clean operation cancelled." 584 fi 585 fi 586} 587 588function removeDirIfExists() { 589 local dir="$1" 590 if [[ -z "${dir}" ]] ; then 591 echo 'script error: removeDirIfExists expects 1 arg' 592 exit 1 593 fi 594 if [[ -d "${dir}" ]] ; then 595 if yesNo "Remove ${dir}?" 'n'; then 596 rm -rf "${dir}" 597 fi 598 return 0 599 fi 600 return 1 601} 602 603function runUninstallAndroidStudio() { 604 if ! yesNo 'This will remove the local Android Studio installation, local SDK, and project gradle files. Proceed?'; then 605 echo "Uninstall operation cancelled." 606 return 607 fi 608 removeDirIfExists "$studioHomeDir" || echo "Android Studio installation not found." 609 removeDirIfExists "$(getLegacySdkDir)" || : 610 removeDirIfExists "$(getLegacySdkDir2)" || : 611 removeDirIfExists "${gradleOutDir}" || : 612 613 runCleanProjectFiles 614} 615 616function adbGetProp() { 617 local prop="$1" 618 echo "$(adb shell getprop $prop 2>/dev/null || true)" 619} 620 621function askToUpdateSdkUsingAdb() { 622 echo '' 623 if yesNo "Update SDK using adb?"; then 624 updateSdk="adb" 625 fi 626} 627 628function checkSdkNeedsUpdate() { 629 printInfo "Android SDK Location: $sdkDir" 630 local localSdkTime="$(getProperyValueAbs $platformDir/build.prop ro.system.build.date.utc)" 631 local localSdkFingerprint="$(getProperyValueAbs $platformDir/build.prop ro.system.build.fingerprint)" 632 local utcTimeOneWeekAgo="$(expr $(date +%s) - 604800)" 633 if [[ -z "$localSdkFingerprint" ]]; then 634 localSdkFingerprint='<Unknown>' 635 fi 636 637 local sdkGenSrc="$(getProperyValueAbs $platformDir/build.prop ro.sdk.gensrc)" 638 sdkGenSrc="${sdkGenSrc:-adb}" 639 printInfo "Android SDK Fingerprint: $localSdkFingerprint" 640 printInfo "Android SDK was last updated using \"--update-sdk ${sdkGenSrc}\"" 641 if [[ -z "$localSdkTime" ]]; then 642 printWarning 'Could not determine age of Android SDK. This means your SDK is corrupt or out of date.' 643 askToUpdateSdkUsingAdb 644 elif [[ "$utcTimeOneWeekAgo" -gt "$localSdkTime" ]]; then 645 printWarning 'Android SDK is more than 7 days old.' 646 askToUpdateSdkUsingAdb 647 else 648 local serial="$(adb get-serialno 2> /dev/null)" 649 if [[ -z "$serial" ]]; then 650 local noDeviceMessage='Skipping adb build check. No devices/emulators found.' 651 if [[ "$sdkGenSrc" == 'adb' ]]; then 652 printWarning "$noDeviceMessage" 653 else 654 printInfo "$noDeviceMessage" 655 fi 656 else 657 local adbSdkBuildTime="$(adbGetProp ro.system.build.date.utc)" 658 if [ "${updateSdk}" != "true" ]; then 659 if [ $adbSdkBuildTime -gt $localSdkTime ]; then 660 printWarning "Attached device has newer SDK." 661 askToUpdateSdkUsingAdb 662 fi 663 fi 664 fi 665 fi 666} 667 668function createPackageXml() { 669 local targetFile="$1" 670 local localPackage="$2" 671 local buildToolsVersion="$3" 672 local typeDetails="$4" 673 local displayName="$5" 674 675 # Split X.Y.Z-rcN into an array of X, Y, Z, and rcN 676 IFS='.-' 677 local versionNumbers=($buildToolsVersion) 678 unset IFS 679 local majorVersionString="" 680 local minorVersionString="" 681 local microVersionString="" 682 local previewVersionString="" 683 if [[ -n "${versionNumbers[0]}" ]]; then 684 majorVersionString="<major>${versionNumbers[0]}</major>" 685 fi 686 if [[ -n "${versionNumbers[1]}" ]]; then 687 minorVersionString="<minor>${versionNumbers[1]}</minor>" 688 fi 689 if [[ -n "${versionNumbers[2]}" ]]; then 690 microVersionString="<micro>${versionNumbers[2]}</micro>" 691 fi 692 # preview version includes rc if it's set, e.g. rc1. It could also be an empty string if it's not 693 # a preview version at all 694 if [[ -n "${versionNumbers[3]}" ]]; then 695 # Remove rc from the preview version number 696 previewVersionString="<preview>${versionNumbers[3]#rc}</preview>" 697 fi 698 699 ( cat <<'EOF' 700<?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 701 702This is the Android Software Development Kit License Agreement 703</license> 704EOF 705) > "$targetFile" 706echo "<localPackage path=\"$localPackage\" obsolete=\"false\"> 707 <type-details xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ns11:platformDetailsType\">$typeDetails</type-details> 708 <revision> 709 $majorVersionString 710 $minorVersionString 711 $microVersionString 712 $previewVersionString 713 </revision> 714 <display-name>$displayName</display-name> 715 <uses-license ref=\"android-sdk-license\"/> 716 </localPackage> 717</ns2:repository> 718" >> $targetFile 719} 720 721function appendLineIfNotExists() { 722 grep -qxF "$1" $2 || echo "$1" >> $2 723} 724 725function setupMockSdk() { 726 rm -rf $sdkDir 727 mkdir -p $sdkDir 728 729 local toolsName 730 if [ "${osName}" == "mac" ]; then 731 toolsName="darwin" 732 else 733 toolsName="linux" 734 fi 735 736 # e.g. 33.0.1 OR 34.0.0-rc1 737 # In Gradle, there's a space before 'rc', but when packaged in Android/Sdk, there's a dash. 738 # To work with that, replace ' ' with '-' for usage in the script 739 local buildToolsVersion="$(getProperyValue ManagedProvisioningGradleProject/gradle.properties BUILD_TOOLS_VERSION | sed 's/ /-/')" 740 741 local adbRelPath="out/host/$toolsName-x86/bin/adb" 742 local compiledAdb="$androidBuildTop/$adbRelPath" 743 # platform tools 744 if [ ! -f $compiledAdb ]; then 745 # Check if adb exists in path (don't exit if `which` fails) 746 compiledAdb="$(which adb || true)" 747 if [ ! -f $compiledAdb ]; then 748 printError "Could not find adb at $adbRelPath or in the environment path" 749 echo "Did you build system image once?" 750 exit 1 751 fi 752 fi 753 printInfo "Using adb binary found at $compiledAdb" 754 mkdir $sdkDir/platform-tools 755 ln -s $compiledAdb $sdkDir/platform-tools/adb 756 createPackageXml $sdkDir/platform-tools/package.xml "platform-tools" "$buildToolsVersion" "" "Android SDK Platform-Tools" 757 758 # Setup build tools 759 mkdir -p $sdkDir/build-tools 760 cp -r $prebuiltsDir/sdk/tools/$toolsName/bin $sdkDir/build-tools/$buildToolsVersion 761 cp -r $prebuiltsDir/sdk/tools/$toolsName/lib $sdkDir/build-tools/$buildToolsVersion/lib 762 cp -r $prebuiltsDir/sdk/tools/$toolsName/lib64 $sdkDir/build-tools/$buildToolsVersion/lib64 763 ln -s $prebuiltsDir/sdk/renderscript $sdkDir/build-tools/$buildToolsVersion/renderscript 764 # All tools are now validated by studio, so we need to collect them all 765 cp -r $prebuiltsDir/sdk/tools/lib/* $sdkDir/build-tools/$buildToolsVersion/lib 766 cp $prebuiltsDir/sdk/tools/dx $sdkDir/build-tools/$buildToolsVersion 767 768 cp -r $prebuiltsDir/build-tools/$toolsName-x86/bin/* $sdkDir/build-tools/$buildToolsVersion/ 769 cp -r $prebuiltsDir/build-tools/$toolsName-x86/lib64/* $sdkDir/build-tools/$buildToolsVersion/lib64/ 770 createPackageXml $sdkDir/build-tools/$buildToolsVersion/package.xml "build-tools;$buildToolsVersion" "$buildToolsVersion" "" "Android SDK Build-Tools $buildToolsVersion" 771 772 # Setup platforms 773 mkdir -p $platformDir 774 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)" 775 776 prebuiltAndroidJar=$prebuiltsDir/sdk/current/system/android.jar 777 778 # Setup test and optional packages 779 mkdir $platformDir/optional 780 cp -r $prebuiltsDir/sdk/current/test/*.*.jar $platformDir/optional/ 781 list= 782 for f in $platformDir/optional/*.jar; do 783 filename=$(basename -- "$f") 784 libname="${filename%.*}" 785 786 [ ! -z "$list" ] && list="$list," 787 br=$'\n' 788 list="$list$br{ \"name\": \"$libname\", \"jar\": \"$filename\", \"manifest\": false }" 789 done 790 echo "[$list]" > $platformDir/optional/optional.json 791} 792 793function createPlatformSdk() { 794 assertHasAdbRoot 795 echo "Setting up SDK from scratch" 796 setupMockSdk 797 798 echo "Updating private apis sdk" 799 local android_jar_outfile="$platformDir/android.jar" 800 local framework_aidl="$platformDir/framework.aidl" 801 rm -rf $android_jar_outfile 802 803 local buildPropFile="$platformDir/build.prop" 804 local buildDate= 805 local buildFingerprint= 806 807 if [[ "$updateSdk" == 'adb' ]]; then 808 local tempFiles="$(mktemp -d)" 809 810 buildDate="$(adbGetProp ro.system.build.date.utc)" 811 buildFingerprint="$(adbGetProp ro.system.build.fingerprint)" 812 813 printInfo "Pulling SDK artifacts from device $buildFingerprint using adb" 814 adb pull /system/framework/framework-res.apk $tempFiles/ 815 adb pull /system/framework/framework.jar $tempFiles/ 816 adb pull /system/framework/framework-location.jar $tempFiles/ 817 818 local dexList="--dex $tempFiles/framework.jar" 819 dexList="$dexList --dex $tempFiles/framework-location.jar" 820 local apexJars=($(adb shell ls /apex/*/javalib/*.jar)) 821 for f in "${apexJars[@]}" 822 do 823 local fileBasename="$(basename $f)" 824 if [[ ! $f = *@* ]] && [[ "$fileBasename" != service-* ]]; then 825 local target="$tempFiles/$(basename $f)" 826 adb pull $f $target 827 dexList="$dexList --dex $target" 828 fi 829 done 830 831 cp $prebuiltsDir/sdk/current/public/framework.aidl $framework_aidl 832 833 java -jar $scriptDir/StubGenerator/StubGenerator.jar -o $android_jar_outfile \ 834 $dexList \ 835 --zip $tempFiles/framework-res.apk \ 836 --zip $prebuiltAndroidJar \ 837 --aidl $framework_aidl 838 839 echo "Removing temp files" 840 rm -rf $tempFiles 841 842 cp $prebuiltsDir/sdk/current/module-lib/core-for-system-modules.jar "$platformDir" 843 elif [[ "$updateSdk" == 'soong' ]]; then 844 printInfo "Building SDK artifacts using soong" 845 846 # TODO(b/251871740): Replace these steps with the output of a soong target 847 # 848 # ---- begin ---- 849 # 850 local frameworks_deps=( 851 out/soong/.intermediates/build/soong/java/core-libraries/legacy.core.platform.api.stubs/android_common/turbine-combined/legacy.core.platform.api.stubs.jar 852 out/soong/.intermediates/libcore/core-lambda-stubs-for-system-modules/android_common/turbine-combined/core-lambda-stubs-for-system-modules.jar 853 out/soong/.intermediates/libcore/core-generated-annotation-stubs/android_common/turbine-combined/core-generated-annotation-stubs.jar 854 out/soong/.intermediates/frameworks/base/framework/android_common/turbine-combined/framework.jar 855 out/soong/.intermediates/frameworks/base/ext/android_common/turbine-combined/ext.jar 856 out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/package-res.apk 857 out/soong/.intermediates/tools/metalava/private-stub-annotations-jar/android_common/turbine-combined/private-stub-annotations-jar.jar 858 ) 859 860 cd $androidBuildTop 861 862 . ./build/envsetup.sh 863 if [[ -n "$TARGET_PRODUCT" && -n "$TARGET_BUILD_VARIANT" ]]; then 864 if [[ -n "$TARGET_RELEASE" ]]; then 865 lunch "$TARGET_PRODUCT-$TARGET_RELEASE-$TARGET_BUILD_VARIANT" 866 else 867 lunch "$TARGET_PRODUCT-$TARGET_BUILD_VARIANT" 868 fi 869 else 870 lunch aosp_arm64-trunk_staging-userdebug 871 fi 872 873 m merge_zips sdkparcelables ${frameworks_deps[@]} 874 875 echo "Updating private apis sdk" 876 merge_zips --ignore-duplicates $android_jar_outfile ${frameworks_deps[@]} 877 878 sdkparcelables "$android_jar_outfile" "$framework_aidl" 879 # TODO(b/259594098): sdkparcelables output aidl file doesn't include all the interfaces we need 880 appendLineIfNotExists "interface android.app.IApplicationThread;" "$framework_aidl" 881 appendLineIfNotExists "interface android.view.IRecentsAnimationController;" "$framework_aidl" 882 appendLineIfNotExists "interface android.view.IRecentsAnimationRunner;" "$framework_aidl" 883 appendLineIfNotExists "interface android.view.IRemoteAnimationRunner;" "$framework_aidl" 884 appendLineIfNotExists "interface android.window.IOnBackInvokedCallback;" "$framework_aidl" 885 886 cp $prebuiltsDir/sdk/current/module-lib/core-for-system-modules.jar "$platformDir" 887 # 888 # ---- end ---- 889 # 890 buildDate="$(date +%s)" 891 buildFingerprint="$(cat $ANDROID_PRODUCT_OUT/build_fingerprint.txt)" 892 893 printInfo "If you'd like to use this SDK on your laptop too, run ./studiow --update-sdk copy $(hostname):$platformDir" 894 elif [[ "${updateSdk}" == 'copy' ]]; then 895 scp "${copySdkSourceDir}"/{framework.aidl,android.jar,build.prop,core-for-system-modules.jar} "$platformDir/." 896 # Copy the build date and fingerprint from build.prop, then delete the file 897 buildDate="$(getProperyValueAbs $buildPropFile ro.system.build.date.utc)" 898 buildFingerprint="$(getProperyValueAbs $buildPropFile ro.system.build.fingerprint)" 899 rm "$buildPropFile" 900 else 901 printError "Internal error. Unknown SDK update source: $updateSdk" 902 exit 1 903 fi 904 905 { 906 echo "ro.system.build.version.sdk=$sdkVersionInt" 907 echo "ro.build.version.codename=$compileSdkPreview" 908 echo "ro.system.build.date.utc=$buildDate" 909 echo "ro.system.build.fingerprint=$buildFingerprint" 910 echo "ro.sdk.gensrc=$updateSdk" 911 } > "$buildPropFile" 912 913 cp "${scriptDir}/development/sdk/sdk.properties" "${platformDir}" 914 915 ( cat <<EOF 916Pkg.Desc=Android SDK Platform $upperCaseBranchName 917Pkg.UserSrc=false 918Platform.Version=$compileSdkPreview 919Platform.CodeName=$compileSdkPreview 920Pkg.Revision=1 921AndroidVersion.ApiLevel=$sdkVersionInt 922AndroidVersion.CodeName=$compileSdkPreview 923AndroidVersion.ExtensionLevel=5 924AndroidVersion.IsBaseSdk=true 925Layoutlib.Api=15 926Layoutlib.Revision=1 927Platform.MinToolsRev=22 928EOF 929) > "${platformDir}/source.properties" 930 931 932 echo "Generating sdk data" 933 # Sympolic link does not work here with android 934 mkdir -p $platformDir/data 935 cp -r $androidBuildTop/frameworks/base/core/res/res $platformDir/data/ 936 mv $platformDir/data/res/values/public-final.xml $platformDir/data/res/values/public.xml 937 938 echo "Removing build cache" 939 rm -rf "${scriptDir}/ManagedProvisioningGradleProject/.build-cache" 940 941 echo "Accepting license" 942 cp -r ${prebuiltsDir}/cmdline-tools $sdkDir/ 943 yes | $sdkDir/cmdline-tools/tools/bin/sdkmanager --licenses >/dev/null 944 rm -rf $sdkDir/cmdline-tools 945 946 echo "Linking sources" 947 ln -s $androidBuildTop/frameworks/base/core/java $platformDir/sources 948 949 echo "Done" 950} 951 952function copyFileIfAbsent() { 953 SRC=$1 954 DEST=$2 955 if [ ! -f $DEST ]; then 956 mkdir -p $(dirname $DEST) 957 cp $SRC $DEST 958 fi 959} 960 961# On mac, a newer JDK must be installed manually. 962function checkJdkVersion() { 963 if [ "${osName}" == "mac" ]; then 964 local javaHome 965 javaHome="$(/usr/libexec/java_home -v 17)" 966 if [[ "$?" -eq 0 && -n "$javaHome" ]]; then 967 return 0 968 else 969 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." 970 exit 971 fi 972 fi 973} 974 975function updateLocalGradleProperties() { 976 export ANDROID_HOME="${sdkDir}" 977 ( cat <<EOF 978sdk.dir=${sdkDir} 979EOF 980) > "$projectDir/local.properties" 981 ( cat <<EOF 982compile.sdk.preview=${compileSdkPreview} 983compile.sdk.version=${compileSdkVersion} 984EOF 985) > "$projectDir/studiow.properties" 986} 987 988function updateStudioConfig() { 989 local legacy_config_dir="${studioHomeDir}/config" 990 local config_dir="${androidStudioSettingsDir}/config" 991 if [[ "${usingDefaultSettingsDir}" == 'true' && -d "$legacy_config_dir" ]]; then 992 local message=( 993 '\n-------------------------------------------------------------------------------------\n\n' 994 "${YELLOW}WARNING:${NC} You have config files stored in ~/.AndroidStudioSystemUI/config " 995 'which will NOT be used by this instance of sysui-studio. ' 996 'To support multi-branch development, ' 997 "the config directory has moved to \$TOP${config_dir#"$androidBuildTop"}" 998 '\n\n' 999 'Each tree now has its own config directory. This means you may need to change your ' 1000 'settings for Android Studio multiple times (per branch) if, for example, you want to ' 1001 'change the default keymap.' 1002 '\n\n' 1003 'Your existing configs have NOT been automatically moved or deleted for the following ' 1004 'reasons:' 1005 '\n\n' 1006 ' 1) You may want to inspect your old settings\n' 1007 ' 2) The old config may still be used by branches on revisions predate this change.' 1008 '\n\n' 1009 'For more info, see http://b/249826650' 1010 '\n\n' 1011 'To make this warning go away, delete ~/.AndroidStudioSystemUI/config' 1012 '\n\n' 1013 'To launch Android Studio with the old config, run the following:' 1014 '\n' 1015 '\n studiow --settings-dir ~/.AndroidStudioSystemUI' 1016 '\n' 1017 '\nYou can then export your old settings using File > Manage IDE Settings > Export Settings' 1018 '\n' 1019 '\n-------------------------------------------------------------------------------------\n\n' 1020 1021 ) 1022 local fmt_cmd=(fmt -w 100 --split-only) 1023 if [ "$osName" == "mac" ]; then 1024 fmt_cmd=(fmt -w 100) 1025 fi 1026 printf "$(printf %s "${message[@]}")" | ${fmt_cmd[@]} 1027 fi 1028 1029 mkdir -p "${config_dir}" 1030 # Disable update checks 1031 copyFileIfAbsent $scriptDir/development/studio/updates.xml "${androidStudioSettingsDir}/updates.xml" 1032 # Disable instant run 1033 copyFileIfAbsent $scriptDir/development/studio/instant-run.xml $config_dir/options/instant-run.xml 1034 # Copy android code style 1035 copyFileIfAbsent $scriptDir/development/studio/AndroidStyle.xml $config_dir/codestyles/AndroidStyle.xml 1036 copyFileIfAbsent $scriptDir/development/studio/code.style.schemes.xml $config_dir/options/code.style.schemes.xml 1037 # Disable notification to update gradle 1038 copyFileIfAbsent $scriptDir/development/studio/notifications.xml $config_dir/options/notifications.xml 1039 1040 # Disable dialog that asks to trust the project 1041 local trustedPathsFile="$config_dir/options/trusted-paths.xml" 1042 if [ ! -f $trustedPathsFile ]; then 1043 sed "s|__PROJECT_DIR_PLACEHOLDER__|$projectDir|" $scriptDir/development/studio/trusted-paths.xml > $trustedPathsFile 1044 fi 1045 1046 # Disable dialog that asks whether to use Studio SDK or Project SDK 1047 local jdkPathsFile="$config_dir/options/jdk.table.xml" 1048 if [ ! -f $jdkPathsFile ]; then 1049 sed "s|__SDK_DIR_PLACEHOLDER__|$sdkDir|g;s|__SDK_VERSION_NAME_PLACEHOLDER__|$compileSdkVersion|g;" $scriptDir/development/studio/jdk.table.xml > $jdkPathsFile 1050 fi 1051} 1052 1053function main() { 1054 parseOptions "$@" 1055 1056 if [ "${uninstallAndroidStudio}" == "true" ]; then 1057 runUninstallAndroidStudio 1058 exit 1059 fi 1060 1061 if [ "${cleanProjectFiles}" == "true" ]; then 1062 runCleanProjectFiles 1063 exit 1064 fi 1065 1066 assertHasAdbRoot 1067 setupBuildSrcSymlinks 1068 1069 if [ "${downloadStudioZip}" == "true" ]; then 1070 fetchStudioUrl 1071 1072 studioDestName="$(basename ${studioUrl})" 1073 studioZipPath="${studioSetupDir}/${studioDestName}" 1074 studioUnzippedPath="$(echo ${studioZipPath} | sed 's/\.zip$//' | sed 's/\.tar\.gz$//')" 1075 fi 1076 1077 # Checks if a platform SDK already exists. If it doesn't, automatically adjust the flags so that 1078 # an SDK is generated by pulling files off the device. 1079 if [[ -z "$updateSdk" ]]; then 1080 if [[ ! -f $platformDir/android.jar ]]; then 1081 printInfo "Android SDK not found. Automatically appending '--update-sdk adb' to the argument list." 1082 updateSdk='adb' 1083 else 1084 checkSdkNeedsUpdate 1085 fi 1086 fi 1087 1088 checkJdkVersion 1089 1090 if [ -n "${updateSdk}" ]; then 1091 createPlatformSdk 1092 updateLocalGradleProperties 1093 fi 1094 1095 if [ "${downloadStudioZip}" == "true" ]; then 1096 updateStudio 1097 createDesktopEntry 1098 fi 1099 if [ "${runStudio}" == "true" ]; then 1100 checkLicenseAgreement 1101 updateLocalGradleProperties 1102 updateStudioConfig 1103 runStudio 1104 fi 1105} 1106 1107function createDesktopEntry() { 1108 studiow_path="${scriptDir}/studiow" 1109 studiow_icon="${studioUnzippedPath}/android-studio/bin/studio.png" 1110 targetDir="${HOME}/.local/share/applications" 1111 mkdir -p "$targetDir" 1112 cat "${scriptDir}/MPStudio.desktop" | \ 1113 sed "s|%STUDIOW_PATH%|${studiow_path}|g" | \ 1114 sed "s|%ANDROID_TOP%|${androidBuildTop}|g" | \ 1115 sed "s|%STUDIOW_ICON%|${studiow_icon}|g" > "${targetDir}/MPStudio.desktop" 1116} 1117 1118main "$@" 1119