blob: 213df337a3586bc9b3aa9897f8eb886edc9995ed [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This script assists with converting from Bazel BUILD files to CMakeLists.txt.
Bazel BUILD files should, where possible, be written to use simple features
that can be directly evaluated and avoid more advanced features like
variables, list comprehensions, etc.
Generated CMake files will be similar in structure to their source BUILD
files by using the functions in build_tools/cmake/ that imitate corresponding
Bazel rules (e.g. cc_library -> iree_cc_library.cmake).
For usage, see:
python3 build_tools/bazel_to_cmake/bazel_to_cmake.py --help
"""
# pylint: disable=missing-docstring
import argparse
import datetime
import os
import re
import sys
import bazel_to_cmake_converter
repo_root = None
EDIT_BLOCKING_PATTERN = re.compile(
r"bazel[\s_]*to[\s_]*cmake[\s_]*:?[\s_]*do[\s_]*not[\s_]*edit",
flags=re.IGNORECASE)
def parse_arguments():
global repo_root
parser = argparse.ArgumentParser(
description="Bazel to CMake conversion helper.")
parser.add_argument(
"--preview",
help="Prints results instead of writing files",
action="store_true",
default=False)
# TODO(b/149926655): Invert the default to be strict and rename this flag.
parser.add_argument(
"--strict",
help="Does not try to generate files where it cannot convert completely",
action="store_true",
default=False)
# Specify only one of these (defaults to --root_dir=iree).
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--dir",
help="Converts the BUILD file in the given directory",
default=None)
group.add_argument(
"--root_dir",
help="Converts all BUILD files under a root directory (defaults to iree/)",
default="iree")
# TODO(scotttodd): --check option that returns success/failure depending on
# if files match the converted versions
args = parser.parse_args()
# --dir takes precedence over --root_dir.
# They are mutually exclusive, but the default value is still set.
if args.dir:
args.root_dir = None
return args
def setup_environment():
"""Sets up some environment globals."""
global repo_root
# Determine the repository root (two dir-levels up).
repo_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def log(*args, **kwargs):
print(*args, **kwargs, file=sys.stderr)
def convert_directory_tree(root_directory_path, write_files, strict):
log(f"convert_directory_tree: {root_directory_path}")
for root, _, _ in os.walk(root_directory_path):
convert_directory(root, write_files, strict)
def convert_directory(directory_path, write_files, strict):
if not os.path.isdir(directory_path):
raise FileNotFoundError(f"Cannot find directory '{directory_path}'")
build_file_path = os.path.join(directory_path, "BUILD")
cmakelists_file_path = os.path.join(directory_path, "CMakeLists.txt")
if not os.path.isfile(build_file_path):
# No Bazel BUILD file in this directory to convert, skip.
return
global repo_root
rel_build_file_path = os.path.relpath(build_file_path, repo_root)
rel_cmakelists_file_path = os.path.relpath(cmakelists_file_path, repo_root)
log(f"Converting {rel_build_file_path} to {rel_cmakelists_file_path}")
cmake_file_exists = os.path.isfile(cmakelists_file_path)
copyright_line = f"# Copyright {datetime.date.today().year} Google LLC"
write_allowed = write_files
if cmake_file_exists:
with open(cmakelists_file_path) as f:
for i, line in enumerate(f):
if line.startswith("# Copyright"):
copyright_line = line.rstrip()
if EDIT_BLOCKING_PATTERN.search(line):
log(f" {rel_cmakelists_file_path} already exists, and "
f"line {i + 1}: '{line.strip()}' prevents edits. "
f"Falling back to preview")
write_allowed = False
if write_allowed:
# TODO(scotttodd): Attempt to merge instead of overwrite?
# Existing CMakeLists.txt may have special logic that should be preserved
if cmake_file_exists:
log(f" {rel_cmakelists_file_path} already exists; overwriting")
else:
log(f" {rel_cmakelists_file_path} does not exist yet; creating")
log("")
with open(build_file_path, "rt") as build_file:
build_file_code = compile(build_file.read(), build_file_path, "exec")
try:
converted_text = bazel_to_cmake_converter.convert_build_file(
build_file_code, copyright_line, strict=strict)
if write_allowed:
with open(cmakelists_file_path, "wt") as cmakelists_file:
cmakelists_file.write(converted_text)
else:
print(converted_text, end="")
except (NameError, NotImplementedError) as e:
log(f"Failed to convert {rel_build_file_path}.", end=" ")
log("Missing a rule handler in bazel_to_cmake.py?")
log(f" Reason: `{type(e).__name__}: {e}`")
except KeyError as e:
log(f"Failed to convert {rel_build_file_path}.", end=" ")
log("Missing a conversion in bazel_to_cmake_targets.py?")
log(f" Reason: `{type(e).__name__}: {e}`")
def main(args):
"""Runs Bazel to CMake conversion."""
global repo_root
write_files = not args.preview
if args.root_dir:
convert_directory_tree(
os.path.join(repo_root, args.root_dir), write_files, args.strict)
elif args.dir:
convert_directory(
os.path.join(repo_root, args.dir), write_files, args.strict)
if __name__ == "__main__":
setup_environment()
main(parse_arguments())