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