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

Allow variables in pps flags #2076

Merged
1 commit merged into from
Apr 29, 2019
Merged

Conversation

mlasson
Copy link
Collaborator

@mlasson mlasson commented Apr 24, 2019

This PR propose to add support for variables in the flags of the "pps" (and "pps_staged") kind of preprocessing.

The use-case I have in mind would be to pass "%{profile}" to the preprocessor which would allow it to generate a different code depending on the profile. This would be useful for preprocessors that implement code instrumentation (eg. bisect, or landmarks): they could implement the "identity preprocessor" when profile is "release" (also they could use the profile name as a scoping mechanism).

Here is an example:

(executable
  (name test)
  (preprocess (pps landmarks.ppx -- --profile "%{profile}"))
)

I made this PR to experiment with a profile-based workflow for landmarks and maybe to see if others think it sounds like a good idea. So, do not hesitate to comment or to close the PR if it happens to be a bad idea.

@mlasson mlasson requested a review from aalekseyev as a code owner April 24, 2019 09:44
@mlasson mlasson force-pushed the mlasson-var-in-pps-flags branch from 5e2ca41 to d4472e9 Compare April 24, 2019 09:45
@nojb
Copy link
Collaborator

nojb commented Apr 24, 2019

Related to #57

@ghost
Copy link

ghost commented Apr 24, 2019

We had a similar discussion in #2020. My idea was that when declaring the ppx rewriter, we would declare what informations it wants. i.e. instead of passing --profile %{profile} everytime you use the ppx rewriter, you would instead declare once and for all when defining the landmarks.ppx rewriter that it wants to know the profile:

(library
 (public_name landmarks.ppx)
 (kind (ppx_rewriter (cookies (profile %{profile})))))

@Khady also mentioned that matching on the profile wasn't great. So I was thinking to add a new variable %{inline-tests} which could be set via env and take 3 values:

  • ignored: silently drop inline tests
  • disabled: keep them in the code, but make them dead code: if false then <test> to avoid warnings caused by deleting code
  • enabled

The default would be enabled except in the release profile where it would be ignored. We could then pass it to ppx_inline_test via (cookies (inline-tests %{inline-tests})). We could do something similar for instrumentation.

However, the current PR still seems good to me. It's nice to be able to use variables in the arguiments of ppx rewriters.

src/dune_file.ml Show resolved Hide resolved
src/merlin.ml Outdated
let compare_flags f1 f2 =
String.compare
(Expander.expand_str expander f1)
(Expander.expand_str expander f2)
Copy link

Choose a reason for hiding this comment

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

It seems that we are going to do the expansion several times in this file. We should either do the expansion in make, or simply comapre the string unexpanded here. The latter seems simpler and enough.

@mlasson mlasson requested a review from rgrinberg as a code owner April 24, 2019 12:46
@mlasson
Copy link
Collaborator Author

mlasson commented Apr 24, 2019

@diml Thanks for your input ! It seems indeed nicer to have it defined in the library stanzas.

I try to implement your suggestions. It was a bit boring to write the comparison functions; should I have used String.compare composed with "String_with_vars.to_string" ?

@ghost
Copy link

ghost commented Apr 24, 2019

The way you did it is good. Unfortunately, we do have to write these boring functions at the moment. However, we are thinking of setting up something based on [@@deriving_inline] in the future.

Could you add a changelog entry and a test for this feature? You can add some variables in the preprocess field of the test_ppx_args library in test/blackbox-tests/test-cases/dune-ppx-driver-system/driver-tests/dune.

BTW, I just noticed that variables are only supported after the --, however it is possible to pass arguments before that by prefixing them with -. For instance: (pps ppx1 -arg1 -- -arg2 x). It would seem more natural to me to support them in arguments before the -- as well, so that one may write -arg1=%{x}. Could you make tihs change? Elements starting with a variable should be treated as ppx rewriter names, and ppx rewriter names with variables should be rejected.

@mlasson
Copy link
Collaborator Author

mlasson commented Apr 25, 2019

Ok, I'll try to do that on the beginning of next week.

@mlasson mlasson force-pushed the mlasson-var-in-pps-flags branch 2 times, most recently from df4ea51 to 776a73f Compare April 29, 2019 12:29
(preprocess (staged_pps -arg1 driver_print_tool -arg2 -- -foo bar)))
(preprocess
(staged_pps -arg1 driver_print_tool -arg2 -arg3=%{env:SOME_UNDEFINED_VARIABLE=undefined}
-- -foo bar %{env:SOME_UNDEFINED_VARIABLE=undefined})
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note to the reviewer: I struggled to find a variable that is actually ... a constant in (almost) all context; that's why I used this "trick" ; the test will fail on machine that define a variable called "SOME_UNDEFINED_VARIABLE".

Copy link

Choose a reason for hiding this comment

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

Ack. I wondered about this actually. To avoid the last pitfall, you can add the following stanza in this dune file:

(env (_ (env-vars (X foo))))

src/dune_file.ml Outdated
in
let flags = Option.value flags ~default:[] in
if syntax_version < (1, 10) then
List.iter ~f:check_allowed_variable flags;
Copy link

Choose a reason for hiding this comment

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

Small nitpick: I would do the check right at the end, on the final more_flags @ flags list. That would make the code slightly shorter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok !

val string_of_var : var -> string

val pp : File_syntax.t -> Format.formatter -> t -> unit

val pp_split_strings : Format.formatter -> t -> unit

val remove_locs : t -> t
val is_prefix : t -> prefix:string -> bool
Copy link

Choose a reason for hiding this comment

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

The false result is slightly ambiguous here, as we don't know whether prefix is really not a prefix or wether we can't answer accurately because the string is not evaluated enough. I suggest to return something else that bool that can express this third state. The case where we don't know should be effectively handled the same way as false, but at least it's explicit when reading the code at the call site. For instance:

type is_prefix_result = Yes | No | Unknown

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Or I could rename the function into "is_prefix_before_expansion" ?

Note that the implementation of is_prefix is not trivial and could be replaced by the simpler

  let is_prefix {parts; _} ~prefix = function Text s :: _ -> String.is_prefix s ~prefix | [] | Var _ :: _ -> String.length prefix = 0

But it assumes that text parts are always interleaved with variables (as does the already existing "text_only" function). I actually think that this simpler implementation is better, 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.

is_prefix_before_expansion makes me think of testing is_prefix on the original string, i.e. is_prefix_before_expansion t ~prefix = String.is_prefix (to_string t) ~prefix.

But it assumes that text parts are always interleaved with variables (as does the already existing "text_only" function). I actually think that this simpler implementation is better, what do you think ?

Agreed. That seems better to me as well. The various functions are also careful about ensuring that we never produce Text x :: Text y or Text "".

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like the 3-way is_prefix_result better than trying to express this in the name.

Regarding canonical representation, I think it's better to not rely on canonical representation if we can help it.

Maybe a nice middle ground could be a function like this:

let get_known_prefix = 
  let rec go t acc = match t with
    | Text s :: rest -> go (s :: acc) rest
    | _ -> String.concat ~sep:"" (List.rev acc)
  in
  fun t -> go t []

(or perhaps a variation that distinguishes fully known string from partially unknown)

In fact I need something like this (for the suffix) in #2073.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FYI, the is_prefix that assume the canonical representation would be:

type is_prefix_result = Yes | No | Unknown
let is_prefix t ~prefix =
  match t.template_.parts with
  | [Text s] -> if String.is_prefix s ~prefix then Yes else No
  | Text s :: _ ->
    if String.is_prefix s ~prefix then
      Yes
    else
      if String.is_prefix prefix ~prefix:s then
        Unknown
      else No
  | [] -> if String.length prefix = 0 then Yes else No
  | Var _ :: _ -> if String.length prefix = 0 then Yes else Unknown

I think I like the "get_known_prefix" better.

in
(pps, more_flags @ Option.value flags ~default:[])
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

( Btw, the final flags are "swapped": when we write "-arg1 -arg2 -- arg3 arg4" in the dune file the preprocessor will read "arg3 arg4 -arg1 -arg2" ; was it on purpose ? )

Copy link

Choose a reason for hiding this comment

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

Hmm, no that does look like a bug.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I actually misread the order is right.

@mlasson mlasson force-pushed the mlasson-var-in-pps-flags branch from 5663510 to b09ee23 Compare April 29, 2019 14:42
This commit adds support for variables in the flags of
the "pps" (and "pps_staged") kind of preprocessing.

This allows, for instance, to pass %{profile},
%{context_name}, or environment variables as preprocessor
argument.

Signed-off-by: Marc Lasson <marc.lasson@lexifi.com>
@mlasson mlasson force-pushed the mlasson-var-in-pps-flags branch from b09ee23 to c3e0c58 Compare April 29, 2019 15:02
@mlasson
Copy link
Collaborator Author

mlasson commented Apr 29, 2019

It should be good now.

@mlasson mlasson changed the title Allow variables in pps flags (after the --) Allow variables in pps flags Apr 29, 2019
@ghost ghost merged commit 4c169ee into ocaml:master Apr 29, 2019
@ghost
Copy link

ghost commented Apr 29, 2019

Looks good, thanks for this PR!

@ghost
Copy link

ghost commented Apr 30, 2019

BTW, would you be happy to help with #2020? :)

I started the work in the branch ppx-cookies of github.com/diml/dune, but I have a few other things to finish on my todo list before I come back to this feature. So far I implemented the parsing of the new syntax, what's left to do is collecting the cookies, instantiating them and passing them to the driver in src/preprocessing.ml.

@mlasson
Copy link
Collaborator Author

mlasson commented Apr 30, 2019

I'll see what I can do ! (but no promise; as I have my own todo list to go through :)).

@ghost
Copy link

ghost commented Apr 30, 2019

Sounds good :)

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)
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.

3 participants