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