1use std::sync::Arc;
6
7use crate::capability::{builtin, CapabilityRegistry, Context};
8use crate::cli::{DiscoveryMode, RepoArgs};
9use crate::discovery::{DiscoverySource, FileSystem, Sources, VcsIndex};
10use crate::error::{Error, Result};
11use crate::git;
12use crate::index::Index;
13use crate::metadata::KindSchema;
14use crate::model::Severity;
15use crate::repository::Repository;
16use crate::share::Share;
17
18pub struct App {
20 pub repository: Repository,
22 pub share: Share,
24 pub schema: KindSchema,
26 pub context: Arc<Context>,
28}
29
30impl App {
31 pub fn load(args: &RepoArgs) -> Result<Self> {
34 let start = match &args.repo {
35 Some(p) => p.clone(),
36 None => std::env::current_dir().map_err(|e| Error::io(".", e))?,
37 };
38 let repository = {
39 let _phase = crate::perf::phase(crate::perf::Phase::RepositoryDiscovery);
40 Repository::discover(&start)?
41 };
42 tracing::info!(repository_root = %repository.root().display(), "repository found");
43 let share = Share::locate(args.share.as_deref(), repository.root())?;
44 tracing::info!(share = %share.dir().display(), origin = share.origin, "share directory");
45 let schema = KindSchema::load(&share, &repository)?;
46 if let Some(manifest_schema) = schema.schema("manifest") {
47 let violations = manifest_schema.validate(&repository.manifest_value()?);
48 if !violations.is_empty() {
49 let unknown: Vec<String> = violations
50 .iter()
51 .flat_map(|v| v.unknown_keys.clone())
52 .collect();
53 let path = repository.root().join(crate::repository::MANIFEST);
54 return Err(if unknown.is_empty() {
55 Error::InvalidManifest {
56 path,
57 reason: violations
58 .iter()
59 .map(|v| format!("{}: {}", v.path, v.message))
60 .collect::<Vec<_>>()
61 .join("; "),
62 }
63 } else {
64 Error::UnknownKeys {
65 path,
66 keys: unknown,
67 }
68 });
69 }
70 }
71 let sources = Sources::load(&repository)?;
72 let git_read_at = std::time::SystemTime::now();
73 let git_state = git::inspect(repository.root());
74 let source: Box<dyn DiscoverySource> = match args.discovery {
75 DiscoveryMode::Vcs => {
76 if let git::GitState::Unavailable { reason } = &git_state {
77 return Err(Error::Git {
78 reason: format!("{reason}; discovery through the version-control index needs git (or pass --discovery filesystem)"),
79 });
80 }
81 if crate::discovery::layer_is_untracked(repository.root()) {
85 tracing::info!("the layer is tracked nowhere; discovery walks the work tree");
86 Box::new(FileSystem {
87 excluded: vec![".git".into(), repository.local_path()],
88 })
89 } else {
90 Box::new(VcsIndex::default())
91 }
92 }
93 DiscoveryMode::Filesystem => Box::new(FileSystem {
94 excluded: vec![".git".into(), repository.local_path()],
95 }),
96 };
97 let scope = crate::scope::Scope::load(&share, &repository)?;
98 tracing::info!(origin = ?scope.origin(), path = scope.path(), "scope");
99 let mut index = Index::build(
100 &repository,
101 &sources,
102 &schema,
103 source.as_ref(),
104 git_state,
105 scope,
106 )?;
107 index.repository.observed.git = crate::index::AnswerObservation::taken(
108 "git read",
109 git_read_at,
110 Some(crate::index::STALE_AFTER),
111 );
112 for d in &index.diagnostics {
113 let path = d.path.as_deref().unwrap_or("-");
114 match d.severity {
115 Severity::Error => {
116 tracing::error!(code = %d.code, source_path = path, "{}", d.message)
117 }
118 Severity::Warning => {
119 tracing::warn!(code = %d.code, source_path = path, "{}", d.message)
120 }
121 Severity::Info => {
122 tracing::info!(code = %d.code, source_path = path, "{}", d.message)
123 }
124 }
125 }
126 let errors = index.errors();
127 if args.strict && errors > 0 {
128 return Err(Error::StrictDiagnostics { count: errors });
129 }
130 Self::compose(repository, share, schema, index)
131 }
132
133 pub fn compose(
135 repository: Repository,
136 share: Share,
137 schema: KindSchema,
138 index: Index,
139 ) -> Result<Self> {
140 let mut index = index;
141 index.distribution = crate::distribution::Model::locate(&share)?;
142 index.providers = share.providers()?;
143 index.share = Some(share.dir().to_path_buf());
146 let registry = CapabilityRegistry::builder()
147 .with_modules(builtin::modules())
148 .with_index(&index)
149 .build()
150 .map_err(|errors| Error::Registry { errors })?;
151 tracing::info!(capabilities = registry.len(), "registry built");
152 let context = Arc::new(Context::new(Arc::new(index), Arc::new(registry)));
153 Ok(App {
154 repository,
155 share,
156 schema,
157 context,
158 })
159 }
160
161 pub fn index(&self) -> &Index {
163 &self.context.index
164 }
165
166 pub fn registry(&self) -> &CapabilityRegistry {
168 &self.context.registry
169 }
170}