1*67e74705SXin Li#!/usr/bin/python 2*67e74705SXin Li 3*67e74705SXin Li# [PR 11661] Note that we hardwire to /usr/bin/python because we 4*67e74705SXin Li# want to the use the system version of Python on Mac OS X. 5*67e74705SXin Li# This one has the scripting bridge enabled. 6*67e74705SXin Li 7*67e74705SXin Liimport sys 8*67e74705SXin Liif sys.version_info < (2, 7): 9*67e74705SXin Li print "set-xcode-analyzer requires Python 2.7 or later" 10*67e74705SXin Li sys.exit(1) 11*67e74705SXin Li 12*67e74705SXin Liimport os 13*67e74705SXin Liimport subprocess 14*67e74705SXin Liimport re 15*67e74705SXin Liimport tempfile 16*67e74705SXin Liimport shutil 17*67e74705SXin Liimport stat 18*67e74705SXin Lifrom AppKit import * 19*67e74705SXin Li 20*67e74705SXin Lidef FindClangSpecs(path): 21*67e74705SXin Li print "(+) Searching for xcspec file in: ", path 22*67e74705SXin Li for root, dirs, files in os.walk(path): 23*67e74705SXin Li for f in files: 24*67e74705SXin Li if f.endswith(".xcspec") and f.startswith("Clang LLVM"): 25*67e74705SXin Li yield os.path.join(root, f) 26*67e74705SXin Li 27*67e74705SXin Lidef ModifySpec(path, isBuiltinAnalyzer, pathToChecker): 28*67e74705SXin Li t = tempfile.NamedTemporaryFile(delete=False) 29*67e74705SXin Li foundAnalyzer = False 30*67e74705SXin Li with open(path) as f: 31*67e74705SXin Li if isBuiltinAnalyzer: 32*67e74705SXin Li # First search for CLANG_ANALYZER_EXEC. Newer 33*67e74705SXin Li # versions of Xcode set EXEC_PATH to be CLANG_ANALYZER_EXEC. 34*67e74705SXin Li with open(path) as f2: 35*67e74705SXin Li for line in f2: 36*67e74705SXin Li if line.find("CLANG_ANALYZER_EXEC") >= 0: 37*67e74705SXin Li pathToChecker = "$(CLANG_ANALYZER_EXEC)" 38*67e74705SXin Li break 39*67e74705SXin Li # Now create a new file. 40*67e74705SXin Li for line in f: 41*67e74705SXin Li if not foundAnalyzer: 42*67e74705SXin Li if line.find("Static Analyzer") >= 0: 43*67e74705SXin Li foundAnalyzer = True 44*67e74705SXin Li else: 45*67e74705SXin Li m = re.search('^(\s*ExecPath\s*=\s*")', line) 46*67e74705SXin Li if m: 47*67e74705SXin Li line = "".join([m.group(0), pathToChecker, '";\n']) 48*67e74705SXin Li # Do not modify further ExecPath's later in the xcspec. 49*67e74705SXin Li foundAnalyzer = False 50*67e74705SXin Li t.write(line) 51*67e74705SXin Li t.close() 52*67e74705SXin Li print "(+) processing:", path 53*67e74705SXin Li try: 54*67e74705SXin Li shutil.copy(t.name, path) 55*67e74705SXin Li os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) 56*67e74705SXin Li except IOError, why: 57*67e74705SXin Li print " (-) Cannot update file:", why, "\n" 58*67e74705SXin Li except OSError, why: 59*67e74705SXin Li print " (-) Cannot update file:", why, "\n" 60*67e74705SXin Li os.unlink(t.name) 61*67e74705SXin Li 62*67e74705SXin Lidef main(): 63*67e74705SXin Li from optparse import OptionParser 64*67e74705SXin Li parser = OptionParser('usage: %prog [options]') 65*67e74705SXin Li parser.set_description(__doc__) 66*67e74705SXin Li parser.add_option("--use-checker-build", dest="path", 67*67e74705SXin Li help="Use the Clang located at the provided absolute path, e.g. /Users/foo/checker-1") 68*67e74705SXin Li parser.add_option("--use-xcode-clang", action="store_const", 69*67e74705SXin Li const="$(CLANG)", dest="default", 70*67e74705SXin Li help="Use the Clang bundled with Xcode") 71*67e74705SXin Li (options, args) = parser.parse_args() 72*67e74705SXin Li if options.path is None and options.default is None: 73*67e74705SXin Li parser.error("You must specify a version of Clang to use for static analysis. Specify '-h' for details") 74*67e74705SXin Li 75*67e74705SXin Li # determine if Xcode is running 76*67e74705SXin Li for x in NSWorkspace.sharedWorkspace().runningApplications(): 77*67e74705SXin Li if x.localizedName().find("Xcode") >= 0: 78*67e74705SXin Li print "(-) You must quit Xcode first before modifying its configuration files." 79*67e74705SXin Li sys.exit(1) 80*67e74705SXin Li 81*67e74705SXin Li isBuiltinAnalyzer = False 82*67e74705SXin Li if options.path: 83*67e74705SXin Li # Expand tildes. 84*67e74705SXin Li path = os.path.expanduser(options.path) 85*67e74705SXin Li if not path.endswith("clang"): 86*67e74705SXin Li print "(+) Using Clang bundled with checker build:", path 87*67e74705SXin Li path = os.path.join(path, "bin", "clang"); 88*67e74705SXin Li else: 89*67e74705SXin Li print "(+) Using Clang located at:", path 90*67e74705SXin Li else: 91*67e74705SXin Li print "(+) Using the Clang bundled with Xcode" 92*67e74705SXin Li path = options.default 93*67e74705SXin Li isBuiltinAnalyzer = True 94*67e74705SXin Li 95*67e74705SXin Li try: 96*67e74705SXin Li xcode_path = subprocess.check_output(["xcode-select", "-print-path"]) 97*67e74705SXin Li except AttributeError: 98*67e74705SXin Li # Fall back to the default install location when using Python < 2.7.0 99*67e74705SXin Li xcode_path = "/Developer" 100*67e74705SXin Li if (xcode_path.find(".app/") != -1): 101*67e74705SXin Li # Cut off the 'Developer' dir, as the xcspec lies in another part 102*67e74705SXin Li # of the Xcode.app subtree. 103*67e74705SXin Li xcode_path = xcode_path.rsplit('/Developer', 1)[0] 104*67e74705SXin Li 105*67e74705SXin Li foundSpec = False 106*67e74705SXin Li for x in FindClangSpecs(xcode_path): 107*67e74705SXin Li foundSpec = True 108*67e74705SXin Li ModifySpec(x, isBuiltinAnalyzer, path) 109*67e74705SXin Li 110*67e74705SXin Li if foundSpec == False: 111*67e74705SXin Li print "(-) No compiler configuration file was found. Xcode's analyzer has not been updated." 112*67e74705SXin Li 113*67e74705SXin Liif __name__ == '__main__': 114*67e74705SXin Li main() 115