/* * 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.sdklib.IAndroidTarget; import com.android.sdklib.ISystemImage; import com.android.sdklib.ISystemImage.LocationType; import com.android.sdklib.SdkManager; import com.android.sdklib.internal.repository.archives.Archive.Arch; import com.android.sdklib.internal.repository.archives.Archive.Os; import com.android.sdklib.internal.repository.packages.AddonPackage; 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.utils.ILogger; import com.android.utils.Pair; 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.Map; import java.util.Properties; /** * Scans a local SDK to find which packages are currently installed. */ public class LocalSdkParser { private Package[] mPackages; /** Parse all SDK folders. */ public static final int PARSE_ALL = 0xFFFF; /** Parse the SDK/tools folder. */ public static final int PARSE_TOOLS = 0x0001; /** Parse the SDK/platform-tools folder */ public static final int PARSE_PLATFORM_TOOLS = 0x0002; /** Parse the SDK/docs folder. */ public static final int PARSE_DOCS = 0x0004; /** * 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 = 0x0010; /** * 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 = 0x0020; /** Parse the SDK/samples folder. * Note: this will not detect samples located in the SDK/extras packages. */ public static final int PARSE_SAMPLES = 0x0100; /** Parse the SDK/sources folder. */ public static final int PARSE_SOURCES = 0x0200; /** Parse the SDK/extras folder. */ public static final int PARSE_EXTRAS = 0x0400; 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()}. */ public @NonNull 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()}. */ public @NonNull 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(10); 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); // 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*/, // this will 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); if (!root.isDirectory()) { // This should not happen. It makes listFiles() return null so let's avoid it. return; } for (File vendor : root.listFiles()) { 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); if (!root.isDirectory()) { // This should not happen. It makes listFiles() return null so let's avoid it. return; } for (File dir : root.listFiles()) { 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 Os.getCurrentOs(), //archiveOs Arch.getCurrentArch(), //archiveArch 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); if (!root.isDirectory()) { // It makes listFiles() return null so let's avoid it. return; } for (File dir : root.listFiles()) { 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); File[] files = addons.listFiles(); if (files == null) { return; } for (File dir : files) { if (dir.isDirectory() && !visited.contains(dir)) { Pair<Map<String, String>, String> infos = SdkManager.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); } } } } /** * 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); File[] files = siRoot.listFiles(); if (files == null) { return; } // The system-images folder contains a list of platform folders. for (File platformDir : files) { if (platformDir.isDirectory() && !visited.contains(platformDir)) { visited.add(platformDir); // In the platform directory, we expect a list of abi folders File[] platformFiles = platformDir.listFiles(); if (platformFiles != null) { for (File abiDir : platformFiles) { if (abiDir.isDirectory() && !visited.contains(abiDir)) { visited.add(abiDir); // Ignore empty directories File[] abiFiles = abiDir.listFiles(); if (abiFiles != null && abiFiles.length > 0) { Properties props = parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP)); try { Package pkg = SystemImagePackage.createBroken(abiDir, 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); File[] subDirs = srcRoot.listFiles(); if (subDirs == null) { return; } // The sources folder contains a list of platform folders. for (File platformDir : subDirs) { 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"); File[] files = toolFolder.listFiles(); if (files != null) { for (File file : files) { 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 Os.getCurrentOs(), //archiveOs Arch.getCurrentArch(), //archiveArch 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 Os.getCurrentOs(), //archiveOs Arch.getCurrentArch(), //archiveArch platformToolsFolder.getPath() //archiveOsPath ); return pkg; } catch (Exception e) { log.error(e, null); } return 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 Os.getCurrentOs(), //archiveOs Arch.getCurrentArch(), //archiveArch 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.size() > 0) { return props; } } } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } } } return null; } }