Skip to content

Commit

Permalink
Add option to specify working directory in extra conf
Browse files Browse the repository at this point in the history
Add an option in .ycm_extra_conf.py file to specify the working directory to
which the paths in the list of flags are relative. By default, these paths are
relative to ycmd working directory.
  • Loading branch information
micbou committed Jul 18, 2017
1 parent 79c4b1c commit 1bbb410
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 85 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ The return value must be one of the following:

- `flags`: (mandatory) a list of compiler flags.

- `working_directory': (optional) the directory to which the paths in the list
of flags are relative. Defaults to ycmd working directory.

- `do_cache`: (optional) a boolean indicating whether or not the result of
this call (i.e. the list of flags) should be cached for this file name.
Defaults to `True`. If unsure, the default is almost always correct.
Expand Down
46 changes: 10 additions & 36 deletions cpp/ycm/.ycm_extra_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,35 +106,6 @@ def DirectoryOfThisScript():
return os.path.dirname( os.path.abspath( __file__ ) )


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' ]
Expand Down Expand Up @@ -166,9 +137,7 @@ def FlagsForFile( filename, **kwargs ):
if not compilation_info:
return None

final_flags = MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_ )
final_flags = compilation_info.compiler_flags_

# NOTE: This is just for YouCompleteMe; it's highly likely that your project
# does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR
Expand All @@ -177,8 +146,13 @@ def FlagsForFile( filename, **kwargs ):
final_flags.remove( '-stdlib=libc++' )
except ValueError:
pass
else:
relative_to = DirectoryOfThisScript()
final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )

return { 'flags': final_flags }
return {
'flags': final_flags,
'working_directory': compilation_info.compiler_working_dir_
}

return {
'flags': flags,
'working_directory': DirectoryOfThisScript()
}
10 changes: 8 additions & 2 deletions ycmd/completers/cpp/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,15 @@ def _CallExtraConfFlagsForFile( module, filename, client_data ):
# For the sake of backwards compatibility, we need to first check whether the
# FlagsForFile function in the extra conf module even allows keyword args.
if inspect.getargspec( module.FlagsForFile ).keywords:
return module.FlagsForFile( filename, client_data = client_data )
flags = module.FlagsForFile( filename, client_data = client_data )
else:
return module.FlagsForFile( filename )
flags = module.FlagsForFile( filename )

flags[ 'flags' ] = _MakeRelativePathsInFlagsAbsolute(
flags[ 'flags' ],
flags.get( 'working_directory' ) )

return flags


def _SysRootSpecifedIn( flags ):
Expand Down
148 changes: 101 additions & 47 deletions ycmd/tests/clang/flags_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,102 +22,156 @@
# Not installing aliases from python-future; it's unreliable and slow.
from builtins import * # noqa

import contextlib
import os

from nose.tools import eq_, ok_
from ycmd.completers.cpp import flags
from mock import patch, Mock
from mock import patch, MagicMock, Mock
from types import ModuleType
from ycmd.tests.test_utils import MacOnly
from ycmd.responses import NoExtraConfDetected
from ycmd.tests.clang import TemporaryClangProject, TemporaryClangTestDir

from hamcrest import assert_that, calling, contains, has_item, not_, raises


@patch( 'ycmd.extra_conf_store.ModuleForSourceFile', return_value = Mock() )
def FlagsForFile_FlagsNotReady_test( *args ):
fake_flags = {
'flags': [ ],
'flags_ready': False
}
@contextlib.contextmanager
def MockExtraConfModule( flags_for_file_function ):
module = MagicMock( spec = ModuleType )
module.FlagsForFile = flags_for_file_function
with patch( 'ycmd.extra_conf_store.ModuleForSourceFile',
return_value = module ):
yield


def FlagsForFile_FlagsNotReady_test():
flags_object = flags.Flags()

def FlagsForFile( filename ):
return {
'flags': [],
'flags_ready': False
}

with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = fake_flags ):
flags_object = flags.Flags()
with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
eq_( list( flags_list ), [ ] )
eq_( list( flags_list ), [] )


@patch( 'ycmd.extra_conf_store.ModuleForSourceFile', return_value = Mock() )
def FlagsForFile_BadNonUnicodeFlagsAreAlsoRemoved_test( *args ):
fake_flags = {
'flags': [ bytes( b'-c' ), '-c', bytes( b'-foo' ), '-bar' ]
}
flags_object = flags.Flags()

with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = fake_flags ):
flags_object = flags.Flags()
def FlagsForFile( filename ):
return {
'flags': [ bytes( b'-c' ), '-c', bytes( b'-foo' ), '-bar' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
eq_( list( flags_list ), [ '-foo', '-bar' ] )


@patch( 'ycmd.extra_conf_store.ModuleForSourceFile', return_value = Mock() )
def FlagsForFile_FlagsCachedByDefault_test( *args ):
def FlagsForFile_FlagsCachedByDefault_test():
flags_object = flags.Flags()

results = { 'flags': [ '-x', 'c' ] }
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c' ) )

results[ 'flags' ] = [ '-x', 'c++' ]
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c++' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c' ) )


@patch( 'ycmd.extra_conf_store.ModuleForSourceFile', return_value = Mock() )
def FlagsForFile_FlagsNotCachedWhenDoCacheIsFalse_test( *args ):
def FlagsForFile_FlagsNotCachedWhenDoCacheIsFalse_test():
flags_object = flags.Flags()

results = {
'flags': [ '-x', 'c' ],
'do_cache': False
}
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c' ],
'do_cache': False
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c' ) )

results[ 'flags' ] = [ '-x', 'c++' ]
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c++' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c++' ) )


@patch( 'ycmd.extra_conf_store.ModuleForSourceFile', return_value = Mock() )
def FlagsForFile_FlagsCachedWhenDoCacheIsTrue_test( *args ):
def FlagsForFile_FlagsCachedWhenDoCacheIsTrue_test():
flags_object = flags.Flags()

results = {
'flags': [ '-x', 'c' ],
'do_cache': True
}
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c' ],
'do_cache': True
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c' ) )

results[ 'flags' ] = [ '-x', 'c++' ]
with patch( 'ycmd.completers.cpp.flags._CallExtraConfFlagsForFile',
return_value = results ):
def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c++' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list, contains( '-x', 'c' ) )


def FlagsForFile_RelativePathsIfNoWorkingDirectory_test():
flags_object = flags.Flags()

def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c', '-I', 'header' ]
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list,
contains( '-x', 'c',
'-I', 'header' ) )


def FlagsForFile_PathsRelativeToWorkingDirectory_test():
flags_object = flags.Flags()

def FlagsForFile( filename ):
return {
'flags': [ '-x', 'c', '-I', 'header' ],
'working_directory': '/working_dir/'
}

with MockExtraConfModule( FlagsForFile ):
flags_list = flags_object.FlagsForFile( '/foo', False )
assert_that( flags_list,
contains( '-x', 'c',
'-I', os.path.normpath( '/working_dir/header' ) ) )


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

0 comments on commit 1bbb410

Please sign in to comment.