Skip to content

Instantly share code, notes, and snippets.

@lizan
Last active September 15, 2020 17:17
Show Gist options
  • Save lizan/bb64be8da2c9c8c8a931dacf1c54dbfa to your computer and use it in GitHub Desktop.
Save lizan/bb64be8da2c9c8c8a931dacf1c54dbfa to your computer and use it in GitHub Desktop.
Transfrom bazel C++ target to CMakeLists.txt only for IDEs
#!/usr/bin/env python2.7
#
# A script to generate CMakeLists.txt from bazel C++ target.
# With CMakeLists.txt file, it can be opened directly from CLion.
#
# Usage:
# $ bazel-cmakelists
#
# CMake also support generating other IDE project files such as Eclipse and Xcode.
# Run following command additionally to generate Xcode project:
#
# $ cmake -G Xcode
#
# DON'T USE CMAKE TO BUILD THE PROJECT. It is not tested and likely to fail.
import gflags as flags
from os import path
import os
import re
import xml.etree.ElementTree as ET
import subprocess
import sys
FLAGS = flags.FLAGS
flags.DEFINE_string('output', 'CMakeLists.txt',
('output file name'))
flags.DEFINE_string('project',
subprocess.check_output("basename $(bazel info workspace)", shell=True).strip(),
('project name'))
flags.DEFINE_string('target', '//src/...',
('bazel target'))
flags.DEFINE_boolean('open', False, ('Open CLion after generation, only supports Mac'))
flags.DEFINE_boolean('mac_debug', False, ('Generate project to use with debugger on Mac'))
EXTERNAL_PATTERN = re.compile("^@(.*)\\/\\/")
SOURCE_EXTENSION = set([".h", ".hpp", ".c", ".cc", ".cpp", ".cxx"])
def GetBasePath(fn):
fn = fn.replace('/:', '/')
fn = fn.replace(':', '/')
if EXTERNAL_PATTERN.match(fn):
fn = EXTERNAL_PATTERN.sub("external/\\1/", fn)
return fn.lstrip('/')
def ConvertGeneratedPath(fn):
bazel_root = 'bazel-' + FLAGS.project
if FLAGS.mac_debug:
genfiles_root = path.join('bazel-out', 'local-dbg', 'genfiles')
if not FLAGS.mac_debug:
genfiles_root = 'bazel-genfiles'
return path.join(genfiles_root, GetBasePath(fn))
def ConvertExternalPath(fn):
bazel_root = 'bazel-' + FLAGS.project
if not FLAGS.mac_debug and EXTERNAL_PATTERN.match(fn):
return path.join(bazel_root, GetBasePath(fn))
return GetBasePath(fn)
def ExtractSources():
query = 'kind("source file", deps(%s))' % FLAGS.target
sources = []
for fn in subprocess.check_output(['bazel', 'query', query]).splitlines():
fn = ConvertExternalPath(fn)
if path.splitext(fn)[1] in SOURCE_EXTENSION:
sources.append(fn)
return sources
def ExtractGenerated():
query = 'kind("generated file", deps(%s))' % FLAGS.target
sources = []
for fn in subprocess.check_output(['bazel', 'query', query]).splitlines():
fn = ConvertGeneratedPath(fn)
if path.splitext(fn)[1] in SOURCE_EXTENSION:
sources.append(fn)
return sources
def ExtractIncludes():
query = 'kind(cc_library, deps(%s))' % FLAGS.target
includes = []
xml = subprocess.check_output(['bazel', 'query', query, '--output', 'xml'])
tree = ET.fromstring(xml)
for e in tree.findall(".//list[@name='includes']/.."):
prefix = e.attrib['name'].split(':')[0]
for val in [i.attrib['value'] for i in e.findall("list[@name='includes']/string")] + ["."]:
geninc = path.normpath(path.join(ConvertGeneratedPath(prefix), val))
if geninc not in includes:
includes.append(geninc)
inc = path.normpath(path.join(ConvertExternalPath(prefix), val))
if inc not in includes:
includes.append(inc)
for e in tree.findall(".//string[@name='include_prefix']/.."):
prefix = e.attrib['name'].split(':')[0]
include_prefix = e.find("string[@name='include_prefix']").attrib['value']
geninc = path.normpath(path.join(ConvertGeneratedPath(prefix), val))
if geninc.endswith(include_prefix):
geninc = geninc[:-len(include_prefix)]
if geninc not in includes:
includes.append(geninc)
inc = path.normpath(path.join(ConvertExternalPath(prefix), val))
if inc.endswith(include_prefix):
inc = inc[:-len(include_prefix)]
if inc not in includes:
includes.append(inc)
return includes
def ExtractDefines():
query = 'attr("defines", "", deps(%s))' % FLAGS.target
xml = subprocess.check_output(['bazel', 'query', query, '--output', 'xml'])
tree = ET.fromstring(xml)
defines = []
for e in tree.findall(".//list[@name='defines']/string"):
defines.append(e.attrib['value'])
return defines
def ExtractCopts():
query = 'attr("copts", "", deps(%s))' % FLAGS.target
xml = subprocess.check_output(['bazel', 'query', query, '--output', 'xml'])
tree = ET.fromstring(xml)
copts = []
for e in tree.findall(".//list[@name='copts']/string"):
copts.append(e.attrib['value'])
return copts
def GenerateCMakeList():
bazel_args = ['build']
bazel_args.extend(['-c', 'dbg'])
bazel_args.append(FLAGS.target)
subprocess.check_output(['bazel'] + bazel_args)
sources = ExtractSources()
generated = ExtractGenerated()
includes = ExtractIncludes()
defines = ExtractDefines()
copts = ExtractCopts()
for opt in copts:
if opt.startswith("-I-"):
includes.append(opt[3:])
elif opt.startswith("-I"):
includes.append(opt[2:])
elif opt.startswith("-D"):
defines.append(opt[2:])
output = FLAGS.output
if FLAGS.mac_debug:
file_root = subprocess.check_output(['bazel', 'info', 'execution_root']).strip()
else:
file_root = subprocess.check_output(['bazel', 'info', 'workspace']).strip()
output = path.join(file_root, FLAGS.output)
try:
os.remove(output)
except:
pass
with open(output, 'w') as cmakelist:
cmakelist.write("cmake_minimum_required(VERSION 3.3)\n")
cmakelist.write("project(%s)\n\n" % FLAGS.project)
cmakelist.write("set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++11\")\n\n")
cmakelist.write("set(SOURCE_FILES\n ")
cmakelist.write("\n ".join(sources))
cmakelist.write(")\n\n")
cmakelist.write("set(GENERATED_SOURCES\n ")
cmakelist.write("\n ".join(generated))
cmakelist.write(")\n\n")
cmakelist.write("set(INCLUDE_DIRECTORIES\n ")
cmakelist.write("\n ".join(includes))
if FLAGS.mac_debug:
cmakelist.write("\n bazel-out/local-dbg/genfiles")
else:
cmakelist.write("\n bazel-genfiles")
cmakelist.write("\n .)\n\n")
if len(defines) > 0:
cmakelist.write("add_definitions(\n -D")
cmakelist.write("\n -D".join(defines))
cmakelist.write(")\n\n")
cmakelist.write("include_directories(${INCLUDE_DIRECTORIES})\n")
cmakelist.write("add_executable(%s ${GENERATED_SOURCES} ${SOURCE_FILES})\n" % FLAGS.project)
if FLAGS.open:
if sys.platform == 'darwin':
subprocess.call(['open', output, '-a', 'CLion'])
else:
sys.stderr.write("Open flag is only supported in Mac.\n")
sys.stderr.write("CMakeLists.txt generated in following directory:\n")
sys.stderr.write(file_root + "\n")
if __name__ == "__main__":
try:
argv = FLAGS(sys.argv) # parse flags
except flags.FlagsError as e:
sys.exit('%s\nUsage: %s ARGS\n%s' % (e, sys.argv[0], FLAGS))
GenerateCMakeList()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment