You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
6 years ago
|
#!/usr/bin/python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
# Copyright (C) 2010, 2011, 2012 Canonical Ltd.
|
||
|
# Author: Martin Pitt <martin.pitt@ubuntu.com>
|
||
|
|
||
|
# 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 of the License.
|
||
|
#
|
||
|
# 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.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
# Look at the ISO tracker for currently tested builds, and generate
|
||
|
# publish-release commands for them.
|
||
|
|
||
|
# publish-release needs to be called as follows:
|
||
|
|
||
|
# for-project <derivative> publish-release <dir> <buildstamp> <type>
|
||
|
# <releaseflag> <name>
|
||
|
#
|
||
|
# <derivative>: ubuntu/kubuntu/edubuntu/xubuntu
|
||
|
# <dir>: daily or daily-live, dir on cdimage.u.c./
|
||
|
# <buildstamp>: e. g. 20070605.3; ubuntu-server/daily/<timestamp> for
|
||
|
# server/netbook/etc.
|
||
|
# <type>: desktop/alternate/server/serveraddon/src
|
||
|
# <releaseflag>: yes/no/poolonly/named (should appear on releases.u.c.?)
|
||
|
# <name>: name of the release (alpha-2, beta, etc.)
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
from collections import defaultdict
|
||
|
import optparse
|
||
|
import re
|
||
|
import sys
|
||
|
|
||
|
# See isotracker.py for setup instructions.
|
||
|
from isotracker import ISOTracker
|
||
|
|
||
|
milestone_name_re = re.compile('(Alpha|Beta|RC|Final|Pre-release)(?: (\d))?')
|
||
|
stable_name_re = re.compile('(Trusty|Xenial|Bionic) (14|16|18)\.04\.\d+')
|
||
|
|
||
|
# do not warn about not being able to handle those
|
||
|
ignore_product_re = re.compile(
|
||
|
'Netboot |Upgrade |Server EC2|Server Windows Azure|line-through')
|
||
|
# this identifies known builds
|
||
|
product_re = re.compile(
|
||
|
'((?:|u|lu|ku|edu|xu|myth)buntu(?: studio|kylin| kylin| gnome| mate| budgie| next)?) '
|
||
|
'(alternate|desktop|dvd|server(?: subiquity)?|mobile|base|active|wubi)(?: preinstalled)? '
|
||
|
'(i386|amd64$|amd64\+mac|armel$|armel\+dove|armel\+omap$|armel\+omap4|'
|
||
|
'armel\+ac100|armel\+mx5|armhf$|armhf\+omap$|armhf\+omap4|armhf\+ac100|'
|
||
|
'armhf\+mx5|armhf\+nexus7|armhf\+raspi2|armhf\+raspi3|arm64$|arm64\+raspi3|'
|
||
|
'powerpc|ppc64el|s390x)', re.I)
|
||
|
|
||
|
# map an image type from the ISO tracker to a source directory for
|
||
|
# publish-release
|
||
|
type_map = {
|
||
|
'desktop': 'daily-live',
|
||
|
'alternate': 'daily',
|
||
|
'src': 'source',
|
||
|
'dvd': 'dvd',
|
||
|
'mobile': 'daily-live',
|
||
|
'active': 'daily-live',
|
||
|
'server': 'daily',
|
||
|
'base': 'daily',
|
||
|
'wubi': 'wubi',
|
||
|
'preinstalled-desktop': 'daily-preinstalled',
|
||
|
'preinstalled-mobile': 'daily-preinstalled',
|
||
|
'preinstalled-active': 'daily-preinstalled',
|
||
|
'preinstalled-server': 'ubuntu-server/daily-preinstalled',
|
||
|
'live-server': 'ubuntu-server/daily-live',
|
||
|
}
|
||
|
|
||
|
|
||
|
def parse_iso_tracker(opts):
|
||
|
'''Get release builds information from ISO tracker.
|
||
|
|
||
|
Return an info dictionary with the following keys:
|
||
|
- build_map: projectname -> type -> build_stamp -> set(arches)
|
||
|
- milestone_name: Milestone name (e. g. "Alpha 3")
|
||
|
- milestone_code: publish-release milestone code (e. g. "alpha-3")
|
||
|
- stable (optional): stable release name (e. g. "lucid")
|
||
|
'''
|
||
|
build_map = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
|
||
|
ret = {'build_map': build_map, 'stable': None}
|
||
|
|
||
|
# access the ISO tracker
|
||
|
isotracker = ISOTracker(target=opts.target)
|
||
|
|
||
|
# get current milestone
|
||
|
if opts.milestone:
|
||
|
ms = isotracker.get_milestone_by_name(opts.milestone)
|
||
|
else:
|
||
|
ms = isotracker.default_milestone()
|
||
|
|
||
|
if ms.status_string != 'Testing':
|
||
|
sys.stderr.write(
|
||
|
'ERROR: Current milestone is not marked as "Testing"\n')
|
||
|
sys.exit(1)
|
||
|
|
||
|
m = milestone_name_re.search(ms.title)
|
||
|
if m:
|
||
|
# require number for alphas
|
||
|
if m.group(1) != 'Alpha' or m.group(2):
|
||
|
ret['milestone_name'] = ' '.join(
|
||
|
[g for g in m.groups() if g is not None])
|
||
|
ret['milestone_code'] = (
|
||
|
ret['milestone_name'].lower().replace(' ', '-'))
|
||
|
|
||
|
if 'milestone_name' not in ret:
|
||
|
sys.stderr.write(
|
||
|
"ERROR: Milestone '%s' isn't a valid target for publishing.\n"
|
||
|
% ms.title)
|
||
|
sys.exit(1)
|
||
|
|
||
|
if ret['milestone_code'] == 'pre-release':
|
||
|
ret['milestone_code'] = 'final'
|
||
|
else:
|
||
|
m = stable_name_re.search(ms.title)
|
||
|
if not m:
|
||
|
sys.stderr.write(
|
||
|
"ERROR: Milestone '%s' isn't a valid target for publishing.\n"
|
||
|
% ms.title)
|
||
|
sys.exit(1)
|
||
|
|
||
|
ret['milestone_name'] = m.group(0)
|
||
|
ret['milestone_code'] = 'final'
|
||
|
ret['stable'] = m.group(1).lower()
|
||
|
|
||
|
# product name lookup
|
||
|
products = {}
|
||
|
for product in isotracker.tracker_products:
|
||
|
products[product.id] = product.title
|
||
|
|
||
|
# builds
|
||
|
for build in isotracker.get_builds(ms):
|
||
|
product = products[build.productid]
|
||
|
|
||
|
# Start by skipping anything in the ignore list
|
||
|
if ignore_product_re.search(product):
|
||
|
continue
|
||
|
|
||
|
if opts.prepublish or opts.include_active:
|
||
|
ready_states = ('Active', 'Ready')
|
||
|
else:
|
||
|
ready_states = ('Ready',)
|
||
|
if build.status_string not in ready_states:
|
||
|
print('Ignoring %s which has status %s' %
|
||
|
(product, build.status_string))
|
||
|
continue
|
||
|
|
||
|
# Fail when finding an unknown product
|
||
|
m = product_re.match(product)
|
||
|
if not m:
|
||
|
sys.stderr.write('ERROR: Cannot handle product %s\n' % product)
|
||
|
sys.exit(1)
|
||
|
|
||
|
project = m.group(1).lower().replace(' ', '')
|
||
|
type = m.group(2).lower()
|
||
|
arch = m.group(3).lower()
|
||
|
|
||
|
if 'Server armhf+raspi2' in product:
|
||
|
# This product is mislabeled in the tracker:
|
||
|
type = 'preinstalled-%s' % type
|
||
|
if 'Server armhf+raspi3' in product:
|
||
|
type = 'preinstalled-%s' % type
|
||
|
if 'Server arm64+raspi3' in product:
|
||
|
type = 'preinstalled-%s' % type
|
||
|
if 'Server Subiquity' in product:
|
||
|
type = 'live-server'
|
||
|
project = 'ubuntu'
|
||
|
if 'Preinstalled' in product:
|
||
|
type = 'preinstalled-%s' % type
|
||
|
if project == 'kubuntu' and type == 'mobile':
|
||
|
project = 'kubuntu-mobile'
|
||
|
if project == 'kubuntu' and type == 'active':
|
||
|
project = 'kubuntu-active'
|
||
|
type = 'desktop'
|
||
|
if project == 'ubuntu' and type == 'base':
|
||
|
project = 'ubuntu-base'
|
||
|
if project == 'ubuntugnome':
|
||
|
project = 'ubuntu-gnome'
|
||
|
if project == 'ubuntumate':
|
||
|
project = 'ubuntu-mate'
|
||
|
if project == 'ubuntubudgie':
|
||
|
project = 'ubuntu-budgie'
|
||
|
if project == 'lubuntunext':
|
||
|
project = 'lubuntu-next'
|
||
|
|
||
|
build_map[project][type][build.version].add(arch)
|
||
|
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def do_publish_release(opts, project, type, buildstamp, arches, milestone,
|
||
|
stable):
|
||
|
'''Process a particular build publishing'''
|
||
|
|
||
|
primary = set(('amd64', 'i386'))
|
||
|
|
||
|
primary_arches = arches & primary
|
||
|
ports_arches = arches - primary
|
||
|
|
||
|
if 'alpha' in milestone:
|
||
|
official = 'no'
|
||
|
else:
|
||
|
official = 'named'
|
||
|
if (project in ('ubuntu',) and
|
||
|
type in ('desktop', 'alternate', 'netbook', 'live-server',
|
||
|
'wubi') and
|
||
|
primary_arches):
|
||
|
official = 'yes'
|
||
|
if opts.prepublish:
|
||
|
if official == 'named':
|
||
|
# no prepublication needed
|
||
|
return
|
||
|
elif official == 'yes':
|
||
|
official = 'poolonly'
|
||
|
|
||
|
# For pre-Natty: remove "official in ('yes', 'poolonly') and"
|
||
|
if official in ('yes', 'poolonly') and primary_arches and ports_arches:
|
||
|
do_publish_release(
|
||
|
opts, project, type, buildstamp, primary_arches, milestone, stable)
|
||
|
do_publish_release(
|
||
|
opts, project, type, buildstamp, ports_arches, milestone, stable)
|
||
|
return
|
||
|
|
||
|
cmd = ['for-project', project, 'publish-release']
|
||
|
if type != 'src':
|
||
|
cmd.insert(0, "ARCHES='%s'" % ' '.join(sorted(arches)))
|
||
|
if stable is not None:
|
||
|
cmd.insert(0, "DIST=%s" % stable)
|
||
|
|
||
|
if opts.dryrun:
|
||
|
cmd.append('--dry-run')
|
||
|
|
||
|
# dir and buildstamp arguments
|
||
|
try:
|
||
|
dir = type_map[type]
|
||
|
# For pre-Natty: uncomment next two lines
|
||
|
#if ports_arches:
|
||
|
# dir = re.sub(r'daily', 'ports/daily', dir)
|
||
|
# Sometimes a daily build is treated as being one project (e.g.
|
||
|
# ubuntu-netbook) but should be published under the auspices of
|
||
|
# another project (e.g. ubuntu). This is of course NOT AT ALL
|
||
|
# CONFUSING.
|
||
|
if project == 'ubuntu' and type == 'server':
|
||
|
dir = 'ubuntu-server/%s' % dir
|
||
|
elif project == 'ubuntu' and type == 'netbook' and primary_arches:
|
||
|
dir = 'ubuntu-netbook/%s' % dir
|
||
|
cmd.append(dir)
|
||
|
cmd.append(buildstamp)
|
||
|
except KeyError:
|
||
|
print('ERROR: Cannot handle type', type, 'for', project,
|
||
|
file=sys.stderr)
|
||
|
return
|
||
|
|
||
|
# type argument
|
||
|
cmd.append(type)
|
||
|
|
||
|
# releaseflag argument
|
||
|
cmd.append(official)
|
||
|
|
||
|
# name argument
|
||
|
if milestone != 'final':
|
||
|
cmd.append(milestone)
|
||
|
|
||
|
print(' '.join(cmd))
|
||
|
|
||
|
|
||
|
def main():
|
||
|
parser = optparse.OptionParser(usage='Usage: %prog [options]')
|
||
|
parser.add_option('-m', '--milestone',
|
||
|
help='post to MILESTONE rather than the default')
|
||
|
parser.add_option('-n', '--dry-run', dest='dryrun',
|
||
|
action='store_true', default=False,
|
||
|
help='Generate dry-run commands')
|
||
|
parser.add_option('-p', '--prepublish', dest='prepublish',
|
||
|
action='store_true', default=False,
|
||
|
help='Pre-publish images to .pool')
|
||
|
parser.add_option('-t', '--target', help='post to an alternate QATracker')
|
||
|
parser.add_option('--include-active', action='store_true', default=False,
|
||
|
help='Always include Active (not Ready) images')
|
||
|
opts, args = parser.parse_args()
|
||
|
|
||
|
info = parse_iso_tracker(opts)
|
||
|
|
||
|
print('\n## make backup:')
|
||
|
print('cd ~/cdimage/; rm -rf www.prev; cp -al www www.prev; cd www')
|
||
|
|
||
|
print('\n## publish images:')
|
||
|
source_milestone = None
|
||
|
for project, builds in info['build_map'].items():
|
||
|
for type, buildstamps in builds.items():
|
||
|
for buildstamp, arches in buildstamps.items():
|
||
|
do_publish_release(opts, project, type, buildstamp, arches,
|
||
|
info['milestone_code'], info['stable'])
|
||
|
source_milestone = info['milestone_code']
|
||
|
print()
|
||
|
|
||
|
if source_milestone:
|
||
|
do_publish_release(opts, 'ubuntu', 'src', 'current', set(),
|
||
|
source_milestone, info['stable'])
|
||
|
|
||
|
if not opts.prepublish:
|
||
|
print('\n## fix name in headers:')
|
||
|
print("find full -path '*/%s*HEADER.html' | "
|
||
|
"xargs sed -i 's/Daily Build/%s/'" %
|
||
|
(info['milestone_code'], info['milestone_name']))
|
||
|
|
||
|
print('\n## check changes against www.prev:')
|
||
|
print('diff -u <(cd ../www.prev/full && find | sort) '
|
||
|
'<(cd full && find | sort) | less')
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|