Skip to content

Commit 043df13

Browse files
authored
Merge pull request #10089 from edolstra/relative-flakes
Improve support for relative path inputs
2 parents 0c10167 + db46d40 commit 043df13

File tree

14 files changed

+358
-114
lines changed

14 files changed

+358
-114
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
synopsis: "Support for relative path inputs"
3+
prs: [10089]
4+
---
5+
6+
Flakes can now refer to other flakes in the same repository using relative paths, e.g.
7+
```nix
8+
inputs.foo.url = "path:./foo";
9+
```
10+
uses the flake in the `foo` subdirectory of the referring flake. For more information, see the documentation on [the `path` flake input type](@docroot@/command-ref/new-cli/nix3-flake.md#path-fetcher).
11+
12+
This feature required a change to the lock file format. Previous Nix versions will not be able to use lock files that have locks for relative path inputs in them.

src/libexpr/call-flake.nix

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ let
4141
(key: node:
4242
let
4343

44+
parentNode = allNodes.${getInputByPath lockFile.root node.parent};
45+
4446
sourceInfo =
4547
if overrides ? ${key}
4648
then
4749
overrides.${key}.sourceInfo
50+
else if node.locked.type == "path" && builtins.substring 0 1 node.locked.path != "/"
51+
then
52+
parentNode.sourceInfo // {
53+
outPath = parentNode.outPath + ("/" + node.locked.path);
54+
}
4855
else
4956
# FIXME: remove obsolete node.info.
5057
# Note: lock file entries are always final.

src/libfetchers/fetchers.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ bool Input::isFinal() const
166166
return maybeGetBoolAttr(attrs, "__final").value_or(false);
167167
}
168168

169+
std::optional<std::string> Input::isRelative() const
170+
{
171+
assert(scheme);
172+
return scheme->isRelative(*this);
173+
}
174+
169175
Attrs Input::toAttrs() const
170176
{
171177
return attrs;

src/libfetchers/fetchers.hh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ struct Input
4141
std::shared_ptr<InputScheme> scheme; // note: can be null
4242
Attrs attrs;
4343

44-
/**
45-
* path of the parent of this input, used for relative path resolution
46-
*/
47-
std::optional<Path> parent;
48-
4944
/**
5045
* Cached result of getFingerprint().
5146
*/
@@ -104,6 +99,12 @@ public:
10499
bool isConsideredLocked(
105100
const Settings & settings) const;
106101

102+
/**
103+
* Only for relative path flakes, i.e. 'path:./foo', returns the
104+
* relative path, i.e. './foo'.
105+
*/
106+
std::optional<std::string> isRelative() const;
107+
107108
/**
108109
* Return whether this is a "final" input, meaning that fetching
109110
* it will not add, remove or change any attributes. (See
@@ -269,6 +270,9 @@ struct InputScheme
269270

270271
virtual bool isLocked(const Input & input) const
271272
{ return false; }
273+
274+
virtual std::optional<std::string> isRelative(const Input & input) const
275+
{ return std::nullopt; }
272276
};
273277

274278
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);

src/libfetchers/path.cc

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ struct PathInputScheme : InputScheme
9191
std::string_view contents,
9292
std::optional<std::string> commitMsg) const override
9393
{
94-
writeFile((CanonPath(getAbsPath(input)) / path).abs(), contents);
94+
writeFile(getAbsPath(input) / path.rel(), contents);
9595
}
9696

97-
std::optional<std::string> isRelative(const Input & input) const
97+
std::optional<std::string> isRelative(const Input & input) const override
9898
{
9999
auto path = getStrAttr(input.attrs, "path");
100100
if (isAbsolute(path))
@@ -108,44 +108,27 @@ struct PathInputScheme : InputScheme
108108
return (bool) input.getNarHash();
109109
}
110110

111-
CanonPath getAbsPath(const Input & input) const
111+
std::filesystem::path getAbsPath(const Input & input) const
112112
{
113113
auto path = getStrAttr(input.attrs, "path");
114114

115-
if (path[0] == '/')
116-
return CanonPath(path);
115+
if (isAbsolute(path))
116+
return canonPath(path);
117117

118118
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
119119
}
120120

121121
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
122122
{
123123
Input input(_input);
124-
std::string absPath;
125124
auto path = getStrAttr(input.attrs, "path");
126125

127-
if (path[0] != '/') {
128-
if (!input.parent)
129-
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
130-
131-
auto parent = canonPath(*input.parent);
132-
133-
// the path isn't relative, prefix it
134-
absPath = nix::absPath(path, parent);
135-
136-
// for security, ensure that if the parent is a store path, it's inside it
137-
if (store->isInStore(parent)) {
138-
auto storePath = store->printStorePath(store->toStorePath(parent).first);
139-
if (!isDirOrInDir(absPath, storePath))
140-
throw BadStorePath("relative path '%s' points outside of its parent's store path '%s'", path, storePath);
141-
}
142-
} else
143-
absPath = path;
126+
auto absPath = getAbsPath(input);
144127

145-
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s'", absPath));
128+
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to the store", absPath));
146129

147130
// FIXME: check whether access to 'path' is allowed.
148-
auto storePath = store->maybeParseStorePath(absPath);
131+
auto storePath = store->maybeParseStorePath(absPath.string());
149132

150133
if (storePath)
151134
store->addTempRoot(*storePath);
@@ -154,7 +137,7 @@ struct PathInputScheme : InputScheme
154137
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
155138
// FIXME: try to substitute storePath.
156139
auto src = sinkToSource([&](Sink & sink) {
157-
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
140+
mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter);
158141
});
159142
storePath = store->addToStoreFromDump(*src, "source");
160143
}
@@ -176,7 +159,7 @@ struct PathInputScheme : InputScheme
176159
store object and the subpath. */
177160
auto path = getAbsPath(input);
178161
try {
179-
auto [storePath, subPath] = store->toStorePath(path.abs());
162+
auto [storePath, subPath] = store->toStorePath(path.string());
180163
auto info = store->queryPathInfo(storePath);
181164
return fmt("path:%s:%s", info->narHash.to_string(HashFormat::Base16, false), subPath);
182165
} catch (Error &) {

0 commit comments

Comments
 (0)