Bug 1684463 - [devtools] Part 1: Shorten the _createAttribute function by refactoring...
[gecko.git] / configure.py
blob5622500a0f6f054b4d4f4b0c872f01a2892c993a
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__ import absolute_import, print_function, unicode_literals
7 import codecs
8 import errno
9 import io
10 import itertools
11 import logging
12 import os
13 import sys
14 import textwrap
17 try:
18 from collections.abc import Iterable
19 except ImportError:
20 from collections import Iterable
23 base_dir = os.path.abspath(os.path.dirname(__file__))
24 sys.path.insert(0, os.path.join(base_dir, "python", "mozboot"))
25 sys.path.insert(0, os.path.join(base_dir, "python", "mozbuild"))
26 sys.path.insert(0, os.path.join(base_dir, "third_party", "python", "six"))
27 from mozbuild.configure import (
28 ConfigureSandbox,
29 TRACE,
31 from mozbuild.pythonutil import iter_modules_in_path
32 from mozbuild.backend.configenvironment import PartialConfigEnvironment
33 from mozbuild.util import write_indented_repr
34 import mozpack.path as mozpath
35 import six
38 def main(argv):
39 config = {}
41 if "OLD_CONFIGURE" not in os.environ:
42 os.environ["OLD_CONFIGURE"] = os.path.join(base_dir, "old-configure")
44 sandbox = ConfigureSandbox(config, os.environ, argv)
46 clobber_file = "CLOBBER"
47 if not os.path.exists(clobber_file):
48 # Simply touch the file.
49 with open(clobber_file, "a"):
50 pass
52 if os.environ.get("MOZ_CONFIGURE_TRACE"):
53 sandbox._logger.setLevel(TRACE)
55 sandbox.run(os.path.join(os.path.dirname(__file__), "moz.configure"))
57 if sandbox._help:
58 return 0
60 logging.getLogger("moz.configure").info("Creating config.status")
62 old_js_configure_substs = config.pop("OLD_JS_CONFIGURE_SUBSTS", None)
63 old_js_configure_defines = config.pop("OLD_JS_CONFIGURE_DEFINES", None)
64 if old_js_configure_substs or old_js_configure_defines:
65 js_config = config.copy()
66 pwd = os.getcwd()
67 try:
68 try:
69 os.makedirs("js/src")
70 except OSError as e:
71 if e.errno != errno.EEXIST:
72 raise
74 os.chdir("js/src")
75 js_config["OLD_CONFIGURE_SUBSTS"] = old_js_configure_substs
76 js_config["OLD_CONFIGURE_DEFINES"] = old_js_configure_defines
77 # The build system frontend expects $objdir/js/src/config.status
78 # to have $objdir/js/src as topobjdir.
79 # We want forward slashes on all platforms.
80 js_config["TOPOBJDIR"] += "/js/src"
81 config_status(js_config, execute=False)
82 finally:
83 os.chdir(pwd)
85 return config_status(config)
88 def check_unicode(obj):
89 """Recursively check that all strings in the object are unicode strings."""
90 if isinstance(obj, dict):
91 result = True
92 for k, v in six.iteritems(obj):
93 if not check_unicode(k):
94 print("%s key is not unicode." % k, file=sys.stderr)
95 result = False
96 elif not check_unicode(v):
97 print("%s value is not unicode." % k, file=sys.stderr)
98 result = False
99 return result
100 if isinstance(obj, bytes):
101 return False
102 if isinstance(obj, six.text_type):
103 return True
104 if isinstance(obj, Iterable):
105 return all(check_unicode(o) for o in obj)
106 return True
109 def config_status(config, execute=True):
110 # Sanitize config data to feed config.status
111 # Ideally, all the backend and frontend code would handle the booleans, but
112 # there are so many things involved, that it's easier to keep config.status
113 # untouched for now.
114 def sanitize_config(v):
115 if v is True:
116 return "1"
117 if v is False:
118 return ""
119 # Serialize types that look like lists and tuples as lists.
120 if not isinstance(v, (bytes, six.text_type, dict)) and isinstance(v, Iterable):
121 return list(v)
122 return v
124 sanitized_config = {}
125 sanitized_config["substs"] = {
126 k: sanitize_config(v)
127 for k, v in six.iteritems(config)
128 if k
129 not in (
130 "DEFINES",
131 "TOPSRCDIR",
132 "TOPOBJDIR",
133 "CONFIG_STATUS_DEPS",
134 "OLD_CONFIGURE_SUBSTS",
135 "OLD_CONFIGURE_DEFINES",
138 for k, v in config["OLD_CONFIGURE_SUBSTS"]:
139 sanitized_config["substs"][k] = sanitize_config(v)
140 sanitized_config["defines"] = {
141 k: sanitize_config(v) for k, v in six.iteritems(config["DEFINES"])
143 for k, v in config["OLD_CONFIGURE_DEFINES"]:
144 sanitized_config["defines"][k] = sanitize_config(v)
145 sanitized_config["topsrcdir"] = config["TOPSRCDIR"]
146 sanitized_config["topobjdir"] = config["TOPOBJDIR"]
147 sanitized_config["mozconfig"] = config.get("MOZCONFIG")
149 if not check_unicode(sanitized_config):
150 print("Configuration should be all unicode.", file=sys.stderr)
151 print("Please file a bug for the above.", file=sys.stderr)
152 sys.exit(1)
154 # Some values in sanitized_config also have more complex types, such as
155 # EnumString, which using when calling config_status would currently
156 # break the build, as well as making it inconsistent with re-running
157 # config.status, for which they are normalized to plain strings via
158 # indented_repr. Likewise for non-dict non-string iterables being
159 # converted to lists.
160 def normalize(obj):
161 if isinstance(obj, dict):
162 return {k: normalize(v) for k, v in six.iteritems(obj)}
163 if isinstance(obj, six.text_type):
164 return six.text_type(obj)
165 if isinstance(obj, Iterable):
166 return [normalize(o) for o in obj]
167 return obj
169 sanitized_config = normalize(sanitized_config)
171 # Create config.status. Eventually, we'll want to just do the work it does
172 # here, when we're able to skip configure tests/use cached results/not rely
173 # on autoconf.
174 with codecs.open("config.status", "w", "utf-8") as fh:
175 fh.write(
176 textwrap.dedent(
177 """\
178 #!%(python)s
179 # coding=utf-8
180 from __future__ import unicode_literals
183 % {"python": config["PYTHON3"]}
185 for k, v in sorted(six.iteritems(sanitized_config)):
186 fh.write("%s = " % k)
187 write_indented_repr(fh, v)
188 fh.write(
189 "__all__ = ['topobjdir', 'topsrcdir', 'defines', " "'substs', 'mozconfig']"
192 if execute:
193 fh.write(
194 textwrap.dedent(
196 if __name__ == '__main__':
197 from mozbuild.util import patch_main
198 patch_main()
199 from mozbuild.config_status import config_status
200 args = dict([(name, globals()[name]) for name in __all__])
201 config_status(**args)
206 partial_config = PartialConfigEnvironment(config["TOPOBJDIR"])
207 partial_config.write_vars(sanitized_config)
209 # Write out a file so the build backend knows to re-run configure when
210 # relevant Python changes.
211 with io.open("config_status_deps.in", "w", encoding="utf-8", newline="\n") as fh:
212 for f in sorted(
213 itertools.chain(
214 config["CONFIG_STATUS_DEPS"],
215 iter_modules_in_path(config["TOPOBJDIR"], config["TOPSRCDIR"]),
218 fh.write("%s\n" % mozpath.normpath(f))
220 # Other things than us are going to run this file, so we need to give it
221 # executable permissions.
222 os.chmod("config.status", 0o755)
223 if execute:
224 from mozbuild.config_status import config_status
226 return config_status(args=[], **sanitized_config)
227 return 0
230 if __name__ == "__main__":
231 sys.exit(main(sys.argv))