dependency service: Cache dependency data.

Cache the dependency data to avoid repeated depgraph generation
on successive calls.

BUG=chromium:1086714
TEST=run_pytest

Change-Id: Id4747cab1f639ce76a92c805289c00edfdf9a05f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2411176
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Chris McDonald <cjmcdonald@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Commit-Queue: Alex Klein <saklein@chromium.org>
diff --git a/api/controller/dependency.py b/api/controller/dependency.py
index fcf9750..338cfac 100644
--- a/api/controller/dependency.py
+++ b/api/controller/dependency.py
@@ -86,7 +86,8 @@
     board = input_proto.build_target.name
     sysroot_path = cros_build_lib.GetSysroot(board or None)
 
-  packages = [controller_util.PackageInfoToCPV(x) for x in input_proto.packages]
+  packages = tuple(
+      controller_util.PackageInfoToCPV(x) for x in input_proto.packages)
 
   json_map, sdk_json_map = dependency.GetBuildDependency(sysroot_path, board,
                                                          packages)
diff --git a/api/controller/dependency_unittest.py b/api/controller/dependency_unittest.py
index 455bd9b..9dfd670 100644
--- a/api/controller/dependency_unittest.py
+++ b/api/controller/dependency_unittest.py
@@ -136,7 +136,7 @@
                                        self.api_config)
     self.assertEqual(self.response.dep_graph.build_target.name, 'deathstar')
     pkg_to_cpv.assert_called_once_with(package)
-    get_dep.assert_called_once_with('/build/target', 'target', [pkg_mock])
+    get_dep.assert_called_once_with('/build/target', 'target', (pkg_mock,))
 
   def testValidateOnly(self):
     """Sanity check that a validate only call does not execute any logic."""
diff --git a/service/dependency.py b/service/dependency.py
index 84a7d70..1adf55e 100644
--- a/service/dependency.py
+++ b/service/dependency.py
@@ -7,6 +7,7 @@
 
 from __future__ import print_function
 
+import functools
 import os
 from pathlib import Path
 import re
@@ -278,6 +279,7 @@
   return results
 
 
+@functools.lru_cache()
 def GetBuildDependency(sysroot_path, board=None, packages=None):
   """Return the build dependency and package -> source path map for |board|.
 
@@ -286,7 +288,7 @@
         used.
     board (str): The name of the board whose artifacts are being created, or
         None if no sysroot is being used.
-    packages (list[CPV]): The packages that need to be built, or empty / None
+    packages (tuple[CPV]): The packages that need to be built, or empty / None
         to use the default list.
 
   Returns:
@@ -401,8 +403,9 @@
     The relevant package dependencies based on the given list of packages and
       src_paths.
   """
+  pkgs = tuple(packages) if packages else None
   json_deps, _sdk_json_deps = GetBuildDependency(
-      sysroot_path, build_target.name, packages=packages)
+      sysroot_path, build_target.name, packages=pkgs)
 
   relevant_packages = set()
   for cpv, dep_src_paths in json_deps['source_path_mapping'].items():
diff --git a/service/packages.py b/service/packages.py
index 4d48891..81d1ac1 100644
--- a/service/packages.py
+++ b/service/packages.py
@@ -684,9 +684,10 @@
   """Check if |build_target| builds |atom| (has it in its depgraph)."""
   cros_build_lib.AssertInsideChroot()
 
+  pkgs = tuple(packages) if packages else None
   # TODO(crbug/1081828): Receive and use sysroot.
   graph, _sdk_graph = dependency.GetBuildDependency(
-      build_target.root, build_target.name, packages)
+      build_target.root, build_target.name, pkgs)
   return any(atom in package for package in graph['package_deps'])