I'm developing a feature for Zenoss that lives in its own Zope product. For the time being, it's fairly independent of Zenoss and can be demoed in a separate Zope instance. So I have a buildout for the Zope environment, also setting up a copy of the new product as a development egg (see this tutorial for an example of how to set that up). I have that development egg pulled into the Zope demo buildout via svn:externals.



At certain points in the development cycle, however, I want to send QA an RPM of the demo instance, with my package installed, so they can test it. This is kind of a hassle if I have to build the egg of the product and get it in the buildout directory on the build box in order to generate an RPM. Nor can I simply leave it as a development egg, because the package wouldn't be in the same place on the test box, so the link would fail.



So here's an easy way to turn a development egg into a production egg for the purposes of creating an RPM, using a separate buildout config file.



You'll want to read this tutorial on basic RPM creation using zc.buildout. In addition, the buildout.cfg will include a develop option in the buildout section:

[buildout]

...

develop = src/Products.MyCustomProduct



( src exists in my demo directory, and has the svn:externals definition pointing at the custom package.) The egg is also included in the instance section:

[instance]

...

eggs =

${buildout:eggs}

Products.MyCustomProduct



zcml = Products.MyCustomProduct



This gets me a development buildout, in which I can work on the package in src/Products.MyCustomProduct without building eggs or anything.



Now when it comes time to build the RPM, we need to modify the rpm config file from the tutorial to build Products.MyCustomProduct as an egg, then install it. We'll use the z3c.recipe.egg, which lets you run setup.py in a source package with arbitrary arguments. Here's the new rpm.cfg:

[buildout]

extends = buildout.cfg

develop =

parts =

MyCustomProduct-build

zope2

instance

zopepy

myzope



[MyCustomProduct-build]

recipe = z3c.recipe.egg:setup

setup = src/Products.MyCustomProduct

args = bdist_egg



[instance]

find-links =

${buildout:find-links}

${MyCustomProduct-build:setup}/dist



[myzope]

...



Explanation. So, the egg is already referenced in buildout.cfg as one of the instance eggs. All we do in this one is remove its declaration as a development egg (with develop= in the buildout section), add a part to create the egg artifact (MyCustomProduct-build), and tell the instance section where to find it (expanded find-links).



z3c.recipe.egg just needs the setup option defined, which points at a package directory containing setup.py. It'll install by default, whereas we just want it to build an egg, so we specify custom args with the args option.



Notice that we have to build the egg first. This is obvious upon reflection, but the first time I just added MyCustomProduct-build to the parts instead of making sure it was first, which meant that buildout couldn't find it in the instance step, because it didn't exist yet.



This will create a deployment instance just fine on its own, in the current directory. But in the RPM example, we build out somewhere else, so we'll have to update the buildout command in the spec file so that it can tell buildout where to find the source of the custom package. This is a modification to the %build script:

%build

...

bin/buildout -c rpm.cfg buildout:directory=%{installdir} MyCustomProduct-build:setup=%{sourcedir}/src/Products/ZenUI3





Then we build the RPM as in the tutorial. This time, it'll package up the egg in its current state and install that artifact into the Zope instance. This means that I can generate an RPM for QA on the fly with a single command, which is lots easier than assembling the parts manually.

