|
#!/usr/bin/env python |
|
|
|
""" |
|
Generate a skeleton CMakeLists.txt from a Bazel project. The CMakeLists.txt |
|
can be used in IDEs like CLion. |
|
|
|
Use like: |
|
|
|
./bazel_to_cmake.py //tensorflow:libtensorflow_cc.so |
|
|
|
For best results, build the codebase first, so that CLion can find the |
|
generated files. |
|
|
|
License: MIT |
|
""" |
|
|
|
from __future__ import print_function |
|
|
|
import argparse |
|
import json |
|
import os |
|
import subprocess |
|
|
|
# Hardcode the contents of the .bzl file here, so the script is easier to share. |
|
BZL_CONTENTS = """ |
|
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") |
|
load( |
|
"@bazel_tools//tools/build_defs/cc:action_names.bzl", |
|
"CPP_COMPILE_ACTION_NAME", |
|
"CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME", |
|
"C_COMPILE_ACTION_NAME", |
|
) |
|
|
|
_cpp_extensions = [ |
|
"cc", |
|
"cpp", |
|
"cxx", |
|
] |
|
|
|
def _cmake_skeleton_aspect_impl(target, ctx): |
|
if ctx.rule.kind not in [ |
|
"cc_library", |
|
"cc_binary", |
|
"cc_test", |
|
"cc_inc_library", |
|
"cc_proto_library", |
|
]: |
|
return [] |
|
|
|
sources_list = [f.path for src in ctx.rule.attr.srcs for f in src.files] |
|
|
|
target_name = str(target.label) |
|
|
|
cc_toolchain = find_cpp_toolchain(ctx) |
|
feature_configuration = cc_common.configure_features( |
|
cc_toolchain = cc_toolchain, |
|
requested_features = ctx.features, |
|
unsupported_features = ctx.disabled_features, |
|
) |
|
compile_variables = cc_common.create_compile_variables( |
|
feature_configuration = feature_configuration, |
|
cc_toolchain = cc_toolchain, |
|
user_compile_flags = ctx.fragments.cpp.cxxopts + |
|
ctx.fragments.cpp.copts, |
|
add_legacy_cxx_options = True, |
|
) |
|
compiler_options = cc_common.get_memory_inefficient_command_line( |
|
feature_configuration = feature_configuration, |
|
action_name = CPP_COMPILE_ACTION_NAME, |
|
variables = compile_variables, |
|
) |
|
|
|
data = struct( |
|
label = target_name, |
|
srcs = sources_list, |
|
system_include_directories = target.cc.system_include_directories, |
|
quote_include_directories = target.cc.quote_include_directories, |
|
include_directories = target.cc.include_directories, |
|
) |
|
|
|
json_file = ctx.new_file('%s.ideinfo.json' % (target.label.name)) |
|
ctx.file_action(json_file, data.to_json()) |
|
|
|
# Collect json files for dependencies too. |
|
all_ide_json_files = depset([json_file]) |
|
for dep in ctx.rule.attr.deps: |
|
if OutputGroupInfo not in dep: |
|
continue |
|
all_ide_json_files += dep[OutputGroupInfo].ide_json_files |
|
|
|
return [ |
|
OutputGroupInfo(ide_json_files = all_ide_json_files) |
|
] |
|
|
|
|
|
cmake_skeleton_aspect = aspect( |
|
attr_aspects = ["deps"], |
|
attrs = { |
|
"_cc_toolchain": attr.label( |
|
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
|
), |
|
}, |
|
fragments = ["cpp"], |
|
implementation = _cmake_skeleton_aspect_impl, |
|
) |
|
|
|
""" |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser() |
|
parser.add_argument("target", nargs="*", default=["//..."]) |
|
parser.add_argument("--convenience-symlink", default="bazel-") |
|
args = parser.parse_args() |
|
|
|
bin_path = subprocess.check_output(["bazel", "info", "bazel-bin"]).strip() |
|
# We could use bazel info execution_root instead, but that creates |
|
# ugly, very long paths. |
|
# execroot_path = subprocess.check_output(['bazel', 'info', 'execution_root']).strip() |
|
execroot_path = args.convenience_symlink + os.path.basename(os.getcwd()) |
|
if not os.path.exists(execroot_path): |
|
print( |
|
"Convenience symlink `{}` not found. Check your --convenience-symlink arg".format( |
|
execroot_path |
|
) |
|
) |
|
return 1 |
|
|
|
find_filter = ["find", "-L", bin_path, "-name", "*.ideinfo.json"] |
|
|
|
# Clean up old files |
|
if os.path.exists(bin_path): |
|
subprocess.check_output(find_filter + ["-delete"]) |
|
|
|
bzl_file_path = "json_ide_aspect.bzl" |
|
with open(bzl_file_path, "w") as f: |
|
f.write(BZL_CONTENTS) |
|
|
|
# Generate new jsons with the aspects. |
|
subprocess.check_call( |
|
[ |
|
"bazel", |
|
"build", |
|
"--aspects={}%cmake_skeleton_aspect".format(bzl_file_path), |
|
"--noshow_progress", |
|
"--noshow_loading_progress", |
|
"--show_result=0", |
|
"--output_groups=ide_json_files", |
|
] + args.target |
|
) |
|
|
|
json_paths = subprocess.check_output(find_filter).splitlines() |
|
|
|
def add_execroot_if_needed(p): |
|
if p.startswith("external/"): |
|
return os.path.join(execroot_path, p) |
|
else: |
|
return p |
|
|
|
contents = "cmake_minimum_required(VERSION 2.8)\n\n" |
|
|
|
for jp in json_paths: |
|
data = json.load(open(jp)) |
|
|
|
fixed_srcs = [add_execroot_if_needed(p) for p in data["srcs"]] |
|
|
|
# Filter out non-existing genfiles to avoid errors during configuration |
|
# if the project is not built yet. |
|
fixed_srcs = [p for p in fixed_srcs if os.path.exists(p)] |
|
|
|
# Also filter out headers |
|
fixed_srcs = [p for p in fixed_srcs if not p.endswith(".h")] |
|
|
|
if not fixed_srcs: |
|
continue |
|
|
|
all_include_dirs = ( |
|
data["system_include_directories"] |
|
+ data["quote_include_directories"] |
|
+ data["include_directories"] |
|
) |
|
fixed_include_dirs = [add_execroot_if_needed(p) for p in all_include_dirs] |
|
|
|
fixed_library_name = ( |
|
data["label"] |
|
.replace("//", "") |
|
.replace("/", "_") |
|
.replace(":", "_") |
|
.replace("@", "external_") |
|
) |
|
|
|
contents += """ |
|
# From target {target} |
|
add_library({name} |
|
{srcs}) |
|
target_include_directories({name} PUBLIC |
|
{include_dirs} |
|
) |
|
""".format( |
|
target=data["label"], |
|
name=fixed_library_name, |
|
srcs="\n ".join(fixed_srcs), |
|
include_dirs="\n ".join(fixed_include_dirs), |
|
) |
|
|
|
with open("CMakeLists.txt", "w") as f: |
|
f.write(contents) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |