##// END OF EJS Templates
updates to release scripts...
MinRK -
Show More
@@ -1,49 +1,27 b''
1 1 #!/usr/bin/env python
2 2 """IPython release build script.
3 3 """
4 4
5 5 import os
6 6
7 7 from toollib import *
8 8
9 # The windows builds are fairly painful to set up on a posix system via wine,
10 # so by default we've disabled them and we just build the windows installers
11 # separately in a true Windows VM.
12 do_windows = False
13
14 # Egg building is also disabled by default. They serve no real purpose in
15 # python2, and getting a setupegg.py file that has valid python2/3 syntax is a
16 # pain in the ass. Since the python devs were too stubborn to leave execfile()
17 # in place in python3, then we just don't build eggs.
18 do_eggs = False
19
20 9 # Get main ipython dir, this will raise if it doesn't pass some checks
21 10 ipdir = get_ipdir()
22 11 cd(ipdir)
23 12
24 13 # Load release info
25 14 execfile(pjoin('IPython', 'core', 'release.py'))
26 15
27 16 # Check that everything compiles
28 17 compile_tree()
29 18
30 19 # Cleanup
31 20 for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'),
32 21 pjoin('docs', 'source', 'api', 'generated')]:
33 22 if os.path.isdir(d):
34 23 remove_tree(d)
35 24
36 25 # Build source and binary distros
37 26 sh(sdists)
38
39 # Build eggs
40 if do_eggs:
41 sh(eggs)
42
43 if do_windows:
44 map(sh, win_builds)
45 # Change name so retarded Vista runs the installer correctly
46 sh("rename 's/linux-i686/win32/' dist/*.exe")
47 sh("rename 's/linux-x86_64/win32/' dist/*.exe")
48 # exe files aren't really executable under *nix.
49 sh("chmod -x dist/*.exe")
27 sh(wheels)
@@ -1,224 +1,224 b''
1 1 #!/usr/bin/env python
2 2 """Simple tools to query github.com and gather stats about issues.
3 3
4 4 To generate a report for IPython 2.0, run:
5 5
6 6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Imports
10 10 #-----------------------------------------------------------------------------
11 11
12 12 from __future__ import print_function
13 13
14 14 import codecs
15 15 import sys
16 16
17 17 from argparse import ArgumentParser
18 18 from datetime import datetime, timedelta
19 19 from subprocess import check_output
20 20
21 21 from gh_api import (
22 22 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
23 23 get_milestone_id, get_issues_list, get_authors,
24 24 )
25 25 #-----------------------------------------------------------------------------
26 26 # Globals
27 27 #-----------------------------------------------------------------------------
28 28
29 29 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
30 30 PER_PAGE = 100
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 def round_hour(dt):
37 37 return dt.replace(minute=0,second=0,microsecond=0)
38 38
39 39 def _parse_datetime(s):
40 40 """Parse dates in the format returned by the Github API."""
41 41 if s:
42 42 return datetime.strptime(s, ISO8601)
43 43 else:
44 44 return datetime.fromtimestamp(0)
45 45
46 46 def issues2dict(issues):
47 47 """Convert a list of issues to a dict, keyed by issue number."""
48 48 idict = {}
49 49 for i in issues:
50 50 idict[i['number']] = i
51 51 return idict
52 52
53 53 def split_pulls(all_issues, project="ipython/ipython"):
54 54 """split a list of closed issues into non-PR Issues and Pull Requests"""
55 55 pulls = []
56 56 issues = []
57 57 for i in all_issues:
58 58 if is_pull_request(i):
59 59 pull = get_pull_request(project, i['number'], auth=True)
60 60 pulls.append(pull)
61 61 else:
62 62 issues.append(i)
63 63 return issues, pulls
64 64
65 65
66 66 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
67 67 """Get all issues closed since a particular point in time. period
68 68 can either be a datetime object, or a timedelta object. In the
69 69 latter case, it is used as a time before the present.
70 70 """
71 71
72 72 which = 'pulls' if pulls else 'issues'
73 73
74 74 if isinstance(period, timedelta):
75 75 since = round_hour(datetime.utcnow() - period)
76 76 else:
77 77 since = period
78 78 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
79 79 allclosed = get_paged_request(url, headers=make_auth_header())
80 80
81 81 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
82 82 if pulls:
83 83 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
84 84 # filter out PRs not against master (backports)
85 85 filtered = [ i for i in filtered if i['base']['ref'] == 'master' ]
86 86 else:
87 87 filtered = [ i for i in filtered if not is_pull_request(i) ]
88 88
89 89 return filtered
90 90
91 91
92 92 def sorted_by_field(issues, field='closed_at', reverse=False):
93 93 """Return a list of issues sorted by closing date date."""
94 94 return sorted(issues, key = lambda i:i[field], reverse=reverse)
95 95
96 96
97 97 def report(issues, show_urls=False):
98 98 """Summary report about a list of issues, printing number and title.
99 99 """
100 100 # titles may have unicode in them, so we must encode everything below
101 101 if show_urls:
102 102 for i in issues:
103 103 role = 'ghpull' if 'merged_at' in i else 'ghissue'
104 print('* :%s:`%d`: %s' % (role, i['number'],
105 i['title'].replace('`', '``').encode('utf-8')))
104 print(u'* :%s:`%d`: %s' % (role, i['number'],
105 i['title'].replace(u'`', u'``')))
106 106 else:
107 107 for i in issues:
108 print('* %d: %s' % (i['number'], i['title'].replace('`', '``').encode('utf-8')))
108 print(u'* %d: %s' % (i['number'], i['title'].replace(u'`', u'``')))
109 109
110 110 #-----------------------------------------------------------------------------
111 111 # Main script
112 112 #-----------------------------------------------------------------------------
113 113
114 114 if __name__ == "__main__":
115 115 # deal with unicode
116 116 sys.stdout = codecs.getwriter('utf8')(sys.stdout)
117 117
118 118 # Whether to add reST urls for all issues in printout.
119 119 show_urls = True
120 120
121 121 parser = ArgumentParser()
122 122 parser.add_argument('--since-tag', type=str,
123 123 help="The git tag to use for the starting point (typically the last major release)."
124 124 )
125 125 parser.add_argument('--milestone', type=str,
126 126 help="The GitHub milestone to use for filtering issues [optional]."
127 127 )
128 128 parser.add_argument('--days', type=int,
129 129 help="The number of days of data to summarize (use this or --since-tag)."
130 130 )
131 131 parser.add_argument('--project', type=str, default="ipython/ipython",
132 132 help="The project to summarize."
133 133 )
134 134
135 135 opts = parser.parse_args()
136 136 tag = opts.since_tag
137 137
138 138 # set `since` from days or git tag
139 139 if opts.days:
140 140 since = datetime.utcnow() - timedelta(days=opts.days)
141 141 else:
142 142 if not tag:
143 143 tag = check_output(['git', 'describe', '--abbrev=0']).strip()
144 144 cmd = ['git', 'log', '-1', '--format=%ai', tag]
145 145 tagday, tz = check_output(cmd).strip().rsplit(' ', 1)
146 146 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
147 147 h = int(tz[1:3])
148 148 m = int(tz[3:])
149 149 td = timedelta(hours=h, minutes=m)
150 150 if tz[0] == '-':
151 151 since += td
152 152 else:
153 153 since -= td
154 154
155 155 since = round_hour(since)
156 156
157 157 milestone = opts.milestone
158 158 project = opts.project
159 159
160 160 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
161 161 if milestone:
162 162 milestone_id = get_milestone_id(project=project, milestone=milestone,
163 163 auth=True)
164 164 issues_and_pulls = get_issues_list(project=project,
165 165 milestone=milestone_id,
166 166 state='closed',
167 167 auth=True,
168 168 )
169 169 issues, pulls = split_pulls(issues_and_pulls)
170 170 else:
171 171 issues = issues_closed_since(since, project=project, pulls=False)
172 172 pulls = issues_closed_since(since, project=project, pulls=True)
173 173
174 174 # For regular reports, it's nice to show them in reverse chronological order
175 175 issues = sorted_by_field(issues, reverse=True)
176 176 pulls = sorted_by_field(pulls, reverse=True)
177 177
178 178 n_issues, n_pulls = map(len, (issues, pulls))
179 179 n_total = n_issues + n_pulls
180 180
181 181 # Print summary report we can directly include into release notes.
182 182
183 183 print()
184 184 since_day = since.strftime("%Y/%m/%d")
185 185 today = datetime.today().strftime("%Y/%m/%d")
186 186 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
187 187 print()
188 188 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
189 189 print()
190 190
191 191 ncommits = 0
192 192 all_authors = []
193 193 if tag:
194 194 # print git info, in addition to GitHub info:
195 195 since_tag = tag+'..'
196 196 cmd = ['git', 'log', '--oneline', since_tag]
197 197 ncommits += len(check_output(cmd).splitlines())
198 198
199 199 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
200 200 all_authors.extend(check_output(author_cmd).decode('utf-8', 'replace').splitlines())
201 201
202 202 pr_authors = []
203 203 for pr in pulls:
204 204 pr_authors.extend(get_authors(pr))
205 205 ncommits = len(pr_authors) + ncommits - len(pulls)
206 206 author_cmd = ['git', 'check-mailmap'] + pr_authors
207 207 with_email = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
208 208 all_authors.extend([ u'* ' + a.split(' <')[0] for a in with_email ])
209 209 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
210 210
211 211 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
212 212 print()
213 213 print('\n'.join(unique_authors))
214 214
215 215 print()
216 216 print("We closed %d issues and merged %d pull requests;\n"
217 217 "this is the full list (generated with the script \n"
218 218 ":file:`tools/github_stats.py`):" % (n_pulls, n_issues))
219 219 print()
220 220 print('Pull Requests (%d):\n' % n_pulls)
221 221 report(pulls, show_urls)
222 222 print()
223 223 print('Issues (%d):\n' % n_issues)
224 224 report(issues, show_urls)
@@ -1,77 +1,79 b''
1 1 #!/usr/bin/env python
2 2 """IPython release script.
3 3
4 4 This should ONLY be run at real release time.
5 5 """
6 6 from __future__ import print_function
7 7
8 8 from toollib import *
9 9 from gh_api import post_download
10 10
11 11 # Get main ipython dir, this will raise if it doesn't pass some checks
12 12 ipdir = get_ipdir()
13 13 tooldir = pjoin(ipdir, 'tools')
14 14 distdir = pjoin(ipdir, 'dist')
15 15
16 16 # Where I keep static backups of each release
17 17 ipbackupdir = os.path.expanduser('~/ipython/backup')
18 if not os.path.exists(ipbackupdir):
19 os.makedirs(ipbackupdir)
18 20
19 21 # Start in main IPython dir
20 22 cd(ipdir)
21 23
22 24 # Load release info
23 25 execfile(pjoin('IPython','core','release.py'))
24 26 # ensure js version is in sync
25 27 sh('./setup.py jsversion')
26 28
27 29 # Build site addresses for file uploads
28 30 release_site = '%s/release/%s' % (archive, version)
29 31 backup_site = '%s/backup/' % archive
30 32
31 33 # Start actual release process
32 34 print()
33 35 print('Releasing IPython')
34 36 print('=================')
35 37 print()
36 38 print('Version:', version)
37 39 print()
38 40 print('Source IPython directory:', ipdir)
39 41 print()
40 42
41 43 # Perform local backup, go to tools dir to run it.
42 44 cd(tooldir)
43 45 sh('./make_tarball.py')
44 46 sh('mv ipython-*.tgz %s' % ipbackupdir)
45 47
46 48 # Build release files
47 49 sh('./build_release %s' % ipdir)
48 50
49 51 # Register with the Python Package Index (PyPI)
50 52 print( 'Registering with PyPI...')
51 53 cd(ipdir)
52 54 sh('./setup.py register')
53 55
54 56 # Upload all files
55 57 sh(sdists + ' upload')
56 for py in ('2.7', '3.3'):
58 for py in ('2.7', '3.4'):
57 59 sh('python%s setupegg.py bdist_wheel' % py)
58 60
59 61 cd(distdir)
60 62 print( 'Uploading distribution files...')
61 63
62 64 for fname in os.listdir('.'):
63 # GitHub doesn't have an API for uploads at the moment
65 # TODO: update to GitHub releases API
64 66 continue
65 67 print('uploading %s to GitHub' % fname)
66 68 desc = "IPython %s source distribution" % version
67 69 post_download("ipython/ipython", fname, description=desc)
68 70
69 71 # Make target dir if it doesn't exist
70 72 sh('ssh %s "mkdir -p %s/release/%s" ' % (archive_user, archive_dir, version))
71 73 sh('scp * %s' % release_site)
72 74
73 75 print( 'Uploading backup files...')
74 76 cd(ipbackupdir)
75 77 sh('scp `ls -1tr *tgz | tail -1` %s' % backup_site)
76 78
77 79 print('Done!')
@@ -1,77 +1,61 b''
1 1 """Various utilities common to IPython release and maintenance tools.
2 2 """
3 3 from __future__ import print_function
4 4
5 5 # Library imports
6 6 import os
7 7 import sys
8 8
9 9 # Useful shorthands
10 10 pjoin = os.path.join
11 11 cd = os.chdir
12 12
13 13 # Constants
14 14
15 15 # SSH root address of the archive site
16 16 archive_user = 'ipython@archive.ipython.org'
17 17 archive_dir = 'archive.ipython.org'
18 18 archive = '%s:%s' % (archive_user, archive_dir)
19 19
20 20 # Build commands
21 21 # Source dists
22 22 sdists = './setup.py sdist --formats=gztar,zip'
23 # Eggs
24 eggs = './setupegg.py bdist_egg'
25
26 # Windows builds.
27 # We do them separately, so that the extra Windows scripts don't get pulled
28 # into Unix builds (setup.py has code which checks for bdist_wininst). Note
29 # that the install scripts args are added to the main distutils call in
30 # setup.py, so they don't need to be passed here.
31 #
32 # The Windows 64-bit installer can't be built by a Linux/Mac Python because ofa
33 # bug in distutils: http://bugs.python.org/issue6792.
34 # So we have to build it with a wine-installed native Windows Python...
35 win_builds = ["python setup.py bdist_wininst "
36 "--install-script=ipython_win_post_install.py",
37 r"%s/.wine/dosdevices/c\:/Python32/python.exe setup.py build "
38 "--plat-name=win-amd64 bdist_wininst "
39 "--install-script=ipython_win_post_install.py" %
40 os.environ['HOME'] ]
23 # Binary dists
24 wheels = './setupegg.py bdist_wheel'
41 25
42 26 # Utility functions
43 27 def sh(cmd):
44 28 """Run system command in shell, raise SystemExit if it returns an error."""
45 29 print("$", cmd)
46 30 stat = os.system(cmd)
47 31 #stat = 0 # Uncomment this and comment previous to run in debug mode
48 32 if stat:
49 33 raise SystemExit("Command %s failed with code: %s" % (cmd, stat))
50 34
51 35 # Backwards compatibility
52 36 c = sh
53 37
54 38 def get_ipdir():
55 39 """Get IPython directory from command line, or assume it's the one above."""
56 40
57 41 # Initialize arguments and check location
58 42 try:
59 43 ipdir = sys.argv[1]
60 44 except IndexError:
61 45 ipdir = '..'
62 46
63 47 ipdir = os.path.abspath(ipdir)
64 48
65 49 cd(ipdir)
66 50 if not os.path.isdir('IPython') and os.path.isfile('setup.py'):
67 51 raise SystemExit('Invalid ipython directory: %s' % ipdir)
68 52 return ipdir
69 53
70 54
71 55 def compile_tree():
72 56 """Compile all Python files below current directory."""
73 57 stat = os.system('python -m compileall .')
74 58 if stat:
75 59 msg = '*** ERROR: Some Python files in tree do NOT compile! ***\n'
76 60 msg += 'See messages above for the actual file that produced it.\n'
77 61 raise SystemExit(msg)
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now