Skip to content

Instantly share code, notes, and snippets.

@Bluexin
Forked from arichardson/CLion_Ninja.md
Last active May 18, 2019 19:35
Show Gist options
  • Save Bluexin/0278fe48443f81d09f03329789e01890 to your computer and use it in GitHub Desktop.
Save Bluexin/0278fe48443f81d09f03329789e01890 to your computer and use it in GitHub Desktop.
Ninja support for CLion IDE

Ninja support for CLion IDE on Windows (working with newer versions of clion)

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: This is a fork made specifically to work on windows with Python 3. Saving cmake logs relies on tee, and by default this script will try to find it on path or rely on git being installed. You do not need it however if you don't plan on saving the logs (for debugging purposes). Other specific change, windows doesn't like executing python scripts directly the way CLion does it. Hence the wrapper for the wrapper.

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

Make sure that CLion successfully detects make program 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 the various log files for any insights :-)

#!/usr/bin/env python3
# from https://gist.github.com/RichardsonAlex/3de8adaebec0d488c13911edd2e0fe63
# Edited by Arnaud 'Bluexin' Sole to work on windows
import os
import shutil
import re
import sys
import subprocess
# ---------------------- Configuration section ------------------------------
TRACING = False
REAL_CMAKE = shutil.which('cmake').replace("\\", "/")
if not os.path.isfile(REAL_CMAKE):
sys.exit('Could not find cmake!')
NINJA_PATH = shutil.which('ninja').replace("\\", "/")
if not os.path.isfile(NINJA_PATH):
sys.exit('Could not find ninja!')
if TRACING:
TEE_PATH = shutil.which('ninja').replace("\\", "/")
if not os.path.isFile(NINJA_PATH):
TEE_PATH = os.path.abspath(os.path.join(os.path.dirname(shutil.which('git')), "../usr/bin/tee.exe")).replace("\\", "/")
if not os.path.isfile(TEE_PATH):
sys.exit('Could not find tee! Tried ' + TEE_PATH)
dir_path = os.path.dirname(os.path.realpath(__file__))
# --------------------------- Code section ----------------------------------
def trace(message, argv = []):
if not TRACING:
return
with open(os.path.join(dir_path, "cmake_wrapper.log"), 'a') as log:
if not argv == []:
log.write("\n\n")
if isinstance(message, bytes):
message = message.decode()
log.write(message)
if not argv == []:
argv = '"%s"' % ('" "'.join(argv))
log.write("\n\n\t%s\n\tat: %s\n" % (argv, os.getcwd()))
def tracerr(message, argv = []):
if not TRACING:
return
with open(os.path.join(dir_path, "cmake_wrapper_err.log"), 'a') as log:
if not argv == []:
log.write("\n\n")
if isinstance(message, bytes):
message = message.decode()
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)
if TRACING:
proc = subprocess.Popen(passing_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
tee = subprocess.Popen([TEE_PATH, os.path.join(dir_path, 'cmake_wrapper-sub.log')], stdin=proc.stdout)
tee2 = subprocess.Popen([TEE_PATH, os.path.join(dir_path, 'cmake_wrapper-sub_err.log')], stdin=proc.stderr)
tee.communicate()
tee2.communicate()
else :
proc = subprocess.Popen(passing_args, stdout=sys.stdout, stderr=sys.stderr, bufsize=0)
return proc.wait()
def is_real_project():
return "cmake-build" in os.getcwd()
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)
cache_file.flush()
def ninjafy(self):
self.alter('CMAKE_GENERATOR:INTERNAL', 'Ninja')
self.alter('CMAKE_MAKE_PROGRAM:FILEPATH', NINJA_PATH)
def makefy(self):
self.alter('CMAKE_GENERATOR:INTERNAL', 'MinGW Makefiles')
self.alter('CMAKE_MAKE_PROGRAM:FILEPATH', REAL_CMAKE)
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 'MinGW Makefiles' in a:
a = a.replace('MinGW 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
exit_code = call_cmake(sys.argv[1:])
trace("cmake exit code: " + str(exit_code) + '\n')
sys.stdout.write("cmake exit code: " + str(exit_code) + '\n')
cache = CMakeCache('CMakeCache.txt')
cache.makefy()
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:]))
@python %~dp0\cmake_ninja_wrapper.py %*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment