From ccb9e9a81af479e31507c07bf9174654cce6c258 Mon Sep 17 00:00:00 2001 From: hhugo Date: Sun, 4 Aug 2024 17:17:48 +0200 Subject: [PATCH] Jsoo: make dune aware of sourcemap generation (#10777) * Jsoo: new test case for jsoo * Jsoo: make dune aware of sourcemap * fix parsing of sourcemap Signed-off-by: Hugo Heuzard --- doc/changes/10777.md | 5 + doc/jsoo.rst | 19 ++- doc/reference/dune/env.rst | 3 + doc/reference/dune/executable.rst | 12 +- src/dune_rules/jsoo/js_of_ocaml.ml | 126 +++++++++++++----- src/dune_rules/jsoo/js_of_ocaml.mli | 26 +++- src/dune_rules/jsoo/jsoo_rules.ml | 53 +++++++- src/dune_rules/stanzas/buildable.ml | 7 +- test/blackbox-tests/test-cases/jsoo/env.t | 7 +- .../test-cases/jsoo/sourcemap.t/bin/dune | 6 + .../test-cases/jsoo/sourcemap.t/bin/main.ml | 2 + .../test-cases/jsoo/sourcemap.t/dune-project | 1 + .../test-cases/jsoo/sourcemap.t/lib/dune | 2 + .../test-cases/jsoo/sourcemap.t/lib/mylib.ml | 1 + .../test-cases/jsoo/sourcemap.t/run.t | 11 ++ 15 files changed, 224 insertions(+), 57 deletions(-) create mode 100644 doc/changes/10777.md create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/dune create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/main.ml create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/dune-project create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/dune create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/mylib.ml create mode 100644 test/blackbox-tests/test-cases/jsoo/sourcemap.t/run.t diff --git a/doc/changes/10777.md b/doc/changes/10777.md new file mode 100644 index 00000000000..a079f594ba7 --- /dev/null +++ b/doc/changes/10777.md @@ -0,0 +1,5 @@ +- New option to control jsoo sourcemap generation in env and executable stanza + (@hhugo, #10777) + +- One can now control jsoo compilation_mode inside an executable stanza + (@hhugo, #10777) diff --git a/doc/jsoo.rst b/doc/jsoo.rst index fb651121a22..89337f7986c 100644 --- a/doc/jsoo.rst +++ b/doc/jsoo.rst @@ -18,7 +18,7 @@ translating OCaml bytecode to JS files. The compiler can be installed with opam: Compiling to JS =============== -Dune has full support building Js_of_ocaml libraries and executables transparently. +Dune has full support building js_of_ocaml libraries and executables transparently. There's no need to customize or enable anything to compile OCaml libraries/executables to JS. @@ -46,7 +46,7 @@ And then request the ``.js`` target: Similar targets are created for libraries, but we recommend sticking to the executable targets. -If you're using the Js_of_ocaml syntax extension, you must remember to add the +If you're using the js_of_ocaml syntax extension, you must remember to add the appropriate PPX in the ``preprocess`` field: .. code:: dune @@ -62,7 +62,7 @@ Separate Compilation Dune supports two modes of compilation: - Direct compilation of a bytecode program to JavaScript. This mode allows - Js_of_ocaml to perform whole-program deadcode elimination and whole-program + js_of_ocaml to perform whole-program deadcode elimination and whole-program inlining. - Separate compilation, where compilation units are compiled to JavaScript @@ -71,7 +71,16 @@ Dune supports two modes of compilation: The separate compilation mode will be selected when the build profile is ``dev``, which is the default. It can also be explicitly specified -in an ``env`` stanza. See :doc:`/reference/dune/env` for more -information. +in an ``env`` stanza (see :doc:`/reference/dune/env`) or per executable +inside ``(js_of_ocaml (compilation_mode ...))`` (see :doc:`/reference/dune/executable`) + +Sourcemap +========= + +Js_of_ocaml can generate sourcemap for the generated JavaScript file. +It can either embed it at the end of the ``.js`` file or write it to separate file. +By default, it is inlined when using the ``dev`` build profile and is not generated otherwise. +The behavior can explicitly be specified in an ``env`` stanza (see :doc:`/reference/dune/env`) +or per executable inside ``(js_of_ocaml (sourcemap ...))`` (see :doc:`/reference/dune/executable`) .. _js_of_ocaml: http://ocsigen.org/js_of_ocaml/ diff --git a/doc/reference/dune/env.rst b/doc/reference/dune/env.rst index 64f2a896800..cb44d06e17d 100644 --- a/doc/reference/dune/env.rst +++ b/doc/reference/dune/env.rst @@ -45,6 +45,9 @@ Fields supported in ```` are: compilation or not where ```` is either ``whole_program`` or ``separate``. +- ``(js_of_ocaml (sourcemap ))`` controls whether to generate sourcemap + or not where ```` is either ``no``, ``file`` (to generate sourcemap in a ``.map`` file next the the generated javascript file) or ``inline`` (to inline the sourcemap at the end of the generated JavaScript file). + - ``(js_of_ocaml (runtest_alias ))`` specifies the alias under which :ref:`inline_tests` and tests (:ref:`tests-stanza`) run for the `js` mode. diff --git a/doc/reference/dune/executable.rst b/doc/reference/dune/executable.rst index b9129d0e380..0ac01b4ec7a 100644 --- a/doc/reference/dune/executable.rst +++ b/doc/reference/dune/executable.rst @@ -242,7 +242,7 @@ contains C stubs you may want to use ``(modes exe)``. js_of_ocaml ~~~~~~~~~~~ -In ``library`` and ``executables`` stanzas, you can specify ``js_of_ocaml`` +In ``library`` and ``executable`` stanzas, you can specify ``js_of_ocaml`` options using ``(js_of_ocaml ())``. ```` are all optional: @@ -259,10 +259,16 @@ options using ``(js_of_ocaml ())``. - ``(javascript_files ())`` to specify ``js_of_ocaml`` JavaScript runtime files. +- ``(compilation_mode )`` where ``>`` is either ``whole_program`` or ``separate``. + This is only available inside ``executable`` stanzas. + +- ``(sourcemap )`` where ``>`` is one of ``no``, ``file`` or ``inline``. + This is only available inside ``executable`` stanzas. + ```` is specified in the :doc:`/reference/ordered-set-language`. -The default value for ``(flags ...)`` depends on the selected build profile. The -build profile ``dev`` (the default) will enable sourcemap and the pretty +The default values for ``flags``, ``compilation_mode`` and ``sourcemap`` depend on the selected build profile. The +build profile ``dev`` (the default) will enable inline sourcemap, separate compilation and pretty JavaScript output. See :ref:`jsoo` for more information. diff --git a/src/dune_rules/jsoo/js_of_ocaml.ml b/src/dune_rules/jsoo/js_of_ocaml.ml index ecec2eab3f3..3cfbf0457ab 100644 --- a/src/dune_rules/jsoo/js_of_ocaml.ml +++ b/src/dune_rules/jsoo/js_of_ocaml.ml @@ -12,6 +12,23 @@ end let field_oslu name = Ordered_set_lang.Unexpanded.field name +module Sourcemap = struct + type t = + | No + | Inline + | File + + let decode = enum [ "no", No; "inline", Inline; "file", File ] + + let equal x y = + match x, y with + | No, No -> true + | Inline, Inline -> true + | File, File -> true + | No, _ | Inline, _ | File, _ -> false + ;; +end + module Flags = struct type 'flags t = { build_runtime : 'flags @@ -38,11 +55,7 @@ module Flags = struct let default ~profile = if Profile.is_dev profile - then - { build_runtime = [ "--pretty"; "--source-map-inline" ] - ; compile = [ "--pretty"; "--source-map-inline" ] - ; link = [ "--source-map-inline" ] - } + then { build_runtime = [ "--pretty" ]; compile = [ "--pretty" ]; link = [] } else empty ;; @@ -87,13 +100,31 @@ module Flags = struct ;; end +module Compilation_mode = struct + type t = + | Whole_program + | Separate_compilation + + let decode = enum [ "whole_program", Whole_program; "separate", Separate_compilation ] + + let equal x y = + match x, y with + | Separate_compilation, Separate_compilation -> true + | Whole_program, Whole_program -> true + | Separate_compilation, _ -> false + | Whole_program, _ -> false + ;; +end + module In_buildable = struct type t = { flags : Ordered_set_lang.Unexpanded.t Flags.t ; javascript_files : string list + ; compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option } - let decode = + let decode ~executable = let* syntax_version = Dune_lang.Syntax.get_exn Stanza.syntax in if syntax_version < (3, 0) then @@ -106,52 +137,70 @@ module In_buildable = struct ; link = flags (* we set link as well to preserve the old semantic *) } ; javascript_files + ; compilation_mode = None + ; sourcemap = None }) else fields (let+ flags = Flags.decode - and+ javascript_files = field "javascript_files" (repeat string) ~default:[] in - { flags; javascript_files }) + and+ javascript_files = field "javascript_files" (repeat string) ~default:[] + and+ compilation_mode = + if executable + then + field_o + "compilation_mode" + (Dune_lang.Syntax.since Stanza.syntax (3, 17) >>> Compilation_mode.decode) + else return None + and+ sourcemap = + if executable + then + field_o + "sourcemap" + (Dune_lang.Syntax.since Stanza.syntax (3, 17) >>> Sourcemap.decode) + else return None + in + { flags; javascript_files; compilation_mode; sourcemap }) ;; - let default = { flags = Flags.standard; javascript_files = [] } + let default = + { flags = Flags.standard + ; javascript_files = [] + ; compilation_mode = None + ; sourcemap = None + } + ;; end module In_context = struct type t = { flags : Ordered_set_lang.Unexpanded.t Flags.t ; javascript_files : Path.Build.t list + ; compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option } let make ~(dir : Path.Build.t) (x : In_buildable.t) = { flags = x.flags ; javascript_files = List.map ~f:(fun name -> Path.Build.relative dir name) x.javascript_files + ; compilation_mode = x.compilation_mode + ; sourcemap = x.sourcemap } ;; - let default = { flags = Flags.standard; javascript_files = [] } -end - -module Compilation_mode = struct - type t = - | Whole_program - | Separate_compilation - - let decode = enum [ "whole_program", Whole_program; "separate", Separate_compilation ] - - let equal x y = - match x, y with - | Separate_compilation, Separate_compilation -> true - | Whole_program, Whole_program -> true - | Separate_compilation, _ -> false - | Whole_program, _ -> false + let default = + { flags = Flags.standard + ; javascript_files = [] + ; compilation_mode = None + ; sourcemap = None + } ;; end module Env = struct type 'a t = { compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option ; runtest_alias : Alias.Name.t option ; flags : 'a Flags.t } @@ -159,25 +208,40 @@ module Env = struct let decode = fields @@ let+ compilation_mode = field_o "compilation_mode" Compilation_mode.decode + and+ sourcemap = + field_o + "sourcemap" + (Dune_lang.Syntax.since Stanza.syntax (3, 17) >>> Sourcemap.decode) and+ runtest_alias = field_o "runtest_alias" Dune_lang.Alias.decode and+ flags = Flags.decode in Option.iter ~f:Alias.register_as_standard runtest_alias; - { compilation_mode; runtest_alias; flags } + { compilation_mode; sourcemap; runtest_alias; flags } ;; - let equal { compilation_mode; runtest_alias; flags } t = + let equal { compilation_mode; sourcemap; runtest_alias; flags } t = Option.equal Compilation_mode.equal compilation_mode t.compilation_mode + && Option.equal Sourcemap.equal sourcemap t.sourcemap && Option.equal Alias.Name.equal runtest_alias t.runtest_alias && Flags.equal Ordered_set_lang.Unexpanded.equal flags t.flags ;; - let map ~f { compilation_mode; runtest_alias; flags } = - { compilation_mode; runtest_alias; flags = Flags.map ~f flags } + let map ~f { compilation_mode; sourcemap; runtest_alias; flags } = + { compilation_mode; sourcemap; runtest_alias; flags = Flags.map ~f flags } ;; - let empty = { compilation_mode = None; runtest_alias = None; flags = Flags.standard } + let empty = + { compilation_mode = None + ; sourcemap = None + ; runtest_alias = None + ; flags = Flags.standard + } + ;; let default ~profile = - { compilation_mode = None; runtest_alias = None; flags = Flags.default ~profile } + { compilation_mode = None + ; sourcemap = None + ; runtest_alias = None + ; flags = Flags.default ~profile + } ;; end diff --git a/src/dune_rules/jsoo/js_of_ocaml.mli b/src/dune_rules/jsoo/js_of_ocaml.mli index 2d69d7e4b40..0716f35ebde 100644 --- a/src/dune_rules/jsoo/js_of_ocaml.mli +++ b/src/dune_rules/jsoo/js_of_ocaml.mli @@ -38,13 +38,28 @@ module Flags : sig val dump : string list Action_builder.t t -> Dune_lang.t list Action_builder.t end +module Sourcemap : sig + type t = + | No + | Inline + | File +end + +module Compilation_mode : sig + type t = + | Whole_program + | Separate_compilation +end + module In_buildable : sig type t = { flags : Flags.Spec.t ; javascript_files : string list + ; compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option } - val decode : t Dune_lang.Decoder.t + val decode : executable:bool -> t Dune_lang.Decoder.t val default : t end @@ -52,21 +67,18 @@ module In_context : sig type t = { flags : Flags.Spec.t ; javascript_files : Path.Build.t list + ; compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option } val make : dir:Path.Build.t -> In_buildable.t -> t val default : t end -module Compilation_mode : sig - type t = - | Whole_program - | Separate_compilation -end - module Env : sig type 'a t = { compilation_mode : Compilation_mode.t option + ; sourcemap : Sourcemap.t option ; runtest_alias : Alias.Name.t option ; flags : 'a Flags.t } diff --git a/src/dune_rules/jsoo/jsoo_rules.ml b/src/dune_rules/jsoo/jsoo_rules.ml index 90c97dbb47b..52f7941c75f 100644 --- a/src/dune_rules/jsoo/jsoo_rules.ml +++ b/src/dune_rules/jsoo/jsoo_rules.ml @@ -13,6 +13,7 @@ let jsoo_env = let+ parent = parent in { Js_of_ocaml.Env.compilation_mode = Option.first_some local.compilation_mode parent.compilation_mode + ; sourcemap = Option.first_some local.sourcemap parent.sourcemap ; runtest_alias = Option.first_some local.runtest_alias parent.runtest_alias ; flags = Js_of_ocaml.Flags.make @@ -219,6 +220,7 @@ let js_of_ocaml_rule ~config ~spec ~target + ~sourcemap = let open Action_builder.O in let jsoo = jsoo ~dir sctx in @@ -236,6 +238,14 @@ let js_of_ocaml_rule | Compile -> S [] | Link -> A "link" | Build_runtime -> A "build-runtime") + ; (match (sourcemap : Js_of_ocaml.Sourcemap.t) with + | No -> A "--no-source-map" + | Inline -> A "--source-map-inline" + | File -> + S + [ A "--source-map" + ; Hidden_targets [ Path.Build.set_extension target ~ext:".map" ] + ]) ; Command.Args.dyn flags ; (match config with | None -> S [] @@ -380,10 +390,10 @@ let link_rule cc ~runtime ~target ~obj_dir cm ~flags ~linkall ~link_time_code_ge js_of_ocaml_rule sctx ~sub_command:Link ~dir ~spec ~target ~flags ~config:None ;; -let build_cm' sctx ~dir ~in_context ~src ~target ~config = +let build_cm' sctx ~dir ~in_context ~src ~target ~config ~sourcemap = let spec = Command.Args.Dep src in let flags = in_context.Js_of_ocaml.In_context.flags in - js_of_ocaml_rule sctx ~sub_command:Compile ~dir ~flags ~spec ~target ~config + js_of_ocaml_rule sctx ~sub_command:Compile ~dir ~flags ~spec ~target ~config ~sourcemap ;; let build_cm sctx ~dir ~in_context ~src ~obj_dir ~config = @@ -396,6 +406,7 @@ let build_cm sctx ~dir ~in_context ~src ~obj_dir ~config = ~src ~target ~config:(Option.map config ~f:Action_builder.return) + ~sourcemap:Js_of_ocaml.Sourcemap.Inline ;; let setup_separate_compilation_rules sctx components = @@ -433,6 +444,8 @@ let setup_separate_compilation_rules sctx components = let in_context = { Js_of_ocaml.In_context.flags = Js_of_ocaml.Flags.standard ; javascript_files = [] + ; compilation_mode = None + ; sourcemap = None } in let src = @@ -447,6 +460,7 @@ let setup_separate_compilation_rules sctx components = ~src ~target ~config:(Some (Action_builder.return config)) + ~sourcemap:Js_of_ocaml.Sourcemap.Inline |> Super_context.add_rule sctx ~dir)) ;; @@ -461,6 +475,17 @@ let js_of_ocaml_compilation_mode t ~dir = else Whole_program ;; +let js_of_ocaml_sourcemap t ~dir = + let open Memo.O in + let+ js_of_ocaml = jsoo_env ~dir in + match js_of_ocaml.sourcemap with + | Some sm -> sm + | None -> + if Super_context.context t |> Context.profile |> Profile.is_dev + then Js_of_ocaml.Sourcemap.Inline + else No +;; + let build_exe cc ~loc @@ -474,7 +499,9 @@ let build_exe = let sctx = Compilation_context.super_context cc in let dir = Compilation_context.dir cc in - let { Js_of_ocaml.In_context.javascript_files; flags } = in_context in + let { Js_of_ocaml.In_context.javascript_files; flags; compilation_mode; sourcemap } = + in_context + in let target = Path.Build.set_extension src ~ext:Js_of_ocaml.Ext.exe in let standalone_runtime = in_obj_dir @@ -488,11 +515,24 @@ let build_exe | Some p -> Promote p in let open Memo.O in - let* cmode = js_of_ocaml_compilation_mode sctx ~dir in + let* cmode = + match compilation_mode with + | None -> js_of_ocaml_compilation_mode sctx ~dir + | Some x -> Memo.return x + and* sourcemap = + match sourcemap with + | None -> js_of_ocaml_sourcemap sctx ~dir + | Some x -> Memo.return x + in match (cmode : Js_of_ocaml.Compilation_mode.t) with | Separate_compilation -> let+ () = - standalone_runtime_rule cc ~javascript_files ~target:standalone_runtime ~flags + standalone_runtime_rule + cc + ~javascript_files + ~target:standalone_runtime + ~flags + ~sourcemap:Js_of_ocaml.Sourcemap.Inline |> Super_context.add_rule ~loc sctx ~dir and+ () = link_rule @@ -504,11 +544,12 @@ let build_exe ~flags ~linkall ~link_time_code_gen + ~sourcemap |> Super_context.add_rule sctx ~loc ~dir ~mode in () | Whole_program -> - exe_rule cc ~javascript_files ~src ~target ~flags + exe_rule cc ~javascript_files ~src ~target ~flags ~sourcemap |> Super_context.add_rule sctx ~loc ~dir ~mode ;; diff --git a/src/dune_rules/stanzas/buildable.ml b/src/dune_rules/stanzas/buildable.ml index 4285dc81295..d4768cc49a4 100644 --- a/src/dune_rules/stanzas/buildable.ml +++ b/src/dune_rules/stanzas/buildable.ml @@ -92,9 +92,14 @@ let decode (for_ : for_) = field "libraries" (Lib_dep.L.decode ~allow_re_export) ~default:[] and+ flags = Ocaml_flags.Spec.decode and+ js_of_ocaml = + let executable = + match for_ with + | Executable -> true + | Library _ -> false + in field "js_of_ocaml" - Js_of_ocaml.In_buildable.decode + (Js_of_ocaml.In_buildable.decode ~executable) ~default:Js_of_ocaml.In_buildable.default and+ allow_overlapping_dependencies = field_b "allow_overlapping_dependencies" and+ version = Dune_lang.Syntax.get_exn Stanza.syntax diff --git a/test/blackbox-tests/test-cases/jsoo/env.t b/test/blackbox-tests/test-cases/jsoo/env.t index dc71ab0f21f..c544cf8eb61 100644 --- a/test/blackbox-tests/test-cases/jsoo/env.t +++ b/test/blackbox-tests/test-cases/jsoo/env.t @@ -7,7 +7,6 @@ > EOF $ dune printenv --field js_of_ocaml_flags --field js_of_ocaml_link_flags --field js_of_ocaml_build_runtime_flags 2>&1 (js_of_ocaml_flags - (--pretty --source-map-inline --no-inline)) - (js_of_ocaml_build_runtime_flags - (--pretty --source-map-inline)) - (js_of_ocaml_link_flags (--source-map-inline)) + (--pretty --no-inline)) + (js_of_ocaml_build_runtime_flags (--pretty)) + (js_of_ocaml_link_flags ()) diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/dune b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/dune new file mode 100644 index 00000000000..d32c88f4362 --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/dune @@ -0,0 +1,6 @@ +(executable + (name main) + (libraries mylib) + (modes js) + (js_of_ocaml + (sourcemap file))) diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/main.ml b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/main.ml new file mode 100644 index 00000000000..944dd132c6f --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/bin/main.ml @@ -0,0 +1,2 @@ +let a,b = 1,2 +let () = Printf.printf "%d + %d = %d\n%!" a b (Mylib.add a b) diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/dune-project b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/dune-project new file mode 100644 index 00000000000..6af72e563d5 --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/dune-project @@ -0,0 +1 @@ +(lang dune 3.17 ) \ No newline at end of file diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/dune b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/dune new file mode 100644 index 00000000000..aa003582cae --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/dune @@ -0,0 +1,2 @@ +(library + (name mylib)) diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/mylib.ml b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/mylib.ml new file mode 100644 index 00000000000..f04575bae8e --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/lib/mylib.ml @@ -0,0 +1 @@ +let add a b = a + b diff --git a/test/blackbox-tests/test-cases/jsoo/sourcemap.t/run.t b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/run.t new file mode 100644 index 00000000000..a2f9658c3e9 --- /dev/null +++ b/test/blackbox-tests/test-cases/jsoo/sourcemap.t/run.t @@ -0,0 +1,11 @@ + + $ dune build bin/main.bc.js + + $ ls _build/default/bin + main.bc.js + main.bc.map + main.ml + main.mli + + $ dune clean + $ dune build bin/main.bc.map