pray_core/
resolve_upstream_latest.rs1use super::super::{source_map, ResolvedPackage, ResolvedProject};
2use super::lock::implied_upstream_source;
3use super::resolution_context::UpstreamResolutionContext;
4use crate::config::load_user_config;
5use crate::constraint::{latest_constraint_for_package, version_satisfies};
6use crate::lockfile::Lockfile;
7use crate::package_spec::parse_package_spec;
8use crate::package_spec_render::render_package_spec;
9use crate::paths::find_prayspec_file;
10use crate::resolve_context::ResolveOptions;
11use crate::resolve_git_sources::prepare_git_sources;
12use crate::{PrayError, PrayResult};
13use std::fs;
14use std::path::PathBuf;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct PathUpstreamLatestConstraint {
18 pub package_name: String,
19 pub upstream_name: String,
20 pub current_constraint: String,
21 pub latest_version: String,
22 pub new_constraint: String,
23 pub package_root: PathBuf,
24}
25
26pub fn plan_path_upstream_latest_constraints(
27 project: &ResolvedProject,
28 previous: Option<&Lockfile>,
29 selected: Option<&str>,
30 options: &ResolveOptions,
31) -> PrayResult<Vec<PathUpstreamLatestConstraint>> {
32 let user_config = load_user_config()?;
33 let git_sources = prepare_git_sources(
34 &project.project_root,
35 &project.manifest.sources,
36 previous,
37 options,
38 )?;
39 let sources = source_map(&project.manifest.sources);
40 let context = UpstreamResolutionContext::new(
41 &project.project_root,
42 &sources,
43 &git_sources,
44 &user_config,
45 previous,
46 options,
47 );
48 let mut plans = Vec::new();
49 for package in &project.packages {
50 if selected.is_some_and(|name| name != package.declaration.name) {
51 continue;
52 }
53 if let Some(plan) = plan_one_path_upstream(&context, package)? {
54 plans.push(plan);
55 }
56 }
57 Ok(plans)
58}
59
60pub fn apply_path_upstream_latest_constraints(
61 plans: &[PathUpstreamLatestConstraint],
62) -> PrayResult<()> {
63 for plan in plans {
64 let spec_path = find_prayspec_file(&plan.package_root)?;
65 let text = fs::read_to_string(&spec_path)?;
66 let mut spec = parse_package_spec(&text)?;
67 let Some(upstream) = spec.upstream.as_mut() else {
68 return Err(PrayError::Resolution(format!(
69 "package {} has no upstream pin",
70 plan.package_name
71 )));
72 };
73 upstream.constraint = plan.new_constraint.clone();
74 crate::transaction::write_file(&spec_path, render_package_spec(&spec))?;
75 }
76 Ok(())
77}
78
79fn plan_one_path_upstream(
80 context: &UpstreamResolutionContext<'_>,
81 package: &ResolvedPackage,
82) -> PrayResult<Option<PathUpstreamLatestConstraint>> {
83 let Some(upstream) = &package.spec.upstream else {
84 return Ok(None);
85 };
86 if package.declaration.path.is_none() {
87 return Ok(None);
88 }
89 let source = implied_upstream_source(&upstream.name, context.sources)?;
90 let resolved = context.resolve(&upstream.name, "*", source.as_deref())?;
91 if version_satisfies(&resolved.spec.version, &upstream.constraint)? {
92 return Ok(None);
93 }
94 let new_constraint =
95 latest_spec_upstream_constraint(&upstream.constraint, &resolved.spec.version)?;
96 if !version_satisfies(&resolved.spec.version, &new_constraint)? {
97 return Err(PrayError::Resolution(format!(
98 "derived constraint {new_constraint} does not admit latest {} for {}",
99 resolved.spec.version, package.declaration.name
100 )));
101 }
102 Ok(Some(PathUpstreamLatestConstraint {
103 package_name: package.declaration.name.clone(),
104 upstream_name: upstream.name.clone(),
105 current_constraint: upstream.constraint.clone(),
106 latest_version: resolved.spec.version,
107 new_constraint,
108 package_root: package.root.clone(),
109 }))
110}
111
112fn latest_spec_upstream_constraint(current: &str, latest_version: &str) -> PrayResult<String> {
113 let derived = latest_constraint_for_package(current, latest_version)?;
114 if derived.starts_with('=') {
115 return Ok(format!("= {latest_version}"));
116 }
117 Ok(derived)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::latest_spec_upstream_constraint;
123
124 #[test]
125 fn exact_pin_uses_spaced_equals() {
126 assert_eq!(
127 latest_spec_upstream_constraint("= 1.4.3", "1.4.4").expect("constraint"),
128 "= 1.4.4"
129 );
130 }
131
132 #[test]
133 fn pessimistic_pin_follows_latest_family() {
134 assert_eq!(
135 latest_spec_upstream_constraint("~> 1.4", "2.0.0").expect("constraint"),
136 "~> 2.0"
137 );
138 }
139}