. .


Automated Bundle Builds

March 29, 2011 15:00:00.000

I've been working on build automation tools for a bit, and this week I started taking a look at one of the more manually intensive parts of the process: putting together the master bundle that will be used for the build itself.

The way things work here, individual developers don't publish bundles - they push individual packages based on the work that needs to get done. We have a person who creates builds, and the first task he has is to create the latest master bundle (for a given branch of development) from the packages that have been published. That's a fair amount of manual labor at present, so I took a look at it.

The biggest hurdle (at least in VW 7.6 - maybe later revs are better, I never delved into Store that deeply even when I was at Cincom) is the actual publishing. Normally, publishing a package or bundle requires a dialog (either PublishPackageDialog or PublishPundleDialog). You fill that in (or let it default) and off it goes. Once I gathered the things that needed publishing, I wanted to have that kick off without the UI. Let's back up a bit and take a look at the steps: first, finding newer versions of published packages within a bundle. To get there, I start with something like this:


bundleDef := BundleDef 
	bundleNamed: 'TestBundle'
	version: '1.2'
	versionMatchString: '1.*'
	baseComment: 'Testing build automation'

"get all the contained packages, all the way down"
bundleDef allContainedPackagesAndBundles.


What that does is find all the packages that make up a bundle (including sub-bundles) by recursively asking Store for that data -like so:


allContainedPackagesAndBundlesFor: aBundle
	"answer a collection of all the packages I have, regardless of bundles in the middle"

	| items  |
	items := aBundle containedItems.
	items do: [:each |
		each isBundle
			ifTrue: [self allContainedPackagesAndBundlesFor: each.
					containedBundles add: each]
			ifFalse: [containedPackages add: each]].


That gets kicked off by this API point in my code:


bundleDef allContainedPackagesAndBundles.


Then I update all the contained packages:


bundleDef updateAllPackages.


That requires some store internals work:


	"iterate over the packages and update them"

	containedPackages do: [:each |
		| all newerMatch |
		all := Store.Package allVersionsWithName: each name newerThan: each.
		newerMatch := all 
                   detect: [:each1 | versionFragment match: each1 version] 
                   ifNone: [nil].
			ifNotNil: [Transcript show: 'Updating: ', each name; cr.
						newerMatch loadSrc]].


That's the easy part. The harder part is publishing the dirty bundles, from bottom to top - you need to create an instance of PublishPundleDialog, stuff it with the right data, initialize aspects of it, and then tell it to publish. The outer method for that looks like this:


publish: aBundle
	"publish the bundle"
	| dlg |
	dlg := self createPublishDialogDataFor: aBundle.
	self setBundleCommentsFrom: dlg for: aBundle.
	dlg newGlobalState.
	 [ dlg publishFromUserData ] 
		on: Store.DbRegistry errorSignals 
		do: [ :exp | Transcript show: 'Publish of: ', aBundle name, ' FAILED'; cr.
					exp return ].


The excitement is in #createPublishDialogDataFor: - which involves all the initialization of the dialog without displaying it:


createPublishDialogDataFor: imageBundle
	"store expects a publish dialog; create one, stuff data in, return it"
	| comment dlg userData fileData storeBundle |
	dlg := Store.PublishPundleDialog new.
	dlg items.
	dlg files.
	dlg blessingLevel value: 20.
	storeBundle := containedBundles detect: [:each | each name = imageBundle name].
	comment := self getVersionStringForBundle: storeBundle.
	dlg blessingComment value: comment.
	userData := Store.PublishPundleDialog publishSpecsFrom: imageBundle.
	fileData := Store.PublishPundleDialog publishFileSpecsFrom: imageBundle.
	dlg items list: userData.
	dlg files list: fileData.


The interesting part here is the dichotomy between the store bundle and the image (unpublished) one. You need to line those up, and use both as appropriate. Once you fill all the data in though, it'spretty easy. I customized the outer level API for my object so as to allow for a base comment in addition to the automated one I toss in. That'sit though - a whole lot simpler than I feared it would be. I'll probably extract this code and publish it to the public store - I think it might be generally useful. What would be even more useful would be Cincom engineering creating some actual domain objects so as to make it easier to do this without screwing around with undisplayed UI classes.

posted by James Robertson

 Share Tweet This