summaryrefslogtreecommitdiff
path: root/chromium/third_party/sqlite/scripts/generate_amalgamation.py
blob: e4fb838b3bf8034999849173f4d42344bad4b02d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/env python3
"""Creates the Chromium SQLite amalgamation.

The amalgamation is a single large source file (sqlite3.c) containing all
of the SQLite code. More at https://www.sqlite.org/amalgamation.html.

Usage:
    generate_amalgamation.py
"""

import argparse
import os
import stat
import subprocess
import sys
import tempfile
from shutil import copyfile, rmtree
from extract_sqlite_api import ProcessSourceFile, header_line, footer_line

# The Chromium SQLite third party directory (i.e. //third_party/sqlite).
_SQLITE_ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

# The Chromium SQLite source directory (i.e. //third_party/sqlite/src).
_SQLITE_SRC_DIR = os.path.join(_SQLITE_ROOT_DIR, 'src')

# The .gni file (also used by BUILD.gn when building) which contains all
# flags passed to the `configuration` script and also used for the compile.
_COMMON_CONFIGURATION_FLAGS_GNI_FILE = os.path.join(
    _SQLITE_ROOT_DIR, 'sqlite_common_configuration_flags.gni')

_CHROMIUM_CONFIGURATION_FLAGS_GNI_FILE = os.path.join(
    _SQLITE_ROOT_DIR, 'sqlite_chromium_configuration_flags.gni')

_DEV_CONFIGURATION_FLAGS_GNI_FILE = os.path.join(
    _SQLITE_ROOT_DIR, 'sqlite_dev_configuration_flags.gni')

# The temporary directory where `make configure` and the amalgamation
# is temporarily created.
_TEMP_CONFIG_DIR = tempfile.mkdtemp()

# Set to True to generate a configuration which is compatible for
# running the SQLite tests.
_CONFIGURE_FOR_TESTING = False


def get_amalgamation_dir(config_name):
    if config_name == 'chromium':
        return os.path.join(_SQLITE_SRC_DIR, 'amalgamation')
    elif config_name == 'dev':
        return os.path.join(_SQLITE_SRC_DIR, 'amalgamation_dev')
    else:
        assert False


def _icu_cpp_flags():
    """Return the libicu C++ flags."""
    cmd = ['icu-config', '--cppflags']
    try:
        return subprocess.check_output(cmd)
    except Exception:
        return ''


def _icu_ld_flags():
    """Return the libicu linker flags."""
    cmd = ['icu-config', '--ldflags']
    try:
        return subprocess.check_output(cmd)
    except Exception:
        return ''


def _strip_flags_for_testing(flags):
    """Accepts the default configure/build flags and strips out those

    incompatible with the SQLite tests.

    When configuring SQLite to run tests this script uses a configuration
    as close to what Chromium ships as possible. Some flags need to be
    omitted for the tests to link and run correct. See comments below.
    """
    test_flags = []
    for flag in flags:
        # Omitting features can cause tests to hang/crash/fail because the
        # SQLite tests don't seem to detect feature omission. Keep them enabled.
        if flag.startswith('SQLITE_OMIT_'):
            continue

        # Some tests compile with specific SQLITE_DEFAULT_PAGE_SIZE so do
        # not hard-code.
        if flag.startswith('SQLITE_DEFAULT_PAGE_SIZE='):
            continue

        # Some tests compile with specific SQLITE_DEFAULT_MEMSTATUS so do
        # not hard-code.
        if flag.startswith('SQLITE_DEFAULT_MEMSTATUS='):
            continue

        # If enabled then get undefined reference to `uregex_open_63' and
        # other *_64 functions.
        if flag == 'SQLITE_ENABLE_ICU':
            continue

        # If defined then the fts4umlaut tests fail with the following error:
        #
        # Error: unknown tokenizer: unicode61
        if flag == 'SQLITE_DISABLE_FTS3_UNICODE':
            continue

        test_flags.append(flag)
    return test_flags


def _read_flags(file_name, param_name):
    config_globals = dict()
    with open(file_name) as input_file:
        code = compile(input_file.read(), file_name, 'exec')
        exec (code, config_globals)
    return config_globals[param_name]


def _read_configuration_values(config_name):
    """Read the configuration flags and return them in an array.


    |config_name| is one of "chromium" or "dev".
    """
    common_flags = _read_flags(_COMMON_CONFIGURATION_FLAGS_GNI_FILE,
                               'sqlite_common_configuration_flags')
    chromium_flags = _read_flags(_CHROMIUM_CONFIGURATION_FLAGS_GNI_FILE,
                                 'sqlite_chromium_configuration_flags')
    dev_flags = _read_flags(_DEV_CONFIGURATION_FLAGS_GNI_FILE,
                            'sqlite_dev_configuration_flags')

    if config_name == 'chromium':
        flags = common_flags + chromium_flags
    elif config_name == 'dev':
        flags = common_flags + dev_flags
    else:
        print('Incorrect config "%s"' % config_name, file=sys.stderr)
        sys.exit(1)

    if _CONFIGURE_FOR_TESTING:
        flags = _strip_flags_for_testing(flags)
    return flags


def _do_configure(config_name):
    """Run the configure script for the SQLite source."""
    configure = os.path.join(_SQLITE_SRC_DIR, 'configure')
    build_flags = ' '.join(
        ['-D' + f for f in _read_configuration_values(config_name)])
    cflags = '-Os {} {}'.format(build_flags, _icu_cpp_flags())
    ldflags = _icu_ld_flags()

    cmd = [
        configure,
        'CFLAGS={}'.format(cflags),
        'LDFLAGS={}'.format(ldflags),
        '--disable-load-extension',
        '--enable-amalgamation',
        '--enable-threadsafe',
    ]
    subprocess.check_call(cmd)

    if _CONFIGURE_FOR_TESTING:
        # Copy the files necessary for building/running tests back
        #into the source directory.
        files = ['Makefile', 'config.h', 'libtool']
        for file_name in files:
            copyfile(
                os.path.join(_TEMP_CONFIG_DIR, file_name),
                os.path.join(_SQLITE_SRC_DIR, file_name))
        file_name = os.path.join(_SQLITE_SRC_DIR, 'libtool')
        st = os.stat(file_name)
        os.chmod(file_name, st.st_mode | stat.S_IEXEC)


def make_aggregate(config_name):
    """Generate the aggregate source files."""
    if not os.path.exists(_TEMP_CONFIG_DIR):
        os.mkdir(_TEMP_CONFIG_DIR)
    try:
        os.chdir(_TEMP_CONFIG_DIR)
        _do_configure(config_name)

        # Chromium compiles 'sqlite3r.c' and 'sqlite3r.h' to use the built-in
        # corruption recovery module. These files are then mapped to the standard
        # 'sqlite3.c' and 'sqlite3.h' files below. This mapping is required if
        # the "SQLITE_HAVE_SQLITE3R" configuration option is specified.
        cmd = ['make', 'shell.c', 'sqlite3r.h', 'sqlite3r.c']
        subprocess.check_call(cmd)

        amalgamation_dir = get_amalgamation_dir(config_name)
        if not os.path.exists(amalgamation_dir):
            os.mkdir(amalgamation_dir)

        readme_dst = os.path.join(amalgamation_dir, 'README.md')
        if not os.path.exists(readme_dst):
            readme_src = os.path.join(_SQLITE_ROOT_DIR, 'scripts',
                                      'README_amalgamation.md')
            copyfile(readme_src, readme_dst)

        copyfile(os.path.join(_TEMP_CONFIG_DIR, 'sqlite3r.c'),
                 os.path.join(amalgamation_dir, 'sqlite3.c'))
        copyfile(os.path.join(_TEMP_CONFIG_DIR, 'sqlite3r.h'),
                 os.path.join(amalgamation_dir, 'sqlite3.h'))

        # shell.c must be placed in a different directory from sqlite3.h,
        # because it contains an '#include "sqlite3.h"' that we want to resolve
        # to our custom //third_party/sqlite/sqlite3.h, not to the sqlite3.h
        # produced here.
        shell_dir = os.path.join(amalgamation_dir, 'shell')
        if not os.path.exists(shell_dir):
            os.mkdir(shell_dir)
        copyfile(
            os.path.join(_TEMP_CONFIG_DIR, 'shell.c'),
            os.path.join(shell_dir, 'shell.c'))
    finally:
        rmtree(_TEMP_CONFIG_DIR)


def extract_sqlite_api(config_name):
    amalgamation_dir = get_amalgamation_dir(config_name)
    input_file = os.path.join(amalgamation_dir, 'sqlite3.h')
    output_file = os.path.join(amalgamation_dir, 'rename_exports.h')
    ProcessSourceFile(
        api_export_macro='SQLITE_API',
        symbol_prefix='chrome_',
        header_line=header_line,
        footer_line=footer_line,
        input_file=input_file,
        output_file=output_file)


if __name__ == '__main__':
    desc = \
    ('Create the SQLite amalgamation. The SQLite amalgamation is documented at '
     'https://www.sqlite.org/amalgamation.html and is a single large file '
     'containing the SQLite source code. Chromium generates the amalgamation with'
     ' this script to ensure that the configuration parameters are identical to '
     'those in the Ninja build file.')

    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument(
        '-t',
        '--testing',
        action='store_true',
        help='Generate an amalgamation for testing (default: false)')
    namespace = parser.parse_args()
    if namespace.testing:
        _CONFIGURE_FOR_TESTING = True
        print('Running configure for testing.')

    for config_name in ['chromium', 'dev']:
        make_aggregate(config_name)
        extract_sqlite_api(config_name)