diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 15:47:16 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 15:47:16 +0100 |
commit | 25335618bf8755ce6b116ee14f47f5a1f2c821e9 (patch) | |
tree | d889d7ab3f9f985d0c54c534cb8052bd2e6d7163 /tools | |
download | bzr-tarball-25335618bf8755ce6b116ee14f47f5a1f2c821e9.tar.gz |
Tarball conversion
Diffstat (limited to 'tools')
38 files changed, 3494 insertions, 0 deletions
diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/__init__.py diff --git a/tools/bzr_epydoc b/tools/bzr_epydoc new file mode 100644 index 0000000..434ec4b --- /dev/null +++ b/tools/bzr_epydoc @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# +# Call the command line interface for Epydoc. +# + +"""Wrapper around Epydoc that just makes it understand bzr's lazy imports.""" + +# This will enable the lazy_import compatibility code +import bzr_epydoc_uid + +from epydoc.cli import cli +cli() + + diff --git a/tools/bzr_epydoc_uid.py b/tools/bzr_epydoc_uid.py new file mode 100644 index 0000000..a9861d1 --- /dev/null +++ b/tools/bzr_epydoc_uid.py @@ -0,0 +1,43 @@ +# Copyright (C) 2007 Canonical Ltd +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Monkey patch to make epydoc work with bzrlib's lazy imports.""" + +import epydoc.uid + +import bzrlib.lazy_import + + +_ObjectUID = epydoc.uid.ObjectUID +_ScopeReplacer = bzrlib.lazy_import.ScopeReplacer + + +class ObjectUID(_ObjectUID): + + def __init__(self, obj): + if isinstance(obj, _ScopeReplacer): + # The isinstance will trigger a replacement if it is a real + # _BzrScopeReplacer, but the local object won't know about it, so + # replace it locally. + obj = object.__getattribute__(obj, '_real_obj') + _ObjectUID.__init__(self, obj) + + +epydoc.uid.ObjectUID = ObjectUID + + +_ScopeReplacer._should_proxy = True + diff --git a/tools/capture_tree.py b/tools/capture_tree.py new file mode 100644 index 0000000..4528df9 --- /dev/null +++ b/tools/capture_tree.py @@ -0,0 +1,40 @@ +#! /usr/bin/env python + +# Copyright (C) 2005 Canonical Ltd + +"""Print to stdout a description of the current directory, +formatted as a Python data structure. + +This can be useful in tests that need to recreate directory +contents.""" + +import sys +import os + +from bzrlib.trace import enable_default_logging +enable_default_logging() +from bzrlib.selftest.treeshape import capture_tree_contents + +def main(argv): + # a lame reimplementation of pformat that splits multi-line + # strings into concatenated string literals. + print '[' + for tt in capture_tree_contents('.'): + assert isinstance(tt, tuple) + print ' (', repr(tt[0]) + ',', + if len(tt) == 1: + print '),' + else: + assert len(tt) == 2 + val = tt[1] + print + if val == '': + print " ''" + else: + for valline in val.splitlines(True): + print ' ', repr(valline) + print ' ),' + print ']' + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/check-newsbugs.py b/tools/check-newsbugs.py new file mode 100755 index 0000000..65fec9f --- /dev/null +++ b/tools/check-newsbugs.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# Simple script that will check which bugs mentioned in NEWS +# are not yet marked Fix Released in Launchpad + +import getopt, re, sys +try: + from launchpadlib.launchpad import Launchpad + from lazr.restfulclient import errors +except ImportError: + print "Please install launchpadlib from lp:launchpadlib" + sys.exit(1) +try: + import hydrazine +except ImportError: + print "Please install hydrazine from lp:hydrazine" + sys.exit(1) + + +options, args = getopt.gnu_getopt(sys.argv, "lw", ["launchpad", 'webbrowser']) +options = dict(options) + +if len(args) == 1: + print ("Usage: check-newsbugs [--launchpad][--webbrowser] " + "doc/en/release-notes/bzr-x.y.txt") + print "Options:" + print "--launchpad Print out Launchpad mail commands for closing bugs " + print " that are already fixed." + print "--webbrowser Open launchpad bug pages for bugs that are already " + print " fixed." + sys.exit(1) + + +def report_notmarked(bug, task, section): + print + print "Bug %d was mentioned in NEWS but is not marked fix released:" % (bug.id, ) + print "Launchpad title: %s" % bug.title + print "NEWS summary: " + print section + if "--launchpad" in options or "-l" in options: + print " bug %d" % bug.id + print " affects %s" % task.bug_target_name + print " status fixreleased" + if "--webbrowser" in options or "-w" in options: + import webbrowser + webbrowser.open('http://pad.lv/%s>' % (bug.id,)) + + +def read_news_bugnos(path): + """Read the bug numbers closed by a particular NEWS file + + :param path: Path to the NEWS file + :return: list of bug numbers that were closed. + """ + # Pattern to find bug numbers + bug_pattern = re.compile("\#([0-9]+)") + ret = set() + f = open(path, 'r') + try: + section = "" + for l in f.readlines(): + if l.strip() == "": + try: + parenthesed = section.rsplit("(", 1)[1] + except IndexError: + parenthesed = "" + # Empty line, next section begins + for bugno in [int(m) for m in bug_pattern.findall(parenthesed)]: + ret.add((bugno, section)) + section = "" + else: + section += l + return ret + finally: + f.close() + + +def print_bug_url(bugno): + print '<URL:http://pad.lv/%s>' % (bugno,) + +launchpad = hydrazine.create_session() +bugnos = read_news_bugnos(args[1]) +for bugno, section in bugnos: + try: + bug = launchpad.bugs[bugno] + except errors.HTTPError, e: + if e.response.status == 401: + print_bug_url(bugno) + # Private, we can't access the bug content + print '%s is private and cannot be accessed' % (bugno,) + continue + raise + + found_bzr = False + fix_released = False + for task in bug.bug_tasks: + parts = task.bug_target_name.split('/') + if len(parts) == 1: + project = parts[0] + distribution = None + else: + project = parts[0] + distribution = parts[1] + if project == "bzr": + found_bzr = True + if not fix_released and task.status == "Fix Released": + # We could check that the NEWS section and task_status are in + # sync, but that would be overkill. (case at hand: bug #416732) + fix_released = True + + if not found_bzr: + print_bug_url(bugno) + print "Bug %d was mentioned in NEWS but is not marked as affecting bzr" % bugno + elif not fix_released: + print_bug_url(bugno) + report_notmarked(bug, task, section) diff --git a/tools/fixed-in.py b/tools/fixed-in.py new file mode 100755 index 0000000..98f2529 --- /dev/null +++ b/tools/fixed-in.py @@ -0,0 +1,172 @@ +#!/usr/bin/python + +# Simple script that will output the release where a given bug was fixed +# searching the NEWS file + +import optparse +import re +import sys + + +class NewsParser(object): + + paren_exp_re = re.compile('\(([^)]+)\)') + release_re = re.compile("bzr[ -]") + release_prefix_length = len('bzr ') + bugs_re = re.compile('#([0-9]+)') + + def __init__(self, news): + self.news = news + # Temporary attributes used by the parser + self.release = None + self.date = None + self.may_be_release = None + self.release_markup = None + self.entry = '' + self.line = None + self.lrs = None + + def set_line(self, line): + self.line = line + self.lrs = line.rstrip() + + def try_release(self): + if self.release_re.match(self.lrs) is not None: + # May be a new release + self.may_be_release = self.lrs + # We know the markup will have the same length as the release + self.release_markup = '#' * len(self.may_be_release) + return True + return False + + def confirm_release(self): + if self.may_be_release is not None and self.lrs == self.release_markup: + # The release is followed by the right markup + self.release = self.may_be_release[self.release_prefix_length:] + # Wait for the associated date + self.date = None + return True + return False + + def try_date(self): + if self.release is None: + return False + date_re = re.compile(':%s: (NOT RELEASED YET|\d{4}-\d{2}-\d{2})' + % (self.release,)) + match = date_re.match(self.lrs) + if match is not None: + self.date = match.group(1) + return True + # The old fashion way + released_re = re.compile(':Released:\s+(\d{4}-\d{2}-\d{2})') + match = released_re.match(self.lrs) + if match is not None: + self.date = match.group(1) + return True + return False + + def add_line_to_entry(self): + if self.lrs == '': + return False + self.entry += self.line + return True + + def extract_bugs_from_entry(self): + """Possibly extract bugs from a NEWS entry and yield them. + + Not all entries will contain bugs and some entries are even garbage and + we don't try to parse them (yet). The trigger is a '#' and what looks + like a bug number inside parens to start with. From that we extract + authors (when present) and multiple bugs if needed. + """ + # FIXME: Malone entries are different + # Join all entry lines to simplify multiple line matching + flat_entry = ' '.join(self.entry.splitlines()) + # Fixed bugs are always inside parens + for par in self.paren_exp_re.findall(flat_entry): + sharp = par.find('#') + if sharp is not None: + # We have at least one bug inside parens. + bugs = list(self.bugs_re.finditer(par)) + if bugs: + # See where the first bug is mentioned + start = bugs[0].start() + end = bugs[-1].end() + if start == 0: + # (bugs/authors) + authors = par[end:] + else: + # (authors/bugs) + authors = par[:start] + for bug_match in bugs: + bug_number = bug_match.group(0) + yield (bug_number, authors, + self.release, self.date, self.entry) + # We've consumed the entry + self.entry = '' + + def parse_bugs(self): + for line in self.news: + self.set_line(line) + if self.try_release(): + continue # line may a be release + try: + if self.confirm_release(): + continue # previous line was indeed a release + finally: + self.may_be_release = None + if self.try_date(): + continue # The release date has been seen + if self.add_line_to_entry(): + continue # accumulate in self.enrty + for b in self.extract_bugs_from_entry(): + yield b # all bugs in the news entry + +def main(): + opt_parser = optparse.OptionParser( + usage="""Usage: %prog [options] BUG_NUMBER + """) + opt_parser.add_option( + '-f', '--file', type='str', dest='news_file', + help='NEWS file (defaults to ./NEWS)') + opt_parser.add_option( + '-m', '--message', type='str', dest='msg_re', + help='A regexp to search for in the news entry ' + '(BUG_NUMBER should not be specified in this case)') + opt_parser.set_defaults(news_file='./NEWS') + (opts, args) = opt_parser.parse_args(sys.argv[1:]) + if opts.msg_re is not None: + if len(args) != 0: + opt_parser.error('BUG_NUMBER and -m are mutually exclusive') + bug = None + msg_re = re.compile(opts.msg_re) + elif len(args) != 1: + opt_parser.error('Expected a single bug number, got %r' % args) + else: + bug = args[0] + + news = open(opts.news_file) + parser = NewsParser(news) + try: + seen = 0 + for b in parser.parse_bugs(): + (number, authors, release, date, entry,) = b + # indent entry + entry = '\n'.join([' ' + l for l in entry.splitlines()]) + found = False + if bug is not None: + if number[1:] == bug: # Strip the leading '#' + found = True + elif msg_re.search(entry) is not None: + found = True + if found: + print 'Bug %s was fixed in bzr-%s/%s by %s:' % ( + number, release, date, authors) + print entry + seen += 1 + finally: + print '%s bugs seen' % (seen,) + news.close() + + +main() diff --git a/tools/generate_docs.py b/tools/generate_docs.py new file mode 100755 index 0000000..b27eeba --- /dev/null +++ b/tools/generate_docs.py @@ -0,0 +1,111 @@ +#!/usr/bin/python + +# Copyright 2005 Canonical Ltd. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""%(prog)s - generate information from built-in bzr help + +%(prog)s creates a file with information on bzr in one of +several different output formats: + + man man page + bash_completion bash completion script + ... + +Examples: + + python2.4 generated-docs.py man + python2.4 generated-docs.py bash_completion + +Run "%(prog)s --help" for the option reference. +""" +import os +import sys +from optparse import OptionParser + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +import bzrlib +from bzrlib import ( + commands, + # Don't remove the following import, it triggers a format registration that + # avoid http://pad.lv/956860 + branch, + doc_generate, + ) + + +def main(argv): + parser = OptionParser(usage="""%prog [options] OUTPUT_FORMAT + +Available OUTPUT_FORMAT: + + man man page + rstx man page in ReStructuredText format + bash_completion bash completion script""") + + parser.add_option("-s", "--show-filename", + action="store_true", dest="show_filename", default=False, + help="print default filename on stdout") + + parser.add_option("-o", "--output", dest="filename", metavar="FILE", + help="write output to FILE") + + parser.add_option("-b", "--bzr-name", + dest="bzr_name", default="bzr", metavar="EXEC_NAME", + help="name of bzr executable") + + parser.add_option("-e", "--examples", + action="callback", callback=print_extended_help, + help="Examples of ways to call generate_doc") + + + (options, args) = parser.parse_args(argv) + + if len(args) != 2: + parser.print_help() + sys.exit(1) + + with bzrlib.initialize(): + commands.install_bzr_command_hooks() + infogen_type = args[1] + infogen_mod = doc_generate.get_module(infogen_type) + if options.filename: + outfilename = options.filename + else: + outfilename = infogen_mod.get_filename(options) + if outfilename == "-": + outfile = sys.stdout + else: + outfile = open(outfilename,"w") + if options.show_filename and (outfilename != "-"): + sys.stdout.write(outfilename) + sys.stdout.write('\n') + infogen_mod.infogen(options, outfile) + + +def print_extended_help(option, opt, value, parser): + """ Program help examples + + Prints out the examples stored in the docstring. + + """ + sys.stdout.write(__doc__ % {"prog":sys.argv[0]}) + sys.stdout.write('\n') + sys.exit(0) + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/generate_release_notes.py b/tools/generate_release_notes.py new file mode 100644 index 0000000..934dd0b --- /dev/null +++ b/tools/generate_release_notes.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +# Copyright 2009-2010 Canonical Ltd. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Generate doc/en/release-notes/index.txt from the per-series NEWS files. + +NEWS files are kept in doc/en/release-notes/, one file per series, e.g. +doc/en/release-notes/bzr-2.3.txt +""" + +# XXX: add test_source test that latest doc/en/release-notes/bzr-*.txt has the +# NEWS file-id (so that merges of new work will tend to always land new NEWS +# entries in the latest series). + + +import os.path +import re +import sys +from optparse import OptionParser + + +preamble_plain = """\ +#################### +Bazaar Release Notes +#################### + + +.. contents:: List of Releases + :depth: 2 + +""" + +preamble_sphinx = """\ +#################### +Bazaar Release Notes +#################### + + +.. toctree:: + :maxdepth: 2 + +""" + + +def natural_sort_key(file_name): + """Split 'aaa-N.MMbbb' into ('aaa-', N, '.' MM, 'bbb') + + e.g. 1.10b1 will sort as greater than 1.2:: + + >>> natural_sort_key('bzr-1.10b1.txt') > natural_sort_key('bzr-1.2.txt') + True + """ + file_name = os.path.basename(file_name) + parts = re.findall(r'(?:[0-9]+|[^0-9]+)', file_name) + result = [] + for part in parts: + if re.match('^[0-9]+$', part) is not None: + part = int(part) + result.append(part) + return tuple(result) + + +def output_news_file_sphinx(out_file, news_file_name): + news_file_name = os.path.basename(news_file_name) + if not news_file_name.endswith('.txt'): + raise AssertionError( + 'NEWS file %s does not have .txt extension.' + % (news_file_name,)) + doc_name = news_file_name[:-4] + link_text = doc_name.replace('-', ' ') + out_file.write(' %s <%s>\n' % (link_text, doc_name)) + + +def output_news_file_plain(out_file, news_file_name): + f = open(news_file_name, 'rb') + try: + lines = f.readlines() + finally: + f.close() + title = os.path.basename(news_file_name)[len('bzr-'):-len('.txt')] + for line in lines: + if line == '####################\n': + line = '#' * len(title) + '\n' + elif line == 'Bazaar Release Notes\n': + line = title + '\n' + elif line == '.. toctree::\n': + continue + elif line == ' :maxdepth: 1\n': + continue + out_file.write(line) + out_file.write('\n\n') + + +def main(argv): + # Check usage + parser = OptionParser(usage="%prog OUTPUT_FILE NEWS_FILE [NEWS_FILE ...]") + (options, args) = parser.parse_args(argv) + if len(args) < 2: + parser.print_help() + sys.exit(1) + + # Open the files and do the work + out_file_name = args[0] + news_file_names = sorted(args[1:], key=natural_sort_key, reverse=True) + + if os.path.basename(out_file_name) == 'index.txt': + preamble = preamble_sphinx + output_news_file = output_news_file_sphinx + else: + preamble = preamble_plain + output_news_file = output_news_file_plain + + out_file = open(out_file_name, 'w') + try: + out_file.write(preamble) + for news_file_name in news_file_names: + output_news_file(out_file, news_file_name) + finally: + out_file.close() + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/package_docs.py b/tools/package_docs.py new file mode 100644 index 0000000..4742f59 --- /dev/null +++ b/tools/package_docs.py @@ -0,0 +1,109 @@ +#!/usr/bin/python + +# Copyright 2009 Canonical Ltd. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +import tarfile +from optparse import OptionParser +from shutil import copy2, copytree, rmtree + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + + +def package_docs(section, src_build, dest_html, dest_downloads): + """Package docs from a Sphinx _build directory into target directories. + + :param section: section in the website being built + :param src_build: the _build directory + :param dest_html: the directory where html should go + :param downloads: the directory where downloads should go + """ + # Copy across the HTML. Explicitly delete the html destination + # directory first though because copytree insists on it not existing. + src_html = os.path.join(src_build, 'html') + if os.path.exists(dest_html): + rmtree(dest_html) + copytree(src_html, dest_html) + + # Package the html as a downloadable archive + archive_root = "bzr-%s-html" % (section,) + archive_basename = "%s.tar.bz2" % (archive_root,) + archive_name = os.path.join(dest_downloads, archive_basename) + build_archive(src_html, archive_name, archive_root, 'bz2') + + # Copy across the PDF docs, if any, including the quick ref card + pdf_files = [] + quick_ref = os.path.join(src_html, + '_static/%s/bzr-%s-quick-reference.pdf' % (section, section)) + if os.path.exists(quick_ref): + pdf_files.append(quick_ref) + src_pdf = os.path.join(src_build, 'latex') + if os.path.exists(src_pdf): + for name in os.listdir(src_pdf): + if name.endswith('.pdf'): + pdf_files.append(os.path.join(src_pdf, name)) + if pdf_files: + dest_pdf = os.path.join(dest_downloads, 'pdf-%s' % (section,)) + if not os.path.exists(dest_pdf): + os.mkdir(dest_pdf) + for pdf in pdf_files: + copy2(pdf, dest_pdf) + + # TODO: copy across the CHM files, if any + + +def build_archive(src_dir, archive_name, archive_root, format): + print "creating %s ..." % (archive_name,) + tar = tarfile.open(archive_name, "w:%s" % (format,)) + for relpath in os.listdir(src_dir): + src_path = os.path.join(src_dir, relpath) + archive_path = os.path.join(archive_root, relpath) + tar.add(src_path, arcname=archive_path) + tar.close() + + +def main(argv): + # Check usage. The first argument is the parent directory of + # the Sphinx _build directory. It will typically be 'doc/xx'. + # The second argument is the website build directory. + parser = OptionParser(usage="%prog SOURCE-DIR WEBSITE-BUILD-DIR") + (options, args) = parser.parse_args(argv) + if len(args) != 2: + parser.print_help() + sys.exit(1) + + # Get the section - locale code or 'developers' + src_dir = args[0] + section = os.path.basename(src_dir) + src_build = os.path.join(src_dir, '_build') + + # Create the destination directories if they doesn't exist. + dest_dir = args[1] + dest_html = os.path.join(dest_dir, section) + dest_downloads = os.path.join(dest_dir, 'downloads') + for d in [dest_dir, dest_downloads]: + if not os.path.exists(d): + print "creating directory %s ..." % (d,) + os.mkdir(d) + + # Package and copy the files across + package_docs(section, src_build, dest_html, dest_downloads) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/package_mf.py b/tools/package_mf.py new file mode 100644 index 0000000..7e2c2ce --- /dev/null +++ b/tools/package_mf.py @@ -0,0 +1,89 @@ +# Copyright (C) 2008 Canonical Ltd +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Custom module finder for entire package""" + +import modulefinder +import os +import sys + + +class CustomModuleFinder(modulefinder.ModuleFinder): + """Custom module finder for processing python packages, + e.g. bzr plugins packages. + + :param path: list of directories to search for modules; + if not specified, python standard library only is used. + """ + + def __init__(self, path=None, debug=0, excludes=[], replace_paths=[]): + if path is None: + path = [os.path.dirname(os.__file__)] # only python std lib + modulefinder.ModuleFinder.__init__(self, path, debug, excludes, + replace_paths) + + def run_package(self, package_path): + """Recursively process each module in package with run_script method. + + :param package_path: path to package directory. + """ + stack = [package_path] + while stack: + curdir = stack.pop(0) + py = os.listdir(curdir) + for i in py: + full = os.path.join(curdir, i) + if os.path.isdir(full): + init = os.path.join(full, '__init__.py') + if os.path.isfile(init): + stack.append(full) + continue + if not i.endswith('.py'): + continue + if i == 'setup.py': # skip + continue + self.run_script(full) + + def get_result(self): + """Return 2-tuple: (list of packages, list of modules)""" + keys = self.modules.keys() + keys.sort() + mods = [] + packs = [] + for key in keys: + m = self.modules[key] + if not m.__file__: # skip builtins + continue + if m.__path__: + packs.append(key) + elif key != '__main__': + mods.append(key) + return (packs, mods) + + +if __name__ == '__main__': + package = sys.argv[1] + + mf = CustomModuleFinder() + mf.run_package(package) + + packs, mods = mf.get_result() + + print 'Packages:' + print packs + + print 'Modules:' + print mods diff --git a/tools/packaging/build-packages.sh b/tools/packaging/build-packages.sh new file mode 100755 index 0000000..be94ef0 --- /dev/null +++ b/tools/packaging/build-packages.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Helper to build packages for the various distributions + +if [ -z "$UBUNTU_RELEASES" ]; then + echo "Configure the distro platforms that you want to" + echo "build with a line like:" + echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"' + exit 1 +fi + +for DISTRO in $UBUNTU_RELEASES; do + (cd "$PACKAGE-$DISTRO" && bzr builddeb -S) +done diff --git a/tools/packaging/lp-upload-release b/tools/packaging/lp-upload-release new file mode 100755 index 0000000..fd61f7a --- /dev/null +++ b/tools/packaging/lp-upload-release @@ -0,0 +1,82 @@ +#! /bin/zsh -e + +# upload a release file to Launchpad +# +# usage: lp-upload-release [--staging] bzr-1.2.3.tgz + +setopt extended_glob + +if [ "$1" = "--staging" ] +then + shift + server="staging.launchpad.net" +else + server="launchpad.net" +fi + +if [ $# -ne 1 ] +then + echo "usage: lp-upload-release VERSION FILENAME" + exit 2 +fi + +if [ -z "$EMAIL" ] +then + echo "please set $EMAIL to an address registered with Launchpad" + exit 2 +fi + +upfile="$1" +if [ \! -f "$1" ] +then + echo "$upfile is not a file" + exit 2 +fi + +sigfile="$upfile.sig" +if [ \! -f "$sigfile" ] +then + echo "couldn't find gpg signature $sigfile" + exit 2 +fi + +# just in case +gpg $sigfile + +# parse the product and release number out of a filename like +# "bzr-foo-1.23rc1.tar.gz" + +# need to strip off the .tar too for .tar.gz files +headpart=${${upfile:r}%.tar} +filetype="${upfile#${headpart}}" +basename="${${headpart:t}%%-[0-9].*}" +version="${${headpart:t}##*-}" +echo $basename $version $filetype + +# bzr puts just the first part of the version into the series, e.g. 1.8 from 1.8rc1 +series=${version%(rc|beta|alpha)*} + +for v in basename version filetype series +do + echo "$v=${(P)v}" +done + +curl -u "$EMAIL" \ + https://$server/$basename/$series/$version/+adddownloadfile \ + -F field.description="$basename $version source" \ + -F field.filecontent="@${upfile}" \ + -F field.contenttype=CODETARBALL \ + -F field.actions.add=Upload + +reported_md5=$( curl https://$server/$basename/$series/$version/+download/$filetail/+md5 ) +expected_md5="$(md5sum "$upfile")" +expected_md5=${${(z)expected_md5}[1]} + +for v in reported_md5 expected_md5 +do + echo "$v=${(P)v}" +done + +[ $reported_md5 = $expected_md5 ] + +# vim: sw=4 diff --git a/tools/packaging/update-changelogs.sh b/tools/packaging/update-changelogs.sh new file mode 100755 index 0000000..3d1dcbc --- /dev/null +++ b/tools/packaging/update-changelogs.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Helper to insert a new entry in debian/changelog + +if [ -z "$UBUNTU_RELEASES" ]; then + echo "Configure the distro platforms that you want to" + echo "build with a line like:" + echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"' + exit 1 +fi + +if [ "x$VERSION" = "x" ]; then + echo "Missing version" + echo "You want something like:" + echo " VERSION=1.6~rc1-1~bazaar1 update-changelogs.sh" + echo "or" + echo " VERSION=1.6-1~bazaar1 update-changelogs.sh" + exit +fi + +if [ -z "$1" ]; then + MSG="New upstream release" +else + MSG=$1 +fi + +for DISTRO in $UBUNTU_RELEASES; do + PPAVERSION="$VERSION~${DISTRO}1" + ( + echo "Updating changelog for $DISTRO" + cd "$PACKAGE-$DISTRO" && + dch -v $PPAVERSION -D $DISTRO "$MSG." && + bzr commit -m "$MSG: $PPAVERSION" + ) +done diff --git a/tools/packaging/update-control.sh b/tools/packaging/update-control.sh new file mode 100755 index 0000000..ae5cd80 --- /dev/null +++ b/tools/packaging/update-control.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Helper to run sed on versions in dependencies in debian/control + +if [ -z "$UBUNTU_RELEASES" ]; then + echo "Configure the distro platforms that you want to" + echo "build with a line like:" + echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"' + exit 1 +fi + +OLD_VERSION=$1 +NEW_VERSION=$2 +NEXT_VERSION=$3 +if [ -z "$OLD_VERSION" -o -z "$NEW_VERSION" -o -z "$NEXT_VERSION" ]; then + echo "Missing version" + echo "You want something like:" + echo " update-control.sh 1.5 1.6 1.7" + exit +fi + +if [ "$PACKAGE" = "bzr" ]; then + continue +fi +for DISTRO in $UBUNTU_RELEASES; do + PPAVERSION="$VERSION~${DISTRO}1" + ( + echo "Updating control for $DISTRO" + cd "$PACKAGE-$DISTRO" && + sed -i -e "s/$NEW_VERSION~/$NEXT_VERSION~/;s/$OLD_VERSION~/$NEW_VERSION~/" control && + bzr commit -m "New upstream release: $PPAVERSION, update control" + ) +done diff --git a/tools/packaging/update-packaging-branches.sh b/tools/packaging/update-packaging-branches.sh new file mode 100755 index 0000000..c56f457 --- /dev/null +++ b/tools/packaging/update-packaging-branches.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Helper for updating all of the packaging branches + +if [ -z "$UBUNTU_RELEASES" ]; then + echo "Configure the distro platforms that you want to" + echo "build with a line like:" + echo ' export UBUNTU_RELEASES="dapper feisty gutsy hardy intrepid jaunty"' + exit 1 +fi + +for DISTRO in $UBUNTU_RELEASES; do + if [ -d "$PACKAGE-$DISTRO" ] ; then + echo "Updating $PACKAGE-$DISTRO" + bzr update $PACKAGE-$DISTRO + if [ "$PACKAGE" = "bzr-svn" ] ; then + cd $PACKAGE-$DISTRO + bzr merge http://bzr.debian.org/pkg-bazaar/bzr-svn/unstable/ + cd .. + fi + else + SRC="lp:~bzr/ubuntu/$DISTRO/$PACKAGE/bzr-ppa" + echo "Checking out $SRC" + bzr co $SRC $PACKAGE-$DISTRO + fi +done diff --git a/tools/prepare_for_latex.py b/tools/prepare_for_latex.py new file mode 100644 index 0000000..970c2df --- /dev/null +++ b/tools/prepare_for_latex.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# +# Modify reStructuredText 'image' directives by adding a percentage 'width' +# attribute so that the images are scaled to fit on the page when the document +# is renderd to LaTeX, and add a center alignment. +# +# Also convert references to PNG images to use PDF files generated from SVG +# files if available. +# +# Without the explicit size specification, the images are ridiculously huge and +# most extend far off the right side of the page. +# +# Copyright (C) 2009 Colin D Bennett +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re +import shutil +import sys +from sys import argv +from subprocess import call + +verbose = False + +IMAGE_DIRECTIVE_PATTERN = re.compile(ur'^..\s+image::\s+(.*)\s+$') +DIRECTIVE_ELEMENT_PATTERN = re.compile(ur'^\s+:[^:]+:\s+') + +class Converter(object): + def __init__(self, srcdir, destdir): + self.srcdir = srcdir + self.destdir = destdir + + # Process .txt files in sourcedir, generating output in destdir. + def process_files(self): + for filename in os.listdir(self.srcdir): + # Process all text files in the current directory. + if filename.endswith('.txt'): + inpath = os.path.join(self.srcdir, filename) + outpath = os.path.join(self.destdir, filename) + self._process_file(inpath, outpath) + + def _process_file(self, inpath, outpath): + infile = open(inpath, 'r') + outfile = open(outpath, 'w') + foundimg = False + for line in infile: + if foundimg and DIRECTIVE_ELEMENT_PATTERN.match(line) is None: + if verbose: + print('Fixing image directive') + # The preceding image directive has no elements. + outfile.write(' :width: 85%\n') + outfile.write(' :align: center\n') + foundimg = False + + image_fixer = ImageFixer(self.srcdir, self.destdir) + image_fixer_lambda = lambda match: image_fixer.substitute_pdf_image(match) + line = IMAGE_DIRECTIVE_PATTERN.sub(image_fixer_lambda, line) + directive_match = IMAGE_DIRECTIVE_PATTERN.match(line) + if directive_match is not None: + image_src = directive_match.group(1) + if verbose: + print('Image ' + image_src + ' in ' + filename + + ': ' + line.strip()) + + foundimg = True + outfile.write(line) + outfile.close() + infile.close() + +class ImageFixer(object): + def __init__(self, srcdir, destdir): + self.srcdir = srcdir + self.destdir = destdir + + def substitute_pdf_image(self, match): + prefix = match.string[:match.start(1)] + newname = self.convert_image_to_pdf(match.group(1)) + suffix = match.string[match.end(1):] + return prefix + newname + suffix + + def replace_extension(self, path, newext): + if path.endswith(newext): + raise Exception("File '" + path + "' already has extension '" + + newext +"'") + dot = path.rfind('.') + if dot == -1: + return path + newext + else: + return path[:dot] + newext + + # Possibly use an SVG alternative to a PNG image, converting the SVG image + # to a PDF first. Whether or not a conversion is made, the image to use is + # written to the destination directory and the path to use in the RST # + # source is returned. + def convert_image_to_pdf(self, filename): + # Make the directory structure for the image in the destination dir. + image_dirname = os.path.dirname(filename) + if image_dirname: + image_dirpath = os.path.join(self.destdir, image_dirname) + if not os.path.exists(image_dirpath): + os.mkdir(image_dirpath) + + # Decide how to handle this image. + if filename.endswith('.png'): + # See if there is a vector alternative. + svgfile = self.replace_extension(filename, '.svg') + svgpath = os.path.join(self.srcdir, svgfile) + if os.path.exists(svgpath): + if verbose: + print('Using SVG alternative to PNG') + # Convert SVG to PDF with Inkscape. + pdffile = self.replace_extension(filename, '.pdf') + pdfpath = os.path.join(self.destdir, pdffile) + if call(['/usr/bin/inkscape', + '--export-pdf=' + pdfpath, svgpath]) != 0: + raise Exception("Conversion to pdf failed") + return pdffile + + # No conversion, just copy the file. + srcpath = os.path.join(self.srcdir, filename) + destpath = os.path.join(self.destdir, filename) + shutil.copyfile(srcpath, destpath) + return filename + +if __name__ == '__main__': + IN_DIR_OPT = '--in-dir=' + OUT_DIR_OPT = '--out-dir=' + srcdir = None + destdir = None + + if len(argv) < 2: + print('Usage: ' + argv[0] + ' ' + IN_DIR_OPT + 'INDIR ' + + OUT_DIR_OPT + 'OUTDIR') + print + print('This will convert all .txt files in INDIR into file in OUTDIR') + print('while adjusting the use of images and possibly converting SVG') + print('images to PDF files so LaTeX can include them.') + sys.exit(1) + + for arg in argv[1:]: + if arg == '-v' or arg == '--verbose': + verbose = True + elif arg.startswith(IN_DIR_OPT): + srcdir = arg[len(IN_DIR_OPT):] + elif arg.startswith(OUT_DIR_OPT): + destdir = arg[len(OUT_DIR_OPT):] + else: + print('Invalid argument ' + arg) + sys.exit(1) + + if srcdir is None or destdir is None: + print('Please specify the ' + IN_DIR_OPT + ' and ' + + OUT_DIR_OPT + ' options.') + sys.exit(1) + + if not os.path.exists(destdir): + os.mkdir(destdir) + Converter(srcdir, destdir).process_files() + +# vim: set ts=4 sw=4 et: diff --git a/tools/riodemo.py b/tools/riodemo.py new file mode 100644 index 0000000..3b97a57 --- /dev/null +++ b/tools/riodemo.py @@ -0,0 +1,60 @@ + + + +# \subsection{Example usage} +# +# \textbf{XXX:} Move these to object serialization code. + +def write_revision(writer, revision): + s = Stanza(revision=revision.revision_id, + committer=revision.committer, + timezone=long(revision.timezone), + timestamp=long(revision.timestamp), + inventory_sha1=revision.inventory_sha1, + message=revision.message) + for parent_id in revision.parent_ids: + s.add('parent', parent_id) + for prop_name, prop_value in revision.properties.items(): + s.add(prop_name, prop_value) + writer.write_stanza(s) + +def write_inventory(writer, inventory): + s = Stanza(inventory_version=7) + writer.write_stanza(s) + + for path, ie in inventory.iter_entries(): + s = Stanza() + s.add(ie.kind, ie.file_id) + for attr in ['name', 'parent_id', 'revision', \ + 'text_sha1', 'text_size', 'executable', \ + 'symlink_target', \ + ]: + attr_val = getattr(ie, attr, None) + if attr == 'executable' and attr_val == 0: + continue + if attr == 'parent_id' and attr_val == 'TREE_ROOT': + continue + if attr_val is not None: + s.add(attr, attr_val) + writer.write_stanza(s) + + +def read_inventory(inv_file): + """Read inventory object from rio formatted inventory file""" + from bzrlib.inventory import Inventory, InventoryFile + s = read_stanza(inv_file) + assert s['inventory_version'] == 7 + inv = Inventory() + for s in read_stanzas(inv_file): + kind, file_id = s.items[0] + parent_id = None + if 'parent_id' in s: + parent_id = s['parent_id'] + if kind == 'file': + ie = InventoryFile(file_id, s['name'], parent_id) + ie.text_sha1 = s['text_sha1'] + ie.text_size = s['text_size'] + else: + raise NotImplementedError() + inv.add(ie) + return inv diff --git a/tools/rst2html.py b/tools/rst2html.py new file mode 100755 index 0000000..788a0be --- /dev/null +++ b/tools/rst2html.py @@ -0,0 +1,55 @@ +#! /usr/bin/env python + +# Originally by Dave Goodger, from the docutils, distribution. +# +# Modified for Bazaar to accommodate options containing dots +# +# This file is in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing HTML. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +import docutils +from docutils.core import publish_cmdline, default_description + +if True: # this is still required in the distutils trunk as-at June 2008. + from docutils.parsers.rst.states import Body + # we have some option names that contain dot; which is not allowed by + # python-docutils 0.4-4 -- so monkeypatch in a better pattern + # + # This is a bit gross to patch because all this is built up at load time. + Body.pats['optname'] = r'[a-zA-Z0-9][a-zA-Z0-9._-]*' + Body.pats['longopt'] = r'(--|/)%(optname)s([ =]%(optarg)s)?' % Body.pats + Body.pats['option'] = r'(%(shortopt)s|%(longopt)s)' % Body.pats + Body.patterns['option_marker'] = r'%(option)s(, %(option)s)*( +| ?$)' % Body.pats + + +description = ('Generates (X)HTML documents from standalone reStructuredText ' + 'sources. ' + default_description) + + +# workaround for bug with <xxx id="tags" name="tags"> in IE +from docutils.writers import html4css1 + +class IESafeHtmlTranslator(html4css1.HTMLTranslator): + + def starttag(self, node, tagname, suffix='\n', empty=0, **attributes): + x = html4css1.HTMLTranslator.starttag(self, node, tagname, suffix, + empty, **attributes) + y = x.replace('id="tags"', 'id="tags_"') + y = y.replace('name="tags"', 'name="tags_"') + y = y.replace('href="#tags"', 'href="#tags_"') + return y + +mywriter = html4css1.Writer() +mywriter.translator_class = IESafeHtmlTranslator + + +publish_cmdline(writer=mywriter, description=description) diff --git a/tools/rst2pdf.py b/tools/rst2pdf.py new file mode 100755 index 0000000..87bb389 --- /dev/null +++ b/tools/rst2pdf.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# $Id: rst2pdf.py 5560 2008-05-20 13:00:31Z milde $ + +# rst2pdf.py +# ========== +# :: + +""" +A front end to the Docutils Publisher, producing PDF. + +Produces a latex file with the "latex" writer and converts +it to PDF with the "rubber" building system for LaTeX documents. +""" + +# ``rst2pdf.py`` is a PDF front-end for docutils that is compatible +# with the ``rst2*.py`` front ends of the docutils_ suite. +# It enables the generation of PDF documents from a reStructuredText source in +# one step. +# +# It is implemented as a combination of docutils' ``rst2latex.py`` +# by David Goodger and rubber_ by Emmanuel Beffara. +# +# Copyright: © 2008 Günter Milde +# Licensed under the `Apache License, Version 2.0`_ +# Provided WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND +# +# Changelog +# --------- +# +# ===== ========== ======================================================= +# 0.1 2008-05-20 first attempt +# ===== ========== ======================================================= +# +# :: + +_version = 0.1 + + +# Imports +# ======= +# :: + +#from pprint import pprint # for debugging +import os + +# Docutils:: + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import default_usage, default_description, Publisher + +# Rubber (rubber is not installed in the PYTHONPATH):: + +import sys +sys.path.append("/usr/share/rubber") + +try: + import rubber.cmdline + import rubber.cmd_pipe +except ImportError: + print "Cannot find the rubber modules, rubber not installed correctly." + sys.exit(1) + +# Generate the latex file +# ======================= +# +# We need to replace the <destination> by a intermediate latex file path. +# The most reliable way to get the value of <destination> is to +# call the Publisher "by hand", and query its settings. +# +# Modeled on the publish_cmdline() function of docutils.core +# +# Default values:: + +reader=None +reader_name='standalone' +parser=None +parser_name='restructuredtext' +writer=None +writer_name='pseudoxml' +settings=None +settings_spec=None +settings_overrides=None +config_section=None +enable_exit_status=1 +argv=None +usage=default_usage +description=default_description + +# Argument values given to publish_cmdline() in rst2latex.py:: + +description = ('Generates PDF documents from standalone reStructuredText ' + 'sources using the "latex" Writer and the "rubber" ' + 'building system for LaTeX documents. ' + default_description) +writer_name = 'latex' + +# Set up the publisher:: + +pub = Publisher(reader, parser, writer, settings=settings) +pub.set_components(reader_name, parser_name, writer_name) + +# Parse the command line args +# (Publisher.publish does this in a try statement):: + +pub.process_command_line(argv, usage, description, settings_spec, + config_section, **(settings_overrides or {})) +# pprint(pub.settings.__dict__) + +# Get source and destination path:: + +source = pub.settings._source +destination = pub.settings._destination +# print source, destination + +# Generate names for the temporary files and set ``destination`` to temporary +# latex file: +# +# make_name() from rubber.cmd_pipe checks that no existing file is +# overwritten. If we are going to support rubbers ``--inplace`` and ``--into`` +# options, the chdir() must occure before this point to have the check in the +# right directory. :: + +tmppath = rubber.cmd_pipe.make_name() +texpath = tmppath + ".tex" +pdfpath = tmppath + ".pdf" + +pub.settings._destination = texpath + +# Now do the rst -> latex conversion:: + +pub.publish(argv, usage, description, settings_spec, settings_overrides, + config_section=config_section, enable_exit_status=enable_exit_status) + + +# Generating the PDF document with rubber +# ======================================= +# +# +# rubber_ has no documentet API for programmatic use. We simualate a command +# line call and pass command line arguments (see man: rubber-pipe) in an array:: + +rubber_argv = ["--pdf", # use pdflatex to produce PDF + "--short", # Display LaTeX’s error messages one error per line. + texpath + ] + +# Get a TeX processing class instance and do the latex->pdf conversion:: + +tex_processor = rubber.cmdline.Main() +tex_processor(rubber_argv) + +# Rename output to _destination or print to stdout:: + +if destination is None: + pdffile = file(pdfpath) + print pdffile.read() + pdffile.close() +else: + os.rename(pdfpath, destination) + +# Clean up (remove intermediate files) +# +# :: + +tex_processor(["--clean"] + rubber_argv) +os.remove(texpath) + + +# .. References +# +# .. _docutils: http://docutils.sourceforge.net/ +# .. _rubber: http://www.pps.jussieu.fr/~beffara/soft/rubber/ +# .. _Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 +# diff --git a/tools/rst2prettyhtml.py b/tools/rst2prettyhtml.py new file mode 100755 index 0000000..9b02b09 --- /dev/null +++ b/tools/rst2prettyhtml.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +import errno +import os +from StringIO import StringIO +import sys + +try: + from docutils.core import publish_file + from docutils.parsers import rst +except ImportError: + print "Missing dependency. Please install docutils." + sys.exit(1) +try: + from elementtree.ElementTree import XML + from elementtree import HTMLTreeBuilder +except ImportError: + print "Missing dependency. Please install ElementTree." + sys.exit(1) +try: + import kid +except ImportError: + print "Missing dependency. Please install Kid." + sys.exit(1) + + +def kidified_rest(rest_file, template_name): + xhtml_file = StringIO() + # prevent docutils from autoclosing the StringIO + xhtml_file.close = lambda: None + xhtml = publish_file(rest_file, writer_name='html', destination=xhtml_file, + settings_overrides={"doctitle_xform": 0} + + ) + xhtml_file.seek(0) + xml = HTMLTreeBuilder.parse(xhtml_file) + head = xml.find('head') + body = xml.find('body') + assert head is not None + assert body is not None + template=kid.Template(file=template_name, + head=head, body=body) + return (template.serialize(output="html")) + + +def safe_open(filename, mode): + try: + return open(filename, mode + 'b') + except IOError, e: + if e.errno != errno.ENOENT: + raise + sys.stderr.write('file not found: %s\n' % sys.argv[2]) + sys.exit(3) + + +def main(template, source=None, target=None): + if source is not None: + rest_file = safe_open(source, 'r') + else: + rest_file = sys.stdin + if target is not None: + out_file = safe_open(target, 'w') + else: + out_file = sys.stdout + out_file.write(kidified_rest(rest_file, template)) + +assert len(sys.argv) > 1 + +# Strip options so only the arguments are passed +args = [x for x in sys.argv[1:] if not x.startswith('-')] +main(*args) diff --git a/tools/subunit-sum b/tools/subunit-sum new file mode 100755 index 0000000..2b042b1 --- /dev/null +++ b/tools/subunit-sum @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# Copyright (C) 2011 Canonical Ltd +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""Displays the sum of a counter found in a subunit stream. + +Each test have (or not) the counter as a named detail in the stream. +""" + +import subunit +import sys +import testtools +import unittest + + +class TestSumCounter(testtools.TestResult): + + def __init__(self, counter_names): + """Create a FilterResult object outputting to stream.""" + testtools.TestResult.__init__(self) + self.counter_names = counter_names + self.totals = {} + self.longest = 0 + for name in counter_names: + l = len(name) + if l > self.longest: self.longest = l + self.totals[name] = 0 + + def addSuccess(self, test, details): + for name in self.counter_names: + try: + counter_text = ''.join(details[name].iter_text()) + except KeyError, e: + # this counter doesn't exist for the test + continue + self.totals[name] += int(counter_text) + + def display_totals(self, stream): + for name in self.counter_names: + stream.write('%-*s: %s\n' + % (self.longest, name, self.totals[name])) + + +counter_names = sys.argv[1:] +result = TestSumCounter(counter_names) +test = subunit.ProtocolTestCase(sys.stdin) +test.run(result) +result.display_totals(sys.stdout) diff --git a/tools/time_graph.py b/tools/time_graph.py new file mode 100644 index 0000000..bfe6056 --- /dev/null +++ b/tools/time_graph.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +import random +import os +import time +import sys +import optparse +from bzrlib import ( + branch, + commands, + graph, + ui, + trace, + _known_graph_py, + _known_graph_pyx, + ) +from bzrlib.ui import text + +p = optparse.OptionParser() +p.add_option('--quick', default=False, action='store_true') +p.add_option('--max-combinations', default=500, type=int) +p.add_option('--lsprof', default=None, type=str) +opts, args = p.parse_args(sys.argv[1:]) + +trace.enable_default_logging() +ui.ui_factory = text.TextUIFactory() + +begin = time.clock() +if len(args) >= 1: + b = branch.Branch.open(args[0]) +else: + b = branch.Branch.open('.') +b.lock_read() +try: + g = b.repository.get_graph() + parent_map = dict(p for p in g.iter_ancestry([b.last_revision()]) + if p[1] is not None) +finally: + b.unlock() +end = time.clock() + +print 'Found %d nodes, loaded in %.3fs' % (len(parent_map), end - begin) + +def all_heads_comp(g, combinations): + h = [] + pb = ui.ui_factory.nested_progress_bar() + try: + for idx, combo in enumerate(combinations): + if idx & 0x1f == 0: + pb.update('proc', idx, len(combinations)) + h.append(g.heads(combo)) + finally: + pb.finished() + return h + +combinations = [] +# parents = parent_map.keys() +# for p1 in parents: +# for p2 in random.sample(parents, 10): +# combinations.append((p1, p2)) +# Times for random sampling of 10x1150 of bzrtools +# Graph KnownGraph +# 96.1s vs 25.7s :) +# Times for 500 'merge parents' from bzr.dev +# 25.6s vs 45.0s :( + +for revision_id, parent_ids in parent_map.iteritems(): + if parent_ids is not None and len(parent_ids) > 1: + combinations.append(parent_ids) +# The largest portion of the graph that has to be walked for a heads() check +# combinations = [('john@arbash-meinel.com-20090312021943-tu6tcog48aiujx4s', +# 'john@arbash-meinel.com-20090312130552-09xa2xsitf6rilzc')] +if opts.max_combinations > 0 and len(combinations) > opts.max_combinations: + combinations = random.sample(combinations, opts.max_combinations) + +print ' %d combinations' % (len(combinations),) + +def combi_graph(graph_klass, comb): + # DEBUG + graph._counters[1] = 0 + graph._counters[2] = 0 + + begin = time.clock() + g = graph_klass(parent_map) + if opts.lsprof is not None: + heads = commands.apply_lsprofiled(opts.lsprof, all_heads_comp, g, comb) + else: + heads = all_heads_comp(g, comb) + end = time.clock() + return dict(elapsed=(end - begin), graph=g, heads=heads) + +def report(name, g): + print '%s: %.3fs' % (name, g['elapsed']) + counters_used = False + for c in graph._counters: + if c: + counters_used = True + if counters_used: + print ' %s' % (graph._counters,) + +known_python = combi_graph(_known_graph_py.KnownGraph, combinations) +report('Known', known_python) + +known_pyrex = combi_graph(_known_graph_pyx.KnownGraph, combinations) +report('Known (pyx)', known_pyrex) + +def _simple_graph(parent_map): + return graph.Graph(graph.DictParentsProvider(parent_map)) + +if opts.quick: + if known_python['heads'] != known_pyrex['heads']: + import pdb; pdb.set_trace() + print 'ratio: %.1f:1 faster' % ( + known_python['elapsed'] / known_pyrex['elapsed'],) +else: + orig = combi_graph(_simple_graph, combinations) + report('Orig', orig) + + if orig['heads'] != known_pyrex['heads']: + import pdb; pdb.set_trace() + + print 'ratio: %.1f:1 faster' % ( + orig['elapsed'] / known_pyrex['elapsed'],) diff --git a/tools/weavemerge.sh b/tools/weavemerge.sh new file mode 100644 index 0000000..27c9889 --- /dev/null +++ b/tools/weavemerge.sh @@ -0,0 +1,57 @@ +#! /bin/zsh -xe + +weave init test.weave + +weave add test.weave <<EOF +aaa +bbb +ccc +EOF + +weave add test.weave 0 <<EOF +aaa +bbb +stuff from martin +ccc +ddd +EOF + +weave add test.weave 0 <<EOF +aaa +bbb +stuff from john +more john stuff +ccc +EOF + +weave add test.weave 1 2 <<EOF +aaa +bbb +stuff from martin +fix up merge +more john stuff +ccc +ddd +EOF + +weave add test.weave 3 <<EOF +aaa +bbb +stuff from martin +fix up merge +modify john's code +ccc +ddd +add stuff here +EOF + +# v5 +weave add test.weave 2 <<EOF +aaa +bbb +stuff from john +more john stuff +john replaced ccc line +EOF + +# now try merging 5(2) with 4(3(2 1))
\ No newline at end of file diff --git a/tools/win32/__init__.py b/tools/win32/__init__.py new file mode 100644 index 0000000..b4e98a6 --- /dev/null +++ b/tools/win32/__init__.py @@ -0,0 +1 @@ +# modules and utilities for making win32 installer diff --git a/tools/win32/bazaar.url b/tools/win32/bazaar.url new file mode 100644 index 0000000..f3eee0f --- /dev/null +++ b/tools/win32/bazaar.url @@ -0,0 +1,7 @@ +[InternetShortcut] +URL=http://bazaar.canonical.com +Hotkey=0 +IconIndex=0 +IconFile= +WorkingDirectory= +ShowCommand=1 diff --git a/tools/win32/bootstrap.py b/tools/win32/bootstrap.py new file mode 100644 index 0000000..2ad3e57 --- /dev/null +++ b/tools/win32/bootstrap.py @@ -0,0 +1,77 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. + +$Id: bootstrap.py 90478 2008-08-27 22:44:46Z georgyberdyshev $ +""" + +import os, shutil, sys, tempfile, urllib2 + +tmpeggs = tempfile.mkdtemp() + +is_jython = sys.platform.startswith('java') + +try: + import pkg_resources +except ImportError: + ez = {} + exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' + ).read() in ez + ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) + + import pkg_resources + +if sys.platform == 'win32': + def quote(c): + if ' ' in c: + return '"%s"' % c # work around spawn lamosity on windows + else: + return c +else: + def quote (c): + return c + +cmd = 'from setuptools.command.easy_install import main; main()' +ws = pkg_resources.working_set + +if is_jython: + import subprocess + + assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', + quote(tmpeggs), 'zc.buildout'], + env=dict(os.environ, + PYTHONPATH= + ws.find(pkg_resources.Requirement.parse('setuptools')).location + ), + ).wait() == 0 + +else: + assert os.spawnle( + os.P_WAIT, sys.executable, quote (sys.executable), + '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout', + dict(os.environ, + PYTHONPATH= + ws.find(pkg_resources.Requirement.parse('setuptools')).location + ), + ) == 0 + +ws.add_entry(tmpeggs) +ws.require('zc.buildout') +import zc.buildout.buildout +zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) +shutil.rmtree(tmpeggs) diff --git a/tools/win32/build_release.py b/tools/win32/build_release.py new file mode 100644 index 0000000..b66fa8f --- /dev/null +++ b/tools/win32/build_release.py @@ -0,0 +1,206 @@ +#!/cygdrive/C/Python25/python
+"""A script to help automate the build process."""
+
+# When preparing a new release, make sure to set all of these to the latest
+# values.
+VERSIONS = {
+ 'bzr': '1.17',
+ 'qbzr': '0.12',
+ 'bzrtools': '1.17.0',
+ 'bzr-svn': '0.6.3',
+ 'bzr-rewrite': '0.5.2',
+ 'subvertpy': '0.6.8',
+}
+
+# This will be passed to 'make' to ensure we build with the right python
+PYTHON='/cygdrive/c/Python25/python'
+
+# Create the final build in this directory
+TARGET_ROOT='release'
+
+DEBUG_SUBPROCESS = True
+
+
+import os
+import shutil
+import subprocess
+import sys
+
+
+BZR_EXE = None
+def bzr():
+ global BZR_EXE
+ if BZR_EXE is not None:
+ return BZR_EXE
+ try:
+ subprocess.call(['bzr', '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ BZR_EXE = 'bzr'
+ except OSError:
+ try:
+ subprocess.call(['bzr.bat', '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ BZR_EXE = 'bzr.bat'
+ except OSError:
+ raise RuntimeError('Could not find bzr or bzr.bat on your path.')
+ return BZR_EXE
+
+
+def call_or_fail(*args, **kwargs):
+ """Call a subprocess, and fail if the return code is not 0."""
+ if DEBUG_SUBPROCESS:
+ print ' calling: "%s"' % (' '.join(args[0]),)
+ p = subprocess.Popen(*args, **kwargs)
+ (out, err) = p.communicate()
+ if p.returncode != 0:
+ raise RuntimeError('Failed to run: %s, %s' % (args, kwargs))
+ return out
+
+
+TARGET = None
+def get_target():
+ global TARGET
+ if TARGET is not None:
+ return TARGET
+ out = call_or_fail([sys.executable, get_bzr_dir() + '/bzr',
+ 'version', '--short'], stdout=subprocess.PIPE)
+ version = out.strip()
+ TARGET = os.path.abspath(TARGET_ROOT + '-' + version)
+ return TARGET
+
+
+def clean_target():
+ """Nuke the target directory so we know we are starting from scratch."""
+ target = get_target()
+ if os.path.isdir(target):
+ print "Deleting: %s" % (target,)
+ shutil.rmtree(target)
+
+def get_bzr_dir():
+ return 'bzr.' + VERSIONS['bzr']
+
+
+def update_bzr():
+ """Make sure we have the latest bzr in play."""
+ bzr_dir = get_bzr_dir()
+ if not os.path.isdir(bzr_dir):
+ bzr_version = VERSIONS['bzr']
+ bzr_url = 'lp:bzr/' + bzr_version
+ print "Getting bzr release %s from %s" % (bzr_version, bzr_url)
+ call_or_fail([bzr(), 'co', bzr_url, bzr_dir])
+ else:
+ print "Ensuring %s is up-to-date" % (bzr_dir,)
+ call_or_fail([bzr(), 'update', bzr_dir])
+
+
+def create_target():
+ target = get_target()
+ print "Creating target dir: %s" % (target,)
+ call_or_fail([bzr(), 'co', get_bzr_dir(), target])
+
+
+def get_plugin_trunk_dir(plugin_name):
+ return '%s/trunk' % (plugin_name,)
+
+
+def get_plugin_release_dir(plugin_name):
+ return '%s/%s' % (plugin_name, VERSIONS[plugin_name])
+
+
+def get_plugin_trunk_branch(plugin_name):
+ return 'lp:%s' % (plugin_name,)
+
+
+def update_plugin_trunk(plugin_name):
+ trunk_dir = get_plugin_trunk_dir(plugin_name)
+ if not os.path.isdir(trunk_dir):
+ plugin_trunk = get_plugin_trunk_branch(plugin_name)
+ print "Getting latest %s trunk" % (plugin_name,)
+ call_or_fail([bzr(), 'co', plugin_trunk,
+ trunk_dir])
+ else:
+ print "Ensuring %s is up-to-date" % (trunk_dir,)
+ call_or_fail([bzr(), 'update', trunk_dir])
+ return trunk_dir
+
+
+def _plugin_tag_name(plugin_name):
+ if plugin_name in ('bzr-svn', 'bzr-rewrite', 'subvertpy'):
+ return '%s-%s' % (plugin_name, VERSIONS[plugin_name])
+ # bzrtools and qbzr use 'release-X.Y.Z'
+ return 'release-' + VERSIONS[plugin_name]
+
+
+def update_plugin(plugin_name):
+ release_dir = get_plugin_release_dir(plugin_name)
+ if not os.path.isdir(plugin_name):
+ if plugin_name in ('bzr-svn', 'bzr-rewrite'):
+ # bzr-svn uses a different repo format
+ call_or_fail([bzr(), 'init-repo', '--rich-root-pack', plugin_name])
+ else:
+ os.mkdir(plugin_name)
+ if os.path.isdir(release_dir):
+ print "Removing existing dir: %s" % (release_dir,)
+ shutil.rmtree(release_dir)
+ # First update trunk
+ trunk_dir = update_plugin_trunk(plugin_name)
+ # Now create the tagged directory
+ tag_name = _plugin_tag_name(plugin_name)
+ print "Creating the branch %s" % (release_dir,)
+ call_or_fail([bzr(), 'co', '-rtag:%s' % (tag_name,),
+ trunk_dir, release_dir])
+ return release_dir
+
+
+def install_plugin(plugin_name):
+ release_dir = update_plugin(plugin_name)
+ # at least bzrtools doesn't like you to call 'setup.py' unless you are in
+ # that directory specifically, so we cd, rather than calling it from
+ # outside
+ print "Installing %s" % (release_dir,)
+ call_or_fail([sys.executable, 'setup.py', 'install', '-O1',
+ '--install-lib=%s' % (get_target(),)],
+ cwd=release_dir)
+
+
+def update_tbzr():
+ tbzr_loc = os.environ.get('TBZR', None)
+ if tbzr_loc is None:
+ raise ValueError('You must set TBZR to the location of tortoisebzr.')
+ print 'Updating %s' % (tbzr_loc,)
+ call_or_fail([bzr(), 'update', tbzr_loc])
+
+
+def build_installer():
+ target = get_target()
+ print
+ print
+ print '*' * 60
+ print 'Building standalone installer'
+ call_or_fail(['make', 'PYTHON=%s' % (PYTHON,), 'installer'],
+ cwd=target)
+
+
+def main(args):
+ import optparse
+
+ p = optparse.OptionParser(usage='%prog [OPTIONS]')
+ opts, args = p.parse_args(args)
+
+ update_bzr()
+ update_tbzr()
+ clean_target()
+ create_target()
+ install_plugin('subvertpy')
+ install_plugin('bzrtools')
+ install_plugin('qbzr')
+ install_plugin('bzr-svn')
+ install_plugin('bzr-rewrite')
+
+ build_installer()
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
+
+# vim: ts=4 sw=4 sts=4 et ai
diff --git a/tools/win32/buildout-templates/bin/build-installer.bat.in b/tools/win32/buildout-templates/bin/build-installer.bat.in new file mode 100644 index 0000000..bc65773 --- /dev/null +++ b/tools/win32/buildout-templates/bin/build-installer.bat.in @@ -0,0 +1,108 @@ +@echo off +setlocal + +set ROOT=${buildout:directory} + +set RELEASE=%ROOT%\release\bzr.${settings:bzr-release} +set DEV=%ROOT%\dev\bzr.dev +set TARGET= +set BZR_TARGET= +set PLUGIN_TARGET= + +set SVN_DEV=${buildout:directory}/${svn-dev:destination} +set SVN_BDB=${buildout:directory}/${svn-db4:destination} +set SVN_LIBINTL=${buildout:directory}/${svn-libintl:destination} +set TBZR=${buildout:directory}/tbzr/trunk +set INSTALLERS=%ROOT%\installers +set PYTHON=${buildout:executable} + +set ORIGINAL_PYTHONPATH=%PYTHONPATH% +set ORIGINAL_DIRECTORY=%CD% + +set BUILD_ERROR=0 + +set TORTOISE_OVERLAYS_MSI_WIN32_CMD=%PYTHON% %ROOT%/ostools.py basename ${settings:tortoise-overlays-win32-url} +FOR /f "tokens=1 delims= " %%G IN ('%TORTOISE_OVERLAYS_MSI_WIN32_CMD%') DO set BASENAME=%%G +set TORTOISE_OVERLAYS_MSI_WIN32=${buildout:directory}/tortoise-overlays/%BASENAME% + +set TORTOISE_OVERLAYS_MSI_X64_CMD=%PYTHON% %ROOT%/ostools.py basename ${settings:tortoise-overlays-x64-url} +FOR /f "tokens=1 delims= " %%G IN ('%TORTOISE_OVERLAYS_MSI_X64_CMD%') DO set BASENAME=%%G +set TORTOISE_OVERLAYS_MSI_X64=${buildout:directory}/tortoise-overlays/%BASENAME% + +FOR /f "tokens=1 delims= " %%G IN ('cygpath %PYTHON%') DO set CYG_PYTHON=%%G + +:ARGS +if "%1"=="release" (set TARGET=%RELEASE%) & (set BZR_TARGET=release) & shift & goto ARGS +if "%1"=="dev" (set TARGET=%DEV%) & (set BZR_TARGET=trunk) & shift & goto ARGS +if "%1"=="plugin-release" (set PLUGIN_TARGET=release) & shift & goto ARGS +if "%1"=="plugin-dev" (set PLUGIN_TARGET=trunk) & shift & goto ARGS + +if not defined TARGET (set TARGET=%RELEASE%) & (set BZR_TARGET=release) +if not defined PLUGIN_TARGET (set PLUGIN_TARGET=release) + +%PYTHON% %ROOT%/ostools.py remove %TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +%PYTHON% %ROOT%/ostools.py makedir %TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +%PYTHON% %ROOT%/ostools.py makedir %INSTALLERS% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +rem Use %COMSPEC% /c in case bzr is actually a .bat file +%COMSPEC% /c bzr co %ROOT%/bzr/%BZR_TARGET% %TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +rem Build the python installers first, because we don't want to +rem include any of the 3rd-party plugins, because we don't bundle +rem their dependencies. +cd %TARGET% + +rem This is slightly redundant with 'make installer-all' +rem except in that case we have to do cygwin path translations for all the +rem different versions of python +${settings:python24} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d . +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +${settings:python25} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d . +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +${settings:python26} setup.py bdist_wininst --install-script="bzr-win32-bdist-postinstall.py" -d . +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %ROOT%/subvertpy/%PLUGIN_TARGET% +%PYTHON% setup.py install -O1 --install-lib=%TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %ROOT%/bzrtools/%PLUGIN_TARGET% +%PYTHON% setup.py install -O1 --install-lib=%TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %ROOT%/qbzr/%PLUGIN_TARGET% +%PYTHON% setup.py install -O1 --install-lib=%TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %ROOT%/bzr-svn/%PLUGIN_TARGET% +%PYTHON% setup.py install -O1 --install-lib=%TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %ROOT%/bzr-rewrite/%PLUGIN_TARGET% +%PYTHON% setup.py install -O1 --install-lib=%TARGET% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +set PYTHONPATH=%PYTHONPATH%;%TARGET% +cd %ROOT%/tbzr/trunk +%PYTHON% setup.py build +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +cd %TARGET% +make installer PYTHON=%CYG_PYTHON% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +%PYTHON% %ROOT%/ostools.py copytodir %TARGET%/bzr*.exe %INSTALLERS% +@if %ERRORLEVEL% NEQ 0 (set BUILD_ERROR=%ERRORLEVEL%) & goto End + +:End +set PYTHONPATH=%ORIGINAL_PYTHONPATH% +cd %ORIGINAL_DIRECTORY% +exit /b %BUILD_ERROR% diff --git a/tools/win32/buildout.cfg b/tools/win32/buildout.cfg new file mode 100644 index 0000000..bc899b7 --- /dev/null +++ b/tools/win32/buildout.cfg @@ -0,0 +1,197 @@ +[buildout] +newest = false +versions = versions +parts = + svn-lib + svn-dev + svn-db4 + svn-libintl + zlib + tortoise-overlays-win32 + tortoise-overlays-x64 + + bzr + qbzr + tbzr + bzrtools + bzr-svn + bzr-rewrite + subvertpy + templates + +develop = + +[versions] +zc.buildout = 1.2.1 +setuptools = 0.6c9 +z3c.recipe.filetemplate = 2.0.3 +gf.recipe.bzr = 1.0rc8 + +[settings] +python24=c:\Python24\python.exe +python25=c:\Python25\python.exe +python26=c:\Python26\python.exe +download-ignore-existing = false +svn-dev-url = + http://subversion.tigris.org/files/documents/15/45228/svn-win32-1.5.6_dev.zip +svn-lib-url = + http://subversion.tigris.org/files/documents/15/45222/svn-win32-1.5.6.zip +svn-db4-url = + http://subversion.tigris.org/files/documents/15/32472/db-4.4.20-win32.zip +svn-libintl-url = + http://subversion.tigris.org/files/documents/15/20739/svn-win32-libintl.zip +tortoise-overlays-win32-url = http://guest:password@tortoisesvn.tigris.org/svn/tortoisesvn/TortoiseOverlays/version-1.0.4/bin/TortoiseOverlays-1.0.4.11886-win32.msi +tortoise-overlays-x64-url = http://guest:password@tortoisesvn.tigris.org/svn/tortoisesvn/TortoiseOverlays/version-1.0.4/bin/TortoiseOverlays-1.0.4.11886-x64.msi +zlib-url = + http://www.zlib.net/zlib123-dll.zip + +bzr-release = 1.18 +# Older releases were @ http://bazaar-vcs.org, new releases are hosted directly +# on Launchpad +# bzr-release-url = http://bazaar-vcs.org/bzr/bzr.${settings:bzr-release} +bzr-release-url = lp:bzr/${settings:bzr-release} +bzr-trunk-url = lp:bzr + +bzrtools-release = 1.18.0 +bzrtools-release-tag = tag:release-${settings:bzrtools-release} +bzrtools-trunk-url = lp:bzrtools + +qbzr-release = 0.13.1 +qbzr-release-tag = tag:release-${settings:qbzr-release} +qbzr-trunk-url = lp:qbzr + +tbzr-release = 0.1.0 +tbzr-release-tag = tag:release-${settings:tbzr-release} +tbzr-trunk-url = lp:tortoisebzr + +bzr-svn-release = 0.6.4 +bzr-svn-release-tag = tag:bzr-svn-${settings:bzr-svn-release} +bzr-svn-trunk-url = lp:bzr-svn + +# This isn't a typo, as of last release, the branch is lp:bzr-rewrite but the +# tag is bzr-rebase-0.5.3 +bzr-rewrite-release = 0.5.3 +bzr-rewrite-release-tag = tag:bzr-rebase-${settings:bzr-rewrite-release} +bzr-rewrite-trunk-url = lp:bzr-rewrite + +subvertpy-release = 0.6.8 +subvertpy-release-tag = tag:subvertpy-${settings:subvertpy-release} +subvertpy-trunk-url = lp:subvertpy + +[templates] +recipe = z3c.recipe.filetemplate +source-directory = buildout-templates + +[svn-lib] +recipe = hexagonit.recipe.download +url = ${settings:svn-lib-url} +ignore-existing = ${settings:download-ignore-existing} +strip-top-level-dir = true +destination = svn + +# This package needs to be extracted on top of svn-lib above, so we +# explicitly set it to ignore existing here. +[svn-dev] +recipe = hexagonit.recipe.download +url = ${settings:svn-dev-url} +ignore-existing = true +strip-top-level-dir = true +destination = svn + +[svn-db4] +recipe = hexagonit.recipe.download +url = ${settings:svn-db4-url} +ignore-existing = ${settings:download-ignore-existing} +strip-top-level-dir = true +destination = db4 + +[svn-libintl] +recipe = hexagonit.recipe.download +url = ${settings:svn-libintl-url} +ignore-existing = ${settings:download-ignore-existing} +strip-top-level-dir = true +destination = libintl + +[zlib] +recipe = hexagonit.recipe.download +url = ${settings:zlib-url} +ignore-existing = ${settings:download-ignore-existing} +strip-top-level-dir = false +destination = zlib + +[tortoise-overlays-win32] +recipe = hexagonit.recipe.download +url = ${settings:tortoise-overlays-win32-url} +ignore-existing = ${settings:download-ignore-existing} +destination = tortoise-overlays +download-only = true + +[tortoise-overlays-x64] +recipe = hexagonit.recipe.download +url = ${settings:tortoise-overlays-x64-url} +ignore-existing = ${settings:download-ignore-existing} +destination = tortoise-overlays +download-only = true + +[bzr] +recipe = gf.recipe.bzr:strict +shared-repo = false +format = 1.9 +urls = + ${settings:bzr-release-url} release + ${settings:bzr-trunk-url} trunk +develop = false + +[bzrtools] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 2a +urls = + ${settings:bzrtools-trunk-url} trunk + ${buildout:directory}/bzrtools/trunk@${settings:bzrtools-release-tag} release +develop = false + +[qbzr] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 1.9 +urls = + ${settings:qbzr-trunk-url} trunk + ${buildout:directory}/qbzr/trunk@${settings:qbzr-release-tag} release +develop = false + +[tbzr] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 1.9 +urls = + ${settings:tbzr-trunk-url} trunk + ${buildout:directory}/tbzr/trunk@${settings:tbzr-release-tag} release +develop = false + +[bzr-svn] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 2a +urls = + ${settings:bzr-svn-trunk-url} trunk + ${buildout:directory}/bzr-svn/trunk@${settings:bzr-svn-release-tag} release +develop = false + +[bzr-rewrite] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 1.9-rich-root +urls = + ${settings:bzr-rewrite-trunk-url} trunk + ${buildout:directory}/bzr-rewrite/trunk@${settings:bzr-rewrite-release-tag} release +develop = false + +[subvertpy] +recipe = gf.recipe.bzr:strict +shared-repo = true +format = 1.9-rich-root +urls = + ${settings:subvertpy-trunk-url} trunk + ${buildout:directory}/subvertpy/trunk@${settings:subvertpy-release-tag} release +develop = false diff --git a/tools/win32/bzr-win32-bdist-postinstall.py b/tools/win32/bzr-win32-bdist-postinstall.py new file mode 100644 index 0000000..a5d1de5 --- /dev/null +++ b/tools/win32/bzr-win32-bdist-postinstall.py @@ -0,0 +1,145 @@ +# (c) Canonical Ltd, 2006 +# written by Alexander Belchenko for bzr project +# +# This script will be executed after installation of bzrlib package +# and before installer exits. +# All printed data will appear on the last screen of installation +# procedure. +# The main goal of this script is to create special batch file +# launcher for bzr. Typical content of this batch file is: +# @python bzr %* +# +# This file works only on Windows 2000/XP. For win98 there is +# should be "%1 %2 %3 %4 %5 %6 %7 %8 %9" instead of "%*". +# Or even more complex thing. +# +# [bialix]: bzr de-facto does not support win98. +# Although it seems to work on. Sometimes. +# 2006/07/30 added minimal support of win98. +# 2007/01/30 added *real* support of win98. + +import os +import sys +import _winreg + +from bzrlib import win32utils + + +def _quoted_path(path): + if ' ' in path: + return '"' + path + '"' + else: + return path + +def _win_batch_args(): + if win32utils.winver == 'Windows NT': + return '%*' + else: + return '%1 %2 %3 %4 %5 %6 %7 %8 %9' + + +if "-install" in sys.argv[1:]: + # try to detect version number automatically + try: + import bzrlib + except ImportError: + ver = '' + else: + ver = bzrlib.__version__ + + ## + # XXX change message for something more appropriate + print """Bazaar %s + +Congratulation! Bzr successfully installed. + +""" % ver + + batch_path = "bzr.bat" + prefix = sys.exec_prefix + try: + ## + # try to create + scripts_dir = os.path.join(prefix, "Scripts") + script_path = _quoted_path(os.path.join(scripts_dir, "bzr")) + python_path = _quoted_path(os.path.join(prefix, "python.exe")) + args = _win_batch_args() + batch_str = "@%s %s %s" % (python_path, script_path, args) + # support of win98 + # if there is no HOME for bzr then set it for Bazaar manually + base = os.environ.get('BZR_HOME', None) + if base is None: + base = win32utils.get_appdata_location() + if base is None: + base = os.environ.get('HOME', None) + if base is None: + base = os.path.splitdrive(sys.prefix)[0] + '\\' + batch_str = ("@SET BZR_HOME=" + _quoted_path(base) + "\n" + + batch_str) + + batch_path = os.path.join(scripts_dir, "bzr.bat") + f = file(batch_path, "w") + f.write(batch_str) + f.close() + file_created(batch_path) # registering manually created files for + # auto-deinstallation procedure + ## + # inform user where batch launcher is. + print "Created:", batch_path + print "Use this batch file to run bzr" + except Exception, e: + print "ERROR: Unable to create %s: %s" % (batch_path, e) + + ## this hunk borrowed from pywin32_postinstall.py + # use bdist_wininst builtins to create a shortcut. + # CSIDL_COMMON_PROGRAMS only available works on NT/2000/XP, and + # will fail there if the user has no admin rights. + if get_root_hkey()==_winreg.HKEY_LOCAL_MACHINE: + try: + fldr = get_special_folder_path("CSIDL_COMMON_PROGRAMS") + except OSError: + # No CSIDL_COMMON_PROGRAMS on this platform + fldr = get_special_folder_path("CSIDL_PROGRAMS") + else: + # non-admin install - always goes in this user's start menu. + fldr = get_special_folder_path("CSIDL_PROGRAMS") + + # make Bazaar entry + fldr = os.path.join(fldr, 'Bazaar') + if not os.path.isdir(fldr): + os.mkdir(fldr) + directory_created(fldr) + + # link to documentation + docs = os.path.join(sys.exec_prefix, 'Doc', 'Bazaar', 'index.html') + dst = os.path.join(fldr, 'Documentation.lnk') + create_shortcut(docs, 'Bazaar Documentation', dst) + file_created(dst) + print 'Documentation for Bazaar: Start => Programs => Bazaar' + + # bzr in cmd shell + if os.name == 'nt': + cmd = os.environ.get('COMSPEC', 'cmd.exe') + args = "/K bzr help" + else: + # minimal support of win98 + cmd = os.environ.get('COMSPEC', 'command.com') + args = "bzr help" + dst = os.path.join(fldr, 'Start bzr.lnk') + create_shortcut(cmd, + 'Start bzr in cmd shell', + dst, + args, + os.path.join(sys.exec_prefix, 'Scripts')) + file_created(dst) + + # uninstall shortcut + uninst = os.path.join(sys.exec_prefix, 'Removebzr.exe') + dst = os.path.join(fldr, 'Uninstall Bazaar.lnk') + create_shortcut(uninst, + 'Uninstall Bazaar', + dst, + '-u bzr-wininst.log', + sys.exec_prefix, + ) + file_created(dst) diff --git a/tools/win32/bzr.iss.cog b/tools/win32/bzr.iss.cog new file mode 100644 index 0000000..1b745f1 --- /dev/null +++ b/tools/win32/bzr.iss.cog @@ -0,0 +1,335 @@ +; Script for Inno Setup installer +; [[[cog cog.outl('; This script created by Cog from bzr.iss.cog source') ]]] +; [[[end]]] +; Cog is http://www.nedbatchelder.com/code/cog/ + +[Setup] +AppName=Bazaar + +; [[[cog +; # Python 2.5 compatibility code +; import os +; import sys +; cwd = os.getcwd() +; if cwd not in sys.path: +; sys.path.insert(0, cwd) +; #/Python 2.5 compatibility code +; +; import bzrlib +; try: +; VERSION = bzrlib.__version__ +; AppVerName = 'Bazaar %s' % VERSION +; OutputBaseFilename = 'bzr-%s-setup' % VERSION +; except: +; VERSION = '' +; AppVerName = 'Bazaar' +; OutputBaseFilename = 'bzr-setup' +; +; cog.outl('AppVerName=%s' % AppVerName) +; cog.outl('OutputBaseFilename=%s' % OutputBaseFilename) +; ]]] +AppVerName=Bazaar +OutputBaseFilename=bzr-setup +; [[[end]]] + +DefaultDirName={pf}\Bazaar +DefaultGroupName=Bazaar + +SolidCompression=yes + +OutputDir="..\" +SourceDir="..\..\win32_bzr.exe" + +SetupIconFile="..\bzr.ico" +InfoBeforeFile="..\tools\win32\info.txt" + +VersionInfoCompany="Canonical Ltd." +VersionInfoCopyright="Canonical Ltd., 2005-2008" +VersionInfoDescription="Windows installer for Bazaar" +; [[[cog +; import bzrlib +; version_number = [] +; for i in bzrlib.version_info[:3]: +; try: +; i = int(i) +; except ValueError: +; i = 0 +; version_number.append(i) +; # incremental build number +; from tools.win32.file_version import * +; try: +; version_prev = get_file_version(OutputBaseFilename + '.exe') +; except (FileNotFound, VersionNotAvailable): +; pass +; else: +; if version_number == list(version_prev[:3]): +; version_number.append((version_prev[-1]+1) % 65536) +; version_str = '.'.join(str(i) for i in version_number) +; cog.outl('VersionInfoVersion="%s"' % version_str) +; ]]] +; [[[end]]] + +AppComments="Bazaar: Friendly distributed version control system" +AppPublisher="Canonical Ltd." +AppPublisherURL="http://bazaar.canonical.com" +AppSupportURL="http://wiki.bazaar.canonical.com/BzrSupport" +AppUpdatesURL="http://wiki.bazaar.canonical.com/WindowsDownloads" +; [[[cog cog.outl('AppVersion=%s' % VERSION) ]]] +; [[[end]]] + +ChangesEnvironment=yes +; MARKH: PrivilegesRequired=none means it can't be installed by a non-admin +; user - but sadly we still need admin - eg, tortoise overlays, installing +; into "\Program Files", installing COM objects etc all must be done by an +; admin. +PrivilegesRequired=admin + +[Files] +; Tortoise files - these are at the top as we use 'ExtractTemporaryFile' on +; the TortoiseOverlays MSI, and inno documents such files should be at the +; start for best performance. +; [[[cog +; if "TBZR" in os.environ: # we need a more formal way of controlling this... +; tovmsi32 = os.environ["TORTOISE_OVERLAYS_MSI_WIN32"] # point at the 32bit TortoiseOverlays .msi +; tovmsi64 = os.environ["TORTOISE_OVERLAYS_MSI_X64"] # point at the 64bit TortoiseOverlays .msi +; cog.outl('Source: "%s"; Flags: dontcopy ignoreversion ; Components: tortoise' % tovmsi32) +; cog.outl('Source: "%s"; Flags: dontcopy ignoreversion ; Components: tortoise' % tovmsi64) +; cog.outl('Source: "tbzrcache.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrcachew.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrcommand.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrcommandw.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrtrace.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: debug') +; # Note 'regserver' here appears to run regsvr32 without elevation, which +; # is no good for us - so we have a [run] entry below. +; cog.outl('Source: "tbzr_old.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrshellext_x86.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise') +; cog.outl('Source: "tbzrshellext_x64.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; Components: tortoise; Check: IsWin64;') +; cog.outl(r'Source: "plugins\qbzr\*"; DestDir: "{app}\plugins\qbzr"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; Components: tortoise') +; +; cog.outl('Source: "%s\\doc\\*.html"; DestDir: "{app}\\doc\\tbzr"; Flags: ignoreversion; Components: tortoise' % os.environ['TBZR']) +; ]]] +; [[[end]]] + +; We can't say '*.*' due to optional components. +Source: "plugins\*.*"; DestDir: "{app}\\plugins"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; Components: plugins +Source: "*.bat"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; +Source: "*.url"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; +Source: "msvc*.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; +Source: "bz*.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; +Source: "Python*.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace uninsrestartdelete; +Source: "lib\*.*"; DestDir: "{app}\lib"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; +Source: "doc\*.*"; DestDir: "{app}\doc"; Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete; +; [[[cog +; try: +; import pycurl +; except ImportError: +; ca_path = None +; else: +; supported = pycurl.version_info()[8] +; if 'https' in supported: +; from bzrlib.transport.http.ca_bundle import get_ca_path +; ca_path = get_ca_path() +; if ca_path: +; cog.outl('Source: "%s"; DestDir: "{app}"; Components: cabundle' % ca_path) +; else: +; cog.msg('You have pycurl with SSL support, ' +; 'but CA Bundle (curl-ca-bundle.crt) not found!') +; ]]] +; [[[end]]] + +; imageformats plugins for PyQt4 +; [[[cog +; plug_dir = os.path.join(os.path.dirname(cog.inFile), # $(bzr_src_root)/tools/win32 +; '..', '..', 'win32_bzr.exe', 'imageformats') +; if os.path.isdir(plug_dir): +; cog.outl('Source: "imageformats\\*.*"; DestDir: "{app}\\imageformats"; ' +; 'Flags: createallsubdirs ignoreversion recursesubdirs restartreplace uninsrestartdelete;') +; else: +; cog.msg('imageformats plugins for PyQt4 not found') +; ]]] +; [[[end]]] + +[Types] +Name: "typical"; Description: "A typical installation" +Name: "full"; Description: "Full Installation (typical installation plus test utilities)" +Name: "compact"; Description: "Compact installation" +Name: "custom"; Description: "Custom installation"; Flags: iscustom + +[Components] +Name: "main"; Description: "Main Files"; Types: full typical compact custom; Flags: fixed +Name: "plugins"; Description: "Default plugins"; Types: full typical custom; +; [[[cog +; if ca_path: +; cog.outl('Name: "cabundle"; ' +; 'Description: "CA certificates for SSL support"; ' +; 'Types: full typical custom') +; if "TBZR" in os.environ: # we need a more formal way of controlling this... +; cog.outl('Name: "tortoise"; Description: "Windows Shell Extensions (TortoiseBZR)"; Types: full typical custom;') +; cog.outl('Name: "debug"; Description: "Test, diagnostic and debugging utilities"; Types: full custom;') +; +; ]]] +; [[[end]]] + +[Dirs] +Name: "{userappdata}\bazaar\2.0" +Name: "{app}\plugins"; Flags: uninsalwaysuninstall + + +[Icons] +Name: "{group}\Documentation index"; Filename: "{app}\doc\index.html"; WorkingDir: "{app}\doc"; +Name: "{group}\Bazaar Home Page"; Filename: "{app}\bazaar.url"; Comment: "http://bazaar.canonical.com"; +Name: "{group}\Start Bzr in cmd shell"; Filename: "{cmd}"; Parameters: "/K start_bzr.bat"; WorkingDir: "{app}"; IconFilename: "{app}\bzr.exe"; Comment: "Open new Bzr session"; +; NOTE: Intent is to change the log file location - the line below will need to change to reflect that. +Name: "{group}\Open Bzr log file"; Filename: "notepad.exe"; Parameters: "{userdocs}\.bzr.log"; Comment: "Launch notepad to view the bzr log file"; + +; [[[cog +; if "TBZR" in os.environ: +; cog.outl(r'Name: "{group}\TortoiseBZR documentation"; Filename: "{app}\doc\tbzr\index.html"; Comment: "Launch TortoiseBZR documentation";') +; ]]] +; [[[end]]] +; No Uninstall here - Control Panel will do + + +[Tasks] +Name: Path; Description: "Add {app} directory to PATH environment variable" +; [[[cog +; if "TBZR" in os.environ: +; cog.outl('Name: TBZRReadme; Description: "View the TortoiseBZR Readme"; Components: tortoise') +; ]]] +; [[[end]]] + + +[Registry] +Root: HKLM; Subkey: "SOFTWARE\Bazaar"; Flags: noerror uninsdeletekey +Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "InstallPath"; ValueType: string; ValueData: "{app}"; Flags: noerror +; Don't write stuff that can be implied +;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "BzrlibPath"; ValueType: string; ValueData: "{app}\lib\library.zip\bzrlib"; Flags: noerror +;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "PluginsPath"; ValueType: string; ValueData: "{app}\plugins"; Flags: noerror +;Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "PythonPath"; ValueType: string; ValueData: "{app}\lib\library.zip"; Flags: noerror +; [[[cog cog.outl('Root: HKLM; Subkey: "SOFTWARE\Bazaar"; ValueName: "Version"; ValueType: string; ValueData: "%s"; Flags: noerror' % VERSION) ]]] +; [[[end]]] + + +[Run] +Filename: "{app}\bzr_postinstall.exe"; Parameters: "--start-bzr"; Flags: skipifdoesntexist runhidden; +Filename: "{app}\bzr_postinstall.exe"; Parameters: "--add-path"; Tasks: Path; Flags: skipifdoesntexist skipifsilent runhidden; +; [[[cog +; if "TBZR" in os.environ: +; cog.outl('Filename: "regsvr32.exe"; Parameters: "/s /i: /n tbzrshellext_x86.dll"; WorkingDir: "{app}"; Components: tortoise; Description: "Registering Tortoise"; StatusMsg: "Registering Tortoise"') +; cog.outl('Filename: "regsvr32.exe"; Parameters: "/s /i: /n tbzrshellext_x64.dll"; WorkingDir: "{app}"; Components: tortoise; Description: "Registering Tortoise"; StatusMsg: "Registering Tortoise"; Check: IsWin64') +; cog.outl(r'Filename: "{app}\doc\tbzr\index.html"; Tasks: TBZRReadme; Flags: shellexec') +; ]]] +; [[[end]]] + + +[UninstallRun] +Filename: "{app}\bzr_postinstall.exe"; Parameters: "--delete-path --delete-shell-menu --silent"; Flags: skipifdoesntexist runhidden; +; [[[cog +; if "TBZR" in os.environ: +; cog.outl('Filename: "regsvr32.exe"; Parameters: "/u /s /i: tbzrshellext_x86.dll"; WorkingDir: "{app}"; Components: tortoise; StatusMsg: "Unregistering Tortoise"; Flags: skipifdoesntexist') +; cog.outl('Filename: "regsvr32.exe"; Parameters: "/u /s /i: tbzrshellext_x64.dll"; WorkingDir: "{app}"; Components: tortoise; StatusMsg: "Unregistering Tortoise"; Flags: skipifdoesntexist; Check: IsWin64') +; ]]] +; [[[end]]] + + +[Code] +const + SHCNF_IDLIST = $0000; + SHCNE_ASSOCCHANGED = $08000000; + WM_QUIT = 18; + MOVEFILE_DELAY_UNTIL_REBOOT = 4; + +procedure SHChangeNotify(wEventId, uFlags, dwItem1, dwItem2: Integer); + external 'SHChangeNotify@shell32.dll stdcall'; + +function MoveFileEx(lpExistingFileName, lpNewFileName: String; dwFlags: Cardinal): Integer; + external 'MoveFileExA@kernel32.dll stdcall'; + +procedure DeleteFileNowOrLater(filename: string); +var + rc : Integer; +begin + if FileExists(filename) and not DeleteFile(filename) then + // can't work out to pass NULL to the API, but an empty string + // seems to work OK. + MoveFileEx(filename, '', MOVEFILE_DELAY_UNTIL_REBOOT); +end; + +procedure ShutdownTBZR; +var + hwnd: HWND; +begin +// [[[cog +// if "TBZR" not in os.environ: +// cog.outl(' Exit; // No TSVN set - exit this procedure.') +// ]]] +// [[[end]]] + // ask the cache process to shut-down. + hwnd := FindWindowByClassName('TBZRCache_Taskbar'); + if hwnd <> 0 then + PostMessage(hwnd, WM_QUIT, 1, 0); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + S, tovmsi, fqtovmsi, params: String; + ErrorCode: Integer; +begin + if CurStep=ssInstall then begin + ShutdownTBZR; + // In case the user hasn't uninstalled the old version before + // upgrading, we unregister and delete some obsolete files + // (regsvr32 remains silent even if the file doesn't exist) + Exec('regsvr32.exe', '/s /u "' + ExpandConstant('{app}\tbzr.dll') + '"', + '', SW_HIDE, ewWaitUntilTerminated, ErrorCode); + DeleteFileNowOrLater(ExpandConstant('{app}\tbzr.dll')); + DeleteFileNowOrLater(ExpandConstant('{app}\tbzrtest.exe')); + DeleteFileNowOrLater(ExpandConstant('{app}\tbzr_tracer.exe')); + end; + + if CurStep=ssPostInstall then begin + // a couple of post-install tasks + if IsComponentSelected('tortoise') then begin + // Need to execute: + // msiexec /i TortoiseOverlays-1.X.X.XXXX-win32.msi /qn /norestart +// 64bit notes: +// We are still primarily a 32bit application - the only 64bit binary is the +// shell extension, but even then, we need to install the 32bit version too. +// Thus, we keep tortoise in 32bit "install mode" - meaning we are installed +// to "\Program Files (x86)". We don't bother trying to install our single +// 64bit DLL into "\Program Files" - we use a different DLL name for 32 and +// 64 bit versions, so nothing will conflict. +// Note however that on a 64bit OS, we only need the 64bit TortoiseOverlays - +// the 32bit apps using shell extensions still work fine with that. +// [[[cog +// if "TBZR" in os.environ: +// import os +// cog.outl("if IsWin64 then") +// cog.outl(" tovmsi := '%s'" % os.path.basename(os.environ["TORTOISE_OVERLAYS_MSI_X64"])) +// cog.outl("else") +// cog.outl(" tovmsi := '%s'" % os.path.basename(os.environ["TORTOISE_OVERLAYS_MSI_WIN32"])) +// else: +// cog.outl("tovmsi := '';") +// ]]] +// [[[end]]] + ExtractTemporaryFile(tovmsi); + fqtovmsi := AddBackslash(ExpandConstant('{tmp}')) + tovmsi; + params := '/i "' + fqtovmsi + '" /qn /norestart'; + if not ShellExec('', 'msiexec.exe', params, '', SW_HIDE, + ewWaitUntilTerminated, ErrorCode) then + MsgBox('Failed to install TortoiseOverlays: ' + SysErrorMessage(ErrorCode), + mbInformation, MB_OK); + // Ideally we could be bzr_postinstall.exe this way too, but + // its needed at uninstall time. + end; + // cause explorer to re-fetch handlers. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0); + end; +end; + + +function InitializeUninstall(): Boolean; +begin + ShutdownTBZR; + result := True; +end; diff --git a/tools/win32/bzr_postinstall.py b/tools/win32/bzr_postinstall.py new file mode 100644 index 0000000..399232f --- /dev/null +++ b/tools/win32/bzr_postinstall.py @@ -0,0 +1,356 @@ +# Copyright (C) 2006, 2007, 2009, 2010 by Canonical Ltd +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""bzr postinstall helper for win32 installation +Written by Alexander Belchenko + +Dependency: ctypes +""" + +import os +import shutil +import sys + + +## +# CONSTANTS + +VERSION = "1.5.20070131" + +USAGE = """Bzr postinstall helper for win32 installation +Usage: %s [options] + +OPTIONS: + -h, --help - help message + -v, --version - version info + + -n, --dry-run - print actions rather than execute them + -q, --silent - no messages for user + + --start-bzr - update start_bzr.bat + --add-path - add bzr directory to environment PATH + --delete-path - delete bzr directory to environment PATH + --add-shell-menu - add shell context menu to start bzr session + --delete-shell-menu - delete context menu from shell + --check-mfc71 - check if MFC71.DLL present in system +""" % os.path.basename(sys.argv[0]) + +# Windows version +_major,_minor,_build,_platform,_text = sys.getwindowsversion() +# from MSDN: +# dwPlatformId +# The operating system platform. +# This member can be one of the following values. +# ========================== ====================================== +# Value Meaning +# -------------------------- -------------------------------------- +# VER_PLATFORM_WIN32_NT The operating system is Windows Vista, +# 2 Windows Server "Longhorn", +# Windows Server 2003, Windows XP, +# Windows 2000, or Windows NT. +# +# VER_PLATFORM_WIN32_WINDOWS The operating system is Windows Me, +# 1 Windows 98, or Windows 95. +# ========================== ====================================== +if _platform == 2: + winver = 'Windows NT' +else: + # don't care about real Windows name, just to force safe operations + winver = 'Windows 98' + + +## +# INTERNAL VARIABLES + +(OK, ERROR) = range(2) +VERSION_FORMAT = "%-50s%s" + + +def main(): + import ctypes + import getopt + import re + import _winreg + + import locale + user_encoding = locale.getpreferredencoding() or 'ascii' + + import ctypes + + hkey_str = {_winreg.HKEY_LOCAL_MACHINE: 'HKEY_LOCAL_MACHINE', + _winreg.HKEY_CURRENT_USER: 'HKEY_CURRENT_USER', + _winreg.HKEY_CLASSES_ROOT: 'HKEY_CLASSES_ROOT', + } + + dry_run = False + silent = False + start_bzr = False + add_path = False + delete_path = False + add_shell_menu = False + delete_shell_menu = False + check_mfc71 = False + + try: + opts, args = getopt.getopt(sys.argv[1:], "hvnq", + ["help", "version", + "dry-run", + "silent", + "start-bzr", + "add-path", + "delete-path", + "add-shell-menu", + "delete-shell-menu", + "check-mfc71", + ]) + + for o, a in opts: + if o in ("-h", "--help"): + print USAGE + return OK + elif o in ("-v", "--version"): + print VERSION_FORMAT % (USAGE.splitlines()[0], VERSION) + return OK + + elif o in ('-n', "--dry-run"): + dry_run = True + elif o in ('-q', '--silent'): + silent = True + + elif o == "--start-bzr": + start_bzr = True + elif o == "--add-path": + add_path = True + elif o == "--delete-path": + delete_path = True + elif o == "--add-shell-menu": + add_shell_menu = True + elif o == "--delete-shell-menu": + delete_shell_menu = True + elif o == "--check-mfc71": + check_mfc71 = True + + except getopt.GetoptError, msg: + print str(msg) + print USAGE + return ERROR + + # message box from Win32API + MessageBoxA = ctypes.windll.user32.MessageBoxA + MB_OK = 0 + MB_ICONERROR = 16 + MB_ICONEXCLAMATION = 48 + + bzr_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + + if start_bzr: + fname = os.path.join(bzr_dir, "start_bzr.bat") + if os.path.isfile(fname): + f = file(fname, "r") + content = f.readlines() + f.close() + else: + content = ["bzr.exe help\n"] + + for ix in xrange(len(content)): + s = content[ix] + if re.match(r'.*(?<!\\)bzr\.exe([ "].*)?$', + s.rstrip('\r\n'), + re.IGNORECASE): + content[ix] = s.replace('bzr.exe', + '"%s"' % os.path.join(bzr_dir, + 'bzr.exe')) + elif s.find(r'C:\Program Files\Bazaar') != -1: + content[ix] = s.replace(r'C:\Program Files\Bazaar', + bzr_dir) + + if dry_run: + print "*** Write file: start_bzr.bat" + print "*** File content:" + print ''.join(content) + else: + f = file(fname, 'w') + f.write(''.join(content)) + f.close() + + if (add_path or delete_path) and winver == 'Windows NT': + # find appropriate registry key: + # 1. HKLM\System\CurrentControlSet\Control\SessionManager\Environment + # 2. HKCU\Environment + keys = ((_winreg.HKEY_LOCAL_MACHINE, (r'System\CurrentControlSet\Control' + r'\Session Manager\Environment')), + (_winreg.HKEY_CURRENT_USER, r'Environment'), + ) + + hkey = None + for key, subkey in keys: + try: + hkey = _winreg.OpenKey(key, subkey, 0, _winreg.KEY_ALL_ACCESS) + try: + path_u, type_ = _winreg.QueryValueEx(hkey, 'Path') + except WindowsError: + if key != _winreg.HKEY_CURRENT_USER: + _winreg.CloseKey(hkey) + hkey = None + continue + else: + path_u = u'' + type_ = _winreg.REG_SZ + except EnvironmentError: + continue + break + + if hkey is None: + print "Cannot find appropriate registry key for PATH" + else: + path_list = [i for i in path_u.split(os.pathsep) if i != ''] + f_change = False + for ix, item in enumerate(path_list[:]): + if item == bzr_dir: + if delete_path: + del path_list[ix] + f_change = True + elif add_path: + print "*** Bzr already in PATH" + break + else: + if add_path and not delete_path: + path_list.append(bzr_dir.decode(user_encoding)) + f_change = True + + if f_change: + path_u = os.pathsep.join(path_list) + if dry_run: + print "*** Registry key %s\\%s" % (hkey_str[key], subkey) + print "*** Modify PATH variable. New value:" + print path_u + else: + _winreg.SetValueEx(hkey, 'Path', 0, type_, path_u) + _winreg.FlushKey(hkey) + + if not hkey is None: + _winreg.CloseKey(hkey) + + if (add_path or delete_path) and winver == 'Windows 98': + # mutating autoexec.bat + # adding or delete string: + # SET PATH=%PATH%;C:\PROGRA~1\Bazaar + abat = 'C:\\autoexec.bat' + abak = 'C:\\autoexec.bak' + + def backup_autoexec_bat(name, backupname, dry_run): + # backup autoexec.bat + if os.path.isfile(name): + if not dry_run: + shutil.copyfile(name, backupname) + else: + print '*** backup copy of autoexec.bat created' + + GetShortPathName = ctypes.windll.kernel32.GetShortPathNameA + buf = ctypes.create_string_buffer(260) + if GetShortPathName(bzr_dir, buf, 260): + bzr_dir_8_3 = buf.value + else: + bzr_dir_8_3 = bzr_dir + pattern = 'SET PATH=%PATH%;' + bzr_dir_8_3 + + # search pattern + f = file(abat, 'r') + lines = f.readlines() + f.close() + found = False + for i in lines: + if i.rstrip('\r\n') == pattern: + found = True + break + + if delete_path and found: + backup_autoexec_bat(abat, abak, dry_run) + if not dry_run: + f = file(abat, 'w') + for i in lines: + if i.rstrip('\r\n') != pattern: + f.write(i) + f.close() + else: + print '*** Remove line <%s> from autoexec.bat' % pattern + + elif add_path and not found: + backup_autoexec_bat(abat, abak, dry_run) + if not dry_run: + f = file(abat, 'a') + f.write(pattern) + f.write('\n') + f.close() + else: + print '*** Add line <%s> to autoexec.bat' % pattern + + if add_shell_menu and not delete_shell_menu: + hkey = None + try: + hkey = _winreg.CreateKey(_winreg.HKEY_CLASSES_ROOT, + r'Folder\shell\bzr') + except EnvironmentError: + if not silent: + MessageBoxA(None, + 'Unable to create registry key for context menu', + 'EnvironmentError', + MB_OK | MB_ICONERROR) + + if not hkey is None: + _winreg.SetValue(hkey, '', _winreg.REG_SZ, 'Bzr Here') + hkey2 = _winreg.CreateKey(hkey, 'command') + _winreg.SetValue(hkey2, '', _winreg.REG_SZ, + '%s /K "%s"' % ( + os.environ.get('COMSPEC', '%COMSPEC%'), + os.path.join(bzr_dir, 'start_bzr.bat'))) + _winreg.CloseKey(hkey2) + _winreg.CloseKey(hkey) + + if delete_shell_menu: + try: + _winreg.DeleteKey(_winreg.HKEY_CLASSES_ROOT, + r'Folder\shell\bzr\command') + except EnvironmentError: + pass + + try: + _winreg.DeleteKey(_winreg.HKEY_CLASSES_ROOT, + r'Folder\shell\bzr') + except EnvironmentError: + pass + + if check_mfc71: + try: + ctypes.windll.LoadLibrary('mfc71.dll') + except WindowsError: + MessageBoxA(None, + ("Library MFC71.DLL is not found on your system.\n" + "This library needed for SFTP transport.\n" + "If you need to work via SFTP you should download\n" + "this library manually and put it to directory\n" + "where Bzr installed.\n" + "For detailed instructions see:\n" + "http://wiki.bazaar.canonical.com/BzrOnPureWindows" + ), + "Warning", + MB_OK | MB_ICONEXCLAMATION) + + return OK + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/win32/file_version.py b/tools/win32/file_version.py new file mode 100644 index 0000000..91cc32d --- /dev/null +++ b/tools/win32/file_version.py @@ -0,0 +1,38 @@ +#!/usr/bin/python + +"""Get file version. +Written by Alexander Belchenko, 2006 +""" + +import os + +import pywintypes # from pywin32 (http://pywin32.sf.net) +import win32api # from pywin32 (http://pywin32.sf.net) + + +__all__ = ['get_file_version', 'FileNotFound', 'VersionNotAvailable'] +__docformat__ = "restructuredtext" + + +class FileNotFound(Exception): + pass + +class VersionNotAvailable(Exception): + pass + + +def get_file_version(filename): + """Get file version (windows properties) + :param filename: path to file + :return: 4-tuple with 4 version numbers + """ + if not os.path.isfile(filename): + raise FileNotFound + + try: + version_info = win32api.GetFileVersionInfo(filename, '\\') + except pywintypes.error: + raise VersionNotAvailable + + return (divmod(version_info['FileVersionMS'], 65536) + + divmod(version_info['FileVersionLS'], 65536)) diff --git a/tools/win32/info.txt b/tools/win32/info.txt new file mode 100644 index 0000000..cafc192 --- /dev/null +++ b/tools/win32/info.txt @@ -0,0 +1,6 @@ +Bazaar is free software; you can redistribute and/or modify it under +the terms of the GNU General Public License version 2. + +See: http://www.gnu.org/copyleft/gpl.html + +Managing source code in Bazaar does not make it subject to the GPL. diff --git a/tools/win32/ostools.py b/tools/win32/ostools.py new file mode 100644 index 0000000..3d7ac5e --- /dev/null +++ b/tools/win32/ostools.py @@ -0,0 +1,138 @@ +#!/usr/bin/python + +"""Cross-platform os tools: files/directories manipulations +Usage: + + ostools.py help + prints this help + + ostools.py copytodir FILES... DIR + copy files to specified directory + + ostools.py copytree FILES... DIR + copy files to specified directory keeping relative paths + + ostools.py remove [FILES...] [DIRS...] + remove files or directories (recursive) +""" + +import glob +import os +import shutil +import sys + +def makedir(dirname): + if not os.path.exists(dirname): + os.makedirs(dirname) + if not os.path.isdir(dirname): + print "Error: Destination is not a directory", dirname + return 2 + return 0 + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + if not argv: + argv = ['help'] + + cmd = argv.pop(0) + + if cmd == 'help': + print __doc__ + return 0 + + if cmd == 'copytodir': + if len(argv) < 2: + print "Usage: ostools.py copytodir FILES... DIR" + return 1 + + todir = argv.pop() + retcode = makedir(todir) + if retcode: + return retcode + + files = [] + for possible_glob in argv: + files += glob.glob(possible_glob) + + for src in files: + dest = os.path.join(todir, os.path.basename(src)) + shutil.copy(src, dest) + print "Copied:", src, "=>", dest + + return 0 + + if cmd == 'copytree': + if len(argv) < 2: + print "Usage: ostools.py copytree FILES... DIR" + return 1 + + todir = argv.pop() + retcode = makedir(todir) + if retcode: + return retcode + + files = [] + for possible_glob in argv: + files += glob.glob(possible_glob) + + for src in files: + relative_path = src + dest = os.path.join(todir, relative_path) + dest_dir = os.path.dirname(dest) + retcode = makedir(dest_dir) + if retcode: + return retcode + shutil.copy(src, dest) + print "Copied:", src, "=>", dest + + return 0 + + if cmd == 'remove': + if len(argv) == 0: + print "Usage: ostools.py remove [FILES...] [DIRS...]" + return 1 + + filesdirs = [] + for possible_glob in argv: + filesdirs += glob.glob(possible_glob) + + for i in filesdirs: + if os.path.isdir(i): + shutil.rmtree(i) + print "Removed:", i + elif os.path.isfile(i): + os.remove(i) + print "Removed:", i + else: + print "Not found:", i + + return 0 + + if cmd == "basename": + if len(argv) == 0: + print "Usage: ostools.py basename [PATH | URL]" + return 1 + + for path in argv: + print os.path.basename(path) + return 0 + + if cmd == 'makedir': + if len(argv) == 0: + print "Usage: ostools.py makedir DIR" + return 1 + + retcode = makedir(argv.pop()) + if retcode: + return retcode + return 0 + + print "Usage error" + print __doc__ + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/win32/py2exe_boot_common.py b/tools/win32/py2exe_boot_common.py new file mode 100644 index 0000000..b7af31a --- /dev/null +++ b/tools/win32/py2exe_boot_common.py @@ -0,0 +1,40 @@ +# Common py2exe boot script - executed for all target types. + +# In the standard py2exe boot script, it setup stderr so that anything written +# to it will be written to exe.log, and a message dialog is shown. +# For Bazaar, we log most things to .bzr.log, and there are many things that +# write to stderr, that are not errors, and so we don't want the py2exe dialog +# message, So also blackhole stderr. + +import sys +if sys.frozen == "windows_exe": + class Blackhole(object): + softspace = 0 + def write(self, text): + pass + def flush(self): + pass + sys.stdout = Blackhole() + sys.stderr = Blackhole() + del Blackhole + +# add more directories to sys.path to allow "installing" third-party libs +# required by some plugins (see bug #743256) +import os +sys.path.append(os.path.join(os.path.dirname(sys.executable), 'site-packages')) +del os +del sys + +# Disable linecache.getline() which is called by +# traceback.extract_stack() when an exception occurs to try and read +# the filenames embedded in the packaged python code. This is really +# annoying on windows when the d: or e: on our build box refers to +# someone elses removable or network drive so the getline() call +# causes it to ask them to insert a disk in that drive. +import linecache +def fake_getline(filename, lineno, module_globals=None): + return '' +linecache.orig_getline = linecache.getline +linecache.getline = fake_getline + +del linecache, fake_getline diff --git a/tools/win32/run_script.py b/tools/win32/run_script.py new file mode 100644 index 0000000..9e6de84 --- /dev/null +++ b/tools/win32/run_script.py @@ -0,0 +1,16 @@ +# A utility that executes a script from our %PYTHON%\Scripts directory. +# Example usage: +# 'python run_script.py cog.py arg1 arg2' +# which will locate %PYTHON_HOME%/Scripts/cog.py and execute it with the args. +# This is only necessary for Windows, and only when the build process is +# executed via a cygwin/*nix based make utility, which doesn't honor the +# PATHEXT environment variable. +import sys +import os + +if __name__ == '__main__': + # clobber me, new sys.argv[0] is the script to run. + del sys.argv[0] + assert not os.path.isabs(sys.argv[0]), "If you know the FQ path, just use it!" + sys.argv[0] = os.path.join(sys.prefix, "Scripts", sys.argv[0]) + execfile(sys.argv[0]) diff --git a/tools/win32/start_bzr.bat b/tools/win32/start_bzr.bat new file mode 100644 index 0000000..2f96910 --- /dev/null +++ b/tools/win32/start_bzr.bat @@ -0,0 +1,30 @@ +@ECHO OFF
+
+REM ******************************************************
+REM ** You can change following environment variables **
+REM ** that affects on bzr behaviour **
+REM ******************************************************
+
+REM Add the Bzr directory to system-wide PATH environment variable
+SET PATH=C:\Program Files\Bazaar;%PATH%
+
+REM Change next line to set-up e-mail to identify yourself in bzr
+REM SET BZREMAIL=
+
+REM Change next line to specify editor to edit commit messages
+REM SET BZR_EDITOR=
+
+REM Change next line to tell where bzr should search for plugins
+REM SET BZR_PLUGIN_PATH=
+
+REM Change next line to use another home directory with bzr
+REM SET BZR_HOME=
+
+REM Change next line to control verbosity of .bzr.log
+REM SET BZR_DEBUG=30
+
+
+REM --------------------------------------------------------------------------
+
+@ECHO ON
+@bzr.exe help
|