1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-11 21:16:02 +01:00

Add position info to path values

(Actually, this adds a position field to *all* values.)

This allows improving the "inefficient double copy" warning by showing
where the source path came from in the source, e.g.

  warning: Performing inefficient double copy of path '/home/eelco/Dev/patchelf/' to the store at /home/eelco/Dev/patchelf/flake.nix:30:17. This can typically be avoided by rewriting an attribute like `src = ./.` to `src = builtins.path { path = ./.; name = "source"; }`.
This commit is contained in:
Eelco Dolstra 2025-05-30 17:31:34 +02:00
parent 8ff43c29ef
commit 3e45b40d66
9 changed files with 33 additions and 20 deletions

View file

@ -149,6 +149,8 @@ PosIdx Value::determinePos(const PosIdx pos) const
// Allow selecting a subset of enum values // Allow selecting a subset of enum values
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wswitch-enum"
if (this->pos != 0)
return PosIdx(this->pos);
switch (internalType) { switch (internalType) {
case tAttrs: return attrs()->pos; case tAttrs: return attrs()->pos;
case tLambda: return payload.lambda.fun->pos; case tLambda: return payload.lambda.fun->pos;
@ -906,7 +908,7 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path)
{ {
mkPath(&*path.accessor, makeImmutableString(path.path.abs())); mkPath(&*path.accessor, makeImmutableString(path.path.abs()), noPos.get());
} }
@ -2356,7 +2358,7 @@ BackedStringView EvalState::coerceToString(
// slash, as in /foo/${x}. // slash, as in /foo/${x}.
v.payload.path.path v.payload.path.path
: copyToStore : copyToStore
? store->printStorePath(copyPathToStore(context, v.path())) ? store->printStorePath(copyPathToStore(context, v.path(), v.determinePos(pos)))
: ({ : ({
auto path = v.path(); auto path = v.path();
if (path.accessor == rootFS && store->isInStore(path.path.abs())) { if (path.accessor == rootFS && store->isInStore(path.path.abs())) {
@ -2434,7 +2436,7 @@ BackedStringView EvalState::coerceToString(
} }
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path) StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path, PosIdx pos)
{ {
if (nix::isDerivation(path.path.abs())) if (nix::isDerivation(path.path.abs()))
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
@ -2448,7 +2450,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
*store, *store,
path.resolveSymlinks(SymlinkResolution::Ancestors), path.resolveSymlinks(SymlinkResolution::Ancestors),
settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy, settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy,
computeBaseName(path), computeBaseName(path, pos),
ContentAddressMethod::Raw::NixArchive, ContentAddressMethod::Raw::NixArchive,
nullptr, nullptr,
repair); repair);

View file

@ -594,7 +594,7 @@ public:
bool coerceMore = false, bool copyToStore = true, bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true);
StorePath copyPathToStore(NixStringContext & context, const SourcePath & path); StorePath copyPathToStore(NixStringContext & context, const SourcePath & path, PosIdx pos);
/** /**
@ -607,7 +607,7 @@ public:
* materialize /nix/store/<hash2>-source though. Still, this * materialize /nix/store/<hash2>-source though. Still, this
* requires reading/hashing the path twice. * requires reading/hashing the path twice.
*/ */
std::string computeBaseName(const SourcePath & path); std::string computeBaseName(const SourcePath & path, PosIdx pos);
/** /**
* Path coercion. * Path coercion.

View file

@ -138,9 +138,9 @@ struct ExprPath : Expr
ref<SourceAccessor> accessor; ref<SourceAccessor> accessor;
std::string s; std::string s;
Value v; Value v;
ExprPath(ref<SourceAccessor> accessor, std::string s) : accessor(accessor), s(std::move(s)) ExprPath(ref<SourceAccessor> accessor, std::string s, PosIdx pos) : accessor(accessor), s(std::move(s))
{ {
v.mkPath(&*accessor, this->s.c_str()); v.mkPath(&*accessor, this->s.c_str(), pos.get());
} }
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS COMMON_METHODS

View file

@ -167,6 +167,7 @@ struct Value
{ {
private: private:
InternalType internalType = tUninitialized; InternalType internalType = tUninitialized;
uint32_t pos{0};
friend std::string showType(const Value & v); friend std::string showType(const Value & v);
@ -289,10 +290,11 @@ public:
unreachable(); unreachable();
} }
inline void finishValue(InternalType newType, Payload newPayload) inline void finishValue(InternalType newType, Payload newPayload, uint32_t newPos = 0)
{ {
payload = newPayload; payload = newPayload;
internalType = newType; internalType = newType;
pos = newPos;
} }
/** /**
@ -339,9 +341,9 @@ public:
void mkPath(const SourcePath & path); void mkPath(const SourcePath & path);
void mkPath(std::string_view path); void mkPath(std::string_view path);
inline void mkPath(SourceAccessor * accessor, const char * path) inline void mkPath(SourceAccessor * accessor, const char * path, uint32_t pos)
{ {
finishValue(tPath, { .path = { .accessor = accessor, .path = path } }); finishValue(tPath, { .path = { .accessor = accessor, .path = path } }, pos);
} }
inline void mkNull() inline void mkNull()
@ -482,6 +484,9 @@ public:
NixFloat fpoint() const NixFloat fpoint() const
{ return payload.fpoint; } { return payload.fpoint; }
inline uint32_t getPos() const
{ return pos; }
}; };

View file

@ -374,8 +374,8 @@ path_start
root filesystem accessor, rather than the accessor of the root filesystem accessor, rather than the accessor of the
current Nix expression. */ current Nix expression. */
literal.front() == '/' literal.front() == '/'
? new ExprPath(state->rootFS, std::move(path)) ? new ExprPath(state->rootFS, std::move(path), CUR_POS)
: new ExprPath(state->basePath.accessor, std::move(path)); : new ExprPath(state->basePath.accessor, std::move(path), CUR_POS);
} }
| HPATH { | HPATH {
if (state->settings.pureEval) { if (state->settings.pureEval) {
@ -385,7 +385,7 @@ path_start
); );
} }
Path path(getHome() + std::string($1.p + 1, $1.l - 1)); Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path)); $$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path), CUR_POS);
} }
; ;

View file

@ -52,15 +52,16 @@ std::string EvalState::devirtualize(std::string_view s, const NixStringContext &
return rewriteStrings(std::string(s), rewrites); return rewriteStrings(std::string(s), rewrites);
} }
std::string EvalState::computeBaseName(const SourcePath & path) std::string EvalState::computeBaseName(const SourcePath & path, PosIdx pos)
{ {
if (path.accessor == rootFS) { if (path.accessor == rootFS) {
if (auto storePath = store->maybeParseStorePath(path.path.abs())) { if (auto storePath = store->maybeParseStorePath(path.path.abs())) {
warn( warn(
"Performing inefficient double copy of path '%s' to the store. " "Performing inefficient double copy of path '%s' to the store at %s. "
"This can typically be avoided by rewriting an attribute like `src = ./.` " "This can typically be avoided by rewriting an attribute like `src = ./.` "
"to `src = builtins.path { path = ./.; name = \"source\"; }`.", "to `src = builtins.path { path = ./.; name = \"source\"; }`.",
path); path,
positions[pos]);
return std::string(fetchToStore(*store, path, FetchMode::DryRun, storePath->name()).to_string()); return std::string(fetchToStore(*store, path, FetchMode::DryRun, storePath->name()).to_string());
} }
} }

View file

@ -2620,7 +2620,7 @@ static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * arg
"while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"); "while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, state.computeBaseName(path), path, args[0], ContentAddressMethod::Raw::NixArchive, std::nullopt, v, context); addPath(state, pos, state.computeBaseName(path, pos), path, args[0], ContentAddressMethod::Raw::NixArchive, std::nullopt, v, context);
} }
static RegisterPrimOp primop_filterSource({ static RegisterPrimOp primop_filterSource({

View file

@ -39,7 +39,7 @@ json printValueAsJSON(EvalState & state, bool strict,
case nPath: case nPath:
if (copyToStore) if (copyToStore)
out = state.store->printStorePath( out = state.store->printStorePath(
state.copyPathToStore(context, v.path())); state.copyPathToStore(context, v.path(), v.determinePos(pos)));
else else
out = v.path().path.abs(); out = v.path().path.abs();
break; break;

View file

@ -15,12 +15,12 @@ class PosIdx
private: private:
uint32_t id; uint32_t id;
public:
explicit PosIdx(uint32_t id) explicit PosIdx(uint32_t id)
: id(id) : id(id)
{ {
} }
public:
PosIdx() PosIdx()
: id(0) : id(0)
{ {
@ -45,6 +45,11 @@ public:
{ {
return std::hash<uint32_t>{}(id); return std::hash<uint32_t>{}(id);
} }
uint32_t get() const
{
return id;
}
}; };
inline PosIdx noPos = {}; inline PosIdx noPos = {};