java - Apache Commons Exec 更改 PATH 并执行 virtualenv 的 pip

标签 java python linux apache-commons apache-commons-exec

我在使用 Apache Commons Exec 库更改 PATH 环境变量以指向目标目录中创建的 Python virtualenv 时遇到一些困难。

理想情况下,我想要相当于激活 Python virtualenv 的东西,但是是在 Java 中。据我所知,最好的方法是更改​​环境变量,以便在我的 othervenv (这是我主要使用的另一个 virtualenv)之前发现它的 pip 和 python 可执行文件。

我的 PluginUtils 类中有这个方法:

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();
    }

然后这个类调用这个方法。

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 会在运行 pip 调用不正确的可执行文件时返回正确的 pip 可执行文件。我可以直接使用 venvPip 来安装 jinja2,但我想避免向 pip 传递绝对路径,而是让它在 PATH 上可发现。

我认为可能存在竞争条件,但我添加了 DefaultExecuteResultHandler,因此所有子进程调用都是同步的,但这似乎没有帮助。

最佳答案

简短回答:在构建命令行时,需要引用正确的 pythonpip 可执行文件。一种简单的方法是将 venv 位置存储在占位符 map 中,例如

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 时发生的情况

  1. Runtime#exec 查阅传递给 JVM 的 PATH 并扫描这些目录以查找名为 which 的可执行文件
  2. Runtime#exec 找到 /usr/bin/which 并使用包含更新的 PATH 的新环境启动它
  3. /usr/bin/which 查阅传递给它的 PATH 并扫描这些目录以查找名为 pip 的可执行文件
  4. 因为 /usr/bin/which 在更新的 PATH 下运行,它会找到 testvenv/bin/pip 并打印其位置

这是启动 pip install jinja2 时发生的情况:

  1. Runtime#exec 查询传递给 JVM 的 PATH 并扫描这些目录以查找名为 pip 的可执行文件
  2. Runtime#exec 找到 otherenv/bin/pip 并使用包含更新的 PATH 的新环境启动它
  3. otherenv/bin/pip 尝试对 otherenv 进行操作,因此无法执行任务

关于java - Apache Commons Exec 更改 PATH 并执行 virtualenv 的 pip,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40771041/

相关文章:

java - 无法为目标加载共享库 'gdx'

java - 如何使用 GSON 解析漂亮的 JSON

java - 在实现使用原始类型的接口(interface)时如何避免警告?

python - 为什么范围在 Python-3 中不会耗尽?

c - OpenBSD 和 Linux 上 pthread_cond_wait 的不同行为

linux - 如果行匹配,如何在目标文件中标记行

python - 更改特定 python 安装的 python 路径

java - 在 Eclipse 中识别类的可视化帮助

python - 如何在 El Capitan 上通过 pip 安装 tensorflow?

python - 如何检查子字符串是否为ascii+latin字符并用空格填充ascii/latin字符子字符串?