lib/depgraph: Add functions to generate some common depgraphs

BUG=chromium:1135361
TEST=manually generated depgraphs

Change-Id: I392dceb91205aef74686385ed99ed080879486a8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2575354
Commit-Queue: Alex Klein <saklein@chromium.org>
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/lib/constants.py b/lib/constants.py
index 9c764ac..3c05a51 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -415,12 +415,23 @@
     MERGE_BRANCH
 ]
 
+# SDK target.
+TARGET_SDK = 'virtual/target-sdk'
 # Default OS target packages.
 TARGET_OS_PKG = 'virtual/target-os'
 TARGET_OS_DEV_PKG = 'virtual/target-os-dev'
 TARGET_OS_TEST_PKG = 'virtual/target-os-test'
 TARGET_OS_FACTORY_PKG = 'virtual/target-os-factory'
 TARGET_OS_FACTORY_SHIM_PKG = 'virtual/target-os-factory-shim'
+# The virtuals composing a "full" build, e.g. what's built in the cq.
+# Local (developer) builds only use target-os by default.
+ALL_TARGET_PACKAGES = (
+    TARGET_OS_PKG,
+    TARGET_OS_DEV_PKG,
+    TARGET_OS_TEST_PKG,
+    TARGET_OS_FACTORY_PKG,
+    TARGET_OS_FACTORY_SHIM_PKG,
+)
 
 # Constants for uprevving Chrome
 
diff --git a/lib/depgraph.py b/lib/depgraph.py
index b844156..f5b16d4 100644
--- a/lib/depgraph.py
+++ b/lib/depgraph.py
@@ -7,12 +7,17 @@
 
 from __future__ import print_function
 
+import collections
 import copy
 import os
 import sys
 import time
+from typing import List, Optional, Union
 
+from chromite.lib import constants
+from chromite.lib import cros_logging as logging
 from chromite.lib import cros_test_lib
+from chromite.lib.parser import package_info
 
 assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
 
@@ -854,3 +859,125 @@
       print('    %s' % (j))
     if not needs:
       print('    no dependencies')
+
+
+# Depgraph results data container used by the raw depgraph functions. The
+# deps attribute has the main depgraph info, while the bdeps attribute
+# contains just the bdepends depgraph, i.e. the depgraph installed to the SDK.
+DepgraphResult = collections.namedtuple('DepgraphResult',
+                                        ('deps', 'bdeps', 'packages'))
+
+
+def _get_emerge_args(sysroot_path: str,
+                     packages: Union[List[str], List[package_info.PackageInfo]],
+                     include_bdeps: bool) -> List[str]:
+  """Get the default emerge arguments for building a depgraph."""
+  # Pretend: Don't actually install anything.
+  # Emptytree: Act as though nothing is installed (even if some packages are).
+  # Sysroot: Which sysroot we're considering.
+  args = ['--quiet', '--pretend', '--emptytree', '--sysroot', sysroot_path]
+  # Also set the root for DepGraphGenerator specific semantics. This might not
+  # be necessary, pending more investigation.
+  # TODO: Document final reason or remove if unnecessary.
+  args.extend(['--root', sysroot_path])
+  if include_bdeps:
+    args.append('--include-bdepend')
+
+  try:
+    # Assume PackageInfo instance.
+    final_pkgs = [p.atom for p in packages]
+  except AttributeError:
+    # Also accept strings.
+    final_pkgs = [str(p) for p in packages]
+  args.extend(final_pkgs)
+
+  logging.info('Generating depgraph for packages: %s', ', '.join(final_pkgs))
+
+  return args
+
+
+def _get_sysroot_path(
+    sysroot: Union[str, 'sysroot_lib.Sysroot'],
+    build_target: Union[str, 'build_target_lib.BuildTarget']) -> str:
+  """Convenience function to support sysroot paths from a variety of sources.
+
+  Get the sysroot path from a sysroot (the path, or a Sysroot instance), or
+  a build target (by name, or from the BuildTarget instance).
+  """
+  if sysroot:
+    try:
+      return sysroot.path
+    except AttributeError:
+      return sysroot
+  else:
+    try:
+      return build_target.root
+    except AttributeError:
+      return cros_build_lib.GetSysroot(build_target)
+
+
+def _get_raw_sdk_depgraph(
+    packages: Optional[Union[List[str], List[package_info.PackageInfo]]] = None
+) -> DepgraphResult:
+  """Get the depgraph for the SDK itself.
+
+  The SDK deps will contain the packages installed to a fresh SDK.
+  The bdeps will always be empty since everything is installed to the SDK.
+  """
+  sysroot_path = cros_build_lib.GetSysroot(board=None)
+  packages = packages or [constants.TARGET_SDK]
+  lib_argv = _get_emerge_args(sysroot_path, packages, include_bdeps=True)
+  deps = DepGraphGenerator()
+  deps.Initialize(lib_argv)
+  deps_tree, _deps_info, bdeps_tree = deps.GenDependencyTree()
+
+  return DepgraphResult(deps=deps_tree, bdeps=bdeps_tree, packages=packages)
+
+
+def _get_raw_sysroot_depgraph(
+    sysroot: Optional['sysroot_lib.Sysroot'] = None,
+    build_target: Optional['build_target_lib.BuildTarget'] = None,
+    packages: Optional[Union[List[str], List[package_info.PackageInfo]]] = None
+) -> DepgraphResult:
+  """Get the sysroot depgraph for a build target.
+
+  The sysroot deps are the packages installed to a sysroot -- effectively
+  the packages installed on a device. The bdeps contains all the packages
+  installed to the SDK when the board is built. The two graphs are technically
+  disjoint, but packages can appear in both (with different roots).
+  """
+  assert build_target or sysroot
+  packages = packages or constants.ALL_TARGET_PACKAGES
+  sysroot_path = _get_sysroot_path(sysroot, build_target)
+  lib_argv = _get_emerge_args(sysroot_path, packages, include_bdeps=False)
+
+  deps = DepGraphGenerator()
+  deps.Initialize(lib_argv)
+  deps_tree, _deps_info, bdeps_tree = deps.GenDependencyTree()
+
+  return DepgraphResult(deps=deps_tree, bdeps=bdeps_tree, packages=packages)
+
+
+def _get_raw_build_target_depgraph(
+    sysroot: Optional['sysroot_lib.Sysroot'] = None,
+    build_target: Optional['build_target_lib.BuildTarget'] = None,
+    packages: Optional[Union[List[str], List[package_info.PackageInfo]]] = None
+) -> DepgraphResult:
+  """Get the full depgraph for a build target - its sysroot and bdepends.
+
+  The build target deps contains the [r]depends packages installed
+  to the sysroot and bdepends packages installed to the SDK.
+  The bdeps contains only the bdepends packages installed to
+  the SDK. The bdeps graph is a subgraph of the deps graph.
+  """
+  assert build_target or sysroot
+  packages = packages or constants.ALL_TARGET_PACKAGES
+  sysroot_path = _get_sysroot_path(sysroot, build_target)
+  lib_argv = _get_emerge_args(sysroot_path, packages, include_bdeps=True)
+
+  deps = DepGraphGenerator()
+  deps.Initialize(lib_argv)
+
+  deps_tree, _deps_info, bdeps_tree = deps.GenDependencyTree()
+  return DepgraphResult(deps=deps_tree, bdeps=bdeps_tree, packages=packages)
+