Skip to content

Commit ce66bf6

Browse files
authored
Move determinate prompting to separate module (#1431)
* Move install command into module * Move determinate prompting to separate module, consolidate functionality * fixup: missed cancelled handling
1 parent 82b2d1b commit ce66bf6

File tree

7 files changed

+157
-145
lines changed

7 files changed

+157
-145
lines changed

src/cli/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pub struct NixInstallerCli {
8686
#[async_trait::async_trait]
8787
impl CommandExecute for NixInstallerCli {
8888
#[tracing::instrument(level = "trace", skip_all)]
89-
async fn execute<T>(self, mut feedback: T) -> eyre::Result<ExitCode>
89+
async fn execute<T>(self, feedback: T) -> eyre::Result<ExitCode>
9090
where
9191
T: crate::feedback::Feedback,
9292
{
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::io::IsTerminal as _;
2+
3+
use owo_colors::OwoColorize as _;
4+
5+
use crate::cli::interaction::PromptChoice;
6+
use crate::feedback::Feedback;
7+
use crate::planner::BuiltinPlanner;
8+
9+
const PRE_PKG_SUGGEST: &str = "For a more robust Nix installation, use the Determinate package for macOS: https://dtr.mn/determinate-nix";
10+
11+
const DETERMINATE_MSG_EXPLAINER: &str = "\
12+
Determinate Nix is Determinate Systems' validated and secure downstream Nix distribution for enterprises. \
13+
It comes bundled with Determinate Nixd, a helpful daemon that automates some otherwise-unpleasant aspects of using Nix, such as garbage collection, and enables you to easily authenticate with FlakeHub.
14+
15+
For more details: https://dtr.mn/determinate-nix\
16+
";
17+
18+
pub(crate) async fn inform_macos_about_pkg<T: Feedback>(feedback: &T) {
19+
if matches!(
20+
target_lexicon::OperatingSystem::host(),
21+
target_lexicon::OperatingSystem::MacOSX { .. } | target_lexicon::OperatingSystem::Darwin
22+
) {
23+
let msg = feedback
24+
.get_feature_ptr_payload::<String>("dni-det-msg-start-pkg-ptr")
25+
.await
26+
.unwrap_or(PRE_PKG_SUGGEST.into());
27+
tracing::info!("{}", msg.trim());
28+
}
29+
}
30+
31+
pub(crate) async fn prompt_for_determinate<T: Feedback>(
32+
feedback: &mut T,
33+
planner: &mut BuiltinPlanner,
34+
no_confirm: bool,
35+
) -> eyre::Result<Option<String>> {
36+
let planner_settings = planner.common_settings_mut();
37+
38+
if !planner_settings.determinate_nix && std::io::stdin().is_terminal() && !no_confirm {
39+
let base_prompt = feedback
40+
.get_feature_ptr_payload::<String>("dni-det-msg-interactive-prompt-ptr")
41+
.await
42+
.unwrap_or("Install Determinate Nix?".into());
43+
44+
let mut explanation: Option<String> = None;
45+
46+
loop {
47+
let prompt = if let Some(ref explanation) = explanation {
48+
&format!("\n{}\n{}", base_prompt.trim().green(), explanation.trim())
49+
} else {
50+
&format!("\n{}", base_prompt.trim().green())
51+
};
52+
53+
let response = crate::cli::interaction::prompt(
54+
prompt.to_string(),
55+
PromptChoice::Yes,
56+
explanation.is_some(),
57+
)
58+
.await?;
59+
60+
match response {
61+
PromptChoice::Explain => {
62+
explanation = Some(
63+
feedback
64+
.get_feature_ptr_payload::<String>(
65+
"dni-det-msg-interactive-explanation-ptr",
66+
)
67+
.await
68+
.unwrap_or(DETERMINATE_MSG_EXPLAINER.into()),
69+
);
70+
},
71+
PromptChoice::Yes => {
72+
planner_settings.determinate_nix = true;
73+
break;
74+
},
75+
PromptChoice::No => {
76+
break;
77+
},
78+
}
79+
}
80+
}
81+
82+
let post_install_message_feature = match (
83+
planner_settings.determinate_nix,
84+
std::io::stdin().is_terminal() && !no_confirm,
85+
) {
86+
(true, true) => Some("dni-post-det-int-ptr"),
87+
(true, false) => None,
88+
(false, true) => Some("dni-post-ups-int-ptr"),
89+
(false, false) => Some("dni-post-ups-scr-ptr"),
90+
};
91+
92+
let msg = if let Some(feat) = post_install_message_feature {
93+
feedback.get_feature_ptr_payload::<String>(feat).await
94+
} else {
95+
None
96+
};
97+
98+
Ok(msg)
99+
}
Lines changed: 52 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
mod determinate;
2+
13
use std::{
2-
io::IsTerminal as _,
34
os::unix::prelude::PermissionsExt,
45
path::{Path, PathBuf},
56
process::ExitCode,
@@ -32,15 +33,6 @@ const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &str = "\
3233
If you are using `nix-installer` in an automated curing process and seeing this message, consider pinning the version you use via https://github.com/DeterminateSystems/nix-installer#accessing-other-versions.\
3334
";
3435

35-
const PRE_PKG_SUGGEST: &str = "For a more robust Nix installation, use the Determinate package for macOS: https://dtr.mn/determinate-nix";
36-
37-
const DETERMINATE_MSG_EXPLAINER: &str = "\
38-
Determinate Nix is Determinate Systems' validated and secure downstream Nix distribution for enterprises. \
39-
It comes bundled with Determinate Nixd, a helpful daemon that automates some otherwise-unpleasant aspects of using Nix, such as garbage collection, and enables you to easily authenticate with FlakeHub.
40-
41-
For more details: https://dtr.mn/determinate-nix\
42-
";
43-
4436
/**
4537
Install Nix using a planner
4638
@@ -82,14 +74,6 @@ pub struct Install {
8274
pub planner: Option<BuiltinPlanner>,
8375
}
8476

85-
#[derive(Eq, PartialEq)]
86-
enum InstallCase {
87-
DeterminateInteractive,
88-
DeterminateScripted,
89-
UpstreamInteractive,
90-
UpstreamScripted,
91-
}
92-
9377
#[async_trait::async_trait]
9478
impl CommandExecute for Install {
9579
#[tracing::instrument(level = "trace", skip_all)]
@@ -131,19 +115,9 @@ impl CommandExecute for Install {
131115
return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense"));
132116
}
133117

134-
if matches!(
135-
target_lexicon::OperatingSystem::host(),
136-
target_lexicon::OperatingSystem::MacOSX { .. }
137-
| target_lexicon::OperatingSystem::Darwin
138-
) {
139-
let msg = feedback
140-
.get_feature_ptr_payload::<String>("dni-det-msg-start-pkg-ptr")
141-
.await
142-
.unwrap_or(PRE_PKG_SUGGEST.into());
143-
tracing::info!("{}", msg.trim());
144-
}
118+
determinate::inform_macos_about_pkg(&feedback).await;
145119

146-
let mut case: Option<InstallCase> = None;
120+
let mut post_install_message = None;
147121

148122
let mut install_plan = if let Some(plan_path) = plan {
149123
let install_plan_string = tokio::fs::read_to_string(&plan_path)
@@ -158,112 +132,52 @@ impl CommandExecute for Install {
158132
.map_err(|e| eyre::eyre!(e))?,
159133
};
160134

161-
match existing_receipt {
162-
Some(existing_receipt) => {
163-
if let Err(e) = existing_receipt.check_compatible() {
164-
eprintln!(
165-
"{}",
166-
format!("\
167-
{e}\n\
168-
\n\
169-
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
170-
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
171-
").red()
135+
if let Some(existing_receipt) = existing_receipt {
136+
if let Err(e) = existing_receipt.check_compatible() {
137+
eprintln!(
138+
"{}",
139+
format!("\
140+
{e}\n\
141+
\n\
142+
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
143+
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
144+
").red()
172145
);
173-
return Ok(ExitCode::FAILURE);
174-
}
146+
return Ok(ExitCode::FAILURE);
147+
}
175148

176-
if existing_receipt.planner.typetag_name() != planner.typetag_name() {
177-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
178-
return Ok(ExitCode::FAILURE);
179-
}
149+
if existing_receipt.planner.typetag_name() != planner.typetag_name() {
150+
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
151+
return Ok(ExitCode::FAILURE);
152+
}
180153

181-
if existing_receipt.planner.settings().map_err(|e| eyre!(e))?
182-
!= planner.settings().map_err(|e| eyre!(e))?
183-
{
184-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
185-
return Ok(ExitCode::FAILURE);
186-
}
154+
if existing_receipt.planner.settings().map_err(|e| eyre!(e))?
155+
!= planner.settings().map_err(|e| eyre!(e))?
156+
{
157+
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install with `{uninstall_command}`").red());
158+
return Ok(ExitCode::FAILURE);
159+
}
187160

188-
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed. Try uninstalling (`{uninstall_command}`) and reinstalling if Nix isn't working").red());
189-
return Ok(ExitCode::SUCCESS);
190-
},
191-
None => {
192-
let planner_settings = planner.common_settings_mut();
193-
194-
if !planner_settings.determinate_nix
195-
&& std::io::stdin().is_terminal()
196-
&& !no_confirm
197-
{
198-
let base_prompt = feedback
199-
.get_feature_ptr_payload::<String>("dni-det-msg-interactive-prompt-ptr")
200-
.await
201-
.unwrap_or("Install Determinate Nix?".into());
202-
203-
let mut explanation: Option<String> = None;
204-
205-
loop {
206-
let prompt = if let Some(ref explanation) = explanation {
207-
&format!("\n{}\n{}", base_prompt.trim().green(), explanation.trim())
208-
} else {
209-
&format!("\n{}", base_prompt.trim().green())
210-
};
211-
212-
let response = interaction::prompt(
213-
prompt.to_string(),
214-
PromptChoice::Yes,
215-
explanation.is_some(),
216-
)
217-
.await?;
218-
219-
match response {
220-
PromptChoice::Explain => {
221-
explanation = Some(
222-
feedback
223-
.get_feature_ptr_payload::<String>(
224-
"dni-det-msg-interactive-explanation-ptr",
225-
)
226-
.await
227-
.unwrap_or(DETERMINATE_MSG_EXPLAINER.into()),
228-
);
229-
},
230-
PromptChoice::Yes => {
231-
planner_settings.determinate_nix = true;
232-
break;
233-
},
234-
PromptChoice::No => {
235-
break;
236-
},
237-
}
238-
}
239-
}
161+
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed. Try uninstalling (`{uninstall_command}`) and reinstalling if Nix isn't working").red());
162+
return Ok(ExitCode::SUCCESS);
163+
}
240164

241-
case = Some(
242-
match (
243-
planner_settings.determinate_nix,
244-
std::io::stdin().is_terminal() && !no_confirm,
245-
) {
246-
(true, true) => InstallCase::DeterminateInteractive,
247-
(true, false) => InstallCase::DeterminateScripted,
248-
(false, true) => InstallCase::UpstreamInteractive,
249-
(false, false) => InstallCase::UpstreamScripted,
250-
},
251-
);
165+
post_install_message =
166+
determinate::prompt_for_determinate(&mut feedback, &mut planner, no_confirm)
167+
.await?;
252168

253-
feedback.set_planner(&planner).await?;
169+
feedback.set_planner(&planner).await?;
254170

255-
let res = planner.plan().await;
256-
match res {
257-
Ok(plan) => plan,
258-
Err(err) => {
259-
feedback.planning_failed(&err).await;
260-
if let Some(expected) = err.expected() {
261-
eprintln!("{}", expected.red());
262-
return Ok(ExitCode::FAILURE);
263-
}
264-
return Err(err)?;
265-
},
171+
let res = planner.plan().await;
172+
match res {
173+
Ok(plan) => plan,
174+
Err(err) => {
175+
feedback.planning_failed(&err).await;
176+
if let Some(expected) = err.expected() {
177+
eprintln!("{}", expected.red());
178+
return Ok(ExitCode::FAILURE);
266179
}
180+
return Err(err)?;
267181
},
268182
}
269183
};
@@ -366,6 +280,10 @@ impl CommandExecute for Install {
366280
eprintln!("{}", expected.red());
367281
return Ok(ExitCode::FAILURE);
368282
}
283+
if matches!(err, NixInstallerError::Cancelled) {
284+
eprintln!("{}", err.red());
285+
return Ok(ExitCode::FAILURE);
286+
}
369287
return Err(err)?;
370288
},
371289
_ => {
@@ -383,6 +301,10 @@ impl CommandExecute for Install {
383301
eprintln!("{}", expected.red());
384302
return Ok(ExitCode::FAILURE);
385303
}
304+
if matches!(err, NixInstallerError::Cancelled) {
305+
eprintln!("{}", err.red());
306+
return Ok(ExitCode::FAILURE);
307+
}
386308

387309
let error = eyre!(err).wrap_err("Install failure");
388310
return Err(error)?;
@@ -423,17 +345,8 @@ impl CommandExecute for Install {
423345
},
424346
);
425347

426-
let feat = match case {
427-
Some(InstallCase::DeterminateInteractive) => Some("dni-post-det-int-ptr"),
428-
Some(InstallCase::UpstreamInteractive) => Some("dni-post-ups-int-ptr"),
429-
Some(InstallCase::UpstreamScripted) => Some("dni-post-ups-scr-ptr"),
430-
_ => None,
431-
};
432-
if let Some(feat) = feat {
433-
let msg = feedback.get_feature_ptr_payload::<String>(feat).await;
434-
if let Some(msg) = msg {
435-
println!("{}\n", msg.trim());
436-
}
348+
if let Some(msg) = post_install_message {
349+
println!("{}\n", msg.trim());
437350
}
438351
},
439352
}

src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ impl ErrorDiagnostic for DiagnosticError {
198198

199199
impl crate::feedback::Feedback for DiagnosticData {
200200
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send + std::fmt::Debug>(
201-
&mut self,
201+
&self,
202202
name: impl Into<String> + std::fmt::Debug + Send,
203203
) -> Option<T> {
204204
self.ids_client.get_feature_ptr_payload::<T>(name).await

src/feedback/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub enum Client {
1010

1111
impl Feedback for Client {
1212
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send + std::fmt::Debug>(
13-
&mut self,
13+
&self,
1414
name: impl Into<String> + core::marker::Send + std::fmt::Debug,
1515
) -> Option<T> {
1616
match self {

src/feedback/devnull.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub fn dev_null() -> (Client, Worker) {
99
pub struct DevNull;
1010
impl Feedback for DevNull {
1111
async fn get_feature_ptr_payload<T: serde::de::DeserializeOwned + Send>(
12-
&mut self,
12+
&self,
1313
_name: impl Into<String> + core::marker::Send,
1414
) -> Option<T> {
1515
None

0 commit comments

Comments
 (0)