1#!/usr/bin/env python 2# Copyright (c) 2011 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""" 7Runs Coverity Prevent on a build of Chromium. 8 9This script should be run in a Visual Studio Command Prompt, so that the 10INCLUDE, LIB, and PATH environment variables are set properly for Visual 11Studio. 12 13Usage examples: 14 coverity.py 15 coverity.py --dry-run 16 coverity.py --target=debug 17 %comspec% /c ""C:\Program Files\Microsoft Visual Studio 8\VC\vcvarsall.bat" 18 x86 && C:\Python24\python.exe C:\coverity.py" 19 20For a full list of options, pass the '--help' switch. 21 22See http://support.microsoft.com/kb/308569 for running this script as a 23Scheduled Task on Windows XP. 24 25""" 26 27import optparse 28import os 29import os.path 30import shutil 31import subprocess 32import sys 33import time 34 35# These constants provide default values, but are exposed as command-line 36# flags. See the --help for more info. Note that for historical reasons 37# (the script started out as Windows-only and has legacy usages which pre-date 38# these switches), the constants are all tuned for Windows. 39# Usage of this script on Linux pretty much requires explicit 40# --source-dir, --coverity-bin-dir, --coverity-intermediate-dir, and 41# --coverity-target command line flags. 42 43CHROMIUM_SOURCE_DIR = 'C:\\chromium.latest' 44 45# Relative to CHROMIUM_SOURCE_DIR. 46CHROMIUM_SOLUTION_FILE = 'src\\chrome\\chrome.sln' 47 48# Relative to CHROMIUM_SOURCE_DIR. 49CHROMIUM_SOLUTION_DIR = 'src\\chrome' 50 51COVERITY_BIN_DIR = 'C:\\coverity\\prevent-win32-4.5.1\\bin' 52 53COVERITY_INTERMEDIATE_DIR = 'C:\\coverity\\cvbuild\\cr_int' 54 55COVERITY_ANALYZE_OPTIONS = ('--cxx --security --concurrency ' 56 '--enable ATOMICITY ' 57 '--enable MISSING_LOCK ' 58 '--enable DELETE_VOID ' 59 '--checker-option PASS_BY_VALUE:size_threshold:16 ' 60 '--checker-option ' 61 'USE_AFTER_FREE:allow_simple_use:false ' 62 '--enable-constraint-fpp ' 63 '--enable-callgraph-metrics') 64 65# Might need to be changed to FQDN 66COVERITY_REMOTE = 'chromecoverity-linux1' 67 68COVERITY_PORT = '5467' 69 70COVERITY_PRODUCT = 'Chromium' 71 72COVERITY_TARGET = 'Windows' 73 74COVERITY_USER = 'admin' 75# looking for a PASSWORD constant? Look at --coverity-password-file instead. 76 77# Relative to CHROMIUM_SOURCE_DIR. Contains the pid of this script. 78LOCK_FILE = 'coverity.lock' 79 80 81def _ReadPassword(pwfilename): 82 """Reads the coverity password in from a file where it was stashed""" 83 pwfile = open(pwfilename, 'r') 84 password = pwfile.readline() 85 pwfile.close() 86 return password.rstrip() 87 88 89def _RunCommand(cmd, dry_run, shell=False, echo_cmd=True): 90 """Runs the command if dry_run is false, otherwise just prints the command.""" 91 if echo_cmd: 92 print cmd 93 if not dry_run: 94 return subprocess.call(cmd, shell=shell) 95 else: 96 return 0 97 98 99def _ReleaseLock(lock_file, lock_filename): 100 """Removes the lockfile. Function-ized so we can bail from anywhere""" 101 os.close(lock_file) 102 os.remove(lock_filename) 103 104 105def run_coverity(options, args): 106 """Runs all the selected tests for the given build type and target.""" 107 # Create the lock file to prevent another instance of this script from 108 # running. 109 lock_filename = os.path.join(options.source_dir, LOCK_FILE) 110 try: 111 lock_file = os.open(lock_filename, 112 os.O_CREAT | os.O_EXCL | os.O_TRUNC | os.O_RDWR) 113 except OSError, err: 114 print 'Failed to open lock file:\n ' + str(err) 115 return 1 116 117 # Write the pid of this script (the python.exe process) to the lock file. 118 os.write(lock_file, str(os.getpid())) 119 120 options.target = options.target.title() 121 122 start_time = time.time() 123 124 print 'Change directory to ' + options.source_dir 125 os.chdir(options.source_dir) 126 127 # The coverity-password filename may have been a relative path. 128 # If so, assume it's relative to the source directory, which means 129 # the time to read the password is after we do the chdir(). 130 coverity_password = _ReadPassword(options.coverity_password_file) 131 132 cmd = 'gclient sync' 133 gclient_exit = _RunCommand(cmd, options.dry_run, shell=True) 134 if gclient_exit != 0: 135 print 'gclient aborted with status %s' % gclient_exit 136 _ReleaseLock(lock_file, lock_filename) 137 return 1 138 139 print 'Elapsed time: %ds' % (time.time() - start_time) 140 141 # Do a clean build. Remove the build output directory first. 142 if sys.platform.startswith('linux'): 143 rm_path = os.path.join(options.source_dir,'src','out',options.target) 144 elif sys.platform == 'win32': 145 rm_path = os.path.join(options.source_dir,options.solution_dir, 146 options.target) 147 elif sys.platform == 'darwin': 148 rm_path = os.path.join(options.source_dir,'src','xcodebuild') 149 else: 150 print 'Platform "%s" unrecognized, aborting' % sys.platform 151 _ReleaseLock(lock_file, lock_filename) 152 return 1 153 154 if options.dry_run: 155 print 'shutil.rmtree(%s)' % repr(rm_path) 156 else: 157 shutil.rmtree(rm_path,True) 158 159 if options.preserve_intermediate_dir: 160 print 'Preserving intermediate directory.' 161 else: 162 if options.dry_run: 163 print 'shutil.rmtree(%s)' % repr(options.coverity_intermediate_dir) 164 print 'os.mkdir(%s)' % repr(options.coverity_intermediate_dir) 165 else: 166 shutil.rmtree(options.coverity_intermediate_dir,True) 167 os.mkdir(options.coverity_intermediate_dir) 168 169 print 'Elapsed time: %ds' % (time.time() - start_time) 170 171 use_shell_during_make = False 172 if sys.platform.startswith('linux'): 173 use_shell_during_make = True 174 os.chdir('src') 175 _RunCommand('pwd', options.dry_run, shell=True) 176 cmd = '%s/cov-build --dir %s make BUILDTYPE=%s chrome' % ( 177 options.coverity_bin_dir, options.coverity_intermediate_dir, 178 options.target) 179 elif sys.platform == 'win32': 180 cmd = ('%s\\cov-build.exe --dir %s devenv.com %s\\%s /build %s ' 181 '/project chrome.vcproj') % ( 182 options.coverity_bin_dir, options.coverity_intermediate_dir, 183 options.source_dir, options.solution_file, options.target) 184 elif sys.platform == 'darwin': 185 use_shell_during_make = True 186 os.chdir('src/chrome') 187 _RunCommand('pwd', options.dry_run, shell=True) 188 cmd = ('%s/cov-build --dir %s xcodebuild -project chrome.xcodeproj ' 189 '-configuration %s -target chrome') % ( 190 options.coverity_bin_dir, options.coverity_intermediate_dir, 191 options.target) 192 193 194 _RunCommand(cmd, options.dry_run, shell=use_shell_during_make) 195 print 'Elapsed time: %ds' % (time.time() - start_time) 196 197 cov_analyze_exe = os.path.join(options.coverity_bin_dir,'cov-analyze') 198 cmd = '%s --dir %s %s' % (cov_analyze_exe, 199 options.coverity_intermediate_dir, 200 options.coverity_analyze_options) 201 _RunCommand(cmd, options.dry_run, shell=use_shell_during_make) 202 print 'Elapsed time: %ds' % (time.time() - start_time) 203 204 cov_commit_exe = os.path.join(options.coverity_bin_dir,'cov-commit-defects') 205 206 # On Linux we have started using a Target with a space in it, so we want 207 # to quote it. On the other hand, Windows quoting doesn't work quite the 208 # same way. To be conservative, I'd like to avoid quoting an argument 209 # that doesn't need quoting and which we haven't historically been quoting 210 # on that platform. So, only quote the target if we have to. 211 coverity_target = options.coverity_target 212 if sys.platform != 'win32': 213 coverity_target = '"%s"' % coverity_target 214 215 cmd = ('%s --dir %s --remote %s --port %s ' 216 '--product %s ' 217 '--target %s ' 218 '--user %s ' 219 '--password %s') % (cov_commit_exe, 220 options.coverity_intermediate_dir, 221 options.coverity_dbhost, 222 options.coverity_port, 223 options.coverity_product, 224 coverity_target, 225 options.coverity_user, 226 coverity_password) 227 # Avoid echoing the Commit command because it has a password in it 228 _RunCommand(cmd, options.dry_run, shell=use_shell_during_make, echo_cmd=False) 229 230 print 'Total time: %ds' % (time.time() - start_time) 231 232 _ReleaseLock(lock_file, lock_filename) 233 234 return 0 235 236 237def main(): 238 option_parser = optparse.OptionParser() 239 option_parser.add_option('', '--dry-run', action='store_true', default=False, 240 help='print but don\'t run the commands') 241 242 option_parser.add_option('', '--target', default='Release', 243 help='build target (Debug or Release)') 244 245 option_parser.add_option('', '--source-dir', dest='source_dir', 246 help='full path to directory ABOVE "src"', 247 default=CHROMIUM_SOURCE_DIR) 248 249 option_parser.add_option('', '--solution-file', dest='solution_file', 250 default=CHROMIUM_SOLUTION_FILE) 251 252 option_parser.add_option('', '--solution-dir', dest='solution_dir', 253 default=CHROMIUM_SOLUTION_DIR) 254 255 option_parser.add_option('', '--coverity-bin-dir', dest='coverity_bin_dir', 256 default=COVERITY_BIN_DIR) 257 258 option_parser.add_option('', '--coverity-intermediate-dir', 259 dest='coverity_intermediate_dir', 260 default=COVERITY_INTERMEDIATE_DIR) 261 262 option_parser.add_option('', '--coverity-analyze-options', 263 dest='coverity_analyze_options', 264 help=('all cov-analyze options, e.g. "%s"' 265 % COVERITY_ANALYZE_OPTIONS), 266 default=COVERITY_ANALYZE_OPTIONS) 267 268 option_parser.add_option('', '--coverity-db-host', 269 dest='coverity_dbhost', 270 help=('coverity defect db server hostname, e.g. %s' 271 % COVERITY_REMOTE), 272 default=COVERITY_REMOTE) 273 274 option_parser.add_option('', '--coverity-db-port', dest='coverity_port', 275 help=('port # of coverity web/db server, e.g. %s' 276 % COVERITY_PORT), 277 default=COVERITY_PORT) 278 279 option_parser.add_option('', '--coverity-product', dest='coverity_product', 280 help=('Product name reported to coverity, e.g. %s' 281 % COVERITY_PRODUCT), 282 default=COVERITY_PRODUCT) 283 284 option_parser.add_option('', '--coverity-target', dest='coverity_target', 285 help='Platform Target reported to coverity', 286 default=COVERITY_TARGET) 287 288 option_parser.add_option('', '--coverity-user', dest='coverity_user', 289 help='Username used to log into coverity', 290 default=COVERITY_USER) 291 292 option_parser.add_option('', '--coverity-password-file', 293 dest='coverity_password_file', 294 help='file containing the coverity password', 295 default='coverity-password') 296 297 helpmsg = ('By default, the intermediate dir is emptied before analysis. ' 298 'This switch disables that behavior.') 299 option_parser.add_option('', '--preserve-intermediate-dir', 300 action='store_true', help=helpmsg, 301 default=False) 302 303 options, args = option_parser.parse_args() 304 return run_coverity(options, args) 305 306 307if '__main__' == __name__: 308 sys.exit(main()) 309