Skip to content

Conversation

@gridbugs
Copy link
Collaborator

Dune cannot handle symlinks with missing targets. Package source archives may contain such broken symlinks. This can be due to packaging errors on the part of the package author which are benign to the correct operation of the package when installed with opam, but render packages uninstallable with dune.

It's assumed that all opam packages can be installed with opam, so if a package contains a broken symlink then the symlink must not be necessary for the correct operation of the package. Thus the fix is to remove broken symlinks from extracted source archives, and to avoid copying broken symlinks from local package source directories.

A less conservative approach than this might attempt to allow dune to handle broken symlinks in general, but this would be a much larger change, and has been attempted and abandoned several times before.

Copy link
Collaborator

@Leonidas-from-XIV Leonidas-from-XIV left a comment

Choose a reason for hiding this comment

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

I think the approach is really good because as you write, both Etienne and me tried to fix the underlying issue in the engine and got stuck so this workaround seems like a really nice pragmatic solution to sidestep the issue without breaking anything.

That said, I have some questions on how this works, I've written them in the comments.

Comment on lines 189 to 190
let traverse =
let rec loop on_file on_dir root stack acc =
let rec loop on_file on_dir on_broken_symlink root stack acc =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
let traverse =
let rec loop on_file on_dir root stack acc =
let rec loop on_file on_dir on_broken_symlink root stack acc =
let traverse ~on_broken_symlink =
let rec loop on_file on_dir root stack acc =

Given on_broken_symlink isn't really part of the loop (it is never changed and the recursive calls always reuses the same one), I'd put it outside of the recursive function arguments. That way one does not have to think whether these get mutated or not (same probably goes for on_file and on_dir) and one less argument to get wrong.

Copy link
Collaborator Author

@gridbugs gridbugs Apr 1, 2025

Choose a reason for hiding this comment

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

Agreed. I was following suit for the other arguments to this function. I'll rearrange it so only the changing arguments are passed to loop (in a separate commit so it's clearly not part of handling broken symlinks).

~on_file:(fun ~dir:_ _ () -> ())
~on_broken_symlink:(fun ~dir fname () ->
let path = Filename.concat target_abs (Filename.concat dir fname) in
Fpath.rm_rf path);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this a blocking operation? We probably need some Fiber version of traverse to avoid blocking the execution while waiting on file IO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is, but I don't think there's an async rm in dune. Further down in this file we do Io.copy_file inside an async context (not that that makes it correct to do so, but at least there's a precedent). The blocking rm_rf is expected to be called very infrequently - only while extracting packages with broken symlinks (which are quite rare).

Copy link
Collaborator

Choose a reason for hiding this comment

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

For me the issue isn't so much the rm (but I see how the location where I placed my comment makes it confusing), my concern is rather that Fpath.traverse is blocking.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah makes sense. Dune doesn't currently have an async way of traversing a directory structure but we can probably throw together an async traverse based on the Dune_engine.Fs_memo module.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you want to do this in this PR or should we defer it to a future PR? The rest of the PR looks good so unless somebody raises concerns LGTM.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think let's defer this work. There are other instances of Fpath.traverse that we should also update, and I don't want this PR to get too drawn out.

@gridbugs gridbugs force-pushed the remove-broken-symlinks branch from 52c1383 to 74d3e57 Compare April 1, 2025 07:51
@Alizter
Copy link
Collaborator

Alizter commented Apr 1, 2025

What about when a broken symlink is purposefully included for say testing reasons?

@gridbugs
Copy link
Collaborator Author

gridbugs commented Apr 2, 2025

What about when a broken symlink is purposefully included for say testing reasons?

Dune doesn't run tests on a project's dependencies anyway so this change won't affect that use case.

@gridbugs gridbugs force-pushed the remove-broken-symlinks branch from 74d3e57 to 0cddc97 Compare April 2, 2025 06:54
@Alizter
Copy link
Collaborator

Alizter commented Apr 2, 2025

Here is a related issue that I tried to tackle previously:

I agree that we should probably just delete these for pkg.

@gridbugs
Copy link
Collaborator Author

gridbugs commented Apr 3, 2025

Yeh there have been a few attempts at fixing the issue in a general way that didn't end up getting merged.

gridbugs added 2 commits April 4, 2025 11:43
Dune cannot handle symlinks with missing targets. Package source
archives may contain such broken symlinks. This can be due to packaging
errors on the part of the package author which are benign to the correct
operation of the package when installed with opam, but render packages
uninstallable with dune.

It's assumed that all opam packages can be installed with opam, so if a
package contains a broken symlink then the symlink must not be necessary
for the correct operation of the package. Thus the fix is to remove
broken symlinks from extracted source archives, and to avoid copying
broken symlinks from local package source directories.

A less conservative approach than this might attempt to allow dune to
handle broken symlinks in general, but this would be a much larger
change, and has been attempted and abandoned several times before.

Signed-off-by: Stephen Sherratt <stephen@sherra.tt>
Signed-off-by: Stephen Sherratt <stephen@sherra.tt>
@gridbugs gridbugs force-pushed the remove-broken-symlinks branch from 0cddc97 to a649216 Compare April 4, 2025 00:43
@gridbugs gridbugs merged commit 175d2d2 into ocaml:main Apr 4, 2025
23 of 25 checks passed
@gridbugs gridbugs deleted the remove-broken-symlinks branch April 4, 2025 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants