Skip to content
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 the ability to choose the host context and create duplicates of the default context with different settings #2098

Merged
merged 3 commits into from
May 8, 2019

Conversation

TheLortex
Copy link
Collaborator

This PR adds two features in dune-workspace files:

  • (host <context_name>) allows to consider the given context as a cross-compiled context, and use context_name as its host context.
  • (name <context_name>) can be used on the default context: this allows have a duplicate of the default context using different flags, for example.

My use case is the following:

(lang dune 1.10)
(context (default))
(context 
  (default 
    (name freestanding)
    (host default)
    (env 
      (_
        (c_flags -DFREESTANDING ...)
)))) 

Which sets up a cross-compiled context based on the default compilation context.

@TheLortex TheLortex requested a review from a user April 29, 2019 13:30
@avsm
Copy link
Member

avsm commented Apr 29, 2019

For context, this would be useful for building mirage unikernels with all C stubs systematically compiled using the right embedded cflags. mirage/mirage#969

@rgrinberg
Copy link
Member

Amusingly, this kind of a host/target context setup is what we had for our initial version of cross compilation. But against our better senses, we went for something that more closely mimics the way cross compilation is currently done with findlib and the opam-cross repos. I'm definitely in favor of bringing up the simplified, and in my opinion more natural setup.

@ghost
Copy link

ghost commented Apr 30, 2019

Well, it's also simplicity of use. It's nice to be able to just do dune build -x windows or add a simple (targets ...) field. In any case, it seems fine to allow the more flexible version as well for more advanced users

src/context.ml Outdated

let resolve_host_contexts contexts =
let empty = String.Map.empty in
let map = List.fold_left
Copy link

Choose a reason for hiding this comment

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

Suggested change
let map = List.fold_left
let map = Map.of_list_map_exn contexts ~f:(fun (_, (_, c)) -> c.name, c)

This makes the intent more explicit and the code more strict

src/context.ml Outdated
>>| List.concat
>>| resolve_host_contexts
Copy link

Choose a reason for hiding this comment

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

If I read this code correctly, we might create several Context.t values with the same name but not physically equal. Do you agree? Given that there are mutable things in Context.t and the Context.t record is quite big, we should make sure to share these values.

I suggest to topologically sort the Workspace.contexts field in Workspace.t, this will allow us to assume that when we resolve for_host here, the Context.t value has already been computed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, the only mutation that happens in resolve_host_contexts that might lead in a duplication of a Context.t is the for_host field update. But I think the case where it happens should be forbidden: it's the case where a sets b as host, and b sets c as host, and the order of resolution is a then b then c. In this case the context value of b duplicated during the resolve_host_context. I'm not sure this configuration is really useful: so instead of building a topological sort we can simply forbid this kind of host chain.
What do you think ?

Copy link

Choose a reason for hiding this comment

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

Forbidding configurations that are not good seems good. However, I feel like it would be easier for someone reading the code to convince themselves that Context.t values are never duplicated if {context with ...} is never explicitly written. Building a topological sort is quite easy with Top_closure BTW

Copy link
Collaborator

Choose a reason for hiding this comment

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

Given this:

let equal x y = String.equal x.name y.name

if there is any chance at all that different context values can end up with the same name, the equal function should change to use physical equality.

I think it was a mistake to make it use anything other than physical equality in the first place.

Copy link

Choose a reason for hiding this comment

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

Well, so far we have made sure to never create different context values with the same name, though it's true that we never enforced it programmatically. Comparing the names seems fine to me given that physically they can be only a single context with a given name, since each context correspond to a sub-directory in _build. To start with, we should make Context.t private, so that at least we only need to enforce this invariant in context.ml.

@avsm
Copy link
Member

avsm commented Apr 30, 2019

In this case, the mirage tool will generate the appropriate workspace files for the chosen backend, so this is not something that will be exposed to end users. It has the benefit that dune can parallelise builds across workspaces, which was the key limitation with using opam switch as mirage2 used to do.

@TheLortex
Copy link
Collaborator Author

@diml I've added the topological sort and the bad configuration check with some tests and little documentation.

@ghost
Copy link

ghost commented May 7, 2019

Thanks, however I'd feel more comfortable if we really didn't have {ctx with ...} in the code. It's easy enough to check things work correctly now, however it'd be pretty easy to forget about it and break this invariant in the future. What we can do instead is maintain a mapping of contexts as we create them, and look up the host context immediately. The topological sort would ensure that the lookup would never fail.

@rgrinberg
Copy link
Member

This PR should make it to 1.10 as well. I will try to review it this week.

@rgrinberg rgrinberg self-assigned this May 7, 2019
@TheLortex TheLortex force-pushed the custom-host-context branch 2 times, most recently from 22cfdac to d697943 Compare May 7, 2019 12:46
@TheLortex
Copy link
Collaborator Author

@diml Okay so now the topological sort is done earlier in order to avoid {ctx with ..} and directly build the mapping. The drawback is that Fiber.parallel_map cannot be used anymore, as there are dependencies between contexts, so I need to unfold sequentially the generated fibers: is that problematic ?
By the way I'm not sure what should happen if both a (host ..) and a (targets ..) option are used in the same context. This should probably fail

@ghost
Copy link

ghost commented May 7, 2019

@diml Okay so now the topological sort is done earlier in order to avoid {ctx with ..} and directly build the mapping. The drawback is that Fiber.parallel_map cannot be used anymore, as there are dependencies between contexts, so I need to unfold sequentially the generated fibers: is that problematic ?

Hmm, it seems reasonable to build the dependant contexts sequentially. However, independent one should be constructed in parallel.

By the way I'm not sure what should happen if both a (host ..) and a (targets ..) option are used in the same context. This should probably fail

Yep. We should fail right at parsing time.

@TheLortex TheLortex force-pushed the custom-host-context branch 2 times, most recently from 2e71431 to 9d4f3fa Compare May 7, 2019 16:11
@TheLortex
Copy link
Collaborator Author

Hmm, it seems reasonable to build the dependant contexts sequentially. However, independent one should be constructed in parallel.

Now the result of the topological sort is split into independent group of contexts and Fiber.parallel_map is used again.

Yep. We should fail right at parsing time.

Done!

I also added two more tests for error cases.

@rgrinberg
Copy link
Member

@TheLortex I've pushed some commits to clean things up. Please review.

@TheLortex
Copy link
Collaborator Author

TheLortex commented May 8, 2019

Looks good to me !

@ghost
Copy link

ghost commented May 8, 2019

I moved the checks to workspace.ml, it seems nicer to me to have such checks as soon as we parse the file.

@rgrinberg
Copy link
Member

Just to make sure I understand the interaction with the existing targets setup. We explicitly disallow for "target" contexts to be used as hosts for other contexts, and we also don't allow a context with targets to have a different host.

@TheLortex that's intended, right?

@rgrinberg
Copy link
Member

@diml does it also make sense to have a Workspace.Context.Name.t type? It could be reused between workspace and context to make things a little less stringly typed.

src/context.ml Outdated
match Workspace.Context.host_context elem, cur with
| None, [] -> (acc, [elem])
| None, _ -> ((List.rev cur) :: acc, [elem])
| Some _, _ -> (acc, elem :: cur)
Copy link

Choose a reason for hiding this comment

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

I'm finding it hard to understand the logic here. Another approach would be to add the following function to Fiber:

module type Memory = sig
  type key
  type 'a t
  val empty : 'a t
  val add : 'a t -> key -> 'a -> 'a t
  val find : 'a t -> key -> 'a option
end

(** [memoize memory f] returns a version of [f] that remembers the
    result of previous call to [f] in order to not recompute them. *)
val memoize
  :  (module Memory with type key := 'key)
  -> ('a -> 'b t)
  -> ('a -> 'b t) Staged.t

and then memoize the instantiation function before passing it to parallel_map.

Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking about this as well, but I'm wondering why do we need anything extra here. In the previous version of this feature, we managed without any topological sorting by doing something like this:

let rec contexts = lazy (
  List.map workspace.contexts ~f:(fun context ->
    let context =
      let* host = String.Map.find_exn (Lazy.force contexts) context.host in
      create_context ~context ~host
    in
    (context.name, context))
  |> String.Map.of_list_exn
) in
let* contexts = Lazy.force contexts |> String.Map.values |> Fiber.all in
...

Actually, I'm pretty sure we used a hashtable before, but I don't see why a map can't be used.

Copy link

Choose a reason for hiding this comment

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

Indeed, that even simpler. There is just one little detail: unlike lwt or async, Fiber.t values are not shared implicitly and need to be shared explicitly, so this code would end up doing the work twice for some contexts. It's easy to fix by using Fiber.Once.t though

Copy link

Choose a reason for hiding this comment

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

More precisely, you should think of Fiber.t values as one time use. Only Fiber.Ivar.t, Fiber.Future.t or Fiber.Once.t values can be shared and used multiple times.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, makes sense. When I wrote this originally, we still had our original Future.t abstraction.

There's just one little blemish here btw, since some contexts may have targets, they introduce more than one context per name. However, in the current code we cannot use targets as hosts for other contexts anyway, so it doesn't matter. But perhaps it would be cleaner if we could include the targets in the map anyways since those can be computed outside the Fiber.t regardless.

Copy link

Choose a reason for hiding this comment

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

Including the targets in the map seems good

@ghost
Copy link

ghost commented May 8, 2019

@rgrinberg yep, that makes sense. Although, if we want to be complete, that's a serious refactoring; think about Path.extract_build_context for instance... If we are not complete, that might make the code slightly worse.

@rgrinberg
Copy link
Member

Although, if we want to be complete, that's a serious refactoring; think about Path.extract_build_context for instance... If we are not complete, that might make the code slightly worse.

This would require us to move Context_name.t to Stdune. That seems hardly desirable. Perhaps we should move this function and others like it to a dune specialized Path module first?

As much as I would like to do this, I don't know of a good way to accomplish this. Adding src/path.ml would be nice, but then open Stdune would shadow it. The only workaround I see is using open Import instead of open Stdune. But then we'd have to re-export every module except for Path in Import manually. 😢

@ghost
Copy link

ghost commented May 8, 2019

A dune specialised path module would be nice indeed. We should simply use a different name to make things simpler. Maybe Dune_path or Dpath

@ghost
Copy link

ghost commented May 8, 2019

And in particular, Path.Build should move to this new module

@rgrinberg
Copy link
Member

@diml see the latest commit where I process all the contexts in parallel. The topological sorting isn't really required now, but I think it's still nice to have. In particular, it's nice to know all the context names have been validated.

@rgrinberg
Copy link
Member

If the commit is good, I will incorporate the target contexts into the map as well.

@ghost
Copy link

ghost commented May 8, 2019

The topological sort is still useful to get a good error message, otherwise we'd get Failure "Fiber.Once.get: recursive evaluation" which is a bit raw :)

@ghost
Copy link

ghost commented May 8, 2019

The commit looks good 👍

@rgrinberg
Copy link
Member

rgrinberg commented May 8, 2019

I think i'm going to leave the code as is for now. The targets are indeed included in the map, it's just they cannot be looked up as strings, which doesn't seem so bad. In the end, I think we just need to refactor the constructors for contexts with targets and a context with a host.

@rgrinberg rgrinberg force-pushed the custom-host-context branch 2 times, most recently from a31f62d to c692a71 Compare May 8, 2019 13:48
TheLortex and others added 3 commits May 8, 2019 21:49
Also allows to use (default) with different names

Signed-off-by: Lucas Pluvinage <lucas.pluvinage@gmail.com>
Signed-off-by: Jeremie Dimino <jeremie@dimino.org>
It also means that the topological sorting isn't rquired.

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
@rgrinberg rgrinberg merged commit da6108f into master May 8, 2019
@rgrinberg rgrinberg deleted the custom-host-context branch May 8, 2019 13:51
rgrinberg added a commit to rgrinberg/opam-repository that referenced this pull request May 30, 2019
CHANGES:

- Restricted the set of variables available for expansion in the destination
  filename of `install` stanza to simplify implementation and avoid dependency
  cycles. (ocaml/dune#2073, @aalekseyev, @diml)

- [menhir] call menhir from context root build_dir (ocaml/dune#2067, @ejgallego,
  review by @diml, @rgrinberg)

- [coq] Add `coq.pp` stanza to help with pre-processing of grammar
  files (ocaml/dune#2054, @ejgallego, review by @rgrinberg)

- Add a new more generic form for the *promote* mode: `(promote
  (until-clean) (into <dir>))` (ocaml/dune#2068, @diml)

- Allow to promote only a subset of the targets via `(promote (only
  <pred>))`. For instance: `(promote (only *.mli))` (ocaml/dune#2068, @diml)

- Improve the behavior when a strict subset of the targets of a rule is already
  in the source tree for projects using the dune language < 1.10 (ocaml/dune#2068, fixes
  ocaml/dune#2061, @diml)

- With lang dune >= 1.10, rules in standard mode are no longer allowed to
  produce targets that are present in the source tree. This has been a warning
  for long enough (ocaml/dune#2068, @diml)

- Allow %{...} variables in pps flags (ocaml/dune#2076, @mlasson review by @diml and
  @aalekseyev).

- Add a 'cookies' option to ppx_rewriter/deriver flags in library stanzas. This
  allow to specify cookie requests from variables expanded at each invocation of
  the preprocessor. (ocaml/dune#2106, @mlasson @diml)

- Add more opam metadata and use it to generate `.opam` files. In particular, a
  `package` field has been added to specify package specific information.
  (ocaml/dune#2017, ocaml/dune#2091, @avsm, @jonludlam, @rgrinberg)

- Clean up the special support for `findlib.dynload`. Before, Dune would simply
  match on the library name. Now, we only match on the findlib package name when
  the library doesn't come from Dune. Someone writing a library called
  `findlib.dynload` with Dune would have to add `(special_builtin_support
  findlib_dynload)` to trigger the special behavior. (ocaml/dune#2115, @diml)

- Install the `future_syntax` preprocessor as `ocaml-syntax-shims.exe` (ocaml/dune#2125,
  @rgrinberg)

- Hide full command on errors and warnings in development and show them in CI.
  (detected using the `CI` environment variable). Commands for which the
  invocation might be omitted must output an error prefixed with `File `. Add an
  `--always-show-command-line` option to disable this behavior and always show
  the full command. (ocaml/dune#2120, fixes ocaml/dune#1733, @rgrinberg)

- In `dune-workspace` files, add the ability to choose the host context and to
  create duplicates of the default context with different settings. (ocaml/dune#2098,
  @TheLortex, review by @diml, @rgrinberg and @aalekseyev)

- Add support for hg in `dune subst` (ocaml/dune#2135, @diml)

- Don't build documentation for implementations of virtual libraries (ocaml/dune#2141,
  fixes ocaml/dune#2138, @jonludlam)

- Fix generation of the `-pp` flag in .merlin (ocaml/dune#2142, @rgrinberg)

- Make `dune subst` add a `(version ...)` field to the `dune-project`
  file (ocaml/dune#2148, @diml)

- Add the `%{os_type}` variable, which is a short-hand for
  `%{ocaml-config:os_type}` (ocaml/dune#1764, @diml)

- Allow `enabled_if` fields in `library` stanzas, restricted to the
  `%{os_type}`, `%{model}`, `%{architecture}`, `%{system}` variables (ocaml/dune#1764,
  ocaml/dune#2164 @diml, @rgrinberg)

- Fix `chdir` on external and source paths. Dune will also fail gracefully if
  the external or source path does not exist (ocaml/dune#2165, fixes ocaml/dune#2158, @rgrinberg)

- Support the `.cc` extension fro C++ sources (ocaml/dune#2195, fixes ocaml/dune#83, @rgrinberg)

- Run `ocamlformat` relative to the context root. This improves the locations of
  errors. (ocaml/dune#2196, fixes ocaml/dune#1370, @rgrinberg)

- Fix detection of `README`, `LICENSE`, `CHANGE`, and `HISTORY` files. These
  would be undetected whenever the project was nested in another workspace.
  (ocaml/dune#2194, @rgrinberg)

- Fix generation of `.merlin` whenever there's more than one stanza with the
  same ppx preprocessing specification (ocaml/dune#2209 ,fixes ocaml/dune#2206, @rgrinberg)

- Fix generation of `.meriln` in the presence of the `copy_files` stanza and
  preprocessing specifications of other stanazs. (ocaml/dune#2211, fixes ocaml/dune#2206,
  @rgrinberg)
rgrinberg added a commit to rgrinberg/opam-repository that referenced this pull request Jun 4, 2019
CHANGES:

- Restricted the set of variables available for expansion in the destination
  filename of `install` stanza to simplify implementation and avoid dependency
  cycles. (ocaml/dune#2073, @aalekseyev, @diml)

- [menhir] call menhir from context root build_dir (ocaml/dune#2067, @ejgallego,
  review by @diml, @rgrinberg)

- [coq] Add `coq.pp` stanza to help with pre-processing of grammar
  files (ocaml/dune#2054, @ejgallego, review by @rgrinberg)

- Add a new more generic form for the *promote* mode: `(promote
  (until-clean) (into <dir>))` (ocaml/dune#2068, @diml)

- Allow to promote only a subset of the targets via `(promote (only
  <pred>))`. For instance: `(promote (only *.mli))` (ocaml/dune#2068, @diml)

- Improve the behavior when a strict subset of the targets of a rule is already
  in the source tree for projects using the dune language < 1.10 (ocaml/dune#2068, fixes
  ocaml/dune#2061, @diml)

- With lang dune >= 1.10, rules in standard mode are no longer allowed to
  produce targets that are present in the source tree. This has been a warning
  for long enough (ocaml/dune#2068, @diml)

- Allow %{...} variables in pps flags (ocaml/dune#2076, @mlasson review by @diml and
  @aalekseyev).

- Add a 'cookies' option to ppx_rewriter/deriver flags in library stanzas. This
  allow to specify cookie requests from variables expanded at each invocation of
  the preprocessor. (ocaml/dune#2106, @mlasson @diml)

- Add more opam metadata and use it to generate `.opam` files. In particular, a
  `package` field has been added to specify package specific information.
  (ocaml/dune#2017, ocaml/dune#2091, @avsm, @jonludlam, @rgrinberg)

- Clean up the special support for `findlib.dynload`. Before, Dune would simply
  match on the library name. Now, we only match on the findlib package name when
  the library doesn't come from Dune. Someone writing a library called
  `findlib.dynload` with Dune would have to add `(special_builtin_support
  findlib_dynload)` to trigger the special behavior. (ocaml/dune#2115, @diml)

- Install the `future_syntax` preprocessor as `ocaml-syntax-shims.exe` (ocaml/dune#2125,
  @rgrinberg)

- Hide full command on errors and warnings in development and show them in CI.
  (detected using the `CI` environment variable). Commands for which the
  invocation might be omitted must output an error prefixed with `File `. Add an
  `--always-show-command-line` option to disable this behavior and always show
  the full command. (ocaml/dune#2120, fixes ocaml/dune#1733, @rgrinberg)

- In `dune-workspace` files, add the ability to choose the host context and to
  create duplicates of the default context with different settings. (ocaml/dune#2098,
  @TheLortex, review by @diml, @rgrinberg and @aalekseyev)

- Add support for hg in `dune subst` (ocaml/dune#2135, @diml)

- Don't build documentation for implementations of virtual libraries (ocaml/dune#2141,
  fixes ocaml/dune#2138, @jonludlam)

- Fix generation of the `-pp` flag in .merlin (ocaml/dune#2142, @rgrinberg)

- Make `dune subst` add a `(version ...)` field to the `dune-project`
  file (ocaml/dune#2148, @diml)

- Add the `%{os_type}` variable, which is a short-hand for
  `%{ocaml-config:os_type}` (ocaml/dune#1764, @diml)

- Allow `enabled_if` fields in `library` stanzas, restricted to the
  `%{os_type}`, `%{model}`, `%{architecture}`, `%{system}` variables (ocaml/dune#1764,
  ocaml/dune#2164 @diml, @rgrinberg)

- Fix `chdir` on external and source paths. Dune will also fail gracefully if
  the external or source path does not exist (ocaml/dune#2165, fixes ocaml/dune#2158, @rgrinberg)

- Support the `.cc` extension fro C++ sources (ocaml/dune#2195, fixes ocaml/dune#83, @rgrinberg)

- Run `ocamlformat` relative to the context root. This improves the locations of
  errors. (ocaml/dune#2196, fixes ocaml/dune#1370, @rgrinberg)

- Fix detection of `README`, `LICENSE`, `CHANGE`, and `HISTORY` files. These
  would be undetected whenever the project was nested in another workspace.
  (ocaml/dune#2194, @rgrinberg)

- Fix generation of `.merlin` whenever there's more than one stanza with the
  same ppx preprocessing specification (ocaml/dune#2209 ,fixes ocaml/dune#2206, @rgrinberg)

- Fix generation of `.merlin` in the presence of the `copy_files` stanza and
  preprocessing specifications of other stanazs. (ocaml/dune#2211, fixes ocaml/dune#2206,
  @rgrinberg)

- Run `refmt` from the context's root directory. This improves error messages in
  case of syntax errors. (ocaml/dune#2223, @rgrinberg)

- In .merlin files, don't pass `-dump-ast` to the `future_syntax` preprocessor.
  Merlin doesn't seem to like it when binary AST is generated by a `-pp`
  preprocessor. (ocaml/dune#2236, @aalekseyev)

- `dune install` will verify that all files mentioned in all .install files
  exist before trying to install anything. This prevents partial installation of
  packages (ocaml/dune#2230, @rgrinberg)
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.

None yet

5 participants