Skip to content
This repository was archived by the owner on Apr 19, 2018. It is now read-only.
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ local.properties
.idea
build
*.iml
*.ipr
*.iws
classes
62 changes: 62 additions & 0 deletions src/main/groovy/com/jakewharton/sdkmanager/SdkManagerAction.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.jakewharton.sdkmanager

import com.jakewharton.sdkmanager.internal.PackageResolver
import com.jakewharton.sdkmanager.internal.SdkResolver
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.StopExecutionException

import java.util.concurrent.TimeUnit

import static com.jakewharton.sdkmanager.internal.Util.hasAndroidPlugin
import static com.jakewharton.sdkmanager.internal.Util.isOfflineBuild

/**
* {@link SdkManagerAction} is the standalone
* {@link org.gradle.api.Action} implementation in this plugin.
*/
class SdkManagerAction implements Action<Project> {
final Logger log = Logging.getLogger SdkManagerPlugin
@Override
void execute( Project project ) {
if ( hasAndroidPlugin( project ) ) {
throw new StopExecutionException(
"Must be applied before 'android' or 'android-library' plugin." )
}

if ( isOfflineBuild( project ) ) {
log.debug 'Offline build. Skipping package resolution.'
return
}

// Eager resolve the SDK and local.properties pointer.
def sdk
time "SDK resolve", {
sdk = SdkResolver.resolve project
}

// Defer resolving SDK package dependencies until after the model is finalized.
project.afterEvaluate {
time "Package resolve", {
PackageResolver.resolve project, sdk
}
}
}

/**
* Executes task closure and logs the
* time it took for it to run in nanoseconds.
*
* @param name the human readable name of the task.
* @param task the task closure to run.
*/
def time( String name, Closure task ) {
long before = java.lang.System.nanoTime()
task.run()
long after = java.lang.System.nanoTime()
long took = TimeUnit.NANOSECONDS.toMillis( after - before )
log.info "$name took $took ms."
}
}
58 changes: 4 additions & 54 deletions src/main/groovy/com/jakewharton/sdkmanager/SdkManagerPlugin.groovy
Original file line number Diff line number Diff line change
@@ -1,63 +1,13 @@
package com.jakewharton.sdkmanager

import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
import com.jakewharton.sdkmanager.internal.PackageResolver
import com.jakewharton.sdkmanager.internal.SdkResolver
import com.jakewharton.sdkmanager.internal.System
import java.util.concurrent.TimeUnit
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.StopExecutionException

/**
* {@link SdkManagerPlugin} is the gradle plugin class.
*/
class SdkManagerPlugin implements Plugin<Project> {
final Logger log = Logging.getLogger SdkManagerPlugin

@Override void apply(Project project) {
if (hasAndroidPlugin(project)) {
throw new StopExecutionException(
"Must be applied before 'android' or 'android-library' plugin.")
}

if (isOfflineBuild(project)) {
log.debug 'Offline build. Skipping package resolution.'
return
}

// Eager resolve the SDK and local.properties pointer.
def sdk
time "SDK resolve", {
sdk = SdkResolver.resolve project
}

// Defer resolving SDK package dependencies until after the model is finalized.
project.afterEvaluate {
if (!hasAndroidPlugin(project)) {
log.debug 'No Android plugin detecting. Skipping package resolution.'
return
}

time "Package resolve", {
PackageResolver.resolve project, sdk
}
}
}

def time(String name, Closure task) {
long before = java.lang.System.nanoTime()
task.run()
long after = java.lang.System.nanoTime()
long took = TimeUnit.NANOSECONDS.toMillis(after - before)
log.info "$name took $took ms."
}

static def hasAndroidPlugin(Project project) {
return project.plugins.hasPlugin(AppPlugin) || project.plugins.hasPlugin(LibraryPlugin)
}

static def isOfflineBuild(Project project) {
return project.getGradle().getStartParameter().isOffline()
new SdkManagerAction().execute( project )
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ import org.gradle.api.logging.Logging
import static com.android.SdkConstants.FD_TOOLS
import static com.android.SdkConstants.androidCmdName

/**
* {@link AndroidCommand} runs commands against the Android SDK Manager.
*/
interface AndroidCommand {
/**
* Runs an update command with the given filter.
*
* @param filter the filter to use.
* @return the exit value of the update command.
*/
int update(String filter);

static final class Real implements AndroidCommand {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
package com.jakewharton.sdkmanager.internal

/**
* {@link Downloader} is an interface used to
* download an android SDK to a specific destination.
*/
interface Downloader {
/**
* Download an android SDK to dest.
*
* @param dest the location to store SDK in.
*/
void download(File dest)

/**
* The actual implementation of the {@link Downloader}.
* Simply delegates to {@link SdkDownload}.
*/
static final class Real implements Downloader {
@Override void download(File dest) {
SdkDownload.get().download(dest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,33 @@ import static com.android.SdkConstants.FD_M2_REPOSITORY
import static com.android.SdkConstants.FD_PLATFORMS
import static com.android.SdkConstants.FD_ADDONS
import static com.android.SdkConstants.FD_PLATFORM_TOOLS

import static com.jakewharton.sdkmanager.internal.Util.hasAndroidPlugin

/**
* {@link PackageResolver} resolves, verifies and downloads (if missing)
* the build tools (1), platform tools, compile version (1),
* support libraries, google play services libraries.
*
* (1): only resolved if applied on a project on which
* an android plugin (app/library) has been applied on.
*/
class PackageResolver {
/**
* Resolves packages for project with the android SDK expected at sdk.
*
* @param project The {@link Project} to resolve for.
* @param sdk The android SDK path is expected in this path.
*/
static void resolve(Project project, File sdk) {
new PackageResolver(project, sdk, new AndroidCommand.Real(sdk, new System.Real())).resolve()
}

/**
* Checks if the given folder exists and is non-empty.
*
* @param folder the folder to check.
* @return true if it existed and was non-empty.
*/
static boolean folderExists(File folder) {
return folder.exists() && folder.list().length != 0
}
Expand All @@ -37,6 +58,14 @@ class PackageResolver {
final File googleRepositoryDir
final AndroidCommand androidCommand

/**
* Constructs a {@link PackageResolver} given a project to resolve for,
* a android sdk path, and an {@link AndroidCommand} to use.
*
* @param project The {@link Project} to resolve for.
* @param sdk The android SDK path is expected in this path.
* @param androidCommand The {@link AndroidCommand} to use for running commands against the SDK.
*/
PackageResolver(Project project, File sdk, AndroidCommand androidCommand) {
this.sdk = sdk
this.project = project
Expand All @@ -54,14 +83,41 @@ class PackageResolver {
googleRepositoryDir = new File(googleExtrasDir, FD_M2_REPOSITORY)
}

/**
* Resolves everything that can be resolved and skips what can't.
* @see PackageResolver PackageResolver for notes about what is resolved when.
*/
def resolve() {
resolveBuildTools()
resolvePlatformTools()
resolveCompileVersion()
resolveSupportLibraryRepository()
resolvePlayServiceRepository()
resolving('build-tools', true, this.&resolveBuildTools)
resolving('platform-tools', false, this.&resolvePlatformTools)
resolving('compile-version', true, this.&resolveCompileVersion)
resolving('support-library', false, this.&resolveSupportLibraryRepository)
resolving('play-services', false, this.&resolvePlayServiceRepository)
}

/**
* Conditionally runs a "resolving" closure under name.
* The closure is skipped if android was required,
* but the android plugin wasn't applied.
*
* @param name The name to give the closure to run.
* @param requireAndroid Whether or not to require that the
* android plugin be applied for the closure to run.
* @param closure The closure to run should the conditions for running be met.
*/
def resolving( String name, boolean requireAndroid, Closure closure ) {
if ( requireAndroid && !hasAndroidPlugin( project ) ) {
log.debug "Skipping: $name, no android plugin detected"
} else {
log.debug "Resolving: $name"
closure()
}
}

/**
* Resolves the build tools using the revision specified in android.buildToolsRevision.
* If missing, the revision will be downloaded.
*/
def resolveBuildTools() {
def buildToolsRevision = project.android.buildToolsRevision
log.debug "Build tools version: $buildToolsRevision"
Expand All @@ -80,6 +136,9 @@ class PackageResolver {
}
}

/**
* Resolves the platform tools and downloads them if missing.
*/
def resolvePlatformTools() {
if (folderExists(platformToolsDir)) {
log.debug 'Platform tools found!'
Expand All @@ -94,6 +153,10 @@ class PackageResolver {
}
}

/**
* Resolves the compile version downloads it if missing.
* Additionally, the google SDK is installed if missing.
*/
def resolveCompileVersion() {
String compileVersion = project.android.compileSdkVersion
log.debug "Compile API version: $compileVersion"
Expand All @@ -113,6 +176,12 @@ class PackageResolver {
}
}

/**
* Installs compilation API version at baseDir if not already installed.
*
* @param baseDir where the compilation API versions are stored.
* @param version the compilation API version that will be installed if needed.
*/
def installIfMissing(baseDir, version) {
def existingDir = new File(baseDir, version)
if (folderExists(existingDir)) {
Expand All @@ -128,6 +197,13 @@ class PackageResolver {
}
}

/**
* Resolves the support libraries if the project
* applied upon has one of them as a dependency.
*
* If missing, they will be downloaded, as well
* as using that path as a local maven repository.
*/
def resolveSupportLibraryRepository() {
def supportLibraryDeps = findDependenciesWithGroup 'com.android.support'
if (supportLibraryDeps.isEmpty()) {
Expand Down Expand Up @@ -158,6 +234,13 @@ class PackageResolver {
}
}

/**
* Resolves the google play services if the project
* applied upon has one of them as a dependency.
*
* If missing, they will be downloaded, as well
* as using that path as a local maven repository.
*/
def resolvePlayServiceRepository() {
def playServicesDeps = findDependenciesWithGroup 'com.google.android.gms'
if (playServicesDeps.isEmpty()) {
Expand Down Expand Up @@ -194,6 +277,12 @@ class PackageResolver {
}
}

/**
* Finds all dependencies in all configurations that has group.
*
* @param group The group to match on.
* @return The list of dependencies.
*/
def findDependenciesWithGroup(String group) {
def deps = []
for (Configuration configuration : project.configurations) {
Expand All @@ -206,6 +295,13 @@ class PackageResolver {
return deps
}

/**
* Checks if the dependencies given by deps
* is actually available on the file system.
*
* @param deps The dependencies to check.
* @return true if available.
*/
def dependenciesAvailable(def deps) {
try {
project.configurations.detachedConfiguration(deps as Dependency[]).files
Expand Down
Loading