Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul nix flake update and nix flake lock UX #8817

Merged
merged 4 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@
- `nix-shell` shebang lines now support single-quoted arguments.

- `builtins.fetchTree` is now marked as stable.


- The interface for creating and updating lock files has been overhauled:

- [`nix flake lock`](@docroot@/command-ref/new-cli/nix3-flake-lock.md) only creates lock files and adds missing inputs now.
It will *never* update existing inputs.

- [`nix flake update`](@docroot@/command-ref/new-cli/nix3-flake-update.md) does the same, but *will* update inputs.
- Passing no arguments will update all inputs of the current flake, just like it already did.
- Passing input names as arguments will ensure only those are updated. This replaces the functionality of `nix flake lock --update-input`
- To operate on a flake outside the current directory, you must now pass `--flake path/to/flake`.

- The flake-specific flags `--recreate-lock-file` and `--update-input` have been removed from all commands operating on installables.
They are superceded by `nix flake update`.
6 changes: 6 additions & 0 deletions src/libcmd/command.hh
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@ struct MixEnvironment : virtual Args {
void setEnviron();
};

void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix);

void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix);

void completeFlakeRefWithFragment(
Expand Down
22 changes: 1 addition & 21 deletions src/libcmd/installables.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

namespace nix {

static void completeFlakeInputPath(
void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs,
Expand All @@ -46,13 +46,6 @@ MixFlakeOptions::MixFlakeOptions()
{
auto category = "Common flake-related options";

addFlag({
.longName = "recreate-lock-file",
.description = "Recreate the flake's lock file from scratch.",
.category = category,
.handler = {&lockFlags.recreateLockFile, true}
});

addFlag({
.longName = "no-update-lock-file",
.description = "Do not allow any updates to the flake's lock file.",
Expand Down Expand Up @@ -85,19 +78,6 @@ MixFlakeOptions::MixFlakeOptions()
.handler = {&lockFlags.commitLockFile, true}
});

addFlag({
.longName = "update-input",
.description = "Update a specific flake input (ignoring its previous entry in the lock file).",
.category = category,
.labels = {"input-path"},
.handler = {[&](std::string s) {
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
}}
});

addFlag({
.longName = "override-input",
.description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
Expand Down
15 changes: 7 additions & 8 deletions src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,8 @@ LockedFlake lockFlake(

assert(input.ref);

/* Do we have an entry in the existing lock file? And we
don't have a --update-input flag for this input? */
/* Do we have an entry in the existing lock file?
And the input is not in updateInputs? */
std::shared_ptr<LockedNode> oldLock;

updatesUsed.insert(inputPath);
Expand All @@ -472,9 +472,8 @@ LockedFlake lockFlake(

node->inputs.insert_or_assign(id, childNode);

/* If we have an --update-input flag for an input
of this input, then we must fetch the flake to
update it. */
/* If we have this input in updateInputs, then we
must fetch the flake to update it. */
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);

auto mustRefetch =
Expand Down Expand Up @@ -616,7 +615,7 @@ LockedFlake lockFlake(

for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i))
warn("the flag '--update-input %s' does not match any input", printInputPath(i));
warn("'%s' does not match any input of this flake", printInputPath(i));

/* Check 'follows' inputs. */
newLockFile.check();
Expand Down Expand Up @@ -651,14 +650,14 @@ LockedFlake lockFlake(

bool lockFileExists = pathExists(outputLockFilePath);

auto s = chomp(diff);
if (lockFileExists) {
auto s = chomp(diff);
if (s.empty())
warn("updating lock file '%s'", outputLockFilePath);
else
warn("updating lock file '%s':\n%s", outputLockFilePath, s);
} else
warn("creating lock file '%s'", outputLockFilePath);
warn("creating lock file '%s': \n%s", outputLockFilePath, s);

std::optional<std::string> commitMessage = std::nullopt;

Expand Down
13 changes: 12 additions & 1 deletion src/libutil/args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,18 @@ bool Args::processArgs(const Strings & args, bool finish)
}
if (!anyCompleted)
exp.handler.fun(ss);
expectedArgs.pop_front();

/* Move the list element to the processedArgs. This is almost the same as
`processedArgs.push_back(expectedArgs.front()); expectedArgs.pop_front()`,
except that it will only adjust the next and prev pointers of the list
elements, meaning the actual contents don't move in memory. This is
critical to prevent invalidating internal pointers! */
processedArgs.splice(
processedArgs.end(),
expectedArgs,
expectedArgs.begin(),
++expectedArgs.begin());
iFreilicht marked this conversation as resolved.
Show resolved Hide resolved

res = true;
}

Expand Down
14 changes: 13 additions & 1 deletion src/libutil/args.hh
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,25 @@ protected:
/**
* Queue of expected positional argument forms.
*
* Positional arugment descriptions are inserted on the back.
* Positional argument descriptions are inserted on the back.
*
* As positional arguments are passed, these are popped from the
* front, until there are hopefully none left as all args that were
* expected in fact were passed.
*/
std::list<ExpectedArg> expectedArgs;
/**
* List of processed positional argument forms.
*
* All items removed from `expectedArgs` are added here. After all
* arguments were processed, this list should be exactly the same as
* `expectedArgs` was before.
*
* This list is used to extend the lifetime of the argument forms.
* If this is not done, some closures that reference the command
* itself will segfault.
*/
std::list<ExpectedArg> processedArgs;

/**
* Process some positional arugments
Expand Down
48 changes: 25 additions & 23 deletions src/nix/flake-lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,39 @@ R""(

# Examples

* Update the `nixpkgs` and `nix` inputs of the flake in the current
directory:
* Create the lock file for the flake in the current directory:

```console
# nix flake lock --update-input nixpkgs --update-input nix
* Updated 'nix': 'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' -> 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c'
* Updated 'nixpkgs': 'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' -> 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293'
# nix flake lock
warning: creating lock file '/home/myself/repos/testflake/flake.lock':
• Added input 'nix':
'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' (2023-06-28)
• Added input 'nixpkgs':
'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' (2023-06-30)
```

# Description

This command updates the lock file of a flake (`flake.lock`) so that
it contains a lock for every flake input specified in
`flake.nix`. Existing lock file entries are not updated unless
required by a flag such as `--update-input`.
* Add missing inputs to the lock file for a flake in a different directory:

Note that every command that operates on a flake will also update the
lock file if needed, and supports the same flags. Therefore,
```console
# nix flake lock ~/repos/another
warning: updating lock file '/home/myself/repos/another/flake.lock':
• Added input 'nixpkgs':
'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' (2023-06-30)
```

```console
# nix flake lock --update-input nixpkgs
# nix build
```
> **Note**
>
> When trying to refer to a flake in a subdirectory, write `./another`
> instead of `another`.
> Otherwise Nix will try to look up the flake in the registry.

is equivalent to:
# Description

```console
# nix build --update-input nixpkgs
```
This command adds inputs to the lock file of a flake (`flake.lock`)
so that it contains a lock for every flake input specified in
`flake.nix`. Existing lock file entries are not updated.

Thus, this command is only useful if you want to update the lock file
separately from any other action such as building.
If you want to update existing lock entries, use
[`nix flake update`](@docroot@/command-ref/new-cli/nix3-flake-update.md)

)""
64 changes: 44 additions & 20 deletions src/nix/flake-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,57 @@ R""(

# Examples

* Recreate the lock file (i.e. update all inputs) and commit the new
lock file:
* Update all inputs (i.e. recreate the lock file from scratch):

```console
# nix flake update --commit-lock-file
* Updated 'nix': 'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' -> 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c'
* Updated 'nixpkgs': 'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' -> 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293'
warning: committed new revision '158bcbd9d6cc08ab859c0810186c1beebc982aad'
# nix flake update
warning: updating lock file '/home/myself/repos/testflake/flake.lock':
• Updated input 'nix':
'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' (2023-06-28)
→ 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c' (2023-07-11)
• Updated input 'nixpkgs':
'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' (2023-06-30)
→ 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293' (2023-07-05)
```

# Description
* Update only a single input:

```console
# nix flake update nixpkgs
warning: updating lock file '/home/myself/repos/testflake/flake.lock':
• Updated input 'nixpkgs':
'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' (2023-06-30)
→ 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293' (2023-07-05)
```

* Update only a single input of a flake in a different directory:

This command recreates the lock file of a flake (`flake.lock`), thus
updating the lock for every unlocked input (like `nixpkgs`) to its
current version. This is equivalent to passing `--recreate-lock-file`
to any command that operates on a flake. That is,
```console
# nix flake update nixpkgs --flake ~/repos/another
warning: updating lock file '/home/myself/repos/another/flake.lock':
• Updated input 'nixpkgs':
'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' (2023-06-30)
→ 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293' (2023-07-05)
```

> **Note**
>
> When trying to refer to a flake in a subdirectory, write `./another`
> instead of `another`.
> Otherwise Nix will try to look up the flake in the registry.

# Description

```console
# nix flake update
# nix build
```
This command updates the inputs in a lock file (`flake.lock`).
**By default, all inputs are updated**. If the lock file doesn't exist
yet, it will be created. If inputs are not in the lock file yet, they will be added.

is equivalent to:
Unlike other `nix flake` commands, `nix flake update` takes a list of names of inputs
to update as its positional arguments and operates on the flake in the current directory.
You can pass a different flake-url with `--flake` to override that default.

```console
# nix build --recreate-lock-file
```
The related command [`nix flake lock`](@docroot@/command-ref/new-cli/nix3-flake-lock.md)
also creates lock files and adds missing inputs, but is safer as it
will never update inputs already in the lock file.

)""
33 changes: 30 additions & 3 deletions src/nix/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ using namespace nix;
using namespace nix::flake;
using json = nlohmann::json;

struct CmdFlakeUpdate;
class FlakeCommand : virtual Args, public MixFlakeOptions
{
protected:
std::string flakeUrl = ".";

public:
Expand Down Expand Up @@ -63,16 +65,40 @@ class FlakeCommand : virtual Args, public MixFlakeOptions

struct CmdFlakeUpdate : FlakeCommand
{
public:

std::string description() override
{
return "update flake lock file";
}

CmdFlakeUpdate()
{
expectedArgs.clear();
addFlag({
.longName="flake",
.description="The flake to operate on. Default is the current directory.",
.labels={"flake-url"},
.handler={&flakeUrl},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeRef(completions, getStore(), prefix);
}}
});
expectArgs({
.label="inputs",
.optional=true,
.handler={[&](std::string inputToUpdate){
auto inputPath = flake::parseInputPath(inputToUpdate);
if (lockFlags.inputUpdates.contains(inputPath))
warn("Input '%s' was specified multiple times. You may have done this by accident.");
lockFlags.inputUpdates.insert(inputPath);
}},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
}}
});

/* Remove flags that don't make sense. */
removeFlag("recreate-lock-file");
removeFlag("update-input");
removeFlag("no-update-lock-file");
removeFlag("no-write-lock-file");
}
Expand All @@ -87,8 +113,9 @@ struct CmdFlakeUpdate : FlakeCommand
void run(nix::ref<nix::Store> store) override
{
settings.tarballTtl = 0;
auto updateAll = lockFlags.inputUpdates.empty();

lockFlags.recreateLockFile = true;
lockFlags.recreateLockFile = updateAll;
lockFlags.writeLockFile = true;
lockFlags.applyNixConfig = true;

Expand Down
11 changes: 7 additions & 4 deletions tests/functional/completions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,18 @@ EOF
# Input override completion
[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --override-input '')" == $'normal\na\t' ]]
[[ "$(NIX_GET_COMPLETIONS=5 nix flake show ./foo --override-input '')" == $'normal\na\t' ]]
cd ./foo
[[ "$(NIX_GET_COMPLETIONS=3 nix flake update '')" == $'normal\na\t' ]]
cd ..
[[ "$(NIX_GET_COMPLETIONS=5 nix flake update --flake './foo' '')" == $'normal\na\t' ]]
## With multiple input flakes
[[ "$(NIX_GET_COMPLETIONS=5 nix build ./foo ./bar --override-input '')" == $'normal\na\t\nb\t' ]]
## With tilde expansion
[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=4 nix build '~/foo' --override-input '')" == $'normal\na\t' ]]
[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=5 nix flake show '~/foo' --update-input '')" == $'normal\na\t' ]]
[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=4 nix run '~/foo' --update-input '')" == $'normal\na\t' ]]
[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=5 nix flake update --flake '~/foo' '')" == $'normal\na\t' ]]
## Out of order
[[ "$(NIX_GET_COMPLETIONS=3 nix build --update-input '' ./foo)" == $'normal\na\t' ]]
[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --update-input '' ./bar)" == $'normal\na\t\nb\t' ]]
[[ "$(NIX_GET_COMPLETIONS=3 nix build --override-input '' '' ./foo)" == $'normal\na\t' ]]
[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --override-input '' '' ./bar)" == $'normal\na\t\nb\t' ]]

# Cli flag completion
NIX_GET_COMPLETIONS=2 nix build --log-form | grep -- "--log-format"
Expand Down