Skip to content

Instantly share code, notes, and snippets.

@nevkontakte
Last active July 10, 2024 09:02
Show Gist options
  • Save nevkontakte/3f22937b2c3a4ff2293c to your computer and use it in GitHub Desktop.
Save nevkontakte/3f22937b2c3a4ff2293c to your computer and use it in GitHub Desktop.
Ninja support for CLion IDE

Ninja support for CLion IDE

This script enables Ninja-powered builds in CLion IDE by wrapping around CMake, which it uses. See my blog post for details.

Disclaimer

This script is provided AS IS with no guarantees given or responsibilities taken by the author. This script relies on undocumented features of CLion IDE and may lead to instability of build and/or IDE. Use it on your own risk under WTFPL terms.

Getting started

Supported OS: I've tested this scipt under Ubuntu and python 2.7. I suppose it should work on Mac and Windows too but I've never tested it.

  1. Make sure you have python 2.7 installed.
  2. Download cmake_ninja_wrapper.py and give it execution permission.
  3. Edit REAL_CMAKE variable at the beginning of the script to point at CLion's bundled CMake binary (recommended) or at system CMake.
  4. In CLion, go to Settings → Build, Execution, Deployment → Toolchains
  5. Under "CMake executable" select cmake_ninja_wrapper.py script.

Make sure that CLion successfully detects make program (it should be still /usr/bin/make) and C/C++ compiler. At this point you are done. Go, reload you CMake project and try to build it.

Troubleshooting

In case of any troubles try setting TRACING variable at the beginning of the file to True and re-parsing CMake project in IDE. After that look at /tmp/cmake_wrapper.log for any insights :-)

#!/usr/bin/python
import sys
import os
import subprocess
import shutil
import re
import select
# ---------------------- Configuration section ------------------------------
REAL_CMAKE = "/usr/bin/cmake"
TRACING = False
# --------------------------- Code section ----------------------------------
def trace(message, argv = []):
if not TRACING:
return
with open('/tmp/cmake_wrapper.log', 'a') as log:
if not argv == []:
log.write("\n\n")
log.write(message)
if not argv == []:
argv = '"%s"' % ('" "'.join(argv))
log.write("\n\n\t%s\n\tat: %s\n" % (argv, os.getcwd()))
def call_cmake(passing_args):
"""Call real cmake as a subprocess passing it's output both to stdout and trace file."""
passing_args = [REAL_CMAKE] + passing_args
trace("Calling real cmake:", passing_args)
proc = subprocess.Popen(passing_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
while True:
reads = [proc.stdout.fileno(), proc.stderr.fileno()]
ret = select.select(reads, [], [])
for fd in ret[0]:
if fd == proc.stdout.fileno():
line = proc.stdout.readline()
sys.stdout.write(line)
sys.stdout.flush()
trace(line)
if fd == proc.stderr.fileno():
line = proc.stderr.readline()
sys.stderr.write(line)
sys.stderr.flush()
trace(line)
if proc.poll() != None:
break
for line in proc.stdout:
sys.stdout.write(line)
trace(line)
for line in proc.stderr:
sys.stderr.write(line)
trace(line)
return proc.poll()
def is_real_project():
"""Detect if called inside clion private directory."""
cwd = os.getcwd()
return "clion" in cwd and "cmake" in cwd and "generated" in cwd
class CMakeCache(object):
"""CMake cache management utility"""
def __init__(self, path):
super(CMakeCache, self).__init__()
self.path = path
def alter(self, variable, value):
"""
Change a variable value in CMake cache.
TODO: Add variable if it doesn't already exist
"""
if not os.path.isfile(self.path):
return
with open(self.path, 'r') as cache_file:
cache_data = cache_file.read()
pattern = '%s=.*' % re.escape(variable)
replacement = '%s=%s' % (variable, value)
cache_data = re.sub(pattern, replacement, cache_data)
with open(self.path, 'w') as cache_file:
cache_file.write(cache_data)
def ninjafy(self):
self.alter('CMAKE_GENERATOR:INTERNAL', 'Ninja')
self.alter('CMAKE_MAKE_PROGRAM:FILEPATH', '/usr/bin/ninja')
def makefy(self):
self.alter('CMAKE_GENERATOR:INTERNAL', 'Unix Makefiles')
self.alter('CMAKE_MAKE_PROGRAM:FILEPATH', '/usr/bin/make')
def ninjafy_argv(original):
"""Replace Unix Makefiles generator with Ninja"""
processed = []
next_g = False
for a in original:
if a == '-G':
next_g = True
elif next_g and 'Unix Makefiles' in a:
a = a.replace('Unix Makefiles', 'Ninja')
processed.append(a)
return processed
trace('Originally called:', sys.argv)
# Enable wrapping logic only when called inside clion private directory.
if not is_real_project():
sys.exit(call_cmake(sys.argv[1:]))
# Check if generator argument was specified
if '-G' in sys.argv:
# Generate Makefile artifacts required by CLion
cache = CMakeCache('CMakeCache.txt')
cache.makefy()
exit_code = call_cmake(sys.argv[1:])
if exit_code != 0:
sys.exit(exit_code)
# Generate Ninja artifacts for actual build
passing_args = ninjafy_argv(sys.argv[1:])
cache.ninjafy()
sys.exit(call_cmake(passing_args))
else:
sys.exit(call_cmake(sys.argv[1:]))
@armarkos
Copy link

armarkos commented May 3, 2017

Hello,

Thanks for the script. Somehow I could not make it work. This is the command CLion 2017.1.1 tries to run: (I use macports' CMake /opt/local/bin/cmake)

cmake_ninja_wrapper.py -DCMAKE_BUILD_TYPE=Debug -DTrilinos_INSTALL_DIR=/Trilinos_mpi -DCMAKE_CXX_COMPILER=/opt/local/bin/mpic++-mpich-clang39 -DCMAKE_C_COMPILER=/opt/local/bin/mpicc-mpich-clang39 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE:BOOL=on -GNinja -DCMAKE_MAKE_PROGRAM=/opt/local/bin/ninja -G "CodeBlocks - Unix Makefiles" ..

And it outputs:

-- The CXX compiler identification is Clang 3.9.1
-- Check for working C compiler: /opt/local/bin/mpicc-mpich-clang39
-- Check for working C compiler: /opt/local/bin/mpicc-mpich-clang39 -- broken
CMake Error at /opt/local/share/cmake-3.8/Modules/CMakeTestCCompiler.cmake:51 (message):
  The C compiler "/opt/local/bin/mpicc-mpich-clang39" is not able to compile
  a simple test program.

  It fails with the following output:

   Change Dir: /Users/..../tests/CMakeFiles/CMakeTmp

  Run Build Command:"/opt/local/bin/ninja" "cmTC_b6488/fast"

  ninja: error: loading 'build.ninja': No such file or directory

  CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
  CMakeLists.txt:14 (PROJECT)


-- Configuring incomplete, errors occurred!
See also "/Users/..../tests/CMakeFiles/CMakeOutput.log".
See also "/Users/.../tests/CMakeFiles/CMakeError.log".```

Do you know what is going on? I can run CMake&ninja outside of CLion fine, but not inside. 

Thanks,
Aram

@SergeZZZ
Copy link

2 armarkos please try a newer Cmake, or the one bundled with Clion.

@long1eu
Copy link

long1eu commented Jul 6, 2019

I doesn't work for me on MacOS.

CLion 2019.1.4
Build #CL-191.7479.33, built on May 30, 2019
Licensed to Razvan Lung
Subscription is active until February 21, 2020
For educational use only.
JRE: 1.8.0_202-release-1483-b53 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.14.5

Screenshot 2019-07-06 at 15 47 31

Screenshot 2019-07-06 at 16 23 16

@Wongboo
Copy link

Wongboo commented Apr 6, 2021

Would it be too slow because of python?

@nevkontakte
Copy link
Author

Not in any noticeable way, I don't think. The script does some very lightweight string manipulation, which is fast enough in just about any language.

That said, it's been years since I've used this script myself, so please let us all know if it worked for you 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment