blob: 74c27b14cadca5ff3550f3ffa79ea89cc01f0645 [file] [log] [blame]
/*
* Copyright (C) 2017 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.tool.sdkmanager;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.repository.api.Channel;
import com.android.repository.api.ConsoleProgressIndicator;
import com.android.repository.api.Downloader;
import com.android.repository.api.ProgressIndicator;
import com.android.repository.api.RepoManager;
import com.android.repository.api.SettingsController;
import com.android.sdklib.repository.AndroidSdkHandler;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
class SdkManagerCliSettings implements SettingsController {
static final class ShowUsageException extends Exception {}
static final class FailSilentlyException extends Exception {}
private static final String CHANNEL_ARG = "--channel=";
private static final String SDK_ROOT_ARG = "--sdk_root=";
private static final String INCLUDE_OBSOLETE_ARG = "--include_obsolete";
private static final String HELP_ARG = "--help";
private static final String NO_HTTPS_ARG = "--no_https";
private static final String VERBOSE_ARG = "--verbose";
private static final String PROXY_TYPE_ARG = "--proxy=";
private static final String PROXY_HOST_ARG = "--proxy_host=";
private static final String PROXY_PORT_ARG = "--proxy_port=";
private static final String NO_PROXY_ARG = "--no_proxy";
private static final String TOOLSDIR = "com.android.sdklib.toolsdir";
private static final Map<String, Function<SdkManagerCliSettings, SdkAction>> ARG_TO_ACTION =
new HashMap<>();
public static final String STUDIO_UNITTEST_DO_NOT_RESOLVE_PROXY_ENV =
"STUDIO_UNITTEST_DO_NOT_RESOLVE_PROXY";
public static final String HTTP_PROXY_ENV = "HTTP_PROXY";
public static final String HTTPS_PROXY_ENV = "HTTPS_PROXY";
static {
InstallAction.register(ARG_TO_ACTION);
UninstallAction.register(ARG_TO_ACTION);
LicensesAction.register(ARG_TO_ACTION);
UpdateAction.register(ARG_TO_ACTION);
ShowVersionAction.register(ARG_TO_ACTION);
ListAction.register(ARG_TO_ACTION);
}
private Path mLocalPath;
private SdkAction mAction;
private int mChannel = 0;
private boolean mIncludeObsolete = false;
private boolean mForceHttp = false;
private boolean mDisableSdkPatches = false;
private boolean mForceNoProxy = false;
private boolean mVerbose = false;
private Proxy.Type mProxyType;
private SocketAddress mProxyHost;
private String mProxyHostStr;
private AndroidSdkHandler mHandler;
private RepoManager mRepoManager;
private PrintStream mOut = System.out;
private BufferedReader mIn;
private Downloader mDownloader;
private final FileSystem mFileSystem;
private final Map<String, String> mEnvironment;
public void setDownloader(@Nullable Downloader downloader) {
mDownloader = downloader;
}
public void setSdkHandler(@Nullable AndroidSdkHandler handler) {
mHandler = handler;
if (handler == null) {
mRepoManager = null;
} else {
// This is just loading the local repo, so no need to show progress here.
mRepoManager =
handler.getSdkManager(new QuietProgressIndicator(getProgressIndicator()));
}
}
public void setOutputStream(@NonNull PrintStream out) {
mOut = out;
}
public void setInputStream(@Nullable InputStream in) {
mIn = in == null ? null : new BufferedReader(new InputStreamReader(in));
}
@NonNull
public static SdkManagerCliSettings createSettings(
@NonNull List<String> args, @NonNull FileSystem fileSystem)
throws ShowUsageException, FailSilentlyException {
return createSettings(args, fileSystem, System.getenv());
}
@NonNull
@VisibleForTesting
static SdkManagerCliSettings createSettings(
@NonNull List<String> args,
@NonNull FileSystem fileSystem,
@NonNull Map<String, String> environment)
throws ShowUsageException, FailSilentlyException {
ProgressIndicator progress =
new ConsoleProgressIndicator() {
@Override
public void logInfo(@NonNull String s) {}
@Override
public void logVerbose(@NonNull String s) {}
};
return new SdkManagerCliSettings(args, fileSystem, environment, progress);
}
private boolean setAction(@NonNull SdkAction action, @NonNull ProgressIndicator progress) {
//noinspection VariableNotUsedInsideIf
if (mAction != null) {
progress.logError(
"Only one of "
+ Joiner.on(", ").join(ARG_TO_ACTION.keySet())
+ " can be specified.");
return false;
}
mAction = action;
return true;
}
@NonNull
private SocketAddress createAddress(@NonNull String host, int port)
throws FailSilentlyException {
if ("1".equals(mEnvironment.get(STUDIO_UNITTEST_DO_NOT_RESOLVE_PROXY_ENV))) {
return InetSocketAddress.createUnresolved(host, port);
}
try {
InetAddress address = InetAddress.getByName(host);
return new InetSocketAddress(address, port);
} catch (UnknownHostException e) {
getProgressIndicator().logError("Failed to resolve host " + host);
throw new FailSilentlyException();
}
}
@Nullable
@Override
public Channel getChannel() {
return Channel.create(mChannel);
}
@Override
public boolean getForceHttp() {
return mForceHttp;
}
@Override
public void setForceHttp(boolean force) {
mForceHttp = force;
}
@Override
public boolean getDisableSdkPatches() {
return mDisableSdkPatches;
}
@Override
public void setDisableSdkPatches(boolean disable) {
mDisableSdkPatches = disable;
}
@VisibleForTesting
public boolean getForceNoProxy() {
return mForceNoProxy;
}
@NonNull
public Path getLocalPath() {
return mLocalPath;
}
@NonNull
public SdkAction getAction() {
return mAction;
}
public boolean isVerbose() {
return mVerbose;
}
public boolean includeObsolete() {
return mIncludeObsolete;
}
@NonNull
public ProgressIndicator getProgressIndicator() {
PrintStream out = getOutputStream();
return new ConsoleProgressIndicator(out, System.err) {
@Override
public void logWarning(@NonNull String s, @Nullable Throwable e) {
if (mVerbose) {
super.logWarning(s, e);
} else {
super.logWarning(s, null);
}
}
@Override
public void logError(@NonNull String s, @Nullable Throwable e) {
if (mVerbose) {
super.logWarning(s, e);
} else {
super.logWarning(s, null);
}
throw new SdkManagerCli.UncheckedCommandFailedException();
}
@Override
public void logInfo(@NonNull String s) {
if (mVerbose) {
super.logInfo(s);
}
}
@Override
public void logVerbose(@NonNull String s) {
if (mVerbose) {
super.logVerbose(s);
}
}
};
}
@NonNull
@Override
public Proxy getProxy() {
return mProxyType == null ? Proxy.NO_PROXY : new Proxy(mProxyType, mProxyHost);
}
@VisibleForTesting
@Nullable
String getProxyHostStr() {
return mProxyHostStr;
}
@Nullable
public RepoManager getRepoManager() {
return mRepoManager;
}
@Nullable
public Downloader getDownloader() {
return mDownloader;
}
@Nullable
public BufferedReader getInputReader() {
return mIn;
}
@NonNull
public PrintStream getOutputStream() {
return mOut;
}
@Nullable
public AndroidSdkHandler getSdkHandler() {
return mHandler;
}
@NonNull
public FileSystem getFileSystem() {
return mFileSystem;
}
@NonNull
private static Proxy.Type extractProxyType(
@NonNull String type, @NonNull ProgressIndicator progress)
throws FailSilentlyException {
if (type.equals("socks")) {
return Proxy.Type.SOCKS;
} else if (type.equals("http") || type.equals("https")) {
return Proxy.Type.HTTP;
}
progress.logError("Valid proxy types are \"socks\" and \"http\".");
throw new FailSilentlyException();
}
private SdkManagerCliSettings(
@NonNull List<String> args,
@NonNull FileSystem fileSystem,
@NonNull Map<String, String> environment,
@NonNull ProgressIndicator progress)
throws ShowUsageException, FailSilentlyException {
mFileSystem = fileSystem;
mEnvironment = environment;
String proxyHost = null;
int proxyPort = -1;
String toolDir = System.getProperty(TOOLSDIR);
if (toolDir != null) {
// Assume we're in something similar to the expected place, "cmdline-tools/1.2.3". The parent of that should be the root.
Path toolRoot = fileSystem.getPath(toolDir).normalize().getParent();
if (toolRoot != null) {
Path toolRootName = toolRoot.getFileName();
// toolRootName can be null if toolRoot is "/"
if (toolRootName != null
&& toolRootName.toString().equals(SdkConstants.FD_CMDLINE_TOOLS)) {
mLocalPath = toolRoot.getParent();
}
}
}
args = new LinkedList<>(args);
Iterator<String> argIter = args.iterator();
// TODO: delegate parsing of arguments to actions that care about those arguments
while (argIter.hasNext()) {
String arg = argIter.next();
if (ARG_TO_ACTION.containsKey(arg)) {
if (!setAction(ARG_TO_ACTION.get(arg).apply(this), progress)) {
throw new ShowUsageException();
}
argIter.remove();
} else if (arg.equals(HELP_ARG)) {
throw new ShowUsageException();
} else if (arg.equals(NO_HTTPS_ARG)) {
setForceHttp(true);
argIter.remove();
} else if (arg.equals(NO_PROXY_ARG)) {
mForceNoProxy = true;
argIter.remove();
} else if (arg.equals(VERBOSE_ARG)) {
mVerbose = true;
argIter.remove();
} else if (arg.equals(INCLUDE_OBSOLETE_ARG)) {
mIncludeObsolete = true;
argIter.remove();
} else if (arg.startsWith(PROXY_HOST_ARG)) {
proxyHost = arg.substring(PROXY_HOST_ARG.length());
argIter.remove();
} else if (arg.startsWith(PROXY_PORT_ARG)) {
String value = arg.substring(PROXY_PORT_ARG.length());
try {
proxyPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
progress.logError(
String.format("Invalid port \"%s\"\nExpected an integer.", value));
throw new FailSilentlyException();
}
argIter.remove();
} else if (arg.startsWith(PROXY_TYPE_ARG)) {
String type = arg.substring(PROXY_TYPE_ARG.length());
mProxyType = extractProxyType(type, progress);
argIter.remove();
} else if (arg.startsWith(CHANNEL_ARG)) {
String value = arg.substring(CHANNEL_ARG.length());
try {
mChannel = Integer.parseInt(value);
} catch (NumberFormatException e) {
progress.logError(
"Invalid channel id \""
+ value
+ "\"Expected an integer.\nTry "
+ HELP_ARG
+ " for more information.");
throw new FailSilentlyException();
}
argIter.remove();
} else if (arg.startsWith(SDK_ROOT_ARG)) {
mLocalPath = fileSystem.getPath(arg.substring(SDK_ROOT_ARG.length()));
argIter.remove();
}
}
if (mAction == null) {
mAction = new InstallAction(this);
}
for (String arg : args) {
if (!mAction.consumeArgument(arg, progress)) {
progress.logError(String.format("Unknown argument \"%s\"", arg));
progress.logError("");
throw new ShowUsageException();
}
}
if (mLocalPath == null) {
progress.logError("Could not determine SDK root.");
progress.logError(
"Either specify it explicitly with "
+ SDK_ROOT_ARG
+ " or move this package into its expected location: <sdk>"
+ File.separator
+ "cmdline-tools"
+ File.separator
+ "latest"
+ File.separator);
throw new FailSilentlyException();
}
if (mForceNoProxy && (proxyHost != null || proxyPort != -1 || mProxyType != null)) {
progress.logError(
String.format(
"None of %1$s, %2$s, and %3$s must be specified if %4$s is.",
PROXY_HOST_ARG, PROXY_PORT_ARG, PROXY_TYPE_ARG, NO_PROXY_ARG));
throw new FailSilentlyException();
} else if (proxyHost == null ^ mProxyType == null || proxyPort == -1 ^ mProxyType == null) {
progress.logError(
String.format(
"All of %1$s, %2$s, and %3$s must be specified if any are.",
PROXY_HOST_ARG, PROXY_PORT_ARG, PROXY_TYPE_ARG));
throw new FailSilentlyException();
} else {
if (mForceNoProxy) {
return;
}
if (proxyHost == null) {
// Try to take values from the HTTP_PROXY / HTTPS_PROXY environment variables.
String proxyEnv;
if (mForceHttp) {
proxyEnv = mEnvironment.get(HTTP_PROXY_ENV);
} else {
proxyEnv = mEnvironment.get(HTTPS_PROXY_ENV);
if (proxyEnv == null) {
proxyEnv = mEnvironment.get(HTTP_PROXY_ENV);
}
}
if (proxyEnv != null) {
try {
URL url = new URL(proxyEnv);
mProxyType = extractProxyType(url.getProtocol(), progress);
proxyHost = url.getHost();
proxyPort = url.getPort();
} catch (MalformedURLException e) {
progress.logError(
"The proxy server URL extracted from HTTP_PROXY or "
+ "HTTPS_PROXY environment variable could not be parsed. "
+ "Either specify the correct URL or unset the environment variable.",
e);
throw new FailSilentlyException();
}
}
}
if (proxyHost != null) {
SocketAddress address = createAddress(proxyHost, proxyPort);
mProxyHost = address;
mProxyHostStr = proxyHost;
}
}
}
}