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

feat: support managing submodules via inputs.self #7862

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/libexpr/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
auto sUrl = state.symbols.create("url");
auto sFlake = state.symbols.create("flake");
auto sFollows = state.symbols.create("follows");
auto sSubmodules = state.symbols.create("submodules");

fetchers::Attrs attrs;
std::optional<std::string> url;
Expand All @@ -117,6 +118,9 @@ static FlakeInput parseFlakeInput(EvalState & state,
} else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sSubmodules) {
expectType(state, nBool, *attr.value, attr.pos);
input.hasSubmodules = attr.value->boolean;
} else if (attr.name == sInputs) {
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
} else if (attr.name == sFollows) {
Expand Down Expand Up @@ -229,8 +233,20 @@ static Flake getFlake(

auto sInputs = state.symbols.create("inputs");

if (auto inputs = vInfo.attrs->get(sInputs))
if (auto inputs = vInfo.attrs->get(sInputs)) {
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath);
if (originalRef.input.attrs.count("submodules") == 0) {
auto self = flake.inputs.find("self");
if (self != flake.inputs.end() && self->second.hasSubmodules) {
Comment on lines +238 to +240
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison is a little ad hoc. It should be described in more general terms. My understanding of the process is:

  1. Nix makes a guess what the self input is
  2. Nix checks that its guess matches the declared self input
  3. Adjust

None of this is specific to git or submodules, so it should be done through virtual methods.

Other than that, I think lazy trees #6530 should make this specific parameter conceptually redundant, and enable it by default, as submodules should be fetched lazily.
However, that's not a reason not to have this mechanism, as we'll need it for at least a git-crypt parameter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general mechanism (but also with submodules as the motivating case) is described and discussed in

auto newRef = originalRef;
newRef.input.attrs["submodules"] = nix::Explicit<bool>{true};
// note: Called twice due to submodule handling in src/libfetcher (55cefd41d6)
warn("Loading submodules for %s because 'inputs.self.submodule' is true at %s",
originalRef.input.to_string(),state.positions[inputs->pos]);
return getFlake(state,newRef,allowLookup,flakeCache,lockRootPath);
}
}
}

auto sOutputs = state.symbols.create("outputs");

Expand Down Expand Up @@ -405,6 +421,7 @@ LockedFlake lockFlake(
necessary (i.e. if they're new or the flakeref changed
from what's in the lock file). */
for (auto & [id, input2] : flakeInputs) {
if (id == "self") continue;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a note, and docs, about the “self” input being special.

auto inputPath(inputPathPrefix);
inputPath.push_back(id);
auto inputPathS = printInputPath(inputPath);
Expand Down
1 change: 1 addition & 0 deletions src/libexpr/flake/flake.hh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ typedef std::map<FlakeId, FlakeInput> FlakeInputs;
struct FlakeInput
{
std::optional<FlakeRef> ref;
bool hasSubmodules = false;
bool isFlake = true; // true = process flake to get outputs, false = (fetched) static source path
std::optional<InputPath> follows;
FlakeInputs overrides;
Expand Down
2 changes: 2 additions & 0 deletions src/nix/flake.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ reference types:

* `ref`: A Git or Mercurial branch or tag name.

* `submodules`: A boolean supporting Git submodules.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `submodules`: A boolean supporting Git submodules.
* `submodules`: Whether to also include Git submodules. Defaults to `false`.


Finally, some attribute are typically not specified by the user, but
can occur in *locked* flake references and are available to Nix code:

Expand Down
118 changes: 118 additions & 0 deletions tests/flakes/submodules.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
source common.sh

set -u

if [[ -z $(type -p git) ]]; then
echo "Git not installed; skipping Git submodule tests"
exit 99
fi

clearStore

rootRepo=$TEST_ROOT/gitSubmodulesRoot
subRepo=$TEST_ROOT/gitSubmodulesSub

rm -rf ${rootRepo} ${subRepo} $TEST_HOME/.cache/nix

# Submodules can't be fetched locally by default, which can cause
# information leakage vulnerabilities, but for these tests our
# submodule is intentionally local and it's all trusted, so we
# disable this restriction. Setting it per repo is not sufficient, as
# the repo-local config does not apply to the commands run from
# outside the repos by Nix.
export XDG_CONFIG_HOME=$TEST_HOME/.config
git config --global protocol.file.allow always

initGitRepo() {
git init $1
git -C $1 config user.email "foobar@example.com"
git -C $1 config user.name "Foobar"
}

addGitContent() {
echo "lorem ipsum" > $1/content
git -C $1 add content
git -C $1 commit -m "Initial commit"
}

initGitRepo $subRepo
addGitContent $subRepo

initGitRepo $rootRepo

git -C $rootRepo submodule init
git -C $rootRepo submodule add $subRepo sub
git -C $rootRepo add sub
git -C $rootRepo commit -m "Add submodule"

rev=$(git -C $rootRepo rev-parse HEAD)

cd $rootRepo

touch flake.nix
git add flake.nix

# Case, CLI parameter
cat <<EOF > flake.nix
{
outputs = {self}: {
sub = self.outPath;
};
}
EOF
[ -e $(nix eval '.?submodules=1#sub' --raw)/sub/content ]

# Case, CLI parameter
cat <<EOF > flake.nix
{
outputs = {self}: {
sub = self.outPath;
};
}
EOF
[ ! -e $(nix eval '.?submodules=0#sub' --raw)/sub/content ]

# Case, flake.nix parameter
cat <<EOF > flake.nix
{
inputs.self.submodules = false;
outputs = {self}: {
sub = self.outPath;
};
}
EOF

[ ! -e $(nix eval .#sub --raw)/sub/content ]

# Case, flake.nix parameter
cat <<EOF > flake.nix
{
inputs.self.submodules = true;
outputs = {self}: {
sub = self.outPath;
};
}
EOF
[ -e $(nix eval .#sub --raw)/sub/content ]

# Case, CLI precedence
cat <<EOF > flake.nix
{
inputs.self.submodules = true;
outputs = {self}: {
sub = self.outPath;
};
}
EOF
[ ! -e $(nix eval '.?submodules=0#sub' --raw)/sub/content ]

# Case, CLI precedence
cat <<EOF > flake.nix
{
inputs.self.submodules = false;
outputs = {self}: {
sub = self.outPath;
};
}
EOF
[ -e $(nix eval '.?submodules=1#sub' --raw)/sub/content ]
1 change: 1 addition & 0 deletions tests/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ nix_tests = \
flakes/unlocked-override.sh \
flakes/absolute-paths.sh \
flakes/build-paths.sh \
flakes/submodules.sh \
ca/gc.sh \
gc.sh \
remote-store.sh \
Expand Down