configure_cmake_file_impl.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. #!/usr/bin/env python3
  2. __copyright__ = """
  3. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  4. Exceptions. See /LICENSE for license information.
  5. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. """
  7. """Script to apply a set of defines to a CMake-style configure file.
  8. This serves as the action implementation for `configure_cmake_file.bzl`. See the
  9. documentation in the rule of that file for more details about how to use this,
  10. or `--help` on the script.
  11. """
  12. import argparse
  13. import re
  14. from typing import Dict
  15. # A set of CMake values that are considered "false".
  16. # Based on https://cmake.org/cmake/help/latest/command/if.html
  17. _CMAKE_FALSE_VALUES = {
  18. "",
  19. "0",
  20. "OFF",
  21. "NO",
  22. "N",
  23. "FALSE",
  24. "IGNORE",
  25. "NOTFOUND",
  26. }
  27. _VAR_AT_PATTERN = re.compile(r"@([^@]*)@")
  28. _VAR_DOLLAR_PATTERN = re.compile(r"${([^}]*)}")
  29. _DIRECTIVE_PATTERN = re.compile(
  30. r"^#(?P<indent>[ \t]*)cmakedefine\s+(?P<var>\w+)(?P<rest>.*)?$"
  31. )
  32. _DIRECTIVE_01_PATTERN = re.compile(
  33. r"^#(?P<indent>[ \t]*)cmakedefine01\s+(?P<var>\w+)$"
  34. )
  35. def _is_cmake_true(value: str) -> bool:
  36. """Returns true if the value is not a CMake false value.
  37. This is how CMake defines values as 'true' vs. 'false':
  38. https://cmake.org/cmake/help/latest/command/if.html
  39. """
  40. return (
  41. value.upper() not in _CMAKE_FALSE_VALUES
  42. and not value.upper().endswith("-NOTFOUND")
  43. )
  44. def _substitute_variables(text: str, defines: Dict[str, str]) -> str:
  45. """Substitutes @VAR@ and ${VAR} style variables in a string."""
  46. def repl(m: re.Match) -> str:
  47. return defines.get(str(m.group(1)), "")
  48. return re.sub(
  49. _VAR_AT_PATTERN, repl, re.sub(_VAR_DOLLAR_PATTERN, repl, text)
  50. )
  51. def main() -> None:
  52. parser = argparse.ArgumentParser()
  53. parser.add_argument("--src", required=True)
  54. parser.add_argument("--out", required=True)
  55. parser.add_argument("--defines", nargs=2, action="append", default=[])
  56. args = parser.parse_args()
  57. defines = dict(args.defines)
  58. with open(args.src, "r") as f:
  59. content = f.read()
  60. output_lines = []
  61. for line in content.splitlines():
  62. if m := re.match(_DIRECTIVE_PATTERN, line):
  63. var = m.group("var")
  64. if var in defines and _is_cmake_true(defines[var]):
  65. rest = _substitute_variables(m.group("rest"), defines)
  66. output_lines.append(
  67. "#%sdefine %s %s" % (m.group("indent"), var, rest)
  68. )
  69. else:
  70. # The variable is false, so leave it undefined.
  71. output_lines.append("/* #undef %s */" % var)
  72. elif m := re.match(_DIRECTIVE_01_PATTERN, line):
  73. var = m.group("var")
  74. indent = m.group("indent")
  75. if var in defines and _is_cmake_true(defines[var]):
  76. output_lines.append("#%sdefine %s 1" % (indent, var))
  77. else:
  78. output_lines.append("#%sdefine %s 0" % (indent, var))
  79. else:
  80. output_lines.append(_substitute_variables(line, defines))
  81. with open(args.out, "w") as f:
  82. f.write("\n".join(output_lines) + "\n")
  83. if __name__ == "__main__":
  84. main()