34

How can I activate virtualenv in a Makefile?

I have tried:

venv:
    @virtualenv venv

active:
    @source venv/bin/activate

And I've also tried:

active:
    @. venv/bin/activate

and it doesn't activate virtualenv.

captncraig
  • 22,118
  • 17
  • 108
  • 151
Regis Santos
  • 3,469
  • 8
  • 43
  • 65
  • 4
    I think what's he is asking is that if a do `make` my shell will use this environment. You'll see the `(venv)` in from of your hostname. – Pobe Sep 08 '17 at 16:22

7 Answers7

16

Here's how to do it:

You can execute a shell command in a Makefile using ();

E.g.

echoTarget: 
    (echo "I'm an echo")

Just be sure to put a tab character before each line in the shell command. i.e. you will need a tab before (echo "I'm an echo")

Here's what will work for activating virtualenv:

activate:
    ( \
       source path/to/virtualenv/activate; \
       pip install -r requirements.txt; \
    )
wizurd
  • 3,541
  • 3
  • 33
  • 50
  • 13
    `/bin/sh: 1: source: not found` – Regis Santos Nov 21 '15 at 04:20
  • try changing "source" to this: bash -c "source path/to/virtualenv/bin/activate"; – wizurd Nov 21 '15 at 04:31
  • 2
    This might install the requirements for the given environment but the user still has to initiate the environment via a shell command. This does not fix the problem. `make` somehow needs to run as the current shell process (if i understand correctly). – Pobe Sep 08 '17 at 16:26
  • 6
    Try using `. venvpath/bin/activate` in place of `source` – eternaltyro Nov 22 '18 at 01:23
  • 3
    `make` might be using `sh` instead of `bash`. `source` is a bash-ism while `.` is the standard POSIX way, hence more portable. This is why it might make a difference. – Marcello Romani Mar 10 '19 at 23:21
  • 2
    Just found a SO answer which explains how to use `bash` `Makefile`s https://stackoverflow.com/questions/589276/how-can-i-use-bash-syntax-in-makefile-targets – Marcello Romani Mar 10 '19 at 23:27
  • 1
    @Pobe is right. Even that the requirements are properly installed into venv, it is not activated in the current shell that `make` was run – artu-hnrq Jan 27 '21 at 02:46
  • @Regis Santos put `SHELL:=/bin/bash` in the `Makefile` to set shell to `bash`. – Bálint Sass Apr 01 '23 at 14:28
14

UPD Mar 14 '21

One more variant for pip install inside virtualenv:

# Makefile

all: install run

install: venv
    : # Activate venv and install smthing inside
    . venv/bin/activate && pip install -r requirements.txt
    : # Other commands here

venv:
    : # Create venv if it doesn't exist
    : # test -d venv || virtualenv -p python3 --no-site-packages venv
    test -d venv || python3 -m venv venv

run:
    : # Run your app here, e.g
    : # determine if we are in venv,
    : # see https://stackoverflow.com/q/1871549
    . venv/bin/activate && pip -V

    : # Or see @wizurd's answer to exec multiple commands
    . venv/bin/activate && (\
      python3 -c 'import sys; print(sys.prefix)'; \
      pip3 -V \
    )

clean:
    rm -rf venv
    find -iname "*.pyc" -delete

So you can run make with different 'standard' ways:

  • make - target to default all
  • make venv - to just create virtualenv
  • make install - to make venv and execute other commands
  • make run - to execute your app inside venv
  • make clean - to remove venv and python binaries
viktorkho
  • 618
  • 6
  • 19
2

I built a simple way to do that by combining an ACTIVATE_LINUX:=. venv/bin/activate variable with .ONESHELL: at the begging of the Makefile:

.ONESHELL:
.PHONY: clean venv tests scripts

ACTIVATE_LINUX:=. venv/bin/activate

setup: venv install scripts
    
venv:
    @echo "Creating python virtual environment in 'venv' folder..."
    @python3 -m venv venv

install:
    @echo "Installing python packages..."
    @$(ACTIVATE_LINUX)
    @pip install -r requirements.txt

clean:
    @echo "Cleaning previous python virtual environment..."
    @rm -rf venv

As pointed out in other answers, Make recipes run on sh instead of bash, so using . in the ACTIVATE_LINUX variable instead of @ to activate the virtual environment is the correct syntax.

I combined this strategy with .ONESHELL:. As shown at this StackOverflow anwser, normally Make runs every command in a recipe in a different subshell. However, setting .ONESHELL: will run all the commands in a recipe in the same subshell, allowing you to activate a virtual environment and then run commands inside it. That's exactly what's happening in make install target and this approach could be applied wherever you need an activated environment to run some python code in your project.

So you can run the following make targets:

  • make - target to default setup;
  • make venv - to just create virtual environment;
  • make install - to activate venv and execute the pip install command; and
  • make clean - to remove previous venv and python binaries.
1

When it is time to execute recipes to update a target, they are executed by invoking a new sub-shell for each line of the recipe. --- GNU Make

Since each line of the recipe executes in a separate sub-shell, we should run the python code in the same line of the recipe.

Simple python script for showing the current source of python environment (filename: whichpy.py):

import sys
print(sys.prefix)

Running python virtual environment (Make recipes run on sh instead of bash, using . to activate the virtual environment is the correct syntax):

test:
    . pyth3.6/bin/activate && python3.6 whichpy.py
    . pyth3.6/bin/activate; python3.6 whichpy.py

Both the above 2 recipes are acceptable and we are free to use backslash/newline to separate one recipe into multiple lines.

Randy Chuang
  • 21
  • 1
  • 4
0

Makefiles can't activate an environment directly. This is what worked for me:

activate:
    bash -c "venv/bin/activate"

If you get a permission denied error make venv/bin/activate executable:

chmod +x venv/bin/activate
  • 2
    `activate` should be sourced, not executed. How does `source venv/bin/activate` work for you, instead of your `bash -c...` line? – Gauthier Apr 16 '20 at 08:44
0

An alternative to activate could be use an environment variable in Makefile. For example:

.PHONY: venv install

VENV=.venv
PYTHON=$(VENV)/bin/python3

venv:
    python3 -m venv $(VENV)

install: venv
    $(PYTHON) -m pip install -r requirements-dev.txt

isort:
    $(PYTHON) -m isort --check-only .

black:
    $(PYTHON) -m black --check .

mypy:
    $(PYTHON) -m mypy .

flake8:
    $(PYTHON) -m flake8 .

bandit:
    $(PYTHON) -m bandit -r app

lint: isort black mypy flake8 bandit

test:  ## Run tests
    $(PYTHON) -m pytest
0

One way to do it: prepend each command in Makefile with . venv/bin/activate && which must be run under venv.

That is, use:

target:
  . venv/bin/activate && command

instead of

target:
  command
Bálint Sass
  • 310
  • 3
  • 11