1
# Copyright 2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Generate extra overrides using Germinate."""
8
'GenerateExtraOverrides',
14
from germinate.germinator import Germinator
15
from germinate.archive import TagFile
16
from germinate.log import GerminateFormatter
17
from germinate.seeds import SeedStructure
19
from zope.component import getUtility
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 (
27
LaunchpadScriptFailure,
29
from lp.soyuz.enums import ArchivePurpose
33
"""Facilitate atomic writing of files."""
35
def __init__(self, filename):
36
self.filename = filename
37
self.fd = open('%s.new' % self.filename, 'w')
42
def __exit__(self, exc_type, exc_value, exc_tb):
44
os.rename('%s.new' % self.filename, self.filename)
47
class GenerateExtraOverrides(LaunchpadScript):
48
"""Main class for scripts/ftpmaster-tools/generate-task-overrides.py."""
52
self.seed_structures = {}
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.')
60
def processOptions(self):
62
self.distribution = getUtility(
63
IDistributionSet)[self.options.distribution_name]
64
except NotFoundError, err:
65
raise LaunchpadScriptFailure(
66
"Could not find distribution %s" % err)
69
wanted_status = (SeriesStatus.DEVELOPMENT,
71
for status in wanted_status:
72
series = self.distribution.getSeriesByStatus(status)
73
if series.count() > 0:
76
raise LaunchpadScriptFailure(
77
'There is no DEVELOPMENT distroseries for %s' %
78
self.options.distribution_name)
79
self.series = series[0]
81
self.architectures = self.series.architectures
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']
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)
96
raise LaunchpadScriptFailure(
97
'There is no PRIMARY archive for %s' %
98
self.options.distribution_name)
101
"""Process options, and set up internal state."""
102
self.processOptions()
103
self.config = self.getConfig()
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
113
def outputPath(self, flavour, arch, base):
115
self.config.germinateroot,
116
'%s_%s_%s_%s' % (base, flavour, self.series.name, arch))
118
def runGerminate(self, override_file, arch, flavours):
119
germinator = Germinator(arch)
121
# Read archive metadata.
123
self.series.name, self.components, arch,
124
'file:/%s' % self.config.archiveroot, cleanup=True)
125
germinator.parse_archive(archive)
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})
137
# Expand dependencies.
138
structure = self.seed_structures[flavour]
139
germinator.plant_seeds(structure)
140
germinator.grow(structure)
141
germinator.add_extras(structure)
143
# Write output files.
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'))
149
# "all" and "all.sources" list the full set of binary and source
150
# packages respectively for a given flavour/suite/architecture
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)
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),
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)
171
# Generate apt-ftparchive "extra overrides" for Task fields.
172
for seedname in structure.names:
173
if seedname == 'extra':
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()
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]:
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)
209
# Generate apt-ftparchive "extra overrides" for Build-Essential
211
if 'build-essential' in structure.names and flavour == flavours[0]:
212
writeOverrides('build-essential', 'Build-Essential', 'yes')
217
for flavour in self.args:
218
self.seed_structures[flavour] = SeedStructure(
219
'%s.%s' % (flavour, self.series.name))
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)