-
Notifications
You must be signed in to change notification settings - Fork 409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a "plugin" linking mode for executables #3141
Conversation
dffb8e2
to
be8bea2
Compare
src/dune/exe.ml
Outdated
@@ -36,6 +36,10 @@ module Linkage = struct | |||
|
|||
let js = { mode = Byte; ext = ".bc.js"; flags = [] } | |||
|
|||
let is_plugin = function | |||
| { ext = ".cma" | ".cmxs"; _ } -> true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Mode
to avoid the hard coded extensions please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would need to extend Link_mode.t
, roughly as follows:
type t =
| Byte
| Native
| Byte_with_stubs_statically_linked_in
(* This two are new *)
| Plugin_native
| Plugin_byte
it it worth it? By the way we also match on the extension in the case of JS compilation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it's worth it. Although I would do Plugin of Mode.t
instead.
But actually, you could also just do:
let is_plugin t = List.mem t.ext [Mode.plugin_ext Byte; Mode.plugin_ext Native]
Not very beautiful, but it's best to avoid having all these extensions everywhere.
The issue with the .js extension is indeed something that we need to fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the second suggestion, it was a less invasive change.
src/dune/dune_file.ml
Outdated
@@ -1565,6 +1571,10 @@ module Executables = struct | |||
"See https://github.com/ocaml/dune/issues/745 for more \ | |||
details." | |||
])) | |||
and+ statically_linked_libraries = | |||
field_o "statically_linked_libraries" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this field? Why can't we just statically link all the libraries in libraries
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The precise reason is that the plugin may require a library which is also linked into the main program. If you link the library into the plugin, then you won't be able to dynlink the plugin into the program because the modules of the library are already loaded. This is fairly common: 1) the common library may contains functionality that is used both by the main program and the plugins, or 2) it could be small utility libraries (think uutf
) that happen to be used by both the main application and the plugins, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even more concretely, the typical way plugins work is that they "register" themselves with the main program via state living on some shared module. This shared module is linked to the main program and should not/could not be linked into the plugins.
Thanks - this looks excellent! With this, the 0install dune file can be reduced to just:
Is there an easy way to get the extension of the plugin, so that it can be installed? I tried |
Good point. Maybe it makes sense to make plugins (with a |
The MR in #3104 makes trivial to add a plugin system to an executable or library, and make it also easy to dynlink plugins (the documentation is not written yet). I'm interested in understanding how the features of this two MR intersects in the use cases. |
Hi @bobot, thanks for pointing me to your PR, I was not aware about it. Just from a brief glance at the PR description, I would say that maybe there is some overlap on the "plugin" stanza that you propose. But if I understand correctly, your PR assumes plugins are split into libraries and use This (tiny, less than 30 lines of meaningful code) PR exposes the flexibility of the the |
ed891be
to
695f4b6
Compare
Giving it some more thought, I don't see why the plugins produced by the |
a5ad9ab
to
d5e858a
Compare
@diml do you have any comments on the proposed |
The plugin stanza is needed for creating the META in the site directory of the package.
It is possible, but @diml prefered that I don't add any specific plugin related fields to executable, he prefered to add a new stanza with a selected subset of the library stanza. Currently there is only My worry are more at the big picture:
|
I updated #3104 with an example so that it is easier to understand. |
For me the following two requirements are absolutely crucial:
Indeed this can happen, and the linking mode in this PR is not meant to be a general solution to the question of plugins. If your plugins needs anything beyond what is offered in this PR then it should use a system such as the one proposed in your PR. For example, if you need to deal with non-trivial dependencies between plugins. The point of this PR is that it is a trivial extension of the
See my two points above; in principle I am happy with any system that can provide them... but I am very partial to the approach in this PR due to its simplicity. |
To be clear it is not just for non-trivial dependencies between plugins, it is just for behind able to reliably dynlink two independently created plugins. From #1544 what I understood of your use case and so what I think is offered by this PR is to be able on windows to create a relocatable directory which contains an executable and different plugins. My goal in #3104 is also to support this use case (which is an important one). Everything is in place, for example findlib is abstracted away and so can be made relocatable, and also the choice of localisation is deferred at installation time so we can add a The PR #1544 is going to be an experimental feature at first, so this PR could also be one and we could see how the use cases are supported. However I would be happy to discuss your use case in #1544 so that it could be fully supported. |
Hey, I'll let you discuss whether we need both PRs or whether one is enough, bust just a comment on linking mode vs stanza. For me, the main difference is the public name. If we attach a name to a plugin and it lives in its own namespace, then it doesn't seem right to me to use the
That seems fine, but it could be mis-interpreted for plain executables. i.e. intuitively, I would interpret it as: these libraries are statically linked and the other ones are dynamically linked, which is not the case. What about
is |
f3ece0b
to
f4f5aa9
Compare
We discussed this in the meeting today, and everyone agreed that having both this PR and #3104 is fine as they serve different purposes. #3104 is a full key in hand system, which will allow users to very quickly get started and add a plugin system to their applications, which is a great feature to add. However, we are adventuring in the runtime territory, i.e. we are not only controlling aspects of how the software is built, but also how it will behave at runtime and it's likely that it won't work for everybody. Indeed, some developers might want to retain control over how their plugins are packaged and distributed and some might simply not have a choice. And if you cannot use #3104 and you use Dune, you are a bit on your own. This PR will bridge the gap and provide a more manual but still manageable solution. |
I added |
src/dune/exe.ml
Outdated
Result.map link_time_code_gen | ||
~f:(fun { Link_time_code_gen.to_link; force_linkall } -> | ||
let to_link = | ||
let rec depends_on lib0 lib = | ||
Lib_name.equal lib0 (Lib.name lib) | ||
|| List.exists (Lib.requires_exn lib) | ||
~f:(depends_on lib0) | ||
in | ||
List.filter_map to_link ~f:(function | ||
| Module _ as x -> Some x | ||
| Lib lib as x -> | ||
if | ||
List.exists embed_in_plugin_libraries | ||
~f:(fun (_, lib0) -> depends_on lib0 lib) | ||
then | ||
Some x | ||
else | ||
None) | ||
in | ||
{ Link_time_code_gen.to_link; force_linkall }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code filters out the library dependencies which are not inverse dependencies of the libraries listed in the (embed_in_plugin_libraries ...)
field. The remaining libraries are linked into the plugin. I would appreciate a second pair of eyes on this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that isn't clear to me is:
if a library is mentioned in (embed_in_plugin_libraries ..)
, do we guarantee in will be linked in the plugin?
This code seems to suggest we also require that the library be a (compile or link?) dependency of the executable. But the name of the field suggests that the libraries should be unconditionally linked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the PR along the lines of your suggestion -- now the libraries listed on that field are unconditionally linked, no questions asked. It does not try to be clever, just simple to understand and predictable.
@@ -241,6 +241,13 @@ module Map = struct | |||
let model = Ocaml_config.model context.ocaml_config in | |||
let system = Ocaml_config.system context.ocaml_config in | |||
let version_string = Ocaml_config.version_string context.ocaml_config in | |||
let ext_plugin = | |||
Mode.plugin_ext | |||
( if Ocaml_config.natdynlink_supported context.ocaml_config then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have this Dynlink_supported
module that's supposed to be used for making these checks. I don't quite remember what its purpose is but it's worth looking at to make sure we aren't missing anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just looked, I think it is OK like this. The Dynlink_supported
module seems to be used to handle (no_dynlink)
. I could use Dynlink_supported.By_the_os.get
instead, but it amounts to the same thing, and I think it ends up obfuscating things more than anything else.
Agreed. I never liked that thing. So what happens when your plugin executable depends on a library with `no_dynlink`?
Should this be an issue?
…On Feb 21, 2020, 10:28 AM +0000, Nicolás Ojeda Bär ***@***.***>, wrote:
@nojb commented on this pull request.
In src/dune/pform.ml:
> @@ -241,6 +241,13 @@ module Map = struct
let model = Ocaml_config.model context.ocaml_config in
let system = Ocaml_config.system context.ocaml_config in
let version_string = Ocaml_config.version_string context.ocaml_config in
+ let ext_plugin =
+ Mode.plugin_ext
+ ( if Ocaml_config.natdynlink_supported context.ocaml_config then
I just looked, I think it is OK like this. The Dynlink_supported module seems to be used to handle (no_dynlink). I could use Dynlink_supported.By_the_os.get instead, but it amounts to the same thing, and I think it ends up obfuscating things more than anything else.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
doc/dune-files.rst
Outdated
to link statically when using ``plugin`` linking mode. By default, no | ||
libraries are linked in. Any library which is a dependency of the executable | ||
that is an inverse dependency of one the libraries in ``<library-list>`` will | ||
also be statically linked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wording is a bit confusing to me.
Any library which is a dependency of the executable
Dependency in the compilation or linking sense?
that is an inverse dependency of one the libraries in ```
So if we have:
(libraries foo baz)
(embed_in_plugin_libraries bar)
and foo
depends on bar,
we should link in foo
and bar
?
What about the transitive dependencies of bar
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the doc according to the new, simpler implementation.
src/dune/compilation_context.ml
Outdated
lazy | ||
(Result.List.map | ||
~f:(fun (loc, name) -> | ||
match Lib.DB.find libs name with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using Lib.DB.resolve
?
@@ -338,6 +345,7 @@ module Map = struct | |||
; ("ext_lib", string context.lib_config.ext_lib) | |||
; ("ext_dll", string context.lib_config.ext_dll) | |||
; ("ext_exe", string ext_exe) | |||
; ("ext_plugin", since ~version:(2, 4) (Var.Values [ String ext_plugin ])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should reflect this variable in the change log as well.
doc/dune-files.rst
Outdated
@@ -668,6 +668,12 @@ Executables can also be linked as object or shared object files. See | |||
pulled in. This field is available since the 2.0 version of the dune | |||
language. | |||
|
|||
- ``(embed_in_plugin_libraries <library-list>)`` especifies a list of libraries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ``(embed_in_plugin_libraries <library-list>)`` especifies a list of libraries | |
- ``(embed_in_plugin_libraries <library-list>)`` specifies a list of libraries |
Also adds a %{ext_plugin} variable Signed-off-by: Nicolás Ojeda Bär <[email protected]>
The issues pointed out in the review were all minor so I just fixed them myself. Thanks @nojb! |
…lugin, dune-private-libs and dune-glob (2.4.0) CHANGES: - Add `mdx` extension and stanza version 0.1 (ocaml/dune#3094, @NathanReb) - Allow to make Odoc warnings fatal. This is configured from the `(env ...)` stanza. (ocaml/dune#3029, @Julow) - Fix separate compilation of JS when findlib is not installed. (ocaml/dune#3177, @nojb) - Add a `dune describe` command to obtain the topology of a dune workspace, for projects such as ROTOR. (ocaml/dune#3128, @diml) - Add `plugin` linking mode for executables and the `(embed_in_plugin_libraries ...)` field. (ocaml/dune#3141, @nojb) - Add an `%{ext_plugin}` variable (ocaml/dune#3141, @nojb) - Dune will no longer build shared objects for stubs if `supports_shared_libraries` is false (ocaml/dune#3225, fixes ocaml/dune#3222, @rgrinberg) - Fix a memory leak in the file-watching mode (`dune build -w`) (ocaml/dune#3220, @snowleopard and @aalekseyev)
Thanks a lot @rgrinberg, much appreciated! (Sorry, I was on holidays this week and only saw the comments now.) |
This PR implements #1544 (comment). Concretely, it adds a
plugin
linking mode forexecutable
andexecutables
stanzas, which produce a.cmxs
(native) and.cma
(bytecode) targets. By default the libraries in(libraries ...)
are used for compilation, but are not linked into the resulting plugin. However, a new field is also added(statically_linked_libraries ...)
(provisional name) which can specify an explicit list of libraries to be linked in.Should solve #3136, #1544 and https://discuss.ocaml.org/t/dune-problems-using-dynlink-plugins/2874