blob: 93598762f4d7f61876d1a21e13ac924953b81820 [file] [log] [blame]
#!/usr/bin/python -tt
##
# Copyright (C) 2013 by Kernel.org
#
# 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; either version 2
# of the License, or (at your option) any later version.
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
import os
import time, datetime
import re
from git import Repo
from pelican import signals, utils
from feedgenerator import Rss201rev2Feed
import json
# Pelican plugin API
def register():
signals.article_generator_init.connect(kernel_releases_init)
signals.article_generate_context.connect(fetch_kernel_releases)
def kernel_releases_init(gen):
gen.releases_instance = KernelReleases(gen)
def fetch_kernel_releases(gen, metadata):
gen.context['current_releases'] = gen.releases_instance.current_releases
gen.context['latest_release'] = gen.releases_instance.latest_release
class KernelReleases():
def __init__(self, generator):
self.release_tracker = '/var/lib/mirror/release-tracker.json'
self.rss_path = os.path.join(generator.output_path, 'feeds', 'kdist.xml')
self.finger_path = os.path.join(generator.output_path, 'finger_banner')
self.json_path = os.path.join(generator.output_path, 'releases.json')
settings = generator.settings
GIT_MAINLINE = settings.get('GIT_MAINLINE')
GIT_STABLE = settings.get('GIT_STABLE')
GIT_NEXT = settings.get('GIT_NEXT')
LONGTERM_KERNELS = settings.get('LONGTERM_KERNELS')
EOL_KERNELS = settings.get('EOL_KERNELS')
self.pub_mount = settings.get('PUB_MOUNT')
repo = Repo(GIT_MAINLINE)
tagrefs = self.get_tagref_list(repo)
rc_reg = re.compile('v.*-rc\d+$')
mainline_rc = self.find_latest_matching(tagrefs, rc_reg)
rel_reg = re.compile('v[^-]*\d+$')
mainline_rel = self.find_latest_matching(tagrefs, rel_reg)
# if mainline_rel is newer than mainline_rc, ignore mainline_rc
if mainline_rc[1] < mainline_rel[1]:
mainline_rc = None
# Move on to the linux-stable repo
repo = Repo(GIT_STABLE)
# ignore any tags older than 6 months
cutoff = time.time() - 15768000
tagrefs = self.get_tagref_list(repo, cutoff)
# find all tags matching vX.X.X
rel_reg = re.compile('v\d+\.\d+\.\d+$')
matched = []
for tagref in tagrefs:
if rel_reg.match(tagref.name):
matched.append(tagref)
stable = []
seen = []
for tagref in matched:
# does it match a longterm release?
ignore = False
for ver in LONGTERM_KERNELS:
if tagref.name.find(ver) > 0:
# this is a long-term release, ignore
ignore = True
continue
if ignore:
continue
# Drop the final \.\d+ and find the latest matching
components = tagref.name.split('.')
regex = re.compile('^' + '\\.'.join(components[:-1]))
if regex in seen:
continue
stable.append(self.find_latest_matching(matched, regex))
seen.append(regex)
stable = sorted(stable, key=lambda tagged: tagged[1], reverse=True)
releases = []
if mainline_rc is not None:
releases.append(self.make_release_line(mainline_rc, 'mainline'))
# look at the last stable and see if mainline_rel fits into it
latest_stable = stable.pop(0)
if latest_stable[0].find(mainline_rel[0]) != 0:
releases.append(self.make_release_line(mainline_rel, 'mainline'))
latest = self.make_release_line(mainline_rel, 'latest')
else:
latest = self.make_release_line(latest_stable, 'latest')
# if latest stable is newer than latest mainline, but there are no
# -rc kernels, list the latest mainline in the releases table anyway.
if mainline_rc is None:
releases.append(self.make_release_line(mainline_rel, 'mainline'))
releases.append(self.make_release_line(latest_stable, 'stable'))
# add other stable kernels and mark EOL accordingly
eolcount = 0
for rel in stable:
iseol = False
for eolrel in EOL_KERNELS:
if rel[0].find(eolrel) >= 0:
iseol = True
eolcount += 1
break
# limit the number of EOL kernels to 2.
if not iseol or (iseol and eolcount <= 2):
releases.append(self.make_release_line(rel, 'stable', iseol))
# find latest long-term releases
for rel in LONGTERM_KERNELS:
components = rel.split('.')
regex = re.compile('^v' + '\\.'.join(components) + '\\.\\d+$')
found = self.find_latest_matching(tagrefs, regex)
if found is not None:
releases.append(self.make_release_line(found, 'longterm'))
# now find latest tag in linux-next
repo = Repo(GIT_NEXT)
tagrefs = self.get_tagref_list(repo, cutoff)
regex = re.compile('^next-')
rel = self.find_latest_matching(tagrefs, regex)
releases.append(self.make_release_line(rel, 'linux-next'))
self.current_releases = releases
self.latest_release = latest
self.generate_rss_feed()
self.generate_finger_banner()
self.generate_releases_json()
self.check_release_tracker()
def check_release_tracker(self):
# Load known releases from the release tracker file
known_old = None
known_new = []
if os.path.exists(self.release_tracker):
try:
fh = open(self.release_tracker, 'r')
known_old = json.load(fh)
fh.close()
except:
# it's better to not announce something than to spam
# people needlessly.
pass
for chunks in self.current_releases:
release = chunks[1]
if known_old is None or release in known_old:
# Don't announce this release. Add to known_new and move on
known_new.append(release)
else:
# This appears to be a new release.
if chunks[5] is None or chunks[6] is None or chunks[7] is None:
# Don't announce anything that doesn't have source, patch or sign.
continue
self.send_release_email(chunks)
known_new.append(release)
fh = open(self.release_tracker, 'w')
json.dump(known_new, fh, indent=4)
fh.close()
def send_release_email(self, chunks):
(label, release, iseol, timestamp, isodate, source, sign, patch, incr, changelog, gitweb) = chunks
if iseol:
eol = ' (EOL)'
else:
eol = ''
import smtplib
from email.mime.text import MIMEText
from email.Utils import COMMASPACE, formatdate, make_msgid
body = ( "Linux kernel version %s%s has been released. It is available from:\r\n" % (release, eol)
+ "\r\n"
+ "Patch: https://www.kernel.org/%s\r\n" % patch
+ "Full source: https://www.kernel.org/%s\r\n" % source
+ "PGP Signature: https://www.kernel.org/%s\r\n" % sign
+ "\r\n"
+ "-----------------------------------------------------------------------------\r\n"
+ "This is an automatically generated message. To unsubscribe from this list,\r\n"
+ "please send a message to majordomo@vger.kernel.org containing the line:\r\n"
+ "\r\n"
+ "\tunsubscribe linux-kernel-announce <your_email_address>\r\n"
+ "\r\n"
+ "... where <your_email_address> is the email address you used to subscribe\r\n"
+ "to this list.\r\n"
+ "-----------------------------------------------------------------------------\r\n"
+ "\r\n"
+ "You can view the summary of the changes at the following URL:\r\n"
+ "https://www.kernel.org/diff/diffview.cgi?file=/%s\r\n" % patch)
msg = MIMEText(body)
msg['Subject'] = "Linux kernel %s released" % release
msg['From'] = 'Linux Kernel Distribution System <kdist@linux.kernel.org>'
msg['To'] = 'linux-kernel-announce@vger.kernel.org'
msg['Bcc'] = 'ftpadmin@kernel.org'
msg['Date'] = formatdate(localtime=True)
msg['Message-Id'] = make_msgid('kdist.linux.kernel.org')
msg['X-Linux-Kernel-Version'] = release
msg['X-Linux-Kernel-Patch-URL'] = "https://www.kernel.org/%s" % patch
msg['X-Linux-Kernel-Full-URL'] = "https://www.kernel.org/%s" % source
s = smtplib.SMTP('mail.kernel.org')
s.sendmail('kdist@linux.kernel.org', ['linux-kernel-announce@vger.kernel.org', 'ftpadmin@kernel.org'], msg.as_string())
s.quit()
def generate_releases_json(self):
# Put release info into a .json file for easy import and parse
out = {'releases': []}
for (label, release, iseol, timestamp, isodate, source, sign, patch, incr, changelog, gitweb) in self.current_releases:
if source:
source = 'https://www.kernel.org/%s' % source
if sign:
sign = 'https://www.kernel.org/%s' % sign
if patch:
patch = 'https://www.kernel.org/%s' % patch
if incr:
incr = 'https://www.kernel.org/%s' % incr
if changelog:
changelog = 'https://www.kernel.org/%s' % changelog
relhash = {
'moniker': label,
'version': release,
'iseol': iseol,
'released': {
'timestamp': timestamp,
'isodate': isodate,
},
'source': source,
'pgp': sign,
'patch': {
'full': patch,
'incremental': incr,
},
'changelog': changelog,
'gitweb': gitweb
}
out['releases'].append(relhash)
out['latest_stable'] = {
'version': self.latest_release[1],
}
fh = open(self.json_path, 'w')
json.dump(out, fh, indent=4)
fh.close()
def generate_finger_banner(self):
# Just a bit of legacy silliness
contents = ''
for chunks in self.current_releases:
label = chunks[0]
release = chunks[1]
iseol = chunks[2]
if label == 'mainline':
line = ' 3'
elif label == 'linux-next':
line = ''
else:
bits = release.split('.')
bits.pop(-1)
line = ' ' + '.'.join(bits)
if iseol:
eol = ' (EOL)'
else:
eol = ''
leftside = 'The latest %s%s version of the Linux kernel is:' % (label, line)
contents += '{0:<61} {1}{2}\n'.format(leftside, release, eol)
fp = open(self.finger_path, 'w')
fp.write(contents)
fp.close()
def generate_rss_feed(self):
feed = Rss201rev2Feed(
title='Latest Linux Kernel Versions',
link='http://www.kernel.org',
feed_url='http://www.kernel.org/feeds/kdist.xml',
description='Latest Linux Kernel Versions',
creator='FTP Admin <ftpadmin@kernel.org>'
)
for (label, release, iseol, timestamp, isodate, source, sign, patch, incr, changelog, gitweb) in self.current_releases:
if iseol:
eol = ' (EOL)'
else:
eol = ''
contents = '''
<table>
<tr><th align="right">Version:</th><td><strong>%s%s</strong> (%s)</td></tr>
<tr><th align="right">Released:</th><td>%s</td></tr>
''' % (release, eol, label, isodate)
if source:
contents += '''
<tr><th align="right">Source:</th><td><a href="https://www.kernel.org/%s">linux-%s.tar.xz</a></td></tr>''' % (source, release)
if sign:
contents += '''
<tr><th align="right">PGP Signature:</th><td><a href="https://www.kernel.org/%s">linux-%s.tar.sign</a></td></tr>''' % (sign, release)
if patch:
contents += '''
<tr><th align="right">Patch:</th><td><a href="https://www.kernel.org/%s">patch-%s.xz</a>''' % (patch, release)
if incr:
contents += ''' (<a href="https://www.kernel.org/%s">Incremental</a>)''' % incr
contents += '''</td></tr>'''
if changelog:
contents += '''
<tr><th align="right">ChangeLog:</th><td><a href="https://www.kernel.org/%s">ChangeLog-%s</a></td></tr>''' % (changelog, release)
contents += '''
</table>'''
feed.add_item(
title='%s: %s' % (release, label),
link='http://www.kernel.org/',
unique_id='kernel.org,%s,%s,%s' % (label, release, isodate),
description=contents,
pubdate=datetime.datetime.fromtimestamp(timestamp)
)
# We really should be generating after site is done,
# but I'm too lazy to figure out the plugin hooks for that
utils.mkdir_p(os.path.dirname(self.rss_path))
fp = open(self.rss_path, 'w')
feed.write(fp, 'utf-8')
fp.close()
def make_release_line(self, rel, label, iseol=False):
# drop the leading 'v':
if rel[0][0] == 'v':
release = rel[0][1:]
else:
release = rel[0]
timestamp = rel[1]
isodate = time.strftime('%Y-%m-%d', time.gmtime(rel[1]))
# some magic here to calculate where the source is
urlpath = 'pub/linux/kernel'
if release.find('3.') == 0:
# This is version 3.x, so will be in /v3.x/
urlpath += '/v3.x'
if release.find('-rc') > 0:
# This is a testing kernel, so will be in /testing/
urlpath += '/testing'
elif release.find('2.6') == 0:
# We're hardcoding ourselves here, but this will rarely change
urlpath += '/v2.6/longterm'
if release.find('2.6.32') == 0:
urlpath += '/v2.6.32'
else:
urlpath += '/v2.6.34'
else:
# Next-kernels are too unpredictable to bother
urlpath = None
source = None
sign = None
patch = None
incr = None
changelog = None
gitweb = None
if urlpath is not None:
source = '%s/linux-%s.tar.xz' % (urlpath, release)
sign = '%s/linux-%s.tar.sign' % (urlpath, release)
patch = '%s/patch-%s.xz' % (urlpath, release)
if label.find('stable') == 0 or label.find('longterm') == 0:
changelog = '%s/ChangeLog-%s' % (urlpath, release)
gitweb = 'https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/log/?id=refs/tags/v%s' % release
# incr patches are named incr/3.5.(X-1)-(X).xz, which we calculate here
try:
bits = release.split('.')
lastbit = int(bits.pop(-1))
if lastbit > 1:
prevbit = str(lastbit - 1)
bits.append(prevbit)
prevrel = '.'.join(bits)
incr = '%s/incr/patch-%s-%s.xz' % (urlpath, prevrel, lastbit)
except ValueError:
incr = None
elif label.find('mainline') == 0:
gitweb = 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?id=refs/tags/v%s' % release
if label.find('linux-next') == 0:
gitweb = 'https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/log/?id=refs/tags/%s' % release
# Verify that source, patch, changelog and incremental patch exist
if source and not os.path.exists('%s/%s' % (self.pub_mount, source)):
source = None
if patch and not os.path.exists('%s/%s' % (self.pub_mount, patch)):
patch = None
if changelog and not os.path.exists('%s/%s' % (self.pub_mount, changelog)):
changelog = None
if incr and not os.path.exists('%s/%s' % (self.pub_mount, incr)):
incr = None
# This needs to be refactored into a hash. In my defense,
# it started with 3 entries.
return (label, release, iseol, timestamp, isodate, source, sign, patch, incr, changelog, gitweb)
def get_tagref_list(self, repo, cutoff=None):
tagrefs = []
for tagref in repo.tags:
try:
tdate = tagref.tag.tagged_date
except:
# Some of the tags break git-python due to not having an associated
# commit. Work around this limitation by ignoring those tags.
continue
# Is it too old?
if cutoff is not None and tdate < cutoff:
continue
tagrefs.append(tagref)
return tagrefs
def find_latest_matching(self, tagrefs, regex):
current = None
for tagref in tagrefs:
tdate = tagref.tag.tagged_date
# Does it match the regex?
if not regex.match(tagref.name):
continue
# is it older than current?
if current is None or current.tag.tagged_date < tdate:
current = tagref
if current is None:
return None
return (current.name, current.tag.tagged_date)