Skip to content

Commit 21b9ab5

Browse files
committed
patchpkg: add packageFS to handle store paths
Add a `packageFS` type that extends `fs.FS` to translate to/from operating system paths. This makes it a little easier to get real file paths when writing or passing files to external commands (like `patchelf`).
1 parent 8c30551 commit 21b9ab5

File tree

1 file changed

+62
-22
lines changed

1 file changed

+62
-22
lines changed

internal/patchpkg/builder.go

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,29 @@ func (d *DerivationBuilder) init() error {
4848
// Build applies patches to a package store path and puts the result in the
4949
// d.Out directory.
5050
func (d *DerivationBuilder) Build(ctx context.Context, pkgStorePath string) error {
51-
slog.DebugContext(ctx, "starting build of patched package", "pkg", pkgStorePath, "out", d.Out)
51+
if err := d.init(); err != nil {
52+
return err
53+
}
5254

55+
slog.DebugContext(ctx, "starting build to patch package",
56+
"pkg", pkgStorePath, "out", d.Out)
57+
return d.build(ctx, newPackageFS(pkgStorePath), newPackageFS(d.Out))
58+
}
59+
60+
func (d *DerivationBuilder) build(ctx context.Context, pkg, out *packageFS) error {
5361
var err error
54-
pkgFS := os.DirFS(pkgStorePath)
55-
for path, entry := range allFiles(pkgFS, ".") {
62+
for path, entry := range allFiles(pkg, ".") {
63+
if ctx.Err() != nil {
64+
return err
65+
}
66+
5667
switch {
5768
case entry.IsDir():
58-
err = d.copyDir(path)
69+
err = d.copyDir(out, path)
5970
case isSymlink(entry.Type()):
60-
err = d.copySymlink(pkgStorePath, path)
71+
err = d.copySymlink(pkg, out, path)
6172
default:
62-
err = d.copyFile(pkgFS, path)
73+
err = d.copyFile(pkg, out, path)
6374
}
6475

6576
if err != nil {
@@ -75,16 +86,16 @@ func (d *DerivationBuilder) Build(ctx context.Context, pkgStorePath string) erro
7586
return cmd.Run()
7687
}
7788

78-
func (d *DerivationBuilder) copyDir(path string) error {
79-
osPath, err := filepath.Localize(path)
89+
func (d *DerivationBuilder) copyDir(out *packageFS, path string) error {
90+
path, err := out.OSPath(path)
8091
if err != nil {
8192
return err
8293
}
83-
return os.Mkdir(filepath.Join(d.Out, osPath), 0o777)
94+
return os.Mkdir(path, 0o777)
8495
}
8596

86-
func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
87-
src, err := pkgFS.Open(path)
97+
func (d *DerivationBuilder) copyFile(pkg, out *packageFS, path string) error {
98+
src, err := pkg.Open(path)
8899
if err != nil {
89100
return err
90101
}
@@ -103,12 +114,10 @@ func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
103114
perm = fs.FileMode(0o777)
104115
}
105116

106-
osPath, err := filepath.Localize(path)
117+
dstPath, err := out.OSPath(path)
107118
if err != nil {
108119
return err
109120
}
110-
dstPath := filepath.Join(d.Out, osPath)
111-
112121
dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, perm)
113122
if err != nil {
114123
return err
@@ -122,22 +131,53 @@ func (d *DerivationBuilder) copyFile(pkgFS fs.FS, path string) error {
122131
return dst.Close()
123132
}
124133

125-
func (d *DerivationBuilder) copySymlink(pkgStorePath, path string) error {
126-
// The fs package doesn't support symlinks, so we need to convert the
127-
// path back to an OS path to see what it points to.
128-
osPath, err := filepath.Localize(path)
134+
func (d *DerivationBuilder) copySymlink(pkg, out *packageFS, path string) error {
135+
link, err := out.OSPath(path)
129136
if err != nil {
130137
return err
131138
}
132-
target, err := os.Readlink(filepath.Join(pkgStorePath, osPath))
139+
target, err := pkg.Readlink(path)
133140
if err != nil {
134141
return err
135142
}
136-
// TODO(gcurtis): translate absolute symlink targets to relative paths.
137-
return os.Symlink(target, filepath.Join(d.Out, osPath))
143+
return os.Symlink(target, link)
144+
}
145+
146+
// packageFS is the tree of files for a package in the Nix store.
147+
type packageFS struct {
148+
fs.FS
149+
storePath string
150+
}
151+
152+
// newPackageFS returns a packageFS for the given store path.
153+
func newPackageFS(storePath string) *packageFS {
154+
return &packageFS{
155+
FS: os.DirFS(storePath),
156+
storePath: storePath,
157+
}
158+
}
159+
160+
// Readlink returns the destination of a symlink.
161+
func (p *packageFS) Readlink(path string) (string, error) {
162+
osPath, err := p.OSPath(path)
163+
if err != nil {
164+
return "", err
165+
}
166+
// TODO(gcurtis): check that the symlink isn't absolute or points
167+
// outside the Nix store.
168+
return os.Readlink(osPath)
169+
}
170+
171+
// OSPath translates a package-relative path to an operating system path.
172+
func (p *packageFS) OSPath(path string) (string, error) {
173+
local, err := filepath.Localize(path)
174+
if err != nil {
175+
return "", err
176+
}
177+
return filepath.Join(p.storePath, local), nil
138178
}
139179

140-
// RegularFiles iterates over all files in fsys starting at root. It silently
180+
// allFiles iterates over all files in fsys starting at root. It silently
141181
// ignores errors.
142182
func allFiles(fsys fs.FS, root string) iter.Seq2[string, fs.DirEntry] {
143183
return func(yield func(string, fs.DirEntry) bool) {

0 commit comments

Comments
 (0)