Skip to content

Commit

Permalink
Windows style flags
Browse files Browse the repository at this point in the history
  • Loading branch information
bstaletic authored and Boris Staletic committed Dec 25, 2017
1 parent 4fa81b5 commit feb5318
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 41 deletions.
76 changes: 59 additions & 17 deletions ycmd/completers/cpp/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
INCLUDE_FLAGS = [ '-isystem', '-I', '-iquote', '-isysroot', '--sysroot',
'-gcc-toolchain', '-include-pch', '-include', '-iframework',
'-F', '-imacros', '-idirafter' ]
PATH_FLAGS = [ '--sysroot=' ] + INCLUDE_FLAGS
INCLUDE_FLAGS_WIN_STYLE = [ '/I' ]
PATH_FLAGS = [ '--sysroot=' ] + INCLUDE_FLAGS + INCLUDE_FLAGS_WIN_STYLE

# We need to remove --fcolor-diagnostics because it will cause shell escape
# sequences to show up in editors, which is bad. See Valloric/YouCompleteMe#1421
Expand All @@ -49,6 +50,8 @@
'-MMD',
'--fcolor-diagnostics' ] )

STATE_FLAGS_TO_SKIP_WIN_STYLE = set( [ '/c' ] )

# The -M* flags spec:
# https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Preprocessor-Options.html
FILE_FLAGS_TO_SKIP = set( [ '-MF',
Expand All @@ -63,6 +66,8 @@
# See Valloric/ycmd#266
CPP_COMPILER_REGEX = re.compile( r'\+\+(-\d+(\.\d+){0,2})?$' )

import logging
_logger = logging.getLogger( __name__ )
# 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.
Expand Down Expand Up @@ -91,6 +96,7 @@ def __init__( self ):
self.flags_for_file = {}
self.extra_clang_flags = _ExtraClangFlags()
self.no_extra_conf_file_warning_posted = False
self.enable_windows_style_flags = False

# We cache the compilation database for any given source directory
# Keys are directory names and values are ycm_core.CompilationDatabase
Expand All @@ -108,6 +114,12 @@ def __init__( self ):
self.file_directory_heuristic_map = dict()


def _ShouldAllowWinStyleFlags( self, flags ):
for flag in flags:
if flag.startswith( '--driver-mode' ):
self.enable_windows_style_flags = ( flag == '--driver-mode=cl' )


def FlagsForFile( self,
filename,
add_extra_clang_flags = True,
Expand Down Expand Up @@ -141,11 +153,14 @@ def FlagsForFile( self,

if add_extra_clang_flags:
flags += self.extra_clang_flags
flags = _AddMacIncludePaths( flags )
flags += _AddMacIncludePaths( flags )

self._ShouldAllowWinStyleFlags( flags )

sanitized_flags = PrepareFlagsForClang( flags,
filename,
add_extra_clang_flags )
add_extra_clang_flags,
self.enable_windows_style_flags )

if results.get( 'do_cache', True ):
self.flags_for_file[ filename ] = sanitized_flags
Expand Down Expand Up @@ -273,10 +288,13 @@ def _SysRootSpecifedIn( flags ):
return False


def PrepareFlagsForClang( flags, filename, add_extra_clang_flags = True ):
flags = _AddLanguageFlagWhenAppropriate( flags )
def PrepareFlagsForClang( flags,
filename,
add_extra_clang_flags = True,
enable_windows_style_flags = False ):
flags = _AddLanguageFlagWhenAppropriate( flags, enable_windows_style_flags )
flags = _RemoveXclangFlags( flags )
flags = _RemoveUnusedFlags( flags, filename )
flags = _RemoveUnusedFlags( flags, filename, enable_windows_style_flags )
if add_extra_clang_flags:
flags = _EnableTypoCorrection( flags )

Expand Down Expand Up @@ -306,18 +324,21 @@ def _RemoveXclangFlags( flags ):
return sanitized_flags


def _RemoveFlagsPrecedingCompiler( flags ):
def _RemoveFlagsPrecedingCompiler( flags, enable_windows_style_flags ):
"""Assuming that the flag just before the first flag (which starts with a
dash) is the compiler path, removes all flags preceding it."""

for index, flag in enumerate( flags ):
if flag.startswith( '-' ):
if ( flag.startswith( '-' ) or
( enable_windows_style_flags and
flag.startswith( '/' ) and
not os.path.exists( flag ) ) ):
return ( flags[ index - 1: ] if index > 1 else
flags )
return flags[ :-1 ]


def _AddLanguageFlagWhenAppropriate( flags ):
def _AddLanguageFlagWhenAppropriate( flags, enable_windows_style_flags ):
"""When flags come from the compile_commands.json file, the flag preceding the
first flag starting with a dash is usually the path to the compiler that
should be invoked. Since LibClang does not deduce the language from the
Expand All @@ -326,9 +347,10 @@ def _AddLanguageFlagWhenAppropriate( flags ):
the file extension. This handles the case where the .h extension is used for
C++ headers."""

flags = _RemoveFlagsPrecedingCompiler( flags )
flags = _RemoveFlagsPrecedingCompiler( flags, enable_windows_style_flags )

# First flag is now the compiler path or a flag starting with a dash.
# First flag is now the compiler path, a flag starting with a dash or
# a flag starting with a forward slash if enable_windows_style_flags is True.
first_flag = flags[ 0 ]

if ( not first_flag.startswith( '-' ) and
Expand All @@ -337,7 +359,7 @@ def _AddLanguageFlagWhenAppropriate( flags ):
return flags


def _RemoveUnusedFlags( flags, filename ):
def _RemoveUnusedFlags( flags, filename, enable_windows_style_flags ):
"""Given an iterable object that produces strings (flags for Clang), removes
the '-c' and '-o' options that Clang does not like to see when it's producing
completions for a file. Same for '-MD' etc.
Expand All @@ -359,15 +381,22 @@ def _RemoveUnusedFlags( flags, filename ):
previous_flag_starts_with_dash = False
current_flag_starts_with_dash = False

# Windows style flags
current_flag_starts_with_slash = False

for flag in flags:
previous_flag_starts_with_dash = current_flag_starts_with_dash
current_flag_starts_with_dash = flag.startswith( '-' )

current_flag_starts_with_slash = flag.startswith( '/' )

if skip_next:
skip_next = False
continue

if flag in STATE_FLAGS_TO_SKIP:
if ( flag in STATE_FLAGS_TO_SKIP or
( enable_windows_style_flags and
flag in STATE_FLAGS_TO_SKIP_WIN_STYLE ) ):
continue

if flag in FILE_FLAGS_TO_SKIP:
Expand All @@ -383,13 +412,25 @@ def _RemoveUnusedFlags( flags, filename ):
# "foo.cpp" when we are compiling "foo.h" because the comp db doesn't have
# flags for headers. The returned flags include "foo.cpp" and we need to
# remove that.
if ( not current_flag_starts_with_dash and
( not previous_flag_starts_with_dash or
( not previous_flag_is_include and '/' in flag ) ) ):
if ( not ( current_flag_starts_with_dash or
( enable_windows_style_flags and
current_flag_starts_with_slash ) ) and
( not previous_flag_starts_with_dash or
( not previous_flag_is_include and '/' in flag ) ) ):
_logger.debug(flag)
_logger.debug(not current_flag_starts_with_dash)
_logger.debug(not previous_flag_starts_with_dash)
_logger.debug(not previous_flag_is_include)
_logger.debug('/' in flag)
_logger.debug(not enable_windows_style_flags)
_logger.debug(os.path.exists(flag))
continue

new_flags.append( flag )
previous_flag_is_include = flag in INCLUDE_FLAGS
previous_flag_is_include = ( flag in INCLUDE_FLAGS or
( enable_windows_style_flags and
flag in INCLUDE_FLAGS_WIN_STYLE ) )

return new_flags


Expand Down Expand Up @@ -541,6 +582,7 @@ def _MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
new_flag = os.path.join( working_directory, flag )
new_flag = os.path.normpath( new_flag )
else:
# TODO: Should we care about `/I` when enable_windows_style_flags
for path_flag in PATH_FLAGS:
# Single dash argument alone, e.g. -isysroot <path>
if flag == path_flag:
Expand Down
91 changes: 67 additions & 24 deletions ycmd/tests/clang/flags_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def FlagsForFile( filename ):

def RemoveUnusedFlags_Passthrough_test():
eq_( [ '-foo', '-bar' ],
flags._RemoveUnusedFlags( [ '-foo', '-bar' ], 'file' ) )
flags._RemoveUnusedFlags( [ '-foo', '-bar' ], 'file', False ) )


def RemoveUnusedFlags_RemoveDashC_test():
Expand All @@ -232,14 +232,14 @@ def RemoveUnusedFlags_RemoveDashC_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( to_remove + expected, filename ) )
flags._RemoveUnusedFlags( to_remove + expected, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags(
expected[ :1 ] + to_remove + expected[ -1: ], filename ) )
expected[ :1 ] + to_remove + expected[ -1: ], filename, False ) )


def RemoveUnusedFlags_RemoveColor_test():
Expand All @@ -248,14 +248,14 @@ def RemoveUnusedFlags_RemoveColor_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( to_remove + expected, filename ) )
flags._RemoveUnusedFlags( to_remove + expected, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags(
expected[ :1 ] + to_remove + expected[ -1: ], filename ) )
expected[ :1 ] + to_remove + expected[ -1: ], filename, False ) )


def RemoveUnusedFlags_RemoveDashO_test():
Expand All @@ -264,14 +264,14 @@ def RemoveUnusedFlags_RemoveDashO_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( to_remove + expected, filename ) )
flags._RemoveUnusedFlags( to_remove + expected, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags(
expected[ :1 ] + to_remove + expected[ -1: ], filename ) )
expected[ :1 ] + to_remove + expected[ -1: ], filename, False ) )


def RemoveUnusedFlags_RemoveMP_test():
Expand All @@ -280,14 +280,14 @@ def RemoveUnusedFlags_RemoveMP_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( to_remove + expected, filename ) )
flags._RemoveUnusedFlags( to_remove + expected, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags(
expected[ :1 ] + to_remove + expected[ -1: ], filename ) )
expected[ :1 ] + to_remove + expected[ -1: ], filename, False ) )


def RemoveUnusedFlags_RemoveFilename_test():
Expand All @@ -296,15 +296,15 @@ def RemoveUnusedFlags_RemoveFilename_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( expected[ :1 ] + to_remove + expected[ 1: ],
filename ) )
filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags(
expected[ :1 ] + to_remove + expected[ -1: ], filename ) )
expected[ :1 ] + to_remove + expected[ -1: ], filename, False ) )


def RemoveUnusedFlags_RemoveFlagWithoutPrecedingDashFlag_test():
Expand All @@ -313,11 +313,50 @@ def RemoveUnusedFlags_RemoveFlagWithoutPrecedingDashFlag_test():
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( expected[ :1 ] + to_remove + expected[ 1: ],
filename ) )
filename, False ) )
expected = [ 'g++', '-foo', '--driver-mode=cl', '-xc++', '-bar',
'include_dir', '/foo' ]
to_remove = [ '..' ]
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename, True ) )

eq_( expected,
flags._RemoveUnusedFlags( expected[ :1 ] + to_remove + expected[ 1: ],
filename, True ) )


def RemoveUnusedFlags_MultipleDriverModeFlags_test():
expected = [ 'g++',
'--driver-mode=cl',
'/Zi',
'-foo',
'--driver-mode=gcc',
'--driver-mode=cl',
'include_dir' ]
to_remove = [ 'unrelated_file', '/c' ]
filename = 'file'

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename, True ) )
eq_( expected,
flags._RemoveUnusedFlags( expected[ :1 ] + to_remove + expected[ 1: ],
filename, True ) )

flags_expected = [ '/usr/bin/g++', '--driver-mode=cl', '--driver-mode=gcc' ]
flags_all = [ '/usr/bin/g++',
'/Zi',
'--driver-mode=cl',
'/foo',
'--driver-mode=gcc' ]
filename = 'file'

eq_( flags_expected, flags._RemoveUnusedFlags( flags_all, filename, False ) )


def RemoveUnusedFlags_Depfiles_test():
Expand All @@ -337,7 +376,7 @@ def RemoveUnusedFlags_Depfiles_test():
'-arch', 'armv7',
]

assert_that( flags._RemoveUnusedFlags( full_flags, 'test.m' ),
assert_that( flags._RemoveUnusedFlags( full_flags, 'test.m', False ),
contains( *expected ) )


Expand Down Expand Up @@ -365,15 +404,15 @@ def tester( flag ):
expected = [ 'clang', flag, '/foo/bar', '-isystem/zoo/goo' ]

eq_( expected,
flags._RemoveUnusedFlags( expected + to_remove, filename ) )
flags._RemoveUnusedFlags( expected + to_remove, filename, False ) )

eq_( expected,
flags._RemoveUnusedFlags( expected[ :1 ] + to_remove + expected[ 1: ],
filename ) )
filename, False ) )

eq_( expected + expected[ 1: ],
flags._RemoveUnusedFlags( expected + to_remove + expected[ 1: ],
filename ) )
filename, False ) )

include_flags = [ '-isystem', '-I', '-iquote', '-isysroot', '--sysroot',
'-gcc-toolchain', '-include', '-include-pch',
Expand Down Expand Up @@ -402,7 +441,11 @@ def RemoveXclangFlags_test():

def AddLanguageFlagWhenAppropriate_Passthrough_test():
eq_( [ '-foo', '-bar' ],
flags._AddLanguageFlagWhenAppropriate( [ '-foo', '-bar' ] ) )
flags._AddLanguageFlagWhenAppropriate( [ '-foo', '-bar' ], False ) )
eq_( [ '-foo', '-bar', '--driver-mode=cl' ],
flags._AddLanguageFlagWhenAppropriate( [ '-foo',
'-bar',
'--driver-mode=cl' ], True ) )


def _AddLanguageFlagWhenAppropriateTester( compiler, language_flag = [] ):
Expand All @@ -416,7 +459,7 @@ def _AddLanguageFlagWhenAppropriateTester( compiler, language_flag = [] ):
for to_remove in to_removes:
eq_( [ compiler ] + language_flag + expected,
flags._AddLanguageFlagWhenAppropriate( to_remove + [ compiler ] +
expected ) )
expected, False ) )


def AddLanguageFlagWhenAppropriate_CCompiler_test():
Expand Down

0 comments on commit feb5318

Please sign in to comment.