~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/archivepublisher/scripts/generate_extra_overrides.py

  • Committer: Colin Watson
  • Date: 2011-12-04 15:14:23 UTC
  • mto: This revision was merged to the branch mainline in revision 14516.
  • Revision ID: cjwatson@canonical.com-20111204151423-70399ppglqhi5sho
Reimplement most of cron.germinate in Python using the new python-germinate
package.  This no longer needs to recompute the dependency expansion of
common seeds over and over again, so is much faster.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2011 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Generate extra overrides using Germinate."""
 
5
 
 
6
__metaclass__ = type
 
7
__all__ = [
 
8
    'GenerateExtraOverrides',
 
9
    ]
 
10
 
 
11
import os
 
12
import logging
 
13
 
 
14
from germinate.germinator import Germinator
 
15
from germinate.archive import TagFile
 
16
from germinate.log import GerminateFormatter
 
17
from germinate.seeds import SeedStructure
 
18
 
 
19
from zope.component import getUtility
 
20
 
 
21
from lp.app.errors import NotFoundError
 
22
from lp.archivepublisher.config import getPubConfig
 
23
from lp.registry.interfaces.distribution import IDistributionSet
 
24
from lp.registry.interfaces.series import SeriesStatus
 
25
from lp.services.scripts.base import (
 
26
    LaunchpadScript,
 
27
    LaunchpadScriptFailure,
 
28
    )
 
29
from lp.soyuz.enums import ArchivePurpose
 
30
 
 
31
 
 
32
class AtomicFile:
 
33
    """Facilitate atomic writing of files."""
 
34
 
 
35
    def __init__(self, filename):
 
36
        self.filename = filename
 
37
        self.fd = open('%s.new' % self.filename, 'w')
 
38
 
 
39
    def __enter__(self):
 
40
        return self.fd
 
41
 
 
42
    def __exit__(self, exc_type, exc_value, exc_tb):
 
43
        self.fd.close()
 
44
        os.rename('%s.new' % self.filename, self.filename)
 
45
 
 
46
 
 
47
class GenerateExtraOverrides(LaunchpadScript):
 
48
    """Main class for scripts/ftpmaster-tools/generate-task-overrides.py."""
 
49
 
 
50
    def __init__(self):
 
51
        self.seeds = {}
 
52
        self.seed_structures = {}
 
53
 
 
54
    def add_my_options(self):
 
55
        """Add a 'distribution' context option."""
 
56
        self.parser.add_option(
 
57
            '-d', '--distribution', dest='distribution_name',
 
58
            default='ubuntu', help='Context distribution name.')
 
59
 
 
60
    def processOptions(self):
 
61
        try:
 
62
            self.distribution = getUtility(
 
63
                IDistributionSet)[self.options.distribution_name]
 
64
        except NotFoundError, err:
 
65
            raise LaunchpadScriptFailure(
 
66
                "Could not find distribution %s" % err)
 
67
 
 
68
        series = None
 
69
        wanted_status = (SeriesStatus.DEVELOPMENT,
 
70
                         SeriesStatus.FROZEN)
 
71
        for status in wanted_status:
 
72
            series = self.distribution.getSeriesByStatus(status)
 
73
            if series.count() > 0:
 
74
                break
 
75
        else:
 
76
            raise LaunchpadScriptFailure(
 
77
                'There is no DEVELOPMENT distroseries for %s' %
 
78
                self.options.distribution_name)
 
79
        self.series = series[0]
 
80
 
 
81
        self.architectures = self.series.architectures
 
82
 
 
83
        # Even if DistroSeries.component_names starts including partner, we
 
84
        # don't want it; this applies to the primary archive only.
 
85
        self.components = [component
 
86
                           for component in self.series.component_names
 
87
                           if component != 'partner']
 
88
 
 
89
    def getConfig(self):
 
90
        """Set up a configuration object for this archive."""
 
91
        for archive in self.distribution.all_distro_archives:
 
92
            # We only work on the primary archive.
 
93
            if archive.purpose == ArchivePurpose.PRIMARY:
 
94
                return getPubConfig(archive)
 
95
        else:
 
96
            raise LaunchpadScriptFailure(
 
97
                'There is no PRIMARY archive for %s' %
 
98
                self.options.distribution_name)
 
99
 
 
100
    def setUp(self):
 
101
        """Process options, and set up internal state."""
 
102
        self.processOptions()
 
103
        self.config = self.getConfig()
 
104
 
 
105
        self.germinate_logger = logging.getLogger('germinate')
 
106
        self.germinate_logger.setLevel(logging.INFO)
 
107
        log_file = os.path.join(self.config.germinateroot, 'germinate.output')
 
108
        handler = logging.FileHandler(log_file, mode='w')
 
109
        handler.setFormatter(GerminateFormatter())
 
110
        self.germinate_logger.addHandler(handler)
 
111
        self.germinate_logger.propagate = False
 
112
 
 
113
    def outputPath(self, flavour, arch, base):
 
114
        return os.path.join(
 
115
            self.config.germinateroot,
 
116
            '%s_%s_%s_%s' % (base, flavour, self.series.name, arch))
 
117
 
 
118
    def runGerminate(self, override_file, arch, flavours):
 
119
        germinator = Germinator(arch)
 
120
 
 
121
        # Read archive metadata.
 
122
        archive = TagFile(
 
123
            self.series.name, self.components, arch,
 
124
            'file:/%s' % self.config.archiveroot, cleanup=True)
 
125
        germinator.parse_archive(archive)
 
126
 
 
127
        for flavour in flavours:
 
128
            self.logger.info('Germinating for %s/%s/%s',
 
129
                             flavour, self.series.name, arch)
 
130
            # Add this to the germinate log as well so that that can be
 
131
            # debugged more easily.  Log a separator line first.
 
132
            self.germinate_logger.info('', extra={'progress': True})
 
133
            self.germinate_logger.info('Germinating for %s/%s/%s',
 
134
                                       flavour, self.series.name, arch,
 
135
                                       extra={'progress': True})
 
136
 
 
137
            # Expand dependencies.
 
138
            structure = self.seed_structures[flavour]
 
139
            germinator.plant_seeds(structure)
 
140
            germinator.grow(structure)
 
141
            germinator.add_extras(structure)
 
142
 
 
143
            # Write output files.
 
144
 
 
145
            # The structure file makes it possible to figure out how the
 
146
            # other output files relate to each other.
 
147
            structure.write(self.outputPath(flavour, arch, 'structure'))
 
148
 
 
149
            # "all" and "all.sources" list the full set of binary and source
 
150
            # packages respectively for a given flavour/suite/architecture
 
151
            # combination.
 
152
            all_path = self.outputPath(flavour, arch, 'all')
 
153
            all_sources_path = self.outputPath(flavour, arch, 'all.sources')
 
154
            germinator.write_all_list(structure, all_path)
 
155
            germinator.write_all_source_list(structure, all_sources_path)
 
156
 
 
157
            # Write the dependency-expanded output for each seed.  Several
 
158
            # of these are used by archive administration tools, and others
 
159
            # are useful for debugging, so it's best to just write them all.
 
160
            for seedname in structure.names:
 
161
                germinator.write_full_list(
 
162
                    structure, self.outputPath(flavour, arch, seedname),
 
163
                    seedname)
 
164
 
 
165
            def writeOverrides(seedname, key, value):
 
166
                packages = germinator.get_full(structure, seedname)
 
167
                for package in sorted(packages):
 
168
                    print >>override_file, '%s/%s  %s  %s' % (
 
169
                        package, arch, key, value)
 
170
 
 
171
            # Generate apt-ftparchive "extra overrides" for Task fields.
 
172
            for seedname in structure.names:
 
173
                if seedname == 'extra':
 
174
                    continue
 
175
 
 
176
                task_headers = {}
 
177
                with structure[seedname] as seedtext:
 
178
                    for line in seedtext:
 
179
                        if line.lower().startswith('task-') and ':' in line:
 
180
                            key, value = line.split(':', 1)
 
181
                            key = key[5:].lower() # e.g. "Task-Name" => "name"
 
182
                            task_headers[key] = value.strip()
 
183
                if not task_headers:
 
184
                    continue
 
185
 
 
186
                # Work out the name of the Task to be generated from this
 
187
                # seed.  If there is a Task-Name header, it wins; otherwise,
 
188
                # seeds with a Task-Per-Derivative header are honoured for
 
189
                # all flavours and put in an appropriate namespace, while
 
190
                # other seeds are only honoured for the first flavour and
 
191
                # have archive-global names.
 
192
                if 'name' in task_headers:
 
193
                    task = task_headers['name']
 
194
                elif 'per-derivative' in task_headers:
 
195
                    task = '%s-%s' % (flavour, seedname)
 
196
                elif flavour == flavours[0]:
 
197
                    task = seedname
 
198
                else:
 
199
                    continue
 
200
 
 
201
                # The list of packages in this task come from this seed plus
 
202
                # any other seeds listed in a Task-Seeds header.
 
203
                scan_seeds = set([seedname])
 
204
                if 'seeds' in task_headers:
 
205
                    scan_seeds.update(task_headers['seeds'].split())
 
206
                for scan_seed in sorted(scan_seeds):
 
207
                    writeOverrides(scan_seed, 'Task', task)
 
208
 
 
209
            # Generate apt-ftparchive "extra overrides" for Build-Essential
 
210
            # fields.
 
211
            if 'build-essential' in structure.names and flavour == flavours[0]:
 
212
                writeOverrides('build-essential', 'Build-Essential', 'yes')
 
213
 
 
214
    def main(self):
 
215
        self.setUp()
 
216
 
 
217
        for flavour in self.args:
 
218
            self.seed_structures[flavour] = SeedStructure(
 
219
                '%s.%s' % (flavour, self.series.name))
 
220
 
 
221
        override_path = os.path.join(
 
222
            self.config.miscroot,
 
223
            'more-extra.override.%s.main' % self.series.name)
 
224
        with AtomicFile(override_path) as override_file:
 
225
            for arch in self.architectures:
 
226
                self.runGerminate(override_file, arch, self.args)