Skip to content

Commit 15f3d04

Browse files
committed
Do not rely on CMake to find Python libraries
1 parent 8617de8 commit 15f3d04

File tree

7 files changed

+157
-112
lines changed

7 files changed

+157
-112
lines changed

.travis.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,12 @@ addons:
4747
# https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
4848
- ubuntu-toolchain-r-test # for new libstdc++
4949
- llvm-toolchain-precise-3.7 # for clang
50-
- deadsnakes # for various versions of python
51-
- kalakris-cmake # for a more recent version of cmake (needed for ninja-build)
50+
- george-edison55-precise-backports # for a more recent version of cmake (3.2.3)
5251
packages:
52+
- cmake-data
5353
- cmake
5454
- clang-3.7
5555
- ninja-build
56-
# The confusing part is that on Travis Linux with YCMD_PYTHON_VERSION=3.3,
57-
# we build the C++ parts against the below system python3.3, but run
58-
# against the pyenv python3.3. This is because stupid cmake 2.8.11 has a
59-
# bug preventing it from finding the pyenv pythons (ostensibly; I haven't
60-
# checked, but online reports say the issue is gone with cmake 3.4).
61-
# Everything still works though, it's just weird.
62-
- python3.3
63-
- python3.3-dev
6456
# Everything below is a Python build dep (though it depends on Python
6557
# version). We need them because pyenv builds Python.
6658
- libssl-dev

build.py

Lines changed: 137 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
from __future__ import division
88
from __future__ import absolute_import
99

10+
from shutil import rmtree
11+
from tempfile import mkdtemp
12+
import errno
13+
import re
14+
import multiprocessing
1015
import os
11-
import subprocess
1216
import os.path as p
13-
import sys
17+
import platform
1418
import shlex
15-
import errno
19+
import subprocess
20+
import sys
1621

1722
PY_MAJOR, PY_MINOR = sys.version_info[ 0 : 2 ]
1823
if not ( ( PY_MAJOR == 2 and PY_MINOR >= 6 ) or
@@ -33,11 +38,22 @@
3338

3439
sys.path.insert( 1, p.abspath( p.join( DIR_OF_THIRD_PARTY, 'argparse' ) ) )
3540

36-
from tempfile import mkdtemp
37-
from shutil import rmtree
38-
import platform
3941
import argparse
40-
import multiprocessing
42+
43+
NO_DYNAMIC_PYTHON_ERROR = (
44+
'ERROR: found static Python library ({library}) but a dynamic one is '
45+
'required. You must use a Python compiled with the {flag} flag. '
46+
'If using pyenv, you need to run the command:\n'
47+
' export PYTHON_CONFIGURE_OPTS="{flag}"\n'
48+
'before installing a Python version.' )
49+
NO_PYTHON_LIBRARY_ERROR = 'ERROR: unable to find an appropriate Python library.'
50+
51+
LIBRARY_LDCONFIG_REGEX = re.compile(
52+
'(?P<library>\S+) \(.*\) => (?P<path>\S+)' )
53+
54+
55+
def OnLinux():
56+
return platform.system() == 'Linux'
4157

4258

4359
def OnMac():
@@ -120,82 +136,125 @@ def CheckOutput( *popen_args, **kwargs ):
120136
return output
121137

122138

139+
def GetPythonNameOnUnix():
140+
python_name = 'python' + str( PY_MAJOR ) + '.' + str( PY_MINOR )
141+
# Python 3 has an 'm' suffix on Unix platforms, for instance libpython3.3m.so.
142+
if PY_MAJOR == 3:
143+
python_name += 'm'
144+
return python_name
145+
146+
147+
def GetStandardPythonLocationsOnUnix( prefix, name ):
148+
return ( '{0}/lib/lib{1}'.format( prefix, name ),
149+
'{0}/include/{1}'.format( prefix, name ) )
150+
151+
152+
def FindPythonLibrariesOnLinux():
153+
python_name = GetPythonNameOnUnix()
154+
python_library_root, python_include = GetStandardPythonLocationsOnUnix(
155+
sys.exec_prefix, python_name )
156+
157+
python_library = python_library_root + '.so'
158+
if p.isfile( python_library ):
159+
return python_library, python_include
160+
161+
python_library = python_library_root + '.a'
162+
if p.isfile( python_library ):
163+
sys.exit( NO_DYNAMIC_PYTHON_ERROR.format( library = python_library,
164+
flag = '--enable-shared' ) )
165+
166+
# On some distributions (Ubuntu for instance), the Python system library is
167+
# not installed in its default path: /usr/lib. We use the ldconfig tool to
168+
# find it.
169+
python_library = 'lib' + python_name + '.so'
170+
ldconfig_output = CheckOutput( [ 'ldconfig', '-p' ] ).strip().decode( 'utf8' )
171+
for line in ldconfig_output.splitlines():
172+
match = LIBRARY_LDCONFIG_REGEX.search( line )
173+
if match and match.group( 'library' ) == python_library:
174+
return match.group( 'path' ), python_include
175+
176+
sys.exit( NO_PYTHON_LIBRARY_ERROR )
177+
178+
179+
def FindPythonLibrariesOnMac():
180+
python_prefix = sys.exec_prefix
181+
182+
python_library = p.join( python_prefix, 'Python' )
183+
if p.isfile( python_library ):
184+
return python_library, p.join( python_prefix, 'Headers' )
185+
186+
python_name = GetPythonNameOnUnix()
187+
python_library_root, python_include = GetStandardPythonLocationsOnUnix(
188+
python_prefix, python_name )
189+
190+
# On MacOS, ycmd does not work with statically linked python library.
191+
# It typically manifests with the following error when there is a
192+
# self-compiled python without --enable-framework (or, technically
193+
# --enable-shared):
194+
#
195+
# Fatal Python error: PyThreadState_Get: no current thread
196+
#
197+
# The most likely explanation for this is that both the ycm_core.so and the
198+
# python binary include copies of libpython.a (or whatever included
199+
# objects). When the python interpreter starts it initializes only the
200+
# globals within its copy, so when ycm_core.so's copy starts executing, it
201+
# points at its own copy which is uninitialized.
202+
#
203+
# Some platforms' dynamic linkers (ld.so) are able to resolve this when
204+
# loading shared libraries at runtime[citation needed], but OSX seemingly
205+
# cannot.
206+
#
207+
# So we do 2 things special on OS X:
208+
# - look for a .dylib first
209+
# - if we find a .a, raise an error.
210+
python_library = python_library_root + '.dylib'
211+
if p.isfile( python_library ):
212+
return python_library, python_include
213+
214+
python_library = python_library_root + '.a'
215+
if p.isfile( python_library ):
216+
sys.exit( NO_DYNAMIC_PYTHON_ERROR.format( library = python_library,
217+
flag = '--enable-framework' ) )
218+
219+
sys.exit( NO_PYTHON_LIBRARY_ERROR )
220+
221+
222+
def FindPythonLibrariesOnWindows():
223+
python_prefix = sys.exec_prefix
224+
python_name = 'python' + str( PY_MAJOR ) + str( PY_MINOR )
225+
226+
python_library = p.join( python_prefix, 'libs', python_name + '.lib' )
227+
if p.isfile( python_library ):
228+
return python_library, p.join( python_prefix, 'include' )
229+
230+
sys.exit( NO_PYTHON_LIBRARY_ERROR )
231+
232+
233+
def FindPythonLibraries():
234+
if OnLinux():
235+
return FindPythonLibrariesOnLinux()
236+
237+
if OnMac():
238+
return FindPythonLibrariesOnMac()
239+
240+
if OnWindows():
241+
return FindPythonLibrariesOnWindows()
242+
243+
sys.exit( 'ERROR: your platform is not supported by this script. Follow the '
244+
'Full Installation Guide instructions in the documentation.' )
245+
246+
123247
def CustomPythonCmakeArgs():
124248
# The CMake 'FindPythonLibs' Module does not work properly.
125249
# So we are forced to do its job for it.
250+
print( 'Searching Python {major}.{minor} libraries...'.format(
251+
major = PY_MAJOR, minor = PY_MINOR ) )
126252

127-
print( 'Searching for python libraries...' )
128-
129-
python_prefix = CheckOutput( [
130-
'python-config',
131-
'--prefix'
132-
] ).strip().decode( 'utf8' )
133-
134-
if p.isfile( p.join( python_prefix, '/Python' ) ):
135-
python_library = p.join( python_prefix, '/Python' )
136-
python_include = p.join( python_prefix, '/Headers' )
137-
print( 'Using OSX-style libs from {0}'.format( python_prefix ) )
138-
else:
139-
major_minor = CheckOutput( [
140-
'python',
141-
'-c',
142-
'import sys;i=sys.version_info;print( "%d.%d" % (i[0], i[1]) )'
143-
] ).strip().decode( 'utf8' )
144-
which_python = 'python' + major_minor
145-
146-
# Python 3 has an 'm' suffix, for instance libpython3.3m.a
147-
if major_minor.startswith( '3' ):
148-
which_python += 'm'
149-
150-
lib_python = '{0}/lib/lib{1}'.format( python_prefix, which_python ).strip()
151-
152-
print( 'Searching for python with prefix: {0} and lib {1}:'.format(
153-
python_prefix, which_python ) )
154-
155-
# On MacOS, ycmd does not work with statically linked python library.
156-
# It typically manifests with the following error when there is a
157-
# self-compiled python without --enable-framework (or, technically
158-
# --enable-shared):
159-
#
160-
# Fatal Python error: PyThreadState_Get: no current thread
161-
#
162-
# The most likely explanation for this is that both the ycm_core.so and the
163-
# python binary include copies of libpython.a (or whatever included
164-
# objects). When the python interpreter starts it initializes only the
165-
# globals within its copy, so when ycm_core.so's copy starts executing, it
166-
# points at its own copy which is uninitialized.
167-
#
168-
# Some platforms' dynamic linkers (ld.so) are able to resolve this when
169-
# loading shared libraries at runtime[citation needed], but OSX seemingly
170-
# cannot.
171-
#
172-
# So we do 2 things special on OS X:
173-
# - look for a .dylib first
174-
# - if we find a .a, raise an error.
175-
176-
if p.isfile( '{0}.dylib'.format( lib_python ) ):
177-
python_library = '{0}.dylib'.format( lib_python )
178-
elif p.isfile( '/usr/lib/lib{0}.dylib'.format( which_python ) ):
179-
# For no clear reason, python2.6 only exists in /usr/lib on OS X and
180-
# not in the python prefix location
181-
python_library = '/usr/lib/lib{0}.dylib'.format( which_python )
182-
elif p.isfile( '{0}.a'.format( lib_python ) ):
183-
if OnMac():
184-
sys.exit( 'ERROR: You must use a python compiled with '
185-
'--enable-shared or --enable-framework (and thus a {0}.dylib '
186-
'library) on OS X'.format( lib_python ) )
187-
188-
python_library = '{0}.a'.format( lib_python )
189-
# This check is for CYGWIN
190-
elif p.isfile( '{0}.dll.a'.format( lib_python ) ):
191-
python_library = '{0}.dll.a'.format( lib_python )
192-
else:
193-
sys.exit( 'ERROR: Unable to find an appropriate python library' )
253+
python_library, python_include = FindPythonLibraries()
194254

195-
python_include = '{0}/include/{1}'.format( python_prefix, which_python )
255+
print( 'Found Python library: {0}'.format( python_library ) )
256+
print( 'Found Python headers folder: {0}'.format( python_include ) )
196257

197-
print( 'Using PYTHON_LIBRARY={0} PYTHON_INCLUDE_DIR={1}'.format(
198-
python_library, python_include ) )
199258
return [
200259
'-DPYTHON_LIBRARY={0}'.format( python_library ),
201260
'-DPYTHON_INCLUDE_DIR={0}'.format( python_include )
@@ -322,8 +381,7 @@ def BuildYcmdLib( args ):
322381

323382
try:
324383
full_cmake_args = [ '-G', GetGenerator( args ) ]
325-
if OnMac():
326-
full_cmake_args.extend( CustomPythonCmakeArgs() )
384+
full_cmake_args.extend( CustomPythonCmakeArgs() )
327385
full_cmake_args.extend( GetCmakeArgs( args ) )
328386
full_cmake_args.append( p.join( DIR_OF_THIS_SCRIPT, 'cpp' ) )
329387

ci/appveyor/appveyor_install.bat

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,6 @@ if %python% == 27 (
3333
set PYTHONHOME=%python_path%
3434
)
3535

36-
:: When using Python 3 on AppVeyor, CMake will always pick the 64 bits
37-
:: libraries. We specifically tell CMake the right path to the libraries
38-
:: according to the architecture.
39-
if %python% == 35 (
40-
set EXTRA_CMAKE_ARGS="-DPYTHON_LIBRARY=%python_path%\libs\python%python%.lib"
41-
)
42-
4336
appveyor DownloadFile https://bootstrap.pypa.io/get-pip.py
4437
python get-pip.py
4538
pip install -r test_requirements.txt

ci/travis/travis_install.linux.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ ln -s /usr/bin/clang-3.7 ${HOME}/bin/gcc
1717

1818
export PATH=${HOME}/bin:${PATH}
1919

20+
# In order to work with ycmd, python *must* be built as a shared library. This
21+
# is set via the PYTHON_CONFIGURE_OPTS option.
22+
export PYTHON_CONFIGURE_OPTS="--enable-shared"

ci/travis/travis_install.osx.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ REQUIREMENTS="node.js
1515
pkg-config
1616
openssl"
1717

18-
# Install node, go, ninja, pyenv and dependencies
18+
# Install node, go, ninja, pyenv and dependencies.
1919
for pkg in $REQUIREMENTS; do
20-
# Install package, or upgrade it if it is already installed
20+
# Install package, or upgrade it if it is already installed.
2121
brew install $pkg || brew outdated $pkg || brew upgrade $pkg
2222
done
2323

2424
# In order to work with ycmd, python *must* be built as a shared library. The
2525
# most compatible way to do this on OS X is with --enable-framework. This is
26-
# set via the PYTHON_CONFIGURE_OPTS option
26+
# set via the PYTHON_CONFIGURE_OPTS option.
2727
export PYTHON_CONFIGURE_OPTS="--enable-framework"

cpp/CMakeLists.txt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,12 @@
1515
# You should have received a copy of the GNU General Public License
1616
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
1717

18-
if ( DEFINED ENV{TRAVIS} )
19-
# We lie to Travis CI about the min CMake version we need. For what we use
20-
# Travis for, the old version of CMake that it has is good enough.
21-
cmake_minimum_required( VERSION 2.8 )
18+
if ( APPLE )
19+
# OSX requires CMake >= 2.8.12, see YCM issue #1439
20+
cmake_minimum_required( VERSION 2.8.12 )
2221
else()
23-
if ( APPLE )
24-
# OSX requires CMake >= 2.8.12, see YCM issue #1439
25-
cmake_minimum_required( VERSION 2.8.12 )
26-
else()
27-
# CMake 2.8.11 is the latest available version on RHEL/CentOS 7
28-
cmake_minimum_required( VERSION 2.8.11 )
29-
endif()
22+
# CMake 2.8.11 is the latest available version on RHEL/CentOS 7
23+
cmake_minimum_required( VERSION 2.8.11 )
3024
endif()
3125

3226
project( YouCompleteMe )

cpp/ycm/CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,15 @@ if ( EXTERNAL_LIBCLANG_PATH OR USE_SYSTEM_LIBCLANG )
276276
else()
277277
# For Macs, we do things differently; look further in this file.
278278
if ( NOT APPLE )
279-
# Setting this to true makes sure that libraries we build will have our rpath
280-
# set even without having to do "make install"
279+
# Setting this to true makes sure that libraries we build will have our
280+
# rpath set even without having to do "make install"
281281
set( CMAKE_BUILD_WITH_INSTALL_RPATH TRUE )
282282
set( CMAKE_INSTALL_RPATH "\$ORIGIN" )
283+
# Add directories from all libraries outside the build tree to the rpath.
284+
# This makes the dynamic linker able to find non system libraries that
285+
# our libraries require, in particular the Python one (from pyenv for
286+
# instance).
287+
set( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE )
283288
endif()
284289
endif()
285290

0 commit comments

Comments
 (0)