Skip to content

Relocatable ocamlfind with sprinkles - #122

Open
dra27 wants to merge 7 commits into
win-fixesfrom
relocatable
Open

Relocatable ocamlfind with sprinkles#122
dra27 wants to merge 7 commits into
win-fixesfrom
relocatable

Conversation

@dra27

@dra27 dra27 commented Jul 17, 2026

Copy link
Copy Markdown
Member

This PR both adapts ocamlfind for OCaml 5.5 and also extends it to be relocatable itself. Relocatable in this instance is according to my definition in ocaml/RFCs#53 - in practice, this means that where the system toolchain supports it, the installation of findlib to two different prefixes for the same compiler configuration should be byte-for-byte identical.

As with the finest of wines, it is best reviewed commit-by-commit:

  • The first commit partially winds back one of the changes made in Relocatable ocamlfind #72 for generating topfind by instead being able to generate the two files which Relocatable ocamlfind #72 merged. This paves the way for there being three variants. The trick instead is that lines which should be present in OCaml 4.00+ are prefixed 4x:
  • The next commit extends the semantics of findlib.conf to interpret relative paths. As noted in the commit message, paths are interpreted relative to findlib.conf or, for files loaded from findlib.conf.d, relative to the directory containing findlib.conf.d.
  • The next commit extends configure to support being configured for potentially relocatable builds. This is done by passing a relative path to -sitelib, which is interpreted relative to the location given for -config. That sounds rather complicated, but as can be seen in the opam file, it just means that where before we configured with -sitelib lib we now configure with -sitelib .. configure then computes the relative path of the directory containing findlib.conf from OCaml's Standard Library.
  • The next commit fixes ocamlfind warning "Cannot read directory ../stublibs which is mentioned in ld.conf" #115 by implementing the required support for reading the relative paths which OCaml 5.5+ uses in ld.conf.
  • As noted in Make paths from ld.conf absolute #119, the non-normalised paths that come out of processing the explicit relative paths are a problem for systems where ocamlfind updates ld.conf (it would just cause it to add multiple entries, though). The next commit puts the plumbing in to detect Unix.realpath and uses it on the resulting paths for comparisons.
  • The final two commits update CI the build to have the incantations required to produce a fully relocatable binary on Windows / macOS / Linux. I've technically implemented that support for GNU make only, because life's too short for POSIX make. CI for OCaml 5.5 creates a second switch (instantly, of course), pins ocamlfind in that switch as well and then verifies that the ocamlfind is identical.

dra27 added 7 commits July 18, 2026 07:24
Relative paths in findlib (including in `path`) are interpreted relative
the directory containing findlib.conf or findlib.conf.d (interpreting
relative paths of files in findlib.conf.d relative to the directory
containing findlib.conf.d rather than to the file itself allows
a findlib.conf path to be moved to/from findlib.conf.d without changing
its semantics).
If -sitelib is passed a relative path, this is interpreted relative to
the path used for findlib.conf (i.e. the path given for -config). For
example, `./configure -config $(opam var lib)/findlib.conf -sitelib .`
has the same meaning as passing `-sitelib $(opam var lib)`.

If Relocatable OCaml is being used, and the OCaml Standard Library is in
a directory underneath `-config` (as an opam switch does, for example),
and the ocamlfind binary is being installed to the same directory as the
compiler, then Relocatable Findlib is enabled. In mode, `ocamlfind`
searches for `findlib.conf` relative to the location of OCaml's Standard
Library and `topfind` similarly locates the `-sitelib` directory
relative to OCaml's Standard Library. `findlib.conf` is also configured
to use relative paths.
OCaml 5.4 interprets entries beginning with an explicit ./ or ../ as
being relative to the directory containing ld.conf. ocamlfind recognises
this too for the DLL consistency checks while ensuring that existing
explicit-relative entries are preserved when ld.conf entries are being
added.
When Findlib is configured to use a shared stublibs directory (which, in
particular, is the configuration opam uses), ocamlfind checks to see
that the shared stublibs directory is mentioned in ld.conf.

Previously, this check tries to take into account path normalisation and
case-insensitive file-systems, but the check is thwarted by OCaml 5.4's
new explicit-relative lines in ld.conf (for opam, it therefore can't
recognise that ../stublibs refers to `$(opam var lib)/stublibs`).

OCaml 4.13+ has Unix.realpath, so we detect the presence of that and use
it to canonicalise the paths. This works reliably because all the
directories are known to exist at the point of the check. Earlier
versions (which may also use the same ld.conf syntax owing to the
Relocatable OCaml backports) will continue to display the innocuous
warning.
Works around cloning problems in OCaml 5.5

@shym shym left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you very much for that PR!
I’ve only a series of small comments and suggestions, everything very light.
And beware that, to paraphrase approximately Knuth, I’ve only read through this, not tested it…

Comment thread Makefile.config.pattern

#----------------------------------------------------------------------
# For Relocatable Findlib, the path of site-lib relative to the OCaml
# Standard Library directory. For normal Findlib, equal to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Standard Library directory. For normal Findlib, equal to
# Standard Library directory. For non-relocatable Findlib, equal to

I’d like to see the relocatable version be the new normal one.

Comment thread configure
Comment on lines +361 to +362
elif [ "x$d1" = "x${d1#$d2}" ] || [ "x$d3" = "x{$d3##/}" ]; then
non_relocatable_reason="findlib.conf and the Standard Library don't share a common path"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
elif [ "x$d1" = "x${d1#$d2}" ] || [ "x$d3" = "x{$d3##/}" ]; then
non_relocatable_reason="findlib.conf and the Standard Library don't share a common path"
elif [ "x$d1" = "x${d1#$d2/}" ]; then
non_relocatable_reason="findlib.conf and the Standard Library don't share a common path"

I think that combines both tests, namely detects whether stdlib is in a strict subdirectory of where findlib.conf will be located, or I’m mistaken?
I read that condition as a bit more restricted that the reason suggests, if that’s the case.
(Side note, this can do probably something funny if findlib.conf’s path is using pattern characters but, oh, well)

Comment thread configure
Comment on lines +369 to +371
ocaml_config_from_stdlib="$(echo "$d3" | sed -e 's|[^/]*[^/]|..|g')"
ocaml_config_from_stdlib="${ocaml_config_from_stdlib##/}"
ocaml_sitelib_from_stdlib="$ocaml_config_from_stdlib"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
ocaml_config_from_stdlib="$(echo "$d3" | sed -e 's|[^/]*[^/]|..|g')"
ocaml_config_from_stdlib="${ocaml_config_from_stdlib##/}"
ocaml_sitelib_from_stdlib="$ocaml_config_from_stdlib"
ocaml_sitelib_from_stdlib="$(echo "$d3" | sed -e 's|[^/]*[^/]|..|g')"
ocaml_sitelib_from_stdlib="${ocaml_sitelib_from_stdlib#/}"

I suggest to get rid of the temporary new var ocaml_sitelib_from_stdlib and to use the shortest prefix removal: when it’s a fixed-length string, I find it a bit weird to ask for the longest one.

Comment thread configure
ocaml_sitelib_from_stdlib="${ocaml_sitelib_from_stdlib}${dir_sep}${ocamlfind_conf_destdir}"
fi
# d3 should be of the form "/.../.../...". Convert all the
# characters between slashes to Filaname.dirname and then remove

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# characters between slashes to Filaname.dirname and then remove
# characters between slashes to Filename.dirname and then remove

Comment thread configure
# d3 should be of the form "/.../.../...". Convert all the
# characters between slashes to Filaname.dirname and then remove
# the slashes. macOS sed doesn't support EREs.
ocamlfind_conf_from_stdlib="fun p -> $(echo "$d3" | sed -e 's|[^/]*[^/]|Filename.dirname (|g;s|/||g')p$(echo "$d3" | sed -e 's|[^/]*[^/]|)|g;s|/||g')"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
ocamlfind_conf_from_stdlib="fun p -> $(echo "$d3" | sed -e 's|[^/]*[^/]|Filename.dirname (|g;s|/||g')p$(echo "$d3" | sed -e 's|[^/]*[^/]|)|g;s|/||g')"
ocamlfind_conf_from_stdlib="fun p -> $(echo "$d3" | sed -e 's|//*[^/]*[^/]|Filename.dirname (|g')p$(echo "$d3" | sed -e 's|//*[^/]*[^/]|)|g')"

One s is enough, isn’t it?

Comment thread src/findlib/Makefile
$(SH) $(TOP)/tools/patch '@RELATIVE_PATHS@' '$(RELATIVE_PATHS)' | \
sed -e 's;@AUTOLINK@;$(OCAML_AUTOLINK);g' \
-e 's;@SYSTEM@;$(SYSTEM);g' \
-e 's;@CONFIG_FROM_STDLIB@;$(CONFIG_FROM_STDLIB);g' \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
-e 's;@CONFIG_FROM_STDLIB@;$(CONFIG_FROM_STDLIB);g' \
-e 's;@CONFIG_FROM_STDLIB@;$(CONFIG_FROM_STDLIB);g' \

to fix alignment

Comment thread src/findlib/findlib.ml
Comment on lines +229 to +235
let rec split acc dir =
let dirname = Filename.dirname dir in
let basename = Filename.basename dir in
if dirname = Filename.current_dir_name then
List.fold_left Filename.concat "" (basename :: acc)
else
split (Filename.basename dir :: acc) dirname in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does split do more than trimming an initial ./ (in a portable way)? (ie, it might be worth a comment to explain it, and maybe rename it trim_dotslash, as the result is re-concatenated)
(And I wondered about a non-recursive function then, even if the depth is probably quite limited, and I understand this is annoying to cover the case of a .// prefix but this annoying case could be just ignored I’d say)

Comment thread src/findlib/findlib.ml
Comment on lines +590 to +591
since OCaml 5.4. The interpretation of non-absolute lines in ld.conf
prior to OCaml 5.4 was not useful, so this behaviour is done without

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be 5.5, shouldn’t it? Or rephrased a bit to only talk about Relocatable OCaml.
(This also appears in 739d3dc and 1bcb370 messages)

Comment thread src/findlib/findlib.mli
(** Entries in ld.conf *)
type ldconf_entry = {
raw: string; (** Raw entry line *)
eff: string (** Evaluated entry line *)

@shym shym Jul 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
eff: string (** Evaluated entry line *)
eff: string
(** Effective entry line with explicit-relative paths resolved to absolute
paths *)

Comment thread .github/workflows/ci.yml
Comment on lines +91 to +93
run: |
first="$(opam var bin | tr -d '\r')/ocamlfind${{ runner.os == 'Windows' && '.exe' || '' }}"
second="$(opam var --switch=second bin | tr -d '\r')/ocamlfind${{ runner.os == 'Windows' && '.exe' || '' }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
run: |
first="$(opam var bin | tr -d '\r')/ocamlfind${{ runner.os == 'Windows' && '.exe' || '' }}"
second="$(opam var --switch=second bin | tr -d '\r')/ocamlfind${{ runner.os == 'Windows' && '.exe' || '' }}"
env:
EXE: ${{ runner.os == 'Windows' && '.exe' || '' }}
run: |
first="$(opam var bin | tr -d '\r')/ocamlfind$EXE"
second="$(opam var --switch=second bin | tr -d '\r')/ocamlfind$EXE"

maybe, if only to shorten lines?

@rlepigre-skylabs-ai

Copy link
Copy Markdown
Contributor

@dra27 Is there anything I can do to help get this merged, and then released?

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.

ocamlfind warning "Cannot read directory ../stublibs which is mentioned in ld.conf"

3 participants