blob: 4f9aff30e60052fb8d7bd07375fa1807223e3f44 [file] [log] [blame]
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +01001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.build.studio
18
Jim Sproch3c4a8582019-10-28 04:01:07 -070019import androidx.build.SupportConfig
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +010020import org.gradle.api.DefaultTask
21import org.gradle.api.Project
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +000022import org.gradle.api.internal.tasks.userinput.UserInputHandler
23import org.gradle.api.tasks.Internal
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +010024import org.gradle.api.tasks.TaskAction
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +000025import org.gradle.internal.service.ServiceRegistry
26import java.io.File
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +010027
28/**
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +000029 * Base task with common logic for updating and launching studio in both the frameworks/support
30 * project and the frameworks/support/ui project. Project-specific configuration is provided by
31 * [RootStudioTask] and [ComposeStudioTask].
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +010032 */
33abstract class StudioTask : DefaultTask() {
34
35 // TODO: support -y and --update-only options? Can use @Option for this
36 @TaskAction
37 fun studiow() {
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +000038 update()
39 launch()
40 }
41
42 private val platformUtilities by lazy {
43 StudioPlatformUtilities.get(projectRoot, studioInstallationDir)
44 }
45
46 @get:Internal
47 protected val projectRoot: File = project.rootDir
48
49 private val studioVersions by lazy { StudioVersions.get() }
50
51 /**
52 * Directory name (not path) that Studio will be unzipped into.
53 */
54 private val studioDirectoryName: String
55 get() {
56 val osName = StudioPlatformUtilities.osName
57 with(studioVersions) {
58 return "android-studio-ide-$ideaMajorVersion.$studioBuildNumber-$osName"
59 }
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +010060 }
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +000061
62 /**
63 * Filename (not path) of the Studio archive
64 */
65 private val studioArchiveName: String
66 get() = studioDirectoryName + platformUtilities.archiveExtension
67
68 /**
69 * The install directory containing Studio
70 *
71 * Note: Given that the contents of this directory changes a lot, we don't want to annotate this
72 * property for task avoidance - it's not stable enough for us to get any value out of this.
73 */
74 private val studioInstallationDir by lazy { File(projectRoot, "studio/$studioDirectoryName") }
75
76 /**
77 * Absolute path of the Studio archive
78 */
79 private val studioArchivePath: String by lazy {
80 File(studioInstallationDir.parentFile, studioArchiveName).absolutePath
81 }
82
83 /**
84 * The idea.properties file that we want to tell Studio to use
85 */
86 @get:Internal
87 protected abstract val ideaProperties: File
88
89 /**
90 * [StudioArchiveCreator] that will ensure that an archive is present at [studioArchivePath]
91 */
92 @get:Internal
93 protected abstract val studioArchiveCreator: StudioArchiveCreator
94
95 /**
96 * Updates the Studio installation and removes any old installation files if they exist.
97 */
98 private fun update() {
99 if (!studioInstallationDir.exists()) {
100 // Create installation directory and any needed parent directories
101 studioInstallationDir.mkdirs()
102 // Attempt to remove any old installations in the parent studio/ folder
103 removeOldInstallations()
104 studioArchiveCreator(project, studioVersions, studioArchiveName, studioArchivePath)
105 println("Extracting archive...")
106 extractStudioArchive()
107 with(platformUtilities) { updateJvmHeapSize() }
108 }
109 }
110
111 /**
112 * Launches Studio if the user accepts / has accepted the license agreement.
113 */
114 private fun launch() {
115 if (checkLicenseAgreement(services)) {
116 println("Launching studio...")
117 launchStudio()
118 } else {
119 println("Exiting without launching studio...")
120 }
121 }
122
123 private fun launchStudio() {
124 val supportRootDir = SupportConfig.getSupportRoot(project)
125 val vmOptions = File(supportRootDir, "development/studio/studio.vmoptions")
126
127 ProcessBuilder().apply {
128 inheritIO()
129 with(platformUtilities) { command(launchCommandArguments) }
130
131 // Some environment properties are already set in gradlew, and these by default carry
132 // through here
133 // TODO: idea.properties should be different for main and ui, fix
134 val additionalStudioEnvironmentProperties = mapOf(
135 "STUDIO_PROPERTIES" to ideaProperties.absolutePath,
136 "STUDIO_VM_OPTIONS" to vmOptions.absolutePath,
137 // This environment variable prevents Studio from showing IDE inspection warnings
138 // for nullability issues, if the context is deprecated. This environment variable
139 // is consumed by InteroperabilityDetector.kt
140 "ANDROID_LINT_NULLNESS_IGNORE_DEPRECATED" to "true"
141 )
142
143 environment().putAll(additionalStudioEnvironmentProperties)
144 start()
145 }
146 }
147
148 private fun checkLicenseAgreement(services: ServiceRegistry): Boolean {
149 val licenseAcceptedFile = File("$studioInstallationDir/STUDIOW_LICENSE_ACCEPTED")
150 if (!licenseAcceptedFile.exists()) {
151 val licensePath = with(platformUtilities) { licensePath }
152
153 val userInput = services.get(UserInputHandler::class.java)
154 val acceptAgreement = userInput.askYesNoQuestion(
155 "Do you accept the license agreement at $licensePath?",
156 /* default answer*/ false
157 )
158 if (!acceptAgreement) {
159 return false
160 }
161 licenseAcceptedFile.createNewFile()
162 }
163 return true
164 }
165
166 private fun extractStudioArchive() {
167 val fromPath = studioArchivePath
168 val toPath = studioInstallationDir.absolutePath
169 println("Extracting to $toPath...")
170 project.exec { execSpec -> platformUtilities.extractArchive(fromPath, toPath, execSpec) }
171 // Remove studio archive once done
172 File(studioArchivePath).delete()
173 }
174
175 private fun removeOldInstallations() {
176 val parentFile = studioInstallationDir.parentFile
177 parentFile.walk().maxDepth(1)
178 .filter { file ->
179 // Remove any files that aren't either the directory / archive matching the
180 // current version, and also ignore the parent `studio/` directory
181 !file.name.contains(studioInstallationDir.name) && file != parentFile
182 }
183 .forEach { file ->
184 println("Removing old installation file ${file.absolutePath}")
185 file.deleteRecursively()
186 }
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +0100187 }
188
189 companion object {
190 private const val STUDIO_TASK = "studio"
191
192 fun Project.registerStudioTask() {
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +0000193 if (SupportConfig.isUiProject()) {
194 tasks.register(STUDIO_TASK, ComposeStudioTask::class.java)
195 } else {
196 tasks.register(STUDIO_TASK, RootStudioTask::class.java)
Jim Sproch3c4a8582019-10-28 04:01:07 -0700197 }
Louis Pullen-Freilich1dff6782019-10-17 18:12:39 +0100198 }
199 }
200}
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +0000201
202/**
203 * Task for launching studio in the frameworks/support project
204 */
205open class RootStudioTask : StudioTask() {
206 override val studioArchiveCreator = UrlArchiveCreator
207 override val ideaProperties get() = projectRoot.resolve("development/studio/idea.properties")
208}
209
210/**
211 * Task for launching studio in the frameworks/support/ui (Compose) project
212 */
213open class ComposeStudioTask : StudioTask() {
Louis Pullen-Freilich659cf442020-02-14 23:46:20 +0000214 override val studioArchiveCreator = UrlArchiveCreator
Louis Pullen-Freilichbee273e22020-02-06 15:18:10 +0000215 override val ideaProperties get() = projectRoot.resolve("idea.properties")
216}