Search code examples
javamultithreadingbashunixprocessbuilder

Activating virtualenv via Java ProcessBuilder


Getting the following when trying to programmatically activate Python's virtualenv via the code below:

java.io.IOException: Cannot run program "." (in directory "/Users/simeon.../..../reporting"): error=13, Permission denied
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at VirtualEnvCreateCmdTest.runCommandInDirectory(VirtualEnvCreateCmdTest.java:30)
    at VirtualEnvCreateCmdTest.createVirtEnv(VirtualEnvCreateCmdTest.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at ......
Caused by: java.io.IOException: error=13, Permission denied
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 25 more

Code:

public class VirtualEnvCreateCmdTest {

    private final static Logger LOG = LoggerFactory.getLogger(VirtualEnvCreateCmdTest.class);

    private void runCommandInDirectory(String path,String ... command) throws Throwable
    {

        LOG.info("Running command '"+String.join(" ",command)+"' in path '"+path+"'");


        ProcessBuilder builder = new ProcessBuilder(command)
                .directory(new File(path))
                .inheritIO();

        Process pr = builder.start();

        final String failureMsg = format("Failed to run '%s' in path '%s'.  Got exit code: %d", join(" ",command), path, pr.exitValue());
        LOG.info("prepared message {}: ", failureMsg);

        if(!pr.waitFor(120, TimeUnit.SECONDS))
        {
            throw new Exception(failureMsg);

        }


        int output = IOUtils.copy(pr.getInputStream(), System.out);

        int exitCode=pr.exitValue();

        if(exitCode!=0)
            throw new Exception(failureMsg);

    }

    @Test
    public void createVirtEnv()  throws Throwable {

        String path = "/Users/simeon/.../reporting";
        String [] commands = new String[]{".", "activate"};
        //String [] commands = new String [] {"/bin/bash", "-c", ". /Users/simeon/..../venv2.7/bin/activate"};
        runCommandInDirectory(path, commands);

    }

Changing permissioning on the file, doesn't seem to work:

chmod u+x ./bin/activate

Doing it via /bin/bash still doesn't work though with a different error.

At the same time the following works fine on the command line:

. /Users/simeon/.../venv2.7/bin/activate

Any samples of how one would invoke python virtualenv activate command from within Java?

=== What worked: =====

The following ended up working for me:

    private void runDjangoMigrate() throws Throwable {

        final String REPORTING_PROJECT_LOCATION = "/Users/simeon.../.../reporting";
        final String UNIX_SHELL_LOCATION = "/bin/bash";
        final String PYTHON_VIRTUALENV_ACTIVATOR_COMMAND =". /Users/simeon.../.../venv2.7/bin/activate;";
        final String PYTHON_VIRTUALENV_ACTIVATOR_COMMAND =". " + PYTHON_VIRTUALENV_ACTIVATE_SCRIPT_LOCATION + ";"; 
        final String DJANGO_MANAGE_MODULE = " manage.py";

        final String[] DJANGO_MIGRATE_COMMAND = new String[] { UNIX_SHELL_LOCATION, "-c", PYTHON_VIRTUALENV_ACTIVATOR_COMMAND
                + PYTHON_INTERPRETER + DJANGO_MANAGE_MODULE + " migrate --noinput --fake-initial" };

        runCommandInDirectory(REPORTING_PROJECT_LOCATION, DJANGO_MIGRATE_COMMAND);


}


    private void runCommandInDirectory(String path, String... command) throws Throwable {

        LOG.info(format("Running '%s' command in path '%s'", join(" ",command),path));

        ProcessBuilder builder = new ProcessBuilder(command).directory(new File(path)).inheritIO();

        Process pr = null;

        pr = builder.start();

        IOUtils.copy(pr.getInputStream(), System.out);  

        boolean terminated = pr.waitFor(SPAWNED_PYTHON_PROCESS_TTL_SEC, SECONDS);

        int exitCode = -1;

        if (terminated) {

            exitCode = pr.exitValue();
        }

        if (exitCode != 0) {

            final String failureMsg = format("Failed to run '%s' in path '%s'.  Got exit code: %d", join(" ", command),
                    path, pr.exitValue());

            throw new Exception(failureMsg);
        }

    }

and producing the following expected output:

[java] System check identified some issues:
 [java]
 [java] WARNINGS:
 [java] ?: (urls.W005) URL namespace 'admin' isn't unique. You may not be able to reverse all URLs in this namespace
 [java] Building permissions...
 [java] Operations to perform:
 [java]   Apply all migrations: admin, auth, contenttypes, sessions
 [java] Running migrations:
 [java]   No migrations to apply.
 [java] System check identified some issues:
 [java]
 [java] WARNINGS:
 [java] ?: (urls.W005) URL namespace 'admin' isn't unique. You may not be able to reverse all URLs in this namespace
 [java] Building permissions...
 [java] User exists, exiting normally due to --preserve
 [java] System check identified some issues:
 [java]

Solution

  • You can activate a Python virtual environment in bash (and perhaps zsh) and Python, but not in Java or any other environment. The main thing to understand is that virtual environment activation doesn't do some system magic — the activation script just changes the current environment, and Python virtual environments are prepared to changes shell or Python but nothing else.

    In terms of Java it means that when you call runCommandInDirectory() it runs a new shell, that new shell briefly activates a virtual env, but then the shell exits and all changes that "activates" the virtual env are gone.

    This in turn means that if you need to run some shell or Python commands in a virtual env you have to activate the environment in every runCommandInDirectory() invocation:

    String [] commands1 = new String [] {"/bin/bash", "-c", ". /Users/simeon/..../venv2.7/bin/activate; script1.sh"};
    runCommandInDirectory(path, commands)
    
    String [] commands2 = new String [] {"/bin/bash", "-c", ". /Users/simeon/..../venv2.7/bin/activate; script2.sh"};
    runCommandInDirectory(path, commands)
    

    For Python scripts it's a bit simpler as you can run python from the environment and it automagically activates the environment:

    String [] commands = new String [] {"/Users/simeon/..../venv2.7/bin/python", "script.py"};
    runCommandInDirectory(path, commands)