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

Reason support #58

Merged
16 commits merged into from
Apr 20, 2017
Merged

Reason support #58

16 commits merged into from
Apr 20, 2017

Conversation

rgrinberg
Copy link
Member

@rgrinberg rgrinberg commented Apr 18, 2017

Attempt at #53

@diml I've left the renaming commit separate at the end so you can judge if you want it or not.

@jordwalke @IwanKaramazow some testing would be appreciated.

Add transparent support for reason sources. This requires adding a flag to
Module.t to distinguish reason and and ocaml sources.
Module.t's ml_fname and mli_fname are no longer good names since Module.t
can contain reason modules as well. impl_fname and intf_fname are better names
@rgrinberg rgrinberg requested a review from a user April 18, 2017 16:27
src/gen_rules.ml Outdated
let ml = Module.ocaml_of_reason re in
let refmt =
match Context.which ctx "refmt" with
| None -> die "refmt not found. Fix with $ opam install reason"
Copy link

Choose a reason for hiding this comment

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

There is a function Utils.program_not_found to raise a standard error, could you use it instead? Additionally, to match other messages, it should print:

Hint: opam install reason

you can add a ?hint argument to Util.program_not_found to do that.

Another issue is that the current code will fail when setting up the rules instead of when evaluating them, which for instance makes external-lib-deps unusable. You can do this instead:

| None -> Build.Prog_spec.Dyn (fun _ -> <raise exception>)
| Some refmt -> Dep refmt

src/gen_rules.ml Outdated
(* Generate rules for the reason modules in [modules] and return a list
a new list of modules with only OCaml sources *)
let reason_rules ~dir ~modules =
let rule re =
Copy link

Choose a reason for hiding this comment

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

This should be called rules given that it returns several rules. Alternatively it could also be called setup_rules and call add_rule directly

src/gen_rules.ml Outdated
; obj_name = lib.name ^ suf
}
Some (Module.create ~name:(main_module_name ^ suf)
~impl_fname:(lib.name ^ suf ^ ".ml-gen")
Copy link

Choose a reason for hiding this comment

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

Won't Module.create choke on the .ml-gen extension? I think it would be simpler to just add an is_reason:bool argument to Module.create

Copy link
Member Author

Choose a reason for hiding this comment

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

I think at that point create becomes useless so I'll just remove it.

src/gen_rules.ml Outdated
| ".re" -> (ml, mli, f :: re, rei)
| ".rei" -> (ml, mli, rei, f :: rei)
| "" -> (ml, mli, re, rei)
| f -> invalid_arg ("Unexpected extension from: " ^ f))
Copy link

Choose a reason for hiding this comment

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

guess_modules is called with all the files in the directory as argument, so it should simply ignore extensions it doesn't recognize.

BTW, the previous code used String.lsplit2 ~on:'.' rather than Filename.extension. This was intentional, it is to filter out filenames such as foo.cppo.ml

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 I'll leave a comment about this fact too

src/gen_rules.ml Outdated
| Some _, Some _ -> assert false
)
else
die "The following modules have both reason and ocaml sources:@.%a"
Copy link

Choose a reason for hiding this comment

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

I don't think this is going to work really well. For instance, you might want to write a .rei file for a generated lexer. Then you have a module with a reason interface and a ml implementation.

I think it's fine to just keep 2 lists as before, but add a flag to each element telling whether it is reason or ocaml. The existing checks will catch cases when there are both a .ml and .re files

Copy link
Member Author

Choose a reason for hiding this comment

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

For instance, you might want to write a .rei file for a generated lexer. Then you have a module with a reason interface and a ml implementation

So in this situation a per module reason flag doesn't even make sense anymore. Since we'd allow to mix and match OCaml/Reason interfaces/implemenations? If so, where would this flag exist? Seems like the type of source could be discovered from the intf_fname and impl_fname

Would let to get this and the comment below straight before I make the changes.

Copy link

Choose a reason for hiding this comment

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

Indeed, we could just look at the extension, but I think it would be cleaner to keep separated flags in [impl_fname] and [intf_fname]. We could change the types in module.mli as follow:

module Syntax : sig
  type t = OCaml | Reason
end

module File : sig
  type t =
    { name   : string
    ; syntax : Syntax.t
    }
end

type t =
  { name : string
  ; impl : File.t
  ; intf : File.t option
  ...

src/gen_rules.ml Outdated
let preprocessor_deps = Dep_conf_interpret.dep_of_list ~dir preprocessor_deps in
String_map.map modules ~f:(fun (m : Module.t) ->
assert (not m.reason);
Copy link

Choose a reason for hiding this comment

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

Instead of doing two successive String_map.map, we could replace this line by:

let m = ocaml_of_reason m in

src/module.ml Outdated
if t.reason then
{ t with
reason = false
; impl_fname = Filename.chop_extension t.impl_fname ^ ".ml"
Copy link

Choose a reason for hiding this comment

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

Actually we should just keep the two extensions, i.e. the filename would be: foo.re.ml. The reason for this is that otherwise the generation of the rules is not a fix point: initially guess_modules picks up only foo.re, but if run again it would pickup foo.re and foo.ml. It doesn't matter right now, but it might for the jenga bridge. It might also matter for the implementation of #35.

src/module.ml Outdated
~f:(fun mli -> Filename.chop_extension mli ^ ".mli")
}
else
invalid_arg "ocaml_of_reason only expect reason sources"
Copy link

Choose a reason for hiding this comment

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

I tend to use code_errorf (defined in import.ml). The Code_error exception is caught in main.ml and a better error message is printed. It should also include the function name

@ghost
Copy link

ghost commented Apr 18, 2017

Thanks. Renaming looks good, let's keep it

@rgrinberg
Copy link
Member Author

ok @diml I've tried to address all of your points. Every new commit tries to address one of your points separately except for the last few which tie-in everything together.

I didn't quite get your suggestion as how to avoid the use of 2 successive String_map.map but I succeeded in getting rid of it anyway. Is that what you meant?

The new rules now also allow to mix & match reason/ocaml
interfaces/implementations
src/module.ml Outdated
| Intf -> Option.map t.mli_fname ~f:(fun _ -> Path.relative dir (t.obj_name ^ ".cmti"))
| Intf -> Option.map t.intf ~f:(fun _ -> Path.relative dir (t.obj_name ^ ".cmti"))

let ocaml_of_reason _t =
Copy link

Choose a reason for hiding this comment

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

This is no longer needed

@ghost
Copy link

ghost commented Apr 19, 2017

Yep, that's what I meant. Code looks good, appart from the leftover function it's ready to be merged

@rgrinberg
Copy link
Member Author

Should we let jordan and co. test it out first?

src/module.mli Outdated
@@ -16,3 +30,5 @@ val file : t -> dir:Path.t -> Ml_kind.t -> Path.t option
val cm_source : t -> dir:Path.t -> Cm_kind.t -> Path.t option
val cm_file : t -> dir:Path.t -> Cm_kind.t -> Path.t
val cmt_file : t -> dir:Path.t -> Ml_kind.t -> Path.t option

val ocaml_of_reason : t -> t
Copy link

Choose a reason for hiding this comment

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

And this one as well

@ghost
Copy link

ghost commented Apr 19, 2017

Sure, our we can test it ourselves. BTW I just though of an improvement, instead of the Context.which ctx "refmt", we could use Artifacts.binary "refmt". The difference is that it would allow to use jbuilder for reason itself, since jbuilder would then use the local refmt binary if it is part of the current workspace.

@rgrinberg
Copy link
Member Author

rgrinberg commented Apr 19, 2017

Yup. I also tested this, but I don't really have any real reason projects to try it on. They can try this out at their own leisure I suppose.

I'll make that change to use Artifacts.

Copy link
Contributor

@jordwalke jordwalke left a comment

Choose a reason for hiding this comment

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

Wow, thank you @rgrinberg.

a new module with only OCaml sources *)
let setup_reason_rules ~dir (m : Module.t) =
let refmt =
match Artifacts.binary "refmt" with
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm, will this work if refmt is a symlink? It very well might be. Nice touch with the hint to install via opam.

Copy link

Choose a reason for hiding this comment

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

Yes. Basically it is the same as writing ${bin:refmt} in a user rule in a jbuild file:

  • if refmt is a tool that is installed by the project being compiled, then jbuilder will use resolve ${bin:refmt} to the local binary
  • otherwise it is looked up in the PATH

Basically this changes nothing for users of reason. What it changes is that if you wanted to use jbuilder to build reason itself, then you could build the tests/examples directly without having to install reason first.

@ghost
Copy link

ghost commented Apr 20, 2017

Ok, well the patch looks ready to me and has been tested so I'm merging now

@ghost ghost merged commit 66f973c into ocaml:master Apr 20, 2017
@rgrinberg rgrinberg deleted the reason branch April 20, 2017 15:50
@jordwalke
Copy link
Contributor

Thanks, everyone! Can't wait to try it out.

This pull request was closed.
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.

2 participants