2

I'm trying to create a Python virtual environment with a Makefile and also activate it once the make command finishes to ease things for the user. Apparently, this is not possible because "a child process can not alter the parent's environment." I was wondering if there's any workaround for this. This is part of my Makefile so far: .PHONY: create-venv venv .DEFAULT_GOAL := all SHELL=/bin/bash

CPUTYPE = $(shell uname -m | sed "s/\\ /_/g")
SYSTYPE = $(shell uname -s)
BUILDDIR = build/$(SYSTYPE)-$(CPUTYPE)
VENV_NAME?=venv
VENV_DIR=$(BUILDDIR)/${VENV_NAME}
VENV_BIN=$(shell pwd)/${VENV_DIR}/bin
VENV_ACTIVATE=. ${VENV_BIN}/activate

PYTHON=${VENV_BIN}/python3

create-venv:
    test -d $(BUILDDIR) || mkdir -p $(BUILDDIR)
    which python3 || apt install -y python3 python3-pip
    test -d $(VENV_DIR) || python3 -m venv $(VENV_DIR)

venv: ${VENV_BIN}/activate
${VENV_BIN}/activate: setup.py
    test -d $(VENV_DIR) || make create-venv
    ${PYTHON} -m pip install -r requirements.txt
    touch $(VENV_BIN)/activate
    source ${VENV_BIN}/activate # <- doesn't work
    . ${VENV_BIN}/activate # <- doesn't work either
Asclepius
  • 57,944
  • 17
  • 167
  • 143
aaragon
  • 2,314
  • 4
  • 26
  • 60
  • There is no possible way to do this solely through a makefile. The answer you quote explains it, there's nothing else to say. This is a fundamental security feature of all UNIX/POSIX systems since they were invented in the 1970's. If you want a venv set in your shell then you HAVE to set it in your shell: either directly, or via an alias or shell function. There's no program, including starting another script, or running make, or anything else, that will get around that. – MadScientist Apr 08 '20 at 12:57
  • 1
    @MadScientist the answer below does exactly what I wanted. – aaragon Apr 08 '20 at 13:11
  • It may do what you wanted but it doesn't do what you asked for. You asked to _create a Python virtual environment with a Makefile and also activate it once the make command finishes_ which is not what the answer below gives you. It gives you a new shell within the context of a makefile recipe (so the make command has not finished). You won't have access to any of the parent shell's local data (shell history, etc.) I should also point out you cannot use that solution with `make -j`. But, if it works for you that's good! – MadScientist Apr 08 '20 at 14:18
  • Yes I understand. From the user point perspective it seems as if the virtual environment was activated within the Makefile. An illusion that fools the user is an illusion that works for me... – aaragon Apr 08 '20 at 14:27
  • Sure. Just note that the user WILL be able to tell. For example, if they use up-arrow to retrieve the last command they invoked... it won't be there... – MadScientist Apr 08 '20 at 14:44

2 Answers2

5

You can activate the environment and run a shell in the activated env:

. ${VENV_BIN}/activate && exec bash

(Please note it must be in one line to be run in one shell; exec is used to replace the shell with a new one.)

When you finish working with the environment you exit and then the Makefile is finished.

phd
  • 82,685
  • 13
  • 120
  • 165
  • That worked! I got the message `The default interactive shell is now zsh.` because I'm on Mac and they changed to zsh, but I guess this is not a problem. Thanks! – aaragon Apr 08 '20 at 13:00
  • 1
    @aaragon Perhaps could be fixed calling `exec $$SHELL` instead of fixed `bash`; double `$` to pass single `$` to the shell — a syntax trick in Makefiles. – phd Apr 08 '20 at 14:32
  • That seems to have solved the zsh problem, but now the user doesn't know he/she is within a virtual environment because the prefix doesn't show anymore. Before it showed `(venv) `. Also, in Ubuntu Linux, both approaches don't show the `(venv)` prefix. – aaragon Apr 08 '20 at 14:52
  • Works for me in `bash`; cannot help with `zsh`, sorry. – phd Apr 08 '20 at 14:57
  • The Linux Shell is bash and none of the approaches shows the `(vent)`. Are you on Linux? Does it work for you? – aaragon Apr 08 '20 at 16:27
  • I'm on Linux. I activated a venv and ran `bash --norc` — it showed `(venv)`. I didn't try `make`, just a second `bash`. (`--norc` to avoid my code in `.basrc` that actiavtes venv is it sees `$VIRTULA_ENV`.) – phd Apr 08 '20 at 17:51
0

You could do something like this.

It relies on your looking at the activate script and seeing what env vars it sets, so it is totally ugly.

$(eval $(shell source $(PYTHON3_VENV)/bin/activate && echo "export PATH := $$PATH; export PYTHONHOME := $$PYTHONHOME; export VIRTUAL_ENV := $$VIRTUAL_ENV" ))
Mort
  • 3,379
  • 1
  • 25
  • 40