PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/perf/build/build_perf.py

https://gitlab.com/adam.lukaitis/fplutil
Python | 822 lines | 675 code | 44 blank | 103 comment | 31 complexity | 6913b4a114c19cfe9fafbb96099c0677 MD5 | raw file
  1. #!/usr/bin/python
  2. # Copyright 2014 Google Inc. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Checkout the Android tree and build perf along with the perfhost tool."""
  16. import argparse
  17. import collections
  18. from HTMLParser import HTMLParser
  19. import multiprocessing
  20. import os
  21. import platform
  22. import re
  23. import shutil
  24. import subprocess
  25. import sys
  26. import urllib2
  27. import xml.etree.ElementTree
  28. ## This script's directory.
  29. SCRIPT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
  30. ## Page which contains the mapping between Android API levels and source tags.
  31. BUILD_NUMBERS_URL = 'http://source.android.com/source/build-numbers.html'
  32. ## URL of the repo manifest git project for Android.
  33. ANDROID_REPO_MANIFEST_URL = (
  34. 'https://android.googlesource.com/platform/manifest')
  35. ## Repo's current manifest path.
  36. REPO_MANIFEST_PATH = os.path.join('.repo', 'manifest.xml')
  37. ## Regular expression which matches the projects that need to be fetched to
  38. ## build perf and perfhost.
  39. REPO_PROJECTS_RE = re.compile(
  40. r'('
  41. r'^bionic$|'
  42. r'^build$|'
  43. r'^external/clang$|'
  44. r'^external/compiler-rt$|'
  45. r'^external/elfutils$|'
  46. r'^external/gcc-demangle$|'
  47. r'^external/gtest$|'
  48. r'^external/jemalloc$|'
  49. r'^external/libcxx$|'
  50. r'^external/linux-tools-perf$|'
  51. r'^external/llvm$|'
  52. r'^external/safe-iop$|'
  53. r'^external/stlport$|'
  54. r'^frameworks/native$|' # Required for AOSP <= 4.1.2_r2.1.
  55. r'^prebuilts/clang/.*$|'
  56. r'^prebuilts/ndk$|'
  57. r'^prebuilts/gcc/.*$|'
  58. r'^system/core$'
  59. r')')
  60. ## Regular expression which matches the OSX host toolchain projects.
  61. REPO_PROJECTS_DARWIN_HOST_GCC_RE = re.compile(
  62. r'^prebuilts/gcc/darwin-x86/host/.*')
  63. ## Makefile that needs to be patched to disable the installed Java version
  64. ## check.
  65. BUILD_CORE_MAIN_MK = os.path.join('build', 'core', 'main.mk')
  66. ## Regular expression which matches conditional expressions in
  67. ## BUILD_CORE_MAIN_MK that need to be disabled to remove the java version
  68. ## check.
  69. MAIN_MK_JAVA_VERSION_RE = re.compile(
  70. r'[^#]?ifn?eq *\(.*\$\((java_version|java_version_str|javac_version|'
  71. r'shell java -version .*)\)')
  72. ## Regular expression which matches a Android lunch (build tool) option string.
  73. LUNCH_OPTION_RE = re.compile(r'\s*([0-9]+)\.\s*([a-zA-Z0-9_-]+)')
  74. ## Project which contains the source for perf and perfhost.
  75. LINUX_TOOLS_PERF_PATH = 'external/linux-tools-perf'
  76. ## Build targets that the project in LINUX_TOOLS_PERF_PATH depends upon.
  77. LINUX_TOOLS_PERF_DEPENDENCIES = (
  78. 'bionic/',
  79. 'build/libs/host/',
  80. 'build/tools/acp',
  81. 'external/clang',
  82. 'external/compiler-rt',
  83. 'external/elfutils',
  84. 'external/gcc-demangle',
  85. 'external/gtest/src',
  86. 'external/llvm',
  87. 'external/stlport',
  88. 'system/core/libcutils',
  89. 'system/core/liblog')
  90. ## Regular expression which matches API levels in BUILD_NUMBERS_URL.
  91. ANDROID_API_LEVEL_RE = re.compile(r'API level ([0-9]+)($|, NDK ([0-9]+))')
  92. ## Regular expression which matches source tags in BUILD_NUMBERS_URL.
  93. ANDROID_SOURCE_TAG_RE = re.compile(r'android-(.*)_r([0-9].*)')
  94. ## Number of components in a source tag version number.
  95. ANDROID_SOURCE_TAG_VERSION_COMPONENTS = 3
  96. ## Number of components in a source tag revision number.
  97. ANDROID_SOURCE_TAG_REVISION_COMPONENTS = 3
  98. class CommandError(Exception):
  99. """Exception raised when a shell command fails.
  100. Attributes:
  101. error: Error message.
  102. command: Command that failed.
  103. returnstatus: Status of the command that failed.
  104. stdout: Standard output stream of the failed command.
  105. stderr: Standard error stream of the failed command.
  106. """
  107. def __init__(self, error, command, returnstatus, stdout=None, stderr=None):
  108. """Initialize this instance.
  109. Args:
  110. error: Error message.
  111. command: Command that failed.
  112. returnstatus: Status of the command that failed.
  113. stdout: Standard output stream of the failed command.
  114. stderr: Standard error stream of the failed command.
  115. """
  116. super(CommandError, self).__init__(error)
  117. self.command = command
  118. self.returnstatus = returnstatus
  119. self.stdout = stdout
  120. self.stderr = stderr
  121. self.error = error
  122. def __str__(self):
  123. """Return a string representation of this exception.
  124. Returns:
  125. String representation of this exception.
  126. """
  127. return '%s: %s (%d) %s%s' % (self.error, self.command, self.returnstatus,
  128. self.stdout if self.stdout else '',
  129. self.stderr if self.stderr else '')
  130. def run_command(command, error_string, cwd=None, verbose=False):
  131. """Run a command in the shell raising CommandError if it fails.
  132. Args:
  133. command: Shell command string to execute.
  134. error_string: String used to prefix the error message that is attached to
  135. the raised CommandError exception.
  136. cwd: Working directory used to run the command.
  137. verbose: Whether to display the command and the working directory.
  138. Raises:
  139. CommandError: If the command returns a non-zero status code.
  140. """
  141. if verbose:
  142. print >> sys.stderr, command, cwd
  143. # If this is Linux or OSX force the use of the bash shell.
  144. shell = '/bin/bash' if platform.system() in ('Darwin', 'Linux') else None
  145. process = subprocess.Popen(command, shell=True, cwd=cwd, executable=shell)
  146. if process.wait() != 0:
  147. raise CommandError(error_string, command, process.returncode)
  148. def run_command_get_output(command, error_string, cwd=None, verbose=False,
  149. stdin=None):
  150. """Run a command in the shell raising CommandError if it fails.
  151. Args:
  152. command: Shell command string to execute.
  153. error_string: String used to prefix the error message that is attached to
  154. the raised CommandError exception.
  155. cwd: Working directory used to run the command.
  156. verbose: Whether to display the command and the working directory.
  157. stdin: String to send to the standard input stream of the command.
  158. Returns:
  159. The standard output stream.
  160. Raises:
  161. CommandError: If the command returns a non-zero status code.
  162. """
  163. if verbose:
  164. print >> sys.stderr, command, cwd if cwd else ''
  165. process = subprocess.Popen(command, shell=True, cwd=cwd,
  166. stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  167. stdout, stderr = process.communicate(input=stdin if stdin else '')
  168. if process.returncode != 0:
  169. raise CommandError(error_string, command, process.returncode,
  170. stdout=stdout, stderr=stderr)
  171. return stdout
  172. def format_build_command(command):
  173. """Format a command string so that it runs in the Anroid build environment.
  174. Args:
  175. command: Command to format.
  176. Returns:
  177. Command modified to run in the Android build environment.
  178. """
  179. environment = []
  180. if platform.system() == 'Darwin':
  181. environment.append('export BUILD_MAC_SDK_EXPERIMENTAL=1')
  182. return ' && '.join(environment + ['source ./build/envsetup.sh', command])
  183. def run_build_command(command, error_string, cwd=None, verbose=False):
  184. """Run a command in the shell with the Android build environment.
  185. Args:
  186. command: Shell command string to execute.
  187. error_string: String used to prefix the error message that is attached to
  188. the raised CommandError exception.
  189. cwd: Working directory used to run the command.
  190. verbose: Whether to display the command and the working directory.
  191. Raises:
  192. CommandError: If the command returns a non-zero status code.
  193. """
  194. run_command(format_build_command(command), error_string, cwd=cwd,
  195. verbose=verbose)
  196. def repo_init(url, branch, verbose=False):
  197. """Initialize a directory to work with repo.
  198. Args:
  199. url: URL of the manifest.
  200. branch: Branch to pull the manifest from in the git project specified by
  201. the URL.
  202. verbose: Whether to display verbose output from the command.
  203. Raises:
  204. CommandError: If the initialization process fails.
  205. """
  206. run_command('repo init -u %s -b %s' % (url, branch),
  207. 'Unable to initialize repo', verbose=verbose)
  208. def extract_project_paths_from_repo_manifest():
  209. """Parse project paths from the repo manifest in the current directory.
  210. Returns:
  211. A list of project paths parsed from the current repo manifest.
  212. """
  213. paths = []
  214. tree = xml.etree.ElementTree.parse(REPO_MANIFEST_PATH)
  215. for project in tree.getroot().iter('project'):
  216. path = project.attrib.get('path')
  217. if path:
  218. paths.append(path)
  219. return paths
  220. def repo_sync(projects, verbose=False):
  221. """Sync a set of projects from the manifest.
  222. Args:
  223. projects: List of project paths to sync. If no paths are specified all
  224. projects in the manifest are downloaded.
  225. verbose: Whether to display verbose output from the command.
  226. Raises:
  227. CommandError: If the sync fails.
  228. """
  229. run_command('repo sync -j%d %s' % (multiprocessing.cpu_count(),
  230. ' '.join(projects)),
  231. 'Unable to sync projects', verbose=verbose)
  232. def reset_git_projects_to_head(search_directory=os.curdir,
  233. verbose=False):
  234. """Reset all git projects in the current directory to the head revision.
  235. Args:
  236. search_directory: Directory to search for git projects.
  237. verbose: Whether to display verbose output from the command.
  238. Raises:
  239. CommandError: If git reset fails.
  240. """
  241. for dirpath, dirnames, _ in os.walk(search_directory):
  242. if '.git' in dirnames:
  243. dirnames = []
  244. for cmd in ['git reset --hard HEAD', 'git clean -dfx']:
  245. run_command(cmd, 'Unable to reset git project %s' % dirpath,
  246. cwd=dirpath, verbose=verbose)
  247. def patch_checks_in_makefile(filename, ifregexp):
  248. """Disable environment checks in a makefile.
  249. Args:
  250. filename: File to patch.
  251. ifregexp: Regular expression of if statements to disable.
  252. """
  253. output_lines = []
  254. with open(filename) as f:
  255. for line in f:
  256. if ifregexp.match(line):
  257. line = 'ifeq (0,1) #' + line
  258. output_lines.append(line)
  259. with open(filename, 'w') as f:
  260. f.writelines(output_lines)
  261. def lunch_get_options(verbose=False):
  262. """Get the set of available build options from the lunch command.
  263. Args:
  264. verbose: Whether to display verbose output from the command.
  265. Returns:
  266. (option_integer, description_string) tuple where option_integer is the
  267. value used to select a lunch build option and description_string is a
  268. string which describes the option.
  269. Raises:
  270. CommandError: If the lunch fails to return a selection.
  271. """
  272. cmd = format_build_command('lunch')
  273. try:
  274. stdout = run_command_get_output(cmd, '', stdin='1' + os.linesep,
  275. verbose=verbose)
  276. except CommandError as error:
  277. stdout = error.stdout
  278. options = []
  279. for l in stdout.splitlines():
  280. m = LUNCH_OPTION_RE.match(l)
  281. if m:
  282. selection, description = m.groups()
  283. options.append((int(selection), description))
  284. if not options:
  285. raise CommandError('Unable to retrieve selection from lunch: %s' % cmd)
  286. return options
  287. def build_perf(lunch_option, version, verbose=False):
  288. """Build Android perf for the architecture specified by the lunch option.
  289. This also builds perfhost for the host architecture.
  290. Args:
  291. lunch_option: Integer which specifies the lunch option used to select the
  292. target architecture.
  293. version: Numerical version of the source tree.
  294. verbose: Whether to display commands during the build process.
  295. Raises:
  296. Error: If the build fails.
  297. """
  298. showcommands = 'showcommands' if verbose else ''
  299. # Prior to 4.3.1 the AOSP build environment did not have a command to build
  300. # a target and its dependencies.
  301. if version <= version_to_value('4.3.1', True):
  302. build_command = 'mmm -j%d %s %s %s:perf,perfhost' % (
  303. multiprocessing.cpu_count(), showcommands,
  304. ' '.join(LINUX_TOOLS_PERF_DEPENDENCIES), LINUX_TOOLS_PERF_PATH)
  305. else:
  306. build_command = 'mmma -j%d %s %s' % (
  307. multiprocessing.cpu_count(), showcommands, LINUX_TOOLS_PERF_PATH)
  308. run_build_command(' && '.join(('lunch %d' % lunch_option, build_command)),
  309. 'Build of %s failed' % LINUX_TOOLS_PERF_PATH,
  310. verbose=verbose)
  311. def make_clean(verbose=False):
  312. """Clean all build targets.
  313. Args:
  314. verbose: Whether to display commands during the build process.
  315. Raises:
  316. Error: If the clean fails.
  317. """
  318. run_build_command('make clean', 'Clean failed', verbose=verbose)
  319. class ApiLevelSourceTagsParser(HTMLParser):
  320. """Parses API levels and source tags for Android builds.
  321. Attributes:
  322. tables: Dictionary of tables where each table contains a dictionary for
  323. each row indexed by column name.
  324. header_id: ID of the most recent h2 tag which identifies the table being
  325. parsed.
  326. tag: Current tag being parsed.
  327. table_columns: Current set of columns for the table being parsed.
  328. table_column_index: Index of the current column being parsed which can be
  329. used to look up the name of the column in "table_columns".
  330. table_row: Dictionary of the current row being parsed.
  331. Class Attributes:
  332. API_LEVELS_ID: ID of the API levels table in the "tables" attribute.
  333. SOURCE_TAGS_ID: ID of the source tags table in the "tables" attribute.
  334. """
  335. API_LEVELS_ID = 'platform-code-names-versions-api-levels-and-ndk-releases'
  336. SOURCE_TAGS_ID = 'source-code-tags-and-builds'
  337. def __init__(self):
  338. """Initialize this instance."""
  339. HTMLParser.__init__(self)
  340. self.tables = collections.defaultdict(list)
  341. self.reset_parse_state()
  342. def reset_parse_state(self):
  343. """Reset the parsing state of this class."""
  344. self.header_id = ''
  345. self.tag = ''
  346. self.table_columns = []
  347. self.table_column_index = 0
  348. self.table_row = {}
  349. def valid_table_header(self):
  350. """Determine whether the current header is a valid table header.
  351. Returns:
  352. True if the table header is in the set of tables recognized by this
  353. class.
  354. """
  355. return self.header_id in (ApiLevelSourceTagsParser.API_LEVELS_ID,
  356. ApiLevelSourceTagsParser.SOURCE_TAGS_ID)
  357. def handle_starttag(self, tag, attrs):
  358. """Store the current tag and its' id attribute if it's a h2 tag.
  359. Args:
  360. tag: Tag to parse.
  361. attrs: List of attributes to search for an id attribute.
  362. """
  363. self.tag = tag
  364. if tag == 'h2':
  365. for attr_id, attr_value in attrs:
  366. if attr_id == 'id':
  367. self.header_id = attr_value
  368. def handle_endtag(self, tag):
  369. """Parse the end of a table row or column.
  370. If the tag specifies the end of a table row, the currently parsed row is
  371. added to the active table. If the tag is the end of a column,
  372. table_column_index is incremented. If the tag is the end of a table the
  373. parse state is reset using reset_parse_state().
  374. Args:
  375. tag: End tag to parse.
  376. """
  377. if tag == 'table':
  378. self.reset_parse_state()
  379. if tag == 'tr':
  380. self.table_column_index = 0
  381. if self.valid_table_header() and self.table_row:
  382. self.tables[self.header_id].append(self.table_row)
  383. self.table_row = {}
  384. if tag == 'td':
  385. self.table_column_index += 1
  386. self.tag = ''
  387. def handle_data(self, data):
  388. """Parse data from the table.
  389. If the tag a header, the data is added to table_columns. If the tag is
  390. data it's added as a column to the row currently being parsed.
  391. Args:
  392. data: Data to parse.
  393. """
  394. if self.valid_table_header():
  395. if self.tag == 'th':
  396. self.table_columns.append(data)
  397. elif self.tag == 'td':
  398. column_name = self.table_columns[self.table_column_index]
  399. self.table_row[column_name] = data
  400. def version_to_value(version, wildcard_min, base=100,
  401. components=(ANDROID_SOURCE_TAG_VERSION_COMPONENTS +
  402. ANDROID_SOURCE_TAG_REVISION_COMPONENTS) - 1):
  403. """Convert a version string to an integer range.
  404. Args:
  405. version: Version string to convert to an integer.
  406. wildcard_min: Whether to replace "x" with 0 (True) or the one minus the
  407. value of "base".
  408. base: Base used to multiply each component of the version number with the
  409. offset of the component in the version string.
  410. components: One minus the maximum number of components in the version
  411. string.
  412. Returns:
  413. Integer version value.
  414. """
  415. value = 0
  416. version_tokens = version.split('.')
  417. assert len(version_tokens) - 1 <= components
  418. version_tokens.extend(['x'] * (components + 1 - len(version_tokens)))
  419. for component in version_tokens:
  420. if component == 'x':
  421. component_value = 0 if wildcard_min else base - 1
  422. else:
  423. # Convert letters to integers - assuming ASCII characters.
  424. component = ''.join([c for c in component if c.isdigit()])
  425. component_value = int(component)
  426. component_value += sum([ord(c) - ord('a')
  427. for c in component if not c.isdigit()])
  428. assert component_value < base
  429. value += component_value * (base ** components)
  430. components -= 1
  431. return value
  432. def version_to_range(version):
  433. """Convert a version string to an integer range.
  434. Args:
  435. version: Version string to convert to an integer range.
  436. Returns:
  437. (min_version, max_version) where min_version and max_version is an
  438. inclusive range of the minimum and maximum version values respectively
  439. generated by version_to_value().
  440. """
  441. range_tokens = version.split(' - ')
  442. if len(range_tokens) == 1:
  443. range_tokens.append(range_tokens[0])
  444. return (version_to_value(range_tokens[0], True),
  445. version_to_value(range_tokens[1], False))
  446. def extract_version_from_source_tag(source_tag):
  447. """Extract a version string from a source tag.
  448. Args:
  449. source_tag: Source tag to parse for a version string.
  450. Returns:
  451. Version string.
  452. """
  453. m = ANDROID_SOURCE_TAG_RE.match(source_tag)
  454. if not m:
  455. return ''
  456. version = m.groups()[0]
  457. version_components = version.split('.')
  458. version_components.extend(['0'] * (ANDROID_SOURCE_TAG_VERSION_COMPONENTS -
  459. len(version_components)))
  460. return '.'.join(version_components + m.groups()[1].split('.'))
  461. def get_released_branches():
  462. """Parse the set of released branches from http://source.android.com/.
  463. Returns:
  464. Dictionary of (source_tag, version_value) tuples indexed by API level
  465. integers where source_tag is the repo branch which contains the API
  466. version and version_value is an integer representation of the version tag.
  467. """
  468. parser = ApiLevelSourceTagsParser()
  469. parser.feed(urllib2.urlopen(BUILD_NUMBERS_URL).read())
  470. # Get the distinct list of Android versions that map to each API level and
  471. # support the NDK.
  472. api_level_to_version_range = {}
  473. ndk_api_version = 0
  474. for row in parser.tables[ApiLevelSourceTagsParser.API_LEVELS_ID]:
  475. api_level_string = row['API level']
  476. groups = ANDROID_API_LEVEL_RE.match(api_level_string).groups()
  477. ndk_api_version = int(groups[2]) if groups[2] else ndk_api_version
  478. if ndk_api_version > 0:
  479. api_level = int(groups[0])
  480. api_level_to_version_range[api_level] = version_to_range(row['Version'])
  481. api_level_to_source_tag = {}
  482. for row in parser.tables[ApiLevelSourceTagsParser.SOURCE_TAGS_ID]:
  483. source_tag = row['Branch']
  484. version = extract_version_from_source_tag(source_tag)
  485. if version:
  486. version_value = version_to_value(version, True)
  487. for api_level, version_range in api_level_to_version_range.iteritems():
  488. if (version_value >= version_range[0] and
  489. version_value <= version_range[1]):
  490. if (version_value >
  491. api_level_to_source_tag.get(api_level, ('', 0))[1]):
  492. api_level_to_source_tag[api_level] = (source_tag, version_value)
  493. break
  494. return api_level_to_source_tag
  495. def apply_patches(verbose=False):
  496. """Apply any patches to the source required to build perf and perfhost.
  497. Args:
  498. verbose: Whether to display commands during the build process.
  499. """
  500. for dirpath, _, filenames in os.walk(SCRIPT_DIRECTORY):
  501. if '@' in dirpath:
  502. filename = os.path.basename(dirpath)
  503. project_path = filename.replace('@', os.path.sep)
  504. if not os.path.exists(project_path):
  505. if verbose:
  506. print >> sys.stderr, '%s does not exist, skipping.' % project_path
  507. continue
  508. commit_id = run_command_get_output(
  509. 'git log -n 1 --format=format:%H',
  510. 'Unable to get commit for project %s' % project_path,
  511. cwd=project_path, verbose=verbose)
  512. for patch in filenames:
  513. if commit_id == os.path.splitext(patch)[0]:
  514. patch_filename = os.path.join(dirpath, patch)
  515. run_command('git apply %s' % patch_filename,
  516. 'Unable to apply patch %s to %s' % (patch_filename,
  517. project_path),
  518. cwd=project_path, verbose=verbose)
  519. break
  520. def archive_build_artifacts(archive_dir, api_level, source_tag, url):
  521. """Archive build artfiacts.
  522. Saves Android perf binaries to
  523. {archive_dir}/android-{api_level}/arch-{target_arch}
  524. and the host binaries to
  525. {archive_dir}/android-{api_level}/{host_os}-{host_arch}
  526. Args:
  527. archive_dir: Directory to copy artifacts to.
  528. api_level: API level of the source used to build perf binaries.
  529. source_tag: Source tag / branch perf was built from.
  530. url: Manifest project which contains references to the source required
  531. to build these binaries.
  532. """
  533. android_api_level = 'android-%d' % api_level
  534. for dirpath, _, filenames in os.walk('out'):
  535. source_file = ''
  536. target_dir = ''
  537. if 'perf' in filenames:
  538. arch_tokens = os.path.basename(os.path.abspath(
  539. os.path.join(dirpath, os.pardir, os.pardir))).split('_')
  540. if len(arch_tokens) == 1:
  541. arch_tokens.append('arm')
  542. source_file = os.path.join(dirpath, 'perf')
  543. target_dir = os.path.join(archive_dir, android_api_level,
  544. 'arch-%s' % arch_tokens[1])
  545. elif 'perfhost' in filenames:
  546. arch = os.path.basename(os.path.abspath(os.path.join(
  547. dirpath, os.pardir)))
  548. if arch == 'EXECUTABLES':
  549. continue
  550. source_file = os.path.join(dirpath, 'perfhost')
  551. target_dir = os.path.join(archive_dir, android_api_level, arch)
  552. if source_file and target_dir:
  553. print source_file, '-->', target_dir
  554. if not os.path.exists(target_dir):
  555. os.makedirs(target_dir)
  556. target_file = os.path.basename(source_file)
  557. with open(os.path.join(target_dir, 'build_info'), 'w') as f:
  558. f.write('%s built from %s branch %s%s' % (
  559. target_file, url, source_tag, os.linesep))
  560. shutil.copy(source_file, os.path.join(target_dir, target_file))
  561. def sync_projects(manifest_url, source_version, source_tag, verbose=False):
  562. """Prepare the source tree for a build.
  563. Args:
  564. manifest_url: URL of the repo manifest to use for synchronization.
  565. source_version: Integer version derived from the source tag.
  566. source_tag: Tag / branch to sync from the manifest.
  567. verbose: Whether to display executed commands.
  568. Returns:
  569. A list of projects that need to be deleted to restore the tree to the
  570. expected state for repo.
  571. """
  572. restore_projects = []
  573. if (platform.system() == 'Darwin' and
  574. source_version < version_to_value('4.3.1', True)):
  575. # The build environment prior to 4.3.1 relied upon the clang toolchain
  576. # distributed with Xcode. Clang can't be used to build external/elfutils
  577. # without modification since elfutils uses nested functions, furthermore
  578. # macports GCC crashes when attempting to link the host tools. Therefore,
  579. # the following synchronizes the prebuilt GCC toolchain from 4.4.4, stores
  580. # it in a temporary directory and restores it after synchronizing the
  581. # target source tree.
  582. repo_init(manifest_url, 'android-4.4.4_r2', verbose=verbose)
  583. reset_git_projects_to_head(verbose=verbose)
  584. toolchain_projects = [p for p in extract_project_paths_from_repo_manifest()
  585. if REPO_PROJECTS_DARWIN_HOST_GCC_RE.match(p)]
  586. repo_sync(toolchain_projects, verbose=verbose)
  587. # Rename the projects so repo doesn't clean them.
  588. for project in toolchain_projects:
  589. project_rename = (project, project + '-temp')
  590. restore_projects.append(project_rename)
  591. os.rename(project_rename[0], project_rename[1])
  592. repo_init(manifest_url, source_tag, verbose=verbose)
  593. reset_git_projects_to_head(verbose=verbose)
  594. repo_sync([p for p in extract_project_paths_from_repo_manifest()
  595. if REPO_PROJECTS_RE.match(p)], verbose=verbose)
  596. # Restore the saved projects.
  597. for project, project_temp in restore_projects:
  598. os.rename(project_temp, project)
  599. return [p[0] for p in restore_projects]
  600. def main():
  601. """Checkout the Android tree and build perf along with the perfhost tool.
  602. For the most recently released branch of each API level do the following:
  603. * Get the minimal set of projects required to build perf.
  604. * Clean the tree.
  605. * Patch files required to perform a minimal build of perf.
  606. * Build perf and perfhost.
  607. * Archive build artifacts.
  608. Returns:
  609. 0 if successful, non-zero otherwise.
  610. """
  611. # Parse command line arguments.
  612. parser = argparse.ArgumentParser()
  613. parser.add_argument('-s', '--source_dir',
  614. help='Directory used to checkout the Android source.',
  615. dest='source_dir',
  616. default=os.path.join(os.getcwd(), 'tmp'))
  617. parser.add_argument('-o', '--output_dir',
  618. help=('Directory where built perf binaries will be '
  619. 'stored.'), dest='output_dir',
  620. default=os.path.join(os.getcwd(), 'bin'))
  621. parser.add_argument('-a', '--api_levels',
  622. help=('List of API levels to build. If this is not '
  623. 'specified all API levels are built.'),
  624. default=[], dest='api_levels', nargs='+')
  625. parser.add_argument('-v', '--verbose',
  626. help='Display verbose output during the build process.',
  627. dest='verbose', default=False, action='store_true')
  628. args = parser.parse_args()
  629. source_directory = os.path.abspath(args.source_dir)
  630. output_directory = os.path.abspath(args.output_dir)
  631. verbose = args.verbose
  632. api_levels = [int(api) for api in args.api_levels if api.isdigit()]
  633. # Retrieve the set of source tags for each API level.
  634. api_level_to_source_tag = get_released_branches()
  635. # Create the source directory and change the working directory to it.
  636. if not os.path.exists(source_directory):
  637. os.makedirs(source_directory)
  638. os.chdir(source_directory)
  639. # Build perf and perfhost for each API level.
  640. trees_to_build = sorted(api_level_to_source_tag.iteritems(),
  641. key=lambda s: s[1][1], reverse=True)
  642. # Filter trees without the Linux perf project.
  643. android_perf_first_version = version_to_value('4.1.x', True)
  644. trees_to_build = [t for t in trees_to_build
  645. if t[1][1] >= android_perf_first_version]
  646. if verbose:
  647. print 'Building Linux perf for:'
  648. for tree in trees_to_build:
  649. print ' API level: %d, Branch: %s' % (tree[0], tree[1][0])
  650. for api_level, source_tag_version in trees_to_build:
  651. source_tag = source_tag_version[0]
  652. source_version = source_tag_version[1]
  653. if api_levels:
  654. if api_level not in api_levels:
  655. if verbose:
  656. print 'Skipping build of %s (API level %d)' % (source_tag, api_level)
  657. continue
  658. if verbose:
  659. print '=== Build %s (API level %d) ===' % (source_tag, api_level)
  660. # Retrieve the source, clean the tree.
  661. delete_projects = sync_projects(ANDROID_REPO_MANIFEST_URL, source_version,
  662. source_tag, verbose=verbose)
  663. # Patch main.mk to disable the Java version check since Java isn't
  664. # required to build the perf tools.
  665. patch_checks_in_makefile(BUILD_CORE_MAIN_MK, MAIN_MK_JAVA_VERSION_RE)
  666. # Apply patches required to build perf and perfhost.
  667. apply_patches(verbose=verbose)
  668. # Clean any build artifacts from a prior build.
  669. make_clean(verbose=verbose)
  670. # Build all AOSP build targets.
  671. for value, description in lunch_get_options(source_version):
  672. if [d for d in ('aosp_', 'full') if description.startswith(d)]:
  673. build_perf(value, source_version, verbose=verbose)
  674. archive_build_artifacts(output_directory, api_level, source_tag,
  675. ANDROID_REPO_MANIFEST_URL)
  676. # Restore the state of the tree for repo.
  677. for p in delete_projects:
  678. shutil.rmtree(p)
  679. return 0
  680. if __name__ == '__main__':
  681. sys.exit(main())