sponsor-patch, backportpackage:

Factor out logging code from sponsor-patch and share it with backportpackage
This commit is contained in:
Evan Broder 2010-12-12 19:27:08 -08:00
parent aff1d83e02
commit fc7faa9780
3 changed files with 122 additions and 100 deletions

View File

@ -15,7 +15,6 @@
#
# ##################################################################
import logging
import optparse
import os
import shutil
@ -26,14 +25,17 @@ import tempfile
from debian.deb822 import Dsc
import launchpadlib.launchpad
from ubuntutools.logger import Logger
devnull = open('/dev/null', 'r+')
lp = None
def error(msg, *args, **kwargs):
logging.error(msg, *args, **kwargs)
def error(msg):
Logger.error(msg)
sys.exit(1)
def check_call(cmd, *args, **kwargs):
Logger.command(cmd)
ret = subprocess.call(cmd, *args, **kwargs)
if ret != 0:
error('%s returned %d' % (cmd, ret))
@ -197,7 +199,6 @@ def do_backport(workdir, package, dscfile, release, opts):
def main(args):
global lp
logging.basicConfig(level=logging.INFO)
os.environ['DEB_VENDOR'] = 'Ubuntu'
opts, (package,) = parse(args[1:])

View File

@ -29,6 +29,7 @@ import debian.debian_support
import launchpadlib.launchpad
import ubuntutools.update_maintainer
from ubuntutools.logger import Logger
USER_ABORT = 2
@ -57,7 +58,7 @@ class BugTask(object):
dsc_file = None
for url in source_files:
filename = urllib.unquote(os.path.basename(url))
Print.info("Downloading %s..." % (filename))
Logger.info("Downloading %s..." % (filename))
urllib.urlretrieve(url, filename)
if url.endswith(".dsc"):
dsc_file = filename
@ -171,7 +172,7 @@ class Pbuilder(Builder):
cmd = ["sudo", "-E", "DIST=" + dist, "pbuilder", "--build",
"--distribution", dist, "--architecture", self.architecture,
"--buildresult", result_directory, dsc_file]
Print.command(cmd)
Logger.command(cmd)
return subprocess.call(cmd)
@ -181,13 +182,13 @@ class Sbuild(Builder):
def build(self, dsc_file, dist, result_directory):
workdir = os.getcwd()
Print.command(["cd", result_directory])
Logger.command(["cd", result_directory])
os.chdir(result_directory)
cmd = ["sbuild", "--arch-all", "--dist=" + dist,
"--arch=" + self.architecture, dsc_file]
Print.command(cmd)
Logger.command(cmd)
result = subprocess.call(cmd)
Print.command(["cd", workdir])
Logger.command(["cd", workdir])
os.chdir(workdir)
return result
@ -220,41 +221,6 @@ class Patch(object):
self.changed_files)) > 0
class Print(object):
script_name = os.path.basename(sys.argv[0])
verbose = False
@classmethod
def command(cls, cmd):
if cls.verbose:
for i in xrange(len(cmd)):
if cmd[i].find(" ") >= 0:
cmd[i] = '"' + cmd[i] + '"'
print "%s: I: %s" % (script_name, " ".join(cmd))
@classmethod
def debug(cls, message):
if cls.verbose:
print "%s: D: %s" % (script_name, message)
@classmethod
def error(cls, message):
print >> sys.stderr, "%s: Error: %s" % (script_name, message)
@classmethod
def info(cls, message):
if cls.verbose:
print "%s: I: %s" % (script_name, message)
@classmethod
def normal(cls, message):
print "%s: %s" % (script_name, message)
@classmethod
def set_verbosity(cls, verbose):
cls.verbose = verbose
def get_source_package_name(bug_task):
package = None
if bug_task.bug_target_name != "ubuntu":
@ -334,7 +300,7 @@ def yes_edit_no_question(question, default):
def edit_source():
# Spawn shell to allow modifications
cmd = [get_user_shell()]
Print.command(cmd)
Logger.command(cmd)
print """An interactive shell was launched in
file://%s
Edit your files. When you are done, exit the shell. If you wish to abort the
@ -342,7 +308,7 @@ process, exit the shell such that it returns an exit code other than zero.
""" % (os.getcwd()),
returncode = subprocess.call(cmd)
if returncode != 0:
Print.error("Shell exited with exit value %i." % (returncode))
Logger.error("Shell exited with exit value %i." % (returncode))
sys.exit(1)
def get_fixed_lauchpad_bugs(changes_file):
@ -380,10 +346,10 @@ def get_patch_or_branch(bug):
linked_branches = map(lambda b: b.branch, bug.linked_branches)
if len(attached_patches) == 0 and len(linked_branches) == 0:
if len(bug.attachments) == 0:
Print.error("No attachment and no linked branch found on bug #%i." \
Logger.error("No attachment and no linked branch found on bug #%i." \
% (bug.id))
else:
Print.error(("No attached patch and no linked branch found. Go " \
Logger.error(("No attached patch and no linked branch found. Go " \
"to https://launchpad.net/bugs/%i and mark an " \
"attachment as patch.") % (bug.id))
sys.exit(1)
@ -393,13 +359,13 @@ def get_patch_or_branch(bug):
branch = linked_branches[0].bzr_identity
else:
if len(attached_patches) == 0:
Print.normal("https://launchpad.net/bugs/%i has %i branches " \
Logger.normal("https://launchpad.net/bugs/%i has %i branches " \
"linked:" % (bug.id, len(linked_branches)))
elif len(linked_branches) == 0:
Print.normal("https://launchpad.net/bugs/%i has %i patches" \
Logger.normal("https://launchpad.net/bugs/%i has %i patches" \
" attached:" % (bug.id, len(attached_patches)))
else:
Print.normal("https://launchpad.net/bugs/%i has %i branch(es)" \
Logger.normal("https://launchpad.net/bugs/%i has %i branch(es)" \
" linked and %i patch(es) attached:" % \
(bug.id, len(linked_branches), len(attached_patches)))
i = 0
@ -421,11 +387,11 @@ def download_patch(patch):
patch_filename = re.sub(" ", "_", patch.title)
if not reduce(lambda r, x: r or patch.title.endswith(x),
(".debdiff", ".diff", ".patch"), False):
Print.info("Patch %s does not have a proper file extension." % \
Logger.info("Patch %s does not have a proper file extension." % \
(patch.title))
patch_filename += ".patch"
Print.info("Downloading %s." % (patch_filename))
Logger.info("Downloading %s." % (patch_filename))
patch_file = open(patch_filename, "w")
patch_file.write(patch.data.open().read())
patch_file.close()
@ -436,18 +402,18 @@ def download_branch(branch):
if os.path.isdir(dir_name):
shutil.rmtree(dir_name)
cmd = ["bzr", "branch", branch]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Failed to download branch %s." % (branch))
Logger.error("Failed to download branch %s." % (branch))
sys.exit(1)
return dir_name
def merge_branch(branch):
edit = False
cmd = ["bzr", "merge", branch]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Failed to merge branch %s." % (branch))
Logger.error("Failed to merge branch %s." % (branch))
ask_for_manual_fixing()
edit = True
return edit
@ -456,9 +422,9 @@ def extract_source(dsc_file, verbose=False):
cmd = ["dpkg-source", "--no-preparation", "-x", dsc_file]
if not verbose:
cmd.insert(1, "-q")
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Extraction of %s failed." % (os.path.basename(dsc_file)))
Logger.error("Extraction of %s failed." % (os.path.basename(dsc_file)))
sys.exit(1)
def apply_patch(task, patch):
@ -466,9 +432,9 @@ def apply_patch(task, patch):
if patch.is_debdiff():
cmd = ["patch", "--merge", "--force", "-p",
str(patch.get_strip_level()), "-i", patch.full_path]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Failed to apply debdiff %s to %s %s." % \
Logger.error("Failed to apply debdiff %s to %s %s." % \
(patch.get_name(), task.package, task.get_version()))
if not edit:
ask_for_manual_fixing()
@ -477,9 +443,9 @@ def apply_patch(task, patch):
# FIXME: edit-patch needs a non-interactive mode
# https://launchpad.net/bugs/612566
cmd = ["edit-patch", patch.full_path]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Failed to apply diff %s to %s %s." % \
Logger.error("Failed to apply diff %s to %s %s." % \
(patch.get_name(), task.package, task.get_version()))
if not edit:
ask_for_manual_fixing()
@ -493,11 +459,11 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
try:
os.makedirs(workdir)
except os.error, error:
Print.error("Failed to create the working directory %s [Errno " \
Logger.error("Failed to create the working directory %s [Errno " \
"%i]: %s." % (workdir, error.errno, error.strerror))
sys.exit(1)
if workdir != os.getcwd():
Print.command(["cd", workdir])
Logger.command(["cd", workdir])
os.chdir(workdir)
script_name = os.path.basename(sys.argv[0])
@ -510,13 +476,13 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
bug_tasks = map(lambda x: BugTask(x, launchpad), bug.bug_tasks)
ubuntu_tasks = filter(lambda x: x.is_ubuntu_task(), bug_tasks)
if len(ubuntu_tasks) == 0:
Print.error("No Ubuntu bug task found on bug #%i." % (bug_number))
Logger.error("No Ubuntu bug task found on bug #%i." % (bug_number))
sys.exit(1)
elif len(ubuntu_tasks) == 1:
task = ubuntu_tasks[0]
if len(ubuntu_tasks) > 1:
if verbose:
Print.info("%i Ubuntu tasks exist for bug #%i." % \
Logger.info("%i Ubuntu tasks exist for bug #%i." % \
(len(ubuntu_tasks), bug_number))
for task in ubuntu_tasks:
print task.get_short_info()
@ -524,7 +490,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
if len(open_ubuntu_tasks) == 1:
task = open_ubuntu_tasks[0]
else:
Print.normal("https://launchpad.net/bugs/%i has %i Ubuntu tasks:" \
Logger.normal("https://launchpad.net/bugs/%i has %i Ubuntu tasks:" \
% (bug_number, len(ubuntu_tasks)))
for i in xrange(len(ubuntu_tasks)):
print "%i) %s" % (i + 1,
@ -532,7 +498,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
selected = input_number("To which Ubuntu tasks do the patch belong",
1, len(ubuntu_tasks))
task = ubuntu_tasks[selected - 1]
Print.info("Selected Ubuntu task: %s" % (task.get_short_info()))
Logger.info("Selected Ubuntu task: %s" % (task.get_short_info()))
dsc_file = task.download_source()
assert os.path.isfile(dsc_file), "%s does not exist." % (dsc_file)
@ -540,15 +506,15 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
if patch:
patch = download_patch(patch)
Print.info("Ubuntu package: %s" % (task.package))
Logger.info("Ubuntu package: %s" % (task.package))
if task.is_merge():
Print.info("The task is a merge request.")
Logger.info("The task is a merge request.")
extract_source(dsc_file, verbose)
# change directory
directory = task.package + '-' + task.get_version().upstream_version
Print.command(["cd", directory])
Logger.command(["cd", directory])
os.chdir(directory)
edit |= apply_patch(task, patch)
@ -556,7 +522,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
branch_dir = download_branch(task.get_branch_link())
# change directory
Print.command(["cd", branch_dir])
Logger.command(["cd", branch_dir])
os.chdir(branch_dir)
edit |= merge_branch(branch)
@ -568,9 +534,9 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
edit = True
# update the Maintainer field
Print.command(["update-maintainer"])
Logger.command(["update-maintainer"])
if ubuntutools.update_maintainer.update_maintainer(verbose) != 0:
Print.error("update-maintainer script failed.")
Logger.error("update-maintainer script failed.")
sys.exit(1)
# Get new version of package
@ -578,7 +544,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
try:
new_version = changelog.get_version()
except IndexError:
Print.error("Debian package version could not be determined. " \
Logger.error("Debian package version could not be determined. " \
"debian/changelog is probably malformed.")
ask_for_manual_fixing()
continue
@ -586,15 +552,15 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
# Check if version of the new package is greater than the version in
# the archive.
if new_version <= task.get_version():
Print.error("The version %s is not greater than the already " \
Logger.error("The version %s is not greater than the already " \
"available %s." % (new_version, task.get_version()))
ask_for_manual_fixing()
continue
cmd = ["dch", "--maintmaint", "--edit", ""]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.info("Failed to update timestamp in debian/changelog.")
Logger.info("Failed to update timestamp in debian/changelog.")
# Build source package
if patch:
@ -615,9 +581,9 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
env = os.environ
if upload == 'ubuntu':
env['DEB_VENDOR'] = 'Ubuntu'
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd, env=env) != 0:
Print.error("Failed to build source tarball.")
Logger.error("Failed to build source tarball.")
# TODO: Add a "retry" option
ask_for_manual_fixing()
continue
@ -634,7 +600,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
debdiff_filename = os.path.join(workdir, debdiff_name)
if not verbose:
cmd.insert(1, "-q")
Print.command(cmd + [">", debdiff_filename])
Logger.command(cmd + [">", debdiff_filename])
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
debdiff = process.communicate()[0]
@ -646,7 +612,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
# Make sure that the Launchpad bug will be closed
changes_file = new_dsc_file[:-4] + "_source.changes"
if bug_number not in get_fixed_lauchpad_bugs(changes_file):
Print.error("Launchpad bug #%i is not closed by new version." % \
Logger.error("Launchpad bug #%i is not closed by new version." % \
(bug_number))
ask_for_manual_fixing()
continue
@ -660,7 +626,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
allowed = map(lambda s: s + "-proposed", supported_series) + \
[devel_series]
if changelog.distributions not in allowed:
Print.error("%s is not an allowed series. It needs to be one " \
Logger.error("%s is not an allowed series. It needs to be one " \
"of %s." % (changelog.distributions,
", ".join(allowed)))
ask_for_manual_fixing()
@ -668,7 +634,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
elif upload and upload.startwith("ppa/"):
allowed = supported_series + [devel_series]
if changelog.distributions not in allowed:
Print.error("%s is not an allowed series. It needs to be one " \
Logger.error("%s is not an allowed series. It needs to be one " \
"of %s." % (changelog.distributions,
", ".join(allowed)))
ask_for_manual_fixing()
@ -683,7 +649,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
dist = re.sub("-.*$", "", changelog.distributions)
result = builder.build(new_dsc_file, dist, buildresult)
if result != 0:
Print.error("Failed to build %s from source with %s." % \
Logger.error("Failed to build %s from source with %s." % \
(os.path.basename(new_dsc_file),
builder.get_name()))
# TODO: Add "retry" and "update" option
@ -699,7 +665,7 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
cmd = ["lintian", "-IE", "--pedantic", "-q", build_changes]
lintian_filename = os.path.join(workdir,
task.package + "_" + strip_epoch(new_version) + ".lintian")
Print.command(cmd + [">", lintian_filename])
Logger.command(cmd + [">", lintian_filename])
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
report = process.communicate()[0]
@ -727,26 +693,26 @@ def main(script_name, bug_number, build, edit, keyid, upload, workdir, builder,
print "Abort."
sys.exit(USER_ABORT)
cmd = ["dput", "--force", upload, changes_file]
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error("Upload of %s to %s failed." % \
Logger.error("Upload of %s to %s failed." % \
(os.path.basename(changes_file), upload))
sys.exit(1)
if branch:
cmd = ['debcommit']
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error('Bzr commit failed.')
Logger.error('Bzr commit failed.')
sys.exit(1)
cmd = ['bzr', 'mark-uploaded']
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error('Bzr tagging failed.')
Logger.error('Bzr tagging failed.')
sys.exit(1)
cmd = ['bzr', 'push', ':parent']
Print.command(cmd)
Logger.command(cmd)
if subprocess.call(cmd) != 0:
Print.error('Bzr push failed.')
Logger.error('Bzr push failed.')
sys.exit(1)
# Leave while loop if everything worked
@ -790,20 +756,20 @@ if __name__ == "__main__":
help="Specify a working directory.")
(options, args) = parser.parse_args()
Print.set_verbosity(options.verbose)
Logger.set_verbosity(options.verbose)
if len(args) == 0:
Print.error("No bug number specified.")
Logger.error("No bug number specified.")
sys.exit(1)
elif len(args) > 1:
Print.error("Multiple bug numbers specified: %s" % (", ".join(args)))
Logger.error("Multiple bug numbers specified: %s" % (", ".join(args)))
sys.exit(1)
bug_number = args[0]
if bug_number.isdigit():
bug_number = int(bug_number)
else:
Print.error("Invalid bug number specified: %s" % (bug_number))
Logger.error("Invalid bug number specified: %s" % (bug_number))
sys.exit(1)
if options.builder == "pbuilder":
@ -811,7 +777,7 @@ if __name__ == "__main__":
elif options.builder == "sbuild":
builder = Sbuild()
else:
Print.error("Unsupported builder specified: %s. Only pbuilder and "
Logger.error("Unsupported builder specified: %s. Only pbuilder and "
"sbuild are supported." % (options.builder))
sys.exit(1)

55
ubuntutools/logger.py Normal file
View File

@ -0,0 +1,55 @@
#
# logger.py - A simple logging helper class
#
# Copyright (C) 2010, Benjamin Drung <bdrung@ubuntu.com>
#
# 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 sys
class Logger(object):
script_name = os.path.basename(sys.argv[0])
verbose = False
@classmethod
def command(cls, cmd):
if cls.verbose:
for i in xrange(len(cmd)):
if cmd[i].find(" ") >= 0:
cmd[i] = '"' + cmd[i] + '"'
print "%s: I: %s" % (cls.script_name, " ".join(cmd))
@classmethod
def debug(cls, message):
if cls.verbose:
print "%s: D: %s" % (cls.script_name, message)
@classmethod
def error(cls, message):
print >> sys.stderr, "%s: Error: %s" % (cls.script_name, message)
@classmethod
def info(cls, message):
if cls.verbose:
print "%s: I: %s" % (cls.script_name, message)
@classmethod
def normal(cls, message):
print "%s: %s" % (cls.script_name, message)
@classmethod
def set_verbosity(cls, verbose):
cls.verbose = verbose