Bug 1574761 - Avoid race condition creating old-configure. r=nalexander
[gecko.git] / build / moz.configure / old.configure
blobb55b99e1366b489a87a95d48563e56bc8e393f3c
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/.
8 @imports('codecs')
9 @imports('sys')
10 def encoded_open(path, mode):
11     encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
12     return codecs.open(path, mode, encoding)
15 option(env='AUTOCONF', nargs=1, help='Path to autoconf 2.13')
18 @depends(mozconfig, 'AUTOCONF')
19 @checking('for autoconf')
20 @imports(_from='os.path', _import='exists')
21 @imports('re')
22 def autoconf(mozconfig, autoconf):
23     mozconfig_autoconf = None
24     if mozconfig['path']:
25         make_extra = mozconfig['make_extra']
26         if make_extra:
27             for assignment in make_extra:
28                 m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
29                              assignment)
30                 if m:
31                     mozconfig_autoconf = m.group(1)
33     autoconf = autoconf[0] if autoconf else None
35     for ac in (mozconfig_autoconf, autoconf, 'autoconf-2.13', 'autoconf2.13',
36                'autoconf213'):
37         if ac:
38             autoconf = find_program(ac)
39             if autoconf:
40                 break
41     else:
42         fink = find_program('fink')
43         if fink:
44             autoconf = os.path.normpath(os.path.join(
45                 fink, '..', '..', 'lib', 'autoconf2.13', 'bin', 'autoconf'))
46         else:
47             brew = find_program('brew')
48             if brew:
49                 autoconf = os.path.normpath(os.path.join(
50                     brew, '..', '..', 'Cellar', 'autoconf213', '2.13', 'bin',
51                     'autoconf213'))
53     if not autoconf:
54         die('Could not find autoconf 2.13')
56     if not exists(autoconf):
57         die('Could not find autoconf 2.13 at %s', autoconf)
59     return autoconf
62 set_config('AUTOCONF', autoconf)
65 @depends(mozconfig)
66 def prepare_mozconfig(mozconfig):
67     if mozconfig['path']:
68         items = {}
69         for key, value in mozconfig['vars']['added'].items():
70             items[key] = (value, 'added')
71         for key, (old, value) in mozconfig['vars']['modified'].items():
72             items[key] = (value, 'modified')
73         for t in ('env', 'vars'):
74             for key in mozconfig[t]['removed'].keys():
75                 items[key] = (None, 'removed ' + t)
76         return items
79 @depends('OLD_CONFIGURE', prepare_mozconfig, autoconf, check_build_environment,
80          shell, old_configure_assignments, build_project)
81 @imports(_from='__builtin__', _import='open')
82 @imports(_from='__builtin__', _import='print')
83 @imports(_from='__builtin__', _import='sorted')
84 @imports('glob')
85 @imports('itertools')
86 @imports('subprocess')
87 # Import getmtime without overwriting the sandbox os.path.
88 @imports(_from='os.path', _import='getmtime')
89 @imports(_from='os.path', _import='exists')
90 @imports(_from='mozbuild.shellutil', _import='quote')
91 @imports(_from='tempfile', _import='NamedTemporaryFile')
92 @imports(_from='os', _import='remove')
93 @imports(_from='os', _import='rename')
94 @imports(_from='__builtin__', _import='OSError')
95 def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell,
96                       old_configure_assignments, build_project):
97     # os.path.abspath in the sandbox will ensure forward slashes on Windows,
98     # which is actually necessary because this path actually ends up literally
99     # as $0, and backslashes there breaks autoconf's detection of the source
100     # directory.
101     old_configure = os.path.abspath(old_configure[0])
102     if build_project == 'js':
103         old_configure_dir = os.path.dirname(old_configure)
104         if not old_configure_dir.endswith('/js/src'):
105             old_configure = os.path.join(old_configure_dir, 'js', 'src',
106                                          os.path.basename(old_configure))
108     refresh = True
109     if exists(old_configure):
110         mtime = getmtime(old_configure)
111         aclocal = os.path.join(build_env.topsrcdir, 'build', 'autoconf',
112                                '*.m4')
113         for input in itertools.chain(
114             (old_configure + '.in',
115              os.path.join(os.path.dirname(old_configure), 'aclocal.m4')),
116             glob.iglob(aclocal),
117         ):
118             if getmtime(input) > mtime:
119                 break
120         else:
121             refresh = False
123     if refresh:
124         log.info('Refreshing %s with %s', old_configure, autoconf)
125         script = subprocess.check_output([
126             shell, autoconf,
127             '--localdir=%s' % os.path.dirname(old_configure),
128             old_configure + '.in'])
129         if not script:
130             die('Generated old-configure is empty! Check that your autoconf 2.13 program works!')
132         # Make old-configure append to config.log, where we put our own log.
133         # This could be done with a m4 macro, but it's way easier this way
134         script = script.replace('>./config.log', '>>${CONFIG_LOG=./config.log}')
136         with NamedTemporaryFile(mode='wb', prefix=os.path.basename(old_configure),
137                                 dir=os.path.dirname(old_configure), delete=False) as fh:
138             fh.write(script)
140         try:
141             rename(fh.name, old_configure)
142         except OSError:
143             try:
144                 # Likely the file already existed (on Windows). Retry after removing it.
145                 remove(old_configure)
146                 rename(fh.name, old_configure)
147             except OSError as e:
148                 die('Failed re-creating old-configure: %s' % e.message)
150     cmd = [shell, old_configure]
151     with encoded_open('old-configure.vars', 'w') as out:
152         log.debug('Injecting the following to old-configure:')
154         def inject(command):
155             print(command, file=out) # noqa Python 2vs3
156             log.debug('| %s', command)
158         if mozconfig:
159             inject('# start of mozconfig values')
160             for key, (value, action) in sorted(mozconfig.items()):
161                 if action.startswith('removed '):
162                     inject("unset %s # from %s" % (
163                         key, action[len('removed '):]))
164                 else:
165                     inject("%s=%s # %s" % (key, quote(value), action))
167             inject('# end of mozconfig values')
169         # Autoconf is special, because it might be passed from
170         # mozconfig['make_extra'], which we don't pass automatically above.
171         inject('export AUTOCONF=%s' % quote(autoconf))
173         for k, v in old_configure_assignments:
174             inject('%s=%s' % (k, quote(v)))
176     return cmd
179 @template
180 def old_configure_options(*options):
181     for opt in options:
182         option(opt, nargs='*', help='Help missing for old configure options')
184     @dependable
185     def all_options():
186         return list(options)
188     return depends(extra_old_configure_args, all_options, *options)
191 @old_configure_options(
192     '--cache-file',
193     '--datadir',
194     '--enable-content-sandbox',
195     '--enable-cookies',
196     '--enable-cpp-rtti',
197     '--enable-crashreporter',
198     '--enable-dbus',
199     '--enable-debug-js-modules',
200     '--enable-directshow',
201     '--enable-dtrace',
202     '--enable-dump-painting',
203     '--enable-extensions',
204     '--enable-feeds',
205     '--enable-gconf',
206     '--enable-icf',
207     '--enable-install-strip',
208     '--enable-ios-target',
209     '--enable-libjpeg-turbo',
210     '--enable-libproxy',
211     '--enable-llvm-hacks',
212     '--enable-logrefcnt',
213     '--enable-mobile-optimize',
214     '--enable-necko-wifi',
215     '--enable-negotiateauth',
216     '--enable-nfc',
217     '--enable-nspr-build',
218     '--enable-official-branding',
219     '--enable-parental-controls',
220     '--enable-pref-extensions',
221     '--enable-readline',
222     '--enable-sandbox',
223     '--enable-startup-notification',
224     '--enable-startupcache',
225     '--enable-strip',
226     '--enable-synth-pico',
227     '--enable-system-cairo',
228     '--enable-system-extension-dirs',
229     '--enable-system-pixman',
230     '--enable-system-sqlite',
231     '--enable-universalchardet',
232     '--enable-updater',
233     '--enable-xul',
234     '--enable-zipwriter',
235     '--includedir',
236     '--libdir',
237     '--prefix',
238     '--with-android-distribution-directory',
239     '--with-android-max-sdk',
240     '--with-android-min-sdk',
241     '--with-app-basename',
242     '--with-app-name',
243     '--with-branding',
244     '--with-cross-lib',
245     '--with-debug-label',
246     '--with-distribution-id',
247     '--with-doc-include-dirs',
248     '--with-doc-input-dirs',
249     '--with-doc-output-dir',
250     '--with-intl-api',
251     '--with-ios-sdk',
252     '--with-jitreport-granularity',
253     '--with-macbundlename-prefix',
254     '--with-nspr-cflags',
255     '--with-nspr-exec-prefix',
256     '--with-nspr-libs',
257     '--with-nspr-prefix',
258     '--with-nss-exec-prefix',
259     '--with-nss-prefix',
260     '--with-qemu-exe',
261     '--with-sixgill',
262     '--with-system-bz2',
263     '--with-system-icu',
264     '--with-system-libevent',
265     '--with-system-nspr',
266     '--with-system-nss',
267     '--with-system-png',
268     '--with-system-zlib',
269     '--with-unify-dist',
270     '--with-user-appdir',
271     '--x-includes',
272     '--x-libraries',
274 def prepare_configure_options(extra_old_configure_args, all_options, *options):
275     # old-configure only supports the options listed in @old_configure_options
276     # so we don't need to pass it every single option we've been passed. Only
277     # the ones that are not supported by python configure need to.
278     options = [
279         value.format(name)
280         for name, value in zip(all_options, options)
281         if value.origin != 'default'
282     ]
284     extra_env = {}
286     # We also pass it the options from js/moz.configure so that it can pass
287     # them down to js/src/configure. Note this list is empty when running
288     # js/src/configure, in which case we don't need to pass those options
289     # to old-configure since old-configure doesn't handle them anyways.
290     if extra_old_configure_args:
291         for arg in extra_old_configure_args:
292             if arg.startswith('-'):
293                 options.append(arg)
294             else:
295                 k, v = arg.split('=', 1)
296                 extra_env[k] = v
298     return namespace(options=options, extra_env=extra_env, all_options=all_options)
301 @depends(prepare_configure, prepare_configure_options, altered_path)
302 @imports(_from='__builtin__', _import='compile')
303 @imports(_from='__builtin__', _import='open')
304 @imports('logging')
305 @imports('os')
306 @imports('subprocess')
307 @imports('sys')
308 @imports('types')
309 @imports(_from='mozbuild.shellutil', _import='quote')
310 @imports(_from='mozbuild.shellutil', _import='split')
311 @imports(_from='mozbuild.util', _import='encode')
312 def old_configure(prepare_configure, prepare_configure_options, altered_path):
313     cmd = prepare_configure + prepare_configure_options.options
314     extra_env = prepare_configure_options.extra_env
316     env = dict(os.environ)
317     if extra_env:
318         env.update(extra_env)
320     # For debugging purpose, in case it's not what we'd expect.
321     log.debug('Running %s', quote(*cmd))
322     if extra_env:
323         log.debug('with extra environment: %s',
324                   ' '.join('%s=%s' % pair for pair in extra_env.iteritems()))
326     # Our logging goes to config.log, the same file old.configure uses.
327     # We can't share the handle on the file, so close it.
328     logger = logging.getLogger('moz.configure')
329     config_log = None
330     for handler in logger.handlers:
331         if isinstance(handler, logging.FileHandler):
332             config_log = handler
333             config_log.close()
334             logger.removeHandler(config_log)
335             env['CONFIG_LOG'] = config_log.baseFilename
336             log_size = os.path.getsize(config_log.baseFilename)
337             break
339     if altered_path:
340         env['PATH'] = altered_path
342     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
343                             env=encode(env))
344     while True:
345         line = proc.stdout.readline()
346         if not line:
347             break
348         log.info(line.rstrip())
350     ret = proc.wait()
351     if ret:
352         with log.queue_debug():
353             if config_log:
354                 with encoded_open(config_log.baseFilename, 'r') as fh:
355                     fh.seek(log_size)
356                     for line in fh:
357                         log.debug(line.rstrip())
358             log.error('old-configure failed')
359         sys.exit(ret)
361     if config_log:
362         # Create a new handler in append mode
363         handler = logging.FileHandler(config_log.baseFilename, mode='a', delay=True)
364         handler.setFormatter(config_log.formatter)
365         logger.addHandler(handler)
367     raw_config = {
368         'split': split,
369         'unique_list': unique_list,
370     }
371     with encoded_open('config.data', 'r') as fh:
372         code = compile(fh.read(), 'config.data', 'exec')
373         # Every variation of the exec() function I tried led to:
374         # SyntaxError: unqualified exec is not allowed in function 'main' it
375         # contains a nested function with free variables
376         exec code in raw_config # noqa
378     # Ensure all the flags known to old-configure appear in the
379     # @old_configure_options above.
380     all_options = set(prepare_configure_options.all_options)
381     for flag in raw_config['flags']:
382         if flag not in all_options:
383             die('Missing option in `@old_configure_options` in %s: %s',
384                 __file__, flag)
386     # If the code execution above fails, we want to keep the file around for
387     # debugging.
388     os.remove('config.data')
390     for c in ('substs', 'defines'):
391         raw_config[c] = [
392             (k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
393             for k, v in raw_config[c]
394         ]
396     return raw_config
399 # set_config is only available in the global namespace, not directly in
400 # @depends functions, but we do need to enumerate the result of
401 # old_configure, so we cheat.
402 @imports('__sandbox__')
403 def set_old_configure_config(name, value):
404     __sandbox__.set_config_impl(name, value)
406 # Same as set_old_configure_config, but for set_define.
409 @imports('__sandbox__')
410 def set_old_configure_define(name, value):
411     __sandbox__.set_define_impl(name, value)
414 @depends(old_configure)
415 def post_old_configure(raw_config):
416     for k, v in raw_config['substs']:
417         set_old_configure_config(k, v)
419     for k, v in dict(raw_config['defines']).iteritems():
420         set_old_configure_define(k, v)
422     set_old_configure_config('non_global_defines',
423                              raw_config['non_global_defines'])
426 # Assuming no other option is declared after this function, handle the
427 # env options that were injected by mozconfig_options by creating dummy
428 # Option instances and having the sandbox's CommandLineHelper handle
429 # them. We only do so for options that haven't been declared so far,
430 # which should be a proxy for the options that old-configure handles
431 # and that we don't know anything about.
432 @depends('--help')
433 @imports('__sandbox__')
434 @imports(_from='mozbuild.configure.options', _import='Option')
435 def remaining_mozconfig_options(_):
436     helper = __sandbox__._helper
437     for arg in helper:
438         if helper._origins[arg] != 'mozconfig':
439             continue
440         name = arg.split('=', 1)[0]
441         if name.isupper() and name not in __sandbox__._options:
442             option = Option(env=name, nargs='*', help=name)
443             helper.handle(option)
445 # Please do not add anything after remaining_mozconfig_options()