diff --git a/backportpackage b/backportpackage index f91ab5f..ae5bb6a 100755 --- a/backportpackage +++ b/backportpackage @@ -30,7 +30,7 @@ from debian.deb822 import Dsc import launchpadlib.launchpad import lsb_release -from ubuntutools.config import get_value, ubu_email +from ubuntutools.config import UDTConfig, ubu_email from ubuntutools.builder import getBuilder from ubuntutools.logger import Logger from ubuntutools.question import YesNoQuestion @@ -71,12 +71,12 @@ def parse(args): help='Build the package before uploading (default: %default)') p.add_option('-B', '--builder', dest='builder', - default=get_value('BUILDER'), - help='Specify the package builder (default: %default)', + default=None, + help='Specify the package builder (default: pbuilder)', metavar='BUILDER') p.add_option('-U', '--update', dest='update', - default=get_value('UPDATE_BUILDER'), + default=False, action='store_true', help='Update the build environment before attempting to build') p.add_option('-u', '--upload', @@ -95,23 +95,32 @@ def parse(args): metavar='VERSION') p.add_option('-w', '--workdir', dest='workdir', - default=get_value('WORKDIR'), + default=None, help='Specify a working directory (default: temporary dir)', metavar='WORKDIR') p.add_option('-l', '--launchpad', dest='launchpad', - default=get_value('LPINSTANCE'), - help='Launchpad instance to connect to (default: %default)', + default=None, + help='Launchpad instance to connect to (default: production)', metavar='INSTANCE') p.add_option('--no-conf', '--noconf', - dest='no_configuration', + dest='no_conf', default=False, - help="Don't read config files, must be the first option given", + help="Don't read config files or environment variables", action='store_true') opts, args = p.parse_args(args) if len(args) != 1: p.error('You must specify a single source package or a .dsc URL/path.') + config = UDTConfig(opts.no_conf) + if opts.builder is None: + opts.builder = config.get_value('BUILDER') + if not opts.update: + opts.update = config.get_value('UPDATE_BUILDER') + if opts.workdir is None: + opts.workdir = config.get_value('WORKDIR') + if opts.launchpad is None: + opts.launchpad = config.get_value('LPINSTANCE') if not opts.upload and not opts.workdir: p.error('Please specify either a working dir or an upload target!') @@ -239,7 +248,6 @@ def do_backport(workdir, package, dscfile, version, suffix, release, build, bp_version = get_backport_version(version, suffix, upload, release) bp_dist = get_backport_dist(upload, release) - ubu_email() check_call(['dch', '--force-bad-version', '--preserve', @@ -262,6 +270,7 @@ def do_backport(workdir, package, dscfile, version, suffix, release, build, def main(args): os.environ['DEB_VENDOR'] = 'Ubuntu' + ubu_email() opts, (package_or_dsc,) = parse(args[1:]) diff --git a/debian/copyright b/debian/copyright index 1c29932..bcc6f85 100644 --- a/debian/copyright +++ b/debian/copyright @@ -189,7 +189,6 @@ Files: sponsor-patch, suspicious-source, ubuntutools/builder.py, - ubuntutools/common.py, ubuntutools/config.py, ubuntutools/logger.py, ubuntutools/question.py, diff --git a/ubuntutools/builder.py b/ubuntutools/builder.py index 8b581cf..c677613 100644 --- a/ubuntutools/builder.py +++ b/ubuntutools/builder.py @@ -22,7 +22,6 @@ import os import subprocess -from ubuntutools.config import get_value from ubuntutools.logger import Logger class Builder(object): @@ -145,10 +144,7 @@ class Sbuild(Builder): return 0 -def getBuilder(builder=None): - if not builder: - builder = get_value('BUILDER') - +def getBuilder(builder='pbuilder'): if builder == 'pbuilder': return Pbuilder() elif builder == 'pbuilder-dist': diff --git a/ubuntutools/common.py b/ubuntutools/common.py deleted file mode 100644 index 34667aa..0000000 --- a/ubuntutools/common.py +++ /dev/null @@ -1,25 +0,0 @@ -# common.py - provides functions which are commonly used by the -# ubuntu-dev-tools package. -# -# Copyright (C) 2010, Stefano Rivera -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -def memoize_noargs(func): - "Simple memoization wrapper, for functions without arguments" - func.cache = None - def wrapper(): - if func.cache is None: - func.cache = func() - return func.cache - return wrapper diff --git a/ubuntutools/config.py b/ubuntutools/config.py index 1f983cc..1161dff 100644 --- a/ubuntutools/config.py +++ b/ubuntutools/config.py @@ -22,65 +22,70 @@ import re import socket import sys -from ubuntutools.common import memoize_noargs +class UDTConfig(object): -defaults = { - 'BUILDER': 'pbuilder', - 'UPDATE_BUILDER': False, - 'LPINSTANCE': 'production', -} + defaults = { + 'BUILDER': 'pbuilder', + 'UPDATE_BUILDER': False, + 'LPINSTANCE': 'production', + } -@memoize_noargs -def get_devscripts_config(): - """Read the devscripts configuration files, and return the values as a - dictionary - """ - config = {} - var_re = re.compile(r'^\s*([A-Z_]+?)=(.+?)\s*$') - for fn in ('/etc/devscripts.conf', '~/.devscripts'): - f = open(os.path.expanduser(fn), 'r') - for line in f: - m = var_re.match(line) - if m: - value = m.group(2) - # This isn't quite the same as bash's parsing, but - # mostly-compatible for configuration files that aren't broken - # like this: KEY=foo bar - if (len(value) > 2 and value[0] == value[-1] - and value[0] in ("'", '"')): - value = value[1:-1] - config[m.group(1)] = value - f.close() - return config + def __init__(self, no_conf=False, prefix=None): -def get_value(key, default=None, prefix=None, compat_keys=[]): - """Retrieve a value from the environment or configuration files. - keys are prefixed with the script name + _, or prefix. + self.no_conf = no_conf + if prefix is None: + prefix = os.path.basename(sys.argv[0]).upper().replace('-', '_') + self.prefix = prefix + if not no_conf: + self.config = self.parse_devscripts_config() - Store Priority: Environment variables, user config file, system config file - Variable Priority: PREFIX_KEY, UBUNTUTOOLS_KEY, compat_keys + def parse_devscripts_config(self): + """Read the devscripts configuration files, and return the values as a + dictionary + """ + config = {} + var_re = re.compile(r'^\s*([A-Z_]+?)=(.+?)\s*$') + for fn in ('/etc/devscripts.conf', '~/.devscripts'): + f = open(os.path.expanduser(fn), 'r') + for line in f: + m = var_re.match(line) + if m: + value = m.group(2) + # This isn't quite the same as bash's parsing, but + # mostly-compatible for configuration files that aren't + # broken like this: KEY=foo bar + if (len(value) > 2 and value[0] == value[-1] + and value[0] in ("'", '"')): + value = value[1:-1] + config[m.group(1)] = value + f.close() + return config - Historical variable names can be supplied via compat_keys, no prefix is - applied to them. - """ - if default is None and key in defaults: - default = defaults[key] - if len(sys.argv) > 1 and sys.argv[1] in ('--no-conf', '--noconf'): + def get_value(self, key, default=None, compat_keys=[]): + """Retrieve a value from the environment or configuration files. + keys are prefixed with the script name, falling back to UBUNTUTOOLS for + package-wide keys. + + Store Priority: Environment variables, user conf, system conf + Variable Priority: PREFIX_KEY, UBUNTUTOOLS_KEY, compat_keys + + Historical variable names can be supplied via compat_keys, no prefix is + applied to them. + """ + if default is None and key in self.defaults: + default = self.defaults[key] + + keys = [self.prefix + '_' + key, 'UBUNTUTOOLS_' + key] + compat_keys + + for store in (os.environ, self.config): + for k in keys: + if k in store: + value = store[k] + if value in ('yes', 'no'): + value = value == 'yes' + return value return default - if prefix is None: - prefix = os.path.basename(sys.argv[0]).upper().replace('-', '_') + '_' - - keys = [prefix + key, 'UBUNTUTOOLS_' + key] + compat_keys - - for store in (os.environ, get_devscripts_config()): - for k in keys: - if k in store: - value = store[k] - if value in ('yes', 'no'): - value = value == 'yes' - return value - return default def ubu_email(name=None, email=None, export=True): """Find the developer's Ubuntu e-mail address, and export it in diff --git a/ubuntutools/test/test_common.py b/ubuntutools/test/test_common.py deleted file mode 100644 index 7e9a2fd..0000000 --- a/ubuntutools/test/test_common.py +++ /dev/null @@ -1,35 +0,0 @@ -# test_common.py - Test suite for ubuntutools.common -# -# Copyright (C) 2010, Stefano Rivera -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -from ubuntutools.test import unittest -from ubuntutools.common import memoize_noargs - -class MemoizeTestCase(unittest.TestCase): - def test_memoize_noargs(self): - global run_count - run_count = 0 - - @memoize_noargs - def test_func(): - global run_count - run_count += 1 - return 42 - - self.assertEqual(run_count, 0) - self.assertEqual(test_func(), 42) - self.assertEqual(run_count, 1) - self.assertEqual(test_func(), 42) - self.assertEqual(run_count, 1) diff --git a/ubuntutools/test/test_config.py b/ubuntutools/test/test_config.py new file mode 100644 index 0000000..0d167bc --- /dev/null +++ b/ubuntutools/test/test_config.py @@ -0,0 +1,169 @@ +# test_config.py - Test suite for ubuntutools.config +# +# Copyright (C) 2010, Stefano Rivera +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +import os +import os.path +from StringIO import StringIO + +import ubuntutools.config +from ubuntutools.config import UDTConfig, ubu_email +from ubuntutools.test import unittest + +config_files = { + 'system': '', + 'user': '', +} + +def fake_open(filename, mode='r'): + if mode != 'r': + raise IOError("Read only fake-file") + files = { + '/etc/devscripts.conf': config_files['system'], + os.path.expanduser('~/.devscripts'): config_files['user'], + } + if filename not in files: + raise IOError("No such file or directory: '%s'" % filename) + return StringIO(files[filename]) + + +class ConfigTestCase(unittest.TestCase): + def setUp(self): + ubuntutools.config.open = fake_open + self.cleanEnvironment() + + def tearDown(self): + del ubuntutools.config.open + self.cleanEnvironment() + + def cleanEnvironment(self): + config_files['system'] = '' + config_files['user'] = '' + for k in os.environ.keys(): + if k.startswith(('UBUNTUTOOLS_', 'TEST_')): + del os.environ[k] + + def test_config_parsing(self): + config_files['user'] = """#COMMENT=yes +\tTAB_INDENTED=yes + SPACE_INDENTED=yes +SPACE_SUFFIX=yes +SINGLE_QUOTE='yes no' +DOUBLE_QUOTE="yes no" +QUOTED_QUOTE="it's" +INHERIT=user +REPEAT=no +REPEAT=yes +""" + config_files['system'] = 'INHERIT=system' + self.assertEqual(UDTConfig(prefix='TEST').config, { + 'TAB_INDENTED': 'yes', + 'SPACE_INDENTED': 'yes', + 'SPACE_SUFFIX': 'yes', + 'SINGLE_QUOTE': 'yes no', + 'DOUBLE_QUOTE': 'yes no', + 'QUOTED_QUOTE': "it's", + 'INHERIT': 'user', + 'REPEAT': 'yes', + }) + + def get_value(self, key, default=None, compat_keys=[]): + config = UDTConfig(prefix='TEST') + return config.get_value(key, default=default, compat_keys=compat_keys) + + def test_defaults(self): + self.assertEqual(self.get_value('BUILDER'), 'pbuilder') + + def test_provided_default(self): + self.assertEqual(self.get_value('BUILDER', default='foo'), 'foo') + + def test_scriptname_precedence(self): + config_files['user'] = """TEST_BUILDER=foo + UBUNTUTOOLS_BUILDER=bar""" + self.assertEqual(self.get_value('BUILDER'), 'foo') + + def test_configfile_precedence(self): + config_files['system'] = "UBUNTUTOOLS_BUILDER=foo" + config_files['user'] = "UBUNTUTOOLS_BUILDER=bar" + self.assertEqual(self.get_value('BUILDER'), 'bar') + + def test_environment_precedence(self): + config_files['user'] = "UBUNTUTOOLS_BUILDER=bar" + os.environ['UBUNTUTOOLS_BUILDER'] = 'baz' + self.assertEqual(self.get_value('BUILDER'), 'baz') + + def test_any_environment_precedence(self): + config_files['user'] = "TEST_BUILDER=bar" + os.environ['UBUNTUTOOLS_BUILDER'] = 'foo' + self.assertEqual(self.get_value('BUILDER'), 'foo') + + def test_compat_environment_precedence(self): + config_files['user'] = "TEST_BUILDER=bar" + os.environ['BUILDER'] = 'baz' + self.assertEqual(self.get_value('BUILDER', compat_keys=['BUILDER']), + 'baz') + + def test_boolean(self): + config_files['user'] = "TEST_BOOLEAN=yes" + self.assertEqual(self.get_value('BOOLEAN'), True) + config_files['user'] = "TEST_BOOLEAN=no" + self.assertEqual(self.get_value('BOOLEAN'), False) + +class UbuEmailTestCase(unittest.TestCase): + def setUp(self): + self.cleanEnvironment() + + def tearDown(self): + self.cleanEnvironment() + + def cleanEnvironment(self): + for k in ('UBUMAIL', 'DEBEMAIL', 'DEBFULLNAME'): + if k in os.environ: + del os.environ[k] + + def test_pristine(self): + os.environ['DEBFULLNAME'] = name = 'Joe Developer' + os.environ['DEBEMAIL'] = email = 'joe@example.net' + self.assertEqual(ubu_email(), (name, email)) + + def test_two_hat(self): + os.environ['DEBFULLNAME'] = name = 'Joe Developer' + os.environ['DEBEMAIL'] = 'joe@debian.org' + os.environ['UBUMAIL'] = email = 'joe@ubuntu.com' + self.assertEqual(ubu_email(), (name, email)) + self.assertEqual(os.environ['DEBFULLNAME'], name) + self.assertEqual(os.environ['DEBEMAIL'], email) + + def test_two_hat_with_name(self): + os.environ['DEBFULLNAME'] = 'Joe Developer' + os.environ['DEBEMAIL'] = 'joe@debian.org' + name = 'Joe Ubuntunista' + email = 'joe@ubuntu.com' + os.environ['UBUMAIL'] = '%s <%s>' % (name, email) + self.assertEqual(ubu_email(), (name, email)) + self.assertEqual(os.environ['DEBFULLNAME'], name) + self.assertEqual(os.environ['DEBEMAIL'], email) + + def test_debfullname_with_email(self): + name = 'Joe Developer' + email = 'joe@example.net' + os.environ['DEBFULLNAME'] = '%s <%s>' % (name, email) + self.assertEqual(ubu_email(), (name, email)) + + def test_debemail_with_name(self): + name = 'Joe Developer' + email = 'joe@example.net' + os.environ['DEBEMAIL'] = '%s <%s>' % (name, email) + self.assertEqual(ubu_email(), (name, email))