repo_utils/
repo_project_selector.rs1use anyhow::{anyhow, bail, Result};
2use serde::Deserialize;
3use serde_xml_rs::from_reader;
4use std::env;
5use std::fs;
6use std::fs::File;
7use std::io::BufRead;
8use std::io::BufReader;
9use std::path::Path;
10use std::path::PathBuf;
11
12pub fn select_projects(
20 include_manifest_repo: bool,
21 filter_by_groups: Option<Vec<String>>,
22 filter_by_manifest_files: Option<Vec<PathBuf>>,
23) -> Result<Vec<String>> {
24 let projects_on_disk = lines_from_file(find_project_list()?)?;
25 let mut selected_projects = projects_on_disk;
26
27 if let Some(groups) = filter_by_groups {
28 let manifest = parse_manifest(&find_repo_folder()?.join("manifest.xml"))?;
29 selected_projects = selected_projects
30 .drain(..)
31 .filter(|path| {
32 let project = manifest.find_project(path);
33 project.is_some() && project.unwrap().in_any_given_group(&groups)
34 })
35 .collect();
36 }
37
38 if let Some(manifest_files) = filter_by_manifest_files {
39 let repo_manifests_folder = find_repo_manifests_folder()?;
40 let mut aggregated_manifest = Manifest::empty();
41 for manifest_file in manifest_files {
42 let manifest = parse_manifest(&repo_manifests_folder.join(&manifest_file))?;
43 aggregated_manifest.append(&manifest);
44 }
45 selected_projects = selected_projects
46 .drain(..)
47 .filter(|p| aggregated_manifest.contains_project(p))
48 .collect();
49 }
50
51 if include_manifest_repo {
52 selected_projects.push(".repo/manifests".to_string());
53 }
54
55 Ok(selected_projects)
56}
57
58fn lines_from_file(filename: impl AsRef<Path>) -> Result<Vec<String>> {
59 BufReader::new(File::open(filename)?)
60 .lines()
61 .collect::<Result<Vec<_>, _>>()
62 .map_err(anyhow::Error::msg)
63}
64
65pub fn find_project_list() -> Result<PathBuf> {
69 let find_project_list = find_repo_folder()?.join("project.list");
70 match find_project_list.is_file() {
71 true => Ok(find_project_list),
72 false => Err(anyhow!("no project.list in .repo found")),
73 }
74}
75
76pub fn find_repo_folder() -> Result<PathBuf> {
80 let base_folder = find_repo_root_folder()?;
81 Ok(base_folder.join(".repo"))
82}
83
84pub fn find_repo_manifests_folder() -> Result<PathBuf> {
88 let base_folder = find_repo_folder()?;
89 Ok(base_folder.join("manifests"))
90}
91
92pub fn find_repo_root_folder() -> Result<PathBuf> {
96 let cwd = env::current_dir()?;
97 for parent in cwd.ancestors() {
98 for entry in fs::read_dir(&parent)? {
99 let entry = entry?;
100 if entry.path().is_dir() && entry.file_name() == ".repo" {
101 return Ok(parent.to_path_buf());
102 }
103 }
104 }
105 bail!("no .repo folder found")
106}
107
108pub fn parse_manifest(path: &Path) -> Result<Manifest> {
109 let file = File::open(path).map_err(|e| anyhow!("Unable to open {:?}: {}", path, e))?;
110 let reader = BufReader::new(file);
111 let mut manifest: Manifest = from_reader(reader)?;
112 let includes: Vec<String> = manifest.includes.iter().map(|i| i.name.clone()).collect();
113 for include in &includes {
114 let path = find_repo_manifests_folder()?.join(include);
115 let child = parse(&path).map_err(|e| anyhow!("Failed to parse {}: {}", include, e))?;
116 manifest.append(&child);
117 }
118 Ok(manifest)
119}
120
121pub fn parse(path: &Path) -> Result<Manifest> {
122 let file = File::open(path)?;
123 let reader = BufReader::new(file);
124 let mut manifest: Manifest = from_reader(reader)?;
125 let includes: Vec<String> = manifest.includes.iter().map(|i| i.name.clone()).collect();
126 for include in &includes {
127 let path = path.with_file_name(include);
128 let child = parse(&path).map_err(|e| anyhow!("Failed to parse {}: {}", include, e))?;
129 manifest.append(&child);
130 }
131 Ok(manifest)
132}
133
134#[derive(Debug, Deserialize)]
136pub struct Manifest {
137 #[serde(rename = "project", default)]
138 pub projects: Vec<Project>,
139 #[serde(rename = "include", default)]
140 pub includes: Vec<Include>,
141}
142
143impl Manifest {
144 pub fn empty() -> Self {
145 Manifest {
146 projects: vec![],
147 includes: vec![],
148 }
149 }
150
151 pub fn append(&mut self, manifest: &Manifest) {
152 let projects = &manifest.projects;
153 self.projects.extend(projects.iter().cloned());
154 }
155
156 pub fn contains_project(&self, local_path: &str) -> bool {
157 self.projects.iter().any(|p| p.path == local_path)
158 }
159
160 pub fn find_project(&self, local_path: &str) -> Option<&Project> {
161 self.projects.iter().find(|p| p.path == local_path)
162 }
163}
164
165#[derive(Debug, Deserialize, Clone)]
167pub struct Project {
168 pub name: String,
169 pub path: String,
170 pub groups: Option<String>,
171}
172
173impl Project {
174 pub fn in_any_given_group(&self, test_for_groups: &[String]) -> bool {
175 let project_groups: Vec<String> = self
176 .groups
177 .as_ref()
178 .unwrap_or(&String::new())
179 .split(&[',', ' '][..])
180 .map(|s| s.to_string())
181 .collect();
182 project_groups
183 .iter()
184 .any(|g| test_for_groups.iter().any(|other| g == other))
185 }
186}
187
188#[derive(Debug, Deserialize, Clone)]
190pub struct Include {
191 pub name: String,
192}