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 """ 7 Runs Coverity Prevent on a build of Chromium. 8 9 This script should be run in a Visual Studio Command Prompt, so that the 10 INCLUDE, LIB, and PATH environment variables are set properly for Visual 11 Studio. 12 13 Usage 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 20 For a full list of options, pass the '--help' switch. 21 22 See http://support.microsoft.com/kb/308569 for running this script as a 23 Scheduled Task on Windows XP. 24 25 """ 26 27 import optparse 28 import os 29 import os.path 30 import shutil 31 import subprocess 32 import sys 33 import 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 43 CHROMIUM_SOURCE_DIR = 'C:\\chromium.latest' 44 45 # Relative to CHROMIUM_SOURCE_DIR. 46 CHROMIUM_SOLUTION_FILE = 'src\\chrome\\chrome.sln' 47 48 # Relative to CHROMIUM_SOURCE_DIR. 49 CHROMIUM_SOLUTION_DIR = 'src\\chrome' 50 51 COVERITY_BIN_DIR = 'C:\\coverity\\prevent-win32-4.5.1\\bin' 52 53 COVERITY_INTERMEDIATE_DIR = 'C:\\coverity\\cvbuild\\cr_int' 54 55 COVERITY_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 66 COVERITY_REMOTE = 'chromecoverity-linux1' 67 68 COVERITY_PORT = '5467' 69 70 COVERITY_PRODUCT = 'Chromium' 71 72 COVERITY_TARGET = 'Windows' 73 74 COVERITY_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. 78 LOCK_FILE = 'coverity.lock' 79 80 81 def _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 89 def _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 99 def _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 105 def 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 237 def 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 307 if '__main__' == __name__: 308 sys.exit(main()) 309