forked from learningequality/ka-lite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup.py
295 lines (250 loc) · 10.7 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function
import os
import re
import logging
import pkg_resources
import shutil
import sys
from distutils import log
from setuptools import setup, find_packages
from setuptools.command.install_scripts import install_scripts
import kalite
try:
# There's an issue on the OSX build server -- sys.stdout and sys.stderr are non-blocking by default,
# which can result in IOError: [Errno 35] Resource temporarily unavailable
# See similar issue here: http://trac.edgewall.org/ticket/2066#comment:1
# So we just make them blocking.
import fcntl
def make_blocking(fd):
"""
Takes a file descriptor, fd, and checks its flags. Unsets O_NONBLOCK if it's set.
This makes the file blocking, so that there are no race conditions if several threads try to access it at once.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
if flags & os.O_NONBLOCK:
sys.stderr.write("Setting to blocking...\n")
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
else:
sys.stderr.write("Already blocking...\n")
sys.stderr.flush()
make_blocking(sys.stdout.fileno())
make_blocking(sys.stderr.fileno())
except ImportError:
pass
# Since pip 7.0.1, bdist_wheel has started to be called automatically when
# the sdist was being installed. Let's not have that.
# The problem is that it will build a 'ka-lite-static' and call it 'ka-lite'
if 'bdist_wheel' in sys.argv and '-d' in sys.argv:
open("/tmp/kalite.pip.log", "w").write(str(sys.argv) + "\n" + str(os.environ))
raise RuntimeError(
"Harmless: We don't support auto-converting to .whl format. Please "
"fetch the .whl instead of the tarball."
)
# Path of setup.py, used to resolve path of requirements.txt file
where_am_i = os.path.dirname(os.path.realpath(__file__))
# Handle requirements
RAW_REQUIREMENTS = open(os.path.join(where_am_i, 'requirements.txt'), 'r').read().split("\n")
def filter_requirement_statements(req):
"""Filter comments and blank lines from a requirements.txt like file
content to feed pip"""
# Strip comments and empty lines, but '#' is allowed in a URL!
if req.startswith('http'):
return req
req_pattern = re.compile(r'^(\s*)([^\#]+)')
m = req_pattern.search(req)
if m:
return m.group(0).replace(" ", "")
# Filter out comments from requirements
RAW_REQUIREMENTS = map(filter_requirement_statements, RAW_REQUIREMENTS)
RAW_REQUIREMENTS = filter(lambda x: bool(x), RAW_REQUIREMENTS)
# Special parser for http://blah#egg=asdasd-1.2.3
DIST_REQUIREMENTS = []
for req in RAW_REQUIREMENTS:
DIST_REQUIREMENTS.append(req)
# Requirements if doing a build with --static
STATIC_REQUIREMENTS = []
# Decide if the invoked command is a request to do building
DIST_BUILDING_COMMAND = any([x in sys.argv for x in ("bdist", "sdist", "bdist_wheel", "bdist_deb", "sdist_dsc")]) and not any([x.startswith("--use-premade-distfile") for x in sys.argv])
# This is where static packages are automatically installed by pip when running
# setup.py sdist --static or if a distributed version built by the static
# installs. This means that "setup.py install" can have two meanings:
# 1. When running from source dir, it means do not install the static version!
# 2. When running from a source built by '--static', it should not install
# any requirements and instead install all requirements into
# kalite/packages/dist
STATIC_DIST_PACKAGES = os.path.join(where_am_i, 'kalite', 'packages', 'dist')
# Default description of the distributed package
DIST_DESCRIPTION = (
"""KA Lite is a light-weight web server for viewing and interacting """
"""with core Khan Academy content (videos and exercises) without """
"""needing an Internet connection."""
)
# This changes when installing the static version
DIST_NAME = 'ka-lite'
STATIC_BUILD = False # Download all dependencies to kalite/packages/dist
NO_CLEAN = False # Do not clean kalite/packages/dist
# Check if user supplied the special '--static' option
if '--static' in sys.argv:
sys.argv.remove('--static')
STATIC_BUILD = True
if '--no-clean' in sys.argv:
NO_CLEAN = True
sys.argv.remove('--no-clean')
DIST_NAME = 'ka-lite-static'
DIST_DESCRIPTION += " This is the static version, which bundles all dependencies. Recommended for a portable installation method."
STATIC_REQUIREMENTS = DIST_REQUIREMENTS
DIST_REQUIREMENTS = []
def enable_log_to_stdout(logname):
"""Given a log name, outputs > INFO to stdout."""
log = logging.getLogger(logname)
log.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
log.addHandler(ch)
def get_installed_packages():
"""Avoid depending on pip for this task"""
return [x.key for x in filter(lambda y: where_am_i not in y.location, pkg_resources.working_set)]
if DIST_BUILDING_COMMAND and not os.path.exists(os.path.join(where_am_i, "kalite", "static-libraries", "docs")):
raise RuntimeError("Not building - kalite/static-libraries/docs not found.")
################
# Windows code #
################
#
# Close your eyes
BAT_TEMPLATE = \
r"""@echo off
set mypath=%~dp0
set pyscript="%mypath%{FNAME}"
set /p line1=<%pyscript%
if "%line1:~0,2%" == "#!" (goto :goodstart)
echo First line of %pyscript% does not start with "#!"
exit /b 1
:goodstart
set py_exe=%line1:~2%
call %py_exe% %pyscript% %*
"""
class my_install_scripts(install_scripts):
def run(self):
install_scripts.run(self)
if not os.name == "nt":
return
for filepath in self.get_outputs():
# If we can find an executable name in the #! top line of the script
# file, make .bat wrapper for script.
with open(filepath, 'rt') as fobj:
first_line = fobj.readline()
if not (first_line.startswith('#!') and
'python' in first_line.lower()):
log.info("No #!python executable found, skipping .bat wrapper")
continue
pth, fname = os.path.split(filepath)
froot, ___ = os.path.splitext(fname)
bat_file = os.path.join(pth, froot + '.bat')
bat_contents = BAT_TEMPLATE.replace('{FNAME}', fname)
log.info("Making %s wrapper for %s" % (bat_file, filepath))
if self.dry_run:
continue
with open(bat_file, 'wt') as fobj:
fobj.write(bat_contents)
# You can open your eyes again
#
#####################
# END: Windows code #
#####################
######################################
# STATIC AND DYNAMIC BUILD SPECIFICS #
######################################
# If it's a static build, we invoke pip to bundle dependencies in kalite/packages/dist
# This would be the case for commands "bdist" and "sdist"
if STATIC_BUILD:
sys.stderr.write(
"This is a static build... we hope you invoked this build through the "
"'make dist' target, because this populates kalite/packages/dist"
)
# It's not a build command with --static or it's not a build command at all
else:
# If the kalite/packages/dist directory is non-empty
# Not empty = more than the __init__.py file
if len(os.listdir(STATIC_DIST_PACKAGES)) > 1:
# If we are building something or running from the source
if DIST_BUILDING_COMMAND:
sys.stderr.write((
"Installing from source or not building with --static, so clearing "
"out: {}\n\nIf you wish to install a static version "
"from the source distribution, use setup.py install --static\n\n"
"ENTER to continue or CTRL+C to cancel\n\n"
).format(STATIC_DIST_PACKAGES))
sys.stdin.readline()
shutil.rmtree(STATIC_DIST_PACKAGES)
os.mkdir(STATIC_DIST_PACKAGES)
open(os.path.join(STATIC_DIST_PACKAGES, '__init__.py'), "w").write("\n")
else:
# There are distributed requirements in kalite/packages, so ignore
# everything in the requirements.txt file
DIST_REQUIREMENTS = []
DIST_NAME = 'ka-lite-static'
if "ka-lite" in get_installed_packages():
raise RuntimeError(
"Already installed ka-lite so cannot install ka-lite-static. "
"Remove existing ka-lite-static packages, for instance using \n"
"\n"
" pip uninstall ka-lite-static # Standard\n"
" sudo apt-get remove ka-lite # Ubuntu/Debian\n"
"\n"
"...or other possible installation mechanisms you may have "
"been using."
)
# No kalite/packages/dist/ and not building, so must be installing the dynamic
# version
elif not DIST_BUILDING_COMMAND:
# Check that static version is not already installed
if "ka-lite-static" in get_installed_packages():
raise RuntimeError(
"Already installed ka-lite-static so cannot install ka-lite. "
"Remove existing ka-lite-static packages, for instance using \n"
"\n"
" pip uninstall ka-lite # Standard\n"
" sudo apt-get remove ka-lite # Ubuntu/Debian\n"
"\n"
"...or other possible installation mechanisms you may have "
"been using."
)
setup(
name=DIST_NAME,
version=kalite.VERSION,
author="Foundation for Learning Equality",
author_email="[email protected]",
url="https://www.learningequality.org",
description=DIST_DESCRIPTION,
license="MIT",
keywords=("khan academy", "offline", "education", "OER"),
scripts=['bin/kalite'],
packages=find_packages(),
zip_safe=False,
install_requires=DIST_REQUIREMENTS,
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries :: Application Frameworks',
],
include_package_data=True,
cmdclass={
'install_scripts': my_install_scripts # Windows bat wrapper
}
)