# This file is NOT licensed under the GPLv3, which is the license for the rest # of ycmd. # # Here's the license text for this file: # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to import os from ycmd import utils import ycm_core # Overview # -------- # # This file provides an attempt to heuristically provide compiler flags for # c-family languages. It works best when a compilation database is used. A # compilation database can be generated by tools such as cmake, bear, etc. It # contains the compiler flags (actually, a compiler invocation) for each source # file in the project, generated by the make system. If no compilation database # is used, an attempt is made to guess some flags. # # Finding the compilation database # -------------------------------- # # While there is no standard, and using something like cmake typically involves # an out-of-tree build, we attempt to find a compilation database by walking the # directory hierarchy looking for a file named compile_commands.json, starting # at the path of the file for which flags were requested (i.e. the file being # edited). # # If a compilation database is found, it is cached against the directory it is # found in, to prevent re-parsing the entire database for every file, while # allowing a single instance ycmd to operate on multiple "projects" at once. # # Guessing flags # -------------- # # The compilation database typically only contains flags (compiler invocations) # for source files known to the make system and/or makefile generator, as these # form the translation units for which calls to the compiler (typically with the # -c flag) are actually made. It does not contain: # - new files, not yet run through the make generator system # - header files # # Additionally, not all projects will have a compilation database. While it is # recommended to generate one (again, see 'bear' if you don't use cmake), we try # to make certain toy or example projects work without user input. # # So the logic in this file applies some simple heuristics to guess the flags in # those cases: # - If the file is a header file (according to HEADER_EXTENSIONS) # above, see if there is an entry in the database with the same root, but # with any of the extensions in SOURCE_EXTENSIONS above. If so, return those # flags. # - Otherwise, if there is an entry in the database for the requested file, # return those flags. # - Otherwise, if we previously found any flags for a file in the same # directory as the requested file, return those flags. # - Otherwise, if we previously returned *any* flags for *any* file with the # same file extension, return those. # - Otherwise, if the file extension is known to us (c++, c, objective c), we # return some absolutely arbitrary default flags, which might work for toy # projects. # - Otherwise, we really can't guess any flags, so return none (and thus a # warning that we can't guess your flags). # # Caution # ------- # # These heuristics might not work for you! Your mileage may vary. It is provided # as a default in the hope that it might be useful for some number of users. # # The file is deliberately heavy on comments. This is to allow it to be adapted # to individual use cases by copying it to a known directory, configuring it # as the 'global_extra_conf' and customising it to your particular # environment. Note that the comments in this file are not a replacement for the # ycmd or YouCompleteMe documentation, nor is is meant to be a replacement for # the comments/examples found in ycmd's own C++ source location # (../cpp/ycm/.ycm_extra_conf.py). # # Tweakables for heuristics {{{ # List of file extensions to be considered "header" files and thus not present # in the compilation database. The logic will try and find an associated # "source" file (see SOURCE_EXTENSIONS below) and use the flags for that. HEADER_EXTENSIONS = [ '.h', '.hxx', '.hpp', '.hh' ] # List of file extensions which are considered "source" files for the purposes # of heuristically locating the flags for a header file. SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] # }}} # Caches for heuristically guessing flags {{{ # We cache the database for any given source directory compilation_database_dir_map = {} # Sometimes we don't actually know what the flags to use are. Rather than # returning no flags, if we've previously found flags for a file in a particular # directory, return them. The will probably work in a high percentage of cases # and allow new files (which are not yet in the compilation database) to receive # at least some flags file_directory_heuristic_map = {} # Assuming we can't find anything in the database, and we haven't previously # seen any compilation information for the directory of the file in question, we # simply return the last set of flags we successfully retrieved for any file # sharing the file extension. This might work for some percentage of files, # which is better than the 0% otherwise. last_compilation_info_ext_map = {} # As a last gambit, we just return some generic flags. This won't work for # any large or complex project, but it might work for trivial/toy projects, # demos, school projects etc. # This map contains the flag lists to return for the supported file types. The # flags are arbitrary, but should work for a large number of toy projects (when # combined with the other os-specific flags added by ycmd). fallback_flags_filetype_map = { 'cpp': [ '-x', 'c++', '-std=c++11', '-Wall', '-Wextra', '-I', '.' ], 'c': [ '-x', 'c', '-std=c99', '-Wall', '-Wextra', '-I', '.' ], 'objc': [ '-x', 'objective-c', '-Wall', '-Wextra', '-I', '.' ], } # This map contains the mapping of file extension (including the .) to the file # type we expect it to be. We would prefer to rely on the editor to tell us the # file type, but we can't because there is no way to force that to happen. fallback_flags_ext_map = { '.cpp': fallback_flags_filetype_map[ 'cpp' ], '.cxx': fallback_flags_filetype_map[ 'cpp' ], '.cc': fallback_flags_filetype_map[ 'cpp' ], '.c': fallback_flags_filetype_map[ 'c' ], '.m': fallback_flags_filetype_map[ 'objc' ], '.mm': fallback_flags_filetype_map[ 'objc' ], # sic: strictly obj-c++ ? } # }}} # Implementation {{{ # Return a compilation database object for the supplied path or None if none # could be found. # # We search up the directory hierarchy, to first see if we have a compilation # database already for that path, or if a compile_commands.json file exists in # that directory. def FindCompilationDatabase( wd ): # Find all the ancestor directories of the supplied directory for folder in utils.PathsToAllParentFolders( wd ): # Did we already cache a database for this path? if folder in compilation_database_dir_map: # Yep. Return that. return compilation_database_dir_map[ folder ] # Guess not. Let's see if a compile_commands.json file already exists... compile_commands = os.path.join( folder, 'compile_commands.json' ) if os.path.exists( compile_commands ): # Yes, it exists. Create a database and cache it in our map. database = ycm_core.CompilationDatabase( folder ) compilation_database_dir_map[ folder ] = database return database # Doesn't exist. Check the next ancestor # Nothing was found. No compilation flags are available. # # Note: we cache the fact that none was found for this folder to speed up # subsequent searches.. compilation_database_dir_map[ wd ] = None return None def HeaderPathManipulations( file_name ): yield file_name # Try some simple manipulations, like removing "include" from the path and # replacing with "src" or "lib" (as used by llvm) include_mappings = [ [ '/include/', '/src/' ], [ '/include/', '/lib/' ], [ '/include/clang/', '/lib/' ], ] for mapping in include_mappings: if mapping[ 0 ] in file_name: yield file_name.replace( mapping[ 0 ], mapping[ 1 ], 1 ) # Find the compilation info structure from the supplied database for the # supplied file. If the source file is a header, try and find an appropriate # source file and return the compilation_info for that. def GetCompilationInfoForFile( database, file_name, file_extension ): # The compilation_commands.json file generated by CMake does not have entries # for header files. So we do our best by asking the db for flags for a # corresponding source file, if any. If one exists, the flags for that file # should be good enough. if file_extension in HEADER_EXTENSIONS: # It's a header file for candidate_file in HeaderPathManipulations( file_name ): for extension in SOURCE_EXTENSIONS: replacement_file = os.path.splitext( candidate_file )[ 0 ] + extension if os.path.exists( replacement_file ): # We found a corresponding source file with the same file_root. Try # and get the flags for that file. compilation_info = database.GetCompilationInfoForFile( replacement_file ) if compilation_info.compiler_flags_: return compilation_info # No corresponding source file was found, so we can't generate any flags for # this header file. return None # It's a source file. Just ask the database for the flags. compilation_info = database.GetCompilationInfoForFile( file_name ) if compilation_info.compiler_flags_: return compilation_info return None # In the absence of flags from a compilation database or heuristic, return some # generic flags, or None if we don't have any generic flags for the supplied # file extension. def GetDefaultFlagsForFile( extension ): if extension in fallback_flags_ext_map: return { 'flags': fallback_flags_ext_map[ extension ], 'do_cache': True } else: # OK we really have no clue about this filetype and we can't find any # flags, even with outrageous guessing. Return nothing and warn the user # that we couldn't find or guess any compiler flags. return { 'flags': [] } 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 # ycmd calls this method to get the compile flags for a given file. It returns a # dictionary with 2 keys: 'flags' and 'do_cache', or None if no flags can be # found. def FlagsForFile( file_name, **kwargs ): file_dir = os.path.dirname( file_name ) ( file_root, file_extension ) = os.path.splitext( file_name ) # Create or retrieve the cached compilation database object database = FindCompilationDatabase( file_dir ) if database is None: # We couldn't find a compilation database. Just return some absolutely # generic flags based on the file extension. # # We don't bother cacheing this as that would just be a waste of memory and # cycles. return GetDefaultFlagsForFile( file_extension ) compilation_info = GetCompilationInfoForFile( database, file_name, file_extension ) if compilation_info is None: print( "No flags in database for " + file_name ) if file_dir in file_directory_heuristic_map: # We previously saw a file in this directory. As a guess, just # return the flags for that file. Hopefully this will at least give some # meaningful suggestions print( " - Using flags for dir: " + file_dir ) compilation_info = file_directory_heuristic_map[ file_dir ] elif file_extension in last_compilation_info_ext_map: # OK there is nothing in the DB for this file, and we didn't cache any # flags for the directory of the file requested, but we did find some # flags previously for this file type. Return them. Once again, this is # an outrageous guess, but it is better to return *some* flags, rather # than nothing. print( " - Using flags for extension: " + file_extension ) compilation_info = last_compilation_info_ext_map[ file_extension ] else: # No cache for this directory or file extension, we really can't conjure # up any flags from the database, just return some absolutely generic # fallback flags. These probably won't work for any actual project, but we # try anyway. print( " - Using generic flags" ) return GetDefaultFlagsForFile( file_extension ) if file_dir not in file_directory_heuristic_map: # This is the first file we've seen in path file_dir. Cache the # compilation_info for it in case we see a file in the same dir with no # flags available file_directory_heuristic_map[ file_dir ] = compilation_info # Cache the last successful set of compilation info that we found. As noted # above, this is used when we completely fail to find any flags. last_compilation_info_ext_map[ file_extension ] = compilation_info return { # We pass the compiler flags from the database unmodified. 'flags': MakeRelativePathsInFlagsAbsolute( compilation_info.compiler_flags_, compilation_info.compiler_working_dir_ ), # We always want to use ycmd's cache, as this significantly improves # performance. 'do_cache': True } # }}}