1#!/usr/bin/env python3
2#  Copyright 2016 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS-IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import yaml
17
18# "smoke tests" are run before other build matrix rows.
19build_matrix_smoke_test_rows = []
20build_matrix_rows = []
21
22
23def determine_compiler_kind(compiler):
24    if compiler.startswith('gcc'):
25        return 'gcc'
26    elif compiler.startswith('clang'):
27        return 'clang'
28    else:
29        raise Exception('Unexpected compiler: %s' % compiler)
30
31
32def determine_tests(asan, ubsan, clang_tidy, smoke_tests, use_precompiled_headers_in_tests, exclude_tests,
33                    include_only_tests):
34    tests = []
35    has_debug_build = False
36    tests += ['ReleasePlain']
37    if asan:
38        has_debug_build = True
39        if ubsan:
40            tests += ['DebugAsanUbsan']
41        else:
42            tests += ['DebugAsan']
43    if ubsan and not asan:
44        raise Exception('Enabling UBSan but not ASan is not currently supported.')
45    if not has_debug_build:
46        tests += ['DebugPlain']
47    for smoke_test in smoke_tests:
48        if smoke_test not in tests:
49            tests += [smoke_test]
50    excessive_excluded_tests = set(exclude_tests) - set(tests)
51    if excessive_excluded_tests:
52        raise Exception(
53            'Some tests were excluded but were not going to run anyway: %s. '
54            'Tests to run (ignoring the possible NoPch/NoClangTidy prefixes): %s'
55            % (excessive_excluded_tests, tests))
56    if include_only_tests is not None:
57        if exclude_tests != []:
58            raise Exception('Using exclude_tests and include_only_tests together is not supported.')
59        tests = include_only_tests
60    else:
61        tests = [test for test in tests if test not in exclude_tests]
62    if not use_precompiled_headers_in_tests:
63        tests = [test + 'NoPch' for test in tests]
64    if not clang_tidy:
65        tests = [test + 'NoClangTidy' for test in tests]
66    return tests
67
68
69def generate_export_statements_for_env(env):
70    return ' '.join(['export %s=\'%s\';' % (var_name, value) for (var_name, value) in sorted(env.items())])
71
72
73def generate_env_string_for_env(env):
74    return ' '.join(['%s=%s' % (var_name, value) for (var_name, value) in sorted(env.items())])
75
76
77def add_ubuntu_tests(ubuntu_version, compiler, os='linux', stl=None, asan=True, ubsan=True, clang_tidy=True,
78                     use_precompiled_headers_in_tests=True, smoke_tests=[], exclude_tests=[], include_only_tests=None):
79    env = {
80        'UBUNTU': ubuntu_version,
81        'COMPILER': compiler
82    }
83    if stl is not None:
84        env['STL'] = stl
85    compiler_kind = determine_compiler_kind(compiler)
86    export_statements = 'export OS=' + os + '; ' + generate_export_statements_for_env(env=env)
87    test_environment_template = {'os': 'linux', 'compiler': compiler_kind,
88                                 'install': '%s extras/scripts/travis_ci_install_linux.sh' % export_statements}
89    tests = determine_tests(asan, ubsan, clang_tidy, smoke_tests,
90                            use_precompiled_headers_in_tests=use_precompiled_headers_in_tests,
91                            exclude_tests=exclude_tests,
92                            include_only_tests=include_only_tests)
93    for test in tests:
94        test_environment = test_environment_template.copy()
95        test_environment['script'] = '%s extras/scripts/postsubmit.sh %s' % (export_statements, test)
96        # The TEST variable has no effect on the test run, but allows to see the test name in the Travis CI dashboard.
97        test_environment['env'] = generate_env_string_for_env(env) + " TEST=%s" % test
98        if test in smoke_tests:
99            build_matrix_smoke_test_rows.append(test_environment)
100        else:
101            build_matrix_rows.append(test_environment)
102
103
104def add_osx_tests(compiler, xcode_version=None, stl=None, asan=True, ubsan=True, clang_tidy=True,
105                  use_precompiled_headers_in_tests=True, smoke_tests=[], exclude_tests=[], include_only_tests=None):
106    env = {'COMPILER': compiler}
107    if stl is not None:
108        env['STL'] = stl
109    compiler_kind = determine_compiler_kind(compiler)
110    export_statements = 'export OS=osx; ' + generate_export_statements_for_env(env=env)
111    test_environment_template = {'os': 'osx', 'compiler': compiler_kind,
112                                 'install': '%s travis_wait extras/scripts/travis_ci_install_osx.sh' % export_statements}
113    if xcode_version is not None:
114        test_environment_template['osx_image'] = 'xcode%s' % xcode_version
115
116    tests = determine_tests(asan, ubsan, clang_tidy, smoke_tests,
117                            use_precompiled_headers_in_tests=use_precompiled_headers_in_tests,
118                            exclude_tests=exclude_tests, include_only_tests=include_only_tests)
119    for test in tests:
120        test_environment = test_environment_template.copy()
121        test_environment['script'] = '%s extras/scripts/postsubmit.sh %s' % (export_statements, test)
122        # The TEST variable has no effect on the test run, but allows to see the test name in the Travis CI dashboard.
123        test_environment['env'] = generate_env_string_for_env(env) + " TEST=%s" % test
124        if test in smoke_tests:
125            build_matrix_smoke_test_rows.append(test_environment)
126        else:
127            build_matrix_rows.append(test_environment)
128
129
130def add_bazel_tests(ubuntu_version, smoke_tests=[]):
131    env = {
132        'UBUNTU': ubuntu_version,
133        'COMPILER': 'bazel',
134    }
135    test = 'DebugPlain'
136    export_statements = 'export OS=linux; ' + generate_export_statements_for_env(env=env)
137    test_environment = {'os': 'linux',
138                        'compiler': 'gcc',
139                        'env': generate_env_string_for_env(env),
140                        'install': '%s extras/scripts/travis_ci_install_linux.sh' % export_statements,
141                        'script': '%s extras/scripts/postsubmit.sh %s' % (export_statements, test)}
142    if test in smoke_tests:
143        build_matrix_smoke_test_rows.append(test_environment)
144    else:
145        build_matrix_rows.append(test_environment)
146
147add_ubuntu_tests(ubuntu_version='20.10', compiler='gcc-7')
148add_ubuntu_tests(ubuntu_version='20.10', compiler='gcc-10',
149                 smoke_tests=['DebugPlain', 'ReleasePlain'])
150add_ubuntu_tests(ubuntu_version='20.10', compiler='clang-8.0', stl='libstdc++',
151                 smoke_tests=['DebugPlain', 'DebugAsanUbsan', 'ReleasePlain'])
152add_ubuntu_tests(ubuntu_version='20.10', compiler='clang-11.0', stl='libstdc++')
153add_ubuntu_tests(ubuntu_version='20.10', compiler='clang-11.0', stl='libc++')
154
155add_ubuntu_tests(ubuntu_version='20.04', compiler='gcc-7')
156add_ubuntu_tests(ubuntu_version='20.04', compiler='clang-6.0', stl='libstdc++',
157                 smoke_tests=['DebugPlain', 'DebugAsanUbsan', 'ReleasePlain'])
158
159add_ubuntu_tests(ubuntu_version='18.04', compiler='gcc-5', asan=False, ubsan=False)
160add_ubuntu_tests(ubuntu_version='18.04', compiler='gcc-8', asan=False, ubsan=False)
161add_ubuntu_tests(ubuntu_version='18.04', compiler='clang-3.9', stl='libstdc++')
162add_ubuntu_tests(ubuntu_version='18.04', compiler='clang-7.0', stl='libstdc++',
163                 # Disabled due to https://bugs.llvm.org/show_bug.cgi?id=41625.
164                 use_precompiled_headers_in_tests=False)
165
166add_bazel_tests(ubuntu_version='18.04', smoke_tests=['DebugPlain'])
167
168# ASan/UBSan are disabled for all these, the analysis on later versions is better anyway.
169# Also, in some combinations they wouldn't work.
170add_ubuntu_tests(ubuntu_version='16.04', compiler='gcc-5', asan=False, ubsan=False)
171add_ubuntu_tests(ubuntu_version='16.04', compiler='clang-3.5', stl='libstdc++', asan=False, ubsan=False)
172add_ubuntu_tests(ubuntu_version='16.04', compiler='clang-3.9', stl='libstdc++', asan=False, ubsan=False)
173
174# Asan/Ubsan are disabled because it generates lots of warnings like:
175#    warning: direct access in [...] to global weak symbol guard variable for [...] means the weak symbol cannot be
176#    overridden at runtime. This was likely caused by different translation units being compiled with different
177#    visibility settings.
178# and the build eventually fails or times out.
179add_osx_tests(compiler='gcc-6', xcode_version='11.4', asan=False, ubsan=False, clang_tidy=False)
180add_osx_tests(compiler='gcc-9', xcode_version='11.4', asan=False, ubsan=False, clang_tidy=False, smoke_tests=['DebugPlain'],
181              # Using PCHs fails with this error:
182              # error: /Users/travis/build/google/fruit/build/tests/test_common-precompiled.h.gch: had text segment
183              # at different address
184              use_precompiled_headers_in_tests=False)
185add_osx_tests(compiler='clang-6.0', xcode_version='11.4', stl='libc++', clang_tidy=False)
186add_osx_tests(compiler='clang-8.0', xcode_version='11.4', stl='libc++', smoke_tests=['DebugPlain'],
187              clang_tidy=False,
188              # Disabled due to https://bugs.llvm.org/show_bug.cgi?id=41625.
189              use_precompiled_headers_in_tests=False)
190
191add_osx_tests(compiler='clang-default', xcode_version='9.4', stl='libc++', clang_tidy=False)
192add_osx_tests(compiler='clang-default', xcode_version='11.3', stl='libc++', clang_tidy=False,
193              # Disabled due to https://bugs.llvm.org/show_bug.cgi?id=41625.
194              use_precompiled_headers_in_tests=False,
195              smoke_tests=['DebugPlain'])
196
197# ** Disabled combinations **
198#
199# These fail with "'type_traits' file not found" (the <type_traits> header is missing).
200#
201#   add_osx_tests('gcc-default', stl='libstdc++')
202#   add_osx_tests('clang-default', stl='libstdc++')
203#   add_osx_tests('clang-3.5', stl='libstdc++')
204#   add_osx_tests('clang-3.6', stl='libstdc++')
205#
206#
207# The compiler complains that the 2-argument constructor of std::pair is ambiguous, even after
208# adding explicit casts to the exact types of the expected overload.
209#
210#   add_osx_tests('clang-default', stl='libc++')
211#
212#
213# This triggers an assert error in the compiler, with the message:
214# "expected to get called on an inlined function!" [...] function isMSExternInline, file Decl.cpp, line 2647.
215#
216#   add_osx_tests('clang-3.5', stl='libc++', asan=False, ubsan=False)
217#
218#
219# This fails with this error:
220# /usr/include/c++/v1/string:1938:44: error: 'basic_string<_CharT, _Traits, _Allocator>' is missing
221# exception specification 'noexcept(is_nothrow_copy_constructible<allocator_type>::value)'
222# TODO: Try again every once in a while (to re-enable these once the bug in libc++ is fixed).
223#
224#   add_ubuntu_tests(ubuntu_version='16.04', compiler='clang-3.8', stl='libc++', asan=False, ubsan=False)
225#
226
227
228yaml_file = {
229    'sudo': 'required',
230    'dist': 'xenial',
231    'services': ['docker'],
232    'language': 'cpp',
233    'branches': {
234        'only': ['master'],
235    },
236    'matrix': {
237        'fast_finish': True,
238        'include': build_matrix_smoke_test_rows + build_matrix_rows,
239    },
240}
241
242
243class CustomDumper(yaml.SafeDumper):
244    def ignore_aliases(self, _data):
245        return True
246
247
248print('#')
249print('# This file was auto-generated from extras/scripts/travis_yml_generator.py, DO NOT EDIT')
250print('#')
251print(yaml.dump(yaml_file, default_flow_style=False, Dumper=CustomDumper))
252