Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added appcast and release notes for the ShiftIt-develop-1.6.zip release

  • Loading branch information...
commit a434fe0e5df527ae7132f20c9144001d06ed546a 1 parent f4c111d
@fikovnik authored
View
2  .gitignore
@@ -1,3 +1,3 @@
-release-notes-*
ShiftIt-*.zip
misc
+fabfile.pyc
View
0  build/.gitkeep
No changes.
View
381 fabfile.py
@@ -1,118 +1,242 @@
-from fabric.api import *
+from fabric.api import local, execute, abort, task, lcd, puts
from fabric.contrib.console import confirm
+from xml.etree import ElementTree
import os
-import base64
-import tempfile
import pystache
-import ftplib
-import urllib
-from StringIO import StringIO
-from xml.etree import ElementTree as et
-from urlparse import urlparse
-from github2.client import Github
-from datetime import datetime
+import github3
+import tempfile
+import base64
+import datetime
##
# Configuration
##
-project_name = 'ShiftIt'
-src_dir = 'ShiftIt'
-private_key = '/Users/krikava/Dropbox/Personal/Keys/ShiftIt/dsa_priv.pem'
-archive_name_template = project_name + '-{version}.zip'
-appcast_url = 'http://fikovnik.net/projects/shiftit/appcast/profileInfo.php'
-appcast_ftpurl = 'ftp://fikovnik.net/www/projects/shiftit/appcast/appcast.xml'
-release_notes_ftpurl_template = 'ftp://fikovnik.net/www/projects/shiftit/release-notes-{version}.html'
-release_notes_url_template = 'http://fikovnik.net/projects/shiftit/release-notes-{version}.html'
-download_url_template = 'http://fikovnik.net/projects/shiftit/downloads/' + archive_name_template
-download_ftpurl_template = 'ftp://fikovnik.net/www/projects/shiftit/downloads/' + archive_name_template
-gitub_keychain_item = 'github'
-fikovnik_ftp_keychain_item = 'fikovnik.net'
+proj_name = 'ShiftIt'
+proj_info_plist = 'ShiftIt-Info.plist'
+proj_src_dir = 'ShiftIt'
+proj_private_key = '/Users/krikava/Dropbox/Personal/Keys/ShiftIt/dsa_priv.pem'
+
release_notes_template = '''
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<body>
-<h1>Version {{version}}</h1>
+<h1>{{proj_name}} version {{proj_version}}</h1>
-<h2>Changes</h2>
-{{#issues}}
+{{#devel}}
+ <b>This is a development release that is intended for testing purposes only!</b>
+{{/devel}}
+
+{{#has_issues}}
+<h2>Issues closed</h2>
<ul>
- <li><b>#{{number}}</b> - <a href="{{html_url}}">{{title}}</a><li>
-</ul>
+{{#issues}}
+ <li><a href="{{html_url}}"><b>#{{number}}</b></a> - {{title}}</li>
{{/issues}}
+</ul>
+{{/has_issues}}
-If you find any bug please report them in <a href="http://github.com/fikovnik/ShiftIt/issues">github</a>
+More information about this release can be found on the <a href="{{milestone_url}}">here</a>.
+<br/><br/>
+If you find any bugs please report them on <a href="http://github.com/fikovnik/ShiftIt/issues">github</a>.
</body>
</html>
-'''
+'''.strip()
+
+appcast_template = '''
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <channel>
+ <title>{{proj_name}} Changelog</title>
+ <link>{{proj_appcast_url}}</link>
+ <language>en</language>
+ <item>
+ <title>{{proj_name}} version {{proj_version}}</title>
+ <sparkle:releaseNotesLink>
+ {{proj_release_notes_url}}
+ </sparkle:releaseNotesLink>
+ <pubDate>{{date}}</pubDate>
+ <enclosure url="{{download_url}}" sparkle:version="{{proj_version}}" length="{{download_size}}" type="application/octet-stream" sparkle:dsaSignature="{{download_signature}}" />
+ </item>
+ </channel>
+</rss>
+'''.strip()
+
+##
+# Code
+##
+
+def _find(f, seq):
+ """Return first item in sequence where f(item) == True."""
+ for item in seq:
+ if f(item):
+ return item
+
+def _get_bundle_version(info_plist):
+ version = local('defaults read %s CFBundleVersion' % info_plist, capture=True)
+ return version.strip()
+
+def _get_git_branch():
+ branch = local('git symbolic-ref HEAD', capture=True)
+ return branch[len('refs/heads/'):].strip()
+
+def _sign(path, private_key, public_key):
+ sign_file = tempfile.mktemp()
+
+ local('openssl dgst -sha1 -binary < %s | openssl dgst -dss1 -sign %s > %s'
+ % (path, private_key, sign_file))
+ local('openssl dgst -sha1 -binary < %s | openssl dgst -dss1 -verify %s -signature %s'
+ % (path, public_key, sign_file))
+
+ signature = None
+ with open(sign_file) as f:
+ signature = base64.b64encode(f.read())
+
+ os.remove(sign_file)
+
+ return signature
-src_dir = os.path.join(os.getcwd(), src_dir)
-app_dir = os.path.join(src_dir,'build','Release',project_name+'.app')
-info_plist_path = os.path.join(app_dir,'Contents','Info.plist')
-public_key = os.path.join(src_dir,'dsa_pub.pem')
+## check if we are clean
+
+# if not local('git diff-index --quiet HEAD --').return_code:
+# abort('There are pending changes in the repository. Run git status')
+
+## settings
+proj_branch = _get_git_branch()
+proj_is_dev = not proj_branch.startswith('release')
+proj_src_dir = os.path.join(os.getcwd(), proj_src_dir)
+proj_build_dir = os.path.join(os.getcwd(), 'build')
+proj_app_dir = os.path.join(proj_src_dir,'build','Release',proj_name+'.app')
+proj_public_key = os.path.join(proj_src_dir,'dsa_pub.pem')
+proj_info_plist = os.path.join(proj_src_dir, proj_info_plist)
+
+proj_version = _get_bundle_version(proj_info_plist)
+proj_archive_tag = '-develop' if proj_is_dev else ''
+proj_archive_name = proj_name + proj_archive_tag + '-' + proj_version + '.zip'
+proj_archive_path = os.path.join(proj_build_dir, proj_archive_name)
+
+proj_download_url = 'https://github.com/downloads/fikovnik/ShiftIt/'+proj_archive_name
+proj_release_notes_url = 'http://htmlpreview.github.com/?https://raw.github.com/fikovnik/ShiftIt/'+proj_branch+'/release/release-notes-'+proj_version+'.html'
+proj_release_notes_file = os.path.join(os.getcwd(),'release','release-notes-'+proj_version+'.html')
+proj_appcast_url = 'https://raw.github.com/fikovnik/ShiftIt/'+proj_branch+'/release/appcast.xml'
+proj_appcast_file = os.path.join(os.getcwd(),'release','appcast.xml')
+
+@task
+def info():
+ print 'Build info:'
+ for (k,v) in [(k,v) for (k,v) in globals().items() if k.startswith('proj_')]:
+ print "\t%s: %s" % (k[len('proj_'):],v)
@task
def build():
- _xcodebuild(src_dir, project_name)
+ '''
+ Makes a build by executing xcodebuild
+ '''
+
+ with lcd(proj_src_dir):
+ local('xcodebuild -target %s -configuration Release' % proj_name)
@task
def archive():
+ '''
+ Archives build
+ '''
+
# dependencies
execute(build)
- version = _get_bundle_version()
- archive_path = archive_name_template.format(version=version)
+ local('ditto -ck --keepParent %s %s' % (proj_app_dir, proj_archive_path))
- _pack(app_dir, archive_path)
+@task
+def prepare_release():
+ # prerequisites
+
+ # the appcast URL matches
+ tree = ElementTree.parse(proj_info_plist)
+ root = tree.getroot().find('dict')
+ elem = list(root.findall('*'))
+
+ plist_appcast_url = _find(lambda (k,v): k.text == 'SUFeedURL', zip(*[iter(elem)]*2))[1].text.strip()
+ if plist_appcast_url != proj_appcast_url:
+ abort('Appcasts are different! Expected: `%s`, got: `%s`' % (proj_appcast_url, plist_appcast_url))
+
+ # dependencies
+ execute(archive)
+
+ appcast = dict( \
+ proj_name=proj_name, \
+ proj_appcast_url=proj_appcast_url, \
+ proj_version=proj_version, \
+ proj_release_notes_url=proj_release_notes_url, \
+ date=datetime.datetime.now().strftime('%a, %d %b %G %T %z'), \
+ download_url=proj_download_url, \
+ download_size=os.path.getsize(proj_archive_path), \
+ download_signature=_sign(proj_archive_path, proj_private_key, proj_public_key), \
+ )
+
+ puts('Appcast properties:')
+ for (k,v) in appcast.items():
+ print "\t%s: %s" % (k,v)
+
+ appcast_str = pystache.render(appcast_template, appcast)
+ release_notes_str = _gen_release_notes()
+
+ puts('Following will update appcast and release-notes, COMMIT and PUSH TO ORIGIN!')
+ if not confirm('Proceed with release (make sure you know what are you doing!)?'):
+ return
+
+ with open(proj_appcast_file,"w") as f:
+ f.write(appcast_str)
+
+ with open(proj_release_notes_file,"w") as f:
+ f.write(release_notes_str)
+
+ local('git add %s' % proj_appcast_file)
+ local('git add %s' % proj_release_notes_file)
+ local('git commit -m "Added appcast and release notes for the %s release"' % proj_archive_name)
+ local('git push origin %s' % proj_branch)
@task
-def release():
+def upload_release():
# dependencies
- #execute(archive)
+ execute(archive)
- version = _get_bundle_version()
- archive_path = archive_name_template.format(version=version)
- appcast = AppCast(appcast_url)
- appcast.add_version(version, \
- release_notes_url_template.format(version=version), \
- download_url_template.format(version=version), \
- datetime.now().strftime('%a, %d %b %G %T %z'), \
- _sign(archive_path, private_key, public_key), \
- os.path.getsize(archive_path))
+ if not confirm('Proceed with upload?'):
+ return
- appcast_str = appcast.to_string()
- release_notes_str = _gen_release_notes(version)
+ github = _github()
+ shiftit = github.repository('fikovnik','ShiftIt')
- release_notes_ftpurl = release_notes_ftpurl_template.format(version=version)
- download_ftpurl = download_ftpurl_template.format(version=version)
+ download = _find(lambda d: d.name == proj_archive_name, shiftit.iter_downloads())
+ if download:
+ if not confirm('Download %s (id: %s, size: %s bytes) already exists. Override?' % (download.name, download.id, download.size)):
+ return
+ else:
+ puts('Deleting download: %s (%s)' % (download.name, download.id))
+ download.delete()
- puts('Version: '+version)
- puts('Archive: '+archive_path)
- puts('Appcast:')
- puts(appcast_str)
- puts('Release Notes:')
- puts(release_notes_str)
- puts('Upload file:')
- puts('\n'.join([appcast_ftpurl, release_notes_ftpurl, download_ftpurl]))
+ download = shiftit.create_download(proj_archive_name, proj_archive_path)
- if confirm('Proceed with upload?'):
- ftp_username = _keychain_get_username(fikovnik_ftp_keychain_item)
- ftp_password = _keychain_get_password(fikovnik_ftp_keychain_item)
+ if not download:
+ raise Exception('Unable to upload')
- _ftp_put(StringIO(appcast_str), appcast_ftpurl,
- ftp_username, ftp_password)
+ puts('Uploaded: %s (id: %s, size: %s bytes): %s' % (download.name, download.id, download.size, download.html_url))
+ proj_download_url=download.html_url
- _ftp_put(StringIO(release_notes_str), release_notes_ftpurl,
- ftp_username, ftp_password)
+@task
+def release():
+ # dependencies
+ execute(prepare_release)
+ execute(upload_release)
- _ftp_put(open(archive_path,'rb'), download_ftpurl,
- ftp_username, ftp_password)
- print _gen_release_notes(version)
+@task
+def print_release_notes():
+ puts(_gen_release_notes())
-def _gen_release_notes(version):
+def _gen_release_notes():
def _convert(i):
return { \
'number': i.number, \
@@ -120,106 +244,49 @@ def _convert(i):
'title': i.title, \
}
- github = Github(_keychain_get_username(gitub_keychain_item),
- _keychain_get_password(gitub_keychain_item))
+ github = _github()
+ shiftit = github.repository('fikovnik','ShiftIt')
- issues = github.issues.list('fikovnik/ShiftIt',state='closed')
- issues = [e for e in issues if 'v'+version in e.labels]
- issues.sort(key=lambda i: i.closed_at)
+ milestone = _find(lambda m: proj_version.startswith(m.title), shiftit.iter_milestones())
+ if not milestone:
+ raise Exception('Unable to find milestone: %s' % proj_version)
- return pystache.render(release_notes_template, \
- version=version, \
- issues=[_convert(e) for e in issues])
-
-def _get_bundle_version():
- version = local('defaults read %s CFBundleVersion' % info_plist_path, capture=True)
- return version.strip()
-def _pack(src, dest):
- local('ditto -ck --keepParent %s %s' % (src, dest))
+ open_issues = list(shiftit.iter_issues(milestone=milestone.number, state='open'))
+ if len(open_issues) > 0 and not proj_is_dev:
+ puts('Warning: there are still open issues')
+ for i in open_issues:
+ print '\t * #%s: %s' % (i.number, i.title)
-def _xcodebuild(src_dir, target):
- with lcd(src_dir):
- local('xcodebuild -target %s -configuration Release' % target)
+ closed_issues = list(shiftit.iter_issues(milestone=milestone.number, state='closed'))
+ closed_issues.sort(key=lambda i: i.closed_at)
-def _sign(path, private_key, public_key):
- sign_file = tempfile.mktemp()
+ release_notes = dict( \
+ has_issues = len(closed_issues) > 0, \
+ issues = closed_issues, \
+ proj_name=proj_name, \
+ proj_version=proj_version, \
+ devel=proj_is_dev, \
+ milestone_url='https://github.com/fikovnik/ShiftIt/issues?milestone=%d' % milestone.number, \
+ )
- local('openssl dgst -sha1 -binary < %s | openssl dgst -dss1 -sign %s > %s'
- % (path, private_key, sign_file))
- local('openssl dgst -sha1 -binary < %s | openssl dgst -dss1 -verify %s -signature %s'
- % (path, public_key, sign_file))
+ puts('Release notes properties:')
+ for (k,v) in release_notes.items():
+ print "\t%s: %s" % (k,v)
- signature = None
- with open(sign_file) as f:
- signature = base64.b64encode(f.read())
+ return pystache.render(release_notes_template, release_notes)
- os.remove(sign_file)
+def _github():
+ return github3.login(_keychain_get_username('github.com'),
+ _keychain_get_password('github.com'))
- return signature
def _keychain_get_username(account):
- username = local("security find-generic-password -l %s | grep 'acct' | " \
+ username = local("security find-internet-password -l %s | grep 'acct' | " \
"cut -d '\"' -f 4" % account, capture=True)
return username
def _keychain_get_password(account):
- password = local("security 2>&1 > /dev/null find-generic-password -g -l" \
+ password = local("security 2>&1 > /dev/null find-internet-password -g -l" \
" %s | cut -d '\"' -f 2" % account, capture=True)
return password
-
-def _ftp_put(f, url, username, password):
- r = urlparse(url)
- host = r.netloc
- dirname = os.path.dirname(r.path)
- basename = os.path.basename(r.path)
-
- puts('Uploading to %s@%s:%s/%s' % (username, host, dirname, basename))
-
- ftp = ftplib.FTP()
- try:
- ftp.connect(host)
- ftp.login(username, password)
- ftp.cwd(dirname)
- ftp.storlines('STOR %s' % basename, f)
- finally:
- ftp.quit()
-
-
-class AppCast:
-
- _appcast_template = '''<?xml version="1.0" encoding="utf-8"?>
-<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
- <item>
- <title>Version {version}</title>
- <sparkle:releaseNotesLink>{release_notes_url}</sparkle:releaseNotesLink>
- <pubDate>{pub_date}</pubDate>
- <enclosure
- url="{download_url}"
- sparkle:version="{version}"
- length="{size}"
- type="application/octet-stream"
- sparkle:dsaSignature="{signature}" />
- </item>
-</rss>'''
-
- def __init__(self,url):
- et.register_namespace('sparkle','http://www.andymatuschak.org/xml-namespaces/sparkle')
- self.url = url
- self.appcast = et.parse(urllib.urlopen(url))
-
- def add_version(self, version, release_notes_url, download_url, pub_date,
- signature, size):
-
- fragment = et.XML(self._appcast_template.format( \
- version = version,\
- release_notes_url = release_notes_url,\
- download_url = download_url,\
- pub_date = pub_date,\
- signature = signature,\
- size = size))
-
- self.appcast.find('channel').append(fragment.find('item'))
-
- def to_string(self):
- return et.tostring(self.appcast.getroot())
View
4 release/appcast.xml
@@ -9,8 +9,8 @@
<sparkle:releaseNotesLink>
http://htmlpreview.github.com/?https://raw.github.com/fikovnik/ShiftIt/develop/release/release-notes-1.6.html
</sparkle:releaseNotesLink>
- <pubDate>Thu, 29 Nov 2012 01:00:38 </pubDate>
- <enclosure url="https://github.com/downloads/fikovnik/ShiftIt/ShiftIt-develop-1.6.zip" sparkle:version="1.6" length="749580" type="application/octet-stream" sparkle:dsaSignature="MC0CFFMS93BksDQ/IwONv+9gpwgT41OKAhUAmWAUMhoHENw8RSsKes8vgssVwZA=" />
+ <pubDate>Thu, 29 Nov 2012 01:13:47 </pubDate>
+ <enclosure url="https://github.com/downloads/fikovnik/ShiftIt/ShiftIt-develop-1.6.zip" sparkle:version="1.6" length="749580" type="application/octet-stream" sparkle:dsaSignature="MC0CFQDEXCKJrwfHh3FYw8QmHVMpCvio1gIUJWp4TofHlEZ4YNFeZsS3V/2wW+M=" />
</item>
</channel>
</rss>
Please sign in to comment.
Something went wrong with that request. Please try again.