diff --git a/README.md b/README.md
index 3567997667..1ae4050877 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,15 @@ It works by building the project with a fake toolchain, which simply and filters
It is reasonably fast, taking ~10 seconds to generate a configuration file for the Linux kernel.
+## Installation
+Add ```NeoBundle 'rdnetto/YCM-Generator'``` to your vimrc (or the equivalent for your plugin manager).
+
+Alternatively, Arch Linux users can install YCM-Generator using the (unofficial) [AUR package](https://aur4.archlinux.org/packages/ycm-generator-git/).
+
## Usage
Run ```./config_gen.py PROJECT_DIRECTORY```, where ```PROJECT_DIRECTORY``` is the root directory of your project's build system (i.e. the one containing the root Makefile, etc.)
-YCM-Generator can also be used as a Vim plugin. Once installed with Vundle/NeoBundle/etc., use the ```:YcmGenerateConfig``` or ```:CCGenerateConfig``` command to generate a config file for the current directory. This command accepts the same arguments as ```./config_gen.py```, but does not require the project directory to be specified (it defaults to the current working directory).
+You can also invoke it from within Vim using the ```:YcmGenerateConfig``` or ```:CCGenerateConfig``` commands to generate a config file for the current directory. These commands accept the same arguments as ```./config_gen.py```, but do not require the project directory to be specified (it defaults to the current working directory).
## Requirements and Limitations
* Requirements:
@@ -19,27 +24,41 @@ YCM-Generator can also be used as a Vim plugin. Once installed with Vundle/NeoBu
+ Clang
* Supported build systems:
- + Make
- + CMake
- + Autotools
+ + make
+ + cmake
+ + qmake
+ + autotools
Your build system should support specifying the compiler through the ```CC```/```CXX``` environment variables, or not use an absolute path to the compiler.
Some flags present in the resulting configuration file may be mutually exclusive with each other - reading the generated file prior to use is recommended.
-# Documentation & Support
+## Documentation & Support
* run ```./config_gen.py --help``` to see the complete list of supported options.
* if you receive the error message ```ERROR: No commands were logged to the build logs```, try using the ```--verbose``` flag to see any error messages
+ + some build systems require certain environment variables to be set. Note that these will *not* be used by YCM-Generator by default, unless `--preserve-environment` is used
+ if you open an issue regarding this error message, please include the output when running with ```--verbose``` and a link to the project repo (if possible)
## Development
Patches are welcome. Please submit pull requests against the ```develop``` branch.
### Windows support
-The script is currently supported under Unices (Linux, BSD, OS X) only.
+The script is currently supported under Unices (Linux, NixOS[1], BSD, OS X) only.
Implementing Windows support consists of porting the contents of ```fake-toolchain/Unix```.
If you are interested in implementing/testing this, please open a pull request.
+[1] May require `--preserve-environment` - see [#19](https://github.com/rdnetto/YCM-Generator/issues/19)
+
+### Test Cases
+The following projects are used for testing:
+
+| Project | Build system | Notes |
+| ------------------------------------------------------------------------- | ----------------- | ------ |
+| [Linux kernel](https://git.kernel.org) | Kbuild (Make) | |
+| [Vim-qt](https://rdnetto@bitbucket.org/equalsraf/vim-qt.git) | Autotools | |
+| [Clementine](https://github.com/clementine-player/Clementine.git) | Cmake | |
+| [ExtPlane](https://github.com/vranki/ExtPlane.git) | Qmake | Should be tested with both versions of Qt. |
+
## License
YCM-Generator is published under the GNU GPLv3.
diff --git a/config_gen.py b/config_gen.py
index da0d3d7203..7d78df3b35 100755
--- a/config_gen.py
+++ b/config_gen.py
@@ -12,6 +12,7 @@
import tempfile
import time
import subprocess
+import glob
# Default flags for make
@@ -30,6 +31,7 @@ def main():
parser.add_argument("-o", "--output", help="Save the config file as OUTPUT. Default: .ycm_extra_conf.py, or .color_coded if --format=cc.")
parser.add_argument("-x", "--language", choices=["c", "c++"], help="Only output flags for the given language. This defaults to whichever language has its compiler invoked the most.")
parser.add_argument("--out-of-tree", action="store_true", help="Build autotools projects out-of-tree. This is a no-op for other project types.")
+ parser.add_argument("--qt-version", choices=["4", "5"], default="5", help="Use the given Qt version for qmake. (Default: 5)")
parser.add_argument("-e", "--preserve-environment", action="store_true", help="Pass environment variables to build processes.")
parser.add_argument("PROJECT_DIR", help="The root directory of the project.")
args = vars(parser.parse_args())
@@ -122,7 +124,7 @@ def main():
print("Created {} config file with {} {} flags".format(output_format.upper(), len(flags), lang.upper()))
-def fake_build(project_dir, c_build_log_path, cxx_build_log_path, verbose, make_cmd, cc, cxx, out_of_tree, configure_opts, make_flags, preserve_environment):
+def fake_build(project_dir, c_build_log_path, cxx_build_log_path, verbose, make_cmd, cc, cxx, out_of_tree, configure_opts, make_flags, preserve_environment, qt_version):
'''Builds the project using the fake toolchain, to collect the compiler flags.
project_dir: the directory containing the source files
@@ -135,6 +137,7 @@ def fake_build(project_dir, c_build_log_path, cxx_build_log_path, verbose, make_
configure_opts: additional flags for configure stage
make_flags: additional flags for make
preserve_environment: pass environment variables to build processes
+ qt_version: The Qt version to use when building with qmake.
'''
# TODO: add Windows support
@@ -167,6 +170,9 @@ def fake_build(project_dir, c_build_log_path, cxx_build_log_path, verbose, make_
# depend upon the existence of various output files
make_args = [make_cmd] + make_flags
+ # Used for the qmake build system below
+ pro_files = glob.glob(os.path.join(project_dir, "*.pro"))
+
# sanity check - make sure the toolchain is available
assert os.path.exists(fake_path), "Could not find toolchain at '{}'".format(fake_path)
@@ -177,11 +183,22 @@ def run(cmd, *args, **kwargs):
# execute the build system
if(os.path.exists(os.path.join(project_dir, "CMakeLists.txt"))):
- # Cmake
+ # cmake
# run cmake in a temporary directory, then compile the project as usual
build_dir = tempfile.mkdtemp()
proc_opts["cwd"] = build_dir
+ # if the project was built in-tree, we need to hide the cache file so that cmake
+ # populates the build dir instead of just re-generating the existing files
+ cache_path = os.path.join(project_dir, "CMakeCache.txt")
+
+ if(os.path.exists(cache_path)):
+ fd, cache_tmp = tempfile.mkstemp()
+ os.close(fd)
+ shutil.move(cache_path, cache_tmp)
+ else:
+ cache_tmp = None
+
print("Running cmake in '{}'...".format(build_dir))
run(["cmake", project_dir] + configure_opts, env=env_config, **proc_opts)
@@ -192,8 +209,11 @@ def run(cmd, *args, **kwargs):
print("")
shutil.rmtree(build_dir)
+ if(cache_tmp):
+ shutil.move(cache_tmp, cache_path)
+
elif(os.path.exists(os.path.join(project_dir, "configure"))):
- # Autotools
+ # autotools
# perform build in-tree, since not all projects handle out-of-tree builds correctly
if(out_of_tree):
@@ -216,8 +236,33 @@ def run(cmd, *args, **kwargs):
else:
run([make_cmd, "maintainer-clean"], env=env, **proc_opts)
+ elif(pro_files):
+ # qmake
+ # make sure there is only one .pro file
+ if len(pro_files) != 1:
+ print("ERROR: Found {} .pro files (expected one): {}.".format(
+ len(pro_files), ', '.join(pro_files)))
+ sys.exit(1)
+
+ # run qmake in a temporary directory, then compile the project as usual
+ build_dir = tempfile.mkdtemp()
+ proc_opts["cwd"] = build_dir
+ env_config["QT_SELECT"] = qt_version
+ env_config["QMAKESPEC"] = "unsupported/linux-clang" if qt_version == "4" else "linux-clang"
+
+ print("Running qmake in '{}' with Qt {}...".format(build_dir, qt_version))
+ run(["qmake"] + configure_opts + [pro_files[0]], env=env_config,
+ **proc_opts)
+
+ print("\nRunning make...")
+ run(make_args, env=env, **proc_opts)
+
+ print("\nCleaning up...")
+ print("")
+ shutil.rmtree(build_dir)
+
elif(any([os.path.exists(os.path.join(project_dir, x)) for x in ["GNUmakefile", "makefile", "Makefile"]])):
- # Make
+ # make
# needs to be handled last, since other build systems can generate Makefiles
print("Preparing build directory...")
run([make_cmd, "clean"], env=env, **proc_opts)
diff --git a/fake-toolchain/Unix/ar b/fake-toolchain/Unix/ar
index 63b10de421..f32a5804e2 120000
--- a/fake-toolchain/Unix/ar
+++ b/fake-toolchain/Unix/ar
@@ -1 +1 @@
-/bin/true
\ No newline at end of file
+true
\ No newline at end of file
diff --git a/fake-toolchain/Unix/as b/fake-toolchain/Unix/as
index 63b10de421..f32a5804e2 120000
--- a/fake-toolchain/Unix/as
+++ b/fake-toolchain/Unix/as
@@ -1 +1 @@
-/bin/true
\ No newline at end of file
+true
\ No newline at end of file
diff --git a/fake-toolchain/Unix/cc b/fake-toolchain/Unix/cc
index 556f226f98..03e796105b 100755
--- a/fake-toolchain/Unix/cc
+++ b/fake-toolchain/Unix/cc
@@ -1,10 +1,10 @@
-#!/bin/bash
+#!/bin/sh
if [[ ! -z "$YCM_CONFIG_GEN_CC_PASSTHROUGH" ]]; then
# Cmake determines compiler properties by compiling a test file, so call clang for this case
$YCM_CONFIG_GEN_CC_PASSTHROUGH $@
-elif [[ "$@" == "-v" ]]; then
+elif [ "$@" = "-v" ]; then
# Needed to enable clang-specific options for certain build systems (e.g. linux)
echo "clang version 3.5.0 (fake toolchain)"
diff --git a/fake-toolchain/Unix/cxx b/fake-toolchain/Unix/cxx
index 2b302dbdc5..3db8744404 100755
--- a/fake-toolchain/Unix/cxx
+++ b/fake-toolchain/Unix/cxx
@@ -1,10 +1,10 @@
-#!/bin/bash
+#!/bin/sh
if [[ ! -z "$YCM_CONFIG_GEN_CXX_PASSTHROUGH" ]]; then
# Cmake determines compiler properties by compiling a test file, so call clang for this case
$YCM_CONFIG_GEN_CXX_PASSTHROUGH $@
-elif [[ "$@" == "-v" ]]; then
+elif [ "$@" = "-v" ]; then
# Needed to enable clang-specific options for certain build systems (e.g. linux)
echo "clang version 3.5.0 (fake toolchain)"
diff --git a/fake-toolchain/Unix/g++ b/fake-toolchain/Unix/g++
new file mode 120000
index 0000000000..34836c6976
--- /dev/null
+++ b/fake-toolchain/Unix/g++
@@ -0,0 +1 @@
+cxx
\ No newline at end of file
diff --git a/fake-toolchain/Unix/ld b/fake-toolchain/Unix/ld
index 63b10de421..f32a5804e2 120000
--- a/fake-toolchain/Unix/ld
+++ b/fake-toolchain/Unix/ld
@@ -1 +1 @@
-/bin/true
\ No newline at end of file
+true
\ No newline at end of file
diff --git a/fake-toolchain/Unix/nm b/fake-toolchain/Unix/nm
index 63b10de421..f32a5804e2 120000
--- a/fake-toolchain/Unix/nm
+++ b/fake-toolchain/Unix/nm
@@ -1 +1 @@
-/bin/true
\ No newline at end of file
+true
\ No newline at end of file
diff --git a/fake-toolchain/Unix/true b/fake-toolchain/Unix/true
new file mode 100755
index 0000000000..420376f58a
--- /dev/null
+++ b/fake-toolchain/Unix/true
@@ -0,0 +1,4 @@
+#!/bin/sh
+# This script is needed because /bin/true does not exist on non-FHS-compliant distros. e.g. NixOS
+exit 0
+
diff --git a/plugin/ycm-generator.vim b/plugin/ycm-generator.vim
index 9067a1f30e..f91b3ab66a 100644
--- a/plugin/ycm-generator.vim
+++ b/plugin/ycm-generator.vim
@@ -10,7 +10,7 @@ function! s:GenerateConfig(fmt, flags)
" Only append the working directory if the last option is a flag
let l:split_flags = split(a:flags)
if len(l:split_flags) == 0 || l:split_flags[-1] =~ "^-"
- let l:cmd = l:cmd . " " . getcwd()
+ let l:cmd = l:cmd . " " . fnameescape(getcwd())
endif
execute l:cmd