configure.impl.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. # Copyright (c) 2023 Project CHIP Authors
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # This file contains private utilities for use by the `configure` script.
  15. # It is self-contained and depends only on the Python 3 standard library.
  16. import collections
  17. import json
  18. import os
  19. import re
  20. import sys
  21. import urllib.request
  22. import zipfile
  23. def download(url, dest):
  24. urllib.request.urlretrieve(url, dest)
  25. def download_and_extract_zip(url, dest_dir, *member_names):
  26. file, *_ = urllib.request.urlretrieve(url)
  27. with zipfile.ZipFile(file) as zip:
  28. for member in zip.infolist():
  29. if member.filename in member_names:
  30. zip.extract(member, dest_dir)
  31. def process_project_args(gn_args_json_file, *params):
  32. processor = ProjectArgProcessor(gn_args_json_file)
  33. processor.process_defaults()
  34. processor.process_env()
  35. processor.process_parameters(params)
  36. for arg, value in processor.args.items():
  37. info(" - %s = %s" % (arg, value))
  38. print("%s = %s" % (arg, value))
  39. class ProjectArgProcessor:
  40. # Prefixes to try when mapping parameters to GN arguments
  41. BOOL_ARG_PREFIXES = ('is_', 'enable_', 'use_',
  42. 'chip_', 'chip_enable_', 'chip_use_', 'chip_config_',
  43. 'matter_', 'matter_enable_', 'matter_use_', 'matter_config_')
  44. GENERIC_ARG_PREFIXES = ('chip_', 'chip_config_', 'matter_', 'matter_config_')
  45. gn_args = {} # GN arg -> type ('s'tring, 'b'ool, 'i'integer, '[' list, '{' struct)
  46. args = collections.OrderedDict() # collected arguments
  47. def __init__(self, gn_args_json_file):
  48. # Parse `gn args --list --json` output and derive arg types from default values
  49. argtype = str.maketrans('"tf0123456789', 'sbbiiiiiiiiii')
  50. with open(gn_args_json_file) as fh:
  51. for arg in json.load(fh):
  52. self.gn_args[arg['name']] = arg['default']['value'][0].translate(argtype)
  53. def process_defaults(self):
  54. self.add_default('custom_toolchain', 'custom')
  55. def process_env(self):
  56. self.add_env_arg('target_cc', 'CC', 'cc')
  57. self.add_env_arg('target_cxx', 'CXX', 'cxx')
  58. self.add_env_arg('target_ar', 'AR', 'ar')
  59. self.add_env_arg('target_cflags', 'CPPFLAGS', list=True)
  60. self.add_env_arg('target_cflags_c', 'CFLAGS', list=True)
  61. self.add_env_arg('target_cflags_cc', 'CXXFLAGS', list=True)
  62. self.add_env_arg('target_cflags_objc', 'OBJCFLAGS', list=True)
  63. self.add_env_arg('target_ldflags', 'LDFLAGS', list=True)
  64. def add_arg(self, arg, value):
  65. # format strings and booleans as JSON, otherwise pass through as is
  66. self.args[arg] = (json.dumps(value) if self.gn_args.get(arg, 's') in 'sb' else value)
  67. def add_default(self, arg, value):
  68. """Add an argument, if supported by the GN build"""
  69. if arg in self.gn_args:
  70. self.add_arg(arg, value)
  71. def add_env_arg(self, arg, envvar, default=None, list=False):
  72. """Add an argument from an environment variable"""
  73. value = os.environ.get(envvar, default)
  74. if not value:
  75. return
  76. type = self.gn_args.get(arg, None)
  77. if not type:
  78. info("Warning: Not propagating %s, project has no build arg '%s'" % (envvar, arg))
  79. return
  80. self.args[arg] = json.dumps(value if type != '[' else value.split() if list else [value])
  81. def gn_arg(self, name, prefixes=(), type=None):
  82. """Finds the GN argument corresponding to a parameter name"""
  83. arg = name.translate(str.maketrans('-', '_'))
  84. candidates = [p + arg for p in (('',) + prefixes) if (p + arg) in self.gn_args]
  85. preferred = [c for c in candidates if self.gn_args[c] == type] if type else []
  86. match = next(iter(preferred + candidates), None)
  87. if not match:
  88. info("Warning: Project has no build arg '%s'" % arg)
  89. return match
  90. def process_triplet_parameter(self, name, value):
  91. if value is None:
  92. fail("Project option --%s requires an argument" % name)
  93. triplet = value.split('-')
  94. if len(triplet) not in (2, 3, 4):
  95. fail("Project option --%s argument must be a cpu-vendor-os[-abi] triplet" % name)
  96. prefix = 'host_' if name == 'build' else 'target_' # "host" has different meanings in GNU and GN!
  97. self.add_arg(prefix + 'cpu', triplet[0])
  98. self.add_arg(prefix + 'os', triplet[1 if len(triplet) == 2 else 2])
  99. def process_enable_parameter(self, name, value):
  100. arg = self.gn_arg(name[len('enable-'):], self.BOOL_ARG_PREFIXES, 'b')
  101. if not arg:
  102. return
  103. if self.gn_args[arg] != 'b':
  104. fail("Project build arg '%s' is not a boolean" % arg)
  105. if value != 'no' and value is not None:
  106. fail("Invalid argument for --%s, must be absent or 'no'" % name)
  107. self.add_arg(arg, value is None)
  108. def process_generic_parameter(self, name, value):
  109. arg = self.gn_arg(name, self.GENERIC_ARG_PREFIXES)
  110. if not arg:
  111. return
  112. if self.gn_args[arg] == 'b':
  113. fail("Project build arg '%s' is a boolean, use --enable-..." % arg)
  114. if value is None:
  115. fail("Project option --%s requires an argument" % name)
  116. self.add_arg(arg, value)
  117. def process_parameter(self, name, value):
  118. if name in ('build', 'host', 'target'):
  119. self.process_triplet_parameter(name, value)
  120. elif name.startswith('enable-'):
  121. self.process_enable_parameter(name, value)
  122. else:
  123. self.process_generic_parameter(name, value)
  124. def process_parameters(self, args):
  125. """Process GNU-style configure command line parameters"""
  126. for arg in args:
  127. match = re.fullmatch(r'--([a-z][a-z0-9-]*)(?:=(.*))?', arg)
  128. if not match:
  129. fail("Invalid argument: '%s'" % arg)
  130. self.process_parameter(match.group(1), match.group(2))
  131. def info(message):
  132. print(message, file=sys.stderr)
  133. def fail(message):
  134. info("Error: " + message)
  135. sys.exit(1)
  136. # `configure` invokes the top-level functions in this file by
  137. # passing the function name and arguments on the command line.
  138. [_, func, *args] = sys.argv
  139. globals()[func](*args)