写这篇笔记的时候,我觉得自己像是职业的“产品评测人员“,在一段时间里孜孜不倦地摆弄某个东西,也许能够发现一些问题,但可能这些问题绝大多数人都不会遇到。我不知道这是否有意义。

Why

前段时间买了个笔记本,人生中第一台自己的笔记本,型号是ThinkPad T460。什么都挺好(4g内存我都忍了),除了显示屏:分辨率是1366x768。是的,你没有看错,这是我在2017年买的全新笔记本所使用的分辨率,可能现在中低端手机上的屏幕分辨率都得比这个高。

装了Android Studio,我只能说写代码的感觉相当憋把:一屏下来就显示不了多少代码,不论是长度还是宽度;界面编辑看着也相当难受。

当然,一切在终端下看着还好。

于是,把vim折腾成开发环境就有了很充足的理由。而且最近学习django,也确实在vim下写了一些python代码,时常YY如果vim能够自动补全django库得多爽。终端下的代码自动补全?必须上YouCompleteMe啊!

我的操作系统是Fedora 25 64bit,内核版本4.10.6-200.fc25.x86_64。部分内容根据事后回忆整理,不一定准确,仅供参考。

下载YCM

我是通过Vundle这一插件管理工具下载YCM的,访问github可能需要使用代理。在我的情况下载速度相当慢,有好几次出现了异常中断的情况。不过这些与本文主题关系不大,略过不表。

简易模式安装

YCM提供了安装脚本,这也是我首先选择的安装方式。不幸的是,脚本执行过程中需要下载clang+llvm的一个prebuilt包,在我的情况中是就是Clang for x86_64 Ubuntu 14.04。虽然通过传入--system-libclang参数使用系统自带clang库(Fedora 25已自带或可以通过dnf安装clang-libs,版本是3.9.1)而免去下载过程,但YCM强烈不建议这么做:

NOTE: We STRONGLY recommend AGAINST use of the system libclang instead of the upstream compiled binaries. Random things may break. Save yourself the hassle and use the upstream pre-built libclang.

似乎这个站点的下载速度更慢(四川电信169套餐,号称带宽100Mb),试过几次,看着下载进度以几分钟1%的速度更新,最后终于决定还是放弃了。

没有其它办法,只能选择YCM官方文档中的Full Installation Guide模式,但这也需要前面提到的那个prebuilt包。于是在uget中新建了一个下载任务,电脑开着的时候就下载。

完全模式安装

几天之后,Clang for x86_64 Ubuntu 14.04终于下载完毕。我把它解压到~/clang-latest目录下(包含bin, include, lib等子目录)。本以为剩下就configure && make && make install就没我啥事儿了,没想到按照YCM文档,在第一步生成makefile的时候就出错了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
paul at fed in ~/ycm_build
$ cmake -G "Unix Makefiles" -DPATH_TO_LLVM_ROOT=~/clang-latest . ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp
-- The C compiler identification is GNU 6.3.1
-- The CXX compiler identification is GNU 6.3.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonLibs: /usr/lib64/libpython2.7.so (found suitable version "2.7.13", minimum required is "2.6")
Using libclang to provide semantic completion for C/C++/ObjC
CMake Error at ycm/CMakeLists.txt:315 (file):
file RENAME failed to rename
/home/paul/clang-latest/lib/libclang.so..
to
/home/paul/clang-latest/lib/libclang.so.
because: No such file or directory
Using external libclang: /home/paul/clang-latest/lib/libclang.so.4.0
-- Found PythonInterp: /usr/bin/python2.7 (found version "2.7.13")
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring incomplete, errors occurred!
See also "/home/paul/ycm_build/CMakeFiles/CMakeOutput.log".
See also "/home/paul/ycm_build/CMakeFiles/CMakeError.log".

上面cmake的输出中提示执行RENAME的时候出错了,但我感觉这应该不是一个关键错误(这一判断是整个过程中我所犯的关键错误。。。),于是从CMakeError.log中寻找线索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Determining if the function pthread_create exists in the pthreads failed with the following output:
Change Dir: /home/paul/ycm_build/CMakeFiles/CMakeTmp
Run Build Command:"/usr/bin/gmake" "cmTC_565cb/fast"
/usr/bin/gmake -f CMakeFiles/cmTC_565cb.dir/build.make CMakeFiles/cmTC_565cb.dir/build
gmake[1]: Entering directory '/home/paul/ycm_build/CMakeFiles/CMakeTmp'
Building C object CMakeFiles/cmTC_565cb.dir/CheckFunctionExists.c.o
/usr/bin/cc -DCHECK_FUNCTION_EXISTS=pthread_create -o CMakeFiles/cmTC_565cb.dir/CheckFunctionExists.c.o -c /usr/share/cmake/Modules/CheckFunctionExists.c
Linking C executable cmTC_565cb
/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_565cb.dir/link.txt --verbose=1
/usr/bin/cc -DCHECK_FUNCTION_EXISTS=pthread_create CMakeFiles/cmTC_565cb.dir/CheckFunctionExists.c.o -o cmTC_565cb -rdynamic -lpthreads
/usr/bin/ld: cannot find -lpthreads
collect2: error: ld returned 1 exit status
CMakeFiles/cmTC_565cb.dir/build.make:97: recipe for target 'cmTC_565cb' failed
gmake[1]: *** [cmTC_565cb] Error 1
gmake[1]: Leaving directory '/home/paul/ycm_build/CMakeFiles/CMakeTmp'
Makefile:126: recipe for target 'cmTC_565cb/fast' failed
gmake: *** [cmTC_565cb/fast] Error 2

原来是在检测pthread库的时候出错了。不过这个/usr/bin/ld: cannot find -lpthreads很奇怪,应该-lpthread才对啊,怎么多了一个s?于是在网上查找相关信息,居然没有遇到和我同样问题的。不过,通过查看相关帖子我发现cmake(我使用的版本是cmake 3.6.2,系统自带)在pthread库测试方面倒是有各种稀奇古怪的选项,比如说我可能需要这个THREADS_PREFER_PTHREAD_FLAG,启用该选项后指示gcc在使用pthread库时使用-pthread而不是-lpthread(居然还能这样写,我真是孤陋寡闻了)。是否需要启用该选项要看gcc的版本,似乎老一点的gcc就不支持-pthread(我的gcc版本是6.3.1,应该算是相当新了),于是,我在~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/tests/gmock/gtest/cmake/internal_utils.cmake文件中增加了一行:

1
2
set(THREADS_PREFER_PTHREAD_FLAG 1) # new
find_package(Threads)

以为万事大吉,不过cmake还是出错了,但这一次CMakeError.log给出的信息不一样了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Determining if the pthread_create exist failed with the following output:
Change Dir: /home/paul/ycm_build/CMakeFiles/CMakeTmp
Run Build Command:"/usr/bin/gmake" "cmTC_502dd/fast"
/usr/bin/gmake -f CMakeFiles/cmTC_502dd.dir/build.make CMakeFiles/cmTC_502dd.dir/build
gmake[1]: Entering directory '/home/paul/ycm_build/CMakeFiles/CMakeTmp'
Building C object CMakeFiles/cmTC_502dd.dir/CheckSymbolExists.c.o
/usr/bin/cc -o CMakeFiles/cmTC_502dd.dir/CheckSymbolExists.c.o -c /home/paul/ycm_build/CMakeFiles/CMakeTmp/CheckSymbolExists.c
Linking C executable cmTC_502dd
/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_502dd.dir/link.txt --verbose=1
/usr/bin/cc CMakeFiles/cmTC_502dd.dir/CheckSymbolExists.c.o -o cmTC_502dd -rdynamic
CMakeFiles/cmTC_502dd.dir/CheckSymbolExists.c.o: In function `main':
CheckSymbolExists.c:(.text+0x16): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
CMakeFiles/cmTC_502dd.dir/build.make:97: recipe for target 'cmTC_502dd' failed
gmake[1]: *** [cmTC_502dd] Error 1
gmake[1]: Leaving directory '/home/paul/ycm_build/CMakeFiles/CMakeTmp'
Makefile:126: recipe for target 'cmTC_502dd/fast' failed
gmake: *** [cmTC_502dd/fast] Error 2

我心说这特么都啥**玩意儿啊?装个软件还这么费尽?人生到底图个啥?

解决第一个编译错误

第一次尝试修改失败,只得认真看看cmake输出的关于RENAME的错误提示到底怎么来的。打开 ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/CMakeList.txt,定位到315行附近:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# One can use the system libclang.[so|dylib] like so:
# cmake -DUSE_SYSTEM_LIBCLANG=1 [...]
# One can also explicitely pick the external libclang.[so|dylib] for use like so:
# cmake -DEXTERNAL_LIBCLANG_PATH=/path/to/libclang.so [...]
# The final .so we build will then first look in the same dir in which it is
# located for libclang.so. This is provided by the rpath = $ORIGIN feature.
message("EXTERNAL_LIBCLANG_PATH=${EXTERNAL_LIBCLANG_PATH}")
message("USE_SYSTEM_LIBCLANG=${USE_SYSTEM_LIBCLANG}")
if ( EXTERNAL_LIBCLANG_PATH OR USE_SYSTEM_LIBCLANG )
if ( USE_SYSTEM_LIBCLANG )
if ( APPLE )
set( ENV_LIB_PATHS ENV DYLD_LIBRARY_PATH )
elseif ( UNIX )
set( ENV_LIB_PATHS ENV LD_LIBRARY_PATH )
elseif ( WIN32 )
set( ENV_LIB_PATHS ENV PATH )
else ()
set( ENV_LIB_PATHS "" )
endif()
# On Debian-based systems, llvm installs into /usr/lib/llvm-x.y.
file( GLOB SYS_LLVM_PATHS "/usr/lib/llvm*/lib" )
# Need TEMP because find_library does not work with an option variable
# On Debian-based systems only a symlink to libclang.so.1 is created
find_library( TEMP
NAMES
clang
libclang.so.1
PATHS
${ENV_LIB_PATHS}
/usr/lib
/usr/lib/llvm
${SYS_LLVM_PATHS}
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib
/Library/Developer/CommandLineTools/usr/lib )
set( EXTERNAL_LIBCLANG_PATH ${TEMP} )
else() # EXTERNAL_LIBCLANG_PATH is set
# For Macs, we do things differently; look further in this file.
if ( NOT APPLE AND NOT MSVC )
# Setting this to true makes sure that libraries we build will have our
# rpath set even without having to do "make install"
set( CMAKE_BUILD_WITH_INSTALL_RPATH TRUE )
set( CMAKE_INSTALL_RPATH "\$ORIGIN" )
# Add directories from all libraries outside the build tree to the rpath.
# This makes the dynamic linker able to find non system libraries that
# our libraries require, in particular the Python one (from pyenv for
# instance).
set( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE )
# When loading our library, the dynamic linker will look for
# libclang.so.4, not libclang.so.4.x.
file( RENAME
${EXTERNAL_LIBCLANG_PATH}.${CLANG_MAJOR_VERSION}.${CLANG_MINOR_VERSION}
${EXTERNAL_LIBCLANG_PATH}.${CLANG_MAJOR_VERSION} )
endif()
endif()
# On Linux, the library target is a symlink of the soversion one. Since it
# will be copied in the project folder, we need the symlinked library.
get_filename_component( LIBCLANG_TARGET "${EXTERNAL_LIBCLANG_PATH}" REALPATH )
message( "Using external libclang: ${LIBCLANG_TARGET}" )
else()
set( LIBCLANG_TARGET )
endif()

看起来CLANG_MAJOR_VERSIONCLANG_MINOR_VERSION都没有被正确定义,导致使用其值拼接文件名时出现了问题。不过,即使这些变量被定义了,这样的RENAME好像也没什道理,因为在~/clang-latest/lib目录下目标文件本来就存在了:

1
2
3
4
5
paul at fed in ~/clang-latest
$ ls -l lib/libclang.so*
lrwxrwxrwx. 1 paul paul 13 Apr 9 21:17 lib/libclang.so -> libclang.so.4
lrwxrwxrwx. 1 paul paul 15 Apr 9 21:17 lib/libclang.so.4 -> libclang.so.4.0
-rw-r--r--. 1 paul paul 59595439 Mar 11 03:01 lib/libclang.so.4.0

最让我觉得别扭的地方在这里:

1
2
# For Macs, we do things differently; look further in this file.
if ( NOT APPLE AND NOT MSVC )

这注释和紧随其后的代码怎么觉得有点对不上呢?NOT APPLE AND NOT MSVC怎么就成了For Macs了呢?难道是写代码时出现的笔误?抱着试试看的心态,我把if (NOT APPLE AND NOT MSVC)改成了if (NOT UNIX AND NOT MSVC)(修改的实际效果是:if条件块中的语句在我所在平台中不会被执行),我擦,cmake居然顺利通过了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
paul at fed in ~/ycm_build
$ cmake -G "Unix Makefiles" -DPATH_TO_LLVM_ROOT=~/clang-latest . ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp
-- The C compiler identification is GNU 6.3.1
-- The CXX compiler identification is GNU 6.3.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonLibs: /usr/lib64/libpython2.7.so (found suitable version "2.7.13", minimum required is "2.6")
Using libclang to provide semantic completion for C/C++/ObjC
Using external libclang: /home/paul/clang-latest/lib/libclang.so.4.0
-- Found PythonInterp: /usr/bin/python2.7 (found version "2.7.13")
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /home/paul/ycm_build

之后的build也顺利通过。什么叫“踏破铁鞋无觅处,得来全不费功夫”,劳资简直有点怀疑人生了。。。想来也许是cmake在一个不应该出错的地方卡了壳,导致后面有些编译选项没有被正确设置,成功地误导我去解决pthread库检测问题。

其实这和编译C++工程时遇到的情况也有些类似,编译器可能给出一大堆抱怨,正确和高效的解决方式是: 总是应该解决你发现的第一个错误,然后立刻编译一次 。很多时候,后面出现的错误都是由于第一个错误引起的。

YCM测试运行

最终的结果,似乎不过是~/.vim/bundle/YouCompleteMe/third_party/ycmd目录下多出来两个文件:libclang.so.4.0和ycm_core.so。这里倒是需要把libclang.so.4.0重命名为libclang.so.4,否则YCM无法正常工作:

1
2
3
4
5
6
7
2017-04-16 00:26:33,712 - ERROR - libclang.so.4: cannot open shared object file: No such file or directory
Traceback (most recent call last):
File "/home/paul/.vim/bundle/YouCompleteMe/third_party/ycmd/ycmd/server_utils.py", line 95, in CompatibleWithCurrentCore
ycm_core = ImportCore()
File "/home/paul/.vim/bundle/YouCompleteMe/third_party/ycmd/ycmd/server_utils.py", line 87, in ImportCore
import ycm_core as ycm_core
ImportError: libclang.so.4: cannot open shared object file: No such file or directory