Repository: ipython/ipython

Issue:
otool should not be unconditionally called on osx
On OSX systems without XCode installed, IPython won't run, crashing with:

<pre>

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.2/bin/ipython3", line 10, in <module>
    launch_new_instance()
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/frontend/terminal/ipapp.py", line 662, in launch_new_instance
    app.start()
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/core/application.py", line 219, in start
    self.initialize()
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/core/application.py", line 211, in initialize
    self.construct()
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/frontend/terminal/ipapp.py", line 478, in construct
    self.shell = TerminalInteractiveShell.instance(config=self.master_config)
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/config/configurable.py", line 203, in instance
    inst = cls(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/frontend/terminal/interactiveshell.py", line 86, in __init__
    user_global_ns=user_global_ns, custom_exceptions=custom_exceptions
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/core/interactiveshell.py", line 400, in __init__
    self.init_readline()
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/core/interactiveshell.py", line 1542, in init_readline
    import IPython.utils.rlineimpl as readline
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/site-packages/IPython/utils/rlineimpl.py", line 59, in <module>
    p = Popen(['otool', '-L', _rl.__file__], stdout=PIPE, stderr=PIPE)
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/subprocess.py", line 736, in __init__
    restore_signals, start_new_session)
  File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/subprocess.py", line 1330, in _execute_child
    raise child_exception_type(errno_num, err_msg)
OSError: [Errno 2] No such file or directory: 'otool'
</pre>


We need to make the otool call conditional, since XCode is a fairly large dependency that we don't want to require of all our users.



Discussion:
I can detect libedt with lsof, which isn't part of xcode:

`'lsof -p %i'%os.getpid()`
and check for libedit.*.dylib

Note that this doesn't check the readline.so object _directly_, so if another module has linked to libedit and is imported prior to IPython, there will be a false positive.


Resolving patch:
diff --git a/IPython/utils/rlineimpl.py b/IPython/utils/rlineimpl.py
--- a/IPython/utils/rlineimpl.py
+++ b/IPython/utils/rlineimpl.py
@@ -9,6 +9,7 @@
 boolean and _outputfile variable used in IPython.utils.
 """
 
+import os
 import re
 import sys
 import time
@@ -44,7 +45,7 @@ def add_history(line):
     try:
         _outputfile=_rl.GetOutputFile()
     except AttributeError:
-        print "Failed GetOutputFile"
+        warnings.warn("Failed GetOutputFile")
         have_readline = False
 
 # Test to see if libedit is being used instead of GNU readline.
@@ -56,21 +57,36 @@ def add_history(line):
     # seems to make this better behaved in environments such as PyQt and gdb
     dt = 1e-3
     while dt < 1:
-        p = Popen(['otool', '-L', _rl.__file__], stdout=PIPE, stderr=PIPE)
-        otool,err = p.communicate()
+        try:
+            p = Popen(['otool', '-L', _rl.__file__], stdout=PIPE, stderr=PIPE)
+        except OSError:
+            try:
+                # otool not available (no XCode), use lsof instead.
+                # This *could* have a false positive
+                # if another package that uses libedit explicitly
+                # has been imported prior to this test.
+                p = Popen(['lsof', '-p', str(os.getpid())], stdout=PIPE, stderr=PIPE)
+            except OSError:
+                # This is highly unlikely, but let's be sure
+                # we don't crash IPython just because we can't find lsof
+                p = out = err = None
+                warnings.warn("libedit detection failed")
+                break
+
+        out,err = p.communicate()
 
         if p.returncode == 4:
             # EINTR
             time.sleep(dt)
             dt *= 2
             continue
-        elif p.returncode:
+        elif p is None or p.returncode:
             warnings.warn("libedit detection failed: %s"%err)
             break
         else:
             break
 
-    if p.returncode == 0 and re.search(r'/libedit[\.\d+]*\.dylib\s', otool):
+    if p is not None and p.returncode == 0 and re.search(br'/libedit[\.\d+]*\.dylib\s', out):
         # we are bound to libedit - new in Leopard
         _rl.parse_and_bind("bind ^I rl_complete")
         warnings.warn("Leopard libedit detected - readline will not be well behaved "
@@ -79,7 +95,8 @@ def add_history(line):
             "which is easy_installable with: 'easy_install readline'",
             RuntimeWarning)
         uses_libedit = True
-
+    # cleanup names
+    del dt,p,out,err
 
 # the clear_history() function was only introduced in Python 2.4 and is
 # actually optional in the readline API, so we must explicitly check for its


Test patch: