blob: 4787f5fd75228ca3295d360321fa978ae0e25c11 [file] [log] [blame]
/*
* Copyright (C) 2015 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.repositoryv2;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.prefs.AndroidLocation;
import com.android.repository.Revision;
import com.android.repository.api.ConstantSourceProvider;
import com.android.repository.api.FallbackRemoteRepoLoader;
import com.android.repository.api.LocalPackage;
import com.android.repository.api.ProgressIndicator;
import com.android.repository.api.RemoteListSourceProvider;
import com.android.repository.api.RepoManager;
import com.android.repository.api.RepoPackage;
import com.android.repository.api.Repository;
import com.android.repository.api.RepositorySource;
import com.android.repository.api.RepositorySourceProvider;
import com.android.repository.api.SchemaModule;
import com.android.repository.impl.installer.BasicInstaller;
import com.android.repository.impl.installer.PackageInstaller;
import com.android.repository.impl.meta.RepositoryPackages;
import com.android.repository.impl.sources.LocalSourceProvider;
import com.android.repository.io.FileOp;
import com.android.repository.io.FileOpUtils;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repositoryv2.meta.DetailsTypes;
import com.android.sdklib.repositoryv2.meta.SysImgFactory;
import com.android.sdklib.repositoryv2.sources.RemoteSiteType;
import com.android.sdklib.repositoryv2.targets.AndroidTargetManager;
import com.android.sdklib.repositoryv2.targets.SystemImage;
import com.android.sdklib.repositoryv2.targets.SystemImageManager;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Map;
/**
* Android SDK interface to {@link RepoManager}. Ensures that the proper android sdk-specific
* schemas and source providers are registered, and provides android sdk-specific package logic
* (pending as adoption continues).
*/
public final class AndroidSdkHandler {
/**
* Schema module containing the package type information to be used in addon repos.
*/
private static final SchemaModule ADDON_MODULE = new SchemaModule(
"com.android.sdklib.repositoryv2.generated.addon.v%d.ObjectFactory",
"sdk-addon-%02d.xsd", AndroidSdkHandler.class);
/**
* Schema module containing the package type information to be used in the primary repo.
*/
private static final SchemaModule REPOSITORY_MODULE = new SchemaModule(
"com.android.sdklib.repositoryv2.generated.repository.v%d.ObjectFactory",
"sdk-repository-%02d.xsd", AndroidSdkHandler.class);
/**
* Schema module containing the package type information to be used in system image repos.
*/
private static final SchemaModule SYS_IMG_MODULE = new SchemaModule(
"com.android.sdklib.repositoryv2.generated.sysimg.v%d.ObjectFactory",
"sdk-sys-img-%02d.xsd", AndroidSdkHandler.class);
/**
* Common schema module used by the other sdk-specific modules.
*/
private static final SchemaModule COMMON_MODULE = new SchemaModule(
"com.android.sdklib.repositoryv2.generated.common.v%d.ObjectFactory",
"sdk-common-%02d.xsd", AndroidSdkHandler.class);
/**
* The URL of the official Google sdk-repository site. The URL ends with a /, allowing easy
* concatenation.
*/
public static final String URL_GOOGLE_SDK_SITE = "https://dl.google.com/android/repository/";
/**
* The name of the environment variable used to override the url of the primary repository, for
* testing.
*/
public static final String SDK_TEST_BASE_URL_ENV_VAR = "SDK_TEST_BASE_URL";
/**
* The latest version of legacy remote packages we should expect to receive from a server. If
* you think you need to change this value you should create a new-style package instead.
*/
public static final int LATEST_LEGACY_VERSION = 12;
/**
* The name of the file containing user-specified remote repositories.
*/
@VisibleForTesting
static final String LOCAL_ADDONS_FILENAME = "repositories.cfg";
/**
* Pattern for the name of a (remote) file containing a list of urls to check for repositories.
*
* @see RemoteListSourceProvider
*/
private static final String DEFAULT_SITE_LIST_FILENAME_PATTERN = "addons_list-%d.xml";
/**
* Lock for synchronizing changes to the our {@link RepoManager}.
*/
private static final Object MANAGER_LOCK = new Object();
/**
* Pattern for the URL pointing to the legacy repository xml (as defined by e.g.
* sdk-repository-12.xsd).
*/
private static final String LEGACY_REPO_URL_PATTERN = "%srepository-%d.xml";
/**
* Pattern for the URL pointing to the repository xml (as defined by sdk-repository-01.xsd in
* repositoryv2).
*/
private static final String REPO_URL_PATTERN = "%srepository2-%d.xml";
/**
* The {@link RepoManager} initialized with our {@link SchemaModule}s, {@link
* RepositorySource}s, and local SDK path.
*/
private RepoManager mRepoManager;
/**
* Finds all {@link SystemImage}s in packages known to {@link #mRepoManager};
*/
private SystemImageManager mSystemImageManager;
/**
* Creates {@link IAndroidTarget}s based on the platforms and addons known to
* {@link #mRepoManager}.
*/
private AndroidTargetManager mAndroidTargetManager;
/**
* Reference to our latest build tool package.
*/
private BuildToolInfo mLatestBuildTool = null;
/**
* {@link FileOp} to use for local file operations. For normal operation should be
* {@link FileOpUtils#create()}.
*/
private final FileOp mFop;
/**
* Singleton instance of this class.
*/
private static Map<File, AndroidSdkHandler> sInstances = Maps.newConcurrentMap();
/**
* Location of the local SDK.
*/
private final File mLocation;
/**
* Provider for user-specified {@link RepositorySource}s.
*/
private LocalSourceProvider mUserSourceProvider;
/**
* Loader capable of loading old-style repository xml files, with namespace like
* http://schemas.android.com/sdk/android/repository/NN or similar.
*
* Static, shared among {@link AndroidSdkHandler} instances.
*/
private static FallbackRemoteRepoLoader sRemoteFallback;
/**
* Lazily-initialized class containing our static repository configuration, shared between
* AndroidSdkHandler instances.
*/
private static RepoConfig sRepoConfig;
/**
* Get a {@code AndroidSdkHandler} instance.
*
* @param localPath The path to the local SDK. If {@code null} this handler will only be used
* for remote operations.
*/
@NonNull
public static AndroidSdkHandler getInstance(@Nullable File localPath) {
File key = localPath == null ? new File("") : localPath;
AndroidSdkHandler instance = sInstances.get(key);
if (instance == null) {
instance = new AndroidSdkHandler(localPath, FileOpUtils.create());
sInstances.put(key, instance);
}
return instance;
}
/**
* Don't use this, use {@link #getInstance(File)}, unless you're in a unit test and need to
* specify a custom {@link FileOp}.
*/
@VisibleForTesting
public AndroidSdkHandler(@Nullable File localPath, @NonNull FileOp fop) {
mFop = fop;
mLocation = localPath;
}
/**
* Fetches {@link RepoManager} set up to interact with android SDK repositories. It should not
* cached by callers of this method, since any changes to the fundamental properties of the
* manager (fallback loaders, local path) will cause a new instance to be created.
*/
@NonNull
public RepoManager getSdkManager(@NonNull ProgressIndicator progress) {
RepoManager result = mRepoManager;
synchronized (MANAGER_LOCK) {
if (result == null) {
mSystemImageManager = null;
mAndroidTargetManager = null;
mLatestBuildTool = null;
result = getRepoConfig(progress)
.createRepoManager(progress, mLocation, sRemoteFallback,
getUserSourceProvider(progress), mFop);
// Invalidate system images, targets, the latest build tool, and the legacy local
// package manager when local packages change
result.registerLocalChangeListener(new RepoManager.RepoLoadedCallback() {
@Override
public void doRun(@NonNull RepositoryPackages packages) {
mSystemImageManager = null;
mAndroidTargetManager = null;
mLatestBuildTool = null;
}
});
mRepoManager = result;
}
}
return mRepoManager;
}
/**
* Gets (and creates if necessary) a {@link SystemImageManager} based on our local sdk packages.
*/
@NonNull
public SystemImageManager getSystemImageManager(@NonNull ProgressIndicator progress) {
if (mSystemImageManager == null) {
getSdkManager(progress);
mSystemImageManager = new SystemImageManager(mRepoManager,
(SysImgFactory) getSysImgModule().createLatestFactory(), mFop);
}
return mSystemImageManager;
}
/**
* Gets (and creates if necessary) an {@link AndroidTargetManager} based on our local sdk
* packages.
*/
@NonNull
public AndroidTargetManager getAndroidTargetManager(@NonNull ProgressIndicator progress) {
if (mAndroidTargetManager == null) {
getSdkManager(progress);
mAndroidTargetManager = new AndroidTargetManager(this, mFop);
}
return mAndroidTargetManager;
}
/**
* Gets the path of the local SDK, if set.
*/
@Nullable
public File getLocation() {
return mLocation;
}
/**
* Convenience to get a package from the local repo.
*/
@Nullable
public LocalPackage getLocalPackage(@NonNull String path, @NonNull ProgressIndicator progress) {
return getSdkManager(progress).getPackages().getLocalPackages().get(path);
}
/**
* Sets the {@link FallbackRemoteRepoLoader} to be used to parse any old-style remote
* repositories we might receive.<p> Invalidates the repo manager; it will be recreated when
* next retrieved.
*/
public static void setRemoteFallback(@Nullable FallbackRemoteRepoLoader fallbackSdk) {
synchronized (MANAGER_LOCK) {
sRemoteFallback = fallbackSdk;
invalidateAll();
}
}
/**
* Resets the {@link RepoManager}s of all cached {@link AndroidSdkHandler}s.
*/
private static void invalidateAll() {
for (AndroidSdkHandler handler : sInstances.values()) {
handler.mRepoManager = null;
}
}
/**
* @return The {@link SchemaModule} containing the common sdk-specific metadata. See
* sdk-common-XX.xsd.
*/
@NonNull
public static SchemaModule getCommonModule() {
return COMMON_MODULE;
}
/**
* @return The {@link SchemaModule} containing the metadata for addon-type {@link Repository}s.
* See sdk-addon-XX.xsd.
*/
@NonNull
public static SchemaModule getAddonModule() {
return ADDON_MODULE;
}
/**
* @return The {@link SchemaModule} containing the metadata for the primary android SDK {@link
* Repository} (containin platforms etc.). See sdk-repository-XX.xsd.
*/
@NonNull
public static SchemaModule getRepositoryModule() {
return REPOSITORY_MODULE;
}
/**
* @return The {@link SchemaModule} containing the metadata for system image-type {@link
* Repository}s. See sdk-sys-img-XX.xsd.
*/
@NonNull
public static SchemaModule getSysImgModule() {
return SYS_IMG_MODULE;
}
@NonNull
@VisibleForTesting
RemoteListSourceProvider getRemoteListSourceProvider(@NonNull ProgressIndicator progress) {
return getRepoConfig(progress).getRemoteListSourceProvider();
}
/**
* Gets the customizable {@link RepositorySourceProvider}. Can be null if there's a problem
* with the user's environment.
*/
@Nullable
public LocalSourceProvider getUserSourceProvider(@NonNull ProgressIndicator progress) {
if (mUserSourceProvider == null) {
mUserSourceProvider = RepoConfig.createUserSourceProvider(progress, mFop);
synchronized (MANAGER_LOCK) {
if (mRepoManager != null) {
// If the repo already exists cause it to be reloaded, so the userSourceProvider
// can be added to the config.
mRepoManager = null;
getSdkManager(progress);
}
}
}
return mUserSourceProvider;
}
@NonNull
private RepoConfig getRepoConfig(@NonNull ProgressIndicator progress) {
if (sRepoConfig == null) {
sRepoConfig = new RepoConfig(progress);
}
return sRepoConfig;
}
/**
* Class containing the repository configuration we can (lazily) create statically, as well
* as a method to create a new {@link RepoManager} based on that configuration.
*
* Instances of this class may be shared between {@link AndroidSdkHandler} instances.
*/
private static class RepoConfig {
/**
* Provider for a list of {@link RepositorySource}s fetched from the google.
*/
private RemoteListSourceProvider mAddonsListSourceProvider;
/**
* Provider for the main new-style {@link RepositorySource}
*/
private ConstantSourceProvider mRepositorySourceProvider;
/**
* Provider for the main legacy {@link RepositorySource}
*/
private ConstantSourceProvider mLegacyRepositorySourceProvider;
/**
* Sets up our {@link SchemaModule}s and {@link RepositorySourceProvider}s if they haven't
* been yet.
*
* @param progress Used for error logging.
*/
public RepoConfig(@NonNull ProgressIndicator progress) {
// Schema module for the list of update sites we download
SchemaModule addonListModule = new SchemaModule(
"com.android.sdklib.repositoryv2.sources.generated.v%d.ObjectFactory",
"sdk-sites-list-%d.xsd", RemoteSiteType.class);
try {
// Specify what modules are allowed to be used by what sites.
Map<Class<? extends RepositorySource>, Collection<SchemaModule>> siteTypes
= ImmutableMap.<Class<? extends RepositorySource>, Collection<SchemaModule>>builder()
.put(RemoteSiteType.AddonSiteType.class, ImmutableSet.of(ADDON_MODULE))
.put(RemoteSiteType.SysImgSiteType.class,
ImmutableSet.of(SYS_IMG_MODULE)).build();
mAddonsListSourceProvider = RemoteListSourceProvider
.create(getAddonListUrl(progress), addonListModule, siteTypes);
} catch (URISyntaxException e) {
progress.logError("Failed to set up addons source provider", e);
}
String url = String.format(REPO_URL_PATTERN, getBaseUrl(progress),
REPOSITORY_MODULE.getNamespaceVersionMap().size());
mRepositorySourceProvider = new ConstantSourceProvider(url, "Android Repository",
ImmutableSet.of(REPOSITORY_MODULE, RepoManager.getGenericModule()));
url = String.format(LEGACY_REPO_URL_PATTERN, getBaseUrl(progress), LATEST_LEGACY_VERSION);
mLegacyRepositorySourceProvider = new ConstantSourceProvider(url,
"Legacy Android Repository",
ImmutableSet.of(REPOSITORY_MODULE, RepoManager.getGenericModule()));
}
/**
* Creates a customizable {@link RepositorySourceProvider}. Can be null if there's a problem
* with the user's environment.
*/
@Nullable
public static LocalSourceProvider createUserSourceProvider(
@NonNull ProgressIndicator progress,
@NonNull FileOp fileOp) {
try {
return new LocalSourceProvider(
new File(AndroidLocation.getFolder(), LOCAL_ADDONS_FILENAME),
ImmutableList.of(SYS_IMG_MODULE, ADDON_MODULE), fileOp);
} catch (AndroidLocation.AndroidLocationException e) {
progress.logWarning("Couldn't find android folder", e);
}
return null;
}
@NonNull
private static String getAddonListUrl(@NonNull ProgressIndicator progress) {
return getBaseUrl(progress) + DEFAULT_SITE_LIST_FILENAME_PATTERN;
}
/**
* Gets the default url (without the actual filename or specific final part of the path
* (e.g. sys-img). This will be either the value of {@link #SDK_TEST_BASE_URL_ENV_VAR} or
* {@link #URL_GOOGLE_SDK_SITE}
*/
@NonNull
private static String getBaseUrl(@NonNull ProgressIndicator progress) {
String baseUrl = System.getenv(SDK_TEST_BASE_URL_ENV_VAR);
if (baseUrl != null) {
if (!baseUrl.isEmpty() && baseUrl.endsWith("/")) {
return baseUrl;
} else {
progress.logWarning("Ignoring invalid SDK_TEST_BASE_URL: " + baseUrl);
}
}
return URL_GOOGLE_SDK_SITE;
}
@NonNull
public RemoteListSourceProvider getRemoteListSourceProvider() {
return mAddonsListSourceProvider;
}
@NonNull
public RepoManager createRepoManager(@NonNull ProgressIndicator progress,
@Nullable File localLocation,
@Nullable FallbackRemoteRepoLoader remoteFallbackLoader,
@Nullable LocalSourceProvider userProvider, @NonNull FileOp fop) {
RepoManager result = RepoManager.create(fop);
// Create the schema modules etc. if they haven't been already.
result.registerSchemaModule(ADDON_MODULE);
result.registerSchemaModule(REPOSITORY_MODULE);
result.registerSchemaModule(SYS_IMG_MODULE);
result.registerSchemaModule(COMMON_MODULE);
result.registerSourceProvider(mRepositorySourceProvider);
result.registerSourceProvider(mLegacyRepositorySourceProvider);
result.registerSourceProvider(mAddonsListSourceProvider);
if (userProvider != null) {
result.registerSourceProvider(userProvider);
// The customizable source provider needs a handle on the repo manager, so it can
// mark the cached packages invalid if the sources change.
userProvider.setRepoManager(result);
}
result.setLocalPath(localLocation);
if (localLocation != null) {
// If we have a local sdk path set, set up the old-style loader so we can parse
// any legacy packages.
result.setFallbackLocalRepoLoader(
new LegacyLocalRepoLoader(localLocation, fop, result));
// If a location is set we'll always want at least the local packages loaded, so
// load them now.
result.loadSynchronously(0, progress, null, null);
}
result.setFallbackRemoteRepoLoader(remoteFallbackLoader);
return result;
}
}
/**
* Finds the best {@link PackageInstaller} for the given {@link RepoPackage}.
*/
@NonNull
public static PackageInstaller findBestInstaller(@NonNull RepoPackage p) {
if (p.getTypeDetails() instanceof DetailsTypes.MavenType) {
return new MavenInstaller();
}
return new BasicInstaller();
}
/**
* Gets a {@link BuildToolInfo} corresponding to the newest installed build tool
* {@link RepoPackage}, or {@code null} if none are installed.
*/
@Nullable
public BuildToolInfo getLatestBuildTool(@NonNull ProgressIndicator progress) {
if (mLatestBuildTool == null) {
RepoManager manager = getSdkManager(progress);
BuildToolInfo info = null;
for (LocalPackage p : manager.getPackages()
.getLocalPackagesForPrefix(SdkConstants.FD_BUILD_TOOLS)) {
if (info == null || info.getRevision().compareTo(p.getVersion()) < 0) {
info = new BuildToolInfo(p.getVersion(), p.getLocation());
}
}
mLatestBuildTool = info;
}
return mLatestBuildTool;
}
/**
* Creates a the {@link BuildToolInfo} for the specificed build tools revision, if available.
*
* @param revision The build tools revision requested
* @param progress {@link ProgressIndicator} for logging.
* @return The {@link BuildToolInfo} corresponding to the specified build tools package, or
* {@code} null if that revision is not installed.
*/
@Nullable
public BuildToolInfo getBuildToolInfo(@NonNull Revision revision,
@NonNull ProgressIndicator progress) {
RepositoryPackages packages = getSdkManager(progress).getPackages();
LocalPackage p = packages.getLocalPackages()
.get(DetailsTypes.getBuildToolsPath(revision));
if (p == null) {
return null;
}
return new BuildToolInfo(p.getVersion(), p.getLocation());
}
/**
* Gets our {@link FileOp}. Useful so both the sdk handler and file op don't both have to be
* injected everywhere.
*/
@NonNull
public FileOp getFileOp() {
return mFop;
}
}