/* * Copyright (C) 2009 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 * * 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.android.sdklib.internal.repository; import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.io.FileWrapper; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISystemImage; import com.android.sdklib.ISystemImage.LocationType; import com.android.sdklib.SdkManager; import com.android.sdklib.internal.androidTarget.PlatformTarget; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.repository.packages.AddonPackage; import com.android.sdklib.internal.repository.packages.BuildToolPackage; import com.android.sdklib.internal.repository.packages.DocPackage; import com.android.sdklib.internal.repository.packages.ExtraPackage; import com.android.sdklib.internal.repository.packages.Package; import com.android.sdklib.internal.repository.packages.PlatformPackage; import com.android.sdklib.internal.repository.packages.PlatformToolPackage; import com.android.sdklib.internal.repository.packages.SamplePackage; import com.android.sdklib.internal.repository.packages.SourcePackage; import com.android.sdklib.internal.repository.packages.SystemImagePackage; import com.android.sdklib.internal.repository.packages.ToolPackage; import com.android.sdklib.io.FileOp; import com.android.sdklib.repository.AddonManifestIniProps; import com.android.sdklib.repository.descriptors.PkgType; import com.android.utils.ILogger; import com.android.utils.Pair; import com.google.common.collect.Lists; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; /** * Scans a local SDK to find which packages are currently installed. * * @deprecated * com.android.sdklib.internal.repository has moved into Studio as * com.android.tools.idea.sdk.remote.internal. */ @Deprecated public class LocalSdkParser { private Package[] mPackages; /** Parse all SDK folders. */ public static final int PARSE_ALL = PkgType.PKG_ALL_INT; /** Parse the SDK/tools folder. */ public static final int PARSE_TOOLS = PkgType.PKG_TOOLS.getIntValue(); /** Parse the SDK/platform-tools folder */ public static final int PARSE_PLATFORM_TOOLS = PkgType.PKG_PLATFORM_TOOLS.getIntValue(); /** Parse the SDK/docs folder. */ public static final int PARSE_DOCS = PkgType.PKG_DOC.getIntValue(); /** * Equivalent to parsing the SDK/platforms folder but does so * by using the <em>valid</em> targets loaded by the {@link SdkManager}. * Parsing the platforms also parses the SDK/system-images folder. */ public static final int PARSE_PLATFORMS = PkgType.PKG_PLATFORM.getIntValue(); /** * Equivalent to parsing the SDK/addons folder but does so * by using the <em>valid</em> targets loaded by the {@link SdkManager}. */ public static final int PARSE_ADDONS = PkgType.PKG_ADDON.getIntValue(); /** Parse the SDK/samples folder. * Note: this will not detect samples located in the SDK/extras packages. */ public static final int PARSE_SAMPLES = PkgType.PKG_SAMPLE.getIntValue(); /** Parse the SDK/sources folder. */ public static final int PARSE_SOURCES = PkgType.PKG_SOURCE.getIntValue(); /** Parse the SDK/extras folder. */ public static final int PARSE_EXTRAS = PkgType.PKG_EXTRA.getIntValue(); /** Parse the SDK/build-tools folder. */ public static final int PARSE_BUILD_TOOLS = PkgType.PKG_BUILD_TOOLS.getIntValue(); public LocalSdkParser() { // pass } /** * Returns the packages found by the last call to {@link #parseSdk}. * <p/> * This returns initially returns null. * Once the parseSdk() method has been called, this returns a possibly empty but non-null array. */ public Package[] getPackages() { return mPackages; } /** * Clear the internal packages list. After this call, {@link #getPackages()} will return * null till {@link #parseSdk} is called. */ public void clearPackages() { mPackages = null; } /** * Scan the give SDK to find all the packages already installed at this location. * <p/> * Store the packages internally. You can use {@link #getPackages()} to retrieve them * at any time later. * <p/> * Equivalent to calling {@code parseSdk(..., PARSE_ALL, ...); } * * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}. * @param sdkManager An existing SDK manager to list current platforms and addons. * @param monitor A monitor to track progress. Cannot be null. * @return The packages found. Can be retrieved later using {@link #getPackages()}. */ @NonNull public Package[] parseSdk( @NonNull String osSdkRoot, @NonNull SdkManager sdkManager, @NonNull ITaskMonitor monitor) { return parseSdk(osSdkRoot, sdkManager, PARSE_ALL, monitor); } /** * Scan the give SDK to find all the packages already installed at this location. * <p/> * Store the packages internally. You can use {@link #getPackages()} to retrieve them * at any time later. * * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}. * @param sdkManager An existing SDK manager to list current platforms and addons. * @param parseFilter Either {@link #PARSE_ALL} or an ORed combination of the other * {@code PARSE_} constants to indicate what should be parsed. * @param monitor A monitor to track progress. Cannot be null. * @return The packages found. Can be retrieved later using {@link #getPackages()}. */ @NonNull public Package[] parseSdk( @NonNull String osSdkRoot, @NonNull SdkManager sdkManager, int parseFilter, @NonNull ITaskMonitor monitor) { ArrayList<Package> packages = new ArrayList<Package>(); HashSet<File> visited = new HashSet<File>(); monitor.setProgressMax(11); File dir = null; Package pkg = null; if ((parseFilter & PARSE_DOCS) != 0) { dir = new File(osSdkRoot, SdkConstants.FD_DOCS); pkg = scanDoc(dir, monitor); if (pkg != null) { packages.add(pkg); visited.add(dir); } } monitor.incProgress(1); if ((parseFilter & PARSE_TOOLS) != 0) { dir = new File(osSdkRoot, SdkConstants.FD_TOOLS); pkg = scanTools(dir, monitor); if (pkg != null) { packages.add(pkg); visited.add(dir); } } monitor.incProgress(1); if ((parseFilter & PARSE_PLATFORM_TOOLS) != 0) { dir = new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS); pkg = scanPlatformTools(dir, monitor); if (pkg != null) { packages.add(pkg); visited.add(dir); } } monitor.incProgress(1); if ((parseFilter & PARSE_BUILD_TOOLS) != 0) { scanBuildTools(sdkManager, visited, packages, monitor); } monitor.incProgress(1); // for platforms, add-ons and samples, rely on the SdkManager parser if ((parseFilter & (PARSE_ADDONS | PARSE_PLATFORMS)) != 0) { File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES); for(IAndroidTarget target : sdkManager.getTargets()) { Properties props = parseProperties(new File(target.getLocation(), SdkConstants.FN_SOURCE_PROP)); try { pkg = null; if (target.isPlatform() && (parseFilter & PARSE_PLATFORMS) != 0) { pkg = PlatformPackage.create(target, props); if (samplesRoot.isDirectory()) { // Get the samples dir for a platform if it is located in the new // root /samples dir. We purposely ignore "old" samples that are // located under the platform dir. File samplesDir = new File(target.getPath(IAndroidTarget.SAMPLES)); if (samplesDir.exists() && samplesDir.getParentFile().equals(samplesRoot)) { Properties samplesProps = parseProperties( new File(samplesDir, SdkConstants.FN_SOURCE_PROP)); if (samplesProps != null) { Package pkg2 = SamplePackage.create(target, samplesProps); packages.add(pkg2); } visited.add(samplesDir); } } } else if ((parseFilter & PARSE_ADDONS) != 0) { pkg = AddonPackage.create(target, props); } if (pkg != null) { for (ISystemImage systemImage : target.getSystemImages()) { if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) { File siDir = systemImage.getLocation(); if (siDir.isDirectory()) { Properties siProps = parseProperties( new File(siDir, SdkConstants.FN_SOURCE_PROP)); Package pkg2 = new SystemImagePackage( target.getVersion(), 0 /*rev*/, // use the one from siProps systemImage.getAbiType(), siProps, siDir.getAbsolutePath()); packages.add(pkg2); visited.add(siDir); } } } } } catch (Exception e) { monitor.error(e, null); } if (pkg != null) { packages.add(pkg); visited.add(new File(target.getLocation())); } } } monitor.incProgress(1); if ((parseFilter & PARSE_PLATFORMS) != 0) { scanMissingSystemImages(sdkManager, visited, packages, monitor); } monitor.incProgress(1); if ((parseFilter & PARSE_ADDONS) != 0) { scanMissingAddons(sdkManager, visited, packages, monitor); } monitor.incProgress(1); if ((parseFilter & PARSE_SAMPLES) != 0) { scanMissingSamples(sdkManager, visited, packages, monitor); } monitor.incProgress(1); if ((parseFilter & PARSE_EXTRAS) != 0) { scanExtras(sdkManager, visited, packages, monitor); } monitor.incProgress(1); if ((parseFilter & PARSE_EXTRAS) != 0) { scanExtrasDirectory(osSdkRoot, visited, packages, monitor); } monitor.incProgress(1); if ((parseFilter & PARSE_SOURCES) != 0) { scanSources(sdkManager, visited, packages, monitor); } monitor.incProgress(1); Collections.sort(packages); mPackages = packages.toArray(new Package[packages.size()]); return mPackages; } /** * Find any directory in the /extras/vendors/path folders for extra packages. * This isn't a recursive search. */ private void scanExtras(SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File root = new File(sdkManager.getLocation(), SdkConstants.FD_EXTRAS); for (File vendor : listFilesNonNull(root)) { if (vendor.isDirectory()) { scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log); } } } /** * Find any other directory in the given "root" directory that hasn't been visited yet * and assume they contain extra packages. This is <em>not</em> a recursive search. */ private void scanExtrasDirectory(String extrasRoot, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File root = new File(extrasRoot); for (File dir : listFilesNonNull(root)) { if (dir.isDirectory() && !visited.contains(dir)) { Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP)); if (props != null) { try { Package pkg = ExtraPackage.create( null, //source props, //properties null, //vendor dir.getName(), //path 0, //revision null, //license null, //description null, //descUrl dir.getPath() //archiveOsPath ); packages.add(pkg); visited.add(dir); } catch (Exception e) { log.error(e, null); } } } } } /** * Find any other sub-directories under the /samples root that hasn't been visited yet * and assume they contain sample packages. This is <em>not</em> a recursive search. * <p/> * The use case is to find samples dirs under /samples when their target isn't loaded. */ private void scanMissingSamples(SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File root = new File(sdkManager.getLocation()); root = new File(root, SdkConstants.FD_SAMPLES); for (File dir : listFilesNonNull(root)) { if (dir.isDirectory() && !visited.contains(dir)) { Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP)); if (props != null) { try { Package pkg = SamplePackage.create(dir.getAbsolutePath(), props); packages.add(pkg); visited.add(dir); } catch (Exception e) { log.error(e, null); } } } } } /** * The sdk manager only lists valid addons. However here we also want to find "broken" * addons, i.e. addons that failed to load for some reason. * <p/> * Find any other sub-directories under the /add-ons root that hasn't been visited yet * and assume they contain broken addons. */ private void scanMissingAddons(SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS); for (File dir : listFilesNonNull(addons)) { if (dir.isDirectory() && !visited.contains(dir)) { Pair<Map<String, String>, String> infos = parseAddonProperties(dir, sdkManager.getTargets(), log); Properties sourceProps = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP)); Map<String, String> addonProps = infos.getFirst(); String error = infos.getSecond(); try { Package pkg = AddonPackage.createBroken(dir.getAbsolutePath(), sourceProps, addonProps, error); packages.add(pkg); visited.add(dir); } catch (Exception e) { log.error(e, null); } } } } /** * Parses the add-on properties and decodes any error that occurs when * loading an addon. * * @param addonDir the location of the addon directory. * @param targetList The list of Android target that were already loaded * from the SDK. * @param log the ILogger object receiving warning/error from the parsing. * @return A pair with the property map and an error string. Both can be * null but not at the same time. If a non-null error is present * then the property map must be ignored. The error should be * translatable as it might show up in the SdkManager UI. */ @Deprecated // Copied from SdkManager.java, dup of LocalAddonPkgInfo.parseAddonProperties. @NonNull public static Pair<Map<String, String>, String> parseAddonProperties( @NonNull File addonDir, @NonNull IAndroidTarget[] targetList, @NonNull ILogger log) { Map<String, String> propertyMap = null; String error = null; FileWrapper addOnManifest = new FileWrapper(addonDir, SdkConstants.FN_MANIFEST_INI); do { if (!addOnManifest.isFile()) { error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI); break; } propertyMap = ProjectProperties.parsePropertyFile(addOnManifest, log); if (propertyMap == null) { error = String.format("Failed to parse properties from %1$s", SdkConstants.FN_MANIFEST_INI); break; } // look for some specific values in the map. // we require name, vendor, and api String name = propertyMap.get(AddonManifestIniProps.ADDON_NAME); if (name == null) { error = String.format("'%1$s' is missing from %2$s.", AddonManifestIniProps.ADDON_NAME, SdkConstants.FN_MANIFEST_INI); break; } String vendor = propertyMap.get(AddonManifestIniProps.ADDON_VENDOR); if (vendor == null) { error = String.format("'%1$s' is missing from %2$s.", AddonManifestIniProps.ADDON_VENDOR, SdkConstants.FN_MANIFEST_INI); break; } String api = propertyMap.get(AddonManifestIniProps.ADDON_API); if (api == null) { error = String.format("'%1$s' is missing from %2$s.", AddonManifestIniProps.ADDON_API, SdkConstants.FN_MANIFEST_INI); break; } // Look for a platform that has a matching api level or codename. PlatformTarget baseTarget = null; for (IAndroidTarget target : targetList) { if (target.isPlatform() && target.getVersion().equals(api)) { baseTarget = (PlatformTarget) target; break; } } if (baseTarget == null) { error = String.format( "Unable to find base platform with API level '%1$s'", api); break; } // get the add-on revision String revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION); if (revision == null) { revision = propertyMap.get(AddonManifestIniProps.ADDON_REVISION_OLD); } if (revision != null) { try { Integer.parseInt(revision); } catch (NumberFormatException e) { // looks like revision does not parse to a number. error = String.format( "%1$s is not a valid number in %2$s.", AddonManifestIniProps.ADDON_REVISION, SdkConstants.FN_BUILD_PROP); break; } } } while (false); return Pair.of(propertyMap, error); } /** * The sdk manager only lists valid system image via its addons or platform targets. * However here we also want to find "broken" system images, that is system images * that are located in the sdk/system-images folder but somehow not loaded properly. */ private void scanMissingSystemImages(SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES); // The system-images folder contains a list of platform folders. for (File platformDir : listFilesNonNull(siRoot)) { if (platformDir.isDirectory() && !visited.contains(platformDir)) { visited.add(platformDir); // In the platform directory, we expect a list of abi folders // or a list of tag/abi folders. Basically parse any folder that has // a source.prop file within 2 levels. List<File> propFiles = Lists.newArrayList(); for (File dir1 : listFilesNonNull(platformDir)) { if (dir1.isDirectory() && !visited.contains(dir1)) { visited.add(dir1); File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP); if (prop1.isFile()) { propFiles.add(prop1); } else { for (File dir2 : listFilesNonNull(dir1)) { if (dir2.isDirectory() && !visited.contains(dir2)) { visited.add(dir2); File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP); if (prop2.isFile()) { propFiles.add(prop2); } } } } } } for (File propFile : propFiles) { Properties props = parseProperties(propFile); try { Package pkg = SystemImagePackage.createBroken(propFile.getParentFile(), props); packages.add(pkg); } catch (Exception e) { log.error(e, null); } } } } } /** * Scan the sources/folders and register valid as well as broken source packages. */ private void scanSources(SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File srcRoot = new File(sdkManager.getLocation(), SdkConstants.FD_PKG_SOURCES); // The sources folder contains a list of platform folders. for (File platformDir : listFilesNonNull(srcRoot)) { if (platformDir.isDirectory() && !visited.contains(platformDir)) { visited.add(platformDir); // Ignore empty directories File[] srcFiles = platformDir.listFiles(); if (srcFiles != null && srcFiles.length > 0) { Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP)); try { Package pkg = SourcePackage.create(platformDir, props); packages.add(pkg); } catch (Exception e) { log.error(e, null); } } } } } /** * Try to find a tools package at the given location. * Returns null if not found. */ private Package scanTools(File toolFolder, ILogger log) { // Can we find some properties? Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP)); // We're not going to check that all tools are present. At the very least // we should expect to find android and an emulator adapted to the current OS. boolean hasEmulator = false; boolean hasAndroid = false; String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe"); String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat"); for (File file : listFilesNonNull(toolFolder)) { String name = file.getName(); if (SdkConstants.FN_EMULATOR.equals(name)) { hasEmulator = true; } if (android1.equals(name) || (android2 != null && android2.equals(name))) { hasAndroid = true; } } if (!hasAndroid || !hasEmulator) { return null; } // Create our package. use the properties if we found any. try { Package pkg = ToolPackage.create( null, //source props, //properties 0, //revision null, //license "Tools", //description null, //descUrl toolFolder.getPath() //archiveOsPath ); return pkg; } catch (Exception e) { log.error(e, null); } return null; } /** * Try to find a platform-tools package at the given location. * Returns null if not found. */ private Package scanPlatformTools(File platformToolsFolder, ILogger log) { // Can we find some properties? Properties props = parseProperties(new File(platformToolsFolder, SdkConstants.FN_SOURCE_PROP)); // We're not going to check that all tools are present. At the very least // we should expect to find adb, aidl, aapt and dx (adapted to the current OS). if (platformToolsFolder.listFiles() == null) { // ListFiles is null if the directory doesn't even exist. // Not going to find anything in there... return null; } // Create our package. use the properties if we found any. try { Package pkg = PlatformToolPackage.create( null, //source props, //properties 0, //revision null, //license "Platform Tools", //description null, //descUrl platformToolsFolder.getPath() //archiveOsPath ); return pkg; } catch (Exception e) { log.error(e, null); } return null; } /** * Scan the build-tool/folders and register valid as well as broken build tool packages. */ private void scanBuildTools( SdkManager sdkManager, HashSet<File> visited, ArrayList<Package> packages, ILogger log) { File buildToolRoot = new File(sdkManager.getLocation(), SdkConstants.FD_BUILD_TOOLS); // The build-tool root folder contains a list of revisioned folders. for (File buildToolDir : listFilesNonNull(buildToolRoot)) { if (buildToolDir.isDirectory() && !visited.contains(buildToolDir)) { visited.add(buildToolDir); // Ignore empty directories File[] srcFiles = buildToolDir.listFiles(); if (srcFiles != null && srcFiles.length > 0) { Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP)); try { Package pkg = BuildToolPackage.create(buildToolDir, props); packages.add(pkg); } catch (Exception e) { log.error(e, null); } } } } } /** * Try to find a docs package at the given location. * Returns null if not found. */ private Package scanDoc(File docFolder, ILogger log) { // Can we find some properties? Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP)); // To start with, a doc folder should have an "index.html" to be acceptable. // We don't actually check the content of the file. if (new File(docFolder, "index.html").isFile()) { try { Package pkg = DocPackage.create( null, //source props, //properties 0, //apiLevel null, //codename 0, //revision null, //license null, //description null, //descUrl docFolder.getPath() //archiveOsPath ); return pkg; } catch (Exception e) { log.error(e, null); } } return null; } /** * Parses the given file as properties file if it exists. * Returns null if the file does not exist, cannot be parsed or has no properties. */ private Properties parseProperties(File propsFile) { FileInputStream fis = null; try { if (propsFile.exists()) { fis = new FileInputStream(propsFile); Properties props = new Properties(); props.load(fis); // To be valid, there must be at least one property in it. if (!props.isEmpty()) { return props; } } } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } } } return null; } /** * Helper method that calls {@link File#listFiles()} and returns * a non-null empty list if the input is not a directory or has * no files. */ @NonNull private static File[] listFilesNonNull(@NonNull File dir) { if (dir.isDirectory()) { File[] files = dir.listFiles(); if (files != null) { return files; } } return FileOp.EMPTY_FILE_ARRAY; } }