Every so often I'll start a new position and upon arrival find that it is very difficult to get the entire system running locally on my machine. I am not talking insane enterprise systems even - the last two positions have been fairly similar in architecture, consisting of 2-3 web sites, 2-3 databases, a set of services, and a couple of queues. This isn't terribly large. There is usually out of date documentation on how to get the system running locally, but some times not even that. This does not bode well for the first day warm fuzzy, and generally is foreshadowing the state of their deployment and CI practices.
Flat out - I think it is an essential practice for developers to use their own sandbox while developing. This is nothing new, but seems to be overlooked continually. This practice has many benefits, including minimal time between code and test, isolated testing, and easy debugging if required, not too mention all the developers on the team generally understand more of the sytem when this is easy to do and practiced. When I see that this is easy to do, I also get the feeling that someone actually thought about the architecture in terms of how they were going to test it.
To run a local sandbox, this would mean coding and testing against all components locally - all sites, services, and databases. On my current team, everyone has done this at some point in time, but only about half do it consistently. The reason for this, and at most shops, is that it can be a daunting task to keep a local environment in check. To do this effectively, all code bases must be kept up to date, compiled and deployed locally at will, and all config settings must be set to run locally. All data repositories must be scripted and quick to deploy, and not necessarily required in my opinion, but nice to have, is a nice set of accurate seed data. (I'll post on that topic separately.)
This is how I choose to work, and is not how everyone needs to work, but I think if you try it you will not want to work any other way. In the spirit of eliminating waste and to aid in making this always possible, I create local build scripts that will allow instant get latest and compile on all local code bases, a method of runnning the sites and services instantly, and a solution for the nightmare of config management (specifically .NET app and web config files when dealing with many environments). The config management post will have to wait another couple of weeks, but here is the first entry in this series.
Local Build Scripts
I create a set of build scripts that anyone on the team can use - of course the dependency exists that all paths are local from a source root. Since I mostly work in .NET, mine are created using MSBuild and I provide a set of batch files as runners (you can use powershell if you prefer). You may kick them off from the command line using msbuild, with your favorite hotkey or command-line runner, or by clicking on the batch files.
I use SlickRun as my command line runner, so this is the method I choose, and around which I built what I currently consider my optimal solution (which of course changes twice a year or so). For example, if I want to get latest on the WPF client for our current product and build it, all I have to do is hit Windows Key + W, then type "build client" into the window, and voila, done. To start the client, all I have to do is hit Windows Key + W, and type "run client" into the window. Now I have a freshly compiled version of the latest source running on my desktop without ever opening Visual Studio. Then I just continue testing my service code change, or whatever it was I was doing.
About the build scripts
Each build script will get latest for its source, and its dependencies, i.e. Tools, Library, common, etc. where needed. The default build is incremental, and the source is not cleaned - output folders remain for each subsequent build. I have the option to 'rebuild,' which simply means nuke everything and get a completely fresh copy and build from scratch. Each build is captured in its own log file created in a sub folder ".\Logs" should any debugging or errors be encountered. The build window will also remain open waiting for the user to 'press any key' to exit, so no quick close with errors.
As for the scripts, I include one each for each solution which represents a major component (i.e. website, service, db, etc.), and one for each database deployment, and one full script, which will get latest on everything and build the entire system. I usually only run the latter as incremental, but can do a full rebuild - this is where you want to get a refill on the coffee. In the spirit of CI I tend to run the latter at least twice a day as well. Also, in the latter, I tend to not deploy dbs by default, just get latest and run an incremental build for expediency's sake.
Using the build runners
There are at least four ways to run the builds:
1. MSBuild from command-line
2. Batch file from command-line
3. Batch files from explorer
4. Command Utilities
MSBuild from command-line
You can always run MSBuild from the command-line on the project file, which is just annoying but useful, and you will have to add your own logger if desired. The default target and param set-up will work just fine by calling the project directly without any additional command line params.
Batch file from command-line
You may also just run any of the batch files from te command line. In addition to a batch file for each project, I created a master build batch file which will run any of builds by accepting the project name as a parameter. The master batch file is BuildLocal.bat. Call this file passing the name of the build file to run, excluding extensions. i.e.
">BuildLocal.bat client" will run client.build.proj with logging.
Batch files from explorer or hotkey app
Double click the individual runners for a build. You cannot use the master with this method, but must click on the build you want to run. i.e. Clicking on Runners\BuildClient.bat will get latest and build the client. If you are using a hotkey app or command-line runner app which does not support parameterization, you can assign these files as the target. i.e. My keyboard has 5 hotkeys, and I could assign the hotkey "1" to the BuildClient.bat, and all I would have to do is hit the "1" key. There is only a target which will be run when the key is executed.
Command Runner Utilities
Use a command-line runner which allows passing params - these are like the built in Windows runner (Windows Key + R), but with more power. I use SlickRun. SlickRun starts anytime I press "Windows Key + W." I use this to run just about everything on my pc - you can get more info at their site. SlickRun supports "magic words," which are simply defined shortcuts for executing something. I set up a magic word, "build" which when run calls the bat file "Runners\BuildLocal.bat" which also takes a single parameter, which is the name of the build file up to the ".build.proj" extension.
So, typing "build client" will run the "build" magic word using "client" as its param, which in turn just executes the BuildLocal.bat file passing the value "client" as described in option 2 above. This is just faster.
Caveats
I am not capturing the output from the tfs get operation to fail immediately if there are conflicts. I plan on adding a feature that will detect this issue, then attempt to auto-resolve the conflicts, and then fail if they cannot be auto-resolved. I generally only run into this with configs, which we'll deal with shortly, and when there is crazy refactoring involved - and then it is on check-in, so we are already in Visual Studio. I also have a script I use that will blow away my local copy of source (full source clean) and then gets a fresh copy from source. I will do this if I am getting conflicts on a project in which I have not worked for a while.
Next up -> Running the applications locally without Visual Studio.
Coming Soon -> Local config management (Actually, all config management)
Other reading:
- http://www.agiledata.org/essays/sandboxes.html
- http://codebetter.com/jeremymiller/2005/09/21/database-per-developer-and-environment/#
a3501546-117e-4678-ab05-02841455a42a|0|.0