mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Swift: cmake generator for better IDE support
A cmake generator in bazel is introduced allowing to import the Swift extractor as a CMake project while keeping Bazel files as the source of truth for the build. Using the CMake project: * requires bazel and clang to be installed and available on the command line * does not require a previous bazel build, however * will require a CMake reconfiguration for changes to generated code (like changes to the schema)
This commit is contained in:
0
misc/bazel/cmake/BUILD.bazel
Normal file
0
misc/bazel/cmake/BUILD.bazel
Normal file
250
misc/bazel/cmake/cmake.bzl
Normal file
250
misc/bazel/cmake/cmake.bzl
Normal file
@@ -0,0 +1,250 @@
|
||||
CmakeInfo = provider(
|
||||
fields = {
|
||||
"name": "",
|
||||
"inputs": "",
|
||||
"kind": "",
|
||||
"modifier": "",
|
||||
"hdrs": "",
|
||||
"srcs": "",
|
||||
"deps": "",
|
||||
"includes": "",
|
||||
"stripped_includes": "",
|
||||
"imported_static_libs": "",
|
||||
"imported_dynamic_libs": "",
|
||||
"copts": "",
|
||||
"linkopts": "",
|
||||
"force_cxx_compilation": "",
|
||||
"defines": "",
|
||||
"local_defines": "",
|
||||
"transitive_deps": "",
|
||||
},
|
||||
)
|
||||
|
||||
def _cmake_name(label):
|
||||
return ("%s_%s_%s" % (label.workspace_name, label.package, label.name)).replace("/", "_")
|
||||
|
||||
def _cmake_file(file):
|
||||
if not file.is_source:
|
||||
return "${BAZEL_EXEC_ROOT}/" + file.path
|
||||
return _cmake_path(file.path)
|
||||
|
||||
def _cmake_path(path):
|
||||
if path.startswith("external/"):
|
||||
return "${BAZEL_OUTPUT_BASE}/" + path
|
||||
return "${BAZEL_WORKSPACE}/" + path
|
||||
|
||||
def _file_kind(file):
|
||||
ext = file.extension
|
||||
if ext in ("c", "cc", "cpp"):
|
||||
return "src"
|
||||
if ext in ("h", "hh", "hpp", "def", "inc"):
|
||||
return "hdr"
|
||||
if ext == "a":
|
||||
return "static_lib"
|
||||
if ext in ("so", "dylib"):
|
||||
return "dynamic_lib"
|
||||
return None
|
||||
|
||||
def _cmake_aspect_impl(target, ctx):
|
||||
if not ctx.rule.kind.startswith("cc_"):
|
||||
return [CmakeInfo(name = None, transitive_deps = depset())]
|
||||
|
||||
name = _cmake_name(ctx.label)
|
||||
|
||||
is_macos = "darwin" in ctx.var["TARGET_CPU"]
|
||||
|
||||
is_binary = ctx.rule.kind == "cc_binary"
|
||||
force_cxx_compilation = "force_cxx_compilation" in ctx.rule.attr.features
|
||||
attr = ctx.rule.attr
|
||||
srcs = attr.srcs + getattr(attr, "hdrs", []) + getattr(attr, "textual_hdrs", [])
|
||||
srcs = [f for src in srcs for f in src.files.to_list()]
|
||||
inputs = [f for f in srcs if not f.is_source or f.path.startswith("external/")]
|
||||
by_kind = {}
|
||||
for f in srcs:
|
||||
by_kind.setdefault(_file_kind(f), []).append(_cmake_file(f))
|
||||
hdrs = by_kind.get("hdr", [])
|
||||
srcs = by_kind.get("src", [])
|
||||
static_libs = by_kind.get("static_lib", [])
|
||||
dynamic_libs = by_kind.get("dynamic_lib", [])
|
||||
if not srcs and is_binary:
|
||||
empty = ctx.actions.declare_file(name + "_empty.cpp")
|
||||
ctx.actions.write(empty, "")
|
||||
inputs.append(empty)
|
||||
srcs = [_cmake_file(empty)]
|
||||
deps = ctx.rule.attr.deps if hasattr(ctx.rule.attr, "deps") else []
|
||||
|
||||
cxx_compilation = force_cxx_compilation or any([not src.endswith(".c") for src in srcs])
|
||||
|
||||
copts = ctx.fragments.cpp.copts + (ctx.fragments.cpp.cxxopts if cxx_compilation else ctx.fragments.cpp.conlyopts)
|
||||
copts += [ctx.expand_make_variables("copts", o, {}) for o in ctx.rule.attr.copts]
|
||||
|
||||
linkopts = ctx.fragments.cpp.linkopts
|
||||
linkopts += [ctx.expand_make_variables("linkopts", o, {}) for o in ctx.rule.attr.linkopts]
|
||||
|
||||
compilation_ctx = target[CcInfo].compilation_context
|
||||
includes = compilation_ctx.system_includes.to_list()
|
||||
includes += compilation_ctx.includes.to_list()
|
||||
includes += compilation_ctx.quote_includes.to_list()
|
||||
includes += [opt[2:] for opt in copts if opt.startswith("-I")]
|
||||
|
||||
# strip prefix is special, as in bazel it creates a _virtual_includes directory with symlinks
|
||||
# as we want to avoid relying on bazel having done that, we must undo that mechanism
|
||||
# also for some reason cmake fails to propagate these with target_include_directories,
|
||||
# so we propagate them ourselvels by using the stripped_includes field
|
||||
# also, including '.' on macOS creates a conflict between a `version` file at the root of the
|
||||
# workspace and a standard library, so we skip that (and hardcode an `-iquote .` in setup.cmake)
|
||||
includes = [_cmake_path(i) for i in includes if not ("/_virtual_includes/" in i or (is_macos and i == "."))]
|
||||
stripped_includes = []
|
||||
if getattr(ctx.rule.attr, "strip_include_prefix", ""):
|
||||
prefix = ctx.rule.attr.strip_include_prefix.strip("/")
|
||||
if ctx.label.workspace_name:
|
||||
stripped_includes = [
|
||||
"${BAZEL_OUTPUT_BASE}/external/%s/%s" % (ctx.label.workspace_name, prefix), # source
|
||||
"${BAZEL_EXEC_ROOT}/%s/external/%s/%s" % (ctx.var["BINDIR"], ctx.label.workspace_name, prefix), # generated
|
||||
]
|
||||
else:
|
||||
stripped_includes = [
|
||||
prefix, # source
|
||||
"${BAZEL_EXEC_ROOT}/%s/%s" % (ctx.var["BINDIR"], prefix), # generated
|
||||
]
|
||||
|
||||
copts = [opt for opt in copts if not opt.startswith("-I")]
|
||||
deps = [dep[CmakeInfo] for dep in deps if CmakeInfo in dep]
|
||||
|
||||
# by the book this should be done with depsets, but so far the performance implication is negligible
|
||||
for dep in deps:
|
||||
if dep.name:
|
||||
stripped_includes += dep.stripped_includes
|
||||
includes += stripped_includes
|
||||
|
||||
return [
|
||||
CmakeInfo(
|
||||
name = name,
|
||||
inputs = inputs,
|
||||
kind = "executable" if is_binary else "library",
|
||||
modifier = "INTERFACE" if not srcs and not is_binary else "",
|
||||
hdrs = hdrs,
|
||||
srcs = srcs,
|
||||
deps = [dep for dep in deps if dep.name != None],
|
||||
includes = includes,
|
||||
stripped_includes = stripped_includes,
|
||||
imported_static_libs = static_libs,
|
||||
imported_dynamic_libs = dynamic_libs,
|
||||
copts = copts,
|
||||
linkopts = linkopts,
|
||||
defines = compilation_ctx.defines.to_list(),
|
||||
local_defines = compilation_ctx.local_defines.to_list(),
|
||||
force_cxx_compilation = force_cxx_compilation,
|
||||
transitive_deps = depset(deps, transitive = [dep.transitive_deps for dep in deps]),
|
||||
),
|
||||
]
|
||||
|
||||
cmake_aspect = aspect(
|
||||
implementation = _cmake_aspect_impl,
|
||||
attr_aspects = ["deps"],
|
||||
fragments = ["cpp"],
|
||||
)
|
||||
|
||||
def _map_cmake_info(info):
|
||||
args = " ".join([info.name, info.modifier] + info.hdrs + info.srcs).strip()
|
||||
commands = [
|
||||
"add_%s(%s)" % (info.kind, args),
|
||||
]
|
||||
if info.imported_static_libs and info.imported_dynamic_libs:
|
||||
commands += [
|
||||
"if(BUILD_SHARED_LIBS)",
|
||||
" target_link_libraries(%s %s %s)" %
|
||||
(info.name, info.modifier or "PUBLIC", " ".join(info.imported_dynamic_libs)),
|
||||
"else()",
|
||||
" target_link_libraries(%s %s %s)" %
|
||||
(info.name, info.modifier or "PUBLIC", " ".join(info.imported_static_libs)),
|
||||
"endif()",
|
||||
]
|
||||
elif info.imported_static_libs or info.imported_dynamic_libs:
|
||||
commands += [
|
||||
"target_link_libraries(%s %s %s)" %
|
||||
(info.name, info.modifier or "PUBLIC", " ".join(info.imported_dynamic_lib + info.imported_static_libs)),
|
||||
]
|
||||
if info.deps:
|
||||
libs = {}
|
||||
if info.modifier == "INTERFACE":
|
||||
libs = {"INTERFACE": [lib.name for lib in info.deps]}
|
||||
else:
|
||||
for lib in info.deps:
|
||||
libs.setdefault(lib.modifier, []).append(lib.name)
|
||||
for modifier, names in libs.items():
|
||||
commands += [
|
||||
"target_link_libraries(%s %s %s)" % (info.name, modifier or "PUBLIC", " ".join(names)),
|
||||
]
|
||||
if info.includes:
|
||||
commands += [
|
||||
"target_include_directories(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.includes)),
|
||||
]
|
||||
if info.copts and info.modifier != "INTERFACE":
|
||||
commands += [
|
||||
"target_compile_options(%s PRIVATE %s)" % (info.name, " ".join(info.copts)),
|
||||
]
|
||||
if info.linkopts:
|
||||
commands += [
|
||||
"target_link_options(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.linkopts)),
|
||||
]
|
||||
if info.force_cxx_compilation and any([f.endswith(".c") for f in info.srcs]):
|
||||
commands += [
|
||||
"set_source_files_properties(%s PROPERTIES LANGUAGE CXX)" % " ".join([f for f in info.srcs if f.endswith(".c")]),
|
||||
]
|
||||
if info.defines:
|
||||
commands += [
|
||||
"target_compile_definitions(%s %s %s)" % (info.name, info.modifier or "PUBLIC", " ".join(info.defines)),
|
||||
]
|
||||
if info.local_defines:
|
||||
commands += [
|
||||
"target_compile_definitions(%s %s %s)" % (info.name, info.modifier or "PRIVATE", " ".join(info.local_defines)),
|
||||
]
|
||||
return commands
|
||||
|
||||
GeneratedCmakeFiles = provider(
|
||||
fields = {
|
||||
"files": "",
|
||||
},
|
||||
)
|
||||
|
||||
def _generate_cmake_impl(ctx):
|
||||
commands = []
|
||||
inputs = []
|
||||
|
||||
infos = {}
|
||||
for dep in ctx.attr.targets:
|
||||
for info in [dep[CmakeInfo]] + dep[CmakeInfo].transitive_deps.to_list():
|
||||
if info.name != None:
|
||||
inputs += info.inputs
|
||||
infos[info.name] = info
|
||||
|
||||
for info in infos.values():
|
||||
commands += _map_cmake_info(info)
|
||||
commands.append("")
|
||||
|
||||
for include in ctx.attr.includes:
|
||||
for file in include[GeneratedCmakeFiles].files.to_list():
|
||||
inputs.append(file)
|
||||
commands.append("include(${BAZEL_EXEC_ROOT}/%s)" % file.path)
|
||||
|
||||
# we want to use a run or run_shell action to register a bunch of files like inputs, but we cannot write all
|
||||
# in a shell command as we would hit the command size limit. So we first write the file and then copy it with
|
||||
# the dummy inputs
|
||||
tmp_output = ctx.actions.declare_file(ctx.label.name + ".cmake~")
|
||||
output = ctx.actions.declare_file(ctx.label.name + ".cmake")
|
||||
ctx.actions.write(tmp_output, "\n".join(commands))
|
||||
ctx.actions.run_shell(outputs = [output], inputs = inputs + [tmp_output], command = "cp %s %s" % (tmp_output.path, output.path))
|
||||
|
||||
return [
|
||||
DefaultInfo(files = depset([output])),
|
||||
GeneratedCmakeFiles(files = depset([output])),
|
||||
]
|
||||
|
||||
generate_cmake = rule(
|
||||
implementation = _generate_cmake_impl,
|
||||
attrs = {
|
||||
"targets": attr.label_list(aspects = [cmake_aspect]),
|
||||
"includes": attr.label_list(providers = [GeneratedCmakeFiles]),
|
||||
},
|
||||
)
|
||||
21
misc/bazel/cmake/setup.cmake
Normal file
21
misc/bazel/cmake/setup.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
option(BUILD_SHARED_LIBS "" 0)
|
||||
|
||||
execute_process(COMMAND bazel info workspace OUTPUT_VARIABLE BAZEL_WORKSPACE COMMAND_ERROR_IS_FATAL ANY OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(COMMAND bazel info output_base OUTPUT_VARIABLE BAZEL_OUTPUT_BASE COMMAND_ERROR_IS_FATAL ANY OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
string(REPLACE "-" "_" BAZEL_EXEC_ROOT ${PROJECT_NAME})
|
||||
set(BAZEL_EXEC_ROOT ${BAZEL_OUTPUT_BASE}/execroot/${BAZEL_EXEC_ROOT})
|
||||
|
||||
execute_process(COMMAND bazel query "kind(generate_cmake, //...)" OUTPUT_VARIABLE BAZEL_GENERATE_CMAKE_TARGETS COMMAND_ERROR_IS_FATAL ANY OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND bazel build ${BAZEL_GENERATE_CMAKE_TARGETS} COMMAND_ERROR_IS_FATAL ANY)
|
||||
|
||||
string(REPLACE "//" "" BAZEL_GENERATE_CMAKE_TARGETS "${BAZEL_GENERATE_CMAKE_TARGETS}")
|
||||
string(REPLACE ":" "/" BAZEL_GENERATE_CMAKE_TARGETS "${BAZEL_GENERATE_CMAKE_TARGETS}")
|
||||
|
||||
foreach (target ${BAZEL_GENERATE_CMAKE_TARGETS})
|
||||
include(${BAZEL_WORKSPACE}/bazel-bin/${target}.cmake)
|
||||
endforeach ()
|
||||
|
||||
if (CMAKE_EXPORT_COMPILE_COMMANDS)
|
||||
file(CREATE_LINK ${PROJECT_BINARY_DIR}/compile_commands.json ${PROJECT_SOURCE_DIR}/compile_commands.json)
|
||||
endif ()
|
||||
10
swift/.gitignore
vendored
10
swift/.gitignore
vendored
@@ -3,3 +3,13 @@
|
||||
|
||||
# output files created by running tests
|
||||
*.o
|
||||
|
||||
# compilation database
|
||||
compile_commands.json
|
||||
|
||||
# CLion project data and build directories
|
||||
/.idea
|
||||
/cmake*
|
||||
|
||||
# VSCode default build directory
|
||||
/build
|
||||
|
||||
14
swift/CMakeLists.txt
Normal file
14
swift/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# this uses generated cmake files to setup cmake compilation of the swift extractor
|
||||
# this is provided solely for IDE integration
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_CXX_COMPILER clang++)
|
||||
|
||||
project(codeql)
|
||||
|
||||
include(../misc/bazel/cmake/setup.cmake)
|
||||
@@ -1,4 +1,5 @@
|
||||
load("//swift:rules.bzl", "swift_cc_binary")
|
||||
load("//misc/bazel/cmake:cmake.bzl", "generate_cmake")
|
||||
|
||||
swift_cc_binary(
|
||||
name = "extractor",
|
||||
@@ -9,8 +10,13 @@ swift_cc_binary(
|
||||
visibility = ["//swift:__pkg__"],
|
||||
deps = [
|
||||
"//swift/extractor/infra",
|
||||
"//swift/extractor/visitors",
|
||||
"//swift/extractor/remapping",
|
||||
"//swift/extractor/visitors",
|
||||
"//swift/tools/prebuilt:swift-llvm-support",
|
||||
],
|
||||
)
|
||||
|
||||
generate_cmake(
|
||||
name = "cmake",
|
||||
targets = [":extractor"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user