forked from spyder-ide/spyder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
create_app.py
285 lines (239 loc) · 9.45 KB
/
create_app.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
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Create a stand-alone Mac OS X app using py2app
To be used like this:
$ python setup.py build (to update the docs)
$ python create_app.py py2app (to build the app)
"""
from __future__ import print_function
from setuptools import setup
from distutils.sysconfig import get_python_lib, get_config_var
import fileinput
import shutil
import os
import os.path as osp
import subprocess
import sys
from IPython.core.completerlib import module_list
from spyder import __version__ as spy_version
from spyder.config.utils import EDIT_FILETYPES, _get_extensions
from spyder.config.base import MAC_APP_NAME
from spyder.utils.programs import find_program
PY2 = sys.version[0] == '2'
# To deal with a bug in py2app 0.9
sys.setrecursionlimit(1500)
#==============================================================================
# Auxiliary functions
#==============================================================================
def get_stdlib_modules():
"""
Returns a list containing the names of all the modules available in the
standard library.
Based on the function get_root_modules from the IPython project.
Present in IPython.core.completerlib in v0.13.1
Copyright (C) 2010-2011 The IPython Development Team.
Distributed under the terms of the BSD License.
"""
modules = list(sys.builtin_module_names)
for path in sys.path[1:]:
if 'site-packages' not in path:
modules += module_list(path)
modules = set(modules)
if '__init__' in modules:
modules.remove('__init__')
modules = list(modules)
return modules
#==============================================================================
# Main message
#==============================================================================
main_message = """
IMPORTANT NOTE
==============
Before running this script, please be sure of following the instructions
present in
https://github.com/spyder-ide/mac-application/blob/master/How to build the app.md
This script only runs succesfully with those instructions!
------
"""
print(main_message)
#==============================================================================
# App creation
#==============================================================================
APP_MAIN_SCRIPT = MAC_APP_NAME[:-4] + '.py'
shutil.copyfile('scripts/spyder', APP_MAIN_SCRIPT)
APP = [APP_MAIN_SCRIPT]
DEPS = ['pylint', 'logilab', 'astroid', 'pycodestyle', 'setuptools']
EXCLUDES = DEPS + ['mercurial']
PACKAGES = ['spyder', 'spyder_breakpoints', 'spyder_io_dcm', 'spyder_io_hdf5',
'spyder_profiler', 'spyder_pylint', 'sphinx', 'jinja2', 'docutils',
'alabaster', 'babel', 'snowballstemmer', 'sphinx_rtd_theme',
'IPython', 'ipykernel', 'ipython_genutils', 'jupyter_client',
'jupyter_core', 'traitlets', 'qtconsole', 'pexpect', 'jedi',
'jsonschema', 'nbconvert', 'nbformat', 'qtpy', 'qtawesome',
'zmq', 'pygments', 'rope', 'distutils', 'PIL', 'PyQt5',
'sklearn', 'skimage', 'pandas', 'sympy', 'pyflakes', 'psutil',
'nose', 'patsy','statsmodels', 'seaborn', 'networkx']
INCLUDES = get_stdlib_modules()
EDIT_EXT = [ext[1:] for ext in _get_extensions(EDIT_FILETYPES)]
OPTIONS = {
'argv_emulation': True,
'compressed' : False,
'optimize': 0,
'packages': PACKAGES,
'includes': INCLUDES,
'excludes': EXCLUDES,
'iconfile': 'img_src/spyder.icns',
'plist': {'CFBundleDocumentTypes': [{'CFBundleTypeExtensions': EDIT_EXT,
'CFBundleTypeName': 'Text File',
'CFBundleTypeRole': 'Editor'}],
'CFBundleIdentifier': 'org.spyder-ide',
'CFBundleShortVersionString': spy_version}
}
setup(
app=APP,
options={'py2app': OPTIONS}
)
# Remove script for app
os.remove(APP_MAIN_SCRIPT)
#==============================================================================
# Post-app creation
#==============================================================================
py_ver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
# Main paths
resources = 'dist/%s/Contents/Resources' % MAC_APP_NAME
system_python_lib = get_python_lib()
app_python_lib = osp.join(resources, 'lib', 'python%s' % py_ver)
# Add our docs to the app
docs_orig = 'build/lib/spyder/doc'
docs_dest = osp.join(app_python_lib, 'spyder', 'doc')
shutil.copytree(docs_orig, docs_dest)
# Create a minimal library inside Resources to add it to PYTHONPATH instead of
# app_python_lib. This must be done when the user changes to an interpreter
# that's not the one that comes with the app, to forbid importing modules
# inside the app.
minimal_lib = osp.join(app_python_lib, 'minimal-lib')
os.mkdir(minimal_lib)
minlib_pkgs = ['spyder', 'spyder_breakpoints', 'spyder_io_dcm',
'spyder_io_hdf5', 'spyder_profiler', 'spyder_pylint']
for p in minlib_pkgs:
shutil.copytree(osp.join(app_python_lib, p), osp.join(minimal_lib, p))
# Add necessary Python programs to the app
PROGRAMS = ['pylint', 'pycodestyle']
system_progs = [find_program(p) for p in PROGRAMS]
progs_dest = [resources + osp.sep + p for p in PROGRAMS]
for i in range(len(PROGRAMS)):
shutil.copy2(system_progs[i], progs_dest[i])
# Add deps needed for PROGRAMS to the app
deps = []
for package in os.listdir(system_python_lib):
for d in DEPS:
if package.startswith(d):
deps.append(package)
for i in deps:
if osp.isdir(osp.join(system_python_lib, i)):
shutil.copytree(osp.join(system_python_lib, i),
osp.join(app_python_lib, i))
else:
shutil.copy2(osp.join(system_python_lib, i),
osp.join(app_python_lib, i))
# Single file dependencies
SINGLE_DEPS = ['path.py', 'simplegeneric.py', 'decorator.py', 'mistune.py',
'mistune.so', 'pickleshare.py', 'sip.so']
for dep in SINGLE_DEPS:
if osp.isfile(osp.join(system_python_lib, dep)):
shutil.copyfile(osp.join(system_python_lib, dep),
osp.join(app_python_lib, dep))
# Function to adjust the interpreter used by PROGRAMS
# (to be added to __boot.py__)
change_interpreter = \
"""
PROGRAMS = %s
def _change_interpreter(program):
import fileinput
import sys
try:
for line in fileinput.input(program, inplace=True):
if line.startswith('#!'):
print('#!' + sys.executable, end='')
else:
print(line, end='')
except:
pass
for p in PROGRAMS:
_change_interpreter(p)
""" % str(PROGRAMS)
# Add RESOURCEPATH to PATH, so that Spyder can find PROGRAMS inside the app
new_path = \
"""
old_path = os.environ['PATH']
os.environ['PATH'] = os.environ['RESOURCEPATH'] + os.pathsep + old_path
"""
# Add IPYTHONDIR to the app env because it seems IPython gets confused
# about its location when running inside the app
ip_dir = \
"""
from IPython.utils.path import get_ipython_dir
os.environ['IPYTHONDIR'] = get_ipython_dir()
"""
# Add a way to grab environment variables inside the app.
# Thanks a lot to Ryan Clary for posting it here
# https://groups.google.com/forum/?fromgroups=#!topic/spyderlib/lCXOYk-FSWI
get_env = \
r"""
def _get_env():
import os
import os.path as osp
import subprocess as sp
user_profile = os.getenv('HOME') + osp.sep + '.profile'
global_profile = '/etc/profile'
if osp.isfile(global_profile) and not osp.isfile(user_profile):
envstr = sp.Popen('source /etc/profile; printenv',
shell=True, stdout=sp.PIPE).communicate()[0]
elif osp.isfile(global_profile) and osp.isfile(user_profile):
envstr = sp.Popen('source /etc/profile; source ~/.profile; printenv',
shell=True, stdout=sp.PIPE).communicate()[0]
else:
envstr = sp.Popen('printenv', shell=True,
stdout=sp.PIPE).communicate()[0]
try:
env_vars = envstr.decode().strip().split('\n')
except UnicodeDecodeError:
env_vars = envstr.decode(encoding='utf-8').strip().split('\n')
env = [a.split('=') for a in env_vars if '=' in a]
os.environ.update(env)
try:
_get_env()
except:
print('Cannot grab environment variables!')
"""
# Add our modifications to __boot__.py so that they can be taken into
# account when the app is started
boot_file = 'dist/%s/Contents/Resources/__boot__.py' % MAC_APP_NAME
reset_line = "_reset_sys_path()"
run_line = "_run()"
for line in fileinput.input(boot_file, inplace=True):
if line.startswith(reset_line):
print(reset_line)
print(get_env)
elif line.startswith(run_line):
print(change_interpreter)
print(new_path)
print(ip_dir)
print(run_line)
else:
print(line, end='')
# Workaround for what appears to be a bug with py2app and Homebrew
# See https://bitbucket.org/ronaldoussoren/py2app/issue/26#comment-2092445
PF_dir = get_config_var('PYTHONFRAMEWORKINSTALLDIR')
if not PY2:
PF_dir = osp.join(PF_dir, 'Versions', py_ver)
app_python_interpreter = 'dist/%s/Contents/MacOS/python' % MAC_APP_NAME
shutil.copyfile(osp.join(PF_dir, 'Resources/Python.app/Contents/MacOS/Python'),
app_python_interpreter)
exec_path = '@executable_path/../Frameworks/Python.framework/Versions/%s/Python' % py_ver
subprocess.call(['install_name_tool', '-change', osp.join(sys.prefix, 'Python'),
exec_path, app_python_interpreter])