##// END OF EJS Templates
remove bundled profiles...
Min RK -
Show More
1 NO CONTENT: file renamed from IPython/config/profile/README_STARTUP to IPython/core/profile/README_STARTUP
@@ -1,317 +1,317 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12 from __future__ import print_function
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import os
26 26
27 27 from IPython.config.application import Application
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.traitlets import Unicode, Bool, Dict
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Constants
39 39 #-----------------------------------------------------------------------------
40 40
41 41 create_help = """Create an IPython profile by name
42 42
43 43 Create an ipython profile directory by its name or
44 44 profile directory path. Profile directories contain
45 45 configuration, log and security related files and are named
46 46 using the convention 'profile_<name>'. By default they are
47 47 located in your ipython directory. Once created, you will
48 48 can edit the configuration files in the profile
49 49 directory to configure IPython. Most users will create a
50 50 profile directory by name,
51 51 `ipython profile create myprofile`, which will put the directory
52 52 in `<ipython_dir>/profile_myprofile`.
53 53 """
54 54 list_help = """List available IPython profiles
55 55
56 56 List all available profiles, by profile location, that can
57 57 be found in the current working directly or in the ipython
58 58 directory. Profile directories are named using the convention
59 59 'profile_<profile>'.
60 60 """
61 61 profile_help = """Manage IPython profiles
62 62
63 63 Profile directories contain
64 64 configuration, log and security related files and are named
65 65 using the convention 'profile_<name>'. By default they are
66 66 located in your ipython directory. You can create profiles
67 67 with `ipython profile create <name>`, or see the profiles you
68 68 already have with `ipython profile list`
69 69
70 70 To get started configuring IPython, simply do:
71 71
72 72 $> ipython profile create
73 73
74 74 and IPython will create the default profile in <ipython_dir>/profile_default,
75 75 where you can edit ipython_config.py to start configuring IPython.
76 76
77 77 """
78 78
79 79 _list_examples = "ipython profile list # list all profiles"
80 80
81 81 _create_examples = """
82 82 ipython profile create foo # create profile foo w/ default config files
83 83 ipython profile create foo --reset # restage default config files over current
84 84 ipython profile create foo --parallel # also stage parallel config files
85 85 """
86 86
87 87 _main_examples = """
88 88 ipython profile create -h # show the help string for the create subcommand
89 89 ipython profile list -h # show the help string for the list subcommand
90 90
91 91 ipython locate profile foo # print the path to the directory for profile 'foo'
92 92 """
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Profile Application Class (for `ipython profile` subcommand)
96 96 #-----------------------------------------------------------------------------
97 97
98 98
99 99 def list_profiles_in(path):
100 100 """list profiles in a given root directory"""
101 101 files = os.listdir(path)
102 102 profiles = []
103 103 for f in files:
104 104 try:
105 105 full_path = os.path.join(path, f)
106 106 except UnicodeError:
107 107 continue
108 108 if os.path.isdir(full_path) and f.startswith('profile_'):
109 109 profiles.append(f.split('_',1)[-1])
110 110 return profiles
111 111
112 112
113 113 def list_bundled_profiles():
114 114 """list profiles that are bundled with IPython."""
115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
115 path = os.path.join(get_ipython_package_dir(), u'core', u'profile')
116 116 files = os.listdir(path)
117 117 profiles = []
118 118 for profile in files:
119 119 full_path = os.path.join(path, profile)
120 120 if os.path.isdir(full_path) and profile != "__pycache__":
121 121 profiles.append(profile)
122 122 return profiles
123 123
124 124
125 125 class ProfileLocate(BaseIPythonApplication):
126 126 description = """print the path to an IPython profile dir"""
127 127
128 128 def parse_command_line(self, argv=None):
129 129 super(ProfileLocate, self).parse_command_line(argv)
130 130 if self.extra_args:
131 131 self.profile = self.extra_args[0]
132 132
133 133 def start(self):
134 134 print(self.profile_dir.location)
135 135
136 136
137 137 class ProfileList(Application):
138 138 name = u'ipython-profile'
139 139 description = list_help
140 140 examples = _list_examples
141 141
142 142 aliases = Dict({
143 143 'ipython-dir' : 'ProfileList.ipython_dir',
144 144 'log-level' : 'Application.log_level',
145 145 })
146 146 flags = Dict(dict(
147 147 debug = ({'Application' : {'log_level' : 0}},
148 148 "Set Application.log_level to 0, maximizing log output."
149 149 )
150 150 ))
151 151
152 152 ipython_dir = Unicode(get_ipython_dir(), config=True,
153 153 help="""
154 154 The name of the IPython directory. This directory is used for logging
155 155 configuration (through profiles), history storage, etc. The default
156 156 is usually $HOME/.ipython. This options can also be specified through
157 157 the environment variable IPYTHONDIR.
158 158 """
159 159 )
160 160
161 161
162 162 def _print_profiles(self, profiles):
163 163 """print list of profiles, indented."""
164 164 for profile in profiles:
165 165 print(' %s' % profile)
166 166
167 167 def list_profile_dirs(self):
168 168 profiles = list_bundled_profiles()
169 169 if profiles:
170 170 print()
171 171 print("Available profiles in IPython:")
172 172 self._print_profiles(profiles)
173 173 print()
174 174 print(" The first request for a bundled profile will copy it")
175 175 print(" into your IPython directory (%s)," % self.ipython_dir)
176 176 print(" where you can customize it.")
177 177
178 178 profiles = list_profiles_in(self.ipython_dir)
179 179 if profiles:
180 180 print()
181 181 print("Available profiles in %s:" % self.ipython_dir)
182 182 self._print_profiles(profiles)
183 183
184 184 profiles = list_profiles_in(py3compat.getcwd())
185 185 if profiles:
186 186 print()
187 187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
188 188 self._print_profiles(profiles)
189 189
190 190 print()
191 191 print("To use any of the above profiles, start IPython with:")
192 192 print(" ipython --profile=<name>")
193 193 print()
194 194
195 195 def start(self):
196 196 self.list_profile_dirs()
197 197
198 198
199 199 create_flags = {}
200 200 create_flags.update(base_flags)
201 201 # don't include '--init' flag, which implies running profile create in other apps
202 202 create_flags.pop('init')
203 203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
204 204 "reset config files in this profile to the defaults.")
205 205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
206 206 "Include the config files for parallel "
207 207 "computing apps (ipengine, ipcontroller, etc.)")
208 208
209 209
210 210 class ProfileCreate(BaseIPythonApplication):
211 211 name = u'ipython-profile'
212 212 description = create_help
213 213 examples = _create_examples
214 214 auto_create = Bool(True, config=False)
215 215 def _log_format_default(self):
216 216 return "[%(name)s] %(message)s"
217 217
218 218 def _copy_config_files_default(self):
219 219 return True
220 220
221 221 parallel = Bool(False, config=True,
222 222 help="whether to include parallel computing config files")
223 223 def _parallel_changed(self, name, old, new):
224 224 parallel_files = [ 'ipcontroller_config.py',
225 225 'ipengine_config.py',
226 226 'ipcluster_config.py'
227 227 ]
228 228 if new:
229 229 for cf in parallel_files:
230 230 self.config_files.append(cf)
231 231 else:
232 232 for cf in parallel_files:
233 233 if cf in self.config_files:
234 234 self.config_files.remove(cf)
235 235
236 236 def parse_command_line(self, argv):
237 237 super(ProfileCreate, self).parse_command_line(argv)
238 238 # accept positional arg as profile name
239 239 if self.extra_args:
240 240 self.profile = self.extra_args[0]
241 241
242 242 flags = Dict(create_flags)
243 243
244 244 classes = [ProfileDir]
245 245
246 246 def _import_app(self, app_path):
247 247 """import an app class"""
248 248 app = None
249 249 name = app_path.rsplit('.', 1)[-1]
250 250 try:
251 251 app = import_item(app_path)
252 252 except ImportError:
253 253 self.log.info("Couldn't import %s, config file will be excluded", name)
254 254 except Exception:
255 255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
256 256 return app
257 257
258 258 def init_config_files(self):
259 259 super(ProfileCreate, self).init_config_files()
260 260 # use local imports, since these classes may import from here
261 261 from IPython.terminal.ipapp import TerminalIPythonApp
262 262 apps = [TerminalIPythonApp]
263 263 for app_path in (
264 264 'IPython.kernel.zmq.kernelapp.IPKernelApp',
265 265 'IPython.terminal.console.app.ZMQTerminalIPythonApp',
266 266 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
267 267 'IPython.html.notebookapp.NotebookApp',
268 268 'IPython.nbconvert.nbconvertapp.NbConvertApp',
269 269 ):
270 270 app = self._import_app(app_path)
271 271 if app is not None:
272 272 apps.append(app)
273 273 if self.parallel:
274 274 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
275 275 from IPython.parallel.apps.ipengineapp import IPEngineApp
276 276 from IPython.parallel.apps.ipclusterapp import IPClusterStart
277 277 from IPython.parallel.apps.iploggerapp import IPLoggerApp
278 278 apps.extend([
279 279 IPControllerApp,
280 280 IPEngineApp,
281 281 IPClusterStart,
282 282 IPLoggerApp,
283 283 ])
284 284 for App in apps:
285 285 app = App()
286 286 app.config.update(self.config)
287 287 app.log = self.log
288 288 app.overwrite = self.overwrite
289 289 app.copy_config_files=True
290 290 app.ipython_dir=self.ipython_dir
291 291 app.profile_dir=self.profile_dir
292 292 app.init_config_files()
293 293
294 294 def stage_default_config_file(self):
295 295 pass
296 296
297 297
298 298 class ProfileApp(Application):
299 299 name = u'ipython profile'
300 300 description = profile_help
301 301 examples = _main_examples
302 302
303 303 subcommands = Dict(dict(
304 304 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
305 305 list = (ProfileList, ProfileList.description.splitlines()[0]),
306 306 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
307 307 ))
308 308
309 309 def start(self):
310 310 if self.subapp is None:
311 311 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
312 312 print()
313 313 self.print_description()
314 314 self.print_subcommands()
315 315 self.exit(1)
316 316 else:
317 317 return self.subapp.start()
@@ -1,249 +1,249 b''
1 1 # encoding: utf-8
2 2 """An object for managing IPython profile directories."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import shutil
9 9 import errno
10 10
11 11 from IPython.config.configurable import LoggingConfigurable
12 12 from IPython.utils.path import get_ipython_package_dir, expand_path, ensure_dir_exists
13 13 from IPython.utils import py3compat
14 14 from IPython.utils.traitlets import Unicode, Bool
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Module errors
18 18 #-----------------------------------------------------------------------------
19 19
20 20 class ProfileDirError(Exception):
21 21 pass
22 22
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Class for managing profile directories
26 26 #-----------------------------------------------------------------------------
27 27
28 28 class ProfileDir(LoggingConfigurable):
29 29 """An object to manage the profile directory and its resources.
30 30
31 31 The profile directory is used by all IPython applications, to manage
32 32 configuration, logging and security.
33 33
34 34 This object knows how to find, create and manage these directories. This
35 35 should be used by any code that wants to handle profiles.
36 36 """
37 37
38 38 security_dir_name = Unicode('security')
39 39 log_dir_name = Unicode('log')
40 40 startup_dir_name = Unicode('startup')
41 41 pid_dir_name = Unicode('pid')
42 42 static_dir_name = Unicode('static')
43 43 security_dir = Unicode(u'')
44 44 log_dir = Unicode(u'')
45 45 startup_dir = Unicode(u'')
46 46 pid_dir = Unicode(u'')
47 47 static_dir = Unicode(u'')
48 48
49 49 location = Unicode(u'', config=True,
50 50 help="""Set the profile location directly. This overrides the logic used by the
51 51 `profile` option.""",
52 52 )
53 53
54 54 _location_isset = Bool(False) # flag for detecting multiply set location
55 55
56 56 def _location_changed(self, name, old, new):
57 57 if self._location_isset:
58 58 raise RuntimeError("Cannot set profile location more than once.")
59 59 self._location_isset = True
60 60 ensure_dir_exists(new)
61 61
62 62 # ensure config files exist:
63 63 self.security_dir = os.path.join(new, self.security_dir_name)
64 64 self.log_dir = os.path.join(new, self.log_dir_name)
65 65 self.startup_dir = os.path.join(new, self.startup_dir_name)
66 66 self.pid_dir = os.path.join(new, self.pid_dir_name)
67 67 self.static_dir = os.path.join(new, self.static_dir_name)
68 68 self.check_dirs()
69 69
70 70 def _log_dir_changed(self, name, old, new):
71 71 self.check_log_dir()
72 72
73 73 def _mkdir(self, path, mode=None):
74 74 """ensure a directory exists at a given path
75 75
76 76 This is a version of os.mkdir, with the following differences:
77 77
78 78 - returns True if it created the directory, False otherwise
79 79 - ignores EEXIST, protecting against race conditions where
80 80 the dir may have been created in between the check and
81 81 the creation
82 82 - sets permissions if requested and the dir already exists
83 83 """
84 84 if os.path.exists(path):
85 85 if mode and os.stat(path).st_mode != mode:
86 86 try:
87 87 os.chmod(path, mode)
88 88 except OSError:
89 89 self.log.warn(
90 90 "Could not set permissions on %s",
91 91 path
92 92 )
93 93 return False
94 94 try:
95 95 if mode:
96 96 os.mkdir(path, mode)
97 97 else:
98 98 os.mkdir(path)
99 99 except OSError as e:
100 100 if e.errno == errno.EEXIST:
101 101 return False
102 102 else:
103 103 raise
104 104
105 105 return True
106 106
107 107 def check_log_dir(self):
108 108 self._mkdir(self.log_dir)
109 109
110 110 def _startup_dir_changed(self, name, old, new):
111 111 self.check_startup_dir()
112 112
113 113 def check_startup_dir(self):
114 114 self._mkdir(self.startup_dir)
115 115
116 116 readme = os.path.join(self.startup_dir, 'README')
117 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
117 src = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'README_STARTUP')
118 118
119 119 if not os.path.exists(src):
120 120 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
121 121
122 122 if os.path.exists(src) and not os.path.exists(readme):
123 123 shutil.copy(src, readme)
124 124
125 125 def _security_dir_changed(self, name, old, new):
126 126 self.check_security_dir()
127 127
128 128 def check_security_dir(self):
129 129 self._mkdir(self.security_dir, 0o40700)
130 130
131 131 def _pid_dir_changed(self, name, old, new):
132 132 self.check_pid_dir()
133 133
134 134 def check_pid_dir(self):
135 135 self._mkdir(self.pid_dir, 0o40700)
136 136
137 137 def _static_dir_changed(self, name, old, new):
138 138 self.check_startup_dir()
139 139
140 140 def check_static_dir(self):
141 141 self._mkdir(self.static_dir)
142 142 custom = os.path.join(self.static_dir, 'custom')
143 143 self._mkdir(custom)
144 144 from IPython.html import DEFAULT_STATIC_FILES_PATH
145 145 for fname in ('custom.js', 'custom.css'):
146 146 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
147 147 dest = os.path.join(custom, fname)
148 148 if not os.path.exists(src):
149 149 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
150 150 continue
151 151 if not os.path.exists(dest):
152 152 shutil.copy(src, dest)
153 153
154 154 def check_dirs(self):
155 155 self.check_security_dir()
156 156 self.check_log_dir()
157 157 self.check_pid_dir()
158 158 self.check_startup_dir()
159 159 self.check_static_dir()
160 160
161 161 def copy_config_file(self, config_file, path=None, overwrite=False):
162 162 """Copy a default config file into the active profile directory.
163 163
164 164 Default configuration files are kept in :mod:`IPython.config.default`.
165 165 This function moves these from that location to the working profile
166 166 directory.
167 167 """
168 168 dst = os.path.join(self.location, config_file)
169 169 if os.path.isfile(dst) and not overwrite:
170 170 return False
171 171 if path is None:
172 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
172 path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
173 173 src = os.path.join(path, config_file)
174 174 shutil.copy(src, dst)
175 175 return True
176 176
177 177 @classmethod
178 178 def create_profile_dir(cls, profile_dir, config=None):
179 179 """Create a new profile directory given a full path.
180 180
181 181 Parameters
182 182 ----------
183 183 profile_dir : str
184 184 The full path to the profile directory. If it does exist, it will
185 185 be used. If not, it will be created.
186 186 """
187 187 return cls(location=profile_dir, config=config)
188 188
189 189 @classmethod
190 190 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
191 191 """Create a profile dir by profile name and path.
192 192
193 193 Parameters
194 194 ----------
195 195 path : unicode
196 196 The path (directory) to put the profile directory in.
197 197 name : unicode
198 198 The name of the profile. The name of the profile directory will
199 199 be "profile_<profile>".
200 200 """
201 201 if not os.path.isdir(path):
202 202 raise ProfileDirError('Directory not found: %s' % path)
203 203 profile_dir = os.path.join(path, u'profile_' + name)
204 204 return cls(location=profile_dir, config=config)
205 205
206 206 @classmethod
207 207 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
208 208 """Find an existing profile dir by profile name, return its ProfileDir.
209 209
210 210 This searches through a sequence of paths for a profile dir. If it
211 211 is not found, a :class:`ProfileDirError` exception will be raised.
212 212
213 213 The search path algorithm is:
214 214 1. ``py3compat.getcwd()``
215 215 2. ``ipython_dir``
216 216
217 217 Parameters
218 218 ----------
219 219 ipython_dir : unicode or str
220 220 The IPython directory to use.
221 221 name : unicode or str
222 222 The name of the profile. The name of the profile directory
223 223 will be "profile_<profile>".
224 224 """
225 225 dirname = u'profile_' + name
226 226 paths = [py3compat.getcwd(), ipython_dir]
227 227 for p in paths:
228 228 profile_dir = os.path.join(p, dirname)
229 229 if os.path.isdir(profile_dir):
230 230 return cls(location=profile_dir, config=config)
231 231 else:
232 232 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
233 233
234 234 @classmethod
235 235 def find_profile_dir(cls, profile_dir, config=None):
236 236 """Find/create a profile dir and return its ProfileDir.
237 237
238 238 This will create the profile directory if it doesn't exist.
239 239
240 240 Parameters
241 241 ----------
242 242 profile_dir : unicode or str
243 243 The path of the profile directory. This is expanded using
244 244 :func:`IPython.utils.genutils.expand_path`.
245 245 """
246 246 profile_dir = expand_path(profile_dir)
247 247 if not os.path.isdir(profile_dir):
248 248 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
249 249 return cls(location=profile_dir, config=config)
@@ -1,166 +1,165 b''
1 1 # coding: utf-8
2 2 """Tests for profile-related functions.
3 3
4 4 Currently only the startup-dir functionality is tested, but more tests should
5 5 be added for:
6 6
7 7 * ipython profile create
8 8 * ipython profile list
9 9 * ipython profile create --parallel
10 10 * security dir permissions
11 11
12 12 Authors
13 13 -------
14 14
15 15 * MinRK
16 16
17 17 """
18 18 from __future__ import absolute_import
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25 import shutil
26 26 import sys
27 27 import tempfile
28 28
29 29 from unittest import TestCase
30 30
31 31 import nose.tools as nt
32 32
33 33 from IPython.core.profileapp import list_profiles_in, list_bundled_profiles
34 34 from IPython.core.profiledir import ProfileDir
35 35
36 36 from IPython.testing import decorators as dec
37 37 from IPython.testing import tools as tt
38 38 from IPython.utils import py3compat
39 39 from IPython.utils.process import getoutput
40 40 from IPython.utils.tempdir import TemporaryDirectory
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Globals
44 44 #-----------------------------------------------------------------------------
45 45 TMP_TEST_DIR = tempfile.mkdtemp()
46 46 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
47 47 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
48 48
49 49 #
50 50 # Setup/teardown functions/decorators
51 51 #
52 52
53 53 def setup():
54 54 """Setup test environment for the module:
55 55
56 56 - Adds dummy home dir tree
57 57 """
58 58 # Do not mask exceptions here. In particular, catching WindowsError is a
59 59 # problem because that exception is only defined on Windows...
60 60 os.makedirs(IP_TEST_DIR)
61 61
62 62
63 63 def teardown():
64 64 """Teardown test environment for the module:
65 65
66 66 - Remove dummy home dir tree
67 67 """
68 68 # Note: we remove the parent test dir, which is the root of all test
69 69 # subdirs we may have created. Use shutil instead of os.removedirs, so
70 70 # that non-empty directories are all recursively removed.
71 71 shutil.rmtree(TMP_TEST_DIR)
72 72
73 73
74 74 #-----------------------------------------------------------------------------
75 75 # Test functions
76 76 #-----------------------------------------------------------------------------
77 77 def win32_without_pywin32():
78 78 if sys.platform == 'win32':
79 79 try:
80 80 import pywin32
81 81 except ImportError:
82 82 return True
83 83 return False
84 84
85 85
86 86 class ProfileStartupTest(TestCase):
87 87 def setUp(self):
88 88 # create profile dir
89 89 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test')
90 90 self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test']
91 91 self.fname = os.path.join(TMP_TEST_DIR, 'test.py')
92 92
93 93 def tearDown(self):
94 94 # We must remove this profile right away so its presence doesn't
95 95 # confuse other tests.
96 96 shutil.rmtree(self.pd.location)
97 97
98 98 def init(self, startup_file, startup, test):
99 99 # write startup python file
100 100 with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f:
101 101 f.write(startup)
102 102 # write simple test file, to check that the startup file was run
103 103 with open(self.fname, 'w') as f:
104 104 f.write(py3compat.doctest_refactor_print(test))
105 105
106 106 def validate(self, output):
107 107 tt.ipexec_validate(self.fname, output, '', options=self.options)
108 108
109 109 @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
110 110 def test_startup_py(self):
111 111 self.init('00-start.py', 'zzz=123\n',
112 112 py3compat.doctest_refactor_print('print zzz\n'))
113 113 self.validate('123')
114 114
115 115 @dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
116 116 def test_startup_ipy(self):
117 117 self.init('00-start.ipy', '%xmode plain\n', '')
118 118 self.validate('Exception reporting mode: Plain')
119 119
120 120
121 121 def test_list_profiles_in():
122 122 # No need to remove these directories and files, as they will get nuked in
123 123 # the module-level teardown.
124 124 td = tempfile.mkdtemp(dir=TMP_TEST_DIR)
125 125 td = py3compat.str_to_unicode(td)
126 126 for name in ('profile_foo', 'profile_hello', 'not_a_profile'):
127 127 os.mkdir(os.path.join(td, name))
128 128 if dec.unicode_paths:
129 129 os.mkdir(os.path.join(td, u'profile_ünicode'))
130 130
131 131 with open(os.path.join(td, 'profile_file'), 'w') as f:
132 132 f.write("I am not a profile directory")
133 133 profiles = list_profiles_in(td)
134 134
135 135 # unicode normalization can turn u'ünicode' into u'u\0308nicode',
136 136 # so only check for *nicode, and that creating a ProfileDir from the
137 137 # name remains valid
138 138 found_unicode = False
139 139 for p in list(profiles):
140 140 if p.endswith('nicode'):
141 141 pd = ProfileDir.find_profile_dir_by_name(td, p)
142 142 profiles.remove(p)
143 143 found_unicode = True
144 144 break
145 145 if dec.unicode_paths:
146 146 nt.assert_true(found_unicode)
147 147 nt.assert_equal(set(profiles), set(['foo', 'hello']))
148 148
149 149
150 150 def test_list_bundled_profiles():
151 151 # This variable will need to be updated when a new profile gets bundled
152 bundled_true = [u'cluster', u'math', u'pysh', u'sympy']
153 152 bundled = sorted(list_bundled_profiles())
154 nt.assert_equal(bundled, bundled_true)
153 nt.assert_equal(bundled, [])
155 154
156 155
157 156 def test_profile_create_ipython_dir():
158 157 """ipython profile create respects --ipython-dir"""
159 158 with TemporaryDirectory() as td:
160 159 getoutput([sys.executable, '-m', 'IPython', 'profile', 'create',
161 160 'foo', '--ipython-dir=%s' % td])
162 161 profile_dir = os.path.join(td, 'profile_foo')
163 162 assert os.path.exists(profile_dir)
164 163 ipython_config = os.path.join(profile_dir, 'ipython_config.py')
165 164 assert os.path.exists(ipython_config)
166 165 No newline at end of file
@@ -1,755 +1,755 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 from __future__ import print_function
16 16
17 17 import errno
18 18 import os
19 19 import sys
20 20
21 21 from distutils import log
22 22 from distutils.command.build_py import build_py
23 23 from distutils.command.build_scripts import build_scripts
24 24 from distutils.command.install import install
25 25 from distutils.command.install_scripts import install_scripts
26 26 from distutils.cmd import Command
27 27 from distutils.errors import DistutilsExecError
28 28 from fnmatch import fnmatch
29 29 from glob import glob
30 30 from subprocess import Popen, PIPE
31 31
32 32 from setupext import install_data_ext
33 33
34 34 #-------------------------------------------------------------------------------
35 35 # Useful globals and utility functions
36 36 #-------------------------------------------------------------------------------
37 37
38 38 # A few handy globals
39 39 isfile = os.path.isfile
40 40 pjoin = os.path.join
41 41 repo_root = os.path.dirname(os.path.abspath(__file__))
42 42
43 43 def oscmd(s):
44 44 print(">", s)
45 45 os.system(s)
46 46
47 47 # Py3 compatibility hacks, without assuming IPython itself is installed with
48 48 # the full py3compat machinery.
49 49
50 50 try:
51 51 execfile
52 52 except NameError:
53 53 def execfile(fname, globs, locs=None):
54 54 locs = locs or globs
55 55 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
56 56
57 57 # A little utility we'll need below, since glob() does NOT allow you to do
58 58 # exclusion on multiple endings!
59 59 def file_doesnt_endwith(test,endings):
60 60 """Return true if test is a file and its name does NOT end with any
61 61 of the strings listed in endings."""
62 62 if not isfile(test):
63 63 return False
64 64 for e in endings:
65 65 if test.endswith(e):
66 66 return False
67 67 return True
68 68
69 69 #---------------------------------------------------------------------------
70 70 # Basic project information
71 71 #---------------------------------------------------------------------------
72 72
73 73 # release.py contains version, authors, license, url, keywords, etc.
74 74 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
75 75
76 76 # Create a dict with the basic information
77 77 # This dict is eventually passed to setup after additional keys are added.
78 78 setup_args = dict(
79 79 name = name,
80 80 version = version,
81 81 description = description,
82 82 long_description = long_description,
83 83 author = author,
84 84 author_email = author_email,
85 85 url = url,
86 86 download_url = download_url,
87 87 license = license,
88 88 platforms = platforms,
89 89 keywords = keywords,
90 90 classifiers = classifiers,
91 91 cmdclass = {'install_data': install_data_ext},
92 92 )
93 93
94 94
95 95 #---------------------------------------------------------------------------
96 96 # Find packages
97 97 #---------------------------------------------------------------------------
98 98
99 99 def find_packages():
100 100 """
101 101 Find all of IPython's packages.
102 102 """
103 103 excludes = ['deathrow', 'quarantine']
104 104 packages = []
105 105 for dir,subdirs,files in os.walk('IPython'):
106 106 package = dir.replace(os.path.sep, '.')
107 107 if any(package.startswith('IPython.'+exc) for exc in excludes):
108 108 # package is to be excluded (e.g. deathrow)
109 109 continue
110 110 if '__init__.py' not in files:
111 111 # not a package
112 112 continue
113 113 packages.append(package)
114 114 return packages
115 115
116 116 #---------------------------------------------------------------------------
117 117 # Find package data
118 118 #---------------------------------------------------------------------------
119 119
120 120 def find_package_data():
121 121 """
122 122 Find IPython's package_data.
123 123 """
124 124 # This is not enough for these things to appear in an sdist.
125 125 # We need to muck with the MANIFEST to get this to work
126 126
127 127 # exclude components and less from the walk;
128 128 # we will build the components separately
129 129 excludes = [
130 130 pjoin('static', 'components'),
131 131 pjoin('static', '*', 'less'),
132 132 ]
133 133
134 134 # walk notebook resources:
135 135 cwd = os.getcwd()
136 136 os.chdir(os.path.join('IPython', 'html'))
137 137 static_data = []
138 138 for parent, dirs, files in os.walk('static'):
139 139 if any(fnmatch(parent, pat) for pat in excludes):
140 140 # prevent descending into subdirs
141 141 dirs[:] = []
142 142 continue
143 143 for f in files:
144 144 static_data.append(pjoin(parent, f))
145 145
146 146 components = pjoin("static", "components")
147 147 # select the components we actually need to install
148 148 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
149 149 static_data.extend([
150 150 pjoin(components, "backbone", "backbone-min.js"),
151 151 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
152 152 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
153 153 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
154 154 pjoin(components, "es6-promise", "*.js"),
155 155 pjoin(components, "font-awesome", "fonts", "*.*"),
156 156 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
157 157 pjoin(components, "jquery", "jquery.min.js"),
158 158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
159 159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
160 160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
161 161 pjoin(components, "marked", "lib", "marked.js"),
162 162 pjoin(components, "requirejs", "require.js"),
163 163 pjoin(components, "underscore", "underscore-min.js"),
164 164 pjoin(components, "moment", "moment.js"),
165 165 pjoin(components, "moment", "min", "moment.min.js"),
166 166 pjoin(components, "term.js", "src", "term.js"),
167 167 pjoin(components, "text-encoding", "lib", "encoding.js"),
168 168 ])
169 169
170 170 # Ship all of Codemirror's CSS and JS
171 171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
172 172 for f in files:
173 173 if f.endswith(('.js', '.css')):
174 174 static_data.append(pjoin(parent, f))
175 175
176 176 os.chdir(os.path.join('tests',))
177 177 js_tests = glob('*.js') + glob('*/*.js')
178 178
179 179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
180 180 nbconvert_templates = [os.path.join(dirpath, '*.*')
181 181 for dirpath, _, _ in os.walk('templates')]
182 182
183 183 os.chdir(cwd)
184 184
185 185 package_data = {
186 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.core' : ['profile/README*'],
187 187 'IPython.core.tests' : ['*.png', '*.jpg'],
188 188 'IPython.lib.tests' : ['*.wav'],
189 189 'IPython.testing.plugin' : ['*.txt'],
190 190 'IPython.html' : ['templates/*'] + static_data,
191 191 'IPython.html.tests' : js_tests,
192 192 'IPython.nbconvert' : nbconvert_templates +
193 193 [
194 194 'tests/files/*.*',
195 195 'exporters/tests/files/*.*',
196 196 'preprocessors/tests/files/*.*',
197 197 ],
198 198 'IPython.nbconvert.filters' : ['marked.js'],
199 199 'IPython.nbformat' : [
200 200 'tests/*.ipynb',
201 201 'v3/nbformat.v3.schema.json',
202 202 'v4/nbformat.v4.schema.json',
203 203 ],
204 204 'IPython.kernel': ['resources/*.*'],
205 205 }
206 206
207 207 return package_data
208 208
209 209
210 210 def check_package_data(package_data):
211 211 """verify that package_data globs make sense"""
212 212 print("checking package data")
213 213 for pkg, data in package_data.items():
214 214 pkg_root = pjoin(*pkg.split('.'))
215 215 for d in data:
216 216 path = pjoin(pkg_root, d)
217 217 if '*' in path:
218 218 assert len(glob(path)) > 0, "No files match pattern %s" % path
219 219 else:
220 220 assert os.path.exists(path), "Missing package data: %s" % path
221 221
222 222
223 223 def check_package_data_first(command):
224 224 """decorator for checking package_data before running a given command
225 225
226 226 Probably only needs to wrap build_py
227 227 """
228 228 class DecoratedCommand(command):
229 229 def run(self):
230 230 check_package_data(self.package_data)
231 231 command.run(self)
232 232 return DecoratedCommand
233 233
234 234
235 235 #---------------------------------------------------------------------------
236 236 # Find data files
237 237 #---------------------------------------------------------------------------
238 238
239 239 def make_dir_struct(tag,base,out_base):
240 240 """Make the directory structure of all files below a starting dir.
241 241
242 242 This is just a convenience routine to help build a nested directory
243 243 hierarchy because distutils is too stupid to do this by itself.
244 244
245 245 XXX - this needs a proper docstring!
246 246 """
247 247
248 248 # we'll use these a lot below
249 249 lbase = len(base)
250 250 pathsep = os.path.sep
251 251 lpathsep = len(pathsep)
252 252
253 253 out = []
254 254 for (dirpath,dirnames,filenames) in os.walk(base):
255 255 # we need to strip out the dirpath from the base to map it to the
256 256 # output (installation) path. This requires possibly stripping the
257 257 # path separator, because otherwise pjoin will not work correctly
258 258 # (pjoin('foo/','/bar') returns '/bar').
259 259
260 260 dp_eff = dirpath[lbase:]
261 261 if dp_eff.startswith(pathsep):
262 262 dp_eff = dp_eff[lpathsep:]
263 263 # The output path must be anchored at the out_base marker
264 264 out_path = pjoin(out_base,dp_eff)
265 265 # Now we can generate the final filenames. Since os.walk only produces
266 266 # filenames, we must join back with the dirpath to get full valid file
267 267 # paths:
268 268 pfiles = [pjoin(dirpath,f) for f in filenames]
269 269 # Finally, generate the entry we need, which is a pari of (output
270 270 # path, files) for use as a data_files parameter in install_data.
271 271 out.append((out_path, pfiles))
272 272
273 273 return out
274 274
275 275
276 276 def find_data_files():
277 277 """
278 278 Find IPython's data_files.
279 279
280 280 Just man pages at this point.
281 281 """
282 282
283 283 manpagebase = pjoin('share', 'man', 'man1')
284 284
285 285 # Simple file lists can be made by hand
286 286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
287 287 if not manpages:
288 288 # When running from a source tree, the manpages aren't gzipped
289 289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
290 290
291 291 # And assemble the entire output list
292 292 data_files = [ (manpagebase, manpages) ]
293 293
294 294 return data_files
295 295
296 296
297 297 def make_man_update_target(manpage):
298 298 """Return a target_update-compliant tuple for the given manpage.
299 299
300 300 Parameters
301 301 ----------
302 302 manpage : string
303 303 Name of the manpage, must include the section number (trailing number).
304 304
305 305 Example
306 306 -------
307 307
308 308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
309 309 ('docs/man/ipython.1.gz',
310 310 ['docs/man/ipython.1'],
311 311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
312 312 """
313 313 man_dir = pjoin('docs', 'man')
314 314 manpage_gz = manpage + '.gz'
315 315 manpath = pjoin(man_dir, manpage)
316 316 manpath_gz = pjoin(man_dir, manpage_gz)
317 317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
318 318 locals() )
319 319 return (manpath_gz, [manpath], gz_cmd)
320 320
321 321 # The two functions below are copied from IPython.utils.path, so we don't need
322 322 # to import IPython during setup, which fails on Python 3.
323 323
324 324 def target_outdated(target,deps):
325 325 """Determine whether a target is out of date.
326 326
327 327 target_outdated(target,deps) -> 1/0
328 328
329 329 deps: list of filenames which MUST exist.
330 330 target: single filename which may or may not exist.
331 331
332 332 If target doesn't exist or is older than any file listed in deps, return
333 333 true, otherwise return false.
334 334 """
335 335 try:
336 336 target_time = os.path.getmtime(target)
337 337 except os.error:
338 338 return 1
339 339 for dep in deps:
340 340 dep_time = os.path.getmtime(dep)
341 341 if dep_time > target_time:
342 342 #print "For target",target,"Dep failed:",dep # dbg
343 343 #print "times (dep,tar):",dep_time,target_time # dbg
344 344 return 1
345 345 return 0
346 346
347 347
348 348 def target_update(target,deps,cmd):
349 349 """Update a target with a given command given a list of dependencies.
350 350
351 351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
352 352
353 353 This is just a wrapper around target_outdated() which calls the given
354 354 command if target is outdated."""
355 355
356 356 if target_outdated(target,deps):
357 357 os.system(cmd)
358 358
359 359 #---------------------------------------------------------------------------
360 360 # Find scripts
361 361 #---------------------------------------------------------------------------
362 362
363 363 def find_entry_points():
364 364 """Defines the command line entry points for IPython
365 365
366 366 This always uses setuptools-style entry points. When setuptools is not in
367 367 use, our own build_scripts_entrypt class below parses these and builds
368 368 command line scripts.
369 369
370 370 Each of our entry points gets both a plain name, e.g. ipython, and one
371 371 suffixed with the Python major version number, e.g. ipython3.
372 372 """
373 373 ep = [
374 374 'ipython%s = IPython:start_ipython',
375 375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
376 376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
377 377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
378 378 'iptest%s = IPython.testing.iptestcontroller:main',
379 379 ]
380 380 suffix = str(sys.version_info[0])
381 381 return [e % '' for e in ep] + [e % suffix for e in ep]
382 382
383 383 script_src = """#!{executable}
384 384 # This script was automatically generated by setup.py
385 385 if __name__ == '__main__':
386 386 from {mod} import {func}
387 387 {func}()
388 388 """
389 389
390 390 class build_scripts_entrypt(build_scripts):
391 391 """Build the command line scripts
392 392
393 393 Parse setuptools style entry points and write simple scripts to run the
394 394 target functions.
395 395
396 396 On Windows, this also creates .cmd wrappers for the scripts so that you can
397 397 easily launch them from a command line.
398 398 """
399 399 def run(self):
400 400 self.mkpath(self.build_dir)
401 401 outfiles = []
402 402 for script in find_entry_points():
403 403 name, entrypt = script.split('=')
404 404 name = name.strip()
405 405 entrypt = entrypt.strip()
406 406 outfile = os.path.join(self.build_dir, name)
407 407 outfiles.append(outfile)
408 408 print('Writing script to', outfile)
409 409
410 410 mod, func = entrypt.split(':')
411 411 with open(outfile, 'w') as f:
412 412 f.write(script_src.format(executable=sys.executable,
413 413 mod=mod, func=func))
414 414
415 415 if sys.platform == 'win32':
416 416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
417 417 # command line
418 418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
419 419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
420 420 python=sys.executable, script=name)
421 421 log.info("Writing %s wrapper script" % cmd_file)
422 422 with open(cmd_file, 'w') as f:
423 423 f.write(cmd)
424 424
425 425 return outfiles, outfiles
426 426
427 427 class install_lib_symlink(Command):
428 428 user_options = [
429 429 ('install-dir=', 'd', "directory to install to"),
430 430 ]
431 431
432 432 def initialize_options(self):
433 433 self.install_dir = None
434 434
435 435 def finalize_options(self):
436 436 self.set_undefined_options('symlink',
437 437 ('install_lib', 'install_dir'),
438 438 )
439 439
440 440 def run(self):
441 441 if sys.platform == 'win32':
442 442 raise Exception("This doesn't work on Windows.")
443 443 pkg = os.path.join(os.getcwd(), 'IPython')
444 444 dest = os.path.join(self.install_dir, 'IPython')
445 445 if os.path.islink(dest):
446 446 print('removing existing symlink at %s' % dest)
447 447 os.unlink(dest)
448 448 print('symlinking %s -> %s' % (pkg, dest))
449 449 os.symlink(pkg, dest)
450 450
451 451 class unsymlink(install):
452 452 def run(self):
453 453 dest = os.path.join(self.install_lib, 'IPython')
454 454 if os.path.islink(dest):
455 455 print('removing symlink at %s' % dest)
456 456 os.unlink(dest)
457 457 else:
458 458 print('No symlink exists at %s' % dest)
459 459
460 460 class install_symlinked(install):
461 461 def run(self):
462 462 if sys.platform == 'win32':
463 463 raise Exception("This doesn't work on Windows.")
464 464
465 465 # Run all sub-commands (at least those that need to be run)
466 466 for cmd_name in self.get_sub_commands():
467 467 self.run_command(cmd_name)
468 468
469 469 # 'sub_commands': a list of commands this command might have to run to
470 470 # get its work done. See cmd.py for more info.
471 471 sub_commands = [('install_lib_symlink', lambda self:True),
472 472 ('install_scripts_sym', lambda self:True),
473 473 ]
474 474
475 475 class install_scripts_for_symlink(install_scripts):
476 476 """Redefined to get options from 'symlink' instead of 'install'.
477 477
478 478 I love distutils almost as much as I love setuptools.
479 479 """
480 480 def finalize_options(self):
481 481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
482 482 self.set_undefined_options('symlink',
483 483 ('install_scripts', 'install_dir'),
484 484 ('force', 'force'),
485 485 ('skip_build', 'skip_build'),
486 486 )
487 487
488 488 #---------------------------------------------------------------------------
489 489 # Verify all dependencies
490 490 #---------------------------------------------------------------------------
491 491
492 492 def check_for_readline():
493 493 """Check for GNU readline"""
494 494 try:
495 495 import gnureadline as readline
496 496 except ImportError:
497 497 pass
498 498 else:
499 499 return True
500 500 try:
501 501 import readline
502 502 except ImportError:
503 503 return False
504 504 else:
505 505 if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
506 506 print("Ignoring readline linked to libedit", file=sys.stderr)
507 507 return False
508 508 return True
509 509
510 510 #---------------------------------------------------------------------------
511 511 # VCS related
512 512 #---------------------------------------------------------------------------
513 513
514 514 # utils.submodule has checks for submodule status
515 515 execfile(pjoin('IPython','utils','submodule.py'), globals())
516 516
517 517 class UpdateSubmodules(Command):
518 518 """Update git submodules
519 519
520 520 IPython's external javascript dependencies live in a separate repo.
521 521 """
522 522 description = "Update git submodules"
523 523 user_options = []
524 524
525 525 def initialize_options(self):
526 526 pass
527 527
528 528 def finalize_options(self):
529 529 pass
530 530
531 531 def run(self):
532 532 failure = False
533 533 try:
534 534 self.spawn('git submodule init'.split())
535 535 self.spawn('git submodule update --recursive'.split())
536 536 except Exception as e:
537 537 failure = e
538 538 print(e)
539 539
540 540 if not check_submodule_status(repo_root) == 'clean':
541 541 print("submodules could not be checked out")
542 542 sys.exit(1)
543 543
544 544
545 545 def git_prebuild(pkg_dir, build_cmd=build_py):
546 546 """Return extended build or sdist command class for recording commit
547 547
548 548 records git commit in IPython.utils._sysinfo.commit
549 549
550 550 for use in IPython.utils.sysinfo.sys_info() calls after installation.
551 551
552 552 Also ensures that submodules exist prior to running
553 553 """
554 554
555 555 class MyBuildPy(build_cmd):
556 556 ''' Subclass to write commit data into installation tree '''
557 557 def run(self):
558 558 build_cmd.run(self)
559 559 # this one will only fire for build commands
560 560 if hasattr(self, 'build_lib'):
561 561 self._record_commit(self.build_lib)
562 562
563 563 def make_release_tree(self, base_dir, files):
564 564 # this one will fire for sdist
565 565 build_cmd.make_release_tree(self, base_dir, files)
566 566 self._record_commit(base_dir)
567 567
568 568 def _record_commit(self, base_dir):
569 569 import subprocess
570 570 proc = subprocess.Popen('git rev-parse --short HEAD',
571 571 stdout=subprocess.PIPE,
572 572 stderr=subprocess.PIPE,
573 573 shell=True)
574 574 repo_commit, _ = proc.communicate()
575 575 repo_commit = repo_commit.strip().decode("ascii")
576 576
577 577 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
578 578 if os.path.isfile(out_pth) and not repo_commit:
579 579 # nothing to write, don't clobber
580 580 return
581 581
582 582 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
583 583
584 584 # remove to avoid overwriting original via hard link
585 585 try:
586 586 os.remove(out_pth)
587 587 except (IOError, OSError):
588 588 pass
589 589 with open(out_pth, 'w') as out_file:
590 590 out_file.writelines([
591 591 '# GENERATED BY setup.py\n',
592 592 'commit = u"%s"\n' % repo_commit,
593 593 ])
594 594 return require_submodules(MyBuildPy)
595 595
596 596
597 597 def require_submodules(command):
598 598 """decorator for instructing a command to check for submodules before running"""
599 599 class DecoratedCommand(command):
600 600 def run(self):
601 601 if not check_submodule_status(repo_root) == 'clean':
602 602 print("submodules missing! Run `setup.py submodule` and try again")
603 603 sys.exit(1)
604 604 command.run(self)
605 605 return DecoratedCommand
606 606
607 607 #---------------------------------------------------------------------------
608 608 # bdist related
609 609 #---------------------------------------------------------------------------
610 610
611 611 def get_bdist_wheel():
612 612 """Construct bdist_wheel command for building wheels
613 613
614 614 Constructs py2-none-any tag, instead of py2.7-none-any
615 615 """
616 616 class RequiresWheel(Command):
617 617 description = "Dummy command for missing bdist_wheel"
618 618 user_options = []
619 619
620 620 def initialize_options(self):
621 621 pass
622 622
623 623 def finalize_options(self):
624 624 pass
625 625
626 626 def run(self):
627 627 print("bdist_wheel requires the wheel package")
628 628 sys.exit(1)
629 629
630 630 if 'setuptools' not in sys.modules:
631 631 return RequiresWheel
632 632 else:
633 633 try:
634 634 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
635 635 except ImportError:
636 636 return RequiresWheel
637 637
638 638 class bdist_wheel_tag(bdist_wheel):
639 639
640 640 def add_requirements(self, metadata_path):
641 641 """transform platform-dependent requirements"""
642 642 pkg_info = read_pkg_info(metadata_path)
643 643 # pkg_info is an email.Message object (?!)
644 644 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
645 645 # and transform them to conditionals
646 646 requires = pkg_info.get_all('Requires-Dist')
647 647 del pkg_info['Requires-Dist']
648 648 def _remove_startswith(lis, prefix):
649 649 """like list.remove, but with startswith instead of =="""
650 650 found = False
651 651 for idx, item in enumerate(lis):
652 652 if item.startswith(prefix):
653 653 found = True
654 654 break
655 655 if found:
656 656 lis.pop(idx)
657 657
658 658 for pkg in ("gnureadline", "pyreadline", "mock", "terminado", "appnope", "pexpect"):
659 659 _remove_startswith(requires, pkg)
660 660 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
661 661 requires.append("terminado (>=0.3.3); extra == 'notebook' and sys.platform != 'win32'")
662 662 requires.append("terminado (>=0.3.3); extra == 'all' and sys.platform != 'win32'")
663 663 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
664 664 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
665 665 requires.append("mock; extra == 'test' and python_version < '3.3'")
666 666 requires.append("appnope; sys.platform == 'darwin'")
667 667 requires.append("pexpect; sys.platform != 'win32'")
668 668 for r in requires:
669 669 pkg_info['Requires-Dist'] = r
670 670 write_pkg_info(metadata_path, pkg_info)
671 671
672 672 return bdist_wheel_tag
673 673
674 674 #---------------------------------------------------------------------------
675 675 # Notebook related
676 676 #---------------------------------------------------------------------------
677 677
678 678 class CompileCSS(Command):
679 679 """Recompile Notebook CSS
680 680
681 681 Regenerate the compiled CSS from LESS sources.
682 682
683 683 Requires various dev dependencies, such as invoke and lessc.
684 684 """
685 685 description = "Recompile Notebook CSS"
686 686 user_options = [
687 687 ('minify', 'x', "minify CSS"),
688 688 ('force', 'f', "force recompilation of CSS"),
689 689 ]
690 690
691 691 def initialize_options(self):
692 692 self.minify = False
693 693 self.force = False
694 694
695 695 def finalize_options(self):
696 696 self.minify = bool(self.minify)
697 697 self.force = bool(self.force)
698 698
699 699 def run(self):
700 700 cmd = ['invoke', 'css']
701 701 if self.minify:
702 702 cmd.append('--minify')
703 703 if self.force:
704 704 cmd.append('--force')
705 705 try:
706 706 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
707 707 except OSError:
708 708 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
709 709 out, err = p.communicate()
710 710 if p.returncode:
711 711 if sys.version_info[0] >= 3:
712 712 err = err.decode('utf8', 'replace')
713 713 raise DistutilsExecError(err.strip())
714 714
715 715
716 716 class JavascriptVersion(Command):
717 717 """write the javascript version to notebook javascript"""
718 718 description = "Write IPython version to javascript"
719 719 user_options = []
720 720
721 721 def initialize_options(self):
722 722 pass
723 723
724 724 def finalize_options(self):
725 725 pass
726 726
727 727 def run(self):
728 728 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
729 729 with open(nsfile) as f:
730 730 lines = f.readlines()
731 731 with open(nsfile, 'w') as f:
732 732 found = False
733 733 for line in lines:
734 734 if line.strip().startswith("IPython.version"):
735 735 line = ' IPython.version = "{0}";\n'.format(version)
736 736 found = True
737 737 f.write(line)
738 738 if not found:
739 739 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
740 740
741 741
742 742 def css_js_prerelease(command):
743 743 """decorator for building js/minified css prior to a release"""
744 744 class DecoratedCommand(command):
745 745 def run(self):
746 746 self.distribution.run_command('jsversion')
747 747 css = self.distribution.get_command_obj('css')
748 748 css.minify = True
749 749 try:
750 750 self.distribution.run_command('css')
751 751 except Exception as e:
752 752 log.warn("rebuilding css and sourcemaps failed (not a problem)")
753 753 log.warn(str(e))
754 754 command.run(self)
755 755 return DecoratedCommand
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now