diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..28134c7
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,23 @@
+environment:
+ # The package name
+ PACKAGE: CMakeBuilder
+ SUBLIME_TEXT_VERSION : "3"
+
+install:
+ - ps: appveyor DownloadFile "https://raw.githubusercontent.com/randy3k/UnitTesting/master/sbin/appveyor.ps1"
+ - ps: .\appveyor.ps1 "bootstrap" -verbose
+ # install Package Control
+ - ps: .\appveyor.ps1 "install_package_control" -verbose
+
+build: off
+
+test_script:
+
+ # run tests with test coverage report
+ - ps: .\appveyor.ps1 "run_tests" -coverage -verbose
+
+after_test:
+ - "SET PYTHON=C:\Python33"
+ - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
+ - pip install codecov
+ - codecov
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..bc31973
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,2 @@
+[run]
+omit = /*/tests/*
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9041b72
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,21 @@
+# Stuff not being exported to ZIP and therefore
+# prevented from being packed into LSP.sublime-package
+
+## git
+.github/ export-ignore
+gh-pages/ export-ignore
+*.git export-ignore
+*.gitignore export-ignore
+*.gitattributes export-ignore
+
+## unit testing
+tests/ export-ignore
+unittesting.json export-ignore
+
+## other configs
+stubs/ export-ignore
+.travis.yml export-ignore
+.appveyor.yml export-ignore
+.coveragerc export-ignore
+*.ini export-ignore
+*.cfg export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ee7b732
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.coverage
+.vagrant
+*.pyc
+tests/*build*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..35ad896
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,55 @@
+env:
+ global:
+ - PACKAGE=CMakeBuilder # Package name
+ - SUBLIME_TEXT_VERSION="3"
+ # use UNITTESTING_TAG to specific tag of UnitTesting
+ # - UNITTESTING_TAG="master"
+
+# mutliple os matrix
+# https://docs.travis-ci.com/user/multi-os/#Python-example-(unsupported-languages)
+matrix:
+ include:
+ - os: linux
+ language: python
+ python: 3.3
+ - os: osx
+ language: generic
+
+
+before_install:
+ - curl -OL https://raw.githubusercontent.com/randy3k/UnitTesting/master/sbin/travis.sh
+ # enable gui, see https://docs.travis-ci.com/user/gui-and-headless-browsers
+ - if [ "$TRAVIS_OS_NAME" == "linux" ]; then
+ export DISPLAY=:99.0;
+ sh -e /etc/init.d/xvfb start;
+ fi
+
+install:
+ # bootstrap the testing environment
+ - sh travis.sh bootstrap
+ # install Package Control and package denepdencies
+ - sh travis.sh install_package_control
+
+script:
+ # run tests with test coverage report
+ - sh travis.sh run_tests --coverage
+ # testing syntax_test files
+ # - sh travis.sh run_syntax_tests
+
+after_success:
+ # remove the following if `coveralls` is not needed
+ - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ brew update;
+ brew install python3;
+ pip3 install python-coveralls;
+ pip3 install codecov;
+ fi
+ - if [ "$TRAVIS_OS_NAME" == "linux" ]; then
+ pip install python-coveralls;
+ pip install codecov;
+ fi
+ - coveralls
+ - codecov
+
+notifications:
+ email: false
diff --git a/CMakeBuilder.sublime-build b/CMakeBuilder.sublime-build
new file mode 100644
index 0000000..f36ea9b
--- /dev/null
+++ b/CMakeBuilder.sublime-build
@@ -0,0 +1,10 @@
+{
+ "target": "cmake_build",
+ "selector": "source.cmake | source.c | source.c++",
+ "variants":
+ {
+ "name": "Select & Build Target",
+ "selector": "source.cmake | source.c | source.c++",
+ "select_target": true
+ }
+}
diff --git a/CMakeBuilder.sublime-commands b/CMakeBuilder.sublime-commands
index 4b7ba6b..5b9790c 100644
--- a/CMakeBuilder.sublime-commands
+++ b/CMakeBuilder.sublime-commands
@@ -1,6 +1,6 @@
[
{
- "command": "cmake_open_build_folder",
+ "command": "cmake_open_build_folder",
"caption": "CMakeBuilder: Browse Build Folder…"
},
{
@@ -8,7 +8,7 @@
"caption": "CMakeBuilder: Configure"
},
{
- "command": "cmake_write_build_targets",
+ "command": "cmake_write_build_targets",
"caption": "CMakeBuilder: Write Build Targets to Sublime Project File"
},
{
@@ -30,5 +30,37 @@
{
"command": "cmake_diagnose",
"caption": "CMakeBuilder: Diagnose (What Should I Do?)"
+ },
+ {
+ "command": "cmake_set_target",
+ "caption": "CMakeBuilder: Set Target"
+ },
+ {
+ "command": "cmake_reveal_include_directories",
+ "caption": "CMakeBuilder: Reveal Include Directories"
+ },
+ {
+ "command": "cmake_dump_file_system_watchers",
+ "caption": "CMakeBuilder: Dump File System Watchers"
+ },
+ {
+ "command": "cmake_dump_inputs",
+ "caption": "CMakeBuilder: Dump CMake Inputs"
+ },
+ {
+ "command": "cmake_set_global_setting",
+ "caption": "CMakeBuilder: Set Global Setting"
+ },
+ {
+ "command": "cmake_show_configure_output",
+ "caption": "CMakeBuilder: Show Configure Output"
+ },
+ {
+ "command": "cmake_switch_scheme",
+ "caption": "CMakeBuilder: Switch Scheme"
+ },
+ {
+ "command": "cmake_restart_server",
+ "caption": "CMakeBuilder: Restart Server For This Project"
}
]
diff --git a/CMakeBuilder.sublime-settings b/CMakeBuilder.sublime-settings
index 7ae99a9..9fde24c 100644
--- a/CMakeBuilder.sublime-settings
+++ b/CMakeBuilder.sublime-settings
@@ -1,23 +1,44 @@
{
- // These are the default settings. They are located in
- //
+ // These are the default settings. They are located in
+ //
// (Installed) Packages/CMakeBuilder/CMakeBuilder.sublime-settings
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- //
+ //
// You should not edit this file, as it gets overwritten after every update.
// Instead, if you want to override the default settings, create a new file
// in Packages/User/CMakeBuilder.sublime-settings, and copy and paste over
// from this file. Then change what you want.
- //
+ //
// If you came here from
- //
+ //
// Preferences -> Package Settings -> CMakeBuilder -> Settings,
//
// then Sublime Text has already opened a "user" file for you to the right
// of this view in which you may override settings.
-
+
//==========================================================================
-
+
+ // If there's a compile_commands.json file generated with
+ // CMAKE_EXPORT_COMPILE_COMMANDS, do we want to copy it over to the source
+ // directory? This is useful for, for instance, clangd.
+ // See: https://clang.llvm.org/extra/clangd.html
+ // See: https://clang.llvm.org/docs/JSONCompilationDatabase.html
+ // See: https://cmake.org/cmake/help/v3.5/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html
+ "copy_compile_commands_to_project_path": false,
+
+ // Wether to auto-update "ecc_flags_sources" upon a succesful configure
+ // to point to the compilation database.
+ "auto_update_EasyClangComplete_compile_commands_location": false,
+
+ // Wether to auto-update the "compile_commands" key upon a succesful
+ // configure to point to the compilation database.
+ "auto_update_compile_commands_project_setting": false,
+
+ // Set this to true to always open an output panel when the server starts
+ // to configure the project. If false, the output panel will only display
+ // when an error occurs.
+ "server_configure_verbose": false,
+
// If the command "CMakeBuilder: Configure" exited with status 0, should we
// write/update build targets in the sublime project file immediately?
"write_build_targets_after_successful_configure": true,
@@ -35,7 +56,7 @@
// the "Configure" command will run.
"configure_on_save": true,
- // The command line arguments that are passed to CTest when you run the
+ // The command line arguments that are passed to CTest when you run the
// command "CMakeBuilder: Run CTest".
"ctest_command_line_args": "--output-on-failure",
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 964efb2..0507a14 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,10 @@
Thank you for showing interest in contributing to CMakeBuilder.
# Style Guide
-The style is loosely based on [PEP8][1].
+Use the LSP plugin (search for it on Package Control) together with the
+python-language-server when you make changes to this plugin. You can install
+python-language-server with pip3. When you're done with the changes, format
+the document. Please trim whitespace and make sure views end with an empty line.
# Things That Need Attention
More platform-specific generators are very welcome. See the folder "Generators".
-
-[1]: https://www.python.org/dev/peps/pep-0008/
diff --git a/Main.sublime-menu b/Main.sublime-menu
index 6b168bd..bda97f4 100644
--- a/Main.sublime-menu
+++ b/Main.sublime-menu
@@ -5,7 +5,7 @@
"id": "tools",
"children":
[
- {
+ {
"caption": "CMakeBuilder",
"id": "build",
"mnemonic": "C",
@@ -18,8 +18,8 @@
"mnemonic": "D"
},
{
- "command": "cmake_configure",
- "mnemonic": "C"
+ "command": "cmake_configure",
+ "mnemonic": "C"
},
{
"command": "cmake_write_build_targets",
@@ -33,7 +33,7 @@
"command": "cmake_edit_cache"
},
{
- "command": "cmake_clear_cache"
+ "command": "cmake_clear_cache"
},
{
"command": "cmake_run_ctest",
@@ -42,6 +42,32 @@
{
"command": "cmake_new_project",
"mnemonic": "N"
+ },
+ {
+ "command": "cmake_set_target",
+ "mnemonic": "T"
+ },
+ {
+ "command": "cmake_reveal_include_directories",
+ "mnemonic": "I"
+ },
+ {
+ "command": "cmake_dump_file_system_watchers",
+ },
+ {
+ "command": "cmake_dump_inputs"
+ },
+ {
+ "command": "cmake_set_global_setting"
+ },
+ {
+ "command": "cmake_show_configure_output"
+ },
+ {
+ "command": "cmake_switch_scheme"
+ },
+ {
+ "command": "cmake_restart_server"
}
]
}
@@ -50,17 +76,17 @@
{
"caption": "Preferences",
"id": "preferences",
- "children":
+ "children":
[
{
"caption": "Package Settings",
"id": "package-settings",
- "children":
+ "children":
[
{
"caption": "CMakeBuilder",
"id": "cmakebuilder",
- "children":
+ "children":
[
{
"command": "open_url",
diff --git a/README.md b/README.md
index 850b447..f4d9b57 100644
--- a/README.md
+++ b/README.md
@@ -10,13 +10,149 @@ Run the command
and look for CMakeBuilder.
+# WANTED: Testers for Version 2.0
+
+Version 2.0 has major differences, because we'll utilize the cmake server for
+version 2. It is currently in an alpha state. You can make Package Control
+download the alpha version by opening the User settings of Package Control and
+adding the following JSON list at the top-level JSON dictionary:
+
+```javascript
+ "install_prereleases":
+ [
+ "CMakeBuilder
+ ],
+```
+
+The alpha version should work out-of-the-box for OSX and Linux, but is not
+recommended for Windows users at this point. I would appreciate it if you report
+any issues that you may find.
+
+# Major Changes Between V1 and V2
+
+Version 2 of CMakeBuilder has cmake-server functionality. There are some major
+changes in how you specify your cmake dict in your project settings. **Please
+read the documentation for version 2.0**.
+
+## Version 2.0
+
+Version 2.0 has server functionality. You need at least CMake 3.7 for this.
+What follows is the documentation for version 2.0.0 and higher.
+
+## TL;DR
+
+1. Open a `.sublime-project`.
+
+2. Add this to the project file in your `"settings"`:
+
+ ```javascript
+ "cmake":
+ {
+ "schemes":
+ [
+ {
+ "name": "Debug",
+ // this assumes your project file is at the root of your project
+ // folder. If it's not, try using ${folder} instead of ${project_path}
+ "build_folder": "${project_path}/build"
+ }
+ ]
+ }
+ ```
+
+3. Save your project, and *open it if you haven't already*. So, from the
+ command-line you would do `subl path/to/projectfile.sublime-project` to open
+ your project.
+
+4. You will be presented with a quick-panel that asks you to select the CMake
+ generator. Choose one.
+
+5. CMakeBuilder will start configuring your CMake project. Look at Sublime's
+ status bar to see its progress going from 0% to 100%.
+
+6. Once the project is configured, select the CMakeBuilder build system in
+ Tools -> Build Systems -> CMakeBuilder.
+
+7. Press CTRL+B to build.
+
+### The CMake Dictionary Version 2.0.0 and Higher
+
+By "CMake dictionary" we mean the JSON dictionary that you define in your
+`"settings"` of your sublime project file with key `"cmake"`. The CMake
+dictionary accepts the following keys:
+
+* `schemes` [required]
+
+ A JSON-list of JSON-dictionaries that define your possible *schemes*.
+ A *scheme* is our way to organize Debug/Release/MinSizeRel builds etc.
+
+Each *scheme* is required to be a JSON-dictionary. It's possible keys are:
+
+* `name` [required]
+
+ A string that represents this scheme. For instance, "Debug" or "Release".
+
+* `build_folder` [required]
+
+ A string pointing to the directory where you want to build the project. A
+ good first choice is `${project_path}/build`.
+
+* `command_line_overrides` [optional]
+
+ A dictionary where each value is either a string or a boolean. The key-value
+ pairs are passed to the CMake invocation when you run `cmake_configure` as
+ `-D` options. For example, if you have the key-value pair `"MY_VAR": "BLOB"`
+ in the dictionary, the CMake invocation will contain `-DMY_VAR=BLOB`. Boolean
+ values are converted to `ON` or `OFF`. For instance, if you have the key-value
+ pair `"BUILD_SHARED_LIBS": true`in the dictionary, the CMake invocation will
+ contain `-DBUILD_SHARED_LIBS=ON`.
+
+## Example Project File for Version 2.0.0 and Higher
+
+Here is an example Sublime project to get you started.
+
+```javascript
+{
+ "folders":
+ [
+ {
+ "path": "."
+ }
+ ],
+ "settings":
+ {
+ "cmake":
+ {
+ "schemes":
+ [
+ {
+ "build_folder": "${project_path}/build/debug",
+ "command_line_overrides":
+ {
+ "BUILD_SHARED_LIBS": true,
+ "CMAKE_BUILD_TYPE": "Debug",
+ "CMAKE_EXPORT_COMPILE_COMMANDS": true
+ }
+ }
+ ]
+ }
+ }
+}
+
+```
+
+## Version 1.0.1 and Lower
+
+Version 1.0.1 and lower do not have server functionality. What follows is the
+documentation for version 1.0.1 and lower.
+
## TL;DR
1. Open a `.sublime-project`.
2. Add this to the project file in your `"settings"`:
- ```json
+ ```javascript
"cmake":
{
"build_folder": "${project_path}/build"
@@ -28,7 +164,7 @@ and look for CMakeBuilder.
from the command palette.
4. Check out your new build system in your `.sublime-project`. If no new build
- system was created, you can also run the command "CMakeBuilder: Write Build
+ system was created, you can also run the command "CMakeBuilder: Write Build
Targets to Sublime Project File" from the command palette.
5. Press CTRL + B or ⌘ + B.
@@ -38,10 +174,10 @@ and look for CMakeBuilder.
## Reference
-### The CMake Dictionary
+### The CMake Dictionary Version 1.0.1 and Lower
-By "CMake dictionary" we mean the JSON dictionary that you define in your
-`"settings"` of your sublime project file with key `"cmake"`. The CMake
+By "CMake dictionary" we mean the JSON dictionary that you define in your
+`"settings"` of your sublime project file with key `"cmake"`. The CMake
dictionary accepts the following keys:
* `build_folder` [required]
@@ -68,7 +204,7 @@ dictionary accepts the following keys:
* `generator` [optional]
- A JSON string specifying the CMake generator.
+ A JSON string specifying the CMake generator.
* Available generators for osx: "Ninja" and "Unix Makefiles".
@@ -80,7 +216,7 @@ dictionary accepts the following keys:
If no generator is specified on osx, "Unix Makefiles" is the default
generator. For "Ninja", you must have ninja installed. Install it with apt.
- * Available generators for windows: "Ninja", "NMake Makefiles" and
+ * Available generators for windows: "Ninja", "NMake Makefiles" and
"Visual Studio".
If no generator is specified on windows, "Visual Studio" is the default
@@ -89,7 +225,7 @@ dictionary accepts the following keys:
for.
**Note**: If you find that the output of the NMake generator is garbled with
- color escape codes, you can try to use `"CMAKE_COLOR_MAKEFILE": false` in
+ color escape codes, you can try to use `"CMAKE_COLOR_MAKEFILE": false` in
your `command_line_overrides` dictionary.
* `root_folder` [optional]
@@ -100,8 +236,8 @@ dictionary accepts the following keys:
* `env` [optional]
- This is a dict of key-value pairs of strings. Place your environment
- variables at configure time in here. For example, to select clang as
+ This is a dict of key-value pairs of strings. Place your environment
+ variables at configure time in here. For example, to select clang as
your compiler if you have gcc set as default, you can use
"env": { "CC": "clang", "CXX": "clang++" }
@@ -138,11 +274,11 @@ Any key may be overridden by a platform-specific override. The platform keys
are one of `"linux"`, `"osx"` or `"windows"`. For an example on how this works,
see below.
-## Example Project File
+## Example Project File for Version 1.0.1 and Lower
Here is an example Sublime project to get you started.
-```json
+```javascript
{
"folders":
[
@@ -204,7 +340,7 @@ menu at the top of the window.
* `configure_on_save` : JSON bool
- If true, will run the `cmake_configure` command whenever you save a
+ If true, will run the `cmake_configure` command whenever you save a
CMakeLists.txt file or CMakeCache.txt file.
* `write_build_targets_after_successful_configure` : JSON bool
@@ -215,7 +351,7 @@ menu at the top of the window.
* `silence_developer_warnings` : JSON bool
- If true, will add the option `-Wno-dev` to the CMake invocation of the
+ If true, will add the option `-Wno-dev` to the CMake invocation of the
`cmake_configure` command.
* `always_clear_cache_before_configure` : JSON bool
@@ -225,7 +361,7 @@ menu at the top of the window.
* `ctest_command_line_args` : JSON string
- Command line arguments passed to the CTest invocation when you run
+ Command line arguments passed to the CTest invocation when you run
`cmake_run_ctest`.
* `generated_name_for_build_system` : JSON string
diff --git a/Syntax/Ninja.sublime-syntax b/Syntax/Ninja.sublime-syntax
index 93067f1..5e5f9fb 100644
--- a/Syntax/Ninja.sublime-syntax
+++ b/Syntax/Ninja.sublime-syntax
@@ -5,89 +5,93 @@ scope: output.build.ninja
contexts:
main:
- - match: ^\[(?=\d)
+ - match: ^(\[)(\d+)(/)(\d+)(\])
+ captures:
+ 0: meta.block.progress.ninja
+ 1: punctuation.section.brackets.begin.ninja
+ 2: constant.numeric.integer.decimal.ninja
+ 3: punctuation.separator.ninja
+ 4: constant.numeric.integer.decimal.ninja
+ 5: punctuation.section.brackets.end.ninja
push: expect-cmake-info-line
- match: (FAILED)(\:)((?:[^\\/]*\\|/)*)(.*)
captures:
- 1: invalid.illegal.compilation.failed
+ 1: invalid.illegal.compilation.failed.ninja
2: punctuation.separator.ninja
- 3: string.unquoted.filepath
- 4: string.unquoted.filepath
- - match: "In file included from .*"
- scope: markup.quote
+ 3: string.unquoted.filepath.ninja
+ 4: string.unquoted.filepath.ninja
+ - match: 'In file included from (.*)'
+ captures:
+ 0: comment.line.ninja
+ 1: markup.italic.ninja
- match: ((?:[^\\/]*\\|/)*)(.*)(:)(\d+)(:)(\d+)
captures:
- 2: string.unquoted.filepath
- 3: punctuation.separator.compiler
- 4: constant.numeric.integer
- 5: punctuation.separator.compiler
- 6: constant.numeric.integer
+ 2: string.unquoted.filepath.ninja
+ 3: punctuation.separator.compiler.ninja
+ 4: constant.numeric.integer.ninja
+ 5: punctuation.separator.compiler.ninja
+ 6: constant.numeric.integer.ninja
push: expect-compiler-message
- match: \^
- scope: punctuation.indicator.clang
+ scope: punctuation.definition.arrow.clang.ninja
push:
- match: $
pop: true
- match: '~+'
- scope: punctuation.indicator.clang
+ scope: punctuation.definition.tilde.clang.ninja
expect-cmake-info-line:
- - meta_scope: meta.block.progress.cmake
- - match: \]
- set:
- - meta_content_scope: meta.block.info.cmake
- - match: $
- pop: true # back to main context
- - match: (Building CX?X? object) (.*)
- captures:
- 1: keyword.operator.cmake
- 2: string.unquoted.filepath
- - match: "(Linking CX?X?(?: shared| static)? (?:library|executable)) (.*)"
- captures:
- 1: keyword.control.cmake
- 2: string.unquoted.filepath
- - match: \d+
- scope: constant.numeric.integer
+ - meta_content_scope: meta.block.info.cmake.ninja
+ - match: $
+ pop: true # back to main context
+ - match: (Building CX?X? object) (.*)
+ captures:
+ 1: keyword.operator.cmake.ninja
+ 2: string.unquoted.filepath.cmake.ninja
+ - match: "(Linking CX?X?(?: shared| static)? (?:library|executable)) (.*)"
+ captures:
+ 1: keyword.control.cmake.ninja
+ 2: string.unquoted.filepath.cmake.ninja
expect-compiler-message:
- meta_scope: meta.block.compiler.diagnostic
- match: ':'
- scope: punctuation.separator
+ scope: punctuation.separator.ninja
set:
- - meta_content_scope: meta.block.compiler.diagnostic
+ - meta_content_scope: meta.block.compiler.diagnostic.ninja
- match: \s*(warning)
captures:
- 1: markup.changed
+ 1: markup.changed.ninja
set:
- - meta_content_scope: meta.block.compiler.diagnostic
+ - meta_content_scope: meta.block.compiler.diagnostic.ninja
- match: ':'
- scope: punctuation.separator
+ scope: punctuation.separator.ninja
set:
- - meta_content_scope: meta.block.compiler.diagnostic
+ - meta_content_scope: meta.block.compiler.diagnostic.ninja
- match: (.+)
- scope: meta.block.compiler.diagnostic markup.changed
+ scope: meta.block.compiler.diagnostic.ninja markup.changed.ninja
pop: true
- match: \s*(error)
captures:
- 1: markup.deleted
+ 1: markup.deleted.ninja
set:
- - meta_content_scope: meta.block.compiler.diagnostic
+ - meta_content_scope: meta.block.compiler.diagnostic.ninja
- match: ':'
- scope: punctuation.separator
+ scope: punctuation.separator.ninja
set:
- match: (.+)
- scope: markup.deleted
+ scope: markup.deleted.ninja
pop: true
- match: \s*(note)
captures:
- 1: markup.quote
+ 1: markup.quote.ninja
set:
- - meta_content_scope: meta.block.compiler.diagnostic
+ - meta_content_scope: meta.block.compiler.diagnostic.ninja
- match: ':'
- scope: punctuation.separator
+ scope: punctuation.separator.ninja
set:
- match: (.+)
- scope: markup.quote
+ scope: markup.quote.ninja
pop: true
- match: .
pop: true
diff --git a/__init__.py b/__init__.py
index 7a16d39..292b9d9 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,5 +1,4 @@
-__version__ = "1.0.0"
-__version_info__ = (1,0,0)
+__version__ = "2.0.0"
+__version_info__ = (2, 0, 0)
from CMakeBuilder.commands import *
-from CMakeBuilder.event_listeners import *
diff --git a/commands/__init__.py b/commands/__init__.py
index d9bd733..b3971bb 100644
--- a/commands/__init__.py
+++ b/commands/__init__.py
@@ -1,21 +1,20 @@
+from .build import CmakeBuildCommand, CmakeExecCommand
from .clear_cache import CmakeClearCacheCommand
from .configure import CmakeConfigureCommand
+from .configure2 import CmakeConfigure2Command
from .diagnose import CmakeDiagnoseCommand
+from .dump_file_system_watchers import CmakeDumpFileSystemWatchersCommand
+from .dump_inputs import CmakeDumpInputsCommand
from .edit_cache import CmakeEditCacheCommand
from .insert_diagnosis import CmakeInsertDiagnosisCommand
from .new_project import CmakeNewProjectCommand
from .open_build_folder import CmakeOpenBuildFolderCommand
+from .reveal_include_directories import CmakeRevealIncludeDirectories
from .run_ctest import CmakeRunCtestCommand
+from .set_global_setting import CmakeSetGlobalSettingCommand
+from .set_target import CmakeSetTargetCommand
from .write_build_targets import CmakeWriteBuildTargetsCommand
-
-__all__ = [
- 'CmakeClearCacheCommand'
- , 'CmakeConfigureCommand'
- , 'CmakeDiagnoseCommand'
- , 'CmakeEditCacheCommand'
- , 'CmakeInsertDiagnosisCommand'
- , 'CmakeNewProjectCommand'
- , 'CmakeOpenBuildFolderCommand'
- , 'CmakeRunCtestCommand'
- , 'CmakeWriteBuildTargetsCommand'
-]
+from .show_configure_output import CmakeShowConfigureOutputCommand
+from .switch_scheme import CmakeSwitchSchemeCommand
+from .command import ServerManager
+from .command import CmakeRestartServerCommand
diff --git a/commands/build.py b/commands/build.py
new file mode 100644
index 0000000..7edc42b
--- /dev/null
+++ b/commands/build.py
@@ -0,0 +1,137 @@
+import sublime
+import Default.exec
+import os
+from .command import CmakeCommand, ServerManager
+from ..support import capabilities
+
+
+class CmakeExecCommand(Default.exec.ExecCommand):
+
+ def run(self, window_id, **kwargs):
+ self.server = ServerManager.get(sublime.Window(window_id))
+ if not self.server:
+ sublime.error_message("Unable to locate server!")
+ return
+ self.server.is_building = True
+ super().run(**kwargs)
+
+ def on_finished(self, proc):
+ super().on_finished(proc)
+ self.server.is_building = False
+
+
+class CmakeBuildCommand(CmakeCommand):
+
+ def run(self, select_target=False):
+ if not capabilities("serverMode"):
+ sublime.error_message("You need CMake 3.7 or higher. It's "
+ "possible that you selected the 'CMake' "
+ "build system in the Tools menu. This build "
+ "system is only available when CMakeBuilder "
+ "is running in 'Server' mode. Server mode "
+ "was added to CMake in version 3.7. If you "
+ "want to use CMakeBuilder, select your "
+ "build system generated in your project "
+ "file instead.")
+ return
+ if not self.is_enabled():
+ sublime.error_message("Cannot build a CMake target!")
+ return
+ path = os.path.join(self.server.cmake.build_folder,
+ "CMakeFiles",
+ "CMakeBuilder",
+ "active_target.txt")
+ if os.path.exists(path):
+ with open(path, "r") as f:
+ active_target = f.read()
+ else:
+ active_target = None
+ if select_target or active_target is None:
+ if not self.server.targets:
+ sublime.error_message(
+ "No targets found. Did you configure the project?")
+ self.items = [
+ [t.name, t.type, t.directory] for t in self.server.targets]
+ self.window.show_quick_panel(self.items, self._on_done)
+ else:
+ self._on_done(active_target)
+
+ def _on_done(self, index):
+ if isinstance(index, str):
+ self.window.run_command("cmake_set_target", {"name": index})
+ target = None
+ for t in self.server.targets:
+ if t.name == index:
+ target = t
+ break
+ elif isinstance(index, int):
+ if index == -1:
+ return
+ target = self.server.targets[index]
+ self.window.run_command("cmake_set_target", {"index": index})
+ else:
+ sublime.error_message("Unknown type: " + type(index))
+ return
+ if target.type == "RUN":
+ self._handle_run_target(target)
+ else:
+ self.window.run_command(
+ "cmake_exec",
+ {
+ "window_id": self.window.id(),
+ "cmd": target.cmd(),
+ "file_regex": self.server.cmake.file_regex,
+ "syntax": self.server.cmake.syntax,
+ "working_dir": self.server.cmake.build_folder
+ }
+ )
+
+ def _handle_run_target(self, target):
+ if sublime.platform() in ("linux", "osx"):
+ prefix = "./"
+ else:
+ prefix = ""
+ cmd = None
+ for t in self.server.targets:
+ if t.name == target.name[len("Run: "):]:
+ cmd = t.cmd()
+ break
+ if not cmd:
+ sublime.error_message("Failed to find corresponding build "
+ 'target for "run" target ' +
+ target.name)
+ return
+ try:
+ if sublime.platform() in ("linux", "osx"):
+ cmd = ["/bin/bash",
+ "-l",
+ "-c",
+ "{} && cd {} && {}".format(" ".join(cmd),
+ t.directory,
+ prefix + t.fullname)]
+ elif sublime.platform() == "windows":
+ raise ImportError
+ else:
+ raise ImportError
+ self._handle_run_target_terminal_view_route(cmd)
+ except ImportError:
+ self.window.run_command(
+ "cmake_exec",
+ {
+ "window_id": self.window.id(),
+ "cmd": cmd,
+ "working_dir": target.directory
+ }
+ )
+ except Exception as e:
+ sublime.error_message("Unknown exception: " + str(e))
+ raise e
+
+ def _handle_run_target_terminal_view_route(self, cmd):
+ import TerminalView # will throw if not present
+ assert TerminalView
+ self.window.run_command(
+ "terminal_view_exec", {
+ "cmd": cmd,
+ "working_dir": self.server.cmake.build_folder
+ })
diff --git a/commands/clear_cache.py b/commands/clear_cache.py
index bf7fd7c..a9a890b 100644
--- a/commands/clear_cache.py
+++ b/commands/clear_cache.py
@@ -20,12 +20,13 @@ def is_enabled(self):
return False
return True
- def description(self):
+ @classmethod
+ def description(cls):
return 'Clear Cache'
def run(self, with_confirmation=True):
build_folder = sublime.expand_variables(
- self.window.project_data()["settings"]["cmake"]["build_folder"],
+ self.window.project_data()["settings"]["cmake"]["build_folder"],
self.window.extract_variables())
files_to_remove = []
dirs_to_remove = []
@@ -50,21 +51,21 @@ def append_file_to_remove(relative_name):
panel = self.window.create_output_panel('files_to_be_deleted')
- self.window.run_command('show_panel',
+ self.window.run_command('show_panel',
{'panel': 'output.files_to_be_deleted'})
- panel.run_command('insert',
+ panel.run_command('insert',
{'characters': 'Files to remove:\n' +
'\n'.join(files_to_remove + dirs_to_remove)})
def on_done(selected):
if selected != 0: return
self.remove(files_to_remove, dirs_to_remove)
- panel.run_command('append',
+ panel.run_command('append',
{'characters': '\nCleared CMake cache files!',
'scroll_to_end': True})
- self.window.show_quick_panel(['Do it', 'Cancel'], on_done,
+ self.window.show_quick_panel(['Do it', 'Cancel'], on_done,
sublime.KEEP_OPEN_ON_FOCUS_LOST)
def remove(self, files_to_remove, dirs_to_remove):
@@ -79,4 +80,4 @@ def remove(self, files_to_remove, dirs_to_remove):
except Exception as e:
sublime.error_message('Cannot remove '+directory)
-
+
diff --git a/commands/command.py b/commands/command.py
new file mode 100644
index 0000000..c34a31c
--- /dev/null
+++ b/commands/command.py
@@ -0,0 +1,377 @@
+import sublime_plugin
+import sublime
+import os
+import pickle
+from ..support.server import Server
+from ..support import capabilities
+from ..support import get_setting
+
+
+def _configure(window):
+ try:
+ cmake = window.project_data()["settings"]["cmake"]
+ build_folder = cmake["build_folder"]
+ build_folder = sublime.expand_variables(
+ build_folder, window.extract_variables())
+ if os.path.exists(build_folder):
+ window.run_command("cmake_configure")
+ except Exception:
+ pass
+
+
+class CmakeCommand(sublime_plugin.WindowCommand):
+
+ def is_enabled(self):
+ self.server = ServerManager.get(self.window)
+ return (self.server is not None and
+ super(CmakeCommand, self).is_enabled())
+
+
+class CmakeRestartServerCommand(CmakeCommand):
+
+ def run(self):
+ try:
+ window_id = self.window.id()
+ ServerManager._servers.pop(window_id, None)
+ self.window.focus_view(self.window.active_view())
+ except Exception as e:
+ sublime.errror_message(str(e))
+
+ @classmethod
+ def description(cls):
+ return "Restart Server For This Project"
+
+
+class CMakeSettings(object):
+
+ __slots__ = ("source_folder", "build_folder", "build_folder_pre_expansion",
+ "generator", "toolset", "platform", "command_line_overrides",
+ "file_regex", "syntax")
+
+ """docstring for CMakeSettings"""
+ def __init__(self):
+ super(CMakeSettings, self).__init__()
+ self.source_folder = ""
+ self.build_folder = ""
+ self.build_folder_pre_expansion = ""
+ self.generator = ""
+ self.toolset = ""
+ self.platform = ""
+ self.command_line_overrides = {}
+ self.file_regex = ""
+ self.syntax = ""
+
+ def cmd(self, target):
+ return target.cmd()
+
+
+class ServerManager(sublime_plugin.EventListener):
+ """Manages the bijection between cmake-enabled projects and server
+ objects."""
+
+ _servers = {}
+
+ @classmethod
+ def get(cls, window):
+ return cls._servers.get(window.id(), None)
+
+ def __init__(self):
+ self.__class__._is_selecting = False
+ self.__class__.generator = ""
+ self.__class__.source_folder = ""
+ self.__class__.build_folder = ""
+ self.__class__.toolset = ""
+ self.__class__.platform = ""
+
+ @classmethod
+ def on_load(cls, view):
+ if not capabilities("serverMode"):
+ print("CMakeBuilder: cmake is not capable of server mode")
+ return
+ if cls._is_selecting:
+ # User is busy entering stuff
+ return
+ if not capabilities("serverMode"):
+ print("CMakeBuilder: cmake is not capable of server mode")
+ return
+ # Check if there's a server running for this window.
+ cls.window = view.window()
+ if not cls.window:
+ return
+ server = cls.get(cls.window)
+ if server:
+ return
+
+ # No server running. Check if there are build settings.
+ data = cls.window.project_data()
+ if not data:
+ return
+ settings = data.get("settings", None)
+ if not settings or not isinstance(settings, dict):
+ return
+ cmake = settings.get("cmake", None)
+ if not cmake or not isinstance(cmake, dict):
+ return
+ cls.schemes = cmake.get("schemes", None)
+ if (not cls.schemes or
+ not isinstance(cls.schemes, list) or
+ len(cls.schemes) == 0):
+ return
+
+ # At this point we found schemes. Let's check if there's a
+ # CMakeLists.txt file to be found somewhere up the directory tree.
+ if not view.file_name():
+ return
+ cmake_file = os.path.dirname(view.file_name())
+ cmake_file = os.path.join(cmake_file, "CMakeLists.txt")
+ while not os.path.isfile(cmake_file):
+ cmake_file = os.path.dirname(os.path.dirname(cmake_file))
+ if os.path.dirname(cmake_file) == cmake_file:
+ # We're at the root of the filesystem.
+ cmake_file = None
+ break
+ cmake_file = os.path.join(cmake_file, "CMakeLists.txt")
+ if not cmake_file:
+ # Not a cmake project
+ return
+ # We found a CMakeLists.txt file, but we might be embedded into a
+ # larger project. Find the true root file.
+ while True:
+ old_cmake_file = cmake_file
+ cmake_file = cmake_file = os.path.dirname(
+ os.path.dirname(cmake_file))
+ cmake_file = os.path.join(cmake_file, "CMakeLists.txt")
+ while not os.path.isfile(cmake_file):
+ cmake_file = os.path.dirname(os.path.dirname(cmake_file))
+ if os.path.dirname(cmake_file) == cmake_file:
+ # We're at the root of the filesystem.
+ cmake_file = None
+ break
+ cmake_file = os.path.join(cmake_file, "CMakeLists.txt")
+ if not cmake_file:
+ # We found the actual root of the project earlier.
+ cmake_file = old_cmake_file
+ break
+ cls.source_folder = os.path.dirname(cmake_file)
+ print("found source folder:", cls.source_folder)
+
+ # At this point we have a bunch of schemes and we have a source folder.
+ cls.items = []
+ for scheme in cls.schemes:
+ if not isinstance(scheme, dict):
+ sublime.error_message("Please make sure all of your schemes "
+ "are JSON dictionaries.")
+ cls.items.append(["INVALID SCHEME", ""])
+ continue
+ name = scheme.get("name", "Untitled Scheme")
+ build_folder = scheme.get("build_folder", "${project_path}/build")
+ variables = cls.window.extract_variables()
+ build_folder = sublime.expand_variables(build_folder, variables)
+ cls.items.append([name, build_folder])
+ if len(cls.schemes) == 0:
+ print("found schemes dict, but it is empty")
+ return
+ cls._is_selecting = True
+ if len(cls.schemes) == 1:
+ # Select the only scheme possible.
+ cls._on_done_select_scheme(0)
+ else:
+ # Ask the user what he/she wants.
+ cls.window.show_quick_panel(cls.items,
+ cls._on_done_select_scheme)
+
+ @classmethod
+ def _on_done_select_scheme(cls, index):
+ if index == -1:
+ cls._is_selecting = False
+ return
+ cls.name = cls.items[index][0]
+ if cls.name == "INVALID SCHEME":
+ cls._is_selecting = False
+ return
+ cls.build_folder_pre_expansion = cls.schemes[index]["build_folder"]
+ cls.build_folder = sublime.expand_variables(
+ cls.build_folder_pre_expansion, cls.window.extract_variables())
+ cls.command_line_overrides = cls.schemes[index].get(
+ "command_line_overrides", {})
+ cls._select_generator()
+
+ @classmethod
+ def _select_generator(cls):
+ if cls.generator:
+ cls._select_toolset()
+ return
+ cls.items = []
+ for g in capabilities("generators"):
+ platform_support = bool(g["platformSupport"])
+ toolset_support = bool(g["toolsetSupport"])
+ platform_support = "Platform support: {}".format(platform_support)
+ toolset_support = "Toolset support: {}".format(toolset_support)
+ cls.items.append([g["name"], platform_support, toolset_support])
+ if len(cls.items) == 1:
+ cls._on_done_select_generator(0)
+ else:
+ cls.window.show_quick_panel(cls.items,
+ cls._on_done_select_generator)
+
+ @classmethod
+ def _on_done_select_generator(cls, index):
+ if index == -1:
+ cls._is_selecting = False
+ return
+ cls.generator = cls.items[index][0]
+ platform_support = cls.items[index][1]
+ toolset_support = cls.items[index][2]
+ cls.platform_support = True if "True" in platform_support else False
+ cls.toolset_support = True if "True" in toolset_support else False
+ print("CMakeBuilder: Selected generator is", cls.generator)
+ if cls.platform_support:
+ text = "Platform for {} (Press Enter for default): ".format(
+ cls.generator)
+ print("CMakeBuilder: Presenting input panel for platform.")
+ cls.window.show_input_panel(text, "",
+ cls._on_done_select_platform,
+ None, None)
+ elif cls.toolset_support:
+ cls._select_toolset()
+ else:
+ cls._run_configure_with_new_settings()
+
+ @classmethod
+ def _select_toolset(cls):
+ if cls.toolset:
+ print("CMakeBuilder: toolset already present:", cls.toolset)
+ return
+ print("CMakeBuilder: Presenting input panel for toolset.")
+ text = "Toolset for {}: (Press Enter for default): ".format(
+ cls.generator)
+ cls.window.show_input_panel(text, "", cls._on_done_select_toolset,
+ None, None)
+
+ @classmethod
+ def _on_done_select_platform(cls, platform):
+ cls.platform = platform
+ print("CMakeBuilder: Selected platform is", cls.platform)
+ if cls.toolset_support:
+ cls._select_toolset()
+ else:
+ cls._run_configure_with_new_settings()
+
+ @classmethod
+ def _on_done_select_toolset(cls, toolset):
+ cls.toolset = toolset
+ print("CMakeBuilder: Selected toolset is", cls.toolset)
+ cls._run_configure_with_new_settings()
+
+ @classmethod
+ def _run_configure_with_new_settings(cls):
+ cls._is_selecting = False
+ cmake_settings = CMakeSettings()
+ cmake_settings.source_folder = cls.source_folder
+ cmake_settings.build_folder = cls.build_folder
+
+ cmake_settings.build_folder_pre_expansion = \
+ cls.build_folder_pre_expansion
+
+ cmake_settings.generator = cls.generator
+ cmake_settings.platform = cls.platform
+ cmake_settings.toolset = cls.toolset
+ cmake_settings.command_line_overrides = cls.command_line_overrides
+
+ if sublime.platform() in ("osx", "linux"):
+ cmake_settings.file_regex = \
+ r'(.+[^:]):(\d+):(\d+): (?:fatal )?((?:error|warning): .+)$'
+ if "Makefile" in cls.generator:
+ cmake_settings.syntax = \
+ "Packages/CMakeBuilder/Syntax/Make.sublime-syntax"
+ elif "Ninja" in cls.generator:
+ cmake_settings.syntax = \
+ "Packages/CMakeBuilder/Syntax/Ninja.sublime-syntax"
+ else:
+ print("CMakeBuilder: Warning: Generator", cls.generator,
+ "will not have syntax highlighting in the output panel.")
+ elif sublime.platform() == "windows":
+ if "Ninja" in cls.generator:
+ cmake_settings.file_regex = r'^(.+)\((\d+)\):() (.+)$'
+ cmake_settings.syntax = \
+ "Packages/CMakeBuilder/Syntax/Ninja+CL.sublime-syntax"
+ elif "Visual Studio" in cls.generator:
+ cmake_settings.file_regex = \
+ (r'^ (.+)\((\d+)\)(): ((?:fatal )?(?:error|warning) ',
+ r'\w+\d\d\d\d: .*) \[.*$')
+ cmake_settings.syntax = \
+ "Packages/CMakeBuilder/Syntax/Visual_Studio.sublime-syntax"
+ elif "NMake" in cls.generator:
+ cmake_settings.file_regex = r'^(.+)\((\d+)\):() (.+)$'
+ cmake_settings.syntax = \
+ "Packages/CMakeBuilder/Syntax/Make.sublime-syntax"
+ else:
+ print("CMakeBuilder: Warning: Generator", cls.generator,
+ "will not have syntax highlighting in the output panel.")
+ else:
+ sublime.error_message("Unknown platform: " + sublime.platform())
+ return
+ path = os.path.join(cls.build_folder, "CMakeFiles", "CMakeBuilder")
+ os.makedirs(path, exist_ok=True)
+ path = os.path.join(path, "settings.pickle")
+
+ # Unpickle the settings first, if there are any.
+ if os.path.isfile(path):
+ old_settings = pickle.load(open(path, "rb"))
+ if (old_settings.generator != cmake_settings.generator or
+ old_settings.platform != cmake_settings.platform or
+ old_settings.toolset != cmake_settings.toolset):
+ print("CMakeBuilder: clearing cache for mismatching generator")
+ try:
+ os.remove(os.path.join(cmake_settings.build_folder,
+ "CMakeCache.txt"))
+ except Exception as e:
+ sublime.error_message(str(e))
+ return
+ pickle.dump(cmake_settings, open(path, "wb"))
+ server = Server(cls.window, cmake_settings)
+ cls.source_folder = ""
+ cls.build_folder = ""
+ cls.build_folder_pre_expansion = ""
+ cls.generator = ""
+ cls.platform = ""
+ cls.toolset = ""
+ cls.items = []
+ cls.schemes = []
+ cls.command_line_overrides = {}
+ cls._servers[cls.window.id()] = server
+
+ @classmethod
+ def on_activated(cls, view):
+ cls.on_load(view)
+ try:
+ server = cls.get(view.window())
+ path = os.path.join(server.cmake.build_folder,
+ "CMakeFiles",
+ "CMakeBuilder",
+ "active_target.txt")
+ with open(path, "r") as f:
+ active_target = f.read()
+ view.set_status("cmake_active_target", "TARGET: " + active_target)
+ except Exception as e:
+ view.erase_status("cmake_active_target")
+
+ @classmethod
+ def on_post_save(cls, view):
+ if not view:
+ return
+ if not get_setting(view, "configure_on_save", False):
+ return
+ name = view.file_name()
+ if not name:
+ return
+ if name.endswith(".sublime-project"):
+ server = cls.get(view.window())
+ if not server:
+ _configure(view.window())
+ else:
+ view.window().run_command("cmake_clear_cache",
+ {"with_confirmation": False})
+ view.window().run_command("cmake_restart_server")
+ elif name.endswith("CMakeLists.txt") or name.endswith("CMakeCache.txt"):
+ _configure(view.window())
diff --git a/commands/configure.py b/commands/configure.py
index a6f8031..66a3428 100644
--- a/commands/configure.py
+++ b/commands/configure.py
@@ -7,6 +7,7 @@
import copy
from CMakeBuilder.support import *
from CMakeBuilder.generators import *
+from .command import ServerManager
class CmakeConfigureCommand(Default.exec.ExecCommand):
"""Configures a CMake project with options set in the sublime project
@@ -18,15 +19,22 @@ def is_enabled(self):
return True
except Exception as e:
return False
-
+
def description(self):
return 'Configure'
def run(self, write_build_targets=False, silence_dev_warnings=False):
+ self.server = ServerManager.get(self.window)
+ if self.server:
+ self.window.run_command("cmake_configure2")
+ return
if get_setting(self.window.active_view(), 'always_clear_cache_before_configure', False):
self.window.run_command('cmake_clear_cache', args={'with_confirmation': False})
project = self.window.project_data()
project_file_name = self.window.project_file_name()
+ if not project_file_name:
+ # A little more flexible
+ project_file_name = os.path.abspath(self.window.extract_variables()["folder"])
project_name = os.path.splitext(project_file_name)[0]
project_path = os.path.dirname(project_file_name)
if not os.path.isfile(os.path.join(project_path, 'CMakeLists.txt')):
@@ -81,7 +89,7 @@ def run(self, write_build_targets=False, silence_dev_warnings=False):
GeneratorClass = class_from_generator_string(generator)
builder = None
try:
- builder = GeneratorClass(self.window, copy.deepcopy(cmake))
+ builder = GeneratorClass(self.window)
except KeyError as e:
sublime.error_message('Unknown variable in cmake dictionary: {}'
.format(str(e)))
@@ -103,15 +111,15 @@ def run(self, write_build_targets=False, silence_dev_warnings=False):
except ValueError as e:
pass
self.builder.on_pre_configure()
- env = self.builder.env()
+ env = self.builder.get_env()
user_env = get_cmake_value(cmake, 'env')
if user_env: env.update(user_env)
- super().run(shell_cmd=cmd,
+ super().run(shell_cmd=cmd,
working_dir=root_folder,
file_regex=r'CMake\s(?:Error|Warning)(?:\s\(dev\))?\sat\s(.+):(\d+)()\s?\(?(\w*)\)?:',
syntax='Packages/CMakeBuilder/Syntax/Configure.sublime-syntax',
env=env)
-
+
def on_finished(self, proc):
super().on_finished(proc)
self.builder.on_post_configure(proc.exit_code())
diff --git a/commands/configure2.py b/commands/configure2.py
new file mode 100644
index 0000000..02d46a9
--- /dev/null
+++ b/commands/configure2.py
@@ -0,0 +1,7 @@
+from .command import CmakeCommand
+
+
+class CmakeConfigure2Command(CmakeCommand):
+
+ def run(self):
+ self.server.configure(self.server.cmake.command_line_overrides)
diff --git a/commands/diagnose.py b/commands/diagnose.py
index 5eefe92..e1e3c52 100644
--- a/commands/diagnose.py
+++ b/commands/diagnose.py
@@ -10,12 +10,13 @@ def run(self):
view.settings().set("rulers", [])
view.settings().set("gutter", False)
view.settings().set("draw_centered", True)
- view.settings().set("syntax",
+ view.settings().set("syntax",
"Packages/CMakeBuilder/Syntax/Diagnosis.sublime-syntax")
view.set_name("CMakeBuilder Diagnosis")
view.run_command("cmake_insert_diagnosis")
view.set_read_only(True)
sublime.active_window().focus_view(view)
- def description(self):
+ @classmethod
+ def description(cls):
return "Diagnose (Help! What should I do?)"
diff --git a/commands/dump_file_system_watchers.py b/commands/dump_file_system_watchers.py
new file mode 100644
index 0000000..c3917c3
--- /dev/null
+++ b/commands/dump_file_system_watchers.py
@@ -0,0 +1,12 @@
+from .command import CmakeCommand
+
+
+class CmakeDumpFileSystemWatchersCommand(CmakeCommand):
+ """Prints the watched files to a new view"""
+
+ def run(self):
+ self.server.file_system_watchers()
+
+ @classmethod
+ def description(cls):
+ return "Dump File System Watchers"
diff --git a/commands/dump_inputs.py b/commands/dump_inputs.py
new file mode 100644
index 0000000..edd308a
--- /dev/null
+++ b/commands/dump_inputs.py
@@ -0,0 +1,12 @@
+from .command import CmakeCommand
+
+
+class CmakeDumpInputsCommand(CmakeCommand):
+ """Prints the cmake inputs to a new view"""
+
+ def run(self):
+ self.server.cmake_inputs()
+
+ @classmethod
+ def description(cls):
+ return "Dump CMake Inputs"
diff --git a/commands/edit_cache.py b/commands/edit_cache.py
index 32c43e4..77c0f81 100644
--- a/commands/edit_cache.py
+++ b/commands/edit_cache.py
@@ -1,21 +1,40 @@
-import sublime, sublime_plugin, os
-from CMakeBuilder.support import *
+import sublime
+import sublime_plugin
+import os
+from .command import CmakeCommand
+from ..support import capabilities
-class CmakeEditCacheCommand(sublime_plugin.WindowCommand):
- """Edit an entry from the CMake cache."""
- def is_enabled(self):
- try:
+
+if capabilities("serverMode"):
+
+
+ class CmakeEditCacheCommand(CmakeCommand):
+
+ def description(self):
+ return "Edit Cache..."
+
+ def run(self):
+ self.server.cache()
+
+else:
+
+
+ class CmakeEditCacheCommand(sublime_plugin.WindowCommand):
+
+ """Edit an entry from the CMake cache."""
+ def is_enabled(self):
+ try:
+ build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
+ build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
+ return os.path.exists(os.path.join(build_folder, "CMakeCache.txt"))
+ except Exception as e:
+ return False
+
+ def description(self):
+ return "Edit Cache..."
+
+ def run(self):
build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
- return os.path.exists(os.path.join(build_folder, "CMakeCache.txt"))
- except Exception as e:
- return False
-
- def description(self):
- return 'Edit Cache...'
-
- def run(self):
- build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
- build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
- self.window.open_file(os.path.join(build_folder, "CMakeCache.txt"))
- self.window.run_command("show_overlay", args={"overlay": "goto", "text": "@"})
+ self.window.open_file(os.path.join(build_folder, "CMakeCache.txt"))
+ self.window.run_command("show_overlay", args={"overlay": "goto", "text": "@"})
diff --git a/commands/insert_diagnosis.py b/commands/insert_diagnosis.py
index 6436d4f..e6e6288 100644
--- a/commands/insert_diagnosis.py
+++ b/commands/insert_diagnosis.py
@@ -1,6 +1,10 @@
-import sublime, sublime_plugin, subprocess, os, shutil, sys, json
+import sublime
+import sublime_plugin
+import os
+import shutil
from tabulate import tabulate # dependencies.json
from CMakeBuilder.support import check_output
+from CMakeBuilder.support import capabilities
class CmakeInsertDiagnosisCommand(sublime_plugin.TextCommand):
@@ -8,7 +12,10 @@ class CmakeInsertDiagnosisCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.error_count = 0
self._diagnose(edit)
- self.view.insert(edit, self.view.size(), tabulate(self.table, headers=["CHECK", "VALUE", "SUGGESTION/FIX"], tablefmt="fancy_grid"))
+ self.view.insert(edit, self.view.size(), tabulate(
+ self.table,
+ headers=["CHECK", "VALUE", "SUGGESTION/FIX"],
+ tablefmt="fancy_grid"))
def _command_exists(self, cmd):
return shutil.which(cmd) is not None
@@ -23,11 +30,11 @@ def _diagnose(self, edit):
else:
self.table.append(["cmake version", output, ""])
try:
- output = json.loads(check_output("cmake -E capabilities"))
- server_mode = output.get("serverMode", False)
+ server_mode = capabilities("serverMode")
self.table.append(["server mode", server_mode, ""])
except Exception as e:
- self.table.append(["server mode", False, "Have cmake version >= 3.7"])
+ self.table.append(["server mode", False,
+ "Have cmake version >= 3.7"])
project = self.view.window().project_data()
project_filename = self.view.window().project_file_name()
@@ -35,48 +42,38 @@ def _diagnose(self, edit):
if project_filename:
self.table.append(["project file", project_filename, ""])
else:
- self.table.append(["project file", "NOT FOUND", "Open a .sublime-project"])
+ self.table.append(["project file", "NOT FOUND",
+ "Open a .sublime-project"])
self.error_count += 1
return
- # cmake = project.get("cmake", None)
- # if cmake:
- # self._ERR(edit, "It looks like you have the cmake dictionary at the top level of your project file.")
- # self._ERR(edit, "Since version 0.11.0, the cmake dict should be in the settings dict of your project file.")
- # self._ERR(edit, "Please edit your project file so that the cmake dict is sitting inside your settings")
- # return
-
cmake = project.get("settings", {}).get("cmake", None)
if cmake:
- cmake = sublime.expand_variables(cmake, self.view.window().extract_variables())
+ cmake = sublime.expand_variables(
+ cmake,
+ self.view.window().extract_variables())
buildFolder = cmake['build_folder']
if buildFolder:
- self.table.append(["cmake dictionary present in settings", True, ""])
- # self._OK(edit, 'Found CMake build folder "{}"'.format(buildFolder))
- # self._OK(edit, 'You can run the "Configure" command.')
+ self.table.append(["cmake dictionary present in settings",
+ True, ""])
cache_file = os.path.join(buildFolder, 'CMakeCache.txt')
if os.path.isfile(cache_file):
- self.table.append(["CMakeCache.txt file present", True, "You may run the Write Build Targets command"])
- # self._OK(edit, 'Found CMakeCache.txt file in "{}"'.format(buildFolder))
- # self._OK(edit, 'You can run the command "Write Build Targets to Sublime Project File"')
- # self._OK(edit, 'If you already populated your project file with build targets, you can build your project with Sublime\'s build system. Go to Tools -> Build System and make sure your build system is selected.')
+ self.table.append([
+ "CMakeCache.txt file present", True,
+ "You may run the Write Build Targets command"])
else:
- self.table.append(["CMakeCache.txt file present", False, "Run the Configure command"])
+ self.table.append(["CMakeCache.txt file present", False,
+ "Run the Configure command"])
self.error_count += 1
- # self._ERR(edit, 'No CMakeCache.txt file found in "{}"'.format(buildFolder))
- # self._ERR(edit, 'You should run the "Configure" command.')
return
else:
- self.table.append(["build_folder present in cmake dictionary", False, "Write a build_folder key"])
+ self.table.append(["build_folder present in cmake dictionary",
+ False, "Write a build_folder key"])
self.error_count += 1
- # self._ERR(edit, 'No build_folder present in cmake dictionary of "{}".'.format(project_filename))
- # self._ERR(edit, 'You should write a key-value pair in the "cmake" dictionary')
- # self._ERR(edit, 'where the key is equal to "build_folder" and the value is the')
- # self._ERR(edit, 'directory where you want to build your project.')
- # self._ERR(edit, 'See the instructions at github.com/rwols/CMakeBuilder')
else:
- self.table.append(["cmake dictionary present in settings", False, "Create a cmake dictionary in your settings"])
+ self.table.append(["cmake dictionary present in settings", False,
+ "Create a cmake dictionary in your settings"])
return
def _printLine(self, edit, str):
diff --git a/commands/new_project.py b/commands/new_project.py
index 615f1e0..edde421 100644
--- a/commands/new_project.py
+++ b/commands/new_project.py
@@ -49,7 +49,8 @@
class CmakeNewProjectCommand(sublime_plugin.WindowCommand):
"""Creates a new template project and opens the project for you."""
- def description(self):
+ @classmethod
+ def description(cls):
return "New Project..."
def run(self):
diff --git a/commands/open_build_folder.py b/commands/open_build_folder.py
index 9fc3d71..14b9b11 100644
--- a/commands/open_build_folder.py
+++ b/commands/open_build_folder.py
@@ -1,21 +1,43 @@
-import sublime, sublime_plugin, os
-from CMakeBuilder.support import *
+import sublime
+import sublime_plugin
+import os
+from .command import CmakeCommand
+from ..support import capabilities
-class CmakeOpenBuildFolderCommand(sublime_plugin.WindowCommand):
- """Opens the build folder."""
- def is_enabled(self):
- try:
- build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
- build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
- return os.path.exists(build_folder)
- except Exception as e:
- return False
+if capabilities("serverMode"):
+
+
+ class CmakeOpenBuildFolderCommand(CmakeCommand):
+ """Opens the build folder."""
+
+ @classmethod
+ def description(cls):
+ return "Browse Build Folder..."
+
+ def run(self):
+ build_folder = self.server.cmake.build_folder
+ self.window.run_command("open_dir", args={"dir": os.path.realpath(build_folder)})
- def description(self):
- return 'Browse Build Folder…'
+else:
- def run(self):
- build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
- build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
- self.window.run_command('open_dir', args={'dir': os.path.realpath(build_folder)})
+
+ class CmakeOpenBuildFolderCommand(sublime_plugin.WindowCommand):
+ """Opens the build folder."""
+
+ @classmethod
+ def description(cls):
+ return "Browse Build Folder..."
+
+ def is_enabled(self):
+ try:
+ build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
+ build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
+ return os.path.exists(build_folder)
+ except Exception as e:
+ return False
+
+ def run(self):
+ build_folder = self.window.project_data()["settings"]["cmake"]["build_folder"]
+ build_folder = sublime.expand_variables(build_folder, self.window.extract_variables())
+ self.window.run_command('open_dir', args={'dir': os.path.realpath(build_folder)})
diff --git a/commands/reveal_include_directories.py b/commands/reveal_include_directories.py
new file mode 100644
index 0000000..a6e9c4f
--- /dev/null
+++ b/commands/reveal_include_directories.py
@@ -0,0 +1,16 @@
+from .command import CmakeCommand
+
+
+class CmakeRevealIncludeDirectories(CmakeCommand):
+ """Prints the include directories to a new view"""
+
+ def run(self):
+ view = self.window.new_file()
+ view.set_name("Project Include Directories")
+ view.set_scratch(True)
+ for path in self.server.include_paths:
+ view.run_command("append", {"characters": path + "\n", "force": True})
+
+ @classmethod
+ def description(cls):
+ return "Reveal Include Directories"
diff --git a/commands/run_ctest.py b/commands/run_ctest.py
index 2343742..15e1b54 100644
--- a/commands/run_ctest.py
+++ b/commands/run_ctest.py
@@ -12,7 +12,8 @@ def is_enabled(self):
except Exception as e:
return False
- def description(self):
+ @classmethod
+ def description(cls):
return 'Run CTest'
def run(self, test_framework='boost'):
@@ -24,11 +25,11 @@ def run(self, test_framework='boost'):
cmd += ' ' + command_line_args
#TODO: check out google test style errors, right now I just assume
# everybody uses boost unit test framework
- super().run(shell_cmd=cmd,
+ super().run(shell_cmd=cmd,
# Guaranteed to exist at this point.
- working_dir=cmake.get('build_folder'),
+ working_dir=cmake.get('build_folder'),
file_regex=r'(.+[^:]):(\d+):() (?:fatal )?((?:error|warning): .+)$',
syntax='Packages/CMakeBuilder/Syntax/CTest.sublime-syntax')
-
+
def on_finished(self, proc):
super().on_finished(proc)
diff --git a/commands/set_global_setting.py b/commands/set_global_setting.py
new file mode 100644
index 0000000..4b16e46
--- /dev/null
+++ b/commands/set_global_setting.py
@@ -0,0 +1,11 @@
+from .command import CmakeCommand
+
+
+class CmakeSetGlobalSettingCommand(CmakeCommand):
+
+ def run(self):
+ self.server.global_settings()
+
+ @classmethod
+ def description(cls):
+ return "Set Global Setting..."
diff --git a/commands/set_target.py b/commands/set_target.py
new file mode 100644
index 0000000..ead5fd2
--- /dev/null
+++ b/commands/set_target.py
@@ -0,0 +1,47 @@
+import os
+import sublime
+from .command import CmakeCommand
+
+
+class CmakeSetTargetCommand(CmakeCommand):
+
+ def run(self, index=None, name=None):
+ if self.server.is_configuring:
+ sublime.error_message("CMake is configuring, please wait.")
+ return
+ if not self.server.targets:
+ sublime.error_message("No targets found! "
+ "Did you configure the project?")
+ return
+ if name is not None:
+ self._on_done(name)
+ elif not index:
+ self.items = [
+ [t.name, t.type, t.directory] for t in self.server.targets]
+ self.window.show_quick_panel(self.items, self._on_done)
+ else:
+ self._on_done(index)
+
+ def _on_done(self, index):
+ if isinstance(index, str):
+ self._write_to_file(index)
+ elif index == -1:
+ self.window.active_view().erase_status("cmake_active_target")
+ else:
+ name = self.server.targets[index].name
+ self._write_to_file(name)
+
+ def _write_to_file(self, name):
+ folder = os.path.join(self.server.cmake.build_folder,
+ "CMakeFiles",
+ "CMakeBuilder")
+ path = os.path.join(folder, "active_target.txt")
+ os.makedirs(folder, exist_ok=True)
+ with open(path, "w") as f:
+ f.write(name)
+ self.window.active_view() \
+ .set_status("cmake_active_target", "TARGET: " + name)
+
+ @classmethod
+ def description(cls):
+ return "Set Target..."
diff --git a/commands/show_configure_output.py b/commands/show_configure_output.py
new file mode 100644
index 0000000..76fec92
--- /dev/null
+++ b/commands/show_configure_output.py
@@ -0,0 +1,12 @@
+from .command import CmakeCommand
+
+
+class CmakeShowConfigureOutputCommand(CmakeCommand):
+
+ def run(self):
+ self.window.run_command("show_panel",
+ {"panel": "output.cmake.configure"})
+
+ @classmethod
+ def description(cls):
+ return "Show Configure Output"
diff --git a/commands/switch_scheme.py b/commands/switch_scheme.py
new file mode 100644
index 0000000..aa6ce31
--- /dev/null
+++ b/commands/switch_scheme.py
@@ -0,0 +1,12 @@
+from .command import CmakeCommand, ServerManager
+
+
+class CmakeSwitchSchemeCommand(CmakeCommand):
+
+ def run(self):
+ ServerManager._servers.pop(self.window.id(), None)
+ ServerManager.on_activated(self.window.active_view())
+
+ @classmethod
+ def description(cls):
+ return "Switch Scheme"
diff --git a/commands/write_build_targets.py b/commands/write_build_targets.py
index a67dd13..900348a 100644
--- a/commands/write_build_targets.py
+++ b/commands/write_build_targets.py
@@ -40,7 +40,7 @@ def run(self, open_project_file=False):
GeneratorClass = class_from_generator_string(generator)
try:
assert cmake
- builder = GeneratorClass(self.window, copy.deepcopy(cmake))
+ builder = GeneratorClass(self.window)
except KeyError as e:
sublime.error_message('Unknown variable in cmake dictionary: {}'
.format(str(e)))
@@ -70,4 +70,4 @@ def run(self, open_project_file=False):
self.window.open_file(self.window.project_file_name())
except Exception as e:
sublime.error_message('An error occured during assigment of the sublime build system: %s' % str(e))
-
+ raise e
diff --git a/event_listeners/__init__.py b/event_listeners/__init__.py
deleted file mode 100644
index 7d77bb9..0000000
--- a/event_listeners/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .configure_on_save import ConfigureOnSave
-
-__all__ = ['ConfigureOnSave']
diff --git a/event_listeners/configure_on_save.py b/event_listeners/configure_on_save.py
deleted file mode 100644
index 0a75352..0000000
--- a/event_listeners/configure_on_save.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sublime
-import sublime_plugin
-import functools
-from CMakeBuilder.support import get_setting
-from CMakeBuilder.commands import CmakeConfigureCommand
-
-def _configure(window):
- if not CmakeConfigureCommand(window).is_enabled(): return
- window.run_command("cmake_configure")
-
-class ConfigureOnSave(sublime_plugin.EventListener):
-
- def on_post_save(self, view):
- if not view:
- return
- if not get_setting(view, "configure_on_save", False):
- return
- name = view.file_name()
- if not name:
- return
- if (name.endswith("CMakeLists.txt") or
- name.endswith("CMakeCache.txt") or
- name.endswith(".sublime-project")):
- _configure(view.window())
diff --git a/generators/__init__.py b/generators/__init__.py
index 44c5379..8070bd4 100644
--- a/generators/__init__.py
+++ b/generators/__init__.py
@@ -2,35 +2,71 @@
import sys
import os
import glob
-from CMakeBuilder.support import *
+from CMakeBuilder.support import get_setting
+
class CMakeGenerator(object):
- def __init__(self, window, cmake):
+
+ @classmethod
+ def create(cls, window):
+ """Factory method to create a new CMakeGenerator object from a sublime
+ Window object."""
+ data = window.project_data()["settings"]["cmake"]
+ generator_str = data.get("generator", None)
+ if not generator_str:
+ if sublime.platform() in ("linux", "osx"):
+ generator_str = "Unix Makefiles"
+ elif sublime.platform() == "windows":
+ generator_str = "Visual Studio"
+ else:
+ raise AttributeError(
+ "unknown sublime platform: %s" % sublime.platform())
+ GeneratorClass = class_from_generator_string(generator_str)
+ return GeneratorClass(window)
+
+ def __init__(self, window):
super(CMakeGenerator, self).__init__()
+ data = window.project_data()["settings"]["cmake"]
+ self.build_folder_pre_expansion = data["build_folder"]
+ data = sublime.expand_variables(data, window.extract_variables())
+ self.build_folder = self._pop(data, "build_folder")
+ if not self.build_folder:
+ raise KeyError('missing required key "build_folder"')
+ self.build_folder = os.path.abspath(self.build_folder)\
+ .replace("\\", "/")
+ pfn = window.project_file_name()
+ if not pfn:
+ self.source_folder = window.extract_variables()["folder"]
+ else:
+ self.source_folder = os.path.dirname(pfn)
+ while os.path.isfile(
+ os.path.join(self.source_folder, "..", "CMakeLists.txt")):
+ self.source_folder = os.path.join(self.source_folder, "..")
+ self.source_folder = os.path.abspath(self.source_folder)
+ self.source_folder = self.source_folder.replace("\\", "/")
+ self.command_line_overrides = self._pop(
+ data, "command_line_overrides", {})
+ self.filter_targets = self._pop(data, "filter_targets", [])
+ self.configurations = self._pop(data, "configurations", [])
+ self.env = self._pop(data, "env", {})
+ self.target_architecture = self._pop(
+ data, "target_architecture", "x86")
+ self.visual_studio_versions = self._pop(
+ data, "visual_studio_versions", [15, 14])
self.window = window
- self.cmake = cmake
- try:
- self.cmake_platform = self.cmake[sublime.platform()]
- except Exception as e:
- self.cmake_platform = None
- self.build_folder_pre_expansion = self.get_cmake_key('build_folder')
- assert self.build_folder_pre_expansion
- self.cmake = sublime.expand_variables(self.cmake, self.window.extract_variables())
- self.build_folder = self.get_cmake_key('build_folder')
- self.filter_targets = self.get_cmake_key('filter_targets')
- self.command_line_overrides = self.get_cmake_key('command_line_overrides')
- self.target_architecture = self.get_cmake_key('target_architecture')
- self.visual_studio_versions = self.get_cmake_key('visual_studio_versions')
assert self.build_folder
+ def _pop(self, data, key, default=None):
+ return data.get(key, default)
+
def __repr__(self):
return repr(type(self))
- def env(self):
- return {} # Empty dict
+ def get_env(self):
+ return {} # Empty dict
def variants(self):
- return [] # Empty list
+ return [] # Empty list
def on_pre_configure(self):
pass
@@ -47,11 +83,13 @@ def create_sublime_build_system(self):
sublime.error_message('Could not get the active view!')
name = get_setting(view, 'generated_name_for_build_system')
if not name:
- sublime.error_message('Could not find the key "generated_name_for_build_system" in the settings!')
+ sublime.error_message('Could not find the key '
+ '"generated_name_for_build_system"'
+ ' in the settings!')
name = sublime.expand_variables(name, self.window.extract_variables())
build_system = {
'name': name,
- 'shell_cmd': self.shell_cmd(),
+ 'shell_cmd': self.shell_cmd(),
'working_dir': self.build_folder_pre_expansion,
'variants': self.variants()
}
@@ -61,7 +99,7 @@ def create_sublime_build_system(self):
syntax = self.syntax()
if syntax:
build_system['syntax'] = syntax
- env = self.env()
+ env = self.get_env()
if env:
build_system['env'] = env
return build_system
@@ -69,6 +107,12 @@ def create_sublime_build_system(self):
def shell_cmd(self):
return 'cmake --build .'
+ def cmd(self, target=None):
+ result = ["cmake", "--build", "."]
+ if target:
+ result.extend(["--target", target.name])
+ return result
+
def syntax(self):
return None
@@ -84,15 +128,19 @@ def get_cmake_key(self, key):
else:
return None
+
def get_generator_module_prefix():
return 'CMakeBuilder.generators.' + sublime.platform() + '.'
+
def get_module_name(generator):
return get_generator_module_prefix() + generator.replace(' ', '_')
+
def is_valid_generator(generator):
return get_module_name(generator) in sys.modules
+
def get_valid_generators():
module_prefix = get_generator_module_prefix()
valid_generators = []
@@ -101,46 +149,59 @@ def get_valid_generators():
valid_generators.append(key[len(module_prefix):].replace('_', ' '))
return valid_generators
+
def class_from_generator_string(generator_string):
if not generator_string:
- if sublime.platform() == 'linux':
+ if sublime.platform() == 'linux':
generator_string = 'Unix Makefiles'
elif sublime.platform() == 'osx':
generator_string = 'Unix Makefiles'
elif sublime.platform() == 'windows':
generator_string = 'Visual Studio'
else:
- sublime.error_message('Unknown sublime platform: {}'.format(sublime.platform()))
+ sublime.error_message('Unknown sublime platform: {}'
+ .format(sublime.platform()))
return
module_name = get_module_name(generator_string)
- if not module_name in sys.modules:
+ if module_name not in sys.modules:
valid_generators = get_valid_generators()
- sublime.error_message('CMakeBuilder: "%s" is not a valid generator. The valid generators for this platform are: %s' % (generator_string, ', '.join(valid_generators)))
+ sublime.error_message('CMakeBuilder: "%s" is not a valid generator. '
+ 'The valid generators for this platform are: %s'
+ % (generator_string, ', '.join(valid_generators))
+ )
return
GeneratorModule = sys.modules[module_name]
GeneratorClass = None
try:
- GeneratorClass = getattr(GeneratorModule, generator_string.replace(' ', '_'))
+ GeneratorClass = getattr(
+ GeneratorModule, generator_string.replace(' ', '_'))
except AttributeError as e:
sublime.error_message('Internal error: %s' % str(e))
return GeneratorClass
+
def _get_pyfiles_from_dir(dir):
for file in glob.iglob(dir + '/*.py'):
- if not os.path.isfile(file): continue
+ if not os.path.isfile(file):
+ continue
base = os.path.basename(file)
- if base.startswith('__'): continue
+ if base.startswith('__'):
+ continue
generator = base[:-3]
yield generator
+
def _import_all_platform_specific_generators():
path = os.path.join(os.path.dirname(__file__), sublime.platform())
return list(_get_pyfiles_from_dir(path))
+
def import_user_generators():
- path = os.path.join(sublime.packages_path(), 'User', 'generators', sublime.platform())
+ path = os.path.join(
+ sublime.packages_path(), 'User', 'generators', sublime.platform())
return list(_get_pyfiles_from_dir(path))
+
if sublime.platform() == 'linux':
from .linux import *
elif sublime.platform() == 'osx':
@@ -149,4 +210,3 @@ def import_user_generators():
from .windows import *
else:
sublime.error_message('Unknown platform: %s' % sublime.platform())
-
diff --git a/generators/linux/Ninja.py b/generators/linux/Ninja.py
index 7a3dd47..888a08c 100644
--- a/generators/linux/Ninja.py
+++ b/generators/linux/Ninja.py
@@ -1,6 +1,8 @@
from CMakeBuilder.generators import CMakeGenerator
import subprocess
import sublime
+import os
+
class Ninja(CMakeGenerator):
@@ -14,14 +16,10 @@ def syntax(self):
return 'Packages/CMakeBuilder/Syntax/Ninja.sublime-syntax'
def variants(self):
- env = None
- if self.window.active_view():
- env = self.window.active_view().settings().get('build_env')
-
shell_cmd = 'cmake --build . --target help'
proc = subprocess.Popen(
['/bin/bash', '-c', shell_cmd],
- env=env,
+ env=self.get_env(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
@@ -29,27 +27,26 @@ def variants(self):
outs, errs = proc.communicate()
errs = errs.decode('utf-8')
if errs:
- sublime.error_message(errs)
- return
+ print(errs) # terrible hack
lines = outs.decode('utf-8').splitlines()
-
+
EXCLUDES = [
'are some of the valid targets for this Makefile:',
- 'All primary targets available:',
+ 'All primary targets available:',
'depend',
'all (the default if no target is provided)',
- 'help',
- 'edit_cache',
+ 'help',
+ 'edit_cache',
'.ninja']
variants = []
for target in lines:
try:
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
target = target.rpartition(':')[0]
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
shell_cmd = 'cmake --build . --target {}'.format(target)
variants.append({'name': target, 'shell_cmd': shell_cmd})
diff --git a/generators/linux/Unix_Makefiles.py b/generators/linux/Unix_Makefiles.py
index e02f613..764192c 100644
--- a/generators/linux/Unix_Makefiles.py
+++ b/generators/linux/Unix_Makefiles.py
@@ -3,6 +3,7 @@
import sublime
import multiprocessing
+
class Unix_Makefiles(CMakeGenerator):
def __repr__(self):
@@ -18,14 +19,10 @@ def shell_cmd(self):
return 'make -j{}'.format(str(multiprocessing.cpu_count()))
def variants(self):
- env = None
- if self.window.active_view():
- env = self.window.active_view().settings().get('build_env')
-
shell_cmd = 'cmake --build . --target help'
proc = subprocess.Popen(
['/bin/bash', '-l', '-c', shell_cmd],
- env=env,
+ env=self.get_env(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
@@ -40,25 +37,25 @@ def variants(self):
variants = []
EXCLUDES = [
'are some of the valid targets for this Makefile:',
- 'All primary targets available:',
+ 'All primary targets available:',
'depend',
'all (the default if no target is provided)',
- 'help',
- 'edit_cache',
+ 'help',
+ 'edit_cache',
'.ninja']
-
+
for target in lines:
try:
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
target = target[4:]
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
- shell_cmd = 'make -j{} {}'.format(str(multiprocessing.cpu_count()), target)
+ shell_cmd = 'make -j{} {}'.format(
+ str(multiprocessing.cpu_count()), target)
variants.append({'name': target, 'shell_cmd': shell_cmd})
except Exception as e:
sublime.error_message(str(e))
# Continue anyway; we're in a for-loop
return variants
-
diff --git a/generators/linux/__init__.py b/generators/linux/__init__.py
index 776a33e..cd2b9ba 100644
--- a/generators/linux/__init__.py
+++ b/generators/linux/__init__.py
@@ -1 +1,3 @@
+from .Ninja import Ninja
+from .Unix_Makefiles import Unix_Makefiles
__all__ = ["Ninja", "Unix_Makefiles"]
diff --git a/generators/osx/Ninja.py b/generators/osx/Ninja.py
index 005ac41..4ee5de0 100644
--- a/generators/osx/Ninja.py
+++ b/generators/osx/Ninja.py
@@ -2,6 +2,7 @@
import subprocess
import sublime
+
class Ninja(CMakeGenerator):
def __repr__(self):
@@ -14,14 +15,10 @@ def syntax(self):
return 'Packages/CMakeBuilder/Syntax/Ninja.sublime-syntax'
def variants(self):
- env = None
- if self.window.active_view():
- env = self.window.active_view().settings().get('build_env')
-
shell_cmd = 'cmake --build . --target help'
proc = subprocess.Popen(
['/bin/bash', '-l', '-c', shell_cmd],
- env=env,
+ env=self.get_env(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
@@ -29,27 +26,26 @@ def variants(self):
outs, errs = proc.communicate()
errs = errs.decode('utf-8')
if errs:
- sublime.error_message(errs)
- return
+ print(errs) # terrible hack
lines = outs.decode('utf-8').splitlines()
-
+
EXCLUDES = [
'are some of the valid targets for this Makefile:',
- 'All primary targets available:',
+ 'All primary targets available:',
'depend',
'all (the default if no target is provided)',
- 'help',
- 'edit_cache',
+ 'help',
+ 'edit_cache',
'.ninja']
variants = []
for target in lines:
try:
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
target = target.rpartition(':')[0]
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
shell_cmd = 'cmake --build . --target {}'.format(target)
variants.append({'name': target, 'shell_cmd': shell_cmd})
@@ -63,5 +59,3 @@ def on_data(self, proc, data):
def on_finished(self, proc):
pass
-
-
diff --git a/generators/osx/Unix_Makefiles.py b/generators/osx/Unix_Makefiles.py
index e02f613..764192c 100644
--- a/generators/osx/Unix_Makefiles.py
+++ b/generators/osx/Unix_Makefiles.py
@@ -3,6 +3,7 @@
import sublime
import multiprocessing
+
class Unix_Makefiles(CMakeGenerator):
def __repr__(self):
@@ -18,14 +19,10 @@ def shell_cmd(self):
return 'make -j{}'.format(str(multiprocessing.cpu_count()))
def variants(self):
- env = None
- if self.window.active_view():
- env = self.window.active_view().settings().get('build_env')
-
shell_cmd = 'cmake --build . --target help'
proc = subprocess.Popen(
['/bin/bash', '-l', '-c', shell_cmd],
- env=env,
+ env=self.get_env(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
@@ -40,25 +37,25 @@ def variants(self):
variants = []
EXCLUDES = [
'are some of the valid targets for this Makefile:',
- 'All primary targets available:',
+ 'All primary targets available:',
'depend',
'all (the default if no target is provided)',
- 'help',
- 'edit_cache',
+ 'help',
+ 'edit_cache',
'.ninja']
-
+
for target in lines:
try:
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
target = target[4:]
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
- shell_cmd = 'make -j{} {}'.format(str(multiprocessing.cpu_count()), target)
+ shell_cmd = 'make -j{} {}'.format(
+ str(multiprocessing.cpu_count()), target)
variants.append({'name': target, 'shell_cmd': shell_cmd})
except Exception as e:
sublime.error_message(str(e))
# Continue anyway; we're in a for-loop
return variants
-
diff --git a/generators/osx/__init__.py b/generators/osx/__init__.py
index 776a33e..cd2b9ba 100644
--- a/generators/osx/__init__.py
+++ b/generators/osx/__init__.py
@@ -1 +1,3 @@
+from .Ninja import Ninja
+from .Unix_Makefiles import Unix_Makefiles
__all__ = ["Ninja", "Unix_Makefiles"]
diff --git a/generators/windows/NMake_Makefiles.py b/generators/windows/NMake_Makefiles.py
index 12f3755..0c64fde 100644
--- a/generators/windows/NMake_Makefiles.py
+++ b/generators/windows/NMake_Makefiles.py
@@ -4,16 +4,17 @@
import sublime
import subprocess
+
class NMake_Makefiles(CMakeGenerator):
def __repr__(self):
return 'NMake Makefiles'
- def env(self):
+ def get_env(self):
if self.visual_studio_versions:
vs_versions = self.visual_studio_versions
else:
- vs_versions = [ 15, 14.1, 14, 13, 12, 11, 10, 9, 8 ]
+ vs_versions = [15, 14.1, 14, 13, 12, 11, 10, 9, 8]
if self.target_architecture:
arch = self.target_architecture
else:
@@ -23,7 +24,8 @@ def env(self):
elif sublime.arch() == 'x64':
host = 'amd64'
else:
- sublime.error_message('Unknown Sublime architecture: %s' % sublime.arch())
+ sublime.error_message(
+ 'Unknown Sublime architecture: %s' % sublime.arch())
return
if arch != host:
arch = host + '_' + arch
@@ -52,31 +54,29 @@ def variants(self):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
lines = subprocess.check_output(
- 'cmake --build . --target help',
- cwd=self.build_folder,
+ 'cmake --build . --target help',
+ cwd=self.build_folder,
startupinfo=startupinfo).decode('utf-8').splitlines()
-
variants = []
EXCLUDES = [
'are some of the valid targets for this Makefile:',
- 'All primary targets available:',
+ 'All primary targets available:',
'depend',
'all (the default if no target is provided)',
- 'help',
- 'edit_cache',
- '.ninja',
+ 'help',
+ 'edit_cache',
+ '.ninja',
'.o',
'.i',
'.s']
-
for target in lines:
try:
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
target = target[4:]
name = target
- if (self.filter_targets and
- not any(f in name for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in name for f in self.filter_targets)):
continue
shell_cmd = 'cmake --build . --target {}'.format(target)
variants.append({'name': name, 'shell_cmd': shell_cmd})
@@ -84,4 +84,3 @@ def variants(self):
sublime.error_message(str(e))
# Continue anyway; we're in a for-loop
return variants
-
diff --git a/generators/windows/Ninja.py b/generators/windows/Ninja.py
index f0118dc..94d045a 100644
--- a/generators/windows/Ninja.py
+++ b/generators/windows/Ninja.py
@@ -4,16 +4,17 @@
import sublime
import subprocess
+
class Ninja(CMakeGenerator):
def __repr__(self):
return 'Ninja'
- def env(self):
+ def get_env(self):
if self.visual_studio_versions:
vs_versions = self.visual_studio_versions
else:
- vs_versions = [ 15, 14.1, 14, 13, 12, 11, 10, 9, 8 ]
+ vs_versions = [15, 14.1, 14, 13, 12, 11, 10, 9, 8]
if self.target_architecture:
arch = self.target_architecture
else:
@@ -23,7 +24,8 @@ def env(self):
elif sublime.arch() == 'x64':
host = 'amd64'
else:
- sublime.error_message('Unknown Sublime architecture: %s' % sublime.arch())
+ sublime.error_message(
+ 'Unknown Sublime architecture: %s' % sublime.arch())
return
if arch != host:
arch = host + '_' + arch
@@ -44,7 +46,7 @@ def syntax(self):
def file_regex(self):
return r'^(.+)\((\d+)\):() (.+)$'
-
+
def variants(self):
startupinfo = None
@@ -52,30 +54,29 @@ def variants(self):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
lines = subprocess.check_output(
- 'cmake --build . --target help',
- cwd=self.build_folder,
+ 'cmake --build . --target help',
+ cwd=self.build_folder,
startupinfo=startupinfo).decode('utf-8').splitlines()
variants = []
EXCLUDES = [
- 'All primary targets available:',
- 'help',
- 'edit_cache',
+ 'All primary targets available:',
+ 'help',
+ 'edit_cache',
'.ninja']
-
for target in lines:
try:
if len(target) == 0:
continue
- if any(exclude in target for exclude in EXCLUDES):
+ if any(exclude in target for exclude in EXCLUDES):
continue
if target.endswith(': phony'):
target = target[:-len(': phony')]
elif target.endswith(': CLEAN'):
target = target[:-len(': CLEAN')]
target = target.strip()
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
shell_cmd = 'cmake --build . --target {}'.format(target)
variants.append({'name': target, 'shell_cmd': shell_cmd})
diff --git a/generators/windows/Visual_Studio.py b/generators/windows/Visual_Studio.py
index 197f2f1..bf1dadf 100644
--- a/generators/windows/Visual_Studio.py
+++ b/generators/windows/Visual_Studio.py
@@ -1,10 +1,12 @@
from CMakeBuilder.generators import CMakeGenerator
from CMakeBuilder.generators.windows.support.vcvarsall import query_vcvarsall
+from CMakeBuilder.support.check_output import check_output
import os
import re
import subprocess
import sublime
+
class Visual_Studio(CMakeGenerator):
def __repr__(self):
@@ -12,9 +14,7 @@ def __repr__(self):
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- lines = subprocess.check_output(
- 'cmake --help',
- startupinfo=startupinfo).decode('utf-8').splitlines()
+ lines = check_output('cmake --help').splitlines()
years = {}
for line in lines:
print(line)
@@ -71,13 +71,15 @@ def variants(self):
target = file
else:
target = relative + '/' + file
- if (self.filter_targets and
- not any(f in target for f in self.filter_targets)):
+ if (self.filter_targets and
+ not any(f in target for f in self.filter_targets)):
continue
if configs:
for config in configs:
- shell_cmd = 'cmake --build . --target {} --config {}'.format(target, config)
- variants.append({'name': target + ' [' + config + ']',
+ shell_cmd = 'cmake --build . --target {} --config {}'\
+ .format(target, config)
+ variants.append({
+ 'name': target + ' [' + config + ']',
'shell_cmd': shell_cmd})
else:
shell_cmd = 'cmake --build . --target {}'.format(target)
@@ -88,13 +90,14 @@ def syntax(self):
return 'Packages/CMakeBuilder/Syntax/Visual_Studio.sublime-syntax'
def file_regex(self):
- return r'^ (.+)\((\d+)\)(): ((?:fatal )?(?:error|warning) \w+\d\d\d\d: .*) \[.*$'
+ return (r'^ (.+)\((\d+)\)(): ((?:fatal )?(?:error|warning) ',
+ r'\w+\d\d\d\d: .*) \[.*$')
- def env(self):
+ def get_env(self):
if self.visual_studio_versions:
vs_versions = self.visual_studio_versions
else:
- vs_versions = [ 15, 14.1, 14, 13, 12, 11, 10, 9, 8 ]
+ vs_versions = [15, 14.1, 14, 13, 12, 11, 10, 9, 8]
if self.target_architecture:
arch = self.target_architecture
else:
@@ -104,7 +107,8 @@ def env(self):
elif sublime.arch() == 'x64':
host = 'amd64'
else:
- sublime.error_message('Unknown Sublime architecture: %s' % sublime.arch())
+ sublime.error_message(
+ 'Unknown Sublime architecture: %s' % sublime.arch())
return
if arch != host:
arch = host + '_' + arch
diff --git a/generators/windows/__init__.py b/generators/windows/__init__.py
index d84832d..e25361b 100644
--- a/generators/windows/__init__.py
+++ b/generators/windows/__init__.py
@@ -1 +1,4 @@
+from .Ninja import Ninja
+from .NMake_Makefiles import NMake_Makefiles
+from .Visual_Studio import Visual_Studio
__all__ = ["Ninja", "NMake_Makefiles", "Visual_Studio"]
diff --git a/messages/2.0.0-alpha.txt b/messages/2.0.0-alpha.txt
new file mode 100644
index 0000000..bb7d903
--- /dev/null
+++ b/messages/2.0.0-alpha.txt
@@ -0,0 +1 @@
+- Add cmake experimental server functionality for cmake >= 3.7
diff --git a/support/__init__.py b/support/__init__.py
index f9b743b..54e9340 100644
--- a/support/__init__.py
+++ b/support/__init__.py
@@ -1,4 +1,13 @@
-from .check_output import *
-from .expand_variables import *
-from .get_cmake_value import *
-from .get_setting import *
+from .check_output import check_output
+from .expand_variables import expand_variables
+from .get_cmake_value import get_cmake_value
+from .get_setting import get_setting
+from .capabilities import capabilities
+
+
+__all__ = [
+ "check_output",
+ "expand_variables",
+ "get_cmake_value",
+ "get_setting",
+ "capabilities"]
diff --git a/support/capabilities.py b/support/capabilities.py
new file mode 100644
index 0000000..7644cc4
--- /dev/null
+++ b/support/capabilities.py
@@ -0,0 +1,14 @@
+from .check_output import check_output
+import json
+
+_capabilities = None
+
+def capabilities(key):
+ global _capabilities
+ if _capabilities is None:
+ try:
+ _capabilities = json.loads(check_output("cmake -E capabilities"))
+ except Exception as e:
+ print("CMakeBuilder: Error: Could not load cmake's capabilities")
+ _capabilities = {"error": None}
+ return _capabilities.get(key, None)
diff --git a/support/check_output.py b/support/check_output.py
index 0b0d4b9..d1985be 100644
--- a/support/check_output.py
+++ b/support/check_output.py
@@ -1,4 +1,7 @@
-import sublime, subprocess, os
+import sublime
+import subprocess
+import os
+
class CheckOutputException(Exception):
"""Gets raised when there's a non-empty error stream."""
@@ -6,6 +9,7 @@ def __init__(self, errs):
super(CheckOutputException, self).__init__()
self.errs = errs
+
def check_output(shell_cmd, env=None, cwd=None):
if sublime.platform() == "linux":
cmd = ["/bin/bash", "-c", shell_cmd]
@@ -15,7 +19,7 @@ def check_output(shell_cmd, env=None, cwd=None):
cmd = ["/bin/bash", "-l", "-c", shell_cmd]
startupinfo = None
shell = False
- else: # sublime.platform() == "windows"
+ else: # sublime.platform() == "windows"
cmd = shell_cmd
if os.name == "nt":
startupinfo = subprocess.STARTUPINFO()
diff --git a/support/db/__init__.py b/support/db/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/support/db/json.py b/support/db/json.py
new file mode 100644
index 0000000..185a206
--- /dev/null
+++ b/support/db/json.py
@@ -0,0 +1,121 @@
+import json
+import os
+import re
+import shlex
+
+from ..models import (CompileCommand, CompilationDatabaseInterface)
+
+
+class JSONCompilationDatabase(CompilationDatabaseInterface):
+ def __init__(self, json_db_path):
+ self.json_db_path = json_db_path
+
+ @classmethod
+ def probe_directory(cls, directory):
+ """Automatically create a CompilationDatabase from build directory."""
+ db_path = os.path.join(directory, 'compile_commands.json')
+ if os.path.exists(db_path):
+ return cls(db_path)
+ return super(JSONCompilationDatabase, cls).probe_directory(directory)
+
+ def get_compile_commands(self, filepath):
+ filepath = os.path.abspath(filepath)
+ for elem in self._data:
+ if os.path.abspath(
+ os.path.join(elem['directory'], elem['file'])) == filepath:
+ yield self._dict_to_compile_command(elem)
+
+ def get_all_files(self):
+ for entry in self._data:
+ yield os.path.normpath(
+ os.path.join(entry['directory'], entry['file']))
+
+ def get_all_compile_commands(self):
+ # PERFORMANCE: I think shlex is inherently slow,
+ # something performing better may be necessary
+ return map(self._dict_to_compile_command, self._data)
+
+ @staticmethod
+ def _dict_to_compile_command(d):
+ command = d['command']
+ if isinstance(command, str):
+ return CompileCommand(d['directory'], d['file'], shlex.split(command))
+ elif isinstance(command, list):
+ return CompileCommand(d['directory'], d['file'], command)
+ else:
+ raise Exception("Unknown type: {}".format(command.__class__))
+
+ @property
+ def _data(self):
+ if not hasattr(self, '__data'):
+ with open(self.json_db_path) as f:
+ self.__data = json.load(f)
+ return self.__data
+
+
+def command_to_json(commands):
+ cmd_line = '"'
+ for i, command in enumerate(commands):
+ if i != 0:
+ cmd_line += ' '
+ has_space = re.search(r"\s", command) is not None
+ # reader now accepts simple quotes, so we need to support them here too
+ has_simple_quote = "'" in command
+ need_quoting = has_space or has_simple_quote
+ if need_quoting:
+ cmd_line += r'\"'
+ cmd_line += command.replace("\\", r'\\\\').replace(r'"', r'\\\"')
+ if need_quoting:
+ cmd_line += r'\"'
+ return cmd_line + '"'
+
+
+def str_to_json(s):
+ return '"{}"'.format(s.replace("\\", "\\\\").replace('"', r'\"'))
+
+
+def compile_command_to_json(compile_command):
+ return r'''{{
+ "directory": {},
+ "command": {},
+ "file": {}
+}}'''.format(
+ str_to_json(compile_command.directory),
+ command_to_json(compile_command.command),
+ str_to_json(compile_command.file))
+
+
+class JSONCompileCommandSerializer(object):
+ def __init__(self, fp):
+ self.fp = fp
+ self.__count = 0
+
+ def __enter__(self):
+ self.fp.write('[\n')
+ return self
+
+ def serialize(self, compile_command):
+ if self.__count != 0:
+ self.fp.write(',\n\n')
+ self.fp.write(compile_command_to_json(compile_command))
+ self.__count += 1
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if self.__count != 0:
+ self.fp.write('\n')
+ self.fp.write(']\n')
+
+
+def compile_commands_to_json(compile_commands, fp):
+ """
+ Dump Json.
+
+ Parameters
+ ----------
+ compile_commands : CompileCommand iterable
+ fp
+ A file-like object, JSON is written to this element.
+ """
+ with JSONCompileCommandSerializer(fp) as serializer:
+ for compile_command in compile_commands:
+ serializer.serialize(compile_command)
diff --git a/support/db/memory.py b/support/db/memory.py
new file mode 100644
index 0000000..5af0390
--- /dev/null
+++ b/support/db/memory.py
@@ -0,0 +1,23 @@
+import os
+
+from ..models import CompilationDatabaseInterface
+
+
+class InMemoryCompilationDatabase(CompilationDatabaseInterface):
+ def __init__(self, compile_commands=None):
+ if compile_commands is None:
+ self.compile_commands = []
+ else:
+ self.compile_commands = compile_commands
+
+ def get_compile_commands(self, filepath):
+ filepath = os.path.abspath(filepath)
+ for compile_command in self.compile_commands:
+ if compile_command.normfile == filepath:
+ yield compile_command
+
+ def get_all_files(self):
+ return (c.normfile for c in self.compile_commands)
+
+ def get_all_compile_commands(self):
+ return iter(self.compile_commands)
diff --git a/support/expand_variables.py b/support/expand_variables.py
index e480c80..a0d324f 100644
--- a/support/expand_variables.py
+++ b/support/expand_variables.py
@@ -1,5 +1,6 @@
import string
+
def expand_variables(the_dict, the_vars):
if not the_dict:
return
diff --git a/support/get_cmake_value.py b/support/get_cmake_value.py
index 03d575d..ec83498 100644
--- a/support/get_cmake_value.py
+++ b/support/get_cmake_value.py
@@ -1,5 +1,6 @@
import sublime
+
def get_cmake_value(the_dict, key):
if not the_dict:
return None
diff --git a/support/get_setting.py b/support/get_setting.py
index 113f63c..126d4bb 100644
--- a/support/get_setting.py
+++ b/support/get_setting.py
@@ -1,5 +1,6 @@
import sublime
+
def get_setting(view, key, default=None):
if view:
settings = view.settings()
diff --git a/support/headerdb.py b/support/headerdb.py
new file mode 100644
index 0000000..fecded4
--- /dev/null
+++ b/support/headerdb.py
@@ -0,0 +1,280 @@
+import os
+import re
+
+from .db.memory import InMemoryCompilationDatabase
+from .db.json import JSONCompilationDatabase
+
+
+def sanitize_compile_options(compile_command):
+ filename = os.path.splitext(compile_command.file)[1]
+ file_norm = compile_command.normfile
+ adjusted = []
+ i = 0
+ command = compile_command.command
+ while i < len(command):
+ # end of options, skip all positional arguments (source files)
+ if command[i] == "--":
+ break
+ # strip -c
+ if command[i] == "-c":
+ i += 1
+ continue
+ # strip -o and -o
+ if command[i].startswith("-o"):
+ if command[i] == "-o":
+ i += 2
+ else:
+ i += 1
+ continue
+ # skip input file
+ if command[i].endswith(filename):
+ arg_norm = os.path.normpath(
+ os.path.join(compile_command.directory, command[i]))
+ if file_norm == arg_norm:
+ i += 1
+ continue
+ adjusted.append(command[i])
+ i += 1
+ return adjusted
+
+
+def mimic_path_relativity(path, other, default_dir):
+ """If 'other' file is relative, make 'path' relative, otherwise make it
+ absolute.
+
+ """
+ if os.path.isabs(other):
+ return os.path.join(default_dir, path)
+ if os.path.isabs(path):
+ return os.path.relpath(path, default_dir)
+ return path
+
+
+def derive_compile_command(header_file, reference):
+ return {
+ "directory":
+ reference.directory,
+ "file":
+ mimic_path_relativity(header_file, reference.file,
+ reference.directory),
+ "command":
+ sanitize_compile_options(reference)
+ }
+
+
+def get_file_includes(path):
+ """Returns a tuple of (quote, filename).
+
+ Quote is one of double quote mark '\"' or opening angle bracket '<'.
+ """
+ includes = []
+ with open(path, "rb") as istream:
+ include_pattern = re.compile(
+ br'\s*#\s*include\s+(?P["<])(?P.+?)[">]')
+ for b_line in istream:
+ b_match = re.match(include_pattern, b_line)
+ if b_match:
+ u_quote = b_match.group('quote').decode('ascii')
+ try:
+ u_filename = b_match.group('filename').decode('utf-8')
+ except UnicodeDecodeError:
+ u_filename = b_match.group('filename').decode('latin-1')
+ includes.append((u_quote, u_filename))
+ return includes
+
+
+def extract_include_dirs(compile_command):
+ header_search_path = []
+ i = 0
+ command = sanitize_compile_options(compile_command)
+ while i < len(command):
+ # -I and -I
+ if command[i].startswith("-I"):
+ if command[i] == "-I":
+ i += 1
+ header_search_path.append(command[i])
+ else:
+ header_search_path.append(command[i][2:])
+ i += 1
+ return [
+ os.path.join(compile_command.directory, p) for p in header_search_path
+ ]
+
+
+def get_implicit_header_search_path(compile_command):
+ return os.path.dirname(
+ os.path.join(compile_command.directory, compile_command.file))
+
+
+SUBWORD_SEPARATORS_RE = re.compile("[^A-Za-z0-9]")
+
+# The comment is shitty because I don't fully understand what is going on.
+# Shamelessly stolen, then modified from:
+# - http://stackoverflow.com/a/29920015/951426
+SUBWORD_CAMEL_SPLIT_RE = re.compile(r"""
+.+? # capture text instead of discarding (#1)
+(
+ (?:(?<=[a-z0-9])) # non-capturing positive lookbehind assertion
+ (?=[A-Z]) # match first uppercase letter without consuming
+|
+ (?<=[A-Z]) # an upper char should prefix
+ (?=[A-Z][a-z0-9]) # an upper char, lookahead assertion: does not
+ # consume the char
+|
+$ # ignore capture text #1
+)""", re.VERBOSE)
+
+
+def subword_split(name):
+ """Split name into subword.
+
+ Split camelCase, lowercase_underscore, and alike into an array of word.
+
+ Subword is the vocabulary stolen from Emacs subword-mode:
+ https://www.gnu.org/software/emacs/manual/html_node/ccmode/Subword-Movement.html
+
+ """
+ words = []
+ for camel_subname in re.split(SUBWORD_SEPARATORS_RE, name):
+ matches = re.finditer(SUBWORD_CAMEL_SPLIT_RE, camel_subname)
+ words.extend([m.group(0) for m in matches])
+ return words
+
+
+# Code shamelessly stolen from: http://stackoverflow.com/a/24547864/951426
+def lcsubstring_length(a, b):
+ """Find the length of the longuest contiguous subsequence of subwords.
+
+ The name is a bit of a misnomer.
+
+ """
+ table = {}
+ k = 0
+ for i, ca in enumerate(a, 1):
+ for j, cb in enumerate(b, 1):
+ if ca == cb:
+ table[i, j] = table.get((i - 1, j - 1), 0) + 1
+ if table[i, j] > k:
+ k = table[i, j]
+ return k
+
+
+def score_other_file(a, b):
+ """Score the similarity of the given file to the other file.
+
+ Paths are expected absolute and normalized.
+ """
+ a_dir, a_filename = os.path.split(os.path.splitext(a)[0])
+ a_subwords = subword_split(a_filename)
+ b_dir, b_filename = os.path.split(os.path.splitext(b)[0])
+ b_subwords = subword_split(b_filename)
+
+ score = 0
+
+ # score subword
+ # if a.cpp and b.cpp includes a_private.hpp, a.cpp should score better
+ subseq_length = lcsubstring_length(a_subwords, b_subwords)
+ score += 10 * subseq_length
+ # We also penalize the length of the mismatch
+ #
+ # For example:
+ # include/String.hpp
+ # include/SmallString.hpp
+ # test/StringTest.cpp
+ # test/SmallStringTest.cpp
+ #
+ # Here we prefer String.hpp to get the compile options of StringTest over
+ # the one of SmallStringTest.
+ score -= 10 * (len(a_subwords) + len(b_subwords) - 2 * subseq_length)
+
+ if a_dir == b_dir:
+ score += 50
+
+ return score
+
+
+class _Data(object):
+ __slots__ = ['score', 'compile_command', 'db_idx']
+
+ def __init__(self, score=0, compile_command=None, db_idx=-1):
+ self.score = score
+ if compile_command is None:
+ self.compile_command = {}
+ else:
+ self.compile_command = compile_command
+ self.db_idx = db_idx
+
+
+def _make_headerdb1(compile_commands_iter, db_files, db_idx, header_mapping):
+ for compile_command in compile_commands_iter:
+ if isinstance(compile_command, dict):
+ compile_command = JSONCompilationDatabase._dict_to_compile_command(
+ compile_command)
+ implicit_search_path = get_implicit_header_search_path(compile_command)
+ header_search_paths = extract_include_dirs(compile_command)
+ src_file = compile_command.normfile
+ for quote, filename in get_file_includes(src_file):
+ header_abspath = None
+ score = 0
+ if quote == '"':
+ candidate = os.path.normpath(
+ os.path.join(implicit_search_path, filename))
+ if os.path.isfile(candidate):
+ header_abspath = candidate
+ if not header_abspath:
+ for search_path in header_search_paths:
+ candidate = os.path.normpath(
+ os.path.join(search_path, filename))
+ if os.path.isfile(candidate):
+ header_abspath = candidate
+ break
+ else:
+ continue
+ norm_abspath = os.path.normpath(header_abspath)
+ # skip files already present in the database
+ if norm_abspath in db_files:
+ continue
+ score = score_other_file(src_file, norm_abspath)
+ try:
+ data = header_mapping[norm_abspath]
+ except KeyError:
+ data = _Data(score=(score - 1))
+ header_mapping[norm_abspath] = data
+ if score > data.score:
+ data.score = score
+ data.compile_command = derive_compile_command(
+ norm_abspath, compile_command)
+ data.db_idx = db_idx
+
+
+def make_headerdb(layers):
+ databases_len = len(layers[0])
+ complementary_databases = [
+ InMemoryCompilationDatabase() for _ in range(databases_len)
+ ]
+
+ db_files = set()
+ for layer in layers:
+ for database in layer:
+ db_files.update(database.get_all_files())
+
+ # loop until there is nothing more to resolve
+ # we first get the files directly included by the compilation database
+ # then the files directly included by these files and so on
+ while True:
+ # mapping of -> _Data
+ db_update = {}
+ for layer in layers:
+ for db_idx, database in enumerate(layer):
+ _make_headerdb1(database.get_all_compile_commands(), db_files,
+ db_idx, db_update)
+ if not db_update:
+ break
+ layers = [[
+ InMemoryCompilationDatabase() for _ in range(databases_len)
+ ]]
+ for k, v in db_update.items():
+ db_files.add(k)
+ for db_list in (layers[0], complementary_databases):
+ db_list[v.db_idx].compile_commands.append(v.compile_command)
+ return complementary_databases
diff --git a/support/models.py b/support/models.py
new file mode 100644
index 0000000..b94802f
--- /dev/null
+++ b/support/models.py
@@ -0,0 +1,71 @@
+import os
+import json
+
+
+class CompileCommand(object):
+
+ __slots__ = ("directory", "file", "command")
+
+ def __init__(self, directory, file, command):
+ self.directory = directory
+ self.file = file
+ self.command = command
+
+ @property
+ def normfile(self):
+ return os.path.normpath(os.path.join(self.directory, self.file))
+
+ def __repr__(self):
+ return '{{directory:"{}",file:"{}",command:"{}"}}'.format(
+ self.directory, self.file, self.command)
+
+ def __str__(self):
+ return self.__repr__()
+
+ def _as_tuple(self):
+ return (self.directory, self.file, self.command)
+
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self._as_tuple() == other._as_tuple()
+ raise NotImplemented()
+
+ def __ne__(self, other):
+ return not self == other
+
+ class JSONEncoder(json.JSONEncoder):
+ def default(self, o):
+ if isinstance(o, CompileCommand):
+ return o.__repr__()
+ else:
+ return super().default(o)
+
+
+class CompilationDatabaseInterface(object):
+ @classmethod
+ def probe_directory(cls, directory):
+ """Probe compilation database for a specific directory.
+
+ Should return an instance of the compilation database
+ if the directory contains a database.
+ If the directory does not contain a database,
+ a FileNotFoundError should be raised (the default action if not
+ overriden).
+ """
+ raise FileNotFoundError(
+ "{}: compilation databases not found".format(directory))
+
+ def get_compile_commands(self, filepath):
+ """Get the compile commands for the given file.
+
+ Return an iterable of CompileCommand.
+ """
+ raise NotImplemented()
+
+ def get_all_files(self):
+ """Return an iterable of path strings."""
+ raise NotImplemented()
+
+ def get_all_compile_commands(self):
+ """Return an iterable of CompileCommand."""
+ raise NotImplemented()
diff --git a/support/server.py b/support/server.py
new file mode 100644
index 0000000..0e713c0
--- /dev/null
+++ b/support/server.py
@@ -0,0 +1,443 @@
+import Default.exec
+import json
+import sublime
+import copy
+import time
+import os
+import threading
+import shutil
+from .headerdb import make_headerdb
+from .db.json import JSONCompilationDatabase
+from .models import CompileCommand
+
+
+class Target(object):
+
+ __slots__ = ("name", "fullname", "type", "directory", "config")
+
+ def __init__(self, name, fullname, type, directory, config):
+ self.name = name
+ self.fullname = fullname
+ self.type = type
+ self.directory = directory
+ self.config = config
+
+ def __hash__(self):
+ return hash(self.name)
+
+ def cmd(self):
+ result = ["cmake", "--build", "."]
+ if self.type == "ALL":
+ return result
+ result.extend(["--target", self.name])
+ if self.config:
+ result.extend["--config", self.config]
+ return result
+
+
+class Server(Default.exec.ProcessListener):
+ def __init__(self,
+ window,
+ cmake_settings,
+ experimental=True,
+ debug=True,
+ protocol=(1, 0),
+ env={}):
+ self.window = window
+ self.cmake = cmake_settings
+ self.experimental = experimental
+ self.protocol = protocol
+ self.supported_protocols = None
+ self.is_configuring = False
+ self.is_building = False # maintained by CmakeBuildCommand
+ self.data_parts = ''
+ self.inside_json_object = False
+ self.include_paths = set()
+ self.targets = None
+ cmd = ["cmake", "-E", "server"]
+ if experimental:
+ cmd.append("--experimental")
+ if debug:
+ cmd.append("--debug")
+ self.proc = Default.exec.AsyncProcess(
+ cmd=cmd, shell_cmd=None, listener=self, env=env)
+
+ def __del__(self):
+ if self.proc:
+ self.proc.kill()
+
+ _BEGIN_TOKEN = '[== "CMake Server" ==['
+ _END_TOKEN = ']== "CMake Server" ==]'
+
+ def on_data(self, _, data):
+ data = data.decode("utf-8").strip()
+ if data.startswith("CMake Error:"):
+ sublime.error_message(data)
+ return
+
+ while data:
+ if self.inside_json_object:
+ end_index = data.find(self.__class__._END_TOKEN)
+ if end_index == -1:
+ # This is okay, wait for more data.
+ self.data_parts += data
+ else:
+ self.data_parts += data[0:end_index]
+ data = data[end_index + len(self.__class__._END_TOKEN):]
+ self.__flush_the_data()
+ else: # not inside json object
+ begin_index = data.find(self.__class__._BEGIN_TOKEN)
+ if begin_index == -1:
+ sublime.error_message("Received unknown data part: " +
+ data)
+ data = None
+ else:
+ begin_token_end = begin_index + len(
+ self.__class__._BEGIN_TOKEN)
+ end_index = data.find(self.__class__._END_TOKEN,
+ begin_token_end)
+ if end_index == -1:
+ # This is okay, wait for more data.
+ self.data_parts += data[begin_token_end:]
+ data = None
+ self.inside_json_object = True
+ else:
+ self.data_parts += data[begin_token_end:end_index]
+ data = data[
+ end_index + len(self.__class__._END_TOKEN):]
+ self.__flush_the_data()
+
+ def __flush_the_data(self):
+ d = json.loads(self.data_parts)
+ self.data_parts = ""
+ self.inside_json_object = False
+ self.receive_dict(d)
+
+ def on_finished(self, _):
+ self.window.status_message("CMake Server has quit (exit code {})"
+ .format(self.proc.exit_code()))
+
+ def send(self, data):
+ while not hasattr(self, "proc"):
+ time.sleep(0.01) # terrible hack :(
+ self.proc.proc.stdin.write(data)
+ self.proc.proc.stdin.flush()
+
+ def send_dict(self, thedict):
+ data = b'\n[== "CMake Server" ==[\n'
+ data += json.dumps(thedict).encode('utf-8') + b'\n'
+ data += b'\n]== "CMake Server" ==]\n'
+ self.send(data)
+
+ def send_handshake(self):
+ self.protocol = {"major": 1, "minor": 0, "isExperimental": True}
+ self.send_dict({
+ "type": "handshake",
+ "protocolVersion": self.protocol,
+ "sourceDirectory": self.cmake.source_folder,
+ "buildDirectory": self.cmake.build_folder,
+ "generator": self.cmake.generator,
+ "platform": self.cmake.platform,
+ "toolset": self.cmake.toolset
+ })
+
+ def set_global_setting(self, key, value):
+ self.send_dict({"type": "setGlobalSettings", key: value})
+
+ def configure(self, cache_arguments={}):
+ if self.is_configuring:
+ return
+ self.is_configuring = True
+ self.bad_configure = False
+ window = self.window
+ view = window.create_output_panel("cmake.configure", True)
+ view.settings().set("result_file_regex", r'CMake\s(?:Error|Warning)'
+ r'(?:\s\(dev\))?\sat\s(.+):(\d+)()\s?\(?(\w*)\)?:')
+ view.settings().set("result_base_dir", self.cmake.source_folder)
+ view.set_syntax_file(
+ "Packages/CMakeBuilder/Syntax/Configure.sublime-syntax")
+ settings = sublime.load_settings("CMakeBuilder.sublime-settings")
+ if settings.get("server_configure_verbose", False):
+ window.run_command("show_panel",
+ {"panel": "output.cmake.configure"})
+ overrides = copy.deepcopy(self.cmake.command_line_overrides)
+ overrides.update(cache_arguments)
+ ovr = []
+ for key, value in overrides.items():
+ if type(value) is bool:
+ value = "ON" if value else "OFF"
+ ovr.append("-D{}={}".format(key, value))
+ self.send_dict({"type": "configure", "cacheArguments": ovr})
+
+ def compute(self):
+ self.send_dict({"type": "compute"})
+
+ def codemodel(self):
+ self.send_dict({"type": "codemodel"})
+
+ def cache(self):
+ self.send_dict({"type": "cache"})
+
+ def file_system_watchers(self):
+ self.send_dict({"type": "fileSystemWatchers"})
+
+ def cmake_inputs(self):
+ self.send_dict({"type": "cmakeInputs"})
+
+ def global_settings(self):
+ self.send_dict({"type": "globalSettings"})
+
+ def receive_dict(self, thedict):
+ t = thedict.pop("type")
+ if t == "hello":
+ self.supported_protocols = thedict.pop("supportedProtocolVersions")
+ self.send_handshake()
+ elif t == "reply":
+ self.receive_reply(thedict)
+ elif t == "error":
+ self.receive_error(thedict)
+ elif t == "progress":
+ self.receive_progress(thedict)
+ elif t == "message":
+ self.receive_message(thedict)
+ elif t == "signal":
+ self.receive_signal(thedict)
+ else:
+ print('CMakeBuilder: Received unknown type "{}"'.format(t))
+ print(thedict)
+
+ def receive_reply(self, thedict):
+ reply = thedict["inReplyTo"]
+ if reply == "handshake":
+ self.window.status_message(
+ "CMake server protocol {}.{}, handshake is OK"
+ .format(self.protocol["major"], self.protocol["minor"]))
+ self.configure()
+ elif reply == "setGlobalSettings":
+ self.window.status_message("Global CMake setting is modified")
+ elif reply == "configure":
+ if self.bad_configure:
+ self.is_configuring = False
+ self.window.status_message(
+ "Some errors occured during configure!")
+ else:
+ self.window.status_message("Project is configured")
+ elif reply == "compute":
+ self.window.status_message("Project is generated")
+ self.is_configuring = False
+ self.codemodel()
+ elif reply == "fileSystemWatchers":
+ self.dump_to_new_view(thedict, "File System Watchers")
+ elif reply == "cmakeInputs":
+ self.dump_to_new_view(thedict, "CMake Inputs")
+ elif reply == "globalSettings":
+ # thedict.pop("inReplyTo")
+ thedict.pop("cookie")
+ thedict.pop("capabilities")
+ self.items = []
+ self.types = []
+ for k, v in thedict.items():
+ if type(v) in (dict, list):
+ continue
+ self.items.append([str(k), str(v)])
+ self.types.append(type(v))
+ window = self.window
+
+ def on_done(index):
+ if index == -1:
+ return
+ key = self.items[index][0]
+ old_value = self.items[index][1]
+ value_type = self.types[index]
+
+ def on_done_input(new_value):
+ if value_type is bool:
+ new_value = bool(new_value)
+ self.set_global_setting(key, new_value)
+
+ window.show_input_panel('new value for "' + key + '": ',
+ old_value, on_done_input, None, None)
+
+ window.show_quick_panel(self.items, on_done)
+ elif reply == "codemodel":
+ configurations = thedict.pop("configurations")
+ self.include_paths = set()
+ self.targets = set()
+ for config in configurations:
+ # name = config.pop("name")
+ projects = config.pop("projects")
+ for project in projects:
+ targets = project.pop("targets")
+ for target in targets:
+ target_type = target.pop("type")
+ target_name = target.pop("name")
+ try:
+ target_fullname = target.pop("fullName")
+ except KeyError as e:
+ target_fullname = target_name
+ target_dir = target.pop("buildDirectory")
+ self.targets.add(
+ Target(target_name, target_fullname, target_type,
+ target_dir, ""))
+ if target_type == "EXECUTABLE":
+ self.targets.add(
+ Target("Run: " + target_name, target_fullname,
+ "RUN", target_dir, ""))
+ file_groups = target.pop("fileGroups", [])
+ for file_group in file_groups:
+ include_paths = file_group.pop("includePath", [])
+ for include_path in include_paths:
+ path = include_path.pop("path", None)
+ if path:
+ self.include_paths.add(path)
+ self.targets.add(
+ Target("BUILD ALL", "BUILD ALL", "ALL",
+ self.cmake.build_folder, ""))
+ self.targets = list(self.targets)
+ path = os.path.join(self.cmake.build_folder,
+ "compile_commands.json")
+ if os.path.isfile(path):
+ self.handle_compdb()
+ elif reply == "cache":
+ cache = thedict.pop("cache")
+ self.items = []
+ for item in cache:
+ t = item["type"]
+ if t in ("INTERNAL", "STATIC"):
+ continue
+ try:
+ docstring = item["properties"]["HELPSTRING"]
+ except Exception as e:
+ docstring = ""
+ key = item["key"]
+ value = item["value"]
+ self.items.append(
+ [key + " [" + t.lower() + "]", value, docstring])
+
+ def on_done(index):
+ if index == -1:
+ return
+ item = self.items[index]
+ key = item[0].split(" ")[0]
+ old_value = item[1]
+
+ def on_done_input(new_value):
+ self.configure({key: value})
+
+ self.window.show_input_panel('new value for "' + key + '": ',
+ old_value, on_done_input, None,
+ None)
+
+ self.window.show_quick_panel(self.items, on_done)
+ else:
+ print("received unknown reply type:", reply)
+
+ def handle_compdb(self):
+ db = JSONCompilationDatabase.probe_directory(self.cmake.build_folder)
+ headerdb = make_headerdb([[db]])[0]
+ db = list(db._data)
+ db.extend(headerdb.get_all_compile_commands())
+ path = os.path.join(self.cmake.build_folder, "compile_commands.json")
+ with open(path, "w") as f:
+ json.dump(
+ db,
+ f,
+ check_circular=False,
+ indent=2,
+ cls=CompileCommand.JSONEncoder)
+ data = self.window.project_data()
+ settings = sublime.load_settings("CMakeBuilder.sublime-settings")
+ setting = "auto_update_EasyClangComplete_compile_commands_location"
+ if settings.get(setting, False):
+ data["settings"]["ecc_flags_sources"] = [{
+ "file":
+ "compile_commands.json",
+ "search_in":
+ self.cmake.build_folder_pre_expansion
+ }]
+ setting = "auto_update_compile_commands_project_setting"
+ if settings.get(setting, False):
+ data["settings"]["compile_commands"] = \
+ self.cmake.build_folder_pre_expansion
+ setting = "copy_compile_commands_to_project_path"
+ if settings.get(setting, False):
+ destination = os.path.join(self.cmake.source_folder,
+ "compile_commands.json")
+ shutil.copyfile(path, destination)
+ self.window.set_project_data(data)
+
+ def receive_error(self, thedict):
+ reply = thedict["inReplyTo"]
+ msg = thedict["errorMessage"]
+ if reply in ("configure", "compute"):
+ self.window.status_message(msg)
+ if self.is_configuring:
+ self.is_configuring = False
+ else:
+ sublime.error_message("{} (in reply to {})".format(msg, reply))
+
+ def receive_progress(self, thedict):
+ view = self.window.active_view()
+ minimum = thedict["progressMinimum"]
+ maximum = thedict["progressMaximum"]
+ current = thedict["progressCurrent"]
+ if maximum == current:
+ view.erase_status("cmake_" + thedict["inReplyTo"])
+ if thedict["inReplyTo"] == "configure" and not self.bad_configure:
+ self.compute()
+ else:
+ status = "{0} {1:.0f}%".format(
+ thedict["progressMessage"],
+ 100.0 * (float(current) / float(maximum - minimum)))
+ view.set_status("cmake_" + thedict["inReplyTo"], status)
+
+ def receive_message(self, thedict):
+ window = self.window
+ if thedict["inReplyTo"] in ("configure", "compute"):
+ name = "cmake.configure"
+ else:
+ name = "cmake." + thedict["inReplyTo"]
+ view = window.find_output_panel(name)
+ assert view
+ settings = sublime.load_settings("CMakeBuilder.sublime-settings")
+ if settings.get("server_configure_verbose", False):
+ window.run_command("show_panel",
+ {"panel": "output.{}".format(name)})
+ view.run_command("append", {
+ "characters": thedict["message"] + "\n",
+ "force": True,
+ "scroll_to_end": True
+ })
+ self._check_for_errors_in_configure(view)
+
+ _signal_lock = threading.Lock()
+
+ def receive_signal(self, thedict):
+ with self.__class__._signal_lock:
+ if (thedict["name"] == "dirty" and not self.is_configuring
+ and not self.is_building):
+ self.configure()
+ else:
+ print("received signal")
+ print(thedict)
+
+ def dump_to_new_view(self, thedict, name):
+ view = self.window.new_file()
+ view.set_scratch(True)
+ view.set_name(name)
+ thedict.pop("inReplyTo")
+ thedict.pop("cookie")
+ view.run_command("append", {
+ "characters": json.dumps(thedict, indent=2),
+ "force": True
+ })
+ view.set_read_only(True)
+ view.set_syntax_file("Packages/JavaScript/JSON.sublime-syntax")
+
+ def _check_for_errors_in_configure(self, view):
+ scopes = view.find_by_selector("invalid.illegal")
+ errorcount = len(scopes)
+ if errorcount > 0:
+ self.bad_configure = True
+ self.window.run_command("show_panel",
+ {"panel": "output.cmake.configure"})
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..606090c
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,13 @@
+# Unit Testing
+
+We use https://github.com/randy3k/UnitTesting for the tests.
+
+Download that package from Package Control, then run
+
+ UnitTesting: Test Current Project
+
+or
+
+ UnitTesting: Test Current File
+
+To add a new test, inherit from TestCase defined in fixtures.py.
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/fixtures.py b/tests/fixtures.py
new file mode 100644
index 0000000..35da521
--- /dev/null
+++ b/tests/fixtures.py
@@ -0,0 +1,73 @@
+"""Defines TestCase"""
+import unittesting
+import os
+import sublime
+from CMakeBuilder import ServerManager
+
+
+class TestCase(unittesting.helpers.TempDirectoryTestCase):
+ """
+ TempDirectoryTestCase is a subclass of DeferrableTestCase which creates and
+ opens a temp directory before running the test case and close the window
+ when the test case finishes running.
+
+ See:
+ https://github.com/divmain/GitSavvy/blob/master/tests/test_git/common.py
+ https://github.com/randy3k/UnitTesting/blob/master/unittesting/helpers.py
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ """Prepares a class for a test case involving project files."""
+ yield from super(TestCase, cls).setUpClass()
+ assert hasattr(cls, "cmake_settings")
+ assert hasattr(cls, "files")
+ assert hasattr(cls, "window")
+ assert isinstance(cls.files, list)
+ assert isinstance(cls.window, sublime.Window)
+ data = cls.window.project_data()
+ assert data["folders"][0]["path"] is not None
+ data["settings"] = {}
+ data["settings"]["cmake"] = cls.cmake_settings
+ cls.window.set_project_data(data)
+ for pair in cls.files:
+ assert isinstance(pair, tuple)
+ assert len(pair) == 2
+ assert isinstance(pair[0], str)
+ assert isinstance(pair[1], str)
+ path = os.path.join(cls._temp_dir, pair[0])
+ content = pair[1]
+ with open(path, "w") as f:
+ f.write(content)
+
+
+class ServerTestCase(TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ yield from super(ServerTestCase, cls).setUpClass()
+ ServerManager.build_folder_pre_expansion = cls.cmake_settings["schemes"][0]["build_folder"]
+ ServerManager.build_folder = sublime.expand_variables(ServerManager.build_folder_pre_expansion, cls.window.extract_variables())
+ ServerManager.source_folder = cls.window.extract_variables()["folder"]
+ ServerManager.command_line_overrides = {
+ "CMAKE_EXPORT_COMPILE_COMMANDS": True
+ }
+ print(cls.window.extract_variables(),
+ ServerManager.build_folder_pre_expansion,
+ ServerManager.build_folder, ServerManager.source_folder)
+ if sublime.platform() in ("osx", "linux"):
+ ServerManager.generator = "Unix Makefiles"
+ else:
+ ServerManager.generator = "NMake Makefiles"
+ # ServerManager._run_configure_with_new_settings()
+ # assert ServerManager.get(cls.window) is not None
+
+ @classmethod
+ def tearDownClass(cls):
+ server = ServerManager._servers.pop(cls.window.id(), None)
+ assert server is not None
+ super(ServerTestCase, cls).tearDownClass()
+
+ @classmethod
+ def get_server(cls):
+ return ServerManager.get(cls.window)
diff --git a/tests/test_configure.py b/tests/test_configure.py
new file mode 100644
index 0000000..a598e1e
--- /dev/null
+++ b/tests/test_configure.py
@@ -0,0 +1,20 @@
+from CMakeBuilder.tests.fixtures import TestCase
+
+
+class TestConfigure(TestCase):
+
+ cmake_settings = {
+ "build_folder": "$folder/build"
+ }
+
+ files = [
+ ("CMakeLists.txt", r"""
+cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
+project(foo)
+message(STATUS "okay")
+""")
+ ]
+
+ def test_configure(self):
+ # self.window.run_command("cmake_configure")
+ self.assertTrue(True)
diff --git a/tests/test_server.py b/tests/test_server.py
new file mode 100644
index 0000000..59f3a82
--- /dev/null
+++ b/tests/test_server.py
@@ -0,0 +1,29 @@
+from CMakeBuilder.tests.fixtures import ServerTestCase
+import CMakeBuilder
+import sublime
+
+
+if CMakeBuilder.support.capabilities("serverMode"):
+
+ class TestServer(ServerTestCase):
+
+ cmake_settings = {
+ "schemes": [
+ {
+ "name": "Debug",
+ "build_folder": "${folder}/build"
+ }
+ ]
+ }
+
+ files = [
+ ("CMakeLists.txt", r"""
+ cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
+ project(foo)
+ message(STATUS "okay")
+""")
+ ]
+
+ # def test_server(self):
+ # server = self.get_server()
+ # self.assertTrue(server is not None)
diff --git a/unittesting.json b/unittesting.json
new file mode 100644
index 0000000..9d4e572
--- /dev/null
+++ b/unittesting.json
@@ -0,0 +1,5 @@
+{
+ "tests_dir" : "tests",
+ "pattern" : "test_*",
+ "deferred": true
+}