Archive for the ‘configuration’ tag
Configurable Builds: One configuration file per machine
I've covered some of the ways that I've seen for making builds configurable in previous posts:
One which I haven't covered which my colleagues Gil Peeters and Jim Barritt have pointed out is having a build with one configuration file for each machine.
Again the setup is fairly similar to one configuration per user or environment. Using Nant we would have the following near the top of the build file:
<property name="machine.name" value="${environment::get-machine-name()}" />
<include buildfile="${trunk.dir}\config\${machine.name}.properties.xml" />We could then have one configuration for each developer machine:
machine1.properties.xml
<?xml version="1.0" ?> <properties> <property name="property1" value="onevalue" /> </properties>
machine2.properties.xml
<?xml version="1.0" ?> <properties> <property name="property1" value="anothervalue" /> </properties>
The build file can be run using the following command:
nant -buildfile:build-file.build target-name
The benefit of this approach can be seen (as Gil points out) in pair programming where the settings on any one machine will always be the same regardless of who is logged in. We also still get the advantage of being able to use remote resources on developer machines.
Having machine specific configuration also allows more flexibility for configurations on continuous integration for example. To quote Gil:
Each CI build (multiple builds per build server) get's it's own [configuration] based on the build host and build name.
The disadvantage again is we have to add a new configuration file every time we want to run the build on a different machine.
Configurable Builds: Overriding properties
Sometimes when configuring our build for flexibility we don't need to spend the time required to create one build configuration per user or one build configuration per environment.
In these cases we can just override properties when we call Nant from the command line.
One recent example where I made use of this was where we had one configuration file with properties in but wanted to override a couple of them when we ran the continuous integration build.
Since build properties are immutable (i.e. once they are set they can't be changed) if we set them from the command line the build script makes use of these values.
For example we might have the following in our build file:
<property name="repository.url" value="http://localhost:3000" />
But when we're running it on cruise control we have the repository on a different machine. We can override it like so:
nant -buildfile:build-file.build target-name -D:repository.url=http://some-remote-url
If we have more than one property we want to override it might be a bit annoying to have to pass them all via the command line. We can define them in a file to overcome this problem:
nant -buildfile:build-file.build target-name @ci.properties.xml
where ci.properties.xml contains the following:
-D:repository.url=http://remote-url:3000 -D:some.other.property=newvalue
If you start seeing a lot of properties in this file then it is probably an indicator that you need to have a more robust solution but this works for providing simple flexibility.
Configurable Builds: One configuration file per user
Following on from my first post about making builds configurable, the second way of doing this that I have seen is to have one configuration build file per user.
This approach is more useful where there are different configurations needed on each developer machine. For example, if the databases being used for development are on a remote server then each developer machine would be assigned a database with a different name.
The setup is fairly similar to configuring by environment – the main difference is that we don't have to pass the user in as a parameter. The following would go near the top of the build file:
<property name="user" value="${environment::get-user-name()}" />
<include buildfile="${trunk.dir}\config\${user}.properties.xml" />We can then have different configurations for two developer machines like so:
developer1.properties.xml
<?xml version="1.0" ?> <properties> <property name="property1" value="onevalue" /> </properties>
developer2.properties.xml
<?xml version="1.0" ?> <properties> <property name="property1" value="anothervalue" /> </properties>
We can then run the build file like this:
nant -buildfile:build-file.build target-name
The disadvantage of this approach is that every time a new developer joins the team they need to create a new configuration file with their settings in. We also need to ensure that the continuous integration build is running using an independent user account. It provides more flexibility and is easier to setup on the plus side.
My colleague Jim Barritt points out a similar technique his team is using here.
Configurable Builds: One configuration file per environment
One of the most important things when coding build files is to try and make them as configurable as possible.
At the very least on an agile project there will be a need for two different configurations – one for developer machines and one for continuous integration.
On my last two .NET projects we have setup our Nant build to take in a parameter which indicates which build configuration should be used. We then have a configuration file by that name which contains the environment specific data.
The build file would contain the following code before anything else:
<fail unless="${property::exists('environment')}" message="You must provide the environment property to the build script using -D:environment=[dev|ci]" /> <include buildfile="${trunk.dir}\config\${environment}.properties.xml" />
dev.properties.xml would look like this:
<?xml version="1.0" ?> <properties> <property name="property1" value="value1" /> <property name="property2" value="value2" /> </properties>
We would call the build file for the dev environment like so:
nant -buildfile:build-file.build target-name -D:environment=dev
Configuring the build this way assumes that the dev builds all have the same properties. On the projects where I used this approach this was the case.
The disadvantage of it is that you need to remember to pass in the environment variable each time you call the build. This can be countered by wrapping the full nant call in a batch script if it becomes too much of a hassle.