One of the things I espoused in my recent presentation on Brownfield
applications is automating your releases and your deployments for continuous
integration. And being a good-natured hillbilly who has been a little lax on
technical content of late, I will now turn your attention to that very
topic.

Note:
Even though I mention this in the context of a Brownfield application, you would
do the same thing to set it up with your Greenfield application as well.

First off, we should define a release and a deployment because they sound
pretty similar.

A release is a packaged version of the application. That is,
it is some artifact or group of artifacts that *could* be deployed if so
desired. A deployment is the actual act of deploying a
release.

A couple of notes on the definitions:

  • There doesn't need to be a one-to-one mapping 'twixt releases and
    deployments. You can create releases all the live long day and not deploy any of
    them.
  • The reverse isn't true. You can't create a deployment without a release.
    Because, there ain't nuthin' to deploy, Jed!

There are a few schools of thought on when to create releases and what to do
with them. Which is another way of saying don't take what I'm about to say as
gospel. This is how I've implemented it on my current project which has a
development team consisting of The Coding Hillbilly 'n his alter-egos, Code Coverage Nazi and Guerrilla Refactorer. It's not without its faults which I'll
talk about in the next post on deployments.

So with my requisite disclaimer out of the way, I create a release on every
build. I.E. Every time I check in code, a new release is built. Wackiness?
Maybe, but that's where the the Coding Hillbilly alias gives me a free pass. My
advice is so easy to dismiss as the ramblings of a hick that when I eventually
do say something useful, it's all the more meaningful.

Why create a release on every check-in? Easy, because I can. It's easy to
create them so why not do so every time you can. That way, if the client says,
"show me what you have" you can give him the absolute latest and, one might
assume, greatest. What's the downside to having a release on each check-in?
Wasted disk space? Give me half a day off and go buy yourself something nice with the savings. (Yes, I'm
ignoring a whole lot of internal politics. Such is the benefit of developing on
your own build environment.)

That's enough theory. Here's the NAnt task that creates my releases, with all
subtasks merged into one. A summary of properties used follows:

     <target name="release">
        <property name="current.release.dir" value="${releases.dir}${project.fullversion}" />
        <delete dir="${current.release.dir}" failonerror="false" />
        <mkdir dir="${current.release.dir}" />
        <copy todir="${current.release.dir}">
            <fileset basedir="${app.src.dir}Trilogy.Gunton.Web">
                <include name="Content*.*" />
                <include name="***.master" />
                <include name="bin*.*" />
                <include name="***.aspx" />
                <include name="***.asax" />
                <include name="***.config" />
            </fileset>
        </copy>
        <copy file="${config.dir}nhibernate-default.cfg.xml.template" tofile="${current.release.dir}nhibernate-default.cfg.xml">
            <filterchain>
                <replacetokens>
                    <token key="SERVER" value="${database.server}" />
                    <token key="DBNAME" value="${database.name.test}" />
                    <token key="SHOW_SQL" value="${debug.show_sql}" />
                    <token key="DATA_ACCESS_ASSEMBLY" value="${data.access.assembly}" />
                </replacetokens>
            </filterchain>
        </copy>
        <delete dir="${latest.release.dir}" failonerror="false" />
        <mkdir dir="${latest.release.dir}" />
        <copy todir="${latest.release.dir}">
            <fileset basedir="${current.release.dir}">
                <include name="***.*" />
            </fileset>
        </copy>
    </target>
releases.dir Path to the releases folder. e.g.
C:projectsLineageOrganizerreleases
project.fullversion Full version number of the project. This is generated partially
using the CCNetLabel
current.release.dir Combines releases.dir and project.fullversion. e.g.
C:projectsLineageOrganizerreleases1.0.31
app.src.dir Path to the source code for the application. e.g.
C:projectsLineageOrganizersrcapp
config.dir Path to a folder that contains templates that vary from
environment to environment. e.g. C:projectsLineageOrganizerconfig
database.server Name of the database server you want to use for this
environment
database.name.test Name of the test database you are using for this
environment.
data.access.assembly Name of the data.access.assembly to be used for
NHibernate
latest.release.dir Path to the latest release folder. e.g.
C:projectsLineageOrganizerreleasesLatest

Some these will become clear when I go through the build file. I won't add "I
hope" because I GUARANTEE lucidity*.

If you look closely at the tasks in this target, it does one of three
things:

  • Deletes folders
  • Makes folders
  • Copies files

That's all a release is in my case. Elsewhere in my build file, I'm already
building the application for testing purposes and as it so happens, I'm building
it in the same way I would release it. This is admittedly a little dicey here
because you may not want to do that. For example, you may want to compile with a
different configuration for your releases. But it's not a pain point for me yet
so the decision can be put off.

So, in order, here is what is happening (more explanation follows):

  1. Get the path to the new release we are building (based on the current
    version number). This uses the CCNetLabel property as described by Donald Belcham, whom I
    should have referenced long ago in this post because most of these ideas come
    from conversations with him.
  2. Delete the folder if it exists (it usually won't) and create/re-create it
  3. Copy the contents of the web application to the release folder
  4. Copy the NHibernate configuration template to the release folder and replace
    its tokens with values appropriate for the test environment.
  5. Delete the folder containing the latest release and re-create it. This is
    NOT the folder labeled with the previous release number. It is a subfolder in
    the releases folder that is physically named Latest. It will always exist
    already except for the first time you run this target (or if you physically
    delete it)
  6. Copy the contents of the current release folder to the latest release folder

Note on #3. We copy only the files we need to run the application. No code
files and no project files here, bub!

Note on #4. The only reason I am creating a separate NHibernate file (rather
than using the one already in the app folder) is because I want my releases to
go against a different database than they do in the normal, everyday testing of
the application. That database gets blown away and re-created every time I run
my integration tests and I don't want that affecting any testing the QA
department is doing on the latest deployment. So the database name is ${database.name.test} rather than ${database.name.dev}. Note that everything here is
running on the same server so I don't need to differentiate the server
names.

Note on #5. Why have a separate folder for the latest release that is simply
a copy of another folder? That's the subject of the next post where I go into
the deployment target.

Kyle the Anticipatory

* I remind you that there are no refunds