Merge ~ddstreet/ubuntu-dev-tools/+git/ubuntu-dev-tools:pull-pkg into ubuntu-dev-tools:master
- Git
- lp:~ddstreet/ubuntu-dev-tools/+git/ubuntu-dev-tools
- pull-pkg
- Merge into master
| Status: | Needs review |
|---|---|
| Proposed branch: | ~ddstreet/ubuntu-dev-tools/+git/ubuntu-dev-tools:pull-pkg |
| Merge into: | ubuntu-dev-tools:master |
| Diff against target: |
3133 lines (+1608/-760) 36 files modified
debian/control (+7/-1) dev/null (+0/-79) doc/pull-debian-ddebs.1 (+1/-0) doc/pull-debian-debs.1 (+1/-0) doc/pull-debian-source.1 (+1/-0) doc/pull-debian-udebs.1 (+1/-0) doc/pull-lp-ddebs.1 (+1/-0) doc/pull-lp-debs.1 (+1/-0) doc/pull-lp-source.1 (+1/-0) doc/pull-lp-udebs.1 (+1/-0) doc/pull-pkg.1 (+134/-0) doc/pull-uca-ddebs.1 (+1/-0) doc/pull-uca-debs.1 (+1/-0) doc/pull-uca-source.1 (+1/-0) doc/pull-uca-udebs.1 (+1/-0) pull-debian-ddebs (+16/-0) pull-debian-debs (+16/-0) pull-debian-source (+6/-133) pull-debian-udebs (+16/-0) pull-lp-ddebs (+16/-0) pull-lp-debs (+16/-0) pull-lp-source (+6/-139) pull-lp-udebs (+16/-0) pull-pkg (+33/-0) pull-uca-ddebs (+16/-0) pull-uca-debs (+16/-0) pull-uca-source (+6/-147) pull-uca-udebs (+16/-0) setup.py (+10/-0) ubuntutools/archive.py (+690/-190) ubuntutools/config.py (+2/-0) ubuntutools/lp/libsupport.py (+24/-0) ubuntutools/lp/lpapicache.py (+240/-44) ubuntutools/pullpkg.py (+281/-0) ubuntutools/requestsync/mail.py (+13/-24) ubuntutools/test/test_archive.py (+0/-3) |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Ubuntu Development Team | 2017-04-20 | Pending | |
|
Review via email:
|
|||
Commit Message
Description of the Change
This is a large change to the pull-DISTRO-source scripts, and their associated libs under ubuntutools/. It consolidates the scripts into a single script, and extends its functionality to be able to pull not only source packages/files, but also binary debs - normal ".deb", dbgsym ".ddeb" and -di ".udeb" files. It also allows listing all packages assoicated with a given source package, and it automatically performs binary package name to source package name lookups. It works with ubuntu (launchpad), as well as debian (using madison/snapshot) and cloud-archive.
- a313e50... by Dan Streetman on 2017-10-27
-
pull-pkg: catch any exception when trying to lookup bpph->build->spph
When looking up a package by its binary pkg name, the easiest way to get
to its SourcePackagePublishingHistory object is through its Build object.
However, some BPPH objects in Launchpad seem to have their Build's
SPPH link removed or redacted, which results in an exception while trying
to access it. So try that first, but catch any exception and then
fallback to recursively calling lp_spph using the looked-up source
package name and version number, which will work. - 24bf863... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py method additions DistroArchSerie
s.getSeries( )
SPPH.getSeriesName()
SPPH.getArchive() - 02b0d8b... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: get pkg using version/status params also Update Archive methods getSourcePackage and getBinaryPackage to allow
searching for a package using its version and/or status fields also - 4578c36... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: find old pkg version binaries Launchpad unfortunately "hides" the BPPH records (and corresponding
binary files/urls) when a newer package version is released. This
means binary debs and ddebs for older package versions are effectively
"gone", which makes debugging problems with older package versions
(e.g. with gdb) very hard.Luckily (and confusingly) Launchpad doesn't actually remove the old
binary files, or the BPPH records. It only *hides* them, because
the SPPH.getPublishedBinaries( ) LP API call only returns Published
or Pending BPPH records.This change detects that (if the state is not Published or Pending)
and instead of using SPPH.getPublishedBinaries( ) goes the MUCH
longer route (since it seems to be the ONLY way left to find the
binaries) of using SPPH.binaryFileUrls() and then parsing the
filenames and looking up each BPPH using Archive.getPublishedBin aries() . This change allows pull-lp-debs and pull-lp-ddebs to now get debs
and/or ddebs for ALL package versions created in Launchpad, even if
they are old versions. - 2c6023c... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: get system arch for archtag 'all' Packages applicable to all archs are labeled with archtag 'all',
so any reverse lookup of a DistroArchSeries by arch 'all'
should return the system's arch series (since the specific arch
doesn't matter for archtag 'all' binaries).
Unmerged commits
- 4578c36... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: find old pkg version binaries Launchpad unfortunately "hides" the BPPH records (and corresponding
binary files/urls) when a newer package version is released. This
means binary debs and ddebs for older package versions are effectively
"gone", which makes debugging problems with older package versions
(e.g. with gdb) very hard.Luckily (and confusingly) Launchpad doesn't actually remove the old
binary files, or the BPPH records. It only *hides* them, because
the SPPH.getPublishedBinaries( ) LP API call only returns Published
or Pending BPPH records.This change detects that (if the state is not Published or Pending)
and instead of using SPPH.getPublishedBinaries( ) goes the MUCH
longer route (since it seems to be the ONLY way left to find the
binaries) of using SPPH.binaryFileUrls() and then parsing the
filenames and looking up each BPPH using Archive.getPublishedBin aries() . This change allows pull-lp-debs and pull-lp-ddebs to now get debs
and/or ddebs for ALL package versions created in Launchpad, even if
they are old versions. - 02b0d8b... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: get pkg using version/status params also Update Archive methods getSourcePackage and getBinaryPackage to allow
searching for a package using its version and/or status fields also - 24bf863... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py method additions DistroArchSerie
s.getSeries( )
SPPH.getSeriesName()
SPPH.getArchive() - 2c6023c... by Dan Streetman on 2017-11-21
-
ubuntutools/
lp/lpapicache. py: get system arch for archtag 'all' Packages applicable to all archs are labeled with archtag 'all',
so any reverse lookup of a DistroArchSeries by arch 'all'
should return the system's arch series (since the specific arch
doesn't matter for archtag 'all' binaries). - a313e50... by Dan Streetman on 2017-10-27
-
pull-pkg: catch any exception when trying to lookup bpph->build->spph
When looking up a package by its binary pkg name, the easiest way to get
to its SourcePackagePublishingHistory object is through its Build object.
However, some BPPH objects in Launchpad seem to have their Build's
SPPH link removed or redacted, which results in an exception while trying
to access it. So try that first, but catch any exception and then
fallback to recursively calling lp_spph using the looked-up source
package name and version number, which will work. - 7057eb8... by Dan Streetman on 2017-09-15
-
pull-pkg: update man pages to all point to single pull-pkg.1 man page
remove the pull-lp-source.1 and pull-debian-
source. 1 man pages,
consolidating them both into a pull-pkg.1 man page. Also create
symlinks for all associated scripts pointing to pull-pkg.1 man page. - 92f6bce... by Dan Streetman on 2017-09-14
-
pull-pkg: create pull-[lp|
debian| uca]-* scripts for backwards compatibility (i.e. to keep pull-lp-source,
pull-debian-source, and pull-uca-source) and for ease of use,
create scripts that default the pull-pkg -p and -D paramsre-create pull-pkg script also, to call ubuntutools/
pullpkg. py
without any default pull or distro parameters - 0d9ca97... by Dan Streetman on 2017-09-15
-
pull-pkg: change pull-pkg script code into modular code
- bbb62b2... by Dan Streetman on 2017-09-15
-
pull-pkg: rename pull-pkg to ubuntutools/
pullpkg. py module rename only with no content changes, so next commit shows code
changes converting script pull-pkg to module pullpkg.py - 0b5736b... by Dan Streetman on 2017-09-14
-
pull-pkg: update to use previous SourcePackage improvements
New pull-pkg allows pulling source, debs, ddebs, or udebs, or just
listing all package files. Also, package lookup by binary name is done
automatically.
Preview Diff
| 1 | diff --git a/debian/control b/debian/control |
| 2 | index 19f2ba5..baf48a9 100644 |
| 3 | --- a/debian/control |
| 4 | +++ b/debian/control |
| 5 | @@ -106,7 +106,13 @@ Description: useful tools for Ubuntu developers |
| 6 | a Debian package and its immediate parent to generate a debdiff. |
| 7 | - pull-debian-source - downloads the latest source package available in |
| 8 | Debian of a package. |
| 9 | - - pull-lp-source - downloads latest source package from Launchpad. |
| 10 | + - pull-lp-source - downloads source package from Launchpad. |
| 11 | + - pull-lp-debs - downloads debs package(s) from Launchpad. |
| 12 | + - pull-lp-ddebs - downloads dbgsym/ddebs package(s) from Launchpad. |
| 13 | + - pull-lp-udebs - downloads udebs package(s) from Launchpad. |
| 14 | + - pull-debian-* - same as pull-lp-* but for Debian packages. |
| 15 | + - pull-uca-* - same as pull-lp-* but for Ubuntu Cloud Archive packages. |
| 16 | + - pull-pkg - common script that provides above pull-* functionality. |
| 17 | - pull-revu-source - downloads the latest source package from REVU |
| 18 | - requestbackport - file a backporting request. |
| 19 | - requestsync - files a sync request with Debian changelog and rationale. |
| 20 | diff --git a/doc/pull-debian-ddebs.1 b/doc/pull-debian-ddebs.1 |
| 21 | new file mode 120000 |
| 22 | index 0000000..a67bbc7 |
| 23 | --- /dev/null |
| 24 | +++ b/doc/pull-debian-ddebs.1 |
| 25 | @@ -0,0 +1 @@ |
| 26 | +pull-pkg.1 |
| 27 | \ No newline at end of file |
| 28 | diff --git a/doc/pull-debian-debs.1 b/doc/pull-debian-debs.1 |
| 29 | new file mode 120000 |
| 30 | index 0000000..a67bbc7 |
| 31 | --- /dev/null |
| 32 | +++ b/doc/pull-debian-debs.1 |
| 33 | @@ -0,0 +1 @@ |
| 34 | +pull-pkg.1 |
| 35 | \ No newline at end of file |
| 36 | diff --git a/doc/pull-debian-source.1 b/doc/pull-debian-source.1 |
| 37 | deleted file mode 100644 |
| 38 | index 468724a..0000000 |
| 39 | --- a/doc/pull-debian-source.1 |
| 40 | +++ /dev/null |
| 41 | @@ -1,89 +0,0 @@ |
| 42 | -.\" Copyright (C) 2010-2011, Stefano Rivera <stefanor@ubuntu.com> |
| 43 | -.\" |
| 44 | -.\" Permission to use, copy, modify, and/or distribute this software for any |
| 45 | -.\" purpose with or without fee is hereby granted, provided that the above |
| 46 | -.\" copyright notice and this permission notice appear in all copies. |
| 47 | -.\" |
| 48 | -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| 49 | -.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| 50 | -.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, |
| 51 | -.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM |
| 52 | -.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR |
| 53 | -.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| 54 | -.\" PERFORMANCE OF THIS SOFTWARE. |
| 55 | -.TH PULL\-DEBIAN\-SOURCE "1" "22 January 2011" "ubuntu\-dev\-tools" |
| 56 | - |
| 57 | -.SH NAME |
| 58 | -pull\-debian\-source \- download and extract a source package from Debian |
| 59 | - |
| 60 | -.SH SYNOPSIS |
| 61 | -.B pull\-debian\-source \fR[\fIoptions\fR] <\fIsource package\fR> |
| 62 | -[\fIrelease\fR|\fIversion\fR] |
| 63 | - |
| 64 | -.SH DESCRIPTION |
| 65 | -\fBpull\-debian\-source\fR downloads and extracts the specified |
| 66 | -\fIversion\fR of \fIsource package\fR, or the latest version in the |
| 67 | -specified Debian \fIrelease\fR. |
| 68 | -.P |
| 69 | -\fBpull\-debian\-source\fR will try the preferred mirror, default |
| 70 | -mirror, security mirror, and fall back to \fBLaunchpad\fR or |
| 71 | -\fBsnapshot.debian.org\fR, in search of the requested version. |
| 72 | - |
| 73 | -.SH OPTIONS |
| 74 | -.TP |
| 75 | -.I source package |
| 76 | -The source package to download from Debian. |
| 77 | -.TP |
| 78 | -.I release |
| 79 | -The release to download the source package from. Defaults to |
| 80 | -\fBunstable\fR. |
| 81 | -.TP |
| 82 | -.I version |
| 83 | -The specific version of the package to download. |
| 84 | -.TP |
| 85 | -.BR \-d ", " \-\-download\-only |
| 86 | -Do not extract the source package. |
| 87 | -.TP |
| 88 | -.B \-m \fIDEBIAN_MIRROR\fR, \fB\-\-mirror\fR=\fIDEBIAN_MIRROR\fR |
| 89 | -Use the specified mirror. |
| 90 | -Should be in the form \fBhttp://ftp.debian.org/debian\fR. |
| 91 | -If the package isn't found on this mirror, \fBpull\-debian\-source\fR |
| 92 | -will fall back to the default mirror. |
| 93 | -.TP |
| 94 | -.B \-s \fIDEBSEC_MIRROR\fR, \fB\-\-security\-mirror\fR=\fIDEBSEC_MIRROR\fR |
| 95 | -Use the specified mirror. |
| 96 | -Should be in the form \fBhttp://security.debian.org\fR. |
| 97 | -If the package isn't found on this mirror, \fBpull\-debian\-source\fR |
| 98 | -will fall back to the default mirror. |
| 99 | -.TP |
| 100 | -.B \-\-no\-conf |
| 101 | -Do not read any configuration files, or configuration from environment |
| 102 | -variables. |
| 103 | -.TP |
| 104 | -.BR \-h ", " \-\-help |
| 105 | -Display the usage instructions and exit. |
| 106 | - |
| 107 | -.SH ENVIRONMENT |
| 108 | -All of the \fBCONFIGURATION VARIABLES\fR below are also supported as |
| 109 | -environment variables. |
| 110 | -Variables in the environment take precedence to those in configuration |
| 111 | -files. |
| 112 | - |
| 113 | -.SH CONFIGURATION VARIABLES |
| 114 | -The following variables can be set in the environment or in |
| 115 | -.BR ubuntu\-dev\-tools (5) |
| 116 | -configuration files. |
| 117 | -In each case, the script\-specific variable takes precedence over the |
| 118 | -package\-wide variable. |
| 119 | -.TP |
| 120 | -.BR PULL_DEBIAN_SOURCE_DEBIAN_MIRROR ", " UBUNTUTOOLS_DEBIAN_MIRROR |
| 121 | -The default value for \fB\-\-mirror\fR. |
| 122 | -.TP |
| 123 | -.BR PULL_DEBIAN_SOURCE_DEBSEC_MIRROR ", " UBUNTUTOOLS_DEBSEC_MIRROR |
| 124 | -The default value for \fB\-\-security\-mirror\fR. |
| 125 | - |
| 126 | -.SH SEE ALSO |
| 127 | -.BR dget (1), |
| 128 | -.BR pull\-debian\-debdiff (1), |
| 129 | -.BR pull\-lp\-source (1), |
| 130 | -.BR ubuntu\-dev\-tools (5) |
| 131 | diff --git a/doc/pull-debian-source.1 b/doc/pull-debian-source.1 |
| 132 | new file mode 120000 |
| 133 | index 0000000..a67bbc7 |
| 134 | --- /dev/null |
| 135 | +++ b/doc/pull-debian-source.1 |
| 136 | @@ -0,0 +1 @@ |
| 137 | +pull-pkg.1 |
| 138 | \ No newline at end of file |
| 139 | diff --git a/doc/pull-debian-udebs.1 b/doc/pull-debian-udebs.1 |
| 140 | new file mode 120000 |
| 141 | index 0000000..a67bbc7 |
| 142 | --- /dev/null |
| 143 | +++ b/doc/pull-debian-udebs.1 |
| 144 | @@ -0,0 +1 @@ |
| 145 | +pull-pkg.1 |
| 146 | \ No newline at end of file |
| 147 | diff --git a/doc/pull-lp-ddebs.1 b/doc/pull-lp-ddebs.1 |
| 148 | new file mode 120000 |
| 149 | index 0000000..a67bbc7 |
| 150 | --- /dev/null |
| 151 | +++ b/doc/pull-lp-ddebs.1 |
| 152 | @@ -0,0 +1 @@ |
| 153 | +pull-pkg.1 |
| 154 | \ No newline at end of file |
| 155 | diff --git a/doc/pull-lp-debs.1 b/doc/pull-lp-debs.1 |
| 156 | new file mode 120000 |
| 157 | index 0000000..a67bbc7 |
| 158 | --- /dev/null |
| 159 | +++ b/doc/pull-lp-debs.1 |
| 160 | @@ -0,0 +1 @@ |
| 161 | +pull-pkg.1 |
| 162 | \ No newline at end of file |
| 163 | diff --git a/doc/pull-lp-source.1 b/doc/pull-lp-source.1 |
| 164 | deleted file mode 100644 |
| 165 | index 757857a..0000000 |
| 166 | --- a/doc/pull-lp-source.1 |
| 167 | +++ /dev/null |
| 168 | @@ -1,79 +0,0 @@ |
| 169 | -.TH PULL\-LP\-SOURCE "1" "4 August 2008" "ubuntu-dev-tools" |
| 170 | - |
| 171 | -.SH NAME |
| 172 | -pull\-lp\-source \- download a source package from Launchpad |
| 173 | - |
| 174 | -.SH SYNOPSIS |
| 175 | -.B pull\-lp\-source \fR[\fIoptions\fR]\fB \fBsource package\fR |
| 176 | -[\fIrelease\fR|\fIversion\fR] |
| 177 | - |
| 178 | -.SH DESCRIPTION |
| 179 | -\fBpull\-lp\-source\fR downloads and extracts the specified |
| 180 | -\fIversion\fR of <\fBsource package\fR> from Launchpad, or the latest |
| 181 | -version of the specified \fIrelease\fR. |
| 182 | -To request a version from a particular pocket say |
| 183 | -\fIrelease\fB\-\fIpocket\fR (with a magic \fB\-release\fR for only the |
| 184 | -release pocket). |
| 185 | -If no \fIversion\fR or \fIrelease\fR is specified, the latest version in |
| 186 | -the development release will be downloaded. |
| 187 | - |
| 188 | -.SH OPTIONS |
| 189 | -Listed below are the command line options for pull\-lp\-source: |
| 190 | -.TP |
| 191 | -.B source package |
| 192 | -This is the source package that you would like to be downloaded from Launchpad. |
| 193 | -.TP |
| 194 | -.B version |
| 195 | -This is the version of the source package to be downloaded. |
| 196 | -.TP |
| 197 | -.B release |
| 198 | -This is the release that you would like the source package to be downloaded from. |
| 199 | -This value defaults to the current development release. |
| 200 | -.TP |
| 201 | -.BR \-h ", " \-\-help |
| 202 | -Display a help message and exit. |
| 203 | -.TP |
| 204 | -.BR \-d ", " \-\-download\-only |
| 205 | -Do not extract the source package. |
| 206 | -.TP |
| 207 | -.B \-m \fIUBUNTU_MIRROR\fR, \fB\-\-mirror\fR=\fIUBUNTU_MIRROR\fR |
| 208 | -Use the specified Ubuntu mirror. |
| 209 | -Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR. |
| 210 | -If the package isn't found on this mirror, \fBpull\-lp\-source\fR will |
| 211 | -fall back to Launchpad, as its name implies. |
| 212 | -.TP |
| 213 | -.B \-\-no\-conf |
| 214 | -Do not read any configuration files, or configuration from environment |
| 215 | -variables. |
| 216 | - |
| 217 | -.SH ENVIRONMENT |
| 218 | -All of the \fBCONFIGURATION VARIABLES\fR below are also supported as |
| 219 | -environment variables. |
| 220 | -Variables in the environment take precedence to those in configuration |
| 221 | -files. |
| 222 | -.TP |
| 223 | -.B |
| 224 | -DIST |
| 225 | -Specifies the default target. |
| 226 | - |
| 227 | -.SH CONFIGURATION VARIABLES |
| 228 | -The following variables can be set in the environment or in |
| 229 | -.BR ubuntu\-dev\-tools (5) |
| 230 | -configuration files. |
| 231 | -In each case, the script\-specific variable takes precedence over the |
| 232 | -package\-wide variable. |
| 233 | -.TP |
| 234 | -.BR PULL_LP_SOURCE_UBUNTU_MIRROR ", " UBUNTUTOOLS_UBUNTU_MIRROR |
| 235 | -The default value for \fB\-\-mirror\fR. |
| 236 | - |
| 237 | -.SH SEE ALSO |
| 238 | -.BR dget (1), |
| 239 | -.BR pull\-debian\-source (1), |
| 240 | -.BR pull\-debian\-debdiff (1), |
| 241 | -.BR ubuntu\-dev\-tools (5) |
| 242 | - |
| 243 | -.SH AUTHOR |
| 244 | -.PP |
| 245 | -\fBpull\-lp\-source\fR and this manual page were written by Iain Lane |
| 246 | -<iain@orangesquash.org.uk>. |
| 247 | -Both are released under the GNU General Public License, version 3 or later. |
| 248 | diff --git a/doc/pull-lp-source.1 b/doc/pull-lp-source.1 |
| 249 | new file mode 120000 |
| 250 | index 0000000..a67bbc7 |
| 251 | --- /dev/null |
| 252 | +++ b/doc/pull-lp-source.1 |
| 253 | @@ -0,0 +1 @@ |
| 254 | +pull-pkg.1 |
| 255 | \ No newline at end of file |
| 256 | diff --git a/doc/pull-lp-udebs.1 b/doc/pull-lp-udebs.1 |
| 257 | new file mode 120000 |
| 258 | index 0000000..a67bbc7 |
| 259 | --- /dev/null |
| 260 | +++ b/doc/pull-lp-udebs.1 |
| 261 | @@ -0,0 +1 @@ |
| 262 | +pull-pkg.1 |
| 263 | \ No newline at end of file |
| 264 | diff --git a/doc/pull-pkg.1 b/doc/pull-pkg.1 |
| 265 | new file mode 100644 |
| 266 | index 0000000..f62aa11 |
| 267 | --- /dev/null |
| 268 | +++ b/doc/pull-pkg.1 |
| 269 | @@ -0,0 +1,134 @@ |
| 270 | +.TH PULL\-PKG "1" "28 August 2017" "ubuntu-dev-tools" |
| 271 | + |
| 272 | +.SH NAME |
| 273 | +pull\-pkg \- download a package for Debian, Ubuntu, or UCA |
| 274 | + |
| 275 | +.SH SYNOPSIS |
| 276 | +.B pull\-pkg \fR[\fIoptions\fR]\fR <\fIpackage name\fR> |
| 277 | +[\fIrelease\fR|\fIversion\fR] |
| 278 | + |
| 279 | +.SH DESCRIPTION |
| 280 | +\fBpull\-pkg\fR downloads the specified \fIversion\fR of |
| 281 | +<\fIpackage name\fR>, or the latest version from the |
| 282 | +specified \fIrelease\fR. To request a version from |
| 283 | +a particular pocket say \fIrelease\fB\-\fIpocket\fR (with a magic |
| 284 | +\fB\-release\fR for only the release pocket). |
| 285 | +If no \fIversion\fR or \fIrelease\fR is specified, the latest version in |
| 286 | +the development release will be downloaded. |
| 287 | + |
| 288 | +There are convenience scripts that set pull type and distribution |
| 289 | +appropriately: these are |
| 290 | +\fBpull\-lp\-source\fR, \fBpull\-lp\-debs\fR, \fBpull\-lp\-ddebs\fR, |
| 291 | +and \fBpull\-lp\-udebs\fR, which all pull Ubuntu packages; |
| 292 | +\fBpull\-debian\-source\fR, \fBpull\-debian\-debs\fR, \fBpull\-debian\-ddebs\fR, |
| 293 | +and \fBpull\-debian\-udebs\fR, which all pull Debian packages; |
| 294 | +and \fBpull\-uca\-source\fR, \fBpull\-uca\-debs\fR, \fBpull\-uca\-ddebs\fR, |
| 295 | +and \fBpull\-uca\-udebs\fR, which all pull Ubuntu Cloud Archive packages. |
| 296 | +Each script pulls the file type in its name, i.e. |
| 297 | +\fIsource\fR, \fIdebs\fR, \fIddebs\fR, or \fIudebs\fR. |
| 298 | + |
| 299 | +.SH OPTIONS |
| 300 | +Listed below are the command line options for pull\-pkg: |
| 301 | +.TP |
| 302 | +.I package name |
| 303 | +This is name of the package to downloaded. |
| 304 | +You can use either the source package name, or binary package name. |
| 305 | +.TP |
| 306 | +.I version |
| 307 | +This is the version of the package to downloaded. |
| 308 | +.TP |
| 309 | +.I release |
| 310 | +This is the release to downloaded from. |
| 311 | +For debian, you can use either the release name like \fBjessie\fR |
| 312 | +or \fBsid\fR, or you can use the special release names \fBunstable\fR, |
| 313 | +\fBstable\fR, or \fBtesting\fR. |
| 314 | +For ubuntu, you can use either the release name like \fBxenial\fR |
| 315 | +or the release-pocket like \fBxenial-proposed\fR. |
| 316 | +For ubuntu cloud archive (uca) you can use either the uca release |
| 317 | +name like \fBmitaka\fR or the ubuntu and uca release names like |
| 318 | +\fBtrusty-mitaka\fR. |
| 319 | +Defaults to the current development release. |
| 320 | +.TP |
| 321 | +.BR \-h ", " \-\-help |
| 322 | +Display a help message and exit. |
| 323 | +.TP |
| 324 | +.BR \-v ", " \-\-verbose |
| 325 | +Be verbose about what is being done. |
| 326 | +.TP |
| 327 | +.BR \-d ", " \-\-download\-only |
| 328 | +Do not extract the source package (applies only to source packages). |
| 329 | +.TP |
| 330 | +.B \-m \fIMIRROR\fR, \fB\-\-mirror\fR=\fIMIRROR\fR |
| 331 | +Use the specified mirror server. |
| 332 | +Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR or |
| 333 | +\fBhttp://deb.debian.org/debian\fR. If not specified or if the |
| 334 | +package is not found on the specified mirror, this will fall |
| 335 | +back to the default mirror(s) and/or mirror(s) from environment |
| 336 | +variables, and then will fall back to Launchpad or Debian Snapshot. |
| 337 | +This can be specified multiple times to try multiple mirrors. |
| 338 | +.TP |
| 339 | +.B \-\-no\-conf |
| 340 | +Do not use mirrors from the default configuration, or from |
| 341 | +any environment variables. |
| 342 | +.TP |
| 343 | +.B \-a \fIARCH\fR, \fB\-\-arch\fR=\fIARCH\fR |
| 344 | +Get binary packages from the \fIARCH\fR architecture. |
| 345 | +Defaults to the local architecture, if it can be deteected. |
| 346 | +.TP |
| 347 | +.B \-p \fIPULL\fR, \fB\-\-pull\fR=\fIPULL\fR |
| 348 | +What to pull: \fBsource\fR, \fBdebs\fR, \fBddebs\fR, \fBudebs\fR, |
| 349 | +or \fBlist\fR. The \fBlist\fR action only lists all a package's |
| 350 | +source and binary files, but does not actually download any. |
| 351 | +Defaults to \fBsource\fR. |
| 352 | +.TP |
| 353 | +.B \-D \fIDISTRO\fR, \fB\-\-distro\fR=\fIDISTRO\fR |
| 354 | +Pull from: \fBdebian\fR, \fBuca\fR, or \fBubuntu\fR. |
| 355 | +\fBlp\fR can be used instead of \fBubuntu\fR. |
| 356 | +Any string containing \fBcloud\fR can be used instead of \fBuca\fR. |
| 357 | +Deafults to \fBubuntu\fR. |
| 358 | + |
| 359 | +.SH ENVIRONMENT |
| 360 | +All of the \fBCONFIGURATION VARIABLES\fR below are also supported as |
| 361 | +environment variables. |
| 362 | +Variables in the environment take precedence to those in configuration |
| 363 | +files. |
| 364 | + |
| 365 | +.SH CONFIGURATION VARIABLES |
| 366 | +The following variables can be set in the environment or in |
| 367 | +.BR ubuntu\-dev\-tools (5) |
| 368 | +configuration files. |
| 369 | +In each case, the script\-specific variable takes precedence over the |
| 370 | +package\-wide variable. |
| 371 | +.TP |
| 372 | +.BR UBUNTUTOOLS_UBUNTU_MIRROR |
| 373 | +The default mirror. |
| 374 | +.TP |
| 375 | +.BR PULL_PKG_UBUNTU_MIRROR |
| 376 | +The default mirror when using the \fBpull\-pkg\fR script. |
| 377 | +.TP |
| 378 | +.BR PULL_[LP|DEBIAN|UCA]_[SOURCE|DEBS|DDEBS|UDEBS]_MIRROR |
| 379 | +The default mirror when using the associated script. |
| 380 | + |
| 381 | +.SH SEE ALSO |
| 382 | +.BR dget (1), |
| 383 | +.BR pull\-lp\-source (1), |
| 384 | +.BR pull\-lp\-debs (1), |
| 385 | +.BR pull\-lp\-ddebs (1), |
| 386 | +.BR pull\-lp\-udebs (1), |
| 387 | +.BR pull\-debian\-source (1), |
| 388 | +.BR pull\-debian\-debs (1), |
| 389 | +.BR pull\-debian\-ddebs (1), |
| 390 | +.BR pull\-debian\-udebs (1), |
| 391 | +.BR pull\-uca\-source (1), |
| 392 | +.BR pull\-uca\-debs (1), |
| 393 | +.BR pull\-uca\-ddebs (1), |
| 394 | +.BR pull\-uca\-udebs (1), |
| 395 | +.BR pull\-debian\-debdiff (1), |
| 396 | +.BR ubuntu\-dev\-tools (5) |
| 397 | + |
| 398 | +.SH AUTHOR |
| 399 | +.PP |
| 400 | +\fBpull\-pkg\fR was written by Dan Streetman <dan.streetman@canonical.com>, |
| 401 | +based on the original \fBpull\-lp\-source\fR; it and this manual page |
| 402 | +were written by Iain Lane <iain@orangesquash.org.uk>. |
| 403 | +All are released under the GNU General Public License, version 3 or later. |
| 404 | diff --git a/doc/pull-uca-ddebs.1 b/doc/pull-uca-ddebs.1 |
| 405 | new file mode 120000 |
| 406 | index 0000000..a67bbc7 |
| 407 | --- /dev/null |
| 408 | +++ b/doc/pull-uca-ddebs.1 |
| 409 | @@ -0,0 +1 @@ |
| 410 | +pull-pkg.1 |
| 411 | \ No newline at end of file |
| 412 | diff --git a/doc/pull-uca-debs.1 b/doc/pull-uca-debs.1 |
| 413 | new file mode 120000 |
| 414 | index 0000000..a67bbc7 |
| 415 | --- /dev/null |
| 416 | +++ b/doc/pull-uca-debs.1 |
| 417 | @@ -0,0 +1 @@ |
| 418 | +pull-pkg.1 |
| 419 | \ No newline at end of file |
| 420 | diff --git a/doc/pull-uca-source.1 b/doc/pull-uca-source.1 |
| 421 | new file mode 120000 |
| 422 | index 0000000..a67bbc7 |
| 423 | --- /dev/null |
| 424 | +++ b/doc/pull-uca-source.1 |
| 425 | @@ -0,0 +1 @@ |
| 426 | +pull-pkg.1 |
| 427 | \ No newline at end of file |
| 428 | diff --git a/doc/pull-uca-udebs.1 b/doc/pull-uca-udebs.1 |
| 429 | new file mode 120000 |
| 430 | index 0000000..a67bbc7 |
| 431 | --- /dev/null |
| 432 | +++ b/doc/pull-uca-udebs.1 |
| 433 | @@ -0,0 +1 @@ |
| 434 | +pull-pkg.1 |
| 435 | \ No newline at end of file |
| 436 | diff --git a/pull-debian-ddebs b/pull-debian-ddebs |
| 437 | new file mode 100755 |
| 438 | index 0000000..05fd628 |
| 439 | --- /dev/null |
| 440 | +++ b/pull-debian-ddebs |
| 441 | @@ -0,0 +1,16 @@ |
| 442 | +#!/usr/bin/python |
| 443 | +# |
| 444 | +# pull-debian-ddebs -- pull ddeb package files for debian |
| 445 | +# Basic usage: pull-debian-ddebs <package name> [version|release] |
| 446 | +# |
| 447 | +# See pull-pkg |
| 448 | + |
| 449 | +from ubuntutools import pullpkg |
| 450 | +from ubuntutools.logger import Logger |
| 451 | + |
| 452 | +if __name__ == '__main__': |
| 453 | + try: |
| 454 | + parser = pullpkg.create_argparser(default_distro='debian', default_pull='ddebs') |
| 455 | + pullpkg.pull(parser.parse_args()) |
| 456 | + except KeyboardInterrupt: |
| 457 | + Logger.normal('User abort.') |
| 458 | diff --git a/pull-debian-debs b/pull-debian-debs |
| 459 | new file mode 100755 |
| 460 | index 0000000..21672a0 |
| 461 | --- /dev/null |
| 462 | +++ b/pull-debian-debs |
| 463 | @@ -0,0 +1,16 @@ |
| 464 | +#!/usr/bin/python |
| 465 | +# |
| 466 | +# pull-debian-debs -- pull deb package files for debian |
| 467 | +# Basic usage: pull-debian-debs <package name> [version|release] |
| 468 | +# |
| 469 | +# See pull-pkg |
| 470 | + |
| 471 | +from ubuntutools import pullpkg |
| 472 | +from ubuntutools.logger import Logger |
| 473 | + |
| 474 | +if __name__ == '__main__': |
| 475 | + try: |
| 476 | + parser = pullpkg.create_argparser(default_distro='debian', default_pull='debs') |
| 477 | + pullpkg.pull(parser.parse_args()) |
| 478 | + except KeyboardInterrupt: |
| 479 | + Logger.normal('User abort.') |
| 480 | diff --git a/pull-debian-source b/pull-debian-source |
| 481 | index d4e0519..9c82e6f 100755 |
| 482 | --- a/pull-debian-source |
| 483 | +++ b/pull-debian-source |
| 484 | @@ -1,143 +1,16 @@ |
| 485 | #!/usr/bin/python |
| 486 | # |
| 487 | -# pull-debian-source -- pull a source package from Launchpad |
| 488 | -# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com> |
| 489 | -# Inspired by a tool of the same name by Nathan Handler. |
| 490 | +# pull-debian-source -- pull source package files for debian |
| 491 | +# Basic usage: pull-debian-source <package name> [version|release] |
| 492 | # |
| 493 | -# Permission to use, copy, modify, and/or distribute this software for any |
| 494 | -# purpose with or without fee is hereby granted, provided that the above |
| 495 | -# copyright notice and this permission notice appear in all copies. |
| 496 | -# |
| 497 | -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| 498 | -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| 499 | -# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, |
| 500 | -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM |
| 501 | -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR |
| 502 | -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| 503 | -# PERFORMANCE OF THIS SOFTWARE. |
| 504 | - |
| 505 | -import json |
| 506 | -import optparse |
| 507 | -import sys |
| 508 | -import urllib2 |
| 509 | - |
| 510 | -from distro_info import DebianDistroInfo, DistroDataOutdated |
| 511 | +# See pull-pkg |
| 512 | |
| 513 | -from ubuntutools.archive import DebianSourcePackage, DownloadError, rmadison |
| 514 | -from ubuntutools.config import UDTConfig |
| 515 | +from ubuntutools import pullpkg |
| 516 | from ubuntutools.logger import Logger |
| 517 | |
| 518 | - |
| 519 | -def is_suite(version): |
| 520 | - """If version could be considered to be a Debian suite, return the |
| 521 | - canonical suite name. Otherwise None |
| 522 | - """ |
| 523 | - debian_info = DebianDistroInfo() |
| 524 | - debian_releases = debian_info.all + ['experimental'] |
| 525 | - |
| 526 | - if '-' in version: |
| 527 | - release, pocket = version.split('-', 1) |
| 528 | - release = debian_info.codename(release, default=release) |
| 529 | - if release in debian_releases: |
| 530 | - if pocket in ('proposed-updates', 'p-u'): |
| 531 | - return (release + '-proposed-updates') |
| 532 | - elif pocket == 'security': |
| 533 | - return (release + '-security') |
| 534 | - else: |
| 535 | - release = debian_info.codename(version, default=version) |
| 536 | - if release in debian_releases: |
| 537 | - return release |
| 538 | - return None |
| 539 | - |
| 540 | - |
| 541 | -def source_package_for(binary, release): |
| 542 | - """Query DDE to find the source package for a particular binary""" |
| 543 | - try: |
| 544 | - release = DebianDistroInfo().codename(release, default=release) |
| 545 | - except DistroDataOutdated, e: |
| 546 | - Logger.warn(e) |
| 547 | - url = ('http://dde.debian.net/dde/q/udd/dist/d:debian/r:%s/p:%s/?t=json' |
| 548 | - % (release, binary)) |
| 549 | - data = None |
| 550 | - try: |
| 551 | - data = json.load(urllib2.urlopen(url))['r'] |
| 552 | - except urllib2.URLError, e: |
| 553 | - Logger.error('Unable to retrieve package information from DDE: ' |
| 554 | - '%s (%s)', url, str(e)) |
| 555 | - except ValueError, e: |
| 556 | - Logger.error('Unable to parse JSON response from DDE: ' |
| 557 | - '%s (%s)', url, str(e)) |
| 558 | - if not data: |
| 559 | - return None |
| 560 | - return data[0]['source'] |
| 561 | - |
| 562 | - |
| 563 | -def main(): |
| 564 | - usage = 'Usage: %prog <package> [release|version]' |
| 565 | - parser = optparse.OptionParser(usage) |
| 566 | - parser.add_option('-d', '--download-only', |
| 567 | - dest='download_only', default=False, action='store_true', |
| 568 | - help='Do not extract the source package') |
| 569 | - parser.add_option('-m', '--mirror', metavar='DEBIAN_MIRROR', |
| 570 | - dest='debian_mirror', |
| 571 | - help='Preferred Debian mirror (default: %s)' |
| 572 | - % UDTConfig.defaults['DEBIAN_MIRROR']) |
| 573 | - parser.add_option('-s', '--security-mirror', metavar='DEBSEC_MIRROR', |
| 574 | - dest='debsec_mirror', |
| 575 | - help='Preferred Debian Security mirror (default: %s)' |
| 576 | - % UDTConfig.defaults['DEBSEC_MIRROR']) |
| 577 | - parser.add_option('--no-conf', |
| 578 | - dest='no_conf', default=False, action='store_true', |
| 579 | - help="Don't read config files or environment variables") |
| 580 | - (options, args) = parser.parse_args() |
| 581 | - if not args: |
| 582 | - parser.error('Must specify package name') |
| 583 | - elif len(args) > 2: |
| 584 | - parser.error('Too many arguments. ' |
| 585 | - 'Must only specify package and (optionally) release.') |
| 586 | - |
| 587 | - config = UDTConfig(options.no_conf) |
| 588 | - if options.debian_mirror is None: |
| 589 | - options.debian_mirror = config.get_value('DEBIAN_MIRROR') |
| 590 | - if options.debsec_mirror is None: |
| 591 | - options.debsec_mirror = config.get_value('DEBSEC_MIRROR') |
| 592 | - |
| 593 | - package = args[0].lower() |
| 594 | - |
| 595 | - version = args[1] if len(args) > 1 else 'unstable' |
| 596 | - component = None |
| 597 | - |
| 598 | - suite = is_suite(version) |
| 599 | - if suite is not None: |
| 600 | - line = list(rmadison('debian', package, suite, 'source')) |
| 601 | - if not line: |
| 602 | - source_package = source_package_for(package, suite) |
| 603 | - if source_package is not None and package != source_package: |
| 604 | - package = source_package |
| 605 | - line = list(rmadison('debian', package, suite, 'source')) |
| 606 | - if not line: |
| 607 | - Logger.error('Unable to find %s in Debian suite "%s".', package, |
| 608 | - suite) |
| 609 | - sys.exit(1) |
| 610 | - line = line[-1] |
| 611 | - version = line['version'] |
| 612 | - component = line['component'] |
| 613 | - |
| 614 | - Logger.normal('Downloading %s version %s', package, version) |
| 615 | - srcpkg = DebianSourcePackage(package, version, component=component, |
| 616 | - mirrors=[options.debian_mirror, |
| 617 | - options.debsec_mirror]) |
| 618 | - try: |
| 619 | - srcpkg.pull() |
| 620 | - except DownloadError, e: |
| 621 | - Logger.error('Failed to download: %s', str(e)) |
| 622 | - sys.exit(1) |
| 623 | - if not options.download_only: |
| 624 | - srcpkg.unpack() |
| 625 | - |
| 626 | - |
| 627 | if __name__ == '__main__': |
| 628 | try: |
| 629 | - main() |
| 630 | + parser = pullpkg.create_argparser(default_distro='debian', default_pull='source') |
| 631 | + pullpkg.pull(parser.parse_args()) |
| 632 | except KeyboardInterrupt: |
| 633 | Logger.normal('User abort.') |
| 634 | diff --git a/pull-debian-udebs b/pull-debian-udebs |
| 635 | new file mode 100755 |
| 636 | index 0000000..808ad54 |
| 637 | --- /dev/null |
| 638 | +++ b/pull-debian-udebs |
| 639 | @@ -0,0 +1,16 @@ |
| 640 | +#!/usr/bin/python |
| 641 | +# |
| 642 | +# pull-debian-udebs -- pull udeb package files for debian |
| 643 | +# Basic usage: pull-debian-udebs <package name> [version|release] |
| 644 | +# |
| 645 | +# See pull-pkg |
| 646 | + |
| 647 | +from ubuntutools import pullpkg |
| 648 | +from ubuntutools.logger import Logger |
| 649 | + |
| 650 | +if __name__ == '__main__': |
| 651 | + try: |
| 652 | + parser = pullpkg.create_argparser(default_distro='debian', default_pull='udebs') |
| 653 | + pullpkg.pull(parser.parse_args()) |
| 654 | + except KeyboardInterrupt: |
| 655 | + Logger.normal('User abort.') |
| 656 | diff --git a/pull-lp-ddebs b/pull-lp-ddebs |
| 657 | new file mode 100755 |
| 658 | index 0000000..39eb0f7 |
| 659 | --- /dev/null |
| 660 | +++ b/pull-lp-ddebs |
| 661 | @@ -0,0 +1,16 @@ |
| 662 | +#!/usr/bin/python |
| 663 | +# |
| 664 | +# pull-lp-ddebs -- pull ddeb package files for ubuntu |
| 665 | +# Basic usage: pull-lp-ddebs <package name> [version|release] |
| 666 | +# |
| 667 | +# See pull-pkg |
| 668 | + |
| 669 | +from ubuntutools import pullpkg |
| 670 | +from ubuntutools.logger import Logger |
| 671 | + |
| 672 | +if __name__ == '__main__': |
| 673 | + try: |
| 674 | + parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='ddebs') |
| 675 | + pullpkg.pull(parser.parse_args()) |
| 676 | + except KeyboardInterrupt: |
| 677 | + Logger.normal('User abort.') |
| 678 | diff --git a/pull-lp-debs b/pull-lp-debs |
| 679 | new file mode 100755 |
| 680 | index 0000000..a66d470 |
| 681 | --- /dev/null |
| 682 | +++ b/pull-lp-debs |
| 683 | @@ -0,0 +1,16 @@ |
| 684 | +#!/usr/bin/python |
| 685 | +# |
| 686 | +# pull-lp-debs -- pull deb package files for ubuntu |
| 687 | +# Basic usage: pull-lp-debs <package name> [version|release] |
| 688 | +# |
| 689 | +# See pull-pkg |
| 690 | + |
| 691 | +from ubuntutools import pullpkg |
| 692 | +from ubuntutools.logger import Logger |
| 693 | + |
| 694 | +if __name__ == '__main__': |
| 695 | + try: |
| 696 | + parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='debs') |
| 697 | + pullpkg.pull(parser.parse_args()) |
| 698 | + except KeyboardInterrupt: |
| 699 | + Logger.normal('User abort.') |
| 700 | diff --git a/pull-lp-source b/pull-lp-source |
| 701 | index 9cb865a..4a7f3cc 100755 |
| 702 | --- a/pull-lp-source |
| 703 | +++ b/pull-lp-source |
| 704 | @@ -1,149 +1,16 @@ |
| 705 | #!/usr/bin/python |
| 706 | # |
| 707 | -# pull-lp-source -- pull a source package from Launchpad |
| 708 | -# Basic usage: pull-lp-source <source package> [<release>] |
| 709 | +# pull-lp-source -- pull source package files for ubuntu |
| 710 | +# Basic usage: pull-lp-source <package name> [version|release] |
| 711 | # |
| 712 | -# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>, |
| 713 | -# 2010-2011, Stefano Rivera <stefanor@ubuntu.com> |
| 714 | -# |
| 715 | -# ################################################################## |
| 716 | -# |
| 717 | -# This program is free software; you can redistribute it and/or |
| 718 | -# modify it under the terms of the GNU General Public License |
| 719 | -# as published by the Free Software Foundation; either version 3 |
| 720 | -# of the License, or (at your option) any later version. |
| 721 | -# |
| 722 | -# This program is distributed in the hope that it will be useful, |
| 723 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 724 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 725 | -# GNU General Public License for more details. |
| 726 | -# |
| 727 | -# See file /usr/share/common-licenses/GPL for more details. |
| 728 | -# |
| 729 | -# ################################################################## |
| 730 | - |
| 731 | +# See pull-pkg |
| 732 | |
| 733 | -import json |
| 734 | -import os |
| 735 | -import sys |
| 736 | -import urllib2 |
| 737 | -from optparse import OptionParser |
| 738 | - |
| 739 | -from distro_info import UbuntuDistroInfo, DistroDataOutdated |
| 740 | - |
| 741 | -from ubuntutools.archive import UbuntuSourcePackage, DownloadError |
| 742 | -from ubuntutools.config import UDTConfig |
| 743 | -from ubuntutools.lp.lpapicache import Distribution, Launchpad |
| 744 | -from ubuntutools.lp.udtexceptions import (SeriesNotFoundException, |
| 745 | - PackageNotFoundException, |
| 746 | - PocketDoesNotExistError) |
| 747 | +from ubuntutools import pullpkg |
| 748 | from ubuntutools.logger import Logger |
| 749 | -from ubuntutools.misc import split_release_pocket |
| 750 | - |
| 751 | - |
| 752 | -def source_package_for(binary, release): |
| 753 | - """Query DDE to find the source package for a particular binary |
| 754 | - Should really do this with LP, but it's not possible LP: #597041 |
| 755 | - """ |
| 756 | - url = ('http://dde.debian.net/dde/q/udd/dist/d:ubuntu/r:%s/p:%s/?t=json' |
| 757 | - % (release, binary)) |
| 758 | - data = None |
| 759 | - try: |
| 760 | - data = json.load(urllib2.urlopen(url))['r'] |
| 761 | - except urllib2.URLError, e: |
| 762 | - Logger.error('Unable to retrieve package information from DDE: ' |
| 763 | - '%s (%s)', url, str(e)) |
| 764 | - except ValueError, e: |
| 765 | - Logger.error('Unable to parse JSON response from DDE: ' |
| 766 | - '%s (%s)', url, str(e)) |
| 767 | - if not data: |
| 768 | - return None |
| 769 | - return data[0]['source'] |
| 770 | - |
| 771 | - |
| 772 | -def main(): |
| 773 | - usage = "Usage: %prog <package> [release|version]" |
| 774 | - opt_parser = OptionParser(usage) |
| 775 | - opt_parser.add_option('-d', '--download-only', |
| 776 | - dest='download_only', default=False, |
| 777 | - action='store_true', |
| 778 | - help="Do not extract the source package") |
| 779 | - opt_parser.add_option('-m', '--mirror', metavar='UBUNTU_MIRROR', |
| 780 | - dest='ubuntu_mirror', |
| 781 | - help='Preferred Ubuntu mirror (default: Launchpad)') |
| 782 | - opt_parser.add_option('--no-conf', |
| 783 | - dest='no_conf', default=False, action='store_true', |
| 784 | - help="Don't read config files or environment " |
| 785 | - "variables") |
| 786 | - (options, args) = opt_parser.parse_args() |
| 787 | - if not args: |
| 788 | - opt_parser.error("Must specify package name") |
| 789 | - |
| 790 | - config = UDTConfig(options.no_conf) |
| 791 | - if options.ubuntu_mirror is None: |
| 792 | - options.ubuntu_mirror = config.get_value('UBUNTU_MIRROR') |
| 793 | - |
| 794 | - # Login anonymously to LP |
| 795 | - Launchpad.login_anonymously() |
| 796 | - |
| 797 | - package = str(args[0]).lower() |
| 798 | - |
| 799 | - ubuntu_info = UbuntuDistroInfo() |
| 800 | - if len(args) > 1: # Custom distribution specified. |
| 801 | - version = str(args[1]) |
| 802 | - else: |
| 803 | - try: |
| 804 | - version = os.getenv('DIST') or ubuntu_info.devel() |
| 805 | - except DistroDataOutdated, e: |
| 806 | - Logger.warn("%s\nOr specify a distribution.", e) |
| 807 | - sys.exit(1) |
| 808 | - component = None |
| 809 | - |
| 810 | - # Release, not package version number: |
| 811 | - release = None |
| 812 | - pocket = None |
| 813 | - try: |
| 814 | - (release, pocket) = split_release_pocket(version, default=None) |
| 815 | - except PocketDoesNotExistError, e: |
| 816 | - pass |
| 817 | - if release in ubuntu_info.all: |
| 818 | - archive = Distribution('ubuntu').getArchive() |
| 819 | - try: |
| 820 | - spph = archive.getSourcePackage(package, release, pocket) |
| 821 | - except SeriesNotFoundException, e: |
| 822 | - Logger.error(str(e)) |
| 823 | - sys.exit(1) |
| 824 | - except PackageNotFoundException, e: |
| 825 | - source_package = source_package_for(package, release) |
| 826 | - if source_package is not None and source_package != package: |
| 827 | - try: |
| 828 | - spph = archive.getSourcePackage(source_package, release, |
| 829 | - pocket) |
| 830 | - package = source_package |
| 831 | - except PackageNotFoundException: |
| 832 | - Logger.error(str(e)) |
| 833 | - sys.exit(1) |
| 834 | - else: |
| 835 | - Logger.error(str(e)) |
| 836 | - sys.exit(1) |
| 837 | - |
| 838 | - version = spph.getVersion() |
| 839 | - component = spph.getComponent() |
| 840 | - |
| 841 | - Logger.normal('Downloading %s version %s', package, version) |
| 842 | - srcpkg = UbuntuSourcePackage(package, version, component=component, |
| 843 | - mirrors=[options.ubuntu_mirror]) |
| 844 | - try: |
| 845 | - srcpkg.pull() |
| 846 | - except DownloadError, e: |
| 847 | - Logger.error('Failed to download: %s', str(e)) |
| 848 | - sys.exit(1) |
| 849 | - if not options.download_only: |
| 850 | - srcpkg.unpack() |
| 851 | - |
| 852 | |
| 853 | if __name__ == '__main__': |
| 854 | try: |
| 855 | - main() |
| 856 | + parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='source') |
| 857 | + pullpkg.pull(parser.parse_args()) |
| 858 | except KeyboardInterrupt: |
| 859 | Logger.normal('User abort.') |
| 860 | diff --git a/pull-lp-udebs b/pull-lp-udebs |
| 861 | new file mode 100755 |
| 862 | index 0000000..d050b61 |
| 863 | --- /dev/null |
| 864 | +++ b/pull-lp-udebs |
| 865 | @@ -0,0 +1,16 @@ |
| 866 | +#!/usr/bin/python |
| 867 | +# |
| 868 | +# pull-lp-udebs -- pull udeb package files for ubuntu |
| 869 | +# Basic usage: pull-lp-udebs <package name> [version|release] |
| 870 | +# |
| 871 | +# See pull-pkg |
| 872 | + |
| 873 | +from ubuntutools import pullpkg |
| 874 | +from ubuntutools.logger import Logger |
| 875 | + |
| 876 | +if __name__ == '__main__': |
| 877 | + try: |
| 878 | + parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='udebs') |
| 879 | + pullpkg.pull(parser.parse_args()) |
| 880 | + except KeyboardInterrupt: |
| 881 | + Logger.normal('User abort.') |
| 882 | diff --git a/pull-pkg b/pull-pkg |
| 883 | new file mode 100755 |
| 884 | index 0000000..98c5d22 |
| 885 | --- /dev/null |
| 886 | +++ b/pull-pkg |
| 887 | @@ -0,0 +1,33 @@ |
| 888 | +#!/usr/bin/python |
| 889 | +# |
| 890 | +# pull-pkg -- pull package files for debian/ubuntu/uca |
| 891 | +# Basic usage: pull-pkg -D distro -p type <package name> [version|release] |
| 892 | +# |
| 893 | +# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>, |
| 894 | +# 2010-2011, Stefano Rivera <stefanor@ubuntu.com> |
| 895 | +# 2017, Dan Streetman <dan.streetman@canonical.com> |
| 896 | +# |
| 897 | +# ################################################################## |
| 898 | +# |
| 899 | +# This program is free software; you can redistribute it and/or |
| 900 | +# modify it under the terms of the GNU General Public License |
| 901 | +# as published by the Free Software Foundation; either version 3 |
| 902 | +# of the License, or (at your option) any later version. |
| 903 | +# |
| 904 | +# This program is distributed in the hope that it will be useful, |
| 905 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 906 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 907 | +# GNU General Public License for more details. |
| 908 | +# |
| 909 | +# See file /usr/share/common-licenses/GPL for more details. |
| 910 | +# |
| 911 | +# ################################################################## |
| 912 | + |
| 913 | +from ubuntutools import pullpkg |
| 914 | +from ubuntutools.logger import Logger |
| 915 | + |
| 916 | +if __name__ == '__main__': |
| 917 | + try: |
| 918 | + pullpkg.pull(pullpkg.create_argparser().parse_args()) |
| 919 | + except KeyboardInterrupt: |
| 920 | + Logger.normal('User abort.') |
| 921 | diff --git a/pull-uca-ddebs b/pull-uca-ddebs |
| 922 | new file mode 100755 |
| 923 | index 0000000..baa6ace |
| 924 | --- /dev/null |
| 925 | +++ b/pull-uca-ddebs |
| 926 | @@ -0,0 +1,16 @@ |
| 927 | +#!/usr/bin/python |
| 928 | +# |
| 929 | +# pull-uca-ddebs -- pull ddeb package files for ubuntu cloud archive |
| 930 | +# Basic usage: pull-uca-ddebs <package name> [version|release] |
| 931 | +# |
| 932 | +# See pull-pkg |
| 933 | + |
| 934 | +from ubuntutools import pullpkg |
| 935 | +from ubuntutools.logger import Logger |
| 936 | + |
| 937 | +if __name__ == '__main__': |
| 938 | + try: |
| 939 | + parser = pullpkg.create_argparser(default_distro='uca', default_pull='ddebs') |
| 940 | + pullpkg.pull(parser.parse_args()) |
| 941 | + except KeyboardInterrupt: |
| 942 | + Logger.normal('User abort.') |
| 943 | diff --git a/pull-uca-debs b/pull-uca-debs |
| 944 | new file mode 100755 |
| 945 | index 0000000..76c72a7 |
| 946 | --- /dev/null |
| 947 | +++ b/pull-uca-debs |
| 948 | @@ -0,0 +1,16 @@ |
| 949 | +#!/usr/bin/python |
| 950 | +# |
| 951 | +# pull-uca-debs -- pull deb package files for ubuntu cloud archive |
| 952 | +# Basic usage: pull-uca-debs <package name> [version|release] |
| 953 | +# |
| 954 | +# See pull-pkg |
| 955 | + |
| 956 | +from ubuntutools import pullpkg |
| 957 | +from ubuntutools.logger import Logger |
| 958 | + |
| 959 | +if __name__ == '__main__': |
| 960 | + try: |
| 961 | + parser = pullpkg.create_argparser(default_distro='uca', default_pull='debs') |
| 962 | + pullpkg.pull(parser.parse_args()) |
| 963 | + except KeyboardInterrupt: |
| 964 | + Logger.normal('User abort.') |
| 965 | diff --git a/pull-uca-source b/pull-uca-source |
| 966 | index 79a60f5..f2dbdf0 100755 |
| 967 | --- a/pull-uca-source |
| 968 | +++ b/pull-uca-source |
| 969 | @@ -1,157 +1,16 @@ |
| 970 | #!/usr/bin/python |
| 971 | # |
| 972 | -# pull-uca-source -- pull a source package from Ubuntu Cloud Archive |
| 973 | -# Basic usage: pull-uca-source <source package> <openstack release> [version] |
| 974 | +# pull-uca-source -- pull source package files for ubuntu cloud archive |
| 975 | +# Basic usage: pull-uca-source <package name> [version|release] |
| 976 | # |
| 977 | -# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>, |
| 978 | -# 2010-2011, Stefano Rivera <stefanor@ubuntu.com> |
| 979 | -# 2016, Corey Bryant <corey.bryant@ubuntu.com> |
| 980 | -# 2016, Dan Streetman <dan.streetman@canonical.com> |
| 981 | -# |
| 982 | -# ################################################################## |
| 983 | -# |
| 984 | -# This program is free software; you can redistribute it and/or |
| 985 | -# modify it under the terms of the GNU General Public License |
| 986 | -# as published by the Free Software Foundation; either version 3 |
| 987 | -# of the License, or (at your option) any later version. |
| 988 | -# |
| 989 | -# This program is distributed in the hope that it will be useful, |
| 990 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 991 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 992 | -# GNU General Public License for more details. |
| 993 | -# |
| 994 | -# See file /usr/share/common-licenses/GPL for more details. |
| 995 | -# |
| 996 | -# ################################################################## |
| 997 | - |
| 998 | +# See pull-pkg |
| 999 | |
| 1000 | -import re |
| 1001 | -import sys |
| 1002 | -from optparse import OptionParser |
| 1003 | - |
| 1004 | -from ubuntutools.archive import UbuntuCloudArchiveSourcePackage, DownloadError |
| 1005 | -from ubuntutools.config import UDTConfig |
| 1006 | -from ubuntutools.lp.lpapicache import Launchpad |
| 1007 | -from ubuntutools.lp.udtexceptions import PocketDoesNotExistError |
| 1008 | +from ubuntutools import pullpkg |
| 1009 | from ubuntutools.logger import Logger |
| 1010 | -from ubuntutools.misc import split_release_pocket |
| 1011 | - |
| 1012 | -from lazr.restfulclient.errors import NotFound |
| 1013 | - |
| 1014 | -from launchpadlib.launchpad import Launchpad as LP |
| 1015 | - |
| 1016 | - |
| 1017 | -def showOpenstackReleases(uca): |
| 1018 | - releases = [] |
| 1019 | - for p in uca.ppas: |
| 1020 | - if re.match(r"\w*-staging", p.name): |
| 1021 | - releases.append(re.sub("-staging", "", p.name)) |
| 1022 | - Logger.error("Openstack releases are:\n\t%s", ", ".join(releases)) |
| 1023 | - |
| 1024 | - |
| 1025 | -def getSPPH(lp, archive, package, version=None, series=None, pocket=None, try_binary=True): |
| 1026 | - params = {'exact_match': True, 'order_by_date': True} |
| 1027 | - if pocket: |
| 1028 | - params['pocket'] = pocket |
| 1029 | - if series: |
| 1030 | - params['distro_series'] = series() |
| 1031 | - elif version: |
| 1032 | - params['version'] = version |
| 1033 | - Logger.normal("checking %s version %s pocket %s", package, version, pocket) |
| 1034 | - spphs = archive.getPublishedSources(source_name=package, **params) |
| 1035 | - if spphs: |
| 1036 | - return spphs[0] |
| 1037 | - if not try_binary: |
| 1038 | - return None |
| 1039 | - |
| 1040 | - # Didn't find any, maybe the package is a binary package name |
| 1041 | - if series: |
| 1042 | - del params['distro_series'] |
| 1043 | - archs = lp.load(series().architectures_collection_link).entries |
| 1044 | - params['distro_arch_series'] = archs[0]['self_link'] |
| 1045 | - bpphs = archive.getPublishedBinaries(binary_name=package, **params) |
| 1046 | - if bpphs: |
| 1047 | - bpph_build = lp.load(bpphs[0].build_link) |
| 1048 | - source_package = bpph_build.source_package_name |
| 1049 | - return getSPPH(lp, archive, source_package, version, series, pocket, |
| 1050 | - try_binary=False) |
| 1051 | - |
| 1052 | - return None |
| 1053 | - |
| 1054 | - |
| 1055 | -def main(): |
| 1056 | - usage = "Usage: %prog <package> <openstack release> [version]" |
| 1057 | - opt_parser = OptionParser(usage) |
| 1058 | - opt_parser.add_option('-d', '--download-only', |
| 1059 | - dest='download_only', default=False, |
| 1060 | - action='store_true', |
| 1061 | - help="Do not extract the source package") |
| 1062 | - opt_parser.add_option('-m', '--mirror', metavar='OPENSTACK_MIRROR', |
| 1063 | - dest='openstack_mirror', |
| 1064 | - help='Preferred Openstack mirror (default: Launchpad)') |
| 1065 | - opt_parser.add_option('--no-conf', |
| 1066 | - dest='no_conf', default=False, action='store_true', |
| 1067 | - help="Don't read config files or environment " |
| 1068 | - "variables") |
| 1069 | - (options, args) = opt_parser.parse_args() |
| 1070 | - if len(args) < 2: |
| 1071 | - opt_parser.error("Must specify package name and openstack release") |
| 1072 | - |
| 1073 | - config = UDTConfig(options.no_conf) |
| 1074 | - if options.openstack_mirror is None: |
| 1075 | - options.openstack_mirror = config.get_value('OPENSTACK_MIRROR') |
| 1076 | - mirrors = [] |
| 1077 | - if options.openstack_mirror: |
| 1078 | - mirrors.append(options.openstack_mirror) |
| 1079 | - |
| 1080 | - # Login anonymously to LP |
| 1081 | - Launchpad.login_anonymously() |
| 1082 | - lp = LP.login_anonymously("pull-uca-source", "production") |
| 1083 | - uca = lp.people("ubuntu-cloud-archive") |
| 1084 | - |
| 1085 | - package = str(args[0]).lower() |
| 1086 | - release = str(args[1]).lower() |
| 1087 | - version = None |
| 1088 | - if len(args) > 2: |
| 1089 | - version = str(args[2]) |
| 1090 | - |
| 1091 | - pocket = None |
| 1092 | - try: |
| 1093 | - (release, pocket) = split_release_pocket(release, default=None) |
| 1094 | - except PocketDoesNotExistError, e: |
| 1095 | - pass |
| 1096 | - |
| 1097 | - try: |
| 1098 | - archive = uca.getPPAByName(name="%s-staging" % release) |
| 1099 | - except NotFound, e: |
| 1100 | - Logger.error('Archive does not exist for Openstack release: %s', |
| 1101 | - release) |
| 1102 | - showOpenstackReleases(uca) |
| 1103 | - sys.exit(1) |
| 1104 | - |
| 1105 | - spph = getSPPH(lp, archive, package, version, pocket=pocket) |
| 1106 | - if not spph: |
| 1107 | - Logger.error("Package %s in %s not found.", package, release) |
| 1108 | - sys.exit(1) |
| 1109 | - |
| 1110 | - package = spph.source_package_name |
| 1111 | - version = spph.source_package_version |
| 1112 | - component = spph.component_name |
| 1113 | - Logger.normal('Downloading %s version %s component %s', package, version, component) |
| 1114 | - srcpkg = UbuntuCloudArchiveSourcePackage(release, package, version, component=component, |
| 1115 | - mirrors=mirrors) |
| 1116 | - |
| 1117 | - try: |
| 1118 | - srcpkg.pull() |
| 1119 | - except DownloadError, e: |
| 1120 | - Logger.error('Failed to download: %s', str(e)) |
| 1121 | - sys.exit(1) |
| 1122 | - if not options.download_only: |
| 1123 | - srcpkg.unpack() |
| 1124 | - |
| 1125 | |
| 1126 | if __name__ == '__main__': |
| 1127 | try: |
| 1128 | - main() |
| 1129 | + parser = pullpkg.create_argparser(default_distro='uca', default_pull='source') |
| 1130 | + pullpkg.pull(parser.parse_args()) |
| 1131 | except KeyboardInterrupt: |
| 1132 | Logger.normal('User abort.') |
| 1133 | diff --git a/pull-uca-udebs b/pull-uca-udebs |
| 1134 | new file mode 100755 |
| 1135 | index 0000000..a7008ff |
| 1136 | --- /dev/null |
| 1137 | +++ b/pull-uca-udebs |
| 1138 | @@ -0,0 +1,16 @@ |
| 1139 | +#!/usr/bin/python |
| 1140 | +# |
| 1141 | +# pull-uca-udebs -- pull udeb package files for ubuntu cloud archive |
| 1142 | +# Basic usage: pull-uca-udebs <package name> [version|release] |
| 1143 | +# |
| 1144 | +# See pull-pkg |
| 1145 | + |
| 1146 | +from ubuntutools import pullpkg |
| 1147 | +from ubuntutools.logger import Logger |
| 1148 | + |
| 1149 | +if __name__ == '__main__': |
| 1150 | + try: |
| 1151 | + parser = pullpkg.create_argparser(default_distro='uca', default_pull='udebs') |
| 1152 | + pullpkg.pull(parser.parse_args()) |
| 1153 | + except KeyboardInterrupt: |
| 1154 | + Logger.normal('User abort.') |
| 1155 | diff --git a/setup.py b/setup.py |
| 1156 | index 221e3a2..894a31f 100755 |
| 1157 | --- a/setup.py |
| 1158 | +++ b/setup.py |
| 1159 | @@ -34,11 +34,21 @@ else: |
| 1160 | 'mk-sbuild', |
| 1161 | 'pbuilder-dist', |
| 1162 | 'pbuilder-dist-simple', |
| 1163 | + 'pull-pkg', |
| 1164 | 'pull-debian-debdiff', |
| 1165 | 'pull-debian-source', |
| 1166 | + 'pull-debian-debs', |
| 1167 | + 'pull-debian-ddebs', |
| 1168 | + 'pull-debian-udebs', |
| 1169 | 'pull-lp-source', |
| 1170 | + 'pull-lp-debs', |
| 1171 | + 'pull-lp-ddebs', |
| 1172 | + 'pull-lp-udebs', |
| 1173 | 'pull-revu-source', |
| 1174 | 'pull-uca-source', |
| 1175 | + 'pull-uca-debs', |
| 1176 | + 'pull-uca-ddebs', |
| 1177 | + 'pull-uca-udebs', |
| 1178 | 'requestbackport', |
| 1179 | 'requestsync', |
| 1180 | 'reverse-build-depends', |
| 1181 | diff --git a/ubuntutools/archive.py b/ubuntutools/archive.py |
| 1182 | index 9324313..dd1f54f 100644 |
| 1183 | --- a/ubuntutools/archive.py |
| 1184 | +++ b/ubuntutools/archive.py |
| 1185 | @@ -42,15 +42,22 @@ except ImportError: |
| 1186 | import re |
| 1187 | import sys |
| 1188 | |
| 1189 | -from debian.changelog import Changelog, Version |
| 1190 | +from debian.changelog import Changelog |
| 1191 | import debian.deb822 |
| 1192 | -import debian.debian_support |
| 1193 | import httplib2 |
| 1194 | +import json |
| 1195 | |
| 1196 | from ubuntutools.config import UDTConfig |
| 1197 | -from ubuntutools.lp.lpapicache import (Launchpad, Distribution, |
| 1198 | - SourcePackagePublishingHistory) |
| 1199 | +from ubuntutools.lp.libsupport import get_system_arch |
| 1200 | +from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam, |
| 1201 | + SourcePackagePublishingHistory, |
| 1202 | + BinaryPackagePublishingHistory) |
| 1203 | +from ubuntutools.lp.udtexceptions import (PackageNotFoundException, |
| 1204 | + SeriesNotFoundException, |
| 1205 | + PocketDoesNotExistError) |
| 1206 | from ubuntutools.logger import Logger |
| 1207 | +from ubuntutools.misc import split_release_pocket |
| 1208 | +from ubuntutools.version import Version |
| 1209 | from ubuntutools import subprocess |
| 1210 | |
| 1211 | if sys.version_info[0] >= 3: |
| 1212 | @@ -130,58 +137,131 @@ class SourcePackage(object): |
| 1213 | distribution = None |
| 1214 | |
| 1215 | def __init__(self, package=None, version=None, component=None, |
| 1216 | - dscfile=None, lp=None, mirrors=(), workdir='.', quiet=False): |
| 1217 | - "Can be initialised either using package, version or dscfile" |
| 1218 | - assert ((package is not None and version is not None) |
| 1219 | - or dscfile is not None) |
| 1220 | + dscfile=None, lp=None, mirrors=(), workdir='.', quiet=False, |
| 1221 | + series=None, pocket=None): |
| 1222 | + """Can be initialised using either package or dscfile. |
| 1223 | + If package is specified, either the version or series can also be |
| 1224 | + specified; using version will get the specific package version, |
| 1225 | + while using the series will get the latest version from that series. |
| 1226 | + Specifying only the package with no version or series will get the |
| 1227 | + latest version from the development series. |
| 1228 | + """ |
| 1229 | + assert (package is not None or dscfile is not None) |
| 1230 | |
| 1231 | self.source = package |
| 1232 | self._lp = lp |
| 1233 | + self.binary = None |
| 1234 | + self.try_binary = True |
| 1235 | self.workdir = workdir |
| 1236 | self.quiet = quiet |
| 1237 | + self._series = series |
| 1238 | + self._use_series = True |
| 1239 | + self._pocket = pocket |
| 1240 | + self._dsc_source = dscfile |
| 1241 | |
| 1242 | # Cached values: |
| 1243 | self._component = component |
| 1244 | self._dsc = None |
| 1245 | self._spph = None |
| 1246 | + self._version = Version(version) if version else None |
| 1247 | |
| 1248 | # State: |
| 1249 | self._dsc_fetched = False |
| 1250 | |
| 1251 | # Mirrors |
| 1252 | - self._dsc_source = dscfile |
| 1253 | self.mirrors = list(mirrors) |
| 1254 | if self.distribution: |
| 1255 | self.masters = [UDTConfig.defaults['%s_MIRROR' |
| 1256 | % self.distribution.upper()]] |
| 1257 | - if dscfile is not None: |
| 1258 | - if self.source is None: |
| 1259 | - self.source = 'unknown' |
| 1260 | - if version is None: |
| 1261 | - version = 'unknown' |
| 1262 | - |
| 1263 | - self.version = debian.debian_support.Version(version) |
| 1264 | |
| 1265 | # uses default proxies from the environment |
| 1266 | proxy = ProxyHandler() |
| 1267 | self.url_opener = build_opener(proxy) |
| 1268 | |
| 1269 | + # if a dsc was specified, pull it to get the source/version info |
| 1270 | + if self._dsc_source: |
| 1271 | + self.pull_dsc() |
| 1272 | + |
| 1273 | @property |
| 1274 | def lp_spph(self): |
| 1275 | "Return the LP Source Package Publishing History entry" |
| 1276 | - if not self._spph: |
| 1277 | - if not Launchpad.logged_in: |
| 1278 | - if self._lp: |
| 1279 | - Launchpad.login_existing(self._lp) |
| 1280 | + if self._spph: |
| 1281 | + return self._spph |
| 1282 | + |
| 1283 | + if not Launchpad.logged_in: |
| 1284 | + if self._lp: |
| 1285 | + Launchpad.login_existing(self._lp) |
| 1286 | + else: |
| 1287 | + Launchpad.login_anonymously() |
| 1288 | + |
| 1289 | + distro = Distribution(self.distribution) |
| 1290 | + series = None |
| 1291 | + archive = self.getArchive() |
| 1292 | + params = {'exact_match': True, 'order_by_date': True} |
| 1293 | + if self._version: |
| 1294 | + # if version was specified, use that |
| 1295 | + params['version'] = self._version.full_version |
| 1296 | + elif self._use_series: |
| 1297 | + if self._series: |
| 1298 | + # if version not specified, get the latest from this series |
| 1299 | + series = distro.getSeries(self._series) |
| 1300 | + else: |
| 1301 | + # if no version or series, get the latest from devel series |
| 1302 | + series = distro.getDevelopmentSeries() |
| 1303 | + params['distro_series'] = series() |
| 1304 | + if self._pocket: |
| 1305 | + params['pocket'] = self._pocket |
| 1306 | + spphs = archive.getPublishedSources(source_name=self.source, **params) |
| 1307 | + if spphs: |
| 1308 | + self._spph = SourcePackagePublishingHistory(spphs[0]) |
| 1309 | + Logger.debug("Found SPPH {}".format(self._spph)) |
| 1310 | + return self._spph |
| 1311 | + |
| 1312 | + if self.try_binary and not self.binary: |
| 1313 | + if series: |
| 1314 | + arch_series = series.getArchSeries() |
| 1315 | + params['distro_arch_series'] = arch_series() |
| 1316 | + del params['distro_series'] |
| 1317 | + bpphs = archive.getPublishedBinaries(binary_name=self.source, **params) |
| 1318 | + if bpphs: |
| 1319 | + bpph = BinaryPackagePublishingHistory(bpphs[0]) |
| 1320 | + Logger.debug("Found BPPH {}".format(bpph)) |
| 1321 | + self.binary = self.source |
| 1322 | + self.source = bpph.getSourcePackageName() |
| 1323 | + Logger.normal("Using source package '{}' for binary package '{}'" |
| 1324 | + .format(self.source, self.binary)) |
| 1325 | + try: |
| 1326 | + spph = bpph.getBuild().getSourcePackagePublishingHistory() |
| 1327 | + except: |
| 1328 | + spph = None |
| 1329 | + if spph: |
| 1330 | + self._spph = spph |
| 1331 | + return self._spph |
| 1332 | else: |
| 1333 | - Launchpad.login_anonymously() |
| 1334 | - spph = Distribution(self.distribution).getArchive().getPublishedSources( |
| 1335 | - source_name=self.source, |
| 1336 | - version=self.version.full_version, |
| 1337 | - exact_match=True, |
| 1338 | - ) |
| 1339 | - self._spph = SourcePackagePublishingHistory(spph[0]) |
| 1340 | - return self._spph |
| 1341 | + # binary build didn't include source link, unfortunately |
| 1342 | + # so try again with the updated self.source name |
| 1343 | + if not self._version: |
| 1344 | + # Get version first if user didn't specify it, as some |
| 1345 | + # binaries have their version hardcoded in their name, |
| 1346 | + # such as the kernel package |
| 1347 | + self._version = Version(bpph.getVersion()) |
| 1348 | + return self.lp_spph |
| 1349 | + |
| 1350 | + msg = "No {} package found".format(self.source) |
| 1351 | + if self._version: |
| 1352 | + msg += " for version {}".format(self._version.full_version) |
| 1353 | + elif series: |
| 1354 | + msg += " in series {}".format(series.name) |
| 1355 | + if self._pocket: |
| 1356 | + msg += " pocket {}".format(self._pocket) |
| 1357 | + raise PackageNotFoundException(msg) |
| 1358 | + |
| 1359 | + @property |
| 1360 | + def version(self): |
| 1361 | + "Return Package version" |
| 1362 | + if not self._version: |
| 1363 | + self._version = Version(self.lp_spph.getVersion()) |
| 1364 | + return self._version |
| 1365 | |
| 1366 | @property |
| 1367 | def component(self): |
| 1368 | @@ -194,10 +274,7 @@ class SourcePackage(object): |
| 1369 | @property |
| 1370 | def dsc_name(self): |
| 1371 | "Return the source package dsc filename for the given package" |
| 1372 | - version = self.version.upstream_version |
| 1373 | - if self.version.debian_version: |
| 1374 | - version += '-' + self.version.debian_version |
| 1375 | - return '%s_%s.dsc' % (self.source, version) |
| 1376 | + return '%s_%s.dsc' % (self.source, self.version.strip_epoch()) |
| 1377 | |
| 1378 | @property |
| 1379 | def dsc_pathname(self): |
| 1380 | @@ -211,6 +288,9 @@ class SourcePackage(object): |
| 1381 | self.pull_dsc() |
| 1382 | return self._dsc |
| 1383 | |
| 1384 | + def getArchive(self): |
| 1385 | + return Distribution(self.distribution).getArchive() |
| 1386 | + |
| 1387 | def _mirror_url(self, mirror, filename): |
| 1388 | "Build a source package URL on a mirror" |
| 1389 | if self.source.startswith('lib'): |
| 1390 | @@ -236,6 +316,22 @@ class SourcePackage(object): |
| 1391 | yield self._mirror_url(mirror, name) |
| 1392 | yield self._lp_url(name) |
| 1393 | |
| 1394 | + def _binary_urls(self, name, default_url): |
| 1395 | + "Generator of URLs for name" |
| 1396 | + for mirror in self.mirrors: |
| 1397 | + yield self._mirror_url(mirror, name) |
| 1398 | + for mirror in self.masters: |
| 1399 | + if mirror not in self.mirrors: |
| 1400 | + yield self._mirror_url(mirror, name) |
| 1401 | + yield self._lp_url(name) |
| 1402 | + yield default_url |
| 1403 | + |
| 1404 | + def _binary_files(self, name, arch): |
| 1405 | + for bpph in self.lp_spph.getBinaries(arch): |
| 1406 | + if name and not re.match(name, bpph.binary_package_name): |
| 1407 | + continue |
| 1408 | + yield (bpph.getFileName(), bpph.getUrl(), 0) |
| 1409 | + |
| 1410 | def pull_dsc(self): |
| 1411 | "Retrieve dscfile and parse" |
| 1412 | if self._dsc_source: |
| 1413 | @@ -271,7 +367,7 @@ class SourcePackage(object): |
| 1414 | "Check that the dsc matches what we are expecting" |
| 1415 | assert self._dsc is not None |
| 1416 | self.source = self.dsc['Source'] |
| 1417 | - self.version = debian.debian_support.Version(self.dsc['Version']) |
| 1418 | + self._version = Version(self.dsc['Version']) |
| 1419 | |
| 1420 | valid = False |
| 1421 | message = None |
| 1422 | @@ -310,28 +406,41 @@ class SourcePackage(object): |
| 1423 | with open(self.dsc_pathname, 'wb') as f: |
| 1424 | f.write(self.dsc.raw_text) |
| 1425 | |
| 1426 | - def _download_file(self, url, filename): |
| 1427 | + def _download_file(self, url, filename, verify=True, size=0): |
| 1428 | "Download url to filename in workdir." |
| 1429 | pathname = os.path.join(self.workdir, filename) |
| 1430 | - if self.dsc.verify_file(pathname): |
| 1431 | - Logger.debug('Using existing %s', filename) |
| 1432 | - return True |
| 1433 | - size = [entry['size'] for entry in self.dsc['Files'] |
| 1434 | - if entry['name'] == filename] |
| 1435 | - assert len(size) == 1 |
| 1436 | - size = int(size[0]) |
| 1437 | + if verify: |
| 1438 | + if self.dsc.verify_file(pathname): |
| 1439 | + Logger.debug('Using existing %s', filename) |
| 1440 | + return True |
| 1441 | + size = [entry['size'] for entry in self.dsc['Files'] |
| 1442 | + if entry['name'] == filename] |
| 1443 | + assert len(size) == 1 |
| 1444 | + size = int(size[0]) |
| 1445 | + |
| 1446 | parsed = urlparse(url) |
| 1447 | - if not self.quiet: |
| 1448 | - Logger.normal('Downloading %s from %s (%0.3f MiB)', |
| 1449 | - filename, parsed.hostname, size / 1024.0 / 1024) |
| 1450 | |
| 1451 | if parsed.scheme == 'file': |
| 1452 | in_ = open(parsed.path, 'rb') |
| 1453 | + if not size: |
| 1454 | + size = int(os.stat(parsed.path).st_size) |
| 1455 | else: |
| 1456 | try: |
| 1457 | in_ = self.url_opener.open(url) |
| 1458 | - except URLError: |
| 1459 | + Logger.debug("Using URL '%s'", url) |
| 1460 | + except URLError as e: |
| 1461 | + Logger.debug("URLError opening '%s': %s", url, e) |
| 1462 | return False |
| 1463 | + if not size: |
| 1464 | + contentlen = in_.info().get('Content-Length') |
| 1465 | + if not contentlen: |
| 1466 | + Logger.error("Invalid response, no Content-Length") |
| 1467 | + return False |
| 1468 | + size = int(contentlen) |
| 1469 | + |
| 1470 | + if not self.quiet: |
| 1471 | + Logger.normal('Downloading %s from %s (%0.3f MiB)', |
| 1472 | + filename, parsed.hostname, size / 1024.0 / 1024) |
| 1473 | |
| 1474 | downloaded = 0 |
| 1475 | bar_width = 60 |
| 1476 | @@ -354,7 +463,7 @@ class SourcePackage(object): |
| 1477 | if not self.quiet: |
| 1478 | Logger.stdout.write(' ' * (bar_width + 7) + '\r') |
| 1479 | Logger.stdout.flush() |
| 1480 | - if not self.dsc.verify_file(pathname): |
| 1481 | + if verify and not self.dsc.verify_file(pathname): |
| 1482 | Logger.error('Checksum for %s does not match.', filename) |
| 1483 | return False |
| 1484 | return True |
| 1485 | @@ -375,6 +484,36 @@ class SourcePackage(object): |
| 1486 | else: |
| 1487 | raise DownloadError('File %s could not be found' % name) |
| 1488 | |
| 1489 | + def pull_binaries(self, name=None, arch=None): |
| 1490 | + """Pull binary debs into workdir. |
| 1491 | + If name is specified, only binary packages matching the regex are included. |
| 1492 | + If arch is specified, only binary packages for that arch are included, |
| 1493 | + otherwise only binary packages for the current arch are included. |
| 1494 | + Returns the number of files downloaded. |
| 1495 | + """ |
| 1496 | + total = 0 |
| 1497 | + |
| 1498 | + if not arch: |
| 1499 | + arch = get_system_arch() |
| 1500 | + Logger.debug("Using system arch %s", arch) |
| 1501 | + |
| 1502 | + for (fname, furl, fsize) in self._binary_files(name, arch): |
| 1503 | + found = False |
| 1504 | + for url in self._binary_urls(fname, furl): |
| 1505 | + try: |
| 1506 | + if self._download_file(url, fname, False, fsize): |
| 1507 | + found = True |
| 1508 | + break |
| 1509 | + except HTTPError as e: |
| 1510 | + Logger.normal('HTTP Error %i: %s', e.code, str(e)) |
| 1511 | + except URLError as e: |
| 1512 | + Logger.normal('URL Error: %s', e.reason) |
| 1513 | + if found: |
| 1514 | + total += 1 |
| 1515 | + else: |
| 1516 | + Logger.normal("Could not download from any location: %s", fname) |
| 1517 | + return total |
| 1518 | + |
| 1519 | def verify(self): |
| 1520 | """Verify that the source package in workdir matches the dsc. |
| 1521 | Return boolean |
| 1522 | @@ -432,33 +571,28 @@ class DebianSourcePackage(SourcePackage): |
| 1523 | super(DebianSourcePackage, self).__init__(*args, **kwargs) |
| 1524 | self.masters.append(UDTConfig.defaults['DEBSEC_MIRROR']) |
| 1525 | # Cached values: |
| 1526 | - self._snapshot_list = None |
| 1527 | + self._snapshot_package = None |
| 1528 | + self._snapshot_files = None |
| 1529 | + # Don't bother searching in LP for debian binaries, they aren't there |
| 1530 | + self.try_binary = False |
| 1531 | |
| 1532 | # Overridden methods: |
| 1533 | @property |
| 1534 | def lp_spph(self): |
| 1535 | "Return the LP Source Package Publishing History entry" |
| 1536 | - if not self._spph: |
| 1537 | - try: |
| 1538 | - return super(DebianSourcePackage, self).lp_spph |
| 1539 | - except IndexError: |
| 1540 | - pass |
| 1541 | - |
| 1542 | - Logger.normal('Using rmadison for component determination') |
| 1543 | - comp = 'main' |
| 1544 | - for record in rmadison(self.distribution, self.source): |
| 1545 | - if record.get('source') != self.source: |
| 1546 | - continue |
| 1547 | - comp = record['component'] |
| 1548 | - if record['version'] == self.version.full_version: |
| 1549 | - self._spph = FakeSPPH(record['source'], record['version'], |
| 1550 | - comp, 'debian') |
| 1551 | - return self._spph |
| 1552 | + if self._spph: |
| 1553 | + return self._spph |
| 1554 | |
| 1555 | - Logger.normal('Guessing component from most recent upload') |
| 1556 | - self._spph = FakeSPPH(self.source, self.version.full_version, comp, |
| 1557 | - 'debian') |
| 1558 | - return self._spph |
| 1559 | + try: |
| 1560 | + self._spph = DebianSPPH(super(DebianSourcePackage, self).lp_spph()) |
| 1561 | + return self._spph |
| 1562 | + except PackageNotFoundException: |
| 1563 | + pass |
| 1564 | + except SeriesNotFoundException: |
| 1565 | + pass |
| 1566 | + |
| 1567 | + Logger.normal('Package not found in Launchpad, using snapshot') |
| 1568 | + return self.snapshot_package.getSPPH() |
| 1569 | |
| 1570 | def _source_urls(self, name): |
| 1571 | "Generator of sources for name" |
| 1572 | @@ -468,8 +602,13 @@ class DebianSourcePackage(SourcePackage): |
| 1573 | yield next(wrapped_iterator) |
| 1574 | except StopIteration: |
| 1575 | break |
| 1576 | - if self.snapshot_list: |
| 1577 | - yield self._snapshot_url(name) |
| 1578 | + yield self.snapshot_files[name] |
| 1579 | + |
| 1580 | + def _binary_files(self, name, arch): |
| 1581 | + for f in self.snapshot_package.getBinaryFiles(arch): |
| 1582 | + if name and not re.match(name, f.package_name): |
| 1583 | + continue |
| 1584 | + yield (f.name, f.getUrl(), f.size) |
| 1585 | |
| 1586 | def pull_dsc(self): |
| 1587 | "Retrieve dscfile and parse" |
| 1588 | @@ -493,41 +632,39 @@ class DebianSourcePackage(SourcePackage): |
| 1589 | |
| 1590 | # Local methods: |
| 1591 | @property |
| 1592 | - def snapshot_list(self): |
| 1593 | - "Return a filename -> hash dictionary from snapshot.debian.org" |
| 1594 | - if self._snapshot_list is None: |
| 1595 | - try: |
| 1596 | - import json |
| 1597 | - except ImportError: |
| 1598 | - import simplejson as json |
| 1599 | - except ImportError: |
| 1600 | - Logger.error("Please install python-simplejson.") |
| 1601 | - raise DownloadError("Unable to dowload from " |
| 1602 | - "snapshot.debian.org without " |
| 1603 | - "python-simplejson") |
| 1604 | - |
| 1605 | - try: |
| 1606 | - data = self.url_opener.open( |
| 1607 | - 'http://snapshot.debian.org/mr/package/%s/%s/srcfiles?fileinfo=1' % |
| 1608 | - (self.source, self.version.full_version)) |
| 1609 | - reader = codecs.getreader('utf-8') |
| 1610 | - srcfiles = json.load(reader(data)) |
| 1611 | - |
| 1612 | - except HTTPError: |
| 1613 | - Logger.error('Version %s of %s not found on ' |
| 1614 | - 'snapshot.debian.org', |
| 1615 | - self.version.full_version, self.source) |
| 1616 | - self._snapshot_list = False |
| 1617 | - return False |
| 1618 | - self._snapshot_list = dict((info[0]['name'], hash_) |
| 1619 | - for hash_, info |
| 1620 | - in srcfiles['fileinfo'].items()) |
| 1621 | - return self._snapshot_list |
| 1622 | + def snapshot_package(self): |
| 1623 | + if not self._snapshot_package: |
| 1624 | + if self._version or self._spph: |
| 1625 | + # as .version uses .lpph, and our .lpph might use us, |
| 1626 | + # only use .version if _version or _spph are set |
| 1627 | + version = self.version.full_version |
| 1628 | + srcpkgs = Snapshot.getSourcePackages(self.source, version=version) |
| 1629 | + if not srcpkgs: |
| 1630 | + msg = "Package {} {} not found".format(self.source, version) |
| 1631 | + raise PackageNotFoundException(msg) |
| 1632 | + self._snapshot_package = srcpkgs[0] |
| 1633 | + else: |
| 1634 | + # we have neither version nor spph, so look up our version using madison |
| 1635 | + Logger.normal('Using madison to find latest version number') |
| 1636 | + series = self._series |
| 1637 | + if self._pocket: |
| 1638 | + series += "-" + self._pocket |
| 1639 | + params = {'series': series} if series else {} |
| 1640 | + srcpkg = Madison(self.distribution).getSourcePackage(self.source, **params) |
| 1641 | + if not srcpkg: |
| 1642 | + raise PackageNotFoundException("Package {} not found".format(self.source)) |
| 1643 | + if self.source != srcpkg.name: |
| 1644 | + self.binary = self.source |
| 1645 | + self.source = srcpkg.name |
| 1646 | + self._snapshot_package = srcpkg |
| 1647 | + return self._snapshot_package |
| 1648 | |
| 1649 | - def _snapshot_url(self, name): |
| 1650 | - "Return the snapshot.debian.org URL for name" |
| 1651 | - return os.path.join('http://snapshot.debian.org/file', |
| 1652 | - self.snapshot_list[name]) |
| 1653 | + @property |
| 1654 | + def snapshot_files(self): |
| 1655 | + if not self._snapshot_files: |
| 1656 | + files = self.snapshot_package.getFiles() |
| 1657 | + self._snapshot_files = {f.name: f.getUrl() for f in files} |
| 1658 | + return self._snapshot_files |
| 1659 | |
| 1660 | |
| 1661 | class UbuntuSourcePackage(SourcePackage): |
| 1662 | @@ -537,37 +674,397 @@ class UbuntuSourcePackage(SourcePackage): |
| 1663 | |
| 1664 | class UbuntuCloudArchiveSourcePackage(UbuntuSourcePackage): |
| 1665 | "Download / unpack an Ubuntu Cloud Archive source package" |
| 1666 | - def __init__(self, uca_release, *args, **kwargs): |
| 1667 | + _ppas = None |
| 1668 | + _ppa_names = None |
| 1669 | + |
| 1670 | + def __init__(self, *args, **kwargs): |
| 1671 | super(UbuntuCloudArchiveSourcePackage, self).__init__(*args, **kwargs) |
| 1672 | - self._uca_release = uca_release |
| 1673 | + self._use_series = False # UCA doesn't really use distro series |
| 1674 | + self._uca_release = self._series |
| 1675 | + self._series = None |
| 1676 | self.masters = ["http://ubuntu-cloud.archive.canonical.com/ubuntu/"] |
| 1677 | |
| 1678 | + @classmethod |
| 1679 | + def getArchives(self): |
| 1680 | + if not self._ppas: |
| 1681 | + ppas = filter(lambda p: p.name.endswith('-staging'), |
| 1682 | + PersonTeam.fetch('ubuntu-cloud-archive').getPPAs()) |
| 1683 | + self._ppas = sorted(ppas, key=lambda p: p.name, reverse=True) |
| 1684 | + return self._ppas |
| 1685 | + |
| 1686 | + @classmethod |
| 1687 | + def getReleaseNames(self): |
| 1688 | + if not self._ppa_names: |
| 1689 | + self._ppa_names = [p.name.split('-', 1)[0] for p in self.getArchives()] |
| 1690 | + return self._ppa_names |
| 1691 | + |
| 1692 | + @classmethod |
| 1693 | + def getDevelopmentRelease(self): |
| 1694 | + return self.getReleaseNames()[0] |
| 1695 | + |
| 1696 | + @property |
| 1697 | + def uca_release(self): |
| 1698 | + if not self._uca_release: |
| 1699 | + self._uca_release = self.getDevelopmentRelease() |
| 1700 | + Logger.normal('Using UCA release %s', self._uca_release) |
| 1701 | + return self._uca_release |
| 1702 | + |
| 1703 | + def getArchive(self): |
| 1704 | + ppas = {p.name: p for p in self.getArchives()} |
| 1705 | + release = '{}-staging'.format(self.uca_release) |
| 1706 | + if release in ppas: |
| 1707 | + Logger.debug('UCA release {} at {}'.format(self.uca_release, |
| 1708 | + ppas[release]())) |
| 1709 | + return ppas[release] |
| 1710 | + raise SeriesNotFoundException('UCA release {} not found.'.format(self.uca_release)) |
| 1711 | + |
| 1712 | def _lp_url(self, filename): |
| 1713 | "Build a source package URL on Launchpad" |
| 1714 | return os.path.join('https://launchpad.net', "~ubuntu-cloud-archive", |
| 1715 | - '+archive', ("%s-staging" % self._uca_release), |
| 1716 | + '+archive', ("%s-staging" % self.uca_release), |
| 1717 | '+files', filename) |
| 1718 | |
| 1719 | |
| 1720 | -class FakeSPPH(object): |
| 1721 | - """Provide the same interface as |
| 1722 | - ubuntutools.lpapicache.SourcePackagePublishingHistory |
| 1723 | +class DebianSPPH(SourcePackagePublishingHistory): |
| 1724 | + """SPPH with getBinaries() overridden, |
| 1725 | + as LP doesn't have Debian binaries |
| 1726 | """ |
| 1727 | - def __init__(self, name, version, component, distribution): |
| 1728 | - self.name = name |
| 1729 | - self.version = version |
| 1730 | + resource_type = 'source_package_publishing_history' |
| 1731 | + |
| 1732 | + def getBinaries(self, arch=None): |
| 1733 | + if not self._binaries: |
| 1734 | + Logger.normal('Using Snapshot to find binary packages') |
| 1735 | + srcpkg = Snapshot.getSourcePackages(self.getPackageName(), |
| 1736 | + version=self.getVersion(), |
| 1737 | + component=self.getComponent())[0] |
| 1738 | + self._binaries = [b.getBPPH() for b in srcpkg.getBinaryFiles()] |
| 1739 | + return super(DebianSPPH, self).getBinaries(arch) |
| 1740 | + |
| 1741 | + |
| 1742 | +class _WebJSON(object): |
| 1743 | + def __init__(self): |
| 1744 | + # uses default proxies from the environment |
| 1745 | + self.url_opener = build_opener(ProxyHandler()) |
| 1746 | + |
| 1747 | + def getHostUrl(self): |
| 1748 | + raise Exception("Not implemented") |
| 1749 | + |
| 1750 | + def load(self, path=''): |
| 1751 | + url = self.getHostUrl() + path |
| 1752 | + Logger.debug("Loading %s", url) |
| 1753 | + data = self.url_opener.open(url) |
| 1754 | + reader = codecs.getreader('utf-8') |
| 1755 | + return json.load(reader(data)) |
| 1756 | + |
| 1757 | + |
| 1758 | +# DAKweb madison API |
| 1759 | +# https://github.com/Debian/dak/blob/master/dakweb/queries/madison.py |
| 1760 | +class Madison(_WebJSON): |
| 1761 | + urls = { |
| 1762 | + 'debian': 'https://api.ftp-master.debian.org/madison', |
| 1763 | + # This currently will NOT work with ubuntu; it doesn't support f=json |
| 1764 | + 'ubuntu': 'http://people.canonical.com/~ubuntu-archive/madison.cgi', |
| 1765 | + } |
| 1766 | + |
| 1767 | + def __init__(self, distro='debian'): |
| 1768 | + super(Madison, self).__init__() |
| 1769 | + self._distro = distro |
| 1770 | + |
| 1771 | + def getHostUrl(self): |
| 1772 | + return self.urls[self._distro] |
| 1773 | + |
| 1774 | + def getSourcePackage(self, name, series='unstable'): |
| 1775 | + url = "?f=json&package={name}&s={series}".format(name=name, series=series) |
| 1776 | + try: |
| 1777 | + result = self.load(url) |
| 1778 | + except HTTPError: |
| 1779 | + result = None |
| 1780 | + if not result: |
| 1781 | + msg = "Package {} not found in '{}'".format(name, series) |
| 1782 | + raise PackageNotFoundException(msg) |
| 1783 | + versions = result[0][name].values()[0] |
| 1784 | + latest = versions[sorted(versions.keys(), reverse=True)[0]] |
| 1785 | + return Snapshot.getSourcePackages(name=latest['source'], |
| 1786 | + version=latest['source_version'], |
| 1787 | + component=latest['component'])[0] |
| 1788 | + |
| 1789 | + |
| 1790 | +# Snapshot API |
| 1791 | +# https://anonscm.debian.org/cgit/mirror/snapshot.debian.org.git/plain/API |
| 1792 | +class _Snapshot(_WebJSON): |
| 1793 | + def getHostUrl(self): |
| 1794 | + return "http://snapshot.debian.org" |
| 1795 | + |
| 1796 | + def _get_package(self, name, url, pkginit, version=None, series=None): |
| 1797 | + try: |
| 1798 | + results = self.load("/mr/{}/{}/".format(url, name))['result'] |
| 1799 | + except HTTPError: |
| 1800 | + raise PackageNotFoundException("Package {} not found.".format(name)) |
| 1801 | + |
| 1802 | + results = [pkginit(r) for r in results] |
| 1803 | + if version: |
| 1804 | + results = [r for r in results if version == r.version] |
| 1805 | + if series: |
| 1806 | + if not version: |
| 1807 | + Logger.warn("Warning: searching Snapshot pkgs by series is VERY slow!") |
| 1808 | + results = [r for r in results if series == r.series] |
| 1809 | + if not results: |
| 1810 | + msg = "Package {} ".format(name) |
| 1811 | + if version: |
| 1812 | + msg += "version {} ".format(version) |
| 1813 | + if series: |
| 1814 | + msg += "in series {} ".format(series) |
| 1815 | + msg += "not found." |
| 1816 | + raise PackageNotFoundException(msg) |
| 1817 | + return sorted(results, key=lambda r: r.version, reverse=True) |
| 1818 | + |
| 1819 | + def getSourcePackages(self, name, version=None, series=None, component=None): |
| 1820 | + return self._get_package(name, "package", |
| 1821 | + lambda obj: SnapshotSourcePackage(obj, name, component), |
| 1822 | + version=version, series=series) |
| 1823 | + |
| 1824 | + def getBinaryPackages(self, name, version=None, series=None, component=None): |
| 1825 | + return self._get_package(name, "binary", SnapshotBinaryPackage, |
| 1826 | + lambda obj: SnapshotBinaryPackage(obj, component), |
| 1827 | + version=version, series=series) |
| 1828 | + |
| 1829 | + |
| 1830 | +Snapshot = _Snapshot() |
| 1831 | + |
| 1832 | + |
| 1833 | +class SnapshotPackage(object): |
| 1834 | + def __init__(self, obj, component): |
| 1835 | + self._obj = obj |
| 1836 | + self._files = None |
| 1837 | + self._series = None |
| 1838 | self.component = component |
| 1839 | - self.distribution = distribution |
| 1840 | - self._changelog = None |
| 1841 | + |
| 1842 | + @property |
| 1843 | + def version(self): |
| 1844 | + return self._obj['version'] |
| 1845 | + |
| 1846 | + @property |
| 1847 | + def series(self): |
| 1848 | + if not self._series: |
| 1849 | + files = self.getFiles() |
| 1850 | + if not files: |
| 1851 | + Logger.error("Package {} {} has no files." |
| 1852 | + .format(self.name, self.version)) |
| 1853 | + self._series = 'unknown' |
| 1854 | + else: |
| 1855 | + self._series = files[0].archive_name |
| 1856 | + if filter(lambda f: f.archive_name != self._series, files): |
| 1857 | + Logger.debug("Package {} {} has files in multiple series." |
| 1858 | + .format(self.name, self.version)) |
| 1859 | + return self._series |
| 1860 | + |
| 1861 | + |
| 1862 | +class SnapshotSourcePackage(SnapshotPackage): |
| 1863 | + def __init__(self, obj, name, component): |
| 1864 | + # obj required fields: 'version' |
| 1865 | + super(SnapshotSourcePackage, self).__init__(obj, component) |
| 1866 | + self.name = name |
| 1867 | + self._binary_files = None |
| 1868 | + self._spph = None |
| 1869 | + |
| 1870 | + def getSPPH(self): |
| 1871 | + if not self._spph: |
| 1872 | + self._spph = SnapshotSPPH(self) |
| 1873 | + return self._spph |
| 1874 | + |
| 1875 | + def getAllFiles(self): |
| 1876 | + return self.getFiles() + self.getBinaryFiles() |
| 1877 | + |
| 1878 | + def getBinaryFiles(self, arch=None): |
| 1879 | + if not self._binary_files: |
| 1880 | + url = "/mr/package/{}/{}/allfiles".format(self.name, self.version) |
| 1881 | + response = Snapshot.load("{}?fileinfo=1".format(url)) |
| 1882 | + info = response['fileinfo'] |
| 1883 | + files = [SnapshotBinaryFile(b['name'], b['version'], self.component, |
| 1884 | + info[r['hash']][0], r['hash'], |
| 1885 | + r['architecture'], self.name) |
| 1886 | + for b in response['result']['binaries'] for r in b['files']] |
| 1887 | + self._binary_files = files |
| 1888 | + if not arch: |
| 1889 | + return list(self._binary_files) |
| 1890 | + return filter(lambda f: f.isArch(arch), self._binary_files) |
| 1891 | + |
| 1892 | + def getFiles(self): |
| 1893 | + if not self._files: |
| 1894 | + url = "/mr/package/{}/{}/srcfiles".format(self.name, self.version) |
| 1895 | + response = Snapshot.load("{}?fileinfo=1".format(url)) |
| 1896 | + info = response['fileinfo'] |
| 1897 | + self._files = [SnapshotSourceFile(self.name, self.version, self.component, |
| 1898 | + info[r['hash']][0], |
| 1899 | + r['hash']) |
| 1900 | + for r in response['result']] |
| 1901 | + return list(self._files) |
| 1902 | + |
| 1903 | + |
| 1904 | +class SnapshotBinaryPackage(SnapshotPackage): |
| 1905 | + def __init__(self, obj, component): |
| 1906 | + # obj required fields: 'version', 'binary_version', 'name', 'source' |
| 1907 | + super(SnapshotBinaryPackage, self).__init__(obj, component) |
| 1908 | + |
| 1909 | + @property |
| 1910 | + def name(self): |
| 1911 | + return self._obj['name'] |
| 1912 | + |
| 1913 | + @property |
| 1914 | + def binary_version(self): |
| 1915 | + return self._obj['binary_version'] |
| 1916 | + |
| 1917 | + @property |
| 1918 | + def source(self): |
| 1919 | + return self._obj['source'] |
| 1920 | + |
| 1921 | + def getBPPH(self, arch): |
| 1922 | + f = self.getFiles(arch) |
| 1923 | + if not f: |
| 1924 | + return None |
| 1925 | + # Can only be 1 binary file for this pkg name/version/arch |
| 1926 | + return f[0].getBPPH() |
| 1927 | + |
| 1928 | + def getFiles(self, arch=None): |
| 1929 | + if not self._files: |
| 1930 | + url = "/mr/binary/{}/{}/binfiles".format(self.name, self.version) |
| 1931 | + response = Snapshot.load("{}?fileinfo=1".format(url)) |
| 1932 | + info = response['fileinfo'] |
| 1933 | + self._files = [SnapshotBinaryFile(self.name, self.version, self.component, |
| 1934 | + info[r['hash']][0], r['hash'], |
| 1935 | + r['architecture'], self.source) |
| 1936 | + for r in response['result']] |
| 1937 | + if not arch: |
| 1938 | + return list(self._files) |
| 1939 | + return filter(lambda f: f.isArch(arch), self._files) |
| 1940 | + |
| 1941 | + |
| 1942 | +class SnapshotFile(object): |
| 1943 | + def __init__(self, pkg_name, pkg_version, pkg_component, obj, h): |
| 1944 | + self.package_name = pkg_name |
| 1945 | + self.package_version = pkg_version |
| 1946 | + self.package_component = pkg_component |
| 1947 | + self._obj = obj |
| 1948 | + self._hash = h |
| 1949 | + |
| 1950 | + @property |
| 1951 | + def getType(self): |
| 1952 | + return None |
| 1953 | + |
| 1954 | + @property |
| 1955 | + def archive_name(self): |
| 1956 | + return self._obj['archive_name'] |
| 1957 | + |
| 1958 | + @property |
| 1959 | + def name(self): |
| 1960 | + return self._obj['name'] |
| 1961 | + |
| 1962 | + @property |
| 1963 | + def path(self): |
| 1964 | + return self._obj['path'] |
| 1965 | + |
| 1966 | + @property |
| 1967 | + def size(self): |
| 1968 | + return self._obj['size'] |
| 1969 | + |
| 1970 | + @property |
| 1971 | + def date(self): |
| 1972 | + if 'run' in self._obj: |
| 1973 | + return self._obj['run'] |
| 1974 | + elif 'first_seen' in self._obj: |
| 1975 | + return self._obj['first_seen'] |
| 1976 | + else: |
| 1977 | + Logger.error('File {} has no date information', self.name) |
| 1978 | + return 'unknown' |
| 1979 | + |
| 1980 | + def getHash(self): |
| 1981 | + return self._hash |
| 1982 | + |
| 1983 | + def getUrl(self): |
| 1984 | + return "{}/file/{}".format(Snapshot.getHostUrl(), self.getHash()) |
| 1985 | + |
| 1986 | + def __repr__(self): |
| 1987 | + return "{}/{} {} bytes {}".format(self.path, self.name, self.size, self.date) |
| 1988 | + |
| 1989 | + |
| 1990 | +class SnapshotSourceFile(SnapshotFile): |
| 1991 | + def __init__(self, name, version, component, obj, h): |
| 1992 | + super(SnapshotSourceFile, self).__init__(name, version, component, obj, h) |
| 1993 | + |
| 1994 | + def getType(self): |
| 1995 | + return 'source' |
| 1996 | + |
| 1997 | + |
| 1998 | +class SnapshotBinaryFile(SnapshotFile): |
| 1999 | + def __init__(self, name, version, component, obj, h, arch, source): |
| 2000 | + super(SnapshotBinaryFile, self).__init__(name, version, component, obj, h) |
| 2001 | + self.source = source |
| 2002 | + self.arch = arch |
| 2003 | + self._bpph = None |
| 2004 | + |
| 2005 | + def isArch(self, arch): |
| 2006 | + if not arch: |
| 2007 | + return True |
| 2008 | + if self.arch == 'all': |
| 2009 | + return True |
| 2010 | + return arch == self.arch |
| 2011 | + |
| 2012 | + def getType(self): |
| 2013 | + return 'binary' |
| 2014 | + |
| 2015 | + def getBPPH(self): |
| 2016 | + if not self._bpph: |
| 2017 | + self._bpph = SnapshotBPPH(self) |
| 2018 | + return self._bpph |
| 2019 | + |
| 2020 | + |
| 2021 | +class SnapshotSPPH(object): |
| 2022 | + """Provide the same interface as SourcePackagePublishingHistory""" |
| 2023 | + def __init__(self, snapshot_pkg): |
| 2024 | + self._pkg = snapshot_pkg |
| 2025 | + |
| 2026 | + # LP API defined fields |
| 2027 | + |
| 2028 | + @property |
| 2029 | + def component_name(self): |
| 2030 | + return self.getComponent() |
| 2031 | + |
| 2032 | + @property |
| 2033 | + def display_name(self): |
| 2034 | + return ("{name} {version} in {seriesandpocket}" |
| 2035 | + .format(name=self.getPackageName(), |
| 2036 | + version=self.getVersion(), |
| 2037 | + seriesandpocket=self.getSeriesAndPocket())) |
| 2038 | + |
| 2039 | + @property |
| 2040 | + def pocket(self): |
| 2041 | + try: |
| 2042 | + (r, p) = split_release_pocket(self.getSeriesAndPocket(), default=None) |
| 2043 | + except PocketDoesNotExistError: |
| 2044 | + p = None |
| 2045 | + return p if p else 'Release' |
| 2046 | + |
| 2047 | + @property |
| 2048 | + def source_package_name(self): |
| 2049 | + return self.getPackageName() |
| 2050 | + |
| 2051 | + @property |
| 2052 | + def source_package_version(self): |
| 2053 | + return self.getVersion() |
| 2054 | + |
| 2055 | + # SPPH functions |
| 2056 | |
| 2057 | def getPackageName(self): |
| 2058 | - return self.name |
| 2059 | + return self._pkg.name |
| 2060 | |
| 2061 | def getVersion(self): |
| 2062 | - return self.version |
| 2063 | + return self._pkg.version |
| 2064 | |
| 2065 | def getComponent(self): |
| 2066 | - return self.component |
| 2067 | + return self._pkg.component |
| 2068 | + |
| 2069 | + def getSeriesAndPocket(self): |
| 2070 | + return self._pkg.series |
| 2071 | |
| 2072 | def getChangelog(self, since_version=None): |
| 2073 | ''' |
| 2074 | @@ -575,27 +1072,22 @@ class FakeSPPH(object): |
| 2075 | May return None if the changelog isn't available |
| 2076 | ''' |
| 2077 | if self._changelog is None: |
| 2078 | - if self.name.startswith('lib'): |
| 2079 | - subdir = 'lib%s' % self.name[3] |
| 2080 | + name = self.getPackageName() |
| 2081 | + if name.startswith('lib'): |
| 2082 | + subdir = 'lib%s' % name[3] |
| 2083 | else: |
| 2084 | - subdir = self.name[0] |
| 2085 | - # Strip epoch from version |
| 2086 | - pkgversion = self.version.split(':', 1)[-1] |
| 2087 | - extension = '' |
| 2088 | - if self.distribution == 'debian': |
| 2089 | - base = 'http://packages.debian.org/' |
| 2090 | - extension = '.txt' |
| 2091 | - elif self.distribution == 'ubuntu': |
| 2092 | - base = 'http://changelogs.ubuntu.com/' |
| 2093 | + subdir = name[0] |
| 2094 | + pkgversion = Version(self.getVersion()).strip_epoch() |
| 2095 | + base = 'http://packages.debian.org/' |
| 2096 | |
| 2097 | url = os.path.join(base, 'changelogs', 'pool', |
| 2098 | - self.component, subdir, self.name, |
| 2099 | - self.name + '_' + pkgversion, |
| 2100 | - 'changelog' + extension) |
| 2101 | + self.getComponent(), subdir, name, |
| 2102 | + name + '_' + pkgversion, |
| 2103 | + 'changelog.txt') |
| 2104 | try: |
| 2105 | self._changelog = urlopen(url).read() |
| 2106 | except HTTPError as error: |
| 2107 | - print(('%s: %s' % (url, error)), file=sys.stderr) |
| 2108 | + Logger.error('{}: {}'.format(url, error)) |
| 2109 | return None |
| 2110 | |
| 2111 | if since_version is None: |
| 2112 | @@ -611,62 +1103,70 @@ class FakeSPPH(object): |
| 2113 | new_entries.append(unicode(block)) |
| 2114 | return u''.join(new_entries) |
| 2115 | |
| 2116 | + def getBinaries(self, arch=None): |
| 2117 | + return [b.getBPPH() for b in self._pkg.getBinaryFiles(arch)] |
| 2118 | |
| 2119 | -def rmadison(url, package, suite=None, arch=None): |
| 2120 | - "Call rmadison and parse the result" |
| 2121 | - cmd = ['rmadison', '-u', url] |
| 2122 | - if suite: |
| 2123 | - cmd += ['-s', suite] |
| 2124 | - if arch: |
| 2125 | - cmd += ['-a', arch] |
| 2126 | - cmd.append(package) |
| 2127 | - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
| 2128 | - stderr=subprocess.PIPE, close_fds=True) |
| 2129 | - output, error_output = process.communicate() |
| 2130 | - if process.wait() != 0: |
| 2131 | - if error_output: |
| 2132 | - Logger.error('rmadison failed with: %s', error_output) |
| 2133 | - else: |
| 2134 | - Logger.error('rmadison failed') |
| 2135 | - sys.exit(1) |
| 2136 | - |
| 2137 | - # rmadison uses some shorthand |
| 2138 | - if suite: |
| 2139 | - suite = suite.replace('-proposed-updates', '-p-u') |
| 2140 | - |
| 2141 | - # pylint bug: http://www.logilab.org/ticket/46273 |
| 2142 | - # pylint: disable=E1103 |
| 2143 | - for line in output.strip().splitlines(): |
| 2144 | - # pylint: enable=E1103 |
| 2145 | - pkg, ver, dist, archs = [x.strip() for x in line.split('|')] |
| 2146 | - comp = 'main' |
| 2147 | - if '/' in dist: |
| 2148 | - dist, comp = dist.split('/') |
| 2149 | - archs = set(x.strip() for x in archs.split(',')) |
| 2150 | - |
| 2151 | - # rmadison returns some results outside the requested set. |
| 2152 | - # It'll include backports, and when given an unknown suite, |
| 2153 | - # it ignores that argument |
| 2154 | - # |
| 2155 | - # some versions (2.14.1ubuntu0.1) of rmadison return 'sid' when |
| 2156 | - # asked about 'unstable'. Others return 'unstable'. Accept either. |
| 2157 | - if (suite and dist != suite and not |
| 2158 | - (suite == 'sid' and dist == 'unstable')): |
| 2159 | - continue |
| 2160 | - |
| 2161 | - if 'source' in archs: |
| 2162 | - yield { |
| 2163 | - 'source': pkg, |
| 2164 | - 'version': ver, |
| 2165 | - 'suite': dist, |
| 2166 | - 'component': comp, |
| 2167 | - } |
| 2168 | - archs.discard('source') |
| 2169 | - if archs: |
| 2170 | - yield { |
| 2171 | - 'binary': pkg, |
| 2172 | - 'version': ver, |
| 2173 | - 'suite': dist, |
| 2174 | - 'component': comp, |
| 2175 | - 'architectures': archs, |
| 2176 | - } |
| 2177 | + |
| 2178 | +class SnapshotBPPH(object): |
| 2179 | + """Provide the same interface as BinaryPackagePublishingHistory""" |
| 2180 | + def __init__(self, snapshot_binfile): |
| 2181 | + self._file = snapshot_binfile |
| 2182 | + |
| 2183 | + # LP API defined fields |
| 2184 | + @property |
| 2185 | + def architecture_specific(self): |
| 2186 | + return self._file.arch != 'all' |
| 2187 | + |
| 2188 | + @property |
| 2189 | + def binary_package_name(self): |
| 2190 | + return self.getPackageName() |
| 2191 | + |
| 2192 | + @property |
| 2193 | + def binary_package_version(self): |
| 2194 | + return self.getVersion() |
| 2195 | + |
| 2196 | + @property |
| 2197 | + def component_name(self): |
| 2198 | + return self.getComponent() |
| 2199 | + |
| 2200 | + @property |
| 2201 | + def display_name(self): |
| 2202 | + return ("{name} {version} in {seriesandpocket}" |
| 2203 | + .format(name=self.getPackageName(), |
| 2204 | + version=self.getVersion(), |
| 2205 | + seriesandpocket=self._file.archive_name)) |
| 2206 | + |
| 2207 | + @property |
| 2208 | + def pocket(self): |
| 2209 | + try: |
| 2210 | + (r, p) = split_release_pocket(self._file.archive_name, default=None) |
| 2211 | + except PocketDoesNotExistError: |
| 2212 | + p = None |
| 2213 | + return p if p else 'Release' |
| 2214 | + |
| 2215 | + # BPPH functions |
| 2216 | + |
| 2217 | + @property |
| 2218 | + def arch(self): |
| 2219 | + return self._file.arch |
| 2220 | + |
| 2221 | + def getSourcePackageName(self): |
| 2222 | + return self._file.source |
| 2223 | + |
| 2224 | + def getPackageName(self): |
| 2225 | + return self._file.package_name |
| 2226 | + |
| 2227 | + def getVersion(self): |
| 2228 | + return self._file.package_version |
| 2229 | + |
| 2230 | + def getComponent(self): |
| 2231 | + return self._file.component |
| 2232 | + |
| 2233 | + def getBuild(self): |
| 2234 | + return None |
| 2235 | + |
| 2236 | + def getUrl(self): |
| 2237 | + return self._file.getUrl() |
| 2238 | + |
| 2239 | + def getFileName(self): |
| 2240 | + return self._file.name |
| 2241 | diff --git a/ubuntutools/config.py b/ubuntutools/config.py |
| 2242 | index ba029fe..dfa53a3 100644 |
| 2243 | --- a/ubuntutools/config.py |
| 2244 | +++ b/ubuntutools/config.py |
| 2245 | @@ -37,10 +37,12 @@ class UDTConfig(object): |
| 2246 | 'BUILDER': 'pbuilder', |
| 2247 | 'DEBIAN_MIRROR': 'http://deb.debian.org/debian', |
| 2248 | 'DEBSEC_MIRROR': 'http://security.debian.org', |
| 2249 | + 'DEBIAN_DDEBS_MIRROR': 'http://debug.mirrors.debian.org/debian-debug', |
| 2250 | 'LPINSTANCE': 'production', |
| 2251 | 'MIRROR_FALLBACK': True, |
| 2252 | 'UBUNTU_MIRROR': 'http://archive.ubuntu.com/ubuntu', |
| 2253 | 'UBUNTU_PORTS_MIRROR': 'http://ports.ubuntu.com', |
| 2254 | + 'UBUNTU_DDEBS_MIRROR': 'http://ddebs.ubuntu.com', |
| 2255 | 'UPDATE_BUILDER': False, |
| 2256 | 'WORKDIR': None, |
| 2257 | 'KEYID': None, |
| 2258 | diff --git a/ubuntutools/lp/libsupport.py b/ubuntutools/lp/libsupport.py |
| 2259 | index 64f3cf4..73dbd4b 100644 |
| 2260 | --- a/ubuntutools/lp/libsupport.py |
| 2261 | +++ b/ubuntutools/lp/libsupport.py |
| 2262 | @@ -19,6 +19,8 @@ |
| 2263 | # |
| 2264 | |
| 2265 | # Modules. |
| 2266 | +import platform |
| 2267 | + |
| 2268 | try: |
| 2269 | from urllib.parse import urlsplit, urlencode, urlunsplit |
| 2270 | except ImportError: |
| 2271 | @@ -53,3 +55,25 @@ def translate_web_api(url, launchpad): |
| 2272 | query = urlencode(query) |
| 2273 | url = urlunsplit((scheme, netloc, api_path + path.lstrip("/"), query, fragment)) |
| 2274 | return url |
| 2275 | + |
| 2276 | + |
| 2277 | +def get_system_arch(): |
| 2278 | + # get the launchpad-named arch for the current system arch |
| 2279 | + arch = platform.machine() |
| 2280 | + |
| 2281 | + # python's arch strings don't quite match launchpad's |
| 2282 | + # x86 |
| 2283 | + if arch == "x86_64": |
| 2284 | + return "amd64" |
| 2285 | + if arch == "i686": |
| 2286 | + return "i386" |
| 2287 | + |
| 2288 | + # PPC |
| 2289 | + if arch == "ppc64le": |
| 2290 | + return "ppc64el" |
| 2291 | + |
| 2292 | + # ARM |
| 2293 | + if arch == "aarch64": |
| 2294 | + return "arm64" |
| 2295 | + |
| 2296 | + return arch |
| 2297 | diff --git a/ubuntutools/lp/lpapicache.py b/ubuntutools/lp/lpapicache.py |
| 2298 | index 601bea8..ccf5d7e 100644 |
| 2299 | --- a/ubuntutools/lp/lpapicache.py |
| 2300 | +++ b/ubuntutools/lp/lpapicache.py |
| 2301 | @@ -30,13 +30,15 @@ from __future__ import print_function |
| 2302 | import collections |
| 2303 | import sys |
| 2304 | |
| 2305 | -from debian.changelog import Changelog, Version |
| 2306 | +from debian.changelog import Changelog |
| 2307 | from httplib2 import Http, HttpLib2Error |
| 2308 | from launchpadlib.launchpad import Launchpad as LP |
| 2309 | from launchpadlib.errors import HTTPError |
| 2310 | from lazr.restfulclient.resource import Entry |
| 2311 | |
| 2312 | +from ubuntutools.version import Version |
| 2313 | from ubuntutools.lp import (service, api_version) |
| 2314 | +from ubuntutools.lp.libsupport import get_system_arch |
| 2315 | from ubuntutools.lp.udtexceptions import (AlreadyLoggedInError, |
| 2316 | ArchiveNotFoundException, |
| 2317 | ArchSeriesNotFoundException, |
| 2318 | @@ -73,6 +75,7 @@ __all__ = [ |
| 2319 | 'Distribution', |
| 2320 | 'DistributionSourcePackage', |
| 2321 | 'DistroSeries', |
| 2322 | + 'DistroArchSeries', |
| 2323 | 'Launchpad', |
| 2324 | 'PersonTeam', |
| 2325 | 'SourcePackagePublishingHistory', |
| 2326 | @@ -296,6 +299,12 @@ class DistroArchSeries(BaseWrapper): |
| 2327 | ''' |
| 2328 | resource_type = 'distro_arch_series' |
| 2329 | |
| 2330 | + def getSeries(self): |
| 2331 | + ''' |
| 2332 | + Get DistroSeries for this. |
| 2333 | + ''' |
| 2334 | + return DistroSeries(self._lpobject.distroseries_link) |
| 2335 | + |
| 2336 | |
| 2337 | class DistroSeries(BaseWrapper): |
| 2338 | ''' |
| 2339 | @@ -307,12 +316,16 @@ class DistroSeries(BaseWrapper): |
| 2340 | if "_architectures" not in self.__dict__: |
| 2341 | self._architectures = dict() |
| 2342 | |
| 2343 | - def getArchSeries(self, archtag): |
| 2344 | + def getArchSeries(self, archtag=None): |
| 2345 | ''' |
| 2346 | Returns a DistroArchSeries object for an architecture passed by name |
| 2347 | (e.g. 'amd64'). |
| 2348 | + If arch is not specified, get the DistroArchSeries for the system arch. |
| 2349 | + The special archtag 'all' will get the system arch. |
| 2350 | If the architecture is not found: raise ArchSeriesNotFoundException. |
| 2351 | ''' |
| 2352 | + if not archtag or archtag == 'all': |
| 2353 | + archtag = get_system_arch() |
| 2354 | if archtag not in self._architectures: |
| 2355 | try: |
| 2356 | architecture = DistroArchSeries( |
| 2357 | @@ -338,16 +351,22 @@ class Archive(BaseWrapper): |
| 2358 | self._pkgset_uploaders = {} |
| 2359 | self._component_uploaders = {} |
| 2360 | |
| 2361 | - def getSourcePackage(self, name, series=None, pocket=None): |
| 2362 | + def getSourcePackage(self, name, series=None, pocket=None, version=None, |
| 2363 | + status=None): |
| 2364 | ''' |
| 2365 | Returns a SourcePackagePublishingHistory object for the most |
| 2366 | recent source package in the distribution 'dist', series and |
| 2367 | pocket. |
| 2368 | |
| 2369 | series defaults to the current development series if not specified. |
| 2370 | + series must be either a series name string, or DistroSeries object. |
| 2371 | + |
| 2372 | + pocket may be a string or a list. If a list, the highest version |
| 2373 | + will be returned. It defaults to all pockets except backports. |
| 2374 | |
| 2375 | - pocket may be a list, if so, the highest version will be returned. |
| 2376 | - It defaults to all pockets except backports. |
| 2377 | + version may be specified to get only the exact version requested. |
| 2378 | + |
| 2379 | + status is optional, to restrict search to a given status only. |
| 2380 | |
| 2381 | If the requested source package doesn't exist a |
| 2382 | PackageNotFoundException is raised. |
| 2383 | @@ -355,33 +374,48 @@ class Archive(BaseWrapper): |
| 2384 | return self._getPublishedItem(name, series, pocket, cache=self._srcpkgs, |
| 2385 | function='getPublishedSources', |
| 2386 | name_key='source_name', |
| 2387 | - wrapper=SourcePackagePublishingHistory) |
| 2388 | + wrapper=SourcePackagePublishingHistory, |
| 2389 | + version=version, |
| 2390 | + status=status, |
| 2391 | + binary=False) |
| 2392 | |
| 2393 | - def getBinaryPackage(self, name, archtag=None, series=None, pocket=None): |
| 2394 | + def getBinaryPackage(self, name, archtag=None, series=None, pocket=None, |
| 2395 | + version=None, status=None): |
| 2396 | ''' |
| 2397 | Returns a BinaryPackagePublishingHistory object for the most |
| 2398 | recent source package in the distribution 'dist', architecture |
| 2399 | 'archtag', series and pocket. |
| 2400 | |
| 2401 | series defaults to the current development series if not specified. |
| 2402 | + series must be either a series name string, or DistroArchSeries object. |
| 2403 | + |
| 2404 | + pocket may be a string or a list. If a list, the highest version |
| 2405 | + will be returned. It defaults to all pockets except backports. |
| 2406 | |
| 2407 | - pocket may be a list, if so, the highest version will be returned. |
| 2408 | - It defaults to all pockets except backports. |
| 2409 | + version may be specified to get only the exact version requested. |
| 2410 | + |
| 2411 | + status is optional, to restrict search to a given status only. |
| 2412 | |
| 2413 | If the requested binary package doesn't exist a |
| 2414 | PackageNotFoundException is raised. |
| 2415 | ''' |
| 2416 | - if archtag is None: |
| 2417 | - archtag = [] |
| 2418 | return self._getPublishedItem(name, series, pocket, archtag=archtag, |
| 2419 | cache=self._binpkgs, |
| 2420 | function='getPublishedBinaries', |
| 2421 | name_key='binary_name', |
| 2422 | - wrapper=BinaryPackagePublishingHistory) |
| 2423 | + wrapper=BinaryPackagePublishingHistory, |
| 2424 | + version=version, |
| 2425 | + status=status, |
| 2426 | + binary=True) |
| 2427 | |
| 2428 | def _getPublishedItem(self, name, series, pocket, cache, |
| 2429 | - function, name_key, wrapper, archtag=None): |
| 2430 | - '''Common code between getSourcePackage and getBinaryPackage |
| 2431 | + function, name_key, wrapper, archtag=None, |
| 2432 | + version=None, status=None, |
| 2433 | + binary=False): |
| 2434 | + ''' |
| 2435 | + Common code between getSourcePackage and getBinaryPackage. |
| 2436 | + |
| 2437 | + Don't use this directly. |
| 2438 | ''' |
| 2439 | if pocket is None: |
| 2440 | pockets = frozenset(('Proposed', 'Updates', 'Security', 'Release')) |
| 2441 | @@ -396,34 +430,44 @@ class Archive(BaseWrapper): |
| 2442 | pocket) |
| 2443 | |
| 2444 | dist = Distribution(self.distribution_link) |
| 2445 | - # Check if series is already a DistroSeries object or not |
| 2446 | - if not isinstance(series, DistroSeries): |
| 2447 | - if series: |
| 2448 | - series = dist.getSeries(series) |
| 2449 | - else: |
| 2450 | - series = dist.getDevelopmentSeries() |
| 2451 | |
| 2452 | - # getPublishedSources requires a distro_series, while |
| 2453 | - # getPublishedBinaries requires a distro_arch_series. |
| 2454 | - # If archtag is not None, I'll assume it's getPublishedBinaries. |
| 2455 | - if archtag is not None and archtag != []: |
| 2456 | - if not isinstance(archtag, DistroArchSeries): |
| 2457 | + arch_series = None |
| 2458 | + |
| 2459 | + # please don't pass DistroArchSeries as archtag! |
| 2460 | + # but, the code was like that before so keep |
| 2461 | + # backwards compatibility. |
| 2462 | + if isinstance(archtag, DistroArchSeries): |
| 2463 | + series = archtag |
| 2464 | + archtag = None |
| 2465 | + |
| 2466 | + if isinstance(series, DistroArchSeries): |
| 2467 | + arch_series = series |
| 2468 | + series = series.getSeries() |
| 2469 | + elif isinstance(series, DistroSeries): |
| 2470 | + pass |
| 2471 | + elif series: |
| 2472 | + series = dist.getSeries(series) |
| 2473 | + else: |
| 2474 | + series = dist.getDevelopmentSeries() |
| 2475 | + |
| 2476 | + if binary: |
| 2477 | + if arch_series is None: |
| 2478 | arch_series = series.getArchSeries(archtag=archtag) |
| 2479 | - else: |
| 2480 | - arch_series = archtag |
| 2481 | + if archtag is None: |
| 2482 | + archtag = arch_series.architecture_tag |
| 2483 | |
| 2484 | - if archtag is not None and archtag != []: |
| 2485 | - index = (name, series.name, archtag, pockets) |
| 2486 | - else: |
| 2487 | - index = (name, series.name, pockets) |
| 2488 | + index = (name, series.name, archtag, pockets, status, version) |
| 2489 | |
| 2490 | if index not in cache: |
| 2491 | params = { |
| 2492 | name_key: name, |
| 2493 | - 'status': 'Published', |
| 2494 | 'exact_match': True, |
| 2495 | } |
| 2496 | - if archtag is not None and archtag != []: |
| 2497 | + |
| 2498 | + if status: |
| 2499 | + params['status'] = status |
| 2500 | + |
| 2501 | + if binary: |
| 2502 | params['distro_arch_series'] = arch_series() |
| 2503 | else: |
| 2504 | params['distro_series'] = series() |
| 2505 | @@ -431,6 +475,9 @@ class Archive(BaseWrapper): |
| 2506 | if len(pockets) == 1: |
| 2507 | params['pocket'] = list(pockets)[0] |
| 2508 | |
| 2509 | + if version: |
| 2510 | + params['version'] = version |
| 2511 | + |
| 2512 | records = getattr(self, function)(**params) |
| 2513 | |
| 2514 | latest = None |
| 2515 | @@ -450,7 +497,7 @@ class Archive(BaseWrapper): |
| 2516 | package_type = "package" |
| 2517 | msg = ("The %s '%s' does not exist in the %s %s archive" % |
| 2518 | (package_type, name, dist.display_name, self.name)) |
| 2519 | - if archtag is not None and archtag != []: |
| 2520 | + if binary: |
| 2521 | msg += " for architecture %s" % archtag |
| 2522 | pockets = [series.name if pocket == 'Release' |
| 2523 | else '%s-%s' % (series.name, pocket.lower()) |
| 2524 | @@ -532,13 +579,23 @@ class SourcePackagePublishingHistory(BaseWrapper): |
| 2525 | resource_type = 'source_package_publishing_history' |
| 2526 | |
| 2527 | def __init__(self, *args): |
| 2528 | + self._archive = None |
| 2529 | self._changelog = None |
| 2530 | self._binaries = None |
| 2531 | + self._distro_series = None |
| 2532 | # Don't share _builds between different |
| 2533 | # SourcePackagePublishingHistory objects |
| 2534 | if '_builds' not in self.__dict__: |
| 2535 | self._builds = dict() |
| 2536 | |
| 2537 | + def getDistroSeries(self): |
| 2538 | + ''' |
| 2539 | + Return the DistroSeries. |
| 2540 | + ''' |
| 2541 | + if not self._distro_series: |
| 2542 | + self._distro_series = DistroSeries(self._lpobject.distro_series_link) |
| 2543 | + return self._distro_series |
| 2544 | + |
| 2545 | def getPackageName(self): |
| 2546 | ''' |
| 2547 | Returns the source package name. |
| 2548 | @@ -557,16 +614,33 @@ class SourcePackagePublishingHistory(BaseWrapper): |
| 2549 | ''' |
| 2550 | return self._lpobject.component_name |
| 2551 | |
| 2552 | + def getSeriesName(self): |
| 2553 | + ''' |
| 2554 | + Returns the series |
| 2555 | + |
| 2556 | + Named getSeriesName() to avoid confusion with |
| 2557 | + getDistroSeries() |
| 2558 | + ''' |
| 2559 | + return self.getDistroSeries().name |
| 2560 | + |
| 2561 | def getSeriesAndPocket(self): |
| 2562 | ''' |
| 2563 | Returns a human-readable release-pocket |
| 2564 | ''' |
| 2565 | - series = DistroSeries(self._lpobject.distro_series_link) |
| 2566 | - release = series.name |
| 2567 | - if self._lpobject.pocket != 'Release': |
| 2568 | - release += '-' + self._lpobject.pocket.lower() |
| 2569 | + release = self.getSeriesName() |
| 2570 | + if self.pocket != 'Release': |
| 2571 | + release += '-' + self.pocket.lower() |
| 2572 | return release |
| 2573 | |
| 2574 | + def getArchive(self): |
| 2575 | + ''' |
| 2576 | + Get this SPPH's archive. |
| 2577 | + ''' |
| 2578 | + if not self._archive: |
| 2579 | + self._archive = Archive(self._lpobject.archive_link) |
| 2580 | + |
| 2581 | + return self._archive |
| 2582 | + |
| 2583 | def getChangelog(self, since_version=None): |
| 2584 | ''' |
| 2585 | Return the changelog, optionally since a particular version |
| 2586 | @@ -603,15 +677,56 @@ class SourcePackagePublishingHistory(BaseWrapper): |
| 2587 | new_entries.append(unicode(block)) |
| 2588 | return u''.join(new_entries) |
| 2589 | |
| 2590 | - def getBinaries(self): |
| 2591 | + def getBinaries(self, arch=None): |
| 2592 | ''' |
| 2593 | - Returns the resulting BinaryPackagePublishingHistorys |
| 2594 | + Returns the resulting BinaryPackagePublishingHistorys. |
| 2595 | + If arch is specified, only returns BPPH for that arch. |
| 2596 | ''' |
| 2597 | if self._binaries is None: |
| 2598 | - self._binaries = [BinaryPackagePublishingHistory(bpph) |
| 2599 | - for bpph in |
| 2600 | - self._lpobject.getPublishedBinaries()] |
| 2601 | - return self._binaries |
| 2602 | + if self.status != "Pending" and self.status != "Published": |
| 2603 | + # great, this is an older version, so we can't use |
| 2604 | + # SPPH.getPublishedBinaries(). We have to do this the |
| 2605 | + # much more painful way, using SPPH.binaryFileUrls() to |
| 2606 | + # get the filenames and then parsing each one, and calling |
| 2607 | + # getPublishedBinaries() on the archive itself to get the |
| 2608 | + # BPPH url. |
| 2609 | + print("Older package version...this will take a while, sorry") |
| 2610 | + bpphs = [] |
| 2611 | + archive = self.getArchive() |
| 2612 | + # This takes a while to query for each pkg, and it |
| 2613 | + # can be sped up for cases that are just looking for |
| 2614 | + # the binary file urls, by creating a 'lazy' loading |
| 2615 | + # BPPH created from the URL and other data used below |
| 2616 | + urls = self.binaryFileUrls() |
| 2617 | + print("Processing %d binary packages" % len(urls)) |
| 2618 | + for url in urls: |
| 2619 | + # strip out the URL leading text. |
| 2620 | + filename = url.rsplit('/', 1)[1] |
| 2621 | + # strip the file suffix |
| 2622 | + name = filename.rsplit('.', 1)[0] |
| 2623 | + # split into name, version, arch |
| 2624 | + (n, v, a) = name.rsplit('_', 2) |
| 2625 | + # Note we ignore the version, as it may be missing epoch |
| 2626 | + params = {'name': n, |
| 2627 | + 'archtag': a, |
| 2628 | + 'series': self.getSeriesName(), |
| 2629 | + 'pocket': self.pocket, |
| 2630 | + 'version': self.getVersion(), |
| 2631 | + 'status': self.status} |
| 2632 | + bpph = archive.getBinaryPackage(**params) |
| 2633 | + bpphs.append(bpph) |
| 2634 | + print('Lookup: %s ' % filename, end='\r') |
| 2635 | + sys.stdout.flush() |
| 2636 | + print('') |
| 2637 | + else: |
| 2638 | + bpphs = map(BinaryPackagePublishingHistory, |
| 2639 | + self._lpobject.getPublishedBinaries()) |
| 2640 | + self._binaries = bpphs |
| 2641 | + |
| 2642 | + if not arch: |
| 2643 | + return list(self._binaries) |
| 2644 | + |
| 2645 | + return [b for b in self._binaries if b.arch == arch] |
| 2646 | |
| 2647 | def _fetch_builds(self): |
| 2648 | '''Populate self._builds with the build records.''' |
| 2649 | @@ -671,6 +786,22 @@ class BinaryPackagePublishingHistory(BaseWrapper): |
| 2650 | ''' |
| 2651 | resource_type = 'binary_package_publishing_history' |
| 2652 | |
| 2653 | + def __init__(self, *args): |
| 2654 | + self._arch = None |
| 2655 | + |
| 2656 | + @property |
| 2657 | + def arch(self): |
| 2658 | + if not self._arch: |
| 2659 | + das = DistroArchSeries(self._lpobject.distro_arch_series_link) |
| 2660 | + self._arch = das.architecture_tag |
| 2661 | + return self._arch |
| 2662 | + |
| 2663 | + def getSourcePackageName(self): |
| 2664 | + ''' |
| 2665 | + Returns the source package name. |
| 2666 | + ''' |
| 2667 | + return self.getBuild().source_package_name |
| 2668 | + |
| 2669 | def getPackageName(self): |
| 2670 | ''' |
| 2671 | Returns the binary package name. |
| 2672 | @@ -700,6 +831,54 @@ class BinaryPackagePublishingHistory(BaseWrapper): |
| 2673 | raise AttributeError("binaryFileUrls can only be found in lpapi " |
| 2674 | "devel, not 1.0. Login using devel to have it.") |
| 2675 | |
| 2676 | + def getBuild(self): |
| 2677 | + ''' |
| 2678 | + Returns the original build of the binary package. |
| 2679 | + ''' |
| 2680 | + return Build(self._lpobject.build_link) |
| 2681 | + |
| 2682 | + def getUrl(self): |
| 2683 | + ''' |
| 2684 | + Returns the original build URL of the binary package. |
| 2685 | + ''' |
| 2686 | + return "{build}/+files/{filename}".format(build=self.getBuild().getUrl(), |
| 2687 | + filename=self.getFileName()) |
| 2688 | + |
| 2689 | + def getFileVersion(self): |
| 2690 | + ''' |
| 2691 | + Returns the file version, which is the package version without the epoch |
| 2692 | + ''' |
| 2693 | + return Version(self.getVersion()).strip_epoch() |
| 2694 | + |
| 2695 | + def getFileArch(self): |
| 2696 | + ''' |
| 2697 | + Returns the file arch, which is 'all' if not arch-specific |
| 2698 | + ''' |
| 2699 | + if bool(self._lpobject.architecture_specific): |
| 2700 | + return self.arch |
| 2701 | + else: |
| 2702 | + return 'all' |
| 2703 | + |
| 2704 | + def getFileExt(self): |
| 2705 | + ''' |
| 2706 | + Returns the file extension; "deb", "ddeb", or "udeb". |
| 2707 | + ''' |
| 2708 | + if self.getPackageName().endswith("-dbgsym"): |
| 2709 | + return "ddeb" |
| 2710 | + elif self.getPackageName().endswith("-di"): |
| 2711 | + return "udeb" |
| 2712 | + else: |
| 2713 | + return "deb" |
| 2714 | + |
| 2715 | + def getFileName(self): |
| 2716 | + ''' |
| 2717 | + Returns the filename for this binary package. |
| 2718 | + ''' |
| 2719 | + return "{name}_{version}_{arch}.{ext}".format(name=self.getPackageName(), |
| 2720 | + version=self.getFileVersion(), |
| 2721 | + arch=self.getFileArch(), |
| 2722 | + ext=self.getFileExt()) |
| 2723 | + |
| 2724 | |
| 2725 | class MetaPersonTeam(MetaWrapper): |
| 2726 | @property |
| 2727 | @@ -729,6 +908,7 @@ class PersonTeam(BaseWrapper): |
| 2728 | |
| 2729 | def __init__(self, *args): |
| 2730 | # Don't share _upload between different PersonTeams |
| 2731 | + self._ppas = None |
| 2732 | if '_upload' not in self.__dict__: |
| 2733 | self._upload = dict() |
| 2734 | |
| 2735 | @@ -806,6 +986,12 @@ class PersonTeam(BaseWrapper): |
| 2736 | |
| 2737 | return canUpload |
| 2738 | |
| 2739 | + def getPPAs(self): |
| 2740 | + if not self._ppas: |
| 2741 | + ppas = Launchpad.load(self._lpobject.ppas_collection_link).entries |
| 2742 | + self._ppas = [Archive(a['self_link']) for a in ppas] |
| 2743 | + return self._ppas |
| 2744 | + |
| 2745 | |
| 2746 | class Build(BaseWrapper): |
| 2747 | ''' |
| 2748 | @@ -816,6 +1002,16 @@ class Build(BaseWrapper): |
| 2749 | def __str__(self): |
| 2750 | return u'%s: %s' % (self.arch_tag, self.buildstate) |
| 2751 | |
| 2752 | + def getSourcePackagePublishingHistory(self): |
| 2753 | + link = self._lpobject.current_source_publication_link |
| 2754 | + if link: |
| 2755 | + return SourcePackagePublishingHistory(link) |
| 2756 | + else: |
| 2757 | + return None |
| 2758 | + |
| 2759 | + def getUrl(self): |
| 2760 | + return self() |
| 2761 | + |
| 2762 | def rescore(self, score): |
| 2763 | if self.can_be_rescored: |
| 2764 | self().rescore(score=score) |
| 2765 | diff --git a/ubuntutools/pullpkg.py b/ubuntutools/pullpkg.py |
| 2766 | new file mode 100755 |
| 2767 | index 0000000..4b94908 |
| 2768 | --- /dev/null |
| 2769 | +++ b/ubuntutools/pullpkg.py |
| 2770 | @@ -0,0 +1,281 @@ |
| 2771 | +# pullpkg.py -- pull package files for debian/ubuntu/uca |
| 2772 | +# modified from ../pull-lp-source and converted to module |
| 2773 | +# |
| 2774 | +# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>, |
| 2775 | +# 2010-2011, Stefano Rivera <stefanor@ubuntu.com> |
| 2776 | +# 2017, Dan Streetman <dan.streetman@canonical.com> |
| 2777 | +# |
| 2778 | +# ################################################################## |
| 2779 | +# |
| 2780 | +# This program is free software; you can redistribute it and/or |
| 2781 | +# modify it under the terms of the GNU General Public License |
| 2782 | +# as published by the Free Software Foundation; either version 3 |
| 2783 | +# of the License, or (at your option) any later version. |
| 2784 | +# |
| 2785 | +# This program is distributed in the hope that it will be useful, |
| 2786 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 2787 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 2788 | +# GNU General Public License for more details. |
| 2789 | +# |
| 2790 | +# See file /usr/share/common-licenses/GPL for more details. |
| 2791 | +# |
| 2792 | +# ################################################################## |
| 2793 | + |
| 2794 | + |
| 2795 | +import re |
| 2796 | +from argparse import ArgumentParser |
| 2797 | + |
| 2798 | +from distro_info import DebianDistroInfo |
| 2799 | + |
| 2800 | +from ubuntutools.archive import (UbuntuSourcePackage, DebianSourcePackage, |
| 2801 | + UbuntuCloudArchiveSourcePackage) |
| 2802 | +from ubuntutools.config import UDTConfig |
| 2803 | +from ubuntutools.lp.lpapicache import (Distribution, Launchpad) |
| 2804 | +from ubuntutools.lp.udtexceptions import (SeriesNotFoundException, |
| 2805 | + PackageNotFoundException, |
| 2806 | + PocketDoesNotExistError) |
| 2807 | +from ubuntutools.logger import Logger |
| 2808 | +from ubuntutools.misc import split_release_pocket |
| 2809 | +from ubuntutools.lp.libsupport import get_system_arch |
| 2810 | + |
| 2811 | +PULL_SOURCE = 'source' |
| 2812 | +PULL_DEBS = 'debs' |
| 2813 | +PULL_DDEBS = 'ddebs' |
| 2814 | +PULL_UDEBS = 'udebs' |
| 2815 | +PULL_LIST = 'list' |
| 2816 | + |
| 2817 | +VALID_PULLS = [PULL_SOURCE, PULL_DEBS, PULL_DDEBS, PULL_UDEBS, PULL_LIST] |
| 2818 | + |
| 2819 | +DISTRO_DEBIAN = 'debian' |
| 2820 | +DISTRO_UBUNTU = 'ubuntu' |
| 2821 | +DISTRO_UCA = 'uca' |
| 2822 | + |
| 2823 | +DISTRO_PKG_CLASS = { |
| 2824 | + DISTRO_DEBIAN: DebianSourcePackage, |
| 2825 | + DISTRO_UBUNTU: UbuntuSourcePackage, |
| 2826 | + DISTRO_UCA: UbuntuCloudArchiveSourcePackage, |
| 2827 | +} |
| 2828 | +VALID_DISTROS = DISTRO_PKG_CLASS.keys() |
| 2829 | + |
| 2830 | + |
| 2831 | +class InvalidPullValueError(ValueError): |
| 2832 | + """ Thrown when --pull value is invalid """ |
| 2833 | + pass |
| 2834 | + |
| 2835 | + |
| 2836 | +class InvalidDistroValueError(ValueError): |
| 2837 | + """ Thrown when --distro value is invalid """ |
| 2838 | + pass |
| 2839 | + |
| 2840 | + |
| 2841 | +def create_argparser(default_pull=None, default_distro=None, default_arch=None): |
| 2842 | + help_default_pull = "What to pull: " + ", ".join(VALID_PULLS) |
| 2843 | + if default_pull: |
| 2844 | + help_default_pull += (" (default: %s)" % default_pull) |
| 2845 | + help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS) |
| 2846 | + if default_distro: |
| 2847 | + help_default_distro += (" (default: %s)" % default_distro) |
| 2848 | + if not default_arch: |
| 2849 | + default_arch = get_system_arch() |
| 2850 | + help_default_arch = ("Get binary packages for arch (default: %s)" % default_arch) |
| 2851 | + |
| 2852 | + parser = ArgumentParser() |
| 2853 | + parser.add_argument('-v', '--verbose', action='store_true', |
| 2854 | + help="Print verbose/debug messages") |
| 2855 | + parser.add_argument('-d', '--download-only', action='store_true', |
| 2856 | + help="Do not extract the source package") |
| 2857 | + parser.add_argument('-m', '--mirror', action='append', |
| 2858 | + help='Preferred mirror(s)') |
| 2859 | + parser.add_argument('--no-conf', action='store_true', |
| 2860 | + help="Don't read config files or environment variables") |
| 2861 | + parser.add_argument('-a', '--arch', default=default_arch, |
| 2862 | + help=help_default_arch) |
| 2863 | + parser.add_argument('-p', '--pull', default=default_pull, |
| 2864 | + help=help_default_pull) |
| 2865 | + parser.add_argument('-D', '--distro', default=default_distro, |
| 2866 | + help=help_default_distro) |
| 2867 | + parser.add_argument('package', help="Package name to pull") |
| 2868 | + parser.add_argument('release', nargs='?', help="Release to pull from") |
| 2869 | + parser.add_argument('version', nargs='?', help="Package version to pull") |
| 2870 | + return parser |
| 2871 | + |
| 2872 | + |
| 2873 | +def parse_pull(pull): |
| 2874 | + if not pull: |
| 2875 | + raise InvalidPullValueError("Must specify --pull") |
| 2876 | + |
| 2877 | + # allow 'dbgsym' as alias for 'ddebs' |
| 2878 | + if pull == 'dbgsym': |
| 2879 | + Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull) |
| 2880 | + pull = PULL_DDEBS |
| 2881 | + # assume anything starting with 'bin' means 'debs' |
| 2882 | + if str(pull).startswith('bin'): |
| 2883 | + Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull) |
| 2884 | + pull = PULL_DEBS |
| 2885 | + # verify pull action is valid |
| 2886 | + if pull not in VALID_PULLS: |
| 2887 | + raise InvalidPullValueError("Invalid pull action '%s'", pull) |
| 2888 | + |
| 2889 | + return pull |
| 2890 | + |
| 2891 | + |
| 2892 | +def parse_distro(distro): |
| 2893 | + if not distro: |
| 2894 | + raise InvalidDistroValueError("Must specify --distro") |
| 2895 | + |
| 2896 | + distro = distro.lower() |
| 2897 | + |
| 2898 | + # allow 'lp' for 'ubuntu' |
| 2899 | + if distro == 'lp': |
| 2900 | + Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro) |
| 2901 | + distro = DISTRO_UBUNTU |
| 2902 | + # assume anything with 'cloud' is UCA |
| 2903 | + if re.match(r'.*cloud.*', distro): |
| 2904 | + Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro) |
| 2905 | + distro = DISTRO_UCA |
| 2906 | + # verify distro is valid |
| 2907 | + if distro not in VALID_DISTROS: |
| 2908 | + raise InvalidDistroValueError("Invalid distro '%s'", distro) |
| 2909 | + |
| 2910 | + return distro |
| 2911 | + |
| 2912 | + |
| 2913 | +def parse_release(release, distro): |
| 2914 | + if distro == DISTRO_UCA: |
| 2915 | + # UCA is special; it is specified UBUNTURELEASE-UCARELEASE or just |
| 2916 | + # UCARELEASE. The user could also specify UCARELEASE-POCKET. But UCA |
| 2917 | + # archives always correspond to only one UBUNTURELEASE, and UCA archives |
| 2918 | + # have only the Release pocket, so only UCARELEASE matters to us. |
| 2919 | + for r in release.split('-'): |
| 2920 | + if r in UbuntuCloudArchiveSourcePackage.getReleaseNames(): |
| 2921 | + Logger.debug("Using UCA release '%s'", r) |
| 2922 | + return (r, None) |
| 2923 | + raise SeriesNotFoundException('UCA release {} not found.'.format(release)) |
| 2924 | + |
| 2925 | + # Check if release[-pocket] is specified |
| 2926 | + (release, pocket) = split_release_pocket(release, default=None) |
| 2927 | + Logger.debug("Parsed release '%s' pocket '%s'", release, pocket) |
| 2928 | + |
| 2929 | + if distro == DISTRO_DEBIAN: |
| 2930 | + # This converts from the aliases like 'unstable' |
| 2931 | + debian_info = DebianDistroInfo() |
| 2932 | + codename = debian_info.codename(release) |
| 2933 | + if codename: |
| 2934 | + Logger.normal("Using release '%s' for '%s'", codename, release) |
| 2935 | + release = codename |
| 2936 | + |
| 2937 | + try: |
| 2938 | + d = Distribution(distro) |
| 2939 | + Logger.debug("Distro '%s' is valid", distro) |
| 2940 | + except: |
| 2941 | + Logger.debug("Distro '%s' not valid", distro) |
| 2942 | + raise SeriesNotFoundException("Distro {} not found".format(distro)) |
| 2943 | + |
| 2944 | + # let SeriesNotFoundException flow up |
| 2945 | + d.getSeries(release) |
| 2946 | + |
| 2947 | + Logger.debug("Using distro '%s' release '%s' pocket '%s'", |
| 2948 | + distro, release, pocket) |
| 2949 | + return (release, pocket) |
| 2950 | + |
| 2951 | + |
| 2952 | +def pull(options): |
| 2953 | + # required options asserted below |
| 2954 | + # 'release' and 'version' are optional strings |
| 2955 | + # 'mirror' is optional list of strings |
| 2956 | + assert hasattr(options, 'verbose') # bool |
| 2957 | + assert hasattr(options, 'download_only') # bool |
| 2958 | + assert hasattr(options, 'no_conf') # bool |
| 2959 | + assert hasattr(options, 'arch') # string |
| 2960 | + assert hasattr(options, 'pull') # string |
| 2961 | + assert hasattr(options, 'distro') # string |
| 2962 | + assert hasattr(options, 'package') # string |
| 2963 | + |
| 2964 | + Logger.set_verbosity(options.verbose) |
| 2965 | + |
| 2966 | + Logger.debug("pullpkg options: %s", options) |
| 2967 | + |
| 2968 | + pull = parse_pull(options.pull) |
| 2969 | + |
| 2970 | + distro = parse_distro(options.distro) |
| 2971 | + |
| 2972 | + config = UDTConfig(options.no_conf) |
| 2973 | + |
| 2974 | + mirrors = [] |
| 2975 | + if hasattr(options, 'mirror') and options.mirror: |
| 2976 | + mirrors += options.mirror |
| 2977 | + if pull == PULL_DDEBS: |
| 2978 | + ddebs_mirror = config.get_value(distro.upper() + '_DDEBS_MIRROR') |
| 2979 | + if ddebs_mirror: |
| 2980 | + mirrors.append(ddebs_mirror) |
| 2981 | + if mirrors: |
| 2982 | + Logger.debug("using mirrors %s", ", ".join(mirrors)) |
| 2983 | + |
| 2984 | + # Login anonymously to LP |
| 2985 | + Launchpad.login_anonymously() |
| 2986 | + |
| 2987 | + package = options.package |
| 2988 | + release = getattr(options, 'release', None) |
| 2989 | + version = getattr(options, 'version', None) |
| 2990 | + pocket = None |
| 2991 | + |
| 2992 | + if release: |
| 2993 | + try: |
| 2994 | + (release, pocket) = parse_release(release, distro) |
| 2995 | + except (SeriesNotFoundException, PocketDoesNotExistError): |
| 2996 | + Logger.debug("Param '%s' not valid series, must be version", release) |
| 2997 | + release, version = version, release |
| 2998 | + if release: |
| 2999 | + try: |
| 3000 | + (release, pocket) = parse_release(release, distro) |
| 3001 | + except (SeriesNotFoundException, PocketDoesNotExistError): |
| 3002 | + Logger.error("Can't find series for '%s' or '%s'", |
| 3003 | + release, version) |
| 3004 | + raise |
| 3005 | + |
| 3006 | + try: |
| 3007 | + pkgcls = DISTRO_PKG_CLASS[distro] |
| 3008 | + srcpkg = pkgcls(package=package, version=version, |
| 3009 | + series=release, pocket=pocket, |
| 3010 | + mirrors=mirrors) |
| 3011 | + spph = srcpkg.lp_spph |
| 3012 | + except PackageNotFoundException as e: |
| 3013 | + Logger.error(str(e)) |
| 3014 | + raise |
| 3015 | + |
| 3016 | + Logger.normal('Found %s-%s', spph.display_name, spph.pocket) |
| 3017 | + |
| 3018 | + if pull == PULL_LIST: |
| 3019 | + Logger.normal("Source files:") |
| 3020 | + for f in srcpkg.dsc['Files']: |
| 3021 | + Logger.normal(" %s", f['name']) |
| 3022 | + Logger.normal("Binary files:") |
| 3023 | + for f in spph.getBinaries(options.arch): |
| 3024 | + Logger.normal(" %s", f.getFileName()) |
| 3025 | + return |
| 3026 | + |
| 3027 | + # allow DownloadError to flow up to caller |
| 3028 | + if pull == PULL_SOURCE: |
| 3029 | + srcpkg.pull() |
| 3030 | + if options.download_only: |
| 3031 | + Logger.debug("--download-only specified, not extracting") |
| 3032 | + else: |
| 3033 | + srcpkg.unpack() |
| 3034 | + else: |
| 3035 | + name = '.*' |
| 3036 | + if package != spph.getPackageName(): |
| 3037 | + Logger.normal("Pulling binary package '%s'", package) |
| 3038 | + Logger.normal("Use package name '%s' to pull all binary packages", |
| 3039 | + spph.getPackageName()) |
| 3040 | + name = package |
| 3041 | + if pull == PULL_DEBS: |
| 3042 | + name = r'{}(?<!-di)(?<!-dbgsym)$'.format(name) |
| 3043 | + elif pull == PULL_DDEBS: |
| 3044 | + name += '-dbgsym$' |
| 3045 | + elif pull == PULL_UDEBS: |
| 3046 | + name += '-di$' |
| 3047 | + else: |
| 3048 | + raise InvalidPullValueError("Invalid pull value %s", pull) |
| 3049 | + total = srcpkg.pull_binaries(name=name, arch=options.arch) |
| 3050 | + if total < 1: |
| 3051 | + Logger.error("No %s found for %s", pull, spph.display_name) |
| 3052 | diff --git a/ubuntutools/requestsync/mail.py b/ubuntutools/requestsync/mail.py |
| 3053 | index e3388f2..a79c9c3 100644 |
| 3054 | --- a/ubuntutools/requestsync/mail.py |
| 3055 | +++ b/ubuntutools/requestsync/mail.py |
| 3056 | @@ -29,10 +29,10 @@ import smtplib |
| 3057 | import socket |
| 3058 | import tempfile |
| 3059 | |
| 3060 | -from debian.changelog import Changelog, Version |
| 3061 | +from debian.changelog import Changelog |
| 3062 | from distro_info import DebianDistroInfo, DistroDataOutdated |
| 3063 | |
| 3064 | -from ubuntutools.archive import rmadison, FakeSPPH |
| 3065 | +from ubuntutools.archive import DebianSourcePackage, UbuntuSourcePackage |
| 3066 | from ubuntutools.lp.udtexceptions import PackageNotFoundException |
| 3067 | from ubuntutools.logger import Logger |
| 3068 | from ubuntutools.question import confirmation_prompt, YesNoQuestion |
| 3069 | @@ -53,32 +53,21 @@ __all__ = [ |
| 3070 | ] |
| 3071 | |
| 3072 | |
| 3073 | -def _get_srcpkg(distro, name, release): |
| 3074 | - if distro == 'debian': |
| 3075 | - # Canonicalise release: |
| 3076 | - debian_info = DebianDistroInfo() |
| 3077 | - try: |
| 3078 | - codename = debian_info.codename(release, default=release) |
| 3079 | - except DistroDataOutdated as e: |
| 3080 | - Logger.warn(e) |
| 3081 | - |
| 3082 | - lines = list(rmadison(distro, name, suite=codename, arch='source')) |
| 3083 | - if not lines: |
| 3084 | - lines = list(rmadison(distro, name, suite=release, arch='source')) |
| 3085 | - if not lines: |
| 3086 | - raise PackageNotFoundException("'%s' doesn't appear to exist in %s '%s'" % |
| 3087 | - (name, distro.capitalize(), release)) |
| 3088 | - pkg = max(lines, key=lambda x: Version(x['version'])) |
| 3089 | - |
| 3090 | - return FakeSPPH(pkg['source'], pkg['version'], pkg['component'], distro) |
| 3091 | - |
| 3092 | - |
| 3093 | def get_debian_srcpkg(name, release): |
| 3094 | - return _get_srcpkg('debian', name, release) |
| 3095 | + # Canonicalise release: |
| 3096 | + debian_info = DebianDistroInfo() |
| 3097 | + try: |
| 3098 | + codename = debian_info.codename(release, default=release) |
| 3099 | + return DebianSourcePackage(package=name, series=codename).lp_spph |
| 3100 | + except DistroDataOutdated as e: |
| 3101 | + Logger.warn(e) |
| 3102 | + except PackageNotFoundException: |
| 3103 | + pass |
| 3104 | + return DebianSourcePackage(package=name, series=release).lp_spph |
| 3105 | |
| 3106 | |
| 3107 | def get_ubuntu_srcpkg(name, release): |
| 3108 | - return _get_srcpkg('ubuntu', name, release) |
| 3109 | + return UbuntuSourcePackage(package=name, series=release).lp_spph |
| 3110 | |
| 3111 | |
| 3112 | def need_sponsorship(name, component, release): |
| 3113 | diff --git a/ubuntutools/test/test_archive.py b/ubuntutools/test/test_archive.py |
| 3114 | index 38c1a63..43222c9 100644 |
| 3115 | --- a/ubuntutools/test/test_archive.py |
| 3116 | +++ b/ubuntutools/test/test_archive.py |
| 3117 | @@ -97,7 +97,6 @@ class LocalSourcePackageTestCase(unittest.TestCase): |
| 3118 | self.workdir = tempfile.mkdtemp(prefix='udt-test') |
| 3119 | |
| 3120 | self._stubout('ubuntutools.archive.Distribution') |
| 3121 | - self._stubout('ubuntutools.archive.rmadison') |
| 3122 | |
| 3123 | self.mock_http = self._stubout('httplib2.Http.request') |
| 3124 | self.mock_http.side_effect = self.request_proxy |
| 3125 | @@ -246,8 +245,6 @@ class DebianLocalSourcePackageTestCase(LocalSourcePackageTestCase): |
| 3126 | sequence = [self.urlopen_null, |
| 3127 | self.urlopen_404, |
| 3128 | self.urlopen_404, |
| 3129 | - self.urlopen_404, |
| 3130 | - self.urlopen_404, |
| 3131 | lambda x: BytesIO( |
| 3132 | b'{"fileinfo": {"hashabc": [{"name": "example_1.0.orig.tar.gz"}]}}'), |
| 3133 | self.urlopen_file('example_1.0.orig.tar.gz'), |
