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/.
4
5# Combined with build/autoconf/config.status.m4, ConfigStatus is an almost
6# drop-in replacement for autoconf 2.13's config.status, with features
7# borrowed from autoconf > 2.5, and additional features.
8
9from __future__ import absolute_import, print_function
10
11import logging
12import os
13import subprocess
14import sys
15import time
16
17from argparse import ArgumentParser
18
19from mach.logging import LoggingManager
20from mozbuild.backend.configenvironment import ConfigEnvironment
21from mozbuild.base import MachCommandConditions
22from mozbuild.frontend.emitter import TreeMetadataEmitter
23from mozbuild.frontend.reader import BuildReader
24from mozbuild.mozinfo import write_mozinfo
25from itertools import chain
26
27from mozbuild.backend import (
28    backends,
29    get_backend_class,
30)
31
32
33log_manager = LoggingManager()
34
35
36ANDROID_IDE_ADVERTISEMENT = '''
37=============
38ADVERTISEMENT
39
40You are building Firefox for Android. After your build completes, you can open
41the top source directory in Android Studio directly and build using Gradle.
42See the documentation at
43
44https://developer.mozilla.org/en-US/docs/Simple_Firefox_for_Android_build
45=============
46'''.strip()
47
48VISUAL_STUDIO_ADVERTISEMENT = '''
49===============================
50Visual Studio Support Available
51
52You are building Firefox on Windows. You can generate Visual Studio
53files by running:
54
55   mach build-backend --backend=VisualStudio
56
57===============================
58'''.strip()
59
60
61def config_status(topobjdir='.', topsrcdir='.', defines=None,
62                  non_global_defines=None, substs=None, source=None,
63                  mozconfig=None, args=sys.argv[1:]):
64    '''Main function, providing config.status functionality.
65
66    Contrary to config.status, it doesn't use CONFIG_FILES or CONFIG_HEADERS
67    variables.
68
69    Without the -n option, this program acts as config.status and considers
70    the current directory as the top object directory, even when config.status
71    is in a different directory. It will, however, treat the directory
72    containing config.status as the top object directory with the -n option.
73
74    The options to this function are passed when creating the
75    ConfigEnvironment. These lists, as well as the actual wrapper script
76    around this function, are meant to be generated by configure.
77    See build/autoconf/config.status.m4.
78    '''
79
80    if 'CONFIG_FILES' in os.environ:
81        raise Exception('Using the CONFIG_FILES environment variable is not '
82            'supported.')
83    if 'CONFIG_HEADERS' in os.environ:
84        raise Exception('Using the CONFIG_HEADERS environment variable is not '
85            'supported.')
86
87    if not os.path.isabs(topsrcdir):
88        raise Exception('topsrcdir must be defined as an absolute directory: '
89            '%s' % topsrcdir)
90
91    default_backends = ['RecursiveMake']
92    default_backends = (substs or {}).get('BUILD_BACKENDS', ['RecursiveMake'])
93
94    parser = ArgumentParser()
95    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
96                        help='display verbose output')
97    parser.add_argument('-n', dest='not_topobjdir', action='store_true',
98                        help='do not consider current directory as top object directory')
99    parser.add_argument('-d', '--diff', action='store_true',
100                        help='print diffs of changed files.')
101    parser.add_argument('-b', '--backend', nargs='+', choices=sorted(backends),
102                        default=default_backends,
103                        help='what backend to build (default: %s).' %
104                        ' '.join(default_backends))
105    parser.add_argument('--dry-run', action='store_true',
106                        help='do everything except writing files out.')
107    options = parser.parse_args(args)
108
109    # Without -n, the current directory is meant to be the top object directory
110    if not options.not_topobjdir:
111        topobjdir = os.path.abspath('.')
112
113    env = ConfigEnvironment(topsrcdir, topobjdir, defines=defines,
114            non_global_defines=non_global_defines, substs=substs,
115            source=source, mozconfig=mozconfig)
116
117    # mozinfo.json only needs written if configure changes and configure always
118    # passes this environment variable.
119    if 'WRITE_MOZINFO' in os.environ:
120        write_mozinfo(os.path.join(topobjdir, 'mozinfo.json'), env, os.environ)
121
122    cpu_start = time.clock()
123    time_start = time.time()
124
125    # Make appropriate backend instances, defaulting to RecursiveMakeBackend,
126    # or what is in BUILD_BACKENDS.
127    selected_backends = [get_backend_class(b)(env) for b in options.backend]
128
129    if options.dry_run:
130        for b in selected_backends:
131            b.dry_run = True
132
133    reader = BuildReader(env)
134    emitter = TreeMetadataEmitter(env)
135    # This won't actually do anything because of the magic of generators.
136    definitions = emitter.emit(reader.read_topsrcdir())
137
138    log_level = logging.DEBUG if options.verbose else logging.INFO
139    log_manager.add_terminal_logging(level=log_level)
140    log_manager.enable_unstructured()
141
142    print('Reticulating splines...', file=sys.stderr)
143    if len(selected_backends) > 1:
144        definitions = list(definitions)
145
146    for the_backend in selected_backends:
147        the_backend.consume(definitions)
148
149    execution_time = 0.0
150    for obj in chain((reader, emitter), selected_backends):
151        summary = obj.summary()
152        print(summary, file=sys.stderr)
153        execution_time += summary.execution_time
154        if hasattr(obj, 'gyp_summary'):
155            summary = obj.gyp_summary()
156            print(summary, file=sys.stderr)
157
158    cpu_time = time.clock() - cpu_start
159    wall_time = time.time() - time_start
160    efficiency = cpu_time / wall_time if wall_time else 100
161    untracked = wall_time - execution_time
162
163    print(
164        'Total wall time: {:.2f}s; CPU time: {:.2f}s; Efficiency: '
165        '{:.0%}; Untracked: {:.2f}s'.format(
166            wall_time, cpu_time, efficiency, untracked),
167        file=sys.stderr
168    )
169
170    if options.diff:
171        for the_backend in selected_backends:
172            for path, diff in sorted(the_backend.file_diffs.items()):
173                print('\n'.join(diff))
174
175    # Advertise Visual Studio if appropriate.
176    if os.name == 'nt' and 'VisualStudio' not in options.backend:
177        print(VISUAL_STUDIO_ADVERTISEMENT)
178
179    # Advertise Android Studio if it is appropriate.
180    if MachCommandConditions.is_android(env):
181        print(ANDROID_IDE_ADVERTISEMENT)
182