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-ftp', 'ftp=no'), 106 ('--without-http', 'http=no'), 107 ('--without-iconv', 'iconv=no'), 108 ('--without-iso8859x', 'iso8859x=no'), 109 ('--without-legacy', 'legacy=no'), 110 ('--without-lzma', 'lzma=no'), 111 ('--without-mem-debug', 'mem_debug=no'), 112 ('--without-modules', 'modules=no'), 113 ('--without-pattern', 'pattern=no'), 114 ('--without-regexps', 'regexps=no'), 115 ('--without-schemas', 'schemas=no'), 116 ('--without-schematron', 'schematron=no'), 117 ('--without-valid', 'valid=no'), 118 ('--without-xinclude', 'xinclude=no'), 119 ('--without-xptr', 'xptr=no'), 120 ('--without-xptr-locs', 'xptr_locs=no'), 121 ('--without-zlib', 'zlib=no'), 122] 123 124 125# These options are only available in configure.ac for Linux and Mac. 126EXTRA_NIX_XML_CONFIGURE_OPTIONS = [ 127 '--without-fexceptions', 128 '--without-minimum', 129 '--without-readline', 130 '--without-history', 131 '--without-tls', 132] 133 134 135# These options are only available in win32/configure.js for Windows. 136EXTRA_WIN32_XML_CONFIGURE_OPTIONS = [ 137 'walker=no', 138] 139 140 141XML_CONFIGURE_OPTIONS = ( 142 [option[0] for option in SHARED_XML_CONFIGURE_OPTIONS] + 143 EXTRA_NIX_XML_CONFIGURE_OPTIONS) 144 145 146XML_WIN32_CONFIGURE_OPTIONS = ( 147 [option[1] for option in SHARED_XML_CONFIGURE_OPTIONS] + 148 EXTRA_WIN32_XML_CONFIGURE_OPTIONS) 149 150 151FILES_TO_REMOVE = [ 152 'src/DOCBparser.c', 153 'src/HACKING', 154 'src/INSTALL', 155 'src/INSTALL.libxml2', 156 'src/MAINTAINERS', 157 'src/Makefile.in', 158 'src/Makefile.win', 159 'src/README.cvs-commits', 160 # This is unneeded "legacy" SAX API, even though we enable SAX1. 161 'src/SAX.c', 162 'src/VxWorks', 163 'src/aclocal.m4', 164 'src/autogen.sh', 165 'src/autom4te.cache', 166 'src/bakefile', 167 'src/build_glob.py', 168 'src/CMakeLists.txt', 169 'src/c14n.c', 170 'src/catalog.c', 171 'src/compile', 172 'src/config.guess', 173 'src/config.sub', 174 'src/configure', 175 'src/chvalid.def', 176 'src/debugXML.c', 177 'src/depcomp', 178 'src/doc', 179 'src/example', 180 'src/fuzz', 181 'src/genChRanges.py', 182 'src/global.data', 183 'src/include/libxml/Makefile.in', 184 'src/include/libxml/xmlversion.h', 185 'src/include/libxml/xmlwin32version.h', 186 'src/include/libxml/xmlwin32version.h.in', 187 'src/include/Makefile.in', 188 'src/install-sh', 189 'src/legacy.c', 190 'src/libxml2.doap', 191 'src/libxml2.syms', 192 'src/ltmain.sh', 193 'src/m4', 194 'src/macos/libxml2.mcp.xml.sit.hqx', 195 'src/missing', 196 'src/optim', 197 'src/os400', 198 'src/python', 199 'src/relaxng.c', 200 'src/result', 201 'src/rngparser.c', 202 'src/schematron.c', 203 'src/test', 204 'src/testOOM.c', 205 'src/testOOMlib.c', 206 'src/testOOMlib.h', 207 'src/vms', 208 'src/win32/VC10/config.h', 209 'src/win32/configure.js', 210 'src/win32/wince', 211 'src/xinclude.c', 212 'src/xlink.c', 213 'src/xml2-config.in', 214 'src/xmlcatalog.c', 215 'src/xmllint.c', 216 'src/xmlmodule.c', 217 'src/xmlregexp.c', 218 'src/xmlschemas.c', 219 'src/xmlschemastypes.c', 220 'src/xpointer.c', 221 'src/xstc', 222 'src/xzlib.c', 223 'linux/.deps', 224 'linux/doc', 225 'linux/example', 226 'linux/fuzz', 227 'linux/include/private', 228 'linux/python', 229 'linux/xstc', 230] 231 232 233THIRD_PARTY_LIBXML_SRC = 'third_party/libxml/src' 234 235 236class WorkingDir(object): 237 """"Changes the working directory and resets it on exit.""" 238 def __init__(self, path): 239 self.prev_path = os.getcwd() 240 self.path = path 241 242 def __enter__(self): 243 os.chdir(self.path) 244 245 def __exit__(self, exc_type, exc_value, traceback): 246 if exc_value: 247 print('was in %s; %s before that' % (self.path, self.prev_path)) 248 os.chdir(self.prev_path) 249 250 251def git(*args): 252 """Runs a git subcommand. 253 254 On Windows this uses the shell because there's a git wrapper 255 batch file in depot_tools. 256 257 Arguments: 258 args: The arguments to pass to git. 259 """ 260 command = ['git'] + list(args) 261 subprocess.check_call(command, shell=(os.name == 'nt')) 262 263 264def remove_tracked_and_local_dir(path): 265 """Removes the contents of a directory from git, and the filesystem. 266 267 Arguments: 268 path: The path to remove. 269 """ 270 remove_tracked_files([path]) 271 shutil.rmtree(path, ignore_errors=True) 272 os.mkdir(path) 273 274 275def remove_tracked_files(files_to_remove): 276 """Removes tracked files from git. 277 278 Arguments: 279 files_to_remove: The files to remove. 280 """ 281 files_to_remove = [f for f in files_to_remove if os.path.exists(f)] 282 if files_to_remove: 283 git('rm', '-rf', '--ignore-unmatch', *files_to_remove) 284 285 286def sed_in_place(input_filename, program): 287 """Replaces text in a file. 288 289 Arguments: 290 input_filename: The file to edit. 291 program: The sed program to perform edits on the file. 292 """ 293 # OS X's sed requires -e 294 subprocess.check_call(['sed', '-i', '-e', program, input_filename]) 295 296 297def check_copying(full_path_to_third_party_libxml_src): 298 path = os.path.join(full_path_to_third_party_libxml_src, 'COPYING') 299 if not os.path.exists(path): 300 return 301 with open(path) as f: 302 s = f.read() 303 if 'GNU' in s: 304 raise Exception('check COPYING') 305 306 307def prepare_libxml_distribution(src_path, libxml2_repo_path, temp_dir): 308 """Makes a libxml2 distribution. 309 310 Args: 311 src_path: The path to the Chromium checkout. 312 libxml2_repo_path: The path to the local clone of the libxml2 repo. 313 temp_dir: A temporary directory to stage the distribution to. 314 315 Returns: A tuple of commit hash and full path to the archive. 316 """ 317 # If it was necessary to push from a distribution prepared upstream, 318 # this is the point to inject it: Return the version string and the 319 # distribution tar file. 320 321 # The libxml2 repo we're pulling changes from should not have 322 # local changes. This *should* be a commit that's publicly visible 323 # in the upstream repo; reviewers should check this. 324 check_clean(libxml2_repo_path) 325 326 temp_config_path = os.path.join(temp_dir, 'config') 327 os.mkdir(temp_config_path) 328 temp_src_path = os.path.join(temp_dir, 'src') 329 os.mkdir(temp_src_path) 330 331 with WorkingDir(libxml2_repo_path): 332 commit = subprocess.check_output( 333 ['git', 'log', '-n', '1', '--pretty=format:%H', 334 'HEAD']).decode('ascii') 335 subprocess.check_call( 336 'git archive HEAD | tar -x -C "%s"' % temp_src_path, 337 shell=True) 338 with WorkingDir(temp_src_path): 339 os.remove('.gitignore') 340 for patch in PATCHES: 341 print('applying %s' % patch) 342 subprocess.check_call( 343 'patch -p1 --fuzz=0 < %s' % os.path.join( 344 src_path, THIRD_PARTY_LIBXML_SRC, '..', 'chromium', patch), 345 shell=True) 346 347 with WorkingDir(temp_config_path): 348 print('../src/autogen.sh %s' % XML_CONFIGURE_OPTIONS) 349 subprocess.check_call(['../src/autogen.sh'] + XML_CONFIGURE_OPTIONS) 350 subprocess.check_call(['make', 'dist-all']) 351 352 # Work out what it is called 353 tar_file = subprocess.check_output( 354 '''awk '/PACKAGE =/ {p=$3} /VERSION =/ {v=$3} ''' 355 '''END {printf("%s-%s.tar.xz", p, v)}' Makefile''', 356 shell=True).decode('ascii') 357 return commit, os.path.abspath(tar_file) 358 359 360def roll_libxml_linux(src_path, libxml2_repo_path, fast): 361 with WorkingDir(src_path): 362 # Export the upstream git repo. 363 try: 364 temp_dir = tempfile.mkdtemp() 365 print('temporary directory: %s' % temp_dir) 366 367 commit, tar_file = prepare_libxml_distribution( 368 src_path, libxml2_repo_path, temp_dir) 369 370 # Remove all of the old libxml to ensure only desired cruft 371 # accumulates 372 remove_tracked_and_local_dir(THIRD_PARTY_LIBXML_SRC) 373 374 # Update the libxml repo and export it to the Chromium tree 375 with WorkingDir(THIRD_PARTY_LIBXML_SRC): 376 subprocess.check_call( 377 'tar xJf %s --strip-components=1' % tar_file, 378 shell=True) 379 finally: 380 shutil.rmtree(temp_dir) 381 382 with WorkingDir(THIRD_PARTY_LIBXML_SRC): 383 # Put the version number is the README file 384 sed_in_place('../README.chromium', 385 's/Version: .*$/Version: %s/' % commit) 386 387 with WorkingDir('../linux'): 388 subprocess.check_call( 389 ['../src/autogen.sh'] + XML_CONFIGURE_OPTIONS) 390 check_copying(os.getcwd()) 391 sed_in_place('config.h', 's/#define HAVE_RAND_R 1//') 392 393 # Add *everything* 394 with WorkingDir('../src'): 395 git('add', '*') 396 if fast: 397 with WorkingDir('..'): 398 remove_tracked_files(FILES_TO_REMOVE) 399 git('commit', '-am', '%s libxml, linux' % commit) 400 if fast: 401 print('Now upload for review, etc.') 402 else: 403 print('Now push to Windows and run steps there.') 404 405 406def roll_libxml_win32(src_path): 407 with WorkingDir(src_path): 408 # Run the configure script. 409 with WorkingDir(os.path.join(THIRD_PARTY_LIBXML_SRC, 'win32')): 410 subprocess.check_call( 411 ['cscript', '//E:jscript', 'configure.js', 'compiler=msvc'] + 412 XML_WIN32_CONFIGURE_OPTIONS) 413 414 # Add and commit the result. 415 shutil.move('../config.h', '../../win32/config.h') 416 git('add', '../../win32/config.h') 417 shutil.move('../include/libxml/xmlversion.h', 418 '../../win32/include/libxml/xmlversion.h') 419 git('add', '../../win32/include/libxml/xmlversion.h') 420 git('commit', '--allow-empty', '-m', 'Windows') 421 git('clean', '-f') 422 print('Now push to Mac and run steps there.') 423 424 425def roll_libxml_mac(src_path, icu4c_path): 426 icu4c_path = os.path.abspath(os.path.expanduser(icu4c_path)) 427 os.environ["LDFLAGS"] = "-L" + os.path.join(icu4c_path, 'lib') 428 os.environ["CPPFLAGS"] = "-I" + os.path.join(icu4c_path, 'include') 429 os.environ["PKG_CONFIG_PATH"] = os.path.join(icu4c_path, 'lib/pkgconfig') 430 431 full_path_to_third_party_libxml = os.path.join( 432 src_path, THIRD_PARTY_LIBXML_SRC, '..') 433 434 with WorkingDir(os.path.join(full_path_to_third_party_libxml, 'mac')): 435 subprocess.check_call(['autoreconf', '-i', '../src']) 436 os.chmod('../src/configure', 437 os.stat('../src/configure').st_mode | stat.S_IXUSR) 438 subprocess.check_call(['../src/configure'] + XML_CONFIGURE_OPTIONS) 439 sed_in_place('config.h', 's/#define HAVE_RAND_R 1//') 440 441 with WorkingDir(full_path_to_third_party_libxml): 442 commit = subprocess.check_output( 443 ['awk', '/Version:/ {print $2}', 444 'README.chromium']).decode('ascii') 445 remove_tracked_files(FILES_TO_REMOVE) 446 commit_message = 'Roll libxml to %s' % commit 447 git('commit', '-am', commit_message) 448 print('Now upload for review, etc.') 449 450 451def check_clean(path): 452 with WorkingDir(path): 453 status = subprocess.check_output(['git', 'status', 454 '-s']).decode('ascii') 455 if len(status) > 0: 456 raise Exception('repository at %s is not clean' % path) 457 458 459def main(): 460 src_dir = os.getcwd() 461 if not os.path.exists(os.path.join(src_dir, 'third_party')): 462 print('error: run this script from the Chromium src directory') 463 sys.exit(1) 464 465 parser = argparse.ArgumentParser( 466 description='Roll the libxml2 dependency in Chromium') 467 platform = parser.add_mutually_exclusive_group(required=True) 468 platform.add_argument('--linux', action='store_true') 469 platform.add_argument('--win32', action='store_true') 470 platform.add_argument('--mac', action='store_true') 471 platform.add_argument('--linuxfast', action='store_true') 472 parser.add_argument( 473 'libxml2_repo_path', 474 type=str, 475 nargs='?', 476 help='The path to the local clone of the libxml2 git repo.') 477 parser.add_argument( 478 '--icu4c_path', 479 help='The path to the homebrew installation of icu4c.') 480 args = parser.parse_args() 481 482 if args.linux or args.linuxfast: 483 libxml2_repo_path = args.libxml2_repo_path 484 if not libxml2_repo_path: 485 print('Specify the path to the local libxml2 repo clone.') 486 sys.exit(1) 487 libxml2_repo_path = os.path.abspath(libxml2_repo_path) 488 roll_libxml_linux(src_dir, libxml2_repo_path, args.linuxfast) 489 elif args.win32: 490 roll_libxml_win32(src_dir) 491 elif args.mac: 492 icu4c_path = args.icu4c_path 493 if not icu4c_path: 494 print('Specify the path to the homebrew installation of icu4c with --icu4c_path.') 495 print(' ex: roll.py --mac --icu4c_path=~/homebrew/opt/icu4c') 496 sys.exit(1) 497 roll_libxml_mac(src_dir, icu4c_path) 498 499 500if __name__ == '__main__': 501 main() 502