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,169 @@ +#!/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 +import subprocess + + +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 _overlay_directories(dst_path, src_path): + bazel_path = os.path.join(src_path, "utils", "bazel") + overlay_path = os.path.join(bazel_path, "llvm-project-overlay") + script_path = os.path.join(bazel_path, "overlay_directories.py") + + cmd = [ + sys.executable, + script_path, + "--src", + src_path, + "--overlay", + overlay_path, + "--target", + dst_path, + ] + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + cmd_str = " ".join([str(arg) for arg in cmd]) + print(f"Failed to execute overlay script: '{cmd}'\n" + + f"Exited with code {e.returncode}\n" + + f"stdout:\n{e.stdout}\n" + + f"stderr:\n{e.stderr}\n", file=sys.stderr) + + +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] != 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_directories(args.dst, src_root) + + 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())