Skip to content

Build dylibs, not bundles, on macOS#988

Merged
damiendoligez merged 1 commit intoocaml:trunkfrom
whitequark:patch-2
Feb 24, 2017
Merged

Build dylibs, not bundles, on macOS#988
damiendoligez merged 1 commit intoocaml:trunkfrom
whitequark:patch-2

Conversation

@whitequark
Copy link
Member

Generally, OCaml creates dynamic libraries in three cases:

  • when building bytecode stubs (dllX.so);
  • when building a .cmxs plugin;
  • when building a .native.so or even .byte.so.

Right now, this results in DLLs on Windows and ELF shared objects on Linux,
all of which can be dynamically loaded (with dlopen() or equivalent) or
linked against (with the -l linker flag or equivalent). However, on macOS,
this is not the case.

macOS has two kinds of dynamic libraries: "bundles" and "dylibs". Prior to
the version 10.4, there have been significant differences between these,
which I will not describe in this commit because 10.4 has long became
irrelevant. After 10.4, there are only two differences:

  • rpath is handled slightly differently.
  • dylibs can be linked against, with the -l linker flag;

Before this commit, ocamlc/ocamlopt on macOS produce bundles, when using
the .so extension for the output file. After this commit, OCaml on macOS
will produce dylibs, when using the same extension. The rationale is as
follows:

  • For bytecode stubs and plugins, the exact structure of which is essentially
    an implementation detail of the OCaml runtime, nothing will change because
    they can still be dynamically loaded.
  • For .native.so and .byte.so objects, there are two changes:
    1. The objects can be linked against with the -l flag.
    2. The objects can be linked with the -cclib -shared flag, which is
      what ocamlbuild and perhaps other buildsystems pass when building
      a shared object through -output-obj/-output-complete-obj.

@whitequark
Copy link
Member Author

Don't merge yet, this doesn't appear to work as expected.

Nevermind, I forgot an opam config env.

Output of otool -hV after this PR:

$ otool -hv _build/main.native.so
_build/main.native.so:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00       DYLIB    14       1528 DYLDLINK WEAK_DEFINES BINDS_TO_WEAK NO_REEXPORTED_DYLIBS

Generally, OCaml creates dynamic libraries in three cases:
  * when building bytecode stubs (dllX.so);
  * when building a .cmxs plugin;
  * when building a .native.so or even .byte.so.

Right now, this results in DLLs on Windows and ELF shared objects on Linux,
all of which can be dynamically loaded (with dlopen() or equivalent) or
linked against (with the -l linker flag or equivalent). However, on macOS,
this is not the case.

macOS has two kinds of dynamic libraries: "bundles" and "dylibs". Prior to
the version 10.4, there have been significant differences between these,
which I will not describe in this commit because 10.4 has long became
irrelevant. After 10.4, there are only two differences:
  * rpath is handled slightly differently.
  * dylibs can be linked against, with the -l linker flag;

Before this commit, ocamlc/ocamlopt on macOS produce bundles, when using
the .so extension for the output file. After this commit, OCaml on macOS
will produce dylibs, when using the same extension. The rationale is as
follows:
  * For bytecode stubs and plugins, the exact structure of which is essentially
    an implementation detail of the OCaml runtime, nothing will change because
    they can still be dynamically loaded.
  * For .native.so and .byte.so objects, there are two changes:
      1. The objects can be linked against with the -l flag.
      2. The objects can be linked with the -cclib -shared flag, which is
         what ocamlbuild and perhaps other buildsystems pass when building
         a shared object through -output-obj/-output-complete-obj.
@gasche
Copy link
Member

gasche commented Dec 26, 2016

Thanks a lot for the detailed explanation. The change was discussed in MPR#6927; maybe @garrigue has an opinion on it?

@garrigue
Copy link
Contributor

I'm perfectly fine with this change. The only reason ocaml use bundles is that, when shared libraries were first introduced in ocaml, this was the only portable way to do it on MacOSX.

Of course, this should be tested thoroughly, both for the building/installing and runtime phases.
I would suggest testing both opam and non-opam settings, as they can behave differently.

and "0 mod <expr>" in the case when <expr> was a non-constant
evaluating to zero (Mark Shinwell)

- MPR#6927, GPR#988: On macOS, when compiling bytecode stubs, plugins,
Copy link
Contributor

Choose a reason for hiding this comment

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

"Mac OS X"

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently so...

@mshinwell
Copy link
Contributor

OK in principle.

What about the issue relating to ".so" versus ".dylib"? I tend to think we should follow the platform-specific convention here, which I believe is ".dylib". Idiosyncratic behaviour of OCaml tools isn't likely to help further their adoption, in my opinion.

Some of the tools in the Mantis issue (e.g. ocamlbuild, ocamlfind, etc) seem as if they should be easily updated before 4.05 is released.

@whitequark
Copy link
Member Author

What about the issue relating to ".so" versus ".dylib"? I tend to think we should follow the platform-specific convention here, which I believe is ".dylib". Idiosyncratic behaviour of OCaml tools isn't likely to help further their adoption, in my opinion.

There's no real convention. For example, anything you install from homebrew (including OCaml, for that matter) will generally use ".so". macOS-only software will usually stick to ".dylib". Bundles don't have any convention. All in all I don't feel that ".so" will be unexpected, surprising, or confusing here, especially looking as bundles and dylibs nearly equivalent anyway, and the massive fallout (every single makefile for OCaml projects will have to be updated) is not worth it.

@dbuenzli
Copy link
Contributor

There's no real convention.

Just a few number on my machine.

> mdfind "kMDItemDisplayName == *.so" | wc -l 
     649
> mdfind "kMDItemDisplayName == *.dylib" | wc -l 
     661

Note that the list for .so includes software installed by apple itself:

> mdfind "kMDItemDisplayName == *.so" | grep "/System" | wc -l 
     438

However that's only for python.

> mdfind "kMDItemDisplayName == *.so" | grep "/System/Library/Frameworks/Python.framework/" | wc -l 
     438

or example, anything you install from homebrew (including OCaml, for that matter) will generally use ".so". macOS-only software will usually stick to ".dylib".

That doesn't match my experience though. A lot of cross platform stuff I have on my machine uses .dylib (e.g. freetype, libssl, sqlite, etc.)

and the massive fallout (every single makefile for OCaml projects will have to be updated) is not worth it.

However these projects would already have to be updated to use $EXT_DLL for windows. So I'd say that maybe using .dylib would actually increase global portability. But I don't have a strong opinion on that one.

@whitequark
Copy link
Member Author

However these projects would already have to be updated to use $EXT_DLL for windows. So I'd say that maybe using .dylib would actually increase global portability. But I don't have a strong opinion on that one.

There is a lot of projects that special-cases Windows with .exe and .dll, from my cross-compilation experience.

@mshinwell
Copy link
Contributor

@dbuenzli What you need to count is the number of files called .so that are actually dylibs though. Some of those may actually be bundles.

@whitequark I feel you may be exaggerating about "every single Makefile". The ones that are going to break are those that actually build these targets on macOS machines. That must surely be a smaller subset, at least. You mentioned in the Mantis issue about XCode's handling of these files/bundles. Can you confirm whether XCode handles files called ".so" that are actually dylibs correctly, or not?

@whitequark
Copy link
Member Author

I feel you may be exaggerating about "every single Makefile". The ones that are going to break are those that actually build these targets on macOS machines.

Quite a few Makefiles build stubs.

@dbuenzli
Copy link
Contributor

@dbuenzli What you need to count is the number of files called .so that are actually dylibs though. Some of those may actually be bundles.

> for f in  $(mdfind "kMDItemDisplayName == *.so"); do if (file $f | grep "bundle" -q); then echo $f; fi; done | wc -l 
     638
> for f in  $(mdfind "kMDItemDisplayName == *.so"); do if (file $f | grep "library" -q); then echo $f; fi; done | wc -l 
       1
> for f in  $(mdfind "kMDItemDisplayName == *.dylib"); do if (file $f | grep "bundle" -q); then echo $f; fi; done | wc -l 
      30
> for f in  $(mdfind "kMDItemDisplayName == *.dylib"); do if (file $f | grep "library" -q); then echo $f; fi; done | wc -l 
     629

@mshinwell
Copy link
Contributor

I think @dbuenzli 's numbers illustrate that there may be some expectation that .so files are indeed bundles. :)

@garrigue
Copy link
Contributor

I think that the point here is that shared stub libraries are for use with ocaml only, and are not passed to the system linker. As such, there does not seem to be a strong reason to rename them. And while this would only require changes in the Makefiles for stubs, for me this just means every library I'm maintaining...

@mshinwell
Copy link
Contributor

@garrigue Does this not affect creation of a normal native shared library for purposes other than bytecode stubs? I suppose we could consider changing it only for those cases, which is indeed when the produced files are more likely to be user-visible.

@whitequark
Copy link
Member Author

I suppose we could consider changing it only for those cases, which is indeed when the produced files are more likely to be user-visible.

I agree that this is less disruptive. However, what are our options here? Consider this:

  • we only have a single EXT_DLL in Makefile.config;
  • some Makefiles (and ocamlbuild) use ocamlmklib to build stubs;
  • some Makefiles invoke the linker directly and use $(EXT_DLL).
    This means that we effectively have to transform EXT_DLL into EXT_DLL_BYTECODE_STUBS and EXT_DLL_OCAML_OUTPUT_AS_SHARED_LIBRARY (or somelike). I see absolutely no point in this change.

Frankly, at this point the whole discussion about the extension strikes me as the worst kind of bikeshedding, the one where any choice except of doing nothing has a negative outcome.

@dbuenzli
Copy link
Contributor

This means that we effectively have to transform EXT_DLL into EXT_DLL_BYTECODE_STUBS and EXT_DLL_OCAML_OUTPUT_AS_SHARED_LIBRARY

Of course this would be non-sense. We should set EXT_DLL to either dylib or so on macOS.

@mshinwell
Copy link
Contributor

I'm still not really worried about changing the extension wholesale, to be honest. There are plenty of other things that have to be updated in OPAM packages (and elsewhere) between releases, and this just doesn't strike me as a particularly problematic one.

I don't think this particular discussion is bikeshedding; as I said above, having different conventions just for OCaml isn't going to further our cause on those platforms.

@mcclure
Copy link

mcclure commented Dec 29, 2016

My shed-related opinion as a long-time mac developer: MacOS by convention uses dylib and MacOS users expect dylib. If as a mac developer I see a "dylib" I understand what to do with it, if I see ".so" there is a moment of pause while I try to figure out if that will work with my other tools. At the other end, at least one of the ancillary tools for OCaml (ocamlbuild, I think?) decides what kind of file to produce by the file extension you ask it for; in the past I believe I've run into confusion because I was trying to ask it for a dylib and it expected me to ask for a .so. Using dylib is more friendly to non-OCaml new users on Macintosh.

It is also important to remember OCaml does not exist in isolation. Last fall I attempted to use OCaml in a mixed C/OCaml project, where OCaml was producing a shared library I expected other people to incorporate into their own C programs. The consumer of the shared libraries would not be familiar with OCaml, and if .so is the convention for OCaml on mac they would not be aware of that. For the convenience of my users, I needed to use dylib. I ran into several problems with this project but one was that the OCaml tools at the time emitted .so, and so I had to add an additional step in my make scripts to rename the .so to .dylib. This is minor but these little inconveniences add up.

I cannot comment on what existing OCaml users will expect or how disruptive a switch to dylib would be for them.

@gasche
Copy link
Member

gasche commented Dec 29, 2016

For what it's worth, I'm fine with ocamlbuild accepting both .dylib and .so as target suffixes, to please both crowds (project maintainers that have hardwritten .so, macOS users that expect .dylib). In fact ocamlbuild (before I was in care of it) documented support for dylib so there was expressed intention -- but no implementation. On the other hand, the parts of ocamlbuild that would be affected have been in large part contributed by @whitequark, whose choice I will thus trust and follow.

@whitequark
Copy link
Member Author

whitequark commented Dec 29, 2016

After having talked to @mcclure and a few more people on Twitter I have reversed my opinion. It seems that macOS people are divided into two camps: those who are ok with either of so/dylib and those who strongly prefer dylib and consider it the only appropriate choice for the platform. As such I now think that the right way is to change EXT_DLL to dylib.

I have also realized that the only way to rewrite Makefiles would be to make them use .$(EXT_DLL) in all cases: this works for all platforms and all OCaml versions since at least 4.00. Therefore the change would be much less invasive than I expected.

@xavierleroy
Copy link
Contributor

There is another OCaml command that creates shared object files: ocamlmklib. Those shared objects contain C stub code and are dynamically linked by ocamlrun. I don't know if this has been considered in this discussion, but I am really not keen to name those shared objects .dylib under macOS and .so under other flavors of Unix. This is just complicating make install procedures with no tangible benefits.

@xavierleroy
Copy link
Contributor

@whitequark : our messages went past each other, I think. But just to reiterate: I need stronger reasons than "this is what half of a random bunch of Twitters recommend".

@whitequark
Copy link
Member Author

this is what half of a random bunch of Twitters recommend

I don't like this condescending attitude. I have put in effort to ask specific people I personally know that have a lot of macOS development experience, much more than me indeed. You have so far simply reiterated what I have said in the very PR description.

@mcclure
Copy link

mcclure commented Dec 29, 2016

I need stronger reasons than "this is what half of a random bunch of Twitters recommend".

Is "it is what Apple recommends" compelling?

Here are the "Dynamic Library Design Guidelines" from Apple's website. They give various guidance on naming dynamic libraries, but they start off with "The filename of a dynamic library normally contains the library’s name with the lib prefix and the .dylib extension". Their dynamic library documentation here uses .dylib throughout, and I do not find any official Apple documentation which suggests .so is allowed.

I'm not actually totally sure by what mechanism it is ".so" even works. I have an unconfirmed suspicion ".so" is just an arbitrary three letter string here and the reason it works is dylibs do not in practice require a file extension at all (I did some tests just now with creating and using a mac shared library with the extension ".dll" and this worked just fine).

@xavierleroy
Copy link
Contributor

You have so far simply reiterated what I have said in the very PR description.

Then you were right from the beginning and there is no point in continuing this discussion.

@dbuenzli
Copy link
Contributor

This is just complicating make install procedures with no tangible benefits.

Arent't these install procedure anyways broken on Windows ? People should be using $EXT_DLL in their makefiles.

@whitequark
Copy link
Member Author

I'm not actually totally sure by what mechanism it is ".so" even works. I have an unconfirmed suspicion ".so" is just an arbitrary three letter string here and the reason it works is dylibs do not in practice require a file extension

The kernel doesn't care about file extensions at all, it looks at the Mach header only. The only place where it would matter is (non-OCaml) tooling.

@avsm
Copy link
Member

avsm commented Dec 29, 2016

@mcclure There used to be a difference, as .so files are bundles and .dylib files are dynamically loadable modules. To run ohtool shows the difference:

$ otool -hv /usr/local/lib/ocaml/libasmrun_shared.so
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00      BUNDLE    12       1472 DYLDLINK WEAK_DEFINES BINDS_TO_WEAK
$ otool -hv /usr/local/lib/libzmq.dylib
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00       DYLIB    14       1752 DYLDLINK WEAK_DEFINES BINDS_TO_WEAK NO_REEXPORTED_DYLIBS

The fink porting guide has more information on what this meant, but from a fairly old version of OSX. The material differences between the two as described by the guide are:

Mach-O shared libraries have the file type MH_DYLIB and carry the extension .dylib. They can be linked against with the usual static linker flags, e.g. -lfoo for libfoo.dylib. However, they can not be loaded as a module. (Side note: Shared libraries can be loaded dynamically through an API. However, that API is different from the API for bundles and the semantics make it useless for an dlopen() emulation. Most notably, shared libraries can not be unloaded.)

Loadable modules are called "bundles" in Mach-O speak. They have the file type MH_BUNDLE. Since no component involved cares about it, they can carry any extension. The extension .bundle is recommended by Apple, but most ported software uses .so for the sake of compatibility. Bundles can be dynamically loaded and unloaded via dyld APIs, and there is a wrapper that emulates dlopen() on top of that API. It is not possible to link against bundles as if they were shared libraries. However, it is possible that a bundle is linked against real shared libraries; those will be loaded automatically when the bundle is loaded.

@mshinwell
Copy link
Contributor

@xavierleroy I think the argument for consistency with the standards of the platform is a fairly strong one, especially for platforms like the Mac, where there is probably less tolerance in the community for arbitrary discrepancies than e.g. on Linux. As far as Makefiles go: isn't @dbuenzli right about this, i.e. that we should be using the variable anyway (isn't @shindere 's Unix/Windows Makefile-unification work going to do that)?

@damiendoligez
Copy link
Member

We discussed this PR at the developer meeting two days ago, and the consensus was to accept the format change (which is important and non-controversial) and leave the suffix change for later (if/when we reach consensus).

@bobot
Copy link
Contributor

bobot commented Feb 13, 2023

For the suffix change, has the consensus been reached 7 years later?

@damiendoligez
Copy link
Member

Not that I know of. A quick search reveals that at least one person is inconvenienced by the use of .so:
ocaml/dune#4948

Does anyone want to propose a PR for changing the extension? With all the work on the makefiles I would expect it to be a lot easier now.

@xavierleroy
Copy link
Contributor

With all the work on the makefiles I would expect it to be a lot easier now.

Well. I'm sure the change will break many Makefiles, including several of mine, that blindly install *.so. Yes, those Makefiles are already broken for Windows, but now they will be broken for macOS too. Is this worth it?

EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
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.

10 participants