• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright 2017 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import argparse
8import os
9import os.path
10import shutil
11import subprocess
12import sys
13import stat
14import tempfile
15
16# How to patch libxml2 in Chromium:
17#
18# 1. Write a .patch file and add it to third_party/libxml/chromium.
19# 2. Apply the patch in src: patch -p1 <../chromium/foo.patch
20# 3. Add the patch to the list of patches in this file.
21# 4. Update README.chromium with the provenance of the patch.
22# 5. Upload a change with the modified documentation, roll script,
23#    patch, applied patch and any other relevant changes like
24#    regression tests. Go through the usual review and commit process.
25#
26# How to roll libxml2 in Chromium:
27#
28# Prerequisites:
29#
30# 1. Check out Chromium somewhere on Linux, Mac and Windows.
31# 2. On Linux:
32#    a. sudo apt-get install libicu-dev
33#    b. git clone https://github.com/GNOME/libxml2.git somewhere
34# 3. On Mac, install these packages with brew:
35#      autoconf automake libtool pkgconfig icu4c
36#
37# Procedure:
38#
39# Warning: This process is destructive. Run it on a clean branch.
40#
41# 1. On Linux, in the libxml2 repo directory:
42#    a. git remote update origin
43#    b. git checkout origin/master
44#
45#    This will be the upstream version of libxml you are rolling to.
46#
47# 2. On Linux, in the Chromium src director:
48#    a. third_party/libxml/chromium/roll.py --linux /path/to/libxml2
49#
50#    If this fails, it may be a patch no longer applies. Reset to
51#    head; modify the patch files, this script, and
52#    README.chromium; then commit the result and run it again.
53#
54#    b. Upload a CL, but do not Start Review.
55#
56# 2. On Windows, in the Chromium src directory:
57#    a. git cl patch <Gerrit Issue ID>
58#    b. third_party\libxml\chromium\roll.py --win32
59#    c. git cl upload
60#
61# 3. On Mac, in the Chromium src directory:
62#    a. git cl patch <Gerrit Issue ID>
63#    b. third_party/libxml/chromium/roll.py --mac --icu4c_path=~/homebrew/opt/icu4c
64#    c. Make and commit any final changes to README.chromium, BUILD.gn, etc.
65#    d. git cl upload
66#    e. Complete the review as usual
67#
68# The --linuxfast argument is an alternative to --linux which also deletes
69# files which are not intended to be checked in. This would normally happen at
70# the end of the --mac run, but if you want to run the roll script and get to
71# the final state without running the configure scripts on all three platforms,
72# this is helpful.
73
74PATCHES = [
75    'undo-sax-deprecation.patch',
76    'remove-getentropy.patch',
77]
78
79
80# See libxml2 configure.ac and win32/configure.js to learn what
81# options are available. We include every option here to more easily track
82# changes from one version to the next, and to be sure we only include what
83# we need.
84# These two sets of options should be in sync. You can check the
85# generated #defines in (win32|mac|linux)/include/libxml/xmlversion.h to confirm
86# this.
87# We would like to disable python but it introduces a host of build errors
88SHARED_XML_CONFIGURE_OPTIONS = [
89    # These options are turned ON
90    ('--with-html', 'html=yes'),
91    ('--with-icu', 'icu=yes'),
92    ('--with-output', 'output=yes'),
93    ('--with-push', 'push=yes'),
94    ('--with-python', 'python=yes'),
95    ('--with-reader', 'reader=yes'),
96    ('--with-sax1', 'sax1=yes'),
97    ('--with-threads', 'threads=yes'),
98    ('--with-tree', 'tree=yes'),
99    ('--with-writer', 'writer=yes'),
100    ('--with-xpath', 'xpath=yes'),
101    # These options are turned OFF
102    ('--without-c14n', 'c14n=no'),
103    ('--without-catalog', 'catalog=no'),
104    ('--without-debug', 'xml_debug=no'),
105    ('--without-http', 'http=no'),
106    ('--without-iconv', 'iconv=no'),
107    ('--without-iso8859x', 'iso8859x=no'),
108    ('--without-legacy', 'legacy=no'),
109    ('--without-lzma', 'lzma=no'),
110    ('--without-modules', 'modules=no'),
111    ('--without-pattern', 'pattern=no'),
112    ('--without-regexps', 'regexps=no'),
113    ('--without-schemas', 'schemas=no'),
114    ('--without-schematron', 'schematron=no'),
115    ('--without-valid', 'valid=no'),
116    ('--without-xinclude', 'xinclude=no'),
117    ('--without-xptr', 'xptr=no'),
118    ('--without-zlib', 'zlib=no'),
119]
120
121
122# These options are only available in configure.ac for Linux and Mac.
123EXTRA_NIX_XML_CONFIGURE_OPTIONS = [
124    '--without-fexceptions',
125    '--without-minimum',
126    '--without-readline',
127    '--without-history',
128    '--without-tls',
129]
130
131
132# These options are only available in win32/configure.js for Windows.
133EXTRA_WIN32_XML_CONFIGURE_OPTIONS = [
134    'walker=no',
135]
136
137
138XML_CONFIGURE_OPTIONS = (
139    [option[0] for option in SHARED_XML_CONFIGURE_OPTIONS] +
140    EXTRA_NIX_XML_CONFIGURE_OPTIONS)
141
142
143XML_WIN32_CONFIGURE_OPTIONS = (
144    [option[1] for option in SHARED_XML_CONFIGURE_OPTIONS] +
145    EXTRA_WIN32_XML_CONFIGURE_OPTIONS)
146
147
148FILES_TO_REMOVE = [
149    'src/DOCBparser.c',
150    'src/HACKING',
151    'src/INSTALL',
152    'src/INSTALL.libxml2',
153    'src/MAINTAINERS',
154    'src/Makefile.in',
155    'src/Makefile.win',
156    'src/README.cvs-commits',
157    # This is unneeded "legacy" SAX API, even though we enable SAX1.
158    'src/SAX.c',
159    'src/VxWorks',
160    'src/aclocal.m4',
161    'src/autogen.sh',
162    'src/autom4te.cache',
163    'src/bakefile',
164    'src/build_glob.py',
165    'src/CMakeLists.txt',
166    'src/c14n.c',
167    'src/catalog.c',
168    'src/compile',
169    'src/config.guess',
170    'src/config.sub',
171    'src/configure',
172    'src/chvalid.def',
173    'src/debugXML.c',
174    'src/depcomp',
175    'src/doc',
176    'src/example',
177    'src/fuzz',
178    'src/genChRanges.py',
179    'src/global.data',
180    'src/include/libxml/Makefile.in',
181    'src/include/libxml/meson.build',
182    'src/include/libxml/xmlversion.h',
183    'src/include/libxml/xmlwin32version.h',
184    'src/include/libxml/xmlwin32version.h.in',
185    'src/include/Makefile.in',
186    'src/include/meson.build',
187    'src/install-sh',
188    'src/legacy.c',
189    'src/libxml2.doap',
190    'src/libxml2.syms',
191    'src/ltmain.sh',
192    'src/m4',
193    'src/macos/libxml2.mcp.xml.sit.hqx',
194    'src/meson.build',
195    'src/meson_options.txt',
196    'src/missing',
197    'src/optim',
198    'src/os400',
199    'src/python',
200    'src/relaxng.c',
201    'src/result',
202    'src/rngparser.c',
203    'src/schematron.c',
204    'src/test',
205    'src/testOOM.c',
206    'src/testOOMlib.c',
207    'src/testOOMlib.h',
208    'src/vms',
209    'src/win32/VC10/config.h',
210    'src/win32/configure.js',
211    'src/win32/wince',
212    'src/xinclude.c',
213    'src/xlink.c',
214    'src/xml2-config.in',
215    'src/xmlcatalog.c',
216    'src/xmllint.c',
217    'src/xmlmodule.c',
218    'src/xmlregexp.c',
219    'src/xmlschemas.c',
220    'src/xmlschemastypes.c',
221    'src/xpointer.c',
222    'src/xstc',
223    'src/xzlib.c',
224    'linux/.deps',
225    'linux/doc',
226    'linux/example',
227    'linux/fuzz',
228    'linux/include/private',
229    'linux/python',
230    'linux/xstc',
231]
232
233
234THIRD_PARTY_LIBXML_SRC = 'third_party/libxml/src'
235
236
237class WorkingDir(object):
238    """"Changes the working directory and resets it on exit."""
239    def __init__(self, path):
240        self.prev_path = os.getcwd()
241        self.path = path
242
243    def __enter__(self):
244        os.chdir(self.path)
245
246    def __exit__(self, exc_type, exc_value, traceback):
247        if exc_value:
248            print('was in %s; %s before that' % (self.path, self.prev_path))
249        os.chdir(self.prev_path)
250
251
252def git(*args):
253    """Runs a git subcommand.
254
255    On Windows this uses the shell because there's a git wrapper
256    batch file in depot_tools.
257
258    Arguments:
259        args: The arguments to pass to git.
260    """
261    command = ['git'] + list(args)
262    subprocess.check_call(command, shell=(os.name == 'nt'))
263
264
265def remove_tracked_and_local_dir(path):
266    """Removes the contents of a directory from git, and the filesystem.
267
268    Arguments:
269        path: The path to remove.
270    """
271    remove_tracked_files([path])
272    shutil.rmtree(path, ignore_errors=True)
273    os.mkdir(path)
274
275
276def remove_tracked_files(files_to_remove):
277    """Removes tracked files from git.
278
279    Arguments:
280        files_to_remove: The files to remove.
281    """
282    files_to_remove = [f for f in files_to_remove if os.path.exists(f)]
283    if files_to_remove:
284        git('rm', '-rf', '--ignore-unmatch', *files_to_remove)
285
286
287def sed_in_place(input_filename, program):
288    """Replaces text in a file.
289
290    Arguments:
291        input_filename: The file to edit.
292        program: The sed program to perform edits on the file.
293    """
294    # OS X's sed requires -e
295    subprocess.check_call(['sed', '-i', '-e', program, input_filename])
296
297
298def check_copying(full_path_to_third_party_libxml_src):
299    path = os.path.join(full_path_to_third_party_libxml_src, 'COPYING')
300    if not os.path.exists(path):
301        return
302    with open(path) as f:
303        s = f.read()
304        if 'GNU' in s:
305            raise Exception('check COPYING')
306
307
308def prepare_libxml_distribution(src_path, libxml2_repo_path, temp_dir):
309    """Makes a libxml2 distribution.
310
311    Args:
312        src_path: The path to the Chromium checkout.
313        libxml2_repo_path: The path to the local clone of the libxml2 repo.
314        temp_dir: A temporary directory to stage the distribution to.
315
316    Returns: A tuple of commit hash and full path to the archive.
317    """
318    # If it was necessary to push from a distribution prepared upstream,
319    # this is the point to inject it: Return the version string and the
320    # distribution tar file.
321
322    # The libxml2 repo we're pulling changes from should not have
323    # local changes. This *should* be a commit that's publicly visible
324    # in the upstream repo; reviewers should check this.
325    check_clean(libxml2_repo_path)
326
327    temp_config_path = os.path.join(temp_dir, 'config')
328    os.mkdir(temp_config_path)
329    temp_src_path = os.path.join(temp_dir, 'src')
330    os.mkdir(temp_src_path)
331
332    with WorkingDir(libxml2_repo_path):
333        commit = subprocess.check_output(
334            ['git', 'log', '-n', '1', '--pretty=format:%H',
335             'HEAD']).decode('ascii')
336        subprocess.check_call(
337            'git archive HEAD | tar -x -C "%s"' % temp_src_path,
338            shell=True)
339    with WorkingDir(temp_src_path):
340        os.remove('.gitignore')
341        for patch in PATCHES:
342            print('applying %s' % patch)
343            subprocess.check_call(
344                'patch -p1 --fuzz=0 < %s' % os.path.join(
345                    src_path, THIRD_PARTY_LIBXML_SRC, '..', 'chromium', patch),
346                shell=True)
347
348    with WorkingDir(temp_config_path):
349        print('../src/autogen.sh %s' % XML_CONFIGURE_OPTIONS)
350        subprocess.check_call(['../src/autogen.sh'] + XML_CONFIGURE_OPTIONS)
351        subprocess.check_call(['make', 'dist-all'])
352
353        # Work out what it is called
354        tar_file = subprocess.check_output(
355            '''awk '/PACKAGE =/ {p=$3} /VERSION =/ {v=$3} '''
356            '''END {printf("%s-%s.tar.xz", p, v)}' Makefile''',
357            shell=True).decode('ascii')
358        return commit, os.path.abspath(tar_file)
359
360
361def roll_libxml_linux(src_path, libxml2_repo_path, fast):
362    with WorkingDir(src_path):
363        # Export the upstream git repo.
364        try:
365            temp_dir = tempfile.mkdtemp()
366            print('temporary directory: %s' % temp_dir)
367
368            commit, tar_file = prepare_libxml_distribution(
369                src_path, libxml2_repo_path, temp_dir)
370
371            # Remove all of the old libxml to ensure only desired cruft
372            # accumulates
373            remove_tracked_and_local_dir(THIRD_PARTY_LIBXML_SRC)
374
375            # Update the libxml repo and export it to the Chromium tree
376            with WorkingDir(THIRD_PARTY_LIBXML_SRC):
377                subprocess.check_call(
378                    'tar xJf %s --strip-components=1' % tar_file,
379                    shell=True)
380        finally:
381            shutil.rmtree(temp_dir)
382
383        with WorkingDir(THIRD_PARTY_LIBXML_SRC):
384            # Put the version and revision IDs in the README file
385            sed_in_place('../README.chromium',
386                         's/Revision: .*$/Revision: %s/' % commit)
387            # TODO(crbug.com/349530088): Read version from VERSION file when we
388            # roll libxml to that point and use it instead of the commit hash.
389            sed_in_place('../README.chromium',
390                         's/Version: .*$/Version: %s/' % commit)
391
392            with WorkingDir('../linux'):
393                subprocess.check_call(
394                    ['../src/autogen.sh'] + XML_CONFIGURE_OPTIONS)
395                check_copying(os.getcwd())
396                sed_in_place('config.h', 's/#define HAVE_RAND_R 1//')
397
398            # Add *everything*
399            with WorkingDir('../src'):
400                git('add', '*')
401                if fast:
402                    with WorkingDir('..'):
403                        remove_tracked_files(FILES_TO_REMOVE)
404                git('commit', '-am', '%s libxml, linux' % commit)
405    if fast:
406        print('Now upload for review, etc.')
407    else:
408        print('Now push to Windows and run steps there.')
409
410
411def roll_libxml_win32(src_path):
412    with WorkingDir(src_path):
413        # Run the configure script.
414        with WorkingDir(os.path.join(THIRD_PARTY_LIBXML_SRC, 'win32')):
415            subprocess.check_call(
416                ['cscript', '//E:jscript', 'configure.js', 'compiler=msvc'] +
417                XML_WIN32_CONFIGURE_OPTIONS)
418
419            # Add and commit the result.
420            shutil.move('../config.h', '../../win32/config.h')
421            git('add', '../../win32/config.h')
422            shutil.move('../include/libxml/xmlversion.h',
423                        '../../win32/include/libxml/xmlversion.h')
424            git('add', '../../win32/include/libxml/xmlversion.h')
425            git('commit', '--allow-empty', '-m', 'Windows')
426            git('clean', '-f')
427    print('Now push to Mac and run steps there.')
428
429
430def roll_libxml_mac(src_path, icu4c_path):
431    icu4c_path = os.path.abspath(os.path.expanduser(icu4c_path))
432    os.environ["LDFLAGS"] = "-L" + os.path.join(icu4c_path, 'lib')
433    os.environ["CPPFLAGS"] = "-I" + os.path.join(icu4c_path, 'include')
434    os.environ["PKG_CONFIG_PATH"] = os.path.join(icu4c_path, 'lib/pkgconfig')
435
436    full_path_to_third_party_libxml = os.path.join(
437        src_path, THIRD_PARTY_LIBXML_SRC, '..')
438
439    with WorkingDir(os.path.join(full_path_to_third_party_libxml, 'mac')):
440        subprocess.check_call(['autoreconf', '-i', '../src'])
441        os.chmod('../src/configure',
442                 os.stat('../src/configure').st_mode | stat.S_IXUSR)
443        subprocess.check_call(['../src/configure'] + XML_CONFIGURE_OPTIONS)
444        sed_in_place('config.h', 's/#define HAVE_RAND_R 1//')
445
446    with WorkingDir(full_path_to_third_party_libxml):
447        commit = subprocess.check_output(
448            ['awk', '/Version:/ {print $2}',
449             'README.chromium']).decode('ascii')
450        remove_tracked_files(FILES_TO_REMOVE)
451        commit_message = 'Roll libxml to %s' % commit
452        git('commit', '-am', commit_message)
453    print('Now upload for review, etc.')
454
455
456def check_clean(path):
457    with WorkingDir(path):
458        status = subprocess.check_output(['git', 'status',
459                                          '-s']).decode('ascii')
460        if len(status) > 0:
461            raise Exception('repository at %s is not clean' % path)
462
463
464def main():
465    src_dir = os.getcwd()
466    if not os.path.exists(os.path.join(src_dir, 'third_party')):
467        print('error: run this script from the Chromium src directory')
468        sys.exit(1)
469
470    parser = argparse.ArgumentParser(
471        description='Roll the libxml2 dependency in Chromium')
472    platform = parser.add_mutually_exclusive_group(required=True)
473    platform.add_argument('--linux', action='store_true')
474    platform.add_argument('--win32', action='store_true')
475    platform.add_argument('--mac', action='store_true')
476    platform.add_argument('--linuxfast', action='store_true')
477    parser.add_argument(
478        'libxml2_repo_path',
479        type=str,
480        nargs='?',
481        help='The path to the local clone of the libxml2 git repo.')
482    parser.add_argument(
483        '--icu4c_path',
484        help='The path to the homebrew installation of icu4c.')
485    args = parser.parse_args()
486
487    if args.linux or args.linuxfast:
488        libxml2_repo_path = args.libxml2_repo_path
489        if not libxml2_repo_path:
490            print('Specify the path to the local libxml2 repo clone.')
491            sys.exit(1)
492        libxml2_repo_path = os.path.abspath(libxml2_repo_path)
493        roll_libxml_linux(src_dir, libxml2_repo_path, args.linuxfast)
494    elif args.win32:
495        roll_libxml_win32(src_dir)
496    elif args.mac:
497        icu4c_path = args.icu4c_path
498        if not icu4c_path:
499            print('Specify the path to the homebrew installation of icu4c with --icu4c_path.')
500            print('  ex: roll.py --mac --icu4c_path=~/homebrew/opt/icu4c')
501            sys.exit(1)
502        roll_libxml_mac(src_dir, icu4c_path)
503
504
505if __name__ == '__main__':
506    main()
507