mirror of
https://git.launchpad.net/ubuntu-dev-tools
synced 2025-03-13 08:01:09 +00:00
Compare commits
185 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
466e2784de | ||
|
ba3f0511f9 | ||
|
2e550ceff2 | ||
|
6c8a5d74bd | ||
|
3d11516599 | ||
|
5a20308ab1 | ||
|
b551877651 | ||
|
4a4c4e0a27 | ||
|
865c1c97bc | ||
|
d09718e976 | ||
|
bff7baecc9 | ||
|
45fbbb5bd1 | ||
|
ca217c035e | ||
|
b5e117788b | ||
|
ddba2d1e98 | ||
|
02d65a5804 | ||
|
bda85fa6a8 | ||
|
86a83bf74d | ||
|
162e758671 | ||
|
049425adb7 | ||
|
f6ca6cad92 | ||
|
3dc17934d6 | ||
|
10a176567a | ||
|
86b366c6c5 | ||
|
50b580b30e | ||
|
6ba0641f63 | ||
|
1e815db9d2 | ||
|
e2f43318bd | ||
|
cdd81232d9 | ||
|
65044d84d9 | ||
|
19e40b49c2 | ||
|
55eb521461 | ||
|
983bb3b70e | ||
|
85f2e46f7d | ||
|
649c3db767 | ||
|
e7ba650414 | ||
|
3bc802a209 | ||
|
92c80d7bb7 | ||
|
d7362d9ed8 | ||
|
c7a855ff20 | ||
|
017941ad70 | ||
|
69914f861e | ||
|
454f1e30c8 | ||
|
55bc403a95 | ||
|
c9339aeae4 | ||
|
c205ee0381 | ||
|
7577e10f13 | ||
|
e328dc05c2 | ||
|
9a94c9dea1 | ||
|
47ab7b608b | ||
|
56044d8eac | ||
|
c523b4cfc4 | ||
|
3df40f6392 | ||
|
6ebffe3f4a | ||
|
f01234e8a5 | ||
|
43891eda88 | ||
|
132866e2ba | ||
|
a0fcac7777 | ||
|
490895075d | ||
|
5186e76d8d | ||
|
bf46f7fbc1 | ||
|
881602c4b9 | ||
|
c869d07f75 | ||
|
59041af613 | ||
|
0ec53180f2 | ||
|
c92fa6502f | ||
|
07d3158ade | ||
|
d5faa9b133 | ||
|
9e710a3d66 | ||
|
010af53d7c | ||
|
0bef4d7352 | ||
|
688202a7cf | ||
|
691c1381db | ||
|
f01502bda2 | ||
|
42f8e5c0d2 | ||
|
bb8a9f7394 | ||
|
a058c716b9 | ||
|
e64fe7e212 | ||
|
f07d3df40c | ||
|
f73f2c1df1 | ||
|
268d082226 | ||
|
6bc59d789e | ||
|
9a4cc312f4 | ||
|
ffc787b454 | ||
|
bce1ef88c5 | ||
|
a9eb902b83 | ||
|
cb7464cf61 | ||
|
19f1df1054 | ||
|
7f64dde12c | ||
|
c2539c6787 | ||
|
fd885ec239 | ||
|
abbc56e185 | ||
|
a2176110f0 | ||
|
a5185e4612 | ||
|
e90ceaf26b | ||
|
47fd5d7cca | ||
|
2f396fe549 | ||
|
5bda35f6b4 | ||
|
db916653cd | ||
|
784e7814e9 | ||
|
bed2dc470d | ||
|
414bc76b50 | ||
|
6f0caf1fc0 | ||
|
4bcc55372a | ||
|
232a73de31 | ||
|
9aab0135a2 | ||
|
23539f28b1 | ||
|
4a09d23db6 | ||
|
534cd254f4 | ||
|
29c3fa98bc | ||
|
7c9c7f2890 | ||
|
739279da3f | ||
|
7c11832ee0 | ||
|
f5512846d6 | ||
|
9e0dff4461 | ||
|
7129e6e27a | ||
|
79d30a9bfc | ||
|
2c6a8b5451 | ||
|
ad014685ea | ||
|
ff1c95e2c0 | ||
|
89e788bf48 | ||
|
a000e9db5e | ||
|
83158d24d9 | ||
|
6e6e1f1e1a | ||
|
c7a7767339 | ||
|
ac2f980e0f | ||
|
ccab82e054 | ||
|
2e4e8b35b2 | ||
|
53fcd577e8 | ||
|
8430d445d8 | ||
|
c8a757eb07 | ||
|
66a2773c1c | ||
|
17d2770451 | ||
|
3136541ca6 | ||
|
f3a0182e1a | ||
|
6498a13f18 | ||
|
d2debf9ed9 | ||
|
a11cb1f630 | ||
|
34578e6a1e | ||
|
21784052ba | ||
|
aa556af89d | ||
|
069a6926c0 | ||
|
444b319c12 | ||
|
4449cf2437 | ||
|
9fa29f6ad5 | ||
|
a160def2ab | ||
|
909d945af4 | ||
|
f6fde2e217 | ||
|
17bed46ffb | ||
|
72add78e9d | ||
|
ab64467f33 | ||
|
b1bc7e1cdc | ||
|
8692bc2b1c | ||
|
a685368ae9 | ||
|
4e27045f49 | ||
|
db0e091e44 | ||
|
3354b526b5 | ||
|
79d24c9df1 | ||
|
932166484b | ||
|
bd770fa6b1 | ||
|
3d54a17403 | ||
|
3bdb827516 | ||
|
0d94b5e747 | ||
|
0f3d2fed2a | ||
|
844d6d942c | ||
|
ae43fd1929 | ||
|
69ac109cdb | ||
|
9f2a53c166 | ||
|
a69c40d403 | ||
|
c1e4b14a98 | ||
|
096d5612e7 | ||
|
b510dbd91e | ||
|
803949ed8b | ||
|
e219eaa5fc | ||
|
60ee986014 | ||
|
dabe475067 | ||
|
0a9e18ed91 | ||
|
7859889438 | ||
|
a3c87e78aa | ||
|
05af489f64 | ||
|
d5fdc00396 | ||
|
7d278cde21 | ||
|
ad402231db | ||
|
562e6b13cd | ||
|
9c1561ff26 |
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,16 +1,2 @@
|
|||||||
.coverage
|
__pycache__
|
||||||
.tox
|
*.egg-info
|
||||||
/ubuntu_dev_tools.egg-info/
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
/build/
|
|
||||||
/.pybuild/
|
|
||||||
/test-data/
|
|
||||||
/debian/python-ubuntutools/
|
|
||||||
/debian/python3-ubuntutools/
|
|
||||||
/debian/ubuntu-dev-tools/
|
|
||||||
/debian/debhelper-build-stamp
|
|
||||||
/debian/files
|
|
||||||
/debian/*.debhelper
|
|
||||||
/debian/*.debhelper.log
|
|
||||||
/debian/*.substvars
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
[MASTER]
|
[MASTER]
|
||||||
|
|
||||||
|
# A comma-separated list of package or module names from where C extensions may
|
||||||
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||||
|
# run arbitrary code.
|
||||||
|
extension-pkg-allow-list=apt_pkg
|
||||||
|
|
||||||
# Pickle collected data for later comparisons.
|
# Pickle collected data for later comparisons.
|
||||||
persistent=no
|
persistent=no
|
||||||
|
|
||||||
@ -9,10 +14,6 @@ jobs=0
|
|||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
|
||||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
|
||||||
confidence=HIGH
|
|
||||||
|
|
||||||
# Disable the message, report, category or checker with the given id(s). You
|
# Disable the message, report, category or checker with the given id(s). You
|
||||||
# can either give multiple identifiers separated by comma (,) or put this
|
# can either give multiple identifiers separated by comma (,) or put this
|
||||||
# option multiple times (only on the command line, not in the configuration
|
# option multiple times (only on the command line, not in the configuration
|
||||||
@ -22,7 +23,18 @@ confidence=HIGH
|
|||||||
# --enable=similarities". If you want to run only the classes checker, but have
|
# --enable=similarities". If you want to run only the classes checker, but have
|
||||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||||
# --disable=W"
|
# --disable=W"
|
||||||
disable=locally-disabled
|
disable=fixme,locally-disabled,missing-docstring,useless-option-value,
|
||||||
|
# TODO: Fix all following disabled checks!
|
||||||
|
invalid-name,
|
||||||
|
consider-using-with,
|
||||||
|
too-many-arguments,
|
||||||
|
too-many-branches,
|
||||||
|
too-many-statements,
|
||||||
|
too-many-locals,
|
||||||
|
duplicate-code,
|
||||||
|
too-many-instance-attributes,
|
||||||
|
too-many-nested-blocks,
|
||||||
|
too-many-lines,
|
||||||
|
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
@ -31,14 +43,6 @@ disable=locally-disabled
|
|||||||
reports=no
|
reports=no
|
||||||
|
|
||||||
|
|
||||||
[TYPECHECK]
|
|
||||||
|
|
||||||
# List of classes names for which member attributes should not be checked
|
|
||||||
# (useful for classes with attributes dynamically set).
|
|
||||||
# lpapicache classes, urlparse
|
|
||||||
ignored-classes=Launchpad,BaseWrapper,PersonTeam,Distribution,Consumer,Credentials,ParseResult,apt_pkg,apt_pkg.Dependency,apt_pkg.BaseDependency
|
|
||||||
|
|
||||||
|
|
||||||
[FORMAT]
|
[FORMAT]
|
||||||
|
|
||||||
# Maximum number of characters on a single line.
|
# Maximum number of characters on a single line.
|
||||||
@ -52,4 +56,10 @@ indent-string=' '
|
|||||||
[BASIC]
|
[BASIC]
|
||||||
|
|
||||||
# Allow variables called e, f, lp
|
# Allow variables called e, f, lp
|
||||||
good-names=i,j,k,ex,Run,_,e,f,lp
|
good-names=i,j,k,ex,Run,_,e,f,lp,me,to
|
||||||
|
|
||||||
|
|
||||||
|
[IMPORTS]
|
||||||
|
|
||||||
|
# Force import order to recognize a module as part of a third party library.
|
||||||
|
known-third-party=debian
|
511
backportpackage
511
backportpackage
@ -18,8 +18,8 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
|
import argparse
|
||||||
import glob
|
import glob
|
||||||
import optparse
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -27,197 +27,223 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
try:
|
||||||
import lsb_release
|
import lsb_release
|
||||||
from httplib2 import Http, HttpLib2Error
|
except ImportError:
|
||||||
|
lsb_release = None
|
||||||
from distro_info import DebianDistroInfo, UbuntuDistroInfo
|
from distro_info import DebianDistroInfo, UbuntuDistroInfo
|
||||||
|
from httplib2 import Http, HttpLib2Error
|
||||||
from ubuntutools.archive import (DebianSourcePackage,
|
|
||||||
UbuntuSourcePackage, DownloadError)
|
|
||||||
from ubuntutools.config import UDTConfig, ubu_email
|
|
||||||
from ubuntutools.builder import get_builder
|
|
||||||
from ubuntutools.lp.lpapicache import (Launchpad, Distribution,
|
|
||||||
SeriesNotFoundException,
|
|
||||||
PackageNotFoundException)
|
|
||||||
from ubuntutools.misc import (system_distribution, vendor_to_distroinfo,
|
|
||||||
codename_to_distribution)
|
|
||||||
from ubuntutools.question import YesNoQuestion
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.archive import DebianSourcePackage, DownloadError, UbuntuSourcePackage
|
||||||
|
from ubuntutools.builder import get_builder
|
||||||
|
from ubuntutools.config import UDTConfig, ubu_email
|
||||||
|
from ubuntutools.lp.lpapicache import (
|
||||||
|
Distribution,
|
||||||
|
Launchpad,
|
||||||
|
PackageNotFoundException,
|
||||||
|
SeriesNotFoundException,
|
||||||
|
)
|
||||||
|
from ubuntutools.misc import codename_to_distribution, system_distribution, vendor_to_distroinfo
|
||||||
|
from ubuntutools.question import YesNoQuestion
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def error(msg):
|
def error(msg, *args):
|
||||||
Logger.error(msg)
|
Logger.error(msg, *args)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def check_call(cmd, *args, **kwargs):
|
def check_call(cmd, *args, **kwargs):
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
ret = subprocess.call(cmd, *args, **kwargs)
|
ret = subprocess.call(cmd, *args, **kwargs)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
error('%s returned %d.' % (cmd[0], ret))
|
error("%s returned %d.", cmd[0], ret)
|
||||||
|
|
||||||
|
|
||||||
def parse(args):
|
def parse(argv):
|
||||||
usage = 'Usage: %prog [options] <source package name or .dsc URL/file>'
|
usage = "%(prog)s [options] <source package name or .dsc URL/file>"
|
||||||
parser = optparse.OptionParser(usage)
|
parser = argparse.ArgumentParser(usage=usage)
|
||||||
parser.add_option('-d', '--destination',
|
parser.add_argument(
|
||||||
metavar='DEST',
|
"-d",
|
||||||
dest='dest_releases',
|
"--destination",
|
||||||
|
metavar="DEST",
|
||||||
|
dest="dest_releases",
|
||||||
default=[],
|
default=[],
|
||||||
action='append',
|
action="append",
|
||||||
help='Backport to DEST release '
|
help="Backport to DEST release (default: current release)",
|
||||||
'(default: current release)')
|
)
|
||||||
parser.add_option('-s', '--source',
|
parser.add_argument(
|
||||||
metavar='SOURCE',
|
"-s",
|
||||||
dest='source_release',
|
"--source",
|
||||||
help='Backport from SOURCE release '
|
metavar="SOURCE",
|
||||||
'(default: devel release)')
|
dest="source_release",
|
||||||
parser.add_option('-S', '--suffix',
|
help="Backport from SOURCE release (default: devel release)",
|
||||||
metavar='SUFFIX',
|
)
|
||||||
help='Suffix to append to version number '
|
parser.add_argument(
|
||||||
'(default: ~ppa1 when uploading to a PPA)')
|
"-S",
|
||||||
parser.add_option('-e', '--message',
|
"--suffix",
|
||||||
metavar='MESSAGE',
|
metavar="SUFFIX",
|
||||||
|
help="Suffix to append to version number (default: ~ppa1 when uploading to a PPA)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e",
|
||||||
|
"--message",
|
||||||
|
metavar="MESSAGE",
|
||||||
default="No-change",
|
default="No-change",
|
||||||
help='Changelog message to use instead of "No-change" '
|
help='Changelog message to use instead of "No-change" '
|
||||||
'(default: No-change backport to DEST.)')
|
"(default: No-change backport to DEST.)",
|
||||||
parser.add_option('-b', '--build',
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-b",
|
||||||
|
"--build",
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Build the package before uploading '
|
help="Build the package before uploading (default: %(default)s)",
|
||||||
'(default: %default)')
|
)
|
||||||
parser.add_option('-B', '--builder',
|
parser.add_argument(
|
||||||
metavar='BUILDER',
|
"-B",
|
||||||
help='Specify the package builder (default: pbuilder)')
|
"--builder",
|
||||||
parser.add_option('-U', '--update',
|
metavar="BUILDER",
|
||||||
|
help="Specify the package builder (default: pbuilder)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-U",
|
||||||
|
"--update",
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Update the build environment before '
|
help="Update the build environment before attempting to build",
|
||||||
'attempting to build')
|
)
|
||||||
parser.add_option('-u', '--upload',
|
parser.add_argument("-u", "--upload", metavar="UPLOAD", help="Specify an upload destination")
|
||||||
metavar='UPLOAD',
|
parser.add_argument(
|
||||||
help='Specify an upload destination')
|
"-k", "--key", dest="keyid", help="Specify the key ID to be used for signing."
|
||||||
parser.add_option("-k", "--key",
|
)
|
||||||
dest='keyid',
|
parser.add_argument(
|
||||||
help="Specify the key ID to be used for signing.")
|
"--dont-sign", dest="keyid", action="store_false", help="Do not sign the upload."
|
||||||
parser.add_option('--dont-sign',
|
)
|
||||||
dest='keyid', action='store_false',
|
parser.add_argument(
|
||||||
help='Do not sign the upload.')
|
"-y",
|
||||||
parser.add_option('-y', '--yes',
|
"--yes",
|
||||||
dest='prompt',
|
dest="prompt",
|
||||||
default=True,
|
default=True,
|
||||||
action='store_false',
|
action="store_false",
|
||||||
help='Do not prompt before uploading to a PPA')
|
help="Do not prompt before uploading to a PPA",
|
||||||
parser.add_option('-v', '--version',
|
)
|
||||||
metavar='VERSION',
|
parser.add_argument(
|
||||||
help='Package version to backport (or verify)')
|
"-v", "--version", metavar="VERSION", help="Package version to backport (or verify)"
|
||||||
parser.add_option('-w', '--workdir',
|
)
|
||||||
metavar='WORKDIR',
|
parser.add_argument(
|
||||||
help='Specify a working directory '
|
"-w",
|
||||||
'(default: temporary dir)')
|
"--workdir",
|
||||||
parser.add_option('-r', '--release-pocket',
|
metavar="WORKDIR",
|
||||||
|
help="Specify a working directory (default: temporary dir)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r",
|
||||||
|
"--release-pocket",
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help='Target the release pocket in the .changes file. '
|
help="Target the release pocket in the .changes file. "
|
||||||
'Necessary (and default) for uploads to PPAs')
|
"Necessary (and default) for uploads to PPAs",
|
||||||
parser.add_option('-c', '--close',
|
)
|
||||||
metavar='BUG',
|
parser.add_argument(
|
||||||
help='Bug to close in the changelog entry.')
|
"-c", "--close", metavar="BUG", help="Bug to close in the changelog entry."
|
||||||
parser.add_option('-m', '--mirror',
|
)
|
||||||
metavar='URL',
|
parser.add_argument(
|
||||||
help='Preferred mirror (default: Launchpad)')
|
"-m", "--mirror", metavar="URL", help="Preferred mirror (default: Launchpad)"
|
||||||
parser.add_option('-l', '--lpinstance',
|
)
|
||||||
metavar='INSTANCE',
|
parser.add_argument(
|
||||||
help='Launchpad instance to connect to '
|
"-l",
|
||||||
'(default: production)')
|
"--lpinstance",
|
||||||
parser.add_option('--no-conf',
|
metavar="INSTANCE",
|
||||||
|
help="Launchpad instance to connect to (default: production)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf",
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
action="store_true",
|
||||||
help="Don't read config files or environment variables")
|
help="Don't read config files or environment variables",
|
||||||
|
)
|
||||||
|
parser.add_argument("package_or_dsc", help=argparse.SUPPRESS)
|
||||||
|
|
||||||
opts, args = parser.parse_args(args)
|
args = parser.parse_args(argv)
|
||||||
if len(args) != 1:
|
config = UDTConfig(args.no_conf)
|
||||||
parser.error('You must specify a single source package or a .dsc '
|
if args.builder is None:
|
||||||
'URL/path.')
|
args.builder = config.get_value("BUILDER")
|
||||||
config = UDTConfig(opts.no_conf)
|
if not args.update:
|
||||||
if opts.builder is None:
|
args.update = config.get_value("UPDATE_BUILDER", boolean=True)
|
||||||
opts.builder = config.get_value('BUILDER')
|
if args.workdir is None:
|
||||||
if not opts.update:
|
args.workdir = config.get_value("WORKDIR")
|
||||||
opts.update = config.get_value('UPDATE_BUILDER', boolean=True)
|
if args.lpinstance is None:
|
||||||
if opts.workdir is None:
|
args.lpinstance = config.get_value("LPINSTANCE")
|
||||||
opts.workdir = config.get_value('WORKDIR')
|
if args.upload is None:
|
||||||
if opts.lpinstance is None:
|
args.upload = config.get_value("UPLOAD")
|
||||||
opts.lpinstance = config.get_value('LPINSTANCE')
|
if args.keyid is None:
|
||||||
if opts.upload is None:
|
args.keyid = config.get_value("KEYID")
|
||||||
opts.upload = config.get_value('UPLOAD')
|
if not args.upload and not args.workdir:
|
||||||
if opts.keyid is None:
|
parser.error("Please specify either a working dir or an upload target!")
|
||||||
opts.keyid = config.get_value('KEYID')
|
if args.upload and args.upload.startswith("ppa:"):
|
||||||
if not opts.upload and not opts.workdir:
|
args.release_pocket = True
|
||||||
parser.error('Please specify either a working dir or an upload target!')
|
|
||||||
if opts.upload and opts.upload.startswith('ppa:'):
|
|
||||||
opts.release_pocket = True
|
|
||||||
|
|
||||||
return opts, args, config
|
return args, config
|
||||||
|
|
||||||
|
|
||||||
def find_release_package(mirror, workdir, package, version, source_release,
|
def find_release_package(mirror, workdir, package, version, source_release, config):
|
||||||
config):
|
|
||||||
srcpkg = None
|
srcpkg = None
|
||||||
|
|
||||||
if source_release:
|
if source_release:
|
||||||
distribution = codename_to_distribution(source_release)
|
distribution = codename_to_distribution(source_release)
|
||||||
if not distribution:
|
if not distribution:
|
||||||
error('Unknown release codename %s' % source_release)
|
error("Unknown release codename %s", source_release)
|
||||||
info = vendor_to_distroinfo(distribution)()
|
info = vendor_to_distroinfo(distribution)()
|
||||||
source_release = info.codename(source_release, default=source_release)
|
source_release = info.codename(source_release, default=source_release)
|
||||||
else:
|
else:
|
||||||
distribution = system_distribution()
|
distribution = system_distribution()
|
||||||
mirrors = [mirror] if mirror else []
|
mirrors = [mirror] if mirror else []
|
||||||
|
|
||||||
mirrors.append(config.get_value('%s_MIRROR' % distribution.upper()))
|
mirrors.append(config.get_value(f"{distribution.upper()}_MIRROR"))
|
||||||
|
|
||||||
if not version:
|
if not version:
|
||||||
archive = Distribution(distribution.lower()).getArchive()
|
archive = Distribution(distribution.lower()).getArchive()
|
||||||
try:
|
try:
|
||||||
spph = archive.getSourcePackage(package, source_release)
|
spph = archive.getSourcePackage(package, source_release)
|
||||||
except (SeriesNotFoundException, PackageNotFoundException) as e:
|
except (SeriesNotFoundException, PackageNotFoundException) as e:
|
||||||
error(str(e))
|
error("%s", str(e))
|
||||||
version = spph.getVersion()
|
version = spph.getVersion()
|
||||||
|
|
||||||
if distribution == 'Debian':
|
if distribution == "Debian":
|
||||||
srcpkg = DebianSourcePackage(package,
|
srcpkg = DebianSourcePackage(package, version, workdir=workdir, mirrors=mirrors)
|
||||||
version,
|
elif distribution == "Ubuntu":
|
||||||
workdir=workdir,
|
srcpkg = UbuntuSourcePackage(package, version, workdir=workdir, mirrors=mirrors)
|
||||||
mirrors=mirrors)
|
|
||||||
elif distribution == 'Ubuntu':
|
|
||||||
srcpkg = UbuntuSourcePackage(package,
|
|
||||||
version,
|
|
||||||
workdir=workdir,
|
|
||||||
mirrors=mirrors)
|
|
||||||
|
|
||||||
return srcpkg
|
return srcpkg
|
||||||
|
|
||||||
|
|
||||||
def find_package(mirror, workdir, package, version, source_release, config):
|
def find_package(mirror, workdir, package, version, source_release, config):
|
||||||
"Returns the SourcePackage"
|
"Returns the SourcePackage"
|
||||||
if package.endswith('.dsc'):
|
if package.endswith(".dsc"):
|
||||||
# Here we are using UbuntuSourcePackage just because we don't have any
|
# Here we are using UbuntuSourcePackage just because we don't have any
|
||||||
# "general" class that is safely instantiable (as SourcePackage is an
|
# "general" class that is safely instantiable (as SourcePackage is an
|
||||||
# abstract class). None of the distribution-specific details within
|
# abstract class). None of the distribution-specific details within
|
||||||
# UbuntuSourcePackage is relevant for this use case.
|
# UbuntuSourcePackage is relevant for this use case.
|
||||||
return UbuntuSourcePackage(version=version, dscfile=package,
|
return UbuntuSourcePackage(
|
||||||
workdir=workdir, mirrors=(mirror,))
|
version=version, dscfile=package, workdir=workdir, mirrors=(mirror,)
|
||||||
|
)
|
||||||
|
|
||||||
if not source_release and not version:
|
if not source_release and not version:
|
||||||
info = vendor_to_distroinfo(system_distribution())
|
info = vendor_to_distroinfo(system_distribution())
|
||||||
source_release = info().devel()
|
source_release = info().devel()
|
||||||
|
|
||||||
srcpkg = find_release_package(mirror, workdir, package, version,
|
srcpkg = find_release_package(mirror, workdir, package, version, source_release, config)
|
||||||
source_release, config)
|
|
||||||
if version and srcpkg.version != version:
|
if version and srcpkg.version != version:
|
||||||
error('Requested backport of version %s but version of %s in %s is %s'
|
error(
|
||||||
% (version, package, source_release, srcpkg.version))
|
"Requested backport of version %s but version of %s in %s is %s",
|
||||||
|
version,
|
||||||
|
package,
|
||||||
|
source_release,
|
||||||
|
srcpkg.version,
|
||||||
|
)
|
||||||
|
|
||||||
return srcpkg
|
return srcpkg
|
||||||
|
|
||||||
@ -225,30 +251,27 @@ def find_package(mirror, workdir, package, version, source_release, config):
|
|||||||
def get_backport_version(version, suffix, upload, release):
|
def get_backport_version(version, suffix, upload, release):
|
||||||
distribution = codename_to_distribution(release)
|
distribution = codename_to_distribution(release)
|
||||||
if not distribution:
|
if not distribution:
|
||||||
error('Unknown release codename %s' % release)
|
error("Unknown release codename %s", release)
|
||||||
if distribution == 'Debian':
|
if distribution == "Debian":
|
||||||
debian_distro_info = DebianDistroInfo()
|
debian_distro_info = DebianDistroInfo()
|
||||||
debian_codenames = debian_distro_info.supported()
|
debian_codenames = debian_distro_info.supported()
|
||||||
if release in debian_codenames:
|
if release in debian_codenames:
|
||||||
release_version = debian_distro_info.version(release)
|
release_version = debian_distro_info.version(release)
|
||||||
if not release_version:
|
if not release_version:
|
||||||
error(f"Can't find the release version for {release}")
|
error("Can't find the release version for %s", release)
|
||||||
backport_version = "{}~bpo{}+1".format(
|
backport_version = f"{version}~bpo{release_version}+1"
|
||||||
version, release_version
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
error(f"{release} is not a supported release ({debian_codenames})")
|
error("%s is not a supported release (%s)", release, debian_codenames)
|
||||||
elif distribution == 'Ubuntu':
|
elif distribution == "Ubuntu":
|
||||||
series = Distribution(distribution.lower()).\
|
series = Distribution(distribution.lower()).getSeries(name_or_version=release)
|
||||||
getSeries(name_or_version=release)
|
|
||||||
|
|
||||||
backport_version = version + ('~bpo%s.1' % (series.version))
|
backport_version = f"{version}~bpo{series.version}.1"
|
||||||
else:
|
else:
|
||||||
error('Unknown distribution «%s» for release «%s»' % (distribution, release))
|
error("Unknown distribution «%s» for release «%s»", distribution, release)
|
||||||
if suffix is not None:
|
if suffix is not None:
|
||||||
backport_version += suffix
|
backport_version += suffix
|
||||||
elif upload and upload.startswith('ppa:'):
|
elif upload and upload.startswith("ppa:"):
|
||||||
backport_version += '~ppa1'
|
backport_version += "~ppa1"
|
||||||
return backport_version
|
return backport_version
|
||||||
|
|
||||||
|
|
||||||
@ -256,26 +279,25 @@ def get_old_version(source, release):
|
|||||||
try:
|
try:
|
||||||
distribution = codename_to_distribution(release)
|
distribution = codename_to_distribution(release)
|
||||||
archive = Distribution(distribution.lower()).getArchive()
|
archive = Distribution(distribution.lower()).getArchive()
|
||||||
pkg = archive.getSourcePackage(source,
|
pkg = archive.getSourcePackage(
|
||||||
release,
|
source, release, ("Release", "Security", "Updates", "Proposed", "Backports")
|
||||||
('Release', 'Security', 'Updates',
|
)
|
||||||
'Proposed', 'Backports'))
|
|
||||||
return pkg.getVersion()
|
return pkg.getVersion()
|
||||||
except (SeriesNotFoundException, PackageNotFoundException):
|
except (SeriesNotFoundException, PackageNotFoundException):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_backport_dist(release, release_pocket):
|
def get_backport_dist(release, release_pocket):
|
||||||
if release_pocket:
|
if release_pocket:
|
||||||
return release
|
return release
|
||||||
else:
|
return f"{release}-backports"
|
||||||
return '%s-backports' % release
|
|
||||||
|
|
||||||
|
|
||||||
def do_build(workdir, dsc, release, builder, update):
|
def do_build(workdir, dsc, release, builder, update):
|
||||||
builder = get_builder(builder)
|
builder = get_builder(builder)
|
||||||
if not builder:
|
if not builder:
|
||||||
return
|
return None
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
if 0 != builder.update(release):
|
if 0 != builder.update(release):
|
||||||
@ -283,41 +305,41 @@ def do_build(workdir, dsc, release, builder, update):
|
|||||||
|
|
||||||
# builder.build is going to chdir to buildresult:
|
# builder.build is going to chdir to buildresult:
|
||||||
workdir = os.path.realpath(workdir)
|
workdir = os.path.realpath(workdir)
|
||||||
return builder.build(os.path.join(workdir, dsc),
|
return builder.build(os.path.join(workdir, dsc), release, os.path.join(workdir, "buildresult"))
|
||||||
release,
|
|
||||||
os.path.join(workdir, "buildresult"))
|
|
||||||
|
|
||||||
|
|
||||||
def do_upload(workdir, package, bp_version, changes, upload, prompt):
|
def do_upload(workdir, package, bp_version, changes, upload, prompt):
|
||||||
print('Please check %s %s in file://%s carefully!' % (package, bp_version, workdir))
|
print(f"Please check {package} {bp_version} in file://{workdir} carefully!")
|
||||||
if prompt or upload == 'ubuntu':
|
if prompt or upload == "ubuntu":
|
||||||
question = 'Do you want to upload the package to %s' % upload
|
question = f"Do you want to upload the package to {upload}"
|
||||||
answer = YesNoQuestion().ask(question, "yes")
|
answer = YesNoQuestion().ask(question, "yes")
|
||||||
if answer == "no":
|
if answer == "no":
|
||||||
return
|
return
|
||||||
|
|
||||||
check_call(['dput', upload, changes], cwd=workdir)
|
check_call(["dput", upload, changes], cwd=workdir)
|
||||||
|
|
||||||
|
|
||||||
def orig_needed(upload, workdir, pkg):
|
def orig_needed(upload, workdir, pkg):
|
||||||
'''Avoid a -sa if possible'''
|
"""Avoid a -sa if possible"""
|
||||||
if not upload or not upload.startswith('ppa:'):
|
if not upload or not upload.startswith("ppa:"):
|
||||||
return True
|
return True
|
||||||
ppa = upload.split(':', 1)[1]
|
ppa = upload.split(":", 1)[1]
|
||||||
user, ppa = ppa.split('/', 1)
|
user, ppa = ppa.split("/", 1)
|
||||||
|
|
||||||
version = pkg.version.upstream_version
|
version = pkg.version.upstream_version
|
||||||
|
|
||||||
h = Http()
|
http = Http()
|
||||||
for filename in glob.glob(os.path.join(workdir, '%s_%s.orig*' % (pkg.source, version))):
|
for filename in glob.glob(os.path.join(workdir, f"{pkg.source}_{version}.orig*")):
|
||||||
url = ('https://launchpad.net/~%s/+archive/%s/+sourcefiles/%s/%s/%s'
|
url = (
|
||||||
% (quote(user), quote(ppa), quote(pkg.source),
|
f"https://launchpad.net/~{quote(user)}/+archive/{quote(ppa)}/+sourcefiles"
|
||||||
quote(pkg.version.full_version),
|
f"/{quote(pkg.source)}/{quote(pkg.version.full_version)}"
|
||||||
quote(os.path.basename(filename))))
|
f"/{quote(os.path.basename(filename))}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
headers, body = h.request(url, 'HEAD')
|
headers = http.request(url, "HEAD")[0]
|
||||||
if (headers.status != 200 or
|
if headers.status != 200 or not headers["content-location"].startswith(
|
||||||
not headers['content-location'].startswith('https://launchpadlibrarian.net')):
|
"https://launchpadlibrarian.net"
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
except HttpLib2Error as e:
|
except HttpLib2Error as e:
|
||||||
Logger.debug(e)
|
Logger.debug(e)
|
||||||
@ -325,61 +347,79 @@ def orig_needed(upload, workdir, pkg):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def do_backport(workdir, pkg, suffix, message, close, release, release_pocket,
|
def do_backport(
|
||||||
build, builder, update, upload, keyid, prompt):
|
workdir,
|
||||||
dirname = '%s-%s' % (pkg.source, release)
|
pkg,
|
||||||
|
suffix,
|
||||||
|
message,
|
||||||
|
close,
|
||||||
|
release,
|
||||||
|
release_pocket,
|
||||||
|
build,
|
||||||
|
builder,
|
||||||
|
update,
|
||||||
|
upload,
|
||||||
|
keyid,
|
||||||
|
prompt,
|
||||||
|
):
|
||||||
|
dirname = f"{pkg.source}-{release}"
|
||||||
srcdir = os.path.join(workdir, dirname)
|
srcdir = os.path.join(workdir, dirname)
|
||||||
|
|
||||||
if os.path.exists(srcdir):
|
if os.path.exists(srcdir):
|
||||||
question = 'Working directory %s already exists. Delete it?' % srcdir
|
question = f"Working directory {srcdir} already exists. Delete it?"
|
||||||
if YesNoQuestion().ask(question, 'no') == 'no':
|
if YesNoQuestion().ask(question, "no") == "no":
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
shutil.rmtree(srcdir)
|
shutil.rmtree(srcdir)
|
||||||
|
|
||||||
pkg.unpack(dirname)
|
pkg.unpack(dirname)
|
||||||
|
|
||||||
bp_version = get_backport_version(pkg.version.full_version, suffix,
|
bp_version = get_backport_version(pkg.version.full_version, suffix, upload, release)
|
||||||
upload, release)
|
|
||||||
old_version = get_old_version(pkg.source, release)
|
old_version = get_old_version(pkg.source, release)
|
||||||
bp_dist = get_backport_dist(release, release_pocket)
|
bp_dist = get_backport_dist(release, release_pocket)
|
||||||
|
|
||||||
changelog = '%s backport to %s.' % (message, release,)
|
changelog = f"{message} backport to {release}."
|
||||||
if close:
|
if close:
|
||||||
changelog += ' (LP: #%s)' % (close,)
|
changelog += f" (LP: #{close})"
|
||||||
check_call(['dch',
|
check_call(
|
||||||
'--force-bad-version',
|
[
|
||||||
'--force-distribution',
|
"dch",
|
||||||
'--preserve',
|
"--force-bad-version",
|
||||||
'--newversion', bp_version,
|
"--force-distribution",
|
||||||
'--distribution', bp_dist,
|
"--preserve",
|
||||||
changelog],
|
"--newversion",
|
||||||
cwd=srcdir)
|
bp_version,
|
||||||
|
"--distribution",
|
||||||
|
bp_dist,
|
||||||
|
changelog,
|
||||||
|
],
|
||||||
|
cwd=srcdir,
|
||||||
|
)
|
||||||
|
|
||||||
cmd = ['debuild', '--no-lintian', '-S', '-nc', '-uc', '-us']
|
cmd = ["debuild", "--no-lintian", "-S", "-nc", "-uc", "-us"]
|
||||||
if orig_needed(upload, workdir, pkg):
|
if orig_needed(upload, workdir, pkg):
|
||||||
cmd.append('-sa')
|
cmd.append("-sa")
|
||||||
else:
|
else:
|
||||||
cmd.append('-sd')
|
cmd.append("-sd")
|
||||||
if old_version:
|
if old_version:
|
||||||
cmd.append('-v%s' % old_version)
|
cmd.append(f"-v{old_version}")
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
# An ubuntu.com e-mail address would make dpkg-buildpackage fail if there
|
# An ubuntu.com e-mail address would make dpkg-buildpackage fail if there
|
||||||
# wasn't an Ubuntu maintainer for an ubuntu-versioned package. LP: #1007042
|
# wasn't an Ubuntu maintainer for an ubuntu-versioned package. LP: #1007042
|
||||||
env.pop('DEBEMAIL', None)
|
env.pop("DEBEMAIL", None)
|
||||||
check_call(cmd, cwd=srcdir, env=env)
|
check_call(cmd, cwd=srcdir, env=env)
|
||||||
|
|
||||||
fn_base = pkg.source + '_' + bp_version.split(':', 1)[-1]
|
fn_base = pkg.source + "_" + bp_version.split(":", 1)[-1]
|
||||||
changes = fn_base + '_source.changes'
|
changes = fn_base + "_source.changes"
|
||||||
|
|
||||||
if build:
|
if build:
|
||||||
if 0 != do_build(workdir, fn_base + '.dsc', release, builder, update):
|
if 0 != do_build(workdir, fn_base + ".dsc", release, builder, update):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# None: sign with the default signature. False: don't sign
|
# None: sign with the default signature. False: don't sign
|
||||||
if keyid is not False:
|
if keyid is not False:
|
||||||
cmd = ['debsign']
|
cmd = ["debsign"]
|
||||||
if keyid:
|
if keyid:
|
||||||
cmd.append('-k' + keyid)
|
cmd.append("-k" + keyid)
|
||||||
cmd.append(changes)
|
cmd.append(changes)
|
||||||
check_call(cmd, cwd=workdir)
|
check_call(cmd, cwd=workdir)
|
||||||
if upload:
|
if upload:
|
||||||
@ -388,63 +428,68 @@ def do_backport(workdir, pkg, suffix, message, close, release, release_pocket,
|
|||||||
shutil.rmtree(srcdir)
|
shutil.rmtree(srcdir)
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(argv):
|
||||||
ubu_email()
|
ubu_email()
|
||||||
|
|
||||||
opts, (package_or_dsc,), config = parse(args[1:])
|
args, config = parse(argv[1:])
|
||||||
|
|
||||||
Launchpad.login_anonymously(service=opts.lpinstance)
|
Launchpad.login_anonymously(service=args.lpinstance)
|
||||||
|
|
||||||
if not opts.dest_releases:
|
if not args.dest_releases:
|
||||||
|
if lsb_release:
|
||||||
distinfo = lsb_release.get_distro_information()
|
distinfo = lsb_release.get_distro_information()
|
||||||
try:
|
try:
|
||||||
current_distro = distinfo['ID']
|
current_distro = distinfo["ID"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
error('No destination release specified and unable to guess yours.')
|
error("No destination release specified and unable to guess yours.")
|
||||||
if current_distro == "Ubuntu":
|
|
||||||
opts.dest_releases = [UbuntuDistroInfo().lts()]
|
|
||||||
if current_distro == "Debian":
|
|
||||||
opts.dest_releases = [DebianDistroInfo().stable()]
|
|
||||||
else:
|
else:
|
||||||
error(f"Unknown distribution {current_distro}, can't guess target release")
|
err, current_distro = subprocess.getstatusoutput("lsb_release --id --short")
|
||||||
|
if err:
|
||||||
|
error("Could not run lsb_release to retrieve distribution")
|
||||||
|
|
||||||
if opts.workdir:
|
if current_distro == "Ubuntu":
|
||||||
workdir = os.path.expanduser(opts.workdir)
|
args.dest_releases = [UbuntuDistroInfo().lts()]
|
||||||
|
elif current_distro == "Debian":
|
||||||
|
args.dest_releases = [DebianDistroInfo().stable()]
|
||||||
else:
|
else:
|
||||||
workdir = tempfile.mkdtemp(prefix='backportpackage-')
|
error("Unknown distribution %s, can't guess target release", current_distro)
|
||||||
|
|
||||||
|
if args.workdir:
|
||||||
|
workdir = os.path.expanduser(args.workdir)
|
||||||
|
else:
|
||||||
|
workdir = tempfile.mkdtemp(prefix="backportpackage-")
|
||||||
|
|
||||||
if not os.path.exists(workdir):
|
if not os.path.exists(workdir):
|
||||||
os.makedirs(workdir)
|
os.makedirs(workdir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pkg = find_package(opts.mirror,
|
pkg = find_package(
|
||||||
workdir,
|
args.mirror, workdir, args.package_or_dsc, args.version, args.source_release, config
|
||||||
package_or_dsc,
|
)
|
||||||
opts.version,
|
|
||||||
opts.source_release,
|
|
||||||
config)
|
|
||||||
pkg.pull()
|
pkg.pull()
|
||||||
|
|
||||||
for release in opts.dest_releases:
|
for release in args.dest_releases:
|
||||||
do_backport(workdir,
|
do_backport(
|
||||||
|
workdir,
|
||||||
pkg,
|
pkg,
|
||||||
opts.suffix,
|
args.suffix,
|
||||||
opts.message,
|
args.message,
|
||||||
opts.close,
|
args.close,
|
||||||
release,
|
release,
|
||||||
opts.release_pocket,
|
args.release_pocket,
|
||||||
opts.build,
|
args.build,
|
||||||
opts.builder,
|
args.builder,
|
||||||
opts.update,
|
args.update,
|
||||||
opts.upload,
|
args.upload,
|
||||||
opts.keyid,
|
args.keyid,
|
||||||
opts.prompt)
|
args.prompt,
|
||||||
|
)
|
||||||
except DownloadError as e:
|
except DownloadError as e:
|
||||||
error(str(e))
|
error("%s", str(e))
|
||||||
finally:
|
finally:
|
||||||
if not opts.workdir:
|
if not args.workdir:
|
||||||
shutil.rmtree(workdir)
|
shutil.rmtree(workdir)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
sys.exit(main(sys.argv))
|
sys.exit(main(sys.argv))
|
||||||
|
@ -36,7 +36,7 @@ _pbuilder-dist()
|
|||||||
for distro in $(ubuntu-distro-info --all; debian-distro-info --all) stable testing unstable; do
|
for distro in $(ubuntu-distro-info --all; debian-distro-info --all) stable testing unstable; do
|
||||||
for builder in pbuilder cowbuilder; do
|
for builder in pbuilder cowbuilder; do
|
||||||
echo "$builder-$distro"
|
echo "$builder-$distro"
|
||||||
for arch in i386 amd64 armel armhf; do
|
for arch in i386 amd64 armhf; do
|
||||||
echo "$builder-$distro-$arch"
|
echo "$builder-$distro-$arch"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
131
check-mir
131
check-mir
@ -21,69 +21,116 @@
|
|||||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
import sys
|
# pylint: disable=invalid-name
|
||||||
import optparse
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
"""Check if any of a package's build or binary dependencies are in universe or multiverse.
|
||||||
|
|
||||||
|
Run this inside an unpacked source package
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os.path
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
import apt
|
import apt
|
||||||
|
|
||||||
|
|
||||||
def check_support(apt_cache, pkgname, alt=False):
|
def check_support(apt_cache, pkgname, alt=False):
|
||||||
'''Check if pkgname is in main or restricted.
|
"""Check if pkgname is in main or restricted.
|
||||||
|
|
||||||
This prints messages if a package is not in main/restricted, or only
|
This prints messages if a package is not in main/restricted, or only
|
||||||
partially (i. e. source in main, but binary in universe).
|
partially (i. e. source in main, but binary in universe).
|
||||||
'''
|
"""
|
||||||
if alt:
|
if alt:
|
||||||
prefix = ' ... alternative ' + pkgname
|
prefix = " ... alternative " + pkgname
|
||||||
else:
|
else:
|
||||||
prefix = ' * ' + pkgname
|
prefix = " * " + pkgname
|
||||||
|
|
||||||
try:
|
prov_packages = apt_cache.get_providing_packages(pkgname)
|
||||||
|
if pkgname in apt_cache:
|
||||||
pkg = apt_cache[pkgname]
|
pkg = apt_cache[pkgname]
|
||||||
except KeyError:
|
|
||||||
print(prefix, 'does not exist (pure virtual?)', file=sys.stderr)
|
# If this is a virtual package, iterate through the binary packages that
|
||||||
|
# provide this, and ensure they are all in Main. Source packages in and of
|
||||||
|
# themselves cannot provide virtual packages, only binary packages can.
|
||||||
|
elif len(prov_packages) > 0:
|
||||||
|
supported, unsupported = [], []
|
||||||
|
for pkg in prov_packages:
|
||||||
|
candidate = pkg.candidate
|
||||||
|
if candidate:
|
||||||
|
section = candidate.section
|
||||||
|
if section.startswith("universe") or section.startswith("multiverse"):
|
||||||
|
unsupported.append(pkg.name)
|
||||||
|
else:
|
||||||
|
supported.append(pkg.name)
|
||||||
|
|
||||||
|
if len(supported) > 0:
|
||||||
|
msg = "is a virtual package, which is provided by the following "
|
||||||
|
msg += "candidates in Main: " + " ".join(supported)
|
||||||
|
print(prefix, msg)
|
||||||
|
elif len(unsupported) > 0:
|
||||||
|
msg = "is a virtual package, but is only provided by the "
|
||||||
|
msg += "following non-Main candidates: " + " ".join(unsupported)
|
||||||
|
print(prefix, msg, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
msg = "is a virtual package that exists but is not provided by "
|
||||||
|
msg += "package currently in the archive. Proceed with caution."
|
||||||
|
print(prefix, msg, file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(prefix, "does not exist", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
section = pkg.candidate.section
|
section = pkg.candidate.section
|
||||||
if section.startswith('universe') or section.startswith('multiverse'):
|
if section.startswith("universe") or section.startswith("multiverse"):
|
||||||
# check if the source package is in main and thus will only need binary
|
# check if the source package is in main and thus will only need binary
|
||||||
# promotion
|
# promotion
|
||||||
source_records = apt.apt_pkg.SourceRecords()
|
source_records = apt.apt_pkg.SourceRecords()
|
||||||
if not source_records.lookup(pkg.candidate.source_name):
|
if not source_records.lookup(pkg.candidate.source_name):
|
||||||
print('ERROR: Cannot lookup source package for', pkg.name,
|
print("ERROR: Cannot lookup source package for", pkg.name, file=sys.stderr)
|
||||||
file=sys.stderr)
|
print(prefix, "package is in", section.split("/")[0])
|
||||||
print(prefix, 'package is in', section.split('/')[0])
|
|
||||||
return False
|
return False
|
||||||
src = apt.apt_pkg.TagSection(source_records.record)
|
src = apt.apt_pkg.TagSection(source_records.record)
|
||||||
if (src['Section'].startswith('universe') or
|
if src["Section"].startswith("universe") or src["Section"].startswith("multiverse"):
|
||||||
src['Section'].startswith('multiverse')):
|
print(prefix, "binary and source package is in", section.split("/")[0])
|
||||||
print(prefix, 'binary and source package is in',
|
|
||||||
section.split('/')[0])
|
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
print(prefix, 'is in', section.split('/')[0] + ', but its source',
|
print(
|
||||||
|
prefix,
|
||||||
|
"is in",
|
||||||
|
section.split("/")[0] + ", but its source",
|
||||||
pkg.candidate.source_name,
|
pkg.candidate.source_name,
|
||||||
'is already in main; file an ubuntu-archive bug for '
|
"is already in main; file an ubuntu-archive bug for "
|
||||||
'promoting the current preferred alternative')
|
"promoting the current preferred alternative",
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if alt:
|
if alt:
|
||||||
print(prefix, 'is already in main; consider preferring it')
|
print(prefix, "is already in main; consider preferring it")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_build_dependencies(apt_cache, control):
|
def check_build_dependencies(apt_cache, control):
|
||||||
print('Checking support status of build dependencies...')
|
print("Checking support status of build dependencies...")
|
||||||
|
|
||||||
any_unsupported = False
|
any_unsupported = False
|
||||||
|
|
||||||
for field in ('Build-Depends', 'Build-Depends-Indep'):
|
for field in ("Build-Depends", "Build-Depends-Indep"):
|
||||||
if field not in control.section:
|
if field not in control.section:
|
||||||
continue
|
continue
|
||||||
for or_group in apt.apt_pkg.parse_src_depends(control.section[field]):
|
for or_group in apt.apt_pkg.parse_src_depends(control.section[field]):
|
||||||
pkgname = or_group[0][0]
|
pkgname = or_group[0][0]
|
||||||
|
|
||||||
|
# debhelper-compat is expected to be a build dependency of every
|
||||||
|
# package, so it is a red herring to display it in this report.
|
||||||
|
# (src:debhelper is in Ubuntu Main anyway)
|
||||||
|
if pkgname == "debhelper-compat":
|
||||||
|
continue
|
||||||
|
|
||||||
if not check_support(apt_cache, pkgname):
|
if not check_support(apt_cache, pkgname):
|
||||||
# check non-preferred alternatives
|
# check non-preferred alternatives
|
||||||
for altpkg in or_group[1:]:
|
for altpkg in or_group[1:]:
|
||||||
@ -98,20 +145,19 @@ def check_build_dependencies(apt_cache, control):
|
|||||||
def check_binary_dependencies(apt_cache, control):
|
def check_binary_dependencies(apt_cache, control):
|
||||||
any_unsupported = False
|
any_unsupported = False
|
||||||
|
|
||||||
print('\nChecking support status of binary dependencies...')
|
print("\nChecking support status of binary dependencies...")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
next(control)
|
next(control)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
for field in ('Depends', 'Pre-Depends', 'Recommends'):
|
for field in ("Depends", "Pre-Depends", "Recommends"):
|
||||||
if field not in control.section:
|
if field not in control.section:
|
||||||
continue
|
continue
|
||||||
for or_group in apt.apt_pkg.parse_src_depends(
|
for or_group in apt.apt_pkg.parse_src_depends(control.section[field]):
|
||||||
control.section[field]):
|
|
||||||
pkgname = or_group[0][0]
|
pkgname = or_group[0][0]
|
||||||
if pkgname.startswith('$'):
|
if pkgname.startswith("$"):
|
||||||
continue
|
continue
|
||||||
if not check_support(apt_cache, pkgname):
|
if not check_support(apt_cache, pkgname):
|
||||||
# check non-preferred alternatives
|
# check non-preferred alternatives
|
||||||
@ -125,32 +171,33 @@ def check_binary_dependencies(apt_cache, control):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
description = "Check if any of a package's build or binary " + \
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
"dependencies are in universe or multiverse. " + \
|
|
||||||
"Run this inside an unpacked source package"
|
|
||||||
parser = optparse.OptionParser(description=description)
|
|
||||||
parser.parse_args()
|
parser.parse_args()
|
||||||
apt_cache = apt.Cache()
|
apt_cache = apt.Cache()
|
||||||
|
|
||||||
if not os.path.exists('debian/control'):
|
if not os.path.exists("debian/control"):
|
||||||
print('debian/control not found. You need to run this tool in a '
|
print(
|
||||||
'source package directory', file=sys.stderr)
|
"debian/control not found. You need to run this tool in a source package directory",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# get build dependencies from debian/control
|
# get build dependencies from debian/control
|
||||||
control = apt.apt_pkg.TagFile(open('debian/control'))
|
control = apt.apt_pkg.TagFile(open("debian/control", encoding="utf-8"))
|
||||||
next(control)
|
next(control)
|
||||||
|
|
||||||
unsupported_build_deps = check_build_dependencies(apt_cache, control)
|
unsupported_build_deps = check_build_dependencies(apt_cache, control)
|
||||||
unsupported_binary_deps = check_binary_dependencies(apt_cache, control)
|
unsupported_binary_deps = check_binary_dependencies(apt_cache, control)
|
||||||
|
|
||||||
if unsupported_build_deps or unsupported_binary_deps:
|
if unsupported_build_deps or unsupported_binary_deps:
|
||||||
print('\nPlease check https://wiki.ubuntu.com/MainInclusionProcess if '
|
print(
|
||||||
'this source package needs to get into in main/restricted, or '
|
"\nPlease check https://wiki.ubuntu.com/MainInclusionProcess if "
|
||||||
'reconsider if the package really needs above dependencies.')
|
"this source package needs to get into in main/restricted, or "
|
||||||
|
"reconsider if the package really needs above dependencies."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print('All dependencies are supported in main or restricted.')
|
print("All dependencies are supported in main or restricted.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
1
debian/.gitignore
vendored
Normal file
1
debian/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
files
|
264
debian/changelog
vendored
264
debian/changelog
vendored
@ -1,3 +1,267 @@
|
|||||||
|
ubuntu-dev-tools (0.206) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Dan Bungert ]
|
||||||
|
* mk-sbuild: enable pkgmaintainermangler
|
||||||
|
|
||||||
|
[ Shengjing Zhu ]
|
||||||
|
* import-bug-from-debian: package option is overridden and not used
|
||||||
|
|
||||||
|
[ Fernando Bravo Hernández ]
|
||||||
|
* Parsing arch parameter to getBinaryPackage() (LP: #2081861)
|
||||||
|
|
||||||
|
[ Simon Quigley ]
|
||||||
|
* Read ~/.devscripts in a more robust way, to ideally pick up multi-line
|
||||||
|
variables (Closes: #725418).
|
||||||
|
* mk-sbuild: default to using UTC for schroots (LP: #2097159).
|
||||||
|
* syncpackage: s/syncblacklist/syncblocklist/g
|
||||||
|
* syncpackage: Cache the sync blocklist in-memory, so it's not fetched
|
||||||
|
multiple times when syncing more than one package.
|
||||||
|
* syncpackage: Catch exceptions cleanly, simply skipping to the next
|
||||||
|
package (erring on the side of caution) if there is an error doing the
|
||||||
|
download (LP: #1943286).
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Tue, 04 Mar 2025 13:43:15 -0600
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.205) unstable; urgency=medium
|
||||||
|
|
||||||
|
* [syncpackage] When syncing multiple packages, if one of the packages is in
|
||||||
|
the sync blocklist, do not exit, simply continue.
|
||||||
|
* [syncpackage] Do not use exit(1) on an error or exception unless it
|
||||||
|
applies to all packages, instead return None so we can continue to the
|
||||||
|
next package.
|
||||||
|
* [syncpackage] Add support for -y or --yes, noted that it should be used
|
||||||
|
with care.
|
||||||
|
* Update Standards-Version to 4.7.2, no changes needed.
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Sat, 01 Mar 2025 11:29:54 -0600
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.204) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Simon Quigley ]
|
||||||
|
* Update Standards-Version to 4.7.1, no changes needed.
|
||||||
|
* Add several Lintian overrides related to .pyc files.
|
||||||
|
* Add my name to the copyright file.
|
||||||
|
* Rename bitesize to lp-bitesize (Closes: #1076224).
|
||||||
|
* Add a manpage for running-autopkgtests.
|
||||||
|
* Add a large warning at the top of mk-sbuild encouraging the use of the
|
||||||
|
unshare backend. This is to provide ample warning to users.
|
||||||
|
* Remove mail line from default ~/.sbuildrc, to resolve the undeclared
|
||||||
|
dependency on sendmail (Closes: #1074632).
|
||||||
|
|
||||||
|
[ Julien Plissonneau Duquène ]
|
||||||
|
* Fix reverse-depends -b crash on packages that b-d on themselves
|
||||||
|
(Closes: #1087760).
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Mon, 24 Feb 2025 19:54:39 -0600
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.203) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Steve Langasek ]
|
||||||
|
* ubuntu-build: handle TOCTOU issue with the "can be retried" value on
|
||||||
|
builds.
|
||||||
|
* Recommend sbuild over pbuilder. sbuild is the tool recommended by
|
||||||
|
Ubuntu developers whose behavior most closely approximates Launchpad
|
||||||
|
builds.
|
||||||
|
|
||||||
|
[ Florent 'Skia' Jacquet ]
|
||||||
|
* import-bug-from-debian: handle multipart message (Closes: #969510)
|
||||||
|
|
||||||
|
[ Benjamin Drung ]
|
||||||
|
* import-bug-from-debian: add type hints
|
||||||
|
* Bump Standards-Version to 4.7.0
|
||||||
|
* Bump year and add missing files to copyright
|
||||||
|
* setup.py: add pm-helper
|
||||||
|
* Format code with black and isort
|
||||||
|
* Address several issues pointed out by Pylint
|
||||||
|
* Depend on python3-yaml for pm-helper
|
||||||
|
|
||||||
|
-- Benjamin Drung <bdrung@debian.org> Sat, 02 Nov 2024 18:19:24 +0100
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.202) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Steve Langasek ]
|
||||||
|
* ubuntu-build: support --batch with no package names to retry all
|
||||||
|
* ubuntu-build: in batch mode, print a count of packages retried
|
||||||
|
* ubuntu-build: make the --arch option top-level.
|
||||||
|
This gets rid of the fugly --arch2 option
|
||||||
|
* ubuntu-build: support retrying builds in other states that failed-to-build
|
||||||
|
* ubuntu-build: Handling of proposed vs release pocket default for ppas
|
||||||
|
* ubuntu-build: update manpage
|
||||||
|
|
||||||
|
[ Chris Peterson ]
|
||||||
|
* Replace Depends on python3-launchpadlib with Depends on
|
||||||
|
python3-launchpadlib-desktop (LP: #2049217)
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@ubuntu.com> Fri, 12 Apr 2024 23:33:14 -0500
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.201) unstable; urgency=medium
|
||||||
|
|
||||||
|
* running-autopkgtests: fix packaging to make the script available
|
||||||
|
(LP: #2055466)
|
||||||
|
|
||||||
|
-- Chris Peterson <chris.peterson@canonical.com> Thu, 29 Feb 2024 11:09:14 -0800
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.200) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Gianfranco Costamagna ]
|
||||||
|
* Team upload
|
||||||
|
|
||||||
|
[ Chris Peterson ]
|
||||||
|
* Add support to see currently running autopkgtests (running-autopkgtests)
|
||||||
|
* running-autopkgtests: use f-strings
|
||||||
|
|
||||||
|
[ Athos Ribeiro ]
|
||||||
|
* syncpackage: log LP authentication errors before halting.
|
||||||
|
|
||||||
|
[ Ying-Chun Liu (PaulLiu) ]
|
||||||
|
* Drop qemu-debootstrap
|
||||||
|
qemu-debootstrap is deprecated for a while. In newer qemu release
|
||||||
|
the command is totally removed. We can use debootstrap directly.
|
||||||
|
Signed-off-by: Ying-Chun Liu (PaulLiu) <paulliu@debian.org>
|
||||||
|
|
||||||
|
[ Logan Rosen ]
|
||||||
|
* Don't rely on debootstrap for validating Ubuntu distro
|
||||||
|
|
||||||
|
-- Gianfranco Costamagna <locutusofborg@debian.org> Thu, 15 Feb 2024 17:53:48 +0100
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.199) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Simon Quigley ]
|
||||||
|
* Add my name to Uploaders.
|
||||||
|
|
||||||
|
[ Steve Langasek ]
|
||||||
|
* Introduce a pm-helper tool.
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Mon, 29 Jan 2024 10:03:22 -0600
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.198) unstable; urgency=medium
|
||||||
|
|
||||||
|
* In check-mir, ignore debhelper-compat when checking the build
|
||||||
|
dependencies. This is expected to be a build dependency of all packages,
|
||||||
|
so warning about it in any way is surely a red herring.
|
||||||
|
* Add proper support for virtual packages in check-mir, basing the
|
||||||
|
determination solely off of binary packages. This is not expected to be a
|
||||||
|
typical case.
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Wed, 10 Jan 2024 20:04:02 -0600
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.197) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Update the manpage for syncpackage to reflect the ability to sync
|
||||||
|
multiple packages at once.
|
||||||
|
* When using pull-*-source to grab a package which already has a defined
|
||||||
|
Vcs- field, display the exact same warning message `apt source` does.
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Tue, 03 Oct 2023 14:01:25 -0500
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.196) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Allow the user to sync multiple packages at one time (LP: #1756748).
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Fri, 04 Aug 2023 14:37:59 -0500
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.195) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Add support for the non-free-firmware components in all tools already
|
||||||
|
referencing non-free.
|
||||||
|
|
||||||
|
-- Simon Quigley <tsimonq2@debian.org> Wed, 26 Jul 2023 13:03:31 -0500
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.194) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Gianfranco Costamagna ]
|
||||||
|
* ubuntu-build: For some reasons, now you need to be authenticated before
|
||||||
|
trying to use the "PersonTeam" class features.
|
||||||
|
Do it at the begin instead of replicating the same code inside the
|
||||||
|
tool itself.
|
||||||
|
|
||||||
|
[ Steve Langasek ]
|
||||||
|
* Remove references to deprecated
|
||||||
|
http://people.canonical.com/~ubuntu-archive.
|
||||||
|
* Remove references to architectures not supported in any active
|
||||||
|
Ubuntu release.
|
||||||
|
* Remove references to ftpmaster.internal. When this name is resolvable
|
||||||
|
but firewalled, syncpackage hangs; and these are tools for developers,
|
||||||
|
not for running in an automated context in the DCs where
|
||||||
|
ftpmaster.internal is reachable.
|
||||||
|
* Excise all references to cdbs (including in test cases)
|
||||||
|
* Set apt preferences for the -proposed pocket in mk-sbuild so that
|
||||||
|
it works as expected for lunar and forward.
|
||||||
|
|
||||||
|
[ Robie Basak ]
|
||||||
|
* ubuntutools/misc: swap iter_content for raw stream with "Accept-Encoding:
|
||||||
|
identity" to fix .diff.gz downloads (LP: #2025748).
|
||||||
|
|
||||||
|
[ Vladimir Petko ]
|
||||||
|
* Fix a typo introduced in the last upload that made mk-sbuild fail
|
||||||
|
unconditionally. LP: #2017177.
|
||||||
|
|
||||||
|
-- Gianfranco Costamagna <locutusofborg@debian.org> Sat, 08 Jul 2023 08:42:05 +0200
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.193) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Don't run linters at build time, or in autopkgtests. (Closes: #1031436).
|
||||||
|
|
||||||
|
-- Stefano Rivera <stefanor@debian.org> Sat, 25 Feb 2023 13:19:56 -0400
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.192) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Benjamin Drung ]
|
||||||
|
* sponsor-patch:
|
||||||
|
+ Ignore exit code 1 of debdiff call.
|
||||||
|
+ Use --skip-patches instead of --no-preparation with dpkg-source -x.
|
||||||
|
* Demote bzr/brz from Recommends to Suggests, as nowadays git is the way.
|
||||||
|
Closes: #940531
|
||||||
|
* Use PEP440 compliant version in setup.py (LP: #1991606)
|
||||||
|
* Fix issues found by flake8 on the Python scripts
|
||||||
|
* Check Python scripts with flake8 again
|
||||||
|
* Format Python code with black and run black during package build
|
||||||
|
* Sort Python imports with isort and run isort during package build
|
||||||
|
* Replace deprecated optparse with argparse
|
||||||
|
* requestbackport: Remove useless loop from locate_package
|
||||||
|
* reverse-depends: Restore field titles format
|
||||||
|
* test: Fix deprecated return value for test case
|
||||||
|
* Fix all errors and warnings found by pylint and implement most refactorings
|
||||||
|
and conventions. Run pylint during package build again.
|
||||||
|
* Bump Standards-Version to 4.6.2
|
||||||
|
* Drop unneeded X-Python3-Version from d/control
|
||||||
|
|
||||||
|
[ Masahiro Yamada ]
|
||||||
|
* mk-sbuild:
|
||||||
|
+ Handle the new location of the Debian bullseye security archive.
|
||||||
|
Closes: #1001832; LP: #1955116
|
||||||
|
|
||||||
|
[ Mattia Rizzolo ]
|
||||||
|
* requestbackport:
|
||||||
|
+ Apply patch from Krytarik Raido and Unit 193 to update the template and
|
||||||
|
workflow after the new Ubuntu Backport process has been established.
|
||||||
|
LP: #1959115
|
||||||
|
|
||||||
|
-- Benjamin Drung <bdrung@debian.org> Wed, 01 Feb 2023 12:45:15 +0100
|
||||||
|
|
||||||
|
ubuntu-dev-tools (0.191) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Dan Streetman ]
|
||||||
|
* lpapicache:
|
||||||
|
+ Make sure that login() actually logins and doesn't use cached credentials.
|
||||||
|
* ubuntu-build:
|
||||||
|
+ Fix crash caused by a change in lpapicache that changed the default
|
||||||
|
operation mode from authenticated to anonymous. LP: #1984113
|
||||||
|
|
||||||
|
[ Stefano Rivera ]
|
||||||
|
* backportpackage:
|
||||||
|
+ Add support for lsb-release-minimal, which doesn't have a Python module.
|
||||||
|
Thanks to Gioele Barabucci for the patch. Closes: #1020901; LP: #1991828
|
||||||
|
|
||||||
|
[ Mattia Rizzolo ]
|
||||||
|
* ubuntutools/archive.py:
|
||||||
|
+ Fix operation of SourcePackage._source_urls() (as used, for example, in
|
||||||
|
SourcePackage.pull() called by backportpackage) to also work when the
|
||||||
|
class is instantiated with a URL as .dsc. Fixes regression from v0.184.
|
||||||
|
Thanks to Unit 193 for the initial patch.
|
||||||
|
|
||||||
|
-- Mattia Rizzolo <mattia@debian.org> Tue, 11 Oct 2022 13:56:03 +0200
|
||||||
|
|
||||||
ubuntu-dev-tools (0.190) unstable; urgency=medium
|
ubuntu-dev-tools (0.190) unstable; urgency=medium
|
||||||
|
|
||||||
[ Dimitri John Ledkov ]
|
[ Dimitri John Ledkov ]
|
||||||
|
25
debian/control
vendored
25
debian/control
vendored
@ -6,7 +6,9 @@ Uploaders:
|
|||||||
Benjamin Drung <bdrung@debian.org>,
|
Benjamin Drung <bdrung@debian.org>,
|
||||||
Stefano Rivera <stefanor@debian.org>,
|
Stefano Rivera <stefanor@debian.org>,
|
||||||
Mattia Rizzolo <mattia@debian.org>,
|
Mattia Rizzolo <mattia@debian.org>,
|
||||||
|
Simon Quigley <tsimonq2@debian.org>,
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
|
black <!nocheck>,
|
||||||
dctrl-tools,
|
dctrl-tools,
|
||||||
debhelper-compat (= 13),
|
debhelper-compat (= 13),
|
||||||
devscripts (>= 2.11.0~),
|
devscripts (>= 2.11.0~),
|
||||||
@ -14,23 +16,26 @@ Build-Depends:
|
|||||||
dh-python,
|
dh-python,
|
||||||
distro-info (>= 0.2~),
|
distro-info (>= 0.2~),
|
||||||
flake8,
|
flake8,
|
||||||
|
isort <!nocheck>,
|
||||||
lsb-release,
|
lsb-release,
|
||||||
|
pylint <!nocheck>,
|
||||||
python3-all,
|
python3-all,
|
||||||
python3-apt,
|
python3-apt,
|
||||||
|
python3-dateutil,
|
||||||
python3-debian,
|
python3-debian,
|
||||||
python3-debianbts,
|
python3-debianbts,
|
||||||
python3-distro-info,
|
python3-distro-info,
|
||||||
python3-httplib2,
|
python3-httplib2,
|
||||||
python3-launchpadlib,
|
python3-launchpadlib-desktop,
|
||||||
python3-pytest,
|
python3-pytest,
|
||||||
python3-requests <!nocheck>,
|
python3-requests <!nocheck>,
|
||||||
python3-setuptools,
|
python3-setuptools,
|
||||||
Standards-Version: 4.6.1
|
python3-yaml <!nocheck>,
|
||||||
|
Standards-Version: 4.7.2
|
||||||
Rules-Requires-Root: no
|
Rules-Requires-Root: no
|
||||||
Vcs-Git: https://git.launchpad.net/ubuntu-dev-tools
|
Vcs-Git: https://git.launchpad.net/ubuntu-dev-tools
|
||||||
Vcs-Browser: https://git.launchpad.net/ubuntu-dev-tools
|
Vcs-Browser: https://git.launchpad.net/ubuntu-dev-tools
|
||||||
Homepage: https://launchpad.net/ubuntu-dev-tools
|
Homepage: https://launchpad.net/ubuntu-dev-tools
|
||||||
X-Python3-Version: >= 3.6
|
|
||||||
|
|
||||||
Package: ubuntu-dev-tools
|
Package: ubuntu-dev-tools
|
||||||
Architecture: all
|
Architecture: all
|
||||||
@ -49,9 +54,10 @@ Depends:
|
|||||||
python3-debianbts,
|
python3-debianbts,
|
||||||
python3-distro-info,
|
python3-distro-info,
|
||||||
python3-httplib2,
|
python3-httplib2,
|
||||||
python3-launchpadlib,
|
python3-launchpadlib-desktop,
|
||||||
python3-lazr.restfulclient,
|
python3-lazr.restfulclient,
|
||||||
python3-ubuntutools (= ${binary:Version}),
|
python3-ubuntutools (= ${binary:Version}),
|
||||||
|
python3-yaml,
|
||||||
sensible-utils,
|
sensible-utils,
|
||||||
sudo,
|
sudo,
|
||||||
tzdata,
|
tzdata,
|
||||||
@ -59,8 +65,6 @@ Depends:
|
|||||||
${perl:Depends},
|
${perl:Depends},
|
||||||
Recommends:
|
Recommends:
|
||||||
arch-test,
|
arch-test,
|
||||||
bzr | brz,
|
|
||||||
bzr-builddeb | brz-debian,
|
|
||||||
ca-certificates,
|
ca-certificates,
|
||||||
debian-archive-keyring,
|
debian-archive-keyring,
|
||||||
debian-keyring,
|
debian-keyring,
|
||||||
@ -68,12 +72,14 @@ Recommends:
|
|||||||
genisoimage,
|
genisoimage,
|
||||||
lintian,
|
lintian,
|
||||||
patch,
|
patch,
|
||||||
pbuilder | cowbuilder | sbuild,
|
sbuild | pbuilder | cowbuilder,
|
||||||
python3-dns,
|
python3-dns,
|
||||||
quilt,
|
quilt,
|
||||||
reportbug (>= 3.39ubuntu1),
|
reportbug (>= 3.39ubuntu1),
|
||||||
ubuntu-keyring | ubuntu-archive-keyring,
|
ubuntu-keyring | ubuntu-archive-keyring,
|
||||||
Suggests:
|
Suggests:
|
||||||
|
bzr | brz,
|
||||||
|
bzr-builddeb | brz-debian,
|
||||||
qemu-user-static,
|
qemu-user-static,
|
||||||
Description: useful tools for Ubuntu developers
|
Description: useful tools for Ubuntu developers
|
||||||
This is a collection of useful tools that Ubuntu developers use to make their
|
This is a collection of useful tools that Ubuntu developers use to make their
|
||||||
@ -112,6 +118,8 @@ Description: useful tools for Ubuntu developers
|
|||||||
- requestsync - files a sync request with Debian changelog and rationale.
|
- requestsync - files a sync request with Debian changelog and rationale.
|
||||||
- reverse-depends - find the reverse dependencies (or build dependencies) of
|
- reverse-depends - find the reverse dependencies (or build dependencies) of
|
||||||
a package.
|
a package.
|
||||||
|
- running-autopkgtests - lists the currently running and/or queued
|
||||||
|
autopkgtests on the Ubuntu autopkgtest infrastructure
|
||||||
- seeded-in-ubuntu - query if a package is safe to upload during a freeze.
|
- seeded-in-ubuntu - query if a package is safe to upload during a freeze.
|
||||||
- setup-packaging-environment - assistant to get an Ubuntu installation
|
- setup-packaging-environment - assistant to get an Ubuntu installation
|
||||||
ready for packaging work.
|
ready for packaging work.
|
||||||
@ -130,10 +138,11 @@ Package: python3-ubuntutools
|
|||||||
Architecture: all
|
Architecture: all
|
||||||
Section: python
|
Section: python
|
||||||
Depends:
|
Depends:
|
||||||
|
python3-dateutil,
|
||||||
python3-debian,
|
python3-debian,
|
||||||
python3-distro-info,
|
python3-distro-info,
|
||||||
python3-httplib2,
|
python3-httplib2,
|
||||||
python3-launchpadlib,
|
python3-launchpadlib-desktop,
|
||||||
python3-lazr.restfulclient,
|
python3-lazr.restfulclient,
|
||||||
python3-requests,
|
python3-requests,
|
||||||
sensible-utils,
|
sensible-utils,
|
||||||
|
25
debian/copyright
vendored
25
debian/copyright
vendored
@ -11,6 +11,7 @@ Files: backportpackage
|
|||||||
doc/check-symbols.1
|
doc/check-symbols.1
|
||||||
doc/requestsync.1
|
doc/requestsync.1
|
||||||
doc/ubuntu-iso.1
|
doc/ubuntu-iso.1
|
||||||
|
doc/running-autopkgtests.1
|
||||||
GPL-2
|
GPL-2
|
||||||
README.updates
|
README.updates
|
||||||
requestsync
|
requestsync
|
||||||
@ -19,12 +20,13 @@ Files: backportpackage
|
|||||||
ubuntu-iso
|
ubuntu-iso
|
||||||
ubuntutools/requestsync/*.py
|
ubuntutools/requestsync/*.py
|
||||||
Copyright: 2007, Albert Damen <albrt@gmx.net>
|
Copyright: 2007, Albert Damen <albrt@gmx.net>
|
||||||
2010-2022, Benjamin Drung <bdrung@ubuntu.com>
|
2010-2024, Benjamin Drung <bdrung@ubuntu.com>
|
||||||
2007-2010, Canonical Ltd.
|
2007-2023, Canonical Ltd.
|
||||||
2006-2007, Daniel Holbach <daniel.holbach@ubuntu.com>
|
2006-2007, Daniel Holbach <daniel.holbach@ubuntu.com>
|
||||||
2010, Evan Broder <evan@ebroder.net>
|
2010, Evan Broder <evan@ebroder.net>
|
||||||
2006-2007, Luke Yelavich <themuso@ubuntu.com>
|
2006-2007, Luke Yelavich <themuso@ubuntu.com>
|
||||||
2009-2010, Michael Bienia <geser@ubuntu.com>
|
2009-2010, Michael Bienia <geser@ubuntu.com>
|
||||||
|
2024-2025, Simon Quigley <tsimonq2@debian.org>
|
||||||
2010-2011, Stefano Rivera <stefanor@ubuntu.com>
|
2010-2011, Stefano Rivera <stefanor@ubuntu.com>
|
||||||
2008, Stephan Hermann <sh@sourcecode.de>
|
2008, Stephan Hermann <sh@sourcecode.de>
|
||||||
2007, Steve Kowalik <stevenk@ubuntu.com>
|
2007, Steve Kowalik <stevenk@ubuntu.com>
|
||||||
@ -72,21 +74,28 @@ License: GPL-2+
|
|||||||
On Debian systems, the complete text of the GNU General Public License
|
On Debian systems, the complete text of the GNU General Public License
|
||||||
version 2 can be found in the /usr/share/common-licenses/GPL-2 file.
|
version 2 can be found in the /usr/share/common-licenses/GPL-2 file.
|
||||||
|
|
||||||
Files: doc/bitesize.1
|
Files: doc/lp-bitesize.1
|
||||||
doc/check-mir.1
|
doc/check-mir.1
|
||||||
doc/grab-merge.1
|
doc/grab-merge.1
|
||||||
doc/merge-changelog.1
|
doc/merge-changelog.1
|
||||||
|
doc/pm-helper.1
|
||||||
doc/setup-packaging-environment.1
|
doc/setup-packaging-environment.1
|
||||||
doc/syncpackage.1
|
doc/syncpackage.1
|
||||||
bitesize
|
lp-bitesize
|
||||||
check-mir
|
check-mir
|
||||||
GPL-3
|
GPL-3
|
||||||
grab-merge
|
grab-merge
|
||||||
merge-changelog
|
merge-changelog
|
||||||
|
pm-helper
|
||||||
|
pyproject.toml
|
||||||
|
run-linters
|
||||||
|
running-autopkgtests
|
||||||
setup-packaging-environment
|
setup-packaging-environment
|
||||||
syncpackage
|
syncpackage
|
||||||
Copyright: 2010, Benjamin Drung <bdrung@ubuntu.com>
|
ubuntutools/running_autopkgtests.py
|
||||||
2007-2011, Canonical Ltd.
|
ubuntutools/utils.py
|
||||||
|
Copyright: 2010-2024, Benjamin Drung <bdrung@ubuntu.com>
|
||||||
|
2007-2024, Canonical Ltd.
|
||||||
2008, Jonathan Patrick Davies <jpds@ubuntu.com>
|
2008, Jonathan Patrick Davies <jpds@ubuntu.com>
|
||||||
2008-2010, Martin Pitt <martin.pitt@canonical.com>
|
2008-2010, Martin Pitt <martin.pitt@canonical.com>
|
||||||
2009, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
|
2009, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
|
||||||
@ -174,11 +183,13 @@ Files: doc/pull-debian-debdiff.1
|
|||||||
ubuntutools/update_maintainer.py
|
ubuntutools/update_maintainer.py
|
||||||
ubuntutools/version.py
|
ubuntutools/version.py
|
||||||
update-maintainer
|
update-maintainer
|
||||||
Copyright: 2009-2011, Benjamin Drung <bdrung@ubuntu.com>
|
.pylintrc
|
||||||
|
Copyright: 2009-2024, Benjamin Drung <bdrung@ubuntu.com>
|
||||||
2010, Evan Broder <evan@ebroder.net>
|
2010, Evan Broder <evan@ebroder.net>
|
||||||
2008, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
|
2008, Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
|
||||||
2010-2011, Stefano Rivera <stefanor@ubuntu.com>
|
2010-2011, Stefano Rivera <stefanor@ubuntu.com>
|
||||||
2017-2021, Dan Streetman <ddstreet@canonical.com>
|
2017-2021, Dan Streetman <ddstreet@canonical.com>
|
||||||
|
2024, Canonical Ltd.
|
||||||
License: ISC
|
License: ISC
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
1
debian/rules
vendored
1
debian/rules
vendored
@ -7,7 +7,6 @@ override_dh_auto_clean:
|
|||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
|
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
|
||||||
flake8 -v --max-line-length=99
|
|
||||||
python3 -m pytest -v ubuntutools
|
python3 -m pytest -v ubuntutools
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
3
debian/source/lintian-overrides
vendored
Normal file
3
debian/source/lintian-overrides
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# pyc files are machine-generated; they're expected to have long lines and have unstated copyright
|
||||||
|
source: file-without-copyright-information *.pyc [debian/copyright]
|
||||||
|
source: very-long-line-length-in-source-file * > 512 [*.pyc:*]
|
5
debian/tests/control
vendored
5
debian/tests/control
vendored
@ -1,8 +1,3 @@
|
|||||||
Test-Command: flake8 -v --max-line-length=99
|
|
||||||
Depends:
|
|
||||||
flake8,
|
|
||||||
Restrictions: allow-stderr
|
|
||||||
|
|
||||||
Test-Command: python3 -m pytest -v ubuntutools
|
Test-Command: python3 -m pytest -v ubuntutools
|
||||||
Depends:
|
Depends:
|
||||||
dh-make,
|
dh-make,
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
.TH bitesize "1" "May 9 2010" "ubuntu-dev-tools"
|
.TH lp-bitesize "1" "May 9 2010" "ubuntu-dev-tools"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
bitesize \- Add \fBbitesize\fR tag to bugs and add a comment.
|
lp-bitesize \- Add \fBbitesize\fR tag to bugs and add a comment.
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitesize \fR<\fIbug number\fR>
|
.B lp-bitesize \fR<\fIbug number\fR>
|
||||||
.br
|
.br
|
||||||
.B bitesize \-\-help
|
.B lp-bitesize \-\-help
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
\fBbitesize\fR adds a bitesize tag to the bug, if it's not there yet. It
|
\fBlp-bitesize\fR adds a bitesize tag to the bug, if it's not there yet. It
|
||||||
also adds a comment to the bug indicating that you are willing to help with
|
also adds a comment to the bug indicating that you are willing to help with
|
||||||
fixing it.
|
fixing it.
|
||||||
It checks for permission to operate on a given bug first,
|
It checks for permission to operate on a given bug first,
|
||||||
then perform required tasks on Launchpad.
|
then perform required tasks on Launchpad.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
Listed below are the command line options for \fBbitesize\fR:
|
Listed below are the command line options for \fBlp-bitesize\fR:
|
||||||
.TP
|
.TP
|
||||||
.BR \-h ", " \-\-help
|
.BR \-h ", " \-\-help
|
||||||
Display a help message and exit.
|
Display a help message and exit.
|
||||||
@ -48,7 +48,7 @@ The default value for \fB--lpinstance\fR.
|
|||||||
.BR ubuntu\-dev\-tools (5)
|
.BR ubuntu\-dev\-tools (5)
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
\fBbitesize\fR and this manual page were written by Daniel Holbach
|
\fBlp-bitesize\fR and this manual page were written by Daniel Holbach
|
||||||
<daniel.holbach@canonical.com>.
|
<daniel.holbach@canonical.com>.
|
||||||
.PP
|
.PP
|
||||||
Both are released under the terms of the GNU General Public License, version 3.
|
Both are released under the terms of the GNU General Public License, version 3.
|
@ -20,7 +20,7 @@ like for example \fBpbuilder\-feisty\fP, \fBpbuilder\-sid\fP, \fBpbuilder\-gutsy
|
|||||||
.PP
|
.PP
|
||||||
The same applies to \fBcowbuilder\-dist\fP, which uses cowbuilder. The main
|
The same applies to \fBcowbuilder\-dist\fP, which uses cowbuilder. The main
|
||||||
difference between both is that pbuilder compresses the created chroot as a
|
difference between both is that pbuilder compresses the created chroot as a
|
||||||
a tarball, thus using less disc space but needing to uncompress (and possibly
|
tarball, thus using less disc space but needing to uncompress (and possibly
|
||||||
compress) its contents again on each run, and cowbuilder doesn't do this.
|
compress) its contents again on each run, and cowbuilder doesn't do this.
|
||||||
|
|
||||||
.SH USAGE
|
.SH USAGE
|
||||||
@ -38,7 +38,7 @@ This optional parameter will attempt to construct a chroot in a foreign
|
|||||||
architecture.
|
architecture.
|
||||||
For some architecture pairs (e.g. i386 on an amd64 install), the chroot
|
For some architecture pairs (e.g. i386 on an amd64 install), the chroot
|
||||||
will be created natively.
|
will be created natively.
|
||||||
For others (e.g. armel on an i386 install), qemu\-user\-static will be
|
For others (e.g. arm64 on an amd64 install), qemu\-user\-static will be
|
||||||
used.
|
used.
|
||||||
Note that some combinations (e.g. amd64 on an i386 install) require
|
Note that some combinations (e.g. amd64 on an i386 install) require
|
||||||
special separate kernel handling, and may break in unexpected ways.
|
special separate kernel handling, and may break in unexpected ways.
|
||||||
|
44
doc/pm-helper.1
Normal file
44
doc/pm-helper.1
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
.\" Copyright (C) 2023, Canonical Ltd.
|
||||||
|
.\"
|
||||||
|
.\" This program is free software; you can redistribute it and/or
|
||||||
|
.\" modify it under the terms of the GNU General Public License, version 3.
|
||||||
|
.\"
|
||||||
|
.\" 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
.TH pm\-helper 1 "June 2023" ubuntu\-dev\-tools
|
||||||
|
|
||||||
|
.SH NAME
|
||||||
|
pm\-helper \- helper to guide a developer through proposed\-migration work
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B pm\-helper \fR[\fIoptions\fR] [\fIpackage\fR]
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
Claim a package from proposed\-migration to work on and get additional
|
||||||
|
information (such as the state of the package in Debian) that may be helpful
|
||||||
|
in unblocking it.
|
||||||
|
.PP
|
||||||
|
This tool is incomplete and under development.
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-l \fIINSTANCE\fR, \fB\-\-launchpad\fR=\fIINSTANCE\fR
|
||||||
|
Use the specified instance of Launchpad (e.g. "staging"), instead of
|
||||||
|
the default of "production".
|
||||||
|
.TP
|
||||||
|
.B \-v\fR, \fB--verbose\fR
|
||||||
|
be more verbose
|
||||||
|
.TP
|
||||||
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
|
Display a help message and exit
|
||||||
|
|
||||||
|
.SH AUTHORS
|
||||||
|
\fBpm\-helper\fR and this manpage were written by Steve Langasek
|
||||||
|
<steve.langasek@ubuntu.com>.
|
||||||
|
.PP
|
||||||
|
Both are released under the GPLv3 license.
|
15
doc/running-autopkgtests.1
Normal file
15
doc/running-autopkgtests.1
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.TH running\-autopkgtests "1" "18 January 2024" "ubuntu-dev-tools"
|
||||||
|
.SH NAME
|
||||||
|
running\-autopkgtests \- dumps a list of currently running autopkgtests
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B running\-autopkgtests
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
Dumps a list of currently running and queued tests in Autopkgtest.
|
||||||
|
Pass --running to only see running tests, or --queued to only see
|
||||||
|
queued tests. Passing both will print both, which is the default behavior.
|
||||||
|
|
||||||
|
.SH AUTHOR
|
||||||
|
.B running\-autopkgtests
|
||||||
|
was written by Chris Peterson <chris.peterson@canonical.com>.
|
@ -11,7 +11,7 @@ contributors to get their Ubuntu installation ready for packaging work. It
|
|||||||
ensures that all four components from Ubuntu's official repositories are enabled
|
ensures that all four components from Ubuntu's official repositories are enabled
|
||||||
along with their corresponding source repositories. It also installs a minimal
|
along with their corresponding source repositories. It also installs a minimal
|
||||||
set of packages needed for Ubuntu packaging work (ubuntu-dev-tools, devscripts,
|
set of packages needed for Ubuntu packaging work (ubuntu-dev-tools, devscripts,
|
||||||
debhelper, cdbs, patchutils, pbuilder, and build-essential). Finally, it assists
|
debhelper, patchutils, pbuilder, and build-essential). Finally, it assists
|
||||||
in defining the DEBEMAIL and DEBFULLNAME environment variables.
|
in defining the DEBEMAIL and DEBFULLNAME environment variables.
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
|
@ -4,11 +4,11 @@ syncpackage \- copy source packages from Debian to Ubuntu
|
|||||||
.\"
|
.\"
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B syncpackage
|
.B syncpackage
|
||||||
[\fIoptions\fR] \fI<.dsc URL/path or package name>\fR
|
[\fIoptions\fR] \fI<.dsc URL/path or package name(s)>\fR
|
||||||
.\"
|
.\"
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
\fBsyncpackage\fR causes a source package to be copied from Debian to
|
\fBsyncpackage\fR causes one or more source package(s) to be copied from Debian
|
||||||
Ubuntu.
|
to Ubuntu.
|
||||||
.PP
|
.PP
|
||||||
\fBsyncpackage\fR allows you to upload files with the same checksums of the
|
\fBsyncpackage\fR allows you to upload files with the same checksums of the
|
||||||
Debian ones, as the common script used by Ubuntu archive administrators does,
|
Debian ones, as the common script used by Ubuntu archive administrators does,
|
||||||
@ -58,7 +58,7 @@ Display more progress information.
|
|||||||
\fB\-F\fR, \fB\-\-fakesync\fR
|
\fB\-F\fR, \fB\-\-fakesync\fR
|
||||||
Perform a fakesync, to work around a tarball mismatch between Debian and
|
Perform a fakesync, to work around a tarball mismatch between Debian and
|
||||||
Ubuntu.
|
Ubuntu.
|
||||||
This option ignores blacklisting, and performs a local sync.
|
This option ignores blocklisting, and performs a local sync.
|
||||||
It implies \fB\-\-no\-lp\fR, and will leave a signed \fB.changes\fR file
|
It implies \fB\-\-no\-lp\fR, and will leave a signed \fB.changes\fR file
|
||||||
for you to upload.
|
for you to upload.
|
||||||
.TP
|
.TP
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
.TH UBUNTU-BUILD "1" "June 2010" "ubuntu-dev-tools"
|
.TH UBUNTU-BUILD "1" "Mar 2024" "ubuntu-dev-tools"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ubuntu-build \- command-line interface to Launchpad build operations
|
ubuntu-build \- command-line interface to Launchpad build operations
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B ubuntu-build <srcpackage> <release> <operation>
|
.nf
|
||||||
|
\fBubuntu-build\fR <srcpackage> <release> <operation>
|
||||||
|
\fBubuntu-build\fR --batch [--retry] [--rescore \fIPRIORITY\fR] [--arch \fIARCH\fR [...]]
|
||||||
|
[--series \fISERIES\fR] [--state \fIBUILD-STATE\fR]
|
||||||
|
[-A \fIARCHIVE\fR] [pkg]...
|
||||||
|
.fi
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
\fBubuntu-build\fR provides a command line interface to the Launchpad build
|
\fBubuntu-build\fR provides a command line interface to the Launchpad build
|
||||||
@ -38,8 +43,7 @@ operations.
|
|||||||
\fB\-a\fR ARCHITECTURE, \fB\-\-arch\fR=\fIARCHITECTURE\fR
|
\fB\-a\fR ARCHITECTURE, \fB\-\-arch\fR=\fIARCHITECTURE\fR
|
||||||
Rebuild or rescore a specific architecture. Valid
|
Rebuild or rescore a specific architecture. Valid
|
||||||
architectures are:
|
architectures are:
|
||||||
armel, armhf, arm64, amd64, hppa, i386, ia64,
|
armhf, arm64, amd64, i386, powerpc, ppc64el, riscv64, s390x.
|
||||||
lpia, powerpc, ppc64el, riscv64, s390x, sparc.
|
|
||||||
.TP
|
.TP
|
||||||
Batch processing:
|
Batch processing:
|
||||||
.IP
|
.IP
|
||||||
@ -59,15 +63,16 @@ Retry builds (give\-back).
|
|||||||
\fB\-\-rescore\fR=\fIPRIORITY\fR
|
\fB\-\-rescore\fR=\fIPRIORITY\fR
|
||||||
Rescore builds to <priority>.
|
Rescore builds to <priority>.
|
||||||
.IP
|
.IP
|
||||||
\fB\-\-arch2\fR=\fIARCHITECTURE\fR
|
\fB\-\-arch\fR=\fIARCHITECTURE\fR
|
||||||
Affect only 'architecture' (can be used several
|
Affect only 'architecture' (can be used several
|
||||||
times). Valid architectures are:
|
times). Valid architectures are:
|
||||||
armel, armhf, arm64, amd64, hppa, i386, ia64,
|
arm64, amd64, i386, powerpc, ppc64el, riscv64, s390x.
|
||||||
lpia, powerpc, ppc64el, riscv64, s390x, sparc.
|
.IP
|
||||||
|
\fB\-A=\fIARCHIVE\fR
|
||||||
|
Act on the named archive (ppa) instead of on the main Ubuntu archive.
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
\fBubuntu-build\fR was written by Martin Pitt <martin.pitt@canonical.com>, and
|
\fBubuntu-build\fR was written by Martin Pitt <martin.pitt@canonical.com>, and
|
||||||
this manual page was written by Jonathan Patrick Davies <jpds@ubuntu.com>.
|
this manual page was written by Jonathan Patrick Davies <jpds@ubuntu.com>.
|
||||||
.PP
|
.PP
|
||||||
Both are released under the terms of the GNU General Public License, version 3
|
Both are released under the terms of the GNU General Public License, version 3.
|
||||||
or (at your option) any later version.
|
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
# UDT_EDIT_WRAPPER_TEMPLATE_RE: An extra boilerplate-detecting regex.
|
# UDT_EDIT_WRAPPER_TEMPLATE_RE: An extra boilerplate-detecting regex.
|
||||||
# UDT_EDIT_WRAPPER_FILE_DESCRIPTION: The type of file being edited.
|
# UDT_EDIT_WRAPPER_FILE_DESCRIPTION: The type of file being edited.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -30,33 +33,30 @@ from ubuntutools.question import EditFile
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser('%prog [options] filename')
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] filename")
|
||||||
options, args = parser.parse_args()
|
parser.add_argument("filename", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not os.path.isfile(args.filename):
|
||||||
|
parser.error(f"File {args.filename} does not exist")
|
||||||
|
|
||||||
if len(args) != 1:
|
if "UDT_EDIT_WRAPPER_EDITOR" in os.environ:
|
||||||
parser.error('A filename must be specified')
|
os.environ["EDITOR"] = os.environ["UDT_EDIT_WRAPPER_EDITOR"]
|
||||||
body = args[0]
|
|
||||||
if not os.path.isfile(body):
|
|
||||||
parser.error('File %s does not exist' % body)
|
|
||||||
|
|
||||||
if 'UDT_EDIT_WRAPPER_EDITOR' in os.environ:
|
|
||||||
os.environ['EDITOR'] = os.environ['UDT_EDIT_WRAPPER_EDITOR']
|
|
||||||
else:
|
else:
|
||||||
del os.environ['EDITOR']
|
del os.environ["EDITOR"]
|
||||||
|
|
||||||
if 'UDT_EDIT_WRAPPER_VISUAL' in os.environ:
|
if "UDT_EDIT_WRAPPER_VISUAL" in os.environ:
|
||||||
os.environ['VISUAL'] = os.environ['UDT_EDIT_WRAPPER_VISUAL']
|
os.environ["VISUAL"] = os.environ["UDT_EDIT_WRAPPER_VISUAL"]
|
||||||
else:
|
else:
|
||||||
del os.environ['VISUAL']
|
del os.environ["VISUAL"]
|
||||||
|
|
||||||
placeholders = []
|
placeholders = []
|
||||||
if 'UDT_EDIT_WRAPPER_TEMPLATE_RE' in os.environ:
|
if "UDT_EDIT_WRAPPER_TEMPLATE_RE" in os.environ:
|
||||||
placeholders.append(re.compile(
|
placeholders.append(re.compile(os.environ["UDT_EDIT_WRAPPER_TEMPLATE_RE"]))
|
||||||
os.environ['UDT_EDIT_WRAPPER_TEMPLATE_RE']))
|
|
||||||
|
|
||||||
description = os.environ.get('UDT_EDIT_WRAPPER_FILE_DESCRIPTION', 'file')
|
description = os.environ.get("UDT_EDIT_WRAPPER_FILE_DESCRIPTION", "file")
|
||||||
|
|
||||||
EditFile(body, description, placeholders).edit()
|
EditFile(args.filename, description, placeholders).edit()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
75
grep-merges
75
grep-merges
@ -19,63 +19,70 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
import sys
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
from httplib2 import Http, HttpLib2Error
|
from httplib2 import Http, HttpLib2Error
|
||||||
|
|
||||||
import ubuntutools.misc
|
import ubuntutools.misc
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser(
|
parser = argparse.ArgumentParser(
|
||||||
usage='%prog [options] [string]',
|
usage="%(prog)s [options] [string]",
|
||||||
description='List pending merges from Debian matching string')
|
description="List pending merges from Debian matching string",
|
||||||
args = parser.parse_args()[1]
|
)
|
||||||
|
parser.add_argument("string", nargs="?", help=argparse.SUPPRESS)
|
||||||
if len(args) > 1:
|
args = parser.parse_args()
|
||||||
parser.error('Too many arguments')
|
|
||||||
elif len(args) == 1:
|
|
||||||
match = args[0]
|
|
||||||
else:
|
|
||||||
match = None
|
|
||||||
|
|
||||||
ubuntutools.misc.require_utf8()
|
ubuntutools.misc.require_utf8()
|
||||||
|
|
||||||
for component in ('main', 'main-manual',
|
for component in (
|
||||||
'restricted', 'restricted-manual',
|
"main",
|
||||||
'universe', 'universe-manual',
|
"main-manual",
|
||||||
'multiverse', 'multiverse-manual'):
|
"restricted",
|
||||||
|
"restricted-manual",
|
||||||
url = 'https://merges.ubuntu.com/%s.json' % component
|
"universe",
|
||||||
|
"universe-manual",
|
||||||
|
"multiverse",
|
||||||
|
"multiverse-manual",
|
||||||
|
):
|
||||||
|
url = f"https://merges.ubuntu.com/{component}.json"
|
||||||
try:
|
try:
|
||||||
headers, page = Http().request(url)
|
headers, page = Http().request(url)
|
||||||
except HttpLib2Error as e:
|
except HttpLib2Error as e:
|
||||||
Logger.exception(e)
|
Logger.exception(e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if headers.status != 200:
|
if headers.status != 200:
|
||||||
Logger.error("%s: %s %s" % (url, headers.status,
|
Logger.error("%s: %s %s", url, headers.status, headers.reason)
|
||||||
headers.reason))
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for merge in json.loads(page):
|
for merge in json.loads(page):
|
||||||
package = merge['source_package']
|
package = merge["source_package"]
|
||||||
author, uploader = '', ''
|
author, uploader = "", ""
|
||||||
if merge.get('user'):
|
if merge.get("user"):
|
||||||
author = merge['user']
|
author = merge["user"]
|
||||||
if merge.get('uploader'):
|
if merge.get("uploader"):
|
||||||
uploader = '(%s)' % merge['uploader']
|
uploader = f"({merge['uploader']})"
|
||||||
teams = merge.get('teams', [])
|
teams = merge.get("teams", [])
|
||||||
|
|
||||||
pretty_uploader = '{} {}'.format(author, uploader)
|
pretty_uploader = f"{author} {uploader}"
|
||||||
if (match is None or match in package or match in author
|
if (
|
||||||
or match in uploader or match in teams):
|
args.string is None
|
||||||
Logger.info('%s\t%s' % (package, pretty_uploader))
|
or args.string in package
|
||||||
|
or args.string in author
|
||||||
|
or args.string in uploader
|
||||||
|
or args.string in teams
|
||||||
|
):
|
||||||
|
Logger.info("%s\t%s", package, pretty_uploader)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -21,40 +21,213 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import debianbts
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from email.message import EmailMessage
|
||||||
|
|
||||||
|
import debianbts
|
||||||
from launchpadlib.launchpad import Launchpad
|
from launchpadlib.launchpad import Launchpad
|
||||||
|
|
||||||
|
from ubuntutools import getLogger
|
||||||
from ubuntutools.config import UDTConfig
|
from ubuntutools.config import UDTConfig
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
ATTACHMENT_MAX_SIZE = 2000
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-b",
|
||||||
|
"--browserless",
|
||||||
|
action="store_true",
|
||||||
|
help="Don't open the bug in the browser at the end",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--lpinstance",
|
||||||
|
metavar="INSTANCE",
|
||||||
|
help="LP instance to connect to (default: production)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose", action="store_true", help="Print info about the bug being imported"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-n",
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Don't actually open a bug (also sets verbose)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--package", help="Launchpad package to file bug against (default: Same as Debian)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf", action="store_true", help="Don't read config files or environment variables."
|
||||||
|
)
|
||||||
|
parser.add_argument("bugs", nargs="+", help="Bug number(s) or URL(s)")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_bug_numbers(bug_list: Iterable[str]) -> list[int]:
|
||||||
bug_re = re.compile(r"bug=(\d+)")
|
bug_re = re.compile(r"bug=(\d+)")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
bug_nums = []
|
||||||
parser.add_argument("-b", "--browserless", action="store_true",
|
|
||||||
help="Don't open the bug in the browser at the end")
|
for bug_num in bug_list:
|
||||||
parser.add_argument("-l", "--lpinstance", metavar="INSTANCE",
|
if bug_num.startswith("http"):
|
||||||
help="LP instance to connect to (default: production)")
|
# bug URL
|
||||||
parser.add_argument("-v", "--verbose", action="store_true",
|
match = bug_re.search(bug_num)
|
||||||
help="Print info about the bug being imported")
|
if match is None:
|
||||||
parser.add_argument("-n", "--dry-run", action="store_true",
|
Logger.error("Can't determine bug number from %s", bug_num)
|
||||||
help="Don't actually open a bug (also sets verbose)")
|
sys.exit(1)
|
||||||
parser.add_argument("-p", "--package",
|
bug_num = match.groups()[0]
|
||||||
help="Launchpad package to file bug against "
|
bug_num = bug_num.lstrip("#")
|
||||||
"(default: Same as Debian)")
|
bug_nums.append(int(bug_num))
|
||||||
parser.add_argument("--no-conf", action="store_true",
|
|
||||||
help="Don't read config files or environment variables.")
|
return bug_nums
|
||||||
parser.add_argument("bugs", nargs="+", help="Bug number(s) or URL(s)")
|
|
||||||
options = parser.parse_args()
|
|
||||||
|
def walk_multipart_message(message: EmailMessage) -> tuple[str, list[tuple[int, EmailMessage]]]:
|
||||||
|
summary = ""
|
||||||
|
attachments = []
|
||||||
|
i = 1
|
||||||
|
for part in message.walk():
|
||||||
|
content_type = part.get_content_type()
|
||||||
|
|
||||||
|
if content_type.startswith("multipart/"):
|
||||||
|
# we're already iterating on multipart items
|
||||||
|
# let's just skip the multipart extra metadata
|
||||||
|
continue
|
||||||
|
if content_type == "application/pgp-signature":
|
||||||
|
# we're not interested in importing pgp signatures
|
||||||
|
continue
|
||||||
|
|
||||||
|
if part.is_attachment():
|
||||||
|
attachments.append((i, part))
|
||||||
|
elif content_type.startswith("image/"):
|
||||||
|
# images here are not attachment, they are inline, but Launchpad can't handle that,
|
||||||
|
# so let's add them as attachments
|
||||||
|
summary += f"Message part #{i}\n"
|
||||||
|
summary += f"[inline image '{part.get_filename()}']\n\n"
|
||||||
|
attachments.append((i, part))
|
||||||
|
elif content_type.startswith("text/html"):
|
||||||
|
summary += f"Message part #{i}\n"
|
||||||
|
summary += "[inline html]\n\n"
|
||||||
|
attachments.append((i, part))
|
||||||
|
elif content_type == "text/plain":
|
||||||
|
summary += f"Message part #{i}\n"
|
||||||
|
summary += part.get_content() + "\n"
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"""Unknown message part
|
||||||
|
Your Debian bug is too weird to be imported in Launchpad, sorry.
|
||||||
|
You can fix that by patching this script in ubuntu-dev-tools.
|
||||||
|
Faulty message part:
|
||||||
|
{part}"""
|
||||||
|
)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return summary, attachments
|
||||||
|
|
||||||
|
|
||||||
|
def process_bugs(
|
||||||
|
bugs: Iterable[debianbts.Bugreport],
|
||||||
|
launchpad: Launchpad,
|
||||||
|
package: str,
|
||||||
|
dry_run: bool = True,
|
||||||
|
browserless: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
debian = launchpad.distributions["debian"]
|
||||||
|
ubuntu = launchpad.distributions["ubuntu"]
|
||||||
|
lp_debbugs = launchpad.bug_trackers.getByName(name="debbugs")
|
||||||
|
|
||||||
|
err = False
|
||||||
|
for bug in bugs:
|
||||||
|
ubupackage = bug.source
|
||||||
|
if package:
|
||||||
|
ubupackage = package
|
||||||
|
bug_num = bug.bug_num
|
||||||
|
subject = bug.subject
|
||||||
|
log = debianbts.get_bug_log(bug_num)
|
||||||
|
message = log[0]["message"]
|
||||||
|
assert isinstance(message, EmailMessage)
|
||||||
|
attachments: list[tuple[int, EmailMessage]] = []
|
||||||
|
if message.is_multipart():
|
||||||
|
summary, attachments = walk_multipart_message(message)
|
||||||
|
else:
|
||||||
|
summary = str(message.get_payload())
|
||||||
|
|
||||||
|
target = ubuntu.getSourcePackage(name=ubupackage)
|
||||||
|
if target is None:
|
||||||
|
Logger.error(
|
||||||
|
"Source package '%s' is not in Ubuntu. Please specify "
|
||||||
|
"the destination source package with --package",
|
||||||
|
ubupackage,
|
||||||
|
)
|
||||||
|
err = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
description = f"Imported from Debian bug http://bugs.debian.org/{bug_num}:\n\n{summary}"
|
||||||
|
# LP limits descriptions to 50K chars
|
||||||
|
description = (description[:49994] + " [...]") if len(description) > 50000 else description
|
||||||
|
|
||||||
|
Logger.debug("Target: %s", target)
|
||||||
|
Logger.debug("Subject: %s", subject)
|
||||||
|
Logger.debug("Description: ")
|
||||||
|
Logger.debug(description)
|
||||||
|
for i, attachment in attachments:
|
||||||
|
Logger.debug("Attachment #%s (%s)", i, attachment.get_filename() or "inline")
|
||||||
|
Logger.debug("Content:")
|
||||||
|
if attachment.get_content_type() == "text/plain":
|
||||||
|
content = attachment.get_content()
|
||||||
|
if len(content) > ATTACHMENT_MAX_SIZE:
|
||||||
|
content = (
|
||||||
|
content[:ATTACHMENT_MAX_SIZE]
|
||||||
|
+ f" [attachment cropped after {ATTACHMENT_MAX_SIZE} characters...]"
|
||||||
|
)
|
||||||
|
Logger.debug(content)
|
||||||
|
else:
|
||||||
|
Logger.debug("[data]")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
Logger.info("Dry-Run: not creating Ubuntu bug.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
u_bug = launchpad.bugs.createBug(target=target, title=subject, description=description)
|
||||||
|
for i, attachment in attachments:
|
||||||
|
name = f"#{i}-{attachment.get_filename() or "inline"}"
|
||||||
|
content = attachment.get_content()
|
||||||
|
if isinstance(content, str):
|
||||||
|
# Launchpad only wants bytes
|
||||||
|
content = content.encode()
|
||||||
|
u_bug.addAttachment(
|
||||||
|
filename=name,
|
||||||
|
data=content,
|
||||||
|
comment=f"Imported from Debian bug http://bugs.debian.org/{bug_num}",
|
||||||
|
)
|
||||||
|
d_sp = debian.getSourcePackage(name=package)
|
||||||
|
if d_sp is None and package:
|
||||||
|
d_sp = debian.getSourcePackage(name=package)
|
||||||
|
d_task = u_bug.addTask(target=d_sp)
|
||||||
|
d_watch = u_bug.addWatch(remote_bug=bug_num, bug_tracker=lp_debbugs)
|
||||||
|
d_task.bug_watch = d_watch
|
||||||
|
d_task.lp_save()
|
||||||
|
Logger.info("Opened %s", u_bug.web_link)
|
||||||
|
if not browserless:
|
||||||
|
webbrowser.open(u_bug.web_link)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
options = parse_args()
|
||||||
|
|
||||||
config = UDTConfig(options.no_conf)
|
config = UDTConfig(options.no_conf)
|
||||||
if options.lpinstance is None:
|
if options.lpinstance is None:
|
||||||
@ -69,77 +242,15 @@ def main():
|
|||||||
if options.verbose:
|
if options.verbose:
|
||||||
Logger.setLevel(logging.DEBUG)
|
Logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
debian = launchpad.distributions['debian']
|
bugs = debianbts.get_status(get_bug_numbers(options.bugs))
|
||||||
ubuntu = launchpad.distributions['ubuntu']
|
|
||||||
lp_debbugs = launchpad.bug_trackers.getByName(name='debbugs')
|
|
||||||
|
|
||||||
bug_nums = []
|
|
||||||
|
|
||||||
for bug_num in options.bugs:
|
|
||||||
if bug_num.startswith("http"):
|
|
||||||
# bug URL
|
|
||||||
match = bug_re.search(bug_num)
|
|
||||||
if match is None:
|
|
||||||
Logger.error("Can't determine bug number from %s", bug_num)
|
|
||||||
sys.exit(1)
|
|
||||||
bug_num = match.groups()[0]
|
|
||||||
bug_num = bug_num.lstrip("#")
|
|
||||||
bug_num = int(bug_num)
|
|
||||||
bug_nums.append(bug_num)
|
|
||||||
|
|
||||||
bugs = debianbts.get_status(*bug_nums)
|
|
||||||
|
|
||||||
if not bugs:
|
if not bugs:
|
||||||
Logger.error("Cannot find any of the listed bugs")
|
Logger.error("Cannot find any of the listed bugs")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
err = False
|
if process_bugs(bugs, launchpad, options.package, options.dry_run, options.browserless):
|
||||||
for bug in bugs:
|
|
||||||
ubupackage = package = bug.source
|
|
||||||
if options.package:
|
|
||||||
ubupackage = options.package
|
|
||||||
bug_num = bug.bug_num
|
|
||||||
subject = bug.subject
|
|
||||||
log = debianbts.get_bug_log(bug_num)
|
|
||||||
summary = log[0]['message'].get_payload()
|
|
||||||
target = ubuntu.getSourcePackage(name=ubupackage)
|
|
||||||
if target is None:
|
|
||||||
Logger.error("Source package '%s' is not in Ubuntu. Please specify "
|
|
||||||
"the destination source package with --package",
|
|
||||||
ubupackage)
|
|
||||||
err = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
description = ('Imported from Debian bug http://bugs.debian.org/%d:\n\n%s' %
|
|
||||||
(bug_num, summary))
|
|
||||||
# LP limits descriptions to 50K chars
|
|
||||||
description = (description[:49994] + ' [...]') if len(description) > 50000 else description
|
|
||||||
|
|
||||||
Logger.debug('Target: %s' % target)
|
|
||||||
Logger.debug('Subject: %s' % subject)
|
|
||||||
Logger.debug('Description: ')
|
|
||||||
Logger.debug(description)
|
|
||||||
|
|
||||||
if options.dry_run:
|
|
||||||
Logger.info('Dry-Run: not creating Ubuntu bug.')
|
|
||||||
continue
|
|
||||||
|
|
||||||
u_bug = launchpad.bugs.createBug(target=target, title=subject,
|
|
||||||
description=description)
|
|
||||||
d_sp = debian.getSourcePackage(name=package)
|
|
||||||
if d_sp is None and options.package:
|
|
||||||
d_sp = debian.getSourcePackage(name=options.package)
|
|
||||||
d_task = u_bug.addTask(target=d_sp)
|
|
||||||
d_watch = u_bug.addWatch(remote_bug=bug_num, bug_tracker=lp_debbugs)
|
|
||||||
d_task.bug_watch = d_watch
|
|
||||||
d_task.lp_save()
|
|
||||||
Logger.info("Opened %s", u_bug.web_link)
|
|
||||||
if not options.browserless:
|
|
||||||
webbrowser.open(u_bug.web_link)
|
|
||||||
|
|
||||||
if err:
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -21,20 +21,20 @@
|
|||||||
# Authors:
|
# Authors:
|
||||||
# Daniel Holbach <daniel.holbach@canonical.com>
|
# Daniel Holbach <daniel.holbach@canonical.com>
|
||||||
|
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
from launchpadlib.launchpad import Launchpad
|
|
||||||
from launchpadlib.errors import HTTPError
|
from launchpadlib.errors import HTTPError
|
||||||
|
from launchpadlib.launchpad import Launchpad
|
||||||
from ubuntutools.config import UDTConfig
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.config import UDTConfig
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def error_out(msg):
|
def error_out(msg, *args):
|
||||||
Logger.error(msg)
|
Logger.error(msg, *args)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -42,54 +42,64 @@ def save_entry(entry):
|
|||||||
try:
|
try:
|
||||||
entry.lp_save()
|
entry.lp_save()
|
||||||
except HTTPError as error:
|
except HTTPError as error:
|
||||||
error_out(error.content)
|
error_out("%s", error.content)
|
||||||
|
|
||||||
|
|
||||||
def tag_bug(bug):
|
def tag_bug(bug):
|
||||||
bug.tags = bug.tags + ['bitesize'] # LP: #254901 workaround
|
bug.tags = bug.tags + ["bitesize"] # LP: #254901 workaround
|
||||||
save_entry(bug)
|
save_entry(bug)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
usage = "Usage: %prog <bug number>"
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] <bug number>")
|
||||||
opt_parser = OptionParser(usage)
|
parser.add_argument(
|
||||||
opt_parser.add_option("-l", "--lpinstance", metavar="INSTANCE",
|
"-l",
|
||||||
help="Launchpad instance to connect to "
|
"--lpinstance",
|
||||||
"(default: production)",
|
metavar="INSTANCE",
|
||||||
dest="lpinstance", default=None)
|
help="Launchpad instance to connect to (default: production)",
|
||||||
opt_parser.add_option("--no-conf",
|
dest="lpinstance",
|
||||||
help="Don't read config files or "
|
default=None,
|
||||||
"environment variables.",
|
)
|
||||||
dest="no_conf", default=False, action="store_true")
|
parser.add_argument(
|
||||||
(options, args) = opt_parser.parse_args()
|
"--no-conf",
|
||||||
config = UDTConfig(options.no_conf)
|
help="Don't read config files or environment variables.",
|
||||||
if options.lpinstance is None:
|
dest="no_conf",
|
||||||
options.lpinstance = config.get_value("LPINSTANCE")
|
default=False,
|
||||||
if len(args) < 1:
|
action="store_true",
|
||||||
opt_parser.error("Need at least one bug number.")
|
)
|
||||||
|
parser.add_argument("bug_number", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
config = UDTConfig(args.no_conf)
|
||||||
|
if args.lpinstance is None:
|
||||||
|
args.lpinstance = config.get_value("LPINSTANCE")
|
||||||
|
|
||||||
launchpad = Launchpad.login_with("ubuntu-dev-tools", options.lpinstance)
|
launchpad = Launchpad.login_with("ubuntu-dev-tools", args.lpinstance)
|
||||||
if launchpad is None:
|
if launchpad is None:
|
||||||
error_out("Couldn't authenticate to Launchpad.")
|
error_out("Couldn't authenticate to Launchpad.")
|
||||||
|
|
||||||
# check that the new main bug isn't a duplicate
|
# check that the new main bug isn't a duplicate
|
||||||
try:
|
try:
|
||||||
bug = launchpad.bugs[args[0]]
|
bug = launchpad.bugs[args.bug_number]
|
||||||
except HTTPError as error:
|
except HTTPError as error:
|
||||||
if error.response.status == 401:
|
if error.response.status == 401:
|
||||||
error_out("Don't have enough permissions to access bug %s. %s" %
|
error_out(
|
||||||
(args[0], error.content))
|
"Don't have enough permissions to access bug %s. %s",
|
||||||
|
args.bug_number,
|
||||||
|
error.content,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
if 'bitesize' in bug.tags:
|
if "bitesize" in bug.tags:
|
||||||
error_out("Bug is already marked as 'bitesize'.")
|
error_out("Bug is already marked as 'bitesize'.")
|
||||||
bug.newMessage(content="I'm marking this bug as 'bitesize' as it looks "
|
bug.newMessage(
|
||||||
|
content="I'm marking this bug as 'bitesize' as it looks "
|
||||||
"like an issue that is easy to fix and suitable "
|
"like an issue that is easy to fix and suitable "
|
||||||
"for newcomers in Ubuntu development. If you need "
|
"for newcomers in Ubuntu development. If you need "
|
||||||
"any help with fixing it, talk to me about it.")
|
"any help with fixing it, talk to me about it."
|
||||||
|
)
|
||||||
bug.subscribe(person=launchpad.me)
|
bug.subscribe(person=launchpad.me)
|
||||||
tag_bug(launchpad.bugs[bug.id]) # fresh bug object, LP: #336866 workaround
|
tag_bug(launchpad.bugs[bug.id]) # fresh bug object, LP: #336866 workaround
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
@ -18,24 +18,31 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from debian.changelog import Changelog
|
from debian.changelog import Changelog
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def usage(exit_code=1):
|
def usage(exit_code=1):
|
||||||
Logger.info('''Usage: merge-changelog <left changelog> <right changelog>
|
Logger.info(
|
||||||
|
"""Usage: merge-changelog <left changelog> <right changelog>
|
||||||
|
|
||||||
merge-changelog takes two changelogs that once shared a common source,
|
merge-changelog takes two changelogs that once shared a common source,
|
||||||
merges them back together, and prints the merged result to stdout. This
|
merges them back together, and prints the merged result to stdout. This
|
||||||
is useful if you need to manually merge a ubuntu package with a new
|
is useful if you need to manually merge a ubuntu package with a new
|
||||||
Debian release of the package.
|
Debian release of the package.
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
# Changelog Management
|
# Changelog Management
|
||||||
########################################################################
|
########################################################################
|
||||||
@ -44,9 +51,9 @@ Debian release of the package.
|
|||||||
def merge_changelog(left_changelog, right_changelog):
|
def merge_changelog(left_changelog, right_changelog):
|
||||||
"""Merge a changelog file."""
|
"""Merge a changelog file."""
|
||||||
|
|
||||||
with open(left_changelog) as f:
|
with open(left_changelog, encoding="utf-8") as f:
|
||||||
left_cl = Changelog(f)
|
left_cl = Changelog(f)
|
||||||
with open(right_changelog) as f:
|
with open(right_changelog, encoding="utf-8") as f:
|
||||||
right_cl = Changelog(f)
|
right_cl = Changelog(f)
|
||||||
|
|
||||||
left_versions = set(left_cl.versions)
|
left_versions = set(left_cl.versions)
|
||||||
@ -55,9 +62,9 @@ def merge_changelog(left_changelog, right_changelog):
|
|||||||
right_blocks = iter(right_cl)
|
right_blocks = iter(right_cl)
|
||||||
|
|
||||||
clist = sorted(left_versions | right_versions, reverse=True)
|
clist = sorted(left_versions | right_versions, reverse=True)
|
||||||
ci = len(clist)
|
remaining = len(clist)
|
||||||
for version in clist:
|
for version in clist:
|
||||||
ci -= 1
|
remaining -= 1
|
||||||
if version in left_versions:
|
if version in left_versions:
|
||||||
block = next(left_blocks)
|
block = next(left_blocks)
|
||||||
if version in right_versions:
|
if version in right_versions:
|
||||||
@ -67,11 +74,11 @@ def merge_changelog(left_changelog, right_changelog):
|
|||||||
|
|
||||||
assert block.version == version
|
assert block.version == version
|
||||||
|
|
||||||
Logger.info(str(block).strip() + ('\n' if ci else ''))
|
Logger.info("%s%s", str(block).strip(), "\n" if remaining else "")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) > 1 and sys.argv[1] in ('-h', '--help'):
|
if len(sys.argv) > 1 and sys.argv[1] in ("-h", "--help"):
|
||||||
usage(0)
|
usage(0)
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
usage(1)
|
usage(1)
|
||||||
@ -83,5 +90,5 @@ def main():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
91
mk-sbuild
91
mk-sbuild
@ -155,6 +155,7 @@ proxy="_unset_"
|
|||||||
DEBOOTSTRAP_NO_CHECK_GPG=0
|
DEBOOTSTRAP_NO_CHECK_GPG=0
|
||||||
EATMYDATA=1
|
EATMYDATA=1
|
||||||
CCACHE=0
|
CCACHE=0
|
||||||
|
USE_PKGBINARYMANGLER=0
|
||||||
|
|
||||||
while :; do
|
while :; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@ -166,7 +167,7 @@ while :; do
|
|||||||
--arch)
|
--arch)
|
||||||
CHROOT_ARCH="$2"
|
CHROOT_ARCH="$2"
|
||||||
case $2 in
|
case $2 in
|
||||||
armel|armhf|i386|lpia)
|
armhf|i386)
|
||||||
if [ -z "$personality" ]; then
|
if [ -z "$personality" ]; then
|
||||||
personality="linux32"
|
personality="linux32"
|
||||||
fi
|
fi
|
||||||
@ -303,10 +304,26 @@ if [ ! -w /var/lib/sbuild ]; then
|
|||||||
# Prepare a usable default .sbuildrc
|
# Prepare a usable default .sbuildrc
|
||||||
if [ ! -e ~/.sbuildrc ]; then
|
if [ ! -e ~/.sbuildrc ]; then
|
||||||
cat > ~/.sbuildrc <<EOM
|
cat > ~/.sbuildrc <<EOM
|
||||||
# *** VERIFY AND UPDATE \$mailto and \$maintainer_name BELOW ***
|
# *** THIS COMMAND IS DEPRECATED ***
|
||||||
|
#
|
||||||
|
# In sbuild 0.87.0 and later, the unshare backend is available. This is
|
||||||
|
# expected to become the default in a future release.
|
||||||
|
#
|
||||||
|
# This is the new preferred way of building Debian packages, making the manual
|
||||||
|
# creation of schroots no longer necessary. To retain the default behavior,
|
||||||
|
# you may remove this comment block and continue.
|
||||||
|
#
|
||||||
|
# To test the unshare backend while retaining the default settings, run sbuild
|
||||||
|
# with --chroot-mode=unshare like this:
|
||||||
|
# $ sbuild --chroot-mode=unshare --dist=unstable hello
|
||||||
|
#
|
||||||
|
# To switch to the unshare backend by default (recommended), uncomment the
|
||||||
|
# following lines and delete the rest of the file (with the exception of the
|
||||||
|
# last two lines):
|
||||||
|
#\$chroot_mode = 'unshare';
|
||||||
|
#\$unshare_mmdebstrap_keep_tarball = 1;
|
||||||
|
|
||||||
# Mail address where logs are sent to (mandatory, no default!)
|
# *** VERIFY AND UPDATE \$mailto and \$maintainer_name BELOW ***
|
||||||
\$mailto = '$USER';
|
|
||||||
|
|
||||||
# Name to use as override in .changes files for the Maintainer: field
|
# Name to use as override in .changes files for the Maintainer: field
|
||||||
#\$maintainer_name='$USER <$USER@localhost>';
|
#\$maintainer_name='$USER <$USER@localhost>';
|
||||||
@ -397,12 +414,12 @@ fi
|
|||||||
# By default DEBOOTSTRAP_SCRIPT must match RELEASE
|
# By default DEBOOTSTRAP_SCRIPT must match RELEASE
|
||||||
DEBOOTSTRAP_SCRIPT="$RELEASE"
|
DEBOOTSTRAP_SCRIPT="$RELEASE"
|
||||||
|
|
||||||
if [ "$DISTRO" = "ubuntu" ]; then
|
dist_ge() {
|
||||||
ubuntu_dist_ge() {
|
local releases="$($3-distro-info --all)"
|
||||||
local releases="$(ubuntu-distro-info --all)"
|
|
||||||
local left=999
|
local left=999
|
||||||
local right=0
|
local right=0
|
||||||
local seq=1
|
local seq=1
|
||||||
|
|
||||||
for i in $releases; do
|
for i in $releases; do
|
||||||
if [ $1 = $i ]; then
|
if [ $1 = $i ]; then
|
||||||
local left=$seq
|
local left=$seq
|
||||||
@ -410,6 +427,7 @@ if [ "$DISTRO" = "ubuntu" ]; then
|
|||||||
fi
|
fi
|
||||||
seq=$((seq+1))
|
seq=$((seq+1))
|
||||||
done
|
done
|
||||||
|
|
||||||
seq=1
|
seq=1
|
||||||
for i in $releases; do
|
for i in $releases; do
|
||||||
if [ $2 = $i ]; then
|
if [ $2 = $i ]; then
|
||||||
@ -418,8 +436,19 @@ if [ "$DISTRO" = "ubuntu" ]; then
|
|||||||
fi
|
fi
|
||||||
seq=$((seq+1))
|
seq=$((seq+1))
|
||||||
done
|
done
|
||||||
|
|
||||||
[ $left -ge $right ] && return 0 || return 1
|
[ $left -ge $right ] && return 0 || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ubuntu_dist_ge () {
|
||||||
|
dist_ge $1 $2 ubuntu
|
||||||
|
}
|
||||||
|
|
||||||
|
debian_dist_ge () {
|
||||||
|
dist_ge $1 $2 debian
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$DISTRO" = "ubuntu" ]; then
|
||||||
# On Ubuntu, set DEBOOTSTRAP_SCRIPT to gutsy to allow building new RELEASES without new debootstrap
|
# On Ubuntu, set DEBOOTSTRAP_SCRIPT to gutsy to allow building new RELEASES without new debootstrap
|
||||||
DEBOOTSTRAP_SCRIPT=gutsy
|
DEBOOTSTRAP_SCRIPT=gutsy
|
||||||
fi
|
fi
|
||||||
@ -639,6 +668,7 @@ ubuntu)
|
|||||||
if ubuntu_dist_ge "$RELEASE" "edgy"; then
|
if ubuntu_dist_ge "$RELEASE" "edgy"; then
|
||||||
# Add pkgbinarymangler (edgy and later)
|
# Add pkgbinarymangler (edgy and later)
|
||||||
BUILD_PKGS="$BUILD_PKGS pkgbinarymangler"
|
BUILD_PKGS="$BUILD_PKGS pkgbinarymangler"
|
||||||
|
USE_PKGBINARYMANGLER=1
|
||||||
# Disable recommends for a smaller chroot (gutsy and later only)
|
# Disable recommends for a smaller chroot (gutsy and later only)
|
||||||
if ubuntu_dist_ge "$RELEASE" "gutsy"; then
|
if ubuntu_dist_ge "$RELEASE" "gutsy"; then
|
||||||
BUILD_PKGS="--no-install-recommends $BUILD_PKGS"
|
BUILD_PKGS="--no-install-recommends $BUILD_PKGS"
|
||||||
@ -655,7 +685,7 @@ debian)
|
|||||||
DEBOOTSTRAP_MIRROR="http://deb.debian.org/debian"
|
DEBOOTSTRAP_MIRROR="http://deb.debian.org/debian"
|
||||||
fi
|
fi
|
||||||
if [ -z "$COMPONENTS" ]; then
|
if [ -z "$COMPONENTS" ]; then
|
||||||
COMPONENTS="main non-free contrib"
|
COMPONENTS="main non-free non-free-firmware contrib"
|
||||||
fi
|
fi
|
||||||
if [ -z "$SOURCES_PROPOSED_SUITE" ]; then
|
if [ -z "$SOURCES_PROPOSED_SUITE" ]; then
|
||||||
SOURCES_PROPOSED_SUITE="RELEASE-proposed-updates"
|
SOURCES_PROPOSED_SUITE="RELEASE-proposed-updates"
|
||||||
@ -663,8 +693,12 @@ debian)
|
|||||||
# Debian only performs security updates
|
# Debian only performs security updates
|
||||||
SKIP_UPDATES=1
|
SKIP_UPDATES=1
|
||||||
if [ -z "$SOURCES_SECURITY_SUITE" ]; then
|
if [ -z "$SOURCES_SECURITY_SUITE" ]; then
|
||||||
|
if debian_dist_ge "$RELEASE" "bullseye"; then
|
||||||
|
SOURCES_SECURITY_SUITE="RELEASE-security"
|
||||||
|
else
|
||||||
SOURCES_SECURITY_SUITE="RELEASE/updates"
|
SOURCES_SECURITY_SUITE="RELEASE/updates"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
if [ -z "$SOURCES_SECURITY_URL" ]; then
|
if [ -z "$SOURCES_SECURITY_URL" ]; then
|
||||||
SOURCES_SECURITY_URL="http://security.debian.org/"
|
SOURCES_SECURITY_URL="http://security.debian.org/"
|
||||||
fi
|
fi
|
||||||
@ -734,12 +768,12 @@ DEBOOTSTRAP_COMMAND=debootstrap
|
|||||||
if [ "$CHROOT_ARCH" != "$HOST_ARCH" ] ; then
|
if [ "$CHROOT_ARCH" != "$HOST_ARCH" ] ; then
|
||||||
case "$CHROOT_ARCH-$HOST_ARCH" in
|
case "$CHROOT_ARCH-$HOST_ARCH" in
|
||||||
# Sometimes we don't need qemu
|
# Sometimes we don't need qemu
|
||||||
amd64-i386|amd64-lpia|armel-armhf|armhf-armel|arm64-armel|arm64-armhf|armel-arm64|armhf-arm64|i386-amd64|i386-lpia|lpia-i386|powerpc-ppc64|ppc64-powerpc|sparc-sparc64|sparc64-sparc)
|
amd64-i386|arm64-armhf|armhf-arm64|i386-amd64|powerpc-ppc64|ppc64-powerpc)
|
||||||
;;
|
;;
|
||||||
# Sometimes we do
|
# Sometimes we do
|
||||||
*)
|
*)
|
||||||
DEBOOTSTRAP_COMMAND=qemu-debootstrap
|
DEBOOTSTRAP_COMMAND=debootstrap
|
||||||
if ! which "$DEBOOTSTRAP_COMMAND"; then
|
if ! which "qemu-x86_64-static"; then
|
||||||
sudo apt-get install qemu-user-static
|
sudo apt-get install qemu-user-static
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@ -858,6 +892,13 @@ EOM
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -z "$SKIP_PROPOSED" ]; then
|
if [ -z "$SKIP_PROPOSED" ]; then
|
||||||
|
TEMP_PREFERENCES=`mktemp -t preferences-XXXXXX`
|
||||||
|
cat >> "$TEMP_PREFERENCES" <<EOM
|
||||||
|
# override for NotAutomatic: yes
|
||||||
|
Package: *
|
||||||
|
Pin: release a=*-proposed
|
||||||
|
Pin-Priority: 500
|
||||||
|
EOM
|
||||||
cat >> "$TEMP_SOURCES" <<EOM
|
cat >> "$TEMP_SOURCES" <<EOM
|
||||||
deb ${MIRROR_ARCHS}${DEBOOTSTRAP_MIRROR} $SOURCES_PROPOSED_SUITE ${COMPONENTS}
|
deb ${MIRROR_ARCHS}${DEBOOTSTRAP_MIRROR} $SOURCES_PROPOSED_SUITE ${COMPONENTS}
|
||||||
deb-src ${DEBOOTSTRAP_MIRROR} $SOURCES_PROPOSED_SUITE ${COMPONENTS}
|
deb-src ${DEBOOTSTRAP_MIRROR} $SOURCES_PROPOSED_SUITE ${COMPONENTS}
|
||||||
@ -883,9 +924,12 @@ fi
|
|||||||
cat "$TEMP_SOURCES" | sed -e "s|RELEASE|$RELEASE|g" | \
|
cat "$TEMP_SOURCES" | sed -e "s|RELEASE|$RELEASE|g" | \
|
||||||
sudo bash -c "cat > $MNT/etc/apt/sources.list"
|
sudo bash -c "cat > $MNT/etc/apt/sources.list"
|
||||||
rm -f "$TEMP_SOURCES"
|
rm -f "$TEMP_SOURCES"
|
||||||
|
if [ -n "$TEMP_PREFERENCES" ]; then
|
||||||
|
sudo mv "$TEMP_PREFERENCES" $MNT/etc/apt/preferences.d/proposed.pref
|
||||||
|
fi
|
||||||
|
|
||||||
# Copy the timezone (comment this out if you want to leave the chroot at UTC)
|
# Copy the timezone (uncomment this if you want to use your local time zone)
|
||||||
sudo cp -P --remove-destination /etc/localtime /etc/timezone "$MNT"/etc/
|
#sudo cp -P --remove-destination /etc/localtime /etc/timezone "$MNT"/etc/
|
||||||
# Create a schroot entry for this chroot
|
# Create a schroot entry for this chroot
|
||||||
TEMP_SCHROOTCONF=`mktemp -t schrootconf-XXXXXX`
|
TEMP_SCHROOTCONF=`mktemp -t schrootconf-XXXXXX`
|
||||||
TEMPLATE_SCHROOTCONF=~/.mk-sbuild.schroot.conf
|
TEMPLATE_SCHROOTCONF=~/.mk-sbuild.schroot.conf
|
||||||
@ -1004,6 +1048,25 @@ EOF
|
|||||||
EOM
|
EOM
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$USE_PKGBINARYMANGLER" = 1 ]; then
|
||||||
|
sudo bash -c "cat >> $MNT/finish.sh" <<EOM
|
||||||
|
mkdir -p /etc/pkgbinarymangler/
|
||||||
|
cat > /etc/pkgbinarymangler/maintainermangler.conf <<EOF
|
||||||
|
# pkgmaintainermangler configuration file
|
||||||
|
|
||||||
|
# pkgmaintainermangler will do nothing unless enable is set to "true"
|
||||||
|
enable: true
|
||||||
|
|
||||||
|
# Configure what happens if /CurrentlyBuilding is present, but invalid
|
||||||
|
# (i. e. it does not contain a Package: field). If "ignore" (default),
|
||||||
|
# the file is ignored (i. e. the Maintainer field is mangled) and a
|
||||||
|
# warning is printed. If "fail" (or any other value), pkgmaintainermangler
|
||||||
|
# exits with an error, which causes a package build to fail.
|
||||||
|
invalid_currentlybuilding: ignore
|
||||||
|
EOF
|
||||||
|
EOM
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$TARGET_ARCH" ]; then
|
if [ -n "$TARGET_ARCH" ]; then
|
||||||
sudo bash -c "cat >> $MNT/finish.sh" <<EOM
|
sudo bash -c "cat >> $MNT/finish.sh" <<EOM
|
||||||
# Configure target architecture
|
# Configure target architecture
|
||||||
@ -1022,7 +1085,7 @@ apt-get update || true
|
|||||||
echo set debconf/frontend Noninteractive | debconf-communicate
|
echo set debconf/frontend Noninteractive | debconf-communicate
|
||||||
echo set debconf/priority critical | debconf-communicate
|
echo set debconf/priority critical | debconf-communicate
|
||||||
# Install basic build tool set, trying to match buildd
|
# Install basic build tool set, trying to match buildd
|
||||||
apt-get -y --force-yes install $BUILD_PKGS
|
apt-get -y --force-yes -o Dpkg::Options::="--force-confold" install $BUILD_PKGS
|
||||||
# Set up expected /dev entries
|
# Set up expected /dev entries
|
||||||
if [ ! -r /dev/stdin ]; then ln -s /proc/self/fd/0 /dev/stdin; fi
|
if [ ! -r /dev/stdin ]; then ln -s /proc/self/fd/0 /dev/stdin; fi
|
||||||
if [ ! -r /dev/stdout ]; then ln -s /proc/self/fd/1 /dev/stdout; fi
|
if [ ! -r /dev/stdout ]; then ln -s /proc/self/fd/1 /dev/stdout; fi
|
||||||
|
386
pbuilder-dist
386
pbuilder-dist
@ -29,26 +29,29 @@
|
|||||||
# configurations. For example, a symlink called pbuilder-hardy will assume
|
# configurations. For example, a symlink called pbuilder-hardy will assume
|
||||||
# that the target distribution is always meant to be Ubuntu Hardy.
|
# that the target distribution is always meant to be Ubuntu Hardy.
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
import debian.deb822
|
import debian.deb822
|
||||||
from contextlib import suppress
|
from distro_info import DebianDistroInfo, DistroDataOutdated, UbuntuDistroInfo
|
||||||
from distro_info import DebianDistroInfo, UbuntuDistroInfo, DistroDataOutdated
|
|
||||||
|
|
||||||
import ubuntutools.misc
|
import ubuntutools.misc
|
||||||
import ubuntutools.version
|
import ubuntutools.version
|
||||||
|
from ubuntutools import getLogger
|
||||||
from ubuntutools.config import UDTConfig
|
from ubuntutools.config import UDTConfig
|
||||||
from ubuntutools.question import YesNoQuestion
|
from ubuntutools.question import YesNoQuestion
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
class PbuilderDist(object):
|
class PbuilderDist:
|
||||||
def __init__(self, builder):
|
def __init__(self, builder):
|
||||||
# Base directory where pbuilder will put all the files it creates.
|
# Base directory where pbuilder will put all the files it creates.
|
||||||
self.base = None
|
self.base = None
|
||||||
@ -87,32 +90,36 @@ class PbuilderDist(object):
|
|||||||
self.chroot_string = None
|
self.chroot_string = None
|
||||||
|
|
||||||
# Authentication method
|
# Authentication method
|
||||||
self.auth = 'sudo'
|
self.auth = "sudo"
|
||||||
|
|
||||||
# Builder
|
# Builder
|
||||||
self.builder = builder
|
self.builder = builder
|
||||||
|
|
||||||
self._debian_distros = DebianDistroInfo().all + \
|
# Distro info
|
||||||
['stable', 'testing', 'unstable']
|
self.debian_distro_info = DebianDistroInfo()
|
||||||
|
self.ubuntu_distro_info = UbuntuDistroInfo()
|
||||||
|
|
||||||
|
self._debian_distros = self.debian_distro_info.all + ["stable", "testing", "unstable"]
|
||||||
|
|
||||||
# Ensure that the used builder is installed
|
# Ensure that the used builder is installed
|
||||||
paths = set(os.environ['PATH'].split(':'))
|
paths = set(os.environ["PATH"].split(":"))
|
||||||
paths |= set(('/sbin', '/usr/sbin', '/usr/local/sbin'))
|
paths |= set(("/sbin", "/usr/sbin", "/usr/local/sbin"))
|
||||||
if not any(os.path.exists(os.path.join(p, builder)) for p in paths):
|
if not any(os.path.exists(os.path.join(p, builder)) for p in paths):
|
||||||
Logger.error('Could not find "%s".', builder)
|
Logger.error('Could not find "%s".', builder)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
|
|
||||||
self.base = os.path.expanduser(os.environ.get('PBUILDFOLDER',
|
self.base = os.path.expanduser(os.environ.get("PBUILDFOLDER", "~/pbuilder/"))
|
||||||
'~/pbuilder/'))
|
|
||||||
|
|
||||||
if 'SUDO_USER' in os.environ:
|
if "SUDO_USER" in os.environ:
|
||||||
Logger.warning('Running under sudo. '
|
Logger.warning(
|
||||||
'This is probably not what you want. '
|
"Running under sudo. "
|
||||||
'pbuilder-dist will use sudo itself, '
|
"This is probably not what you want. "
|
||||||
'when necessary.')
|
"pbuilder-dist will use sudo itself, "
|
||||||
if os.stat(os.environ['HOME']).st_uid != os.getuid():
|
"when necessary."
|
||||||
|
)
|
||||||
|
if os.stat(os.environ["HOME"]).st_uid != os.getuid():
|
||||||
Logger.error("You don't own $HOME")
|
Logger.error("You don't own $HOME")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -123,8 +130,8 @@ class PbuilderDist(object):
|
|||||||
Logger.error('Cannot create base directory "%s"', self.base)
|
Logger.error('Cannot create base directory "%s"', self.base)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if 'PBUILDAUTH' in os.environ:
|
if "PBUILDAUTH" in os.environ:
|
||||||
self.auth = os.environ['PBUILDAUTH']
|
self.auth = os.environ["PBUILDAUTH"]
|
||||||
|
|
||||||
self.system_architecture = ubuntutools.misc.host_architecture()
|
self.system_architecture = ubuntutools.misc.host_architecture()
|
||||||
self.system_distro = ubuntutools.misc.system_distribution()
|
self.system_distro = ubuntutools.misc.system_distribution()
|
||||||
@ -145,16 +152,17 @@ class PbuilderDist(object):
|
|||||||
Logger.error('"%s" is an invalid distribution codename.', distro)
|
Logger.error('"%s" is an invalid distribution codename.', distro)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not os.path.isfile(os.path.join('/usr/share/debootstrap/scripts/',
|
if not os.path.isfile(os.path.join("/usr/share/debootstrap/scripts/", distro)):
|
||||||
distro)):
|
if os.path.isdir("/usr/share/debootstrap/scripts/"):
|
||||||
if os.path.isdir('/usr/share/debootstrap/scripts/'):
|
|
||||||
# Debian experimental doesn't have a debootstrap file but
|
# Debian experimental doesn't have a debootstrap file but
|
||||||
# should work nevertheless.
|
# should work nevertheless. Ubuntu releases automatically use
|
||||||
if distro not in self._debian_distros:
|
# the gutsy script as of debootstrap 1.0.128+nmu2ubuntu1.1.
|
||||||
question = ('Warning: Unknown distribution "%s". '
|
if distro not in (self._debian_distros + self.ubuntu_distro_info.all):
|
||||||
'Do you want to continue' % distro)
|
question = (
|
||||||
answer = YesNoQuestion().ask(question, 'no')
|
f'Warning: Unknown distribution "{distro}". ' "Do you want to continue"
|
||||||
if answer == 'no':
|
)
|
||||||
|
answer = YesNoQuestion().ask(question, "no")
|
||||||
|
if answer == "no":
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
Logger.error('Please install package "debootstrap".')
|
Logger.error('Please install package "debootstrap".')
|
||||||
@ -169,22 +177,23 @@ class PbuilderDist(object):
|
|||||||
depending on this either save it into the appropiate variable
|
depending on this either save it into the appropiate variable
|
||||||
or finalize pbuilder-dist's execution.
|
or finalize pbuilder-dist's execution.
|
||||||
"""
|
"""
|
||||||
arguments = ('create', 'update', 'build', 'clean', 'login', 'execute')
|
arguments = ("create", "update", "build", "clean", "login", "execute")
|
||||||
|
|
||||||
if operation not in arguments:
|
if operation not in arguments:
|
||||||
if operation.endswith('.dsc'):
|
if operation.endswith(".dsc"):
|
||||||
if os.path.isfile(operation):
|
if os.path.isfile(operation):
|
||||||
self.operation = 'build'
|
self.operation = "build"
|
||||||
return [operation]
|
return [operation]
|
||||||
else:
|
|
||||||
Logger.error('Could not find file "%s".', operation)
|
Logger.error('Could not find file "%s".', operation)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
|
||||||
Logger.error('"%s" is not a recognized argument.\n'
|
Logger.error(
|
||||||
'Please use one of these: %s.',
|
'"%s" is not a recognized argument.\nPlease use one of these: %s.',
|
||||||
operation, ', '.join(arguments))
|
operation,
|
||||||
|
", ".join(arguments),
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
|
||||||
self.operation = operation
|
self.operation = operation
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -200,30 +209,34 @@ class PbuilderDist(object):
|
|||||||
if self.build_architecture == self.system_architecture:
|
if self.build_architecture == self.system_architecture:
|
||||||
self.chroot_string = self.target_distro
|
self.chroot_string = self.target_distro
|
||||||
else:
|
else:
|
||||||
self.chroot_string = (self.target_distro + '-'
|
self.chroot_string = self.target_distro + "-" + self.build_architecture
|
||||||
+ self.build_architecture)
|
|
||||||
|
|
||||||
prefix = os.path.join(self.base, self.chroot_string)
|
prefix = os.path.join(self.base, self.chroot_string)
|
||||||
if '--buildresult' not in remaining_arguments:
|
if "--buildresult" not in remaining_arguments:
|
||||||
result = os.path.normpath('%s_result/' % prefix)
|
result = os.path.normpath(f"{prefix}_result/")
|
||||||
else:
|
else:
|
||||||
location_of_arg = remaining_arguments.index('--buildresult')
|
location_of_arg = remaining_arguments.index("--buildresult")
|
||||||
result = os.path.normpath(remaining_arguments[location_of_arg + 1])
|
result = os.path.normpath(remaining_arguments[location_of_arg + 1])
|
||||||
remaining_arguments.pop(location_of_arg + 1)
|
remaining_arguments.pop(location_of_arg + 1)
|
||||||
remaining_arguments.pop(location_of_arg)
|
remaining_arguments.pop(location_of_arg)
|
||||||
|
|
||||||
if not self.logfile and self.operation != 'login':
|
if not self.logfile and self.operation != "login":
|
||||||
if self.operation == 'build':
|
if self.operation == "build":
|
||||||
dsc_files = [a for a in remaining_arguments
|
dsc_files = [a for a in remaining_arguments if a.strip().endswith(".dsc")]
|
||||||
if a.strip().endswith('.dsc')]
|
|
||||||
assert len(dsc_files) == 1
|
assert len(dsc_files) == 1
|
||||||
dsc = debian.deb822.Dsc(open(dsc_files[0]))
|
dsc = debian.deb822.Dsc(open(dsc_files[0], encoding="utf-8"))
|
||||||
version = ubuntutools.version.Version(dsc['Version'])
|
version = ubuntutools.version.Version(dsc["Version"])
|
||||||
name = (dsc['Source'] + '_' + version.strip_epoch() + '_' +
|
name = (
|
||||||
self.build_architecture + '.build')
|
dsc["Source"]
|
||||||
|
+ "_"
|
||||||
|
+ version.strip_epoch()
|
||||||
|
+ "_"
|
||||||
|
+ self.build_architecture
|
||||||
|
+ ".build"
|
||||||
|
)
|
||||||
self.logfile = os.path.join(result, name)
|
self.logfile = os.path.join(result, name)
|
||||||
else:
|
else:
|
||||||
self.logfile = os.path.join(result, 'last_operation.log')
|
self.logfile = os.path.join(result, "last_operation.log")
|
||||||
|
|
||||||
if not os.path.isdir(result):
|
if not os.path.isdir(result):
|
||||||
try:
|
try:
|
||||||
@ -233,90 +246,89 @@ class PbuilderDist(object):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
arguments = [
|
arguments = [
|
||||||
'--%s' % self.operation,
|
f"--{self.operation}",
|
||||||
'--distribution', self.target_distro,
|
"--distribution",
|
||||||
'--buildresult', result,
|
self.target_distro,
|
||||||
|
"--buildresult",
|
||||||
|
result,
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.operation == 'update':
|
if self.operation == "update":
|
||||||
arguments += ['--override-config']
|
arguments += ["--override-config"]
|
||||||
|
|
||||||
if self.builder == 'pbuilder':
|
if self.builder == "pbuilder":
|
||||||
arguments += ['--basetgz', prefix + '-base.tgz']
|
arguments += ["--basetgz", prefix + "-base.tgz"]
|
||||||
elif self.builder == 'cowbuilder':
|
elif self.builder == "cowbuilder":
|
||||||
arguments += ['--basepath', prefix + '-base.cow']
|
arguments += ["--basepath", prefix + "-base.cow"]
|
||||||
else:
|
else:
|
||||||
Logger.error('Unrecognized builder "%s".', self.builder)
|
Logger.error('Unrecognized builder "%s".', self.builder)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if self.logfile:
|
if self.logfile:
|
||||||
arguments += ['--logfile', self.logfile]
|
arguments += ["--logfile", self.logfile]
|
||||||
|
|
||||||
if os.path.exists('/var/cache/archive/'):
|
if os.path.exists("/var/cache/archive/"):
|
||||||
arguments += ['--bindmounts', '/var/cache/archive/']
|
arguments += ["--bindmounts", "/var/cache/archive/"]
|
||||||
|
|
||||||
config = UDTConfig()
|
config = UDTConfig()
|
||||||
if self.target_distro in self._debian_distros:
|
if self.target_distro in self._debian_distros:
|
||||||
mirror = os.environ.get('MIRRORSITE',
|
mirror = os.environ.get("MIRRORSITE", config.get_value("DEBIAN_MIRROR"))
|
||||||
config.get_value('DEBIAN_MIRROR'))
|
components = "main"
|
||||||
components = 'main'
|
|
||||||
if self.extra_components:
|
if self.extra_components:
|
||||||
components += ' contrib non-free'
|
components += " contrib non-free non-free-firmware"
|
||||||
else:
|
else:
|
||||||
mirror = os.environ.get('MIRRORSITE',
|
mirror = os.environ.get("MIRRORSITE", config.get_value("UBUNTU_MIRROR"))
|
||||||
config.get_value('UBUNTU_MIRROR'))
|
if self.build_architecture not in ("amd64", "i386"):
|
||||||
if self.build_architecture not in ('amd64', 'i386'):
|
mirror = os.environ.get("MIRRORSITE", config.get_value("UBUNTU_PORTS_MIRROR"))
|
||||||
mirror = os.environ.get(
|
components = "main restricted"
|
||||||
'MIRRORSITE', config.get_value('UBUNTU_PORTS_MIRROR'))
|
|
||||||
components = 'main restricted'
|
|
||||||
if self.extra_components:
|
if self.extra_components:
|
||||||
components += ' universe multiverse'
|
components += " universe multiverse"
|
||||||
|
|
||||||
arguments += ['--mirror', mirror]
|
arguments += ["--mirror", mirror]
|
||||||
|
|
||||||
othermirrors = []
|
othermirrors = []
|
||||||
localrepo = '/var/cache/archive/' + self.target_distro
|
localrepo = f"/var/cache/archive/{self.target_distro}"
|
||||||
if os.path.exists(localrepo):
|
if os.path.exists(localrepo):
|
||||||
repo = 'deb file:///var/cache/archive/ %s/' % self.target_distro
|
repo = f"deb file:///var/cache/archive/ {self.target_distro}/"
|
||||||
othermirrors.append(repo)
|
othermirrors.append(repo)
|
||||||
|
|
||||||
if self.target_distro in self._debian_distros:
|
if self.target_distro in self._debian_distros:
|
||||||
debian_info = DebianDistroInfo()
|
|
||||||
try:
|
try:
|
||||||
codename = debian_info.codename(self.target_distro,
|
codename = self.debian_distro_info.codename(
|
||||||
default=self.target_distro)
|
self.target_distro, default=self.target_distro
|
||||||
|
)
|
||||||
except DistroDataOutdated as error:
|
except DistroDataOutdated as error:
|
||||||
Logger.warning(error)
|
Logger.warning(error)
|
||||||
if codename in (debian_info.devel(), 'experimental'):
|
if codename in (self.debian_distro_info.devel(), "experimental"):
|
||||||
self.enable_security = False
|
self.enable_security = False
|
||||||
self.enable_updates = False
|
self.enable_updates = False
|
||||||
self.enable_proposed = False
|
self.enable_proposed = False
|
||||||
elif codename in (debian_info.testing(), 'testing'):
|
elif codename in (self.debian_distro_info.testing(), "testing"):
|
||||||
self.enable_updates = False
|
self.enable_updates = False
|
||||||
|
|
||||||
if self.enable_security:
|
if self.enable_security:
|
||||||
pocket = '-security'
|
pocket = "-security"
|
||||||
with suppress(ValueError):
|
with suppress(ValueError):
|
||||||
# before bullseye (version 11) security suite is /updates
|
# before bullseye (version 11) security suite is /updates
|
||||||
if float(debian_info.version(codename)) < 11.0:
|
if float(self.debian_distro_info.version(codename)) < 11.0:
|
||||||
pocket = '/updates'
|
pocket = "/updates"
|
||||||
othermirrors.append('deb %s %s%s %s'
|
othermirrors.append(
|
||||||
% (config.get_value('DEBSEC_MIRROR'),
|
f"deb {config.get_value('DEBSEC_MIRROR')}"
|
||||||
self.target_distro, pocket, components))
|
f" {self.target_distro}{pocket} {components}"
|
||||||
|
)
|
||||||
if self.enable_updates:
|
if self.enable_updates:
|
||||||
othermirrors.append('deb %s %s-updates %s'
|
othermirrors.append(f"deb {mirror} {self.target_distro}-updates {components}")
|
||||||
% (mirror, self.target_distro, components))
|
|
||||||
if self.enable_proposed:
|
if self.enable_proposed:
|
||||||
othermirrors.append('deb %s %s-proposed-updates %s'
|
othermirrors.append(
|
||||||
% (mirror, self.target_distro, components))
|
f"deb {mirror} {self.target_distro}-proposed-updates {components}"
|
||||||
|
)
|
||||||
if self.enable_backports:
|
if self.enable_backports:
|
||||||
othermirrors.append('deb %s %s-backports %s'
|
othermirrors.append(f"deb {mirror} {self.target_distro}-backports {components}")
|
||||||
% (mirror, self.target_distro, components))
|
|
||||||
|
|
||||||
aptcache = os.path.join(self.base, 'aptcache', 'debian')
|
aptcache = os.path.join(self.base, "aptcache", "debian")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
dev_release = self.target_distro == UbuntuDistroInfo().devel()
|
dev_release = self.target_distro == self.ubuntu_distro_info.devel()
|
||||||
except DistroDataOutdated as error:
|
except DistroDataOutdated as error:
|
||||||
Logger.warning(error)
|
Logger.warning(error)
|
||||||
dev_release = True
|
dev_release = True
|
||||||
@ -326,46 +338,45 @@ class PbuilderDist(object):
|
|||||||
self.enable_updates = False
|
self.enable_updates = False
|
||||||
|
|
||||||
if self.enable_security:
|
if self.enable_security:
|
||||||
othermirrors.append('deb %s %s-security %s'
|
othermirrors.append(f"deb {mirror} {self.target_distro}-security {components}")
|
||||||
% (mirror, self.target_distro, components))
|
|
||||||
if self.enable_updates:
|
if self.enable_updates:
|
||||||
othermirrors.append('deb %s %s-updates %s'
|
othermirrors.append(f"deb {mirror} {self.target_distro}-updates {components}")
|
||||||
% (mirror, self.target_distro, components))
|
|
||||||
if self.enable_proposed:
|
if self.enable_proposed:
|
||||||
othermirrors.append('deb %s %s-proposed %s'
|
othermirrors.append(f"deb {mirror} {self.target_distro}-proposed {components}")
|
||||||
% (mirror, self.target_distro, components))
|
|
||||||
|
|
||||||
aptcache = os.path.join(self.base, 'aptcache', 'ubuntu')
|
aptcache = os.path.join(self.base, "aptcache", "ubuntu")
|
||||||
|
|
||||||
if 'OTHERMIRROR' in os.environ:
|
if "OTHERMIRROR" in os.environ:
|
||||||
othermirrors += os.environ['OTHERMIRROR'].split('|')
|
othermirrors += os.environ["OTHERMIRROR"].split("|")
|
||||||
|
|
||||||
if othermirrors:
|
if othermirrors:
|
||||||
arguments += ['--othermirror', '|'.join(othermirrors)]
|
arguments += ["--othermirror", "|".join(othermirrors)]
|
||||||
|
|
||||||
# Work around LP:#599695
|
# Work around LP:#599695
|
||||||
if (ubuntutools.misc.system_distribution() == 'Debian'
|
if (
|
||||||
and self.target_distro not in self._debian_distros):
|
ubuntutools.misc.system_distribution() == "Debian"
|
||||||
if not os.path.exists(
|
and self.target_distro not in self._debian_distros
|
||||||
'/usr/share/keyrings/ubuntu-archive-keyring.gpg'):
|
):
|
||||||
Logger.error('ubuntu-keyring not installed')
|
if not os.path.exists("/usr/share/keyrings/ubuntu-archive-keyring.gpg"):
|
||||||
|
Logger.error("ubuntu-keyring not installed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
arguments += [
|
arguments += [
|
||||||
'--debootstrapopts',
|
"--debootstrapopts",
|
||||||
'--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg',
|
"--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg",
|
||||||
]
|
]
|
||||||
elif (ubuntutools.misc.system_distribution() == 'Ubuntu'
|
elif (
|
||||||
and self.target_distro in self._debian_distros):
|
ubuntutools.misc.system_distribution() == "Ubuntu"
|
||||||
if not os.path.exists(
|
and self.target_distro in self._debian_distros
|
||||||
'/usr/share/keyrings/debian-archive-keyring.gpg'):
|
):
|
||||||
Logger.error('debian-archive-keyring not installed')
|
if not os.path.exists("/usr/share/keyrings/debian-archive-keyring.gpg"):
|
||||||
|
Logger.error("debian-archive-keyring not installed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
arguments += [
|
arguments += [
|
||||||
'--debootstrapopts',
|
"--debootstrapopts",
|
||||||
'--keyring=/usr/share/keyrings/debian-archive-keyring.gpg',
|
"--keyring=/usr/share/keyrings/debian-archive-keyring.gpg",
|
||||||
]
|
]
|
||||||
|
|
||||||
arguments += ['--aptcache', aptcache, '--components', components]
|
arguments += ["--aptcache", aptcache, "--components", components]
|
||||||
|
|
||||||
if not os.path.isdir(aptcache):
|
if not os.path.isdir(aptcache):
|
||||||
try:
|
try:
|
||||||
@ -375,13 +386,11 @@ class PbuilderDist(object):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if self.build_architecture != self.system_architecture:
|
if self.build_architecture != self.system_architecture:
|
||||||
arguments += ['--debootstrapopts',
|
arguments += ["--debootstrapopts", "--arch=" + self.build_architecture]
|
||||||
'--arch=' + self.build_architecture]
|
|
||||||
|
|
||||||
apt_conf_dir = os.path.join(self.base,
|
apt_conf_dir = os.path.join(self.base, f"etc/{self.target_distro}/apt.conf")
|
||||||
'etc/%s/apt.conf' % self.target_distro)
|
|
||||||
if os.path.exists(apt_conf_dir):
|
if os.path.exists(apt_conf_dir):
|
||||||
arguments += ['--aptconfdir', apt_conf_dir]
|
arguments += ["--aptconfdir", apt_conf_dir]
|
||||||
|
|
||||||
# Append remaining arguments
|
# Append remaining arguments
|
||||||
if remaining_arguments:
|
if remaining_arguments:
|
||||||
@ -392,12 +401,12 @@ class PbuilderDist(object):
|
|||||||
# With both common variable name schemes (BTS: #659060).
|
# With both common variable name schemes (BTS: #659060).
|
||||||
return [
|
return [
|
||||||
self.auth,
|
self.auth,
|
||||||
'HOME=' + os.path.expanduser('~'),
|
"HOME=" + os.path.expanduser("~"),
|
||||||
'ARCHITECTURE=' + self.build_architecture,
|
"ARCHITECTURE=" + self.build_architecture,
|
||||||
'DISTRIBUTION=' + self.target_distro,
|
"DISTRIBUTION=" + self.target_distro,
|
||||||
'ARCH=' + self.build_architecture,
|
"ARCH=" + self.build_architecture,
|
||||||
'DIST=' + self.target_distro,
|
"DIST=" + self.target_distro,
|
||||||
'DEB_BUILD_OPTIONS=' + os.environ.get('DEB_BUILD_OPTIONS', ''),
|
"DEB_BUILD_OPTIONS=" + os.environ.get("DEB_BUILD_OPTIONS", ""),
|
||||||
self.builder,
|
self.builder,
|
||||||
] + arguments
|
] + arguments
|
||||||
|
|
||||||
@ -407,7 +416,7 @@ def show_help(exit_code=0):
|
|||||||
|
|
||||||
Print a help message for pbuilder-dist, and exit with the given code.
|
Print a help message for pbuilder-dist, and exit with the given code.
|
||||||
"""
|
"""
|
||||||
Logger.info('See man pbuilder-dist for more information.')
|
Logger.info("See man pbuilder-dist for more information.")
|
||||||
|
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
@ -421,27 +430,25 @@ def main():
|
|||||||
the script and runs pbuilder itself or exists with an error message.
|
the script and runs pbuilder itself or exists with an error message.
|
||||||
"""
|
"""
|
||||||
script_name = os.path.basename(sys.argv[0])
|
script_name = os.path.basename(sys.argv[0])
|
||||||
parts = script_name.split('-')
|
parts = script_name.split("-")
|
||||||
|
|
||||||
# Copy arguments into another list for save manipulation
|
# Copy arguments into another list for save manipulation
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
if ('-' in script_name and parts[0] not in ('pbuilder', 'cowbuilder')
|
if "-" in script_name and parts[0] not in ("pbuilder", "cowbuilder") or len(parts) > 3:
|
||||||
or len(parts) > 3):
|
Logger.error('"%s" is not a valid name for a "pbuilder-dist" executable.', script_name)
|
||||||
Logger.error('"%s" is not a valid name for a "pbuilder-dist" '
|
|
||||||
'executable.', script_name)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
Logger.error('Insufficient number of arguments.')
|
Logger.error("Insufficient number of arguments.")
|
||||||
show_help(1)
|
show_help(1)
|
||||||
|
|
||||||
if args[0] in ('-h', '--help', 'help'):
|
if args[0] in ("-h", "--help", "help"):
|
||||||
show_help(0)
|
show_help(0)
|
||||||
|
|
||||||
app = PbuilderDist(parts[0])
|
app = PbuilderDist(parts[0])
|
||||||
|
|
||||||
if len(parts) > 1 and parts[1] != 'dist' and '.' not in parts[1]:
|
if len(parts) > 1 and parts[1] != "dist" and "." not in parts[1]:
|
||||||
app.set_target_distro(parts[1])
|
app.set_target_distro(parts[1])
|
||||||
else:
|
else:
|
||||||
app.set_target_distro(args.pop(0))
|
app.set_target_distro(args.pop(0))
|
||||||
@ -449,24 +456,31 @@ def main():
|
|||||||
if len(parts) > 2:
|
if len(parts) > 2:
|
||||||
requested_arch = parts[2]
|
requested_arch = parts[2]
|
||||||
elif len(args) > 0:
|
elif len(args) > 0:
|
||||||
if shutil.which('arch-test') is not None:
|
if shutil.which("arch-test") is not None:
|
||||||
if subprocess.run(
|
arch_test = subprocess.run(
|
||||||
['arch-test', args[0]],
|
["arch-test", args[0]], check=False, stdout=subprocess.DEVNULL
|
||||||
stdout=subprocess.DEVNULL).returncode == 0:
|
)
|
||||||
|
if arch_test.returncode == 0:
|
||||||
requested_arch = args.pop(0)
|
requested_arch = args.pop(0)
|
||||||
elif (os.path.isdir('/usr/lib/arch-test')
|
elif os.path.isdir("/usr/lib/arch-test") and args[0] in os.listdir(
|
||||||
and args[0] in os.listdir('/usr/lib/arch-test/')):
|
"/usr/lib/arch-test/"
|
||||||
Logger.error('Architecture "%s" is not supported on your '
|
):
|
||||||
'currently running kernel. Consider installing '
|
Logger.error(
|
||||||
'the qemu-user-static package to enable the use of '
|
'Architecture "%s" is not supported on your '
|
||||||
'foreign architectures.', args[0])
|
"currently running kernel. Consider installing "
|
||||||
|
"the qemu-user-static package to enable the use of "
|
||||||
|
"foreign architectures.",
|
||||||
|
args[0],
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
requested_arch = None
|
requested_arch = None
|
||||||
else:
|
else:
|
||||||
Logger.error('Cannot determine if "%s" is a valid architecture. '
|
Logger.error(
|
||||||
'Please install the arch-test package and retry.',
|
'Cannot determine if "%s" is a valid architecture. '
|
||||||
args[0])
|
"Please install the arch-test package and retry.",
|
||||||
|
args[0],
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
requested_arch = None
|
requested_arch = None
|
||||||
@ -474,62 +488,64 @@ def main():
|
|||||||
if requested_arch:
|
if requested_arch:
|
||||||
app.build_architecture = requested_arch
|
app.build_architecture = requested_arch
|
||||||
# For some foreign architectures we need to use qemu
|
# For some foreign architectures we need to use qemu
|
||||||
if (requested_arch != app.system_architecture
|
if requested_arch != app.system_architecture and (
|
||||||
and (app.system_architecture, requested_arch) not in [
|
app.system_architecture,
|
||||||
('amd64', 'i386'), ('amd64', 'lpia'), ('arm', 'armel'),
|
requested_arch,
|
||||||
('armel', 'arm'), ('armel', 'armhf'), ('armhf', 'armel'),
|
) not in [
|
||||||
('arm64', 'arm'), ('arm64', 'armhf'), ('arm64', 'armel'),
|
("amd64", "i386"),
|
||||||
('i386', 'lpia'), ('lpia', 'i386'), ('powerpc', 'ppc64'),
|
("arm64", "arm"),
|
||||||
('ppc64', 'powerpc'), ('sparc', 'sparc64'),
|
("arm64", "armhf"),
|
||||||
('sparc64', 'sparc')]):
|
("powerpc", "ppc64"),
|
||||||
args += ['--debootstrap', 'qemu-debootstrap']
|
("ppc64", "powerpc"),
|
||||||
|
]:
|
||||||
|
args += ["--debootstrap", "debootstrap"]
|
||||||
|
|
||||||
if 'mainonly' in sys.argv or '--main-only' in sys.argv:
|
if "mainonly" in sys.argv or "--main-only" in sys.argv:
|
||||||
app.extra_components = False
|
app.extra_components = False
|
||||||
if 'mainonly' in sys.argv:
|
if "mainonly" in sys.argv:
|
||||||
args.remove('mainonly')
|
args.remove("mainonly")
|
||||||
else:
|
else:
|
||||||
args.remove('--main-only')
|
args.remove("--main-only")
|
||||||
|
|
||||||
if '--release-only' in sys.argv:
|
if "--release-only" in sys.argv:
|
||||||
args.remove('--release-only')
|
args.remove("--release-only")
|
||||||
app.enable_security = False
|
app.enable_security = False
|
||||||
app.enable_updates = False
|
app.enable_updates = False
|
||||||
app.enable_proposed = False
|
app.enable_proposed = False
|
||||||
elif '--security-only' in sys.argv:
|
elif "--security-only" in sys.argv:
|
||||||
args.remove('--security-only')
|
args.remove("--security-only")
|
||||||
app.enable_updates = False
|
app.enable_updates = False
|
||||||
app.enable_proposed = False
|
app.enable_proposed = False
|
||||||
elif '--updates-only' in sys.argv:
|
elif "--updates-only" in sys.argv:
|
||||||
args.remove('--updates-only')
|
args.remove("--updates-only")
|
||||||
app.enable_proposed = False
|
app.enable_proposed = False
|
||||||
elif '--backports' in sys.argv:
|
elif "--backports" in sys.argv:
|
||||||
args.remove('--backports')
|
args.remove("--backports")
|
||||||
app.enable_backports = True
|
app.enable_backports = True
|
||||||
|
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
Logger.error('Insufficient number of arguments.')
|
Logger.error("Insufficient number of arguments.")
|
||||||
show_help(1)
|
show_help(1)
|
||||||
|
|
||||||
# Parse the operation
|
# Parse the operation
|
||||||
args = app.set_operation(args.pop(0)) + args
|
args = app.set_operation(args.pop(0)) + args
|
||||||
|
|
||||||
if app.operation == 'build':
|
if app.operation == "build":
|
||||||
if len([a for a in args if a.strip().endswith('.dsc')]) != 1:
|
if len([a for a in args if a.strip().endswith(".dsc")]) != 1:
|
||||||
msg = 'You have to specify one .dsc file if you want to build.'
|
msg = "You have to specify one .dsc file if you want to build."
|
||||||
Logger.error(msg)
|
Logger.error(msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Execute the pbuilder command
|
# Execute the pbuilder command
|
||||||
if '--debug-echo' not in args:
|
if "--debug-echo" not in args:
|
||||||
sys.exit(subprocess.call(app.get_command(args)))
|
sys.exit(subprocess.call(app.get_command(args)))
|
||||||
else:
|
else:
|
||||||
Logger.info(app.get_command([arg for arg in args if arg != '--debug-echo']))
|
Logger.info(app.get_command([arg for arg in args if arg != "--debug-echo"]))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
Logger.error('Manually aborted.')
|
Logger.error("Manually aborted.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
142
pm-helper
Executable file
142
pm-helper
Executable file
@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# Find the next thing to work on for proposed-migration
|
||||||
|
# Copyright (C) 2023 Canonical Ltd.
|
||||||
|
# Author: Steve Langasek <steve.langasek@ubuntu.com>
|
||||||
|
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License, version 3.
|
||||||
|
|
||||||
|
# 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import lzma
|
||||||
|
import sys
|
||||||
|
import webbrowser
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from launchpadlib.launchpad import Launchpad
|
||||||
|
|
||||||
|
from ubuntutools.utils import get_url
|
||||||
|
|
||||||
|
# proposed-migration is only concerned with the devel series; unlike other
|
||||||
|
# tools, don't make this configurable
|
||||||
|
excuses_url = "https://ubuntu-archive-team.ubuntu.com/proposed-migration/update_excuses.yaml.xz"
|
||||||
|
|
||||||
|
|
||||||
|
def get_proposed_version(excuses, package):
|
||||||
|
for k in excuses["sources"]:
|
||||||
|
if k["source"] == package:
|
||||||
|
return k.get("new-version")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def claim_excuses_bug(launchpad, bug, package):
|
||||||
|
print(f"LP: #{bug.id}: {bug.title}")
|
||||||
|
ubuntu = launchpad.distributions["ubuntu"]
|
||||||
|
series = ubuntu.current_series.fullseriesname
|
||||||
|
|
||||||
|
for task in bug.bug_tasks:
|
||||||
|
# targeting to a series doesn't make the default task disappear,
|
||||||
|
# it just makes it useless
|
||||||
|
if task.bug_target_name == f"{package} ({series})":
|
||||||
|
our_task = task
|
||||||
|
break
|
||||||
|
if task.bug_target_name == f"{package} (Ubuntu)":
|
||||||
|
our_task = task
|
||||||
|
|
||||||
|
if our_task.assignee == launchpad.me:
|
||||||
|
print("Bug already assigned to you.")
|
||||||
|
return True
|
||||||
|
if our_task.assignee:
|
||||||
|
print(f"Currently assigned to {our_task.assignee.name}")
|
||||||
|
|
||||||
|
print("""Do you want to claim this bug? [yN] """, end="")
|
||||||
|
sys.stdout.flush()
|
||||||
|
response = sys.stdin.readline()
|
||||||
|
if response.strip().lower().startswith("y"):
|
||||||
|
our_task.assignee = launchpad.me
|
||||||
|
our_task.lp_save()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_excuses_bug(launchpad, package, version):
|
||||||
|
print("Will open a new bug")
|
||||||
|
bug = launchpad.bugs.createBug(
|
||||||
|
title=f"proposed-migration for {package} {version}",
|
||||||
|
tags=("update-excuse"),
|
||||||
|
target=f"https://api.launchpad.net/devel/ubuntu/+source/{package}",
|
||||||
|
description=f"{package} {version} is stuck in -proposed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
task = bug.bug_tasks[0]
|
||||||
|
task.assignee = launchpad.me
|
||||||
|
task.lp_save()
|
||||||
|
|
||||||
|
print(f"Opening {bug.web_link} in browser")
|
||||||
|
webbrowser.open(bug.web_link)
|
||||||
|
return bug
|
||||||
|
|
||||||
|
|
||||||
|
def has_excuses_bugs(launchpad, package):
|
||||||
|
ubuntu = launchpad.distributions["ubuntu"]
|
||||||
|
pkg = ubuntu.getSourcePackage(name=package)
|
||||||
|
if not pkg:
|
||||||
|
raise ValueError(f"No such source package: {package}")
|
||||||
|
|
||||||
|
tasks = pkg.searchTasks(tags=["update-excuse"], order_by=["id"])
|
||||||
|
|
||||||
|
bugs = [task.bug for task in tasks]
|
||||||
|
if not bugs:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(bugs) == 1:
|
||||||
|
print(f"There is 1 open update-excuse bug against {package}")
|
||||||
|
else:
|
||||||
|
print(f"There are {len(bugs)} open update-excuse bugs against {package}")
|
||||||
|
|
||||||
|
for bug in bugs:
|
||||||
|
if claim_excuses_bug(launchpad, bug, package):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument("-l", "--launchpad", dest="launchpad_instance", default="production")
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose", default=False, action="store_true", help="be more verbose"
|
||||||
|
)
|
||||||
|
parser.add_argument("package", nargs="?", help="act on this package only")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
args.launchpad = Launchpad.login_with("pm-helper", args.launchpad_instance, version="devel")
|
||||||
|
|
||||||
|
f = get_url(excuses_url, False)
|
||||||
|
with lzma.open(f) as lzma_f:
|
||||||
|
excuses = yaml.load(lzma_f, Loader=yaml.CSafeLoader)
|
||||||
|
|
||||||
|
if args.package:
|
||||||
|
try:
|
||||||
|
if not has_excuses_bugs(args.launchpad, args.package):
|
||||||
|
proposed_version = get_proposed_version(excuses, args.package)
|
||||||
|
if not proposed_version:
|
||||||
|
print(f"Package {args.package} not found in -proposed.")
|
||||||
|
sys.exit(1)
|
||||||
|
create_excuses_bug(args.launchpad, args.package, proposed_version)
|
||||||
|
except ValueError as e:
|
||||||
|
sys.stderr.write(f"{e}\n")
|
||||||
|
else:
|
||||||
|
pass # for now
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='debian', pull='ddebs')
|
PullPkg.main(distro="debian", pull="ddebs")
|
||||||
|
@ -17,29 +17,32 @@
|
|||||||
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import debian.changelog
|
import debian.changelog
|
||||||
|
|
||||||
|
from ubuntutools import getLogger
|
||||||
from ubuntutools.archive import DebianSourcePackage, DownloadError
|
from ubuntutools.archive import DebianSourcePackage, DownloadError
|
||||||
from ubuntutools.config import UDTConfig
|
from ubuntutools.config import UDTConfig
|
||||||
from ubuntutools.version import Version
|
from ubuntutools.version import Version
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def previous_version(package, version, distance):
|
def previous_version(package, version, distance):
|
||||||
"Given an (extracted) package, determine the version distance versions ago"
|
"Given an (extracted) package, determine the version distance versions ago"
|
||||||
upver = Version(version).upstream_version
|
upver = Version(version).upstream_version
|
||||||
filename = '%s-%s/debian/changelog' % (package, upver)
|
filename = f"{package}-{upver}/debian/changelog"
|
||||||
changelog_file = open(filename, 'r')
|
changelog_file = open(filename, "r", encoding="utf-8")
|
||||||
changelog = debian.changelog.Changelog(changelog_file.read())
|
changelog = debian.changelog.Changelog(changelog_file.read())
|
||||||
changelog_file.close()
|
changelog_file.close()
|
||||||
seen = 0
|
seen = 0
|
||||||
for entry in changelog:
|
for entry in changelog:
|
||||||
if entry.distributions == 'UNRELEASED':
|
if entry.distributions == "UNRELEASED":
|
||||||
continue
|
continue
|
||||||
if seen == distance:
|
if seen == distance:
|
||||||
return entry.version.full_version
|
return entry.version.full_version
|
||||||
@ -48,69 +51,78 @@ def previous_version(package, version, distance):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser('%prog [options] <package> <version> '
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] <package> <version> [distance]")
|
||||||
'[distance]')
|
parser.add_argument(
|
||||||
parser.add_option('-f', '--fetch',
|
"-f",
|
||||||
dest='fetch_only', default=False, action='store_true',
|
"--fetch",
|
||||||
help="Only fetch the source packages, don't diff.")
|
dest="fetch_only",
|
||||||
parser.add_option('-d', '--debian-mirror', metavar='DEBIAN_MIRROR',
|
default=False,
|
||||||
dest='debian_mirror',
|
action="store_true",
|
||||||
help='Preferred Debian mirror '
|
help="Only fetch the source packages, don't diff.",
|
||||||
'(default: http://deb.debian.org/debian)')
|
)
|
||||||
parser.add_option('-s', '--debsec-mirror', metavar='DEBSEC_MIRROR',
|
parser.add_argument(
|
||||||
dest='debsec_mirror',
|
"-d",
|
||||||
help='Preferred Debian Security mirror '
|
"--debian-mirror",
|
||||||
'(default: http://security.debian.org)')
|
metavar="DEBIAN_MIRROR",
|
||||||
parser.add_option('--no-conf',
|
dest="debian_mirror",
|
||||||
dest='no_conf', default=False, action='store_true',
|
help="Preferred Debian mirror (default: http://deb.debian.org/debian)",
|
||||||
help="Don't read config files or environment variables")
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--debsec-mirror",
|
||||||
|
metavar="DEBSEC_MIRROR",
|
||||||
|
dest="debsec_mirror",
|
||||||
|
help="Preferred Debian Security mirror (default: http://security.debian.org)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf",
|
||||||
|
dest="no_conf",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Don't read config files or environment variables",
|
||||||
|
)
|
||||||
|
parser.add_argument("package", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("version", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("distance", default=1, type=int, nargs="?", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
opts, args = parser.parse_args()
|
config = UDTConfig(args.no_conf)
|
||||||
if len(args) < 2:
|
if args.debian_mirror is None:
|
||||||
parser.error('Must specify package and version')
|
args.debian_mirror = config.get_value("DEBIAN_MIRROR")
|
||||||
elif len(args) > 3:
|
if args.debsec_mirror is None:
|
||||||
parser.error('Too many arguments')
|
args.debsec_mirror = config.get_value("DEBSEC_MIRROR")
|
||||||
package = args[0]
|
mirrors = [args.debsec_mirror, args.debian_mirror]
|
||||||
version = args[1]
|
|
||||||
distance = int(args[2]) if len(args) > 2 else 1
|
|
||||||
|
|
||||||
config = UDTConfig(opts.no_conf)
|
Logger.info("Downloading %s %s", args.package, args.version)
|
||||||
if opts.debian_mirror is None:
|
|
||||||
opts.debian_mirror = config.get_value('DEBIAN_MIRROR')
|
|
||||||
if opts.debsec_mirror is None:
|
|
||||||
opts.debsec_mirror = config.get_value('DEBSEC_MIRROR')
|
|
||||||
mirrors = [opts.debsec_mirror, opts.debian_mirror]
|
|
||||||
|
|
||||||
Logger.info('Downloading %s %s', package, version)
|
newpkg = DebianSourcePackage(args.package, args.version, mirrors=mirrors)
|
||||||
|
|
||||||
newpkg = DebianSourcePackage(package, version, mirrors=mirrors)
|
|
||||||
try:
|
try:
|
||||||
newpkg.pull()
|
newpkg.pull()
|
||||||
except DownloadError as e:
|
except DownloadError as e:
|
||||||
Logger.error('Failed to download: %s', str(e))
|
Logger.error("Failed to download: %s", str(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
newpkg.unpack()
|
newpkg.unpack()
|
||||||
|
|
||||||
if opts.fetch_only:
|
if args.fetch_only:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
oldversion = previous_version(package, version, distance)
|
oldversion = previous_version(args.package, args.version, args.distance)
|
||||||
if not oldversion:
|
if not oldversion:
|
||||||
Logger.error('No previous version could be found')
|
Logger.error("No previous version could be found")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
Logger.info('Downloading %s %s', package, oldversion)
|
Logger.info("Downloading %s %s", args.package, oldversion)
|
||||||
|
|
||||||
oldpkg = DebianSourcePackage(package, oldversion, mirrors=mirrors)
|
oldpkg = DebianSourcePackage(args.package, oldversion, mirrors=mirrors)
|
||||||
try:
|
try:
|
||||||
oldpkg.pull()
|
oldpkg.pull()
|
||||||
except DownloadError as e:
|
except DownloadError as e:
|
||||||
Logger.error('Failed to download: %s', str(e))
|
Logger.error("Failed to download: %s", str(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
Logger.info('file://' + oldpkg.debdiff(newpkg, diffstat=True))
|
Logger.info("file://%s", oldpkg.debdiff(newpkg, diffstat=True))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
Logger.info('User abort.')
|
Logger.info("User abort.")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='debian', pull='debs')
|
PullPkg.main(distro="debian", pull="debs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='debian', pull='source')
|
PullPkg.main(distro="debian", pull="source")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='debian', pull='udebs')
|
PullPkg.main(distro="debian", pull="udebs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ubuntu', pull='ddebs')
|
PullPkg.main(distro="ubuntu", pull="ddebs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ubuntu', pull='debs')
|
PullPkg.main(distro="ubuntu", pull="debs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ubuntu', pull='source')
|
PullPkg.main(distro="ubuntu", pull="source")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ubuntu', pull='udebs')
|
PullPkg.main(distro="ubuntu", pull="udebs")
|
||||||
|
5
pull-pkg
5
pull-pkg
@ -23,7 +23,10 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main()
|
PullPkg.main()
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ppa', pull='ddebs')
|
PullPkg.main(distro="ppa", pull="ddebs")
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ppa', pull='debs')
|
PullPkg.main(distro="ppa", pull="debs")
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ppa', pull='source')
|
PullPkg.main(distro="ppa", pull="source")
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='ppa', pull='udebs')
|
PullPkg.main(distro="ppa", pull="udebs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='uca', pull='ddebs')
|
PullPkg.main(distro="uca", pull="ddebs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='uca', pull='debs')
|
PullPkg.main(distro="uca", pull="debs")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='uca', pull='source')
|
PullPkg.main(distro="uca", pull="source")
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
#
|
#
|
||||||
# See pull-pkg
|
# See pull-pkg
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
from ubuntutools.pullpkg import PullPkg
|
from ubuntutools.pullpkg import PullPkg
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
PullPkg.main(distro='uca', pull='udebs')
|
PullPkg.main(distro="uca", pull="udebs")
|
||||||
|
6
pyproject.toml
Normal file
6
pyproject.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[tool.black]
|
||||||
|
line-length = 99
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
line_length = 99
|
||||||
|
profile = "black"
|
275
requestbackport
275
requestbackport
@ -14,22 +14,20 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
from collections import defaultdict
|
import argparse
|
||||||
import optparse
|
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import apt
|
import apt
|
||||||
from distro_info import UbuntuDistroInfo
|
from distro_info import UbuntuDistroInfo
|
||||||
|
|
||||||
from ubuntutools.config import UDTConfig
|
|
||||||
from ubuntutools.lp.lpapicache import Launchpad, Distribution
|
|
||||||
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
|
||||||
from ubuntutools.question import (YesNoQuestion, EditBugReport,
|
|
||||||
confirmation_prompt)
|
|
||||||
from ubuntutools.rdepends import query_rdepends, RDependsException
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.config import UDTConfig
|
||||||
|
from ubuntutools.lp.lpapicache import Distribution, Launchpad
|
||||||
|
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
||||||
|
from ubuntutools.question import EditBugReport, YesNoQuestion, confirmation_prompt
|
||||||
|
from ubuntutools.rdepends import RDependsException, query_rdepends
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
@ -40,16 +38,14 @@ class DestinationException(Exception):
|
|||||||
def determine_destinations(source, destination):
|
def determine_destinations(source, destination):
|
||||||
ubuntu_info = UbuntuDistroInfo()
|
ubuntu_info = UbuntuDistroInfo()
|
||||||
if destination is None:
|
if destination is None:
|
||||||
destination = ubuntu_info.stable()
|
destination = ubuntu_info.lts()
|
||||||
|
|
||||||
if source not in ubuntu_info.all:
|
if source not in ubuntu_info.all:
|
||||||
raise DestinationException("Source release %s does not exist" % source)
|
raise DestinationException(f"Source release {source} does not exist")
|
||||||
if destination not in ubuntu_info.all:
|
if destination not in ubuntu_info.all:
|
||||||
raise DestinationException("Destination release %s does not exist"
|
raise DestinationException(f"Destination release {destination} does not exist")
|
||||||
% destination)
|
|
||||||
if destination not in ubuntu_info.supported():
|
if destination not in ubuntu_info.supported():
|
||||||
raise DestinationException("Destination release %s is not supported"
|
raise DestinationException(f"Destination release {destination} is not supported")
|
||||||
% destination)
|
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
destinations = []
|
destinations = []
|
||||||
@ -77,41 +73,36 @@ def determine_destinations(source, destination):
|
|||||||
|
|
||||||
|
|
||||||
def disclaimer():
|
def disclaimer():
|
||||||
print("Ubuntu's backports are not for fixing bugs in stable releases, "
|
print(
|
||||||
|
"Ubuntu's backports are not for fixing bugs in stable releases, "
|
||||||
"but for bringing new features to older, stable releases.\n"
|
"but for bringing new features to older, stable releases.\n"
|
||||||
"See https://wiki.ubuntu.com/UbuntuBackports for the Ubuntu "
|
"See https://wiki.ubuntu.com/UbuntuBackports for the Ubuntu "
|
||||||
"Backports policy and processes.\n"
|
"Backports policy and processes.\n"
|
||||||
"See https://wiki.ubuntu.com/StableReleaseUpdates for the process "
|
"See https://wiki.ubuntu.com/StableReleaseUpdates for the process "
|
||||||
"for fixing bugs in stable releases.")
|
"for fixing bugs in stable releases."
|
||||||
|
)
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
|
|
||||||
def check_existing(package, destinations):
|
def check_existing(package):
|
||||||
"""Search for possible existing bug reports"""
|
"""Search for possible existing bug reports"""
|
||||||
# The LP bug search is indexed, not substring:
|
distro = Distribution("ubuntu")
|
||||||
query = re.findall(r'[a-z]+', package)
|
srcpkg = distro.getSourcePackage(name=package.getPackageName())
|
||||||
bugs = []
|
|
||||||
for release in destinations:
|
bugs = srcpkg.searchTasks(
|
||||||
project_name = '{}-backports'.format(release)
|
omit_duplicates=True,
|
||||||
try:
|
search_text="[BPO]",
|
||||||
project = Launchpad.projects[project_name]
|
status=["Incomplete", "New", "Confirmed", "Triaged", "In Progress", "Fix Committed"],
|
||||||
except KeyError:
|
)
|
||||||
Logger.error("The backports tracking project '%s' doesn't seem to "
|
|
||||||
"exist. Please check the situation with the "
|
|
||||||
"backports team.", project_name)
|
|
||||||
sys.exit(1)
|
|
||||||
bugs += project.searchTasks(omit_duplicates=True,
|
|
||||||
search_text=query,
|
|
||||||
status=["Incomplete", "New", "Confirmed",
|
|
||||||
"Triaged", "In Progress",
|
|
||||||
"Fix Committed"])
|
|
||||||
if not bugs:
|
if not bugs:
|
||||||
return
|
return
|
||||||
|
|
||||||
Logger.info("There are existing bug reports that look similar to your "
|
Logger.info(
|
||||||
"request. Please check before continuing:")
|
"There are existing bug reports that look similar to your "
|
||||||
|
"request. Please check before continuing:"
|
||||||
|
)
|
||||||
|
|
||||||
for bug in sorted(set(bug_task.bug for bug_task in bugs)):
|
for bug in sorted([bug_task.bug for bug_task in bugs], key=lambda bug: bug.id):
|
||||||
Logger.info(" * LP: #%-7i: %s %s", bug.id, bug.title, bug.web_link)
|
Logger.info(" * LP: #%-7i: %s %s", bug.id, bug.title, bug.web_link)
|
||||||
|
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
@ -122,9 +113,9 @@ def find_rdepends(releases, published_binaries):
|
|||||||
|
|
||||||
# We want to display every pubilshed binary, even if it has no rdepends
|
# We want to display every pubilshed binary, even if it has no rdepends
|
||||||
for binpkg in published_binaries:
|
for binpkg in published_binaries:
|
||||||
intermediate[binpkg]
|
intermediate[binpkg] # pylint: disable=pointless-statement
|
||||||
|
|
||||||
for arch in ('any', 'source'):
|
for arch in ("any", "source"):
|
||||||
for release in releases:
|
for release in releases:
|
||||||
for binpkg in published_binaries:
|
for binpkg in published_binaries:
|
||||||
try:
|
try:
|
||||||
@ -135,20 +126,20 @@ def find_rdepends(releases, published_binaries):
|
|||||||
for relationship, rdeps in raw_rdeps.items():
|
for relationship, rdeps in raw_rdeps.items():
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
# Ignore circular deps:
|
# Ignore circular deps:
|
||||||
if rdep['Package'] in published_binaries:
|
if rdep["Package"] in published_binaries:
|
||||||
continue
|
continue
|
||||||
# arch==any queries return Reverse-Build-Deps:
|
# arch==any queries return Reverse-Build-Deps:
|
||||||
if arch == 'any' and rdep.get('Architectures', []) == ['source']:
|
if arch == "any" and rdep.get("Architectures", []) == ["source"]:
|
||||||
continue
|
continue
|
||||||
intermediate[binpkg][rdep['Package']].append((release, relationship))
|
intermediate[binpkg][rdep["Package"]].append((release, relationship))
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
for binpkg, rdeps in intermediate.items():
|
for binpkg, rdeps in intermediate.items():
|
||||||
output += ['', binpkg, '-' * len(binpkg)]
|
output += ["", binpkg, "-" * len(binpkg)]
|
||||||
for pkg, appearences in rdeps.items():
|
for pkg, appearences in rdeps.items():
|
||||||
output += ['* %s' % pkg]
|
output += [f"* {pkg}"]
|
||||||
for release, relationship in appearences:
|
for release, relationship in appearences:
|
||||||
output += [' [ ] %s (%s)' % (release, relationship)]
|
output += [f" [ ] {release} ({relationship})"]
|
||||||
|
|
||||||
found_any = sum(len(rdeps) for rdeps in intermediate.values())
|
found_any = sum(len(rdeps) for rdeps in intermediate.values())
|
||||||
if found_any:
|
if found_any:
|
||||||
@ -163,7 +154,7 @@ def find_rdepends(releases, published_binaries):
|
|||||||
"package currently in the release still works with the new "
|
"package currently in the release still works with the new "
|
||||||
"%(package)s installed. "
|
"%(package)s installed. "
|
||||||
"Reverse- Recommends, Suggests, and Enhances don't need to be "
|
"Reverse- Recommends, Suggests, and Enhances don't need to be "
|
||||||
"tested, and are listed for completeness-sake."
|
"tested, and are listed for completeness-sake.",
|
||||||
] + output
|
] + output
|
||||||
else:
|
else:
|
||||||
output = ["No reverse dependencies"]
|
output = ["No reverse dependencies"]
|
||||||
@ -172,146 +163,164 @@ def find_rdepends(releases, published_binaries):
|
|||||||
|
|
||||||
|
|
||||||
def locate_package(package, distribution):
|
def locate_package(package, distribution):
|
||||||
archive = Distribution('ubuntu').getArchive()
|
archive = Distribution("ubuntu").getArchive()
|
||||||
for pass_ in ('source', 'binary'):
|
|
||||||
try:
|
try:
|
||||||
package_spph = archive.getSourcePackage(package, distribution)
|
package_spph = archive.getSourcePackage(package, distribution)
|
||||||
return package_spph
|
return package_spph
|
||||||
except PackageNotFoundException as e:
|
except PackageNotFoundException as e:
|
||||||
if pass_ == 'binary':
|
|
||||||
Logger.error(str(e))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apt_pkg = apt.Cache()[package]
|
apt_pkg = apt.Cache()[package]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
Logger.error(str(e))
|
||||||
|
sys.exit(1)
|
||||||
package = apt_pkg.candidate.source_name
|
package = apt_pkg.candidate.source_name
|
||||||
Logger.info("Binary package specified, considering its source "
|
Logger.info(
|
||||||
"package instead: %s", package)
|
"Binary package specified, considering its source package instead: %s", package
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def request_backport(package_spph, source, destinations):
|
def request_backport(package_spph, source, destinations):
|
||||||
|
|
||||||
published_binaries = set()
|
published_binaries = set()
|
||||||
for bpph in package_spph.getBinaries():
|
for bpph in package_spph.getBinaries():
|
||||||
published_binaries.add(bpph.getPackageName())
|
published_binaries.add(bpph.getPackageName())
|
||||||
|
|
||||||
if not published_binaries:
|
if not published_binaries:
|
||||||
Logger.error("%s (%s) has no published binaries in %s. ",
|
Logger.error(
|
||||||
package_spph.getPackageName(), package_spph.getVersion(),
|
"%s (%s) has no published binaries in %s. ",
|
||||||
source)
|
package_spph.getPackageName(),
|
||||||
Logger.info("Is it stuck in bin-NEW? It can't be backported until "
|
package_spph.getVersion(),
|
||||||
"the binaries have been accepted.")
|
source,
|
||||||
|
)
|
||||||
|
Logger.info(
|
||||||
|
"Is it stuck in bin-NEW? It can't be backported until "
|
||||||
|
"the binaries have been accepted."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
testing = []
|
testing = ["[Testing]", ""]
|
||||||
testing += ["You can test-build the backport in your PPA with "
|
|
||||||
"backportpackage:"]
|
|
||||||
testing += ["$ backportpackage -u ppa:<lp username>/<ppa name> "
|
|
||||||
"-s %s -d %s %s"
|
|
||||||
% (source, dest, package_spph.getPackageName())
|
|
||||||
for dest in destinations]
|
|
||||||
testing += [""]
|
|
||||||
for dest in destinations:
|
for dest in destinations:
|
||||||
testing += ['* %s:' % dest]
|
testing += [f" * {dest.capitalize()}:"]
|
||||||
testing += [" [ ] Package builds without modification"]
|
testing += [" [ ] Package builds without modification"]
|
||||||
testing += ["[ ] %s installs cleanly and runs" % binary
|
testing += [f" [ ] {binary} installs cleanly and runs" for binary in published_binaries]
|
||||||
for binary in published_binaries]
|
|
||||||
|
|
||||||
subst = {
|
subst = {
|
||||||
'package': package_spph.getPackageName(),
|
"package": package_spph.getPackageName(),
|
||||||
'version': package_spph.getVersion(),
|
"version": package_spph.getVersion(),
|
||||||
'component': package_spph.getComponent(),
|
"component": package_spph.getComponent(),
|
||||||
'source': package_spph.getSeriesAndPocket(),
|
"source": package_spph.getSeriesAndPocket(),
|
||||||
'destinations': ', '.join(destinations),
|
"destinations": ", ".join(destinations),
|
||||||
}
|
}
|
||||||
subject = ("Please backport %(package)s %(version)s (%(component)s) "
|
subject = "[BPO] %(package)s %(version)s to %(destinations)s" % subst
|
||||||
"from %(source)s" % subst)
|
body = (
|
||||||
body = ('\n'.join(
|
"\n".join(
|
||||||
[
|
[
|
||||||
"Please backport %(package)s %(version)s (%(component)s) "
|
"[Impact]",
|
||||||
"from %(source)s to %(destinations)s.",
|
|
||||||
"",
|
"",
|
||||||
"Reason for the backport:",
|
" * Justification for backporting the new version to the stable release.",
|
||||||
"========================",
|
"",
|
||||||
">>> Enter your reasoning here <<<",
|
"[Scope]",
|
||||||
|
"",
|
||||||
|
" * List the Ubuntu release you will backport from,"
|
||||||
|
" and the specific package version.",
|
||||||
|
"",
|
||||||
|
" * List the Ubuntu release(s) you will backport to.",
|
||||||
|
"",
|
||||||
|
"[Other Info]",
|
||||||
|
"",
|
||||||
|
" * Anything else you think is useful to include",
|
||||||
"",
|
"",
|
||||||
"Testing:",
|
|
||||||
"========",
|
|
||||||
"Mark off items in the checklist [X] as you test them, "
|
|
||||||
"but please leave the checklist so that backporters can quickly "
|
|
||||||
"evaluate the state of testing.",
|
|
||||||
""
|
|
||||||
]
|
]
|
||||||
+ testing
|
+ testing
|
||||||
+ [""]
|
+ [""]
|
||||||
+ find_rdepends(destinations, published_binaries)
|
+ find_rdepends(destinations, published_binaries)
|
||||||
+ [""]) % subst)
|
+ [""]
|
||||||
|
)
|
||||||
|
% subst
|
||||||
|
)
|
||||||
|
|
||||||
editor = EditBugReport(subject, body)
|
editor = EditBugReport(subject, body)
|
||||||
editor.edit()
|
editor.edit()
|
||||||
subject, body = editor.get_report()
|
subject, body = editor.get_report()
|
||||||
|
|
||||||
Logger.info('The final report is:\nSummary: %s\nDescription:\n%s\n',
|
Logger.info("The final report is:\nSummary: %s\nDescription:\n%s\n", subject, body)
|
||||||
subject, body)
|
|
||||||
if YesNoQuestion().ask("Request this backport", "yes") == "no":
|
if YesNoQuestion().ask("Request this backport", "yes") == "no":
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
targets = [Launchpad.projects['%s-backports' % destination]
|
distro = Distribution("ubuntu")
|
||||||
for destination in destinations]
|
pkgname = package_spph.getPackageName()
|
||||||
bug = Launchpad.bugs.createBug(title=subject, description=body,
|
|
||||||
target=targets[0])
|
bug = Launchpad.bugs.createBug(
|
||||||
for target in targets[1:]:
|
title=subject, description=body, target=distro.getSourcePackage(name=pkgname)
|
||||||
bug.addTask(target=target)
|
)
|
||||||
|
|
||||||
|
bug.subscribe(person=Launchpad.people["ubuntu-backporters"])
|
||||||
|
|
||||||
|
for dest in destinations:
|
||||||
|
series = distro.getSeries(dest)
|
||||||
|
try:
|
||||||
|
bug.addTask(target=series.getSourcePackage(name=pkgname))
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
break
|
||||||
|
|
||||||
Logger.info("Backport request filed as %s", bug.web_link)
|
Logger.info("Backport request filed as %s", bug.web_link)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser('%prog [options] package')
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] package")
|
||||||
parser.add_option('-d', '--destination', metavar='DEST',
|
parser.add_argument(
|
||||||
help='Backport to DEST release and necessary '
|
"-d",
|
||||||
'intermediate releases '
|
"--destination",
|
||||||
'(default: current stable release)')
|
metavar="DEST",
|
||||||
parser.add_option('-s', '--source', metavar='SOURCE',
|
help="Backport to DEST release and necessary "
|
||||||
help='Backport from SOURCE release '
|
"intermediate releases "
|
||||||
'(default: current devel release)')
|
"(default: current LTS release)",
|
||||||
parser.add_option('-l', '--lpinstance', metavar='INSTANCE', default=None,
|
)
|
||||||
help='Launchpad instance to connect to '
|
parser.add_argument(
|
||||||
'(default: production).')
|
"-s",
|
||||||
parser.add_option('--no-conf', action='store_true',
|
"--source",
|
||||||
dest='no_conf', default=False,
|
metavar="SOURCE",
|
||||||
help="Don't read config files or environment variables")
|
help="Backport from SOURCE release (default: current devel release)",
|
||||||
options, args = parser.parse_args()
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--lpinstance",
|
||||||
|
metavar="INSTANCE",
|
||||||
|
default=None,
|
||||||
|
help="Launchpad instance to connect to (default: production).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf",
|
||||||
|
action="store_true",
|
||||||
|
dest="no_conf",
|
||||||
|
default=False,
|
||||||
|
help="Don't read config files or environment variables",
|
||||||
|
)
|
||||||
|
parser.add_argument("package", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
if len(args) != 1:
|
config = UDTConfig(args.no_conf)
|
||||||
parser.error("One (and only one) package must be specified")
|
|
||||||
package = args[0]
|
|
||||||
|
|
||||||
config = UDTConfig(options.no_conf)
|
if args.lpinstance is None:
|
||||||
|
args.lpinstance = config.get_value("LPINSTANCE")
|
||||||
|
Launchpad.login(args.lpinstance)
|
||||||
|
|
||||||
if options.lpinstance is None:
|
if args.source is None:
|
||||||
options.lpinstance = config.get_value('LPINSTANCE')
|
args.source = Distribution("ubuntu").getDevelopmentSeries().name
|
||||||
Launchpad.login(options.lpinstance)
|
|
||||||
|
|
||||||
if options.source is None:
|
|
||||||
options.source = Distribution('ubuntu').getDevelopmentSeries().name
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
destinations = determine_destinations(options.source,
|
destinations = determine_destinations(args.source, args.destination)
|
||||||
options.destination)
|
|
||||||
except DestinationException as e:
|
except DestinationException as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
disclaimer()
|
disclaimer()
|
||||||
|
|
||||||
check_existing(package, destinations)
|
package_spph = locate_package(args.package, args.source)
|
||||||
|
|
||||||
package_spph = locate_package(package, options.source)
|
check_existing(package_spph)
|
||||||
request_backport(package_spph, options.source, destinations)
|
request_backport(package_spph, args.source, destinations)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
380
requestsync
380
requestsync
@ -26,19 +26,19 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
import optparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distro_info import UbuntuDistroInfo
|
from distro_info import UbuntuDistroInfo
|
||||||
|
|
||||||
|
from ubuntutools import getLogger
|
||||||
from ubuntutools.config import UDTConfig, ubu_email
|
from ubuntutools.config import UDTConfig, ubu_email
|
||||||
from ubuntutools.lp import udtexceptions
|
from ubuntutools.lp import udtexceptions
|
||||||
from ubuntutools.misc import require_utf8
|
from ubuntutools.misc import require_utf8
|
||||||
from ubuntutools.question import confirmation_prompt, EditBugReport
|
from ubuntutools.question import EditBugReport, confirmation_prompt
|
||||||
from ubuntutools.version import Version
|
from ubuntutools.version import Version
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -48,170 +48,190 @@ Logger = getLogger()
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Our usage options.
|
# Our usage options.
|
||||||
usage = ('Usage: %prog [options] '
|
usage = "%(prog)s [options] <source package> [<target release> [base version]]"
|
||||||
'<source package> [<target release> [base version]]')
|
parser = argparse.ArgumentParser(usage=usage)
|
||||||
parser = optparse.OptionParser(usage)
|
|
||||||
|
|
||||||
parser.add_option('-d', type='string',
|
parser.add_argument(
|
||||||
dest='dist', default='unstable',
|
"-d", dest="dist", default="unstable", help="Debian distribution to sync from."
|
||||||
help='Debian distribution to sync from.')
|
)
|
||||||
parser.add_option('-k', type='string',
|
parser.add_argument(
|
||||||
dest='keyid', default=None,
|
"-k",
|
||||||
help='GnuPG key ID to use for signing report '
|
dest="keyid",
|
||||||
'(only used when emailing the sync request).')
|
default=None,
|
||||||
parser.add_option('-n', action='store_true',
|
help="GnuPG key ID to use for signing report "
|
||||||
dest='newpkg', default=False,
|
"(only used when emailing the sync request).",
|
||||||
help='Whether package to sync is a new package in '
|
)
|
||||||
'Ubuntu.')
|
parser.add_argument(
|
||||||
parser.add_option('--email', action='store_true', default=False,
|
"-n",
|
||||||
help='Use a PGP-signed email for filing the sync '
|
action="store_true",
|
||||||
'request, rather than the LP API.')
|
dest="newpkg",
|
||||||
parser.add_option('--lp', dest='deprecated_lp_flag',
|
default=False,
|
||||||
action='store_true', default=False,
|
help="Whether package to sync is a new package in Ubuntu.",
|
||||||
help=optparse.SUPPRESS_HELP)
|
)
|
||||||
parser.add_option('-l', '--lpinstance', metavar='INSTANCE',
|
parser.add_argument(
|
||||||
dest='lpinstance', default=None,
|
"--email",
|
||||||
help='Launchpad instance to connect to '
|
action="store_true",
|
||||||
'(default: production).')
|
default=False,
|
||||||
parser.add_option('-s', action='store_true',
|
help="Use a PGP-signed email for filing the sync request, rather than the LP API.",
|
||||||
dest='sponsorship', default=False,
|
)
|
||||||
help='Force sponsorship')
|
parser.add_argument(
|
||||||
parser.add_option('-C', action='store_true',
|
"--lp",
|
||||||
dest='missing_changelog_ok', default=False,
|
dest="deprecated_lp_flag",
|
||||||
help='Allow changelog to be manually filled in '
|
action="store_true",
|
||||||
'when missing')
|
default=False,
|
||||||
parser.add_option('-e', action='store_true',
|
help=argparse.SUPPRESS,
|
||||||
dest='ffe', default=False,
|
)
|
||||||
help='Use this after FeatureFreeze for non-bug fix '
|
parser.add_argument(
|
||||||
'syncs, changes default subscription to the '
|
"-l",
|
||||||
'appropriate release team.')
|
"--lpinstance",
|
||||||
parser.add_option('--no-conf', action='store_true',
|
metavar="INSTANCE",
|
||||||
dest='no_conf', default=False,
|
dest="lpinstance",
|
||||||
help="Don't read config files or environment variables")
|
default=None,
|
||||||
|
help="Launchpad instance to connect to (default: production).",
|
||||||
(options, args) = parser.parse_args()
|
)
|
||||||
|
parser.add_argument(
|
||||||
if not len(args):
|
"-s", action="store_true", dest="sponsorship", default=False, help="Force sponsorship"
|
||||||
parser.print_help()
|
)
|
||||||
sys.exit(1)
|
parser.add_argument(
|
||||||
|
"-C",
|
||||||
|
action="store_true",
|
||||||
|
dest="missing_changelog_ok",
|
||||||
|
default=False,
|
||||||
|
help="Allow changelog to be manually filled in when missing",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e",
|
||||||
|
action="store_true",
|
||||||
|
dest="ffe",
|
||||||
|
default=False,
|
||||||
|
help="Use this after FeatureFreeze for non-bug fix "
|
||||||
|
"syncs, changes default subscription to the "
|
||||||
|
"appropriate release team.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf",
|
||||||
|
action="store_true",
|
||||||
|
dest="no_conf",
|
||||||
|
default=False,
|
||||||
|
help="Don't read config files or environment variables",
|
||||||
|
)
|
||||||
|
parser.add_argument("source_package", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("release", nargs="?", help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument("base_version", nargs="?", type=Version, help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
require_utf8()
|
require_utf8()
|
||||||
|
|
||||||
config = UDTConfig(options.no_conf)
|
config = UDTConfig(args.no_conf)
|
||||||
|
|
||||||
if options.deprecated_lp_flag:
|
if args.deprecated_lp_flag:
|
||||||
Logger.info("The --lp flag is now default, ignored.")
|
Logger.info("The --lp flag is now default, ignored.")
|
||||||
if options.email:
|
if args.email:
|
||||||
options.lpapi = False
|
args.lpapi = False
|
||||||
else:
|
else:
|
||||||
options.lpapi = config.get_value('USE_LPAPI', default=True,
|
args.lpapi = config.get_value("USE_LPAPI", default=True, boolean=True)
|
||||||
boolean=True)
|
if args.lpinstance is None:
|
||||||
if options.lpinstance is None:
|
args.lpinstance = config.get_value("LPINSTANCE")
|
||||||
options.lpinstance = config.get_value('LPINSTANCE')
|
|
||||||
|
|
||||||
if options.keyid is None:
|
if args.keyid is None:
|
||||||
options.keyid = config.get_value('KEYID')
|
args.keyid = config.get_value("KEYID")
|
||||||
|
|
||||||
if not options.lpapi:
|
if not args.lpapi:
|
||||||
if options.lpinstance == 'production':
|
if args.lpinstance == "production":
|
||||||
bug_mail_domain = 'bugs.launchpad.net'
|
bug_mail_domain = "bugs.launchpad.net"
|
||||||
elif options.lpinstance == 'staging':
|
elif args.lpinstance == "staging":
|
||||||
bug_mail_domain = 'bugs.staging.launchpad.net'
|
bug_mail_domain = "bugs.staging.launchpad.net"
|
||||||
else:
|
else:
|
||||||
Logger.error('Error: Unknown launchpad instance: %s'
|
Logger.error("Error: Unknown launchpad instance: %s", args.lpinstance)
|
||||||
% options.lpinstance)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mailserver_host = config.get_value('SMTP_SERVER',
|
mailserver_host = config.get_value(
|
||||||
default=None,
|
"SMTP_SERVER", default=None, compat_keys=["UBUSMTP", "DEBSMTP"]
|
||||||
compat_keys=['UBUSMTP', 'DEBSMTP'])
|
)
|
||||||
if not options.lpapi and not mailserver_host:
|
if not args.lpapi and not mailserver_host:
|
||||||
try:
|
try:
|
||||||
import DNS
|
import DNS # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
DNS.DiscoverNameServers()
|
DNS.DiscoverNameServers()
|
||||||
mxlist = DNS.mxlookup(bug_mail_domain)
|
mxlist = DNS.mxlookup(bug_mail_domain)
|
||||||
firstmx = mxlist[0]
|
firstmx = mxlist[0]
|
||||||
mailserver_host = firstmx[1]
|
mailserver_host = firstmx[1]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
Logger.error('Please install python-dns to support '
|
Logger.error("Please install python-dns to support Launchpad mail server lookup.")
|
||||||
'Launchpad mail server lookup.')
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mailserver_port = config.get_value('SMTP_PORT', default=25,
|
mailserver_port = config.get_value(
|
||||||
compat_keys=['UBUSMTP_PORT',
|
"SMTP_PORT", default=25, compat_keys=["UBUSMTP_PORT", "DEBSMTP_PORT"]
|
||||||
'DEBSMTP_PORT'])
|
)
|
||||||
mailserver_user = config.get_value('SMTP_USER',
|
mailserver_user = config.get_value("SMTP_USER", compat_keys=["UBUSMTP_USER", "DEBSMTP_USER"])
|
||||||
compat_keys=['UBUSMTP_USER',
|
mailserver_pass = config.get_value("SMTP_PASS", compat_keys=["UBUSMTP_PASS", "DEBSMTP_PASS"])
|
||||||
'DEBSMTP_USER'])
|
|
||||||
mailserver_pass = config.get_value('SMTP_PASS',
|
|
||||||
compat_keys=['UBUSMTP_PASS',
|
|
||||||
'DEBSMTP_PASS'])
|
|
||||||
|
|
||||||
# import the needed requestsync module
|
# import the needed requestsync module
|
||||||
if options.lpapi:
|
# pylint: disable=import-outside-toplevel
|
||||||
from ubuntutools.requestsync.lp import (check_existing_reports,
|
if args.lpapi:
|
||||||
get_debian_srcpkg,
|
|
||||||
get_ubuntu_srcpkg,
|
|
||||||
get_ubuntu_delta_changelog,
|
|
||||||
need_sponsorship, post_bug)
|
|
||||||
from ubuntutools.lp.lpapicache import Distribution, Launchpad
|
from ubuntutools.lp.lpapicache import Distribution, Launchpad
|
||||||
|
from ubuntutools.requestsync.lp import (
|
||||||
|
check_existing_reports,
|
||||||
|
get_debian_srcpkg,
|
||||||
|
get_ubuntu_delta_changelog,
|
||||||
|
get_ubuntu_srcpkg,
|
||||||
|
need_sponsorship,
|
||||||
|
post_bug,
|
||||||
|
)
|
||||||
|
|
||||||
# See if we have LP credentials and exit if we don't -
|
# See if we have LP credentials and exit if we don't -
|
||||||
# cannot continue in this case
|
# cannot continue in this case
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# devel for changelogUrl()
|
# devel for changelogUrl()
|
||||||
Launchpad.login(service=options.lpinstance, api_version='devel')
|
Launchpad.login(service=args.lpinstance, api_version="devel")
|
||||||
except IOError:
|
except IOError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
from ubuntutools.requestsync.mail import (check_existing_reports,
|
from ubuntutools.requestsync.mail import (
|
||||||
|
check_existing_reports,
|
||||||
get_debian_srcpkg,
|
get_debian_srcpkg,
|
||||||
get_ubuntu_srcpkg,
|
|
||||||
get_ubuntu_delta_changelog,
|
get_ubuntu_delta_changelog,
|
||||||
mail_bug, need_sponsorship)
|
get_ubuntu_srcpkg,
|
||||||
if not any(x in os.environ for x in ('UBUMAIL', 'DEBEMAIL', 'EMAIL')):
|
mail_bug,
|
||||||
Logger.error('The environment variable UBUMAIL, DEBEMAIL or EMAIL needs '
|
need_sponsorship,
|
||||||
'to be set to let this script mail the sync request.')
|
)
|
||||||
|
|
||||||
|
if not any(x in os.environ for x in ("UBUMAIL", "DEBEMAIL", "EMAIL")):
|
||||||
|
Logger.error(
|
||||||
|
"The environment variable UBUMAIL, DEBEMAIL or EMAIL needs "
|
||||||
|
"to be set to let this script mail the sync request."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
newsource = options.newpkg
|
newsource = args.newpkg
|
||||||
sponsorship = options.sponsorship
|
sponsorship = args.sponsorship
|
||||||
distro = options.dist
|
distro = args.dist
|
||||||
ffe = options.ffe
|
ffe = args.ffe
|
||||||
lpapi = options.lpapi
|
lpapi = args.lpapi
|
||||||
need_interaction = False
|
need_interaction = False
|
||||||
force_base_version = None
|
srcpkg = args.source_package
|
||||||
srcpkg = args[0]
|
|
||||||
|
|
||||||
if len(args) == 1:
|
if not args.release:
|
||||||
if lpapi:
|
if lpapi:
|
||||||
release = Distribution('ubuntu').getDevelopmentSeries().name
|
args.release = Distribution("ubuntu").getDevelopmentSeries().name
|
||||||
else:
|
else:
|
||||||
ubu_info = UbuntuDistroInfo()
|
ubu_info = UbuntuDistroInfo()
|
||||||
release = ubu_info.devel()
|
args.release = ubu_info.devel()
|
||||||
Logger.warning('Target release missing - assuming %s' % release)
|
Logger.warning("Target release missing - assuming %s", args.release)
|
||||||
elif len(args) == 2:
|
|
||||||
release = args[1]
|
|
||||||
elif len(args) == 3:
|
|
||||||
release = args[1]
|
|
||||||
force_base_version = Version(args[2])
|
|
||||||
else:
|
|
||||||
Logger.error('Too many arguments.')
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Get the current Ubuntu source package
|
# Get the current Ubuntu source package
|
||||||
try:
|
try:
|
||||||
ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, release, 'Proposed')
|
ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, args.release, "Proposed")
|
||||||
ubuntu_version = Version(ubuntu_srcpkg.getVersion())
|
ubuntu_version = Version(ubuntu_srcpkg.getVersion())
|
||||||
ubuntu_component = ubuntu_srcpkg.getComponent()
|
ubuntu_component = ubuntu_srcpkg.getComponent()
|
||||||
newsource = False # override the -n flag
|
newsource = False # override the -n flag
|
||||||
except udtexceptions.PackageNotFoundException:
|
except udtexceptions.PackageNotFoundException:
|
||||||
ubuntu_srcpkg = None
|
ubuntu_srcpkg = None
|
||||||
ubuntu_version = Version('~')
|
ubuntu_version = Version("~")
|
||||||
ubuntu_component = None # Set after getting the Debian info
|
ubuntu_component = None # Set after getting the Debian info
|
||||||
if not newsource:
|
if not newsource:
|
||||||
Logger.info("'%s' doesn't exist in 'Ubuntu %s'." % (srcpkg, release))
|
Logger.info("'%s' doesn't exist in 'Ubuntu %s'.", srcpkg, args.release)
|
||||||
Logger.info("Do you want to sync a new package?")
|
Logger.info("Do you want to sync a new package?")
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
newsource = True
|
newsource = True
|
||||||
@ -232,15 +252,16 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if ubuntu_component is None:
|
if ubuntu_component is None:
|
||||||
if debian_component == 'main':
|
if debian_component == "main":
|
||||||
ubuntu_component = 'universe'
|
ubuntu_component = "universe"
|
||||||
else:
|
else:
|
||||||
ubuntu_component = 'multiverse'
|
ubuntu_component = "multiverse"
|
||||||
|
|
||||||
# Stop if Ubuntu has already the version from Debian or a newer version
|
# Stop if Ubuntu has already the version from Debian or a newer version
|
||||||
if (ubuntu_version >= debian_version) and options.lpapi:
|
if (ubuntu_version >= debian_version) and args.lpapi:
|
||||||
# try rmadison
|
# try rmadison
|
||||||
import ubuntutools.requestsync.mail
|
import ubuntutools.requestsync.mail # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
debian_srcpkg = ubuntutools.requestsync.mail.get_debian_srcpkg(srcpkg, distro)
|
debian_srcpkg = ubuntutools.requestsync.mail.get_debian_srcpkg(srcpkg, distro)
|
||||||
debian_version = Version(debian_srcpkg.getVersion())
|
debian_version = Version(debian_srcpkg.getVersion())
|
||||||
@ -250,72 +271,80 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if ubuntu_version == debian_version:
|
if ubuntu_version == debian_version:
|
||||||
Logger.error('The versions in Debian and Ubuntu are the '
|
Logger.error(
|
||||||
'same already (%s). Aborting.' % ubuntu_version)
|
"The versions in Debian and Ubuntu are the same already (%s). Aborting.",
|
||||||
|
ubuntu_version,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if ubuntu_version > debian_version:
|
if ubuntu_version > debian_version:
|
||||||
Logger.error('The version in Ubuntu (%s) is newer than '
|
Logger.error(
|
||||||
'the version in Debian (%s). Aborting.'
|
"The version in Ubuntu (%s) is newer than the version in Debian (%s). Aborting.",
|
||||||
% (ubuntu_version, debian_version))
|
ubuntu_version,
|
||||||
|
debian_version,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# -s flag not specified - check if we do need sponsorship
|
# -s flag not specified - check if we do need sponsorship
|
||||||
if not sponsorship:
|
if not sponsorship:
|
||||||
sponsorship = need_sponsorship(srcpkg, ubuntu_component, release)
|
sponsorship = need_sponsorship(srcpkg, ubuntu_component, args.release)
|
||||||
|
|
||||||
if not sponsorship and not ffe:
|
if not sponsorship and not ffe:
|
||||||
Logger.error('Consider using syncpackage(1) for syncs that '
|
Logger.error(
|
||||||
'do not require feature freeze exceptions.')
|
"Consider using syncpackage(1) for syncs that "
|
||||||
|
"do not require feature freeze exceptions."
|
||||||
|
)
|
||||||
|
|
||||||
# Check for existing package reports
|
# Check for existing package reports
|
||||||
if not newsource:
|
if not newsource:
|
||||||
check_existing_reports(srcpkg)
|
check_existing_reports(srcpkg)
|
||||||
|
|
||||||
# Generate bug report
|
# Generate bug report
|
||||||
pkg_to_sync = ('%s %s (%s) from Debian %s (%s)'
|
pkg_to_sync = (
|
||||||
% (srcpkg, debian_version, ubuntu_component,
|
f"{srcpkg} {debian_version} ({ubuntu_component})"
|
||||||
distro, debian_component))
|
f" from Debian {distro} ({debian_component})"
|
||||||
title = "Sync %s" % pkg_to_sync
|
)
|
||||||
|
title = f"Sync {pkg_to_sync}"
|
||||||
if ffe:
|
if ffe:
|
||||||
title = "FFe: " + title
|
title = "FFe: " + title
|
||||||
report = "Please sync %s\n\n" % pkg_to_sync
|
report = f"Please sync {pkg_to_sync}\n\n"
|
||||||
|
|
||||||
if 'ubuntu' in str(ubuntu_version):
|
if "ubuntu" in str(ubuntu_version):
|
||||||
need_interaction = True
|
need_interaction = True
|
||||||
|
|
||||||
Logger.info('Changes have been made to the package in Ubuntu.')
|
Logger.info("Changes have been made to the package in Ubuntu.")
|
||||||
Logger.info('Please edit the report and give an explanation.')
|
Logger.info("Please edit the report and give an explanation.")
|
||||||
Logger.info('Not saving the report file will abort the request.')
|
Logger.info("Not saving the report file will abort the request.")
|
||||||
report += ('Explanation of the Ubuntu delta and why it can be '
|
report += (
|
||||||
'dropped:\n%s\n>>> ENTER_EXPLANATION_HERE <<<\n\n'
|
f"Explanation of the Ubuntu delta and why it can be dropped:\n"
|
||||||
% get_ubuntu_delta_changelog(ubuntu_srcpkg))
|
f"{get_ubuntu_delta_changelog(ubuntu_srcpkg)}\n>>> ENTER_EXPLANATION_HERE <<<\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
if ffe:
|
if ffe:
|
||||||
need_interaction = True
|
need_interaction = True
|
||||||
|
|
||||||
Logger.info('To approve FeatureFreeze exception, you need to state')
|
Logger.info("To approve FeatureFreeze exception, you need to state")
|
||||||
Logger.info('the reason why you feel it is necessary.')
|
Logger.info("the reason why you feel it is necessary.")
|
||||||
Logger.info('Not saving the report file will abort the request.')
|
Logger.info("Not saving the report file will abort the request.")
|
||||||
report += ('Explanation of FeatureFreeze exception:\n'
|
report += "Explanation of FeatureFreeze exception:\n>>> ENTER_EXPLANATION_HERE <<<\n\n"
|
||||||
'>>> ENTER_EXPLANATION_HERE <<<\n\n')
|
|
||||||
|
|
||||||
if need_interaction:
|
if need_interaction:
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
base_version = force_base_version or ubuntu_version
|
base_version = args.base_version or ubuntu_version
|
||||||
|
|
||||||
if newsource:
|
if newsource:
|
||||||
report += 'All changelog entries:\n\n'
|
report += "All changelog entries:\n\n"
|
||||||
else:
|
else:
|
||||||
report += ('Changelog entries since current %s version %s:\n\n'
|
report += f"Changelog entries since current {args.release} version {ubuntu_version}:\n\n"
|
||||||
% (release, ubuntu_version))
|
|
||||||
changelog = debian_srcpkg.getChangelog(since_version=base_version)
|
changelog = debian_srcpkg.getChangelog(since_version=base_version)
|
||||||
if not changelog:
|
if not changelog:
|
||||||
if not options.missing_changelog_ok:
|
if not args.missing_changelog_ok:
|
||||||
Logger.error("Did not retrieve any changelog entries. "
|
Logger.error(
|
||||||
|
"Did not retrieve any changelog entries. "
|
||||||
"Do you need to specify '-C'? "
|
"Do you need to specify '-C'? "
|
||||||
"Was the package recently uploaded? (check "
|
"Was the package recently uploaded? (check "
|
||||||
"http://packages.debian.org/changelogs/)")
|
"http://packages.debian.org/changelogs/)"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
need_interaction = True
|
need_interaction = True
|
||||||
@ -326,36 +355,49 @@ def main():
|
|||||||
editor.edit(optional=not need_interaction)
|
editor.edit(optional=not need_interaction)
|
||||||
title, report = editor.get_report()
|
title, report = editor.get_report()
|
||||||
|
|
||||||
if 'XXX FIXME' in report:
|
if "XXX FIXME" in report:
|
||||||
Logger.error("changelog boilerplate found in report, "
|
Logger.error(
|
||||||
"please manually add changelog when using '-C'")
|
"changelog boilerplate found in report, "
|
||||||
|
"please manually add changelog when using '-C'"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# bug status and bug subscriber
|
# bug status and bug subscriber
|
||||||
status = 'confirmed'
|
status = "confirmed"
|
||||||
subscribe = 'ubuntu-archive'
|
subscribe = "ubuntu-archive"
|
||||||
if sponsorship:
|
if sponsorship:
|
||||||
status = 'new'
|
status = "new"
|
||||||
subscribe = 'ubuntu-sponsors'
|
subscribe = "ubuntu-sponsors"
|
||||||
if ffe:
|
if ffe:
|
||||||
status = 'new'
|
status = "new"
|
||||||
subscribe = 'ubuntu-release'
|
subscribe = "ubuntu-release"
|
||||||
|
|
||||||
srcpkg = not newsource and srcpkg or None
|
srcpkg = None if newsource else srcpkg
|
||||||
if lpapi:
|
if lpapi:
|
||||||
# Map status to the values expected by LP API
|
# Map status to the values expected by LP API
|
||||||
mapping = {'new': 'New', 'confirmed': 'Confirmed'}
|
mapping = {"new": "New", "confirmed": "Confirmed"}
|
||||||
# Post sync request using LP API
|
# Post sync request using LP API
|
||||||
post_bug(srcpkg, subscribe, mapping[status], title, report)
|
post_bug(srcpkg, subscribe, mapping[status], title, report)
|
||||||
else:
|
else:
|
||||||
email_from = ubu_email(export=False)[1]
|
email_from = ubu_email(export=False)[1]
|
||||||
# Mail sync request
|
# Mail sync request
|
||||||
mail_bug(srcpkg, subscribe, status, title, report, bug_mail_domain,
|
mail_bug(
|
||||||
options.keyid, email_from, mailserver_host, mailserver_port,
|
srcpkg,
|
||||||
mailserver_user, mailserver_pass)
|
subscribe,
|
||||||
|
status,
|
||||||
|
title,
|
||||||
|
report,
|
||||||
|
bug_mail_domain,
|
||||||
|
args.keyid,
|
||||||
|
email_from,
|
||||||
|
mailserver_host,
|
||||||
|
mailserver_port,
|
||||||
|
mailserver_user,
|
||||||
|
mailserver_pass,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
python-debian
|
python-debian
|
||||||
python-debianbts
|
python-debianbts
|
||||||
|
dateutil
|
||||||
distro-info
|
distro-info
|
||||||
httplib2
|
httplib2
|
||||||
launchpadlib
|
launchpadlib
|
||||||
|
206
reverse-depends
206
reverse-depends
@ -14,16 +14,18 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distro_info import DistroDataOutdated
|
from distro_info import DistroDataOutdated
|
||||||
|
|
||||||
from ubuntutools.misc import (system_distribution, vendor_to_distroinfo,
|
|
||||||
codename_to_distribution)
|
|
||||||
from ubuntutools.rdepends import query_rdepends, RDependsException
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.misc import codename_to_distribution, system_distribution, vendor_to_distroinfo
|
||||||
|
from ubuntutools.rdepends import RDependsException, query_rdepends
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
DEFAULT_MAX_DEPTH = 10 # We want avoid any infinite loop...
|
DEFAULT_MAX_DEPTH = 10 # We want avoid any infinite loop...
|
||||||
@ -35,77 +37,107 @@ def main():
|
|||||||
default_release = system_distro_info.devel()
|
default_release = system_distro_info.devel()
|
||||||
except DistroDataOutdated as e:
|
except DistroDataOutdated as e:
|
||||||
Logger.warning(e)
|
Logger.warning(e)
|
||||||
default_release = 'unstable'
|
default_release = "unstable"
|
||||||
|
|
||||||
description = ("List reverse-dependencies of package. "
|
description = (
|
||||||
|
"List reverse-dependencies of package. "
|
||||||
"If the package name is prefixed with src: then the "
|
"If the package name is prefixed with src: then the "
|
||||||
"reverse-dependencies of all the binary packages that "
|
"reverse-dependencies of all the binary packages that "
|
||||||
"the specified source package builds will be listed.")
|
"the specified source package builds will be listed."
|
||||||
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=description)
|
parser = argparse.ArgumentParser(description=description)
|
||||||
parser.add_argument('-r', '--release', default=default_release,
|
parser.add_argument(
|
||||||
help='Query dependencies in RELEASE. '
|
"-r",
|
||||||
'Default: %s' % default_release)
|
"--release",
|
||||||
parser.add_argument('-R', '--without-recommends', action='store_false',
|
default=default_release,
|
||||||
dest='recommends',
|
help="Query dependencies in RELEASE. Default: %(default)s",
|
||||||
help='Only consider Depends relationships, '
|
)
|
||||||
'not Recommends')
|
parser.add_argument(
|
||||||
parser.add_argument('-s', '--with-suggests', action='store_true',
|
"-R",
|
||||||
help='Also consider Suggests relationships')
|
"--without-recommends",
|
||||||
parser.add_argument('-b', '--build-depends', action='store_true',
|
action="store_false",
|
||||||
help='Query build dependencies (synonym for --arch=source)')
|
dest="recommends",
|
||||||
parser.add_argument('-a', '--arch', default='any',
|
help="Only consider Depends relationships, not Recommends",
|
||||||
help='Query dependencies in ARCH. Default: any')
|
)
|
||||||
parser.add_argument('-c', '--component', action='append',
|
parser.add_argument(
|
||||||
help='Only consider reverse-dependencies in COMPONENT. '
|
"-s", "--with-suggests", action="store_true", help="Also consider Suggests relationships"
|
||||||
'Can be specified multiple times. Default: all')
|
)
|
||||||
parser.add_argument('-l', '--list', action='store_true',
|
parser.add_argument(
|
||||||
help='Display a simple, machine-readable list')
|
"-b",
|
||||||
parser.add_argument('-u', '--service-url', metavar='URL',
|
"--build-depends",
|
||||||
dest='server', default=None,
|
action="store_true",
|
||||||
help='Reverse Dependencies webservice URL. '
|
help="Query build dependencies (synonym for --arch=source)",
|
||||||
'Default: UbuntuWire')
|
)
|
||||||
parser.add_argument('-x', '--recursive', action='store_true',
|
parser.add_argument(
|
||||||
help='Consider to find reverse dependencies recursively.')
|
"-a", "--arch", default="any", help="Query dependencies in ARCH. Default: any"
|
||||||
parser.add_argument('-d', '--recursive-depth', type=int,
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--component",
|
||||||
|
action="append",
|
||||||
|
help="Only consider reverse-dependencies in COMPONENT. "
|
||||||
|
"Can be specified multiple times. Default: all",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l", "--list", action="store_true", help="Display a simple, machine-readable list"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-u",
|
||||||
|
"--service-url",
|
||||||
|
metavar="URL",
|
||||||
|
dest="server",
|
||||||
|
default=None,
|
||||||
|
help="Reverse Dependencies webservice URL. Default: UbuntuWire",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-x",
|
||||||
|
"--recursive",
|
||||||
|
action="store_true",
|
||||||
|
help="Consider to find reverse dependencies recursively.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--recursive-depth",
|
||||||
|
type=int,
|
||||||
default=DEFAULT_MAX_DEPTH,
|
default=DEFAULT_MAX_DEPTH,
|
||||||
help='If recusive, you can specify the depth.')
|
help="If recusive, you can specify the depth.",
|
||||||
parser.add_argument('package')
|
)
|
||||||
|
parser.add_argument("package")
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
opts = {}
|
opts = {}
|
||||||
if options.server is not None:
|
if options.server is not None:
|
||||||
opts['server'] = options.server
|
opts["server"] = options.server
|
||||||
|
|
||||||
# Convert unstable/testing aliases to codenames:
|
# Convert unstable/testing aliases to codenames:
|
||||||
distribution = codename_to_distribution(options.release)
|
distribution = codename_to_distribution(options.release)
|
||||||
if not distribution:
|
if not distribution:
|
||||||
parser.error('Unknown release codename %s' % options.release)
|
parser.error(f"Unknown release codename {options.release}")
|
||||||
distro_info = vendor_to_distroinfo(distribution)()
|
distro_info = vendor_to_distroinfo(distribution)()
|
||||||
try:
|
try:
|
||||||
options.release = distro_info.codename(options.release,
|
options.release = distro_info.codename(options.release, default=options.release)
|
||||||
default=options.release)
|
|
||||||
except DistroDataOutdated:
|
except DistroDataOutdated:
|
||||||
# We already logged a warning
|
# We already logged a warning
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if options.build_depends:
|
if options.build_depends:
|
||||||
options.arch = 'source'
|
options.arch = "source"
|
||||||
|
|
||||||
if options.arch == 'source':
|
if options.arch == "source":
|
||||||
fields = [
|
fields = [
|
||||||
'Reverse-Build-Depends',
|
"Reverse-Build-Depends",
|
||||||
'Reverse-Build-Depends-Indep',
|
"Reverse-Build-Depends-Indep",
|
||||||
'Reverse-Build-Depends-Arch',
|
"Reverse-Build-Depends-Arch",
|
||||||
'Reverse-Testsuite-Triggers',
|
"Reverse-Testsuite-Triggers",
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
fields = ['Reverse-Depends']
|
fields = ["Reverse-Depends"]
|
||||||
if options.recommends:
|
if options.recommends:
|
||||||
fields.append('Reverse-Recommends')
|
fields.append("Reverse-Recommends")
|
||||||
if options.with_suggests:
|
if options.with_suggests:
|
||||||
fields.append('Reverse-Suggests')
|
fields.append("Reverse-Suggests")
|
||||||
|
|
||||||
def build_results(package, result, fields, component, recursive):
|
def build_results(package, result, fields, component, recursive):
|
||||||
try:
|
try:
|
||||||
@ -119,9 +151,9 @@ def main():
|
|||||||
if fields:
|
if fields:
|
||||||
data = {k: v for k, v in data.items() if k in fields}
|
data = {k: v for k, v in data.items() if k in fields}
|
||||||
if component:
|
if component:
|
||||||
data = {k: [rdep for rdep in v
|
data = {
|
||||||
if rdep['Component'] in component]
|
k: [rdep for rdep in v if rdep["Component"] in component] for k, v in data.items()
|
||||||
for k, v in data.items()}
|
}
|
||||||
data = {k: v for k, v in data.items() if v}
|
data = {k: v for k, v in data.items() if v}
|
||||||
|
|
||||||
result[package] = data
|
result[package] = data
|
||||||
@ -129,13 +161,16 @@ def main():
|
|||||||
if recursive > 0:
|
if recursive > 0:
|
||||||
for rdeps in result[package].values():
|
for rdeps in result[package].values():
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
build_results(
|
build_results(rdep["Package"], result, fields, component, recursive - 1)
|
||||||
rdep['Package'], result, fields, component, recursive - 1)
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
build_results(
|
build_results(
|
||||||
options.package, result, fields, options.component,
|
options.package,
|
||||||
options.recursive and options.recursive_depth or 0)
|
result,
|
||||||
|
fields,
|
||||||
|
options.component,
|
||||||
|
options.recursive and options.recursive_depth or 0,
|
||||||
|
)
|
||||||
|
|
||||||
if options.list:
|
if options.list:
|
||||||
display_consise(result)
|
display_consise(result)
|
||||||
@ -148,52 +183,59 @@ def display_verbose(package, values):
|
|||||||
Logger.info("No reverse dependencies found")
|
Logger.info("No reverse dependencies found")
|
||||||
return
|
return
|
||||||
|
|
||||||
def log_field(field):
|
def log_package(values, package, arch, dependency, visited, offset=0):
|
||||||
Logger.info(field)
|
line = f"{' ' * offset}* {package}"
|
||||||
Logger.info('=' * len(field))
|
|
||||||
|
|
||||||
def log_package(values, package, arch, dependency, offset=0):
|
|
||||||
line = ' ' * offset + '* %s' % package
|
|
||||||
if all_archs and set(arch) != all_archs:
|
if all_archs and set(arch) != all_archs:
|
||||||
line += ' [%s]' % ' '.join(sorted(arch))
|
line += f" [{' '.join(sorted(arch))}]"
|
||||||
if dependency:
|
if dependency:
|
||||||
if len(line) < 30:
|
if len(line) < 30:
|
||||||
line += ' ' * (30 - len(line))
|
line += " " * (30 - len(line))
|
||||||
line += ' (for %s)' % dependency
|
line += f" (for {dependency})"
|
||||||
Logger.info(line)
|
Logger.info(line)
|
||||||
|
if package in visited:
|
||||||
|
return
|
||||||
|
visited = visited.copy().add(package)
|
||||||
data = values.get(package)
|
data = values.get(package)
|
||||||
if data:
|
if data:
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
for rdeps in data.values():
|
for rdeps in data.values():
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
log_package(values,
|
log_package(
|
||||||
rdep['Package'],
|
values,
|
||||||
rdep.get('Architectures', all_archs),
|
rdep["Package"],
|
||||||
rdep.get('Dependency'),
|
rdep.get("Architectures", all_archs),
|
||||||
offset)
|
rdep.get("Dependency"),
|
||||||
|
visited,
|
||||||
|
offset,
|
||||||
|
)
|
||||||
|
|
||||||
all_archs = set()
|
all_archs = set()
|
||||||
# This isn't accurate, but we make up for it by displaying what we found
|
# This isn't accurate, but we make up for it by displaying what we found
|
||||||
for data in values.values():
|
for data in values.values():
|
||||||
for rdeps in data.values():
|
for rdeps in data.values():
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
if 'Architectures' in rdep:
|
if "Architectures" in rdep:
|
||||||
all_archs.update(rdep['Architectures'])
|
all_archs.update(rdep["Architectures"])
|
||||||
|
|
||||||
for field, rdeps in values[package].items():
|
for field, rdeps in values[package].items():
|
||||||
Logger.info(field)
|
Logger.info("%s", field)
|
||||||
rdeps.sort(key=lambda x: x['Package'])
|
Logger.info("%s", "=" * len(field))
|
||||||
|
rdeps.sort(key=lambda x: x["Package"])
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
log_package(values,
|
log_package(
|
||||||
rdep['Package'],
|
values,
|
||||||
rdep.get('Architectures', all_archs),
|
rdep["Package"],
|
||||||
rdep.get('Dependency'))
|
rdep.get("Architectures", all_archs),
|
||||||
|
rdep.get("Dependency"),
|
||||||
|
{package},
|
||||||
|
)
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
|
|
||||||
if all_archs:
|
if all_archs:
|
||||||
Logger.info("Packages without architectures listed are "
|
Logger.info(
|
||||||
"reverse-dependencies in: %s"
|
"Packages without architectures listed are reverse-dependencies in: %s",
|
||||||
% ', '.join(sorted(list(all_archs))))
|
", ".join(sorted(list(all_archs))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def display_consise(values):
|
def display_consise(values):
|
||||||
@ -201,10 +243,10 @@ def display_consise(values):
|
|||||||
for data in values.values():
|
for data in values.values():
|
||||||
for rdeps in data.values():
|
for rdeps in data.values():
|
||||||
for rdep in rdeps:
|
for rdep in rdeps:
|
||||||
result.add(rdep['Package'])
|
result.add(rdep["Package"])
|
||||||
|
|
||||||
Logger.info('\n'.join(sorted(list(result))))
|
Logger.info("\n".join(sorted(list(result))))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
19
run-linters
Executable file
19
run-linters
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Copyright 2023, Canonical Ltd.
|
||||||
|
# SPDX-License-Identifier: GPL-3.0
|
||||||
|
|
||||||
|
PYTHON_SCRIPTS=$(grep -l -r '^#! */usr/bin/python3$' .)
|
||||||
|
|
||||||
|
echo "Running black..."
|
||||||
|
black --check --diff . $PYTHON_SCRIPTS
|
||||||
|
|
||||||
|
echo "Running isort..."
|
||||||
|
isort --check-only --diff .
|
||||||
|
|
||||||
|
echo "Running flake8..."
|
||||||
|
flake8 --max-line-length=99 --ignore=E203,W503 . $PYTHON_SCRIPTS
|
||||||
|
|
||||||
|
echo "Running pylint..."
|
||||||
|
pylint $(find * -name '*.py') $PYTHON_SCRIPTS
|
81
running-autopkgtests
Executable file
81
running-autopkgtests
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||||
|
|
||||||
|
# Authors:
|
||||||
|
# Andy P. Whitcroft
|
||||||
|
# Christian Ehrhardt
|
||||||
|
# Chris Peterson <chris.peterson@canonical.com>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2024 Canonical Ltd.
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 3, as published
|
||||||
|
# by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||||
|
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
"""Dumps a list of currently running tests in Autopkgtest"""
|
||||||
|
|
||||||
|
__example__ = """
|
||||||
|
Display first listed test running on amd64 hardware:
|
||||||
|
$ running-autopkgtests | grep amd64 | head -n1
|
||||||
|
R 0:01:40 systemd-upstream - focal amd64\
|
||||||
|
upstream-systemd-ci/systemd-ci - ['CFLAGS=-O0', 'DEB_BUILD_PROFILES=noudeb',\
|
||||||
|
'TEST_UPSTREAM=1', 'CONFFLAGS_UPSTREAM=--werror -Dslow-tests=true',\
|
||||||
|
'UPSTREAM_PULL_REQUEST=23153',\
|
||||||
|
'GITHUB_STATUSES_URL=https://api.github.com/repos/\
|
||||||
|
systemd/systemd/statuses/cfb0935923dff8050315b5dd22ce8ab06461ff0e']
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
||||||
|
|
||||||
|
from ubuntutools.running_autopkgtests import get_queued, get_running
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
description = (
|
||||||
|
"Dumps a list of currently running and queued tests in Autopkgtest. "
|
||||||
|
"Pass --running to only see running tests, or --queued to only see "
|
||||||
|
"queued tests. Passing both will print both, which is the default behavior. "
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = ArgumentParser(
|
||||||
|
prog="running-autopkgtests",
|
||||||
|
description=description,
|
||||||
|
epilog=f"example: {__example__}",
|
||||||
|
formatter_class=RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--running", action="store_true", help="Print runnning autopkgtests (default: true)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-q", "--queued", action="store_true", help="Print queued autopkgtests (default: true)"
|
||||||
|
)
|
||||||
|
|
||||||
|
options = parser.parse_args()
|
||||||
|
|
||||||
|
# If neither flag was specified, default to both not neither
|
||||||
|
if not options.running and not options.queued:
|
||||||
|
options.running = True
|
||||||
|
options.queued = True
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
if args.running:
|
||||||
|
print(get_running())
|
||||||
|
if args.queued:
|
||||||
|
print(get_queued())
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
116
seeded-in-ubuntu
116
seeded-in-ubuntu
@ -14,51 +14,53 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import collections
|
import collections
|
||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
import optparse
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
from ubuntutools.lp.lpapicache import (Distribution, Launchpad,
|
|
||||||
PackageNotFoundException)
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.lp.lpapicache import Distribution, Launchpad, PackageNotFoundException
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
DATA_URL = 'http://qa.ubuntuwire.org/ubuntu-seeded-packages/seeded.json.gz'
|
DATA_URL = "http://qa.ubuntuwire.org/ubuntu-seeded-packages/seeded.json.gz"
|
||||||
|
|
||||||
|
|
||||||
def load_index(url):
|
def load_index(url):
|
||||||
'''Download a new copy of the image contents index, if necessary,
|
"""Download a new copy of the image contents index, if necessary,
|
||||||
and read it.
|
and read it.
|
||||||
'''
|
"""
|
||||||
cachedir = os.path.expanduser('~/.cache/ubuntu-dev-tools')
|
cachedir = os.path.expanduser("~/.cache/ubuntu-dev-tools")
|
||||||
fn = os.path.join(cachedir, 'seeded.json.gz')
|
seeded = os.path.join(cachedir, "seeded.json.gz")
|
||||||
|
|
||||||
if (not os.path.isfile(fn)
|
if not os.path.isfile(seeded) or time.time() - os.path.getmtime(seeded) > 60 * 60 * 2:
|
||||||
or time.time() - os.path.getmtime(fn) > 60 * 60 * 2):
|
|
||||||
if not os.path.isdir(cachedir):
|
if not os.path.isdir(cachedir):
|
||||||
os.makedirs(cachedir)
|
os.makedirs(cachedir)
|
||||||
urllib.request.urlretrieve(url, fn)
|
urllib.request.urlretrieve(url, seeded)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with gzip.open(fn, 'r') as f:
|
with gzip.open(seeded, "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as e:
|
except Exception as e: # pylint: disable=broad-except
|
||||||
Logger.error("Unable to parse seed data: %s. "
|
Logger.error(
|
||||||
"Deleting cached data, please try again.",
|
"Unable to parse seed data: %s. Deleting cached data, please try again.", str(e)
|
||||||
str(e))
|
)
|
||||||
os.unlink(fn)
|
os.unlink(seeded)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def resolve_binaries(sources):
|
def resolve_binaries(sources):
|
||||||
'''Return a dict of source:binaries for all binary packages built by
|
"""Return a dict of source:binaries for all binary packages built by
|
||||||
sources
|
sources
|
||||||
'''
|
"""
|
||||||
archive = Distribution('ubuntu').getArchive()
|
archive = Distribution("ubuntu").getArchive()
|
||||||
binaries = {}
|
binaries = {}
|
||||||
for source in sources:
|
for source in sources:
|
||||||
try:
|
try:
|
||||||
@ -66,80 +68,84 @@ def resolve_binaries(sources):
|
|||||||
except PackageNotFoundException as e:
|
except PackageNotFoundException as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
continue
|
continue
|
||||||
binaries[source] = sorted(set(bpph.getPackageName()
|
binaries[source] = sorted(set(bpph.getPackageName() for bpph in spph.getBinaries()))
|
||||||
for bpph in spph.getBinaries()))
|
|
||||||
|
|
||||||
return binaries
|
return binaries
|
||||||
|
|
||||||
|
|
||||||
def present_on(appearences):
|
def present_on(appearences):
|
||||||
'''Format a list of (flavor, type) tuples into a human-readable string'''
|
"""Format a list of (flavor, type) tuples into a human-readable string"""
|
||||||
present = collections.defaultdict(set)
|
present = collections.defaultdict(set)
|
||||||
for flavor, type_ in appearences:
|
for flavor, type_ in appearences:
|
||||||
present[flavor].add(type_)
|
present[flavor].add(type_)
|
||||||
for flavor, types in present.items():
|
for flavor, types in present.items():
|
||||||
if len(types) > 1:
|
if len(types) > 1:
|
||||||
types.discard('supported')
|
types.discard("supported")
|
||||||
output = [' %s: %s' % (flavor, ', '.join(sorted(types)))
|
output = [f" {flavor}: {', '.join(sorted(types))}" for flavor, types in present.items()]
|
||||||
for flavor, types in present.items()]
|
|
||||||
output.sort()
|
output.sort()
|
||||||
return '\n'.join(output)
|
return "\n".join(output)
|
||||||
|
|
||||||
|
|
||||||
def output_binaries(index, binaries):
|
def output_binaries(index, binaries):
|
||||||
'''Print binaries found in index'''
|
"""Print binaries found in index"""
|
||||||
for binary in binaries:
|
for binary in binaries:
|
||||||
if binary in index:
|
if binary in index:
|
||||||
Logger.info("%s is seeded in:" % binary)
|
Logger.info("%s is seeded in:", binary)
|
||||||
Logger.info(present_on(index[binary]))
|
Logger.info(present_on(index[binary]))
|
||||||
else:
|
else:
|
||||||
Logger.info("%s is not seeded (and may not exist)." % binary)
|
Logger.info("%s is not seeded (and may not exist).", binary)
|
||||||
|
|
||||||
|
|
||||||
def output_by_source(index, by_source):
|
def output_by_source(index, by_source):
|
||||||
'''Logger.Info(binaries found in index. Grouped by source'''
|
"""Logger.Info(binaries found in index. Grouped by source"""
|
||||||
for source, binaries in by_source.items():
|
for source, binaries in by_source.items():
|
||||||
seen = False
|
seen = False
|
||||||
if not binaries:
|
if not binaries:
|
||||||
Logger.info("Status unknown: No binary packages built by the latest "
|
Logger.info(
|
||||||
"%s.\nTry again using -b and the expected binary packages."
|
"Status unknown: No binary packages built by the latest "
|
||||||
% source)
|
"%s.\nTry again using -b and the expected binary packages.",
|
||||||
|
source,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
for binary in binaries:
|
for binary in binaries:
|
||||||
if binary in index:
|
if binary in index:
|
||||||
seen = True
|
seen = True
|
||||||
Logger.info("%s (from %s) is seeded in:" % (binary, source))
|
Logger.info("%s (from %s) is seeded in:", binary, source)
|
||||||
Logger.info(present_on(index[binary]))
|
Logger.info(present_on(index[binary]))
|
||||||
if not seen:
|
if not seen:
|
||||||
Logger.info("%s's binaries are not seeded." % source)
|
Logger.info("%s's binaries are not seeded.", source)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
'''Query which images the specified packages are on'''
|
"""Query which images the specified packages are on"""
|
||||||
parser = optparse.OptionParser('%prog [options] package...')
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] package...")
|
||||||
parser.add_option('-b', '--binary',
|
parser.add_argument(
|
||||||
default=False, action='store_true',
|
"-b",
|
||||||
help="Binary packages are being specified, "
|
"--binary",
|
||||||
"not source packages (fast)")
|
default=False,
|
||||||
parser.add_option('-u', '--data-url', metavar='URL',
|
action="store_true",
|
||||||
|
help="Binary packages are being specified, not source packages (fast)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-u",
|
||||||
|
"--data-url",
|
||||||
|
metavar="URL",
|
||||||
default=DATA_URL,
|
default=DATA_URL,
|
||||||
help='URL for the seeded packages index. '
|
help="URL for the seeded packages index. Default: UbuntuWire",
|
||||||
'Default: UbuntuWire')
|
)
|
||||||
options, args = parser.parse_args()
|
parser.add_argument("packages", metavar="package", nargs="+", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
if len(args) < 1:
|
|
||||||
parser.error("At least one package must be specified")
|
|
||||||
|
|
||||||
# Login anonymously to LP
|
# Login anonymously to LP
|
||||||
Launchpad.login_anonymously()
|
Launchpad.login_anonymously()
|
||||||
|
|
||||||
index = load_index(options.data_url)
|
index = load_index(args.data_url)
|
||||||
if options.binary:
|
if args.binary:
|
||||||
output_binaries(index, args)
|
output_binaries(index, args.packages)
|
||||||
else:
|
else:
|
||||||
binaries = resolve_binaries(args)
|
binaries = resolve_binaries(args.packages)
|
||||||
output_by_source(index, binaries)
|
output_by_source(index, binaries)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -104,7 +104,7 @@ echo "In order to do packaging work, you'll need a minimal set of packages."
|
|||||||
echo "Those, together with other packages which, though optional, have proven"
|
echo "Those, together with other packages which, though optional, have proven"
|
||||||
echo "to be useful, will now be installed."
|
echo "to be useful, will now be installed."
|
||||||
echo
|
echo
|
||||||
sudo apt-get install ubuntu-dev-tools devscripts debhelper cdbs patchutils pbuilder build-essential
|
sudo apt-get install ubuntu-dev-tools devscripts debhelper patchutils pbuilder build-essential
|
||||||
separator2
|
separator2
|
||||||
|
|
||||||
echo "Enabling the source repository"
|
echo "Enabling the source repository"
|
||||||
|
145
setup.py
145
setup.py
@ -1,81 +1,100 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
from setuptools import setup
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# look/set what version we have
|
from setuptools import setup
|
||||||
changelog = "debian/changelog"
|
|
||||||
if os.path.exists(changelog):
|
|
||||||
head = open(changelog, 'r', encoding='utf-8').readline()
|
def get_debian_version() -> str:
|
||||||
|
"""Look what Debian version we have."""
|
||||||
|
changelog = pathlib.Path(__file__).parent / "debian" / "changelog"
|
||||||
|
with changelog.open("r", encoding="utf-8") as changelog_f:
|
||||||
|
head = changelog_f.readline()
|
||||||
match = re.compile(r".*\((.*)\).*").match(head)
|
match = re.compile(r".*\((.*)\).*").match(head)
|
||||||
if match:
|
if not match:
|
||||||
version = match.group(1)
|
raise ValueError(f"Failed to extract Debian version from '{head}'.")
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pep440_compliant(version: str) -> str:
|
||||||
|
"""Convert the version into a PEP440 compliant version."""
|
||||||
|
public_version_re = re.compile(r"^([0-9][0-9.]*(?:(?:a|b|rc|.post|.dev)[0-9]+)*)\+?")
|
||||||
|
_, public, local = public_version_re.split(version, maxsplit=1)
|
||||||
|
if not local:
|
||||||
|
return version
|
||||||
|
sanitized_local = re.sub("[+~]+", ".", local).strip(".")
|
||||||
|
pep440_version = f"{public}+{sanitized_local}"
|
||||||
|
assert re.match("^[a-zA-Z0-9.]+$", sanitized_local), f"'{pep440_version}' not PEP440 compliant"
|
||||||
|
return pep440_version
|
||||||
|
|
||||||
|
|
||||||
scripts = [
|
scripts = [
|
||||||
'backportpackage',
|
"backportpackage",
|
||||||
'bitesize',
|
"check-mir",
|
||||||
'check-mir',
|
"check-symbols",
|
||||||
'check-symbols',
|
"dch-repeat",
|
||||||
'dch-repeat',
|
"grab-merge",
|
||||||
'grab-merge',
|
"grep-merges",
|
||||||
'grep-merges',
|
"import-bug-from-debian",
|
||||||
'import-bug-from-debian',
|
"lp-bitesize",
|
||||||
'merge-changelog',
|
"merge-changelog",
|
||||||
'mk-sbuild',
|
"mk-sbuild",
|
||||||
'pbuilder-dist',
|
"pbuilder-dist",
|
||||||
'pbuilder-dist-simple',
|
"pbuilder-dist-simple",
|
||||||
'pull-pkg',
|
"pm-helper",
|
||||||
'pull-debian-debdiff',
|
"pull-pkg",
|
||||||
'pull-debian-source',
|
"pull-debian-debdiff",
|
||||||
'pull-debian-debs',
|
"pull-debian-source",
|
||||||
'pull-debian-ddebs',
|
"pull-debian-debs",
|
||||||
'pull-debian-udebs',
|
"pull-debian-ddebs",
|
||||||
'pull-lp-source',
|
"pull-debian-udebs",
|
||||||
'pull-lp-debs',
|
"pull-lp-source",
|
||||||
'pull-lp-ddebs',
|
"pull-lp-debs",
|
||||||
'pull-lp-udebs',
|
"pull-lp-ddebs",
|
||||||
'pull-ppa-source',
|
"pull-lp-udebs",
|
||||||
'pull-ppa-debs',
|
"pull-ppa-source",
|
||||||
'pull-ppa-ddebs',
|
"pull-ppa-debs",
|
||||||
'pull-ppa-udebs',
|
"pull-ppa-ddebs",
|
||||||
'pull-uca-source',
|
"pull-ppa-udebs",
|
||||||
'pull-uca-debs',
|
"pull-uca-source",
|
||||||
'pull-uca-ddebs',
|
"pull-uca-debs",
|
||||||
'pull-uca-udebs',
|
"pull-uca-ddebs",
|
||||||
'requestbackport',
|
"pull-uca-udebs",
|
||||||
'requestsync',
|
"requestbackport",
|
||||||
'reverse-depends',
|
"requestsync",
|
||||||
'seeded-in-ubuntu',
|
"reverse-depends",
|
||||||
'setup-packaging-environment',
|
"running-autopkgtests",
|
||||||
'sponsor-patch',
|
"seeded-in-ubuntu",
|
||||||
'submittodebian',
|
"setup-packaging-environment",
|
||||||
'syncpackage',
|
"sponsor-patch",
|
||||||
'ubuntu-build',
|
"submittodebian",
|
||||||
'ubuntu-iso',
|
"syncpackage",
|
||||||
'ubuntu-upload-permission',
|
"ubuntu-build",
|
||||||
'update-maintainer',
|
"ubuntu-iso",
|
||||||
|
"ubuntu-upload-permission",
|
||||||
|
"update-maintainer",
|
||||||
]
|
]
|
||||||
data_files = [
|
data_files = [
|
||||||
('share/bash-completion/completions', glob.glob("bash_completion/*")),
|
("share/bash-completion/completions", glob.glob("bash_completion/*")),
|
||||||
('share/man/man1', glob.glob("doc/*.1")),
|
("share/man/man1", glob.glob("doc/*.1")),
|
||||||
('share/man/man5', glob.glob("doc/*.5")),
|
("share/man/man5", glob.glob("doc/*.5")),
|
||||||
('share/ubuntu-dev-tools', ['enforced-editing-wrapper']),
|
("share/ubuntu-dev-tools", ["enforced-editing-wrapper"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name='ubuntu-dev-tools',
|
name="ubuntu-dev-tools",
|
||||||
version=version,
|
version=make_pep440_compliant(get_debian_version()),
|
||||||
scripts=scripts,
|
scripts=scripts,
|
||||||
packages=[
|
packages=[
|
||||||
'ubuntutools',
|
"ubuntutools",
|
||||||
'ubuntutools/lp',
|
"ubuntutools/lp",
|
||||||
'ubuntutools/requestsync',
|
"ubuntutools/requestsync",
|
||||||
'ubuntutools/sponsor_patch',
|
"ubuntutools/sponsor_patch",
|
||||||
'ubuntutools/test',
|
"ubuntutools/test",
|
||||||
],
|
],
|
||||||
data_files=data_files,
|
data_files=data_files,
|
||||||
test_suite='ubuntutools.test',
|
test_suite="ubuntutools.test",
|
||||||
)
|
)
|
||||||
|
186
sponsor-patch
186
sponsor-patch
@ -14,123 +14,153 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
|
||||||
|
|
||||||
from ubuntutools.builder import get_builder
|
|
||||||
from ubuntutools.config import UDTConfig
|
|
||||||
from ubuntutools.sponsor_patch.sponsor_patch import sponsor_patch, check_dependencies
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.builder import get_builder
|
||||||
|
from ubuntutools.config import UDTConfig
|
||||||
|
from ubuntutools.sponsor_patch.sponsor_patch import check_dependencies, sponsor_patch
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def parse(script_name):
|
def parse(script_name):
|
||||||
"""Parse the command line parameters."""
|
"""Parse the command line parameters."""
|
||||||
usage = ("%s [options] <bug number>\n" % (script_name)
|
usage = (
|
||||||
+ "One of --upload, --workdir, or --sponsor must be specified.")
|
"%(prog)s [options] <bug number>\n"
|
||||||
epilog = "See %s(1) for more info." % (script_name)
|
"One of --upload, --workdir, or --sponsor must be specified."
|
||||||
parser = optparse.OptionParser(usage=usage, epilog=epilog)
|
)
|
||||||
|
epilog = f"See {script_name}(1) for more info."
|
||||||
|
parser = argparse.ArgumentParser(usage=usage, epilog=epilog)
|
||||||
|
|
||||||
parser.add_option("-b", "--build", dest="build",
|
parser.add_argument(
|
||||||
|
"-b",
|
||||||
|
"--build",
|
||||||
|
dest="build",
|
||||||
help="Build the package with the specified builder.",
|
help="Build the package with the specified builder.",
|
||||||
action="store_true", default=False)
|
|
||||||
parser.add_option("-B", "--builder", dest="builder", default=None,
|
|
||||||
help="Specify the package builder (default pbuilder)")
|
|
||||||
parser.add_option("-e", "--edit",
|
|
||||||
help="launch sub-shell to allow editing of the patch",
|
|
||||||
dest="edit", action="store_true", default=False)
|
|
||||||
parser.add_option("-k", "--key", dest="keyid", default=None,
|
|
||||||
help="Specify the key ID to be used for signing.")
|
|
||||||
parser.add_option("-l", "--lpinstance", dest="lpinstance", default=None,
|
|
||||||
help="Launchpad instance to connect to "
|
|
||||||
"(default: production)",
|
|
||||||
metavar="INSTANCE")
|
|
||||||
parser.add_option("--no-conf", dest="no_conf", default=False,
|
|
||||||
help="Don't read config files or environment variables.",
|
|
||||||
action="store_true")
|
|
||||||
parser.add_option("-s", "--sponsor", help="sponsoring; equals -b -u ubuntu",
|
|
||||||
dest="sponsoring", action="store_true", default=False)
|
|
||||||
parser.add_option("-u", "--upload", dest="upload", default=None,
|
|
||||||
help="Specify an upload destination (default none).")
|
|
||||||
parser.add_option("-U", "--update", dest="update", default=False,
|
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Update the build environment before building.")
|
)
|
||||||
parser.add_option("-v", "--verbose", help="print more information",
|
parser.add_argument(
|
||||||
dest="verbose", action="store_true", default=False)
|
"-B", "--builder", dest="builder", help="Specify the package builder (default pbuilder)"
|
||||||
parser.add_option("-w", "--workdir", dest="workdir", default=None,
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-e",
|
||||||
|
"--edit",
|
||||||
|
help="launch sub-shell to allow editing of the patch",
|
||||||
|
dest="edit",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-k", "--key", dest="keyid", help="Specify the key ID to be used for signing."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--lpinstance",
|
||||||
|
dest="lpinstance",
|
||||||
|
help="Launchpad instance to connect to (default: production)",
|
||||||
|
metavar="INSTANCE",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-conf",
|
||||||
|
dest="no_conf",
|
||||||
|
help="Don't read config files or environment variables.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--sponsor",
|
||||||
|
help="sponsoring; equals -b -u ubuntu",
|
||||||
|
dest="sponsoring",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-u", "--upload", dest="upload", help="Specify an upload destination (default none)."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-U",
|
||||||
|
"--update",
|
||||||
|
dest="update",
|
||||||
|
action="store_true",
|
||||||
|
help="Update the build environment before building.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose", help="print more information", dest="verbose", action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-w",
|
||||||
|
"--workdir",
|
||||||
|
dest="workdir",
|
||||||
help="Specify a working directory (default is a "
|
help="Specify a working directory (default is a "
|
||||||
"temporary directory, deleted afterwards).")
|
"temporary directory, deleted afterwards).",
|
||||||
|
)
|
||||||
|
parser.add_argument("bug_number", type=int, help=argparse.SUPPRESS)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if options.verbose:
|
if args.verbose:
|
||||||
Logger.setLevel(logging.DEBUG)
|
Logger.setLevel(logging.DEBUG)
|
||||||
check_dependencies()
|
check_dependencies()
|
||||||
|
|
||||||
if len(args) == 0:
|
config = UDTConfig(args.no_conf)
|
||||||
Logger.error("No bug number specified.")
|
if args.builder is None:
|
||||||
sys.exit(1)
|
args.builder = config.get_value("BUILDER")
|
||||||
elif len(args) > 1:
|
if args.lpinstance is None:
|
||||||
Logger.error("Multiple bug numbers specified: %s" % (", ".join(args)))
|
args.lpinstance = config.get_value("LPINSTANCE")
|
||||||
sys.exit(1)
|
if not args.update:
|
||||||
|
args.update = config.get_value("UPDATE_BUILDER", boolean=True)
|
||||||
|
if args.workdir is None:
|
||||||
|
args.workdir = config.get_value("WORKDIR")
|
||||||
|
if args.keyid is None:
|
||||||
|
args.keyid = config.get_value("KEYID")
|
||||||
|
|
||||||
bug_number = args[0]
|
if args.sponsoring:
|
||||||
if bug_number.isdigit():
|
args.build = True
|
||||||
bug_number = int(bug_number)
|
args.upload = "ubuntu"
|
||||||
else:
|
|
||||||
Logger.error("Invalid bug number specified: %s" % (bug_number))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
config = UDTConfig(options.no_conf)
|
return args
|
||||||
if options.builder is None:
|
|
||||||
options.builder = config.get_value("BUILDER")
|
|
||||||
if options.lpinstance is None:
|
|
||||||
options.lpinstance = config.get_value("LPINSTANCE")
|
|
||||||
if not options.update:
|
|
||||||
options.update = config.get_value("UPDATE_BUILDER", boolean=True)
|
|
||||||
if options.workdir is None:
|
|
||||||
options.workdir = config.get_value("WORKDIR")
|
|
||||||
if options.keyid is None:
|
|
||||||
options.keyid = config.get_value("KEYID")
|
|
||||||
|
|
||||||
if options.sponsoring:
|
|
||||||
options.build = True
|
|
||||||
options.upload = "ubuntu"
|
|
||||||
|
|
||||||
return (options, bug_number)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
script_name = os.path.basename(sys.argv[0])
|
script_name = os.path.basename(sys.argv[0])
|
||||||
(options, bug_number) = parse(script_name)
|
args = parse(script_name)
|
||||||
|
|
||||||
builder = get_builder(options.builder)
|
builder = get_builder(args.builder)
|
||||||
if not builder:
|
if not builder:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not options.upload and not options.workdir:
|
if not args.upload and not args.workdir:
|
||||||
Logger.error("Please specify either a working directory or an upload "
|
Logger.error("Please specify either a working directory or an upload target!")
|
||||||
"target!")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if options.workdir is None:
|
if args.workdir is None:
|
||||||
workdir = tempfile.mkdtemp(prefix=script_name + "-")
|
workdir = tempfile.mkdtemp(prefix=script_name + "-")
|
||||||
else:
|
else:
|
||||||
workdir = options.workdir
|
workdir = args.workdir
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sponsor_patch(bug_number, options.build, builder, options.edit,
|
sponsor_patch(
|
||||||
options.keyid, options.lpinstance, options.update,
|
args.bug_number,
|
||||||
options.upload, workdir)
|
args.build,
|
||||||
|
builder,
|
||||||
|
args.edit,
|
||||||
|
args.keyid,
|
||||||
|
args.lpinstance,
|
||||||
|
args.update,
|
||||||
|
args.upload,
|
||||||
|
workdir,
|
||||||
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
Logger.error("User abort.")
|
Logger.error("User abort.")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
finally:
|
finally:
|
||||||
if options.workdir is None:
|
if args.workdir is None:
|
||||||
shutil.rmtree(workdir)
|
shutil.rmtree(workdir)
|
||||||
|
|
||||||
|
|
||||||
|
180
submittodebian
180
submittodebian
@ -22,32 +22,36 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
import optparse
|
"""Submit the Ubuntu changes in a package to Debian.
|
||||||
|
|
||||||
|
Run inside an unpacked Ubuntu source package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
from subprocess import DEVNULL, PIPE, Popen, call, check_call, run
|
||||||
from subprocess import call, check_call, run, Popen, PIPE, DEVNULL
|
|
||||||
|
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
from debian.changelog import Changelog
|
from debian.changelog import Changelog
|
||||||
from distro_info import UbuntuDistroInfo, DistroDataOutdated
|
from distro_info import DistroDataOutdated, UbuntuDistroInfo
|
||||||
|
|
||||||
from ubuntutools.config import ubu_email
|
|
||||||
from ubuntutools.question import YesNoQuestion, EditFile
|
|
||||||
from ubuntutools.update_maintainer import update_maintainer, restore_maintainer
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.config import ubu_email
|
||||||
|
from ubuntutools.question import EditFile, YesNoQuestion
|
||||||
|
from ubuntutools.update_maintainer import restore_maintainer, update_maintainer
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def get_most_recent_debian_version(changelog):
|
def get_most_recent_debian_version(changelog):
|
||||||
for block in changelog:
|
for block in changelog:
|
||||||
version = block.version.full_version
|
version = block.version.full_version
|
||||||
if not re.search('(ubuntu|build)', version):
|
if not re.search("(ubuntu|build)", version):
|
||||||
return version
|
return version
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_bug_body(changelog):
|
def get_bug_body(changelog):
|
||||||
@ -65,19 +69,20 @@ In Ubuntu, the attached patch was applied to achieve the following:
|
|||||||
%s
|
%s
|
||||||
|
|
||||||
Thanks for considering the patch.
|
Thanks for considering the patch.
|
||||||
""" % ("\n".join([a for a in entry.changes()]))
|
""" % (
|
||||||
|
"\n".join(entry.changes())
|
||||||
|
)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def build_source_package():
|
def build_source_package():
|
||||||
if os.path.isdir('.bzr'):
|
if os.path.isdir(".bzr"):
|
||||||
cmd = ['bzr', 'bd', '--builder=dpkg-buildpackage', '-S',
|
cmd = ["bzr", "bd", "--builder=dpkg-buildpackage", "-S", "--", "-uc", "-us", "-nc"]
|
||||||
'--', '-uc', '-us', '-nc']
|
|
||||||
else:
|
else:
|
||||||
cmd = ['dpkg-buildpackage', '-S', '-uc', '-us', '-nc']
|
cmd = ["dpkg-buildpackage", "-S", "-uc", "-us", "-nc"]
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
# Unset DEBEMAIL in case there's an @ubuntu.com e-mail address
|
# Unset DEBEMAIL in case there's an @ubuntu.com e-mail address
|
||||||
env.pop('DEBEMAIL', None)
|
env.pop("DEBEMAIL", None)
|
||||||
check_call(cmd, env=env)
|
check_call(cmd, env=env)
|
||||||
|
|
||||||
|
|
||||||
@ -88,30 +93,35 @@ def gen_debdiff(tmpdir, changelog):
|
|||||||
newver = next(changelog_it).version
|
newver = next(changelog_it).version
|
||||||
oldver = next(changelog_it).version
|
oldver = next(changelog_it).version
|
||||||
|
|
||||||
debdiff = os.path.join(tmpdir, '%s_%s.debdiff' % (pkg, newver))
|
debdiff = os.path.join(tmpdir, f"{pkg}_{newver}.debdiff")
|
||||||
|
|
||||||
diff_cmd = ['bzr', 'diff', '-r', 'tag:' + str(oldver)]
|
diff_cmd = ["bzr", "diff", "-r", "tag:" + str(oldver)]
|
||||||
if call(diff_cmd, stdout=DEVNULL, stderr=DEVNULL) == 1:
|
if call(diff_cmd, stdout=DEVNULL, stderr=DEVNULL) == 1:
|
||||||
Logger.info("Extracting bzr diff between %s and %s" % (oldver, newver))
|
Logger.info("Extracting bzr diff between %s and %s", oldver, newver)
|
||||||
else:
|
else:
|
||||||
if oldver.epoch is not None:
|
if oldver.epoch is not None:
|
||||||
oldver = str(oldver)[str(oldver).index(":") + 1 :]
|
oldver = str(oldver)[str(oldver).index(":") + 1 :]
|
||||||
if newver.epoch is not None:
|
if newver.epoch is not None:
|
||||||
newver = str(newver)[str(newver).index(":") + 1 :]
|
newver = str(newver)[str(newver).index(":") + 1 :]
|
||||||
|
|
||||||
olddsc = '../%s_%s.dsc' % (pkg, oldver)
|
olddsc = f"../{pkg}_{oldver}.dsc"
|
||||||
newdsc = '../%s_%s.dsc' % (pkg, newver)
|
newdsc = f"../{pkg}_{newver}.dsc"
|
||||||
|
|
||||||
check_file(olddsc)
|
check_file(olddsc)
|
||||||
check_file(newdsc)
|
check_file(newdsc)
|
||||||
|
|
||||||
Logger.info("Generating debdiff between %s and %s" % (oldver, newver))
|
Logger.info("Generating debdiff between %s and %s", oldver, newver)
|
||||||
diff_cmd = ['debdiff', olddsc, newdsc]
|
diff_cmd = ["debdiff", olddsc, newdsc]
|
||||||
|
|
||||||
with Popen(diff_cmd, stdout=PIPE, encoding='utf-8') as diff:
|
with Popen(diff_cmd, stdout=PIPE, encoding="utf-8") as diff:
|
||||||
with open(debdiff, 'w', encoding='utf-8') as debdiff_f:
|
with open(debdiff, "w", encoding="utf-8") as debdiff_f:
|
||||||
run(['filterdiff', '-x', '*changelog*'],
|
run(
|
||||||
stdin=diff.stdout, stdout=debdiff_f, encoding='utf-8')
|
["filterdiff", "-x", "*changelog*"],
|
||||||
|
check=False,
|
||||||
|
stdin=diff.stdout,
|
||||||
|
stdout=debdiff_f,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
return debdiff
|
return debdiff
|
||||||
|
|
||||||
@ -119,10 +129,9 @@ def gen_debdiff(tmpdir, changelog):
|
|||||||
def check_file(fname, critical=True):
|
def check_file(fname, critical=True):
|
||||||
if os.path.exists(fname):
|
if os.path.exists(fname):
|
||||||
return fname
|
return fname
|
||||||
else:
|
|
||||||
if not critical:
|
if not critical:
|
||||||
return False
|
return False
|
||||||
Logger.info("Couldn't find «%s».\n" % fname)
|
Logger.info("Couldn't find «%s».\n", fname)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -131,76 +140,84 @@ def submit_bugreport(body, debdiff, deb_version, changelog):
|
|||||||
devel = UbuntuDistroInfo().devel()
|
devel = UbuntuDistroInfo().devel()
|
||||||
except DistroDataOutdated as e:
|
except DistroDataOutdated as e:
|
||||||
Logger.info(str(e))
|
Logger.info(str(e))
|
||||||
devel = ''
|
devel = ""
|
||||||
|
|
||||||
if os.path.dirname(sys.argv[0]).startswith('/usr/bin'):
|
if os.path.dirname(sys.argv[0]).startswith("/usr/bin"):
|
||||||
editor_path = '/usr/share/ubuntu-dev-tools'
|
editor_path = "/usr/share/ubuntu-dev-tools"
|
||||||
else:
|
else:
|
||||||
editor_path = os.path.dirname(sys.argv[0])
|
editor_path = os.path.dirname(sys.argv[0])
|
||||||
env = dict(os.environ.items())
|
env = dict(os.environ.items())
|
||||||
if 'EDITOR' in env:
|
if "EDITOR" in env:
|
||||||
env['UDT_EDIT_WRAPPER_EDITOR'] = env['EDITOR']
|
env["UDT_EDIT_WRAPPER_EDITOR"] = env["EDITOR"]
|
||||||
if 'VISUAL' in env:
|
if "VISUAL" in env:
|
||||||
env['UDT_EDIT_WRAPPER_VISUAL'] = env['VISUAL']
|
env["UDT_EDIT_WRAPPER_VISUAL"] = env["VISUAL"]
|
||||||
env['EDITOR'] = os.path.join(editor_path, 'enforced-editing-wrapper')
|
env["EDITOR"] = os.path.join(editor_path, "enforced-editing-wrapper")
|
||||||
env['VISUAL'] = os.path.join(editor_path, 'enforced-editing-wrapper')
|
env["VISUAL"] = os.path.join(editor_path, "enforced-editing-wrapper")
|
||||||
env['UDT_EDIT_WRAPPER_TEMPLATE_RE'] = (
|
env["UDT_EDIT_WRAPPER_TEMPLATE_RE"] = ".*REPLACE THIS WITH ACTUAL INFORMATION.*"
|
||||||
'.*REPLACE THIS WITH ACTUAL INFORMATION.*')
|
env["UDT_EDIT_WRAPPER_FILE_DESCRIPTION"] = "bug report"
|
||||||
env['UDT_EDIT_WRAPPER_FILE_DESCRIPTION'] = 'bug report'
|
|
||||||
|
|
||||||
# In external mua mode, attachments are lost (Reportbug bug: #679907)
|
# In external mua mode, attachments are lost (Reportbug bug: #679907)
|
||||||
internal_mua = True
|
internal_mua = True
|
||||||
for cfgfile in ('/etc/reportbug.conf', '~/.reportbugrc'):
|
for cfgfile in ("/etc/reportbug.conf", "~/.reportbugrc"):
|
||||||
cfgfile = os.path.expanduser(cfgfile)
|
cfgfile = os.path.expanduser(cfgfile)
|
||||||
if not os.path.exists(cfgfile):
|
if not os.path.exists(cfgfile):
|
||||||
continue
|
continue
|
||||||
with open(cfgfile, 'r') as f:
|
with open(cfgfile, "r", encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line in ('gnus', 'mutt', 'nmh') or line.startswith('mua '):
|
if line in ("gnus", "mutt", "nmh") or line.startswith("mua "):
|
||||||
internal_mua = False
|
internal_mua = False
|
||||||
break
|
break
|
||||||
|
|
||||||
cmd = ('reportbug',
|
cmd = (
|
||||||
'--no-check-available',
|
"reportbug",
|
||||||
'--no-check-installed',
|
"--no-check-available",
|
||||||
'--pseudo-header', 'User: ubuntu-devel@lists.ubuntu.com',
|
"--no-check-installed",
|
||||||
'--pseudo-header', 'Usertags: origin-ubuntu %s ubuntu-patch'
|
"--pseudo-header",
|
||||||
% devel,
|
"User: ubuntu-devel@lists.ubuntu.com",
|
||||||
'--tag', 'patch',
|
"--pseudo-header",
|
||||||
'--bts', 'debian',
|
f"Usertags: origin-ubuntu {devel} ubuntu-patch",
|
||||||
'--include', body,
|
"--tag",
|
||||||
'--attach' if internal_mua else '--include', debdiff,
|
"patch",
|
||||||
'--package-version', deb_version,
|
"--bts",
|
||||||
changelog.package)
|
"debian",
|
||||||
|
"--include",
|
||||||
|
body,
|
||||||
|
"--attach" if internal_mua else "--include",
|
||||||
|
debdiff,
|
||||||
|
"--package-version",
|
||||||
|
deb_version,
|
||||||
|
changelog.package,
|
||||||
|
)
|
||||||
check_call(cmd, env=env)
|
check_call(cmd, env=env)
|
||||||
|
|
||||||
|
|
||||||
def check_reportbug_config():
|
def check_reportbug_config():
|
||||||
fn = os.path.expanduser('~/.reportbugrc')
|
reportbugrc_filename = os.path.expanduser("~/.reportbugrc")
|
||||||
if os.path.exists(fn):
|
if os.path.exists(reportbugrc_filename):
|
||||||
return
|
return
|
||||||
email = ubu_email()[1]
|
email = ubu_email()[1]
|
||||||
reportbugrc = """# Reportbug configuration generated by submittodebian(1)
|
reportbugrc = f"""# Reportbug configuration generated by submittodebian(1)
|
||||||
# See reportbug.conf(5) for the configuration file format.
|
# See reportbug.conf(5) for the configuration file format.
|
||||||
|
|
||||||
# Use Debian's reportbug SMTP Server:
|
# Use Debian's reportbug SMTP Server:
|
||||||
# Note: it's limited to 5 connections per hour, and cannot CC you at submission
|
# Note: it's limited to 5 connections per hour, and cannot CC you at submission
|
||||||
# time. See /usr/share/doc/reportbug/README.Users.gz for more details.
|
# time. See /usr/share/doc/reportbug/README.Users.gz for more details.
|
||||||
smtphost reportbug.debian.org:587
|
smtphost reportbug.debian.org:587
|
||||||
header "X-Debbugs-CC: %s"
|
header "X-Debbugs-CC: {email}"
|
||||||
no-cc
|
no-cc
|
||||||
|
|
||||||
# Use GMail's SMTP Server:
|
# Use GMail's SMTP Server:
|
||||||
#smtphost smtp.googlemail.com:587
|
#smtphost smtp.googlemail.com:587
|
||||||
#smtpuser "<your address>@gmail.com"
|
#smtpuser "<your address>@gmail.com"
|
||||||
#smtptls
|
#smtptls
|
||||||
""" % email
|
"""
|
||||||
|
|
||||||
with open(fn, 'w') as f:
|
with open(reportbugrc_filename, "w", encoding="utf-8") as f:
|
||||||
f.write(reportbugrc)
|
f.write(reportbugrc)
|
||||||
|
|
||||||
Logger.info("""\
|
Logger.info(
|
||||||
|
"""\
|
||||||
You have not configured reportbug. Assuming this is the first time you have
|
You have not configured reportbug. Assuming this is the first time you have
|
||||||
used it. Writing a ~/.reportbugrc that will use Debian's mail server, and CC
|
used it. Writing a ~/.reportbugrc that will use Debian's mail server, and CC
|
||||||
the bug to you at <%s>
|
the bug to you at <%s>
|
||||||
@ -211,40 +228,43 @@ the bug to you at <%s>
|
|||||||
|
|
||||||
If this is not correct, please exit now and edit ~/.reportbugrc or run
|
If this is not correct, please exit now and edit ~/.reportbugrc or run
|
||||||
reportbug --configure for its configuration wizard.
|
reportbug --configure for its configuration wizard.
|
||||||
""" % (email, reportbugrc.strip()))
|
""",
|
||||||
|
email,
|
||||||
|
reportbugrc.strip(),
|
||||||
|
)
|
||||||
|
|
||||||
if YesNoQuestion().ask("Continue submitting this bug", "yes") == "no":
|
if YesNoQuestion().ask("Continue submitting this bug", "yes") == "no":
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
description = 'Submit the Ubuntu changes in a package to Debian. ' + \
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
'Run inside an unpacked Ubuntu source package.'
|
|
||||||
parser = optparse.OptionParser(description=description)
|
|
||||||
parser.parse_args()
|
parser.parse_args()
|
||||||
|
|
||||||
if not os.path.exists('/usr/bin/reportbug'):
|
if not os.path.exists("/usr/bin/reportbug"):
|
||||||
Logger.error("This utility requires the «reportbug» package, which isn't "
|
Logger.error(
|
||||||
"currently installed.")
|
"This utility requires the «reportbug» package, which isn't currently installed."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
check_reportbug_config()
|
check_reportbug_config()
|
||||||
changelog_file = (check_file('debian/changelog', critical=False) or
|
changelog_file = check_file("debian/changelog", critical=False) or check_file(
|
||||||
check_file('../debian/changelog'))
|
"../debian/changelog"
|
||||||
with open(changelog_file) as f:
|
)
|
||||||
|
with open(changelog_file, encoding="utf-8") as f:
|
||||||
changelog = Changelog(f.read())
|
changelog = Changelog(f.read())
|
||||||
|
|
||||||
deb_version = get_most_recent_debian_version(changelog)
|
deb_version = get_most_recent_debian_version(changelog)
|
||||||
bug_body = get_bug_body(changelog)
|
bug_body = get_bug_body(changelog)
|
||||||
|
|
||||||
tmpdir = mkdtemp()
|
tmpdir = mkdtemp()
|
||||||
body = os.path.join(tmpdir, 'bug_body')
|
body = os.path.join(tmpdir, "bug_body")
|
||||||
with open(body, 'wb') as f:
|
with open(body, "wb") as f:
|
||||||
f.write(bug_body.encode('utf-8'))
|
f.write(bug_body.encode("utf-8"))
|
||||||
|
|
||||||
restore_maintainer('debian')
|
restore_maintainer("debian")
|
||||||
build_source_package()
|
build_source_package()
|
||||||
update_maintainer('debian')
|
update_maintainer("debian")
|
||||||
|
|
||||||
debdiff = gen_debdiff(tmpdir, changelog)
|
debdiff = gen_debdiff(tmpdir, changelog)
|
||||||
|
|
||||||
@ -252,7 +272,7 @@ def main():
|
|||||||
# reverted in the most recent build
|
# reverted in the most recent build
|
||||||
build_source_package()
|
build_source_package()
|
||||||
|
|
||||||
EditFile(debdiff, 'debdiff').edit(optional=True)
|
EditFile(debdiff, "debdiff").edit(optional=True)
|
||||||
|
|
||||||
submit_bugreport(body, debdiff, deb_version, changelog)
|
submit_bugreport(body, debdiff, deb_version, changelog)
|
||||||
os.unlink(body)
|
os.unlink(body)
|
||||||
@ -260,5 +280,5 @@ def main():
|
|||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
749
syncpackage
749
syncpackage
File diff suppressed because it is too large
Load Diff
466
ubuntu-build
466
ubuntu-build
@ -2,16 +2,16 @@
|
|||||||
#
|
#
|
||||||
# ubuntu-build - command line interface for Launchpad buildd operations.
|
# ubuntu-build - command line interface for Launchpad buildd operations.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2007 Canonical Ltd.
|
# Copyright (C) 2007-2024 Canonical Ltd.
|
||||||
# Authors:
|
# Authors:
|
||||||
# - Martin Pitt <martin.pitt@canonical.com>
|
# - Martin Pitt <martin.pitt@canonical.com>
|
||||||
# - Jonathan Davies <jpds@ubuntu.com>
|
# - Jonathan Davies <jpds@ubuntu.com>
|
||||||
# - Michael Bienia <geser@ubuntu.com>
|
# - Michael Bienia <geser@ubuntu.com>
|
||||||
|
# - Steve Langasek <steve.langasek@canonical.com>
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, version 3 of the License.
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
@ -22,106 +22,181 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
# Our modules to import.
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
from optparse import OptionGroup
|
|
||||||
from optparse import OptionParser
|
import lazr.restfulclient.errors
|
||||||
from ubuntutools.lp.udtexceptions import (SeriesNotFoundException,
|
from launchpadlib.launchpad import Launchpad
|
||||||
PackageNotFoundException,
|
|
||||||
PocketDoesNotExistError,)
|
|
||||||
from ubuntutools.lp.lpapicache import Distribution, PersonTeam
|
|
||||||
from ubuntutools.misc import split_release_pocket
|
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
|
||||||
|
from ubuntutools.misc import split_release_pocket
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_states(pkg, archs):
|
||||||
|
res = []
|
||||||
|
|
||||||
|
for build in pkg.getBuilds():
|
||||||
|
if build.arch_tag in archs:
|
||||||
|
res.append(f" {build.arch_tag}: {build.buildstate}")
|
||||||
|
msg = "\n".join(res)
|
||||||
|
return f"Build state(s) for '{pkg.source_package_name}':\n{msg}"
|
||||||
|
|
||||||
|
|
||||||
|
def rescore_builds(pkg, archs, score):
|
||||||
|
res = []
|
||||||
|
|
||||||
|
for build in pkg.getBuilds():
|
||||||
|
arch = build.arch_tag
|
||||||
|
if arch in archs:
|
||||||
|
if not build.can_be_rescored:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
build.rescore(score=score)
|
||||||
|
res.append(f" {arch}: done")
|
||||||
|
except lazr.restfulclient.errors.Unauthorized:
|
||||||
|
Logger.error(
|
||||||
|
"You don't have the permissions to rescore builds."
|
||||||
|
" Ignoring your rescore request."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
except lazr.restfulclient.errors.BadRequest:
|
||||||
|
Logger.info("Cannot rescore build of %s on %s.", build.source_package_name, arch)
|
||||||
|
res.append(f" {arch}: failed")
|
||||||
|
|
||||||
|
msg = "\n".join(res)
|
||||||
|
return f"Rescoring builds of '{pkg.source_package_name}' to {score}:\n{msg}"
|
||||||
|
|
||||||
|
|
||||||
|
def retry_builds(pkg, archs):
|
||||||
|
res = []
|
||||||
|
for build in pkg.getBuilds():
|
||||||
|
arch = build.arch_tag
|
||||||
|
if arch in archs:
|
||||||
|
try:
|
||||||
|
build.retry()
|
||||||
|
res.append(f" {arch}: done")
|
||||||
|
except lazr.restfulclient.errors.BadRequest:
|
||||||
|
res.append(f" {arch}: failed")
|
||||||
|
msg = "\n".join(res)
|
||||||
|
return f"Retrying builds of '{pkg.source_package_name}':\n{msg}"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Usage.
|
# Usage.
|
||||||
usage = "%prog <srcpackage> <release> <operation>\n\n"
|
usage = "%(prog)s <srcpackage> <release> <operation>\n\n"
|
||||||
usage += "Where operation may be one of: rescore, retry, or status.\n"
|
usage += "Where operation may be one of: rescore, retry, or status.\n"
|
||||||
usage += "Only Launchpad Buildd Admins may rescore package builds."
|
usage += "Only Launchpad Buildd Admins may rescore package builds."
|
||||||
|
|
||||||
# Valid architectures.
|
# Valid architectures.
|
||||||
valid_archs = set([
|
valid_archs = set(
|
||||||
"armel", "armhf", "arm64", "amd64", "hppa", "i386", "ia64",
|
["armhf", "arm64", "amd64", "i386", "powerpc", "ppc64el", "riscv64", "s390x"]
|
||||||
"lpia", "powerpc", "ppc64el", "riscv64", "s390x", "sparc",
|
)
|
||||||
])
|
|
||||||
|
|
||||||
# Prepare our option parser.
|
# Prepare our option parser.
|
||||||
opt_parser = OptionParser(usage)
|
parser = argparse.ArgumentParser(usage=usage)
|
||||||
|
|
||||||
# Retry options
|
parser.add_argument(
|
||||||
retry_rescore_options = OptionGroup(opt_parser, "Retry and rescore options",
|
"-a",
|
||||||
"These options may only be used with "
|
"--arch",
|
||||||
"the 'retry' and 'rescore' operations.")
|
action="append",
|
||||||
retry_rescore_options.add_option("-a", "--arch", type="string",
|
dest="architecture",
|
||||||
action="append", dest="architecture",
|
help=f"Rebuild or rescore a specific architecture. Valid architectures "
|
||||||
help="Rebuild or rescore a specific "
|
f"include: {', '.join(valid_archs)}.",
|
||||||
"architecture. Valid architectures "
|
)
|
||||||
"include: %s." %
|
|
||||||
", ".join(valid_archs))
|
parser.add_argument("-A", "--archive", help="operate on ARCHIVE", default="ubuntu")
|
||||||
|
|
||||||
# Batch processing options
|
# Batch processing options
|
||||||
batch_options = OptionGroup(opt_parser, "Batch processing",
|
batch_options = parser.add_argument_group(
|
||||||
|
"Batch processing",
|
||||||
"These options and parameter ordering is only "
|
"These options and parameter ordering is only "
|
||||||
"available in --batch mode.\nUsage: "
|
"available in --batch mode.\nUsage: "
|
||||||
"ubuntu-build --batch [options] <package>...")
|
"ubuntu-build --batch [options] <package>...",
|
||||||
batch_options.add_option('--batch',
|
)
|
||||||
action='store_true', dest='batch', default=False,
|
batch_options.add_argument(
|
||||||
help='Enable batch mode')
|
"--batch", action="store_true", dest="batch", help="Enable batch mode"
|
||||||
batch_options.add_option('--series',
|
)
|
||||||
action='store', dest='series', type='string',
|
batch_options.add_argument(
|
||||||
help='Selects the Ubuntu series to operate on '
|
"--series",
|
||||||
'(default: current development series)')
|
action="store",
|
||||||
batch_options.add_option('--retry',
|
dest="series",
|
||||||
action='store_true', dest='retry', default=False,
|
help="Selects the Ubuntu series to operate on (default: current development series)",
|
||||||
help='Retry builds (give-back).')
|
)
|
||||||
batch_options.add_option('--rescore',
|
batch_options.add_argument(
|
||||||
action='store', dest='priority', type='int',
|
"--retry", action="store_true", dest="retry", help="Retry builds (give-back)."
|
||||||
help='Rescore builds to <priority>.')
|
)
|
||||||
batch_options.add_option('--arch2', action='append', dest='architecture',
|
batch_options.add_argument(
|
||||||
type='string',
|
"--rescore",
|
||||||
help="Affect only 'architecture' (can be used "
|
action="store",
|
||||||
"several times). Valid architectures are: %s."
|
dest="priority",
|
||||||
% ', '.join(valid_archs))
|
type=int,
|
||||||
|
help="Rescore builds to <priority>.",
|
||||||
|
)
|
||||||
|
batch_options.add_argument(
|
||||||
|
"--state",
|
||||||
|
action="store",
|
||||||
|
dest="state",
|
||||||
|
help="Act on builds that are in the specified state",
|
||||||
|
)
|
||||||
|
|
||||||
# Add the retry options to the main group.
|
parser.add_argument("packages", metavar="package", nargs="*", help=argparse.SUPPRESS)
|
||||||
opt_parser.add_option_group(retry_rescore_options)
|
|
||||||
# Add the batch mode to the main group.
|
|
||||||
opt_parser.add_option_group(batch_options)
|
|
||||||
|
|
||||||
# Parse our options.
|
# Parse our options.
|
||||||
(options, args) = opt_parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if not len(args):
|
launchpad = Launchpad.login_with("ubuntu-dev-tools", "production", version="devel")
|
||||||
opt_parser.print_help()
|
ubuntu = launchpad.distributions["ubuntu"]
|
||||||
|
|
||||||
|
if args.batch:
|
||||||
|
release = args.series
|
||||||
|
if not release:
|
||||||
|
# ppas don't have a proposed pocket so just use the release pocket;
|
||||||
|
# but for the main archive we default to -proposed
|
||||||
|
release = ubuntu.getDevelopmentSeries()[0].name
|
||||||
|
if args.archive == "ubuntu":
|
||||||
|
release = f"{release}-proposed"
|
||||||
|
try:
|
||||||
|
(release, pocket) = split_release_pocket(release)
|
||||||
|
except PocketDoesNotExistError as error:
|
||||||
|
Logger.error(error)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
else:
|
||||||
if not options.batch:
|
|
||||||
# Check we have the correct number of arguments.
|
# Check we have the correct number of arguments.
|
||||||
if len(args) < 3:
|
if len(args.packages) < 3:
|
||||||
opt_parser.error("Incorrect number of arguments.")
|
parser.error("Incorrect number of arguments.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
package = str(args[0]).lower()
|
package = str(args.packages[0]).lower()
|
||||||
release = str(args[1]).lower()
|
release = str(args.packages[1]).lower()
|
||||||
op = str(args[2]).lower()
|
operation = str(args.packages[2]).lower()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
opt_parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
archive = launchpad.archives.getByReference(reference=args.archive)
|
||||||
|
try:
|
||||||
|
distroseries = ubuntu.getSeries(name_or_version=release)
|
||||||
|
except lazr.restfulclient.errors.NotFound as error:
|
||||||
|
Logger.error(error)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not args.batch:
|
||||||
# Check our operation.
|
# Check our operation.
|
||||||
if op not in ("rescore", "retry", "status"):
|
if operation not in ("rescore", "retry", "status"):
|
||||||
Logger.error("Invalid operation: %s." % op)
|
Logger.error("Invalid operation: %s.", operation)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# If the user has specified an architecture to build, we only wish to
|
# If the user has specified an architecture to build, we only wish to
|
||||||
# rebuild it and nothing else.
|
# rebuild it and nothing else.
|
||||||
if options.architecture:
|
if args.architecture:
|
||||||
if options.architecture[0] not in valid_archs:
|
if args.architecture[0] not in valid_archs:
|
||||||
Logger.error("Invalid architecture specified: %s."
|
Logger.error("Invalid architecture specified: %s.", args.architecture[0])
|
||||||
% options.architecture[0])
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
one_arch = True
|
one_arch = True
|
||||||
@ -135,148 +210,239 @@ def main():
|
|||||||
Logger.error(error)
|
Logger.error(error)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Get the ubuntu archive
|
|
||||||
try:
|
|
||||||
ubuntu_archive = Distribution('ubuntu').getArchive()
|
|
||||||
# Will fail here if we have no credentials, bail out
|
|
||||||
except IOError:
|
|
||||||
sys.exit(1)
|
|
||||||
# Get list of published sources for package in question.
|
# Get list of published sources for package in question.
|
||||||
try:
|
try:
|
||||||
sources = ubuntu_archive.getSourcePackage(package, release, pocket)
|
sources = archive.getPublishedSources(
|
||||||
distroseries = Distribution('ubuntu').getSeries(release)
|
distro_series=distroseries,
|
||||||
except (SeriesNotFoundException, PackageNotFoundException) as error:
|
exact_match=True,
|
||||||
Logger.error(error)
|
pocket=pocket,
|
||||||
|
source_name=package,
|
||||||
|
status="Published",
|
||||||
|
)[0]
|
||||||
|
except IndexError:
|
||||||
|
Logger.error("No publication found for package %s", package)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
# Get list of builds for that package.
|
# Get list of builds for that package.
|
||||||
builds = sources.getBuilds()
|
builds = sources.getBuilds()
|
||||||
|
|
||||||
# Find out the version and component in given release.
|
# Find out the version and component in given release.
|
||||||
version = sources.getVersion()
|
version = sources.source_package_version
|
||||||
component = sources.getComponent()
|
component = sources.component_name
|
||||||
|
|
||||||
# Operations that are remaining may only be done by Ubuntu developers
|
# Operations that are remaining may only be done by Ubuntu developers
|
||||||
# (retry) or buildd admins (rescore). Check if the proper permissions
|
# (retry) or buildd admins (rescore). Check if the proper permissions
|
||||||
# are in place.
|
# are in place.
|
||||||
me = PersonTeam.me
|
if operation == "retry":
|
||||||
if op == "rescore":
|
necessary_privs = archive.checkUpload(
|
||||||
necessary_privs = me.isLpTeamMember('launchpad-buildd-admins')
|
component=sources.getComponent(),
|
||||||
if op == "retry":
|
distroseries=distroseries,
|
||||||
necessary_privs = me.canUploadPackage(ubuntu_archive, distroseries,
|
person=launchpad.me,
|
||||||
sources.getPackageName(),
|
pocket=pocket,
|
||||||
sources.getComponent(),
|
sourcepackagename=sources.getPackageName(),
|
||||||
pocket=pocket)
|
)
|
||||||
|
if not necessary_privs:
|
||||||
if op in ('rescore', 'retry') and not necessary_privs:
|
Logger.error(
|
||||||
Logger.error("You cannot perform the %s operation on a %s "
|
"You cannot perform the %s operation on a %s package as you"
|
||||||
"package as you do not have the permissions "
|
" do not have the permissions to do this action.",
|
||||||
"to do this action." % (op, component))
|
operation,
|
||||||
|
component,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Output details.
|
# Output details.
|
||||||
Logger.info("The source version for '%s' in %s (%s) is at %s." %
|
Logger.info(
|
||||||
(package, release.capitalize(), component, version))
|
"The source version for '%s' in %s (%s) is at %s.",
|
||||||
|
package,
|
||||||
|
release.capitalize(),
|
||||||
|
component,
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
|
||||||
Logger.info("Current build status for this package:")
|
Logger.info("Current build status for this package:")
|
||||||
|
|
||||||
# Output list of arches for package and their status.
|
# Output list of arches for package and their status.
|
||||||
done = False
|
done = False
|
||||||
for build in builds:
|
for build in builds:
|
||||||
if one_arch and build.arch_tag != options.architecture[0]:
|
if one_arch and build.arch_tag != args.architecture[0]:
|
||||||
# Skip this architecture.
|
# Skip this architecture.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
done = True
|
done = True
|
||||||
Logger.info("%s: %s." % (build.arch_tag, build.buildstate))
|
Logger.info("%s: %s.", build.arch_tag, build.buildstate)
|
||||||
if op == 'rescore':
|
if operation == "rescore":
|
||||||
if build.can_be_rescored:
|
if build.can_be_rescored:
|
||||||
# FIXME: make priority an option
|
# FIXME: make priority an option
|
||||||
priority = 5000
|
priority = 5000
|
||||||
Logger.info('Rescoring build %s to %d...' % (build.arch_tag, priority))
|
Logger.info("Rescoring build %s to %d...", build.arch_tag, priority)
|
||||||
|
try:
|
||||||
build.rescore(score=priority)
|
build.rescore(score=priority)
|
||||||
|
except lazr.restfulclient.errors.Unauthorized:
|
||||||
|
Logger.error(
|
||||||
|
"You don't have the permissions to rescore builds."
|
||||||
|
" Ignoring your rescore request."
|
||||||
|
)
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
Logger.info('Cannot rescore build on %s.' % build.arch_tag)
|
Logger.info("Cannot rescore build on %s.", build.arch_tag)
|
||||||
if op == 'retry':
|
if operation == "retry":
|
||||||
if build.can_be_retried:
|
if build.can_be_retried:
|
||||||
Logger.info('Retrying build on %s...' % build.arch_tag)
|
Logger.info("Retrying build on %s...", build.arch_tag)
|
||||||
build.retry()
|
build.retry()
|
||||||
else:
|
else:
|
||||||
Logger.info('Cannot retry build on %s.' % build.arch_tag)
|
Logger.info("Cannot retry build on %s.", build.arch_tag)
|
||||||
|
|
||||||
# We are done
|
# We are done
|
||||||
if done:
|
if done:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
Logger.info("No builds for '%s' found in the %s release" % (package, release.capitalize()))
|
Logger.info("No builds for '%s' found in the %s release", package, release.capitalize())
|
||||||
Logger.info("It may have been built in a former release.")
|
Logger.info("It may have been built in a former release.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Batch mode
|
# Batch mode
|
||||||
|
|
||||||
if not options.architecture:
|
if not args.architecture:
|
||||||
# no specific architectures specified, assume all valid ones
|
# no specific architectures specified, assume all valid ones
|
||||||
archs = valid_archs
|
archs = valid_archs
|
||||||
else:
|
else:
|
||||||
archs = set(options.architecture)
|
archs = set(args.architecture)
|
||||||
|
|
||||||
# filter out duplicate and invalid architectures
|
# filter out duplicate and invalid architectures
|
||||||
archs.intersection_update(valid_archs)
|
archs.intersection_update(valid_archs)
|
||||||
|
|
||||||
release = options.series
|
if not args.packages:
|
||||||
if not release:
|
retry_count = 0
|
||||||
release = (Distribution('ubuntu').getDevelopmentSeries().name
|
can_rescore = True
|
||||||
+ '-proposed')
|
|
||||||
try:
|
|
||||||
(release, pocket) = split_release_pocket(release)
|
|
||||||
except PocketDoesNotExistError as error:
|
|
||||||
Logger.error(error)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
ubuntu_archive = Distribution('ubuntu').getArchive()
|
if not args.state:
|
||||||
try:
|
if args.retry:
|
||||||
distroseries = Distribution('ubuntu').getSeries(release)
|
args.state = "Failed to build"
|
||||||
except SeriesNotFoundException as error:
|
elif args.priority:
|
||||||
Logger.error(error)
|
args.state = "Needs building"
|
||||||
sys.exit(1)
|
# there is no equivalent to series.getBuildRecords() for a ppa.
|
||||||
me = PersonTeam.me
|
# however, we don't want to have to traverse all build records for
|
||||||
|
# all series when working on the main archive, so we use
|
||||||
|
# series.getBuildRecords() for ubuntu and handle ppas separately
|
||||||
|
series = ubuntu.getSeries(name_or_version=release)
|
||||||
|
if args.archive == "ubuntu":
|
||||||
|
builds = series.getBuildRecords(build_state=args.state, pocket=pocket)
|
||||||
|
else:
|
||||||
|
builds = []
|
||||||
|
for build in archive.getBuildRecords(build_state=args.state, pocket=pocket):
|
||||||
|
if not build.current_source_publication:
|
||||||
|
continue
|
||||||
|
if build.current_source_publication.distro_series == series:
|
||||||
|
builds.append(build)
|
||||||
|
for build in builds:
|
||||||
|
if build.arch_tag not in archs:
|
||||||
|
continue
|
||||||
|
if not build.current_source_publication:
|
||||||
|
continue
|
||||||
|
# fixme: refactor
|
||||||
|
# Check permissions (part 2): check upload permissions for the
|
||||||
|
# source package
|
||||||
|
can_retry = args.retry and archive.checkUpload(
|
||||||
|
component=build.current_source_publication.component_name,
|
||||||
|
distroseries=series,
|
||||||
|
person=launchpad.me,
|
||||||
|
pocket=pocket,
|
||||||
|
sourcepackagename=build.source_package_name,
|
||||||
|
)
|
||||||
|
if args.retry and not can_retry:
|
||||||
|
Logger.error(
|
||||||
|
"You don't have the permissions to retry the build of '%s', skipping.",
|
||||||
|
build.source_package_name,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
Logger.info(
|
||||||
|
"The source version for '%s' in '%s' (%s) is: %s",
|
||||||
|
build.source_package_name,
|
||||||
|
release,
|
||||||
|
pocket,
|
||||||
|
build.source_package_version,
|
||||||
|
)
|
||||||
|
|
||||||
# Check permisions (part 1): Rescoring can only be done by buildd admins
|
if args.retry and build.can_be_retried:
|
||||||
can_rescore = ((options.priority
|
Logger.info(
|
||||||
and me.isLpTeamMember('launchpad-buildd-admins'))
|
"Retrying build of %s on %s...", build.source_package_name, build.arch_tag
|
||||||
or False)
|
)
|
||||||
if options.priority and not can_rescore:
|
|
||||||
Logger.error("You don't have the permissions to rescore "
|
|
||||||
"builds. Ignoring your rescore request.")
|
|
||||||
|
|
||||||
for pkg in args:
|
|
||||||
try:
|
try:
|
||||||
pkg = ubuntu_archive.getSourcePackage(pkg, release, pocket)
|
build.retry()
|
||||||
except PackageNotFoundException as error:
|
retry_count += 1
|
||||||
Logger.error(error)
|
except lazr.restfulclient.errors.BadRequest:
|
||||||
|
Logger.info(
|
||||||
|
"Failed to retry build of %s on %s",
|
||||||
|
build.source_package_name,
|
||||||
|
build.arch_tag,
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.priority and can_rescore:
|
||||||
|
if build.can_be_rescored:
|
||||||
|
try:
|
||||||
|
build.rescore(score=args.priority)
|
||||||
|
except lazr.restfulclient.errors.Unauthorized:
|
||||||
|
Logger.error(
|
||||||
|
"You don't have the permissions to rescore builds."
|
||||||
|
" Ignoring your rescore request."
|
||||||
|
)
|
||||||
|
can_rescore = False
|
||||||
|
except lazr.restfulclient.errors.BadRequest:
|
||||||
|
Logger.info(
|
||||||
|
"Cannot rescore build of %s on %s.",
|
||||||
|
build.source_package_name,
|
||||||
|
build.arch_tag,
|
||||||
|
)
|
||||||
|
|
||||||
|
Logger.info("")
|
||||||
|
if args.retry:
|
||||||
|
Logger.info("%d package builds retried", retry_count)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
for pkg in args.packages:
|
||||||
|
try:
|
||||||
|
pkg = archive.getPublishedSources(
|
||||||
|
distro_series=distroseries,
|
||||||
|
exact_match=True,
|
||||||
|
pocket=pocket,
|
||||||
|
source_name=pkg,
|
||||||
|
status="Published",
|
||||||
|
)[0]
|
||||||
|
except IndexError:
|
||||||
|
Logger.error("No publication found for package %s", pkg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check permissions (part 2): check upload permissions for the source
|
# Check permissions (part 2): check upload permissions for the source
|
||||||
# package
|
# package
|
||||||
can_retry = options.retry and me.canUploadPackage(ubuntu_archive,
|
can_retry = args.retry and archive.checkUpload(
|
||||||
distroseries,
|
component=pkg.component_name,
|
||||||
pkg.getPackageName(),
|
distroseries=distroseries,
|
||||||
pkg.getComponent())
|
person=launchpad.me,
|
||||||
if options.retry and not can_retry:
|
pocket=pocket,
|
||||||
Logger.error("You don't have the permissions to retry the "
|
sourcepackagename=pkg.source_package_name,
|
||||||
"build of '%s'. Ignoring your request."
|
)
|
||||||
% pkg.getPackageName())
|
if args.retry and not can_retry:
|
||||||
|
Logger.error(
|
||||||
|
"You don't have the permissions to retry the "
|
||||||
|
"build of '%s'. Ignoring your request.",
|
||||||
|
pkg.source_package_name,
|
||||||
|
)
|
||||||
|
|
||||||
Logger.info("The source version for '%s' in '%s' (%s) is: %s" %
|
Logger.info(
|
||||||
(pkg.getPackageName(), release, pocket, pkg.getVersion()))
|
"The source version for '%s' in '%s' (%s) is: %s",
|
||||||
|
pkg.source_package_name,
|
||||||
|
release,
|
||||||
|
pocket,
|
||||||
|
pkg.source_package_version,
|
||||||
|
)
|
||||||
|
|
||||||
Logger.info(pkg.getBuildStates(archs))
|
Logger.info(get_build_states(pkg, archs))
|
||||||
if can_retry:
|
if can_retry:
|
||||||
Logger.info(pkg.retryBuilds(archs))
|
Logger.info(retry_builds(pkg, archs))
|
||||||
if options.priority and can_rescore:
|
if args.priority:
|
||||||
Logger.info(pkg.rescoreBuilds(archs, options.priority))
|
Logger.info(rescore_builds(pkg, archs, args.priority))
|
||||||
|
|
||||||
Logger.info('')
|
Logger.info("")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
36
ubuntu-iso
36
ubuntu-iso
@ -20,19 +20,23 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
from ubuntutools import getLogger
|
||||||
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def extract(iso, path):
|
def extract(iso, path):
|
||||||
command = ['isoinfo', '-R', '-i', iso, '-x', path]
|
command = ["isoinfo", "-R", "-i", iso, "-x", path]
|
||||||
pipe = subprocess.run(command, encoding='utf-8',
|
pipe = subprocess.run(
|
||||||
stdout=subprocess.PIPE,
|
command, check=False, encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
stderr=subprocess.PIPE)
|
)
|
||||||
|
|
||||||
if pipe.returncode != 0:
|
if pipe.returncode != 0:
|
||||||
sys.stderr.write(pipe.stderr)
|
sys.stderr.write(pipe.stderr)
|
||||||
@ -42,22 +46,22 @@ def extract(iso, path):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
desc = 'Given an ISO, %prog will display the Ubuntu version information'
|
desc = "Given an ISO, %(prog)s will display the Ubuntu version information"
|
||||||
parser = optparse.OptionParser(usage='%prog [options] iso...',
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] iso...", description=desc)
|
||||||
description=desc)
|
parser.add_argument("isos", nargs="*", help=argparse.SUPPRESS)
|
||||||
isos = parser.parse_args()[1]
|
args = parser.parse_args()
|
||||||
err = False
|
err = False
|
||||||
|
|
||||||
for iso in isos:
|
for iso in args.isos:
|
||||||
if len(isos) > 1:
|
if len(args.isos) > 1:
|
||||||
prefix = '%s:' % iso
|
prefix = f"{iso}:"
|
||||||
else:
|
else:
|
||||||
prefix = ''
|
prefix = ""
|
||||||
|
|
||||||
version = extract(iso, '/.disk/info')
|
version = extract(iso, "/.disk/info")
|
||||||
|
|
||||||
if len(version) == 0:
|
if len(version) == 0:
|
||||||
Logger.error('%s does not appear to be an Ubuntu ISO' % iso)
|
Logger.error("%s does not appear to be an Ubuntu ISO", iso)
|
||||||
err = True
|
err = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -67,6 +71,6 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -14,131 +14,159 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam,
|
from ubuntutools import getLogger
|
||||||
Packageset, PackageNotFoundException,
|
from ubuntutools.lp.lpapicache import (
|
||||||
SeriesNotFoundException)
|
Distribution,
|
||||||
|
Launchpad,
|
||||||
|
PackageNotFoundException,
|
||||||
|
Packageset,
|
||||||
|
PersonTeam,
|
||||||
|
SeriesNotFoundException,
|
||||||
|
)
|
||||||
from ubuntutools.misc import split_release_pocket
|
from ubuntutools.misc import split_release_pocket
|
||||||
|
|
||||||
from ubuntutools import getLogger
|
|
||||||
Logger = getLogger()
|
Logger = getLogger()
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
def parse_arguments():
|
||||||
'''Parse arguments and return (options, package)'''
|
"""Parse arguments and return (options, package)"""
|
||||||
parser = optparse.OptionParser('%prog [options] package')
|
parser = argparse.ArgumentParser(usage="%(prog)s [options] package")
|
||||||
parser.add_option('-r', '--release', default=None, metavar='RELEASE',
|
parser.add_argument(
|
||||||
help='Use RELEASE, rather than the current development '
|
"-r",
|
||||||
'release')
|
"--release",
|
||||||
parser.add_option('-a', '--list-uploaders',
|
metavar="RELEASE",
|
||||||
default=False, action='store_true',
|
help="Use RELEASE, rather than the current development release",
|
||||||
help='List all the people/teams with upload rights')
|
)
|
||||||
parser.add_option('-t', '--list-team-members',
|
parser.add_argument(
|
||||||
default=False, action='store_true',
|
"-a",
|
||||||
help='List all team members of teams with upload rights '
|
"--list-uploaders",
|
||||||
'(implies --list-uploaders)')
|
action="store_true",
|
||||||
options, args = parser.parse_args()
|
help="List all the people/teams with upload rights",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--list-team-members",
|
||||||
|
action="store_true",
|
||||||
|
help="List all team members of teams with upload rights (implies --list-uploaders)",
|
||||||
|
)
|
||||||
|
parser.add_argument("package", help=argparse.SUPPRESS)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
if len(args) != 1:
|
if args.list_team_members:
|
||||||
parser.error("One (and only one) package must be specified")
|
args.list_uploaders = True
|
||||||
package = args[0]
|
|
||||||
|
|
||||||
if options.list_team_members:
|
return args
|
||||||
options.list_uploaders = True
|
|
||||||
|
|
||||||
return (options, package)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
'''Query upload permissions'''
|
"""Query upload permissions"""
|
||||||
options, package = parse_arguments()
|
args = parse_arguments()
|
||||||
# Need to be logged in to see uploaders:
|
# Need to be logged in to see uploaders:
|
||||||
Launchpad.login()
|
Launchpad.login()
|
||||||
|
|
||||||
ubuntu = Distribution('ubuntu')
|
ubuntu = Distribution("ubuntu")
|
||||||
archive = ubuntu.getArchive()
|
archive = ubuntu.getArchive()
|
||||||
if options.release is None:
|
if args.release is None:
|
||||||
options.release = ubuntu.getDevelopmentSeries().name
|
args.release = ubuntu.getDevelopmentSeries().name
|
||||||
try:
|
try:
|
||||||
release, pocket = split_release_pocket(options.release)
|
release, pocket = split_release_pocket(args.release)
|
||||||
series = ubuntu.getSeries(release)
|
series = ubuntu.getSeries(release)
|
||||||
except SeriesNotFoundException as e:
|
except SeriesNotFoundException as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
spph = archive.getSourcePackage(package)
|
spph = archive.getSourcePackage(args.package)
|
||||||
except PackageNotFoundException as e:
|
except PackageNotFoundException as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
component = spph.getComponent()
|
component = spph.getComponent()
|
||||||
if (options.list_uploaders and (pocket != 'Release' or series.status in
|
if args.list_uploaders and (
|
||||||
('Experimental', 'Active Development', 'Pre-release Freeze'))):
|
pocket != "Release"
|
||||||
|
or series.status in ("Experimental", "Active Development", "Pre-release Freeze")
|
||||||
component_uploader = archive.getUploadersForComponent(
|
):
|
||||||
component_name=component)[0]
|
component_uploader = archive.getUploadersForComponent(component_name=component)[0]
|
||||||
Logger.info("All upload permissions for %s:" % package)
|
Logger.info("All upload permissions for %s:", args.package)
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
Logger.info("Component (%s)" % component)
|
Logger.info("Component (%s)", component)
|
||||||
Logger.info("============" + ("=" * len(component)))
|
Logger.info("============%s", "=" * len(component))
|
||||||
print_uploaders([component_uploader], options.list_team_members)
|
print_uploaders([component_uploader], args.list_team_members)
|
||||||
|
|
||||||
packagesets = sorted(Packageset.setsIncludingSource(
|
packagesets = sorted(
|
||||||
distroseries=series,
|
Packageset.setsIncludingSource(distroseries=series, sourcepackagename=args.package),
|
||||||
sourcepackagename=package), key=lambda p: p.name)
|
key=lambda p: p.name,
|
||||||
|
)
|
||||||
if packagesets:
|
if packagesets:
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
Logger.info("Packagesets")
|
Logger.info("Packagesets")
|
||||||
Logger.info("===========")
|
Logger.info("===========")
|
||||||
for packageset in packagesets:
|
for packageset in packagesets:
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
Logger.info("%s:" % packageset.name)
|
Logger.info("%s:", packageset.name)
|
||||||
print_uploaders(archive.getUploadersForPackageset(
|
print_uploaders(
|
||||||
packageset=packageset), options.list_team_members)
|
archive.getUploadersForPackageset(packageset=packageset),
|
||||||
|
args.list_team_members,
|
||||||
|
)
|
||||||
|
|
||||||
ppu_uploaders = archive.getUploadersForPackage(
|
ppu_uploaders = archive.getUploadersForPackage(source_package_name=args.package)
|
||||||
source_package_name=package)
|
|
||||||
if ppu_uploaders:
|
if ppu_uploaders:
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
Logger.info("Per-Package-Uploaders")
|
Logger.info("Per-Package-Uploaders")
|
||||||
Logger.info("=====================")
|
Logger.info("=====================")
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
print_uploaders(ppu_uploaders, options.list_team_members)
|
print_uploaders(ppu_uploaders, args.list_team_members)
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
|
|
||||||
if PersonTeam.me.canUploadPackage(archive, series, package, component,
|
if PersonTeam.me.canUploadPackage(archive, series, args.package, component, pocket):
|
||||||
pocket):
|
Logger.info("You can upload %s to %s.", args.package, args.release)
|
||||||
Logger.info("You can upload %s to %s." % (package, options.release))
|
|
||||||
else:
|
else:
|
||||||
Logger.info("You can not upload %s to %s, yourself." % (package, options.release))
|
Logger.info("You can not upload %s to %s, yourself.", args.package, args.release)
|
||||||
if (series.status in ('Current Stable Release', 'Supported', 'Obsolete')
|
if (
|
||||||
and pocket == 'Release'):
|
series.status in ("Current Stable Release", "Supported", "Obsolete")
|
||||||
Logger.info("%s is in the '%s' state. You may want to query the %s-proposed pocket." %
|
and pocket == "Release"
|
||||||
(release, series.status, release))
|
):
|
||||||
|
Logger.info(
|
||||||
|
"%s is in the '%s' state. You may want to query the %s-proposed pocket.",
|
||||||
|
release,
|
||||||
|
series.status,
|
||||||
|
release,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
Logger.info("But you can still contribute to it via the sponsorship "
|
Logger.info(
|
||||||
"process: https://wiki.ubuntu.com/SponsorshipProcess")
|
"But you can still contribute to it via the sponsorship "
|
||||||
if not options.list_uploaders:
|
"process: https://wiki.ubuntu.com/SponsorshipProcess"
|
||||||
Logger.info("To see who has the necessary upload rights, "
|
)
|
||||||
"use the --list-uploaders option.")
|
if not args.list_uploaders:
|
||||||
|
Logger.info(
|
||||||
|
"To see who has the necessary upload rights, "
|
||||||
|
"use the --list-uploaders option."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def print_uploaders(uploaders, expand_teams=False, prefix=''):
|
def print_uploaders(uploaders, expand_teams=False, prefix=""):
|
||||||
"""Given a list of uploaders, pretty-print them all
|
"""Given a list of uploaders, pretty-print them all
|
||||||
Each line is prefixed with prefix.
|
Each line is prefixed with prefix.
|
||||||
If expand_teams is set, recurse, adding more spaces to prefix on each
|
If expand_teams is set, recurse, adding more spaces to prefix on each
|
||||||
recursion.
|
recursion.
|
||||||
"""
|
"""
|
||||||
for uploader in sorted(uploaders, key=lambda p: p.display_name):
|
for uploader in sorted(uploaders, key=lambda p: p.display_name):
|
||||||
Logger.info("%s* %s (%s)%s" %
|
Logger.info(
|
||||||
(prefix, uploader.display_name, uploader.name,
|
"%s* %s (%s)%s",
|
||||||
' [team]' if uploader.is_team else ''))
|
prefix,
|
||||||
|
uploader.display_name,
|
||||||
|
uploader.name,
|
||||||
|
" [team]" if uploader.is_team else "",
|
||||||
|
)
|
||||||
if expand_teams and uploader.is_team:
|
if expand_teams and uploader.is_team:
|
||||||
print_uploaders(uploader.participants, True, prefix=prefix + ' ')
|
print_uploaders(uploader.participants, True, prefix=prefix + " ")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -7,8 +7,8 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def getLogger():
|
def getLogger(): # pylint: disable=invalid-name
|
||||||
''' Get the logger instance for this module
|
"""Get the logger instance for this module
|
||||||
|
|
||||||
Quick guide for using this or not: if you want to call ubuntutools
|
Quick guide for using this or not: if you want to call ubuntutools
|
||||||
module code and have its output print to stdout/stderr ONLY, you can
|
module code and have its output print to stdout/stderr ONLY, you can
|
||||||
@ -33,12 +33,12 @@ def getLogger():
|
|||||||
This should only be used by runnable scripts provided by the
|
This should only be used by runnable scripts provided by the
|
||||||
ubuntu-dev-tools package, or other runnable scripts that want the behavior
|
ubuntu-dev-tools package, or other runnable scripts that want the behavior
|
||||||
described above.
|
described above.
|
||||||
'''
|
"""
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
|
|
||||||
fmt = logging.Formatter('%(message)s')
|
fmt = logging.Formatter("%(message)s")
|
||||||
|
|
||||||
stdout_handler = logging.StreamHandler(stream=sys.stdout)
|
stdout_handler = logging.StreamHandler(stream=sys.stdout)
|
||||||
stdout_handler.setFormatter(fmt)
|
stdout_handler.setFormatter(fmt)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,10 +18,10 @@
|
|||||||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -31,20 +31,21 @@ def _build_preparation(result_directory):
|
|||||||
os.makedirs(result_directory)
|
os.makedirs(result_directory)
|
||||||
|
|
||||||
|
|
||||||
class Builder(object):
|
class Builder:
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
cmd = ["dpkg-architecture", "-qDEB_BUILD_ARCH_CPU"]
|
cmd = ["dpkg-architecture", "-qDEB_BUILD_ARCH_CPU"]
|
||||||
self.architecture = subprocess.check_output(cmd, encoding='utf-8').strip()
|
self.architecture = subprocess.check_output(cmd, encoding="utf-8").strip()
|
||||||
|
|
||||||
def _build_failure(self, returncode, dsc_file):
|
def _build_failure(self, returncode, dsc_file):
|
||||||
if returncode != 0:
|
if returncode != 0:
|
||||||
Logger.error("Failed to build %s from source with %s." %
|
Logger.error(
|
||||||
(os.path.basename(dsc_file), self.name))
|
"Failed to build %s from source with %s.", os.path.basename(dsc_file), self.name
|
||||||
|
)
|
||||||
return returncode
|
return returncode
|
||||||
|
|
||||||
def exists_in_path(self):
|
def exists_in_path(self):
|
||||||
for path in os.environ.get('PATH', os.defpath).split(os.pathsep):
|
for path in os.environ.get("PATH", os.defpath).split(os.pathsep):
|
||||||
if os.path.isfile(os.path.join(path, self.name)):
|
if os.path.isfile(os.path.join(path, self.name)):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -57,8 +58,7 @@ class Builder(object):
|
|||||||
|
|
||||||
def _update_failure(self, returncode, dist):
|
def _update_failure(self, returncode, dist):
|
||||||
if returncode != 0:
|
if returncode != 0:
|
||||||
Logger.error("Failed to update %s chroot for %s." %
|
Logger.error("Failed to update %s chroot for %s.", dist, self.name)
|
||||||
(dist, self.name))
|
|
||||||
return returncode
|
return returncode
|
||||||
|
|
||||||
|
|
||||||
@ -68,19 +68,39 @@ class Pbuilder(Builder):
|
|||||||
|
|
||||||
def build(self, dsc_file, dist, result_directory):
|
def build(self, dsc_file, dist, result_directory):
|
||||||
_build_preparation(result_directory)
|
_build_preparation(result_directory)
|
||||||
cmd = ["sudo", "-E", "ARCH=" + self.architecture, "DIST=" + dist,
|
cmd = [
|
||||||
self.name, "--build",
|
"sudo",
|
||||||
"--architecture", self.architecture, "--distribution", dist,
|
"-E",
|
||||||
"--buildresult", result_directory, dsc_file]
|
f"ARCH={self.architecture}",
|
||||||
Logger.debug(' '.join(cmd))
|
f"DIST={dist}",
|
||||||
|
self.name,
|
||||||
|
"--build",
|
||||||
|
"--architecture",
|
||||||
|
self.architecture,
|
||||||
|
"--distribution",
|
||||||
|
dist,
|
||||||
|
"--buildresult",
|
||||||
|
result_directory,
|
||||||
|
dsc_file,
|
||||||
|
]
|
||||||
|
Logger.debug(" ".join(cmd))
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
return self._build_failure(returncode, dsc_file)
|
return self._build_failure(returncode, dsc_file)
|
||||||
|
|
||||||
def update(self, dist):
|
def update(self, dist):
|
||||||
cmd = ["sudo", "-E", "ARCH=" + self.architecture, "DIST=" + dist,
|
cmd = [
|
||||||
self.name, "--update",
|
"sudo",
|
||||||
"--architecture", self.architecture, "--distribution", dist]
|
"-E",
|
||||||
Logger.debug(' '.join(cmd))
|
f"ARCH={self.architecture}",
|
||||||
|
f"DIST={dist}",
|
||||||
|
self.name,
|
||||||
|
"--update",
|
||||||
|
"--architecture",
|
||||||
|
self.architecture,
|
||||||
|
"--distribution",
|
||||||
|
dist,
|
||||||
|
]
|
||||||
|
Logger.debug(" ".join(cmd))
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
return self._update_failure(returncode, dist)
|
return self._update_failure(returncode, dist)
|
||||||
|
|
||||||
@ -91,15 +111,22 @@ class Pbuilderdist(Builder):
|
|||||||
|
|
||||||
def build(self, dsc_file, dist, result_directory):
|
def build(self, dsc_file, dist, result_directory):
|
||||||
_build_preparation(result_directory)
|
_build_preparation(result_directory)
|
||||||
cmd = [self.name, dist, self.architecture,
|
cmd = [
|
||||||
"build", dsc_file, "--buildresult", result_directory]
|
self.name,
|
||||||
Logger.debug(' '.join(cmd))
|
dist,
|
||||||
|
self.architecture,
|
||||||
|
"build",
|
||||||
|
dsc_file,
|
||||||
|
"--buildresult",
|
||||||
|
result_directory,
|
||||||
|
]
|
||||||
|
Logger.debug(" ".join(cmd))
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
return self._build_failure(returncode, dsc_file)
|
return self._build_failure(returncode, dsc_file)
|
||||||
|
|
||||||
def update(self, dist):
|
def update(self, dist):
|
||||||
cmd = [self.name, dist, self.architecture, "update"]
|
cmd = [self.name, dist, self.architecture, "update"]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
return self._update_failure(returncode, dist)
|
return self._update_failure(returncode, dist)
|
||||||
|
|
||||||
@ -111,41 +138,40 @@ class Sbuild(Builder):
|
|||||||
def build(self, dsc_file, dist, result_directory):
|
def build(self, dsc_file, dist, result_directory):
|
||||||
_build_preparation(result_directory)
|
_build_preparation(result_directory)
|
||||||
workdir = os.getcwd()
|
workdir = os.getcwd()
|
||||||
Logger.debug("cd " + result_directory)
|
Logger.debug("cd %s", result_directory)
|
||||||
os.chdir(result_directory)
|
os.chdir(result_directory)
|
||||||
cmd = ["sbuild", "--arch-all", "--dist=" + dist,
|
cmd = ["sbuild", "--arch-all", f"--dist={dist}", f"--arch={self.architecture}", dsc_file]
|
||||||
"--arch=" + self.architecture, dsc_file]
|
Logger.debug(" ".join(cmd))
|
||||||
Logger.debug(' '.join(cmd))
|
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
Logger.debug("cd " + workdir)
|
Logger.debug("cd %s", workdir)
|
||||||
os.chdir(workdir)
|
os.chdir(workdir)
|
||||||
return self._build_failure(returncode, dsc_file)
|
return self._build_failure(returncode, dsc_file)
|
||||||
|
|
||||||
def update(self, dist):
|
def update(self, dist):
|
||||||
cmd = ["schroot", "--list"]
|
cmd = ["schroot", "--list"]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
process = subprocess.run(cmd, stdout=subprocess.PIPE, encoding='utf-8')
|
process = subprocess.run(cmd, check=False, stdout=subprocess.PIPE, encoding="utf-8")
|
||||||
chroots, _ = process.stdout.strip().split()
|
chroots, _ = process.stdout.strip().split()
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
return process.returncode
|
return process.returncode
|
||||||
|
|
||||||
params = {"dist": dist, "arch": self.architecture}
|
params = {"dist": dist, "arch": self.architecture}
|
||||||
for chroot in ("%(dist)s-%(arch)s-sbuild-source",
|
for chroot in (
|
||||||
|
"%(dist)s-%(arch)s-sbuild-source",
|
||||||
"%(dist)s-sbuild-source",
|
"%(dist)s-sbuild-source",
|
||||||
"%(dist)s-%(arch)s-source",
|
"%(dist)s-%(arch)s-source",
|
||||||
"%(dist)s-source"):
|
"%(dist)s-source",
|
||||||
|
):
|
||||||
chroot = chroot % params
|
chroot = chroot % params
|
||||||
if chroot in chroots:
|
if chroot in chroots:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
commands = [["sbuild-update"],
|
commands = [["sbuild-update"], ["sbuild-distupgrade"], ["sbuild-clean", "-a", "-c"]]
|
||||||
["sbuild-distupgrade"],
|
|
||||||
["sbuild-clean", "-a", "-c"]]
|
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
# pylint: disable=W0631
|
# pylint: disable=W0631
|
||||||
Logger.debug(' '.join(cmd) + " " + chroot)
|
Logger.debug("%s %s", " ".join(cmd), chroot)
|
||||||
ret = subprocess.call(cmd + [chroot])
|
ret = subprocess.call(cmd + [chroot])
|
||||||
# pylint: enable=W0631
|
# pylint: enable=W0631
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
@ -156,9 +182,9 @@ class Sbuild(Builder):
|
|||||||
_SUPPORTED_BUILDERS = {
|
_SUPPORTED_BUILDERS = {
|
||||||
"cowbuilder": lambda: Pbuilder("cowbuilder"),
|
"cowbuilder": lambda: Pbuilder("cowbuilder"),
|
||||||
"cowbuilder-dist": lambda: Pbuilderdist("cowbuilder-dist"),
|
"cowbuilder-dist": lambda: Pbuilderdist("cowbuilder-dist"),
|
||||||
"pbuilder": lambda: Pbuilder(),
|
"pbuilder": Pbuilder,
|
||||||
"pbuilder-dist": lambda: Pbuilderdist(),
|
"pbuilder-dist": Pbuilderdist,
|
||||||
"sbuild": lambda: Sbuild(),
|
"sbuild": Sbuild,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -170,5 +196,5 @@ def get_builder(name):
|
|||||||
Logger.error("Builder doesn't appear to be installed: %s", name)
|
Logger.error("Builder doesn't appear to be installed: %s", name)
|
||||||
else:
|
else:
|
||||||
Logger.error("Unsupported builder specified: %s.", name)
|
Logger.error("Unsupported builder specified: %s.", name)
|
||||||
Logger.error("Supported builders: %s",
|
Logger.error("Supported builders: %s", ", ".join(sorted(_SUPPORTED_BUILDERS.keys())))
|
||||||
", ".join(sorted(_SUPPORTED_BUILDERS.keys())))
|
return None
|
||||||
|
@ -15,39 +15,39 @@
|
|||||||
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import locale
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import locale
|
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class UDTConfig(object):
|
class UDTConfig:
|
||||||
"""Ubuntu Dev Tools configuration file (devscripts config file) and
|
"""Ubuntu Dev Tools configuration file (devscripts config file) and
|
||||||
environment variable parsing.
|
environment variable parsing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
no_conf = False
|
no_conf = False
|
||||||
# Package wide configuration variables.
|
# Package wide configuration variables.
|
||||||
# These are reqired to be used by at least two scripts.
|
# These are reqired to be used by at least two scripts.
|
||||||
defaults = {
|
defaults = {
|
||||||
'BUILDER': 'pbuilder',
|
"BUILDER": "pbuilder",
|
||||||
'DEBIAN_MIRROR': 'http://deb.debian.org/debian',
|
"DEBIAN_MIRROR": "http://deb.debian.org/debian",
|
||||||
'DEBSEC_MIRROR': 'http://security.debian.org',
|
"DEBSEC_MIRROR": "http://security.debian.org",
|
||||||
'DEBIAN_DDEBS_MIRROR': 'http://debug.mirrors.debian.org/debian-debug',
|
"DEBIAN_DDEBS_MIRROR": "http://debug.mirrors.debian.org/debian-debug",
|
||||||
'LPINSTANCE': 'production',
|
"LPINSTANCE": "production",
|
||||||
'MIRROR_FALLBACK': True,
|
"MIRROR_FALLBACK": True,
|
||||||
'UBUNTU_MIRROR': 'http://archive.ubuntu.com/ubuntu',
|
"UBUNTU_MIRROR": "http://archive.ubuntu.com/ubuntu",
|
||||||
'UBUNTU_PORTS_MIRROR': 'http://ports.ubuntu.com',
|
"UBUNTU_PORTS_MIRROR": "http://ports.ubuntu.com",
|
||||||
'UBUNTU_INTERNAL_MIRROR': 'http://ftpmaster.internal/ubuntu',
|
"UBUNTU_DDEBS_MIRROR": "http://ddebs.ubuntu.com",
|
||||||
'UBUNTU_DDEBS_MIRROR': 'http://ddebs.ubuntu.com',
|
"UPDATE_BUILDER": False,
|
||||||
'UPDATE_BUILDER': False,
|
"WORKDIR": None,
|
||||||
'WORKDIR': None,
|
"KEYID": None,
|
||||||
'KEYID': None,
|
|
||||||
}
|
}
|
||||||
# Populated from the configuration files:
|
# Populated from the configuration files:
|
||||||
config = {}
|
config = {}
|
||||||
@ -55,30 +55,32 @@ class UDTConfig(object):
|
|||||||
def __init__(self, no_conf=False, prefix=None):
|
def __init__(self, no_conf=False, prefix=None):
|
||||||
self.no_conf = no_conf
|
self.no_conf = no_conf
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
prefix = os.path.basename(sys.argv[0]).upper().replace('-', '_')
|
prefix = os.path.basename(sys.argv[0]).upper().replace("-", "_")
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
if not no_conf:
|
if not no_conf:
|
||||||
self.config = self.parse_devscripts_config()
|
self.config = self.parse_devscripts_config()
|
||||||
|
|
||||||
def parse_devscripts_config(self):
|
@staticmethod
|
||||||
|
def parse_devscripts_config():
|
||||||
"""Read the devscripts configuration files, and return the values as a
|
"""Read the devscripts configuration files, and return the values as a
|
||||||
dictionary
|
dictionary
|
||||||
"""
|
"""
|
||||||
config = {}
|
config = {}
|
||||||
for filename in ('/etc/devscripts.conf', '~/.devscripts'):
|
for filename in ("/etc/devscripts.conf", "~/.devscripts"):
|
||||||
try:
|
try:
|
||||||
f = open(os.path.expanduser(filename), 'r')
|
with open(os.path.expanduser(filename), "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
except IOError:
|
except IOError:
|
||||||
continue
|
continue
|
||||||
for line in f:
|
try:
|
||||||
parsed = shlex.split(line, comments=True)
|
tokens = shlex.split(content, comments=True)
|
||||||
if len(parsed) > 1:
|
except ValueError as e:
|
||||||
Logger.warning('Cannot parse variable assignment in %s: %s',
|
Logger.error("Error parsing %s: %s", filename, e)
|
||||||
getattr(f, 'name', '<config>'), line)
|
continue
|
||||||
if len(parsed) >= 1 and '=' in parsed[0]:
|
for token in tokens:
|
||||||
key, value = parsed[0].split('=', 1)
|
if "=" in token:
|
||||||
|
key, value = token.split("=", 1)
|
||||||
config[key] = value
|
config[key] = value
|
||||||
f.close()
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_value(self, key, default=None, boolean=False, compat_keys=()):
|
def get_value(self, key, default=None, boolean=False, compat_keys=()):
|
||||||
@ -95,9 +97,9 @@ class UDTConfig(object):
|
|||||||
if default is None and key in self.defaults:
|
if default is None and key in self.defaults:
|
||||||
default = self.defaults[key]
|
default = self.defaults[key]
|
||||||
|
|
||||||
keys = [self.prefix + '_' + key]
|
keys = [f"{self.prefix}_{key}"]
|
||||||
if key in self.defaults:
|
if key in self.defaults:
|
||||||
keys.append('UBUNTUTOOLS_' + key)
|
keys.append(f"UBUNTUTOOLS_{key}")
|
||||||
keys += compat_keys
|
keys += compat_keys
|
||||||
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
@ -105,16 +107,19 @@ class UDTConfig(object):
|
|||||||
if k in store:
|
if k in store:
|
||||||
value = store[k]
|
value = store[k]
|
||||||
if boolean:
|
if boolean:
|
||||||
if value in ('yes', 'no'):
|
if value in ("yes", "no"):
|
||||||
value = value == 'yes'
|
value = value == "yes"
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
if k in compat_keys:
|
if k in compat_keys:
|
||||||
replacements = self.prefix + '_' + key
|
replacements = f"{self.prefix}_{key}"
|
||||||
if key in self.defaults:
|
if key in self.defaults:
|
||||||
replacements += 'or UBUNTUTOOLS_' + key
|
replacements += f"or UBUNTUTOOLS_{key}"
|
||||||
Logger.warning('Using deprecated configuration variable %s. '
|
Logger.warning(
|
||||||
'You should use %s.', k, replacements)
|
"Using deprecated configuration variable %s. You should use %s.",
|
||||||
|
k,
|
||||||
|
replacements,
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
return default
|
return default
|
||||||
|
|
||||||
@ -132,7 +137,7 @@ def ubu_email(name=None, email=None, export=True):
|
|||||||
|
|
||||||
Return name, email.
|
Return name, email.
|
||||||
"""
|
"""
|
||||||
name_email_re = re.compile(r'^\s*(.+?)\s*<(.+@.+)>\s*$')
|
name_email_re = re.compile(r"^\s*(.+?)\s*<(.+@.+)>\s*$")
|
||||||
|
|
||||||
if email:
|
if email:
|
||||||
match = name_email_re.match(email)
|
match = name_email_re.match(email)
|
||||||
@ -140,11 +145,16 @@ def ubu_email(name=None, email=None, export=True):
|
|||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
email = match.group(2)
|
email = match.group(2)
|
||||||
|
|
||||||
if export and not name and not email and 'UBUMAIL' not in os.environ:
|
if export and not name and not email and "UBUMAIL" not in os.environ:
|
||||||
export = False
|
export = False
|
||||||
|
|
||||||
for var, target in (('UBUMAIL', 'email'), ('DEBFULLNAME', 'name'), ('DEBEMAIL', 'email'),
|
for var, target in (
|
||||||
('EMAIL', 'email'), ('NAME', 'name')):
|
("UBUMAIL", "email"),
|
||||||
|
("DEBFULLNAME", "name"),
|
||||||
|
("DEBEMAIL", "email"),
|
||||||
|
("EMAIL", "email"),
|
||||||
|
("NAME", "name"),
|
||||||
|
):
|
||||||
if name and email:
|
if name and email:
|
||||||
break
|
break
|
||||||
if var in os.environ:
|
if var in os.environ:
|
||||||
@ -154,30 +164,30 @@ def ubu_email(name=None, email=None, export=True):
|
|||||||
name = match.group(1)
|
name = match.group(1)
|
||||||
if not email:
|
if not email:
|
||||||
email = match.group(2)
|
email = match.group(2)
|
||||||
elif target == 'name' and not name:
|
elif target == "name" and not name:
|
||||||
name = os.environ[var].strip()
|
name = os.environ[var].strip()
|
||||||
elif target == 'email' and not email:
|
elif target == "email" and not email:
|
||||||
email = os.environ[var].strip()
|
email = os.environ[var].strip()
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
gecos_name = pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0].strip()
|
gecos_name = pwd.getpwuid(os.getuid()).pw_gecos.split(",")[0].strip()
|
||||||
if gecos_name:
|
if gecos_name:
|
||||||
name = gecos_name
|
name = gecos_name
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
mailname = socket.getfqdn()
|
mailname = socket.getfqdn()
|
||||||
if os.path.isfile('/etc/mailname'):
|
if os.path.isfile("/etc/mailname"):
|
||||||
mailname = open('/etc/mailname', 'r').read().strip()
|
mailname = open("/etc/mailname", "r", encoding="utf-8").read().strip()
|
||||||
email = pwd.getpwuid(os.getuid()).pw_name + '@' + mailname
|
email = f"{pwd.getpwuid(os.getuid()).pw_name}@{mailname}"
|
||||||
|
|
||||||
if export:
|
if export:
|
||||||
os.environ['DEBFULLNAME'] = name
|
os.environ["DEBFULLNAME"] = name
|
||||||
os.environ['DEBEMAIL'] = email
|
os.environ["DEBEMAIL"] = email
|
||||||
|
|
||||||
# decode env var or gecos raw string with the current locale's encoding
|
# decode env var or gecos raw string with the current locale's encoding
|
||||||
encoding = locale.getdefaultlocale()[1]
|
encoding = locale.getlocale()[1]
|
||||||
if not encoding:
|
if not encoding:
|
||||||
encoding = 'utf-8'
|
encoding = "utf-8"
|
||||||
if name and isinstance(name, bytes):
|
if name and isinstance(name, bytes):
|
||||||
name = name.decode(encoding)
|
name = name.decode(encoding)
|
||||||
return name, email
|
return name, email
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
# ubuntu-dev-tools Launchpad Python modules.
|
# ubuntu-dev-tools Launchpad Python modules.
|
||||||
#
|
#
|
||||||
|
|
||||||
service = 'production'
|
SERVICE = "production"
|
||||||
api_version = 'devel'
|
API_VERSION = "devel"
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,26 @@
|
|||||||
class PackageNotFoundException(BaseException):
|
class PackageNotFoundException(BaseException):
|
||||||
"""Thrown when a package is not found"""
|
"""Thrown when a package is not found"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SeriesNotFoundException(BaseException):
|
class SeriesNotFoundException(BaseException):
|
||||||
"""Thrown when a distroseries is not found"""
|
"""Thrown when a distroseries is not found"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PocketDoesNotExistError(Exception):
|
class PocketDoesNotExistError(Exception):
|
||||||
'''Raised when a invalid pocket is used.'''
|
"""Raised when a invalid pocket is used."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ArchiveNotFoundException(BaseException):
|
class ArchiveNotFoundException(BaseException):
|
||||||
"""Thrown when an archive for a distibution is not found"""
|
"""Thrown when an archive for a distibution is not found"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AlreadyLoggedInError(Exception):
|
class AlreadyLoggedInError(Exception):
|
||||||
'''Raised when a second login is attempted.'''
|
"""Raised when a second login is attempted."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ArchSeriesNotFoundException(BaseException):
|
class ArchSeriesNotFoundException(BaseException):
|
||||||
"""Thrown when a distroarchseries is not found."""
|
"""Thrown when a distroarchseries is not found."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidDistroValueError(ValueError):
|
class InvalidDistroValueError(ValueError):
|
||||||
"""Thrown when distro value is invalid"""
|
"""Thrown when distro value is invalid"""
|
||||||
pass
|
|
||||||
|
@ -22,47 +22,45 @@
|
|||||||
#
|
#
|
||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
import distro_info
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import locale
|
import locale
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import check_output, CalledProcessError
|
from subprocess import CalledProcessError, check_output
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import distro_info
|
||||||
|
import requests
|
||||||
|
|
||||||
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
|
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_POCKETS = ('Release', 'Security', 'Updates', 'Proposed')
|
DEFAULT_POCKETS = ("Release", "Security", "Updates", "Proposed")
|
||||||
POCKETS = DEFAULT_POCKETS + ('Backports',)
|
POCKETS = DEFAULT_POCKETS + ("Backports",)
|
||||||
|
|
||||||
DEFAULT_STATUSES = ('Pending', 'Published')
|
DEFAULT_STATUSES = ("Pending", "Published")
|
||||||
STATUSES = DEFAULT_STATUSES + ('Superseded', 'Deleted', 'Obsolete')
|
STATUSES = DEFAULT_STATUSES + ("Superseded", "Deleted", "Obsolete")
|
||||||
|
|
||||||
UPLOAD_QUEUE_STATUSES = ('New', 'Unapproved', 'Accepted', 'Done', 'Rejected')
|
UPLOAD_QUEUE_STATUSES = ("New", "Unapproved", "Accepted", "Done", "Rejected")
|
||||||
|
|
||||||
DOWNLOAD_BLOCKSIZE_DEFAULT = 8192
|
DOWNLOAD_BLOCKSIZE_DEFAULT = 8192
|
||||||
|
|
||||||
_system_distribution_chain = []
|
_SYSTEM_DISTRIBUTION_CHAIN: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
class DownloadError(Exception):
|
class DownloadError(Exception):
|
||||||
"Unable to pull a source package"
|
"Unable to pull a source package"
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotFoundError(DownloadError):
|
class NotFoundError(DownloadError):
|
||||||
"Source package not found"
|
"Source package not found"
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def system_distribution_chain():
|
def system_distribution_chain():
|
||||||
@ -74,27 +72,32 @@ def system_distribution_chain():
|
|||||||
the distribution chain can't be determined, print an error message
|
the distribution chain can't be determined, print an error message
|
||||||
and return an empty list.
|
and return an empty list.
|
||||||
"""
|
"""
|
||||||
global _system_distribution_chain
|
if len(_SYSTEM_DISTRIBUTION_CHAIN) == 0:
|
||||||
if len(_system_distribution_chain) == 0:
|
|
||||||
try:
|
try:
|
||||||
vendor = check_output(('dpkg-vendor', '--query', 'Vendor'),
|
vendor = check_output(("dpkg-vendor", "--query", "Vendor"), encoding="utf-8").strip()
|
||||||
encoding='utf-8').strip()
|
_SYSTEM_DISTRIBUTION_CHAIN.append(vendor)
|
||||||
_system_distribution_chain.append(vendor)
|
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
Logger.error('Could not determine what distribution you are running.')
|
Logger.error("Could not determine what distribution you are running.")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
parent = check_output((
|
parent = check_output(
|
||||||
'dpkg-vendor', '--vendor', _system_distribution_chain[-1],
|
(
|
||||||
'--query', 'Parent'), encoding='utf-8').strip()
|
"dpkg-vendor",
|
||||||
|
"--vendor",
|
||||||
|
_SYSTEM_DISTRIBUTION_CHAIN[-1],
|
||||||
|
"--query",
|
||||||
|
"Parent",
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
).strip()
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
# Vendor has no parent
|
# Vendor has no parent
|
||||||
break
|
break
|
||||||
_system_distribution_chain.append(parent)
|
_SYSTEM_DISTRIBUTION_CHAIN.append(parent)
|
||||||
|
|
||||||
return _system_distribution_chain
|
return _SYSTEM_DISTRIBUTION_CHAIN
|
||||||
|
|
||||||
|
|
||||||
def system_distribution():
|
def system_distribution():
|
||||||
@ -115,14 +118,12 @@ def host_architecture():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
arch = check_output(('dpkg', '--print-architecture'),
|
arch = check_output(("dpkg", "--print-architecture"), encoding="utf-8").strip()
|
||||||
encoding='utf-8').strip()
|
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
arch = None
|
arch = None
|
||||||
|
|
||||||
if not arch or 'not found' in arch:
|
if not arch or "not found" in arch:
|
||||||
Logger.error('Not running on a Debian based system; '
|
Logger.error("Not running on a Debian based system; could not detect its architecture.")
|
||||||
'could not detect its architecture.')
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return arch
|
return arch
|
||||||
@ -134,16 +135,16 @@ def readlist(filename, uniq=True):
|
|||||||
Read a list of words from the indicated file. If 'uniq' is True, filter
|
Read a list of words from the indicated file. If 'uniq' is True, filter
|
||||||
out duplicated words.
|
out duplicated words.
|
||||||
"""
|
"""
|
||||||
p = Path(filename)
|
path = Path(filename)
|
||||||
|
|
||||||
if not p.is_file():
|
if not path.is_file():
|
||||||
Logger.error(f'File {p} does not exist.')
|
Logger.error("File %s does not exist.", path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
content = p.read_text().replace('\n', ' ').replace(',', ' ')
|
content = path.read_text(encoding="utf-8").replace("\n", " ").replace(",", " ")
|
||||||
|
|
||||||
if not content.strip():
|
if not content.strip():
|
||||||
Logger.error(f'File {p} is empty.')
|
Logger.error("File %s is empty.", path)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
items = [item for item in content.split() if item]
|
items = [item for item in content.split() if item]
|
||||||
@ -154,38 +155,40 @@ def readlist(filename, uniq=True):
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
def split_release_pocket(release, default='Release'):
|
def split_release_pocket(release, default="Release"):
|
||||||
'''Splits the release and pocket name.
|
"""Splits the release and pocket name.
|
||||||
|
|
||||||
If the argument doesn't contain a pocket name then the 'Release' pocket
|
If the argument doesn't contain a pocket name then the 'Release' pocket
|
||||||
is assumed.
|
is assumed.
|
||||||
|
|
||||||
Returns the release and pocket name.
|
Returns the release and pocket name.
|
||||||
'''
|
"""
|
||||||
pocket = default
|
pocket = default
|
||||||
|
|
||||||
if release is None:
|
if release is None:
|
||||||
raise ValueError('No release name specified')
|
raise ValueError("No release name specified")
|
||||||
|
|
||||||
if '-' in release:
|
if "-" in release:
|
||||||
(release, pocket) = release.rsplit('-', 1)
|
(release, pocket) = release.rsplit("-", 1)
|
||||||
pocket = pocket.capitalize()
|
pocket = pocket.capitalize()
|
||||||
|
|
||||||
if pocket not in POCKETS:
|
if pocket not in POCKETS:
|
||||||
raise PocketDoesNotExistError("Pocket '%s' does not exist." % pocket)
|
raise PocketDoesNotExistError(f"Pocket '{pocket}' does not exist.")
|
||||||
|
|
||||||
return (release, pocket)
|
return (release, pocket)
|
||||||
|
|
||||||
|
|
||||||
def require_utf8():
|
def require_utf8():
|
||||||
'''Can be called by programs that only function in UTF-8 locales'''
|
"""Can be called by programs that only function in UTF-8 locales"""
|
||||||
if locale.getpreferredencoding() != 'UTF-8':
|
if locale.getpreferredencoding() != "UTF-8":
|
||||||
Logger.error("This program only functions in a UTF-8 locale. Aborting.")
|
Logger.error("This program only functions in a UTF-8 locale. Aborting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
_vendor_to_distroinfo = {"Debian": distro_info.DebianDistroInfo,
|
_vendor_to_distroinfo = {
|
||||||
"Ubuntu": distro_info.UbuntuDistroInfo}
|
"Debian": distro_info.DebianDistroInfo,
|
||||||
|
"Ubuntu": distro_info.UbuntuDistroInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def vendor_to_distroinfo(vendor):
|
def vendor_to_distroinfo(vendor):
|
||||||
@ -212,9 +215,10 @@ def codename_to_distribution(codename):
|
|||||||
|
|
||||||
if info().valid(codename):
|
if info().valid(codename):
|
||||||
return distro
|
return distro
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def verify_file_checksums(pathname, checksums={}, size=0):
|
def verify_file_checksums(pathname, checksums=None, size=0):
|
||||||
"""verify checksums of file
|
"""verify checksums of file
|
||||||
|
|
||||||
Any failure will log an error.
|
Any failure will log an error.
|
||||||
@ -228,29 +232,33 @@ def verify_file_checksums(pathname, checksums={}, size=0):
|
|||||||
|
|
||||||
Returns True if all checks pass, False otherwise
|
Returns True if all checks pass, False otherwise
|
||||||
"""
|
"""
|
||||||
p = Path(pathname)
|
if checksums is None:
|
||||||
|
checksums = {}
|
||||||
|
path = Path(pathname)
|
||||||
|
|
||||||
if not p.is_file():
|
if not path.is_file():
|
||||||
Logger.error(f'File {p} not found')
|
Logger.error("File %s not found", path)
|
||||||
return False
|
return False
|
||||||
filesize = p.stat().st_size
|
filesize = path.stat().st_size
|
||||||
if size and size != filesize:
|
if size and size != filesize:
|
||||||
Logger.error(f'File {p} incorrect size, got {filesize} expected {size}')
|
Logger.error("File %s incorrect size, got %s expected %s", path, filesize, size)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for (alg, checksum) in checksums.items():
|
for alg, checksum in checksums.items():
|
||||||
h = hashlib.new(alg)
|
hash_ = hashlib.new(alg)
|
||||||
with p.open('rb') as f:
|
with path.open("rb") as f:
|
||||||
while True:
|
while True:
|
||||||
block = f.read(h.block_size)
|
block = f.read(hash_.block_size)
|
||||||
if len(block) == 0:
|
if len(block) == 0:
|
||||||
break
|
break
|
||||||
h.update(block)
|
hash_.update(block)
|
||||||
digest = h.hexdigest()
|
digest = hash_.hexdigest()
|
||||||
if digest == checksum:
|
if digest == checksum:
|
||||||
Logger.debug(f'File {p} checksum ({alg}) verified: {checksum}')
|
Logger.debug("File %s checksum (%s) verified: %s", path, alg, checksum)
|
||||||
else:
|
else:
|
||||||
Logger.error(f'File {p} checksum ({alg}) mismatch: got {digest} expected {checksum}')
|
Logger.error(
|
||||||
|
"File %s checksum (%s) mismatch: got %s expected %s", path, alg, digest, checksum
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -282,9 +290,13 @@ def extract_authentication(url):
|
|||||||
|
|
||||||
This returns a tuple in the form (url, username, password)
|
This returns a tuple in the form (url, username, password)
|
||||||
"""
|
"""
|
||||||
u = urlparse(url)
|
components = urlparse(url)
|
||||||
if u.username or u.password:
|
if components.username or components.password:
|
||||||
return (u._replace(netloc=u.hostname).geturl(), u.username, u.password)
|
return (
|
||||||
|
components._replace(netloc=components.hostname).geturl(),
|
||||||
|
components.username,
|
||||||
|
components.password,
|
||||||
|
)
|
||||||
return (url, None, None)
|
return (url, None, None)
|
||||||
|
|
||||||
|
|
||||||
@ -315,122 +327,142 @@ def download(src, dst, size=0, *, blocksize=DOWNLOAD_BLOCKSIZE_DEFAULT):
|
|||||||
dst = dst / Path(parsedsrc.path).name
|
dst = dst / Path(parsedsrc.path).name
|
||||||
|
|
||||||
# Copy if src is a local file
|
# Copy if src is a local file
|
||||||
if parsedsrc.scheme in ['', 'file']:
|
if parsedsrc.scheme in ["", "file"]:
|
||||||
src = Path(parsedsrc.path).expanduser().resolve()
|
src = Path(parsedsrc.path).expanduser().resolve()
|
||||||
if src != parsedsrc.path:
|
if src != parsedsrc.path:
|
||||||
Logger.info(f'Parsed {parsedsrc.path} as {src}')
|
Logger.info("Parsed %s as %s", parsedsrc.path, src)
|
||||||
if not src.exists():
|
if not src.exists():
|
||||||
raise NotFoundError(f'Source file {src} not found')
|
raise NotFoundError(f"Source file {src} not found")
|
||||||
if dst.exists():
|
if dst.exists():
|
||||||
if src.samefile(dst):
|
if src.samefile(dst):
|
||||||
Logger.info(f'Using existing file {dst}')
|
Logger.info("Using existing file %s", dst)
|
||||||
return dst
|
return dst
|
||||||
Logger.info(f'Replacing existing file {dst}')
|
Logger.info("Replacing existing file %s", dst)
|
||||||
Logger.info(f'Copying file {src} to {dst}')
|
Logger.info("Copying file %s to %s", src, dst)
|
||||||
shutil.copyfile(src, dst)
|
shutil.copyfile(src, dst)
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
(src, username, password) = extract_authentication(src)
|
(src, username, password) = extract_authentication(src)
|
||||||
auth = (username, password) if username or password else None
|
auth = (username, password) if username or password else None
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
tmpdst = Path(d) / 'dst'
|
tmpdst = Path(tmpdir) / "dst"
|
||||||
try:
|
try:
|
||||||
with requests.get(src, stream=True, auth=auth) as fsrc, tmpdst.open('wb') as fdst:
|
# We must use "Accept-Encoding: identity" so that Launchpad doesn't
|
||||||
|
# compress changes files. See LP: #2025748.
|
||||||
|
with requests.get(
|
||||||
|
src, stream=True, timeout=60, auth=auth, headers={"accept-encoding": "identity"}
|
||||||
|
) as fsrc:
|
||||||
|
with tmpdst.open("wb") as fdst:
|
||||||
fsrc.raise_for_status()
|
fsrc.raise_for_status()
|
||||||
_download(fsrc, fdst, size, blocksize=blocksize)
|
_download(fsrc, fdst, size, blocksize=blocksize)
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as error:
|
||||||
if e.response is not None and e.response.status_code == 404:
|
if error.response is not None and error.response.status_code == 404:
|
||||||
raise NotFoundError(f'URL {src} not found: {e}')
|
raise NotFoundError(f"URL {src} not found: {error}") from error
|
||||||
raise DownloadError(e)
|
raise DownloadError(error) from error
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as error:
|
||||||
# This is likely a archive hostname that doesn't resolve, like 'ftpmaster.internal'
|
# This is likely a archive hostname that doesn't resolve, like 'ftpmaster.internal'
|
||||||
raise NotFoundError(f'URL {src} not found: {e}')
|
raise NotFoundError(f"URL {src} not found: {error}") from error
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as error:
|
||||||
raise DownloadError(e)
|
raise DownloadError(error) from error
|
||||||
shutil.move(tmpdst, dst)
|
shutil.move(tmpdst, dst)
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
|
|
||||||
class _StderrProgressBar(object):
|
class _StderrProgressBar:
|
||||||
BAR_WIDTH_MIN = 40
|
BAR_WIDTH_MIN = 40
|
||||||
BAR_WIDTH_DEFAULT = 60
|
BAR_WIDTH_DEFAULT = 60
|
||||||
|
|
||||||
def __init__(self, max_width):
|
def __init__(self, max_width):
|
||||||
self.full_width = min(max_width, self.BAR_WIDTH_DEFAULT)
|
self.full_width = min(max_width, self.BAR_WIDTH_DEFAULT)
|
||||||
self.width = self.full_width - len('[] 99%')
|
self.width = self.full_width - len("[] 99%")
|
||||||
self.show_progress = self.full_width >= self.BAR_WIDTH_MIN
|
self.show_progress = self.full_width >= self.BAR_WIDTH_MIN
|
||||||
|
|
||||||
def update(self, progress, total):
|
def update(self, progress, total):
|
||||||
if not self.show_progress:
|
if not self.show_progress:
|
||||||
return
|
return
|
||||||
pct = progress * 100 // total
|
pct = progress * 100 // total
|
||||||
pctstr = f'{pct:>3}%'
|
pctstr = f"{pct:>3}%"
|
||||||
barlen = self.width * pct // 100
|
barlen = self.width * pct // 100
|
||||||
barstr = '=' * barlen
|
barstr = "=" * barlen
|
||||||
barstr = barstr[:-1] + '>'
|
barstr = f"{barstr[:-1]}>"
|
||||||
barstr = barstr.ljust(self.width)
|
barstr = barstr.ljust(self.width)
|
||||||
fullstr = f'\r[{barstr}]{pctstr}'
|
fullstr = f"\r[{barstr}]{pctstr}"
|
||||||
sys.stderr.write(fullstr)
|
sys.stderr.write(fullstr)
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
if not self.show_progress:
|
if not self.show_progress:
|
||||||
return
|
return
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write("\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|
||||||
def _download(fsrc, fdst, size, *, blocksize):
|
def _download(fsrc, fdst, size, *, blocksize):
|
||||||
"""helper method to download src to dst using requests library."""
|
"""helper method to download src to dst using requests library."""
|
||||||
url = fsrc.url
|
url = fsrc.url
|
||||||
Logger.debug(f'Using URL: {url}')
|
Logger.debug("Using URL: %s", url)
|
||||||
|
|
||||||
if not size:
|
if not size:
|
||||||
with suppress(AttributeError, TypeError, ValueError):
|
with suppress(AttributeError, TypeError, ValueError):
|
||||||
size = int(fsrc.headers.get('Content-Length'))
|
size = int(fsrc.headers.get("Content-Length"))
|
||||||
|
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
filename = Path(parsed.path).name
|
filename = Path(parsed.path).name
|
||||||
hostname = parsed.hostname
|
hostname = parsed.hostname
|
||||||
sizemb = ' (%0.3f MiB)' % (size / 1024.0 / 1024) if size else ''
|
sizemb = f" ({size / 1024.0 / 1024:0.3f} MiB)" if size else ""
|
||||||
Logger.info(f'Downloading {filename} from {hostname}{sizemb}')
|
Logger.info("Downloading %s from %s%s", filename, hostname, sizemb)
|
||||||
|
|
||||||
# Don't show progress if:
|
# Don't show progress if:
|
||||||
# logging INFO is suppressed
|
# logging INFO is suppressed
|
||||||
# stderr isn't a tty
|
# stderr isn't a tty
|
||||||
# we don't know the total file size
|
# we don't know the total file size
|
||||||
# the file is content-encoded (i.e. compressed)
|
# the file is content-encoded (i.e. compressed)
|
||||||
show_progress = all((Logger.isEnabledFor(logging.INFO),
|
show_progress = all(
|
||||||
|
(
|
||||||
|
Logger.isEnabledFor(logging.INFO),
|
||||||
sys.stderr.isatty(),
|
sys.stderr.isatty(),
|
||||||
size > 0,
|
size > 0,
|
||||||
'Content-Encoding' not in fsrc.headers))
|
"Content-Encoding" not in fsrc.headers,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
terminal_width = 0
|
terminal_width = 0
|
||||||
if show_progress:
|
if show_progress:
|
||||||
try:
|
try:
|
||||||
terminal_width = os.get_terminal_size(sys.stderr.fileno()).columns
|
terminal_width = os.get_terminal_size(sys.stderr.fileno()).columns
|
||||||
except Exception as e:
|
except Exception as e: # pylint: disable=broad-except
|
||||||
Logger.error(f'Error finding stderr width, suppressing progress bar: {e}')
|
Logger.error("Error finding stderr width, suppressing progress bar: %s", e)
|
||||||
progress_bar = _StderrProgressBar(max_width=terminal_width)
|
progress_bar = _StderrProgressBar(max_width=terminal_width)
|
||||||
|
|
||||||
downloaded = 0
|
downloaded = 0
|
||||||
try:
|
try:
|
||||||
for block in fsrc.iter_content(blocksize):
|
while True:
|
||||||
|
# We use fsrc.raw so that compressed files stay compressed as we
|
||||||
|
# write them to disk. For example, if this is a .diff.gz, then it
|
||||||
|
# needs to remain compressed and unmodified to remain valid as part
|
||||||
|
# of a source package later, even though Launchpad sends
|
||||||
|
# "Content-Encoding: gzip" and the requests library therefore would
|
||||||
|
# want to decompress it. See LP: #2025748.
|
||||||
|
block = fsrc.raw.read(blocksize)
|
||||||
|
if not block:
|
||||||
|
break
|
||||||
fdst.write(block)
|
fdst.write(block)
|
||||||
downloaded += len(block)
|
downloaded += len(block)
|
||||||
progress_bar.update(downloaded, size)
|
progress_bar.update(downloaded, size)
|
||||||
finally:
|
finally:
|
||||||
progress_bar.finish()
|
progress_bar.finish()
|
||||||
if size and size > downloaded:
|
if size and size > downloaded:
|
||||||
Logger.error('Partial download: %0.3f MiB of %0.3f MiB' %
|
Logger.error(
|
||||||
(downloaded / 1024.0 / 1024,
|
"Partial download: %0.3f MiB of %0.3f MiB",
|
||||||
size / 1024.0 / 1024))
|
downloaded / 1024.0 / 1024,
|
||||||
|
size / 1024.0 / 1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _download_text(src, binary, *, blocksize):
|
def _download_text(src, binary, *, blocksize):
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
dst = Path(d) / 'dst'
|
dst = Path(tmpdir) / "dst"
|
||||||
download(src, dst, blocksize=blocksize)
|
download(src, dst, blocksize=blocksize)
|
||||||
return dst.read_bytes() if binary else dst.read_text()
|
return dst.read_bytes() if binary else dst.read_text()
|
||||||
|
|
||||||
@ -449,7 +481,7 @@ def download_text(src, mode=None, *, blocksize=DOWNLOAD_BLOCKSIZE_DEFAULT):
|
|||||||
|
|
||||||
Returns text content of downloaded file
|
Returns text content of downloaded file
|
||||||
"""
|
"""
|
||||||
return _download_text(src, binary='b' in (mode or ''), blocksize=blocksize)
|
return _download_text(src, binary="b" in (mode or ""), blocksize=blocksize)
|
||||||
|
|
||||||
|
|
||||||
def download_bytes(src, *, blocksize=DOWNLOAD_BLOCKSIZE_DEFAULT):
|
def download_bytes(src, *, blocksize=DOWNLOAD_BLOCKSIZE_DEFAULT):
|
||||||
|
@ -22,54 +22,58 @@
|
|||||||
# ##################################################################
|
# ##################################################################
|
||||||
|
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import errno
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
from distro_info import DebianDistroInfo
|
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from ubuntutools.archive import (UbuntuSourcePackage, DebianSourcePackage,
|
from distro_info import DebianDistroInfo
|
||||||
UbuntuCloudArchiveSourcePackage,
|
|
||||||
PersonalPackageArchiveSourcePackage)
|
|
||||||
from ubuntutools.config import UDTConfig
|
|
||||||
from ubuntutools.lp.lpapicache import (Distribution, Launchpad)
|
|
||||||
from ubuntutools.lp.udtexceptions import (AlreadyLoggedInError,
|
|
||||||
SeriesNotFoundException,
|
|
||||||
PackageNotFoundException,
|
|
||||||
PocketDoesNotExistError,
|
|
||||||
InvalidDistroValueError)
|
|
||||||
from ubuntutools.misc import (split_release_pocket,
|
|
||||||
host_architecture,
|
|
||||||
download,
|
|
||||||
UPLOAD_QUEUE_STATUSES,
|
|
||||||
STATUSES)
|
|
||||||
|
|
||||||
|
|
||||||
# by default we use standard logging.getLogger() and only use
|
# by default we use standard logging.getLogger() and only use
|
||||||
# ubuntutools.getLogger() in PullPkg().main()
|
# ubuntutools.getLogger() in PullPkg().main()
|
||||||
from ubuntutools import getLogger as ubuntutools_getLogger
|
from ubuntutools import getLogger as ubuntutools_getLogger
|
||||||
import logging
|
from ubuntutools.archive import (
|
||||||
|
DebianSourcePackage,
|
||||||
|
PersonalPackageArchiveSourcePackage,
|
||||||
|
UbuntuCloudArchiveSourcePackage,
|
||||||
|
UbuntuSourcePackage,
|
||||||
|
)
|
||||||
|
from ubuntutools.config import UDTConfig
|
||||||
|
from ubuntutools.lp.lpapicache import Distribution, Launchpad
|
||||||
|
from ubuntutools.lp.udtexceptions import (
|
||||||
|
AlreadyLoggedInError,
|
||||||
|
InvalidDistroValueError,
|
||||||
|
PackageNotFoundException,
|
||||||
|
PocketDoesNotExistError,
|
||||||
|
SeriesNotFoundException,
|
||||||
|
)
|
||||||
|
from ubuntutools.misc import (
|
||||||
|
STATUSES,
|
||||||
|
UPLOAD_QUEUE_STATUSES,
|
||||||
|
download,
|
||||||
|
host_architecture,
|
||||||
|
split_release_pocket,
|
||||||
|
)
|
||||||
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PULL_SOURCE = 'source'
|
PULL_SOURCE = "source"
|
||||||
PULL_DEBS = 'debs'
|
PULL_DEBS = "debs"
|
||||||
PULL_DDEBS = 'ddebs'
|
PULL_DDEBS = "ddebs"
|
||||||
PULL_UDEBS = 'udebs'
|
PULL_UDEBS = "udebs"
|
||||||
PULL_LIST = 'list'
|
PULL_LIST = "list"
|
||||||
|
|
||||||
VALID_PULLS = [PULL_SOURCE, PULL_DEBS, PULL_DDEBS, PULL_UDEBS, PULL_LIST]
|
VALID_PULLS = [PULL_SOURCE, PULL_DEBS, PULL_DDEBS, PULL_UDEBS, PULL_LIST]
|
||||||
VALID_BINARY_PULLS = [PULL_DEBS, PULL_DDEBS, PULL_UDEBS]
|
VALID_BINARY_PULLS = [PULL_DEBS, PULL_DDEBS, PULL_UDEBS]
|
||||||
|
|
||||||
DISTRO_DEBIAN = 'debian'
|
DISTRO_DEBIAN = "debian"
|
||||||
DISTRO_UBUNTU = 'ubuntu'
|
DISTRO_UBUNTU = "ubuntu"
|
||||||
DISTRO_UCA = 'uca'
|
DISTRO_UCA = "uca"
|
||||||
DISTRO_PPA = 'ppa'
|
DISTRO_PPA = "ppa"
|
||||||
|
|
||||||
DISTRO_PKG_CLASS = {
|
DISTRO_PKG_CLASS = {
|
||||||
DISTRO_DEBIAN: DebianSourcePackage,
|
DISTRO_DEBIAN: DebianSourcePackage,
|
||||||
@ -82,11 +86,11 @@ VALID_DISTROS = DISTRO_PKG_CLASS.keys()
|
|||||||
|
|
||||||
class InvalidPullValueError(ValueError):
|
class InvalidPullValueError(ValueError):
|
||||||
"""Thrown when --pull value is invalid"""
|
"""Thrown when --pull value is invalid"""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PullPkg(object):
|
class PullPkg:
|
||||||
"""Class used to pull file(s) associated with a specific package"""
|
"""Class used to pull file(s) associated with a specific package"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def main(cls, *args, **kwargs):
|
def main(cls, *args, **kwargs):
|
||||||
"""For use by stand-alone cmdline scripts.
|
"""For use by stand-alone cmdline scripts.
|
||||||
@ -101,59 +105,74 @@ class PullPkg(object):
|
|||||||
unexpected errors will flow up to the caller.
|
unexpected errors will flow up to the caller.
|
||||||
On success, this simply returns.
|
On success, this simply returns.
|
||||||
"""
|
"""
|
||||||
Logger = ubuntutools_getLogger()
|
logger = ubuntutools_getLogger()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cls(*args, **kwargs).pull()
|
cls(*args, **kwargs).pull()
|
||||||
return
|
return
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
Logger.info('User abort.')
|
logger.info("User abort.")
|
||||||
except (PackageNotFoundException, SeriesNotFoundException,
|
except (
|
||||||
PocketDoesNotExistError, InvalidDistroValueError,
|
PackageNotFoundException,
|
||||||
InvalidPullValueError) as e:
|
SeriesNotFoundException,
|
||||||
Logger.error(str(e))
|
PocketDoesNotExistError,
|
||||||
|
InvalidDistroValueError,
|
||||||
|
InvalidPullValueError,
|
||||||
|
) as error:
|
||||||
|
logger.error(str(error))
|
||||||
sys.exit(errno.ENOENT)
|
sys.exit(errno.ENOENT)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs): # pylint: disable=unused-argument
|
||||||
self._default_pull = kwargs.get('pull')
|
self._default_pull = kwargs.get("pull")
|
||||||
self._default_distro = kwargs.get('distro')
|
self._default_distro = kwargs.get("distro")
|
||||||
self._default_arch = kwargs.get('arch', host_architecture())
|
self._default_arch = kwargs.get("arch", host_architecture())
|
||||||
|
|
||||||
def parse_args(self, args):
|
def parse_args(self, args):
|
||||||
args = args[:]
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
help_default_pull = "What to pull: " + ", ".join(VALID_PULLS)
|
help_default_pull = "What to pull: " + ", ".join(VALID_PULLS)
|
||||||
if self._default_pull:
|
if self._default_pull:
|
||||||
help_default_pull += (" (default: %s)" % self._default_pull)
|
help_default_pull += f" (default: {self._default_pull})"
|
||||||
help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS)
|
help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS)
|
||||||
if self._default_distro:
|
if self._default_distro:
|
||||||
help_default_distro += (" (default: %s)" % self._default_distro)
|
help_default_distro += f" (default: {self._default_distro})"
|
||||||
help_default_arch = ("Get binary packages for arch")
|
help_default_arch = "Get binary packages for arch"
|
||||||
help_default_arch += ("(default: %s)" % self._default_arch)
|
help_default_arch += f"(default: {self._default_arch})"
|
||||||
|
|
||||||
# use add_help=False because we do parse_known_args() below, and if
|
# use add_help=False because we do parse_known_args() below, and if
|
||||||
# that sees --help then it exits immediately
|
# that sees --help then it exits immediately
|
||||||
parser = ArgumentParser(add_help=False)
|
parser = ArgumentParser(add_help=False)
|
||||||
parser.add_argument('-L', '--login', action='store_true',
|
parser.add_argument("-L", "--login", action="store_true", help="Login to Launchpad")
|
||||||
help="Login to Launchpad")
|
parser.add_argument(
|
||||||
parser.add_argument('-v', '--verbose', action='count', default=0,
|
"-v", "--verbose", action="count", default=0, help="Increase verbosity/debug"
|
||||||
help="Increase verbosity/debug")
|
)
|
||||||
parser.add_argument('-d', '--download-only', action='store_true',
|
parser.add_argument(
|
||||||
help="Do not extract the source package")
|
"-d", "--download-only", action="store_true", help="Do not extract the source package"
|
||||||
parser.add_argument('-m', '--mirror', action='append',
|
)
|
||||||
help='Preferred mirror(s)')
|
parser.add_argument("-m", "--mirror", action="append", help="Preferred mirror(s)")
|
||||||
parser.add_argument('--no-conf', action='store_true',
|
parser.add_argument(
|
||||||
help="Don't read config files or environment variables")
|
"--no-conf",
|
||||||
parser.add_argument('--no-verify-signature', action='store_true',
|
action="store_true",
|
||||||
help="Don't fail if dsc signature can't be verified")
|
help="Don't read config files or environment variables",
|
||||||
parser.add_argument('-s', '--status', action='append', default=[],
|
)
|
||||||
help="Search for packages with specific status(es)")
|
parser.add_argument(
|
||||||
parser.add_argument('-a', '--arch', default=self._default_arch,
|
"--no-verify-signature",
|
||||||
help=help_default_arch)
|
action="store_true",
|
||||||
parser.add_argument('-p', '--pull', default=self._default_pull,
|
help="Don't fail if dsc signature can't be verified",
|
||||||
help=help_default_pull)
|
)
|
||||||
parser.add_argument('-D', '--distro', default=self._default_distro,
|
parser.add_argument(
|
||||||
help=help_default_distro)
|
"-s",
|
||||||
|
"--status",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
help="Search for packages with specific status(es)",
|
||||||
|
)
|
||||||
|
parser.add_argument("-a", "--arch", default=self._default_arch, help=help_default_arch)
|
||||||
|
parser.add_argument("-p", "--pull", default=self._default_pull, help=help_default_pull)
|
||||||
|
parser.add_argument(
|
||||||
|
"-D", "--distro", default=self._default_distro, help=help_default_distro
|
||||||
|
)
|
||||||
|
|
||||||
# add distro-specific params
|
# add distro-specific params
|
||||||
try:
|
try:
|
||||||
@ -163,75 +182,84 @@ class PullPkg(object):
|
|||||||
distro = None
|
distro = None
|
||||||
|
|
||||||
if distro == DISTRO_UBUNTU:
|
if distro == DISTRO_UBUNTU:
|
||||||
parser.add_argument('--security', action='store_true',
|
parser.add_argument(
|
||||||
help='Pull from the Ubuntu Security Team (proposed) PPA')
|
"--security",
|
||||||
parser.add_argument('--upload-queue', action='store_true',
|
action="store_true",
|
||||||
help='Pull from the Ubuntu upload queue')
|
help="Pull from the Ubuntu Security Team (proposed) PPA",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--upload-queue", action="store_true", help="Pull from the Ubuntu upload queue"
|
||||||
|
)
|
||||||
if distro == DISTRO_PPA:
|
if distro == DISTRO_PPA:
|
||||||
parser.add_argument('--ppa', help='PPA to pull from')
|
parser.add_argument("--ppa", help="PPA to pull from")
|
||||||
if parser.parse_known_args(args)[0].ppa is None:
|
if parser.parse_known_args(args)[0].ppa is None:
|
||||||
# check for any param starting with "ppa:"
|
# check for any param starting with "ppa:"
|
||||||
# if found, move it to a --ppa param
|
# if found, move it to a --ppa param
|
||||||
for param in args:
|
for param in args:
|
||||||
if param.startswith('ppa:'):
|
if param.startswith("ppa:"):
|
||||||
args.remove(param)
|
args.remove(param)
|
||||||
args.insert(0, param)
|
args.insert(0, param)
|
||||||
args.insert(0, '--ppa')
|
args.insert(0, "--ppa")
|
||||||
break
|
break
|
||||||
|
|
||||||
# add the positional params
|
# add the positional params
|
||||||
parser.add_argument('package', help="Package name to pull")
|
parser.add_argument("package", help="Package name to pull")
|
||||||
parser.add_argument('release', nargs='?', help="Release to pull from")
|
parser.add_argument("release", nargs="?", help="Release to pull from")
|
||||||
parser.add_argument('version', nargs='?', help="Package version to pull")
|
parser.add_argument("version", nargs="?", help="Package version to pull")
|
||||||
|
|
||||||
epilog = ("Note on --status: if a version is provided, all status types "
|
epilog = (
|
||||||
|
"Note on --status: if a version is provided, all status types "
|
||||||
"will be searched; if no version is provided, by default only "
|
"will be searched; if no version is provided, by default only "
|
||||||
"'Pending' and 'Published' status will be searched.")
|
"'Pending' and 'Published' status will be searched."
|
||||||
|
)
|
||||||
|
|
||||||
# since parser has no --help handler, create a new parser that does
|
# since parser has no --help handler, create a new parser that does
|
||||||
newparser = ArgumentParser(parents=[parser], epilog=epilog)
|
newparser = ArgumentParser(parents=[parser], epilog=epilog)
|
||||||
|
|
||||||
return self.parse_options(vars(newparser.parse_args(args)))
|
return self.parse_options(vars(newparser.parse_args(args)))
|
||||||
|
|
||||||
def parse_pull(self, pull):
|
@staticmethod
|
||||||
|
def parse_pull(pull):
|
||||||
if not pull:
|
if not pull:
|
||||||
raise InvalidPullValueError("Must specify --pull")
|
raise InvalidPullValueError("Must specify --pull")
|
||||||
|
|
||||||
# allow 'dbgsym' as alias for 'ddebs'
|
# allow 'dbgsym' as alias for 'ddebs'
|
||||||
if pull == 'dbgsym':
|
if pull == "dbgsym":
|
||||||
Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull)
|
Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull)
|
||||||
pull = PULL_DDEBS
|
pull = PULL_DDEBS
|
||||||
# assume anything starting with 'bin' means 'debs'
|
# assume anything starting with 'bin' means 'debs'
|
||||||
if str(pull).startswith('bin'):
|
if str(pull).startswith("bin"):
|
||||||
Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull)
|
Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull)
|
||||||
pull = PULL_DEBS
|
pull = PULL_DEBS
|
||||||
# verify pull action is valid
|
# verify pull action is valid
|
||||||
if pull not in VALID_PULLS:
|
if pull not in VALID_PULLS:
|
||||||
raise InvalidPullValueError("Invalid pull action '%s'" % pull)
|
raise InvalidPullValueError(f"Invalid pull action '{pull}'")
|
||||||
|
|
||||||
return pull
|
return pull
|
||||||
|
|
||||||
def parse_distro(self, distro):
|
@staticmethod
|
||||||
|
def parse_distro(distro):
|
||||||
if not distro:
|
if not distro:
|
||||||
raise InvalidDistroValueError("Must specify --distro")
|
raise InvalidDistroValueError("Must specify --distro")
|
||||||
|
|
||||||
distro = distro.lower()
|
distro = distro.lower()
|
||||||
|
|
||||||
# allow 'lp' for 'ubuntu'
|
# allow 'lp' for 'ubuntu'
|
||||||
if distro == 'lp':
|
if distro == "lp":
|
||||||
Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro)
|
Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro)
|
||||||
distro = DISTRO_UBUNTU
|
distro = DISTRO_UBUNTU
|
||||||
# assume anything with 'cloud' is UCA
|
# assume anything with 'cloud' is UCA
|
||||||
if re.match(r'.*cloud.*', distro):
|
if re.match(r".*cloud.*", distro):
|
||||||
Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro)
|
Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro)
|
||||||
distro = DISTRO_UCA
|
distro = DISTRO_UCA
|
||||||
# verify distro is valid
|
# verify distro is valid
|
||||||
if distro not in VALID_DISTROS:
|
if distro not in VALID_DISTROS:
|
||||||
raise InvalidDistroValueError("Invalid distro '%s'" % distro)
|
raise InvalidDistroValueError(f"Invalid distro '{distro}'")
|
||||||
|
|
||||||
return distro
|
return distro
|
||||||
|
|
||||||
def parse_release(self, distro, release):
|
@staticmethod
|
||||||
|
def parse_release(distro, release):
|
||||||
if distro == DISTRO_UCA:
|
if distro == DISTRO_UCA:
|
||||||
return UbuntuCloudArchiveSourcePackage.parseReleaseAndPocket(release)
|
return UbuntuCloudArchiveSourcePackage.parseReleaseAndPocket(release)
|
||||||
|
|
||||||
@ -249,15 +277,14 @@ class PullPkg(object):
|
|||||||
|
|
||||||
if distro == DISTRO_PPA:
|
if distro == DISTRO_PPA:
|
||||||
# PPAs are part of Ubuntu distribution
|
# PPAs are part of Ubuntu distribution
|
||||||
d = Distribution(DISTRO_UBUNTU)
|
distribution = Distribution(DISTRO_UBUNTU)
|
||||||
else:
|
else:
|
||||||
d = Distribution(distro)
|
distribution = Distribution(distro)
|
||||||
|
|
||||||
# let SeriesNotFoundException flow up
|
# let SeriesNotFoundException flow up
|
||||||
d.getSeries(release)
|
distribution.getSeries(release)
|
||||||
|
|
||||||
Logger.debug("Using distro '%s' release '%s' pocket '%s'",
|
Logger.debug("Using distro '%s' release '%s' pocket '%s'", distro, release, pocket)
|
||||||
distro, release, pocket)
|
|
||||||
return (release, pocket)
|
return (release, pocket)
|
||||||
|
|
||||||
def parse_release_and_version(self, distro, release, version, try_swap=True):
|
def parse_release_and_version(self, distro, release, version, try_swap=True):
|
||||||
@ -281,153 +308,196 @@ class PullPkg(object):
|
|||||||
# they should all be provided, though the optional ones may be None
|
# they should all be provided, though the optional ones may be None
|
||||||
|
|
||||||
# type bool
|
# type bool
|
||||||
assert 'verbose' in options
|
assert "verbose" in options
|
||||||
assert 'download_only' in options
|
assert "download_only" in options
|
||||||
assert 'no_conf' in options
|
assert "no_conf" in options
|
||||||
assert 'no_verify_signature' in options
|
assert "no_verify_signature" in options
|
||||||
assert 'status' in options
|
assert "status" in options
|
||||||
# type string
|
# type string
|
||||||
assert 'pull' in options
|
assert "pull" in options
|
||||||
assert 'distro' in options
|
assert "distro" in options
|
||||||
assert 'arch' in options
|
assert "arch" in options
|
||||||
assert 'package' in options
|
assert "package" in options
|
||||||
# type string, optional
|
# type string, optional
|
||||||
assert 'release' in options
|
assert "release" in options
|
||||||
assert 'version' in options
|
assert "version" in options
|
||||||
# type list of strings, optional
|
# type list of strings, optional
|
||||||
assert 'mirror' in options
|
assert "mirror" in options
|
||||||
|
|
||||||
options['pull'] = self.parse_pull(options['pull'])
|
options["pull"] = self.parse_pull(options["pull"])
|
||||||
options['distro'] = self.parse_distro(options['distro'])
|
options["distro"] = self.parse_distro(options["distro"])
|
||||||
|
|
||||||
# ensure these are always included so we can just check for None/False later
|
# ensure these are always included so we can just check for None/False later
|
||||||
options['ppa'] = options.get('ppa', None)
|
options["ppa"] = options.get("ppa", None)
|
||||||
options['security'] = options.get('security', False)
|
options["security"] = options.get("security", False)
|
||||||
options['upload_queue'] = options.get('upload_queue', False)
|
options["upload_queue"] = options.get("upload_queue", False)
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def _get_params(self, options):
|
def _get_params(self, options):
|
||||||
distro = options['distro']
|
distro = options["distro"]
|
||||||
pull = options['pull']
|
pull = options["pull"]
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
params['package'] = options['package']
|
params["package"] = options["package"]
|
||||||
|
params["arch"] = options["arch"]
|
||||||
|
|
||||||
if options['release']:
|
if options["release"]:
|
||||||
(r, v, p) = self.parse_release_and_version(distro, options['release'],
|
(release, version, pocket) = self.parse_release_and_version(
|
||||||
options['version'])
|
distro, options["release"], options["version"]
|
||||||
params['series'] = r
|
)
|
||||||
params['version'] = v
|
params["series"] = release
|
||||||
params['pocket'] = p
|
params["version"] = version
|
||||||
|
params["pocket"] = pocket
|
||||||
|
|
||||||
if (params['package'].endswith('.dsc') and not params['series'] and not params['version']):
|
if params["package"].endswith(".dsc") and not params["series"] and not params["version"]:
|
||||||
params['dscfile'] = params['package']
|
params["dscfile"] = params["package"]
|
||||||
params.pop('package')
|
params.pop("package")
|
||||||
|
|
||||||
if options['security']:
|
if options["security"]:
|
||||||
if options['ppa']:
|
if options["ppa"]:
|
||||||
Logger.warning('Both --security and --ppa specified, ignoring --ppa')
|
Logger.warning("Both --security and --ppa specified, ignoring --ppa")
|
||||||
Logger.debug('Checking Ubuntu Security PPA')
|
Logger.debug("Checking Ubuntu Security PPA")
|
||||||
# --security is just a shortcut for --ppa ppa:ubuntu-security-proposed/ppa
|
# --security is just a shortcut for --ppa ppa:ubuntu-security-proposed/ppa
|
||||||
options['ppa'] = 'ubuntu-security-proposed/ppa'
|
options["ppa"] = "ubuntu-security-proposed/ppa"
|
||||||
|
|
||||||
if options['ppa']:
|
if options["ppa"]:
|
||||||
if options['ppa'].startswith('ppa:'):
|
if options["ppa"].startswith("ppa:"):
|
||||||
params['ppa'] = options['ppa'][4:]
|
params["ppa"] = options["ppa"][4:]
|
||||||
else:
|
else:
|
||||||
params['ppa'] = options['ppa']
|
params["ppa"] = options["ppa"]
|
||||||
elif distro == DISTRO_PPA:
|
elif distro == DISTRO_PPA:
|
||||||
raise ValueError('Must specify PPA to pull from')
|
raise ValueError("Must specify PPA to pull from")
|
||||||
|
|
||||||
mirrors = []
|
mirrors = []
|
||||||
if options['mirror']:
|
if options["mirror"]:
|
||||||
mirrors.extend(options['mirror'])
|
mirrors.extend(options["mirror"])
|
||||||
if pull == PULL_DDEBS:
|
if pull == PULL_DDEBS:
|
||||||
config = UDTConfig(options['no_conf'])
|
config = UDTConfig(options["no_conf"])
|
||||||
ddebs_mirror = config.get_value(distro.upper() + '_DDEBS_MIRROR')
|
ddebs_mirror = config.get_value(distro.upper() + "_DDEBS_MIRROR")
|
||||||
if ddebs_mirror:
|
if ddebs_mirror:
|
||||||
mirrors.append(ddebs_mirror)
|
mirrors.append(ddebs_mirror)
|
||||||
if mirrors:
|
if mirrors:
|
||||||
Logger.debug("using mirrors %s", ", ".join(mirrors))
|
Logger.debug("using mirrors %s", ", ".join(mirrors))
|
||||||
params['mirrors'] = mirrors
|
params["mirrors"] = mirrors
|
||||||
|
|
||||||
params['verify_signature'] = not options['no_verify_signature']
|
params["verify_signature"] = not options["no_verify_signature"]
|
||||||
|
|
||||||
params['status'] = STATUSES if 'all' in options['status'] else options['status']
|
params["status"] = STATUSES if "all" in options["status"] else options["status"]
|
||||||
|
|
||||||
# special handling for upload queue
|
# special handling for upload queue
|
||||||
if options['upload_queue']:
|
if options["upload_queue"]:
|
||||||
if len(options['status']) > 1:
|
if len(options["status"]) > 1:
|
||||||
raise ValueError("Too many --status provided, "
|
raise ValueError(
|
||||||
"can only search for a single status or 'all'")
|
"Too many --status provided, can only search for a single status or 'all'"
|
||||||
if not options['status']:
|
)
|
||||||
params['status'] = None
|
if not options["status"]:
|
||||||
elif options['status'][0].lower() == 'all':
|
params["status"] = None
|
||||||
params['status'] = 'all'
|
elif options["status"][0].lower() == "all":
|
||||||
elif options['status'][0].capitalize() in UPLOAD_QUEUE_STATUSES:
|
params["status"] = "all"
|
||||||
params['status'] = options['status'][0].capitalize()
|
elif options["status"][0].capitalize() in UPLOAD_QUEUE_STATUSES:
|
||||||
|
params["status"] = options["status"][0].capitalize()
|
||||||
else:
|
else:
|
||||||
msg = ("Invalid upload queue status '%s': valid values are %s" %
|
raise ValueError(
|
||||||
(options['status'][0], ', '.join(UPLOAD_QUEUE_STATUSES)))
|
f"Invalid upload queue status '{options['status'][0]}':"
|
||||||
raise ValueError(msg)
|
f" valid values are {', '.join(UPLOAD_QUEUE_STATUSES)}"
|
||||||
|
)
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def pull(self, args=sys.argv[1:]):
|
def pull(self, args=None):
|
||||||
"""Pull (download) specified package file(s)"""
|
"""Pull (download) specified package file(s)"""
|
||||||
options = self.parse_args(args)
|
options = self.parse_args(args)
|
||||||
|
|
||||||
if options['verbose']:
|
if options["verbose"]:
|
||||||
Logger.setLevel(logging.DEBUG)
|
Logger.setLevel(logging.DEBUG)
|
||||||
if options['verbose'] > 1:
|
if options["verbose"] > 1:
|
||||||
logging.getLogger(__package__).setLevel(logging.DEBUG)
|
logging.getLogger(__package__).setLevel(logging.DEBUG)
|
||||||
|
|
||||||
Logger.debug("pullpkg options: %s", options)
|
Logger.debug("pullpkg options: %s", options)
|
||||||
|
|
||||||
pull = options['pull']
|
pull = options["pull"]
|
||||||
distro = options['distro']
|
distro = options["distro"]
|
||||||
|
|
||||||
if options['login']:
|
if options["login"]:
|
||||||
Logger.debug("Logging in to Launchpad:")
|
Logger.debug("Logging in to Launchpad:")
|
||||||
try:
|
try:
|
||||||
Launchpad.login()
|
Launchpad.login()
|
||||||
except AlreadyLoggedInError:
|
except AlreadyLoggedInError:
|
||||||
Logger.error("Launchpad singleton has already performed a login, "
|
Logger.error(
|
||||||
"and its design prevents another login")
|
"Launchpad singleton has already performed a login, "
|
||||||
|
"and its design prevents another login"
|
||||||
|
)
|
||||||
Logger.warning("Continuing anyway, with existing Launchpad instance")
|
Logger.warning("Continuing anyway, with existing Launchpad instance")
|
||||||
|
|
||||||
params = self._get_params(options)
|
params = self._get_params(options)
|
||||||
package = params['package']
|
package = params["package"]
|
||||||
|
|
||||||
if options['upload_queue']:
|
if options["upload_queue"]:
|
||||||
# upload queue API is different/simpler
|
# upload queue API is different/simpler
|
||||||
self.pull_upload_queue(pull, arch=options['arch'],
|
self.pull_upload_queue( # pylint: disable=missing-kwoa
|
||||||
download_only=options['download_only'],
|
pull, arch=options["arch"], download_only=options["download_only"], **params
|
||||||
**params)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# call implementation, and allow exceptions to flow up to caller
|
# call implementation, and allow exceptions to flow up to caller
|
||||||
srcpkg = DISTRO_PKG_CLASS[distro](**params)
|
srcpkg = DISTRO_PKG_CLASS[distro](**params)
|
||||||
spph = srcpkg.lp_spph
|
spph = srcpkg.lp_spph
|
||||||
|
|
||||||
Logger.info('Found %s', spph.display_name)
|
Logger.info("Found %s", spph.display_name)
|
||||||
|
|
||||||
|
# The VCS detection logic was modeled after `apt source`
|
||||||
|
for key in srcpkg.dsc.keys():
|
||||||
|
original_key = key
|
||||||
|
key = key.lower()
|
||||||
|
|
||||||
|
if key.startswith("vcs-"):
|
||||||
|
if key == "vcs-browser":
|
||||||
|
continue
|
||||||
|
if key == "vcs-git":
|
||||||
|
vcs = "Git"
|
||||||
|
elif key == "vcs-bzr":
|
||||||
|
vcs = "Bazaar"
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
uri = srcpkg.dsc[original_key]
|
||||||
|
|
||||||
|
Logger.warning(
|
||||||
|
"\nNOTICE: '%s' packaging is maintained in "
|
||||||
|
"the '%s' version control system at:\n %s\n",
|
||||||
|
package,
|
||||||
|
vcs,
|
||||||
|
uri,
|
||||||
|
)
|
||||||
|
|
||||||
|
if vcs == "Bazaar":
|
||||||
|
vcscmd = " $ bzr branch " + uri
|
||||||
|
elif vcs == "Git":
|
||||||
|
vcscmd = " $ git clone " + uri
|
||||||
|
|
||||||
|
if vcscmd:
|
||||||
|
Logger.info(
|
||||||
|
"Please use:\n%s\n"
|
||||||
|
"to retrieve the latest (possibly unreleased) updates to the package.\n",
|
||||||
|
vcscmd,
|
||||||
|
)
|
||||||
|
|
||||||
if pull == PULL_LIST:
|
if pull == PULL_LIST:
|
||||||
Logger.info("Source files:")
|
Logger.info("Source files:")
|
||||||
for f in srcpkg.dsc['Files']:
|
for f in srcpkg.dsc["Files"]:
|
||||||
Logger.info(" %s", f['name'])
|
Logger.info(" %s", f["name"])
|
||||||
Logger.info("Binary files:")
|
Logger.info("Binary files:")
|
||||||
for f in spph.getBinaries(options['arch']):
|
for f in spph.getBinaries(options["arch"]):
|
||||||
archtext = ''
|
archtext = ""
|
||||||
name = f.getFileName()
|
name = f.getFileName()
|
||||||
if name.rpartition('.')[0].endswith('all'):
|
if name.rpartition(".")[0].endswith("all"):
|
||||||
archtext = f" ({f.arch})"
|
archtext = f" ({f.arch})"
|
||||||
Logger.info(f" {name}{archtext}")
|
Logger.info(" %s%s", name, archtext)
|
||||||
elif pull == PULL_SOURCE:
|
elif pull == PULL_SOURCE:
|
||||||
# allow DownloadError to flow up to caller
|
# allow DownloadError to flow up to caller
|
||||||
srcpkg.pull()
|
srcpkg.pull()
|
||||||
if options['download_only']:
|
if options["download_only"]:
|
||||||
Logger.debug("--download-only specified, not extracting")
|
Logger.debug("--download-only specified, not extracting")
|
||||||
else:
|
else:
|
||||||
srcpkg.unpack()
|
srcpkg.unpack()
|
||||||
@ -435,104 +505,116 @@ class PullPkg(object):
|
|||||||
name = None
|
name = None
|
||||||
if package != spph.getPackageName():
|
if package != spph.getPackageName():
|
||||||
Logger.info("Pulling only binary package '%s'", package)
|
Logger.info("Pulling only binary package '%s'", package)
|
||||||
Logger.info("Use package name '%s' to pull all binary packages",
|
Logger.info(
|
||||||
spph.getPackageName())
|
"Use package name '%s' to pull all binary packages", spph.getPackageName()
|
||||||
|
)
|
||||||
name = package
|
name = package
|
||||||
|
|
||||||
# e.g. 'debs' -> 'deb'
|
# e.g. 'debs' -> 'deb'
|
||||||
ext = pull.rstrip('s')
|
ext = pull.rstrip("s")
|
||||||
|
|
||||||
if distro == DISTRO_DEBIAN:
|
if distro == DISTRO_DEBIAN:
|
||||||
# Debian ddebs don't use .ddeb extension, unfortunately :(
|
# Debian ddebs don't use .ddeb extension, unfortunately :(
|
||||||
if pull in [PULL_DEBS, PULL_DDEBS]:
|
if pull in [PULL_DEBS, PULL_DDEBS]:
|
||||||
name = name or '.*'
|
name = name or ".*"
|
||||||
ext = 'deb'
|
ext = "deb"
|
||||||
if pull == PULL_DEBS:
|
if pull == PULL_DEBS:
|
||||||
name += r'(?<!-dbgsym)$'
|
name += r"(?<!-dbgsym)$"
|
||||||
if pull == PULL_DDEBS:
|
if pull == PULL_DDEBS:
|
||||||
name += r'-dbgsym$'
|
name += r"-dbgsym$"
|
||||||
|
|
||||||
# allow DownloadError to flow up to caller
|
# allow DownloadError to flow up to caller
|
||||||
total = srcpkg.pull_binaries(name=name, ext=ext, arch=options['arch'])
|
total = srcpkg.pull_binaries(name=name, ext=ext, arch=options["arch"])
|
||||||
if total < 1:
|
if total < 1:
|
||||||
Logger.error("No %s found for %s %s", pull,
|
Logger.error("No %s found for %s %s", pull, package, spph.getVersion())
|
||||||
package, spph.getVersion())
|
|
||||||
else:
|
else:
|
||||||
Logger.error("Internal error: invalid pull value after parse_pull()")
|
Logger.error("Internal error: invalid pull value after parse_pull()")
|
||||||
raise InvalidPullValueError("Invalid pull value '%s'" % pull)
|
raise InvalidPullValueError(f"Invalid pull value '{pull}'")
|
||||||
|
|
||||||
def pull_upload_queue(self, pull, *,
|
def pull_upload_queue(
|
||||||
package, version=None, arch=None, series=None, pocket=None,
|
self,
|
||||||
status=None, download_only=None, **kwargs):
|
pull,
|
||||||
|
*,
|
||||||
|
package,
|
||||||
|
version=None,
|
||||||
|
arch=None,
|
||||||
|
series=None,
|
||||||
|
pocket=None,
|
||||||
|
status=None,
|
||||||
|
download_only=None,
|
||||||
|
**kwargs,
|
||||||
|
): # pylint: disable=no-self-use,unused-argument
|
||||||
if not series:
|
if not series:
|
||||||
Logger.error("Using --upload-queue requires specifying series")
|
Logger.error("Using --upload-queue requires specifying series")
|
||||||
return
|
return
|
||||||
|
|
||||||
series = Distribution('ubuntu').getSeries(series)
|
series = Distribution("ubuntu").getSeries(series)
|
||||||
|
|
||||||
queueparams = {'name': package}
|
queueparams = {"name": package}
|
||||||
if pocket:
|
if pocket:
|
||||||
queueparams['pocket'] = pocket
|
queueparams["pocket"] = pocket
|
||||||
|
|
||||||
if status == 'all':
|
if status == "all":
|
||||||
queueparams['status'] = None
|
queueparams["status"] = None
|
||||||
queuetype = 'any'
|
queuetype = "any"
|
||||||
elif status:
|
elif status:
|
||||||
queueparams['status'] = status
|
queueparams["status"] = status
|
||||||
queuetype = status
|
queuetype = status
|
||||||
else:
|
else:
|
||||||
queuetype = 'Unapproved'
|
queuetype = "Unapproved"
|
||||||
|
|
||||||
packages = [p for p in series.getPackageUploads(**queueparams) if
|
packages = [
|
||||||
p.package_version == version or
|
p
|
||||||
str(p.id) == version or
|
for p in series.getPackageUploads(**queueparams)
|
||||||
not version]
|
if p.package_version == version or str(p.id) == version or not version
|
||||||
|
]
|
||||||
|
|
||||||
if pull == PULL_SOURCE:
|
if pull == PULL_SOURCE:
|
||||||
packages = [p for p in packages if p.contains_source]
|
packages = [p for p in packages if p.contains_source]
|
||||||
elif pull in VALID_BINARY_PULLS:
|
elif pull in VALID_BINARY_PULLS:
|
||||||
packages = [p for p in packages if
|
packages = [
|
||||||
p.contains_build and
|
p
|
||||||
(arch in ['all', 'any'] or
|
for p in packages
|
||||||
arch in p.display_arches.replace(',', '').split())]
|
if p.contains_build
|
||||||
|
and (arch in ["all", "any"] or arch in p.display_arches.replace(",", "").split())
|
||||||
|
]
|
||||||
|
|
||||||
if not packages:
|
if not packages:
|
||||||
msg = ("Package %s not found in %s upload queue for %s" %
|
msg = f"Package {package} not found in {queuetype} upload queue for {series.name}"
|
||||||
(package, queuetype, series.name))
|
|
||||||
if version:
|
if version:
|
||||||
msg += " with version/id %s" % version
|
msg += f" with version/id {version}"
|
||||||
if pull in VALID_BINARY_PULLS:
|
if pull in VALID_BINARY_PULLS:
|
||||||
msg += " for arch %s" % arch
|
msg += f" for arch {arch}"
|
||||||
raise PackageNotFoundException(msg)
|
raise PackageNotFoundException(msg)
|
||||||
|
|
||||||
if pull == PULL_LIST:
|
if pull == PULL_LIST:
|
||||||
for p in packages:
|
for pkg in packages:
|
||||||
msg = "Found %s %s (ID %s)" % (p.package_name, p.package_version, p.id)
|
msg = f"Found {pkg.package_name} {pkg.package_version} (ID {pkg.id})"
|
||||||
if p.display_arches:
|
if pkg.display_arches:
|
||||||
msg += " arch %s" % p.display_arches
|
msg += f" arch {pkg.display_arches}"
|
||||||
Logger.info(msg)
|
Logger.info(msg)
|
||||||
url = p.changesFileUrl()
|
url = pkg.changesFileUrl()
|
||||||
if url:
|
if url:
|
||||||
Logger.info("Changes file:")
|
Logger.info("Changes file:")
|
||||||
Logger.info(" %s", url)
|
Logger.info(" %s", url)
|
||||||
else:
|
else:
|
||||||
Logger.info("No changes file")
|
Logger.info("No changes file")
|
||||||
urls = p.sourceFileUrls()
|
urls = pkg.sourceFileUrls()
|
||||||
if urls:
|
if urls:
|
||||||
Logger.info("Source files:")
|
Logger.info("Source files:")
|
||||||
for url in urls:
|
for url in urls:
|
||||||
Logger.info(" %s", url)
|
Logger.info(" %s", url)
|
||||||
else:
|
else:
|
||||||
Logger.info("No source files")
|
Logger.info("No source files")
|
||||||
urls = p.binaryFileUrls()
|
urls = pkg.binaryFileUrls()
|
||||||
if urls:
|
if urls:
|
||||||
Logger.info("Binary files:")
|
Logger.info("Binary files:")
|
||||||
for url in urls:
|
for url in urls:
|
||||||
Logger.info(" %s", url)
|
Logger.info(" %s", url)
|
||||||
Logger.info(" { %s }" % p.binaryFileProperties(url))
|
Logger.info(" { %s }", pkg.binaryFileProperties(url))
|
||||||
else:
|
else:
|
||||||
Logger.info("No binary files")
|
Logger.info("No binary files")
|
||||||
urls = p.customFileUrls()
|
urls = pkg.customFileUrls()
|
||||||
if urls:
|
if urls:
|
||||||
Logger.info("Custom files:")
|
Logger.info("Custom files:")
|
||||||
for url in urls:
|
for url in urls:
|
||||||
@ -542,53 +624,58 @@ class PullPkg(object):
|
|||||||
if len(packages) > 1:
|
if len(packages) > 1:
|
||||||
msg = "Found multiple packages"
|
msg = "Found multiple packages"
|
||||||
if version:
|
if version:
|
||||||
msg += " with version %s, please specify the ID instead" % version
|
msg += f" with version {version}, please specify the ID instead"
|
||||||
else:
|
else:
|
||||||
msg += ", please specify the version"
|
msg += ", please specify the version"
|
||||||
Logger.error("Available package versions/ids are:")
|
Logger.error("Available package versions/ids are:")
|
||||||
for p in packages:
|
for pkg in packages:
|
||||||
Logger.error("%s %s (id %s)" % (p.package_name, p.package_version, p.id))
|
Logger.error("%s %s (id %s)", pkg.package_name, pkg.package_version, pkg.id)
|
||||||
raise PackageNotFoundException(msg)
|
raise PackageNotFoundException(msg)
|
||||||
|
|
||||||
p = packages[0]
|
pkg = packages[0]
|
||||||
|
|
||||||
urls = set(p.customFileUrls())
|
urls = set(pkg.customFileUrls())
|
||||||
if p.changesFileUrl():
|
if pkg.changesFileUrl():
|
||||||
urls.add(p.changesFileUrl())
|
urls.add(pkg.changesFileUrl())
|
||||||
|
|
||||||
if pull == PULL_SOURCE:
|
if pull == PULL_SOURCE:
|
||||||
urls |= set(p.sourceFileUrls())
|
urls |= set(pkg.sourceFileUrls())
|
||||||
if not urls:
|
if not urls:
|
||||||
Logger.error("No source files to download")
|
Logger.error("No source files to download")
|
||||||
dscfile = None
|
dscfile = None
|
||||||
for url in urls:
|
for url in urls:
|
||||||
dst = download(url, os.getcwd())
|
dst = download(url, os.getcwd())
|
||||||
if dst.name.endswith('.dsc'):
|
if dst.name.endswith(".dsc"):
|
||||||
dscfile = dst
|
dscfile = dst
|
||||||
if download_only:
|
if download_only:
|
||||||
Logger.debug("--download-only specified, not extracting")
|
Logger.debug("--download-only specified, not extracting")
|
||||||
elif not dscfile:
|
elif not dscfile:
|
||||||
Logger.error("No source dsc file found, cannot extract")
|
Logger.error("No source dsc file found, cannot extract")
|
||||||
else:
|
else:
|
||||||
cmd = ['dpkg-source', '-x', dscfile.name]
|
cmd = ["dpkg-source", "-x", dscfile.name]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
result = subprocess.run(cmd, encoding='utf-8',
|
result = subprocess.run(
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
cmd,
|
||||||
|
check=False,
|
||||||
|
encoding="utf-8",
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
Logger.error('Source unpack failed.')
|
Logger.error("Source unpack failed.")
|
||||||
Logger.debug(result.stdout)
|
Logger.debug(result.stdout)
|
||||||
else:
|
else:
|
||||||
name = '.*'
|
name = ".*"
|
||||||
if pull == PULL_DEBS:
|
if pull == PULL_DEBS:
|
||||||
name = r'{}(?<!-di)(?<!-dbgsym)$'.format(name)
|
name = rf"{name}(?<!-di)(?<!-dbgsym)$"
|
||||||
elif pull == PULL_DDEBS:
|
elif pull == PULL_DDEBS:
|
||||||
name += '-dbgsym$'
|
name += "-dbgsym$"
|
||||||
elif pull == PULL_UDEBS:
|
elif pull == PULL_UDEBS:
|
||||||
name += '-di$'
|
name += "-di$"
|
||||||
else:
|
else:
|
||||||
raise InvalidPullValueError("Invalid pull value %s" % pull)
|
raise InvalidPullValueError(f"Invalid pull value {pull}")
|
||||||
|
|
||||||
urls |= set(p.binaryFileUrls())
|
urls |= set(pkg.binaryFileUrls())
|
||||||
if not urls:
|
if not urls:
|
||||||
Logger.error("No binary files to download")
|
Logger.error("No binary files to download")
|
||||||
for url in urls:
|
for url in urls:
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import tempfile
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
class Question(object):
|
class Question:
|
||||||
def __init__(self, options, show_help=True):
|
def __init__(self, options, show_help=True):
|
||||||
assert len(options) >= 2
|
assert len(options) >= 2
|
||||||
self.options = [s.lower() for s in options]
|
self.options = [s.lower() for s in options]
|
||||||
@ -31,9 +31,9 @@ class Question(object):
|
|||||||
|
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
if len(self.options) == 2:
|
if len(self.options) == 2:
|
||||||
options = self.options[0] + " or " + self.options[1]
|
options = f"{self.options[0]} or {self.options[1]}"
|
||||||
else:
|
else:
|
||||||
options = ", ".join(self.options[:-1]) + ", or " + self.options[-1]
|
options = f"{', '.join(self.options[:-1])}, or {self.options[-1]}"
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def ask(self, question, default=None):
|
def ask(self, question, default=None):
|
||||||
@ -57,7 +57,7 @@ class Question(object):
|
|||||||
try:
|
try:
|
||||||
selected = input(question).strip().lower()
|
selected = input(question).strip().lower()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
print('\nAborting as requested.')
|
print("\nAborting as requested.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if selected == "":
|
if selected == "":
|
||||||
selected = default
|
selected = default
|
||||||
@ -67,7 +67,7 @@ class Question(object):
|
|||||||
if selected == option[0]:
|
if selected == option[0]:
|
||||||
selected = option
|
selected = option
|
||||||
if selected not in self.options:
|
if selected not in self.options:
|
||||||
print("Please answer the question with " + self.get_options() + ".")
|
print(f"Please answer the question with {self.get_options()}.")
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ class YesNoQuestion(Question):
|
|||||||
|
|
||||||
def input_number(question, min_number, max_number, default=None):
|
def input_number(question, min_number, max_number, default=None):
|
||||||
if default:
|
if default:
|
||||||
question += " [%i]? " % (default)
|
question += f" [{default}]? "
|
||||||
else:
|
else:
|
||||||
question += "? "
|
question += "? "
|
||||||
selected = None
|
selected = None
|
||||||
@ -86,7 +86,7 @@ def input_number(question, min_number, max_number, default=None):
|
|||||||
try:
|
try:
|
||||||
selected = input(question).strip()
|
selected = input(question).strip()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
print('\nAborting as requested.')
|
print("\nAborting as requested.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if default and selected == "":
|
if default and selected == "":
|
||||||
selected = default
|
selected = default
|
||||||
@ -94,40 +94,40 @@ def input_number(question, min_number, max_number, default=None):
|
|||||||
try:
|
try:
|
||||||
selected = int(selected)
|
selected = int(selected)
|
||||||
if selected < min_number or selected > max_number:
|
if selected < min_number or selected > max_number:
|
||||||
print("Please input a number between %i and %i." % (min_number, max_number))
|
print(f"Please input a number between {min_number} and {max_number}.")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("Please input a number.")
|
print("Please input a number.")
|
||||||
assert type(selected) == int
|
assert isinstance(selected, int)
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
def confirmation_prompt(message=None, action=None):
|
def confirmation_prompt(message=None, action=None):
|
||||||
'''Display message, or a stock message including action, and wait for the
|
"""Display message, or a stock message including action, and wait for the
|
||||||
user to press Enter
|
user to press Enter
|
||||||
'''
|
"""
|
||||||
if message is None:
|
if message is None:
|
||||||
if action is None:
|
if action is None:
|
||||||
action = 'continue'
|
action = "continue"
|
||||||
message = 'Press [Enter] to %s. Press [Ctrl-C] to abort now.' % action
|
message = f"Press [Enter] to {action}. Press [Ctrl-C] to abort now."
|
||||||
try:
|
try:
|
||||||
input(message)
|
input(message)
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
print('\nAborting as requested.')
|
print("\nAborting as requested.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
class EditFile(object):
|
class EditFile:
|
||||||
def __init__(self, filename, description, placeholders=None):
|
def __init__(self, filename, description, placeholders=None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.description = description
|
self.description = description
|
||||||
if placeholders is None:
|
if placeholders is None:
|
||||||
placeholders = (re.compile(r'^>>>.*<<<$', re.UNICODE),)
|
placeholders = (re.compile(r"^>>>.*<<<$", re.UNICODE),)
|
||||||
self.placeholders = placeholders
|
self.placeholders = placeholders
|
||||||
|
|
||||||
def edit(self, optional=False):
|
def edit(self, optional=False):
|
||||||
if optional:
|
if optional:
|
||||||
print("\n\nCurrently the %s looks like:" % self.description)
|
print(f"\n\nCurrently the {self.description} looks like:")
|
||||||
with open(self.filename, 'r', encoding='utf-8') as f:
|
with open(self.filename, "r", encoding="utf-8") as f:
|
||||||
print(f.read())
|
print(f.read())
|
||||||
if YesNoQuestion().ask("Edit", "no") == "no":
|
if YesNoQuestion().ask("Edit", "no") == "no":
|
||||||
return
|
return
|
||||||
@ -135,68 +135,65 @@ class EditFile(object):
|
|||||||
done = False
|
done = False
|
||||||
while not done:
|
while not done:
|
||||||
old_mtime = os.stat(self.filename).st_mtime
|
old_mtime = os.stat(self.filename).st_mtime
|
||||||
subprocess.check_call(['sensible-editor', self.filename])
|
subprocess.check_call(["sensible-editor", self.filename])
|
||||||
modified = old_mtime != os.stat(self.filename).st_mtime
|
modified = old_mtime != os.stat(self.filename).st_mtime
|
||||||
placeholders_present = False
|
placeholders_present = False
|
||||||
if self.placeholders:
|
if self.placeholders:
|
||||||
with open(self.filename, 'r', encoding='utf-8') as f:
|
with open(self.filename, "r", encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
for placeholder in self.placeholders:
|
for placeholder in self.placeholders:
|
||||||
if placeholder.search(line.strip()):
|
if placeholder.search(line.strip()):
|
||||||
placeholders_present = True
|
placeholders_present = True
|
||||||
|
|
||||||
if placeholders_present:
|
if placeholders_present:
|
||||||
print("Placeholders still present in the %s. "
|
print(
|
||||||
"Please replace them with useful information."
|
f"Placeholders still present in the {self.description}. "
|
||||||
% self.description)
|
f"Please replace them with useful information."
|
||||||
confirmation_prompt(action='edit again')
|
)
|
||||||
|
confirmation_prompt(action="edit again")
|
||||||
elif not modified:
|
elif not modified:
|
||||||
print("The %s was not modified" % self.description)
|
print(f"The {self.description} was not modified")
|
||||||
if YesNoQuestion().ask("Edit again", "yes") == "no":
|
if YesNoQuestion().ask("Edit again", "yes") == "no":
|
||||||
done = True
|
done = True
|
||||||
elif self.check_edit():
|
elif self.check_edit():
|
||||||
done = True
|
done = True
|
||||||
|
|
||||||
def check_edit(self):
|
def check_edit(self): # pylint: disable=no-self-use
|
||||||
'''Override this to implement extra checks on the edited report.
|
"""Override this to implement extra checks on the edited report.
|
||||||
Should return False if another round of editing is needed,
|
Should return False if another round of editing is needed,
|
||||||
and should prompt the user to confirm that, if necessary.
|
and should prompt the user to confirm that, if necessary.
|
||||||
'''
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class EditBugReport(EditFile):
|
class EditBugReport(EditFile):
|
||||||
split_re = re.compile(r'^Summary.*?:\s+(.*?)\s+'
|
split_re = re.compile(r"^Summary.*?:\s+(.*?)\s+Description:\s+(.*)$", re.DOTALL | re.UNICODE)
|
||||||
r'Description:\s+(.*)$',
|
|
||||||
re.DOTALL | re.UNICODE)
|
|
||||||
|
|
||||||
def __init__(self, subject, body, placeholders=None):
|
def __init__(self, subject, body, placeholders=None):
|
||||||
prefix = os.path.basename(sys.argv[0]) + '_'
|
prefix = f"{os.path.basename(sys.argv[0])}_"
|
||||||
tmpfile = tempfile.NamedTemporaryFile(prefix=prefix, suffix='.txt',
|
tmpfile = tempfile.NamedTemporaryFile(prefix=prefix, suffix=".txt", delete=False)
|
||||||
delete=False)
|
tmpfile.write((f"Summary (one line):\n{subject}\n\nDescription:\n{body}").encode("utf-8"))
|
||||||
tmpfile.write((u'Summary (one line):\n%s\n\nDescription:\n%s'
|
|
||||||
% (subject, body)).encode('utf-8'))
|
|
||||||
tmpfile.close()
|
tmpfile.close()
|
||||||
super(EditBugReport, self).__init__(tmpfile.name, 'bug report',
|
super().__init__(tmpfile.name, "bug report", placeholders)
|
||||||
placeholders)
|
|
||||||
|
|
||||||
def check_edit(self):
|
def check_edit(self):
|
||||||
with open(self.filename, 'r', encoding='utf-8') as f:
|
with open(self.filename, "r", encoding="utf-8") as f:
|
||||||
report = f.read()
|
report = f.read()
|
||||||
|
|
||||||
if self.split_re.match(report) is None:
|
if self.split_re.match(report) is None:
|
||||||
print("The %s doesn't start with 'Summary:' and 'Description:' "
|
print(
|
||||||
"blocks" % self.description)
|
f"The {self.description} doesn't start with 'Summary:' and 'Description:' blocks"
|
||||||
confirmation_prompt('edit again')
|
)
|
||||||
|
confirmation_prompt("edit again")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_report(self):
|
def get_report(self):
|
||||||
with open(self.filename, 'r', encoding='utf-8') as f:
|
with open(self.filename, "r", encoding="utf-8") as f:
|
||||||
report = f.read()
|
report = f.read()
|
||||||
|
|
||||||
match = self.split_re.match(report)
|
match = self.split_re.match(report)
|
||||||
title = match.group(1).replace(u'\n', u' ')
|
title = match.group(1).replace("\n", " ")
|
||||||
report = (title, match.group(2))
|
report = (title, match.group(2))
|
||||||
os.unlink(self.filename)
|
os.unlink(self.filename)
|
||||||
return report
|
return report
|
||||||
|
@ -22,13 +22,12 @@ class RDependsException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def query_rdepends(package, release, arch,
|
def query_rdepends(package, release, arch, server="http://qa.ubuntuwire.org/rdepends"):
|
||||||
server='http://qa.ubuntuwire.org/rdepends'):
|
|
||||||
"""Look up a packages reverse-dependencies on the Ubuntuwire
|
"""Look up a packages reverse-dependencies on the Ubuntuwire
|
||||||
Reverse- webservice
|
Reverse- webservice
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = os.path.join(server, 'v1', release, arch, package)
|
url = os.path.join(server, "v1", release, arch, package)
|
||||||
|
|
||||||
response, data = httplib2.Http().request(url)
|
response, data = httplib2.Http().request(url)
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
# Please see the /usr/share/common-licenses/GPL-2 file for the full text
|
# Please see the /usr/share/common-licenses/GPL-2 file for the full text
|
||||||
# of the GNU General Public License license.
|
# of the GNU General Public License license.
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from debian.deb822 import Changes
|
from debian.deb822 import Changes
|
||||||
@ -27,16 +28,19 @@ from distro_info import DebianDistroInfo, DistroDataOutdated
|
|||||||
from httplib2 import Http, HttpLib2Error
|
from httplib2 import Http, HttpLib2Error
|
||||||
|
|
||||||
from ubuntutools.lp import udtexceptions
|
from ubuntutools.lp import udtexceptions
|
||||||
from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam,
|
from ubuntutools.lp.lpapicache import (
|
||||||
DistributionSourcePackage)
|
Distribution,
|
||||||
|
DistributionSourcePackage,
|
||||||
|
Launchpad,
|
||||||
|
PersonTeam,
|
||||||
|
)
|
||||||
from ubuntutools.question import confirmation_prompt
|
from ubuntutools.question import confirmation_prompt
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_debian_srcpkg(name, release):
|
def get_debian_srcpkg(name, release):
|
||||||
debian = Distribution('debian')
|
debian = Distribution("debian")
|
||||||
debian_archive = debian.getArchive()
|
debian_archive = debian.getArchive()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -47,82 +51,83 @@ def get_debian_srcpkg(name, release):
|
|||||||
return debian_archive.getSourcePackage(name, release)
|
return debian_archive.getSourcePackage(name, release)
|
||||||
|
|
||||||
|
|
||||||
def get_ubuntu_srcpkg(name, release, pocket='Release'):
|
def get_ubuntu_srcpkg(name, release, pocket="Release"):
|
||||||
ubuntu = Distribution('ubuntu')
|
ubuntu = Distribution("ubuntu")
|
||||||
ubuntu_archive = ubuntu.getArchive()
|
ubuntu_archive = ubuntu.getArchive()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return ubuntu_archive.getSourcePackage(name, release, pocket)
|
return ubuntu_archive.getSourcePackage(name, release, pocket)
|
||||||
except udtexceptions.PackageNotFoundException:
|
except udtexceptions.PackageNotFoundException:
|
||||||
if pocket != 'Release':
|
if pocket != "Release":
|
||||||
parent_pocket = 'Release'
|
parent_pocket = "Release"
|
||||||
if pocket == 'Updates':
|
if pocket == "Updates":
|
||||||
parent_pocket = 'Proposed'
|
parent_pocket = "Proposed"
|
||||||
return get_ubuntu_srcpkg(name, release, parent_pocket)
|
return get_ubuntu_srcpkg(name, release, parent_pocket)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def need_sponsorship(name, component, release):
|
def need_sponsorship(name, component, release):
|
||||||
'''
|
"""
|
||||||
Check if the user has upload permissions for either the package
|
Check if the user has upload permissions for either the package
|
||||||
itself or the component
|
itself or the component
|
||||||
'''
|
"""
|
||||||
archive = Distribution('ubuntu').getArchive()
|
archive = Distribution("ubuntu").getArchive()
|
||||||
distroseries = Distribution('ubuntu').getSeries(release)
|
distroseries = Distribution("ubuntu").getSeries(release)
|
||||||
|
|
||||||
need_sponsor = not PersonTeam.me.canUploadPackage(archive, distroseries,
|
need_sponsor = not PersonTeam.me.canUploadPackage(archive, distroseries, name, component)
|
||||||
name, component)
|
|
||||||
if need_sponsor:
|
if need_sponsor:
|
||||||
print('''You are not able to upload this package directly to Ubuntu.
|
print(
|
||||||
|
"""You are not able to upload this package directly to Ubuntu.
|
||||||
Your sync request shall require an approval by a member of the appropriate
|
Your sync request shall require an approval by a member of the appropriate
|
||||||
sponsorship team, who shall be subscribed to this bug report.
|
sponsorship team, who shall be subscribed to this bug report.
|
||||||
This must be done before it can be processed by a member of the Ubuntu Archive
|
This must be done before it can be processed by a member of the Ubuntu Archive
|
||||||
team.''')
|
team."""
|
||||||
|
)
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
return need_sponsor
|
return need_sponsor
|
||||||
|
|
||||||
|
|
||||||
def check_existing_reports(srcpkg):
|
def check_existing_reports(srcpkg):
|
||||||
'''
|
"""
|
||||||
Check existing bug reports on Launchpad for a possible sync request.
|
Check existing bug reports on Launchpad for a possible sync request.
|
||||||
|
|
||||||
If found ask for confirmation on filing a request.
|
If found ask for confirmation on filing a request.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Fetch the package's bug list from Launchpad
|
# Fetch the package's bug list from Launchpad
|
||||||
pkg = Distribution('ubuntu').getSourcePackage(name=srcpkg)
|
pkg = Distribution("ubuntu").getSourcePackage(name=srcpkg)
|
||||||
pkg_bug_list = pkg.searchTasks(status=["Incomplete", "New", "Confirmed",
|
pkg_bug_list = pkg.searchTasks(
|
||||||
"Triaged", "In Progress",
|
status=["Incomplete", "New", "Confirmed", "Triaged", "In Progress", "Fix Committed"],
|
||||||
"Fix Committed"],
|
omit_duplicates=True,
|
||||||
omit_duplicates=True)
|
)
|
||||||
|
|
||||||
# Search bug list for other sync requests.
|
# Search bug list for other sync requests.
|
||||||
for bug in pkg_bug_list:
|
for bug in pkg_bug_list:
|
||||||
# check for Sync or sync and the package name
|
# check for Sync or sync and the package name
|
||||||
if not bug.is_complete and 'ync %s' % srcpkg in bug.title:
|
if not bug.is_complete and f"ync {srcpkg}" in bug.title:
|
||||||
print('The following bug could be a possible duplicate sync bug '
|
print(
|
||||||
'on Launchpad:\n'
|
f"The following bug could be a possible duplicate sync bug on Launchpad:\n"
|
||||||
' * %s (%s)\n'
|
f" * {bug.title} ({bug.web_link})\n"
|
||||||
'Please check the above URL to verify this before '
|
f"Please check the above URL to verify this before continuing."
|
||||||
'continuing.'
|
)
|
||||||
% (bug.title, bug.web_link))
|
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
|
|
||||||
def get_ubuntu_delta_changelog(srcpkg):
|
def get_ubuntu_delta_changelog(srcpkg):
|
||||||
'''
|
"""
|
||||||
Download the Ubuntu changelog and extract the entries since the last sync
|
Download the Ubuntu changelog and extract the entries since the last sync
|
||||||
from Debian.
|
from Debian.
|
||||||
'''
|
"""
|
||||||
archive = Distribution('ubuntu').getArchive()
|
archive = Distribution("ubuntu").getArchive()
|
||||||
spph = archive.getPublishedSources(source_name=srcpkg.getPackageName(),
|
spph = archive.getPublishedSources(
|
||||||
exact_match=True, pocket='Release')
|
source_name=srcpkg.getPackageName(), exact_match=True, pocket="Release"
|
||||||
|
)
|
||||||
debian_info = DebianDistroInfo()
|
debian_info = DebianDistroInfo()
|
||||||
topline = re.compile(r'^(\w%(name_chars)s*) \(([^\(\) \t]+)\)'
|
name_chars = "[-+0-9a-z.]"
|
||||||
r'((\s+%(name_chars)s+)+)\;'
|
topline = re.compile(
|
||||||
% {'name_chars': '[-+0-9a-z.]'},
|
rf"^(\w%({name_chars})s*) \(([^\(\) \t]+)\)((\s+%({name_chars})s+)+)\;", re.IGNORECASE
|
||||||
re.IGNORECASE)
|
)
|
||||||
delta = []
|
delta = []
|
||||||
for record in spph:
|
for record in spph:
|
||||||
changes_url = record.changesFileUrl()
|
changes_url = record.changesFileUrl()
|
||||||
@ -130,61 +135,57 @@ def get_ubuntu_delta_changelog(srcpkg):
|
|||||||
# Native sync
|
# Native sync
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
response, body = Http().request(changes_url)
|
response = Http().request(changes_url)[0]
|
||||||
except HttpLib2Error as e:
|
except HttpLib2Error as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
break
|
break
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
Logger.error("%s: %s %s", changes_url, response.status,
|
Logger.error("%s: %s %s", changes_url, response.status, response.reason)
|
||||||
response.reason)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
changes = Changes(Http().request(changes_url)[1])
|
changes = Changes(Http().request(changes_url)[1])
|
||||||
for line in changes['Changes'].splitlines():
|
for line in changes["Changes"].splitlines():
|
||||||
line = line[1:]
|
line = line[1:]
|
||||||
m = topline.match(line)
|
match = topline.match(line)
|
||||||
if m:
|
if match:
|
||||||
distribution = m.group(3).split()[0].split('-')[0]
|
distribution = match.group(3).split()[0].split("-")[0]
|
||||||
if debian_info.valid(distribution):
|
if debian_info.valid(distribution):
|
||||||
break
|
break
|
||||||
if line.startswith(u' '):
|
if line.startswith(" "):
|
||||||
delta.append(line)
|
delta.append(line)
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
return '\n'.join(delta)
|
return "\n".join(delta)
|
||||||
|
|
||||||
|
|
||||||
def post_bug(srcpkg, subscribe, status, bugtitle, bugtext):
|
def post_bug(srcpkg, subscribe, status, bugtitle, bugtext):
|
||||||
'''
|
"""
|
||||||
Use the LP API to file the sync request.
|
Use the LP API to file the sync request.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
print('The final report is:\nSummary: %s\nDescription:\n%s\n'
|
print(f"The final report is:\nSummary: {bugtitle}\nDescription:\n{bugtext}\n")
|
||||||
% (bugtitle, bugtext))
|
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
if srcpkg:
|
if srcpkg:
|
||||||
bug_target = DistributionSourcePackage(
|
# pylint: disable=protected-access
|
||||||
'%subuntu/+source/%s' % (Launchpad._root_uri, srcpkg))
|
bug_target = DistributionSourcePackage(f"{Launchpad._root_uri}ubuntu/+source/{srcpkg}")
|
||||||
else:
|
else:
|
||||||
# new source package
|
# new source package
|
||||||
bug_target = Distribution('ubuntu')
|
bug_target = Distribution("ubuntu")
|
||||||
|
|
||||||
# create bug
|
# create bug
|
||||||
bug = Launchpad.bugs.createBug(title=bugtitle, description=bugtext,
|
bug = Launchpad.bugs.createBug(title=bugtitle, description=bugtext, target=bug_target())
|
||||||
target=bug_target())
|
|
||||||
|
|
||||||
# newly created bugreports have only one task
|
# newly created bugreports have only one task
|
||||||
task = bug.bug_tasks[0]
|
task = bug.bug_tasks[0]
|
||||||
# only members of ubuntu-bugcontrol can set importance
|
# only members of ubuntu-bugcontrol can set importance
|
||||||
if PersonTeam.me.isLpTeamMember('ubuntu-bugcontrol'):
|
if PersonTeam.me.isLpTeamMember("ubuntu-bugcontrol"):
|
||||||
task.importance = 'Wishlist'
|
task.importance = "Wishlist"
|
||||||
task.status = status
|
task.status = status
|
||||||
task.lp_save()
|
task.lp_save()
|
||||||
|
|
||||||
bug.subscribe(person=PersonTeam(subscribe)())
|
bug.subscribe(person=PersonTeam(subscribe)())
|
||||||
|
|
||||||
print('Sync request filed as bug #%i: %s'
|
print(f"Sync request filed as bug #{bug.id}: {bug.web_link}")
|
||||||
% (bug.id, bug.web_link))
|
|
||||||
|
@ -20,12 +20,13 @@
|
|||||||
# Please see the /usr/share/common-licenses/GPL-2 file for the full text
|
# Please see the /usr/share/common-licenses/GPL-2 file for the full text
|
||||||
# of the GNU General Public License license.
|
# of the GNU General Public License license.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from debian.changelog import Changelog
|
from debian.changelog import Changelog
|
||||||
@ -33,19 +34,18 @@ from distro_info import DebianDistroInfo, DistroDataOutdated
|
|||||||
|
|
||||||
from ubuntutools.archive import DebianSourcePackage, UbuntuSourcePackage
|
from ubuntutools.archive import DebianSourcePackage, UbuntuSourcePackage
|
||||||
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
from ubuntutools.lp.udtexceptions import PackageNotFoundException
|
||||||
from ubuntutools.question import confirmation_prompt, YesNoQuestion
|
from ubuntutools.question import YesNoQuestion, confirmation_prompt
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'get_debian_srcpkg',
|
"get_debian_srcpkg",
|
||||||
'get_ubuntu_srcpkg',
|
"get_ubuntu_srcpkg",
|
||||||
'need_sponsorship',
|
"need_sponsorship",
|
||||||
'check_existing_reports',
|
"check_existing_reports",
|
||||||
'get_ubuntu_delta_changelog',
|
"get_ubuntu_delta_changelog",
|
||||||
'mail_bug',
|
"mail_bug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -67,73 +67,86 @@ def get_ubuntu_srcpkg(name, release):
|
|||||||
|
|
||||||
|
|
||||||
def need_sponsorship(name, component, release):
|
def need_sponsorship(name, component, release):
|
||||||
'''
|
"""
|
||||||
Ask the user if he has upload permissions for the package or the
|
Ask the user if he has upload permissions for the package or the
|
||||||
component.
|
component.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
val = YesNoQuestion().ask("Do you have upload permissions for the '%s' component or "
|
val = YesNoQuestion().ask(
|
||||||
"the package '%s' in Ubuntu %s?\nIf in doubt answer 'n'." %
|
f"Do you have upload permissions for the '{component}' component or "
|
||||||
(component, name, release), 'no')
|
f"the package '{name}' in Ubuntu {release}?\nIf in doubt answer 'n'.",
|
||||||
return val == 'no'
|
"no",
|
||||||
|
)
|
||||||
|
return val == "no"
|
||||||
|
|
||||||
|
|
||||||
def check_existing_reports(srcpkg):
|
def check_existing_reports(srcpkg):
|
||||||
'''
|
"""
|
||||||
Point the user to the URL to manually check for duplicate bug reports.
|
Point the user to the URL to manually check for duplicate bug reports.
|
||||||
'''
|
"""
|
||||||
print('Please check on '
|
print(
|
||||||
'https://bugs.launchpad.net/ubuntu/+source/%s/+bugs\n'
|
f"Please check on https://bugs.launchpad.net/ubuntu/+source/{srcpkg}/+bugs\n"
|
||||||
'for duplicate sync requests before continuing.' % srcpkg)
|
f"for duplicate sync requests before continuing."
|
||||||
|
)
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
|
|
||||||
def get_ubuntu_delta_changelog(srcpkg):
|
def get_ubuntu_delta_changelog(srcpkg):
|
||||||
'''
|
"""
|
||||||
Download the Ubuntu changelog and extract the entries since the last sync
|
Download the Ubuntu changelog and extract the entries since the last sync
|
||||||
from Debian.
|
from Debian.
|
||||||
'''
|
"""
|
||||||
changelog = Changelog(srcpkg.getChangelog())
|
changelog = Changelog(srcpkg.getChangelog())
|
||||||
if changelog is None:
|
if changelog is None:
|
||||||
return ''
|
return ""
|
||||||
delta = []
|
delta = []
|
||||||
debian_info = DebianDistroInfo()
|
debian_info = DebianDistroInfo()
|
||||||
for block in changelog:
|
for block in changelog:
|
||||||
distribution = block.distributions.split()[0].split('-')[0]
|
distribution = block.distributions.split()[0].split("-")[0]
|
||||||
if debian_info.valid(distribution):
|
if debian_info.valid(distribution):
|
||||||
break
|
break
|
||||||
delta += [str(change) for change in block.changes()
|
delta += [str(change) for change in block.changes() if change.strip()]
|
||||||
if change.strip()]
|
|
||||||
|
|
||||||
return '\n'.join(delta)
|
return "\n".join(delta)
|
||||||
|
|
||||||
|
|
||||||
def mail_bug(srcpkg, subscribe, status, bugtitle, bugtext, bug_mail_domain,
|
def mail_bug(
|
||||||
keyid, myemailaddr, mailserver_host, mailserver_port,
|
srcpkg,
|
||||||
mailserver_user, mailserver_pass):
|
subscribe,
|
||||||
'''
|
status,
|
||||||
|
bugtitle,
|
||||||
|
bugtext,
|
||||||
|
bug_mail_domain,
|
||||||
|
keyid,
|
||||||
|
myemailaddr,
|
||||||
|
mailserver_host,
|
||||||
|
mailserver_port,
|
||||||
|
mailserver_user,
|
||||||
|
mailserver_pass,
|
||||||
|
):
|
||||||
|
"""
|
||||||
Submit the sync request per email.
|
Submit the sync request per email.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
to = 'new@' + bug_mail_domain
|
to = f"new@{bug_mail_domain}"
|
||||||
|
|
||||||
# generate mailbody
|
# generate mailbody
|
||||||
if srcpkg:
|
if srcpkg:
|
||||||
mailbody = ' affects ubuntu/%s\n' % srcpkg
|
mailbody = f" affects ubuntu/{srcpkg}\n"
|
||||||
else:
|
else:
|
||||||
mailbody = ' affects ubuntu\n'
|
mailbody = " affects ubuntu\n"
|
||||||
mailbody += '''\
|
mailbody += f"""\
|
||||||
status %s
|
status {status}
|
||||||
importance wishlist
|
importance wishlist
|
||||||
subscribe %s
|
subscribe {subscribe}
|
||||||
done
|
done
|
||||||
|
|
||||||
%s''' % (status, subscribe, bugtext)
|
{bugtext}"""
|
||||||
|
|
||||||
# prepare sign command
|
# prepare sign command
|
||||||
gpg_command = None
|
gpg_command = None
|
||||||
for cmd in ('gnome-gpg', 'gpg2', 'gpg'):
|
for cmd in ("gnome-gpg", "gpg2", "gpg"):
|
||||||
if os.access('/usr/bin/%s' % cmd, os.X_OK):
|
if os.access(f"/usr/bin/{cmd}", os.X_OK):
|
||||||
gpg_command = [cmd]
|
gpg_command = [cmd]
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -141,107 +154,130 @@ def mail_bug(srcpkg, subscribe, status, bugtitle, bugtext, bug_mail_domain,
|
|||||||
Logger.error("Cannot locate gpg, please install the 'gnupg' package!")
|
Logger.error("Cannot locate gpg, please install the 'gnupg' package!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
gpg_command.append('--clearsign')
|
gpg_command.append("--clearsign")
|
||||||
if keyid:
|
if keyid:
|
||||||
gpg_command.extend(('-u', keyid))
|
gpg_command.extend(("-u", keyid))
|
||||||
|
|
||||||
# sign the mail body
|
# sign the mail body
|
||||||
gpg = subprocess.Popen(
|
gpg = subprocess.Popen(
|
||||||
gpg_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
gpg_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding="utf-8"
|
||||||
encoding='utf-8')
|
)
|
||||||
signed_report = gpg.communicate(mailbody)[0]
|
signed_report = gpg.communicate(mailbody)[0]
|
||||||
if gpg.returncode != 0:
|
if gpg.returncode != 0:
|
||||||
Logger.error("%s failed.", gpg_command[0])
|
Logger.error("%s failed.", gpg_command[0])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# generate email
|
# generate email
|
||||||
mail = '''\
|
mail = f"""\
|
||||||
From: %s
|
From: {myemailaddr}
|
||||||
To: %s
|
To: {to}
|
||||||
Subject: %s
|
Subject: {bugtitle}
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
|
||||||
%s''' % (myemailaddr, to, bugtitle, signed_report)
|
{signed_report}"""
|
||||||
|
|
||||||
print('The final report is:\n%s' % mail)
|
print(f"The final report is:\n{mail}")
|
||||||
confirmation_prompt()
|
confirmation_prompt()
|
||||||
|
|
||||||
# save mail in temporary file
|
# save mail in temporary file
|
||||||
backup = tempfile.NamedTemporaryFile(
|
backup = tempfile.NamedTemporaryFile(
|
||||||
mode='w',
|
mode="w",
|
||||||
delete=False,
|
delete=False,
|
||||||
prefix='requestsync-' + re.sub(r'[^a-zA-Z0-9_-]', '', bugtitle.replace(' ', '_'))
|
prefix=f"requestsync-{re.sub('[^a-zA-Z0-9_-]', '', bugtitle.replace(' ', '_'))}",
|
||||||
)
|
)
|
||||||
with backup:
|
with backup:
|
||||||
backup.write(mail)
|
backup.write(mail)
|
||||||
|
|
||||||
Logger.info('The e-mail has been saved in %s and will be deleted '
|
Logger.info(
|
||||||
'after succesful transmission', backup.name)
|
"The e-mail has been saved in %s and will be deleted after succesful transmission",
|
||||||
|
backup.name,
|
||||||
|
)
|
||||||
|
|
||||||
# connect to the server
|
# connect to the server
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
Logger.info('Connecting to %s:%s ...', mailserver_host,
|
Logger.info("Connecting to %s:%s ...", mailserver_host, mailserver_port)
|
||||||
mailserver_port)
|
smtp = smtplib.SMTP(mailserver_host, mailserver_port)
|
||||||
s = smtplib.SMTP(mailserver_host, mailserver_port)
|
|
||||||
break
|
break
|
||||||
except smtplib.SMTPConnectError as s:
|
except smtplib.SMTPConnectError as error:
|
||||||
try:
|
try:
|
||||||
# py2 path
|
# py2 path
|
||||||
# pylint: disable=unsubscriptable-object
|
# pylint: disable=unsubscriptable-object
|
||||||
Logger.error('Could not connect to %s:%s: %s (%i)',
|
Logger.error(
|
||||||
mailserver_host, mailserver_port, s[1], s[0])
|
"Could not connect to %s:%s: %s (%i)",
|
||||||
|
mailserver_host,
|
||||||
|
mailserver_port,
|
||||||
|
error[1],
|
||||||
|
error[0],
|
||||||
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
Logger.error('Could not connect to %s:%s: %s (%i)',
|
Logger.error(
|
||||||
mailserver_host, mailserver_port, s.strerror, s.errno)
|
"Could not connect to %s:%s: %s (%i)",
|
||||||
if s.smtp_code == 421:
|
mailserver_host,
|
||||||
confirmation_prompt(message='This is a temporary error, press [Enter] '
|
mailserver_port,
|
||||||
'to retry. Press [Ctrl-C] to abort now.')
|
error.strerror,
|
||||||
except socket.error as s:
|
error.errno,
|
||||||
|
)
|
||||||
|
if error.smtp_code == 421:
|
||||||
|
confirmation_prompt(
|
||||||
|
message="This is a temporary error, press [Enter] "
|
||||||
|
"to retry. Press [Ctrl-C] to abort now."
|
||||||
|
)
|
||||||
|
except socket.error as error:
|
||||||
try:
|
try:
|
||||||
# py2 path
|
# py2 path
|
||||||
# pylint: disable=unsubscriptable-object
|
# pylint: disable=unsubscriptable-object
|
||||||
Logger.error('Could not connect to %s:%s: %s (%i)',
|
Logger.error(
|
||||||
mailserver_host, mailserver_port, s[1], s[0])
|
"Could not connect to %s:%s: %s (%i)",
|
||||||
|
mailserver_host,
|
||||||
|
mailserver_port,
|
||||||
|
error[1],
|
||||||
|
error[0],
|
||||||
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
Logger.error('Could not connect to %s:%s: %s (%i)',
|
Logger.error(
|
||||||
mailserver_host, mailserver_port, s.strerror, s.errno)
|
"Could not connect to %s:%s: %s (%i)",
|
||||||
|
mailserver_host,
|
||||||
|
mailserver_port,
|
||||||
|
error.strerror,
|
||||||
|
error.errno,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if mailserver_user and mailserver_pass:
|
if mailserver_user and mailserver_pass:
|
||||||
try:
|
try:
|
||||||
s.login(mailserver_user, mailserver_pass)
|
smtp.login(mailserver_user, mailserver_pass)
|
||||||
except smtplib.SMTPAuthenticationError:
|
except smtplib.SMTPAuthenticationError:
|
||||||
Logger.error('Error authenticating to the server: '
|
Logger.error("Error authenticating to the server: invalid username and password.")
|
||||||
'invalid username and password.')
|
smtp.quit()
|
||||||
s.quit()
|
|
||||||
return
|
return
|
||||||
except smtplib.SMTPException:
|
except smtplib.SMTPException:
|
||||||
Logger.error('Unknown SMTP error.')
|
Logger.error("Unknown SMTP error.")
|
||||||
s.quit()
|
smtp.quit()
|
||||||
return
|
return
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
s.sendmail(myemailaddr, to, mail.encode('utf-8'))
|
smtp.sendmail(myemailaddr, to, mail.encode("utf-8"))
|
||||||
s.quit()
|
smtp.quit()
|
||||||
os.remove(backup.name)
|
os.remove(backup.name)
|
||||||
Logger.info('Sync request mailed.')
|
Logger.info("Sync request mailed.")
|
||||||
break
|
break
|
||||||
except smtplib.SMTPRecipientsRefused as smtperror:
|
except smtplib.SMTPRecipientsRefused as smtperror:
|
||||||
smtp_code, smtp_message = smtperror.recipients[to]
|
smtp_code, smtp_message = smtperror.recipients[to]
|
||||||
Logger.error('Error while sending: %i, %s', smtp_code, smtp_message)
|
Logger.error("Error while sending: %i, %s", smtp_code, smtp_message)
|
||||||
if smtp_code == 450:
|
if smtp_code == 450:
|
||||||
confirmation_prompt(message='This is a temporary error, press [Enter] '
|
confirmation_prompt(
|
||||||
'to retry. Press [Ctrl-C] to abort now.')
|
message="This is a temporary error, press [Enter] "
|
||||||
|
"to retry. Press [Ctrl-C] to abort now."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
except smtplib.SMTPResponseException as e:
|
except smtplib.SMTPResponseException as error:
|
||||||
Logger.error('Error while sending: %i, %s',
|
Logger.error("Error while sending: %i, %s", error.smtp_code, error.smtp_error)
|
||||||
e.smtp_code, e.smtp_error)
|
|
||||||
return
|
return
|
||||||
except smtplib.SMTPServerDisconnected:
|
except smtplib.SMTPServerDisconnected:
|
||||||
Logger.error('Server disconnected while sending the mail.')
|
Logger.error("Server disconnected while sending the mail.")
|
||||||
return
|
return
|
||||||
|
95
ubuntutools/running_autopkgtests.py
Normal file
95
ubuntutools/running_autopkgtests.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Copyright (C) 2024 Canonical Ltd.
|
||||||
|
# Author: Chris Peterson <chris.peterson@canonical.com>
|
||||||
|
# Author: Andy P. Whitcroft
|
||||||
|
# Author: Christian Ehrhardt
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 3, as published
|
||||||
|
# by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||||
|
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import urllib
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
URL_RUNNING = "http://autopkgtest.ubuntu.com/static/running.json"
|
||||||
|
URL_QUEUED = "http://autopkgtest.ubuntu.com/queues.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_jobs(url: str) -> dict:
|
||||||
|
request = urllib.request.Request(url, headers={"Cache-Control": "max-age-0"})
|
||||||
|
with urllib.request.urlopen(request) as response:
|
||||||
|
data = response.read()
|
||||||
|
jobs = json.loads(data.decode("utf-8"))
|
||||||
|
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
|
def get_running():
|
||||||
|
jobs = _get_jobs(URL_RUNNING)
|
||||||
|
|
||||||
|
running = []
|
||||||
|
for pkg in jobs:
|
||||||
|
for handle in jobs[pkg]:
|
||||||
|
for series in jobs[pkg][handle]:
|
||||||
|
for arch in jobs[pkg][handle][series]:
|
||||||
|
jobinfo = jobs[pkg][handle][series][arch]
|
||||||
|
triggers = ",".join(jobinfo[0].get("triggers", "-"))
|
||||||
|
ppas = ",".join(jobinfo[0].get("ppas", "-"))
|
||||||
|
time = jobinfo[1]
|
||||||
|
env = jobinfo[0].get("env", "-")
|
||||||
|
time = str(datetime.timedelta(seconds=jobinfo[1]))
|
||||||
|
try:
|
||||||
|
line = (
|
||||||
|
f"R {time:6} {pkg:30} {'-':10} {series:8} {arch:8}"
|
||||||
|
f" {ppas:31} {triggers} {env}\n"
|
||||||
|
)
|
||||||
|
running.append((jobinfo[1], line))
|
||||||
|
except BrokenPipeError:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
for time, row in sorted(running, reverse=True):
|
||||||
|
output += f"{row}"
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_queued():
|
||||||
|
queues = _get_jobs(URL_QUEUED)
|
||||||
|
output = ""
|
||||||
|
for origin in queues:
|
||||||
|
for series in queues[origin]:
|
||||||
|
for arch in queues[origin][series]:
|
||||||
|
n = 0
|
||||||
|
for key in queues[origin][series][arch]:
|
||||||
|
if key == "private job":
|
||||||
|
pkg = triggers = ppas = "private job"
|
||||||
|
else:
|
||||||
|
(pkg, json_data) = key.split(maxsplit=1)
|
||||||
|
try:
|
||||||
|
jobinfo = json.loads(json_data)
|
||||||
|
triggers = ",".join(jobinfo.get("triggers", "-"))
|
||||||
|
ppas = ",".join(jobinfo.get("ppas", "-"))
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
pkg = triggers = ppas = "failed to parse"
|
||||||
|
continue
|
||||||
|
|
||||||
|
n = n + 1
|
||||||
|
try:
|
||||||
|
output += (
|
||||||
|
f"Q{n:04d} {'-:--':>6} {pkg:30} {origin:10} {series:8} {arch:8}"
|
||||||
|
f" {ppas:31} {triggers}\n"
|
||||||
|
)
|
||||||
|
except BrokenPipeError:
|
||||||
|
sys.exit(1)
|
||||||
|
return output
|
@ -15,6 +15,7 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
@ -25,7 +26,6 @@ import httplib2
|
|||||||
|
|
||||||
from ubuntutools.version import Version
|
from ubuntutools.version import Version
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ def is_sync(bug):
|
|||||||
return "sync" in bug.title.lower().split(" ") or "sync" in bug.tags
|
return "sync" in bug.title.lower().split(" ") or "sync" in bug.tags
|
||||||
|
|
||||||
|
|
||||||
class BugTask(object):
|
class BugTask:
|
||||||
def __init__(self, bug_task, launchpad):
|
def __init__(self, bug_task, launchpad):
|
||||||
self.bug_task = bug_task
|
self.bug_task = bug_task
|
||||||
self.launchpad = launchpad
|
self.launchpad = launchpad
|
||||||
@ -58,7 +58,7 @@ class BugTask(object):
|
|||||||
self.series = components[2].lower()
|
self.series = components[2].lower()
|
||||||
|
|
||||||
if self.package is None:
|
if self.package is None:
|
||||||
title_re = r'^Sync ([a-z0-9+.-]+) [a-z0-9.+:~-]+ \([a-z]+\) from.*'
|
title_re = r"^Sync ([a-z0-9+.-]+) [a-z0-9.+:~-]+ \([a-z]+\) from.*"
|
||||||
match = re.match(title_re, self.get_bug_title(), re.U | re.I)
|
match = re.match(title_re, self.get_bug_title(), re.U | re.I)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
self.package = match.group(1)
|
self.package = match.group(1)
|
||||||
@ -68,34 +68,42 @@ class BugTask(object):
|
|||||||
dsc_file = ""
|
dsc_file = ""
|
||||||
for url in source_files:
|
for url in source_files:
|
||||||
filename = unquote(os.path.basename(url))
|
filename = unquote(os.path.basename(url))
|
||||||
Logger.debug("Downloading %s..." % (filename))
|
Logger.debug("Downloading %s...", filename)
|
||||||
# HttpLib2 isn't suitable for large files (it reads into memory),
|
# HttpLib2 isn't suitable for large files (it reads into memory),
|
||||||
# but we want its https certificate validation on the .dsc
|
# but we want its https certificate validation on the .dsc
|
||||||
if url.endswith(".dsc"):
|
if url.endswith(".dsc"):
|
||||||
response, data = httplib2.Http().request(url)
|
response, data = httplib2.Http().request(url)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, "wb") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
dsc_file = os.path.join(os.getcwd(), filename)
|
dsc_file = os.path.join(os.getcwd(), filename)
|
||||||
else:
|
else:
|
||||||
urlretrieve(url, filename)
|
urlretrieve(url, filename)
|
||||||
assert os.path.isfile(dsc_file), "%s does not exist." % (dsc_file)
|
assert os.path.isfile(dsc_file), f"{dsc_file} does not exist."
|
||||||
return dsc_file
|
return dsc_file
|
||||||
|
|
||||||
def get_branch_link(self):
|
def get_branch_link(self):
|
||||||
return "lp:" + self.project + "/" + self.get_series() + "/" + \
|
return "lp:" + self.project + "/" + self.get_series() + "/" + self.package
|
||||||
self.package
|
|
||||||
|
|
||||||
def get_bug_title(self):
|
def get_bug_title(self):
|
||||||
"""Returns the title of the related bug."""
|
"""Returns the title of the related bug."""
|
||||||
return self.bug_task.bug.title
|
return self.bug_task.bug.title
|
||||||
|
|
||||||
def get_long_info(self):
|
def get_long_info(self):
|
||||||
return "Bug task: " + str(self.bug_task) + "\n" + \
|
return (
|
||||||
"Package: " + str(self.package) + "\n" + \
|
"Bug task: "
|
||||||
"Project: " + str(self.project) + "\n" + \
|
+ str(self.bug_task)
|
||||||
"Series: " + str(self.series)
|
+ "\n"
|
||||||
|
+ "Package: "
|
||||||
|
+ str(self.package)
|
||||||
|
+ "\n"
|
||||||
|
+ "Project: "
|
||||||
|
+ str(self.project)
|
||||||
|
+ "\n"
|
||||||
|
+ "Series: "
|
||||||
|
+ str(self.series)
|
||||||
|
)
|
||||||
|
|
||||||
def get_lp_task(self):
|
def get_lp_task(self):
|
||||||
"""Returns the Launchpad bug task object."""
|
"""Returns the Launchpad bug task object."""
|
||||||
@ -118,7 +126,6 @@ class BugTask(object):
|
|||||||
if self.series is None or latest_release:
|
if self.series is None or latest_release:
|
||||||
dist = self.launchpad.distributions[self.project]
|
dist = self.launchpad.distributions[self.project]
|
||||||
return dist.current_series.name
|
return dist.current_series.name
|
||||||
else:
|
|
||||||
return self.series
|
return self.series
|
||||||
|
|
||||||
def get_short_info(self):
|
def get_short_info(self):
|
||||||
@ -137,14 +144,16 @@ class BugTask(object):
|
|||||||
dist = self.launchpad.distributions[project]
|
dist = self.launchpad.distributions[project]
|
||||||
archive = dist.getArchive(name="primary")
|
archive = dist.getArchive(name="primary")
|
||||||
distro_series = dist.getSeries(name_or_version=series)
|
distro_series = dist.getSeries(name_or_version=series)
|
||||||
published = archive.getPublishedSources(source_name=self.package,
|
published = archive.getPublishedSources(
|
||||||
|
source_name=self.package,
|
||||||
distro_series=distro_series,
|
distro_series=distro_series,
|
||||||
status="Published",
|
status="Published",
|
||||||
exact_match=True)
|
exact_match=True,
|
||||||
|
)
|
||||||
|
|
||||||
latest_source = None
|
latest_source = None
|
||||||
for source in published:
|
for source in published:
|
||||||
if source.pocket in ('Release', 'Security', 'Updates', 'Proposed'):
|
if source.pocket in ("Release", "Security", "Updates", "Proposed"):
|
||||||
latest_source = source
|
latest_source = source
|
||||||
break
|
break
|
||||||
return latest_source
|
return latest_source
|
||||||
@ -156,7 +165,7 @@ class BugTask(object):
|
|||||||
def get_latest_released_version(self):
|
def get_latest_released_version(self):
|
||||||
source = self.get_source(True)
|
source = self.get_source(True)
|
||||||
if source is None: # Not currently published in Ubuntu
|
if source is None: # Not currently published in Ubuntu
|
||||||
version = '~'
|
version = "~"
|
||||||
else:
|
else:
|
||||||
version = source.source_package_version
|
version = source.source_package_version
|
||||||
return Version(version)
|
return Version(version)
|
||||||
|
@ -15,27 +15,27 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from ubuntutools.sponsor_patch.question import ask_for_manual_fixing
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import logging
|
from ubuntutools.sponsor_patch.question import ask_for_manual_fixing
|
||||||
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Patch(object):
|
class Patch:
|
||||||
"""This object represents a patch that can be downloaded from Launchpad."""
|
"""This object represents a patch that can be downloaded from Launchpad."""
|
||||||
|
|
||||||
def __init__(self, patch):
|
def __init__(self, patch):
|
||||||
self._patch = patch
|
self._patch = patch
|
||||||
self._patch_file = re.sub(" |/", "_", patch.title)
|
self._patch_file = re.sub(" |/", "_", patch.title)
|
||||||
if not reduce(lambda r, x: r or self._patch.title.endswith(x),
|
if not reduce(
|
||||||
(".debdiff", ".diff", ".patch"), False):
|
lambda r, x: r or self._patch.title.endswith(x), (".debdiff", ".diff", ".patch"), False
|
||||||
Logger.debug("Patch %s does not have a proper file extension." %
|
):
|
||||||
(self._patch.title))
|
Logger.debug("Patch %s does not have a proper file extension.", self._patch.title)
|
||||||
self._patch_file += ".patch"
|
self._patch_file += ".patch"
|
||||||
self._full_path = os.path.realpath(self._patch_file)
|
self._full_path = os.path.realpath(self._patch_file)
|
||||||
self._changed_files = None
|
self._changed_files = None
|
||||||
@ -45,21 +45,36 @@ class Patch(object):
|
|||||||
assert self._changed_files is not None, "You forgot to download the patch."
|
assert self._changed_files is not None, "You forgot to download the patch."
|
||||||
edit = False
|
edit = False
|
||||||
if self.is_debdiff():
|
if self.is_debdiff():
|
||||||
cmd = ["patch", "--merge", "--force", "-p",
|
cmd = [
|
||||||
str(self.get_strip_level()), "-i", self._full_path]
|
"patch",
|
||||||
Logger.debug(' '.join(cmd))
|
"--merge",
|
||||||
|
"--force",
|
||||||
|
"-p",
|
||||||
|
str(self.get_strip_level()),
|
||||||
|
"-i",
|
||||||
|
self._full_path,
|
||||||
|
]
|
||||||
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Failed to apply debdiff %s to %s %s.",
|
Logger.error(
|
||||||
self._patch_file, task.package, task.get_version())
|
"Failed to apply debdiff %s to %s %s.",
|
||||||
|
self._patch_file,
|
||||||
|
task.package,
|
||||||
|
task.get_version(),
|
||||||
|
)
|
||||||
if not edit:
|
if not edit:
|
||||||
ask_for_manual_fixing()
|
ask_for_manual_fixing()
|
||||||
edit = True
|
edit = True
|
||||||
else:
|
else:
|
||||||
cmd = ["add-patch", self._full_path]
|
cmd = ["add-patch", self._full_path]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Failed to apply diff %s to %s %s.",
|
Logger.error(
|
||||||
self._patch_file, task.package, task.get_version())
|
"Failed to apply diff %s to %s %s.",
|
||||||
|
self._patch_file,
|
||||||
|
task.package,
|
||||||
|
task.get_version(),
|
||||||
|
)
|
||||||
if not edit:
|
if not edit:
|
||||||
ask_for_manual_fixing()
|
ask_for_manual_fixing()
|
||||||
edit = True
|
edit = True
|
||||||
@ -67,13 +82,13 @@ class Patch(object):
|
|||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
"""Downloads the patch from Launchpad."""
|
"""Downloads the patch from Launchpad."""
|
||||||
Logger.debug("Downloading %s." % (self._patch_file))
|
Logger.debug("Downloading %s.", self._patch_file)
|
||||||
patch_f = open(self._patch_file, "wb")
|
patch_f = open(self._patch_file, "wb")
|
||||||
patch_f.write(self._patch.data.open().read())
|
patch_f.write(self._patch.data.open().read())
|
||||||
patch_f.close()
|
patch_f.close()
|
||||||
|
|
||||||
cmd = ["diffstat", "-l", "-p0", self._full_path]
|
cmd = ["diffstat", "-l", "-p0", self._full_path]
|
||||||
changed_files = subprocess.check_output(cmd, encoding='utf-8')
|
changed_files = subprocess.check_output(cmd, encoding="utf-8")
|
||||||
self._changed_files = [f for f in changed_files.split("\n") if f != ""]
|
self._changed_files = [f for f in changed_files.split("\n") if f != ""]
|
||||||
|
|
||||||
def get_strip_level(self):
|
def get_strip_level(self):
|
||||||
@ -81,13 +96,11 @@ class Patch(object):
|
|||||||
assert self._changed_files is not None, "You forgot to download the patch."
|
assert self._changed_files is not None, "You forgot to download the patch."
|
||||||
strip_level = None
|
strip_level = None
|
||||||
if self.is_debdiff():
|
if self.is_debdiff():
|
||||||
changelog = [f for f in self._changed_files
|
changelog = [f for f in self._changed_files if f.endswith("debian/changelog")][0]
|
||||||
if f.endswith("debian/changelog")][0]
|
|
||||||
strip_level = len(changelog.split(os.sep)) - 2
|
strip_level = len(changelog.split(os.sep)) - 2
|
||||||
return strip_level
|
return strip_level
|
||||||
|
|
||||||
def is_debdiff(self):
|
def is_debdiff(self):
|
||||||
"""Checks if the patch is a debdiff (= modifies debian/changelog)."""
|
"""Checks if the patch is a debdiff (= modifies debian/changelog)."""
|
||||||
assert self._changed_files is not None, "You forgot to download the patch."
|
assert self._changed_files is not None, "You forgot to download the patch."
|
||||||
return len([f for f in self._changed_files
|
return len([f for f in self._changed_files if f.endswith("debian/changelog")]) > 0
|
||||||
if f.endswith("debian/changelog")]) > 0
|
|
||||||
|
@ -37,8 +37,7 @@ def ask_for_ignoring_or_fixing():
|
|||||||
def ask_for_manual_fixing():
|
def ask_for_manual_fixing():
|
||||||
"""Ask the user to resolve an issue manually."""
|
"""Ask the user to resolve an issue manually."""
|
||||||
|
|
||||||
answer = YesNoQuestion().ask("Do you want to resolve this issue manually",
|
answer = YesNoQuestion().ask("Do you want to resolve this issue manually", "yes")
|
||||||
"yes")
|
|
||||||
if answer == "no":
|
if answer == "no":
|
||||||
user_abort()
|
user_abort()
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -24,21 +25,22 @@ import debian.changelog
|
|||||||
import debian.deb822
|
import debian.deb822
|
||||||
|
|
||||||
from ubuntutools.question import Question, YesNoQuestion
|
from ubuntutools.question import Question, YesNoQuestion
|
||||||
|
from ubuntutools.sponsor_patch.question import (
|
||||||
from ubuntutools.sponsor_patch.question import (ask_for_ignoring_or_fixing,
|
ask_for_ignoring_or_fixing,
|
||||||
ask_for_manual_fixing,
|
ask_for_manual_fixing,
|
||||||
user_abort)
|
user_abort,
|
||||||
|
)
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _get_series(launchpad):
|
def _get_series(launchpad):
|
||||||
"""Returns a tuple with the development and list of supported series."""
|
"""Returns a tuple with the development and list of supported series."""
|
||||||
ubuntu = launchpad.distributions['ubuntu']
|
ubuntu = launchpad.distributions["ubuntu"]
|
||||||
devel_series = ubuntu.current_series.name
|
devel_series = ubuntu.current_series.name
|
||||||
supported_series = [series.name for series in ubuntu.series
|
supported_series = [
|
||||||
if series.active and series.name != devel_series]
|
series.name for series in ubuntu.series if series.active and series.name != devel_series
|
||||||
|
]
|
||||||
return (devel_series, supported_series)
|
return (devel_series, supported_series)
|
||||||
|
|
||||||
|
|
||||||
@ -49,14 +51,14 @@ def strip_epoch(version):
|
|||||||
return "1.1.3-1".
|
return "1.1.3-1".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parts = version.full_version.split(':')
|
parts = version.full_version.split(":")
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
del parts[0]
|
del parts[0]
|
||||||
version_without_epoch = ':'.join(parts)
|
version_without_epoch = ":".join(parts)
|
||||||
return version_without_epoch
|
return version_without_epoch
|
||||||
|
|
||||||
|
|
||||||
class SourcePackage(object):
|
class SourcePackage:
|
||||||
"""This class represents a source package."""
|
"""This class represents a source package."""
|
||||||
|
|
||||||
def __init__(self, package, builder, workdir, branch):
|
def __init__(self, package, builder, workdir, branch):
|
||||||
@ -74,11 +76,10 @@ class SourcePackage(object):
|
|||||||
if upload == "ubuntu":
|
if upload == "ubuntu":
|
||||||
self._print_logs()
|
self._print_logs()
|
||||||
question = Question(["yes", "edit", "no"])
|
question = Question(["yes", "edit", "no"])
|
||||||
answer = question.ask("Do you want to acknowledge the sync request",
|
answer = question.ask("Do you want to acknowledge the sync request", "no")
|
||||||
"no")
|
|
||||||
if answer == "edit":
|
if answer == "edit":
|
||||||
return False
|
return False
|
||||||
elif answer == "no":
|
if answer == "no":
|
||||||
user_abort()
|
user_abort()
|
||||||
|
|
||||||
bug = task.bug
|
bug = task.bug
|
||||||
@ -90,33 +91,32 @@ class SourcePackage(object):
|
|||||||
|
|
||||||
msg = "Sync request ACK'd."
|
msg = "Sync request ACK'd."
|
||||||
if self._build_log:
|
if self._build_log:
|
||||||
msg = ("%s %s builds on %s. " + msg) % \
|
msg = (
|
||||||
(self._package, self._version,
|
f"{self._package} {self._version} builds"
|
||||||
self._builder.get_architecture())
|
f" on {self._builder.get_architecture()}. {msg}"
|
||||||
|
)
|
||||||
bug.newMessage(content=msg, subject="sponsor-patch")
|
bug.newMessage(content=msg, subject="sponsor-patch")
|
||||||
Logger.debug("Acknowledged sync request bug #%i.", bug.id)
|
Logger.debug("Acknowledged sync request bug #%i.", bug.id)
|
||||||
|
|
||||||
bug.subscribe(person=launchpad.people['ubuntu-archive'])
|
bug.subscribe(person=launchpad.people["ubuntu-archive"])
|
||||||
Logger.debug("Subscribed ubuntu-archive to bug #%i.", bug.id)
|
Logger.debug("Subscribed ubuntu-archive to bug #%i.", bug.id)
|
||||||
|
|
||||||
bug.subscribe(person=launchpad.me)
|
bug.subscribe(person=launchpad.me)
|
||||||
Logger.debug("Subscribed me to bug #%i.", bug.id)
|
Logger.debug("Subscribed me to bug #%i.", bug.id)
|
||||||
|
|
||||||
sponsorsteam = launchpad.people['ubuntu-sponsors']
|
sponsorsteam = launchpad.people["ubuntu-sponsors"]
|
||||||
for sub in bug.subscriptions:
|
for sub in bug.subscriptions:
|
||||||
if sub.person == sponsorsteam and sub.canBeUnsubscribedByUser():
|
if sub.person == sponsorsteam and sub.canBeUnsubscribedByUser():
|
||||||
bug.unsubscribe(person=launchpad.people['ubuntu-sponsors'])
|
bug.unsubscribe(person=launchpad.people["ubuntu-sponsors"])
|
||||||
Logger.debug("Unsubscribed ubuntu-sponsors from bug #%i.",
|
Logger.debug("Unsubscribed ubuntu-sponsors from bug #%i.", bug.id)
|
||||||
bug.id)
|
|
||||||
elif sub.person == sponsorsteam:
|
elif sub.person == sponsorsteam:
|
||||||
Logger.debug("Couldn't unsubscribe ubuntu-sponsors from "
|
Logger.debug("Couldn't unsubscribe ubuntu-sponsors from bug #%i.", bug.id)
|
||||||
"bug #%i.", bug.id)
|
|
||||||
|
|
||||||
Logger.info("Successfully acknowledged sync request bug #%i.",
|
Logger.info("Successfully acknowledged sync request bug #%i.", bug.id)
|
||||||
bug.id)
|
|
||||||
else:
|
else:
|
||||||
Logger.error("Sync requests can only be acknowledged when the "
|
Logger.error(
|
||||||
"upload target is Ubuntu.")
|
"Sync requests can only be acknowledged when the upload target is Ubuntu."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -135,34 +135,35 @@ class SourcePackage(object):
|
|||||||
else:
|
else:
|
||||||
target = upload
|
target = upload
|
||||||
question = Question(["yes", "edit", "no"])
|
question = Question(["yes", "edit", "no"])
|
||||||
answer = question.ask("Do you want to upload the package to %s" % target, "no")
|
answer = question.ask(f"Do you want to upload the package to {target}", "no")
|
||||||
if answer == "edit":
|
if answer == "edit":
|
||||||
return False
|
return False
|
||||||
elif answer == "no":
|
if answer == "no":
|
||||||
user_abort()
|
user_abort()
|
||||||
cmd = ["dput", "--force", upload, self._changes_file]
|
cmd = ["dput", "--force", upload, self._changes_file]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Upload of %s to %s failed." %
|
Logger.error(
|
||||||
(os.path.basename(self._changes_file), upload))
|
"Upload of %s to %s failed.", os.path.basename(self._changes_file), upload
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Push the branch if the package is uploaded to the Ubuntu archive.
|
# Push the branch if the package is uploaded to the Ubuntu archive.
|
||||||
if upload == "ubuntu" and self._branch:
|
if upload == "ubuntu" and self._branch:
|
||||||
cmd = ['debcommit']
|
cmd = ["debcommit"]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error('Bzr commit failed.')
|
Logger.error("Bzr commit failed.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
cmd = ['bzr', 'mark-uploaded']
|
cmd = ["bzr", "mark-uploaded"]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error('Bzr tagging failed.')
|
Logger.error("Bzr tagging failed.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
cmd = ['bzr', 'push', ':parent']
|
cmd = ["bzr", "push", ":parent"]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error('Bzr push failed.')
|
Logger.error("Bzr push failed.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -175,8 +176,10 @@ class SourcePackage(object):
|
|||||||
|
|
||||||
if dist is None:
|
if dist is None:
|
||||||
dist = re.sub("-.*$", "", self._changelog.distributions)
|
dist = re.sub("-.*$", "", self._changelog.distributions)
|
||||||
build_name = "{}_{}_{}.build".format(self._package, strip_epoch(self._version),
|
build_name = (
|
||||||
self._builder.get_architecture())
|
f"{self._package}_{strip_epoch(self._version)}"
|
||||||
|
f"_{self._builder.get_architecture()}.build"
|
||||||
|
)
|
||||||
self._build_log = os.path.join(self._buildresult, build_name)
|
self._build_log = os.path.join(self._buildresult, build_name)
|
||||||
|
|
||||||
successful_built = False
|
successful_built = False
|
||||||
@ -191,19 +194,17 @@ class SourcePackage(object):
|
|||||||
update = False
|
update = False
|
||||||
|
|
||||||
# build package
|
# build package
|
||||||
result = self._builder.build(self._dsc_file, dist,
|
result = self._builder.build(self._dsc_file, dist, self._buildresult)
|
||||||
self._buildresult)
|
|
||||||
if result != 0:
|
if result != 0:
|
||||||
question = Question(["yes", "update", "retry", "no"])
|
question = Question(["yes", "update", "retry", "no"])
|
||||||
answer = question.ask("Do you want to resolve this issue manually", "yes")
|
answer = question.ask("Do you want to resolve this issue manually", "yes")
|
||||||
if answer == "yes":
|
if answer == "yes":
|
||||||
break
|
break
|
||||||
elif answer == "update":
|
if answer == "update":
|
||||||
update = True
|
update = True
|
||||||
continue
|
continue
|
||||||
elif answer == "retry":
|
if answer == "retry":
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
user_abort()
|
user_abort()
|
||||||
successful_built = True
|
successful_built = True
|
||||||
if not successful_built:
|
if not successful_built:
|
||||||
@ -224,13 +225,14 @@ class SourcePackage(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._branch:
|
if self._branch:
|
||||||
cmd = ['bzr', 'builddeb', '--builder=debuild', '-S',
|
cmd = ["bzr", "builddeb", "--builder=debuild", "-S", "--", "--no-lintian", "-nc"]
|
||||||
'--', '--no-lintian', '-nc']
|
|
||||||
else:
|
else:
|
||||||
cmd = ['debuild', '--no-lintian', '-nc', '-S']
|
cmd = ["debuild", "--no-lintian", "-nc", "-S"]
|
||||||
cmd.append("-v" + previous_version.full_version)
|
cmd.append("-v" + previous_version.full_version)
|
||||||
if previous_version.upstream_version == \
|
if (
|
||||||
self._changelog.upstream_version and upload == "ubuntu":
|
previous_version.upstream_version == self._changelog.upstream_version
|
||||||
|
and upload == "ubuntu"
|
||||||
|
):
|
||||||
# FIXME: Add proper check that catches cases like changed
|
# FIXME: Add proper check that catches cases like changed
|
||||||
# compression (.tar.gz -> tar.bz2) and multiple orig source tarballs
|
# compression (.tar.gz -> tar.bz2) and multiple orig source tarballs
|
||||||
cmd.append("-sd")
|
cmd.append("-sd")
|
||||||
@ -239,9 +241,9 @@ class SourcePackage(object):
|
|||||||
if keyid is not None:
|
if keyid is not None:
|
||||||
cmd += ["-k" + keyid]
|
cmd += ["-k" + keyid]
|
||||||
env = os.environ
|
env = os.environ
|
||||||
if upload == 'ubuntu':
|
if upload == "ubuntu":
|
||||||
env['DEB_VENDOR'] = 'Ubuntu'
|
env["DEB_VENDOR"] = "Ubuntu"
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd, env=env) != 0:
|
if subprocess.call(cmd, env=env) != 0:
|
||||||
Logger.error("Failed to build source tarball.")
|
Logger.error("Failed to build source tarball.")
|
||||||
# TODO: Add a "retry" option
|
# TODO: Add a "retry" option
|
||||||
@ -252,8 +254,9 @@ class SourcePackage(object):
|
|||||||
@property
|
@property
|
||||||
def _changes_file(self):
|
def _changes_file(self):
|
||||||
"""Returns the file name of the .changes file."""
|
"""Returns the file name of the .changes file."""
|
||||||
return os.path.join(self._workdir, "{}_{}_source.changes"
|
return os.path.join(
|
||||||
.format(self._package, strip_epoch(self._version)))
|
self._workdir, f"{self._package}_{strip_epoch(self._version)}_source.changes"
|
||||||
|
)
|
||||||
|
|
||||||
def check_target(self, upload, launchpad):
|
def check_target(self, upload, launchpad):
|
||||||
"""Make sure that the target is correct.
|
"""Make sure that the target is correct.
|
||||||
@ -265,18 +268,24 @@ class SourcePackage(object):
|
|||||||
(devel_series, supported_series) = _get_series(launchpad)
|
(devel_series, supported_series) = _get_series(launchpad)
|
||||||
|
|
||||||
if upload == "ubuntu":
|
if upload == "ubuntu":
|
||||||
allowed = supported_series + \
|
allowed = (
|
||||||
[s + "-proposed" for s in supported_series] + \
|
supported_series + [s + "-proposed" for s in supported_series] + [devel_series]
|
||||||
[devel_series]
|
)
|
||||||
if self._changelog.distributions not in allowed:
|
if self._changelog.distributions not in allowed:
|
||||||
Logger.error("%s is not an allowed series. It needs to be one of %s." %
|
Logger.error(
|
||||||
(self._changelog.distributions, ", ".join(allowed)))
|
"%s is not an allowed series. It needs to be one of %s.",
|
||||||
|
self._changelog.distributions,
|
||||||
|
", ".join(allowed),
|
||||||
|
)
|
||||||
return ask_for_ignoring_or_fixing()
|
return ask_for_ignoring_or_fixing()
|
||||||
elif upload and upload.startswith("ppa/"):
|
elif upload and upload.startswith("ppa/"):
|
||||||
allowed = supported_series + [devel_series]
|
allowed = supported_series + [devel_series]
|
||||||
if self._changelog.distributions not in allowed:
|
if self._changelog.distributions not in allowed:
|
||||||
Logger.error("%s is not an allowed series. It needs to be one of %s." %
|
Logger.error(
|
||||||
(self._changelog.distributions, ", ".join(allowed)))
|
"%s is not an allowed series. It needs to be one of %s.",
|
||||||
|
self._changelog.distributions,
|
||||||
|
", ".join(allowed),
|
||||||
|
)
|
||||||
return ask_for_ignoring_or_fixing()
|
return ask_for_ignoring_or_fixing()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -288,8 +297,11 @@ class SourcePackage(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._version <= previous_version:
|
if self._version <= previous_version:
|
||||||
Logger.error("The version %s is not greater than the already "
|
Logger.error(
|
||||||
"available %s.", self._version, previous_version)
|
"The version %s is not greater than the already available %s.",
|
||||||
|
self._version,
|
||||||
|
previous_version,
|
||||||
|
)
|
||||||
return ask_for_ignoring_or_fixing()
|
return ask_for_ignoring_or_fixing()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -298,8 +310,8 @@ class SourcePackage(object):
|
|||||||
bug title."""
|
bug title."""
|
||||||
|
|
||||||
if not task.title_contains(self._version):
|
if not task.title_contains(self._version):
|
||||||
print("Bug #%i title: %s" % (bug_number, task.get_bug_title()))
|
print(f"Bug #{bug_number} title: {task.get_bug_title()}")
|
||||||
msg = "Is %s %s the version that should be synced" % (self._package, self._version)
|
msg = f"Is {self._package} {self._version} the version that should be synced"
|
||||||
answer = YesNoQuestion().ask(msg, "no")
|
answer = YesNoQuestion().ask(msg, "no")
|
||||||
if answer == "no":
|
if answer == "no":
|
||||||
user_abort()
|
user_abort()
|
||||||
@ -307,32 +319,27 @@ class SourcePackage(object):
|
|||||||
@property
|
@property
|
||||||
def _debdiff_filename(self):
|
def _debdiff_filename(self):
|
||||||
"""Returns the file name of the .debdiff file."""
|
"""Returns the file name of the .debdiff file."""
|
||||||
debdiff_name = "{}_{}.debdiff".format(self._package, strip_epoch(self._version))
|
debdiff_name = f"{self._package}_{strip_epoch(self._version)}.debdiff"
|
||||||
return os.path.join(self._workdir, debdiff_name)
|
return os.path.join(self._workdir, debdiff_name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _dsc_file(self):
|
def _dsc_file(self):
|
||||||
"""Returns the file name of the .dsc file."""
|
"""Returns the file name of the .dsc file."""
|
||||||
return os.path.join(self._workdir, "{}_{}.dsc".format(self._package,
|
return os.path.join(self._workdir, f"{self._package}_{strip_epoch(self._version)}.dsc")
|
||||||
strip_epoch(self._version)))
|
|
||||||
|
|
||||||
def generate_debdiff(self, dsc_file):
|
def generate_debdiff(self, dsc_file):
|
||||||
"""Generates a debdiff between the given .dsc file and this source
|
"""Generates a debdiff between the given .dsc file and this source
|
||||||
package."""
|
package."""
|
||||||
|
|
||||||
assert os.path.isfile(dsc_file), "%s does not exist." % (dsc_file)
|
assert os.path.isfile(dsc_file), f"{dsc_file} does not exist."
|
||||||
assert os.path.isfile(self._dsc_file), "%s does not exist." % \
|
assert os.path.isfile(self._dsc_file), f"{self._dsc_file} does not exist."
|
||||||
(self._dsc_file)
|
|
||||||
cmd = ["debdiff", dsc_file, self._dsc_file]
|
cmd = ["debdiff", dsc_file, self._dsc_file]
|
||||||
if not Logger.isEnabledFor(logging.DEBUG):
|
if not Logger.isEnabledFor(logging.DEBUG):
|
||||||
cmd.insert(1, "-q")
|
cmd.insert(1, "-q")
|
||||||
Logger.debug(' '.join(cmd) + " > " + self._debdiff_filename)
|
Logger.debug("%s > %s", " ".join(cmd), self._debdiff_filename)
|
||||||
debdiff = subprocess.check_output(cmd, encoding='utf-8')
|
with open(self._debdiff_filename, "w", encoding="utf-8") as debdiff_file:
|
||||||
|
debdiff = subprocess.run(cmd, check=False, stdout=debdiff_file)
|
||||||
# write debdiff file
|
assert debdiff.returncode in (0, 1)
|
||||||
debdiff_file = open(self._debdiff_filename, "w")
|
|
||||||
debdiff_file.writelines(debdiff)
|
|
||||||
debdiff_file.close()
|
|
||||||
|
|
||||||
def is_fixed(self, lp_bug):
|
def is_fixed(self, lp_bug):
|
||||||
"""Make sure that the given Launchpad bug is closed.
|
"""Make sure that the given Launchpad bug is closed.
|
||||||
@ -341,8 +348,8 @@ class SourcePackage(object):
|
|||||||
change something.
|
change something.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert os.path.isfile(self._changes_file), "%s does not exist." % (self._changes_file)
|
assert os.path.isfile(self._changes_file), f"{self._changes_file} does not exist."
|
||||||
changes = debian.deb822.Changes(open(self._changes_file))
|
changes = debian.deb822.Changes(open(self._changes_file, encoding="utf-8"))
|
||||||
fixed_bugs = []
|
fixed_bugs = []
|
||||||
if "Launchpad-Bugs-Fixed" in changes:
|
if "Launchpad-Bugs-Fixed" in changes:
|
||||||
fixed_bugs = changes["Launchpad-Bugs-Fixed"].split(" ")
|
fixed_bugs = changes["Launchpad-Bugs-Fixed"].split(" ")
|
||||||
@ -354,7 +361,7 @@ class SourcePackage(object):
|
|||||||
lp_bug = lp_bug.duplicate_of
|
lp_bug = lp_bug.duplicate_of
|
||||||
|
|
||||||
if lp_bug.id not in fixed_bugs:
|
if lp_bug.id not in fixed_bugs:
|
||||||
Logger.error("Launchpad bug #%i is not closed by new version." % (lp_bug.id))
|
Logger.error("Launchpad bug #%i is not closed by new version.", lp_bug.id)
|
||||||
return ask_for_ignoring_or_fixing()
|
return ask_for_ignoring_or_fixing()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -362,7 +369,7 @@ class SourcePackage(object):
|
|||||||
"""Print things that should be checked before uploading a package."""
|
"""Print things that should be checked before uploading a package."""
|
||||||
|
|
||||||
lintian_filename = self._run_lintian()
|
lintian_filename = self._run_lintian()
|
||||||
print("\nPlease check %s %s carefully:" % (self._package, self._version))
|
print(f"\nPlease check {self._package} {self._version} carefully:")
|
||||||
if os.path.isfile(self._debdiff_filename):
|
if os.path.isfile(self._debdiff_filename):
|
||||||
print("file://" + self._debdiff_filename)
|
print("file://" + self._debdiff_filename)
|
||||||
print("file://" + lintian_filename)
|
print("file://" + lintian_filename)
|
||||||
@ -379,8 +386,9 @@ class SourcePackage(object):
|
|||||||
# Check the changelog
|
# Check the changelog
|
||||||
self._changelog = debian.changelog.Changelog()
|
self._changelog = debian.changelog.Changelog()
|
||||||
try:
|
try:
|
||||||
self._changelog.parse_changelog(open("debian/changelog"),
|
self._changelog.parse_changelog(
|
||||||
max_blocks=1, strict=True)
|
open("debian/changelog", encoding="utf-8"), max_blocks=1, strict=True
|
||||||
|
)
|
||||||
except debian.changelog.ChangelogParseError as error:
|
except debian.changelog.ChangelogParseError as error:
|
||||||
Logger.error("The changelog entry doesn't validate: %s", str(error))
|
Logger.error("The changelog entry doesn't validate: %s", str(error))
|
||||||
ask_for_manual_fixing()
|
ask_for_manual_fixing()
|
||||||
@ -390,8 +398,10 @@ class SourcePackage(object):
|
|||||||
try:
|
try:
|
||||||
self._version = self._changelog.get_version()
|
self._version = self._changelog.get_version()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
Logger.error("Debian package version could not be determined. "
|
Logger.error(
|
||||||
"debian/changelog is probably malformed.")
|
"Debian package version could not be determined. "
|
||||||
|
"debian/changelog is probably malformed."
|
||||||
|
)
|
||||||
ask_for_manual_fixing()
|
ask_for_manual_fixing()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -405,25 +415,29 @@ class SourcePackage(object):
|
|||||||
|
|
||||||
# Determine whether to use the source or binary build for lintian
|
# Determine whether to use the source or binary build for lintian
|
||||||
if self._build_log:
|
if self._build_log:
|
||||||
build_changes = self._package + "_" + strip_epoch(self._version) + \
|
build_changes = (
|
||||||
"_" + self._builder.get_architecture() + ".changes"
|
self._package
|
||||||
|
+ "_"
|
||||||
|
+ strip_epoch(self._version)
|
||||||
|
+ "_"
|
||||||
|
+ self._builder.get_architecture()
|
||||||
|
+ ".changes"
|
||||||
|
)
|
||||||
changes_for_lintian = os.path.join(self._buildresult, build_changes)
|
changes_for_lintian = os.path.join(self._buildresult, build_changes)
|
||||||
else:
|
else:
|
||||||
changes_for_lintian = self._changes_file
|
changes_for_lintian = self._changes_file
|
||||||
|
|
||||||
# Check lintian
|
# Check lintian
|
||||||
assert os.path.isfile(changes_for_lintian), "%s does not exist." % \
|
assert os.path.isfile(changes_for_lintian), f"{changes_for_lintian} does not exist."
|
||||||
(changes_for_lintian)
|
cmd = ["lintian", "-IE", "--pedantic", "-q", "--profile", "ubuntu", changes_for_lintian]
|
||||||
cmd = ["lintian", "-IE", "--pedantic", "-q", "--profile", "ubuntu",
|
lintian_filename = os.path.join(
|
||||||
changes_for_lintian]
|
self._workdir, self._package + "_" + strip_epoch(self._version) + ".lintian"
|
||||||
lintian_filename = os.path.join(self._workdir,
|
)
|
||||||
self._package + "_" +
|
Logger.debug("%s > %s", " ".join(cmd), lintian_filename)
|
||||||
strip_epoch(self._version) + ".lintian")
|
report = subprocess.check_output(cmd, encoding="utf-8")
|
||||||
Logger.debug(' '.join(cmd) + " > " + lintian_filename)
|
|
||||||
report = subprocess.check_output(cmd, encoding='utf-8')
|
|
||||||
|
|
||||||
# write lintian report file
|
# write lintian report file
|
||||||
lintian_file = open(lintian_filename, "w")
|
lintian_file = open(lintian_filename, "w", encoding="utf-8")
|
||||||
lintian_file.writelines(report)
|
lintian_file.writelines(report)
|
||||||
lintian_file.close()
|
lintian_file.close()
|
||||||
|
|
||||||
@ -433,17 +447,25 @@ class SourcePackage(object):
|
|||||||
"""Does a sync of the source package."""
|
"""Does a sync of the source package."""
|
||||||
|
|
||||||
if upload == "ubuntu":
|
if upload == "ubuntu":
|
||||||
cmd = ["syncpackage", self._package, "-b", str(bug_number), "-f",
|
cmd = [
|
||||||
"-s", requester, "-V", str(self._version),
|
"syncpackage",
|
||||||
"-d", series]
|
self._package,
|
||||||
Logger.debug(' '.join(cmd))
|
"-b",
|
||||||
|
str(bug_number),
|
||||||
|
"-f",
|
||||||
|
"-s",
|
||||||
|
requester,
|
||||||
|
"-V",
|
||||||
|
str(self._version),
|
||||||
|
"-d",
|
||||||
|
series,
|
||||||
|
]
|
||||||
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Syncing of %s %s failed.", self._package,
|
Logger.error("Syncing of %s %s failed.", self._package, str(self._version))
|
||||||
str(self._version))
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
# FIXME: Support this use case!
|
# FIXME: Support this use case!
|
||||||
Logger.error("Uploading a synced package other than to Ubuntu "
|
Logger.error("Uploading a synced package other than to Ubuntu is not supported yet!")
|
||||||
"is not supported yet!")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return True
|
return True
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
import shutil
|
import shutil
|
||||||
@ -22,47 +23,43 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distro_info import UbuntuDistroInfo
|
from distro_info import UbuntuDistroInfo
|
||||||
|
|
||||||
from launchpadlib.launchpad import Launchpad
|
from launchpadlib.launchpad import Launchpad
|
||||||
|
|
||||||
from ubuntutools.update_maintainer import (update_maintainer,
|
|
||||||
MaintainerUpdateException)
|
|
||||||
from ubuntutools.question import input_number
|
from ubuntutools.question import input_number
|
||||||
|
|
||||||
from ubuntutools.sponsor_patch.bugtask import BugTask, is_sync
|
from ubuntutools.sponsor_patch.bugtask import BugTask, is_sync
|
||||||
from ubuntutools.sponsor_patch.patch import Patch
|
from ubuntutools.sponsor_patch.patch import Patch
|
||||||
from ubuntutools.sponsor_patch.question import ask_for_manual_fixing
|
from ubuntutools.sponsor_patch.question import ask_for_manual_fixing
|
||||||
from ubuntutools.sponsor_patch.source_package import SourcePackage
|
from ubuntutools.sponsor_patch.source_package import SourcePackage
|
||||||
|
from ubuntutools.update_maintainer import MaintainerUpdateException, update_maintainer
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def is_command_available(command, check_sbin=False):
|
def is_command_available(command, check_sbin=False):
|
||||||
"Is command in $PATH?"
|
"Is command in $PATH?"
|
||||||
path = os.environ.get('PATH', '/usr/bin:/bin').split(':')
|
path = os.environ.get("PATH", "/usr/bin:/bin").split(":")
|
||||||
if check_sbin:
|
if check_sbin:
|
||||||
path += [directory[:-3] + 'sbin'
|
path += [f"{directory[:-3]}sbin" for directory in path if directory.endswith("/bin")]
|
||||||
for directory in path if directory.endswith('/bin')]
|
return any(os.access(os.path.join(directory, command), os.X_OK) for directory in path)
|
||||||
return any(os.access(os.path.join(directory, command), os.X_OK)
|
|
||||||
for directory in path)
|
|
||||||
|
|
||||||
|
|
||||||
def check_dependencies():
|
def check_dependencies():
|
||||||
"Do we have all the commands we need for full functionality?"
|
"Do we have all the commands we need for full functionality?"
|
||||||
missing = []
|
missing = []
|
||||||
for cmd in ('patch', 'bzr', 'quilt', 'dput', 'lintian'):
|
for cmd in ("patch", "bzr", "quilt", "dput", "lintian"):
|
||||||
if not is_command_available(cmd):
|
if not is_command_available(cmd):
|
||||||
missing.append(cmd)
|
missing.append(cmd)
|
||||||
if not is_command_available('bzr-buildpackage'):
|
if not is_command_available("bzr-buildpackage"):
|
||||||
missing.append('bzr-builddeb')
|
missing.append("bzr-builddeb")
|
||||||
if not any(is_command_available(cmd, check_sbin=True)
|
if not any(
|
||||||
for cmd in ('pbuilder', 'sbuild', 'cowbuilder')):
|
is_command_available(cmd, check_sbin=True) for cmd in ("pbuilder", "sbuild", "cowbuilder")
|
||||||
missing.append('pbuilder/cowbuilder/sbuild')
|
):
|
||||||
|
missing.append("pbuilder/cowbuilder/sbuild")
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
Logger.warning("sponsor-patch requires %s to be installed for full "
|
Logger.warning(
|
||||||
"functionality", ', '.join(missing))
|
"sponsor-patch requires %s to be installed for full functionality", ", ".join(missing)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_source_package_name(bug_task):
|
def get_source_package_name(bug_task):
|
||||||
@ -84,15 +81,18 @@ def get_user_shell():
|
|||||||
def edit_source():
|
def edit_source():
|
||||||
# Spawn shell to allow modifications
|
# Spawn shell to allow modifications
|
||||||
cmd = [get_user_shell()]
|
cmd = [get_user_shell()]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
print("""An interactive shell was launched in
|
print(
|
||||||
file://%s
|
f"""An interactive shell was launched in
|
||||||
|
file://{os.getcwd()}
|
||||||
Edit your files. When you are done, exit the shell. If you wish to abort the
|
Edit your files. When you are done, exit the shell. If you wish to abort the
|
||||||
process, exit the shell such that it returns an exit code other than zero.
|
process, exit the shell such that it returns an exit code other than zero.
|
||||||
""" % (os.getcwd()), end=' ')
|
""",
|
||||||
|
end=" ",
|
||||||
|
)
|
||||||
returncode = subprocess.call(cmd)
|
returncode = subprocess.call(cmd)
|
||||||
if returncode != 0:
|
if returncode != 0:
|
||||||
Logger.error("Shell exited with exit value %i." % (returncode))
|
Logger.error("Shell exited with exit value %i.", returncode)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -100,30 +100,26 @@ def ask_for_patch_or_branch(bug, attached_patches, linked_branches):
|
|||||||
patch = None
|
patch = None
|
||||||
branch = None
|
branch = None
|
||||||
if len(attached_patches) == 0:
|
if len(attached_patches) == 0:
|
||||||
msg = "https://launchpad.net/bugs/%i has %i branches linked:" % \
|
msg = f"{len(linked_branches)} branches linked:"
|
||||||
(bug.id, len(linked_branches))
|
|
||||||
elif len(linked_branches) == 0:
|
elif len(linked_branches) == 0:
|
||||||
msg = "https://launchpad.net/bugs/%i has %i patches attached:" % \
|
msg = f"{len(attached_patches)} patches attached:"
|
||||||
(bug.id, len(attached_patches))
|
|
||||||
else:
|
else:
|
||||||
branches = "%i branch" % len(linked_branches)
|
branches = f"{len(linked_branches)} branch"
|
||||||
if len(linked_branches) > 1:
|
if len(linked_branches) > 1:
|
||||||
branches += "es"
|
branches += "es"
|
||||||
patches = "%i patch" % len(attached_patches)
|
patches = f"{len(attached_patches)} patch"
|
||||||
if len(attached_patches) > 1:
|
if len(attached_patches) > 1:
|
||||||
patches += "es"
|
patches += "es"
|
||||||
msg = "https://launchpad.net/bugs/%i has %s linked and %s attached:" % \
|
msg = f"{branches} linked and {patches} attached:"
|
||||||
(bug.id, branches, patches)
|
Logger.info("https://launchpad.net/bugs/%i has %s", bug.id, msg)
|
||||||
Logger.info(msg)
|
|
||||||
i = 0
|
i = 0
|
||||||
for linked_branch in linked_branches:
|
for linked_branch in linked_branches:
|
||||||
i += 1
|
i += 1
|
||||||
print("%i) %s" % (i, linked_branch.display_name))
|
print(f"{i}) {linked_branch.display_name}")
|
||||||
for attached_patch in attached_patches:
|
for attached_patch in attached_patches:
|
||||||
i += 1
|
i += 1
|
||||||
print("%i) %s" % (i, attached_patch.title))
|
print(f"{i}) {attached_patch.title}")
|
||||||
selected = input_number("Which branch or patch do you want to download",
|
selected = input_number("Which branch or patch do you want to download", 1, i, i)
|
||||||
1, i, i)
|
|
||||||
if selected <= len(linked_branches):
|
if selected <= len(linked_branches):
|
||||||
branch = linked_branches[selected - 1].bzr_identity
|
branch = linked_branches[selected - 1].bzr_identity
|
||||||
else:
|
else:
|
||||||
@ -139,21 +135,26 @@ def get_patch_or_branch(bug):
|
|||||||
linked_branches = [b.branch for b in bug.linked_branches]
|
linked_branches = [b.branch for b in bug.linked_branches]
|
||||||
if len(attached_patches) == 0 and len(linked_branches) == 0:
|
if len(attached_patches) == 0 and len(linked_branches) == 0:
|
||||||
if len(bug.attachments) == 0:
|
if len(bug.attachments) == 0:
|
||||||
Logger.error("No attachment and no linked branch found on "
|
Logger.error(
|
||||||
|
"No attachment and no linked branch found on "
|
||||||
"bug #%i. Add the tag sync to the bug if it is "
|
"bug #%i. Add the tag sync to the bug if it is "
|
||||||
"a sync request.", bug.id)
|
"a sync request.",
|
||||||
|
bug.id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
Logger.error("No attached patch and no linked branch found. "
|
Logger.error(
|
||||||
|
"No attached patch and no linked branch found. "
|
||||||
"Go to https://launchpad.net/bugs/%i and mark an "
|
"Go to https://launchpad.net/bugs/%i and mark an "
|
||||||
"attachment as patch.", bug.id)
|
"attachment as patch.",
|
||||||
|
bug.id,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif len(attached_patches) == 1 and len(linked_branches) == 0:
|
elif len(attached_patches) == 1 and len(linked_branches) == 0:
|
||||||
patch = Patch(attached_patches[0])
|
patch = Patch(attached_patches[0])
|
||||||
elif len(attached_patches) == 0 and len(linked_branches) == 1:
|
elif len(attached_patches) == 0 and len(linked_branches) == 1:
|
||||||
branch = linked_branches[0].bzr_identity
|
branch = linked_branches[0].bzr_identity
|
||||||
else:
|
else:
|
||||||
patch, branch = ask_for_patch_or_branch(bug, attached_patches,
|
patch, branch = ask_for_patch_or_branch(bug, attached_patches, linked_branches)
|
||||||
linked_branches)
|
|
||||||
return (patch, branch)
|
return (patch, branch)
|
||||||
|
|
||||||
|
|
||||||
@ -162,9 +163,9 @@ def download_branch(branch):
|
|||||||
if os.path.isdir(dir_name):
|
if os.path.isdir(dir_name):
|
||||||
shutil.rmtree(dir_name)
|
shutil.rmtree(dir_name)
|
||||||
cmd = ["bzr", "branch", branch]
|
cmd = ["bzr", "branch", branch]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Failed to download branch %s." % (branch))
|
Logger.error("Failed to download branch %s.", branch)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return dir_name
|
return dir_name
|
||||||
|
|
||||||
@ -172,21 +173,21 @@ def download_branch(branch):
|
|||||||
def merge_branch(branch):
|
def merge_branch(branch):
|
||||||
edit = False
|
edit = False
|
||||||
cmd = ["bzr", "merge", branch]
|
cmd = ["bzr", "merge", branch]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Failed to merge branch %s." % (branch))
|
Logger.error("Failed to merge branch %s.", branch)
|
||||||
ask_for_manual_fixing()
|
ask_for_manual_fixing()
|
||||||
edit = True
|
edit = True
|
||||||
return edit
|
return edit
|
||||||
|
|
||||||
|
|
||||||
def extract_source(dsc_file, verbose=False):
|
def extract_source(dsc_file, verbose=False):
|
||||||
cmd = ["dpkg-source", "--no-preparation", "-x", dsc_file]
|
cmd = ["dpkg-source", "--skip-patches", "-x", dsc_file]
|
||||||
if not verbose:
|
if not verbose:
|
||||||
cmd.insert(1, "-q")
|
cmd.insert(1, "-q")
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.error("Extraction of %s failed." % (os.path.basename(dsc_file)))
|
Logger.error("Extraction of %s failed.", os.path.basename(dsc_file))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -201,19 +202,18 @@ def get_open_ubuntu_bug_task(launchpad, bug, branch=None):
|
|||||||
ubuntu_tasks = [x for x in bug_tasks if x.is_ubuntu_task()]
|
ubuntu_tasks = [x for x in bug_tasks if x.is_ubuntu_task()]
|
||||||
bug_id = bug.id
|
bug_id = bug.id
|
||||||
if branch:
|
if branch:
|
||||||
branch = branch.split('/')
|
branch = branch.split("/")
|
||||||
# Non-production LP?
|
# Non-production LP?
|
||||||
if len(branch) > 5:
|
if len(branch) > 5:
|
||||||
branch = branch[3:]
|
branch = branch[3:]
|
||||||
|
|
||||||
if len(ubuntu_tasks) == 0:
|
if len(ubuntu_tasks) == 0:
|
||||||
Logger.error("No Ubuntu bug task found on bug #%i." % (bug_id))
|
Logger.error("No Ubuntu bug task found on bug #%i.", bug_id)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif len(ubuntu_tasks) == 1:
|
elif len(ubuntu_tasks) == 1:
|
||||||
task = ubuntu_tasks[0]
|
task = ubuntu_tasks[0]
|
||||||
if len(ubuntu_tasks) > 1 and branch and branch[1] == 'ubuntu':
|
if len(ubuntu_tasks) > 1 and branch and branch[1] == "ubuntu":
|
||||||
tasks = [t for t in ubuntu_tasks if
|
tasks = [t for t in ubuntu_tasks if t.get_series() == branch[2] and t.package == branch[3]]
|
||||||
t.get_series() == branch[2] and t.package == branch[3]]
|
|
||||||
if len(tasks) > 1:
|
if len(tasks) > 1:
|
||||||
# A bug targeted to the development series?
|
# A bug targeted to the development series?
|
||||||
tasks = [t for t in tasks if t.series is not None]
|
tasks = [t for t in tasks if t.series is not None]
|
||||||
@ -221,21 +221,26 @@ def get_open_ubuntu_bug_task(launchpad, bug, branch=None):
|
|||||||
task = tasks[0]
|
task = tasks[0]
|
||||||
elif len(ubuntu_tasks) > 1:
|
elif len(ubuntu_tasks) > 1:
|
||||||
task_list = [t.get_short_info() for t in ubuntu_tasks]
|
task_list = [t.get_short_info() for t in ubuntu_tasks]
|
||||||
Logger.debug("%i Ubuntu tasks exist for bug #%i.\n%s", len(ubuntu_tasks),
|
Logger.debug(
|
||||||
bug_id, "\n".join(task_list))
|
"%i Ubuntu tasks exist for bug #%i.\n%s",
|
||||||
|
len(ubuntu_tasks),
|
||||||
|
bug_id,
|
||||||
|
"\n".join(task_list),
|
||||||
|
)
|
||||||
open_ubuntu_tasks = [x for x in ubuntu_tasks if not x.is_complete()]
|
open_ubuntu_tasks = [x for x in ubuntu_tasks if not x.is_complete()]
|
||||||
if len(open_ubuntu_tasks) == 1:
|
if len(open_ubuntu_tasks) == 1:
|
||||||
task = open_ubuntu_tasks[0]
|
task = open_ubuntu_tasks[0]
|
||||||
else:
|
else:
|
||||||
Logger.info("https://launchpad.net/bugs/%i has %i Ubuntu tasks:" %
|
Logger.info(
|
||||||
(bug_id, len(ubuntu_tasks)))
|
"https://launchpad.net/bugs/%i has %i Ubuntu tasks:", bug_id, len(ubuntu_tasks)
|
||||||
for i in range(len(ubuntu_tasks)):
|
)
|
||||||
print("%i) %s" % (i + 1,
|
for i, ubuntu_task in enumerate(ubuntu_tasks):
|
||||||
ubuntu_tasks[i].get_package_and_series()))
|
print(f"{i + 1}) {ubuntu_task.get_package_and_series()}")
|
||||||
selected = input_number("To which Ubuntu task does the patch belong",
|
selected = input_number(
|
||||||
1, len(ubuntu_tasks))
|
"To which Ubuntu task does the patch belong", 1, len(ubuntu_tasks)
|
||||||
|
)
|
||||||
task = ubuntu_tasks[selected - 1]
|
task = ubuntu_tasks[selected - 1]
|
||||||
Logger.debug("Selected Ubuntu task: %s" % (task.get_short_info()))
|
Logger.debug("Selected Ubuntu task: %s", task.get_short_info())
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
@ -246,11 +251,15 @@ def _create_and_change_into(workdir):
|
|||||||
try:
|
try:
|
||||||
os.makedirs(workdir)
|
os.makedirs(workdir)
|
||||||
except os.error as error:
|
except os.error as error:
|
||||||
Logger.error("Failed to create the working directory %s [Errno %i]: %s." %
|
Logger.error(
|
||||||
(workdir, error.errno, error.strerror))
|
"Failed to create the working directory %s [Errno %i]: %s.",
|
||||||
|
workdir,
|
||||||
|
error.errno,
|
||||||
|
error.strerror,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if workdir != os.getcwd():
|
if workdir != os.getcwd():
|
||||||
Logger.debug("cd " + workdir)
|
Logger.debug("cd %s", workdir)
|
||||||
os.chdir(workdir)
|
os.chdir(workdir)
|
||||||
|
|
||||||
|
|
||||||
@ -267,7 +276,7 @@ def _update_maintainer_field():
|
|||||||
def _update_timestamp():
|
def _update_timestamp():
|
||||||
"""Run dch to update the timestamp of debian/changelog."""
|
"""Run dch to update the timestamp of debian/changelog."""
|
||||||
cmd = ["dch", "--maintmaint", "--release", ""]
|
cmd = ["dch", "--maintmaint", "--release", ""]
|
||||||
Logger.debug(' '.join(cmd))
|
Logger.debug(" ".join(cmd))
|
||||||
if subprocess.call(cmd) != 0:
|
if subprocess.call(cmd) != 0:
|
||||||
Logger.debug("Failed to update timestamp in debian/changelog.")
|
Logger.debug("Failed to update timestamp in debian/changelog.")
|
||||||
|
|
||||||
@ -279,13 +288,13 @@ def _download_and_change_into(task, dsc_file, patch, branch):
|
|||||||
branch_dir = download_branch(task.get_branch_link())
|
branch_dir = download_branch(task.get_branch_link())
|
||||||
|
|
||||||
# change directory
|
# change directory
|
||||||
Logger.debug("cd " + branch_dir)
|
Logger.debug("cd %s", branch_dir)
|
||||||
os.chdir(branch_dir)
|
os.chdir(branch_dir)
|
||||||
else:
|
else:
|
||||||
if patch:
|
if patch:
|
||||||
patch.download()
|
patch.download()
|
||||||
|
|
||||||
Logger.debug("Ubuntu package: %s" % (task.package))
|
Logger.debug("Ubuntu package: %s", task.package)
|
||||||
if task.is_merge():
|
if task.is_merge():
|
||||||
Logger.debug("The task is a merge request.")
|
Logger.debug("The task is a merge request.")
|
||||||
if task.is_sync():
|
if task.is_sync():
|
||||||
@ -294,13 +303,12 @@ def _download_and_change_into(task, dsc_file, patch, branch):
|
|||||||
extract_source(dsc_file, Logger.isEnabledFor(logging.DEBUG))
|
extract_source(dsc_file, Logger.isEnabledFor(logging.DEBUG))
|
||||||
|
|
||||||
# change directory
|
# change directory
|
||||||
directory = task.package + '-' + task.get_version().upstream_version
|
directory = f"{task.package}-{task.get_version().upstream_version}"
|
||||||
Logger.debug("cd " + directory)
|
Logger.debug("cd %s", directory)
|
||||||
os.chdir(directory)
|
os.chdir(directory)
|
||||||
|
|
||||||
|
|
||||||
def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update,
|
def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update, upload, workdir):
|
||||||
upload, workdir):
|
|
||||||
workdir = os.path.realpath(os.path.expanduser(workdir))
|
workdir = os.path.realpath(os.path.expanduser(workdir))
|
||||||
_create_and_change_into(workdir)
|
_create_and_change_into(workdir)
|
||||||
|
|
||||||
@ -331,16 +339,12 @@ def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update,
|
|||||||
update = False
|
update = False
|
||||||
else:
|
else:
|
||||||
# We are going to run lintian, so we need a source package
|
# We are going to run lintian, so we need a source package
|
||||||
successful = source_package.build_source(None, upload,
|
successful = source_package.build_source(None, upload, previous_version)
|
||||||
previous_version)
|
|
||||||
|
|
||||||
if successful:
|
if successful:
|
||||||
series = task.get_debian_source_series()
|
series = task.get_debian_source_series()
|
||||||
if source_package.sync(upload, series, bug_number, bug.owner.name):
|
if source_package.sync(upload, series, bug_number, bug.owner.name):
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
edit = True
|
|
||||||
else:
|
|
||||||
edit = True
|
edit = True
|
||||||
|
|
||||||
if patch:
|
if patch:
|
||||||
@ -363,8 +367,7 @@ def sponsor_patch(bug_number, build, builder, edit, keyid, lpinstance, update,
|
|||||||
|
|
||||||
_update_timestamp()
|
_update_timestamp()
|
||||||
|
|
||||||
if not source_package.build_source(keyid, upload,
|
if not source_package.build_source(keyid, upload, task.get_previous_version()):
|
||||||
task.get_previous_version()):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
source_package.generate_debdiff(dsc_file)
|
source_package.generate_debdiff(dsc_file)
|
||||||
|
@ -17,66 +17,76 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ubuntutools.version import Version
|
from ubuntutools.version import Version
|
||||||
|
|
||||||
|
|
||||||
class ExamplePackage(object):
|
class ExamplePackage:
|
||||||
def __init__(self, source='example', version='1.0-1', destdir='test-data'):
|
def __init__(self, source="example", version="1.0-1", destdir="test-data"):
|
||||||
self.source = source
|
self.source = source
|
||||||
self.version = Version(version)
|
self.version = Version(version)
|
||||||
self.destdir = Path(destdir)
|
self.destdir = Path(destdir)
|
||||||
|
|
||||||
self.env = dict(os.environ)
|
self.env = dict(os.environ)
|
||||||
self.env['DEBFULLNAME'] = 'Example'
|
self.env["DEBFULLNAME"] = "Example"
|
||||||
self.env['DEBEMAIL'] = 'example@example.net'
|
self.env["DEBEMAIL"] = "example@example.net"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def orig(self):
|
def orig(self):
|
||||||
return self.destdir / f'{self.source}_{self.version.upstream_version}.orig.tar.xz'
|
return self.destdir / f"{self.source}_{self.version.upstream_version}.orig.tar.xz"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def debian(self):
|
def debian(self):
|
||||||
return self.destdir / f'{self.source}_{self.version}.debian.tar.xz'
|
return self.destdir / f"{self.source}_{self.version}.debian.tar.xz"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dsc(self):
|
def dsc(self):
|
||||||
return self.destdir / f'{self.source}_{self.version}.dsc'
|
return self.destdir / f"{self.source}_{self.version}.dsc"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dirname(self):
|
def dirname(self):
|
||||||
return f'{self.source}-{self.version.upstream_version}'
|
return f"{self.source}-{self.version.upstream_version}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content_filename(self):
|
def content_filename(self):
|
||||||
return 'content'
|
return "content"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content_text(self):
|
def content_text(self):
|
||||||
return 'my content'
|
return "my content"
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
self._create(Path(d))
|
self._create(Path(tmpdir))
|
||||||
|
|
||||||
def _create(self, d):
|
def _create(self, directory: Path):
|
||||||
pkgdir = d / self.dirname
|
pkgdir = directory / self.dirname
|
||||||
pkgdir.mkdir()
|
pkgdir.mkdir()
|
||||||
(pkgdir / self.content_filename).write_text(self.content_text)
|
(pkgdir / self.content_filename).write_text(self.content_text)
|
||||||
|
|
||||||
# run dh_make to create orig tarball
|
# run dh_make to create orig tarball
|
||||||
subprocess.run('dh_make -sy --createorig'.split(),
|
subprocess.run(
|
||||||
check=True, env=self.env, cwd=str(pkgdir),
|
"dh_make -sy --createorig".split(),
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
check=True,
|
||||||
|
env=self.env,
|
||||||
|
cwd=str(pkgdir),
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
# run dpkg-source -b to create debian tar and dsc
|
# run dpkg-source -b to create debian tar and dsc
|
||||||
subprocess.run(f'dpkg-source -b {self.dirname}'.split(),
|
subprocess.run(
|
||||||
check=True, env=self.env, cwd=str(d),
|
f"dpkg-source -b {self.dirname}".split(),
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
check=True,
|
||||||
|
env=self.env,
|
||||||
|
cwd=str(directory),
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
# move tarballs and dsc to destdir
|
# move tarballs and dsc to destdir
|
||||||
self.destdir.mkdir(parents=True, exist_ok=True)
|
self.destdir.mkdir(parents=True, exist_ok=True)
|
||||||
(d / self.orig.name).rename(self.orig)
|
(directory / self.orig.name).rename(self.orig)
|
||||||
(d / self.debian.name).rename(self.debian)
|
(directory / self.debian.name).rename(self.debian)
|
||||||
(d / self.dsc.name).rename(self.dsc)
|
(directory / self.dsc.name).rename(self.dsc)
|
||||||
|
@ -18,19 +18,17 @@
|
|||||||
import filecmp
|
import filecmp
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import ubuntutools.archive
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import ubuntutools.archive
|
||||||
from ubuntutools.test.example_package import ExamplePackage
|
from ubuntutools.test.example_package import ExamplePackage
|
||||||
|
|
||||||
|
|
||||||
class BaseVerificationTestCase(unittest.TestCase):
|
class BaseVerificationTestCase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
d = tempfile.TemporaryDirectory()
|
tmpdir = tempfile.TemporaryDirectory()
|
||||||
self.addCleanup(d.cleanup)
|
self.addCleanup(tmpdir.cleanup)
|
||||||
self.pkg = ExamplePackage(destdir=Path(d.name))
|
self.pkg = ExamplePackage(destdir=Path(tmpdir.name))
|
||||||
self.pkg.create()
|
self.pkg.create()
|
||||||
self.dsc = ubuntutools.archive.Dsc(self.pkg.dsc.read_bytes())
|
self.dsc = ubuntutools.archive.Dsc(self.pkg.dsc.read_bytes())
|
||||||
|
|
||||||
@ -41,7 +39,7 @@ class DscVerificationTestCase(BaseVerificationTestCase):
|
|||||||
self.assertTrue(self.dsc.verify_file(self.pkg.debian))
|
self.assertTrue(self.dsc.verify_file(self.pkg.debian))
|
||||||
|
|
||||||
def test_missing(self):
|
def test_missing(self):
|
||||||
self.assertFalse(self.dsc.verify_file(self.pkg.destdir / 'does.not.exist'))
|
self.assertFalse(self.dsc.verify_file(self.pkg.destdir / "does.not.exist"))
|
||||||
|
|
||||||
def test_bad(self):
|
def test_bad(self):
|
||||||
data = self.pkg.orig.read_bytes()
|
data = self.pkg.orig.read_bytes()
|
||||||
@ -51,13 +49,13 @@ class DscVerificationTestCase(BaseVerificationTestCase):
|
|||||||
self.assertFalse(self.dsc.verify_file(self.pkg.orig))
|
self.assertFalse(self.dsc.verify_file(self.pkg.orig))
|
||||||
|
|
||||||
def test_sha1(self):
|
def test_sha1(self):
|
||||||
del self.dsc['Checksums-Sha256']
|
del self.dsc["Checksums-Sha256"]
|
||||||
self.test_good()
|
self.test_good()
|
||||||
self.test_bad()
|
self.test_bad()
|
||||||
|
|
||||||
def test_md5(self):
|
def test_md5(self):
|
||||||
del self.dsc['Checksums-Sha256']
|
del self.dsc["Checksums-Sha256"]
|
||||||
del self.dsc['Checksums-Sha1']
|
del self.dsc["Checksums-Sha1"]
|
||||||
self.test_good()
|
self.test_good()
|
||||||
self.test_bad()
|
self.test_bad()
|
||||||
|
|
||||||
@ -67,17 +65,17 @@ class LocalSourcePackageTestCase(BaseVerificationTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
d = tempfile.TemporaryDirectory()
|
tmpdir = tempfile.TemporaryDirectory()
|
||||||
self.addCleanup(d.cleanup)
|
self.addCleanup(tmpdir.cleanup)
|
||||||
self.workdir = Path(d.name)
|
self.workdir = Path(tmpdir.name)
|
||||||
|
|
||||||
def pull(self, **kwargs):
|
def pull(self, **kwargs):
|
||||||
''' Do the pull from pkg dir to the workdir, return the SourcePackage '''
|
"""Do the pull from pkg dir to the workdir, return the SourcePackage"""
|
||||||
srcpkg = self.SourcePackage(dscfile=self.pkg.dsc, workdir=self.workdir, **kwargs)
|
srcpkg = self.SourcePackage(dscfile=self.pkg.dsc, workdir=self.workdir, **kwargs)
|
||||||
srcpkg.pull()
|
srcpkg.pull()
|
||||||
return srcpkg
|
return srcpkg
|
||||||
|
|
||||||
def test_pull(self, **kwargs):
|
def _test_pull(self, **kwargs):
|
||||||
srcpkg = self.pull(**kwargs)
|
srcpkg = self.pull(**kwargs)
|
||||||
self.assertTrue(filecmp.cmp(self.pkg.dsc, self.workdir / self.pkg.dsc.name))
|
self.assertTrue(filecmp.cmp(self.pkg.dsc, self.workdir / self.pkg.dsc.name))
|
||||||
self.assertTrue(filecmp.cmp(self.pkg.orig, self.workdir / self.pkg.orig.name))
|
self.assertTrue(filecmp.cmp(self.pkg.orig, self.workdir / self.pkg.orig.name))
|
||||||
@ -85,16 +83,16 @@ class LocalSourcePackageTestCase(BaseVerificationTestCase):
|
|||||||
return srcpkg
|
return srcpkg
|
||||||
|
|
||||||
def test_unpack(self, **kwargs):
|
def test_unpack(self, **kwargs):
|
||||||
srcpkg = kwargs.get('srcpkg', self.pull(**kwargs))
|
srcpkg = kwargs.get("srcpkg", self.pull(**kwargs))
|
||||||
srcpkg.unpack()
|
srcpkg.unpack()
|
||||||
content = self.workdir / self.pkg.dirname / self.pkg.content_filename
|
content = self.workdir / self.pkg.dirname / self.pkg.content_filename
|
||||||
self.assertEqual(self.pkg.content_text, content.read_text())
|
self.assertEqual(self.pkg.content_text, content.read_text())
|
||||||
debian = self.workdir / self.pkg.dirname / 'debian'
|
debian = self.workdir / self.pkg.dirname / "debian"
|
||||||
self.assertTrue(debian.exists())
|
self.assertTrue(debian.exists())
|
||||||
self.assertTrue(debian.is_dir())
|
self.assertTrue(debian.is_dir())
|
||||||
|
|
||||||
def test_pull_and_unpack(self, **kwargs):
|
def test_pull_and_unpack(self, **kwargs):
|
||||||
self.test_unpack(srcpkg=self.test_pull(**kwargs))
|
self.test_unpack(srcpkg=self._test_pull(**kwargs))
|
||||||
|
|
||||||
def test_with_package(self):
|
def test_with_package(self):
|
||||||
self.test_pull_and_unpack(package=self.pkg.source)
|
self.test_pull_and_unpack(package=self.pkg.source)
|
||||||
@ -103,12 +101,12 @@ class LocalSourcePackageTestCase(BaseVerificationTestCase):
|
|||||||
self.test_pull_and_unpack(package=self.pkg.source, version=self.pkg.version)
|
self.test_pull_and_unpack(package=self.pkg.source, version=self.pkg.version)
|
||||||
|
|
||||||
def test_with_package_version_component(self):
|
def test_with_package_version_component(self):
|
||||||
self.test_pull_and_unpack(package=self.pkg.source,
|
self.test_pull_and_unpack(
|
||||||
version=self.pkg.version,
|
package=self.pkg.source, version=self.pkg.version, componet="main"
|
||||||
componet='main')
|
)
|
||||||
|
|
||||||
def test_verification(self):
|
def test_verification(self):
|
||||||
corruption = b'CORRUPTION'
|
corruption = b"CORRUPTION"
|
||||||
|
|
||||||
self.pull()
|
self.pull()
|
||||||
|
|
||||||
@ -119,7 +117,7 @@ class LocalSourcePackageTestCase(BaseVerificationTestCase):
|
|||||||
testfile.write_bytes(corruption)
|
testfile.write_bytes(corruption)
|
||||||
self.assertEqual(testfile.read_bytes(), corruption)
|
self.assertEqual(testfile.read_bytes(), corruption)
|
||||||
|
|
||||||
self.test_pull()
|
self._test_pull()
|
||||||
self.assertTrue(testfile.exists())
|
self.assertTrue(testfile.exists())
|
||||||
self.assertTrue(testfile.is_file())
|
self.assertTrue(testfile.is_file())
|
||||||
self.assertNotEqual(testfile.read_bytes(), corruption)
|
self.assertNotEqual(testfile.read_bytes(), corruption)
|
||||||
|
@ -17,9 +17,7 @@
|
|||||||
|
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
# import sys
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@ -27,27 +25,25 @@ from ubuntutools.config import UDTConfig, ubu_email
|
|||||||
|
|
||||||
|
|
||||||
class ConfigTestCase(unittest.TestCase):
|
class ConfigTestCase(unittest.TestCase):
|
||||||
_config_files = {
|
_config_files = {"system": "", "user": ""}
|
||||||
'system': '',
|
|
||||||
'user': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
def _fake_open(self, filename, mode='r'):
|
def _fake_open(self, filename, mode="r", encoding=None):
|
||||||
if mode != 'r':
|
self.assertTrue(encoding, f"encoding for {filename} not specified")
|
||||||
|
if mode != "r":
|
||||||
raise IOError("Read only fake-file")
|
raise IOError("Read only fake-file")
|
||||||
files = {
|
files = {
|
||||||
'/etc/devscripts.conf': self._config_files['system'],
|
"/etc/devscripts.conf": self._config_files["system"],
|
||||||
os.path.expanduser('~/.devscripts'): self._config_files['user'],
|
os.path.expanduser("~/.devscripts"): self._config_files["user"],
|
||||||
}
|
}
|
||||||
if filename not in files:
|
if filename not in files:
|
||||||
raise IOError("No such file or directory: '%s'" % filename)
|
raise IOError(f"No such file or directory: '{filename}'")
|
||||||
return StringIO(files[filename])
|
return StringIO(files[filename])
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ConfigTestCase, self).setUp()
|
super().setUp()
|
||||||
m = mock.mock_open()
|
open_mock = mock.mock_open()
|
||||||
m.side_effect = self._fake_open
|
open_mock.side_effect = self._fake_open
|
||||||
patcher = mock.patch('builtins.open', m)
|
patcher = mock.patch("builtins.open", open_mock)
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
|
|
||||||
@ -65,14 +61,16 @@ class ConfigTestCase(unittest.TestCase):
|
|||||||
self.clean_environment()
|
self.clean_environment()
|
||||||
|
|
||||||
def clean_environment(self):
|
def clean_environment(self):
|
||||||
self._config_files['system'] = ''
|
self._config_files["system"] = ""
|
||||||
self._config_files['user'] = ''
|
self._config_files["user"] = ""
|
||||||
for k in list(os.environ.keys()):
|
for k in list(os.environ.keys()):
|
||||||
if k.startswith(('UBUNTUTOOLS_', 'TEST_')):
|
if k.startswith(("UBUNTUTOOLS_", "TEST_")):
|
||||||
del os.environ[k]
|
del os.environ[k]
|
||||||
|
|
||||||
def test_config_parsing(self):
|
def test_config_parsing(self):
|
||||||
self._config_files['user'] = """#COMMENT=yes
|
self._config_files[
|
||||||
|
"user"
|
||||||
|
] = """#COMMENT=yes
|
||||||
\tTAB_INDENTED=yes
|
\tTAB_INDENTED=yes
|
||||||
SPACE_INDENTED=yes
|
SPACE_INDENTED=yes
|
||||||
SPACE_SUFFIX=yes
|
SPACE_SUFFIX=yes
|
||||||
@ -85,59 +83,64 @@ INHERIT=user
|
|||||||
REPEAT=no
|
REPEAT=no
|
||||||
REPEAT=yes
|
REPEAT=yes
|
||||||
"""
|
"""
|
||||||
self._config_files['system'] = 'INHERIT=system'
|
self._config_files["system"] = "INHERIT=system"
|
||||||
self.assertEqual(UDTConfig(prefix='TEST').config, {
|
self.assertEqual(
|
||||||
'TAB_INDENTED': 'yes',
|
UDTConfig(prefix="TEST").config,
|
||||||
'SPACE_INDENTED': 'yes',
|
{
|
||||||
'SPACE_SUFFIX': 'yes',
|
"TAB_INDENTED": "yes",
|
||||||
'SINGLE_QUOTE': 'yes no',
|
"SPACE_INDENTED": "yes",
|
||||||
'DOUBLE_QUOTE': 'yes no',
|
"SPACE_SUFFIX": "yes",
|
||||||
'QUOTED_QUOTE': "it's",
|
"SINGLE_QUOTE": "yes no",
|
||||||
'PAIR_QUOTES': 'yes a no',
|
"DOUBLE_QUOTE": "yes no",
|
||||||
'COMMAND_EXECUTION': 'a',
|
"QUOTED_QUOTE": "it's",
|
||||||
'INHERIT': 'user',
|
"PAIR_QUOTES": "yes a no",
|
||||||
'REPEAT': 'yes',
|
"COMMAND_EXECUTION": "a",
|
||||||
})
|
"INHERIT": "user",
|
||||||
|
"REPEAT": "yes",
|
||||||
|
},
|
||||||
|
)
|
||||||
# errs = Logger.stderr.getvalue().strip()
|
# errs = Logger.stderr.getvalue().strip()
|
||||||
# Logger.stderr = StringIO()
|
# Logger.stderr = StringIO()
|
||||||
# self.assertEqual(len(errs.splitlines()), 1)
|
# self.assertEqual(len(errs.splitlines()), 1)
|
||||||
# self.assertRegex(errs,
|
# self.assertRegex(errs,
|
||||||
# r'Warning: Cannot parse.*\bCOMMAND_EXECUTION=a')
|
# r'Warning: Cannot parse.*\bCOMMAND_EXECUTION=a')
|
||||||
|
|
||||||
def get_value(self, *args, **kwargs):
|
@staticmethod
|
||||||
config = UDTConfig(prefix='TEST')
|
def get_value(*args, **kwargs):
|
||||||
|
config = UDTConfig(prefix="TEST")
|
||||||
return config.get_value(*args, **kwargs)
|
return config.get_value(*args, **kwargs)
|
||||||
|
|
||||||
def test_defaults(self):
|
def test_defaults(self):
|
||||||
self.assertEqual(self.get_value('BUILDER'), 'pbuilder')
|
self.assertEqual(self.get_value("BUILDER"), "pbuilder")
|
||||||
|
|
||||||
def test_provided_default(self):
|
def test_provided_default(self):
|
||||||
self.assertEqual(self.get_value('BUILDER', default='foo'), 'foo')
|
self.assertEqual(self.get_value("BUILDER", default="foo"), "foo")
|
||||||
|
|
||||||
def test_scriptname_precedence(self):
|
def test_scriptname_precedence(self):
|
||||||
self._config_files['user'] = """TEST_BUILDER=foo
|
self._config_files[
|
||||||
|
"user"
|
||||||
|
] = """TEST_BUILDER=foo
|
||||||
UBUNTUTOOLS_BUILDER=bar"""
|
UBUNTUTOOLS_BUILDER=bar"""
|
||||||
self.assertEqual(self.get_value('BUILDER'), 'foo')
|
self.assertEqual(self.get_value("BUILDER"), "foo")
|
||||||
|
|
||||||
def test_configfile_precedence(self):
|
def test_configfile_precedence(self):
|
||||||
self._config_files['system'] = "UBUNTUTOOLS_BUILDER=foo"
|
self._config_files["system"] = "UBUNTUTOOLS_BUILDER=foo"
|
||||||
self._config_files['user'] = "UBUNTUTOOLS_BUILDER=bar"
|
self._config_files["user"] = "UBUNTUTOOLS_BUILDER=bar"
|
||||||
self.assertEqual(self.get_value('BUILDER'), 'bar')
|
self.assertEqual(self.get_value("BUILDER"), "bar")
|
||||||
|
|
||||||
def test_environment_precedence(self):
|
def test_environment_precedence(self):
|
||||||
self._config_files['user'] = "UBUNTUTOOLS_BUILDER=bar"
|
self._config_files["user"] = "UBUNTUTOOLS_BUILDER=bar"
|
||||||
os.environ['UBUNTUTOOLS_BUILDER'] = 'baz'
|
os.environ["UBUNTUTOOLS_BUILDER"] = "baz"
|
||||||
self.assertEqual(self.get_value('BUILDER'), 'baz')
|
self.assertEqual(self.get_value("BUILDER"), "baz")
|
||||||
|
|
||||||
def test_general_environment_specific_config_precedence(self):
|
def test_general_environment_specific_config_precedence(self):
|
||||||
self._config_files['user'] = "TEST_BUILDER=bar"
|
self._config_files["user"] = "TEST_BUILDER=bar"
|
||||||
os.environ['UBUNTUTOOLS_BUILDER'] = 'foo'
|
os.environ["UBUNTUTOOLS_BUILDER"] = "foo"
|
||||||
self.assertEqual(self.get_value('BUILDER'), 'bar')
|
self.assertEqual(self.get_value("BUILDER"), "bar")
|
||||||
|
|
||||||
def test_compat_keys(self):
|
def test_compat_keys(self):
|
||||||
self._config_files['user'] = 'COMPATFOOBAR=bar'
|
self._config_files["user"] = "COMPATFOOBAR=bar"
|
||||||
self.assertEqual(self.get_value('QUX', compat_keys=['COMPATFOOBAR']),
|
self.assertEqual(self.get_value("QUX", compat_keys=["COMPATFOOBAR"]), "bar")
|
||||||
'bar')
|
|
||||||
# errs = Logger.stderr.getvalue().strip()
|
# errs = Logger.stderr.getvalue().strip()
|
||||||
# Logger.stderr = StringIO()
|
# Logger.stderr = StringIO()
|
||||||
# self.assertEqual(len(errs.splitlines()), 1)
|
# self.assertEqual(len(errs.splitlines()), 1)
|
||||||
@ -145,16 +148,16 @@ REPEAT=yes
|
|||||||
# r'deprecated.*\bCOMPATFOOBAR\b.*\bTEST_QUX\b')
|
# r'deprecated.*\bCOMPATFOOBAR\b.*\bTEST_QUX\b')
|
||||||
|
|
||||||
def test_boolean(self):
|
def test_boolean(self):
|
||||||
self._config_files['user'] = "TEST_BOOLEAN=yes"
|
self._config_files["user"] = "TEST_BOOLEAN=yes"
|
||||||
self.assertEqual(self.get_value('BOOLEAN', boolean=True), True)
|
self.assertEqual(self.get_value("BOOLEAN", boolean=True), True)
|
||||||
self._config_files['user'] = "TEST_BOOLEAN=no"
|
self._config_files["user"] = "TEST_BOOLEAN=no"
|
||||||
self.assertEqual(self.get_value('BOOLEAN', boolean=True), False)
|
self.assertEqual(self.get_value("BOOLEAN", boolean=True), False)
|
||||||
self._config_files['user'] = "TEST_BOOLEAN=true"
|
self._config_files["user"] = "TEST_BOOLEAN=true"
|
||||||
self.assertEqual(self.get_value('BOOLEAN', boolean=True), None)
|
self.assertEqual(self.get_value("BOOLEAN", boolean=True), None)
|
||||||
|
|
||||||
def test_nonpackagewide(self):
|
def test_nonpackagewide(self):
|
||||||
self._config_files['user'] = 'UBUNTUTOOLS_FOOBAR=a'
|
self._config_files["user"] = "UBUNTUTOOLS_FOOBAR=a"
|
||||||
self.assertEqual(self.get_value('FOOBAR'), None)
|
self.assertEqual(self.get_value("FOOBAR"), None)
|
||||||
|
|
||||||
|
|
||||||
class UbuEmailTestCase(unittest.TestCase):
|
class UbuEmailTestCase(unittest.TestCase):
|
||||||
@ -164,72 +167,72 @@ class UbuEmailTestCase(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.clean_environment()
|
self.clean_environment()
|
||||||
|
|
||||||
def clean_environment(self):
|
@staticmethod
|
||||||
for k in ('UBUMAIL', 'DEBEMAIL', 'DEBFULLNAME'):
|
def clean_environment():
|
||||||
|
for k in ("UBUMAIL", "DEBEMAIL", "DEBFULLNAME"):
|
||||||
if k in os.environ:
|
if k in os.environ:
|
||||||
del os.environ[k]
|
del os.environ[k]
|
||||||
|
|
||||||
def test_pristine(self):
|
def test_pristine(self):
|
||||||
os.environ['DEBFULLNAME'] = name = 'Joe Developer'
|
os.environ["DEBFULLNAME"] = name = "Joe Developer"
|
||||||
os.environ['DEBEMAIL'] = email = 'joe@example.net'
|
os.environ["DEBEMAIL"] = email = "joe@example.net"
|
||||||
self.assertEqual(ubu_email(), (name, email))
|
self.assertEqual(ubu_email(), (name, email))
|
||||||
|
|
||||||
def test_two_hat(self):
|
def test_two_hat(self):
|
||||||
os.environ['DEBFULLNAME'] = name = 'Joe Developer'
|
os.environ["DEBFULLNAME"] = name = "Joe Developer"
|
||||||
os.environ['DEBEMAIL'] = 'joe@debian.org'
|
os.environ["DEBEMAIL"] = "joe@debian.org"
|
||||||
os.environ['UBUMAIL'] = email = 'joe@ubuntu.com'
|
os.environ["UBUMAIL"] = email = "joe@ubuntu.com"
|
||||||
self.assertEqual(ubu_email(), (name, email))
|
self.assertEqual(ubu_email(), (name, email))
|
||||||
self.assertEqual(os.environ['DEBFULLNAME'], name)
|
self.assertEqual(os.environ["DEBFULLNAME"], name)
|
||||||
self.assertEqual(os.environ['DEBEMAIL'], email)
|
self.assertEqual(os.environ["DEBEMAIL"], email)
|
||||||
|
|
||||||
def test_two_hat_cmdlineoverride(self):
|
def test_two_hat_cmdlineoverride(self):
|
||||||
os.environ['DEBFULLNAME'] = 'Joe Developer'
|
os.environ["DEBFULLNAME"] = "Joe Developer"
|
||||||
os.environ['DEBEMAIL'] = 'joe@debian.org'
|
os.environ["DEBEMAIL"] = "joe@debian.org"
|
||||||
os.environ['UBUMAIL'] = 'joe@ubuntu.com'
|
os.environ["UBUMAIL"] = "joe@ubuntu.com"
|
||||||
name = 'Foo Bar'
|
name = "Foo Bar"
|
||||||
email = 'joe@example.net'
|
email = "joe@example.net"
|
||||||
self.assertEqual(ubu_email(name, email), (name, email))
|
self.assertEqual(ubu_email(name, email), (name, email))
|
||||||
self.assertEqual(os.environ['DEBFULLNAME'], name)
|
self.assertEqual(os.environ["DEBFULLNAME"], name)
|
||||||
self.assertEqual(os.environ['DEBEMAIL'], email)
|
self.assertEqual(os.environ["DEBEMAIL"], email)
|
||||||
|
|
||||||
def test_two_hat_noexport(self):
|
def test_two_hat_noexport(self):
|
||||||
os.environ['DEBFULLNAME'] = name = 'Joe Developer'
|
os.environ["DEBFULLNAME"] = name = "Joe Developer"
|
||||||
os.environ['DEBEMAIL'] = demail = 'joe@debian.org'
|
os.environ["DEBEMAIL"] = demail = "joe@debian.org"
|
||||||
os.environ['UBUMAIL'] = uemail = 'joe@ubuntu.com'
|
os.environ["UBUMAIL"] = uemail = "joe@ubuntu.com"
|
||||||
self.assertEqual(ubu_email(export=False), (name, uemail))
|
self.assertEqual(ubu_email(export=False), (name, uemail))
|
||||||
self.assertEqual(os.environ['DEBFULLNAME'], name)
|
self.assertEqual(os.environ["DEBFULLNAME"], name)
|
||||||
self.assertEqual(os.environ['DEBEMAIL'], demail)
|
self.assertEqual(os.environ["DEBEMAIL"], demail)
|
||||||
|
|
||||||
def test_two_hat_with_name(self):
|
def test_two_hat_with_name(self):
|
||||||
os.environ['DEBFULLNAME'] = 'Joe Developer'
|
os.environ["DEBFULLNAME"] = "Joe Developer"
|
||||||
os.environ['DEBEMAIL'] = 'joe@debian.org'
|
os.environ["DEBEMAIL"] = "joe@debian.org"
|
||||||
name = 'Joe Ubuntunista'
|
name = "Joe Ubuntunista"
|
||||||
email = 'joe@ubuntu.com'
|
email = "joe@ubuntu.com"
|
||||||
os.environ['UBUMAIL'] = '%s <%s>' % (name, email)
|
os.environ["UBUMAIL"] = f"{name} <{email}>"
|
||||||
self.assertEqual(ubu_email(), (name, email))
|
self.assertEqual(ubu_email(), (name, email))
|
||||||
self.assertEqual(os.environ['DEBFULLNAME'], name)
|
self.assertEqual(os.environ["DEBFULLNAME"], name)
|
||||||
self.assertEqual(os.environ['DEBEMAIL'], email)
|
self.assertEqual(os.environ["DEBEMAIL"], email)
|
||||||
|
|
||||||
def test_debemail_with_name(self):
|
def test_debemail_with_name(self):
|
||||||
name = 'Joe Developer'
|
name = "Joe Developer"
|
||||||
email = 'joe@example.net'
|
email = "joe@example.net"
|
||||||
os.environ['DEBEMAIL'] = orig = '%s <%s>' % (name, email)
|
os.environ["DEBEMAIL"] = orig = f"{name} <{email}>"
|
||||||
self.assertEqual(ubu_email(), (name, email))
|
self.assertEqual(ubu_email(), (name, email))
|
||||||
self.assertEqual(os.environ['DEBEMAIL'], orig)
|
self.assertEqual(os.environ["DEBEMAIL"], orig)
|
||||||
|
|
||||||
def test_unicode_name(self):
|
def test_unicode_name(self):
|
||||||
encoding = locale.getdefaultlocale()[1]
|
encoding = locale.getlocale()[1]
|
||||||
if not encoding:
|
if not encoding:
|
||||||
encoding = 'utf-8'
|
encoding = "utf-8"
|
||||||
name = 'Jöe Déveloper'
|
name = "Jöe Déveloper"
|
||||||
env_name = name
|
env_name = name
|
||||||
if isinstance(name, bytes):
|
if isinstance(name, bytes):
|
||||||
name = 'Jöe Déveloper'.decode('utf-8')
|
name = "Jöe Déveloper".decode("utf-8")
|
||||||
env_name = name.encode(encoding)
|
env_name = name.encode(encoding)
|
||||||
try:
|
try:
|
||||||
os.environ['DEBFULLNAME'] = env_name
|
os.environ["DEBFULLNAME"] = env_name
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
raise unittest.SkipTest("python interpreter is not running in an "
|
self.skipTest("python interpreter is not running in an unicode capable locale")
|
||||||
"unicode capable locale")
|
os.environ["DEBEMAIL"] = email = "joe@example.net"
|
||||||
os.environ['DEBEMAIL'] = email = 'joe@example.net'
|
|
||||||
self.assertEqual(ubu_email(), (name, email))
|
self.assertEqual(ubu_email(), (name, email))
|
||||||
|
@ -19,7 +19,6 @@ import unittest
|
|||||||
|
|
||||||
from setup import scripts
|
from setup import scripts
|
||||||
|
|
||||||
|
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
@ -27,10 +26,12 @@ class HelpTestCase(unittest.TestCase):
|
|||||||
def test_script(self):
|
def test_script(self):
|
||||||
for script in scripts:
|
for script in scripts:
|
||||||
with self.subTest(script=script):
|
with self.subTest(script=script):
|
||||||
result = subprocess.run([f'./{script}', '--help'],
|
result = subprocess.run(
|
||||||
encoding='UTF-8',
|
[f"./{script}", "--help"],
|
||||||
|
encoding="UTF-8",
|
||||||
timeout=10,
|
timeout=10,
|
||||||
check=True,
|
check=True,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
self.assertFalse(result.stderr.strip())
|
self.assertFalse(result.stderr.strip())
|
||||||
|
33
ubuntutools/test/test_requestsync.py
Normal file
33
ubuntutools/test/test_requestsync.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (C) 2024 Canonical Ltd.
|
||||||
|
# Author: Chris Peterson <chris.peterson@canonical.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 unittest
|
||||||
|
|
||||||
|
|
||||||
|
# Binary Tests
|
||||||
|
class BinaryTests(unittest.TestCase):
|
||||||
|
|
||||||
|
# The requestsync binary has the option of using the launchpad api
|
||||||
|
# to log in but requires python3-keyring in addition to
|
||||||
|
# python3-launchpadlib. Testing the integrated login functionality
|
||||||
|
# automatically isn't very feasbile, but we can at least write a smoke
|
||||||
|
# test to make sure the required packages are installed.
|
||||||
|
# See LP: #2049217
|
||||||
|
def test_keyring_installed(self):
|
||||||
|
"""Smoke test for required lp api dependencies"""
|
||||||
|
try:
|
||||||
|
import keyring # noqa: F401
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
raise ModuleNotFoundError("package python3-keyring is not installed")
|
128
ubuntutools/test/test_running_autopkgtests.py
Normal file
128
ubuntutools/test/test_running_autopkgtests.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Copyright (C) 2024 Canonical Ltd.
|
||||||
|
# Author: Chris Peterson <chris.peterson@canonical.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.
|
||||||
|
""" Tests for running_autopkgtests
|
||||||
|
Tests using cached data from autopkgtest servers.
|
||||||
|
|
||||||
|
These tests only ensure code changes don't change parsing behavior
|
||||||
|
of the response data. If the response format changes, then the cached
|
||||||
|
responses will need to change as well.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from ubuntutools.running_autopkgtests import (
|
||||||
|
URL_QUEUED,
|
||||||
|
URL_RUNNING,
|
||||||
|
_get_jobs,
|
||||||
|
get_queued,
|
||||||
|
get_running,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cached binary response data from autopkgtest server
|
||||||
|
RUN_DATA = (
|
||||||
|
b'{"pyatem": {'
|
||||||
|
b" \"submit-time_2024-01-19 19:37:36;triggers_['python3-defaults/3.12.1-0ubuntu1'];\":"
|
||||||
|
b' {"noble": {"arm64": [{"triggers": ["python3-defaults/3.12.1-0ubuntu1"],'
|
||||||
|
b' "submit-time": "2024-01-19 19:37:36"}, 380, "<omitted log>"]}}}}'
|
||||||
|
)
|
||||||
|
QUEUED_DATA = (
|
||||||
|
b'{"ubuntu": {"noble": {"arm64": ["libobject-accessor-perl {\\"requester\\": \\"someone\\",'
|
||||||
|
b' \\"submit-time\\": \\"2024-01-18 01:08:55\\",'
|
||||||
|
b' \\"triggers\\": [\\"perl/5.38.2-3\\", \\"liblocale-gettext-perl/1.07-6build1\\"]}"]}}}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Expected result(s) of parsing the above JSON data
|
||||||
|
RUNNING_JOB = {
|
||||||
|
"pyatem": {
|
||||||
|
"submit-time_2024-01-19 19:37:36;triggers_['python3-defaults/3.12.1-0ubuntu1'];": {
|
||||||
|
"noble": {
|
||||||
|
"arm64": [
|
||||||
|
{
|
||||||
|
"triggers": ["python3-defaults/3.12.1-0ubuntu1"],
|
||||||
|
"submit-time": "2024-01-19 19:37:36",
|
||||||
|
},
|
||||||
|
380,
|
||||||
|
"<omitted log>",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QUEUED_JOB = {
|
||||||
|
"ubuntu": {
|
||||||
|
"noble": {
|
||||||
|
"arm64": [
|
||||||
|
'libobject-accessor-perl {"requester": "someone",'
|
||||||
|
' "submit-time": "2024-01-18 01:08:55",'
|
||||||
|
' "triggers": ["perl/5.38.2-3", "liblocale-gettext-perl/1.07-6build1"]}'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PRIVATE_JOB = {"ppa": {"noble": {"arm64": ["private job"]}}}
|
||||||
|
|
||||||
|
|
||||||
|
# Expected textual output of the program based on the above data
|
||||||
|
RUNNING_OUTPUT = (
|
||||||
|
"R 0:06:20 pyatem - noble arm64"
|
||||||
|
" - python3-defaults/3.12.1-0ubuntu1 -\n"
|
||||||
|
)
|
||||||
|
QUEUED_OUTPUT = (
|
||||||
|
"Q0001 -:-- libobject-accessor-perl ubuntu noble arm64"
|
||||||
|
" - perl/5.38.2-3,liblocale-gettext-perl/1.07-6build1\n"
|
||||||
|
)
|
||||||
|
PRIVATE_OUTPUT = (
|
||||||
|
"Q0001 -:-- private job ppa noble arm64"
|
||||||
|
" private job private job\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RunningAutopkgtestTestCase(unittest.TestCase):
|
||||||
|
"""Assert helper functions parse data correctly"""
|
||||||
|
|
||||||
|
maxDiff = None
|
||||||
|
|
||||||
|
@patch("urllib.request.urlopen")
|
||||||
|
def test_get_running_jobs(self, mock_response):
|
||||||
|
"""Test: Correctly parse autopkgtest json data for running tests"""
|
||||||
|
mock_response.return_value.__enter__.return_value.read.return_value = RUN_DATA
|
||||||
|
jobs = _get_jobs(URL_RUNNING)
|
||||||
|
self.assertEqual(RUNNING_JOB, jobs)
|
||||||
|
|
||||||
|
@patch("urllib.request.urlopen")
|
||||||
|
def test_get_queued_jobs(self, mock_response):
|
||||||
|
"""Test: Correctly parse autopkgtest json data for queued tests"""
|
||||||
|
mock_response.return_value.__enter__.return_value.read.return_value = QUEUED_DATA
|
||||||
|
jobs = _get_jobs(URL_QUEUED)
|
||||||
|
self.assertEqual(QUEUED_JOB, jobs)
|
||||||
|
|
||||||
|
def test_get_running_output(self):
|
||||||
|
"""Test: Correctly print running tests"""
|
||||||
|
with patch("ubuntutools.running_autopkgtests._get_jobs", return_value=RUNNING_JOB):
|
||||||
|
self.assertEqual(get_running(), RUNNING_OUTPUT)
|
||||||
|
|
||||||
|
def test_get_queued_output(self):
|
||||||
|
"""Test: Correctly print queued tests"""
|
||||||
|
with patch("ubuntutools.running_autopkgtests._get_jobs", return_value=QUEUED_JOB):
|
||||||
|
self.assertEqual(get_queued(), QUEUED_OUTPUT)
|
||||||
|
|
||||||
|
def test_private_queued_job(self):
|
||||||
|
"""Test: Correctly print queued private job"""
|
||||||
|
with patch("ubuntutools.running_autopkgtests._get_jobs", return_value=PRIVATE_JOB):
|
||||||
|
self.assertEqual(get_queued(), PRIVATE_OUTPUT)
|
@ -17,9 +17,7 @@
|
|||||||
"""Test suite for ubuntutools.update_maintainer"""
|
"""Test suite for ubuntutools.update_maintainer"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
# import sys
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@ -167,8 +165,7 @@ Source: seahorse-plugins
|
|||||||
Section: gnome
|
Section: gnome
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Emilio Pozuelo Monfort <pochu@debian.org>
|
Maintainer: Emilio Pozuelo Monfort <pochu@debian.org>
|
||||||
Build-Depends: debhelper (>= 5),
|
Build-Depends: debhelper (>= 5)
|
||||||
cdbs (>= 0.4.41)
|
|
||||||
Standards-Version: 3.8.3
|
Standards-Version: 3.8.3
|
||||||
Homepage: http://live.gnome.org/Seahorse
|
Homepage: http://live.gnome.org/Seahorse
|
||||||
|
|
||||||
@ -186,8 +183,7 @@ Section: gnome
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
|
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
|
||||||
XSBC-Original-Maintainer: Emilio Pozuelo Monfort <pochu@debian.org>
|
XSBC-Original-Maintainer: Emilio Pozuelo Monfort <pochu@debian.org>
|
||||||
Build-Depends: debhelper (>= 5),
|
Build-Depends: debhelper (>= 5)
|
||||||
cdbs (>= 0.4.41)
|
|
||||||
Standards-Version: 3.8.3
|
Standards-Version: 3.8.3
|
||||||
Homepage: http://live.gnome.org/Seahorse
|
Homepage: http://live.gnome.org/Seahorse
|
||||||
|
|
||||||
@ -200,25 +196,25 @@ class UpdateMaintainerTestCase(unittest.TestCase):
|
|||||||
"""TestCase object for ubuntutools.update_maintainer"""
|
"""TestCase object for ubuntutools.update_maintainer"""
|
||||||
|
|
||||||
_directory = "/"
|
_directory = "/"
|
||||||
_files = {
|
_files = {"changelog": None, "control": None, "control.in": None, "rules": None}
|
||||||
"changelog": None,
|
|
||||||
"control": None,
|
|
||||||
"control.in": None,
|
|
||||||
"rules": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _fake_isfile(self, filename):
|
def _fake_isfile(self, filename):
|
||||||
"""Check only for existing fake files."""
|
"""Check only for existing fake files."""
|
||||||
directory, base = os.path.split(filename)
|
directory, base = os.path.split(filename)
|
||||||
return (directory == self._directory and base in self._files and
|
return (
|
||||||
self._files[base] is not None)
|
directory == self._directory and base in self._files and self._files[base] is not None
|
||||||
|
)
|
||||||
|
|
||||||
def _fake_open(self, filename, mode='r'):
|
def _fake_open(self, filename, mode="r", encoding=None):
|
||||||
"""Provide StringIO objects instead of real files."""
|
"""Provide StringIO objects instead of real files."""
|
||||||
|
self.assertTrue(encoding, f"encoding for {filename} not specified")
|
||||||
directory, base = os.path.split(filename)
|
directory, base = os.path.split(filename)
|
||||||
if (directory != self._directory or base not in self._files or
|
if (
|
||||||
(mode == "r" and self._files[base] is None)):
|
directory != self._directory
|
||||||
raise IOError("No such file or directory: '%s'" % filename)
|
or base not in self._files
|
||||||
|
or (mode == "r" and self._files[base] is None)
|
||||||
|
):
|
||||||
|
raise IOError(f"No such file or directory: '{filename}'")
|
||||||
if mode == "w":
|
if mode == "w":
|
||||||
self._files[base] = StringIO()
|
self._files[base] = StringIO()
|
||||||
self._files[base].close = lambda: None
|
self._files[base].close = lambda: None
|
||||||
@ -228,11 +224,11 @@ class UpdateMaintainerTestCase(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
m = mock.mock_open()
|
m = mock.mock_open()
|
||||||
m.side_effect = self._fake_open
|
m.side_effect = self._fake_open
|
||||||
patcher = mock.patch('builtins.open', m)
|
patcher = mock.patch("builtins.open", m)
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
m = mock.MagicMock(side_effect=self._fake_isfile)
|
m = mock.MagicMock(side_effect=self._fake_isfile)
|
||||||
patcher = mock.patch('os.path.isfile', m)
|
patcher = mock.patch("os.path.isfile", m)
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
self._files["rules"] = StringIO(_SIMPLE_RULES)
|
self._files["rules"] = StringIO(_SIMPLE_RULES)
|
||||||
@ -292,8 +288,7 @@ class UpdateMaintainerTestCase(unittest.TestCase):
|
|||||||
self._files["changelog"] = StringIO(_LUCID_CHANGELOG)
|
self._files["changelog"] = StringIO(_LUCID_CHANGELOG)
|
||||||
self._files["control"] = StringIO(_SEAHORSE_PLUGINS_CONTROL)
|
self._files["control"] = StringIO(_SEAHORSE_PLUGINS_CONTROL)
|
||||||
update_maintainer(self._directory)
|
update_maintainer(self._directory)
|
||||||
self.assertEqual(self._files["control"].getvalue(),
|
self.assertEqual(self._files["control"].getvalue(), _SEAHORSE_PLUGINS_UPDATED)
|
||||||
_SEAHORSE_PLUGINS_UPDATED)
|
|
||||||
|
|
||||||
def test_skip_smart_rules(self):
|
def test_skip_smart_rules(self):
|
||||||
"""Test: Skip update when XSBC-Original in debian/rules."""
|
"""Test: Skip update when XSBC-Original in debian/rules."""
|
||||||
|
@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
"""This module is for updating the Maintainer field of an Ubuntu package."""
|
"""This module is for updating the Maintainer field of an Ubuntu package."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import debian.changelog
|
import debian.changelog
|
||||||
|
|
||||||
import logging
|
|
||||||
Logger = logging.getLogger(__name__)
|
Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Prior May 2009 these Maintainers were used:
|
# Prior May 2009 these Maintainers were used:
|
||||||
@ -37,26 +37,26 @@ class MaintainerUpdateException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Control(object):
|
class Control:
|
||||||
"""Represents a debian/control file"""
|
"""Represents a debian/control file"""
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
assert os.path.isfile(filename), "%s does not exist." % (filename)
|
assert os.path.isfile(filename), f"{filename} does not exist."
|
||||||
self._filename = filename
|
self._filename = filename
|
||||||
self._content = open(filename).read()
|
self._content = open(filename, encoding="utf-8").read()
|
||||||
|
|
||||||
def get_maintainer(self):
|
def get_maintainer(self):
|
||||||
"""Returns the value of the Maintainer field."""
|
"""Returns the value of the Maintainer field."""
|
||||||
maintainer = re.search("^Maintainer: ?(.*)$", self._content,
|
maintainer = re.search("^Maintainer: ?(.*)$", self._content, re.MULTILINE)
|
||||||
re.MULTILINE)
|
|
||||||
if maintainer:
|
if maintainer:
|
||||||
maintainer = maintainer.group(1)
|
maintainer = maintainer.group(1)
|
||||||
return maintainer
|
return maintainer
|
||||||
|
|
||||||
def get_original_maintainer(self):
|
def get_original_maintainer(self):
|
||||||
"""Returns the value of the XSBC-Original-Maintainer field."""
|
"""Returns the value of the XSBC-Original-Maintainer field."""
|
||||||
orig_maintainer = re.search("^(?:[XSBC]*-)?Original-Maintainer: ?(.*)$",
|
orig_maintainer = re.search(
|
||||||
self._content, re.MULTILINE)
|
"^(?:[XSBC]*-)?Original-Maintainer: ?(.*)$", self._content, re.MULTILINE
|
||||||
|
)
|
||||||
if orig_maintainer:
|
if orig_maintainer:
|
||||||
orig_maintainer = orig_maintainer.group(1)
|
orig_maintainer = orig_maintainer.group(1)
|
||||||
return orig_maintainer
|
return orig_maintainer
|
||||||
@ -65,38 +65,38 @@ class Control(object):
|
|||||||
"""Saves the control file."""
|
"""Saves the control file."""
|
||||||
if filename:
|
if filename:
|
||||||
self._filename = filename
|
self._filename = filename
|
||||||
control_file = open(self._filename, "w")
|
control_file = open(self._filename, "w", encoding="utf-8")
|
||||||
control_file.write(self._content)
|
control_file.write(self._content)
|
||||||
control_file.close()
|
control_file.close()
|
||||||
|
|
||||||
def set_maintainer(self, maintainer):
|
def set_maintainer(self, maintainer):
|
||||||
"""Sets the value of the Maintainer field."""
|
"""Sets the value of the Maintainer field."""
|
||||||
pattern = re.compile("^Maintainer: ?.*$", re.MULTILINE)
|
pattern = re.compile("^Maintainer: ?.*$", re.MULTILINE)
|
||||||
self._content = pattern.sub("Maintainer: " + maintainer, self._content)
|
self._content = pattern.sub(f"Maintainer: {maintainer}", self._content)
|
||||||
|
|
||||||
def set_original_maintainer(self, original_maintainer):
|
def set_original_maintainer(self, original_maintainer):
|
||||||
"""Sets the value of the XSBC-Original-Maintainer field."""
|
"""Sets the value of the XSBC-Original-Maintainer field."""
|
||||||
original_maintainer = "XSBC-Original-Maintainer: " + original_maintainer
|
original_maintainer = f"XSBC-Original-Maintainer: {original_maintainer}"
|
||||||
if self.get_original_maintainer():
|
if self.get_original_maintainer():
|
||||||
pattern = re.compile("^(?:[XSBC]*-)?Original-Maintainer:.*$",
|
pattern = re.compile("^(?:[XSBC]*-)?Original-Maintainer:.*$", re.MULTILINE)
|
||||||
re.MULTILINE)
|
|
||||||
self._content = pattern.sub(original_maintainer, self._content)
|
self._content = pattern.sub(original_maintainer, self._content)
|
||||||
else:
|
else:
|
||||||
pattern = re.compile("^(Maintainer:.*)$", re.MULTILINE)
|
pattern = re.compile("^(Maintainer:.*)$", re.MULTILINE)
|
||||||
self._content = pattern.sub(r"\1\n" + original_maintainer,
|
self._content = pattern.sub(f"\\1\\n{original_maintainer}", self._content)
|
||||||
self._content)
|
|
||||||
|
|
||||||
def remove_original_maintainer(self):
|
def remove_original_maintainer(self):
|
||||||
"""Strip out out the XSBC-Original-Maintainer line"""
|
"""Strip out out the XSBC-Original-Maintainer line"""
|
||||||
pattern = re.compile("^(?:[XSBC]*-)?Original-Maintainer:.*?$.*?^",
|
pattern = re.compile(
|
||||||
re.MULTILINE | re.DOTALL)
|
"^(?:[XSBC]*-)?Original-Maintainer:.*?$.*?^", re.MULTILINE | re.DOTALL
|
||||||
self._content = pattern.sub('', self._content)
|
)
|
||||||
|
self._content = pattern.sub("", self._content)
|
||||||
|
|
||||||
|
|
||||||
def _get_distribution(changelog_file):
|
def _get_distribution(changelog_file):
|
||||||
"""get distribution of latest changelog entry"""
|
"""get distribution of latest changelog entry"""
|
||||||
changelog = debian.changelog.Changelog(open(changelog_file), strict=False,
|
changelog = debian.changelog.Changelog(
|
||||||
max_blocks=1)
|
open(changelog_file, encoding="utf-8"), strict=False, max_blocks=1
|
||||||
|
)
|
||||||
distribution = changelog.distributions.split()[0]
|
distribution = changelog.distributions.split()[0]
|
||||||
# Strip things like "-proposed-updates" or "-security" from distribution
|
# Strip things like "-proposed-updates" or "-security" from distribution
|
||||||
return distribution.split("-", 1)[0]
|
return distribution.split("-", 1)[0]
|
||||||
@ -107,25 +107,24 @@ def _find_files(debian_directory, verbose):
|
|||||||
Returns (changelog, control files list)
|
Returns (changelog, control files list)
|
||||||
Raises an exception if none can be found.
|
Raises an exception if none can be found.
|
||||||
"""
|
"""
|
||||||
possible_contol_files = [os.path.join(debian_directory, f) for
|
possible_contol_files = [os.path.join(debian_directory, f) for f in ["control.in", "control"]]
|
||||||
f in ["control.in", "control"]]
|
|
||||||
|
|
||||||
changelog_file = os.path.join(debian_directory, "changelog")
|
changelog_file = os.path.join(debian_directory, "changelog")
|
||||||
control_files = [f for f in possible_contol_files if os.path.isfile(f)]
|
control_files = [f for f in possible_contol_files if os.path.isfile(f)]
|
||||||
|
|
||||||
# Make sure that a changelog and control file is available
|
# Make sure that a changelog and control file is available
|
||||||
if len(control_files) == 0:
|
if len(control_files) == 0:
|
||||||
raise MaintainerUpdateException(
|
raise MaintainerUpdateException(f"No control file found in {debian_directory}.")
|
||||||
"No control file found in %s." % debian_directory)
|
|
||||||
if not os.path.isfile(changelog_file):
|
if not os.path.isfile(changelog_file):
|
||||||
raise MaintainerUpdateException(
|
raise MaintainerUpdateException(f"No changelog file found in {debian_directory}.")
|
||||||
"No changelog file found in %s." % debian_directory)
|
|
||||||
|
|
||||||
# If the rules file accounts for XSBC-Original-Maintainer, we should not
|
# If the rules file accounts for XSBC-Original-Maintainer, we should not
|
||||||
# touch it in this package (e.g. the python package).
|
# touch it in this package (e.g. the python package).
|
||||||
rules_file = os.path.join(debian_directory, "rules")
|
rules_file = os.path.join(debian_directory, "rules")
|
||||||
if os.path.isfile(rules_file) and \
|
if (
|
||||||
'XSBC-Original-' in open(rules_file).read():
|
os.path.isfile(rules_file)
|
||||||
|
and "XSBC-Original-" in open(rules_file, encoding="utf-8").read()
|
||||||
|
):
|
||||||
if verbose:
|
if verbose:
|
||||||
print("XSBC-Original is managed by 'rules' file. Doing nothing.")
|
print("XSBC-Original is managed by 'rules' file. Doing nothing.")
|
||||||
control_files = []
|
control_files = []
|
||||||
@ -161,8 +160,8 @@ def update_maintainer(debian_directory, verbose=False):
|
|||||||
|
|
||||||
if original_maintainer.strip().lower() in _PREVIOUS_UBUNTU_MAINTAINER:
|
if original_maintainer.strip().lower() in _PREVIOUS_UBUNTU_MAINTAINER:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("The old maintainer was: %s" % original_maintainer)
|
print(f"The old maintainer was: {original_maintainer}")
|
||||||
print("Resetting as: %s" % _UBUNTU_MAINTAINER)
|
print(f"Resetting as: {_UBUNTU_MAINTAINER}")
|
||||||
control.set_maintainer(_UBUNTU_MAINTAINER)
|
control.set_maintainer(_UBUNTU_MAINTAINER)
|
||||||
control.save()
|
control.save()
|
||||||
continue
|
continue
|
||||||
@ -178,12 +177,13 @@ def update_maintainer(debian_directory, verbose=False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if control.get_original_maintainer() is not None:
|
if control.get_original_maintainer() is not None:
|
||||||
Logger.warning("Overwriting original maintainer: %s",
|
Logger.warning(
|
||||||
control.get_original_maintainer())
|
"Overwriting original maintainer: %s", control.get_original_maintainer()
|
||||||
|
)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print("The original maintainer is: %s" % original_maintainer)
|
print(f"The original maintainer is: {original_maintainer}")
|
||||||
print("Resetting as: %s" % _UBUNTU_MAINTAINER)
|
print(f"Resetting as: {_UBUNTU_MAINTAINER}")
|
||||||
control.set_original_maintainer(original_maintainer)
|
control.set_original_maintainer(original_maintainer)
|
||||||
control.set_maintainer(_UBUNTU_MAINTAINER)
|
control.set_maintainer(_UBUNTU_MAINTAINER)
|
||||||
control.save()
|
control.save()
|
||||||
@ -194,7 +194,7 @@ def update_maintainer(debian_directory, verbose=False):
|
|||||||
def restore_maintainer(debian_directory, verbose=False):
|
def restore_maintainer(debian_directory, verbose=False):
|
||||||
"""Restore the original maintainer"""
|
"""Restore the original maintainer"""
|
||||||
try:
|
try:
|
||||||
changelog_file, control_files = _find_files(debian_directory, verbose)
|
control_files = _find_files(debian_directory, verbose)[1]
|
||||||
except MaintainerUpdateException as e:
|
except MaintainerUpdateException as e:
|
||||||
Logger.error(str(e))
|
Logger.error(str(e))
|
||||||
raise
|
raise
|
||||||
@ -205,7 +205,7 @@ def restore_maintainer(debian_directory, verbose=False):
|
|||||||
if not orig_maintainer:
|
if not orig_maintainer:
|
||||||
continue
|
continue
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Restoring original maintainer: %s" % orig_maintainer)
|
print(f"Restoring original maintainer: {orig_maintainer}")
|
||||||
control.set_maintainer(orig_maintainer)
|
control.set_maintainer(orig_maintainer)
|
||||||
control.remove_original_maintainer()
|
control.remove_original_maintainer()
|
||||||
control.save()
|
control.save()
|
||||||
|
79
ubuntutools/utils.py
Normal file
79
ubuntutools/utils.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright (C) 2019-2023 Canonical Ltd.
|
||||||
|
# Author: Brian Murray <brian.murray@canonical.com> et al.
|
||||||
|
|
||||||
|
# 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; version 3 of the License.
|
||||||
|
#
|
||||||
|
# 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""Portions of archive related code that is re-used by various tools."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import dateutil.parser
|
||||||
|
from dateutil.tz import tzutc
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_dir():
|
||||||
|
cache_dir = os.environ.get("XDG_CACHE_HOME", os.path.expanduser(os.path.join("~", ".cache")))
|
||||||
|
uat_cache = os.path.join(cache_dir, "ubuntu-archive-tools")
|
||||||
|
os.makedirs(uat_cache, exist_ok=True)
|
||||||
|
return uat_cache
|
||||||
|
|
||||||
|
|
||||||
|
def get_url(url, force_cached):
|
||||||
|
"""Return file to the URL, possibly caching it"""
|
||||||
|
cache_file = None
|
||||||
|
|
||||||
|
# ignore bileto urls wrt caching, they're usually too small to matter
|
||||||
|
# and we don't do proper cache expiry
|
||||||
|
m = re.search("ubuntu-archive-team.ubuntu.com/proposed-migration/([^/]*)/([^/]*)", url)
|
||||||
|
if m:
|
||||||
|
cache_dir = get_cache_dir()
|
||||||
|
cache_file = os.path.join(cache_dir, f"{m.group(1)}_{m.group(2)}")
|
||||||
|
else:
|
||||||
|
# test logs can be cached, too
|
||||||
|
m = re.search(
|
||||||
|
"https://autopkgtest.ubuntu.com/results/autopkgtest-[^/]*/([^/]*)/([^/]*)"
|
||||||
|
"/[a-z0-9]*/([^/]*)/([_a-f0-9]*)@/log.gz",
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
if m:
|
||||||
|
cache_dir = get_cache_dir()
|
||||||
|
cache_file = os.path.join(
|
||||||
|
cache_dir, f"{m.group(1)}_{m.group(2)}_{m.group(3)}_{m.group(4)}.gz"
|
||||||
|
)
|
||||||
|
|
||||||
|
if cache_file:
|
||||||
|
try:
|
||||||
|
prev_mtime = os.stat(cache_file).st_mtime
|
||||||
|
except FileNotFoundError:
|
||||||
|
prev_mtime = 0
|
||||||
|
prev_timestamp = datetime.fromtimestamp(prev_mtime, tz=tzutc())
|
||||||
|
new_timestamp = datetime.now(tz=tzutc()).timestamp()
|
||||||
|
if force_cached:
|
||||||
|
return open(cache_file, "rb")
|
||||||
|
|
||||||
|
f = urllib.request.urlopen(url)
|
||||||
|
|
||||||
|
if cache_file:
|
||||||
|
remote_ts = dateutil.parser.parse(f.headers["last-modified"])
|
||||||
|
if remote_ts > prev_timestamp:
|
||||||
|
with open(f"{cache_file}.new", "wb") as new_cache:
|
||||||
|
for line in f:
|
||||||
|
new_cache.write(line)
|
||||||
|
os.rename(f"{cache_file}.new", cache_file)
|
||||||
|
os.utime(cache_file, times=(new_timestamp, new_timestamp))
|
||||||
|
f.close()
|
||||||
|
f = open(cache_file, "rb")
|
||||||
|
return f
|
@ -17,28 +17,28 @@ import debian.debian_support
|
|||||||
|
|
||||||
class Version(debian.debian_support.Version):
|
class Version(debian.debian_support.Version):
|
||||||
def strip_epoch(self):
|
def strip_epoch(self):
|
||||||
'''Removes the epoch from a Debian version string.
|
"""Removes the epoch from a Debian version string.
|
||||||
|
|
||||||
strip_epoch(1:1.52-1) will return "1.52-1" and strip_epoch(1.1.3-1)
|
strip_epoch(1:1.52-1) will return "1.52-1" and strip_epoch(1.1.3-1)
|
||||||
will return "1.1.3-1".
|
will return "1.1.3-1".
|
||||||
'''
|
"""
|
||||||
parts = self.full_version.split(':')
|
parts = self.full_version.split(":")
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
del parts[0]
|
del parts[0]
|
||||||
version_without_epoch = ':'.join(parts)
|
version_without_epoch = ":".join(parts)
|
||||||
return version_without_epoch
|
return version_without_epoch
|
||||||
|
|
||||||
def get_related_debian_version(self):
|
def get_related_debian_version(self):
|
||||||
'''Strip the ubuntu-specific bits off the version'''
|
"""Strip the ubuntu-specific bits off the version"""
|
||||||
related_debian_version = self.full_version
|
related_debian_version = self.full_version
|
||||||
uidx = related_debian_version.find('ubuntu')
|
uidx = related_debian_version.find("ubuntu")
|
||||||
if uidx > 0:
|
if uidx > 0:
|
||||||
related_debian_version = related_debian_version[:uidx]
|
related_debian_version = related_debian_version[:uidx]
|
||||||
uidx = related_debian_version.find('build')
|
uidx = related_debian_version.find("build")
|
||||||
if uidx > 0:
|
if uidx > 0:
|
||||||
related_debian_version = related_debian_version[:uidx]
|
related_debian_version = related_debian_version[:uidx]
|
||||||
return Version(related_debian_version)
|
return Version(related_debian_version)
|
||||||
|
|
||||||
def is_modified_in_ubuntu(self):
|
def is_modified_in_ubuntu(self):
|
||||||
'''Did Ubuntu modify this (and mark the version appropriately)?'''
|
"""Did Ubuntu modify this (and mark the version appropriately)?"""
|
||||||
return 'ubuntu' in self.full_version
|
return "ubuntu" in self.full_version
|
||||||
|
@ -14,13 +14,18 @@
|
|||||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import optparse
|
# pylint: disable=invalid-name
|
||||||
|
# pylint: enable=invalid-name
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ubuntutools.update_maintainer import (update_maintainer,
|
from ubuntutools.update_maintainer import (
|
||||||
|
MaintainerUpdateException,
|
||||||
restore_maintainer,
|
restore_maintainer,
|
||||||
MaintainerUpdateException)
|
update_maintainer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def find_debian_dir(depth=6):
|
def find_debian_dir(depth=6):
|
||||||
@ -30,42 +35,42 @@ def find_debian_dir(depth=6):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
:returns: a path to an existing debian/ directory, or None
|
:returns: a path to an existing debian/ directory, or None
|
||||||
"""
|
"""
|
||||||
for path in ['../'*n or './' for n in list(range(0, depth+1))]:
|
for path in ["../" * n or "./" for n in list(range(0, depth + 1))]:
|
||||||
debian_path = '{}debian'.format(path)
|
debian_path = f"{path}debian"
|
||||||
if os.path.exists(os.path.join(debian_path, 'control')) \
|
if os.path.exists(os.path.join(debian_path, "control")) and os.path.exists(
|
||||||
and os.path.exists(os.path.join(debian_path, 'changelog')):
|
os.path.join(debian_path, "changelog")
|
||||||
|
):
|
||||||
return debian_path
|
return debian_path
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
script_name = os.path.basename(sys.argv[0])
|
script_name = os.path.basename(sys.argv[0])
|
||||||
usage = "%s [options]" % (script_name)
|
epilog = f"See {script_name}(1) for more info."
|
||||||
epilog = "See %s(1) for more info." % (script_name)
|
parser = argparse.ArgumentParser(epilog=epilog)
|
||||||
parser = optparse.OptionParser(usage=usage, epilog=epilog)
|
parser.add_argument(
|
||||||
parser.add_option("-d", "--debian-directory", dest="debian_directory",
|
"-d",
|
||||||
help="location of the 'debian' directory (default: "
|
"--debian-directory",
|
||||||
"%default).", metavar="PATH",
|
dest="debian_directory",
|
||||||
default=find_debian_dir() or './debian')
|
help="location of the 'debian' directory (default: %(default)s).",
|
||||||
parser.add_option("-r", "--restore",
|
metavar="PATH",
|
||||||
help="Restore the original maintainer",
|
default=find_debian_dir() or "./debian",
|
||||||
action='store_true', default=False)
|
)
|
||||||
parser.add_option("-q", "--quiet", help="print no informational messages",
|
parser.add_argument(
|
||||||
dest="quiet", action="store_true", default=False)
|
"-r", "--restore", help="Restore the original maintainer", action="store_true"
|
||||||
(options, args) = parser.parse_args()
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-q", "--quiet", help="print no informational messages", dest="quiet", action="store_true"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
if len(args) != 0:
|
if not args.restore:
|
||||||
print("%s: Error: Unsupported additional parameters specified: %s"
|
|
||||||
% (script_name, ", ".join(args)), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not options.restore:
|
|
||||||
operation = update_maintainer
|
operation = update_maintainer
|
||||||
else:
|
else:
|
||||||
operation = restore_maintainer
|
operation = restore_maintainer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
operation(options.debian_directory, not options.quiet)
|
operation(args.debian_directory, not args.quiet)
|
||||||
except MaintainerUpdateException:
|
except MaintainerUpdateException:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user