Build API: Add build packages endpoint.

Adding the chromite.api.SysrootService/InstallPackages method.

BUG=chromium:905027, b:127860573
TEST=run_tests

Change-Id: Ia460d278a075b36d08d2bf45cc8f4ea6e7ae7434
Reviewed-on: https://chromium-review.googlesource.com/1548338
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Alex Klein <saklein@chromium.org>
diff --git a/api/controller/sysroot.py b/api/controller/sysroot.py
index 52daec7..c30922b 100644
--- a/api/controller/sysroot.py
+++ b/api/controller/sysroot.py
@@ -9,11 +9,16 @@
 
 from chromite.lib import build_target_util
 from chromite.lib import cros_build_lib
+from chromite.lib import portage_util
 from chromite.lib import sysroot_lib
 from chromite.service import sysroot
 
 _ACCEPTED_LICENSES = '@CHROMEOS'
 
+# Return codes.
+RC_ERROR = 1
+
+
 def Create(input_proto, output_proto):
   """Create or replace a sysroot."""
   update_chroot = not input_proto.flags.chroot_current
@@ -27,8 +32,8 @@
 
   build_target = build_target_util.BuildTarget(name=build_target_name,
                                                profile=profile)
-  run_configs = sysroot.SetupBoardRunConfig(force=replace_sysroot,
-                                            upgrade_chroot=update_chroot)
+  run_configs = sysroot.SetupBoardRunConfig(
+      force=replace_sysroot, upgrade_chroot=update_chroot)
 
   try:
     created = sysroot.Create(build_target, run_configs,
@@ -64,9 +69,64 @@
     # Error installing - populate the failed package info.
     for package in e.failed_toolchain_info:
       package_info = output_proto.failed_packages.add()
-      package_info.category = package.category
-      package_info.package_name = package.package
-      if package.version:
-        package_info.version = package.version
+      _CPVToPackageInfo(package, package_info)
 
-    return 1
+    return RC_ERROR
+
+
+def InstallPackages(input_proto, output_proto):
+  compile_source = input_proto.flags.compile_source
+  event_file = input_proto.flags.event_file
+
+  sysroot_path = input_proto.sysroot.path
+  build_target_name = input_proto.sysroot.build_target.name
+  packages = map(_PackageInfoToCPV, input_proto.packages)
+
+  if not build_target_name:
+    cros_build_lib.Die('Build target name is required.')
+  if not sysroot_path:
+    cros_build_lib.Die('Sysroot path is required')
+
+  build_target = build_target_util.BuildTarget(build_target_name)
+  target_sysroot = sysroot_lib.Sysroot(sysroot_path)
+
+  if not target_sysroot.IsToolchainInstalled():
+    cros_build_lib.Die('Toolchain must first be installed.')
+
+  build_packages_config = sysroot.BuildPackagesRunConfig(
+      event_file=event_file, usepkg=not compile_source,
+      install_debug_symbols=True, packages=packages)
+
+  try:
+    sysroot.BuildPackages(build_target, target_sysroot, build_packages_config)
+  except sysroot_lib.PackageInstallError as e:
+    for package in e.failed_packages:
+      package_info = output_proto.failed_packages.add()
+      _CPVToPackageInfo(package, package_info)
+
+    return RC_ERROR
+
+
+def _CPVToPackageInfo(cpv, package_info):
+  """Helper to translate CPVs into a PackageInfo message."""
+  package_info.package_name = cpv.package
+  if cpv.category:
+    package_info.category = cpv.category
+  if cpv.version:
+    package_info.version = cpv.version
+
+
+def _PackageInfoToCPV(package_info):
+  """Helper to translate a PackageInfo message into a CPV."""
+  if not package_info or not package_info.package_name:
+    return None
+
+  # Combine the components into the full CPV string that SplitCPV parses.
+  # TODO: Turn portage_util.CPV into a class that can handle building out an
+  #  instance from components.
+  c = ('%s/' % package_info.category) if package_info.category else ''
+  p = package_info.package_name
+  v = ('-%s' % package_info.version) if package_info.version else ''
+  cpv = '%s%s%s' % (c, p, v)
+
+  return portage_util.SplitCPV(cpv)