Complex dependencies - Is this scenario even possible?

It's complicated. Here's the picture:

I have a build numbering scheme thus: Major#.Minor#.Build#.Revision#. The current, official build number is stored in a text file in source control's mainline. The file can be modified manually and/or checked back in to manipulate the build number, or a build script can manipulate it and/or check it back in. Checking the file back in obviously makes the version change (increment) permanent, but a developer can manually tweak the numbers on their own machines for their own purposes if they wish, but to the outside world the SCC-controlled number is official. All of our components in the build procedure use the version file (directly or indirectly) to drive their build process in some way (could be generating .rc or .inc files, could be stamping a file version on a utility, or a document etc).

Major# and Minor# are manually maintained -- they are bumped by a human according to our product release branching strategy (details not important).
Build# needs to be bumped whenever any of a certain subset of files in our primary component changes, but we want to coalesce such "breaking" changes to these files over time into one bump. In other words, it's only important to bump the Build# number for important, "internal/external release" builds; builds for which we actually go to the trouble of indexing and storing symbols and building and publishing an installer for. We don't want to bump the number for every change to these files; for instance if many small changes to these files come over the course of a few days at the beginning of an iteration we don't want one increment per change, we want one increment covering all the changes.
Revision# needs to get bumped every time we do a build, and reset to 0 whenever the Build# increments.

Major# and Minor# are easy, and TeamCity doesn't need to be involved. A dev will manually change and check in the version file and the next build will see it.
Build# increments we can automate by creating a configuration (say "VersionBreaker") tied to an SCC root that covers just the relevant files (say "BreakingRoot"). This config can be triggered manually when we see there are pending changes to these files and we want to "lock in" a Build# increment. The config simply runs a script that bumps the Build# in the file and checks in the change.
Revision# is easy, too -- our primary component's config (say "Primary") runs a script that bumps the Revision# before starting the compile.

Components that depend on the "Primary" (snapshot dependents, say "Secondary"), get their build number from "Primary" (%dep.bt<NN>.build.number%). Any incremental builds triggered on "Secondary" configs simply reuse the current "Primary" build number (I know you're not supposed to do this but it's not important to us for every build number to be unique for these components -- it IS important that they have the same build number as the "Primary" build they were compiled against).

So far everything works just fine. Any changes to components trigger a build of the appropriate components (and or dependencies), bumping only the Revision# on "Primary" if necessary, and changes to "BreakingRoot" files in "Primary" are "accumulated" in "VersionBreaker" until such time we want to manually bump the Build#.

Now, say we have a configuration that builds an installer for our product (say "Installer"). This config has snapshot dependencies on "Primary" and "Secondary", and when we (manually) run this configuration it correctly builds all the dependent configs if necessary, and then builds the installer. This too currently works fine, and I don't feel like I'm fighting the system's intended design.

And now, here's the problem. Even though we can manually build an installer at any point with a minimum of fuss, if we didn't remember to run "VersionBreaker" first, we'll potentially ship two installers with the same Build# but with "BreakingRoot" changes, because "VersionBreaker" it's not a dependency of "Installer". Even if we make it a dependency of "Installer", it runs LAST, AFTER all the other dependencies of "Installer", because none of those other dependencies themselves depend on "VersionBreaker". If we make "Primary" dependent on "VersionBreaker", it will build every time there are pending change to the "BreakingRoot", which is not what we wanted.

In summary:

VersionBreaker (no dependencies)
Primary (no dependencies)
Secondary (dep. Primary)
Installer (dep Primary, Secondary)

When I run Installer here, I want VersionBreaker to build if there are pending changes, then Primary, then Secondary, then Installer. If I do this:

VersionBreaker (no dependencies)
Primary (no dependencies)
Secondary (dep. Primary)
Installer (dep. VersionBreaker, Primary, Secondary)

Now when I run Installer, the build goes in the order: Primary, Secondary, VersionBreaker, Installer. Not what I want. If I do this:

VersionBreaker (no dependencies)
Primary (dep. VersionBreaker)
Secondary (dep. Primary)
Installer (dep. VersionBreaker, Primary, Secondary)

VersionBreaker runs when any "breaking" change to Primary happens. Not what I want.

Make any sense? Is there a way out?

9 comments
Comment actions Permalink

As I understand with this setup:
VersionBreaker (no dependencies)
Primary (dep. VersionBreaker)
Secondary (dep. Primary)
Installer (dep. VersionBreaker, Primary, Secondary)

there is only one problem VersionBreaker is triggered too often. Do the VersionBreaker and Primary use the same VCS root, i.e. do they see the same pending changes?

0
Comment actions Permalink

Not exactly the same root: VersionBreaker is a subset of Primary (a set of specific file types from Primary).

0
Comment actions Permalink

How is this subset configured?

0
Comment actions Permalink

Primary is a Perforce VCS root with a specified client mapping:

//depot/dev/Primary/... //team-city-agent/...

VersionBreaker is a Perforce VCS root with a specified client mapping:

//depot/dev/Primary/.../*.ext1 //team-city-agent/.../*.ext1
//depot/dev/Primary/.../*.ext2 //team-city-agent/.../*.ext2

0
Comment actions Permalink

Then I probably do not understand why TeamCity triggers VersionBreaker too often. If there are no changes affecting both Primary & VersionBreaker then VersionBreaker should be reused (i.e. should not start if similar build exist in the history). Do you have option "Do not run new build if there is a suitable one" turned on for the dependency Primary -> VersionBreaker?

0
Comment actions Permalink

Sure enough, if a file under Primary changes that is NOT a *.ext1 or *.ext2 file, ONLY Primary is triggered (correctly). But if a *.ext1 or *.ext2 file is changed, I want to build Primary, but I DO NOT want to automatically build VersionBreaker, since that is the script that permanently bumps the build number. With the dependency, Primary is triggered, and since VersionBreaker is a dependency and has pending changes, it builds too. I ONLY want VersionBreaker to be built when Installer builds, but I can't guarantee that VersionBreaker will build before Primary without a dependency from Primary -> VersionBreaker.

0
Comment actions Permalink

To sum up you need to run VersionBreaker only if it is triggered by Installer or if there are pending changes in VersionBreaker itself and VersionBreaker should be started before Primary. Right?

0
Comment actions Permalink

Run VersionBreaker only if it is triggered by Installer AND it has pending changes, and it must run before Primary.

So if I have this:

VersionBreaker     (1 pending change = xyz.ext1)  [dep. none]
Primary               (1 pending change = xyz.ext1) [dep. none]
Installer               (no pending changes)     [dep. Version Breaker, Primary]

And I run Installer, the build order must be guaranteed to be VersionBreaker, Primary, Installer
What I get is Primary, VersionBreaker, Installer

If I have this:

VersionBreaker     (1 pending change = xyz.ext1)  [dep. none]
Primary               (1 pending change = xyz.ext1) [dep. none]
Installer               (no pending changes)     [dep. Version Breaker, Primary]

And I run Primary, I must end up with this:

VersionBreaker     (1 pending change = xyz.ext1)  [dep. none]
Primary               (no pending changes) [dep. none]
Installer               (no pending changes)     [dep. Version Breaker, Primary]

0
Comment actions Permalink

Such configuration can't be implemented in TeamCity straightly, but probably you can make VersionBreaker a part of Primary? Build script could analyze who triggered it and if it was triggered by Installer it may increase version. To determine who triggered build you can use this plugin: http://www.jetbrains.net/confluence/display/TW/Groovy+plug

Then you will need to check whether there were changes in the files which should cause version increase. TeamCity creates special file on the agent with information about changed files in this build, you can obtain path to this file from the teamcity.build.changedFiles.file property (file format described here: http://www.jetbrains.net/confluence/display/TCD4/Risk+Tests+Reordering+in+Custom+Test+Runner)

Hope this helps.

0

Please sign in to leave a comment.