1

I've encountered a problem trying to set up a C++ project on my Mac (Yosemite with Xcode 6) using CMake with Unix style Makefiles and vim with the youcompleteme plugin (I'm a Linux veteran and Mac newbie so I prefer this setup to Xcode). The code builds and runs but youcompleteme throws some bogus errors which I think boil down to it not being able to find the <cstdint> header.

I've just tried it on Linux too, and had the same problem.

I've configured .ycm_extra_conf.py to use a compile_commands.json generated by cake. The "command" lines in compile_commands.json use these flags:

  "command": "/usr/bin/c++     -std=c++11 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk -F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks -I/usr/local/include -I/Users/tony/Dev/cow/jni -I/Users/tony/Library/Frameworks/SDL2.framework/Headers    -Wall -Wextra -Wunused -F/Users/tony/Library/Frameworks  -o ...

There doesn't seem to be an explicit reference there to any directory containing stdint as a direct parent.

Is there a way I can get youcompleteme to do its stuff with libclang in such a way that it can find the directory implicitly, which seems to work when running c++ on the command line? Or what's the best way to get cmake to add an appropriate system header path without hardwiring it? I want my CMakeLists.txt to be portable and to be able to cope with toolchain upgrades.

My .ycm_extra_conf.py was pretty much a copy of the supplied example modified slightly to find compile_commands.json where I put it.

realh
  • 962
  • 2
  • 7
  • 22
  • can you provide us with your `.ycm_extra_conf.py` file and tell us a little more about how you organised you project. – ladislas Jan 07 '15 at 22:10
  • I've added .ycm_extra_conf to my original question because it's too long for a separate comment. It's pretty much the same as the example provided with the plugin, I just changed the bit where it finds compilation_database_folder (and removed the comments before pasting here). I think it is finding the database file OK because at one point I dumped final_flags to a file and found it included stuff present in compile_commands.json but not in the hardwired flags in this script. – realh Jan 07 '15 at 23:28
  • My project is laid out with two main subdirectories that are relevant to this problem. One called jni (in anticipation of porting to Android) which contains all my source files, and one called build-make which is the working directory for cmake. – realh Jan 07 '15 at 23:33

3 Answers3

3

As @ladislas said, YCM needs to be explicitly pointed to all relevant include directories as libclang won't use the same implicit location a normal compiler driver invocation (i.e. clang++ from the command line) would use.

What I usually do, on OSX, is to let YCM know about Xcode's libc++ headers with something like (in .ycm_extra_conf.py):

import os
import ycm_core
import subprocess
xcode_cpp11headers = subprocess.Popen("xcode-select -p", stdout = subprocess.PIPE, shell=True).communicate()[0].rstrip('\n') + '/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1'
.
.
flags = [
.
.
'-isystem',
xcode_cpp11headers,
.
.
]

The "xcode_cpp11headers" variable is populated with the proper path depending on the location of your current Xcode's installation, and you can change it accordingly if you want to use say the commandline-tools version of libc++ (i.e. the includes are in /Library/Developer/CommandLineTools/usr/include/c++/v1) or a source distribution of libc++ if you have compiled your own.

Of course this is platform dependant and either you provide a platform specific .ycm_extra_conf.py alongside your project, or you can populate that variable differently with some extra python code accordingly to the current platform.

abigagli
  • 2,769
  • 4
  • 29
  • 32
  • Thanks both. For portability I'll try knocking up something that I can run from cmake to parse the output from one of the commands suggested at [this other question](http://stackoverflow.com/questions/11946294/dump-include-paths-from-g). – realh Jan 08 '15 at 18:18
  • very elegant! I too use something like this to parse my `lib` directory and look for `.h` files to add their path to `flags`. Here it is if it can be useful :) http://git.io/IiR1JA – ladislas Jan 08 '15 at 18:29
  • I gave up trying to get cmake to run an extra script with the project-spcific compiler flags and added a GetSystemIncludePaths() function to .ycm_extra_conf.py to run cpp, using a cache. Posted above. – realh Jan 09 '15 at 17:48
2

You must add all the paths where YCM needs to look for your sources, libraries, and so on.

It does not work recursively, so it's a little bit cumbersome at first but is should not change once set for your project.

As an example, here is mine for an Arduino project :

https://github.com/ladislas/Bare-Arduino-Project/blob/master/.ycm_extra_conf.py#L21

Hope this helps!

EDIT - 2015/01/08

The solution of @abigagli is very elegant! I too use something like this to parse my lib directory and look for .h files to add their path to flags.

Here it is if it can be useful :) http://git.io/IiR1JA

ladislas
  • 3,037
  • 19
  • 27
0

As I found out from the above answers, YCM needs to be told the compiler's system include paths which are usually implicit in other ways of using the compiler. I added a function GetSystemIncludePaths() to .ycm_extra_conf.py to discover and cache these paths portably. Here's the full file with comments and irrelevant content of flags list snipped. The original is Copyright (C) 2014 Google Inc with a GPL2+ licence:

import subprocess, os
import ycm_core

flags = []


def DirectoryOfThisScript():
  return os.path.dirname( os.path.abspath( __file__ ) )

compilation_database_folder = os.path.abspath(
        os.path.join(DirectoryOfThisScript(), 'build-make'))

if os.path.exists( compilation_database_folder ):
  database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
  database = None

SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]

def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
  if not working_directory:
    return list( flags )
  new_flags = []
  make_next_absolute = False
  path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
  for flag in flags:
    new_flag = flag

    if make_next_absolute:
      make_next_absolute = False
      if not flag.startswith( '/' ):
        new_flag = os.path.join( working_directory, flag )

    for path_flag in path_flags:
      if flag == path_flag:
        make_next_absolute = True
        break

      if flag.startswith( path_flag ):
        path = flag[ len( path_flag ): ]
        new_flag = path_flag + os.path.join( working_directory, path )
        break

    if new_flag:
      new_flags.append( new_flag )
  return new_flags


def IsHeaderFile( filename ):
  extension = os.path.splitext( filename )[ 1 ]
  return extension in [ '.h', '.hxx', '.hpp', '.hh' ]


def GetCompilationInfoForFile( filename ):
  if IsHeaderFile( filename ):
    basename = os.path.splitext( filename )[ 0 ]
    for extension in SOURCE_EXTENSIONS:
      replacement_file = basename + extension
      if os.path.exists( replacement_file ):
        compilation_info = database.GetCompilationInfoForFile(
          replacement_file )
        if compilation_info.compiler_flags_:
          return compilation_info
    return None
  return database.GetCompilationInfoForFile( filename )


def GetSystemIncludePaths():
  cache = os.path.join(DirectoryOfThisScript(), ".ycm_sys_incs")
  if os.path.exists(cache):
    fp = open(cache, 'r')
    flags = fp.readlines()
    fp.close()
    flags = [s.strip() for s in flags]
  else:
    devnull = open(os.devnull, 'r')
    child = subprocess.Popen(["/usr/bin/cpp", "-xc++", "-v"],
        stdin = devnull, stderr = subprocess.PIPE)
    output = child.communicate()[1].split('\n')
    devnull.close()
    flags = []
    status = 0
    for l in output:
      l = l.strip()
      if l == '#include "..." search starts here:':
        status = 1
      elif l == '#include <...> search starts here:':
        status = 2
      elif status:
        if l == 'End of search list.':
          break
        elif l.endswith('(framework directory)'):
          continue
        elif status == 1:
          flags.append('-I')
        elif status == 2:
          flags.append('-isystem')
        flags.append(os.path.normpath(l))
    fp = open(cache, 'w')
    fp.write('\n'.join(flags))
    fp.close()
  return flags


def FlagsForFile( filename, **kwargs ):
  if database:
    compilation_info = GetCompilationInfoForFile( filename )
    if not compilation_info:
      return None

    final_flags = MakeRelativePathsInFlagsAbsolute(
      compilation_info.compiler_flags_,
      compilation_info.compiler_working_dir_ )
    sys_incs = GetSystemIncludePaths()
    if sys_incs:
        final_flags += sys_incs
  else:
    relative_to = DirectoryOfThisScript()
    final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )

  return {
    'flags': final_flags,
    'do_cache': True
  }
realh
  • 962
  • 2
  • 7
  • 22