Skip to content

Commit

Permalink
Merge pull request #1222 from android/memory-footprint
Browse files Browse the repository at this point in the history
Adds support for Memory Footprint tool
  • Loading branch information
garanj authored Dec 8, 2024
2 parents 16f2a63 + 8ffb7f8 commit eee6fba
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,36 @@
import com.android.build.api.variant.BuiltArtifactsLoader
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.process.ExecOperations
import java.io.File
import javax.inject.Inject

const val SET_WATCH_FACE_CMD =
"shell am broadcast -a com.google.android.wearable.app.DEBUG_SURFACE --es operation set-watchface --es watchFaceId %s"

/**
* Installs and sets watch face on an attached device via ADB.
*/
open class AdbInstallTask : DefaultTask() {
abstract class AdbInstallTask() : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations

@set:Option(option = "device", description = "The ADB device to install on")
@get:Input
var device: String = ""

@get:Input
lateinit var apkLocation: Provider<Directory>
@get:InputDirectory
abstract val apkLocation: DirectoryProperty

@get:Input
lateinit var artifactLoader: BuiltArtifactsLoader
@get:Internal
abstract val artifactsLoader: Property<BuiltArtifactsLoader>

// As this task has no outputs defined, it will always be executed, which is desirable as the
// APK should be installed even if the APK itself hasn't changed. (It may have been removed from
Expand All @@ -48,7 +55,8 @@ open class AdbInstallTask : DefaultTask() {
@TaskAction
fun install() {
val artifacts =
artifactLoader.load(apkLocation.get()) ?: throw GradleException("Cannot load APKs")
artifactsLoader.get().load(apkLocation.get())
?: throw GradleException("Cannot load APKs")
if (artifacts.elements.size != 1)
throw GradleException("Expected only one APK!")
val apkPath = File(artifacts.elements.single().outputFile).toPath()
Expand All @@ -57,13 +65,13 @@ open class AdbInstallTask : DefaultTask() {
val installWatchFaceCmd = deviceArg + "install $apkPath"
val setWatchFaceCmd = deviceArg + SET_WATCH_FACE_CMD.format(artifacts.applicationId)

project.exec {
execOperations.exec {
val argsList = installWatchFaceCmd.split(" ").toList()
it.commandLine("adb")
it.args(argsList)
}

project.exec {
execOperations.exec {
val argsList = setWatchFaceCmd.split(" ").toList()
it.commandLine("adb")
it.args(argsList)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.android.build.api.variant.BuiltArtifactsLoader
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import java.io.File
import javax.inject.Inject

/**
* Runs the Memory Footprint checker tool.
*/
@CacheableTask
abstract class MemoryFootprintTask() : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations

@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val apkLocation: DirectoryProperty

@get:Internal
abstract val artifactsLoader: Property<BuiltArtifactsLoader>

@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val memoryFootprintJarPath: RegularFileProperty

@get:OutputFile
abstract val memoryFootprintOutputFile: RegularFileProperty

@get:Input
abstract val wffVersion: Property<Int>

@TaskAction
fun runMemoryFootprintCheck() {
val artifacts =
artifactsLoader.get().load(apkLocation.get())
?: throw GradleException("Cannot load APKs")
if (artifacts.elements.size != 1)
throw GradleException("Expected only one APK!")
val apkPath = File(artifacts.elements.single().outputFile).toPath()

val result = execOperations.javaexec {
it.classpath = project.files(memoryFootprintJarPath)
it.args(
"--schema-version",
wffVersion.get().toString(),
"--watch-face",
apkPath,
"--verbose"
)
}
memoryFootprintOutputFile.get().asFile.writeText(result.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@ import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import javax.inject.Inject

/**
* Runs the validator against WFF XML files.
*/
@CacheableTask
abstract class ValidateWffFilesTask : DefaultTask() {
init {
this.outputs.upToDateWhen { true }
}
@get:Inject
abstract val execOperations: ExecOperations

@get:InputFiles
@get:Incremental
Expand All @@ -47,6 +49,9 @@ abstract class ValidateWffFilesTask : DefaultTask() {
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val validatorJarPath: RegularFileProperty

@get:OutputFile
abstract val validatorOutputFile: RegularFileProperty

@get:Input
abstract val wffVersion: Property<Int>

Expand All @@ -55,12 +60,13 @@ abstract class ValidateWffFilesTask : DefaultTask() {
val changedFiles = inputs.getFileChanges(wffFiles)
changedFiles.forEach { change ->
if (change.changeType != ChangeType.REMOVED) {
project.javaexec {
val result = execOperations.javaexec {
it.classpath = project.files(validatorJarPath)
// Stop-on-fail ensures that the Gradle Task throws an exception when a WFF file fails
// to validate.
it.args(wffVersion.get().toString(), "--stop-on-fail", change.file.absolutePath)
}
validatorOutputFile.get().asFile.writeText(result.toString())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,27 @@ import org.gradle.api.tasks.TaskAction
import java.nio.file.Path

/**
* Downloads the WFF validator for use in the build process.
* Downloads a WFF-related tool for use in the build process.
*/
@CacheableTask
abstract class ValidatorDownloadTask : DefaultTask() {
abstract class WffToolDownloadTask : DefaultTask() {
@get:OutputFile
abstract val validatorJarPath: RegularFileProperty
abstract val toolJarPath: RegularFileProperty

@get:Input
abstract val validatorUrl: Property<String>
abstract val toolUrl: Property<String>

@TaskAction
fun install() {
downloadFileToPath(validatorJarPath.get().asFile.toPath(), validatorUrl.get())
downloadFileToPath(toolJarPath.get().asFile.toPath(), toolUrl.get())
}

private fun downloadFileToPath(filePath: Path, url: String) {
val client = HttpClient { expectSuccess = true }
val file = filePath.toFile()

// The validator generally won't exist already -- but there is the potential for the input
// URL to change, in which case the existing validator should be removed.
// The tool generally won't exist already -- but there is the potential for the input URL to
// change, in which case the existing validator should be removed.
if (file.exists()) {
file.delete()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,61 @@ import org.gradle.kotlin.dsl.register
const val ASSEMBLE_DEBUG_TASK = "assembleDebug"
const val BUNDLE_DEBUG_TASK = "bundleDebug"
const val VALIDATE_TASK = "validateWff"
const val MEMORY_FOOTPRINT_TASK = "memoryFootprint"
const val DOWNLOAD_VALIDATOR_TASK = "downloadWffValidator"
const val DOWNLOAD_MEMORY_FOOTPRINT_TASK = "downloadMemoryFootprint"
const val INSTALL_TASK = "validateWffAndInstall"

// TODO move from here
private const val DEFAULT_RELEASE_TAG = "release"
private const val VALIDATOR_URL =
"https://github.com/google/watchface/releases/download/release/dwf-format-2-validator-1.0.jar"
private const val VALIDATOR_PATH = "validator/validator.jar"
"https://github.com/google/watchface/releases/download/%s/dwf-format-2-validator-1.0.jar"
private const val VALIDATOR_PATH = "validator/validator-%s.jar"
private const val VALIDATOR_OUTPUT_PATH = "validator/validator-%s.txt"

private const val MEMORY_FOOTPRINT_URL =
"https://github.com/google/watchface/releases/download/%s/memory-footprint.jar"
private const val MEMORY_FOOTPRINT_PATH = "memory-footprint/memory-footprint-%s.jar"
private const val MEMORY_FOOTPRINT_OUTPUT_PATH = "memory-footprint/memory-footprint-%s.txt"

class WffValidatorPlugin : Plugin<Project> {
private lateinit var manifestPath: String

override fun apply(project: Project) {
val downloadTask = project.tasks.register<ValidatorDownloadTask>(DOWNLOAD_VALIDATOR_TASK) {
val validatorPath = project.layout.buildDirectory.file(VALIDATOR_PATH)
this.validatorUrl.set(VALIDATOR_URL)
this.validatorJarPath.set(validatorPath)
}
// Parameter optionally set on the command-line for the whole project, indicating which tag
// to use for releases downloaded from the watch face repo. Defaults to "release" but could
// be set to "latest".
val toolReleaseTag = project.properties["release"] ?: DEFAULT_RELEASE_TAG

val validatorUrl = VALIDATOR_URL.format(toolReleaseTag)
val validatorJar = VALIDATOR_PATH.format(toolReleaseTag)
val validatorOutput = VALIDATOR_OUTPUT_PATH.format(toolReleaseTag)

val memoryFootprintUrl = MEMORY_FOOTPRINT_URL.format(toolReleaseTag)
val memoryFootprintJar = MEMORY_FOOTPRINT_PATH.format(toolReleaseTag)
val memoryFootprintOutput = MEMORY_FOOTPRINT_OUTPUT_PATH.format(toolReleaseTag)

val validatorDownloadTask =
project.tasks.register<WffToolDownloadTask>(DOWNLOAD_VALIDATOR_TASK) {
val validatorPath = project.layout.buildDirectory.file(validatorJar)
this.toolUrl.set(validatorUrl)
this.toolJarPath.set(validatorPath)
}

val memoryFootprintDownloadTask =
project.tasks.register<WffToolDownloadTask>(DOWNLOAD_MEMORY_FOOTPRINT_TASK) {
val memoryFootprintPath = project.layout.buildDirectory.file(memoryFootprintJar)
this.toolUrl.set(memoryFootprintUrl)
this.toolJarPath.set(memoryFootprintPath)
}

project.tasks.register<ValidateWffFilesTask>(VALIDATE_TASK) {
val wffFileCollection = getWffFileCollection(project)
if (wffFileCollection.isEmpty) {
throw GradleException("No WFF XML files found in project!")
}
validatorJarPath.set(downloadTask.get().validatorJarPath)
val validatorOutputPath = project.layout.buildDirectory.file(validatorOutput)
validatorJarPath.set(validatorDownloadTask.get().toolJarPath)
validatorOutputFile.set(validatorOutputPath)
wffFiles.setFrom(wffFileCollection)
wffVersion.set(getWffVersion(manifestPath))
}
Expand All @@ -79,8 +110,18 @@ class WffValidatorPlugin : Plugin<Project> {

// Register additional task that allows for installing and setting of watch face.
proj.tasks.register<AdbInstallTask>(INSTALL_TASK) {
apkLocation = apkDirectoryProvider
artifactLoader = loader
apkLocation.set(apkDirectoryProvider)
artifactsLoader.set(loader)
dependsOn(ASSEMBLE_DEBUG_TASK)
}

proj.tasks.register<MemoryFootprintTask>(MEMORY_FOOTPRINT_TASK) {
val memoryFootprintOutputPath = project.layout.buildDirectory.file(memoryFootprintOutput)
memoryFootprintJarPath.set(memoryFootprintDownloadTask.get().toolJarPath)
apkLocation.set(apkDirectoryProvider)
artifactsLoader.set(loader)
wffVersion.set(getWffVersion(manifestPath))
memoryFootprintOutputFile.set(memoryFootprintOutputPath)
dependsOn(ASSEMBLE_DEBUG_TASK)
}
}
Expand All @@ -93,5 +134,3 @@ class WffValidatorPlugin : Plugin<Project> {
.filter { it.parentFile.name.startsWith("raw") }
}
}


0 comments on commit eee6fba

Please sign in to comment.