fetch_all.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. #!/usr/bin/env python3
  2. # Copyright 2018 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.
  5. """A script used to manage Google Maven dependencies for Chromium.
  6. For each dependency in `build.gradle`:
  7. - Download the library
  8. - Download the LICENSE
  9. - Generate a README.chromium file
  10. - Generate a GN target in BUILD.gn
  11. - Generate .info files for AAR libraries
  12. - Generate a 'deps' entry in DEPS.
  13. """
  14. import argparse
  15. import collections
  16. import concurrent.futures
  17. import contextlib
  18. import fnmatch
  19. import logging
  20. import tempfile
  21. import textwrap
  22. import os
  23. import re
  24. import shutil
  25. import subprocess
  26. import urllib.request
  27. import zipfile
  28. # Assume this script is stored under third_party/android_deps/
  29. _CHROMIUM_SRC = os.path.normpath(os.path.join(__file__, '..', '..', '..'))
  30. # Default android_deps directory.
  31. _PRIMARY_ANDROID_DEPS_DIR = os.path.join(_CHROMIUM_SRC, 'third_party',
  32. 'android_deps')
  33. # Path to additional_readme_paths.json relative to custom 'android_deps' directory.
  34. _ADDITIONAL_README_PATHS = 'additional_readme_paths.json'
  35. # Path to BUILD.gn file from custom 'android_deps' directory.
  36. _BUILD_GN = 'BUILD.gn'
  37. # Path to build.gradle file relative to custom 'android_deps' directory.
  38. _BUILD_GRADLE = 'build.gradle'
  39. # Location of the android_deps libs directory relative to custom 'android_deps' directory.
  40. _LIBS_DIR = 'libs'
  41. _GN_PATH = os.path.join(_CHROMIUM_SRC, 'third_party', 'depot_tools', 'gn')
  42. _GRADLEW = os.path.join(_CHROMIUM_SRC, 'third_party', 'gradle_wrapper',
  43. 'gradlew')
  44. # Git-controlled files needed by, but not updated by this tool.
  45. # Relative to _PRIMARY_ANDROID_DEPS_DIR.
  46. _PRIMARY_ANDROID_DEPS_FILES = [
  47. 'buildSrc',
  48. 'licenses',
  49. 'settings.gradle.template',
  50. 'vulnerability_supressions.xml',
  51. ]
  52. # Git-controlled files needed by and updated by this tool.
  53. # Relative to args.android_deps_dir.
  54. _CUSTOM_ANDROID_DEPS_FILES = [
  55. os.path.join('..', '..', 'DEPS'),
  56. _BUILD_GN,
  57. _ADDITIONAL_README_PATHS,
  58. 'subprojects.txt',
  59. ]
  60. # Dictionary mapping long info file names to shorter ones to avoid paths being
  61. # over 200 chars. This must match the dictionary in BuildConfigGenerator.groovy.
  62. _REDUCED_ID_LENGTH_MAP = {
  63. 'com_google_android_apps_common_testing_accessibility_framework_accessibility_test_framework':
  64. 'com_google_android_accessibility_test_framework',
  65. }
  66. # If this file exists in an aar file then it is appended to LICENSE
  67. _THIRD_PARTY_LICENSE_FILENAME = 'third_party_licenses.txt'
  68. # Path to the aar.py script used to generate .info files.
  69. _AAR_PY = os.path.join(_CHROMIUM_SRC, 'build', 'android', 'gyp', 'aar.py')
  70. @contextlib.contextmanager
  71. def BuildDir(dirname=None):
  72. """Helper function used to manage a build directory.
  73. Args:
  74. dirname: Optional build directory path. If not provided, a temporary
  75. directory will be created and cleaned up on exit.
  76. Returns:
  77. A python context manager modelling a directory path. The manager
  78. removes the directory if necessary on exit.
  79. """
  80. delete = False
  81. if not dirname:
  82. dirname = tempfile.mkdtemp()
  83. delete = True
  84. try:
  85. yield dirname
  86. finally:
  87. if delete:
  88. shutil.rmtree(dirname)
  89. def RaiseCommandException(args, returncode, output, error):
  90. """Raise an exception whose message describing a command failure.
  91. Args:
  92. args: shell command-line (as passed to subprocess.call())
  93. returncode: status code.
  94. error: standard error output.
  95. Raises:
  96. a new Exception.
  97. """
  98. message = 'Command failed with status {}: {}\n'.format(returncode, args)
  99. if output:
  100. message += 'Output:-----------------------------------------\n{}\n' \
  101. '------------------------------------------------\n'.format(output)
  102. if error:
  103. message += 'Error message: ---------------------------------\n{}\n' \
  104. '------------------------------------------------\n'.format(error)
  105. raise Exception(message)
  106. def RunCommand(args, print_stdout=False, cwd=None):
  107. """Run a new shell command.
  108. This function runs without printing anything.
  109. Args:
  110. args: A string or a list of strings for the shell command.
  111. Raises:
  112. On failure, raise an Exception that contains the command's arguments,
  113. return status, and standard output + error merged in a single message.
  114. """
  115. logging.debug('Run %s', args)
  116. stdout = None if print_stdout else subprocess.PIPE
  117. p = subprocess.Popen(args, stdout=stdout, cwd=cwd)
  118. pout, _ = p.communicate()
  119. if p.returncode != 0:
  120. RaiseCommandException(args, p.returncode, None, pout)
  121. def RunCommandAndGetOutput(args):
  122. """Run a new shell command. Return its output. Exception on failure.
  123. This function runs without printing anything.
  124. Args:
  125. args: A string or a list of strings for the shell command.
  126. Returns:
  127. The command's output.
  128. Raises:
  129. On failure, raise an Exception that contains the command's arguments,
  130. return status, and standard output, and standard error as separate
  131. messages.
  132. """
  133. logging.debug('Run %s', args)
  134. p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  135. pout, perr = p.communicate()
  136. if p.returncode != 0:
  137. RaiseCommandException(args, p.returncode, pout, perr)
  138. return pout
  139. def MakeDirectory(dir_path):
  140. """Make directory |dir_path| recursively if necessary."""
  141. if dir_path != '' and not os.path.isdir(dir_path):
  142. logging.debug('mkdir [%s]', dir_path)
  143. os.makedirs(dir_path)
  144. def DeleteDirectory(dir_path):
  145. """Recursively delete a directory if it exists."""
  146. if os.path.exists(dir_path):
  147. logging.debug('rmdir [%s]', dir_path)
  148. shutil.rmtree(dir_path)
  149. def Copy(src_dir, src_paths, dst_dir, dst_paths, src_path_must_exist=True):
  150. """Copies |src_paths| in |src_dir| to |dst_paths| in |dst_dir|.
  151. Args:
  152. src_dir: Directory containing |src_paths|.
  153. src_paths: Files to copy.
  154. dst_dir: Directory containing |dst_paths|.
  155. dst_paths: Copy destinations.
  156. src_paths_must_exist: If False, do not throw error if the file for one of
  157. |src_paths| does not exist.
  158. """
  159. assert len(src_paths) == len(dst_paths)
  160. missing_files = []
  161. for src_path, dst_path in zip(src_paths, dst_paths):
  162. abs_src_path = os.path.join(src_dir, src_path)
  163. abs_dst_path = os.path.join(dst_dir, dst_path)
  164. if os.path.exists(abs_src_path):
  165. CopyFileOrDirectory(abs_src_path, abs_dst_path)
  166. elif src_path_must_exist:
  167. missing_files.append(src_path)
  168. if missing_files:
  169. raise Exception('Missing files from {}: {}'.format(
  170. src_dir, missing_files))
  171. def CopyFileOrDirectory(src_path, dst_path, ignore_extension=None):
  172. """Copy file or directory |src_path| into |dst_path| exactly.
  173. Args:
  174. src_path: Source path.
  175. dst_path: Destination path.
  176. ignore_extension: File extension of files not to copy, starting with '.'. If None, all files
  177. are copied.
  178. """
  179. assert not ignore_extension or ignore_extension[0] == '.'
  180. src_path = os.path.normpath(src_path)
  181. dst_path = os.path.normpath(dst_path)
  182. logging.debug('copy [%s -> %s]', src_path, dst_path)
  183. MakeDirectory(os.path.dirname(dst_path))
  184. if os.path.isdir(src_path):
  185. # Copy directory recursively.
  186. DeleteDirectory(dst_path)
  187. ignore = None
  188. if ignore_extension:
  189. ignore = shutil.ignore_patterns('*' + ignore_extension)
  190. shutil.copytree(src_path, dst_path, ignore=ignore)
  191. elif not ignore_extension or not re.match('.*\.' + ignore_extension[1:],
  192. src_path):
  193. shutil.copy(src_path, dst_path)
  194. def ReadFile(file_path):
  195. """Read a file, return its content."""
  196. with open(file_path) as f:
  197. return f.read()
  198. def ReadFileAsLines(file_path):
  199. """Read a file as a series of lines."""
  200. with open(file_path) as f:
  201. return f.readlines()
  202. def WriteFile(file_path, file_data):
  203. """Write a file."""
  204. if isinstance(file_data, str):
  205. file_data = file_data.encode('utf8')
  206. MakeDirectory(os.path.dirname(file_path))
  207. with open(file_path, 'wb') as f:
  208. f.write(file_data)
  209. def FindInDirectory(directory, filename_filter):
  210. """Find all files in a directory that matches a given filename filter."""
  211. files = []
  212. for root, _dirnames, filenames in os.walk(directory):
  213. matched_files = fnmatch.filter(filenames, filename_filter)
  214. files.extend((os.path.join(root, f) for f in matched_files))
  215. return files
  216. # Named tuple describing a CIPD package.
  217. # - path: Path to cipd.yaml file.
  218. # - name: cipd package name.
  219. # - tag: cipd tag.
  220. CipdPackageInfo = collections.namedtuple('CipdPackageInfo',
  221. ['path', 'name', 'tag'])
  222. # Regular expressions used to extract useful info from cipd.yaml files
  223. # generated by Gradle. See BuildConfigGenerator.groovy:makeCipdYaml()
  224. _RE_CIPD_CREATE = re.compile('cipd create --pkg-def cipd.yaml -tag (\S*)')
  225. _RE_CIPD_PACKAGE = re.compile('package: (\S*)')
  226. def _ParseSubprojects(subproject_path):
  227. """Parses listing of subproject build.gradle files. Returns list of paths."""
  228. if not os.path.exists(subproject_path):
  229. return None
  230. subprojects = []
  231. for subproject in open(subproject_path):
  232. subproject = subproject.strip()
  233. if subproject and not subproject.startswith('#'):
  234. subprojects.append(subproject)
  235. return subprojects
  236. def _GenerateSettingsGradle(subproject_dirs, settings_template_path,
  237. settings_out_path):
  238. """Generates settings file by replacing "{{subproject_dirs}}" string in template.
  239. Args:
  240. subproject_dirs: List of subproject directories to substitute into template.
  241. settings_template_path: Path of template file to substitute into.
  242. settings_out_path: Path of output settings.gradle file.
  243. """
  244. with open(settings_template_path) as f:
  245. template_content = f.read()
  246. subproject_dirs_str = ''
  247. if subproject_dirs:
  248. subproject_dirs_str = '\'' + '\',\''.join(subproject_dirs) + '\''
  249. template_content = template_content.replace('{{subproject_dirs}}',
  250. subproject_dirs_str)
  251. with open(settings_out_path, 'w') as f:
  252. f.write(template_content)
  253. def _BuildGradleCmd(build_android_deps_dir, task):
  254. return [
  255. _GRADLEW, '-p', build_android_deps_dir, '--stacktrace',
  256. '--warning-mode', 'all', task
  257. ]
  258. def _CheckVulnerabilities(build_android_deps_dir, report_dst):
  259. logging.warning('Running Gradle dependencyCheckAnalyze. This may take a '
  260. 'few minutes the first time.')
  261. # Separate command from main gradle command so that we can provide specific
  262. # diagnostics in case of failure of this step.
  263. gradle_cmd = _BuildGradleCmd(build_android_deps_dir,
  264. 'dependencyCheckAnalyze')
  265. report_src = os.path.join(build_android_deps_dir, 'build', 'reports')
  266. if os.path.exists(report_dst):
  267. shutil.rmtree(report_dst)
  268. try:
  269. logging.info('CMD: %s', ' '.join(gradle_cmd))
  270. subprocess.run(gradle_cmd, check=True)
  271. except subprocess.CalledProcessError:
  272. report_path = os.path.join(report_dst, 'dependency-check-report.html')
  273. logging.error(
  274. textwrap.dedent("""
  275. =============================================================================
  276. A package has a known vulnerability. It may not be in a package or packages
  277. which you just added, but you need to resolve the problem before proceeding.
  278. If you can't easily fix it by rolling the package to a fixed version now,
  279. please file a crbug of type= Bug-Security providing all relevant information,
  280. and then rerun this command with --ignore-vulnerabilities.
  281. The html version of the report is avialable at: {}
  282. =============================================================================
  283. """.format(report_path)))
  284. raise
  285. finally:
  286. if os.path.exists(report_src):
  287. CopyFileOrDirectory(report_src, report_dst)
  288. def _ReduceNameLength(path_str):
  289. """Returns a shorter path string if needed.
  290. Args:
  291. path_str: A String representing the path.
  292. Returns:
  293. A String (possibly shortened) of that path.
  294. """
  295. return _REDUCED_ID_LENGTH_MAP.get(path_str, path_str)
  296. def GetCipdPackageInfo(cipd_yaml_path):
  297. """Returns the CIPD package name corresponding to a given cipd.yaml file.
  298. Args:
  299. cipd_yaml_path: Path of input cipd.yaml file.
  300. Returns:
  301. A (package_name, package_tag) tuple.
  302. Raises:
  303. Exception if the file could not be read.
  304. """
  305. package_name = None
  306. package_tag = None
  307. for line in ReadFileAsLines(cipd_yaml_path):
  308. m = _RE_CIPD_PACKAGE.match(line)
  309. if m:
  310. package_name = m.group(1)
  311. m = _RE_CIPD_CREATE.search(line)
  312. if m:
  313. package_tag = m.group(1)
  314. if not package_name or not package_tag:
  315. raise Exception('Invalid cipd.yaml format: ' + cipd_yaml_path)
  316. return (package_name, package_tag)
  317. def ParseDeps(root_dir, libs_dir):
  318. """Parse an android_deps/libs and retrieve package information.
  319. Args:
  320. root_dir: Path to a root Chromium or build directory.
  321. Returns:
  322. A directory mapping package names to tuples of
  323. (cipd_yaml_file, package_name, package_tag), where |cipd_yaml_file|
  324. is the path to the cipd.yaml file, related to |libs_dir|,
  325. and |package_name| and |package_tag| are the extracted from it.
  326. """
  327. result = {}
  328. root_dir = os.path.abspath(root_dir)
  329. libs_dir = os.path.abspath(os.path.join(root_dir, libs_dir))
  330. for cipd_file in FindInDirectory(libs_dir, 'cipd.yaml'):
  331. pkg_name, pkg_tag = GetCipdPackageInfo(cipd_file)
  332. cipd_path = os.path.dirname(cipd_file)
  333. cipd_path = cipd_path[len(root_dir) + 1:]
  334. result[pkg_name] = CipdPackageInfo(cipd_path, pkg_name, pkg_tag)
  335. return result
  336. def PrintPackageList(packages, list_name):
  337. """Print a list of packages to standard output.
  338. Args:
  339. packages: list of package names.
  340. list_name: a simple word describing the package list (e.g. 'new')
  341. """
  342. print(' {} {} packages:'.format(len(packages), list_name))
  343. print('\n'.join(' - ' + p for p in packages))
  344. def _DownloadOverrides(overrides, build_libs_dir):
  345. for spec in overrides:
  346. subpath, url = spec.split(':', 1)
  347. target_path = os.path.join(build_libs_dir, subpath)
  348. if not os.path.isfile(target_path):
  349. found_files = 'Found instead:\n' + '\n'.join(
  350. FindInDirectory(os.path.dirname(target_path), '*'))
  351. raise Exception(
  352. f'Override path does not exist: {target_path}\n{found_files}')
  353. logging.info('Fetching override for %s', target_path)
  354. with urllib.request.urlopen(url) as response:
  355. with open(target_path, 'wb') as f:
  356. shutil.copyfileobj(response, f)
  357. def _CreateAarInfos(aar_files):
  358. jobs = []
  359. for aar_file in aar_files:
  360. aar_dirname = os.path.dirname(aar_file)
  361. aar_info_name = _ReduceNameLength(
  362. os.path.basename(aar_dirname)) + '.info'
  363. aar_info_path = os.path.join(aar_dirname, aar_info_name)
  364. logging.debug('- %s', aar_info_name)
  365. cmd = [_AAR_PY, 'list', aar_file, '--output', aar_info_path]
  366. if aar_info_name == 'com_google_android_material_material.info':
  367. # Keep in sync with copy in BuildConfigGenerator.groovy.
  368. resource_exclusion_glbos = [
  369. 'res/layout*/*calendar*',
  370. 'res/layout*/*chip_input*',
  371. 'res/layout*/*clock*',
  372. 'res/layout*/*picker*',
  373. 'res/layout*/*time*',
  374. ]
  375. cmd += [
  376. '--resource-exclusion-globs',
  377. repr(resource_exclusion_glbos).replace("'", '"')
  378. ]
  379. proc = subprocess.Popen(cmd)
  380. jobs.append((cmd, proc))
  381. for cmd, proc in jobs:
  382. if proc.wait():
  383. raise Exception('Command Failed: {}\n'.format(' '.join(cmd)))
  384. def main():
  385. parser = argparse.ArgumentParser(
  386. description=__doc__,
  387. formatter_class=argparse.RawDescriptionHelpFormatter)
  388. parser.add_argument(
  389. '--android-deps-dir',
  390. help='Path to directory containing build.gradle from chromium-dir.',
  391. default=_PRIMARY_ANDROID_DEPS_DIR)
  392. parser.add_argument(
  393. '--build-dir',
  394. help='Path to build directory (default is temporary directory).')
  395. parser.add_argument('--ignore-licenses',
  396. help='Ignores licenses for these deps.',
  397. action='store_true')
  398. parser.add_argument('--ignore-vulnerabilities',
  399. help='Ignores vulnerabilities for these deps.',
  400. action='store_true')
  401. parser.add_argument('--override-artifact',
  402. action='append',
  403. help='lib_subpath:url of .aar / .jar to override.')
  404. parser.add_argument('-v',
  405. '--verbose',
  406. dest='verbose_count',
  407. default=0,
  408. action='count',
  409. help='Verbose level (multiple times for more)')
  410. args = parser.parse_args()
  411. logging.basicConfig(
  412. level=logging.WARNING - 10 * args.verbose_count,
  413. format='%(levelname).1s %(relativeCreated)6d %(message)s')
  414. debug = args.verbose_count >= 2
  415. if not os.path.isfile(os.path.join(args.android_deps_dir, _BUILD_GRADLE)):
  416. raise Exception('--android-deps-dir {} does not contain {}.'.format(
  417. args.android_deps_dir, _BUILD_GRADLE))
  418. is_primary_android_deps = args.android_deps_dir == _PRIMARY_ANDROID_DEPS_DIR
  419. android_deps_subdir = os.path.relpath(args.android_deps_dir, _CHROMIUM_SRC)
  420. with BuildDir(args.build_dir) as build_dir:
  421. build_android_deps_dir = os.path.join(build_dir, android_deps_subdir)
  422. logging.info('Using build directory: %s', build_dir)
  423. Copy(_PRIMARY_ANDROID_DEPS_DIR, _PRIMARY_ANDROID_DEPS_FILES,
  424. build_android_deps_dir, _PRIMARY_ANDROID_DEPS_FILES)
  425. Copy(args.android_deps_dir, [_BUILD_GRADLE], build_android_deps_dir,
  426. [_BUILD_GRADLE])
  427. Copy(args.android_deps_dir,
  428. _CUSTOM_ANDROID_DEPS_FILES,
  429. build_android_deps_dir,
  430. _CUSTOM_ANDROID_DEPS_FILES,
  431. src_path_must_exist=is_primary_android_deps)
  432. subprojects = _ParseSubprojects(
  433. os.path.join(args.android_deps_dir, 'subprojects.txt'))
  434. subproject_dirs = []
  435. if subprojects:
  436. for (index, subproject) in enumerate(subprojects):
  437. subproject_dir = 'subproject{}'.format(index)
  438. Copy(args.android_deps_dir, [subproject],
  439. build_android_deps_dir,
  440. [os.path.join(subproject_dir, 'build.gradle')])
  441. subproject_dirs.append(subproject_dir)
  442. _GenerateSettingsGradle(
  443. subproject_dirs,
  444. os.path.join(_PRIMARY_ANDROID_DEPS_DIR,
  445. 'settings.gradle.template'),
  446. os.path.join(build_android_deps_dir, 'settings.gradle'))
  447. if not args.ignore_vulnerabilities:
  448. report_dst = os.path.join(args.android_deps_dir,
  449. 'vulnerability_reports')
  450. _CheckVulnerabilities(build_android_deps_dir, report_dst)
  451. logging.info('Running Gradle.')
  452. # This gradle command generates the new DEPS and BUILD.gn files, it can
  453. # also handle special cases.
  454. # Edit BuildConfigGenerator.groovy#addSpecialTreatment for such cases.
  455. gradle_cmd = _BuildGradleCmd(build_android_deps_dir, 'setupRepository')
  456. if debug:
  457. gradle_cmd.append('--debug')
  458. if args.ignore_licenses:
  459. gradle_cmd.append('-PskipLicenses=true')
  460. subprocess.run(gradle_cmd, check=True)
  461. logging.info('# Reformat %s.',
  462. os.path.join(args.android_deps_dir, _BUILD_GN))
  463. gn_args = [
  464. os.path.relpath(_GN_PATH, _CHROMIUM_SRC), 'format',
  465. os.path.join(os.path.relpath(build_android_deps_dir, _CHROMIUM_SRC),
  466. _BUILD_GN)
  467. ]
  468. RunCommand(gn_args, print_stdout=debug, cwd=_CHROMIUM_SRC)
  469. build_libs_dir = os.path.join(build_android_deps_dir, _LIBS_DIR)
  470. if args.override_artifact:
  471. _DownloadOverrides(args.override_artifact, build_libs_dir)
  472. aar_files = FindInDirectory(build_libs_dir, '*.aar')
  473. logging.info('# Generate Android .aar info files.')
  474. _CreateAarInfos(aar_files)
  475. if not args.ignore_licenses:
  476. logging.info('# Looking for nested license files.')
  477. for aar_file in aar_files:
  478. # Play Services .aar files have embedded licenses.
  479. with zipfile.ZipFile(aar_file) as z:
  480. if _THIRD_PARTY_LICENSE_FILENAME in z.namelist():
  481. aar_dirname = os.path.dirname(aar_file)
  482. license_path = os.path.join(aar_dirname, 'LICENSE')
  483. # Make sure to append as we don't want to lose the
  484. # existing license.
  485. with open(license_path, 'ab') as f:
  486. f.write(z.read(_THIRD_PARTY_LICENSE_FILENAME))
  487. logging.info('# Compare CIPD packages.')
  488. existing_packages = ParseDeps(args.android_deps_dir, _LIBS_DIR)
  489. build_packages = ParseDeps(build_android_deps_dir, _LIBS_DIR)
  490. deleted_packages = []
  491. updated_packages = []
  492. for pkg in sorted(existing_packages):
  493. if pkg not in build_packages:
  494. deleted_packages.append(pkg)
  495. else:
  496. existing_info = existing_packages[pkg]
  497. build_info = build_packages[pkg]
  498. if existing_info.tag != build_info.tag:
  499. updated_packages.append(pkg)
  500. new_packages = sorted(set(build_packages) - set(existing_packages))
  501. # Copy updated DEPS and BUILD.gn to build directory.
  502. Copy(build_android_deps_dir,
  503. _CUSTOM_ANDROID_DEPS_FILES,
  504. args.android_deps_dir,
  505. _CUSTOM_ANDROID_DEPS_FILES,
  506. src_path_must_exist=is_primary_android_deps)
  507. # Delete obsolete or updated package directories.
  508. for pkg in existing_packages.values():
  509. pkg_path = os.path.join(args.android_deps_dir, pkg.path)
  510. DeleteDirectory(pkg_path)
  511. # Copy new and updated packages from build directory.
  512. for pkg in build_packages.values():
  513. pkg_path = pkg.path
  514. dst_pkg_path = os.path.join(args.android_deps_dir, pkg_path)
  515. src_pkg_path = os.path.join(build_android_deps_dir, pkg_path)
  516. CopyFileOrDirectory(src_pkg_path,
  517. dst_pkg_path,
  518. ignore_extension=".tmp")
  519. # Useful for printing timestamp.
  520. logging.info('All Done.')
  521. if new_packages:
  522. PrintPackageList(new_packages, 'new')
  523. if updated_packages:
  524. PrintPackageList(updated_packages, 'updated')
  525. if deleted_packages:
  526. PrintPackageList(deleted_packages, 'deleted')
  527. if __name__ == "__main__":
  528. main()