source: germinate/trunk/fuentes/.pc/ignore_gpg_check/germinate/scripts/germinate_update_metapackage.py @ 1926

Last change on this file since 1926 was 1926, checked in by kbut, 5 years ago

add ignore gpg

File size: 18.4 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 Canonical Ltd.
4# Copyright (C) 2006 Gustavo Franco
5#
6# This file is part of Germinate.
7#
8# Germinate is free software; you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by the
10# Free Software Foundation; either version 2, or (at your option) any
11# later version.
12#
13# Germinate is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16# General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with Germinate; see the file COPYING.  If not, write to the Free
20# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21# 02110-1301, USA.
22
23# TODO:
24# - Exclude essential packages from dependencies
25
26from __future__ import print_function
27
28from collections import defaultdict
29import logging
30import optparse
31import os
32import re
33import subprocess
34import sys
35
36try:
37    # >= 3.0
38    from configparser import NoOptionError, NoSectionError
39    if (sys.version_info[0] < 3 or
40        (sys.version_info[0] == 3 and sys.version_info[1] < 2)):
41        # < 3.2
42        from configparser import SafeConfigParser
43    else:
44        # >= 3.2
45        from configparser import ConfigParser as SafeConfigParser
46except ImportError:
47    # < 3.0
48    from ConfigParser import NoOptionError, NoSectionError, SafeConfigParser
49
50import germinate.archive
51from germinate.germinator import Germinator
52from germinate.log import germinate_logging
53from germinate.seeds import SeedError, SeedStructure, SeedVcs
54import germinate.version
55
56
57__pychecker__ = 'maxlocals=80'
58
59
60def error_exit(message):
61    print("%s: %s" % (sys.argv[0], message), file=sys.stderr)
62    sys.exit(1)
63
64
65def parse_options(argv):
66    description = '''\
67Update metapackage lists for distribution 'dist' as defined in
68update.cfg.'''
69
70    parser = optparse.OptionParser(
71        prog='germinate-update-metapackage',
72        usage='%prog [options] [dist]',
73        version='%prog ' + germinate.version.VERSION,
74        description=description)
75    parser.add_option('-o', '--output-directory', dest='outdir',
76                      default='.', metavar='DIR',
77                      help='output in specific directory')
78    parser.add_option('--nodch', dest='nodch', action='store_true',
79                      default=False,
80                      help="don't modify debian/changelog")
81    parser.add_option('--vcs', dest='vcs',
82                      action='store_const', const=SeedVcs.AUTO,
83                      help='fetch seeds using a version control system')
84    parser.add_option('--bzr', dest='vcs', action='store_true',
85                      help='fetch seeds using bzr (requires bzr to be '
86                           'installed; use --vcs instead)')
87    parser.add_option('-r','--recursive', dest='recursive', action='store_true',
88                      default=False,
89                      help="expand all seeds dependencies from STRUCTURE")
90    return parser.parse_args(argv[1:])
91
92
93def main(argv):
94    options, args = parse_options(argv)
95
96    if not os.path.exists('debian/control'):
97        error_exit('must be run from the top level of a source package')
98    this_source = None
99    with open('debian/control') as control:
100        for line in control:
101            if line.startswith('Source:'):
102                this_source = line[7:].strip()
103                break
104            elif line == '':
105                break
106    if this_source is None:
107        error_exit('cannot find Source: in debian/control')
108    if not this_source.endswith('-meta'):
109        error_exit('source package name must be *-meta')
110    metapackage = this_source[:-5]
111
112    print("[info] Initialising %s-* package lists update..." % metapackage)
113
114    config = SafeConfigParser()
115    with open('update.cfg') as config_file:
116        try:
117            # >= 3.2
118            config.read_file(config_file)
119        except AttributeError:
120            # < 3.2
121            config.readfp(config_file)
122
123    if len(args) > 0:
124        dist = args[0]
125    else:
126        dist = config.get('DEFAULT', 'dist')
127
128    seeds = config.get(dist, 'seeds').split()
129    try:
130        output_seeds = config.get(dist, 'output_seeds').split()
131    except NoOptionError:
132        output_seeds = list(seeds)
133    architectures = config.get(dist, 'architectures').split()
134    try:
135        archive_base_default = config.get(dist, 'archive_base/default')
136        archive_base_default = re.split(r'[, ]+', archive_base_default)
137    except (NoSectionError, NoOptionError):
138        archive_base_default = None
139
140    archive_base = {}
141    for arch in architectures:
142        try:
143            archive_base[arch] = config.get(dist, 'archive_base/%s' % arch)
144            archive_base[arch] = re.split(r'[, ]+', archive_base[arch])
145        except (NoSectionError, NoOptionError):
146            if archive_base_default is not None:
147                archive_base[arch] = archive_base_default
148            else:
149                error_exit('no archive_base configured for %s' % arch)
150
151    if options.vcs and config.has_option("%s/vcs" % dist, 'seed_base'):
152        seed_base = config.get("%s/vcs" % dist, 'seed_base')
153    elif options.vcs and config.has_option("%s/bzr" % dist, 'seed_base'):
154        # Backward compatibility.
155        seed_base = config.get("%s/bzr" % dist, 'seed_base')
156    else:
157        seed_base = config.get(dist, 'seed_base')
158    seed_base = re.split(r'[, ]+', seed_base)
159    if options.vcs and config.has_option("%s/vcs" % dist, 'seed_dist'):
160        seed_dist = config.get("%s/vcs" % dist, 'seed_dist')
161    elif options.vcs and config.has_option("%s/bzr" % dist, 'seed_dist'):
162        # Backward compatibility.
163        seed_dist = config.get("%s/bzr" % dist, 'seed_dist')
164    elif config.has_option(dist, 'seed_dist'):
165        seed_dist = config.get(dist, 'seed_dist')
166    else:
167        seed_dist = dist
168    if config.has_option(dist, 'dists'):
169        dists = config.get(dist, 'dists').split()
170    else:
171        dists = [dist]
172    components = config.get(dist, 'components').split()
173
174    def seed_packages(germinator_method, structure, seed_name):
175        if config.has_option(dist, "seed_map/%s" % seed_name):
176            mapped_seeds = config.get(dist, "seed_map/%s" % seed_name).split()
177        else:
178            mapped_seeds = []
179            task_seeds_re = re.compile('^Task-Seeds:\s*(.*)', re.I)
180            with structure[seed_name] as seed:
181                for line in seed:
182                    task_seeds_match = task_seeds_re.match(line)
183                    if task_seeds_match is not None:
184                        mapped_seeds = task_seeds_match.group(1).split()
185                        break
186            if seed_name not in mapped_seeds:
187                mapped_seeds.append(seed_name)
188        packages = []
189        task_seeds_re = re.compile('^Task-Recursive:\s*(.*)', re.I)
190        with structure[seed_name] as seed:
191            for line in seed:
192                task_seeds_match = task_seeds_re.match(line)
193                if task_seeds_match is not None:
194                    mapped_seeds.extend(structure.inner_seeds(seed_name))
195                    mapped_seeds = list(set(mapped_seeds))
196                    break
197        for mapped_seed in mapped_seeds:
198            packages.extend(germinator_method(structure, mapped_seed))
199        return packages
200
201    def metapackage_name(structure, seed_name):
202        if config.has_option(dist, "metapackage_map/%s" % seed_name):
203            return config.get(dist, "metapackage_map/%s" % seed_name)
204        else:
205            task_meta_re = re.compile('^Task-Metapackage:\s*(.*)', re.I)
206            with structure[seed_name] as seed:
207                for line in seed:
208                    task_meta_match = task_meta_re.match(line)
209                    if task_meta_match is not None:
210                        return task_meta_match.group(1)
211            return "%s-%s" % (metapackage, seed_name)
212
213    debootstrap_version_file = 'debootstrap-version'
214
215    def get_debootstrap_version():
216        version_cmd = subprocess.Popen(
217            ['dpkg-query', '-W', '--showformat', '${Version}', 'debootstrap'],
218            stdout=subprocess.PIPE, universal_newlines=True)
219        version, _ = version_cmd.communicate()
220        if not version:
221            error_exit('debootstrap does not appear to be installed')
222
223        return version
224
225    def debootstrap_packages(arch):
226        env = dict(os.environ)
227        if 'PATH' in env:
228            env['PATH'] = '/usr/sbin:/sbin:%s' % env['PATH']
229        else:
230            env['PATH'] = '/usr/sbin:/sbin:/usr/bin:/bin'
231        debootstrap = subprocess.Popen(
232            ['debootstrap', '--arch', arch,
233             '--components', ','.join(components),
234             '--print-debs', dist, 'debootstrap-dir', archive_base[arch][0]],
235            stdout=subprocess.PIPE, env=env, stderr=subprocess.PIPE,
236            universal_newlines=True)
237        (debootstrap_stdout, debootstrap_stderr) = debootstrap.communicate()
238        if debootstrap.returncode != 0:
239            error_exit('Unable to retrieve package list from debootstrap; '
240                       'stdout: %s\nstderr: %s' %
241                       (debootstrap_stdout, debootstrap_stderr))
242
243        # sometimes debootstrap gives empty packages / multiple separators
244        packages = [pkg for pkg in debootstrap_stdout.split() if pkg]
245
246        return sorted(packages)
247
248    def check_debootstrap_version():
249        if os.path.exists(debootstrap_version_file):
250            with open(debootstrap_version_file) as debootstrap:
251                old_debootstrap_version = debootstrap.read().strip()
252            debootstrap_version = get_debootstrap_version()
253            failed = subprocess.call(
254                ['dpkg', '--compare-versions',
255                 debootstrap_version, 'ge', old_debootstrap_version])
256            if failed:
257                error_exit('Installed debootstrap is older than in the '
258                           'previous version! (%s < %s)' %
259                           (debootstrap_version, old_debootstrap_version))
260
261    def update_debootstrap_version():
262        with open(debootstrap_version_file, 'w') as debootstrap:
263            debootstrap.write(get_debootstrap_version() + '\n')
264
265    def format_changes(items):
266        by_arch = defaultdict(set)
267        for pkg, arch in items:
268            by_arch[pkg].add(arch)
269        all_pkgs = sorted(by_arch)
270        chunks = []
271        for pkg in all_pkgs:
272            arches = by_arch[pkg]
273            if set(architectures) - arches:
274                # only some architectures
275                chunks.append('%s [%s]' % (pkg, ' '.join(sorted(arches))))
276            else:
277                # all architectures
278                chunks.append(pkg)
279        return ', '.join(chunks)
280
281    germinate_logging(logging.DEBUG)
282
283    check_debootstrap_version()
284
285    additions = defaultdict(list)
286    removals = defaultdict(list)
287    moves = defaultdict(list)
288    metapackage_map = {}
289    for architecture in architectures:
290        print("[%s] Downloading available package lists..." % architecture)
291        germinator = Germinator(architecture)
292        archive = germinate.archive.TagFile(
293            dists, components, architecture,
294            archive_base[architecture], source_mirrors=archive_base_default,
295            cleanup=True)
296        germinator.parse_archive(archive)
297        debootstrap_base = set(debootstrap_packages(architecture))
298
299        print("[%s] Loading seed lists..." % architecture)
300        try:
301            structure = SeedStructure(seed_dist, seed_base, options.vcs)
302            germinator.plant_seeds(structure, seeds=seeds)
303        except SeedError:
304            sys.exit(1)
305
306        print("[%s] Merging seeds with available package lists..." %
307              architecture)
308        for seed_name in output_seeds:
309            meta_name = metapackage_name(structure, seed_name)
310            metapackage_map[seed_name] = meta_name
311
312            output_filename = os.path.join(
313                options.outdir, '%s-%s' % (seed_name, architecture))
314            old_list = None
315            if os.path.exists(output_filename):
316                with open(output_filename) as output:
317                    old_list = set(map(str.strip, output.readlines()))
318                os.rename(output_filename, output_filename + '.old')
319
320            # work on the depends
321            new_list = []
322            packages = seed_packages(germinator.get_seed_entries,
323                                     structure, seed_name)
324            for package in packages:
325                if package == meta_name:
326                    print("%s/%s: Skipping package %s (metapackage)" %
327                          (seed_name, architecture, package))
328                elif (seed_name == 'minimal' and
329                      package not in debootstrap_base):
330                    print("%s/%s: Skipping package %s (package not in "
331                          "debootstrap)" % (seed_name, architecture, package))
332                elif germinator.is_essential(package):
333                    print("%s/%s: Skipping package %s (essential)" %
334                          (seed_name, architecture, package))
335                else:
336                    new_list.append(package)
337
338            new_list.sort()
339            with open(output_filename, 'w') as output:
340                for package in new_list:
341                    output.write(package)
342                    output.write('\n')
343
344            # work on the recommends
345            old_recommends_list = None
346            new_recommends_list = []
347            packages = seed_packages(germinator.get_seed_recommends_entries,
348                                     structure, seed_name)
349            for package in packages:
350                if package == meta_name:
351                    print("%s/%s: Skipping package %s (metapackage)" %
352                          (seed_name, architecture, package))
353                    continue
354                if seed_name == 'minimal' and package not in debootstrap_base:
355                    print("%s/%s: Skipping package %s (package not in "
356                          "debootstrap)" % (seed_name, architecture, package))
357                else:
358                    new_recommends_list.append(package)
359
360            new_recommends_list.sort()
361            seed_name_recommends = '%s-recommends' % seed_name
362            output_recommends_filename = os.path.join(
363                options.outdir, '%s-%s' % (seed_name_recommends, architecture))
364            if os.path.exists(output_recommends_filename):
365                with open(output_recommends_filename) as output:
366                    old_recommends_list = set(
367                        map(str.strip, output.readlines()))
368                os.rename(
369                    output_recommends_filename,
370                    output_recommends_filename + '.old')
371
372            with open(output_recommends_filename, 'w') as output:
373                for package in new_recommends_list:
374                    output.write(package)
375                    output.write('\n')
376
377            # Calculate deltas
378            merged = defaultdict(int)
379            recommends_merged = defaultdict(int)
380            if old_list is not None:
381                for package in new_list:
382                    merged[package] += 1
383                for package in old_list:
384                    merged[package] -= 1
385            if old_recommends_list is not None:
386                for package in new_recommends_list:
387                    recommends_merged[package] += 1
388                for package in old_recommends_list:
389                    recommends_merged[package] -= 1
390
391            mergeditems = sorted(merged.items())
392            for package, value in mergeditems:
393                #print(package, value)
394                if value == 1:
395                    if recommends_merged.get(package, 0) == -1:
396                        moves[package].append([seed_name, architecture])
397                        recommends_merged[package] += 1
398                    else:
399                        additions[package].append([seed_name, architecture])
400                elif value == -1:
401                    if recommends_merged.get(package, 0) == 1:
402                        moves[package].append([seed_name_recommends,
403                                               architecture])
404                        recommends_merged[package] -= 1
405                    else:
406                        removals[package].append([seed_name, architecture])
407
408            mergedrecitems = sorted(recommends_merged.items())
409            for package, value in mergedrecitems:
410                #print(package, value)
411                if value == 1:
412                    additions[package].append([seed_name_recommends,
413                                               architecture])
414                elif value == -1:
415                    removals[package].append([seed_name_recommends,
416                                              architecture])
417
418    with open('metapackage-map', 'w') as metapackage_map_file:
419        for seed_name in output_seeds:
420            print(seed_name, metapackage_map[seed_name],
421                  file=metapackage_map_file)
422
423    if not options.nodch and (additions or removals or moves):
424        dch_help = subprocess.Popen(['dch', '--help'], stdout=subprocess.PIPE,
425                                    universal_newlines=True)
426        try:
427            have_U = '-U' in dch_help.stdout.read()
428        finally:
429            if dch_help.stdout:
430                dch_help.stdout.close()
431            dch_help.wait()
432        if have_U:
433            subprocess.check_call(['dch', '-iU', 'Refreshed dependencies'])
434        else:
435            subprocess.check_call(['dch', '-i', 'Refreshed dependencies'])
436        changes = []
437        for package in sorted(additions):
438            changes.append('Added %s to %s' %
439                           (package, format_changes(additions[package])))
440        for package in sorted(removals):
441            changes.append('Removed %s from %s' %
442                           (package, format_changes(removals[package])))
443        for package in sorted(moves):
444            # TODO: We should really list where it moved from as well, but
445            # that gets wordy very quickly, and at the moment this is only
446            # implemented for depends->recommends or vice versa. In future,
447            # using this for moves between seeds might also be useful.
448            changes.append('Moved %s to %s' %
449                           (package, format_changes(moves[package])))
450        for change in changes:
451            print(change)
452            subprocess.check_call(['dch', '-a', change])
453        update_debootstrap_version()
454    else:
455        if not options.nodch:
456            print("No changes found")
457
458    return 0
Note: See TracBrowser for help on using the repository browser.