#!/usr/bin/env python2
# Copyright 2009-2015 Canonical, Ltd
# Author: Kees Cook, Jamie Strandboge
# License: GPLv3
# Reports source packages in a given seed.
#
# DAPPER_EXCLUDE="--src-exclude xorg"
# HARDY_EXCLUDE="--src-exclude xorg"
# LUCID_EXCLUDE="--src-exclude xorg,libsdl1.2,pulseaudio,libsndfile,libogg,libvorbis,hal,hal-info"
#
# RELEASE=lucid
# EXCLUDE="$LUCID_EXCLUDE"
#
# ### initial setup for a given release
# sudo apt-get install -y germinate
# cd /scratch/ubuntu
# mkdir seeds
# cd seeds
# bzr co lp:~ubuntu-core-dev/ubuntu-seeds/ubuntu.$RELEASE
# bzr co lp:~ubuntu-core-dev/ubuntu-seeds/platform.$RELEASE
# mkdir -p germinate-output/ubuntu.$RELEASE
#
# ### seed output and supported source package generation
# cd /scratch/ubuntu/seeds/germinate-output-adjusted/ubuntu.$RELEASE/
# rm -rf *
# germinate -S file:///scratch/ubuntu/seeds/ -s ubuntu.$RELEASE -m http://us.archive.ubuntu.com/ubuntu/ -d $RELEASE,$RELEASE-updates,$RELEASE-security -a i386 -c main,universe,restricted,multiverse
# $UCT/scripts/seed-report --debug --release $RELEASE --seed-uri file:///scratch/ubuntu/seeds --germinate-uri file:///scratch/ubuntu/seeds/germinate-output-adjusted $EXCLUDE supported-server,server-ship > /tmp/supported.srcs2
# diff -u /tmp/supported.srcs /tmp/supported.srcs2
# cp /tmp/supported.srcs2 /tmp/supported.srcs
# ### modify seeds and repeat if needed
#
# ### how to search details for doing a seed modify...
# cd /scratch/ubuntu/seeds/germinate-output-adjusted/ubuntu.$RELEASE/rdepends
# ### for each unwanted source, cd to $SOURCE/ and check seeds:
# grep ' seed' * | egrep -v '(Desktop|Extra|Education|Documentation|Dvd|Mobile)'
# cd /scratch/ubuntu/seeds/ubuntu.$RELEASE
# ### re-arrange seeds
#
# ###  specifying a different flavor
# germinate -S file:///scratch/ubuntu/seeds/ -s ubuntu-core.$RELEASE -m http://us.archive.ubuntu.com/ubuntu -d $RELEASE -a i386 -c main,restricted,universe,multiverse
# $UCT/scripts/seed-report --debug --release $RELEASE --flavor ubuntu-core
# --seed-uri file:///scratch/ubuntu/seeds --germinate-uri
# file:///scratch/ubuntu/seeds/germinate-output-adjusted $EXCLUDE standard >
# /tmp/supported-core.srcs
#
# Useful information
# FLAVOR        RELEASE                 SEED
# ubuntu        trusty                  live,server,server-ship,supported
# ubuntu        trusty                  minimal (original Ubuntu Core)
# ubuntu        utopic                  live,server,server-ship,supported
# ubuntu-core   utopic                  minimal
# ubuntu-core   vivid                   system-image
# ubuntu-touch  trusty-vivid            touch
# Handy options:
# * Everything on the server CD
#   --flavor ubuntu --release <release> server-ship
# * Everything in the default install (excluded seeds may need to be updated)
#   --flavor ubuntu --release trusty server-ship dns-server,lamp-server,print-server,samba-server,postgresql-server,mail-server,tomcat-server,virt-host
# * Everything in the default install plus openssh-server
#   --flavor ubuntu --release trusty server-ship dns-server,lamp-server,print-server,samba-server,postgresql-server,mail-server,tomcat-server,virt-host,openssh-server
#
import sys, os
import optparse
import urllib2

default_release = 'dapper'
default_flavor = 'ubuntu'
default_seed_uri = 'http://people.canonical.com/~ubuntu-archive/seeds'
default_germinate_uri = 'http://people.canonical.com/~ubuntu-archive/germinate-output'

parser = optparse.OptionParser('''%prog [OPTIONS] seed-wanted [seed-excluded]
Eg:

# See all source packages on server CD for trusty
$ seed-report ... --release trusty server-ship

# See all source packages in default install + openssh on trusty (server-ship
# excluding all optional package sets except openssh-server)
$ seed-report ... --release trusty server-ship dns-server,lamp-server,print-server,samba-server,postgresql-server,mail-server,tomcat-server,virt-host

Note: specified seeds to exclude do not recurse
''')
parser.add_option("--binaries", help="Show binary packages instead of source packages", action='store_true')
parser.add_option("--debug", help="Show debugging details", action='store_true')
parser.add_option("--release", help="Which release to examine (default: %s)" % (default_release), action='store', default=default_release)
parser.add_option("--flavor", help="Which flavor to examine (default: %s)" % (default_flavor), action='store', default=default_flavor)
parser.add_option("--src-exclude", help="Exclude a specific source package from the report", action='store')
parser.add_option("--seed-uri", help="Where to load seed STRUCTURE from (default: %s)" % (default_seed_uri), action='store', metavar="URI", default=default_seed_uri)
parser.add_option("--germinate-uri", help="Where to load germinate output from (default: %s)" % (default_germinate_uri), action='store', metavar="URI", default=default_germinate_uri)
(opt, args) = parser.parse_args()

if len(args)<1:
    parser.print_help()
    sys.exit(1)

wanted = args[0]
try:
    excluded = args[1].split(',')
except:
    excluded = []

if opt.src_exclude:
    opt.src_exclude = opt.src_exclude.split(',')

def load_structure(structure, path):
    for line in urllib2.urlopen(os.path.join(path,"STRUCTURE")):
        line = line.strip()
        if line.startswith('#'):
            continue
        elif line.startswith('include '):
            load_structure(structure, os.path.join(os.path.dirname(path),line.split(' ',1)[1]))
        elif line.startswith('feature '):
            pass
        else:
            try:
                name, values = line.split(':',1)
            except:
                print >>sys.stderr, "Failed to process '%s'" % (line)
                raise
            name = name.strip()
            seeds.add(name)
            structure.setdefault(name, [])
            for item in values.strip().split():
                structure[name].append(item)
                seeds.add(item)

def get_germinate(seed_name, depth=0, skip=[], recurse=True):
    def read_seed_file(fn):
        listening = 0
        for line in urllib2.urlopen(fn):
            if line.startswith('-'):
                if listening:
                    break
                listening = 1
                continue
            if listening:
                bin, src, otherstuff = line.split('|',2)
                bin = bin.strip()
                src = src.strip()

                if opt.src_exclude and src in opt.src_exclude:
                    if opt.debug:
                        print >>sys.stderr, 'excluded src %s (binary %s)' % (src, bin)
                    continue

                bins.add(bin)
                srcs.add(src)

    bins = set()
    srcs = set()

    if seed_name in skip:
        if opt.debug:
            print >>sys.stderr, 'skipped seed %s' % (seed_name)
        return bins, srcs

    if opt.debug:
        print >>sys.stderr, "%swant '%s'" % ("  " * depth, seed_name)
    if seed_name not in loaded:
        filename = '%s/%s.%s/%s' % (opt.germinate_uri, opt.flavor, opt.release, seed_name)
        if opt.debug:
            print >>sys.stderr, "%s reading '%s'" % ("  " * depth, seed_name)
        read_seed_file(filename)
        loaded.add(seed_name)

    if recurse:
        for seed in structure[seed_name]:
            if seed in loaded:
                continue
            recursive_bins, recursive_srcs = get_germinate(seed, depth+1)
            bins = bins.union(recursive_bins)
            srcs = srcs.union(recursive_srcs)

    return bins, srcs

# Track which seeds we've already fetched
seeds = set()
structure = dict()
load_structure(structure, '%s/%s.%s' % (opt.seed_uri, opt.flavor, opt.release))

loaded = set()

wanted_bins = set()
wanted_srcs = set()
for seed in wanted.split(','):
    bins, srcs = get_germinate(seed)
    wanted_bins = wanted_bins.union(bins)
    wanted_srcs = wanted_srcs.union(srcs)

loaded = set()

excluded_bins = set()
excluded_srcs = set()
for exclude in excluded:
    bins, srcs = get_germinate(exclude, recurse=False)
    excluded_bins = excluded_bins.union(bins)
    excluded_srcs = excluded_srcs.union(srcs)
    if opt.debug:
        for src in excluded_srcs:
            print >>sys.stderr, '\texcluded src %s (from %s)' % (src, exclude)
        for bin in excluded_bins:
            print >>sys.stderr, '\texcluded bin %s (from %s)' % (bin, exclude)

out_bins = wanted_bins.difference(excluded_bins)
out_srcs = wanted_srcs.difference(excluded_srcs)
if opt.binaries:
    for bin in sorted(out_bins):
        print bin
else:
    for src in sorted(out_srcs):
        print src
