Skip to content

Commit 5829cec

Browse files
authored
Merge pull request #320 from stuebinm/nonflake-build-flag
`--file` argument analogous to `nix build` & friends
2 parents aa07eb0 + e1a3bdd commit 5829cec

File tree

4 files changed

+102
-57
lines changed

4 files changed

+102
-57
lines changed

nix/tests/default.nix

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ in {
134134
isLocal = false;
135135
deployArgs = "-s .#server --remote-build -- --offline";
136136
};
137+
non-flake-remote-build = mkTest {
138+
name = "non-flake-remote-build";
139+
isLocal = false;
140+
flakes = false;
141+
deployArgs = "-s .#server --remote-build";
142+
};
137143
# Deployment with overridden options
138144
options-overriding = mkTest {
139145
name = "options-overriding";
@@ -158,8 +164,13 @@ in {
158164
};
159165
# Deployment using a non-flake nix
160166
non-flake-build = mkTest {
161-
name = "local-build";
167+
name = "non-flake-build";
162168
flakes = false;
163169
deployArgs = "-s .#server";
164170
};
171+
non-flake-with-flakes = mkTest {
172+
name = "non-flake-with-flakes";
173+
flakes = true;
174+
deployArgs = "--file . --targets server";
175+
};
165176
}

src/cli.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ pub struct Opts {
3030
/// A list of flakes to deploy alternatively
3131
#[clap(long, group = "deploy")]
3232
targets: Option<Vec<String>>,
33+
/// Treat targets as files instead of flakes
34+
#[clap(short, long)]
35+
file: Option<String>,
3336
/// Check signatures when using `nix copy`
3437
#[clap(short, long)]
3538
checksigs: bool,
@@ -677,10 +680,19 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
677680
.targets
678681
.unwrap_or_else(|| vec![opts.clone().target.unwrap_or_else(|| ".".to_string())]);
679682

680-
let deploy_flakes: Vec<DeployFlake> = deploys
683+
let deploy_flakes: Vec<DeployFlake> =
684+
if let Some(file) = &opts.file {
685+
deploys
686+
.iter()
687+
.map(|f| deploy::parse_file(file.as_str(), f.as_str()))
688+
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
689+
}
690+
else {
691+
deploys
681692
.iter()
682693
.map(|f| deploy::parse_flake(f.as_str()))
683-
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?;
694+
.collect::<Result<Vec<DeployFlake>, ParseFlakeError>>()?
695+
};
684696

685697
let cmd_overrides = deploy::CmdOverrides {
686698
ssh_user: opts.ssh_user,
@@ -700,22 +712,29 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> {
700712
};
701713

702714
let supports_flakes = test_flake_support().await.map_err(RunError::FlakeTest)?;
715+
let do_not_want_flakes = opts.file.is_some();
703716

704717
if !supports_flakes {
705718
warn!("A Nix version without flakes support was detected, support for this is work in progress");
706719
}
707720

721+
if do_not_want_flakes {
722+
warn!("The --file option for deployments without flakes is experimental");
723+
}
724+
725+
let using_flakes = supports_flakes && !do_not_want_flakes;
726+
708727
if !opts.skip_checks {
709728
for deploy_flake in &deploy_flakes {
710-
check_deployment(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?;
729+
check_deployment(using_flakes, deploy_flake.repo, &opts.extra_build_args).await?;
711730
}
712731
}
713732
let result_path = opts.result_path.as_deref();
714-
let data = get_deployment_data(supports_flakes, &deploy_flakes, &opts.extra_build_args).await?;
733+
let data = get_deployment_data(using_flakes, &deploy_flakes, &opts.extra_build_args).await?;
715734
run_deploy(
716735
deploy_flakes,
717736
data,
718-
supports_flakes,
737+
using_flakes,
719738
opts.checksigs,
720739
opts.interactive,
721740
&cmd_overrides,

src/lib.rs

Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,54 @@ pub enum ParseFlakeError {
184184
#[error("Unrecognized node or token encountered")]
185185
Unrecognized,
186186
}
187+
188+
fn parse_fragment(fragment: &str) -> Result<(Option<String>, Option<String>), ParseFlakeError> {
189+
let mut node: Option<String> = None;
190+
let mut profile: Option<String> = None;
191+
192+
let ast = rnix::parse(fragment);
193+
194+
let first_child = match ast.root().node().first_child() {
195+
Some(x) => x,
196+
None => return Ok((None, None))
197+
};
198+
199+
let mut node_over = false;
200+
201+
for entry in first_child.children_with_tokens() {
202+
let x: Option<String> = match (entry.kind(), node_over) {
203+
(TOKEN_DOT, false) => {
204+
node_over = true;
205+
None
206+
}
207+
(TOKEN_DOT, true) => {
208+
return Err(ParseFlakeError::PathTooLong);
209+
}
210+
(NODE_IDENT, _) => Some(entry.into_node().unwrap().text().to_string()),
211+
(TOKEN_IDENT, _) => Some(entry.into_token().unwrap().text().to_string()),
212+
(NODE_STRING, _) => {
213+
let c = entry
214+
.into_node()
215+
.unwrap()
216+
.children_with_tokens()
217+
.nth(1)
218+
.unwrap();
219+
220+
Some(c.into_token().unwrap().text().to_string())
221+
}
222+
_ => return Err(ParseFlakeError::Unrecognized),
223+
};
224+
225+
if !node_over {
226+
node = x;
227+
} else {
228+
profile = x;
229+
}
230+
}
231+
232+
Ok((node, profile))
233+
}
234+
187235
pub fn parse_flake(flake: &str) -> Result<DeployFlake, ParseFlakeError> {
188236
let flake_fragment_start = flake.find('#');
189237
let (repo, maybe_fragment) = match flake_fragment_start {
@@ -195,51 +243,7 @@ pub fn parse_flake(flake: &str) -> Result<DeployFlake, ParseFlakeError> {
195243
let mut profile: Option<String> = None;
196244

197245
if let Some(fragment) = maybe_fragment {
198-
let ast = rnix::parse(fragment);
199-
200-
let first_child = match ast.root().node().first_child() {
201-
Some(x) => x,
202-
None => {
203-
return Ok(DeployFlake {
204-
repo,
205-
node: None,
206-
profile: None,
207-
})
208-
}
209-
};
210-
211-
let mut node_over = false;
212-
213-
for entry in first_child.children_with_tokens() {
214-
let x: Option<String> = match (entry.kind(), node_over) {
215-
(TOKEN_DOT, false) => {
216-
node_over = true;
217-
None
218-
}
219-
(TOKEN_DOT, true) => {
220-
return Err(ParseFlakeError::PathTooLong);
221-
}
222-
(NODE_IDENT, _) => Some(entry.into_node().unwrap().text().to_string()),
223-
(TOKEN_IDENT, _) => Some(entry.into_token().unwrap().text().to_string()),
224-
(NODE_STRING, _) => {
225-
let c = entry
226-
.into_node()
227-
.unwrap()
228-
.children_with_tokens()
229-
.nth(1)
230-
.unwrap();
231-
232-
Some(c.into_token().unwrap().text().to_string())
233-
}
234-
_ => return Err(ParseFlakeError::Unrecognized),
235-
};
236-
237-
if !node_over {
238-
node = x;
239-
} else {
240-
profile = x;
241-
}
242-
}
246+
(node, profile) = parse_fragment(fragment)?;
243247
}
244248

245249
Ok(DeployFlake {
@@ -315,6 +319,16 @@ fn test_parse_flake() {
315319
);
316320
}
317321

322+
pub fn parse_file<'a>(file: &'a str, attribute: &'a str) -> Result<DeployFlake<'a>, ParseFlakeError> {
323+
let (node, profile) = parse_fragment(attribute)?;
324+
325+
Ok(DeployFlake {
326+
repo: &file,
327+
node,
328+
profile,
329+
})
330+
}
331+
318332
#[derive(Debug, Clone)]
319333
pub struct DeployData<'a> {
320334
pub node_name: &'a str,

src/push.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// SPDX-License-Identifier: MPL-2.0
44

5-
use log::{debug, info};
5+
use log::{debug, info, warn};
66
use std::collections::HashMap;
77
use std::path::Path;
88
use std::process::Stdio;
@@ -41,8 +41,6 @@ pub enum PushProfileError {
4141
Copy(std::io::Error),
4242
#[error("Nix copy command resulted in a bad exit code: {0:?}")]
4343
CopyExit(Option<i32>),
44-
#[error("The remote building option is not supported when using legacy nix")]
45-
RemoteBuildWithLegacyNix,
4644

4745
#[error("Failed to run Nix path-info command: {0}")]
4846
PathInfo(std::io::Error),
@@ -169,7 +167,9 @@ pub async fn build_profile_remotely(data: &PushProfileData<'_>, derivation_name:
169167

170168

171169
// copy the derivation to remote host so it can be built there
172-
let copy_command_status = Command::new("nix").arg("copy")
170+
let copy_command_status = Command::new("nix")
171+
.arg("--experimental-features").arg("nix-command")
172+
.arg("copy")
173173
.arg("-s") // fetch dependencies from substitures, not localhost
174174
.arg("--to").arg(&store_address)
175175
.arg("--derivation").arg(derivation_name)
@@ -186,6 +186,7 @@ pub async fn build_profile_remotely(data: &PushProfileData<'_>, derivation_name:
186186

187187
let mut build_command = Command::new("nix");
188188
build_command
189+
.arg("--experimental-features").arg("nix-command")
189190
.arg("build").arg(derivation_name)
190191
.arg("--eval-store").arg("auto")
191192
.arg("--store").arg(&store_address)
@@ -244,7 +245,7 @@ pub async fn build_profile(data: PushProfileData<'_>) -> Result<(), PushProfileE
244245
.next()
245246
.ok_or(PushProfileError::ShowDerivationEmpty)?;
246247

247-
let new_deriver = &if data.supports_flakes {
248+
let new_deriver = &if data.supports_flakes || data.deploy_data.merged_settings.remote_build.unwrap_or(false) {
248249
// Since nix 2.15.0 'nix build <path>.drv' will build only the .drv file itself, not the
249250
// derivation outputs, '^out' is used to refer to outputs explicitly
250251
deriver.to_owned().to_string() + "^out"
@@ -276,7 +277,7 @@ pub async fn build_profile(data: PushProfileData<'_>) -> Result<(), PushProfileE
276277
};
277278
if data.deploy_data.merged_settings.remote_build.unwrap_or(false) {
278279
if !data.supports_flakes {
279-
return Err(PushProfileError::RemoteBuildWithLegacyNix)
280+
warn!("remote builds using non-flake nix are experimental");
280281
}
281282

282283
build_profile_remotely(&data, &deriver).await?;

0 commit comments

Comments
 (0)