| /* |
| * Copyright 2012-present Facebook, Inc. |
| * |
| * 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 |
| * |
| * http://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. |
| */ |
| |
| package com.facebook.buck.util; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Function; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FilenameFilter; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Represents a platform to target for Android. Eventually, it should be possible to construct an |
| * arbitrary platform target, but currently, we only recognize a fixed set of targets. |
| */ |
| public class AndroidPlatformTarget { |
| |
| public static final String DEFAULT_ANDROID_PLATFORM_TARGET = "Google Inc.:Google APIs:18"; |
| |
| private final String name; |
| private final File androidJar; |
| private final List<File> bootclasspathEntries; |
| private final File aaptExecutable; |
| private final File adbExecutable; |
| private final File aidlExecutable; |
| private final File zipalignExecutable; |
| private final File dxExecutable; |
| private final File androidFrameworkIdlFile; |
| private final File proguardJar; |
| private final File proguardConfig; |
| private final File optimizedProguardConfig; |
| |
| private AndroidPlatformTarget( |
| String name, |
| File androidJar, |
| List<File> bootclasspathEntries, |
| File aaptExecutable, |
| File adbExecutable, |
| File aidlExecutable, |
| File zipalignExecutable, |
| File dxExecutable, |
| File androidFrameworkIdlFile, |
| File proguardJar, |
| File proguardConfig, |
| File optimizedProguardConfig) { |
| this.name = Preconditions.checkNotNull(name); |
| this.androidJar = Preconditions.checkNotNull(androidJar); |
| this.bootclasspathEntries = ImmutableList.copyOf(bootclasspathEntries); |
| this.aaptExecutable = Preconditions.checkNotNull(aaptExecutable); |
| this.adbExecutable = Preconditions.checkNotNull(adbExecutable); |
| this.aidlExecutable = Preconditions.checkNotNull(aidlExecutable); |
| this.zipalignExecutable = Preconditions.checkNotNull(zipalignExecutable); |
| this.dxExecutable = Preconditions.checkNotNull(dxExecutable); |
| this.androidFrameworkIdlFile = Preconditions.checkNotNull(androidFrameworkIdlFile); |
| this.proguardJar = Preconditions.checkNotNull(proguardJar); |
| this.proguardConfig = Preconditions.checkNotNull(proguardConfig); |
| this.optimizedProguardConfig = Preconditions.checkNotNull(optimizedProguardConfig); |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public String toString() { |
| return getName(); |
| } |
| |
| public File getAndroidJar() { |
| return androidJar; |
| } |
| |
| public List<File> getBootclasspathEntries() { |
| return bootclasspathEntries; |
| } |
| |
| public File getAaptExecutable() { |
| return aaptExecutable; |
| } |
| |
| public File getAdbExecutable() { |
| return adbExecutable; |
| } |
| |
| public File getAidlExecutable() { |
| return aidlExecutable; |
| } |
| |
| public File getZipalignExecutable() { |
| return zipalignExecutable; |
| } |
| |
| public File getDxExecutable() { |
| return dxExecutable; |
| } |
| |
| public File getAndroidFrameworkIdlFile() { |
| return androidFrameworkIdlFile; |
| } |
| |
| public File getProguardJar() { |
| return proguardJar; |
| } |
| |
| public File getProguardConfig() { |
| return proguardConfig; |
| } |
| |
| public File getOptimizedProguardConfig() { |
| return optimizedProguardConfig; |
| } |
| |
| /** |
| * @param platformId for the platform, such as "Google Inc.:Google APIs:16" |
| * @param androidSdkDir directory where the user's Android SDK is installed |
| */ |
| public static Optional<AndroidPlatformTarget> getTargetForId( |
| String platformId, |
| File androidSdkDir) { |
| Preconditions.checkNotNull(platformId); |
| Preconditions.checkNotNull(androidSdkDir); |
| |
| Pattern platformPattern = Pattern.compile("Google Inc\\.:Google APIs:(\\d+)"); |
| Matcher platformMatcher = platformPattern.matcher(platformId); |
| if (platformMatcher.matches()) { |
| try { |
| int apiLevel = Integer.parseInt(platformMatcher.group(1)); |
| return Optional.of(new AndroidWithGoogleApisFactory().newInstance(androidSdkDir, apiLevel)); |
| } catch (NumberFormatException e) { |
| return Optional.absent(); |
| } |
| } else { |
| return Optional.absent(); |
| } |
| } |
| |
| public static AndroidPlatformTarget getDefaultPlatformTarget(File androidSdkDir) { |
| return getTargetForId(DEFAULT_ANDROID_PLATFORM_TARGET, androidSdkDir).get(); |
| } |
| |
| private static interface Factory { |
| public AndroidPlatformTarget newInstance(File androidSdkDir, int apiLevel); |
| } |
| |
| /** |
| * Resolves all of the jarPaths against the androidSdkDir path. |
| * @return a mutable list |
| */ |
| private static LinkedList<File> resolvePaths(final File androidSdkDir, Set<String> jarPaths) { |
| return Lists.newLinkedList(Iterables.transform(jarPaths, new Function<String, File>() { |
| @Override |
| public File apply(String jarPath) { |
| File jar = new File(androidSdkDir, jarPath); |
| if (!jar.isFile()) { |
| throw new RuntimeException("File not found: " + jar.getAbsolutePath()); |
| } |
| return jar; |
| } |
| })); |
| } |
| |
| /** |
| * Given the path to the Android SDK as well as the platform path within the Android SDK, |
| * find all the files needed to create the {@link AndroidPlatformTarget}, assuming that the |
| * organization of the Android SDK conforms to the ordinary directory structure. |
| */ |
| @VisibleForTesting |
| static AndroidPlatformTarget createFromDefaultDirectoryStructure( |
| String name, |
| File androidSdkDir, |
| String platformDirectoryPath, |
| Set<String> additionalJarPaths) { |
| File platformDirectory = new File(androidSdkDir, platformDirectoryPath); |
| File androidJar = new File(platformDirectory, "android.jar"); |
| LinkedList<File> bootclasspathEntries = resolvePaths(androidSdkDir, additionalJarPaths); |
| |
| // Make sure android.jar is at the front of the bootclasspath. |
| bootclasspathEntries.addFirst(androidJar); |
| |
| File buildToolsDir = new File(androidSdkDir, "build-tools"); |
| |
| // This is the relative path under the Android SDK directory to the directory that contains the |
| // aapt, aidl, and dx executables. |
| String buildToolsPath; |
| |
| if (buildToolsDir.isDirectory()) { |
| // In older versions of the ADT that have been upgraded via the SDK manager, the build-tools |
| // directory appears to contain subfolders of the form "17.0.0". However, newer versions of |
| // the ADT that are downloaded directly from http://developer.android.com/ appear to have |
| // subfolders of the form android-4.2.2. We need to support both of these scenarios. |
| File[] directories = buildToolsDir.listFiles(new FileFilter() { |
| @Override |
| public boolean accept(File pathname) { |
| return pathname.isDirectory(); |
| } |
| }); |
| |
| if (directories.length == 0) { |
| throw new HumanReadableException( |
| Joiner.on(System.getProperty("line.separator")).join( |
| "%s was empty, but should have contained a subdirectory with build tools.", |
| "Install them using the Android SDK Manager (%s)."), |
| buildToolsDir.getAbsolutePath(), |
| new File(androidSdkDir, Joiner.on(File.separator).join("tools", "android")) |
| ); |
| } else { |
| File newestBuildToolsDir = pickNewestBuildToolsDir(ImmutableSet.copyOf(directories)); |
| buildToolsPath = "build-tools/" + newestBuildToolsDir.getName(); |
| } |
| } else { |
| buildToolsPath = "platform-tools"; |
| } |
| |
| File androidFrameworkIdlFile = new File(platformDirectory, "framework.aidl"); |
| File proguardJar = new File(androidSdkDir, "tools/proguard/lib/proguard.jar"); |
| File proguardConfig = new File(androidSdkDir, "tools/proguard/proguard-android.txt"); |
| File optimizedProguardConfig = |
| new File(androidSdkDir, "tools/proguard/proguard-android-optimize.txt"); |
| |
| return new AndroidPlatformTarget( |
| name, |
| androidJar, |
| bootclasspathEntries, |
| new File(androidSdkDir, buildToolsPath + "/aapt"), |
| new File(androidSdkDir, "platform-tools/adb"), |
| new File(androidSdkDir, buildToolsPath + "/aidl"), |
| new File(androidSdkDir, "tools/zipalign"), |
| new File(androidSdkDir, buildToolsPath + "/dx"), |
| androidFrameworkIdlFile, |
| proguardJar, |
| proguardConfig, |
| optimizedProguardConfig); |
| } |
| |
| private static File pickNewestBuildToolsDir(Set<File> directories) { |
| if (directories.size() == 1) { |
| return Iterables.getOnlyElement(directories); |
| } |
| |
| List<File> androidVersionDirectories = Lists.newArrayList(); |
| List<File> apiVersionDirectories = Lists.newArrayList(); |
| for (File dir : directories) { |
| if (dir.getName().startsWith("android-")) { |
| androidVersionDirectories.add(dir); |
| } else { |
| apiVersionDirectories.add(dir); |
| } |
| } |
| |
| final VersionStringComparator comparator = new VersionStringComparator(); |
| |
| // This is the directory from newer downloads from http://developer.android.com/, so prefer |
| // these. |
| if (!androidVersionDirectories.isEmpty()) { |
| Collections.sort(androidVersionDirectories, new Comparator<File>() { |
| @Override |
| public int compare(File a, File b) { |
| String versionA = a.getName().substring("android-".length()); |
| String versionB = b.getName().substring("android-".length()); |
| return comparator.compare(versionA, versionB); |
| } |
| }); |
| // Return the last element in the list. |
| return androidVersionDirectories.get(androidVersionDirectories.size() - 1); |
| } else { |
| Collections.sort(apiVersionDirectories, new Comparator<File>() { |
| @Override |
| public int compare(File a, File b) { |
| String versionA = a.getName(); |
| String versionB = b.getName(); |
| return comparator.compare(versionA, versionB); |
| } |
| }); |
| // Return the last element in the list. |
| return apiVersionDirectories.get(apiVersionDirectories.size() - 1); |
| } |
| } |
| |
| /** |
| * Factory to build an AndroidPlatformTarget that corresponds to a given Google API level. |
| */ |
| private static class AndroidWithGoogleApisFactory implements Factory { |
| |
| @Override |
| public AndroidPlatformTarget newInstance(File androidSdkDir, int apiLevel) { |
| String addonPath = String.format("/add-ons/addon-google_apis-google-%d/libs/", apiLevel); |
| File addonDirectory = new File(androidSdkDir.getPath() + addonPath); |
| String[] addonFiles; |
| |
| if (!addonDirectory.isDirectory() || |
| (addonFiles = addonDirectory.list(new AddonFilter())) == null || |
| addonFiles.length == 0) { |
| throw new HumanReadableException( |
| "Google APIs not found in %s.\n" + |
| "Please run '%s/tools/android sdk' and select both 'SDK Platform' and " + |
| "'Google APIs' under Android (API %d)", |
| addonDirectory.getAbsolutePath(), |
| androidSdkDir.getPath(), |
| apiLevel); |
| } |
| |
| ImmutableSet.Builder<String> builder = ImmutableSet.builder(); |
| |
| Arrays.sort(addonFiles); |
| for (String filename : addonFiles) { |
| builder.add(addonPath + filename); |
| } |
| Set<String> additionalJarPaths = builder.build(); |
| |
| return createFromDefaultDirectoryStructure( |
| String.format("Google Inc.:Google APIs:%d", apiLevel), |
| androidSdkDir, |
| String.format("platforms/android-%d", apiLevel), |
| additionalJarPaths); |
| } |
| } |
| |
| private static class AddonFilter implements FilenameFilter { |
| |
| @Override |
| public boolean accept(File dir, String name) { |
| return name.endsWith(".jar"); |
| } |
| } |
| } |