blob: 86689ea3380ee009d5d9672537b3836d3866566f [file] [log] [blame]
/*
* Copyright (C) 2016 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.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.TreeSet;
import java.util.Collection;
/**
* Sets up a Python virtualenv on the host and installs packages. To activate it, the working
* directory is changed to the root of the virtualenv.
*
* This's a fork of PythonVirtualenvPreparer and is forked in order to simplify the change
* deployment process and reduce the deployment time, which are critical for VTS services.
* That means changes here will be upstreamed gradually.
*/
@OptionClass(alias = "python-venv")
public class VtsPythonVirtualenvPreparer implements ITargetPreparer, ITargetCleaner {
private static final String PIP = "pip";
private static final String PATH = "PATH";
private static final String OS_NAME = "os.name";
private static final String WINDOWS = "Windows";
protected static final String PYTHONPATH = "PYTHONPATH";
protected static final String VIRTUAL_ENV_PATH = "VIRTUALENVPATH";
private static final int BASE_TIMEOUT = 1000 * 60;
private static final String[] DEFAULT_DEP_MODULES = {
"future", "futures", "enum", "protobuf", "requests", "httplib2",
"google-api-python-client", "oauth2client"};
@Option(name = "venv-dir", description = "path of an existing virtualenv to use")
private File mVenvDir = null;
@Option(name = "requirements-file", description = "pip-formatted requirements file")
private File mRequirementsFile = null;
@Option(name = "script-file", description = "scripts which need to be executed in advance")
private Collection<String> mScriptFiles = new TreeSet<>();
@Option(name = "dep-module", description = "modules which need to be installed by pip")
private Collection<String> mDepModules = new TreeSet<>(Arrays.asList(DEFAULT_DEP_MODULES));
IRunUtil mRunUtil = new RunUtil();
String mPip = PIP;
/**
* {@inheritDoc}
*/
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
startVirtualenv(buildInfo);
installDeps(buildInfo);
}
/**
* {@inheritDoc}
*/
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
if (mVenvDir != null) {
FileUtil.recursiveDelete(mVenvDir);
CLog.i("Deleted the virtual env's temp working dir, %s.", mVenvDir);
mVenvDir = null;
}
}
protected void installDeps(IBuildInfo buildInfo) throws TargetSetupError {
boolean hasDependencies = false;
if (!mScriptFiles.isEmpty()) {
for (String scriptFile : mScriptFiles) {
CLog.i("Attempting to execute a script, %s", scriptFile);
CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, scriptFile);
if (c.getStatus() != CommandStatus.SUCCESS) {
CLog.e("Executing script %s failed", scriptFile);
throw new TargetSetupError("Failed to source a script");
}
}
}
if (mRequirementsFile != null) {
CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip,
"install", "-r", mRequirementsFile.getAbsolutePath());
if (c.getStatus() != CommandStatus.SUCCESS) {
CLog.e("Installing dependencies from %s failed",
mRequirementsFile.getAbsolutePath());
throw new TargetSetupError("Failed to install dependencies with pip");
}
hasDependencies = true;
}
if (!mDepModules.isEmpty()) {
for (String dep : mDepModules) {
CLog.i("Attempting installation of %s", dep);
CommandResult result = mRunUtil.runTimedCmd(
BASE_TIMEOUT * 5, mPip, "install", dep);
CLog.i(String.format(
"Result %s. stdout: %s, stderr: %s",
result.getStatus(), result.getStdout(), result.getStderr()));
if (result.getStatus() != CommandStatus.SUCCESS) {
CLog.e("Installing %s failed.", dep);
CLog.i("Attempting to upgrade %s", dep);
result = mRunUtil.runTimedCmd(
BASE_TIMEOUT * 5, mPip, "install", "--upgrade", dep);
if (result.getStatus() != CommandStatus.SUCCESS) {
throw new TargetSetupError(String.format(
"Failed to install dependencies with pip. "
+ "Result %s. stdout: %s, stderr: %s",
result.getStatus(), result.getStdout(), result.getStderr()));
} else {
CLog.i(String.format(
"Result %s. stdout: %s, stderr: %s",
result.getStatus(), result.getStdout(), result.getStderr()));
}
}
hasDependencies = true;
}
}
if (!hasDependencies) {
CLog.i("No dependencies to install");
} else {
// make the install directory of new packages available to other classes that
// receive the build
buildInfo.setFile(PYTHONPATH, new File(mVenvDir,
"local/lib/python2.7/site-packages"),
buildInfo.getBuildId());
}
}
protected void startVirtualenv(IBuildInfo buildInfo) throws TargetSetupError {
if (mVenvDir != null) {
CLog.i("Using existing virtualenv based at %s", mVenvDir.getAbsolutePath());
activate();
return;
}
try {
mVenvDir = buildInfo.getFile(VIRTUAL_ENV_PATH);
if (mVenvDir == null) {
mVenvDir = FileUtil.createTempDir(buildInfo.getTestTag() + "-virtualenv");
}
String virtualEnvPath = mVenvDir.getAbsolutePath();
mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", virtualEnvPath);
CLog.i(VIRTUAL_ENV_PATH + " = " + virtualEnvPath + "\n");
buildInfo.setFile(VIRTUAL_ENV_PATH, new File(virtualEnvPath),
buildInfo.getBuildId());
activate();
} catch (IOException e) {
CLog.e("Failed to create temp directory for virtualenv");
throw new TargetSetupError("Error creating virtualenv", e);
}
}
protected void addDepModule(String module) {
mDepModules.add(module);
}
protected void setRequirementsFile(File f) {
mRequirementsFile = f;
}
/**
* This method returns whether the OS is Windows.
*/
private static boolean isOnWindows() {
return System.getProperty(OS_NAME).contains(WINDOWS);
}
private void activate() {
File binDir = new File(mVenvDir, isOnWindows() ? "Scripts" : "bin");
mRunUtil.setWorkingDir(binDir);
String path = System.getenv(PATH);
mRunUtil.setEnvVariable(PATH, binDir + File.pathSeparator + path);
File pipFile = new File(binDir, PIP);
pipFile.setExecutable(true);
mPip = pipFile.getAbsolutePath();
}
}