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.
6 """Windows can't run .sh files, so this is a Python implementation of
7 update.sh. This script should replace update.sh on all platforms eventually."""
18 # Do NOT CHANGE this if you don't know what you're doing -- see
19 # https://code.google.com/p/chromium/wiki/UpdatingClang
20 # Reverting problematic clang rolls is safe, though.
21 # Note: this revision is only used for Windows. Other platforms use update.sh.
22 LLVM_WIN_REVISION
= 'HEAD'
24 # ASan on Windows is useful enough to use it even while the clang/win is still
25 # in bringup. Use a pinned revision to make it slightly more stable.
26 if (re
.search(r
'\b(asan)=1', os
.environ
.get('GYP_DEFINES', '')) and
27 not 'LLVM_FORCE_HEAD_REVISION' in os
.environ
):
28 LLVM_WIN_REVISION
= '233530'
30 # Path constants. (All of these should be absolute paths.)
31 THIS_DIR
= os
.path
.abspath(os
.path
.dirname(__file__
))
32 CHROMIUM_DIR
= os
.path
.abspath(os
.path
.join(THIS_DIR
, '..', '..', '..'))
33 LLVM_DIR
= os
.path
.join(CHROMIUM_DIR
, 'third_party', 'llvm')
34 CHROME_TOOLS_SHIM_DIR
= os
.path
.join(LLVM_DIR
, 'tools', 'chrometools')
35 LLVM_BUILD_DIR
= os
.path
.join(CHROMIUM_DIR
, 'third_party', 'llvm-build',
37 COMPILER_RT_BUILD_DIR
= os
.path
.join(LLVM_BUILD_DIR
, '32bit-compiler-rt')
38 CLANG_DIR
= os
.path
.join(LLVM_DIR
, 'tools', 'clang')
39 LLD_DIR
= os
.path
.join(LLVM_DIR
, 'tools', 'lld')
40 COMPILER_RT_DIR
= os
.path
.join(LLVM_DIR
, 'projects', 'compiler-rt')
41 STAMP_FILE
= os
.path
.join(LLVM_BUILD_DIR
, 'cr_build_revision')
44 LLVM_REPO_URL
='https://llvm.org/svn/llvm-project'
45 if 'LLVM_REPO_URL' in os
.environ
:
46 LLVM_REPO_URL
= os
.environ
['LLVM_REPO_URL']
50 """Return the contents of the stamp file, or '' if it doesn't exist."""
52 with
open(STAMP_FILE
, 'r') as f
:
58 def WriteStampFile(s
):
59 """Write s to the stamp file."""
60 if not os
.path
.exists(LLVM_BUILD_DIR
):
61 os
.makedirs(LLVM_BUILD_DIR
)
62 with
open(STAMP_FILE
, 'w') as f
:
68 def ChmodAndRetry(func
, path
, _
):
69 # Subversion can leave read-only files around.
70 if not os
.access(path
, os
.W_OK
):
71 os
.chmod(path
, stat
.S_IWUSR
)
75 shutil
.rmtree(dir, onerror
=ChmodAndRetry
)
78 def ClobberChromiumBuildFiles():
79 """Clobber Chomium build files."""
80 print 'Clobbering Chromium build files...'
81 out_dir
= os
.path
.join(CHROMIUM_DIR
, 'out')
82 if os
.path
.isdir(out_dir
):
84 print 'Removed Chromium out dir: %s.' % (out_dir
)
87 def RunCommand(command
, fail_hard
=True):
88 """Run command and return success (True) or failure; or if fail_hard is
89 True, exit on failure."""
91 print 'Running %s' % (str(command
))
92 if subprocess
.call(command
, shell
=True) == 0:
100 def CopyFile(src
, dst
):
101 """Copy a file from src to dst."""
102 shutil
.copy(src
, dst
)
103 print "Copying %s to %s" % (src
, dst
)
106 def CopyDirectoryContents(src
, dst
, filename_filter
=None):
107 """Copy the files from directory src to dst
108 with an optional filename filter."""
109 if not os
.path
.exists(dst
):
111 for root
, _
, files
in os
.walk(src
):
113 if filename_filter
and not re
.match(filename_filter
, f
):
115 CopyFile(os
.path
.join(root
, f
), dst
)
118 def Checkout(name
, url
, dir):
119 """Checkout the SVN module at url into dir. Use name for the log message."""
120 print "Checking out %s r%s into '%s'" % (name
, LLVM_WIN_REVISION
, dir)
122 command
= ['svn', 'checkout', '--force', url
+ '@' + LLVM_WIN_REVISION
, dir]
123 if RunCommand(command
, fail_hard
=False):
126 if os
.path
.isdir(dir):
127 print "Removing %s." % (dir)
134 def DeleteChromeToolsShim():
135 shutil
.rmtree(CHROME_TOOLS_SHIM_DIR
, ignore_errors
=True)
138 def CreateChromeToolsShim():
139 """Hooks the Chrome tools into the LLVM build.
141 Several Chrome tools have dependencies on LLVM/Clang libraries. The LLVM build
142 detects implicit tools in the tools subdirectory, so this helper install a
143 shim CMakeLists.txt that forwards to the real directory for the Chrome tools.
145 Note that the shim directory name intentionally has no - or _. The implicit
146 tool detection logic munges them in a weird way."""
147 assert not any(i
in os
.path
.basename(CHROME_TOOLS_SHIM_DIR
) for i
in '-_')
148 os
.mkdir(CHROME_TOOLS_SHIM_DIR
)
149 with
file(os
.path
.join(CHROME_TOOLS_SHIM_DIR
, 'CMakeLists.txt'), 'w') as f
:
150 f
.write('# Automatically generated by tools/clang/scripts/update.py. ' +
152 f
.write('# Since tools/clang is located in another directory, use the \n')
153 f
.write('# two arg version to specify where build artifacts go. CMake\n')
154 f
.write('# disallows reuse of the same binary dir for multiple source\n')
155 f
.write('# dirs, so the build artifacts need to go into a subdirectory.\n')
156 f
.write('add_subdirectory(${CHROMIUM_TOOLS_SRC} ' +
157 '${CMAKE_CURRENT_BINARY_DIR}/a)\n')
160 def AddCMakeToPath():
161 """Look for CMake and add it to PATH if it's not there already."""
163 # First check if cmake is already on PATH.
164 subprocess
.call(['cmake', '--version'])
167 if e
.errno
!= os
.errno
.ENOENT
:
170 cmake_locations
= ['C:\\Program Files (x86)\\CMake\\bin',
171 'C:\\Program Files (x86)\\CMake 2.8\\bin']
172 for d
in cmake_locations
:
174 os
.environ
['PATH'] = os
.environ
.get('PATH', '') + os
.pathsep
+ d
176 print 'Failed to find CMake!'
186 # Try using the toolchain in depot_tools.
187 # This sets environment variables used by SelectVisualStudioVersion below.
188 sys
.path
.append(os
.path
.join(CHROMIUM_DIR
, 'build'))
190 vs_toolchain
.SetEnvironmentAndGetRuntimeDllDirs()
192 # Use gyp to find the MSVS installation, either in depot_tools as per above,
193 # or a system-wide installation otherwise.
194 sys
.path
.append(os
.path
.join(CHROMIUM_DIR
, 'tools', 'gyp', 'pylib'))
195 import gyp
.MSVSVersion
196 vs_version
= gyp
.MSVSVersion
.SelectVisualStudioVersion('2013')
200 def SubversionCmakeArg():
201 # Since cmake's find_program can only find .exe and .com,
202 # svn.bat in depot_tools will be ignored.
203 default_pathext
= ('.com', '.exe', '.bat', '.cmd')
204 for path
in os
.environ
.get('PATH', '').split(os
.pathsep
):
205 for ext
in default_pathext
:
206 candidate
= os
.path
.join(path
, 'svn' + ext
)
207 if os
.path
.isfile(candidate
):
208 return '-DSubversion_SVN_EXECUTABLE=%s' % candidate
212 def UpdateClang(args
):
213 print 'Updating Clang to %s...' % (LLVM_WIN_REVISION
)
214 if LLVM_WIN_REVISION
!= 'HEAD' and ReadStampFile() == LLVM_WIN_REVISION
:
215 print 'Already up to date.'
220 ClobberChromiumBuildFiles()
222 # Reset the stamp file in case the build is unsuccessful.
225 DeleteChromeToolsShim();
226 Checkout('LLVM', LLVM_REPO_URL
+ '/llvm/trunk', LLVM_DIR
)
227 Checkout('Clang', LLVM_REPO_URL
+ '/cfe/trunk', CLANG_DIR
)
228 Checkout('LLD', LLVM_REPO_URL
+ '/lld/trunk', LLD_DIR
)
229 Checkout('compiler-rt', LLVM_REPO_URL
+ '/compiler-rt/trunk', COMPILER_RT_DIR
)
230 CreateChromeToolsShim();
232 if not os
.path
.exists(LLVM_BUILD_DIR
):
233 os
.makedirs(LLVM_BUILD_DIR
)
234 os
.chdir(LLVM_BUILD_DIR
)
236 cmake_args
= ['-GNinja', '-DCMAKE_BUILD_TYPE=Release',
237 '-DLLVM_ENABLE_ASSERTIONS=ON', SubversionCmakeArg(),
238 '-DCHROMIUM_TOOLS_SRC=%s' % os
.path
.join(
239 CHROMIUM_DIR
, 'tools', 'clang')]
241 cmake_args
.append('-DCHROMIUM_TOOLS=%s' % ';'.join(args
.tools
))
243 RunCommand(GetVSVersion().SetupScript('x64') +
244 ['&&', 'cmake'] + cmake_args
+ [LLVM_DIR
])
245 RunCommand(GetVSVersion().SetupScript('x64') + ['&&', 'ninja', 'all'])
247 # Do an x86 build of compiler-rt to get the 32-bit ASan run-time.
248 # TODO(hans): Remove once the regular build above produces this.
249 if not os
.path
.exists(COMPILER_RT_BUILD_DIR
):
250 os
.makedirs(COMPILER_RT_BUILD_DIR
)
251 os
.chdir(COMPILER_RT_BUILD_DIR
)
252 RunCommand(GetVSVersion().SetupScript('x86') +
253 ['&&', 'cmake'] + cmake_args
+ [LLVM_DIR
])
254 RunCommand(GetVSVersion().SetupScript('x86') + ['&&', 'ninja', 'compiler-rt'])
256 # TODO(hans): Make this (and the .gypi and .isolate files) version number
258 asan_rt_lib_src_dir
= os
.path
.join(COMPILER_RT_BUILD_DIR
, 'lib', 'clang',
259 VERSION
, 'lib', 'windows')
260 asan_rt_lib_dst_dir
= os
.path
.join(LLVM_BUILD_DIR
, 'lib', 'clang',
261 VERSION
, 'lib', 'windows')
262 CopyDirectoryContents(asan_rt_lib_src_dir
, asan_rt_lib_dst_dir
,
264 CopyDirectoryContents(asan_rt_lib_src_dir
, asan_rt_lib_dst_dir
,
267 CopyFile(os
.path
.join(asan_rt_lib_src_dir
, '..', '..', 'asan_blacklist.txt'),
268 os
.path
.join(asan_rt_lib_dst_dir
, '..', '..'))
270 # Make an extra copy of the sanitizer headers, to be put on the include path
271 # of the fallback compiler.
272 sanitizer_include_dir
= os
.path
.join(LLVM_BUILD_DIR
, 'lib', 'clang', VERSION
,
273 'include', 'sanitizer')
274 aux_sanitizer_include_dir
= os
.path
.join(LLVM_BUILD_DIR
, 'lib', 'clang',
275 VERSION
, 'include_sanitizer',
277 if not os
.path
.exists(aux_sanitizer_include_dir
):
278 os
.makedirs(aux_sanitizer_include_dir
)
279 for _
, _
, files
in os
.walk(sanitizer_include_dir
):
281 CopyFile(os
.path
.join(sanitizer_include_dir
, f
),
282 aux_sanitizer_include_dir
)
284 WriteStampFile(LLVM_WIN_REVISION
)
285 print 'Clang update was successful.'
290 if not sys
.platform
in ['win32', 'cygwin']:
291 # For non-Windows, fall back to update.sh.
292 # TODO(hans): Make update.py replace update.sh completely.
294 # This script is called by gclient. gclient opens its hooks subprocesses
295 # with (stdout=subprocess.PIPE, stderr=subprocess.STDOUT) and then does
296 # custom output processing that breaks printing '\r' characters for
297 # single-line updating status messages as printed by curl and wget.
298 # Work around this by setting stderr of the update.sh process to stdin (!):
299 # gclient doesn't redirect stdin, and while stdin itself is read-only, a
300 # dup()ed sys.stdin is writable, try
301 # fd2 = os.dup(sys.stdin.fileno()); os.write(fd2, 'hi')
302 # TODO: Fix gclient instead, http://crbug.com/95350
303 return subprocess
.call(
304 [os
.path
.join(os
.path
.dirname(__file__
), 'update.sh')] + sys
.argv
[1:],
305 stderr
=os
.fdopen(os
.dup(sys
.stdin
.fileno())))
307 if not re
.search(r
'\b(clang|asan)=1', os
.environ
.get('GYP_DEFINES', '')):
308 print 'Skipping Clang update (clang=1 was not set in GYP_DEFINES).'
311 if re
.search(r
'\b(make_clang_dir)=', os
.environ
.get('GYP_DEFINES', '')):
312 print 'Skipping Clang update (make_clang_dir= was set in GYP_DEFINES).'
315 parser
= argparse
.ArgumentParser(description
='Build Clang.')
316 parser
.add_argument('--no-clobber', dest
='clobber', action
='store_false')
317 parser
.add_argument('--tools', nargs
='*')
318 # For now, this flag is only used for the non-Windows flow, but argparser gets
319 # mad if it sees a flag it doesn't recognize.
320 parser
.add_argument('--if-needed', action
='store_true')
321 return UpdateClang(parser
.parse_args())
324 if __name__
== '__main__':