1# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- 2# vim: set filetype=python: 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 8m4 = check_prog( 9 "M4", 10 ( 11 "gm4", 12 "m4", 13 ), 14) 15 16 17@depends(mozconfig) 18def prepare_mozconfig(mozconfig): 19 if mozconfig["path"]: 20 items = {} 21 for key, value in mozconfig["vars"]["added"].items(): 22 items[key] = (value, "added") 23 for key, (old, value) in mozconfig["vars"]["modified"].items(): 24 items[key] = (value, "modified") 25 for t in ("env", "vars"): 26 for key in mozconfig[t]["removed"].keys(): 27 items[key] = (None, "removed " + t) 28 return items 29 30 31@depends("OLD_CONFIGURE", build_project) 32def old_configure(old_configure, build_project): 33 if not old_configure: 34 die("The OLD_CONFIGURE environment variable must be set") 35 36 # os.path.abspath in the sandbox will ensure forward slashes on Windows, 37 # which is actually necessary because this path actually ends up literally 38 # as $0, and backslashes there breaks autoconf's detection of the source 39 # directory. 40 old_configure = os.path.abspath(old_configure[0]) 41 if build_project == "js": 42 old_configure_dir = os.path.dirname(old_configure) 43 if not old_configure_dir.endswith("/js/src"): 44 old_configure = os.path.join( 45 old_configure_dir, "js", "src", os.path.basename(old_configure) 46 ) 47 return old_configure 48 49 50@depends(prepare_mozconfig, old_configure_assignments) 51@imports(_from="__builtin__", _import="open") 52@imports(_from="__builtin__", _import="print") 53@imports(_from="mozbuild.shellutil", _import="quote") 54def prepare_configure(mozconfig, old_configure_assignments): 55 with open("old-configure.vars", "w") as out: 56 log.debug("Injecting the following to old-configure:") 57 58 def inject(command): 59 print(command, file=out) # noqa Python 2vs3 60 log.debug("| %s", command) 61 62 if mozconfig: 63 inject("# start of mozconfig values") 64 for key, (value, action) in sorted(mozconfig.items()): 65 if action.startswith("removed "): 66 inject("unset %s # from %s" % (key, action[len("removed ") :])) 67 else: 68 inject("%s=%s # %s" % (key, quote(value), action)) 69 70 inject("# end of mozconfig values") 71 72 for k, v in old_configure_assignments: 73 inject("%s=%s" % (k, quote(v))) 74 75 76@template 77def old_configure_options(*options): 78 for opt in options: 79 option(opt, nargs="*", help="Help missing for old configure options") 80 81 @dependable 82 def all_options(): 83 return list(options) 84 85 return depends( 86 host_for_sub_configure, target_for_sub_configure, all_options, *options 87 ) 88 89 90@old_configure_options( 91 "--cache-file", 92 "--datadir", 93 "--enable-official-branding", 94 "--includedir", 95 "--libdir", 96 "--prefix", 97 "--with-branding", 98 "--with-distribution-id", 99 "--with-macbundlename-prefix", 100 "--x-includes", 101 "--x-libraries", 102) 103def prepare_configure_options(host, target, all_options, *options): 104 # old-configure only supports the options listed in @old_configure_options 105 # so we don't need to pass it every single option we've been passed. Only 106 # the ones that are not supported by python configure need to. 107 options = [ 108 value.format(name) 109 for name, value in zip(all_options, options) 110 if value.origin != "default" 111 ] + [host, target] 112 113 return namespace(options=options, all_options=all_options) 114 115 116@template 117def old_configure_for(old_configure_path, extra_env=None): 118 if extra_env is None: 119 extra_env = dependable(None) 120 121 @depends( 122 prepare_configure, 123 prepare_configure_options, 124 altered_path, 125 extra_env, 126 build_environment, 127 old_configure_path, 128 "MOZILLABUILD", 129 awk, 130 m4, 131 shell, 132 ) 133 @imports(_from="__builtin__", _import="compile") 134 @imports(_from="__builtin__", _import="open") 135 @imports(_from="__builtin__", _import="OSError") 136 @imports("glob") 137 @imports("itertools") 138 @imports("logging") 139 @imports("os") 140 @imports("subprocess") 141 @imports("sys") 142 @imports(_from="mozbuild.shellutil", _import="quote") 143 @imports(_from="mozbuild.shellutil", _import="split") 144 @imports(_from="tempfile", _import="NamedTemporaryFile") 145 @imports(_from="subprocess", _import="CalledProcessError") 146 @imports(_from="__builtin__", _import="exec") 147 def old_configure( 148 prepare_configure, 149 prepare_configure_options, 150 altered_path, 151 extra_env, 152 build_env, 153 old_configure, 154 mozillabuild, 155 awk, 156 m4, 157 shell, 158 ): 159 # Use prepare_configure to make lint happy 160 prepare_configure 161 refresh = True 162 if os.path.exists(old_configure): 163 mtime = os.path.getmtime(old_configure) 164 aclocal = os.path.join(build_env.topsrcdir, "build", "autoconf", "*.m4") 165 for input in itertools.chain( 166 ( 167 old_configure + ".in", 168 os.path.join(os.path.dirname(old_configure), "aclocal.m4"), 169 ), 170 glob.iglob(aclocal), 171 ): 172 if os.path.getmtime(input) > mtime: 173 break 174 else: 175 refresh = False 176 177 if refresh: 178 autoconf = os.path.join( 179 build_env.topsrcdir, "build", "autoconf", "autoconf.sh" 180 ) 181 log.info("Refreshing %s with %s", old_configure, autoconf) 182 env = dict(os.environ) 183 env["M4"] = m4 184 env["AWK"] = awk 185 env["AC_MACRODIR"] = os.path.join(build_env.topsrcdir, "build", "autoconf") 186 187 try: 188 script = subprocess.check_output( 189 [ 190 shell, 191 autoconf, 192 "--localdir=%s" % os.path.dirname(old_configure), 193 old_configure + ".in", 194 ], 195 # Fix the working directory, so that when m4 is called, that 196 # includes of relative paths are deterministically resolved 197 # relative to the directory containing old-configure. 198 cwd=os.path.dirname(old_configure), 199 env=env, 200 ) 201 except CalledProcessError as exc: 202 # Autoconf on win32 may break due to a bad $PATH. Let the user know 203 # their $PATH is suspect. 204 if mozillabuild: 205 mozillabuild_path = normsep(mozillabuild[0]) 206 sh_path = normsep(find_program("sh")) 207 if mozillabuild_path not in sh_path: 208 log.warning( 209 "The '{}msys/bin' directory is not first in $PATH. " 210 "This may cause autoconf to fail. ($PATH is currently " 211 "set to: {})".format(mozillabuild_path, os.environ["PATH"]) 212 ) 213 die("autoconf exited with return code {}".format(exc.returncode)) 214 215 if not script: 216 die( 217 "Generated old-configure is empty! Check that your autoconf 2.13 program works!" 218 ) 219 220 # Make old-configure append to config.log, where we put our own log. 221 # This could be done with a m4 macro, but it's way easier this way 222 script = script.replace(b">./config.log", b">>${CONFIG_LOG=./config.log}") 223 224 with NamedTemporaryFile( 225 mode="wb", 226 prefix=os.path.basename(old_configure), 227 dir=os.path.dirname(old_configure), 228 delete=False, 229 ) as fh: 230 fh.write(script) 231 232 try: 233 os.rename(fh.name, old_configure) 234 except OSError: 235 try: 236 # Likely the file already existed (on Windows). Retry after removing it. 237 os.remove(old_configure) 238 os.rename(fh.name, old_configure) 239 except OSError as e: 240 die("Failed re-creating old-configure: %s" % e.message) 241 242 cmd = [shell, old_configure] + prepare_configure_options.options 243 244 env = dict(os.environ) 245 246 # For debugging purpose, in case it's not what we'd expect. 247 log.debug("Running %s", quote(*cmd)) 248 249 # Our logging goes to config.log, the same file old.configure uses. 250 # We can't share the handle on the file, so close it. 251 logger = logging.getLogger("moz.configure") 252 config_log = None 253 for handler in logger.handlers: 254 if isinstance(handler, logging.FileHandler): 255 config_log = handler 256 config_log.close() 257 logger.removeHandler(config_log) 258 env["CONFIG_LOG"] = config_log.baseFilename 259 log_size = os.path.getsize(config_log.baseFilename) 260 break 261 262 if altered_path: 263 env["PATH"] = altered_path 264 265 if extra_env: 266 env.update(extra_env) 267 268 env["OLD_CONFIGURE_VARS"] = os.path.join( 269 build_env.topobjdir, "old-configure.vars" 270 ) 271 proc = subprocess.Popen( 272 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env 273 ) 274 while True: 275 line = proc.stdout.readline() 276 if not line: 277 break 278 log.info(line.rstrip()) 279 280 ret = proc.wait() 281 if ret: 282 with log.queue_debug(): 283 if config_log: 284 with open(config_log.baseFilename, "r") as fh: 285 fh.seek(log_size) 286 for line in fh: 287 log.debug(line.rstrip()) 288 log.error("old-configure failed") 289 sys.exit(ret) 290 291 if config_log: 292 # Create a new handler in append mode 293 handler = logging.FileHandler(config_log.baseFilename, mode="a", delay=True) 294 handler.setFormatter(config_log.formatter) 295 logger.addHandler(handler) 296 297 raw_config = { 298 "split": split, 299 "unique_list": unique_list, 300 } 301 with open("config.data", "r") as fh: 302 code = compile(fh.read(), "config.data", "exec") 303 exec(code, raw_config) 304 305 # Ensure all the flags known to old-configure appear in the 306 # @old_configure_options above. 307 all_options = set(prepare_configure_options.all_options) 308 for flag in raw_config["flags"]: 309 if flag not in all_options: 310 die( 311 "Missing option in `@old_configure_options` in %s: %s", 312 __file__, 313 flag, 314 ) 315 316 # If the code execution above fails, we want to keep the file around for 317 # debugging. 318 os.remove("config.data") 319 320 return namespace( 321 **{ 322 c: [ 323 (k[1:-1], v[1:-1] if isinstance(v, str) else v) 324 for k, v in raw_config[c] 325 ] 326 for c in ("substs", "defines") 327 } 328 ) 329 330 return old_configure 331 332 333old_configure = old_configure_for(old_configure) 334set_config("OLD_CONFIGURE_SUBSTS", old_configure.substs) 335set_config("OLD_CONFIGURE_DEFINES", old_configure.defines) 336 337 338# Assuming no other option is declared after this function, handle the 339# env options that were injected by mozconfig_options by creating dummy 340# Option instances and having the sandbox's CommandLineHelper handle 341# them. We only do so for options that haven't been declared so far, 342# which should be a proxy for the options that old-configure handles 343# and that we don't know anything about. 344@depends("--help") 345@imports("__sandbox__") 346@imports(_from="mozbuild.configure.options", _import="Option") 347def remaining_mozconfig_options(_): 348 helper = __sandbox__._helper 349 for arg in list(helper): 350 if helper._origins[arg] != "mozconfig": 351 continue 352 name = arg.split("=", 1)[0] 353 if name.isupper() and name not in __sandbox__._options: 354 option = Option(env=name, nargs="*", help=name) 355 helper.handle(option) 356 357 358# Please do not add anything after remaining_mozconfig_options() 359