shell.nix for new cli #463
	
		Labels
		
	
	
	
	No labels
	
		
			
	
	Affects/CppNix
		
			Affects/Nightly
		
			Affects/Only nightly
		
			Affects/Stable
		
			Area/build-packaging
		
			Area/cli
		
			Area/evaluator
		
			Area/fetching
		
			Area/flakes
		
			Area/language
		
			Area/lix ci
		
			Area/nix-eval-jobs
		
			Area/profiles
		
			Area/protocol
		
			Area/releng
		
			Area/remote-builds
		
			Area/repl
		
			Area/repl/debugger
		
			Area/store
		
			bug
		
			Context
contributors
		
			Context
drive-by
		
			Context
maintainers
		
			Context
RFD
		
			crash 💥
		
			Cross Compilation
		
			devx
		
			docs
		
			Downstream Dependents
		
			E/easy
		
			E/hard
		
			E/help wanted
		
			E/reproducible
		
			E/requires rearchitecture
		
			Feature/S3
		
			imported
		
			Language/Bash
		
			Language/C++
		
			Language/NixLang
		
			Language/Python
		
			Language/Rust
		
			Needs Langver
		
			OS/Linux
		
			OS/macOS
		
			performance
		
			regression
		
			release-blocker
		
			stability
		
			Status
blocked
		
			Status
invalid
		
			Status
postponed
		
			Status
wontfix
		
			testing
		
			testing/flakey
		
			Topic/Large Scale Installations
		
			ux
		
		
	
		No milestone
		
			
		
	No project
	
		
	
	
	
	
		No assignees
		
	
	
		
			
		
	
	
	
		3 participants
	
	
		
		
	Notifications
	
		
	
	
	
		
	
	
	Due date
No due date set.
	
		Dependencies
		
		
	
	
	No dependencies set.
	
	
		
	
	
		
			Reference
		
	
	
		
	
	
			lix-project/lix#463
			
		
	
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	
	No description provided.
		
		Delete branch "%!s()"
	 
	Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I asked about an issue because there’re some design considerations I’d like to discuss (I’ll share them here, but feel free to point me to a more suitable venue).
old-style
shell.nixdoesn’t currently accept any arguments, it just returns a derivation (you kinda can pass arguments via--arg, but they require default values). that means that adding auto-filling for arguments with no default values is not a breaking change, which is nice. there’re some options as to which arguments get passed though:flakes from the registry (so basically only
nixpkgsfor most shells). requires the shell toimport nixpkgsmanually every time, but allows e.g. overlays, so is the most flexible.packages via
callPackage, asnix-builddoes. very concise and pleasant for simple shells, but less flexible and doesn’t provide a clean way to access other flakes from the registry.somehow both? placing them into the same namespace feels bad though, name conflicts are bound to happen. something like
could potentially solve this problem, but that’s very much not a syntax today.
I’m leaning towards the first option, as it’s flexible, has simple semantics and doesn’t require new syntax, and I’ll try to write a basic implementation to see how that works. I’d be interested to hear another opinions on the issue though.
Originally posted by @goldstein in #170 (comment)
I’ll try to tinker with possible implementations.
the first option seems like a good thing to try first, yes. it's also closest to the current old-cli shell.nix pattern of starting everything with
{ pkgs ? import <nixpkgs> {} }:, and the additional let needed to actually import things seems fine for letting us avoid theNIX_PATHshenanigans the old style relied upon. (and we can even use flakes as flakes bygetFlakeing the args if needed, so we don't lose anything)as long as it's behind an experimental feature gate we're free to experiment and break things if a better way becomes evident. :)
fyi you may be interested in qyriad's project xil https://github.com/qyriad/xil
another consideration is whether this should work as
nix shellor asnix-shell. I’d personally likenix-shell/nix develop-like behaviour, so e.g. C headers get propagated. I could just add new semantics tonix developinstead ofnix shell, but it isn’t quite right either, becausenix developignores$SHELLby default so it can inject bash functions into the environment. I started writing it as a separate command for a proof of concept, but it should probably be integrated into eithernix shellornix developlater.I played around with this for a bit, here’re some thoughts:
it’s kind of easier to implement this out-of-tree than in-tree. I thought to reuse
nix developlogic, butnix developbasically generates some bespoke Bash scripts and then evaluates them. we can generate similar scripts withnix print-dev-env. it’s still very doable in-tree, but I experimented mostly out-of-tree because I’m not that comfortable with Lix codebase yet.“half-locking” (locking inputs that do not come from registry) can be achieved with a syntax like
where
lockedis a function that resolves input via a local lockfile, or falls back togetFlakeif needed.I really want this to be an impure evaluation so we can avoid preemptively copying all the files to store (this is a big slowdown with my current flake-based workflow), but impure evaluations are not cached. out-of-tree tool can just cache evaluations (+ instantiations) in some local database, but it would be kinda weird to do this for a proper Lix subcommand.
the big problem is LSP integration. nixd supports flakes, but any new bespoke format would have to be explicitly supported so it knows what gets passed to the closure arguments. this is a point in favour of in-tree implementation, because supporting an “official” feature makes more sense for LSP than supporting a random external tool. I’ll have to look into nixd code though, maybe there is a way to teach it a custom format without literally hardcoding it.
there is an external tool here that implements a plainly better eval cache by just tracing nix io: https://github.com/xzfc/cached-nix-shell
this, ironically, actually is better for cache behaviour than flakes in a certain sense because flakes get moved to the store under different paths all the time and so this strategy simply wouldn't work because the absolute paths keep changing (and this is visible to nix code too; ouch).
“just tracing all the files Nix accesses” is a cool strategy, thanks for the link! it’s somewhat more complex than what I had in mind, but it’s even better. I’ll try to reuse it if I make this as an external tool.
it still would feel somewhat weird for a built-in command (and it’s hard to imagine how that would look in code). AFAIK Lix doesn’t currently store stuff (aside from repl history) outside of the, well, store, so it would be a big exception to the general rules.
I wonder how hard it is to “internally intercept” file accesses of the evaluator to make a list without syscall hooking. even if implementing “new shell.nix” as an external tool, this could be provided as an API like
nix eval --dump-file-deps..local/share/nix, .cache/nix beg to differ. fetchGit, fetchTarball (without hashes; there's a cache of what hash was last obtained when no hash is given), flake eval cache, etc do all store things.
we would never ship a dylib crimes tracer like that though; much easier to make the evaluator io itself possible to intercept, also enabling a lazy trees like optimization one day.
TIL about fetcher cache, thanks. intercepting a bunch of primops is easy (I think it’s just
__read{FileType,File,Dir}?), but there’re also fetchers which can read from disk, including indrectly, like viagit. fetchers are already cached though, so maybe it’s fine to ignore them.