diff --git a/utils/bazel/configure.bzl b/utils/bazel/configure.bzl --- a/utils/bazel/configure.bzl +++ b/utils/bazel/configure.bzl @@ -33,11 +33,10 @@ "XCore", ] -def _overlay_directories(repository_ctx): +def _llvm_configure_impl(repository_ctx): src_path = repository_ctx.path(Label("//:WORKSPACE")).dirname bazel_path = src_path.get_child("utils").get_child("bazel") - overlay_path = bazel_path.get_child("llvm-project-overlay") - script_path = bazel_path.get_child("overlay_directories.py") + script_path = bazel_path.get_child("configure_overlay.py") python_bin = repository_ctx.which("python3") if not python_bin: @@ -48,20 +47,19 @@ if not python_bin: fail("Failed to find python3 binary") + # Create a starlark file with the requested LLVM targets. + targets = repository_ctx.attr.targets cmd = [ python_bin, script_path, - "--src", - src_path, - "--overlay", - overlay_path, - "--target", + "--dst", ".", - ] + "--targets", + ] + targets exec_result = repository_ctx.execute(cmd, timeout = 20) if exec_result.return_code != 0: - fail(("Failed to execute overlay script: '{cmd}'\n" + + fail(("Failed to execute overlay configure script: '{cmd}'\n" + "Exited with code {return_code}\n" + "stdout:\n{stdout}\n" + "stderr:\n{stderr}\n").format( @@ -71,101 +69,6 @@ stderr = exec_result.stderr, )) -def _extract_cmake_settings(repository_ctx, llvm_cmake): - # The list to be written to vars.bzl - # `CMAKE_CXX_STANDARD` may be used from WORKSPACE for the toolchain. - c = { - "CMAKE_CXX_STANDARD": None, - "LLVM_VERSION_MAJOR": None, - "LLVM_VERSION_MINOR": None, - "LLVM_VERSION_PATCH": None, - } - - # It would be easier to use external commands like sed(1) and python. - # For portability, the parser should run on Starlark. - llvm_cmake_path = repository_ctx.path(Label("//:" + llvm_cmake)) - for line in repository_ctx.read(llvm_cmake_path).splitlines(): - # Extract "set ( FOO bar ... " - setfoo = line.partition("(") - if setfoo[1] != "(": - continue - if setfoo[0].strip().lower() != "set": - continue - - # `kv` is assumed as \s*KEY\s+VAL\s*\).* - # Typical case is like - # LLVM_REQUIRED_CXX_STANDARD 17) - # Possible case -- It should be ignored. - # CMAKE_CXX_STANDARD ${...} CACHE STRING "...") - kv = setfoo[2].strip() - i = kv.find(" ") - if i < 0: - continue - k = kv[:i] - - # Prefer LLVM_REQUIRED_CXX_STANDARD instead of CMAKE_CXX_STANDARD - if k == "LLVM_REQUIRED_CXX_STANDARD": - k = "CMAKE_CXX_STANDARD" - c[k] = None - if k not in c: - continue - - # Skip if `CMAKE_CXX_STANDARD` is set with - # `LLVM_REQUIRED_CXX_STANDARD`. - # Then `v` will not be desired form, like "${...} CACHE" - if c[k] != None: - continue - - # Pick up 1st word as the value. - # Note: It assumes unquoted word. - v = kv[i:].strip().partition(")")[0].partition(" ")[0] - c[k] = v - - # Synthesize `LLVM_VERSION` for convenience. - c["LLVM_VERSION"] = "{}.{}.{}".format( - c["LLVM_VERSION_MAJOR"], - c["LLVM_VERSION_MINOR"], - c["LLVM_VERSION_PATCH"], - ) - - return c - -def _write_dict_to_file(repository_ctx, filepath, header, vars): - # (fci + individual vars) + (fcd + dict items) + (fct) - fci = header - fcd = "\nllvm_vars={\n" - fct = "}\n" - - for k, v in vars.items(): - fci += '{} = "{}"\n'.format(k, v) - fcd += ' "{}": "{}",\n'.format(k, v) - - repository_ctx.file(filepath, content = fci + fcd + fct) - -def _llvm_configure_impl(repository_ctx): - _overlay_directories(repository_ctx) - - llvm_cmake = "llvm/CMakeLists.txt" - vars = _extract_cmake_settings( - repository_ctx, - llvm_cmake, - ) - - _write_dict_to_file( - repository_ctx, - filepath = "vars.bzl", - header = "# Generated from {}\n\n".format(llvm_cmake), - vars = vars, - ) - - # Create a starlark file with the requested LLVM targets. - targets = repository_ctx.attr.targets - repository_ctx.file( - "llvm/targets.bzl", - content = "llvm_targets = " + str(targets), - executable = False, - ) - llvm_configure = repository_rule( implementation = _llvm_configure_impl, local = True, diff --git a/utils/bazel/configure_overlay.py b/utils/bazel/configure_overlay.py new file mode 100644 --- /dev/null +++ b/utils/bazel/configure_overlay.py @@ -0,0 +1,148 @@ +#!/bin/python3 + +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +""" +Configure an overlay of the LLVM source tree that this file is part of and +the bazel files available in a subdirectory and generate the extra files +needed by the bazel build system. +""" + +import os +import sys +import argparse +from overlay_directories import overlay_directories + + +def _check_python_version(): + if sys.version_info[:2] < (3, 6): + raise RuntimeError( + "Must be invoked with a python 3.6+ interpreter but was {}" + + "Python {}".format(sys.version_info) + ) + + +def parse_arguments(): + parser = argparse.ArgumentParser( + description=""" + Configure an overlay of the LLVM source tree that this file is part of and + the bazel files available in a subdirectory and generate the extra files + needed by the bazel build system. + """ + ) + parser.add_argument( + "--dst", + required=True, + help="Directory in which to place the overlay.", + ) + parser.add_argument( + "--targets", + required=True, + nargs="+", + help="Space separated list of targets to build LLVM for.", + ) + + args = parser.parse_args() + return args + + +def _extract_cmake_settings(llvm_cmake_path): + # The list to be written to vars.bzl + # `CMAKE_CXX_STANDARD` may be used from WORKSPACE for the toolchain. + c = { + "CMAKE_CXX_STANDARD": None, + "LLVM_VERSION_MAJOR": None, + "LLVM_VERSION_MINOR": None, + "LLVM_VERSION_PATCH": None, + } + + with open(llvm_cmake_path, "r") as f: + for line in f: + # Extract "set ( FOO bar ... " + setfoo = line.partition("(") + if setfoo[1] != "(": + continue + if setfoo[0].strip().lower() != "set": + continue + + # `kv` is assumed as \s*KEY\s+VAL\s*\).* + # Typical case is like + # LLVM_REQUIRED_CXX_STANDARD 17) + # Possible case -- It should be ignored. + # CMAKE_CXX_STANDARD ${...} CACHE STRING "...") + kv = setfoo[2].strip() + i = kv.find(" ") + if i < 0: + continue + k = kv[:i] + + # Prefer LLVM_REQUIRED_CXX_STANDARD instead of CMAKE_CXX_STANDARD + if k == "LLVM_REQUIRED_CXX_STANDARD": + k = "CMAKE_CXX_STANDARD" + c[k] = None + if k not in c: + continue + + # Skip if `CMAKE_CXX_STANDARD` is set with + # `LLVM_REQUIRED_CXX_STANDARD`. + # Then `v` will not be desired form, like "${...} CACHE" + if c[k] is not None: + continue + + # Pick up 1st word as the value. + # Note: It assumes unquoted word. + v = kv[i:].strip().partition(")")[0].partition(" ")[0] + c[k] = v + + # Synthesize `LLVM_VERSION` for convenience. + c["LLVM_VERSION"] = "{}.{}.{}".format( + c["LLVM_VERSION_MAJOR"], + c["LLVM_VERSION_MINOR"], + c["LLVM_VERSION_PATCH"], + ) + + return c + + +def _write_dict_to_file(filepath, header, vars): + # (fci + individual vars) + (fcd + dict items) + (fct) + fci = header + fcd = "\nllvm_vars={\n" + fct = "}\n" + + for k, v in vars.items(): + fci += f'{k} = "{v}"\n' + fcd += f' "{k}": "{v}",\n' + + with open(filepath, "w") as f: + f.write(fci + fcd + fct) + + +def main(args): + file_dir = os.path.dirname(os.path.abspath(__file__)) + src_root = os.path.abspath(os.path.join(file_dir, "../..")) + overlay_path = os.path.join(file_dir, "llvm-project-overlay") + overlay_directories(src_root, overlay_path, args.dst) + + llvm_cmake = "llvm/CMakeLists.txt" + llvm_cmake_path = os.path.join(src_root, llvm_cmake) + vars = _extract_cmake_settings(llvm_cmake_path) + + _write_dict_to_file( + filepath=os.path.join(args.dst, "vars.bzl"), + header="# Generated from {}\n\n".format(llvm_cmake), + vars=vars, + ) + + # Create a starlark file with the requested LLVM targets. + targets_bzl_path = os.path.join(args.dst, "llvm", "targets.bzl") + targets_list_str = ", ".join([f'"{tgt}"' for tgt in args.targets]) + with open(targets_bzl_path, "w") as tgt_file: + print(f"llvm_targets = [{targets_list_str}]", end="", file=tgt_file) + + +if __name__ == "__main__": + _check_python_version() + main(parse_arguments()) diff --git a/utils/bazel/overlay_directories.py b/utils/bazel/overlay_directories.py --- a/utils/bazel/overlay_directories.py +++ b/utils/bazel/overlay_directories.py @@ -64,29 +64,30 @@ os.symlink(os.path.abspath(from_path), os.path.abspath(to_path)) -def main(args): - for root, dirs, files in os.walk(args.overlay): +def overlay_directories(src, overlay, target): + for root, dirs, files in os.walk(overlay): # We could do something more intelligent here and only symlink individual # files if the directory is present in both overlay and src. This could also # be generalized to an arbitrary number of directories without any # "src/overlay" distinction. In the current use case we only have two and # the overlay directory is always small, so putting that off for now. - rel_root = os.path.relpath(root, start=args.overlay) + rel_root = os.path.relpath(root, start=overlay) if rel_root != ".": - os.mkdir(os.path.join(args.target, rel_root)) + os.mkdir(os.path.join(target, rel_root)) for file in files: relpath = os.path.join(rel_root, file) - _symlink_abs(os.path.join(args.overlay, relpath), - os.path.join(args.target, relpath)) + _symlink_abs(os.path.join(overlay, relpath), + os.path.join(target, relpath)) - for src_entry in os.listdir(os.path.join(args.src, rel_root)): + for src_entry in os.listdir(os.path.join(src, rel_root)): if src_entry not in dirs: relpath = os.path.join(rel_root, src_entry) - _symlink_abs(os.path.join(args.src, relpath), - os.path.join(args.target, relpath)) + _symlink_abs(os.path.join(src, relpath), + os.path.join(target, relpath)) if __name__ == "__main__": _check_python_version() - main(parse_arguments()) + args = parse_arguments() + overlay_directories(args.src, args.overlay, args.target)