Skip to content

Commit 7682d2f

Browse files
committed
Script for uploading AppVeyor artifacts to PyPI
[ci skip]
1 parent db435eb commit 7682d2f

File tree

2 files changed

+145
-2
lines changed

2 files changed

+145
-2
lines changed

setup.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ def restore_cencode():
132132
extra_link_args=link_flags,
133133
)
134134

135+
install_requires = ['six']
136+
tests_require = ['Werkzeug >= 0.9']
137+
135138

136139
def version(sass_filename='sass.py'):
137140
with open(sass_filename) as f:
@@ -226,9 +229,15 @@ def run(self):
226229
['sassc = sassc:main']
227230
]
228231
},
229-
install_requires=['six'],
230-
tests_require=['Werkzeug >= 0.9'],
232+
install_requires=install_requires,
233+
tests_require=tests_require,
231234
test_suite='sasstests.suite',
235+
extras_require={
236+
'tests': tests_require,
237+
'upload_appveyor_builds': [
238+
'twine == 1.5.0',
239+
],
240+
},
232241
classifiers=[
233242
'Development Status :: 5 - Production/Stable',
234243
'Environment :: Web Environment',

upload_appveyor_builds.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python
2+
# TODO: Upload to GitHub releases
3+
# TODO: .pypirc configuration
4+
from __future__ import print_function
5+
6+
import argparse
7+
import json
8+
import os
9+
import os.path
10+
import shutil
11+
import subprocess
12+
13+
from six import PY3
14+
from six.moves.urllib.parse import urljoin
15+
from six.moves.urllib.request import urlopen
16+
from twine.commands.upload import upload
17+
18+
19+
APPVEYOR_API_BASE_URL = 'https://ci.appveyor.com/api/'
20+
APPVEYOR_API_PROJECT_URL = urljoin(APPVEYOR_API_BASE_URL,
21+
'projects/dahlia/libsass-python/')
22+
APPVEYOR_API_BUILDS_URL = urljoin(APPVEYOR_API_PROJECT_URL,
23+
'history?recordsNumber=50&branch=python')
24+
APPVEYOR_API_JOBS_URL = urljoin(APPVEYOR_API_PROJECT_URL,
25+
'build/')
26+
APPVEYOR_API_JOB_URL = urljoin(APPVEYOR_API_BASE_URL, 'buildjobs/')
27+
28+
29+
def ci_builds():
30+
response = urlopen(APPVEYOR_API_BUILDS_URL)
31+
projects = json.loads(response.read().decode('utf-8')) # py3 compat
32+
response.close()
33+
return projects['builds']
34+
35+
36+
def ci_tag_build(tag):
37+
builds = ci_builds()
38+
commit_id = git_tags().get(tag)
39+
for build in builds:
40+
if build['isTag'] and build['tag'] == tag:
41+
return build
42+
elif build['commitId'] == commit_id:
43+
return build
44+
45+
46+
def git_tags():
47+
try:
48+
tags = subprocess.check_output(['git', 'tag'])
49+
except subprocess.CalledProcessError:
50+
return {}
51+
52+
def read(tag):
53+
command = ['git', 'rev-list', tag]
54+
p = subprocess.Popen(command, stdout=subprocess.PIPE)
55+
try:
56+
firstline = p.stdout.readline()
57+
finally:
58+
p.terminate()
59+
return firstline.decode().strip()
60+
return {tag: read(tag) for tag in tags.decode().split()}
61+
62+
63+
def ci_jobs(build):
64+
url = urljoin(APPVEYOR_API_JOBS_URL, build['version'])
65+
response = urlopen(url)
66+
build = json.loads(response.read().decode('utf-8')) # py3 compat
67+
response.close()
68+
return build['build']['jobs']
69+
70+
71+
def ci_artifacts(job):
72+
url = urljoin(urljoin(APPVEYOR_API_JOB_URL, job['jobId'] + '/'),
73+
'artifacts/')
74+
response = urlopen(url)
75+
files = json.loads(response.read().decode('utf-8')) # py3 compat
76+
response.close()
77+
for file_ in files:
78+
file_['url'] = urljoin(url, file_['fileName'])
79+
return files
80+
81+
82+
def download_artifact(artifact, target_dir, overwrite=False):
83+
print('Downloading {0}...'.format(artifact['fileName']))
84+
response = urlopen(artifact['url'])
85+
filename = os.path.basename(artifact['fileName'])
86+
target_path = os.path.join(target_dir, filename)
87+
if (os.path.isfile(target_path) and
88+
os.path.getsize(target_path) == artifact['size']):
89+
if overwrite:
90+
print(artifact['fileName'], ' already exists; overwrite...')
91+
else:
92+
print(artifact['fileName'], ' already exists; skip...')
93+
return target_path
94+
with open(target_path, 'wb') as f:
95+
shutil.copyfileobj(response, f)
96+
assert f.tell() == artifact['size']
97+
response.close()
98+
return target_path
99+
100+
101+
def main():
102+
parser = argparse.ArgumentParser()
103+
parser.add_argument('--overwrite', action='store_true', default=False,
104+
help='Overwrite files if already exist')
105+
parser.add_argument('--dist-dir', default='./dist/',
106+
help='The temporary directory to download artifacts')
107+
parser.add_argument('tag', help='Git tag of the version to upload')
108+
args = parser.parse_args()
109+
build = ci_tag_build(args.tag)
110+
jobs = ci_jobs(build)
111+
if not os.path.isdir(args.dist_dir):
112+
print(args.dist_dir, 'does not exist yet; creating a new directory...')
113+
os.makedirs(args.dist_dir)
114+
dists = []
115+
for job in jobs:
116+
artifacts = ci_artifacts(job)
117+
for artifact in artifacts:
118+
dist = download_artifact(artifact, args.dist_dir, args.overwrite)
119+
dists.append(dist)
120+
print('Uploading {0} file(s)...'.format(len(dists)))
121+
upload(
122+
repository='pypi',
123+
sign=False,
124+
sign_with='gpg',
125+
identity=None,
126+
username=None,
127+
password=None,
128+
comment=None,
129+
dists=dists
130+
)
131+
132+
133+
if __name__ == '__main__':
134+
main()

0 commit comments

Comments
 (0)