Add lazy loading and caching of project registry.

Change-Id: If5d20a1037fa9738f6f08a8ec5a9b603e1a878a3
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/qg/+/125131
Commit-Queue: Erik Gilling <konkers@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/qg-cli/src/target.rs b/qg-cli/src/target.rs
index 497da63..07f5ccf 100644
--- a/qg-cli/src/target.rs
+++ b/qg-cli/src/target.rs
@@ -24,7 +24,7 @@
 #[allow(clippy::unused_async)]
 pub async fn run(_args: &Command) -> Result<()> {
     let project = Project::locate()?;
-    let registry = project.parse_manifests()?;
+    let registry = project.registry()?;
 
     println!(
         "Project {}, {} total targets",
diff --git a/qg/src/project/mod.rs b/qg/src/project/mod.rs
index 68f32ec..06ecfd1 100644
--- a/qg/src/project/mod.rs
+++ b/qg/src/project/mod.rs
@@ -13,6 +13,7 @@
 // the License.
 
 use std::{
+    cell::{Ref, RefCell},
     ffi::OsStr,
     path::{Path, PathBuf},
 };
@@ -33,6 +34,9 @@
     root: PathBuf,
     cache_dir: PathBuf,
     name: String,
+    // Use a RefCell to provide interior mutability on the registry so that it can
+    // be lazily loaded without taking a mutable reference on the Project.
+    registry: RefCell<Option<Registry>>,
 }
 
 impl Project {
@@ -78,6 +82,7 @@
             root: project_root.to_owned(),
             cache_dir: project_root.join(Self::CACHE_DIRECTORY),
             name: manifest.project.name,
+            registry: RefCell::new(None),
         })
     }
 
@@ -108,6 +113,7 @@
             root: root.to_owned(),
             cache_dir: root.join(Self::CACHE_DIRECTORY),
             name: name.into(),
+            registry: RefCell::new(None),
         };
 
         let manifest = Manifest::new(name);
@@ -146,13 +152,36 @@
         Self::relative_file(&self.cache_dir, Path::new(path.as_ref()))
     }
 
+    /// Returns the target registry for this project.  Will locate and parse
+    /// manifests if they have not already been read.
+    ///
+    /// # Errors
+    /// Returns an error if manifest files could not be read or are improperly
+    /// structured.
+    pub fn registry(&self) -> Result<Ref<Registry>> {
+        {
+            // Scope the mutable borrow to just the existence check and
+            // manifest parsing.
+            let mut registry = self.registry.borrow_mut();
+            if registry.is_none() {
+                *registry = Some(self.parse_manifests()?);
+            }
+        }
+
+        Ok(Ref::map(self.registry.borrow(), |registry| {
+            registry
+                .as_ref()
+                .expect("registry should be valid due to being created above")
+        }))
+    }
+
     /// Reads `qg` manifest files starting from the project root into a registry
     /// of available packages.
     ///
     /// # Errors
     /// Returns an error if manifest files could not be read or are improperly
     /// structured.
-    pub fn parse_manifests(&self) -> Result<Registry> {
+    fn parse_manifests(&self) -> Result<Registry> {
         let root_manifest_file = self.root_manifest();
         let root_manifest = root_manifest_file.deserialize_toml::<Manifest>()?;
         let mut registry = Registry::new();
@@ -244,6 +273,7 @@
             cache_dir: root.join("qg-cache"),
             root,
             name: "qg2".into(),
+            registry: RefCell::new(None),
         };
 
         assert_eq!(
@@ -267,6 +297,7 @@
             cache_dir: root.join("qg-cache"),
             root,
             name: "qg2".into(),
+            registry: RefCell::new(None),
         };
 
         assert!(matches!(