summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'BuildTools/GenerateAppCastFeeds.py')
-rwxr-xr-xBuildTools/GenerateAppCastFeeds.py153
1 files changed, 153 insertions, 0 deletions
diff --git a/BuildTools/GenerateAppCastFeeds.py b/BuildTools/GenerateAppCastFeeds.py
new file mode 100755
index 0000000..8135134
--- /dev/null
+++ b/BuildTools/GenerateAppCastFeeds.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python2
+
+# This script generates three app cast feeds for macOS Sparkle updates from Swift releases in the download folder on the Swift website.
+
+from xml.etree import ElementTree as ET
+import argparse
+import datetime
+import email.utils as eut
+import fnmatch
+import jinja2
+import os.path
+import re
+import time
+import urllib2
+import urlparse
+
+class Release:
+ def __init__(self, version, absoluteURL, sizeInBytes, date):
+ # This is the version string used for update detection.
+ self.fullVersion = version.split('-', 1)[1]
+ # This is a human readable version string, only used for presentation.
+ self.presentationVersion = version
+ self.url = absoluteURL
+ self.sizeInBytes = sizeInBytes
+ self.date = date
+ dateTumple = date.timetuple()
+ dateTimestamp = time.mktime(dateTumple)
+ self.dateString = eut.formatdate(dateTimestamp)
+
+ def __str__(self):
+ return "Release(%s, %s, %s, %s)" % (self.fullVersion, self.url, self.sizeInBytes, self.date)
+
+ def __repr__(self):
+ return "Release(%s, %s, %s, %s)" % (self.fullVersion, self.url, self.sizeInBytes, self.date)
+
+def getReleaseFromAbsoluteFilePath(absolutePath, downloadsFolder, releasesURL):
+ version = os.path.splitext(absolutePath.split('/')[-1])[0]
+ sizeInBytes = os.path.getsize(absolutePath)
+ date = datetime.datetime.fromtimestamp(os.path.getmtime(absolutePath))
+ absoluteURL = urlparse.urljoin(releasesURL, os.path.relpath(absolutePath, downloadsFolder))
+ return Release(version, absoluteURL, sizeInBytes, date)
+
+def getReleaseFromReleaseFolder(releaseFolder, downloadsFolder, releasesURL, extension):
+ release = None
+ regex = re.compile(fnmatch.translate(extension))
+
+ files = [f for f in os.listdir(releaseFolder) if os.path.isfile(os.path.join(releaseFolder, f))]
+ for file in files:
+ fileFullPath = os.path.join(releaseFolder, file)
+ if regex.match(fileFullPath):
+ release = getReleaseFromAbsoluteFilePath(fileFullPath, downloadsFolder, releasesURL)
+ return release
+
+def getReleaseFilesInReleasesFolder(releasesFolder, releasesURL, extension):
+ releases = []
+
+ dirs = [d for d in os.listdir(releasesFolder) if os.path.isdir(os.path.join(releasesFolder, d))]
+ for d in dirs:
+ release = getReleaseFromReleaseFolder(os.path.join(releasesFolder, d), releasesFolder, releasesURL, extension)
+ if release:
+ releases.append(release)
+
+ return releases
+
+def getReleaseFilesInDevelopmentFolder(developmentMacFolder, developmentMacURL, extension):
+ extensionRegex = re.compile(fnmatch.translate(extension))
+ devPatternRegex = re.compile(".+-dev\d+")
+
+ releases = []
+
+ files = [f for f in os.listdir(developmentMacFolder) if os.path.isfile(os.path.join(developmentMacFolder, f))]
+ for f in files:
+ # Only use dev builds from the development folder.
+ if devPatternRegex.match(f):
+ fileFullPath = os.path.join(developmentMacFolder, f)
+ if extensionRegex.match(fileFullPath):
+ releases.append(getReleaseFromAbsoluteFilePath(fileFullPath, developmentMacFolder, developmentMacURL))
+
+ return releases
+
+def writeAppcastFile(filename, title, description, regexPattern, appcastURL, releases):
+ template = jinja2.Template('''<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
+ <channel>
+ <title>{{ title }}</title>
+ <link>{{ appcast_url }}</link>
+ <description>{{ description }}</description>
+ <language>en</language>
+ {% for item in releases %}<item>
+ <title>Swift version {{ item.fullVersion }}</title>
+ <pubDate>{{ item.dateString }}</pubDate>
+ <enclosure url="{{ item.url }}"
+ sparkle:version="{{ item.fullVersion }}"
+ sparkle:shortVersionString="{{ item.presentationVersion }}"
+ length="{{ item.sizeInBytes }}"
+ type="application/octet-stream" />
+ </item>
+ {% endfor %}</channel>
+</rss>''')
+
+ matchingReleases = [i for i in releases if re.match(regexPattern, i.fullVersion)]
+ matchingReleases = matchingReleases[:2] # only include the first two matches in the appcast
+
+ appcastContent = template.render(title=title, appcast_url=appcastURL, description=description, releases=matchingReleases)
+
+ contentParsesOK = False
+ try:
+ x = ET.fromstring(appcastContent)
+ contentParsesOK = True
+ except :
+ contentParsesOK = False
+
+ if contentParsesOK:
+ with open(filename, 'w') as file:
+ file.write(appcastContent)
+ else:
+ print("Failed to generate valid appcast feed %s." % filename)
+
+parser = argparse.ArgumentParser(description='Generate stable/testing/development appcast feeds for Sparkle updater.')
+parser.add_argument('downloadsFolder', type=str, help="e.g. /Users/foo/website/downloads/")
+parser.add_argument('downloadsURL', type=str, help="e.g. https://swift.im/downloads/")
+parser.add_argument('outputFolder', type=str, help="e.g. /Users/foo/website/downloads/")
+
+args = parser.parse_args()
+
+releasesPath = os.path.join(args.downloadsFolder, "releases")
+developmentMacPath = os.path.join(args.downloadsFolder, "development", "mac")
+
+manualReleases = getReleaseFilesInReleasesFolder(releasesPath, urlparse.urljoin(args.downloadsURL, "releases/"), "*.dmg")
+manualReleases.sort(key=lambda release: release.date, reverse=True)
+
+automaticReleases = manualReleases
+automaticReleases.extend(getReleaseFilesInDevelopmentFolder(developmentMacPath, urlparse.urljoin(args.downloadsURL, "development/mac/"), "*.dmg"))
+automaticReleases.sort(key=lambda release: release.date, reverse=True)
+
+
+writeAppcastFile(filename=os.path.join(args.outputFolder, "swift-stable-appcast-mac.xml"),
+ title="Swift Stable Releases",
+ description="",
+ regexPattern="^\d+(\.\d+)?(\.\d+)?$",
+ appcastURL=urlparse.urljoin(args.downloadsURL, "swift-stable-appcast-mac.xml"),
+ releases=manualReleases)
+writeAppcastFile(filename=os.path.join(args.outputFolder, "swift-testing-appcast-mac.xml"),
+ title="Swift Testing Releases",
+ description="",
+ regexPattern="^\d+(\.\d+)?(\.\d+)?(beta\d+)?(rc\d+)?$",
+ appcastURL=urlparse.urljoin(args.downloadsURL, "swift-testing-appcast-mac.xml"),
+ releases=manualReleases)
+writeAppcastFile(filename=os.path.join(args.outputFolder, "swift-development-appcast-mac.xml"),
+ title="Swift Development Releases",
+ description="",
+ regexPattern="^\d+(\.\d+)?(\.\d+)?(alpha)?(beta\d+)?(rc\d+)?(-dev\d+)?$",
+ appcastURL=urlparse.urljoin(args.downloadsURL, "swift-development-appcast-mac.xml"),
+ releases=automaticReleases)