As a developer with several ongoing projects, I frequently find myself switching contexts as one project becomes blocked (read: I lose interest) and I want to move on another. Typically that means unloading a bunch of files from my editor and loading others. Some IDEs, and even editors such as TextMate, use a "project" file to manage the files that belong to a project. Some go so far as to use the project file as a hint for how to build the library or application.
Using a project file typically involves pre-defining the members of a project, and then managing the project file as another artifact of the project itself. To switch contexts, you simply close one project file and all of its related code files, then open another. A good editor will even remember which files were open when the project was last in use, and restore your workspace to the same state. A great editor will let you automate the steps needed to switch contexts by loading different project files.
emacs desktop-mode
I've been an emacs user for something like 10 years now. I use vi frequently, and TextMate regularly as well, but most of the time I live in emacs for text editing and development tasks. The implementation of project files I use with emacs is desktop-mode. I like desktop-mode because instead of forcing me to pre-declare all of the files in my project, it assumes that all open buffers should be remembered. There are alternatives (there are always alternatives in emacs, right?).
Setup of desktop-mode is straightforward:
1. Run
customize-group on the "desktop" group. 2. Turn
desktop-save-mode on to enable the minor mode. 3. Optionally change the base name for desktop files in
desktop-base-file-name. I like using "emacs.desktop" so the file is not hidden and I know exactly what it contains.4. Set a default search path for the desktop file in
desktop-path. I set my path to include ~/emacs, but this is only the default. We will be saving a separate desktop in each virtualenv.5. Set
desktop-save to "Always save". There are other values that work, but some require interaction with the editor during the context move to confirm file saves, and I want to avoid that.There are a few other options that may be useful to tweak, depending on the other features of emacs you use. For example, I set desktop-clear-preserve-buffers so clearing the desktop does not delete the
*Messages*, *Org Agenda*, or *scratch* buffers since those are related to emacs operation and not limited to any one project.virtualenvwrapper hooks
The next step is to set up the tool chain so emacs loads a new desktop when you change virtualenvs from the command line.
In addition to making it simpler to manage multiple virtual environments, virtualenvwrapper adds several hook points to trigger changes outside of the virtual environment when various events occur. For example, each time you run
workon to change environments, the predeactivate and postactivate hooks are run. The most interesting of these for our purposes is the postactivate hook, called right after the new environment is activated.I usually add a
cd command to the env-specific postactivate script (in $VIRTUAL_ENV/bin/postactivate) to move my shell session to the working directory for that project and change the $PATH, making it easier to run the tests for my project from the shell. For example:pymotw_root=/Users/dhellmann/Documents/PyMOTW
cd $pymotw_root/src
PATH=$pymotw_root/src/bin:$VIRTUAL_ENV/gettext/bin:$PATH
Changing Desktops
In addition to the virtualenv-specific hook, there is also a global
postactivate script, and that's the one I use to switch contexts in emacs. Edit $WORKON_HOME/postactivate to add these lines:# Change emacs' desktop directory
emacsclient -e "(desktop-change-dir \"$VIRTUAL_ENV\")" >/dev/null
The function
desktop-change-dir tells emacs where to save the desktop session file. By changing the directory each time we change environments, we can have a separate desktop file in each environment. Changing the directory automatically saves the old desktop first, of course, so the old project status is preserved until you come back to it.Now that the hook is configured, the next step is to initialize the desktop for your projects. For each project, run
workon to activate it, then open several files from that project. As you workon the next project, the session will be saved in $VIRTUAL_ENV/emacs.desktop (assuming you set your desktop-base-file-name to emacs.desktop). Once you have initialized a few virtualenvs, switch back to the first. Emacs should load the desktop file for that environment and restore the buffers you were editing.Other Editors
The example above works for emacs, but other editors may need to use the
predeactivate hook to save a project file before loading the new one from postactivate. There are global and local versions of both hooks; refer to the virtualenvwrapper documentation for more details.
14 comments:
One handy hook I use is to overwrite some shell buildins. For example I have a custom cd, you can do so much with totally localized environments.
#this function will overwrite the default behavior of cd to go to the root of the venv, to go to HOME use cd ~
cd () {
if (( $# == 0 ))
then
builtin cd $VIRTUAL_ENV
else
builtin cd "$@"
fi
}
#move into the venv after activated
cd
Doug, have you ever thought of untyin the python/virtualenv side of things?
A friend of mine and I have been working on taking virtualenvwrapper and turning it into a more general productivity solution:
http://github.com/mitechie/workit
Since only a small portion of my projects are python. I'm using it to setup shell vars/paths to use with vim and such. Start services like pgsql/mysql on entering a project, etc.
@mae - That's slick. I'm not sure how far I'd want to take it (overriding builtins could be dangerous), but I like the idea of defining new custom functions in the hook.
@Deuce868 - Nice adaptation! I particularly like the bit where you create the hook scripts automatically. I'm going to steal that back. ;-)
Are you planning to add some of the PATH munging that virtualenv does, too?
Not sure what path munging you mean.
I just make sure workit creates a $PROJ_PATH environment variable once you activate a project. Then all your path related items can be based off that in your postactive/etc.
i.e. in a python project I 'activate' it with
Here's one example for a large php project I work on. It sets some paths for some js/css minifying paver commands in the environment:
=========================================
#! /bin/zsh
# variables needed for the jsbuild.sh script
export JS_PATH="$PROJ_PATH/proj";
export JS_BUILD_PATH="/proj/html/javascript/ula.build.min.js"
export CSS_PATH="$PROJ_PATH/proj";
export CSS_BUILD_PATH="/proj/html/css/ula.build.min.css"
# create quick alias to launch JS unit tests
qunit() { chrome "http://proj/javascript/tests/runtests.html" }
# run ctags when we enter the project so vim is up to date for completion purposes
ctags -f $PROJ_PATH/tags -R $PROJ_PATH/externals/framework/Ula $PROJ_PATH/tengine_admin 2> /dev/null &!
zpgsql_start
============================================
#!/usr/bin/zsh
# a python project example, turbogears web app
#activate the virtualenv
source $PROJ_PATH/tesupport/bin/activate
cd 'tesupport/tesupport'
#update ctags
ctags -f $PROJ_PATH/tesupport/tesupport/tags -R $PROJ_PATH/tesupport/tesupport 2> /dev/null &!
zpgsql_start
alias dev="paster serve --reload development.ini"
alias shell="ipython shell development.ini"
~
While a virtualenv is active, $PATH includes $VIRTUAL_ENV/bin. Deactivating the environment resets the PATH to the previous value. You could do something similar with workit.
Ah, ic. No, we haven't done anything like that so far. It's not come up as a need for our projects yet.
I can see where that would be handy though, especially when testing workit itself, without blowing up the copy currently running on the system.
Yep. FWIW, I've found shunit2 a good tool for writing tests in bash/ksh.
Yep, saw your use of shunit in virtualenvwrapper. I have to confess that my shell-fu isn't all that powerful and I've not gotten into trying to implement/test it. One of those "wonder if I can make virtualenvwrapper work without the virtualenv part" hacks that's gotten farther than I thought it would
Hey, I like it. I'm trying to think of how I might be able to use it in a project where I can't use virtualenv.
Well I guess we'll have to put some work into it now. My friend and I are both zsh users so we skipped the complicated looking bash completion functions and such.
Deuce868, very interesting project. I'm currently doing exclusively python but I'll take a look if I need to get out of this sweet spot.
@Doug, yea I know my cd will probably never make it into the core but I have been thinking of stuff like that for some time. I had a cdsitepackages equivalent in there. Before that made it into the core. it will be really interesting if we could setup something for people to share their crazy hooks :)
@mae - I like the idea of sharing hooks. What do you think about adding a "contrib" directory to the distro?
Post a Comment