Skip to content

Commit cdaf8f9

Browse files
committed
Adds 'spk repo index' subcommand for index generation and updates.
Adds --use-indexes and --no-indexes flags to repository. Updates resolvo solver to get global variables data from an indexed repository. Signed-off-by: David Gilligan-Cook <dcook@imageworks.com>
1 parent 1e28bf4 commit cdaf8f9

File tree

10 files changed

+273
-36
lines changed

10 files changed

+273
-36
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/spk-cli/cmd-repo/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ miette = { workspace = true, features = ["fancy"] }
1717
async-trait = { workspace = true }
1818
clap = { workspace = true }
1919
spk-cli-common = { workspace = true }
20+
spk-schema = { workspace = true }
2021
spk-storage = { workspace = true }
2122
tracing = { workspace = true }

crates/spk-cli/cmd-repo/src/cmd_repo.rs

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// https://github.com/spkenv/spk
44

5+
use std::str::FromStr;
6+
use std::time::Instant;
7+
58
use clap::{Args, Subcommand};
69
use miette::{Context, Result};
7-
use spk_cli_common::{CommandArgs, Run};
8-
use spk_storage as storage;
10+
use spk_cli_common::{CommandArgs, Run, flags};
11+
use spk_schema::VersionIdent;
12+
use spk_storage::{self as storage, FlatBufferRepoIndex, RepositoryIndexMut};
913
use storage::Repository;
1014

1115
/// Perform repository-level actions and maintenance
@@ -45,19 +49,104 @@ pub enum RepoCommand {
4549
#[clap(name = "REPO")]
4650
repo: String,
4751
},
52+
/// Generate an index for a repository
53+
Index {
54+
#[clap(flatten)]
55+
repos: flags::Repositories,
56+
57+
/// Package/version of a published package to update in an
58+
/// existing index.
59+
///
60+
/// Other packages in the index will remain unchanged. Without
61+
/// this the full index will be constructed from scratch. If
62+
/// the repo does not have an index, a full index will be
63+
/// constructed from scratch if possible.
64+
///
65+
/// This option is only supported for flatbuffer indexes.
66+
#[clap(long, name = "PACKAGE/VERSION")]
67+
update: Option<String>,
68+
},
4869
}
4970

5071
impl RepoCommand {
5172
pub async fn run(&mut self) -> Result<i32> {
52-
let repo = match &self {
53-
Self::Upgrade { repo } => repo,
54-
};
55-
let repo = match repo.as_str() {
56-
"local" => storage::local_repository().await?,
57-
_ => storage::remote_repository(repo).await?,
58-
};
59-
let status = repo.upgrade().await.wrap_err("Upgrade failed")?;
60-
tracing::info!("{}", status);
61-
Ok(1)
73+
match &self {
74+
// spk repo upgrade ...
75+
Self::Upgrade { repo: repo_name } => {
76+
let repo = match repo_name.as_str() {
77+
"local" => storage::local_repository().await?,
78+
_ => storage::remote_repository(repo_name).await?,
79+
};
80+
81+
let status = repo.upgrade().await.wrap_err("Upgrade failed")?;
82+
tracing::info!("{}", status);
83+
Ok(1)
84+
}
85+
86+
// spk repo index ...
87+
Self::Index { repos, update } => {
88+
// Generate or update an index a repo. The repo must
89+
// be the underlying repo and not an indexed repo. So
90+
// this disables index use for this command regardless
91+
// of config or command line flags.
92+
flags::disable_index_use();
93+
let repos = repos.get_repos_for_non_destructive_operation().await?;
94+
if repos.len() != 1 {
95+
tracing::error!(
96+
"{} repos specified, Can only index one repo at a time. Please specify a single repo.",
97+
repos.len()
98+
);
99+
return Ok(2);
100+
}
101+
102+
if let Some(package_version) = update {
103+
// Update the existing index for the given package/version
104+
let start = Instant::now();
105+
let version_ident = VersionIdent::from_str(package_version)?;
106+
let mut was_full_index = String::from("");
107+
108+
// Load the current index for this repo now
109+
let repo = &repos[0].1;
110+
match FlatBufferRepoIndex::from_repo_file(repo).await {
111+
Ok(current_index) => {
112+
let repo = &repos[0].1;
113+
current_index
114+
.update_repo_with_package_version(repo, &version_ident)
115+
.await?
116+
}
117+
Err(err) => {
118+
// There isn't an existing index, so generate one from scratch that
119+
// will also include the update package version.
120+
// TODO: could also just error out and say run a full index first,
121+
// or ask the user "are you sure?" before continuing.
122+
tracing::warn!("Failed to load flatbuffer index: {err}");
123+
tracing::warn!("No current index to update. Creating a full index ...");
124+
FlatBufferRepoIndex::index_repo(&repos).await?;
125+
was_full_index =
126+
" [no previous index, so a full index was created]".to_string()
127+
}
128+
};
129+
130+
tracing::info!(
131+
"Index update for '{package_version}' in '{}' repo completed in: {} secs{was_full_index}",
132+
repo.name(),
133+
start.elapsed().as_secs_f64()
134+
);
135+
} else {
136+
// Generate a full index from scratch
137+
let start = Instant::now();
138+
FlatBufferRepoIndex::index_repo(&repos).await?;
139+
140+
let repo = &repos[0].1;
141+
tracing::info!(
142+
"Index generation for '{}' repo completed in: {} secs",
143+
repo.name(),
144+
start.elapsed().as_secs_f64()
145+
);
146+
}
147+
148+
Ok(0)
149+
}
150+
}
62151
}
63152
}

crates/spk-cli/common/src/flags.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ mod variant;
77
use std::collections::HashSet;
88
use std::convert::From;
99
use std::sync::Arc;
10+
use std::sync::atomic::AtomicBool;
1011

1112
use clap::{Args, ValueEnum, ValueHint};
1213
use miette::{Context, IntoDiagnostic, Result, bail, miette};
14+
use once_cell::sync::Lazy;
1315
use solve::{
1416
DEFAULT_SOLVER_RUN_FILE_PREFIX,
1517
DecisionFormatter,
@@ -45,6 +47,7 @@ use spk_solve as solve;
4547
#[cfg(feature = "statsd")]
4648
use spk_solve::{SPK_RUN_TIME_METRIC, get_metrics_client};
4749
use spk_storage as storage;
50+
use spk_storage::IndexedRepository;
4851
use spk_workspace::{FindOrLoadPackageTemplateError, FindPackageTemplateError};
4952
pub use variant::{Variant, VariantBuildStatus, VariantLocation};
5053

@@ -61,6 +64,12 @@ static SPK_SOLVER_OUTPUT_TO_DIR: &str = "SPK_SOLVER_OUTPUT_TO_DIR";
6164
static SPK_SOLVER_OUTPUT_TO_DIR_MIN_VERBOSITY: &str = "SPK_SOLVER_OUTPUT_TO_DIR_MIN_VERBOSITY";
6265
static SPK_SOLVER_OUTPUT_FILE_PREFIX: &str = "SPK_SOLVER_OUTPUT_FILE_PREFIX";
6366

67+
static DISABLE_INDEX_USE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
68+
69+
pub fn disable_index_use() {
70+
DISABLE_INDEX_USE.store(true, std::sync::atomic::Ordering::Release);
71+
}
72+
6473
#[derive(Args, Clone)]
6574
pub struct Runtime {
6675
/// Reconfigure the current spfs runtime (useful for speed and debugging)
@@ -1052,6 +1061,20 @@ pub struct Repositories {
10521061
/// per-job or per-show repos.
10531062
#[clap(long)]
10541063
pub wrap_origin: Option<std::path::PathBuf>,
1064+
1065+
/// Get the package data from the repo indexes instead of the
1066+
/// repos. This only applies to non-destructive repo operations.
1067+
/// This can be configured as the default in spk's config file, or
1068+
/// on a per-repo basis in spk's config file.
1069+
#[clap(long)]
1070+
pub use_indexes: bool,
1071+
1072+
/// Do not get the package data from the repo index, always use
1073+
/// the repo instead. This only applies to non-destructive repo
1074+
/// operations. This option can be configured as the default in
1075+
/// spk's config file.
1076+
#[clap(long, conflicts_with = "use_indexes")]
1077+
pub no_indexes: bool,
10551078
}
10561079

10571080
impl Repositories {
@@ -1152,8 +1175,39 @@ impl Repositories {
11521175
repos.push(("local".into(), repo.into()));
11531176
}
11541177
if self.local_repo_only {
1178+
// Local repo only case does not use indexes because they
1179+
// are typically small and not indexed. If local repos
1180+
// became large and were indexed, this might change.
11551181
return Ok(repos);
11561182
}
1183+
1184+
// Check whether using the indexes for the repos is enabled or not
1185+
let disable_all_index_use = DISABLE_INDEX_USE.load(std::sync::atomic::Ordering::Relaxed);
1186+
1187+
// Get the overrides from the command line flags
1188+
let use_index_cli_override = if self.use_indexes || self.no_indexes {
1189+
if self.no_indexes {
1190+
// Don't use any indexes
1191+
Some(false)
1192+
} else {
1193+
// Use indexes, if the command line flag is given to use them
1194+
Some(self.use_indexes)
1195+
}
1196+
} else {
1197+
// No command line index flags given, so there's no index override
1198+
None
1199+
};
1200+
1201+
// Get the overall config setting, which includes env var
1202+
// setting for it, but not the per repo settings. Those are
1203+
// checked below as repos are created.
1204+
let config = spk_config::get_config()?;
1205+
let all_use_index_from_config = config.solver.use_indexes;
1206+
tracing::debug!(
1207+
"Disable all indexes: {disable_all_index_use}, Cli use all indexes: {use_index_cli_override:?}, Config use all indexes: {all_use_index_from_config}"
1208+
);
1209+
1210+
// Add the enabled repos
11571211
for (name, ts, is_default_origin) in enabled
11581212
.into_iter()
11591213
.map(|(name, ts)| (name, ts, false))
@@ -1190,7 +1244,57 @@ impl Repositories {
11901244
if let Some(ts) = ts.as_ref().or(self.when.as_ref()) {
11911245
repo.pin_at_time(ts);
11921246
}
1193-
repos.push((name.into(), repo.into()));
1247+
1248+
// Decide whether to use an index for this repository based on
1249+
// the various settings: all repos, per repo, and overrides.
1250+
let use_index = if disable_all_index_use {
1251+
// Disable all take precedence over everything
1252+
tracing::debug!("All index use disabled");
1253+
false
1254+
} else if let Some(use_indexes) = use_index_cli_override {
1255+
// Otherwise the all indexes command line flag takes
1256+
// precedence over what was in the config file
1257+
tracing::debug!("A cli-based use index override was given as: {use_indexes}");
1258+
use_indexes
1259+
} else {
1260+
// Otherwise use this specific repository's config setting, if any
1261+
match config.repositories.get(name) {
1262+
Some(r_config) => {
1263+
tracing::debug!("Using index for '{name}' repo");
1264+
r_config.use_index
1265+
}
1266+
// And last use the all indexes env var and config
1267+
// file setting.
1268+
None => {
1269+
tracing::debug!("Using config settings for all indexes");
1270+
all_use_index_from_config
1271+
}
1272+
}
1273+
};
1274+
tracing::debug!("Using index for '{name}': {use_index}");
1275+
1276+
if use_index {
1277+
// Use the index for this repo, if there is one,
1278+
// otherwise use the repo itself.
1279+
tracing::debug!("Using a repo index for '{name}'");
1280+
let indexed_repo = match IndexedRepository::load_from_repo(Arc::new(
1281+
repo.clone().into(),
1282+
))
1283+
.await
1284+
{
1285+
Ok(ir) => ir.into(),
1286+
Err(_err) => {
1287+
tracing::warn!(
1288+
"Failed to load index for '{name}' repo, falling back to the repo itself"
1289+
);
1290+
repo.into()
1291+
}
1292+
};
1293+
repos.push((name.to_string(), indexed_repo));
1294+
} else {
1295+
tracing::debug!("Not using a repo index for '{name}' repo");
1296+
repos.push((name.to_string(), repo.into()));
1297+
}
11941298
}
11951299
Ok(repos)
11961300
}

crates/spk-cli/common/src/flags_test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ async fn test_get_solver_with_host_options(
6767
disable_repo: Default::default(),
6868
when: None,
6969
wrap_origin: None,
70+
use_indexes: false,
71+
no_indexes: false,
7072
},
7173
decision_formatter_settings: DecisionFormatterSettings {
7274
time: Default::default(),

crates/spk-cli/group4/src/cmd_view.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,11 @@ impl View {
781781

782782
/// Display the contents of a package spec
783783
fn print_build_spec(&self, package_spec: Arc<Spec>) -> Result<i32> {
784+
// TODO: does not handle packages from indexes. This will
785+
// crash if --use-indexes was used. Those packages would need
786+
// to be converted to something that could be serialized, or
787+
// pieces extracted individually from the index packages, for
788+
// this to work.
784789
match &self.format.clone().unwrap_or_default() {
785790
OutputFormat::Yaml => serde_yaml::to_writer(std::io::stdout(), &*package_spec)
786791
.into_diagnostic()

crates/spk-solve/src/solvers/resolvo/mod.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
2121
use std::sync::Arc;
2222
use std::time::Instant;
2323

24-
use pkg_request_version_set::{SpkSolvable, SyntheticComponent};
24+
use pkg_request_version_set::{SpkSolvable, SyntheticComponent, VarValue};
2525
use spk_provider::SpkProvider;
2626
use spk_schema::ident::{
2727
InclusionPolicy,
@@ -36,7 +36,7 @@ use spk_schema::ident::{
3636
VarRequest,
3737
};
3838
use spk_schema::ident_component::Component;
39-
use spk_schema::name::PkgNameBuf;
39+
use spk_schema::name::{OptNameBuf, PkgNameBuf};
4040
use spk_schema::prelude::{HasVersion, Named, Versioned};
4141
use spk_schema::version_range::VersionFilter;
4242
use spk_schema::{OptionMap, Package, Spec};
@@ -146,7 +146,28 @@ impl Solver {
146146
self.build_from_source_trail = trail;
147147
}
148148

149-
pub async fn solve(&self) -> Result<Solution> {
149+
pub async fn solve(&mut self) -> Result<Solution> {
150+
let mut known_global_vars: HashMap<OptNameBuf, HashSet<VarValue>> = Default::default();
151+
152+
// Gather the global vars from any indexed repos and use them
153+
// to prime the known global vars cache.
154+
for repo in self.repos.iter() {
155+
if let RepositoryHandle::Indexed(indexed_repo) = &**repo {
156+
let start_gv = Instant::now();
157+
for (name, values) in indexed_repo.get_global_var_values().into_iter() {
158+
let entry = known_global_vars.entry(name).or_default();
159+
for v in values {
160+
entry.insert(VarValue::Owned(v));
161+
}
162+
}
163+
tracing::debug!(
164+
"Resolvo: gathered global var from '{}' in: {} secs",
165+
repo.name(),
166+
start_gv.elapsed().as_secs_f64()
167+
);
168+
}
169+
}
170+
150171
let repos = self.repos.clone();
151172
let requests = self.requests.clone();
152173
let binary_only = self.binary_only;
@@ -155,6 +176,7 @@ impl Solver {
155176
let solvables = tokio::task::spawn_blocking(move || {
156177
let mut provider = Some(SpkProvider::new(
157178
repos.clone(),
179+
known_global_vars.clone(),
158180
binary_only,
159181
build_from_source_trail,
160182
));

0 commit comments

Comments
 (0)