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 IntelliJ or Android Studio directly and build using
42Gradle.  See the documentation at
43
44https://developer.mozilla.org/en-US/docs/Simple_Firefox_for_Android_build
45
46PLEASE BE AWARE THAT GRADLE AND INTELLIJ/ANDROID STUDIO SUPPORT IS EXPERIMENTAL.
47You should verify any changes using |mach build|.
48=============
49'''.strip()
50
51VISUAL_STUDIO_ADVERTISEMENT = '''
52===============================
53Visual Studio Support Available
54
55You are building Firefox on Windows. You can generate Visual Studio
56files by running:
57
58   mach build-backend --backend=VisualStudio
59
60===============================
61'''.strip()
62
63
64def config_status(topobjdir='.', topsrcdir='.', defines=None,
65                  non_global_defines=None, substs=None, source=None,
66                  mozconfig=None, args=sys.argv[1:]):
67    '''Main function, providing config.status functionality.
68
69    Contrary to config.status, it doesn't use CONFIG_FILES or CONFIG_HEADERS
70    variables.
71
72    Without the -n option, this program acts as config.status and considers
73    the current directory as the top object directory, even when config.status
74    is in a different directory. It will, however, treat the directory
75    containing config.status as the top object directory with the -n option.
76
77    The options to this function are passed when creating the
78    ConfigEnvironment. These lists, as well as the actual wrapper script
79    around this function, are meant to be generated by configure.
80    See build/autoconf/config.status.m4.
81    '''
82
83    if 'CONFIG_FILES' in os.environ:
84        raise Exception('Using the CONFIG_FILES environment variable is not '
85            'supported.')
86    if 'CONFIG_HEADERS' in os.environ:
87        raise Exception('Using the CONFIG_HEADERS environment variable is not '
88            'supported.')
89
90    if not os.path.isabs(topsrcdir):
91        raise Exception('topsrcdir must be defined as an absolute directory: '
92            '%s' % topsrcdir)
93
94    default_backends = ['RecursiveMake']
95    default_backends = (substs or {}).get('BUILD_BACKENDS', ['RecursiveMake'])
96
97    parser = ArgumentParser()
98    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
99                        help='display verbose output')
100    parser.add_argument('-n', dest='not_topobjdir', action='store_true',
101                        help='do not consider current directory as top object directory')
102    parser.add_argument('-d', '--diff', action='store_true',
103                        help='print diffs of changed files.')
104    parser.add_argument('-b', '--backend', nargs='+', choices=sorted(backends),
105                        default=default_backends,
106                        help='what backend to build (default: %s).' %
107                        ' '.join(default_backends))
108    parser.add_argument('--dry-run', action='store_true',
109                        help='do everything except writing files out.')
110    options = parser.parse_args(args)
111
112    # Without -n, the current directory is meant to be the top object directory
113    if not options.not_topobjdir:
114        topobjdir = os.path.abspath('.')
115
116    env = ConfigEnvironment(topsrcdir, topobjdir, defines=defines,
117            non_global_defines=non_global_defines, substs=substs,
118            source=source, mozconfig=mozconfig)
119
120    # mozinfo.json only needs written if configure changes and configure always
121    # passes this environment variable.
122    if 'WRITE_MOZINFO' in os.environ:
123        write_mozinfo(os.path.join(topobjdir, 'mozinfo.json'), env, os.environ)
124
125    cpu_start = time.clock()
126    time_start = time.time()
127
128    # Make appropriate backend instances, defaulting to RecursiveMakeBackend,
129    # or what is in BUILD_BACKENDS.
130    selected_backends = [get_backend_class(b)(env) for b in options.backend]
131
132    if options.dry_run:
133        for b in selected_backends:
134            b.dry_run = True
135
136    reader = BuildReader(env)
137    emitter = TreeMetadataEmitter(env)
138    # This won't actually do anything because of the magic of generators.
139    definitions = emitter.emit(reader.read_topsrcdir())
140
141    log_level = logging.DEBUG if options.verbose else logging.INFO
142    log_manager.add_terminal_logging(level=log_level)
143    log_manager.enable_unstructured()
144
145    print('Reticulating splines...', file=sys.stderr)
146    if len(selected_backends) > 1:
147        definitions = list(definitions)
148
149    for the_backend in selected_backends:
150        the_backend.consume(definitions)
151
152    execution_time = 0.0
153    for obj in chain((reader, emitter), selected_backends):
154        summary = obj.summary()
155        print(summary, file=sys.stderr)
156        execution_time += summary.execution_time
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 Eclipse if it is appropriate.
180    if MachCommandConditions.is_android(env):
181        if 'AndroidEclipse' not in options.backend:
182            print(ANDROID_IDE_ADVERTISEMENT)
183