• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Returns the latest LLVM version's hash."""
8
9from __future__ import print_function
10
11import argparse
12import os
13import shutil
14import subprocess
15import sys
16import tempfile
17from contextlib import contextmanager
18
19import git_llvm_rev
20from subprocess_helpers import CheckCommand
21from subprocess_helpers import check_output
22
23_LLVM_GIT_URL = ('https://chromium.googlesource.com/external/github.com/llvm'
24                 '/llvm-project')
25
26KNOWN_HASH_SOURCES = {'google3', 'google3-unstable', 'tot'}
27
28
29def GetVersionFrom(src_dir, git_hash):
30  """Obtain an SVN-style version number based on the LLVM git hash passed in.
31
32  Args:
33    src_dir: LLVM's source directory.
34    git_hash: The git hash.
35
36  Returns:
37    An SVN-style version number associated with the git hash.
38  """
39
40  version = git_llvm_rev.translate_sha_to_rev(
41      git_llvm_rev.LLVMConfig(remote='origin', dir=src_dir), git_hash)
42  # Note: branches aren't supported
43  assert version.branch == git_llvm_rev.MAIN_BRANCH, version.branch
44  return version.number
45
46
47def GetGitHashFrom(src_dir, version):
48  """Finds the commit hash(es) of the LLVM version in the git log history.
49
50  Args:
51    src_dir: The LLVM source tree.
52    version: The version number.
53
54  Returns:
55    A git hash string corresponding to the version number.
56
57  Raises:
58    subprocess.CalledProcessError: Failed to find a git hash.
59  """
60
61  return git_llvm_rev.translate_rev_to_sha(
62      git_llvm_rev.LLVMConfig(remote='origin', dir=src_dir),
63      git_llvm_rev.Rev(branch=git_llvm_rev.MAIN_BRANCH, number=version))
64
65
66@contextmanager
67def CreateTempLLVMRepo(temp_dir):
68  """Adds a LLVM worktree to 'temp_dir'.
69
70  Creating a worktree because the LLVM source tree in
71  '../toolchain-utils/llvm_tools/llvm-project-copy' should not be modified.
72
73  This is useful for applying patches to a source tree but do not want to modify
74  the actual LLVM source tree in 'llvm-project-copy'.
75
76  Args:
77    temp_dir: An absolute path to the temporary directory to put the worktree in
78    (obtained via 'tempfile.mkdtemp()').
79
80  Returns:
81    The absolute path to 'temp_dir'.
82
83  Raises:
84    subprocess.CalledProcessError: Failed to remove the worktree.
85    ValueError: Failed to add a worktree.
86  """
87
88  abs_path_to_llvm_project_dir = GetAndUpdateLLVMProjectInLLVMTools()
89  CheckCommand([
90      'git', '-C', abs_path_to_llvm_project_dir, 'worktree', 'add', '--detach',
91      temp_dir, git_llvm_rev.MAIN_BRANCH
92  ])
93
94  try:
95    yield temp_dir
96  finally:
97    if os.path.isdir(temp_dir):
98      check_output([
99          'git', '-C', abs_path_to_llvm_project_dir, 'worktree', 'remove', '-f',
100          temp_dir
101      ])
102
103
104def GetAndUpdateLLVMProjectInLLVMTools():
105  """Gets the absolute path to 'llvm-project-copy' directory in 'llvm_tools'.
106
107  The intent of this function is to avoid cloning the LLVM repo and then
108  discarding the contents of the repo. The function will create a directory
109  in '../toolchain-utils/llvm_tools' called 'llvm-project-copy' if this
110  directory does not exist yet. If it does not exist, then it will use the
111  LLVMHash() class to clone the LLVM repo into 'llvm-project-copy'. Otherwise,
112  it will clean the contents of that directory and then fetch from the chromium
113  LLVM mirror. In either case, this function will return the absolute path to
114  'llvm-project-copy' directory.
115
116  Raises:
117    ValueError: LLVM repo (in 'llvm-project-copy' dir.) has changes or failed to
118    checkout to main or failed to fetch from chromium mirror of LLVM.
119  """
120
121  abs_path_to_llvm_tools_dir = os.path.dirname(os.path.abspath(__file__))
122
123  abs_path_to_llvm_project_dir = os.path.join(abs_path_to_llvm_tools_dir,
124                                              'llvm-project-copy')
125
126  if not os.path.isdir(abs_path_to_llvm_project_dir):
127    print(
128        'Checking out LLVM from scratch. This could take a while...\n'
129        '(This should only need to be done once, though.)',
130        file=sys.stderr)
131    os.mkdir(abs_path_to_llvm_project_dir)
132
133    LLVMHash().CloneLLVMRepo(abs_path_to_llvm_project_dir)
134  else:
135    # `git status` has a '-s'/'--short' option that shortens the output.
136    # With the '-s' option, if no changes were made to the LLVM repo, then the
137    # output (assigned to 'repo_status') would be empty.
138    repo_status = check_output(
139        ['git', '-C', abs_path_to_llvm_project_dir, 'status', '-s'])
140
141    if repo_status.rstrip():
142      raise ValueError('LLVM repo in %s has changes, please remove.' %
143                       abs_path_to_llvm_project_dir)
144
145    CheckCommand([
146        'git', '-C', abs_path_to_llvm_project_dir, 'checkout',
147        git_llvm_rev.MAIN_BRANCH
148    ])
149    CheckCommand(['git', '-C', abs_path_to_llvm_project_dir, 'pull'])
150
151  return abs_path_to_llvm_project_dir
152
153
154def GetGoogle3LLVMVersion(stable):
155  """Gets the latest google3 LLVM version.
156
157  Returns:
158    The latest LLVM SVN version as an integer.
159
160  Raises:
161    subprocess.CalledProcessError: An invalid path has been provided to the
162    `cat` command.
163  """
164
165  subdir = 'stable' if stable else 'llvm_unstable'
166
167  # Cmd to get latest google3 LLVM version.
168  cmd = [
169      'cat',
170      os.path.join('/google/src/head/depot/google3/third_party/crosstool/v18',
171                   subdir, 'installs/llvm/git_origin_rev_id')
172  ]
173
174  # Get latest version.
175  git_hash = check_output(cmd)
176
177  # Change type to an integer
178  return GetVersionFrom(GetAndUpdateLLVMProjectInLLVMTools(), git_hash.rstrip())
179
180
181def is_svn_option(svn_option):
182  """Validates whether the argument (string) is a git hash option.
183
184  The argument is used to find the git hash of LLVM.
185
186  Args:
187    svn_option: The option passed in as a command line argument.
188
189  Raises:
190    ValueError: Invalid svn option provided.
191  """
192
193  if svn_option.lower() in KNOWN_HASH_SOURCES:
194    return svn_option.lower()
195
196  try:
197    svn_version = int(svn_option)
198
199    return svn_version
200
201  # Unable to convert argument to an int, so the option is invalid.
202  #
203  # Ex: 'one'.
204  except ValueError:
205    pass
206
207  raise ValueError('Invalid LLVM git hash option provided: %s' % svn_option)
208
209
210def GetLLVMHashAndVersionFromSVNOption(svn_option):
211  """Gets the LLVM hash and LLVM version based off of the svn option.
212
213  Args:
214    svn_option: A valid svn option obtained from the command line.
215      Ex: 'google3', 'tot', or <svn_version> such as 365123.
216
217  Returns:
218    A tuple that is the LLVM git hash and LLVM version.
219  """
220
221  new_llvm_hash = LLVMHash()
222
223  # Determine which LLVM git hash to retrieve.
224  if svn_option == 'tot':
225    git_hash = new_llvm_hash.GetTopOfTrunkGitHash()
226    version = GetVersionFrom(GetAndUpdateLLVMProjectInLLVMTools(), git_hash)
227  elif isinstance(svn_option, int):
228    version = svn_option
229    git_hash = GetGitHashFrom(GetAndUpdateLLVMProjectInLLVMTools(), version)
230  else:
231    assert svn_option in ('google3', 'google3-unstable')
232    version = GetGoogle3LLVMVersion(stable=svn_option == 'google3')
233
234    git_hash = GetGitHashFrom(GetAndUpdateLLVMProjectInLLVMTools(), version)
235
236  return git_hash, version
237
238
239class LLVMHash(object):
240  """Provides methods to retrieve a LLVM hash."""
241
242  @staticmethod
243  @contextmanager
244  def CreateTempDirectory():
245    temp_dir = tempfile.mkdtemp()
246
247    try:
248      yield temp_dir
249    finally:
250      if os.path.isdir(temp_dir):
251        shutil.rmtree(temp_dir, ignore_errors=True)
252
253  def CloneLLVMRepo(self, temp_dir):
254    """Clones the LLVM repo.
255
256    Args:
257      temp_dir: The temporary directory to clone the repo to.
258
259    Raises:
260      ValueError: Failed to clone the LLVM repo.
261    """
262
263    clone_cmd = ['git', 'clone', _LLVM_GIT_URL, temp_dir]
264
265    clone_cmd_obj = subprocess.Popen(clone_cmd, stderr=subprocess.PIPE)
266    _, stderr = clone_cmd_obj.communicate()
267
268    if clone_cmd_obj.returncode:
269      raise ValueError('Failed to clone the LLVM repo: %s' % stderr)
270
271  def GetLLVMHash(self, version):
272    """Retrieves the LLVM hash corresponding to the LLVM version passed in.
273
274    Args:
275      version: The LLVM version to use as a delimiter.
276
277    Returns:
278      The hash as a string that corresponds to the LLVM version.
279    """
280
281    hash_value = GetGitHashFrom(GetAndUpdateLLVMProjectInLLVMTools(), version)
282    return hash_value
283
284  def GetGoogle3LLVMHash(self):
285    """Retrieves the google3 LLVM hash."""
286
287    return self.GetLLVMHash(GetGoogle3LLVMVersion(stable=True))
288
289  def GetGoogle3UnstableLLVMHash(self):
290    """Retrieves the LLVM hash of google3's unstable compiler."""
291    return self.GetLLVMHash(GetGoogle3LLVMVersion(stable=False))
292
293  def GetTopOfTrunkGitHash(self):
294    """Gets the latest git hash from top of trunk of LLVM."""
295
296    path_to_main_branch = 'refs/heads/main'
297    llvm_tot_git_hash = check_output(
298        ['git', 'ls-remote', _LLVM_GIT_URL, path_to_main_branch])
299    return llvm_tot_git_hash.rstrip().split()[0]
300
301
302def main():
303  """Prints the git hash of LLVM.
304
305  Parses the command line for the optional command line
306  arguments.
307  """
308
309  # Create parser and add optional command-line arguments.
310  parser = argparse.ArgumentParser(description='Finds the LLVM hash.')
311  parser.add_argument(
312      '--llvm_version',
313      type=is_svn_option,
314      required=True,
315      help='which git hash of LLVM to find. Either a svn revision, or one '
316      'of %s' % sorted(KNOWN_HASH_SOURCES))
317
318  # Parse command-line arguments.
319  args_output = parser.parse_args()
320
321  cur_llvm_version = args_output.llvm_version
322
323  new_llvm_hash = LLVMHash()
324
325  if isinstance(cur_llvm_version, int):
326    # Find the git hash of the specific LLVM version.
327    print(new_llvm_hash.GetLLVMHash(cur_llvm_version))
328  elif cur_llvm_version == 'google3':
329    print(new_llvm_hash.GetGoogle3LLVMHash())
330  elif cur_llvm_version == 'google3-unstable':
331    print(new_llvm_hash.GetGoogle3UnstableLLVMHash())
332  else:
333    assert cur_llvm_version == 'tot'
334    print(new_llvm_hash.GetTopOfTrunkGitHash())
335
336
337if __name__ == '__main__':
338  main()
339