Apache Commons Exec 更改 PATH 并执行 virtualenv 的 pip
Apache Commons Exec Change PATH and Execute virtualenv's pip
我在使用 Apache Commons Exec 库更改 PATH 环境变量以指向目标目录中创建的 Python virtualenv 时遇到了一些困难。
理想情况下,我想要的东西相当于激活 Python virtualenv,但在 Java 中。据我所知,最好的方法是更改环境变量,以便在我的 othervenv
(这是我主要使用的另一个 virtualenv)之前发现它的 pip 和 python 可执行文件。
我的 PluginUtils
class:
中有这个方法
public static String callAndGetOutput(CommandLine commandLine, Map<String, String> environment) throws IOException
{
CollectingLogOutputStream outputStream = new CollectingLogOutputStream();
Executor executor = new DefaultExecutor();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
executor.execute(commandLine, environment, resultHandler);
try
{
// Wait for the subprocess to finish.
resultHandler.waitFor();
}
catch(InterruptedException e)
{
throw new IOException(e);
}
return outputStream.getOuput();
}
然后这个 class 调用这个方法。
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.environment.EnvironmentUtils;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
public class Example
{
public void run() throws Exception
{
Map<String, String> env = EnvironmentUtils.getProcEnvironment();
// env.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which python"), env));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which pip"), env));
Path venvDir = Paths.get("", "target", "testvenv");
Path venvBin = venvDir.resolve("bin");
assert(Files.isDirectory(venvDir));
assert(Files.isDirectory(venvBin));
env.put("PATH", venvBin.toAbsolutePath().toString()+ File.pathSeparator +env.get("PATH"));
env.put("VIRTUAL_ENV", venvDir.toAbsolutePath().toString());
// env.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which python"), env));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which pip"), env));
Path venvPip = venvBin.resolve("pip");
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("pip install jinja2"), env));
}
public static void main(String[] args) throws Exception
{
Example example = new Example();
example.run();
}
}
输出结果如下:
/home/lucas/.virtualenvs/othervenv/bin/python
/home/lucas/.virtualenvs/othervenv/bin/pip
/home/lucas/projects/myproject/mymodule/target/testvenv/bin/python
/home/lucas/projects/myproject/mymodule/target/testvenv/bin/pip
Requirement already satisfied: jinja2 in /home/lucas/.virtualenvs/othervenv/lib/python2.7/site-packages
Requirement already satisfied: MarkupSafe in /home/lucas/.virtualenvs/othervenv/lib/python2.7/site-packages (from jinja2)
我很困惑为什么 which pip
会 return 正确的 pip 可执行文件,而 运行 pip
调用不正确的可执行文件。我能够直接使用 venvPip
来安装 jinja2,但我想避免将绝对路径传递给 pip,而是让它在 PATH 上被发现。
我认为可能存在竞争条件,但我添加了 DefaultExecuteResultHandler
因此所有子进程调用都是同步的,这似乎没有帮助。
简答:构建命令行时需要参考正确的python
或pip
可执行文件。一种简化方法是将 venv 位置存储在占位符映射中,例如
CommandLine.parse("${VBIN}/pip install jinja2",
Collections.singletonMap("VBIN", venvBin.toAbsolutePath().toString()))
从技术上讲,也应该可以通过 shell 启动命令,例如sh pip install jinja2
但这不能移植到非 unix 系统。
长答案:
Java Runtime#exec
(commons.exec 最终在大多数平台上调用)用于搜索可执行文件的 PATH 不受后来传递给衍生进程的环境的影响。
这是 which pip
启动时发生的情况
Runtime#exec
查阅传递给 JVM 的 PATH 并扫描这些目录以查找名为 which
的可执行文件
Runtime#exec
找到 /usr/bin/which
并使用包含更新的 PATH 的新环境启动它
/usr/bin/which
查阅传递给它的 PATH 并扫描这些目录以查找名为 pip
的可执行文件
- 因为
/usr/bin/which
根据更新的 PATH 运行,它找到 testvenv/bin/pip
并打印其位置
这是启动 pip install jinja2
时发生的情况:
Runtime#exec
查阅传递给 JVM 的 PATH 并扫描这些目录以查找名为 pip
的可执行文件
Runtime#exec
找到 otherenv/bin/pip
并使用包含更新的 PATH 的新环境启动它
otherenv/bin/pip
尝试对 otherenv
进行操作,因此无法执行任务
我在使用 Apache Commons Exec 库更改 PATH 环境变量以指向目标目录中创建的 Python virtualenv 时遇到了一些困难。
理想情况下,我想要的东西相当于激活 Python virtualenv,但在 Java 中。据我所知,最好的方法是更改环境变量,以便在我的 othervenv
(这是我主要使用的另一个 virtualenv)之前发现它的 pip 和 python 可执行文件。
我的 PluginUtils
class:
public static String callAndGetOutput(CommandLine commandLine, Map<String, String> environment) throws IOException
{
CollectingLogOutputStream outputStream = new CollectingLogOutputStream();
Executor executor = new DefaultExecutor();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
executor.execute(commandLine, environment, resultHandler);
try
{
// Wait for the subprocess to finish.
resultHandler.waitFor();
}
catch(InterruptedException e)
{
throw new IOException(e);
}
return outputStream.getOuput();
}
然后这个 class 调用这个方法。
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.environment.EnvironmentUtils;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
public class Example
{
public void run() throws Exception
{
Map<String, String> env = EnvironmentUtils.getProcEnvironment();
// env.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which python"), env));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which pip"), env));
Path venvDir = Paths.get("", "target", "testvenv");
Path venvBin = venvDir.resolve("bin");
assert(Files.isDirectory(venvDir));
assert(Files.isDirectory(venvBin));
env.put("PATH", venvBin.toAbsolutePath().toString()+ File.pathSeparator +env.get("PATH"));
env.put("VIRTUAL_ENV", venvDir.toAbsolutePath().toString());
// env.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which python"), env));
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("which pip"), env));
Path venvPip = venvBin.resolve("pip");
System.out.println(PluginUtilities.callAndGetOutput(CommandLine.parse("pip install jinja2"), env));
}
public static void main(String[] args) throws Exception
{
Example example = new Example();
example.run();
}
}
输出结果如下:
/home/lucas/.virtualenvs/othervenv/bin/python
/home/lucas/.virtualenvs/othervenv/bin/pip
/home/lucas/projects/myproject/mymodule/target/testvenv/bin/python
/home/lucas/projects/myproject/mymodule/target/testvenv/bin/pip
Requirement already satisfied: jinja2 in /home/lucas/.virtualenvs/othervenv/lib/python2.7/site-packages
Requirement already satisfied: MarkupSafe in /home/lucas/.virtualenvs/othervenv/lib/python2.7/site-packages (from jinja2)
我很困惑为什么 which pip
会 return 正确的 pip 可执行文件,而 运行 pip
调用不正确的可执行文件。我能够直接使用 venvPip
来安装 jinja2,但我想避免将绝对路径传递给 pip,而是让它在 PATH 上被发现。
我认为可能存在竞争条件,但我添加了 DefaultExecuteResultHandler
因此所有子进程调用都是同步的,这似乎没有帮助。
简答:构建命令行时需要参考正确的python
或pip
可执行文件。一种简化方法是将 venv 位置存储在占位符映射中,例如
CommandLine.parse("${VBIN}/pip install jinja2",
Collections.singletonMap("VBIN", venvBin.toAbsolutePath().toString()))
从技术上讲,也应该可以通过 shell 启动命令,例如sh pip install jinja2
但这不能移植到非 unix 系统。
长答案:
Java Runtime#exec
(commons.exec 最终在大多数平台上调用)用于搜索可执行文件的 PATH 不受后来传递给衍生进程的环境的影响。
这是 which pip
启动时发生的情况
Runtime#exec
查阅传递给 JVM 的 PATH 并扫描这些目录以查找名为which
的可执行文件
Runtime#exec
找到/usr/bin/which
并使用包含更新的 PATH 的新环境启动它
/usr/bin/which
查阅传递给它的 PATH 并扫描这些目录以查找名为pip
的可执行文件
- 因为
/usr/bin/which
根据更新的 PATH 运行,它找到testvenv/bin/pip
并打印其位置
这是启动 pip install jinja2
时发生的情况:
Runtime#exec
查阅传递给 JVM 的 PATH 并扫描这些目录以查找名为pip
的可执行文件
Runtime#exec
找到otherenv/bin/pip
并使用包含更新的 PATH 的新环境启动它
otherenv/bin/pip
尝试对otherenv
进行操作,因此无法执行任务