Skip to content

Commit

Permalink
Add proper flags parsing for cmake (#700)
Browse files Browse the repository at this point in the history
* Add proper flags parsing for cmake
* Update docs
* Update message
  • Loading branch information
niosus authored May 1, 2020
1 parent fc8fbb5 commit e0f6929
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 18 deletions.
12 changes: 6 additions & 6 deletions EasyClangComplete.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@
// - ".clang_complete"
//
// Every entry has an option:
// - "search_in": <path> <-- a path to the folder[s] containing the file.
// "search_in": <path> <-- a path to the folder containing the file.
//
// "cmake" entry has additional options:
// - "flags": [<flags>] <-- Additional flags to pass to cmake.
// - "prefix_paths": [<paths>] <-- array of folders that should be
// appended to CMAKE_PREFIX_PATHS before
// cmake is run.
// The entry with "CMakeLists.txt" entry has additional option to set flags.
// Note that if any variables from above or globbing symbols are encountered
// in the body of the flag, the flag will be treated as a path and these
// symbols will be expanded. Set these flags as follows:
// "flags": [<flags>] <-- Flags to pass to cmake.
"flags_sources": [
{"file": "CMakeLists.txt"},
{"file": "compile_commands.json"},
Expand Down
24 changes: 19 additions & 5 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,23 @@ page](../configs/#geting-correct-compiler-flags) of the documentation.
#### CMake-specific options
CMake is handled in a special way and there are additional settings that can be specified for this type of flag source:

- `"flags": [<flags>]` <small>OPTIONAL</small> - defines a list of flags that can be passed to the `cmake` program upon calling it
- `"prefix_paths": [<paths>]` <small>OPTIONAL</small> - defines a list of paths that will be set as prefix paths when running `cmake`
- `"flags": [<flags>]` <small>OPTIONAL</small> - defines a list of flags that can be passed to the `cmake` program upon calling it. Note, that you can still use any variables or glob expansion in the flags and it should just work.

??? example "Example cmake flags <small>(click to expand)</small>"
```json
"ecc_flags_sources":
[
{
"file": "CMakeLists.txt",
"flags":
[
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_PREFIX_PATH=~/some_folder/*",
"-D SOME_FLAG=ON"
]
}
]
```

#### Search order
The flag sources are searched in a strictly hierarchical order from top to
Expand All @@ -153,8 +168,7 @@ explanations.
"flags":
[
"-DCMAKE_BUILD_TYPE=Release",
],
"prefix_paths": ["/opt/ros/indigo"]
]
},
{
"file": "Makefile"
Expand All @@ -166,7 +180,7 @@ explanations.
```
Here, first the plugin tries to find a `CMakeLists.txt` with `project(<smth>)` inside of it. If this is successful, then it invokes a command
```bash
cmake -DCMAKE_PREFIX_PATHS=/opt/ros/indigo -DCMAKE_BUILD_TYPE=Release <folder_to_CMakeLists.txt>
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release <folder_to_CMakeLists.txt>
```
storing the generated files in a temporary build folder.

Expand Down
3 changes: 2 additions & 1 deletion messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@
"6.3.0": "messages/6.3.0.rst",
"6.4.0": "messages/6.4.0.rst",
"6.4.1": "messages/6.4.1.rst",
"6.4.2": "messages/6.4.2.rst"
"6.4.2": "messages/6.4.2.rst",
"6.4.3": "messages/6.4.3.rst"
}
35 changes: 35 additions & 0 deletions messages/6.4.3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Version 6.4.3
=============

Improvements and bug fixes:
---------------------------
- More powerful cmake-related flags parsing. If you ever wanted to use flags
like `-DCMAKE_PREFIX_PATH=~/some_path/*`, now you can!
See [1] for details.
- **WARNING**: the sub-setting of the `CMakeLists.txt` entry `prefix_paths`
is now deprecated as all the functionality is covered by the more useful
`flags` sub-setting. You can still use it for now, but I will remove it
in the future.
- Fixed a jump to a wrong location when going to declaration, thanks @berteauxjb
- Fixed typo in the message below, thanks @berkus

[1]: https://niosus.github.io/EasyClangComplete/settings/#cmake-specific-options

Love this plugin? Support its development! 💰💸💶
-------------------------------------------------
‼️NEW‼️ Head to GitHub Sponsors and support my work if you enjoy the plugin!
https://github.com/sponsors/niosus

Alternatively, become a backer on Open Collective!
https://opencollective.com/EasyClangComplete#backers

If you use this plugin in a company, push to become a sponsor!
https://opencollective.com/EasyClangComplete#sponsor

This plugin took a significant amount of effort. It is available and will always
be available for free, both as freedom and as beer.

If you appreciate it - support it. How much would you say it is worth to you?

For more info head here:
https://github.com/niosus/EasyClangComplete#support-it
52 changes: 46 additions & 6 deletions plugin/settings/settings_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def __init__(self, settings_handle):
self.__load_vars_from_settings(settings_handle,
project_specific=False)

def update_from_view(self, view):
def update_from_view(self, view, project_specific=True):
"""Update from view using view-specific settings.
Args:
Expand All @@ -148,7 +148,7 @@ def update_from_view(self, view):
log.error("no view to populate common flags from")
return
self.__load_vars_from_settings(view.settings(),
project_specific=True)
project_specific=project_specific)
# Initialize wildcard values with view.
self.__update_wildcard_values(view)
# Replace wildcards in various paths.
Expand Down Expand Up @@ -217,6 +217,16 @@ def is_valid(self):
error_msg = "Linter mark style '{}' is not one of {}".format(
self.linter_mark_style, SettingsStorage.LINTER_MARK_STYLES)
return False, error_msg
flags_sources_valid, error_msg = self.__flag_sources_are_valid()
if not flags_sources_valid:
return False, error_msg
lang_tags_valid, error_msg = self.__are_language_tags_valid()
if not lang_tags_valid:
return False, error_msg
return True, None

def __flag_sources_are_valid(self):
"""Check that flag sources are valid."""
for source_dict in self.flags_sources:
if SettingsStorage.FILE_TAG not in source_dict:
error_msg = "No '%s' setting in a flags source '{}'".format(
Expand All @@ -229,7 +239,10 @@ def is_valid(self):
source_dict[SettingsStorage.FILE_TAG],
SettingsStorage.FLAG_SOURCES)
return False, error_msg
# Check if all languages are present in language-specific settings.
return True, None

def __are_language_tags_valid(self):
"""Check that language tags are valid."""
for lang_tag in SublBridge.LANG_TAGS:
if lang_tag not in self.lang_flags.keys():
error_msg = "lang '{}' is not in {}".format(
Expand All @@ -243,7 +256,7 @@ def is_valid(self):
error_msg = "No '{}' in syntaxes '{}'".format(
lang_tag, self.target_compilers)
return False, error_msg
return True, ""
return True, None

def __load_vars_from_settings(self, settings, project_specific=False):
"""Load all settings and add them as attributes of self.
Expand Down Expand Up @@ -283,6 +296,28 @@ def __load_vars_from_settings(self, settings, project_specific=False):

def __populate_flags_source_paths(self):
"""Populate variables inside flags sources."""
def expand_paths_in_flags_if_needed(flags):
"""Expand paths in flags if they are present."""
from os import path
new_flags = []
for flag in flags:
if '=' not in flag:
new_flags.append(flag)
split_flag = flag.split('=')
prefix = split_flag[0].strip()
value = split_flag[1].strip()
expanded_values = self.__replace_wildcard_if_needed(
value)
if not expanded_values:
continue
joined_values = ';'.join(expanded_values)
if path.isabs(expanded_values[0]):
new_flags.append(
prefix + '="' + joined_values + '"')
else:
new_flags.append(prefix + '=' + joined_values)
return new_flags

if not self.flags_sources:
log.critical(" Cannot update paths of flag sources.")
return
Expand All @@ -292,8 +327,12 @@ def __populate_flags_source_paths(self):
continue
if not source_dict[option]:
continue
source_dict[option] = self.__replace_wildcard_if_needed(
source_dict[option])
if option == SettingsStorage.FLAGS_TAG:
source_dict[option] = expand_paths_in_flags_if_needed(
source_dict[option])
else:
source_dict[option] = self.__replace_wildcard_if_needed(
source_dict[option])

def __populate_common_flags(self):
"""Populate the variables inside common_flags with real values."""
Expand All @@ -320,6 +359,7 @@ def __replace_wildcard_if_needed(self, query, expand_globbing=True):
return self.__replace_wildcard_if_needed([query])
if not isinstance(query, list):
log.critical("We can only update wildcards in a list!")
return None
result = []
for query_path in query:
result += File.expand_all(input_path=query_path,
Expand Down
40 changes: 40 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
from EasyClangComplete.tests.gui_test_wrapper import GuiTestWrapper

from EasyClangComplete.plugin.settings import settings_manager
from EasyClangComplete.plugin.settings import settings_storage
from EasyClangComplete.plugin.utils import flag

imp.reload(settings_manager)
imp.reload(settings_storage)
imp.reload(flag)

SettingsManager = settings_manager.SettingsManager
SettingsStorage = settings_storage.SettingsStorage
Flag = flag.Flag


Expand Down Expand Up @@ -45,6 +48,43 @@ def test_valid(self):
valid, _ = settings.is_valid()
self.assertTrue(valid)

def test_parse_cmake_flags(self):
"""Testing that we can parse cmake flags."""
file_name = path.join(path.dirname(__file__),
'test_files',
'test_wrong_triggers.cpp')
self.set_up_view(file_name)
current_folder = path.dirname(__file__)
flags_sources = [
{
"file": "CMakeLists.txt",
"flags": [
"-DBLAH={}/*".format(current_folder),
"-DSMTH=ON",
"-D XXX=1",
"-D FLAG=word"
]
}
]

self.view.settings().set("flags_sources", flags_sources)
settings = SettingsManager().user_settings()
settings.update_from_view(self.view, project_specific=False)
valid, _ = settings.is_valid()
self.assertTrue(valid)
self.assertEquals(len(settings.flags_sources), 1)
entry = settings.flags_sources[0]
self.assertIn('flags', entry)
flags = entry['flags']
self.assertEquals(len(flags), 4)
self.assertIn('-DSMTH=ON', flags)
self.assertIn('-D FLAG=word', flags)
self.assertIn('-D XXX=1', flags)
import glob
all_files = glob.glob(path.join(current_folder, "*"))
for file in all_files:
self.assertIn(file, flags[0])

def test_populate_flags(self):
"""Testing include population."""
# open any existing file
Expand Down

0 comments on commit e0f6929

Please sign in to comment.