[Treepkg-commits] r2 - in trunk: . bin enterprise test treepkg
scm-commit@wald.intevation.org
scm-commit at wald.intevation.org
Thu May 3 20:31:32 CEST 2007
Author: bh
Date: 2007-05-03 20:31:32 +0200 (Thu, 03 May 2007)
New Revision: 2
Added:
trunk/COPYING
trunk/README
trunk/bin/
trunk/bin/createstaticweb.py
trunk/bin/initpbuilder.py
trunk/bin/publishstaticweb.py
trunk/bin/reportstatus.py
trunk/bin/runtreepkg.py
trunk/bin/starttreepkgweb.py
trunk/bin/treepkgcmd.py
trunk/cherrypy.cfg
trunk/demo.cfg
trunk/demostaticweb.cfg
trunk/enterprise/
trunk/enterprise/__init__.py
trunk/enterprise/kdei18n.py
trunk/enterprise/kdepim.py
trunk/test/
trunk/test/runtests.py
trunk/test/test_cmdexpand.py
trunk/test/test_status.py
trunk/treepkg/
trunk/treepkg/__init__.py
trunk/treepkg/cmdexpand.py
trunk/treepkg/options.py
trunk/treepkg/packager.py
trunk/treepkg/readconfig.py
trunk/treepkg/report.py
trunk/treepkg/run.py
trunk/treepkg/status.py
trunk/treepkg/subversion.py
trunk/treepkg/util.py
trunk/treepkg/web-status.html
trunk/treepkg/web.py
Log:
initial import
Added: trunk/COPYING
===================================================================
--- trunk/COPYING 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/COPYING 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Property changes on: trunk/COPYING
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/README
===================================================================
--- trunk/README 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/README 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,166 @@
+README for TreePackager
+=======================
+
+
+TreePackager is a tool to automatically build debian packages from SVN.
+
+
+Prerequisites
+-------------
+
+You need the following software to run TreePackager. In the list below,
+parentheses contain the name of the corresponding package in Debian Etch
+if its not the same as the software. The version required is usually
+the one from debian etch.
+
+ Python 2.4 (python2.4-minimal)
+ Debian devscripts (devscripts)
+ subversion
+ pbuilder
+ sudo
+ bzip2
+
+For the web front-end you also need the following software:
+
+ Genshi (python-genshi)
+ CherryPy (python-cherrypy)
+
+Some of the packagers require additional software. The KDEPIM
+enterprise branch packagers require the following additional software:
+
+ autoconf2.13
+ automake1.9
+
+
+Installation
+------------
+
+You can run the tree packager itself directly from the source tree.
+However, you need to configure it first and setup pbuilder.
+
+
+Configuration
+-------------
+
+To understand the configuration, first a few notes about the
+architecture of TreePackager. The TreePackager consist of one program
+that periodically updates svn working directories and if something has
+changed, builds a new debian package from the working directory. The
+program should run as a normal user. The sample configuration assumes
+that it's the user "builder" with a home directory "/home/builder". The
+default configuration manages a directory tree under
+"/home/builder/enterprise".
+
+The binary packages are built with pbuilder. Because pbuilder uses a
+chroot environment to build the packages, it must be run as root. The
+tree packager therefore needs a way to run pbuilder as root even though
+itself runs as a non-root user. By default the tree packager uses sudo,
+so you have to setup sudo to allow the tree packager user to invoke
+pbuilder without a password. This can be accomplished with the
+following line in /etc/sudoers (using the default user name):
+
+ builder ALL = NOPASSWD: /usr/sbin/pbuilder
+
+
+Configure TreePackager
+~~~~~~~~~~~~~~~~~~~~~~
+
+The file demo.cfg contains example configuration that contains most of
+what is needed to package KDEPIM and kde-i18n from the KDEPIM enterprise
+branch. Copy this file to treepkg.cfg and adapt it to your needs. The
+comments in the file should provide most of the hints to get you
+started. Some more information is in the "Configuring a packager"
+section below.
+
+
+Configuring a packager
+~~~~~~~~~~~~~~~~~~~~~~
+
+The configuration file contains one section for each packager. The
+section name starts with a "pkg_" prefix. The possible options are
+described in demo.cfg. However there are some things that need to be
+set up outside of the config file.
+
+Each packager has a base directory (the base_dir option in the
+corresponding pkg_-section). One thing needed by a packager is the
+contents of the debian sub-directory of the debian source package. When
+creating the source package, the packager simply copies the
+sub-directory "debian" of the base_dir into the directory making up the
+source tree. How the debian directory is created and maintained is up
+to you. Usually it's a good idea to start with the debian sub-directory
+an existing debian package for the software.
+
+
+Configure pbuilder
+~~~~~~~~~~~~~~~~~~
+
+It's best to give the tree packager its own pbuilder configuration and
+directories. The default configuration uses a "pbuilder" sub-directory
+in /home/builder/enterprise. If you have created the treepkg.cfg file
+with at least one packager and the correct root_cmd and pbuilderrc
+options (the defaults for both should be OK if you use sudo as described
+above), you can create the directories, the pbuilder configuration and
+the chroot environment with the script initpbuilder.py like this:
+
+ bin/initpbuilder.py --mirrorsite=<URL of preferred debian mirror>
+
+You can specify some more mirrors with the --othermirror option. For
+more information run "bin/initpbuilder.py --help" and consult the pbuilder
+documentation.
+
+
+Configure the web front-end
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The web front-end consists of a single HTML-page with an overview of the
+packager status and links to build logs when available. There are two
+ways to publish this front-end: as little web-server with a dynamic
+web-page or as a directory with a bunch of files making up a static
+web-site.
+
+Web-server:
+
+The default configuration should be OK in most cases. If you want you
+can customize it in cherrypy.cfg. Start the web front-end with
+
+ bin/starttreepkgweb.py
+
+starttreepkgweb has some options to specify which configuration files to use.
+
+
+Static pages:
+
+The static pages are published using two programs, createstaticweb.py
+and publishstaticweb.py. createstaticweb.py is run on the system where
+the tree packager runs. publishstaticweb.py is usually run on another
+system and connects via ssh and rsync to the tree packager host, creates
+the files with createstaticweb.py and copies the files from the tree
+packager host to a third host.
+
+The configuration file for publishstaticweb.py is demostaticweb.cfg.
+Copy this file to staticweb.cfg and adapt it to your system. The
+comments in the file describe the options. Afterwards, run the script
+with
+
+ bin/publishstaticweb.py
+
+
+Running the Tree Packager
+-------------------------
+
+After configuration, run the tree packager with
+
+ bin/runtreepkg.py [options] [packager...]
+
+For each packager listed on the command line -- or all configured
+packagers if none are given -- the tree packager checks out or updates
+the sources and builds binary packages if the new revision hasn't been
+packaged yet.
+
+If the option --once has been given, the tree packager exits after it
+has checked each packager once. Without it, the check is done
+repeatedly. The interval between two checks can be set in the configuration
+file.
+
+Call runtreepkg.py with the --help option to see a list of the available
+options.
Property changes on: trunk/README
___________________________________________________________________
Name: svn:eol-style
+ native
Added: trunk/bin/createstaticweb.py
===================================================================
--- trunk/bin/createstaticweb.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/createstaticweb.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,29 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Creates a static web-site with a status report"""
+
+import sys
+import os
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.web import Status
+
+def parse_commandline():
+ return create_parser().parse_args()
+
+def create_static_site(treepkg_config, destdir):
+ status = Status(treepkg_config=treepkg_config)
+ status.create_static_site(destdir)
+
+def main():
+ options, args = parse_commandline()
+ create_static_site(options.config_file, args[0])
+
+main()
Property changes on: trunk/bin/createstaticweb.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/initpbuilder.py
===================================================================
--- trunk/bin/initpbuilder.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/initpbuilder.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,125 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Script to initialize the pbuilder environment for the tree packager
+
+The script assumes that the config file for the tree packager already
+contains the pbuilder settings. Also, this script assumes that there is
+only one pbuilder setting for all packagers.
+"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.packager import create_package_track, PackagerGroup
+from treepkg.readconfig import read_config
+from treepkg.util import ensure_directory, writefile
+from treepkg.run import call
+
+
+pbuilderrc_template = '''\
+# This file was automatically generated by initpbuilder.py.
+# for the possible settings see "man pbuilderrc"
+
+BASETGZ=%(basedir)s/base.tgz
+BUILDPLACE=%(builddir)s
+USEPROC=yes
+USEDEVPTS=yes
+BUILDRESULT=%(resultdir)s
+DISTRIBUTION=%(distribution)s
+APTCACHE=%(basedir)s/aptcache
+APTCACHEHARDLINK=yes
+REMOVEPACKAGES=lilo
+MIRRORSITE="%(mirrorsite)s"
+OTHERMIRROR="%(othermirror)s"
+BINDMOUNTS="%(extra-pkgdir)s"
+PKGNAME_LOGFILE=yes
+'''
+
+
+def init_pbuilder(pbuilderrc, distribution, mirrorsite, extramirrors, root_cmd):
+ if not os.path.isabs(pbuilderrc):
+ print >>sys.stderr, "pbuilderrc must be an absolute filename"
+ sys.exit(1)
+
+ if os.path.exists(pbuilderrc):
+ print >>sys.stderr, "pbuilderrc %r already exists." % pbuilderrc
+ sys.exit(1)
+
+ basedir = os.path.dirname(pbuilderrc)
+ replacements = dict(basedir=basedir,
+ distribution=distribution,
+ mirrorsite=mirrorsite)
+
+ # create the pbuilder directories. basedir is created implicitly by
+ # creating its subdirectories.
+ for subdir in ["base", "build", "result", "aptcache", "extra-pkg"]:
+ directory = os.path.join(basedir, subdir)
+ replacements[subdir + "dir"] = directory
+ print "creating directory:", repr(directory)
+ ensure_directory(directory)
+
+ # build OTHERMIRROR value. We always include the extra-pkg dir.
+ othermirror = "deb file://%(extra-pkgdir)s ./" % replacements
+ if extramirrors:
+ othermirror += " | " + extramirrors
+ replacements["othermirror"] = othermirror
+
+ # create the pbuilderrcfile
+ print "creating pbuilderrc:", repr(pbuilderrc)
+ writefile(pbuilderrc, pbuilderrc_template % replacements)
+
+ # turn the extra-pkg directory into a property deb archive
+ print "turning the extra-pkg dir into a debian archive"
+ extra_pkgdir = replacements["extra-pkgdir"]
+ call(["apt-ftparchive", "packages", "."],
+ stdout=open(os.path.join(extra_pkgdir, "Packages"), "w"),
+ cwd=extra_pkgdir)
+
+ # create the base.tgz chroot
+ print "running pbuilder create"
+ call(root_cmd + ["pbuilder", "create", "--configfile", pbuilderrc])
+
+
+def parse_commandline():
+ parser = create_parser()
+ parser.set_defaults(distribution="etch")
+ parser.add_option("--mirrorsite",
+ help=("The debian mirror site"
+ " (pbuilder MIRRORSITE setting). Required."))
+ parser.add_option("--othermirror",
+ help=("Extra contents of the OTHERMIRROR setting."
+ " See the pbuilder documentation for the format."))
+ parser.add_option("--distribution",
+ help=("The debian distribution for the pbuilder chroot."
+ " Default is etch."))
+ return parser.parse_args()
+
+
+def main():
+ options, args = parse_commandline()
+
+ if options.mirrorsite is None:
+ print >>sys.stderr, "Missing required option --mirrorsite"
+ sys.exit(1)
+
+ treepkg_opts, packager_opts = read_config(options.config_file)
+ group = PackagerGroup([create_package_track(**opts)
+ for opts in packager_opts],
+ **treepkg_opts)
+ track = group.get_package_tracks()[0]
+ init_pbuilder(track.pbuilderrc,
+ distribution=options.distribution,
+ mirrorsite=options.mirrorsite,
+ extramirrors=options.othermirror,
+ root_cmd=track.root_cmd)
+
+main()
Property changes on: trunk/bin/initpbuilder.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/publishstaticweb.py
===================================================================
--- trunk/bin/publishstaticweb.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/publishstaticweb.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,75 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Publishes a static web-site with a status report"""
+
+import sys
+import os
+from optparse import OptionParser
+from ConfigParser import SafeConfigParser
+
+import treepkgcmd
+from treepkg.readconfig import read_config_section
+from treepkg.run import call
+from treepkg.cmdexpand import cmdexpand
+from treepkg.util import ensure_directory
+
+def remove_trailing_slashes(s):
+ return s.rstrip("/")
+
+def expand_filename(filename):
+ """
+ Applies os.path.expanduser and os.path.expandvars to filename
+ """
+ return os.path.expandvars(os.path.expanduser(filename))
+
+staticweb_desc = ["build_user", "build_host", "build_create",
+ ("build_dir", remove_trailing_slashes),
+ "publish_user", "publish_host",
+ ("publish_dir", remove_trailing_slashes),
+ ("cachedir",
+ lambda s: expand_filename(remove_trailing_slashes(s)))]
+
+def read_config(filename):
+ parser = SafeConfigParser()
+ parser.read([filename])
+ return read_config_section(parser, "staticweb", staticweb_desc)
+
+def parse_commandline():
+ parser = OptionParser()
+ parser.set_defaults(config_file=os.path.join(treepkgcmd.topdir,
+ "staticweb.cfg"))
+ parser.add_option("--config-file",
+ help=("The tree packager config file."
+ " Default staticweb.cfg"))
+ return parser.parse_args()
+
+def publish_static_site(config_filename):
+ config = read_config(config_filename)
+
+ # create web-page on build host
+ call(cmdexpand("ssh $build_user$@$build_host $build_create $build_dir",
+ **config))
+
+ # rsync the new web-pages to the local cache
+ ensure_directory(config["cachedir"])
+ call(cmdexpand("rsync -rL --delete $build_user$@$build_host:$build_dir/"
+ " $cachedir",
+ **config))
+
+ # rsync the web pages from the local cache to the publishing host
+ call(cmdexpand("rsync -rL --delete $cachedir/"
+ " $publish_user$@$publish_host:$publish_dir",
+ **config))
+
+
+def main():
+ options, args = parse_commandline()
+ publish_static_site(options.config_file)
+
+main()
Property changes on: trunk/bin/publishstaticweb.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/reportstatus.py
===================================================================
--- trunk/bin/reportstatus.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/reportstatus.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,40 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Reports the current status of the tree packager"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.report import get_packager_group, prepare_report
+
+def parse_commandline():
+ return create_parser().parse_args()
+
+
+def report_text(group):
+ report = prepare_report(group)
+ for revno, row in report.revisions:
+ for col in row:
+ if col:
+ print "%s %s: %s" % (col.name, revno, col.status.desc)
+ if col.status.start:
+ print " Start:", col.status.start
+ print " Stop:", col.status.stop
+ print
+
+def main():
+ options, args = parse_commandline()
+ group = get_packager_group(options.config_file)
+ report_text(group)
+
+
+main()
Property changes on: trunk/bin/reportstatus.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/runtreepkg.py
===================================================================
--- trunk/bin/runtreepkg.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/runtreepkg.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,64 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Starts the tree packager"""
+
+import sys
+import os
+import logging
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.packager import create_package_track, PackagerGroup
+from treepkg.readconfig import read_config
+
+def initialize_logging():
+ """Initializes the logging system"""
+ root = logging.getLogger()
+ root.setLevel(logging.DEBUG)
+ hdlr = logging.StreamHandler()
+ fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
+ hdlr.setFormatter(fmt)
+ root.addHandler(hdlr)
+
+def parse_commandline():
+ parser = create_parser()
+ parser.add_option("--once", action="store_true",
+ help=("Check the packagers only once and exit afterwards."
+ " Without this option, the tree packager will"
+ " check periodically."))
+ return parser.parse_args()
+
+def main():
+ options, args = parse_commandline()
+
+ initialize_logging()
+
+ treepkg_opts, packager_opts = read_config(options.config_file)
+
+ if args:
+ packager_opts = [opts for opts in packager_opts if opts["name"] in args]
+ # check whether we got all of the names in args:
+ for opts in packager_opts:
+ name = opts["name"]
+ if name in args:
+ args.remove(name)
+ for name in args:
+ print >>sys.stderr, "No package tracks found named %r" % name
+
+ if packager_opts:
+ group = PackagerGroup([create_package_track(**opts)
+ for opts in packager_opts],
+ **treepkg_opts)
+ if options.once:
+ group.check_package_tracks()
+ else:
+ group.run()
+
+main()
Property changes on: trunk/bin/runtreepkg.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/starttreepkgweb.py
===================================================================
--- trunk/bin/starttreepkgweb.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/starttreepkgweb.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,32 @@
+#! /usr/bin/python2.4
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Starts the tree packager webinterface"""
+
+import sys
+import os
+from optparse import OptionParser
+
+import treepkgcmd
+from treepkg.options import create_parser
+from treepkg.web import runserver
+
+def parse_commandline():
+ parser = create_parser()
+ parser.set_defaults(cherrypy_config=os.path.join(treepkgcmd.topdir,
+ "cherrypy.cfg"))
+ parser.add_option("--cherrypy-config",
+ help=("The cherrypy config file for the web interface."
+ " Default cherrypy.cfg"))
+ return parser.parse_args()
+
+def main():
+ options, args = parse_commandline()
+ runserver(options.config_file, options.cherrypy_config)
+
+main()
Property changes on: trunk/bin/starttreepkgweb.py
___________________________________________________________________
Name: svn:executable
+ *
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/bin/treepkgcmd.py
===================================================================
--- trunk/bin/treepkgcmd.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/bin/treepkgcmd.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,16 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Initializes the Python interpreter for the treepkg frontends"""
+
+import sys
+import os
+
+# make sure we can import treepkg. The topdir variable may be
+# referenced by users of this module.
+topdir=os.path.join(os.path.dirname(__file__), os.pardir)
+sys.path.insert(0, topdir)
Property changes on: trunk/bin/treepkgcmd.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/cherrypy.cfg
===================================================================
--- trunk/cherrypy.cfg 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/cherrypy.cfg 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,4 @@
+[global]
+server.socketPort = 9090
+server.threadPool = 10
+server.environment = "production"
Added: trunk/demo.cfg
===================================================================
--- trunk/demo.cfg 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/demo.cfg 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,79 @@
+[DEFAULT]
+# the default section provides defaults for the other sections. Also,
+# other sections can use the values here in interpolations.
+
+# The basedir to use for interpolations in other sections
+treepkg_dir: /home/builder/enterprise
+
+# The command to use to gain the permissions to execute pbuilder. The
+# default is sudo. The actual command line used to run pbuilder is
+# root_cmd followed by the pbuilder command line. With the default it
+# is something like
+#
+# sudo pbuilder build <name-of-dsc-file>
+#
+# The value of this option is split into words with the python function
+# shlex.split. See the python documentation for the precise semantics.
+# shlex.split works pretty much like a POSIX shell but it doesn't to any
+# expansions.
+#
+# Set an empty value to indicate that no special command is needed.
+#
+# You can override this in the pkg_ sections if you need package
+# specific values
+root_cmd: sudo
+
+# The pbuilder config file to use. It should be an absolute filename.
+# The script initpbuilder.py can create it and the rest of the pbuilder
+# files and directories. You can override this in the pkg_ sections for
+# individual packagers if necessaary. You will have to adapt pbuilder
+# yourself, then, though.
+pbuilderrc: %(treepkg_dir)s/pbuilder/pbuilderrc
+
+
+# Email address and name to use as the packager in the debian packages.
+# You can override this in the pkg_ sections if you need package
+# specific values
+deb_email: packager at example.com
+deb_fullname: Sample Packager
+
+
+[treepkg]
+# Section for general tree packager configuration
+
+# Interval in seconds between checks for updates
+check_interval: 300
+
+
+[pkg_kdepim]
+# Sections with names starting with "pkg_" define the configuration for
+# a package.
+
+# The SVN URL to check out for packaging. Will only be used for the
+# initial checkout
+svn_url: svn://anonsvn.kde.org/home/kde/branches/kdepim/enterprise/kdepim
+
+# The directory under which the packager directory structure will be
+# created.
+base_dir: %(treepkg_dir)s/kdepim
+
+# The packager class defines how the packaging works. The
+# enterprise.kdepim class knows how to package the enterprise branch of
+# KDE-PIM.
+packager_class: enterprise.kdepim
+
+# Required fields for a pkg_ section inherited from the DEFAULT section
+# in this example: root_cmd deb_email deb_fullname
+
+# An additional option, name, is inferred from the section name. Its
+# value is the part of the section name after the pkg_ prefix.
+
+
+[pkg_i18n]
+# Another packager. This one for the the localizations. This packager
+# requires and additional parameter, orig_tarball.
+svn_url: svn://anonsvn.kde.org/home/kde/branches/kdepim/enterprise/kde-l10n
+base_dir: %(treepkg_dir)s/kde-i18n
+orig_tarball: /home/builder/kde-i18n-de-3.5.5.tar.bz2
+
+packager_class: enterprise.kdei18n
Added: trunk/demostaticweb.cfg
===================================================================
--- trunk/demostaticweb.cfg 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/demostaticweb.cfg 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,42 @@
+# Demo config file for publishstaticweb.py. The default config file
+# used by publishstaticweb.py is staticweb.cfg, so to use this file as
+# the basis for your configuration, copy or rename this file and adapt
+# it to your needs.
+
+[staticweb]
+# Username and host on which the treepackager runs. publishstaticweb.py
+# has to be able to connect to that host as the build_user via ssh
+# without knowing the password. This is best achieved with the
+# ssh-agent. Also, publishstaticweb.py uses rsync to copy files from
+# build_host to the local host.
+build_user: builder
+build_host: localhost
+
+# the program to run on build_host to create the static web-page.
+# Currently publishstaticweb.py assumes that the default configuration
+# for that program works.
+build_create: ~/treepackager/createstaticweb.py
+
+# the directory on build_host where the static web-site should be put.
+# This value is used as the parameter to the build_create command on
+# build_host.
+build_dir: /tmp/treepkg-web
+
+# Username and host on which to publish the static pages.
+# publishstaticweb.py uses rsync to copy the files from the local cache
+# to the publish_host.
+publish_user: builder
+publish_host: localhost
+
+# the directory on the publish_host where the web-site resides. It's
+# the directory where the index.html file will be found. The script may
+# delete files under that directory.
+publish_dir: /tmp/treepkg-status
+
+# local cache directory. publishstaticweb.py may delete it and its
+# contents. The value is passed through the python functions
+# os.path.expanduser and os.path.expandvars, so references to home
+# directories of the form ~ or ~user at the beginning of the value and
+# environment variable references of the form $VAR or ${VAR} are
+# expanded.
+cachedir: /tmp/${USER}/treepkg-status-cache
Added: trunk/enterprise/__init__.py
===================================================================
Property changes on: trunk/enterprise/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/enterprise/kdei18n.py
===================================================================
--- trunk/enterprise/kdei18n.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/enterprise/kdei18n.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,139 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import logging
+import shutil
+import time
+
+import treepkg.packager
+import treepkg.util
+import treepkg.run as run
+from treepkg.cmdexpand import cmdexpand
+
+
+class SourcePackager(treepkg.packager.SourcePackager):
+
+ """Creates the debian source package for the i18n files
+
+ This is quite complicated. The orig.tar.gz file of the debian
+ source package contains one .tar.bz2 file for every language. Those
+ .tar.bz files are the kde-18n-<lang> files released by the KDE
+ project. For now, we only have the German localization in the
+ enterprise source package, so the orig.tar.gz file will have the
+ following contents:
+
+ kde-i18n-<version>/
+ kde-i18n-<version>/kde-i18n-de-<version>.tar.bz2
+
+ <version> is the same everywhere.
+
+ The kde-i18n-de tarball contains the localization files for the
+ entire KDE project, including KDE-PIM. The SVN enterprise branch
+ only contains the localizations for KDE-PIM, though, so we have to
+ assemble a new .tar.bz2 from an original
+ kde-i18n-de-<version>.tar.bz and the new files from the enterprise
+ branch.
+ """
+
+ pkg_basename = "kde-i18n"
+
+ def determine_package_version(self, directory):
+ enterprise_version = (time.strftime("%Y%m%d", time.localtime()) \
+ + "." + str(self.revision))
+ kdepimversion = "3.5.5"
+ version_template = "%(kdepimversion)s.enterprise.0.%(enterprise_version)s"
+ return version_template % locals()
+
+ def unpack_orig_tarball(self):
+ orig_tarball = self.track.orig_tarball
+ run.call(cmdexpand("tar xjf $tarball -C $directory",
+ tarball=orig_tarball, directory=self.work_dir))
+ tarbasename = os.path.basename(orig_tarball)
+ splitext = os.path.splitext
+ return os.path.join(self.work_dir,
+ splitext(splitext(tarbasename)[0])[0])
+
+ def create_i18n_de_tarball(self, pkgbasedir, pkgbaseversion):
+ """Creates a new kde-i18n-de tarball and returns its filename
+
+ This is the tarball as it would be released by KDE. It is not
+ yet the tarball that will become the .orig.tar.gz for the debian
+ package.
+ """
+ logging.info("Creating kde-i18n-de tarball")
+ untarred_dir = self.unpack_orig_tarball()
+ new_de_dir = os.path.join(pkgbasedir, "new-de")
+ de_dir = os.path.join(pkgbasedir, "de")
+ os.rename(de_dir, new_de_dir)
+ treepkg.util.copytree(untarred_dir, de_dir)
+ treepkg.util.copytree(new_de_dir, de_dir)
+ logging.info("Running scripts/autogen.sh for kde-i18n-de tarball")
+ run.call(cmdexpand("/bin/sh scripts/autogen.sh de"), cwd=pkgbasedir,
+ suppress_output=True)
+
+ tarballdir = "kde-i18n-de-" + pkgbaseversion
+ os.rename(de_dir, os.path.join(pkgbasedir, tarballdir))
+
+ tarball = os.path.join(os.path.dirname(pkgbasedir),
+ tarballdir + ".tar.bz2")
+ run.call(cmdexpand("tar cjf $tarball -C $pkgbasedir $tarballdir",
+ **locals()))
+ logging.info("Created kde-i18n-de tarball")
+ return tarball
+
+ def do_package(self):
+ # Create a new kde-i18n-de tarball from current SVN and the base
+ # kde-i18n-de tarball.
+ pkgbaseversion, pkgbasedir = self.export_sources()
+ tarball = self.create_i18n_de_tarball(pkgbasedir, pkgbaseversion)
+
+ # We have to reuse the same directory when building the
+ # orig.tar.gz. However, we need to preserver the scripts
+ # sub-directory because it's not needed for the kde-i18n-de
+ # tarball but for the .orig.tar.gz.
+ pkg_scripts_dir = os.path.join(pkgbasedir, "scripts")
+ tmp_scripts_dir = os.path.join(self.work_dir, "scripts")
+ os.rename(pkg_scripts_dir, tmp_scripts_dir)
+ shutil.rmtree(pkgbasedir)
+ os.mkdir(pkgbasedir)
+ os.rename(tmp_scripts_dir, pkg_scripts_dir)
+
+ pkgbasename = self.pkg_basename + "_" + pkgbaseversion
+ origtargz = os.path.join(self.work_dir,
+ pkgbasename + ".orig.tar.gz")
+ os.rename(tarball, os.path.join(pkgbasedir,
+ os.path.basename(tarball)))
+ self.create_tarball(origtargz, self.work_dir,
+ os.path.basename(pkgbasedir))
+
+ changemsg = ("Update to SVN enterprise branch rev. %d"
+ % (self.revision,))
+ self.copy_debian_directory(pkgbasedir, pkgbaseversion,
+ changemsg)
+
+ self.create_source_package(pkgbasedir, origtargz)
+ self.move_source_package(pkgbasename)
+
+
+class RevisionPackager(treepkg.packager.RevisionPackager):
+
+ source_packager_cls = SourcePackager
+
+
+class PackageTrack(treepkg.packager.PackageTrack):
+
+ revision_packager_cls = RevisionPackager
+
+ svn_external_subdirs = ["scripts", "scripts/admin"]
+
+ extra_config_desc = ["orig_tarball"]
+
+ def __init__(self, *args, **kw):
+ self.orig_tarball = kw.pop("orig_tarball")
+ super(PackageTrack, self).__init__(*args, **kw)
+
Property changes on: trunk/enterprise/kdei18n.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/enterprise/kdepim.py
===================================================================
--- trunk/enterprise/kdepim.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/enterprise/kdepim.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,82 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Packager that builds KDE-PIM debian packages from the enterprise branch"""
+
+import os
+import time
+import re
+
+import treepkg.util
+import treepkg.packager
+
+
+class SourcePackager(treepkg.packager.SourcePackager):
+
+ pkg_basename = "kdepim"
+
+ def __init__(self, *args, **kw):
+ super(SourcePackager, self).__init__(*args, **kw)
+ self.enterprise_version = (time.strftime("%Y%m%d", time.localtime()) \
+ + "." + str(self.revision))
+
+ def kdepim_version(self, directory):
+ """Determine the kdepim version.
+
+ The version is taken from the kdepim.lsm file.
+ """
+ return treepkg.util.extract_lsm_version(os.path.join(directory,
+ "kdepim.lsm"))
+
+ def determine_package_version(self, directory):
+ enterprise_version = self.enterprise_version
+ kdepimversion = self.kdepim_version(directory)
+ version_template = "%(kdepimversion)s.enterprise.0.%(enterprise_version)s"
+ return version_template % locals()
+
+ def update_version_numbers(self, pkgbasedir):
+ """Overrides the inherited method to update version numbers in the code
+ """
+ versionstring = "(enterprise %s)" % self.enterprise_version
+ for versionfile in ["kmail/kmversion.h", "kontact/src/main.cpp",
+ "korganizer/version.h"]:
+ filename = os.path.join(pkgbasedir, versionfile)
+ patched = re.sub("\(enterprise ([^)]*)\)", versionstring,
+ open(filename).read())
+ f = open(filename, "w")
+ f.write(patched)
+ f.close()
+
+ def do_package(self):
+ pkgbaseversion, pkgbasedir = self.export_sources()
+ self.update_version_numbers(pkgbasedir)
+
+ pkgbasename = "kdepim_" + pkgbaseversion
+ origtargz = os.path.join(self.work_dir,
+ pkgbasename + ".orig.tar.gz")
+ self.create_tarball(origtargz, self.work_dir,
+ os.path.basename(pkgbasedir))
+
+ changemsg = ("Update to SVN enterprise branch rev. %d"
+ % (self.revision,))
+ self.copy_debian_directory(pkgbasedir, pkgbaseversion,
+ changemsg)
+
+ self.create_source_package(pkgbasedir, origtargz)
+ self.move_source_package(pkgbasename)
+
+
+class RevisionPackager(treepkg.packager.RevisionPackager):
+
+ source_packager_cls = SourcePackager
+
+
+class PackageTrack(treepkg.packager.PackageTrack):
+
+ revision_packager_cls = RevisionPackager
+
+ svn_external_subdirs = ["admin"]
Property changes on: trunk/enterprise/kdepim.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/test/runtests.py
===================================================================
--- trunk/test/runtests.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/runtests.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,72 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""
+Main entry point for the test suite.
+
+Just run this file as a python script to execute all tests
+"""
+
+import os
+import sys
+import unittest
+import optparse
+
+test_dir = os.path.dirname(__file__)
+sys.path.append(os.path.join(test_dir, os.pardir))
+
+def find_test_modules(dirname, package = None):
+ """Return a list the names of the test modules in the directory dirname
+
+ The return value is a list of names that can be passed to
+ unittest.defaultTestLoader.loadTestsFromNames. Each name of the
+ list is the name of a pure python module, one for each file in
+ dirname whose name starts with 'test' and ends with '.py'.
+
+ The optional parameter package should be the name of the python
+ package whose directory is dirname. If package is given all names
+ in the returned list will be prefixed with package and a dot.
+ """
+ if package:
+ prefix = package + "."
+ else:
+ prefix = ""
+
+ suffix = ".py"
+ return [prefix + name[:-len(suffix)]
+ for name in os.listdir(dirname)
+ if name.startswith("test") and name.endswith(suffix)]
+
+
+def main():
+ """Run all the tests in the test suite"""
+
+ parser = optparse.OptionParser()
+ parser.set_defaults(verbosity=1)
+ parser.add_option("-v", "--verbose", action="store_const", const=2,
+ dest="verbosity")
+ opts, rest = parser.parse_args()
+
+ # Build the list of test names. If names were given on the command
+ # line, run exactly those. Othwerwise build a default list of
+ # names.
+ if rest:
+ names = rest
+ else:
+ # All Python files starting with 'test' in the current directory
+ # and some directories in Extensions contain test cases.
+ # FIXME: It should be possible to run runtests.py even when not in
+ # the test directory
+ names = find_test_modules(test_dir)
+ suite = unittest.defaultTestLoader.loadTestsFromNames(names)
+ runner = unittest.TextTestRunner(verbosity=opts.verbosity)
+ result = runner.run(suite)
+ sys.exit(not result.wasSuccessful())
+
+
+if __name__ == "__main__":
+ main()
Property changes on: trunk/test/runtests.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/test/test_cmdexpand.py
===================================================================
--- trunk/test/test_cmdexpand.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/test_cmdexpand.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,124 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Tests for the cmdexpand function"""
+
+import unittest
+
+from treepkg.cmdexpand import cmdexpand
+
+
+class TestCMDExpand(unittest.TestCase):
+
+ def test_words(self):
+ """Test cmdexpand with simple whitespace separated words"""
+ self.assertEquals(cmdexpand("abc defg xyz zy"),
+ ['abc', 'defg', 'xyz', 'zy'])
+
+ def test_single_quoted(self):
+ """Test cmdexpand with some single quoted words"""
+ self.assertEquals(cmdexpand("abc 'defg xyz' zy"),
+ ['abc', 'defg xyz', 'zy'])
+
+ def test_double_quoted(self):
+ """Test cmdexpand with some double quoted words"""
+ self.assertEquals(cmdexpand('abc "defg xyz" zy'),
+ ['abc', 'defg xyz', 'zy'])
+
+ def test_word_expansion(self):
+ """Test cmdexpand with simple word expansion"""
+ self.assertEquals(cmdexpand('abc $foo ghi', foo="def"),
+ ['abc', 'def', 'ghi'])
+ self.assertEquals(cmdexpand('abc $foo ghi $bar', foo="def", bar="X"),
+ ['abc', 'def', 'ghi', 'X'])
+
+ def test_word_expansion_braced_name(self):
+ """Test cmdexpand with word expansion using braced names"""
+ self.assertEquals(cmdexpand('abc ${foo} x${foo}y ghi', foo="def"),
+ ['abc', 'def', 'xdefy', 'ghi'])
+
+ def test_word_expansion_non_byte_string(self):
+ """Test cmdexpand quoting of dollar signs"""
+ self.assertEquals(cmdexpand('abc $foo $bar ghi', foo=123, bar=u"1 2 3"),
+ ['abc', '123', '1 2 3', 'ghi'])
+
+ def test_word_expansion_non_identifier(self):
+ """Test cmdexpand word expansion if dollar not followed by identifier"""
+ # $ immediately followed by a non-identifier character
+ self.assertRaises(ValueError, cmdexpand, 'abc $#foo bar', foo="def")
+
+ def test_word_expansion_inside_words(self):
+ """Test cmdexpand word expansions in parts of words"""
+ self.assertEquals(cmdexpand("$foo x$bar y$baz.",
+ foo="abc", bar="yz", baz="zx"),
+ ["abc", "xyz", "yzx."])
+ self.assertEquals(cmdexpand("$foo x$bar-$baz.",
+ foo="abc", bar="yz", baz="zx"),
+ ["abc", "xyz-zx."])
+
+ def test_case_sensitivity(self):
+ """Test case sensitivity of expansion keys"""
+ self.assertEquals(cmdexpand('abc $foo $Foo $FOO',
+ foo="def", Foo="DEF", FOO="Def"),
+ ['abc', 'def', 'DEF', 'Def'])
+
+ def test_list_expansion(self):
+ """Test cmdexpand with list expansion"""
+ self.assertEquals(cmdexpand('abc @foo ghi', foo=["d", "e", "f"]),
+ ['abc', 'd', 'e', 'f', 'ghi'])
+
+ def test_list_expansion_non_string(self):
+ """Test cmdexpand with list expansion"""
+ self.assertEquals(cmdexpand('abc @foo ghi', foo=[1, 1.0, None]),
+ ['abc', '1', '1.0', 'None', 'ghi'])
+
+ def test_list_expansion_with_iterators(self):
+ """Test cmdexpand with list expansion using an iterator"""
+ self.assertEquals(cmdexpand('abc @foo ghi',
+ foo=(i**2 for i in range(3))),
+ ['abc', '0', '1', '4', 'ghi'])
+
+ def test_list_expansion_non_identifier(self):
+ """Test cmdexpand with at-sign not followed by identifier"""
+ # @+identifier do not cover entire word
+ self.assertRaises(ValueError, cmdexpand, 'abc @foo, ghi',
+ foo=["d", "e", "f"])
+
+ # @ immediately followed by a non-identifier character
+ self.assertRaises(ValueError, cmdexpand, 'abc @. z')
+
+ def test_list_expansion_inside_word(self):
+ """Test whether cmdexpand raises ValueError for at-signs inside words"""
+ self.assertRaises(ValueError, cmdexpand, 'abc x at foo ghi',
+ foo=["d", "e", "f"])
+
+
+ def test_dollar_quoting(self):
+ """Test cmdexpand quoting of dollar signs"""
+ self.assertEquals(cmdexpand('abc $$foo $foo g$$hi', foo="def"),
+ ['abc', '$foo', 'def', 'g$hi'])
+
+ def test_atsign_quoting(self):
+ """Test cmdexpand quoting of at-signs"""
+ self.assertEquals(cmdexpand('abc @foo $@foo g$@i', foo=["d", "e", "f"]),
+ ['abc', 'd', 'e', 'f', '@foo', 'g at i'])
+
+ def test_interaction_with_shlex_quoting(self):
+ """Test cmdexpand's interaction with shlex's quoting"""
+ # Unlike unix-shells the expansion isn't influenced much by
+ # shell quoting as supported by shlex.
+ self.assertEquals(cmdexpand('abc "@foo" \'@foo\' ghi',
+ foo=["d", "e", "f"]),
+ ['abc', 'd', 'e', 'f', 'd', 'e', 'f', 'ghi'])
+ self.assertEquals(cmdexpand('abc "$foo" \'$foo\' ghi', foo="def"),
+ ['abc', 'def', 'def', 'ghi'])
+ self.assertEquals(cmdexpand('abc " $foo" \'a $foo\' ghi', foo="def"),
+ ['abc', ' def', 'a def', 'ghi'])
+
+
+if __name__ == "__main__":
+ unittest.main()
Property changes on: trunk/test/test_cmdexpand.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/test/test_status.py
===================================================================
--- trunk/test/test_status.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/test/test_status.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,87 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Tests for the Status classes"""
+
+import os
+import stat
+import unittest
+from datetime import datetime
+
+from treepkg.status import RevisionStatus, Status, EnumFieldDesc
+from treepkg.util import ensure_directory, writefile
+
+
+
+class TestStatus(unittest.TestCase):
+
+ def tempfilename(self):
+ tempdir = os.path.join(os.path.dirname(__file__), "temp")
+ ensure_directory(tempdir)
+ return os.path.join(tempdir, self.id())
+
+ def setUp(self):
+ self.filename = self.tempfilename()
+ if os.path.exists(self.filename):
+ os.remove(self.filename)
+
+ def test_status(self):
+ status = RevisionStatus(self.filename)
+ status.error()
+
+ otherstatus = RevisionStatus(self.filename)
+ self.assertEquals(otherstatus.status.name, "error")
+
+ def test_status_file_permissions(self):
+ status = RevisionStatus(self.filename)
+ status.error()
+
+ mode = os.stat(self.filename).st_mode
+ self.assertEquals(stat.S_IMODE(mode) & 0444, 0444)
+
+ def test_getting_unknown_fields(self):
+ status = RevisionStatus(self.filename)
+ self.assertRaises(AttributeError, getattr, status, "unknown_field")
+
+ def test_setting_unknown_fields(self):
+ status = RevisionStatus(self.filename)
+ self.assertRaises(AttributeError,
+ setattr, status, "unknown_field", "some value")
+
+ def test_default_values(self):
+ status = RevisionStatus(self.filename)
+ self.assertEquals(status.status.name, "unknown")
+ self.assertEquals(status.start, None)
+ self.assertEquals(status.stop, None)
+
+ def test_date(self):
+ timestamp = datetime(2007, 3, 9, 17, 32, 55)
+ status = RevisionStatus(self.filename)
+ status.start = timestamp
+
+ otherstatus = RevisionStatus(self.filename)
+ self.assertEquals(otherstatus.start, timestamp)
+
+ def test_magic(self):
+ writefile(self.filename,
+ "Some other magic\nstart: 2007-03-09 17:32:55\n")
+ self.assertRaises(ValueError, RevisionStatus, self.filename)
+
+ def test_status_enum(self):
+
+ class TestStatus(Status):
+ status = EnumFieldDesc()
+ status.add("not_running", "Process is not running",
+ default=True)
+ status.add("running", "Process is running")
+ status.add("error", "An error occurred")
+
+ status = TestStatus(self.filename)
+ status.running()
+
+ otherstatus = TestStatus(self.filename)
+ self.assertEquals(otherstatus.status.name, "running")
Property changes on: trunk/test/test_status.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/__init__.py
===================================================================
--- trunk/treepkg/__init__.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/__init__.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,6 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
Property changes on: trunk/treepkg/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/cmdexpand.py
===================================================================
--- trunk/treepkg/cmdexpand.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/cmdexpand.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,116 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Shell like string splitting and expansion"""
+
+import re
+import shlex
+
+
+# helper for the other regular expression matching a python identifier
+match_identifier = "[_a-zA-Z][_a-zA-Z0-9]*"
+
+# regular expression to use for word expansion matching a dollar
+# followed by exactly one of these:
+# a) another dollar sign or the at-sign (for quoting of these characters)
+# b) a python identifier
+# c) a python identifier enclosed in braces
+# d) something else which indicates invalid use of the dollar sign
+rx_word_expansion = re.compile(r"\$((?P<delim>[$@])"
+ r"|(?P<named>%(identifier)s)"
+ r"|\{(?P<braced>%(identifier)s)\}"
+ r"|(?P<invalid>))"
+ % dict(identifier=match_identifier))
+
+# regular expression matching an entire word that has to be list
+# expanded. The regex matches if the word starts with an at-sign. The
+# part of the word that followes the at-sign either matches an
+# identifier with the named group "named" or anything else which
+# indicates invalid use the at-sign.
+rx_list_expansion = re.compile(r"^@((?P<named>%(identifier)s)|(?P<invalid>.+))$"
+ % dict(identifier=match_identifier))
+
+# match an unquoted at-sign.
+rx_unquoted_at = re.compile("[^$]@")
+
+def expandword(word, mapping):
+ def replacment(match):
+ key = match.group("named") or match.group("braced")
+ if key:
+ return str(mapping[key])
+
+ delim = match.group("delim")
+ if delim:
+ return delim
+
+ # otherwise invalid has matched and we raise a value error
+ assert match.group("invalid") != None
+ raise ValueError
+
+ return rx_word_expansion.sub(replacment, word)
+
+def cmdexpand(string, **kw):
+ """Split the string into 'words' and expand variable references.
+
+The string is first split into words with shlex.split. Each of the
+words is then subjected to either word expansion or list expansion.
+Word expansion is very similar to what the Template class in Python's
+string module provides:
+
+ '$$' is expanded to '$'
+
+ '$@' is expanded to '@'
+
+ '$identifier' is expanded to the value of the variable given by
+ identifier. The identifier has the same syntax as a normal Python
+ identifier. The identifier stops at the first non-identifier
+ character. The value is converted to a string with str.
+
+ '${identifier}' is treated like '$identifier' and provides a way to
+ delimit the identifier in cases where the identifier is followed by
+ characters that would otherwise be interpreted as part of the
+ identifier.
+
+A word will remain a single word after the expansion even if the
+expanded string would be treated as multiple words by shlex.
+
+A list expansion is applied to words that consist of a '@' followed by
+an identifier. Nothing else must be in the word. The variable the
+identifier refers to must be a sequence and the word will be replaced by
+the sequence with each element of the sequence converted to a string
+with str.
+
+The variables known to the function are the keyword arguments.
+
+Examples:
+
+ >>> from cmdexpand import cmdexpand
+ >>> cmdexpand("ssh $user$@$host", user="john", host="python")
+ ['ssh', 'john at python']
+
+ >>> cmdexpand("scp @files $user$@$host:$remotedir", user="john",
+ ... host="python", files=["main.py", "cmdexpand.py"],
+ ... remotedir="/home/john/files")
+ ['scp', 'main.py', 'cmdexpand.py', 'john at python:/home/john/files']
+"""
+ words = shlex.split(string)
+ for index, word in reversed(list(enumerate(words))):
+ match = rx_unquoted_at.search(word)
+ if match:
+ raise ValueError("%r contains an unquoted '@'" % word)
+ match = rx_list_expansion.match(word)
+ if match:
+ key = match.group("named")
+ if key:
+ words[index:index + 1] = (str(item) for item in kw[key])
+ else:
+ assert match.group("invalid") != None
+ raise ValueError("In %r the characters after the '@'"
+ " do not match a python identifier" % word)
+ else:
+ words[index] = expandword(word, kw)
+ return words
Property changes on: trunk/treepkg/cmdexpand.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/options.py
===================================================================
--- trunk/treepkg/options.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/options.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,23 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Common command line options"""
+
+import os
+from optparse import OptionParser
+
+
+def create_parser():
+ """Creates an OptionParser with common tree packager options"""
+ parser = OptionParser()
+ dirname = os.path.dirname(__file__)
+ parser.set_defaults(config_file=os.path.join(dirname, os.pardir,
+ "treepkg.cfg"))
+ parser.add_option("--config-file",
+ help=("The tree packager config file."
+ " Default treepkg.cfg"))
+ return parser
Property changes on: trunk/treepkg/options.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/packager.py
===================================================================
--- trunk/treepkg/packager.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/packager.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,416 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Classes to automatically build debian packages from subversion checkouts"""
+
+import os
+import time
+import re
+import logging
+import shutil
+import traceback
+import datetime
+
+import util
+import subversion
+import run
+import status
+from cmdexpand import cmdexpand
+
+def _filenameproperty(relative_dir):
+ def get(self):
+ return os.path.join(self.base_dir, relative_dir)
+ return property(get)
+
+
+class SourcePackager(object):
+
+ # Derived classes must supply the package basename
+ pkg_basename = None
+
+ def __init__(self, track, status, work_dir, src_dir, revision):
+ self.track = track
+ self.status = status
+ self.work_dir = work_dir
+ self.src_dir = src_dir
+ self.revision = revision
+ assert(self.pkg_basename)
+
+ def determine_package_version(self, directory):
+ """Returns the version number of the new package as a string
+
+ The directory parameter is the name of the directory containing
+ the newly exported sources. The sources were exported with the
+ export_sources method.
+
+ The default implementation simply returns the revision converted
+ to a string.
+ """
+ return str(self.revision)
+
+ def export_sources(self):
+ """Export the sources from the subversion working directory
+
+ This method first exports the sources to a temporary directory
+ and then renames the directory. The new name is of the form
+
+ <pkg_basename>-<version>
+
+ Where pkg_basename is the value of self.pkg_basename and version
+ is the return value of the determine_package_version() method.
+ """
+ temp_dir = os.path.join(self.work_dir, "temp")
+ self.track.export_sources(temp_dir)
+
+ pkgbaseversion = self.determine_package_version(temp_dir)
+ pkgbasedir = os.path.join(self.work_dir,
+ self.pkg_basename + "-" + pkgbaseversion)
+
+ os.rename(temp_dir, pkgbasedir)
+ return pkgbaseversion, pkgbasedir
+
+
+ def update_version_numbers(self, pkgbasedir):
+ """Updates the version numbers in the code in pkgbasedir.
+
+ The default implementation does nothing. Derived classes should
+ override this method if necessary.
+ """
+
+ def create_tarball(self, tarballname, workdir, basedir):
+ """Creates a new tarball.
+
+ Parameters:
+
+ tarballname -- the filename of the new tarball
+ workdir -- The directory into which to change before running tar.
+ (actually this is done with GNUI tar's -C option)
+ basedir -- The basedirectory of the files that are packaged
+ into the tarfile. This should be a relative
+ filename directly in workdir.
+ """
+ logging.info("Creating tarball %r", tarballname)
+ run.call(cmdexpand("tar czf $tarballname -C $workdir $basedir",
+ **locals()))
+
+ def copy_debian_directory(self, pkgbasedir, pkgbaseversion, changemsg):
+ """Copies the debian directory and updates the copy's changelog
+
+ Parameter:
+ pkgbasedir -- The directory holding the unpacked source package
+ pkgbaseversion -- The version to update the changelog to
+ changemsg -- The message for the changelog
+
+ When determining the actual version for the new package, this
+ function looks at the previous version in the changelog. If it
+ has a prefix separated from the version number by a colon this
+ prefix is prepended to the pkgbaseversion parameter. Debian
+ uses such prefixes for the kde packages.
+ """
+ debian_dir = os.path.join(pkgbasedir, "debian")
+ changelog = os.path.join(debian_dir, "changelog")
+
+ self.track.copy_debian_directory(debian_dir)
+
+ logging.info("Updating %r", changelog)
+ oldversion = util.debian_changelog_version(changelog)
+ if ":" in oldversion:
+ oldversionprefix = oldversion.split(":")[0] + ":"
+ else:
+ oldversionprefix = ""
+ run.call(cmdexpand("debchange -c $changelog"
+ " -v ${oldversionprefix}${pkgbaseversion}-kk1"
+ " $changemsg", **locals()),
+ env=self.track.debian_environment())
+
+
+ def create_source_package(self, pkgbasedir, origtargz):
+ """Creates a new source package from pkgbasedir and origtargz"""
+ logging.info("Creating new source package")
+ run.call(cmdexpand("dpkg-source -b $directory $tarball",
+ directory=os.path.basename(pkgbasedir),
+ tarball=os.path.basename(origtargz)),
+ cwd=os.path.dirname(pkgbasedir),
+ suppress_output=True,
+ env=self.track.debian_environment())
+
+ def move_source_package(self, pkgbasename):
+ """Moves the new source package from the work_dir to the src_dir"""
+ logging.info("Moving source package to %r", self.src_dir)
+ util.ensure_directory(self.src_dir)
+ for filename in [filename for filename in os.listdir(self.work_dir)
+ if filename.startswith(pkgbasename)]:
+ os.rename(os.path.join(self.work_dir, filename),
+ os.path.join(self.src_dir, filename))
+
+ def package(self):
+ """Creates a source package from a subversion checkout.
+
+ After setting up the working directory, this method calls the
+ do_package method to do the actual packaging. Afterwards the
+ work directory is removed.
+ """
+ util.ensure_directory(self.work_dir)
+ try:
+ self.status.creating_source_package()
+ self.do_package()
+ self.status.source_package_created()
+ finally:
+ logging.info("Removing workdir %r", self.work_dir)
+ shutil.rmtree(self.work_dir)
+
+ def do_package(self):
+ """Does the work of creating a source package
+ This method must be overriden by derived classes.
+
+ The method should do the work in self.work_dir. When the
+ package is done, the source package files should be in
+ self.src_dir.
+ """
+ raise NotImplementedError
+
+
+class BinaryPackager(object):
+
+ def __init__(self, track, status, binary_dir, dsc_file, logfile):
+ self.track = track
+ self.status = status
+ self.binary_dir = binary_dir
+ self.dsc_file = dsc_file
+ self.logfile = logfile
+
+ def package(self):
+ self.status.creating_binary_package()
+ util.ensure_directory(self.binary_dir)
+ logging.info("Building binary package; logging to %r", self.logfile)
+ run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder build"
+ " --configfile $pbuilderrc"
+ " --logfile $logfile --buildresult $bindir $dsc",
+ rootcmd=self.track.root_cmd,
+ pbuilderrc=self.track.pbuilderrc,
+ logfile=self.logfile, bindir=self.binary_dir,
+ dsc=self.dsc_file),
+ suppress_output=True)
+ self.status.binary_package_created()
+
+
+class RevisionPackager(object):
+
+ source_packager_cls = SourcePackager
+ binary_packager_cls = BinaryPackager
+
+ def __init__(self, track, revision):
+ self.track = track
+ self.revision = revision
+ self.base_dir = self.track.pkg_dir_for_revision(self.revision, 1)
+ self.status = status.RevisionStatus(os.path.join(self.base_dir,
+ "status"))
+
+ work_dir = _filenameproperty("work")
+ binary_dir = _filenameproperty("binary")
+ src_dir = _filenameproperty("src")
+ build_log = _filenameproperty("build.log")
+
+ def find_dsc_file(self):
+ for filename in os.listdir(self.src_dir):
+ if filename.endswith(".dsc"):
+ return os.path.join(self.src_dir, filename)
+ return None
+
+ def has_build_log(self):
+ return os.path.exists(self.build_log)
+
+ def package(self):
+ try:
+ util.ensure_directory(self.work_dir)
+ self.status.start = datetime.datetime.utcnow()
+ src_packager = self.source_packager_cls(self.track, self.status,
+ self.work_dir, self.src_dir,
+ self.revision)
+ src_packager.package()
+
+ dsc_file = self.find_dsc_file()
+ if dsc_file is None:
+ raise RuntimeError("Cannot find dsc File in %r" % self.src_dir)
+
+ bin_packager = self.binary_packager_cls(self.track, self.status,
+ self.binary_dir, dsc_file,
+ self.build_log)
+ bin_packager.package()
+ self.status.stop = datetime.datetime.utcnow()
+ except:
+ self.status.error()
+ self.status.stop = datetime.datetime.utcnow()
+ raise
+
+ def remove_package_dir(self):
+ logging.info("Removing pkgdir %r", self.base_dir)
+ shutil.rmtree(self.base_dir)
+
+
+class PackageTrack(object):
+
+ revision_packager_cls = RevisionPackager
+
+ svn_external_subdirs = []
+
+ extra_config_desc = []
+
+ def __init__(self, name, base_dir, svn_url, root_cmd, pbuilderrc, deb_email,
+ deb_fullname, packager_class="treepkg.packager"):
+ self.name = name
+ self.base_dir = base_dir
+ self.svn_url = svn_url
+ self.root_cmd = root_cmd
+ self.pbuilderrc = pbuilderrc
+ self.deb_email = deb_email
+ self.deb_fullname = deb_fullname
+ self.pkg_dir_template = "%(revision)d-%(increment)d"
+ self.pkg_dir_regex \
+ = re.compile(r"(?P<revision>[0-9]+)-(?P<increment>[0-9]+)$")
+
+ checkout_dir = _filenameproperty("checkout")
+ debian_dir = _filenameproperty("debian")
+ pkg_dir = _filenameproperty("pkg")
+
+ def pkg_dir_for_revision(self, revision, increment):
+ return os.path.join(self.pkg_dir,
+ self.pkg_dir_template % locals())
+
+ def last_changed_revision(self):
+ revisions = []
+ for directory in [self.checkout_dir] + self.svn_external_subdirs:
+ directory = os.path.join(self.checkout_dir, directory)
+ revisions.append(subversion.last_changed_revision(directory))
+ return max(revisions)
+
+ def get_revision_numbers(self):
+ """Returns a list of the numbers of the packaged revisions"""
+ revisions = []
+ if os.path.exists(self.pkg_dir):
+ for filename in os.listdir(self.pkg_dir):
+ match = self.pkg_dir_regex.match(filename)
+ if match:
+ revisions.append(int(match.group("revision")))
+ return revisions
+
+ def last_packaged_revision(self):
+ """Returns the revision number of the highest packaged revision.
+
+ If the revision cannot be determined because no already packaged
+ revisions can be found, the function returns -1.
+ """
+ return max([-1] + self.get_revision_numbers())
+
+ def debian_source(self):
+ return util.extract_value_for_key(open(os.path.join(self.debian_dir,
+ "control")),
+ "Source:")
+
+ def update_checkout(self):
+ """Updates the working copy of self.svn_url in self.checkout_dir.
+
+ If self.checkout_dir doesn't exist yet, self.svn_url is checked
+ out into that directory.
+ """
+ localdir = self.checkout_dir
+ if os.path.exists(localdir):
+ logging.info("Updating the working copy in %r", localdir)
+ subversion.update(localdir)
+ else:
+ logging.info("The working copy in %r doesn't exist yet."
+ " Checking out from %r", localdir,
+ self.svn_url)
+ subversion.checkout(self.svn_url, localdir)
+
+ def export_sources(self, to_dir):
+ logging.info("Exporting sources for tarball to %r", to_dir)
+ subversion.export(self.checkout_dir, to_dir)
+ # some versions of svn (notably version 1.4.2 shipped with etch)
+ # do export externals such as the admin subdirectory. We may
+ # have to do that in an extra step.
+ for subdir in self.svn_external_subdirs:
+ absdir = os.path.join(to_dir, subdir)
+ if not os.path.isdir(absdir):
+ subversion.export(os.path.join(self.checkout_dir, subdir),
+ absdir)
+
+ def copy_debian_directory(self, to_dir):
+ logging.info("Copying debian directory to %r", to_dir)
+ shutil.copytree(self.debian_dir, to_dir)
+
+ def debian_environment(self):
+ """Returns the environment variables for the debian commands"""
+ env = os.environ.copy()
+ env["DEBFULLNAME"] = self.deb_fullname
+ env["DEBEMAIL"] = self.deb_email
+ return env
+
+ def package_if_updated(self):
+ """Checks if the checkout changed and returns a new packager if so"""
+ self.update_checkout()
+ current_revision = self.last_changed_revision()
+ logging.info("New revision is %d", current_revision)
+ previous_revision = self.last_packaged_revision()
+ logging.info("Previously packaged revision was %d", previous_revision)
+ if current_revision > previous_revision:
+ logging.info("New revision has not been packaged yet")
+ return self.revision_packager_cls(self, current_revision)
+ else:
+ logging.info("New revision already packaged.")
+
+ def get_revisions(self):
+ """Returns RevisionPackager objects for each packaged revision"""
+ return [self.revision_packager_cls(self, revision)
+ for revision in self.get_revision_numbers()]
+
+
+def create_package_track(packager_class, **kw):
+ module = util.import_dotted_name(packager_class)
+ return module.PackageTrack(**kw)
+
+
+class PackagerGroup(object):
+
+ def __init__(self, package_tracks, check_interval):
+ self.package_tracks = package_tracks
+ self.check_interval = check_interval
+
+ def run(self):
+ """Runs the packager group indefinitely"""
+ logging.info("Starting in periodic check mode."
+ " Will check every %d seconds", self.check_interval)
+ last_check = -1
+ while 1:
+ now = time.time()
+ if now > last_check + self.check_interval:
+ self.check_package_tracks()
+ last_check = now
+ next_check = now + self.check_interval
+ to_sleep = next_check - time.time()
+ if to_sleep > 0:
+ logging.info("Next check at %s",
+ time.strftime("%Y-%m-%d %H:%M:%S",
+ time.localtime(next_check)))
+ time.sleep(to_sleep)
+ else:
+ logging.info("Next check now")
+
+ def check_package_tracks(self):
+ logging.info("Checking package tracks")
+ for track in self.package_tracks:
+ try:
+ packager = track.package_if_updated()
+ if packager:
+ packager.package()
+ except:
+ logging.exception("An error occurred while"
+ " checking packager track %r", track.name)
+ logging.info("Checked all package tracks")
+
+ def get_package_tracks(self):
+ return self.package_tracks
Property changes on: trunk/treepkg/packager.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/readconfig.py
===================================================================
--- trunk/treepkg/readconfig.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/readconfig.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,78 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Reads the configuration file"""
+
+import sys
+import shlex
+from ConfigParser import SafeConfigParser, NoOptionError
+
+import util
+
+defaults = dict(root_cmd="sudo")
+
+packager_desc = [
+ "name", "base_dir", "svn_url", "packager_class",
+ ("root_cmd", shlex.split), "pbuilderrc",
+ "deb_email", "deb_fullname",
+ ]
+
+treepkg_desc = [
+ ("check_interval", int),
+ ]
+
+
+def read_config_section(parser, section, item_desc, defaults=None):
+ if defaults is None:
+ defaults = dict()
+ options = dict()
+ for item in item_desc:
+ if isinstance(item, tuple):
+ key, converter = item
+ else:
+ key = item
+ converter = str
+ try:
+ value = parser.get(section, key, vars=defaults)
+ options[key] = converter(value)
+ except NoOptionError:
+ print >>sys.stderr, "Missing option %r in section %r" \
+ % (key, section)
+ sys.exit(1)
+ return options
+
+
+def read_config(filename):
+ """Reads the tree packager configuration from the file given by filename.
+
+ The function returns a tuple with a ('treepkg') and a list of dicts
+ ('packagers'). The treepkg dict contains the main configuration of
+ the tree packager. The packagers list contains one dict with the
+ configuratiin for each packager.
+ """
+ parser = SafeConfigParser(defaults)
+ parser.read([filename])
+
+ # extract packager configurations
+ packagers = []
+ for section in parser.sections():
+ if section.startswith("pkg_"):
+ packager_class = parser.get(section, "packager_class")
+ module = util.import_dotted_name(packager_class)
+ desc = packager_desc + module.PackageTrack.extra_config_desc
+ packagers.append(read_config_section(parser, section, desc,
+ dict(name=section[4:])))
+
+ # main config
+ treepkg = read_config_section(parser, "treepkg", treepkg_desc)
+
+ return treepkg, packagers
+
+
+if __name__ == "__main__":
+ import pprint
+ print pprint.pprint(read_config(sys.argv[1]))
Property changes on: trunk/treepkg/readconfig.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/report.py
===================================================================
--- trunk/treepkg/report.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/report.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,89 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Support for status reports"""
+
+import datetime
+
+from packager import create_package_track, PackagerGroup
+from readconfig import read_config
+
+class struct(object):
+
+ """Class to create simple struct like objects
+
+ All keyword arguments passed to the constructor are available as
+ instance variables.
+ """
+
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def __repr__(self):
+ fields = ["%s=%r" % item for item in self.__dict__.items()]
+ return "struct(" + ", ".join(fields) + ")"
+
+
+def get_packager_group(config_file):
+ treepkg_opts, packager_opts = read_config(config_file)
+ return PackagerGroup([create_package_track(**opts)
+ for opts in packager_opts],
+ **treepkg_opts)
+
+def status_class(status):
+ """Returns the CSS class for a status"""
+ if status.finished:
+ return "finished"
+ elif status.error:
+ return "error"
+ else:
+ return "inprogress"
+
+def format_time(timestamp):
+ """Formats a datetime object for a status report
+
+ if the argument is true, the return value is simply str applied to
+ the argument, which for datetime objects is a string with the format
+ 'YYYY-MM-DD hh:mm:ss'. If the argument is false, the return value
+ is '<unknown>'.
+ """
+ if timestamp:
+ return timestamp.strftime("%Y-%m-%d %H:%M:%SZ")
+ else:
+ return "<unknown>"
+
+
+def prepare_status(status):
+ return struct(desc=status.status.description,
+ start=format_time(status.start),
+ stop=format_time(status.stop),
+ cls=status_class(status.status))
+
+def prepare_report(group):
+ revisions = {}
+ columns = []
+ tracks = group.get_package_tracks()
+ num_columns = len(tracks)
+ for column, track in enumerate(tracks):
+ columns.append((column, track.name))
+ for revision in track.get_revisions():
+ row = revisions.setdefault(revision.revision, [None] * num_columns)
+ row[column] = struct(revno=revision.revision,
+ revision=revision,
+ column=column,
+ name=track.name,
+ status=prepare_status(revision.status))
+
+ # convert the revisions dict into a sorted list of (revno, row)
+ # pairs
+ revisions = revisions.items()
+ revisions.sort()
+ revisions.reverse()
+
+ return struct(columns=columns,
+ revisions=revisions,
+ date=format_time(datetime.datetime.utcnow()))
Property changes on: trunk/treepkg/report.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/run.py
===================================================================
--- trunk/treepkg/run.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/run.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,50 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Helper functions to run subprocesses in various ways"""
+
+import os
+import subprocess
+
+
+class SubprocessError(EnvironmentError):
+
+ def __init__(self, command, returncode):
+ EnvironmentError.__init__(self,
+ "Command %r finished with return code %d"
+ % (command, returncode))
+ self.returncode = returncode
+
+
+def call(command, suppress_output=False, **kw):
+ """Run command as a subprocess and wait until it is finished.
+
+ The command should be given as a list of strings to avoid problems
+ with shell quoting. If the command exits with a return code other
+ than 0, a SubprocessError is raised.
+ """
+ if suppress_output:
+ kw["stdout"] = open(os.devnull, "w")
+ kw["stderr"] = open(os.devnull, "w")
+ ret = subprocess.call(command, **kw)
+ if ret != 0:
+ raise SubprocessError(command, ret)
+
+
+def capture_output(command, **kw):
+ """Return the stdout and stderr of the command as a string
+
+ The command should be given as a list of strings to avoid problems
+ with shell quoting. If the command exits with a return code other
+ than 0, a SubprocessError is raised.
+ """
+ proc = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, **kw)
+ output = proc.communicate()[0]
+ if proc.returncode != 0:
+ raise SubprocessError(command, proc.returncode)
+ return output
Property changes on: trunk/treepkg/run.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/status.py
===================================================================
--- trunk/treepkg/status.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/status.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,189 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import datetime
+import time
+
+import util
+
+
+# special object to indicate no default value
+nodefault = object()
+
+
+class FieldDesc(object):
+
+ def __init__(self, default=nodefault):
+ self.default = default
+
+ def has_default(self):
+ return self.default is not nodefault
+
+ def set_default(self, value):
+ self.default = value
+
+ def serialize(self, value):
+ raise NotImplementedError
+
+ def deserialize(self, string):
+ raise NotImplementedError
+
+
+class StringFieldDesc(FieldDesc):
+
+ def serialize(self, value):
+ return str(value)
+
+ def deserialize(self, value):
+ return value.strip()
+
+
+class DateFieldDesc(FieldDesc):
+
+ date_format = "%Y-%m-%d %H:%M:%S"
+
+ def serialize(self, value):
+ return value.strftime(self.date_format)
+
+ def deserialize(self, string):
+ return datetime.datetime(*time.strptime(string.strip(),
+ self.date_format)[:6])
+
+
+class EnumValue(object):
+
+ def __init__(self, name, description, finished=False, error=False):
+ self.name = name
+ self.description = description
+ self.finished = finished
+ self.error = error
+
+
+class EnumFieldDesc(FieldDesc):
+
+ def __init__(self, *args, **kw):
+ super(EnumFieldDesc, self).__init__(*args, **kw)
+ self.values = {}
+
+ def add(self, name, description, default=False, **kw):
+ enum = EnumValue(name, description, **kw)
+ self.values[enum.name] = enum
+ if default:
+ self.set_default(enum)
+
+ def __iter__(self):
+ return self.values.itervalues()
+
+ def serialize(self, value):
+ assert value.name is not None
+ return value.name
+
+ def deserialize(self, string):
+ return self.values[string.strip()]
+
+
+def make_setter(fieldname, enum):
+ def setter(self):
+ setattr(self, fieldname, enum)
+ setter.__name__ = enum.name
+ return setter
+
+
+class StatusMetaClass(type):
+
+ def __new__(cls, name, bases, clsdict):
+ # Generate the _fields class variable from the field descriptors
+ # in clsdict and remove the descriptors themselves. Also, add
+ # one setter method for each enum.
+ fields = dict()
+ for key, value in clsdict.items():
+ if isinstance(value, FieldDesc):
+ fields[key] = value
+ del clsdict[key]
+ if isinstance(value, EnumFieldDesc):
+ for enum in value:
+ clsdict[enum.name] = make_setter(key, enum)
+ clsdict["_fields"] = fields
+ return type.__new__(cls, name, bases, clsdict)
+
+
+class Status(object):
+
+ __metaclass__ = StatusMetaClass
+
+ # Overwrite in derived classes with a different magic string
+ _magic = "Status 0.0\n"
+
+ # Derived classes may extend a copy of this set with more instance
+ # variables.
+ _attrs = set(["_filename", "_values"])
+
+ def __init__(self, filename):
+ assert os.path.isabs(filename)
+ self._filename = filename
+ self.read()
+
+ def _init_values(self):
+ self._values = {}
+
+ def read(self):
+ self._init_values()
+ if not os.path.exists(self._filename):
+ return
+ f = open(self._filename)
+ try:
+ magic = f.next()
+ if magic != self._magic:
+ raise ValueError("File %r has wrong magic" % self._filename)
+ for line in f:
+ field, value = line.split(":", 1)
+ self._values[field] = self._fields[field].deserialize(value)
+ finally:
+ f.close()
+
+ def write(self):
+ lines = [self._magic]
+ for field, desc in self._fields.items():
+ if field in self._values:
+ lines.append("%s: %s\n"
+ % (field, desc.serialize(self._values[field])))
+ util.writefile(self._filename, "".join(lines), 0644)
+
+ def __getattr__(self, attr):
+ desc = self._fields.get(attr)
+ if desc is not None:
+ if attr in self._values:
+ return self._values[attr]
+ elif desc.has_default():
+ return desc.default
+ raise AttributeError(attr)
+
+ def __setattr__(self, attr, value):
+ if attr in self._fields:
+ self._values[attr] = value
+ self.write()
+ elif attr in self._attrs:
+ self.__dict__[attr] = value
+ else:
+ raise AttributeError(attr)
+
+
+class RevisionStatus(Status):
+
+ _magic = "TreePackagerStatus 0.0\n"
+
+ status = EnumFieldDesc()
+ status.add("creating_source_package", "creating source package")
+ status.add("source_package_created", "source package created")
+ status.add("creating_binary_package", "building binary packages")
+ status.add("binary_package_created", "build successful", finished=True)
+ status.add("error", "error", error=True)
+ status.add("unknown", "unknown", default=True)
+
+ start = DateFieldDesc(default=None)
+ stop = DateFieldDesc(default=None)
Property changes on: trunk/treepkg/status.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/subversion.py
===================================================================
--- trunk/treepkg/subversion.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/subversion.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,40 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Collection of subversion utility functions"""
+
+import os
+
+import run
+from cmdexpand import cmdexpand
+from util import extract_value_for_key
+
+
+def checkout(url, localdir):
+ """Runs svn to checkout the repository at url into the localdir"""
+ run.call(cmdexpand("svn checkout -q $url $localdir", **locals()))
+
+def update(localdir):
+ """Runs svn update on the localdir"""
+ run.call(cmdexpand("svn update -q $localdir", **locals()))
+
+def export(src, dest):
+ """Runs svn export src dest"""
+ run.call(cmdexpand("svn export -q $src $dest", **locals()))
+
+def last_changed_revision(svn_working_copy):
+ """return the last changed revision of an SVN working copy as an int"""
+ # Make sure we run svn under the C locale to avoid localized
+ # messages
+ env = os.environ.copy()
+ env["LANG"] = "C"
+
+ output = run.capture_output(cmdexpand("svn info $svn_working_copy",
+ **locals()),
+ env=env)
+ return int(extract_value_for_key(output.splitlines(),
+ "Last Changed Rev:"))
Property changes on: trunk/treepkg/subversion.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/util.py
===================================================================
--- trunk/treepkg/util.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/util.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,133 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Collection of functions that didn't fit elsewhere"""
+
+import os
+import tempfile
+import shutil
+
+import run
+
+
+def import_dotted_name(dotted_name):
+ module = __import__(dotted_name)
+ for name in dotted_name.split(".")[1:]:
+ module = getattr(module, name)
+ return module
+
+def extract_value_for_key(lines, key):
+ """Parses a sequence of strings for a key and returns the associated value
+
+ The function determines the first string in lines that starts with
+ key. It returns the rest of the lines stripped of leading and
+ trailing whitespace.
+ """
+ for line in lines:
+ if line.startswith(key):
+ return line[len(key):].strip()
+
+def extract_lsm_version(lsm_file):
+ return extract_value_for_key(open(lsm_file), "Version:")
+
+def debian_changelog_version(changelog):
+ """Returns the newest version in a debian changelog."""
+ output = run.capture_output(["dpkg-parsechangelog", "-l" + changelog])
+ return extract_value_for_key(output.splitlines(), "Version:")
+
+
+def ensure_directory(directory):
+ """Creates directory and all its parents.
+
+ Unlike os.makedirs, this function doesn't throw an exception
+ """
+ if not os.path.isdir(directory):
+ os.makedirs(directory)
+
+
+def copytree(src, dst, symlinks=False):
+ """Recursively copy a directory tree using copy2().
+
+ This version is basically the same as the one in the shutil module
+ in the python standard library, however, it's OK if the destination
+ directory already exists.
+
+ If the optional symlinks flag is true, symbolic links in the
+ source tree result in symbolic links in the destination tree; if
+ it is false, the contents of the files pointed to by symbolic
+ links are copied.
+ """
+ names = os.listdir(src)
+ ensure_directory(dst)
+ errors = []
+ for name in names:
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if symlinks and os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ os.symlink(linkto, dstname)
+ elif os.path.isdir(srcname):
+ copytree(srcname, dstname, symlinks)
+ else:
+ shutil.copy2(srcname, dstname)
+ # XXX What about devices, sockets etc.?
+ except (IOError, os.error), why:
+ errors.append((srcname, dstname, why))
+ if errors:
+ raise Error, errors
+
+
+
+def writefile(filename, contents, permissions=None):
+ """Write contents to filename in an atomic way.
+
+ The contents are first written to a temporary file in the same
+ directory as filename. Once the contents are written, the temporary
+ file is closed and renamed to filename.
+
+ The optional parameter permissions, if given, are the permissions
+ for the new file. By default, or if the parameter is None, the
+ default permissions set by the tempfile.mkstemp are used which means
+ that the file is only readable for the user that created the file.
+ The permissions value is used as the second parameter to os.chmod.
+ """
+ dirname, basename = os.path.split(filename)
+ fileno, tempname = tempfile.mkstemp("", basename, dirname)
+ try:
+ os.write(fileno, contents)
+ if not contents.endswith("\n"):
+ os.write(fileno, "\n")
+ os.close(fileno)
+ if permissions is not None:
+ os.chmod(tempname, permissions)
+ os.rename(tempname, filename)
+ finally:
+ if os.path.exists(tempname):
+ os.remove(tempname)
+
+
+class StatusFile(object):
+
+ def __init__(self, filename):
+ assert os.path.isabs(filename)
+ self.filename = filename
+
+ def read(self):
+ f = open(self.filename)
+ try:
+ self.status = f.read()
+ finally:
+ f.close()
+
+ def set(self, status, extra=""):
+ ensure_directory(os.path.dirname(self.filename))
+ writefile(self.filename, status + "\n" + extra)
+
+ def get(self):
+ self.read()
+ return self.status.strip()
Property changes on: trunk/treepkg/util.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
Added: trunk/treepkg/web-status.html
===================================================================
--- trunk/treepkg/web-status.html 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/web-status.html 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,55 @@
+<html xmlns:py="http://genshi.edgewall.org/">
+ <head>
+ <title>Tree Packager Status</title>
+ <style type="text/css">
+ .statustable { background:#F4F4F4; }
+ .statustablehead { background:#E0E0E0; }
+ .statusheading { font-weight:bold; }
+ .finished { background:#C0FFC0; }
+ .inprogress { background:#FFFFC0; }
+ .error { background:#FFC0C0; }
+ td { padding:5px; background:#FFFFFF}
+ </style>
+ </head>
+ <body>
+ <h1>Tree Packager Status</h1>
+
+ <table class="statustable">
+ <tr>
+ <th class="statustablehead">Revision</th>
+ <py:for each="col in report.columns">
+ <th class="statustablehead">${col[1]}</th>
+ </py:for>
+ </tr>
+
+ <py:for each="row in report.revisions">
+ <tr>
+ <td>${row[0]}</td>
+ <py:for each="col in row[1]">
+ <py:choose>
+ <py:when test="col">
+ <td class="${col.status.cls}">
+ <span class="statusheading">${col.status.desc}</span><br/>
+ Start: ${col.status.start}<br/>
+ Stop: ${col.status.stop}<br/>
+ <py:if test="col.revision.has_build_log">
+ <a href="${col.name}/${col.revno}/build_log.txt">build log</a>
+ </py:if>
+ </td>
+ </py:when>
+ <py:otherwise>
+ <td></td>
+ </py:otherwise>
+ </py:choose>
+ </py:for>
+
+ </tr>
+ </py:for>
+ </table>
+
+ <hr/>
+ report generated at ${report.date},
+ powered by <a href="http://treepkg.wald.intevation.org/">Tree Packager</a>
+
+ </body>
+</html>
Added: trunk/treepkg/web.py
===================================================================
--- trunk/treepkg/web.py 2007-05-03 10:18:12 UTC (rev 1)
+++ trunk/treepkg/web.py 2007-05-03 18:31:32 UTC (rev 2)
@@ -0,0 +1,97 @@
+# Copyright (C) 2007 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+import os
+import shutil
+
+from genshi.template import TemplateLoader
+
+import cherrypy
+from cherrypy import expose
+from cherrypy.lib import cptools
+
+import report
+
+
+class Status(object):
+
+ """Implements the tree packager status pages"""
+
+ def __init__(self, treepkg_config):
+ self.treepkg_config = treepkg_config
+ self.loader = TemplateLoader([os.path.dirname(__file__)])
+
+ @expose
+ def index(self):
+ group = report.get_packager_group(self.treepkg_config)
+ tmpl = self.loader.load('web-status.html')
+ stream = tmpl.generate(report=report.prepare_report(group))
+ return stream.render('html')
+
+ def build_log_filename(self, package_track_name, revno):
+ """Returns the name of the build log file of a revision if it exists"""
+ group = report.get_packager_group(self.treepkg_config)
+ for track in group.get_package_tracks():
+ if track.name == package_track_name:
+ for revision in track.get_revisions():
+ if str(revision.revision) == revno:
+ if revision.has_build_log():
+ return revision.build_log
+
+ @expose
+ def default(self, *rest):
+ """Handles requests for .../pkg/revno/build_log.txt"""
+ filename = None
+ if len(rest) == 3 and rest[2] == "build_log.txt":
+ filename = self.build_log_filename(*rest[:2])
+ if filename is not None:
+ return cptools.serveFile(filename, contentType="text/plain")
+ else:
+ raise cherrypy.HTTPError(status="404")
+
+ def create_static_site(self, destdir):
+ """Creates a static web-page under destdir"""
+ # make sure we have an empty destdir
+ shutil.rmtree(destdir, True)
+ os.mkdir(destdir)
+
+ # create the index file
+ f = open(os.path.join(destdir, "index.html"), "wt")
+ f.write(self.index())
+ f.close()
+
+ # symlink the build-logs
+ group = report.get_packager_group(self.treepkg_config)
+ for track in group.get_package_tracks():
+ trackdir = os.path.join(destdir, track.name)
+ for revision in track.get_revisions():
+ revdir = os.path.join(trackdir, str(revision.revision))
+ if revision.has_build_log():
+ if not os.path.isdir(trackdir):
+ os.mkdir(trackdir)
+ os.mkdir(revdir)
+ os.symlink(revision.build_log,
+ os.path.join(revdir, "build_log.txt"))
+
+
+
+
+class TreePKG(object):
+
+ """Root object for the tree packager web interface"""
+
+ @expose
+ def index(self):
+ raise cherrypy.HTTPRedirect('/status')
+
+
+def runserver(treepkg_config, cherrypy_config):
+ cherrypy.root = TreePKG()
+ cherrypy.root.status = Status(treepkg_config=treepkg_config)
+
+ cherrypy.config.update(file=cherrypy_config)
+ cherrypy.server.start()
Property changes on: trunk/treepkg/web.py
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ native
More information about the Treepkg-commits
mailing list