From 2ecbf779cf4d5a35439fbde48a6a0da9886fc27b 2014-09-03 00:26:03 From: Min RK Date: 2014-09-03 00:26:03 Subject: [PATCH] Merge pull request #6291 from takluyver/explicit-install-native-kernelspec Make installation of native kernelspec explicit --- diff --git a/IPython/kernel/kernelspec.py b/IPython/kernel/kernelspec.py index f129886..a0a81a1 100644 --- a/IPython/kernel/kernelspec.py +++ b/IPython/kernel/kernelspec.py @@ -9,6 +9,7 @@ pjoin = os.path.join from IPython.utils.path import get_ipython_dir from IPython.utils.py3compat import PY3 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict, Any +from .launcher import make_ipkernel_cmd if os.name == 'nt': programdata = os.environ.get('PROGRAMDATA', None) @@ -100,51 +101,59 @@ class KernelSpecManager(HasTraits): self.user_kernel_dir, ] - def _make_native_kernel_dir(self): + @property + def _native_kernel_dict(self): """Makes a kernel directory for the native kernel. The native kernel is the kernel using the same Python runtime as this process. This will put its informatino in the user kernels directory. """ - path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME) - os.makedirs(path, mode=0o755) - with open(pjoin(path, 'kernel.json'), 'w') as f: - json.dump({'argv':[NATIVE_KERNEL_NAME, '-c', - 'from IPython.kernel.zmq.kernelapp import main; main()', - '-f', '{connection_file}'], - 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2), - 'language': 'python', - 'codemirror_mode': {'name': 'ipython', - 'version': sys.version_info[0]}, - }, - f, indent=1) - # TODO: Copy icons into directory - return path - + return {'argv':make_ipkernel_cmd( + 'from IPython.kernel.zmq.kernelapp import main; main()'), + 'display_name': 'IPython (Python %d)' % (3 if PY3 else 2), + 'language': 'python', + 'codemirror_mode': {'name': 'ipython', + 'version': sys.version_info[0]}, + } + + @property + def _native_kernel_resource_dir(self): + # TODO: This may be different when we actually have any resources + return os.path.dirname(__file__) + def find_kernel_specs(self): """Returns a dict mapping kernel names to resource directories.""" d = {} for kernel_dir in self.kernel_dirs: d.update(_list_kernels_in(kernel_dir)) - - if NATIVE_KERNEL_NAME not in d: - d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir() + + d[NATIVE_KERNEL_NAME] = self._native_kernel_resource_dir return d # TODO: Caching? - + def get_kernel_spec(self, kernel_name): """Returns a :class:`KernelSpec` instance for the given kernel_name. Raises :exc:`NoSuchKernel` if the given kernel name is not found. """ - if kernel_name == 'python': - kernel_name = NATIVE_KERNEL_NAME + if kernel_name in {'python', NATIVE_KERNEL_NAME}: + return KernelSpec(self._native_kernel_resource_dir, **self._native_kernel_dict) + d = self.find_kernel_specs() try: resource_dir = d[kernel_name.lower()] except KeyError: raise NoSuchKernel(kernel_name) return KernelSpec.from_resource_dir(resource_dir) + + def _get_destination_dir(self, kernel_name, system=False): + if system: + if SYSTEM_KERNEL_DIRS: + return os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name) + else: + raise EnvironmentError("No system kernel directory is available") + else: + return os.path.join(self.user_kernel_dir, kernel_name) def install_kernel_spec(self, source_dir, kernel_name=None, system=False, replace=False): @@ -164,20 +173,32 @@ class KernelSpecManager(HasTraits): if not kernel_name: kernel_name = os.path.basename(source_dir) kernel_name = kernel_name.lower() - - if system: - if SYSTEM_KERNEL_DIRS: - destination = os.path.join(SYSTEM_KERNEL_DIRS[-1], kernel_name) - else: - raise EnvironmentError("No system kernel directory is available") - else: - destination = os.path.join(self.user_kernel_dir, kernel_name) + + destination = self._get_destination_dir(kernel_name, system=system) if replace and os.path.isdir(destination): shutil.rmtree(destination) shutil.copytree(source_dir, destination) + def install_native_kernel_spec(self, system=False): + """Install the native kernel spec to the filesystem + + This allows a Python 3 frontend to use a Python 2 kernel, or vice versa. + The kernelspec will be written pointing to the Python executable on + which this is run. + + If ``system`` is True, it will attempt to install into the systemwide + kernel registry. If the process does not have appropriate permissions, + an :exc:`OSError` will be raised. + """ + path = self._get_destination_dir(NATIVE_KERNEL_NAME, system=system) + os.makedirs(path, mode=0o755) + with open(pjoin(path, 'kernel.json'), 'w') as f: + json.dump(self._native_kernel_dict, f, indent=1) + # TODO: Copy icons into directory + return path + def find_kernel_specs(): """Returns a dict mapping kernel names to resource directories.""" return KernelSpecManager().find_kernel_specs() @@ -194,3 +215,8 @@ def install_kernel_spec(source_dir, kernel_name=None, system=False, replace=Fals system, replace) install_kernel_spec.__doc__ = KernelSpecManager.install_kernel_spec.__doc__ + +def install_native_kernel_spec(self, system=False): + return KernelSpecManager().install_native_kernel_spec(system=system) + +install_native_kernel_spec.__doc__ = KernelSpecManager.install_native_kernel_spec.__doc__ diff --git a/IPython/kernel/kernelspecapp.py b/IPython/kernel/kernelspecapp.py index 0de87eb..be94f3f 100644 --- a/IPython/kernel/kernelspecapp.py +++ b/IPython/kernel/kernelspecapp.py @@ -91,15 +91,46 @@ class InstallKernelSpec(BaseIPythonApplication): self.exit(1) raise +class InstallNativeKernelSpec(BaseIPythonApplication): + description = """Install the native kernel spec directory for this Python.""" + kernel_spec_manager = Instance(KernelSpecManager) + + def _kernel_spec_manager_default(self): + return KernelSpecManager(ipython_dir=self.ipython_dir) + + system = Bool(False, config=True, + help=""" + Try to install the kernel spec to the systemwide directory instead of + the per-user directory. + """ + ) + + # Not all of the base aliases are meaningful (e.g. profile) + aliases = {k: base_aliases[k] for k in ['ipython-dir', 'log-level']} + flags = {'system': ({'InstallOwnKernelSpec': {'system': True}}, + "Install to the systemwide kernel registry"), + 'debug': base_flags['debug'], + } + + def start(self): + try: + self.kernel_spec_manager.install_native_kernel_spec(system=self.system) + except OSError as e: + if e.errno == errno.EACCES: + print("Permission denied") + self.exit(1) + raise + class KernelSpecApp(Application): name = "ipython kernelspec" description = """Manage IPython kernel specifications.""" - subcommands = Dict(dict( - list = (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), - install = (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]) - )) - + subcommands = Dict({ + 'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), + 'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]), + 'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]), + }) + aliases = {} flags = {} diff --git a/IPython/kernel/launcher.py b/IPython/kernel/launcher.py index dc95c04..0c9c8ea 100644 --- a/IPython/kernel/launcher.py +++ b/IPython/kernel/launcher.py @@ -97,14 +97,11 @@ def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw): A Popen command list """ - - # Build the kernel launch command. if executable is None: executable = sys.executable arguments = [ executable, '-c', code, '-f', '{connection_file}' ] arguments.extend(extra_arguments) - # Spawn a kernel. if sys.platform == 'win32': # If the kernel is running on pythonw and stdout/stderr are not been diff --git a/IPython/kernel/manager.py b/IPython/kernel/manager.py index 318254b..9dcef8c 100644 --- a/IPython/kernel/manager.py +++ b/IPython/kernel/manager.py @@ -159,18 +159,13 @@ class KernelManager(ConnectionFileMixin): # Kernel management #-------------------------------------------------------------------------- - def format_kernel_cmd(self, **kw): + def format_kernel_cmd(self, extra_arguments=None): """replace templated args (e.g. {connection_file})""" + extra_arguments = extra_arguments or [] if self.kernel_cmd: - cmd = self.kernel_cmd - elif self.kernel_name == kernelspec.NATIVE_KERNEL_NAME: - # The native kernel gets special handling - cmd = make_ipkernel_cmd( - 'from IPython.kernel.zmq.kernelapp import main; main()', - **kw - ) + cmd = self.kernel_cmd + extra_arguments else: - cmd = self.kernel_spec.argv + cmd = self.kernel_spec.argv + extra_arguments ns = dict(connection_file=self.connection_file) ns.update(self._launch_args) @@ -227,7 +222,8 @@ class KernelManager(ConnectionFileMixin): # save kwargs for use in restart self._launch_args = kw.copy() # build the Popen cmd - kernel_cmd = self.format_kernel_cmd(**kw) + extra_arguments = kw.pop('extra_arguments', []) + kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments) if self.kernel_cmd: # If kernel_cmd has been set manually, don't refer to a kernel spec env = os.environ diff --git a/docs/source/whatsnew/pr/incompat-kernelmanager-start-args b/docs/source/whatsnew/pr/incompat-kernelmanager-start-args new file mode 100644 index 0000000..46af03e --- /dev/null +++ b/docs/source/whatsnew/pr/incompat-kernelmanager-start-args @@ -0,0 +1,2 @@ +- :meth:`~.KernelManager.start_kernel` and :meth:`~.KernelManager.format_kernel_cmd` + no longer accept a ``executable`` parameter. Use the kernelspec machinery instead.