This file is indexed.

/usr/bin/lp-recipe-status is in lptools 0.2.0-2.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#! /usr/bin/python
# vi: expandtab:sts=4

# Copyright (C) 2011 Jelmer Vernooij <jelmer@samba.org>
#
# ##################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# See file /usr/share/common-licenses/GPL-3 for more details.
#
# ##################################################################
#

"""Show the status of the recipes owned by a particular user.
"""

from cStringIO import StringIO
import gzip
import optparse
import os
import re
import sys
import urllib

from lptools import config

try:
    import tdb
except ImportError:
    cache = {}
else:
    cache = tdb.Tdb("recipe-status-cache.tdb", 1000, tdb.DEFAULT,
        os.O_RDWR|os.O_CREAT)

def gather_per_distroseries_source_builds(recipe):
    last_per_distroseries = {}
    for build in recipe.completed_builds:
        if build.distro_series.self_link not in recipe.distroseries:
            # Skip distro series that are no longer being build
            continue
        distro_series_name = build.distro_series.name
        previous_build = last_per_distroseries.get(distro_series_name)
        if previous_build is None or previous_build.datecreated < build.datecreated:
            last_per_distroseries[distro_series_name] = build
    return last_per_distroseries


def build_failure_link(build):
    if build.buildstate == "Failed to upload":
        return build.upload_log_url
    elif build.buildstate in ("Failed to build", "Dependency wait", "Chroot problem"):
        return build.build_log_url
    else:
        return None

version_matcher = re.compile("^dpkg-buildpackage: source version (.*)$")
source_name_matcher = re.compile("^dpkg-buildpackage: source package (.*)$")


def source_build_find_version(source_build):
    cached_version = cache.get("version/%s" % str(source_build.self_link))
    if cached_version:
        return tuple(cached_version.split(" "))
    # FIXME: Find a more efficient way to retrieve the package/version that was built
    build_log_gz = urllib.urlopen(source_build.build_log_url)
    build_log = gzip.GzipFile(fileobj=StringIO(build_log_gz.read()))
    version = None
    source_name = None
    for l in build_log.readlines():
        m = version_matcher.match(l)
        if m:
            version = m.group(1)
        m = source_name_matcher.match(l)
        if m:
            source_name = m.group(1)
    if not source_name:
        raise Exception("unable to find source name in %s" %
            source_build.build_log_url)
    if not version:
        raise Exception("unable to find version in %s" %
            source_build.build_log_url)
    cache["version/%s" % str(source_build.self_link)] = "%s %s" % (
        source_name, version)
    return (source_name, version)


def find_binary_builds(recipe, source_builds):
    """Gather binary builds for a set of source builds.

    :param recipe: Recipe to build
    :param source_builds: Source builds to analyse
    :return: Dictionary mapping series name to binary builds
    """
    ret = {}
    for source_build in source_builds:
        archive = source_build.archive
        (source_name, version) = source_build_find_version(source_build)
        sources = archive.getPublishedSources(
            distro_series=source_build.distro_series,
            exact_match=True, pocket="Release", source_name=source_name,
            version=version)
        assert len(sources) == 1
        source = sources[0]
        ret[source_build.distro_series.name] = source.getBuilds()
    return ret


def build_failure_summary(build):
    # FIXME: Perhaps parse the relevant logs and extract a summary line?
    return build.buildstate


def build_class(build):
    """Determine the CSS class for a build status.

    :param build: Launchpad build object
    :return: CSS class name
    """
    return {
        "Failed to build": "failed-to-build",
        "Failed to upload": "failed-to-upload",
        "Dependency wait": "dependency-wait",
        "Chroot problem": "chroot-problem",
        "Uploading build": "uploading-build",
        "Currently building": "currently-building",
        "Build for superseded Source": "superseded-source",
        "Successfully built": "successfully-built",
        "Needs building": "needs-building",
        }[build.buildstate]


def filter_source_builds(builds):
    """Filter out successful and failed builds.

    :param builds: List of builds
    :return: Tuple with set of successful and set of failed builds
    """
    sp_success = set()
    sp_failures = set()
    for build in builds:
        if build.buildstate == "Successfully built":
            sp_success.add(build)
        else:
            sp_failures.add(build)
    return (sp_success, sp_failures)


def recipe_status_html(launchpad, person, recipes, outf):
    """Render a recipe status table in HTML.

    :param launchpad: launchpadlib Launchpad object
    :param person: Person owning all the recipes
    :param recipes: List of recipes to render
    :param outf: File-like object to write HTML to
    """
    from chameleon.zpt.loader import TemplateLoader
    tl = TemplateLoader(os.path.join(config.data_dir(), "templates"))
    relevant_distroseries = set()
    source_builds = {}
    binary_builds = {}
    all_binary_builds_ok = {}
    for recipe in recipes:
        sys.stderr.write("Processing recipe %s\n" % recipe.name)
        last_per_distroseries = gather_per_distroseries_source_builds(recipe)
        source_builds[recipe.name] = last_per_distroseries
        relevant_distroseries.update(set(last_per_distroseries))
        (sp_success, sp_failures) = filter_source_builds(last_per_distroseries.values())
        binary_builds[recipe.name] = find_binary_builds(recipe, sp_success)
        all_binary_builds_ok[recipe.name] = {}
        for distroseries, recipe_binary_builds in binary_builds[recipe.name].iteritems():
            all_binary_builds_ok[recipe.name][distroseries] = all(
                [bb.buildstate == "Successfully built" for bb in recipe_binary_builds])
    relevant_distroseries = list(relevant_distroseries)
    relevant_distroseries.sort()
    page = tl.load("recipe-status.html")
    outf.write(page.render(person=person,
        relevant_distroseries=relevant_distroseries,
        recipes=person.recipes, source_builds=source_builds,
        build_failure_summary=build_failure_summary,
        build_failure_link=build_failure_link,
        binary_builds=binary_builds,
        ubuntu=launchpad.distributions["ubuntu"],
        build_class=build_class,
        all_binary_builds_ok=all_binary_builds_ok))


def recipe_status_text(recipes, outf):
    """Display a recipe status table in plain text.

    :param recipes: List of recipes to display status of
    :param outf: file-like object to write to
    """
    for recipe in recipes:
        last_per_distroseries = gather_per_distroseries_source_builds(recipe)
        (sp_success, sp_failures) = filter_source_builds(
            last_per_distroseries.values())
        sp_success_distroseries = [build.distro_series.name for build in sp_success]
        if sp_failures:
            outf.write("%s source build failures (%s successful):\n" % (
                recipe.name, ", ".join(sp_success_distroseries)))
            for failed_build in sp_failures:
                url = build_failure_link(failed_build)
                outf.write("  %s(%s)" % (
                    failed_build.distro_series.name, failed_build.buildstate))
                if url:
                    outf.write(": %s" % url)
                outf.write("\n")
        elif sp_success:
            outf.write("%s source built successfully on %s\n" % (
                recipe.name, ", ".join(sp_success_distroseries)))
        else:
            outf.write("%s never built\n" % recipe.name)
        binary_builds = find_binary_builds(recipe, sp_success)
        for source_build in sp_success:
            for binary_build in binary_builds.get(source_build, []):
                if binary_build.buildstate != "Successfully built":
                    url = build_failure_link(binary_build)
                    outf.write("  %s,%s(%s)" %
                            (binary_build.distro_series.name,
                                binary_build.arch_tag,
                                binary_build.buildstate))
                    if url:
                        outf.write(": %s" % url)
                    outf.write("\n")


def main(argv):
    parser = optparse.OptionParser('%prog [options] PERSON\n\n'
        '  PERSON is the launchpad person or team whose recipes to check')
    parser.add_option("--html", help="Generate HTML", action="store_true")
    opts, args = parser.parse_args()
    if len(args) != 1:
        parser.print_usage()
        return 1
    person = args[0]
    launchpad = config.get_launchpad("recipe-status")
    person = launchpad.people[person]

    if opts.html:
        recipe_status_html(launchpad, person, person.recipes, sys.stdout)
    else:
        recipe_status_text(person.recipes, sys.stdout)

if __name__ == '__main__':
    sys.exit(main(sys.argv))