Jeremy Satterfield
Coding, Making and Tulsa Life

Manage All the Languages Using Python Virtualenv

A couple of years ago I was working with a couple other languages beside Python and began to get frustrated with their virtualenv equivalents. Not that they were doing anything wrong, just that they didn't work the way I was used to with virtualenvs. On top of that, they didn't work well together. I wanted to see if I could get something working that used the workflow I was used to and managed all the languages I was working with. So after a little poking around I found that many of the newer, "modern" languages had ways of running them from custom paths. I did a bit of work and before long I had a couple of these languages installed in virtualenvs and working side-by-side.

Then, a couple days ago I ran across this conversation on Twitter.

Most common Python issue I see with students is they've installed Python three or four different ways & have all their paths confused. 1/

— Jake VanderPlas (@jakevdp) October 24, 2017

I need to write up how I manage hundreds of projects without this being a problem. It took years to figure it out but it works. https://t.co/xM3fTs7e6u

— Jeff Triplett ✨ (@webology) October 25, 2017

A new development I'm really excited about is not just virtualenv, but also putting any non-Python executables in the virtualenvwrapper bin dir so it's associated with the project too.

— Rachel SKellyton 🔮 (@wholemilk) October 25, 2017

I have flirted with that for non-Python over the years. I liked it but lacked enough Node/Ruby knowledge to pull it off medium term.

— Jeff Triplett ✨ (@webology) October 25, 2017

THIS. It’s always intrigued me that there isn’t a “virtualenv for *all*” - one env, virtualising Py, Ruby, C… whatever.

— Russell Keith-Magee (@freakboy3742) October 25, 2017

And I realized that I had solved this problem for myself a while back and never really mentioned to anyone except a couple coworkers from time to time, and I certainly never shared it on this site or social media. So I'm going to take this opportunity to share how I manage several different languages using virtualenv.

One last note before we begin. I'm exclusively a Linux user and have been for many years. Most of these commands will work on Macs through the magic of Unix, but I have neither the knowledge or tools to troubleshoot Mac or Windows specific problems. Feel free to share solutions to problems you run into in the comments for others, but I will likely not be able to help too much for those OS's. If you're on Windows, please just use the Subsytem for Linux.

Python

With virtualenv being part of the Python tool chain, you're clearly going to need Python up and running. On Linux and Mac, your system probably already has it installed and ready to go. If your project works fine on this version or you're only managing other languages, this version should be fine. If you want to use a specific version, check out pyenv or downloading the source, but they each have docs that can walk you through those processes.

Installing virtualenv

I recommend, and assume for the rest of this post, using virtualenvwrapper and virtualenvs stored at ~/.virtualenv. If you have other preferences, everything here should still work, but you will need to replace my references to ~/.virtualenvs with the path to your environment.

Assuming you already have pip installed, with your Python version, you need to make sure that ~/.local/bin (or /home/<user>/.local/bin or /Users/<user>/.local/bin) is in your $PATH (echo $PATH). If not, add the following to your ~/.profile (or .zprofile, config.fish, etc.)

PATH=~/.local/bin:$PATH

If you needed to make this change, you should probably reboot your machine now to load that value into all your shell sessions.

This is going to allow you to use the --user flag for pip which installs the packages specified at a "global" level just for that user. This is useful for packages like virtualenv, virtualenvwrapper, ipdb, etc which can be used to manage Python and your environments. This should NOT be used for packages used within your project.

So with that set, install virtualenv:

pip install --user virtualenv virtualenvwrapper

Now you want need to edit your ~/.profile (or equivalent) and add the following and restart your shell.

export WORKON_HOME=$HOME/.virtualenvs
source $HOME/.local/bin/virtualenvwrapper.sh

Create an Virtual Environment

virtualenvwrapper make this easy. If you're using your OS's default Python, just use the following.

mkvirtualenv myenv

If you want to use a different version you've installed elsewhere, use the -p flag to specify it's path.

mkvirtualenv myenv -p /path/to/python

From now on, to activate this environment again just use: workon myenv.

Using virtualenvs

Since you're opting to use virtualenvs for managing other languages, I'm going to assume you're familiar with the day-to-day process of using them. If you're not familiar, now is a good time to check out the virtualenv and virtualenvwrapper docs and Google for more info.

NodeJs

Node is incredibly easy to setup in virtualenvs, thanks largely to the fact that there is a Python package to handle it for you.

pip install nodeenv

This package provides a tool that does everything for you. If you are using node in many projects, you might consider just installing nodeenv with the --user flag as discussed above.

Now install node using something like one of the following.

nodeenv -p  # install into active virtualenv
nodeenv .env/  # install into a virtualenv at a specific path
nodeenv -p -n 8.8.1  # install a specific version of node

That's it. It'll download and install the appropriate version, then update the activate script for your environment to provide all the environment variables Node needs to know it.

Keep in mind that npm has the concept of "global" and "local" modules, in this case "local" will continue to be the current working directory where npm is run, and "global" will be in the lib directory of the virtualenv you specified.

Use the node and npm commands for manage Node in this environment further.

Ruby

Much like Node, some kind soul has provided a Python package for managing Ruby  versions. This may also be a good candidate for installing with the --user flag if you're gonig to use it often.

pip install rubyenv

Now you simply install the Ruby version of choice.

rubyenv install 2.4.2

Since it's building from source you may need to install some system level packages for the build to succeed, but it should let you know.

Now use the ruby and gem commands to setup and run whatever you need.

Go (Golang)

Go is a little more work to set up, but not much. There's not a virtualenv aware tool, so you'll need to download the Go Tools binaries. Be sure to grab the appropriate tar.gz archive, not an installer, for your OS. Once you've got the archive, you just need to extract it into your virtualenv.

tar -C $VIRTUAL_ENV -xvf download/go1.9.2.linux-amd64.tar.gz --strip 1

The -C $VIRTUAL_ENV and --strip 1 are particularly important for the contents of the go directory inside the archive to be extracted into you virtualenv correctly.

Next, you need to tell Go where it's based. The best way to do this is have virtualenv set the GOROOT environment variable for you when you activate the virtualenv. If you only ever use Go inside a virtualenv, you can add the following to you user's ~/.virtualenv/postactivate, then you only need to do the extraction step above when you create a new virtualenv. If you only want to use virtualenv Go for this one virtualenv, add the following to ~/.virtualenv/<env name>/bin/postactivate.

GOROOT="$VIRTUAL_ENV"

Deactivate and reactivate your vritualenv and you're all set. Manage everything using the go command as usual.

Rust

Rust's primary install tool, rustup, has the ability to install into custom paths. You just need to add a couple environment variables to tell it how, and we'll let virtualenv activation handle that for us.

Like Go, if you only use Rust within virtualenvs, and want to save a step in the future, you can add the following to ~/.virtualenvs/postactivate. If you just want to set up this one virtualenv, add it to ~/.virtualenvs/<env name>/bin/postactivate.

CARGO_HOME=$VIRTUAL_ENV
RUSTUP_HOME=$VIRTUAL_ENV

Now's a good time to deactivate and reactivate your virtualenv to load those variables. And we're ready to run the rustup setup script.

curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path

Done. Now rustc, cargo and all the other Rust commands should be good to go.

Conclusion

That's it. Now you've got a pretty solid set of tools for developing different languages. They should all run side-by-side in any mixed and matched set you want to use and safely quarantined from the rest of your system.

I'm sure there are several other languages that can be managed this way, and if I run across more I'll try to update this post with details. Otherwise I hope this makes your development processes better.