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

Use deep merge for :compiler-options #135

Open
zlorf opened this issue Sep 7, 2016 · 11 comments
Open

Use deep merge for :compiler-options #135

zlorf opened this issue Sep 7, 2016 · 11 comments
Milestone

Comments

@zlorf
Copy link

zlorf commented Sep 7, 2016

Using deep merge for :compiler-options would allow to merge :closure-defines from task definition and .cljs.edn files instead of replacing one by another.

It'd be especially usefull for :parallel-build.

@Deraen
Copy link
Contributor

Deraen commented Sep 7, 2016

I'm somewhat conflicted about using deep-merge here, it can potentially make it harder to undefine some options for production, which are set as default for development. We can probably prevent this with some log messages.

I wonder if we should manually define the merge strategy per option?

  • :foreign-libs - into
  • :externs - into
  • :preloads - into or replace because sometimes it can be necessary to reset the preloads option
  • :modules - merge? or replace
  • :warnings merge
  • :libs into
  • :preamble into or replace
  • :closure-warnings merge
  • :closure-defines merge
  • :closure-extra-annotations ?

Leiningen allows using ^:replace metadata per value to control how data is merged. Those are not available to us as we are using edn files as one source of data. Though those could maybe be used with the task options.

@zlorf
Copy link
Author

zlorf commented Sep 7, 2016

Leiningen-like hints like ^:replace would be great and sufficient, and it will allow to either merge or replace (or undefine) each option.
And since task options take precedence (override) edn options (since last PR), it's only meaningful to use ^:replace in tasks (since edn don't overwrite anything). And that's exactly what we want.

@Deraen
Copy link
Contributor

Deraen commented Sep 7, 2016

A small problem is that the metadata can't be provided when using command line options. But I guess that doesn't matter, if user needs to use metadata, one can add the options to build.boot.

@zlorf
Copy link
Author

zlorf commented Sep 7, 2016

Agreed.
To maintain current behaviour, the 'replace' strategy should be a default one, and 'merging' should be triggered by a metadata keyword, something like ^:join or ^:merge (it'd be the opposite way of Leiningen, where merging is default).

@Deraen
Copy link
Contributor

Deraen commented Sep 7, 2016

The change to option precedence already broke backwards compatibility so I don't think it would be too bad to use merge as default for maps. I think replace would be better default for sequence like types, but I guess it would be good to check leiningen.

@kennyjwilli
Copy link

@micha and I had a conversation about this last month. See https://clojurians-log.clojureverse.org/boot/2016-08-04.html. The conclusion we drew was:

The conclusion I have drawn is that tasks will generally not have a complex map for specifying options. If the task does need a map to specify options it should be set in a task-specific file that can be overridden by task-options. If the task-specific file is overridden by task options, it should be completely overridden so you don't introduce any additional complexity of merging.

@zlorf
Copy link
Author

zlorf commented Sep 8, 2016

@kennyjwilli So I will provide a counter-example for your conclusion, based on my current need. It is something like you've described in discussion, but connected to :closure-defines.
I have one codebase that gets compiled into browser script and node script. There is goog-define flag called nodejs? that is being set (in :compiler-options :closure-defines) to false in browser.cljs.edn and to true in node.cljs.edn. This way advanced optimizations cut the dead code for every (if nodejs? (do-something-on-node-only) (do-something-in-browser-only)). So far so good.
build.boot contains only one to call (cljs :optimizations :advanced :compiler-options {:parallel-build true}) that produce two scripts based on their edn files.
However, I wanted to introduce a new goog-define flag (let's call it test-environment?) that would depend on some config file (e.g. dev/production) and trigger another "conditional compilation" via dead code elimination. But if I use it like (cljs :optimizations :advanced :compiler-options {:parallel-build true :closure-defines {"foo.bar.test_environment_Q_MARK" <true/false - value read from config file>}}), then then nodejs? flag is not set, because :closure-defines in edn files were overwritten.
I cannot read config files in edn, and splitting cljs task in build.boot into two separate tasks just to set nodejs? flag seems wrong.
So what I'd like is a possibility to combine task-specific (in terms of edn file) :closure-defines with another :closure-defines that are related to selected config file.

I'd like to do something like (deftask doit [] (comp (cljs) (cljs-compiler-options :update #(update-in % [:closure-defines] into ['foo.bar "asdf"])))) if merging doesn't happen by default, but it seems that it is not possible now.

@kennyjwilli
Copy link

From what I understand (correct me if I'm wrong), merging maps in boot-cljs is not as simple as it sounds because it wraps each property passed to the compiler. I believe it could be done but it would take a significant amount of work.

Anyway, your use case is almost identical to the one I have and I still believe the update-file option would work for you. I should have also mentioned how we wanted to make this easier for boot users going forward by adding this ability to the built in task sift. This would allow you to regex match a file and get the content passed, as a stream, to a handler function, also taking an output stream. You would write the new contents of the matched file to the output stream and the cljs task would pick up that file later in the pipeline. Of course you probably don't want to be messing with streams when your goal is simple -- you just want to update the map data structure in an edn file -- so we would provide handy macros for this sort of thing (e.g. with-edn).

The task I wrote as a starting place is located here.

@zlorf
Copy link
Author

zlorf commented Sep 8, 2016

Merging maps shouldn't be tricky: I think it's just a matter of replacing merge by merge-with https://github.com/adzerk-oss/boot-cljs/blob/master/src/adzerk/boot_cljs/middleware.clj#L43

Thank you for the update-file code; I'll give it a try, but for now it seems to be able to resolve my use case.

@zlorf
Copy link
Author

zlorf commented Sep 9, 2016

@kennyjwilli It seems to be working, however I needed to add make-parents call (proper comment in the gist).

@kennyjwilli
Copy link

Thanks. This will be added to to the sift task in the end.

@Deraen Deraen added this to the 2.1.0 milestone Apr 3, 2017
@Deraen Deraen modified the milestones: 2.2.0, 2.1.0 May 14, 2017
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

No branches or pull requests

3 participants