1 #!/usr/bin/python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Do all the steps required to build and test against nacl.""" 7 8 9 import optparse 10 import os.path 11 import re 12 import shutil 13 import subprocess 14 import sys 15 16 import find_chrome 17 18 19 # Copied from buildbot/buildbot_lib.py 20 def TryToCleanContents(path, file_name_filter=lambda fn: True): 21 """ 22 Remove the contents of a directory without touching the directory itself. 23 Ignores all failures. 24 """ 25 if os.path.exists(path): 26 for fn in os.listdir(path): 27 TryToCleanPath(os.path.join(path, fn), file_name_filter) 28 29 30 # Copied from buildbot/buildbot_lib.py 31 def TryToCleanPath(path, file_name_filter=lambda fn: True): 32 """ 33 Removes a file or directory. 34 Ignores all failures. 35 """ 36 if os.path.exists(path): 37 if file_name_filter(path): 38 print 'Trying to remove %s' % path 39 if os.path.isdir(path): 40 shutil.rmtree(path, ignore_errors=True) 41 else: 42 try: 43 os.remove(path) 44 except Exception: 45 pass 46 else: 47 print 'Skipping %s' % path 48 49 50 # TODO(ncbray): this is somewhat unsafe. We should fix the underlying problem. 51 def CleanTempDir(): 52 # Only delete files and directories like: 53 # a) C:\temp\83C4.tmp 54 # b) /tmp/.org.chromium.Chromium.EQrEzl 55 file_name_re = re.compile( 56 r'[\\/]([0-9a-fA-F]+\.tmp|\.org\.chrom\w+\.Chrom\w+\..+)$') 57 file_name_filter = lambda fn: file_name_re.search(fn) is not None 58 59 path = os.environ.get('TMP', os.environ.get('TEMP', '/tmp')) 60 if len(path) >= 4 and os.path.isdir(path): 61 print 62 print "Cleaning out the temp directory." 63 print 64 TryToCleanContents(path, file_name_filter) 65 else: 66 print 67 print "Cannot find temp directory, not cleaning it." 68 print 69 70 71 def RunCommand(cmd, cwd, env): 72 sys.stdout.write('\nRunning %s\n\n' % ' '.join(cmd)) 73 sys.stdout.flush() 74 retcode = subprocess.call(cmd, cwd=cwd, env=env) 75 if retcode != 0: 76 sys.stdout.write('\nFailed: %s\n\n' % ' '.join(cmd)) 77 sys.exit(retcode) 78 79 80 def RunTests(name, cmd, nacl_dir, env): 81 sys.stdout.write('\n\nBuilding files needed for %s testing...\n\n' % name) 82 RunCommand(cmd + ['do_not_run_tests=1', '-j8'], nacl_dir, env) 83 sys.stdout.write('\n\nRunning %s tests...\n\n' % name) 84 RunCommand(cmd, nacl_dir, env) 85 86 87 def BuildAndTest(options): 88 # Refuse to run under cygwin. 89 if sys.platform == 'cygwin': 90 raise Exception('I do not work under cygwin, sorry.') 91 92 # By default, use the version of Python is being used to run this script. 93 python = sys.executable 94 if sys.platform == 'darwin': 95 # Mac 10.5 bots tend to use a particularlly old version of Python, look for 96 # a newer version. 97 macpython27 = '/Library/Frameworks/Python.framework/Versions/2.7/bin/python' 98 if os.path.exists(macpython27): 99 python = macpython27 100 101 script_dir = os.path.dirname(os.path.abspath(__file__)) 102 src_dir = os.path.dirname(os.path.dirname(os.path.dirname(script_dir))) 103 nacl_dir = os.path.join(src_dir, 'native_client') 104 105 # Decide platform specifics. 106 env = dict(os.environ) 107 if sys.platform in ['win32', 'cygwin']: 108 if options.bits == 64: 109 bits = 64 110 elif options.bits == 32: 111 bits = 32 112 elif '64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or \ 113 '64' in os.environ.get('PROCESSOR_ARCHITEW6432', ''): 114 bits = 64 115 else: 116 bits = 32 117 msvs_path = ';'.join([ 118 r'c:\Program Files\Microsoft Visual Studio 9.0\VC', 119 r'c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC', 120 r'c:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools', 121 r'c:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools', 122 r'c:\Program Files\Microsoft Visual Studio 8\VC', 123 r'c:\Program Files (x86)\Microsoft Visual Studio 8\VC', 124 r'c:\Program Files\Microsoft Visual Studio 8\Common7\Tools', 125 r'c:\Program Files (x86)\Microsoft Visual Studio 8\Common7\Tools', 126 ]) 127 env['PATH'] += ';' + msvs_path 128 scons = [python, 'scons.py'] 129 elif sys.platform == 'darwin': 130 bits = 32 131 scons = [python, 'scons.py'] 132 else: 133 p = subprocess.Popen( 134 'uname -m | ' 135 'sed -e "s/i.86/ia32/;s/x86_64/x64/;s/amd64/x64/;s/arm.*/arm/"', 136 shell=True, stdout=subprocess.PIPE) 137 (p_stdout, _) = p.communicate() 138 assert p.returncode == 0 139 if options.bits == 64: 140 bits = 64 141 elif options.bits == 32: 142 bits = 32 143 elif p_stdout.find('64') >= 0: 144 bits = 64 145 else: 146 bits = 32 147 # xvfb-run has a 2-second overhead per invocation, so it is cheaper to wrap 148 # the entire build step rather than each test (browser_headless=1). 149 scons = ['xvfb-run', '--auto-servernum', python, 'scons.py'] 150 151 if options.browser_path: 152 chrome_filename = options.browser_path 153 else: 154 chrome_filename = find_chrome.FindChrome(src_dir, [options.mode]) 155 if chrome_filename is None: 156 raise Exception('Cannot find a chome binary - specify one with ' 157 '--browser_path?') 158 159 if options.jobs > 1: 160 scons.append('-j%d' % options.jobs) 161 162 scons.append('disable_tests=%s' % options.disable_tests) 163 164 if options.buildbot is not None: 165 scons.append('buildbot=%s' % (options.buildbot,)) 166 167 # Clean the output of the previous build. 168 # Incremental builds can get wedged in weird ways, so we're trading speed 169 # for reliability. 170 shutil.rmtree(os.path.join(nacl_dir, 'scons-out'), True) 171 172 # check that the HOST (not target) is 64bit 173 # this is emulating what msvs_env.bat is doing 174 if '64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or \ 175 '64' in os.environ.get('PROCESSOR_ARCHITEW6432', ''): 176 # 64bit HOST 177 env['VS90COMNTOOLS'] = ('c:\\Program Files (x86)\\' 178 'Microsoft Visual Studio 9.0\\Common7\\Tools\\') 179 env['VS80COMNTOOLS'] = ('c:\\Program Files (x86)\\' 180 'Microsoft Visual Studio 8.0\\Common7\\Tools\\') 181 else: 182 # 32bit HOST 183 env['VS90COMNTOOLS'] = ('c:\\Program Files\\Microsoft Visual Studio 9.0\\' 184 'Common7\\Tools\\') 185 env['VS80COMNTOOLS'] = ('c:\\Program Files\\Microsoft Visual Studio 8.0\\' 186 'Common7\\Tools\\') 187 188 # Run nacl/chrome integration tests. 189 # Note that we have to add nacl_irt_test to --mode in order to get 190 # inbrowser_test_runner to run. 191 # TODO(mseaborn): Change it so that inbrowser_test_runner is not a 192 # special case. 193 cmd = scons + ['--verbose', '-k', 'platform=x86-%d' % bits, 194 '--mode=opt-host,nacl,nacl_irt_test', 195 'chrome_browser_path=%s' % chrome_filename, 196 ] 197 if not options.integration_bot and not options.morenacl_bot: 198 cmd.append('disable_flaky_tests=1') 199 cmd.append('chrome_browser_tests') 200 201 # Download the toolchain(s). 202 RunCommand([python, 203 os.path.join(nacl_dir, 'build', 'download_toolchains.py'), 204 '--no-arm-trusted', '--no-pnacl', 'TOOL_REVISIONS'], 205 nacl_dir, os.environ) 206 207 CleanTempDir() 208 209 if options.enable_newlib: 210 RunTests('nacl-newlib', cmd, nacl_dir, env) 211 212 if options.enable_glibc: 213 RunTests('nacl-glibc', cmd + ['--nacl_glibc'], nacl_dir, env) 214 215 216 def MakeCommandLineParser(): 217 parser = optparse.OptionParser() 218 parser.add_option('-m', '--mode', dest='mode', default='Debug', 219 help='Debug/Release mode') 220 parser.add_option('-j', dest='jobs', default=1, type='int', 221 help='Number of parallel jobs') 222 223 parser.add_option('--enable_newlib', dest='enable_newlib', default=-1, 224 type='int', help='Run newlib tests?') 225 parser.add_option('--enable_glibc', dest='enable_glibc', default=-1, 226 type='int', help='Run glibc tests?') 227 228 # Deprecated, but passed to us by a script in the Chrome repo. 229 # Replaced by --enable_glibc=0 230 parser.add_option('--disable_glibc', dest='disable_glibc', 231 action='store_true', default=False, 232 help='Do not test using glibc.') 233 234 parser.add_option('--disable_tests', dest='disable_tests', 235 type='string', default='', 236 help='Comma-separated list of tests to omit') 237 builder_name = os.environ.get('BUILDBOT_BUILDERNAME', '') 238 is_integration_bot = 'nacl-chrome' in builder_name 239 parser.add_option('--integration_bot', dest='integration_bot', 240 type='int', default=int(is_integration_bot), 241 help='Is this an integration bot?') 242 is_morenacl_bot = ( 243 'More NaCl' in builder_name or 244 'naclmore' in builder_name) 245 parser.add_option('--morenacl_bot', dest='morenacl_bot', 246 type='int', default=int(is_morenacl_bot), 247 help='Is this a morenacl bot?') 248 249 # Not used on the bots, but handy for running the script manually. 250 parser.add_option('--bits', dest='bits', action='store', 251 type='int', default=None, 252 help='32/64') 253 parser.add_option('--browser_path', dest='browser_path', action='store', 254 type='string', default=None, 255 help='Path to the chrome browser.') 256 parser.add_option('--buildbot', dest='buildbot', action='store', 257 type='string', default=None, 258 help='Value passed to scons as buildbot= option.') 259 return parser 260 261 262 def Main(): 263 parser = MakeCommandLineParser() 264 options, args = parser.parse_args() 265 if options.integration_bot and options.morenacl_bot: 266 parser.error('ERROR: cannot be both an integration bot and a morenacl bot') 267 268 # Set defaults for enabling newlib. 269 if options.enable_newlib == -1: 270 options.enable_newlib = 1 271 272 # Set defaults for enabling glibc. 273 if options.enable_glibc == -1: 274 if options.integration_bot or options.morenacl_bot: 275 options.enable_glibc = 1 276 else: 277 options.enable_glibc = 0 278 279 if args: 280 parser.error('ERROR: invalid argument') 281 BuildAndTest(options) 282 283 284 if __name__ == '__main__': 285 Main() 286