{
    "href": "/post/2006/04/11/automating-release-tasks/",
    "relId": "2006/04/11/automating-release-tasks",
    "title": "Automating Release Tasks",
    "author": "pmjones",
    "markup": "html",
    "tags": [
        {
            "href": "/tag/php/",
            "relId": "php",
            "title": "PHP",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        },
        {
            "href": "/tag/solar/",
            "relId": "solar",
            "title": "Solar",
            "author": null,
            "created": null,
            "updated": [],
            "markup": "markdown"
        }
    ],
    "created": "2006-04-11 17:57:02 UTC",
    "updated": [
        "2006-04-11 17:57:02 UTC"
    ],
    "html": "<p>I noted earlier today that I've made 5 releases of <a href=\"http://solarphp.com/\">Solar</a> in 7 days. (Clearly I'm a fan of \"release early, release often.\"  ;-)  Obviously, I have the Solar-talk subscribers to thank for pointing out bugs and and making functionality requests; these are what drive the need for a release ... thanks, guys. :-)</p>\n<p>But what I want to talk about in this entry is the release process itself.  With the help of Greg Beaver (indirectly) and Clay Loveless (directly), Solar now has <a href=\"http://solarphp.com/svn/release.php\">a moderate-length PHP script</a> that handles almost all aspects of the release process automatically.  Usage is at the command line; issue \"php release.php\" for a test run, or \"php release.php commit\" for a full release-and-commit cycle.</p>\n<p>With any luck, the lessons I've learned here will be of use to someone else; with more luck, perhaps someone else will see possible improvements and mention them here.  Read on for a narrative of how the script came to be.</p>\n<p><!--more--></p>\n<h3>First Iteration</h3>\n<p>The first person I have to thank here is <a href=\"http://greg.chiaraquartet.net/\">Greg Beaver</a> and his great work on the <a href=\"http://pear.php.net/\">PEAR</a> packaging and installation tools.  Without his stewardship and maintenenance of this wonderful toolset, the Solar release process would be much more difficult than it is.</p>\n<p>Even with the PEAR installer, though, building a package XML release file by hand is a terribly tedious exercise.  So the second person I have to thank is <a href=\"http://www.killersoft.com/randomstrings/\">Clay Loveless</a>:  some months ago, he wrote up the first iteration of the auto-packager for Solar using <a href=\"http://pear.php.net/package/pear_packagefilemanager/\">PEAR_PackageFileManager</a>.  I added on a bit to it at that time so that we could keep release information (authors, change notes, etc) in a separate location.</p>\n<p>The point of keeping the relase info in a separate location was so we could avoid having to modify the packaging script for every release.  The script looks for an \"info/\" directory; in that directory, there is a sub-directory for every release number, and contained therein are a number text and comma-separated values files.  The packaging script then uses those files to generate the \"header\" portions of the PEAR pacakge file.  You can see an example \"info/\" directory <a href=\"http://solarphp.com/svn/info/0.16.0/\">here</a>.</p>\n<h3>Second Iteration</h3>\n<p>The PEAR_PackageFileManager has a great auto-replacement utility built in.  It can look through the source code at <del>installation</del><ins>package</ins> time and replace certain keywords, such as \"@package_version@\", with the proper value for that particular installation.  <ins>(UPDATE: [19:33 CST] Greg Beaver pointed out that addGlobalReplacement() <strong>does</strong> replace at package-time.  Once again, Greg proves he has thought of all the right things.  Thanks man.)</ins></p>\n<p><del>But there's a small problem with that for non-PEAR users.  Because the replacements happen at installation-time, not at package-time, it means the pearball still has the keywords in place, not the actual values.  For methods like <a href=\"http://solarphp.com/index.php/docs/Solar/apiVersion()\">Solar::apiVersion()</a> which depend on the automated replacement of \"@pacakge_version@\" with the correct version number, this is a functionality break for them.</del></p>\n<p>It turns out there's an easy, if somewhat brute-force, solution:  copy the original source to a temporary location, replace keywords yourself, and then have PEAR package that new codebase with the keywords pre-replaced.  You can see this portion of the code in <a href=\"http://solarphp.com/svn/release.php\">release.php</a> at the comment noted \"prepare package directory\".</p>\n<p>We copy the original source (\"src/\") to a packaging directory (\"pkg/\") <del>and str_replace() keywords in  the new \"pkg/\" directory</del>.  When PEAR_PackageFileManager comes into play, it works on the \"pkg/\" directory instead of the original source.  Note also that the <del>pre-</del>replacements happen in documentation files as well (more on that in a bit).</p>\n<p>The end result of this is a pearball/tarball that is equally useful to PEAR and non-PEAR installation processes.</p>\n<h3>Third (and Current) Iteration</h3>\n<p>So that takes care of the packaging itself.  The last issues to handle are mostly administrative.  On a full commit cycle, we need to check a few things before rolling the release:</p>\n<ul>\n<li>Set Subversion keywords on all files</li>\n<li>Update the wiki files in \"docs/apiref/\" (these are built with a custom class that applies the PHP5 Reflection API to each class in Solar; more on that in another post)\n</li>\n<li>Make sure all pending changes have been committed to Subversion</li>\n</ul>\n<p>If those pre-flight checks pass, we then need to actually build the package (see iterations 1 and 2 above).  After that:</p>\n<ul>\n<li>Tag the release using \"svn copy\"</li>\n<li>Commit the tagged release to Subversion</li>\n<li>Extract the documentation files for updating the Solar website</li>\n</ul>\n<p>The <a href=\"http://solarphp.com/svn/release.php\">release.php</a> script splits this into three task sets.</p>\n<p>First, the administrative tasks are handled at the top of the script under \"status check\".  This sets keywords, builds docs, etc., and then makes sure that there weren't any changes as a result.  If these automated tasks generated changes (as noted by \"svn status\" returning any ouput at all), the script terminates with a warning to handle outstanding issues.</p>\n<p>Second, after the package is built, near the end of the script, we issue a series of \"svn\" commands to tag the release and commit to the repository.</p>\n<p>Finally, we unzip a copy of the pearball and pull out a copy of the docs; these are re-zipped in a separate tarball for upload to the Solar site.  We use the packaged docs instead of the source docs so we don't get all the .svn files mixed in with them.</p>\n<h3>Manual Tasks</h3>\n<p>There are still some manual tasks to accomplish in a release, but they're easy:  upload the pearball to the channel server, upload the docs to the website ... oh, and write an entry about the release on my blog.  ;-)</p>\n"
}
