2016-11-23 41 views
2

我在使用Apache Commons Exec库更改PATH环境变量以指向我的目标目录中创建的Python virtualenv时遇到了一些困难。Apache Commons Exec更改PATH并执行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) 

我很困惑,为什么在运行pipwhich pip将返回正确点子可执行文件调用不正确的可执行文件。我能够直接使用venvPip来安装jinja2,但我想避免将绝对路径传递给pip,而是让它在PATH上被发现。

我在想这可能是一个竞争条件,但我增加了DefaultExecuteResultHandler所以所有的子进程调用是同步的,这似乎没有帮助。

回答

2

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

CommandLine.parse("${VBIN}/pip install jinja2", 
    Collections.singletonMap("VBIN", venvBin.toAbsolutePath().toString())) 

从技术上讲,还应该可以通过shell启动命令,例如, sh pip install jinja2但这不能移植到非unix系统。

龙回答: 的路径的Java Runtime#exec(即commons.exec最终调用在大多数平台上)使用搜索可执行不受稍后传递到生成过程中的环境。

这是当which pip启动

  1. Runtime#exec会发生什么咨询传递给JVM PATH和扫描这些目录名为可执行文件which
  2. Runtime#exec发现/usr/bin/which并用新的环境,启动它是包含更新的PATH
  3. /usr/bin/which查询传递给它的PATH并扫描这些目录以查找名为pip
  4. 01的可执行文件
  5. 因为/usr/bin/which工作过更新的路径中找到testvenv/bin/pip并打印其位置

这就是当pip install jinja2启动情况:

  1. Runtime#exec咨询传递给JVM PATH和扫描这些目录对于名为pip
  2. Runtime#execRuntime#exec的可执行文件找到otherenv/bin/pip,并使用包含更新的PATH的新环境启动它
  3. otherenv/bin/pip尝试在otherenv上运行,从而无法执行任务
相关问题