Skip to content

feat: support manifest base directories - #66

Open
ZetabS wants to merge 4 commits into
feel-co:mainfrom
ZetabS:feat/manifest-base-dirs
Open

ZetabS wants to merge 4 commits into
feel-co:mainfrom
ZetabS:feat/manifest-base-dirs

Conversation

@ZetabS

@ZetabS ZetabS commented Sep 27, 2026

Copy link
Copy Markdown

Summary

Add optional base directories for deterministic relative path resolution.

A manifest can define:

  • base_dir as the default for relative source and target paths;
  • source_base_dir as the source-specific override; and
  • target_base_dir as the target-specific override.

Relative paths are resolved when the manifest is read. Absolute paths are
unchanged.

For a relative source, the resolution order is:

  1. source_base_dir;
  2. base_dir; and
  3. the current working directory when --impure is enabled.

Relative targets follow the same order with target_base_dir in place of
source_base_dir. If no applicable option is available, the relative path is
ignored.

Motivation

smfh already supports relative paths in impure mode, but those paths depend on
the process current working directory. An explicit base directory makes the
path context deterministic without requiring callers to control the working
directory.

Hjem use case

Hjem can use source_base_dir for repository-relative live sources while
keeping its existing absolute target semantics. This allows Hjem to inject the
checkout root at runtime while preserving the logical relative source in its
configuration and generation state.

Related Hjem integration:

cli: support source base directories in standalone #194

The Hjem integration depends on this PR being merged first because it passes
source_base_dir through the manifest consumed by smfh.

Other use cases

Generated configuration manifests used by CI, deployment scripts, or other
automation can refer to files relative to a known project or configuration
root instead of relying on the caller's current directory.

Manifest version

The manifest version is bumped from 3 to 4 because the manifest semantics
change when base directories are present. Older smfh versions do not apply
these fields and therefore cannot provide the same path resolution behavior.

The version bump is kept in a separate commit. If maintainers consider these
optional fields compatible with version 3, that commit can be removed from the
PR.

clean

clean continues to read, verify, sort, and re-serialize the parsed manifest.
When a base directory is provided, relative source and target paths are
resolved while reading the manifest, so clean serializes those resolved
paths.

If maintainers prefer clean to preserve logical relative paths, I am happy
to revise this behavior or add an explicit option for resolved output.

Changes

  • Add base_dir, source_base_dir, and target_base_dir.
  • Resolve relative paths when reading a manifest.
  • Add a dedicated InvalidBaseDir read error.
  • Document base directory precedence and --impure interaction.
  • Add unit tests for base directory resolution and overrides.
  • Bump the manifest format version in a separate commit for maintainer review.

Validation

  • nix develop -c cargo fmt --check
  • nix develop -c cargo check
  • nix develop -c cargo test

The current macOS run has 27 passing tests out of 29. The two existing
failures compare /tmp/... with /private/tmp/..., caused by macOS /tmp
canonicalization and unrelated to this change.

Review questions

  • Is manifest version 4 necessary for these optional fields, or should the
    version bump be removed?
  • Should clean print resolved paths or preserve logical relative paths?
  • Is the proposed split between generic base_dir and source/target-specific
    overrides appropriate for smfh's manifest model?

Sanity Checking

  • I have tested, and self-reviewed my code
  • Style and consistency
    • I formatted all relevant code (nix develop cargo fmt --check)
    • My changes are consistent with the rest of the codebase
  • If new changes are particularly complex:
    • My code includes comments in particularly complex areas
    • I have included a section in the documentation
  • Tested on platform(s)
    • x86_64-linux
    • aarch64-linux
    • aarch64-darwin

@eclairevoyant

Copy link
Copy Markdown
Member

Obvious AI slop aside, this seems like a non-feature when you can just pass base_dir as the prefix for the relative path, making it an absolute path anyway. I don't see the point of this PR.

@eclairevoyant eclairevoyant left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

see above

@ZetabS

This comment was marked as spam.

@NotAShelf

Copy link
Copy Markdown
Member

Please stop using AI to communicate. If English is not your first language, that is perfectly fine. I would appreciate if you worded your own thoughts, in your own language and style, and let us figure out translation.

I believe your intentions are good, but this form of communication where you dump your AI's output on us and expect us to sift through it is not feasible.

@ZetabS

ZetabS commented Sep 28, 2026 •

Copy link
Copy Markdown
Author

I'm sorry. I will stop using AI on contributing hjem / smfh.
I'm really not good at English. It could be awkward, aggresive or defensive to read.

Here is my thoughts in Korean. and manually translated to English.
Markdown is my prefered style because I like its structure. Not AI prefer.

저는 hjem을 사용해본 적이 없지만 chezmoi와 home-manager를 사용했습니다.
둘 다 마음에 들지 않았습니다. 왜냐하면 chezmoi는 nix를 사용하지 않았고 home-manager는 모듈이 실제 구현을 감추기 때문입니다.
그리고 home-manager는 nix에 절대 경로 하드코딩 없이는 symlink를 지원하지 않았습니다. (mkOutOfStoreSymlink)
그래서 저는 대안을 찾고 있었고, hjem에 symlink 기능이 있다면 제 유스케이스에 맞다고 생각했습니다.
저는 기여를 위해 최선을 다 할 것입니다. 다음으로 뭘 하면 좋을지 알려주세요.
예를 들면, 필요하다면 저는 PR을 AI 없이 다시 작성하거나 설명해드릴 수 있습니다.

I haven't used hjem yet. but I have used chezmoi and home-manager.
I don't like both because chezmoi didn't use nix and home-manager hide actual implementation in modules.
And home-manager didn't support symlink without hardcoded absolute path. (mkOutOfStoreSymlink)
So I found alternatives. I thoughts that hjem would fit my use case if it had symlink feature.
I will do my best to contribute. Please tell me what to do next.
For example. I can rewrite PR body without AI if needed. or I can explain PR without AI.

@ZetabS

ZetabS commented Sep 28, 2026

Copy link
Copy Markdown
Author

Verbose and long answer is my style to give more information. I can answer short if needed.

@ZetabS

ZetabS commented Sep 28, 2026

Copy link
Copy Markdown
Author

base_dir을 지원한다면 hjem 측의 변경을 줄일 수 있을 것이라고 생각했습니다.
hjem #194이 실제 구현입니다.
AI를 사용하여 너무 많은 정보를 전달한 것에 대해서는 사과드립니다.

I thoughts that supporting base_dir could simplify hjem side diff.
Here is actual implemetation: hjem #194
I'm sorry that there's too much information caused by AI.

@ZetabS

ZetabS commented Sep 28, 2026 •

Copy link
Copy Markdown
Author

It is my second time to contributing. I'm still learning about that. I'm worried and unsure about contributor's strong opinion seems to be aggresive.
But if I have to "sift" myself and that would be more friendly and helpful.
I claim that this PR is definitely useful to remove unnecessary complexity from hjem when implement "out of store symlink" without hardcode path in nix expression.

@eclairevoyant

eclairevoyant commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Thank you for explaining without AI.
So I want to confirm your reasons for the feature - is it mostly for convenience/reducing repetition to set an option like base_dir, rather than explicitly setting "${base_dir}/relative/path" every time? Or is there another use case that is completely impossible with absolute path strings but is possible with relative path strings + base dirs?

I'm really not good at English. It could be awkward, aggresive or defensive to read.

Don't worry, we will assume you are making your best effort to communicate clearly.

@ZetabS

ZetabS commented Sep 29, 2026 •

Copy link
Copy Markdown
Author

It is difficult to explain.

is it mostly for convenience/reducing repetition to set an option like base_dir, rather than explicitly setting "${base_dir}/relative/path" every time?

Yes. I want to reducing repetition to simplify hjem implementation. mostly for convenience.
Because I should have understand code before submit PR.

If "recoverable rollback" described below is not needed, It could be purely convenience feature.

Or is there another use case that is completely impossible with absolute path strings but is possible with relative path strings + base dirs?

Also Yes. If you move your dotfiles repo to somewhere, rollback will fail.
But it is recoverable with relative path strings + base dirs via cli options that explicitly overrides generation base dirs.

like:

hjem rollback --source-base-dir /somewhere/moved/repo/path

If this PR merged, it will be very easy to implement.
Adds only one field(source_base_dir) to manifest then throw it to smfh.
It also allows saving manifest to generation without additional processing.

If this PR not merged, it will be complicate problem.
I had to preserve relative path and base_dir outside of manifest since manifest should contains absolute path only and can't determine relative path from absolute path reliably. It lost information to override base_dir immediatly when concatenated.
It seems huge hjem generation metadata rework or saving both manifest(relative and absolute) in generation will solves this problem without changing smfh.

But I can't make more complicate PR without this. because I'm new in rust. I just learned it really briefly for contribute hjem/smfh. I should have ensure that I "understand" codes in PR even though it mostly written by AI assistant. Current PR seems not difficult to understand for me even though I'm new in rust.

I could just ignore the edge case with throwing error, but there was simpler way(this PR) that solves simplification and edge case both. It seems logical to me.
So I decided to submit this PR.

My only concern is that clean command should preserve relative path or normalize to absolute path?
Currently it won't preserve relative path as I understand. because it concatenate path at reading and modify manifest struct in memory. Intentionally simplified.
I couldn't decide which is better because docs explain it "prints a normalized JSON representation" and "Useful for reformatting manifests".
I thoughts preserving relative path will useful for reformatting, but what does the "normalized" means?...

@NotAShelf

Copy link
Copy Markdown
Member

Thank you for communicating your own words, that is much easier to read and parse for me.

As far as I understand, you seem to want to make live symlinks from a Nix-managed config to files in a Git checkout without hardcoding the checkout's absolute path in Nix. I think this is a good idea to prevent the previously implicit behaviour that was "just use the absolute path" (which was not ideal), and in this new model (which I think means that Hjem supplies the checkout path at runtime; smfh uses it to resolve relative sources) it is less ambiguous, and finally worth supporting as a first-class citizen. You also seem to want a moved checkout to remain usable during rollback. I don't quite see the benefit of this, but I'm all in for enabling more advanced usecases in smfh.

That said, I have a few concerns. My first concern is whether it fits. While I want smfh to be more usable, I'd much rather avoid scope creep. This concept fits Hjem: supporting editable, out-of-store dotfiles is a clear use case for a $HOME configuration manager. For smfh though, I think it's plausible at best. smfh does manifest path handling, so a reusable way to resolve relative paths fits its role. But the Hjem use case only needs source_base_dir; the generic base_dir and target_base_dir need their own justification. Consider nixos-core for example, would this change benefit it?

Secondly, we have to settle clean's design. Should it preserve relative paths, or print the resolved paths? Also, please clarify whether you want relocating a checkout and overriding its base during rollback included in this change, or considered as future feature.

I'm currently at work. Given the AI-assisted nature of this PR, I'd like to take some ample time to review and nitpick. In the meantime, we can clarify the scope, design, and semantics.

@ZetabS

ZetabS commented Sep 29, 2026

Copy link
Copy Markdown
Author

But the Hjem use case only needs source_base_dir; the generic base_dir and target_base_dir need their own justification.

I agree. The reason was these are pair and it could be used in future. I couldn't find actual use cases yet. So I can remove it from PR.
I think it is enough reason for some perspective. if source base dir exists, then target base dir exists. but I won't claim that it is must. I read and understand nixos-core's philosophy and what you concerns.

Secondly, we have to settle clean's design. Should it preserve relative paths, or print the resolved paths?

I have one idea that needs more review. Separate format and clean command in future.
But I think it is out of scope. I will choice not adding more command and retain current behavior on this PR so codes remains simple if I couldn't find reason to preserve relative path.

Also, please clarify whether you want relocating a checkout and overriding its base during rollback included in this change, or considered as future feature.

I didn't implemented on hjem side yet. I will include in hjem PR before changing it to "ready for review".
I might misunderstand your English. So I should clarify that. No smfh side additional work is needed, because rollback is hjem feature, but it depends on this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants