[WIP] Load rules lazily (#370)

* Change jbuilder to load rules lazily

Rules are now loaded on a per directory basis as needed. This speed up
the start up time on large workspaces.

Does various refactoring as well.

* Simplify the handling of META files

We no longer generate a META.foo.from-jbuilder file. Nobody is using
this feature and it's making the new code more complicated.
This commit is contained in:
Jérémie Dimino 2018-01-19 08:50:06 +00:00 committed by GitHub
parent 1c8ca6718c
commit 9dd5ab74e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1891 additions and 1405 deletions

View File

@ -56,6 +56,15 @@ next
ppx_driver. This allows to use `[@@deriving_inline]` in .ml/.mli ppx_driver. This allows to use `[@@deriving_inline]` in .ml/.mli
files. This require `ppx_driver >= v0.10.2` to work properly (#415) files. This require `ppx_driver >= v0.10.2` to work properly (#415)
- Make jbuilder load rules lazily instead of generating them all
eagerly. This speeds up the initial startup time of jbuilder on big
workspaces (#370)
- Now longer generate a `META.pkg.from-jbuilder` file. Now the only
way to customise the generated `META` file is through
`META.pkg.template`. This feature was unused and was making the code
complicated (#370)
1.0+beta16 (05/11/2017) 1.0+beta16 (05/11/2017)
----------------------- -----------------------

View File

@ -24,6 +24,7 @@ type common =
; x : string option ; x : string option
; diff_command : string option ; diff_command : string option
; auto_promote : bool ; auto_promote : bool
; force : bool
; (* Original arguments for the external-lib-deps hint *) ; (* Original arguments for the external-lib-deps hint *)
orig_args : string list orig_args : string list
} }
@ -43,6 +44,7 @@ let set_common c ~targets =
Clflags.workspace_root := Sys.getcwd (); Clflags.workspace_root := Sys.getcwd ();
Clflags.diff_command := c.diff_command; Clflags.diff_command := c.diff_command;
Clflags.auto_promote := c.auto_promote; Clflags.auto_promote := c.auto_promote;
Clflags.force := c.force;
Clflags.external_lib_deps_hint := Clflags.external_lib_deps_hint :=
List.concat List.concat
[ ["jbuilder"; "external-lib-deps"; "--missing"] [ ["jbuilder"; "external-lib-deps"; "--missing"]
@ -73,10 +75,9 @@ let restore_cwd_and_execve common prog argv env =
module Main = struct module Main = struct
include Jbuilder.Main include Jbuilder.Main
let setup ~log ?unlink_aliases ?filter_out_optional_stanzas_with_missing_deps common = let setup ~log ?filter_out_optional_stanzas_with_missing_deps common =
setup setup
~log ~log
?unlink_aliases
?workspace_file:common.workspace_file ?workspace_file:common.workspace_file
?only_packages:common.only_packages ?only_packages:common.only_packages
?filter_out_optional_stanzas_with_missing_deps ?filter_out_optional_stanzas_with_missing_deps
@ -86,17 +87,29 @@ end
type target = type target =
| File of Path.t | File of Path.t
| Alias_rec of Alias.t | Alias_rec of Path.t
let request_of_targets (setup : Main.setup) targets = let request_of_targets (setup : Main.setup) targets =
let open Build.O in let open Build.O in
let contexts = List.map setup.contexts ~f:(fun c -> c.Context.name) in
List.fold_left targets ~init:(Build.return ()) ~f:(fun acc target -> List.fold_left targets ~init:(Build.return ()) ~f:(fun acc target ->
acc >>> acc >>>
match target with match target with
| File path -> Build.path path | File path -> Build.path path
| Alias_rec alias -> | Alias_rec path ->
Alias.dep_rec ~loc:(Loc.in_file "<command-line>") let dir = Path.parent path in
~file_tree:setup.file_tree alias) let name = Path.basename path in
let contexts, dir =
match Path.extract_build_context dir with
| None -> (contexts, dir)
| Some ("install", _) ->
die "Invalid alias: %s.\n\
There are no aliases in _build/install."
(Path.to_string_maybe_quoted path)
| Some (ctx, dir) -> ([ctx], dir)
in
Build_system.Alias.dep_rec_multi_contexts ~dir ~name
~file_tree:setup.file_tree ~contexts)
let do_build (setup : Main.setup) targets = let do_build (setup : Main.setup) targets =
Build_system.do_build_exn setup.build_system Build_system.do_build_exn setup.build_system
@ -162,6 +175,7 @@ let common =
workspace_file workspace_file
diff_command diff_command
auto_promote auto_promote
force
(root, only_packages, orig) (root, only_packages, orig)
x x
= =
@ -190,6 +204,7 @@ let common =
; target_prefix = String.concat ~sep:"" (List.map to_cwd ~f:(sprintf "%s/")) ; target_prefix = String.concat ~sep:"" (List.map to_cwd ~f:(sprintf "%s/"))
; diff_command ; diff_command
; auto_promote ; auto_promote
; force
; only_packages = ; only_packages =
Option.map only_packages Option.map only_packages
~f:(fun s -> String_set.of_list (String.split s ~on:',')) ~f:(fun s -> String_set.of_list (String.split s ~on:','))
@ -287,6 +302,13 @@ let common =
~doc:"Automatically promote files. This is similar to running ~doc:"Automatically promote files. This is similar to running
$(b,jbuilder promote) after the build.") $(b,jbuilder promote) after the build.")
in in
let force =
Arg.(value
& flag
& info ["force"; "f"]
~doc:"Force actions associated to aliases to be re-executed even
if their dependencies haven't changed.")
in
let for_release = "for-release-of-packages" in let for_release = "for-release-of-packages" in
let frop = let frop =
Arg.(value Arg.(value
@ -349,6 +371,7 @@ let common =
$ workspace_file $ workspace_file
$ diff_command $ diff_command
$ auto_promote $ auto_promote
$ force
$ root_and_only_packages $ root_and_only_packages
$ x $ x
) )
@ -423,21 +446,43 @@ let target_hint (setup : Main.setup) path =
let candidates = String_set.of_list candidates |> String_set.elements in let candidates = String_set.of_list candidates |> String_set.elements in
hint (Path.to_string path) candidates hint (Path.to_string path) candidates
let check_path contexts =
let contexts = String_set.of_list (List.map contexts ~f:(fun c -> c.Context.name)) in
fun path ->
let internal path =
die "This path is internal to jbuilder: %s" (Path.to_string_maybe_quoted path)
in
if Path.is_in_build_dir path then
match Path.extract_build_context path with
| None -> internal path
| Some (name, _) ->
if name = "" || name.[0] = '.' then internal path;
if not (name = "install" || String_set.mem name contexts) then
die "%s refers to unknown build context: %s%s"
(Path.to_string_maybe_quoted path)
name
(hint name (String_set.elements contexts))
let resolve_targets ~log common (setup : Main.setup) user_targets = let resolve_targets ~log common (setup : Main.setup) user_targets =
match user_targets with match user_targets with
| [] -> [] | [] -> []
| _ -> | _ ->
let check_path = check_path setup.contexts in
let targets = let targets =
List.map user_targets ~f:(fun s -> List.map user_targets ~f:(fun s ->
if String.is_prefix s ~prefix:"@" then if String.is_prefix s ~prefix:"@" then begin
let s = String.sub s ~pos:1 ~len:(String.length s - 1) in let s = String.sub s ~pos:1 ~len:(String.length s - 1) in
let path = Path.relative Path.root (prefix_target common s) in let path = Path.relative Path.root (prefix_target common s) in
check_path path;
if Path.is_root path then if Path.is_root path then
die "@@ on the command line must be followed by a valid alias name" die "@@ on the command line must be followed by a valid alias name"
else if not (Path.is_local path) then
die "@@ on the command line must be followed by a relative path"
else else
Ok [Alias_rec (Alias.of_path path)] Ok [Alias_rec path]
else end else begin
let path = Path.relative Path.root (prefix_target common s) in let path = Path.relative Path.root (prefix_target common s) in
check_path path;
let can't_build path = let can't_build path =
Error (path, target_hint setup path); Error (path, target_hint setup path);
in in
@ -450,23 +495,17 @@ let resolve_targets ~log common (setup : Main.setup) user_targets =
can't_build path can't_build path
end else end else
match match
let l = List.filter_map setup.contexts ~f:(fun ctx ->
List.filter_map setup.contexts ~f:(fun ctx -> let path = Path.append ctx.Context.build_dir path in
let path = Path.append ctx.Context.build_dir path in if Build_system.is_target setup.build_system path then
if Build_system.is_target setup.build_system path then Some (File path)
Some (File path) else
else None)
None)
in
if Build_system.is_target setup.build_system path ||
Path.exists path then
File path :: l
else
l
with with
| [] -> can't_build path | [] -> can't_build path
| l -> Ok l | l -> Ok l
) end
)
in in
if !Clflags.verbose then begin if !Clflags.verbose then begin
Log.info log "Actual targets:"; Log.info log "Actual targets:";
@ -477,8 +516,7 @@ let resolve_targets ~log common (setup : Main.setup) user_targets =
List.iter targets ~f:(function List.iter targets ~f:(function
| File path -> | File path ->
Log.info log @@ "- " ^ (Path.to_string path) Log.info log @@ "- " ^ (Path.to_string path)
| Alias_rec alias -> | Alias_rec path ->
let path = Alias.fully_qualified_name alias in
Log.info log @@ "- recursive alias " ^ Log.info log @@ "- recursive alias " ^
(Path.to_string_maybe_quoted path)); (Path.to_string_maybe_quoted path));
flush stdout; flush stdout;
@ -524,8 +562,7 @@ let runtest =
] ]
in in
let name_ = Arg.info [] ~docv:"DIR" in let name_ = Arg.info [] ~docv:"DIR" in
let go common force dirs = let go common dirs =
let unlink_aliases = if force then Some ["runtest"] else None in
set_common common set_common common
~targets:(List.map dirs ~f:(function ~targets:(List.map dirs ~f:(function
| "" | "." -> "@runtest" | "" | "." -> "@runtest"
@ -533,16 +570,17 @@ let runtest =
| dir -> sprintf "@%s/runtest" dir)); | dir -> sprintf "@%s/runtest" dir));
let log = Log.create () in let log = Log.create () in
Future.Scheduler.go ~log Future.Scheduler.go ~log
(Main.setup ?unlink_aliases ~log common >>= fun setup -> (Main.setup ~log common >>= fun setup ->
let check_path = check_path setup.contexts in
let targets = let targets =
List.map dirs ~f:(fun dir -> List.map dirs ~f:(fun dir ->
let dir = Path.(relative root) (prefix_target common dir) in let dir = Path.(relative root) (prefix_target common dir) in
Alias_rec (Alias.runtest ~dir)) check_path dir;
Alias_rec (Path.relative dir "runtest"))
in in
do_build setup targets) in do_build setup targets) in
( Term.(const go ( Term.(const go
$ common $ common
$ Arg.(value & flag & info ["force"; "f"])
$ Arg.(value & pos_all string ["."] name_)) $ Arg.(value & pos_all string ["."] name_))
, Term.info "runtest" ~doc ~man) , Term.info "runtest" ~doc ~man)
@ -557,7 +595,8 @@ let clean =
let go common = let go common =
begin begin
set_common common ~targets:[]; set_common common ~targets:[];
Build_system.all_targets_ever_built () |> List.iter ~f:Path.unlink_no_err; Build_system.files_in_source_tree_to_delete ()
|> List.iter ~f:Path.unlink_no_err;
Path.(rm_rf (append root (of_string "_build"))) Path.(rm_rf (append root (of_string "_build")))
end end
in in
@ -928,7 +967,7 @@ let exec =
| [] -> () | [] -> ()
| targets -> | targets ->
Future.Scheduler.go ~log (do_build setup targets); Future.Scheduler.go ~log (do_build setup targets);
Build_system.dump_trace setup.build_system Build_system.finalize setup.build_system
end; end;
match prog_where with match prog_where with
| `Search prog -> | `Search prog ->
@ -1061,7 +1100,7 @@ let utop =
do_build setup [File target] >>| fun () -> do_build setup [File target] >>| fun () ->
(setup.build_system, context, Path.to_string target) (setup.build_system, context, Path.to_string target)
) |> Future.Scheduler.go ~log in ) |> Future.Scheduler.go ~log in
Build_system.dump_trace build_system; Build_system.finalize build_system;
restore_cwd_and_execve common utop_path (Array.of_list (utop_path :: args)) restore_cwd_and_execve common utop_path (Array.of_list (utop_path :: args))
(Context.env_for_exec context) (Context.env_for_exec context)
in in

View File

@ -16,26 +16,19 @@ of a project to Jbuilder, it is allowed to write/generate a specific
one. one.
In order to do that, write or setup a rule to generate a In order to do that, write or setup a rule to generate a
``META.<package>`` file in the same directory as the ``<package>.opam`` ``META.<package>.template`` file in the same directory as the
file. If you do that, Jbuilder will still generate a ``META`` file but ``<package>.opam`` file. Jbuilder will generate a ``META.<package>``
it will be called ``META.<package>.from-jbuilder``. So for instance if file from the ``META.<package>.template`` file by replacing lines of
you want to extend the ``META`` file generated by Jbuilder you can the form ``# JBUILDER_GEN`` by the contents of the ``META`` it would
write: normally generate.
.. code:: scheme For instance if you want to extend the ``META`` file generated by
Jbuilder you can write the folliwing ``META.foo.template`` file:
(rule .. code::
((targets (META.foo))
(deps (META.foo.from-jbuilder))
(action (with-stdout-to ${@}
(progn
(cat ${<})
(echo blah))))))
Additionally, Jbuilder provides a simpler mechanism for this scheme: # JBUILDER_GEN
just write or generate a ``META.<package>.template`` file containing a blah = "..."
line of the form ``# JBUILDER_GEN``. Jbuilder will automatically insert
its generated ``META`` contents in place of this line.
.. _custom-driver: .. _custom-driver:

View File

@ -91,14 +91,13 @@ the command line.
Resolution Resolution
---------- ----------
Most targets that Jbuilder knows how to build lives in the ``_build`` directory, All targets that Jbuilder knows how to build live in the ``_build``
except for a few: directory. Although, some are sometimes copied to the source tree for
the need of external tools. These includes:
= ``.merlin`` files - ``.merlin`` files
- ``<package>.install`` files; for the ``default`` context Jbuilder knows how - ``<package>.install`` files
generate the install file both in ``_build/default`` and in the source tree
so that ``opam`` can find it
As a result, if you want to ask ``jbuilder`` to produce a particular ``.exe`` As a result, if you want to ask ``jbuilder`` to produce a particular ``.exe``
file you would have to type: file you would have to type:
@ -107,14 +106,15 @@ file you would have to type:
$ jbuilder build _build/default/bin/prog.exe $ jbuilder build _build/default/bin/prog.exe
However, for convenience when a target on the command line doesn't start with However, for convenience when a target on the command line doesn't
``_build``, ``jbuilder`` will expand it to the corresponding target in all the start with ``_build``, ``jbuilder`` will expand it to the
build contexts where it knows how to build it. It prints out the actual set of corresponding target in all the build contexts where it knows how to
targets when starting so that you know what is happening: build it. When using ``--verbose``, It prints out the actual set of
targets when starting:
.. code:: bash .. code:: bash
$ jbuilder build bin/prog.exe $ jbuilder build bin/prog.exe --verbose
... ...
Actual targets: Actual targets:
- _build/default/bin/prog.exe - _build/default/bin/prog.exe
@ -126,11 +126,11 @@ Aliases
Targets starting with a ``@`` are interpreted as aliases. For instance Targets starting with a ``@`` are interpreted as aliases. For instance
``@src/runtest`` means the alias ``runtest`` in all descendant of ``@src/runtest`` means the alias ``runtest`` in all descendant of
``src`` where it is defined. If you want to refer to a target starting ``src`` in all build contexts where it is defined. If you want to
with a ``@``, simply write: ``./@foo``. refer to a target starting with a ``@``, simply write: ``./@foo``.
Note that an alias not pointing to the ``_build`` directory always To build and run the tests for a particular build context, use
depends on all the corresponding aliases in build contexts. ``@_build/default/runtest`` instead.
So for instance: So for instance:

View File

@ -795,7 +795,7 @@ let rec exec t ~ectx ~dir ~env_extra ~stdout_to ~stderr_to =
exec_echo stdout_to s exec_echo stdout_to s
| Diff { optional; file1; file2 } -> | Diff { optional; file1; file2 } ->
if (optional && not (Path.exists file1 && Path.exists file2)) || if (optional && not (Path.exists file1 && Path.exists file2)) ||
Io.read_file (Path.to_string file1) = Io.read_file (Path.to_string file2) then Io.compare_files (Path.to_string file1) (Path.to_string file2) = 0 then
return () return ()
else begin else begin
let is_copied_from_source_tree file = let is_copied_from_source_tree file =

View File

@ -1,210 +0,0 @@
open! Import
(** Fully qualified name *)
module Fq_name : sig
type t
val pp : Format.formatter -> t -> unit
val make : Path.t -> t
val path : t -> Path.t
end = struct
type t = Path.t
let make t = t
let path t = t
let pp = Path.pp
end
type t =
{ name : Fq_name.t
; file : Path.t
}
let pp fmt t =
Format.fprintf fmt "@[<2>{ name@ =@ %a@ ;@ file@ =@ %a }@]"
Path.pp (Fq_name.path t.name) Path.pp t.file
let aliases_path = Path.(relative root) "_build/.aliases"
let suffix = "-" ^ String.make 32 '0'
let of_path path =
if not (Path.is_local path) then
die "Aliases are only supported for local paths!\n\
Tried to reference alias %S"
(Path.to_string path);
{ name = Fq_name.make path
; file = Path.extend_basename (Path.append aliases_path path) ~suffix
}
let name t = Path.basename (Fq_name.path t.name)
let dir t = Path.parent (Fq_name.path t.name)
let fully_qualified_name t = Fq_name.path t.name
let make name ~dir =
assert (not (String.contains name '/'));
of_path (Path.relative dir name)
let dep t = Build.path t.file
let is_standard = function
| "runtest" | "install" | "doc" | "lint" -> true
| _ -> false
let dep_rec ~loc ~file_tree t =
let path = Path.parent (Fq_name.path t.name) |> Path.drop_optional_build_context in
let name = Path.basename (Fq_name.path t.name) in
match File_tree.find_dir file_tree path with
| None -> Build.fail { fail = fun () ->
Loc.fail loc "Don't know about directory %s!" (Path.to_string_maybe_quoted path) }
| Some dir ->
let open Build.O in
File_tree.Dir.fold dir ~traverse_ignored_dirs:false ~init:(Build.return true)
~f:(fun dir acc ->
let path = File_tree.Dir.path dir in
let t = of_path (Path.relative path name) in
acc
>>>
Build.if_file_exists t.file
~then_:(Build.path t.file
>>^
fun _ -> false)
~else_:(Build.arr (fun x -> x)))
>>^ fun is_empty ->
if is_empty && not (is_standard name) then
Loc.fail loc "This alias is empty.\n\
Alias %S is not defined in %s or any of its descendants."
name (Path.to_string_maybe_quoted path)
let file t = t.file
let file_with_digest_suffix t ~digest =
let dir = Path.parent t.file in
let base = Path.basename t.file in
let len = String.length base in
Path.relative dir
(String.sub base ~pos:0 ~len:(len - 32) ^ Digest.to_hex digest)
let of_file fn =
match Path.extract_build_context fn with
| Some (".aliases", fn) -> begin
let dir = Path.parent fn in
let name = Path.basename fn in
match String.rsplit2 name ~on:'-' with
| None -> assert false
| Some (name, digest) ->
assert (String.length digest = 32);
Some (make name ~dir)
end
| _ -> None
let name_of_file fn =
match Path.extract_build_context fn with
| Some (".aliases", fn) -> begin
let name = Path.basename fn in
match String.rsplit2 name ~on:'-' with
| None -> assert false
| Some (name, digest) ->
assert (String.length digest = 32);
Some name
end
| _ -> None
let default = make "DEFAULT"
let runtest = make "runtest"
let install = make "install"
let doc = make "doc"
let lint = make "lint"
module Store = struct
type entry =
{ alias : t
; mutable deps : Path.Set.t
}
let pp_entry fmt entry =
let pp_deps fmt deps =
Format.pp_print_list Path.pp fmt (Path.Set.elements deps) in
Format.fprintf fmt "@[<2>{@ alias@ =@ %a@ ;@ deps@ = (%a)@ }@]"
pp entry.alias pp_deps entry.deps
type t = (Fq_name.t, entry) Hashtbl.t
let pp fmt (t : t) =
let bindings = Hashtbl.fold ~init:[] ~f:(fun ~key ~data acc ->
(key, data)::acc
) t in
let pp_bindings fmt b =
Format.pp_print_list (fun fmt (k, v) ->
Format.fprintf fmt "@[<2>(%a@ %a)@]" Fq_name.pp k pp_entry v
) fmt b in
Format.fprintf fmt "Store.t@ @[@<2>(%a)@]" pp_bindings bindings
let create () = Hashtbl.create 1024
let unlink (store : t) = function
| [] -> ()
| alias_basenames ->
store
|> Hashtbl.fold ~init:Path.Set.empty ~f:(fun ~key:_ ~data:entry acc ->
if List.mem (name entry.alias) ~set:alias_basenames then (
Path.Set.union acc (Path.Set.add entry.alias.file entry.deps)
) else (
acc
))
|> Path.Set.iter ~f:Path.unlink_no_err
end
let add_deps store t deps =
let deps = Path.Set.of_list deps in
match Hashtbl.find store t.name with
| None ->
Hashtbl.add store ~key:t.name
~data:{ Store.alias = t
; deps = deps
}
| Some e -> e.deps <- Path.Set.union deps e.deps
let rules store =
(* For each alias @_build/blah/../x, add a dependency: @../x --> @_build/blah/../x *)
Hashtbl.fold store ~init:[] ~f:(fun ~key:_ ~data:{ Store. alias; _ } acc ->
match Path.extract_build_context (Fq_name.path alias.name) with
| None -> acc
| Some (_, in_src) -> (of_path in_src, alias) :: acc)
|> List.iter ~f:(fun (in_src, in_build_dir) ->
add_deps store in_src [in_build_dir.file]);
Hashtbl.fold store ~init:[] ~f:(fun ~key:_ ~data:{ Store. alias; deps } acc ->
let open Build.O in
let rule =
Build_interpret.Rule.make
(Build.path_set deps >>>
Build.action ~targets:[alias.file]
(Redirect (Stdout,
alias.file,
Digest_files
(Path.Set.elements deps))))
in
rule :: acc)
let add_build store t ~stamp build =
let digest = Digest.string (Sexp.to_string stamp) in
let digest_path = file_with_digest_suffix t ~digest in
add_deps store t [digest_path];
Build.progn
[ build
; Build.create_file digest_path
]
let add_builds store t builds =
let digest_files, actions =
List.split
(List.map builds ~f:(fun (stamp, build) ->
let digest = Digest.string (Sexp.to_string stamp) in
let digest_path = file_with_digest_suffix t ~digest in
(digest_path,
Build.progn
[ build
; Build.create_file digest_path
])))
in
add_deps store t digest_files;
actions

View File

@ -1,95 +0,0 @@
(** Rule aliases. *)
open Import
type t
val pp : t Fmt.t
val make : string -> dir:Path.t -> t
val of_path : Path.t -> t
(** The following always holds:
{[
make (name t) ~dir:(dir t) = t
]}
*)
val name : t -> string
val dir : t -> Path.t
val fully_qualified_name : t -> Path.t
val default : dir:Path.t -> t
val runtest : dir:Path.t -> t
val install : dir:Path.t -> t
val doc : dir:Path.t -> t
val lint : dir:Path.t -> t
val dep : t -> ('a, 'a) Build.t
(** Implements [(alias_rec ...)] in dependency specification and
[@alias] on the command line. *)
val dep_rec : loc:Loc.t -> file_tree:File_tree.t -> t -> (unit, unit) Build.t
(** File that represent the alias in the filesystem. It is a file under
[_build/.aliases]. *)
val file : t -> Path.t
(** Same as [file t], except that it sets the digest suffix to [digest]. Files
representing aliases ends with a hex-encoded md5sum of some data. It is usually filled
with zeros except for files that represent the running of an action associated to an
alias, it which case it is the md5 checksum of the action and its dependencies. *)
val file_with_digest_suffix : t -> digest:Digest.t -> Path.t
(** The following holds for any path [p]:
{[
match of_file p with
| None -> true
| Some t -> p = file t
]}
*)
val of_file : Path.t -> t option
(** Same as [Option.map (of_file p) ~f:name] but more efficient. *)
val name_of_file : Path.t -> string option
module Store : sig
type t
val pp : t Fmt.t
val create : unit -> t
val unlink : t -> string list -> unit
end
(** [add_build store alias deps] arrange things so that all [deps] are built as part of
the build of alias [alias]. *)
val add_deps : Store.t -> t -> Path.t list -> unit
(** [add_build store alias ~stamp build] arrange things so that [build] is part of the
build of alias [alias]. [stamp] is any S-expression that is unique and persistent
S-expression.
Return a rule that must be added with [Super_context.add_rule].
*)
val add_build
: Store.t
-> t
-> stamp:Sexp.t
-> (unit, Action.t) Build.t
-> (unit, Action.t) Build.t
(** Same as calling [add_build] in a loop but slightly more efficient. *)
val add_builds
: Store.t
-> t
-> (Sexp.t * (unit, Action.t) Build.t) list
-> (unit, Action.t) Build.t list
val rules : Store.t -> Build_interpret.Rule.t list

View File

@ -26,7 +26,7 @@ module Static_deps = struct
} }
end end
let static_deps t ~all_targets_by_dir = let static_deps t ~all_targets =
let rec loop : type a b. (a, b) t -> Static_deps.t -> Static_deps.t = fun t acc -> let rec loop : type a b. (a, b) t -> Static_deps.t -> Static_deps.t = fun t acc ->
match t with match t with
| Arr _ -> acc | Arr _ -> acc
@ -43,30 +43,32 @@ let static_deps t ~all_targets_by_dir =
| G_evaluated l -> | G_evaluated l ->
{ acc with action_deps = Pset.union acc.action_deps (Pset.of_list l) } { acc with action_deps = Pset.union acc.action_deps (Pset.of_list l) }
| G_unevaluated (loc, dir, re) -> | G_unevaluated (loc, dir, re) ->
match Pmap.find dir (Lazy.force all_targets_by_dir) with let targets = all_targets ~dir in
| None -> let result =
Loc.warn loc "Directory %s doesn't exist." Pset.filter targets ~f:(fun path ->
(Path.to_string_maybe_quoted dir); Re.execp re (Path.basename path))
state := G_evaluated []; in
acc if Pset.is_empty result then begin
| Some targets -> if not (Path.exists dir) then
let result = Loc.warn loc "Directory %s doesn't exist."
Pset.filter targets ~f:(fun path -> (Path.to_string_maybe_quoted dir)
Re.execp re (Path.basename path)) else if not (Path.is_directory dir) then
in Loc.warn loc "%s is not a directory."
state := G_evaluated (Pset.elements result); (Path.to_string_maybe_quoted dir)
let action_deps = Pset.union result acc.action_deps in else
{ acc with action_deps } (* diml: we should probably warn in this case as well *)
()
end;
state := G_evaluated (Pset.elements result);
let action_deps = Pset.union result acc.action_deps in
{ acc with action_deps }
end end
| If_file_exists (p, state) -> begin | If_file_exists (p, state) -> begin
match !state with match !state with
| Decided (_, t) -> loop t acc | Decided (_, t) -> loop t acc
| Undecided (then_, else_) -> | Undecided (then_, else_) ->
let dir = Path.parent p in let dir = Path.parent p in
let targets = let targets = all_targets ~dir in
Option.value (Pmap.find dir (Lazy.force all_targets_by_dir))
~default:Pset.empty
in
if Pset.mem p targets then begin if Pset.mem p targets then begin
state := Decided (true, then_); state := Decided (true, then_);
loop then_ acc loop then_ acc
@ -157,19 +159,38 @@ module Rule = struct
; build : (unit, Action.t) Build.t ; build : (unit, Action.t) Build.t
; targets : Target.t list ; targets : Target.t list
; sandbox : bool ; sandbox : bool
; fallback : Jbuild.Rule.Fallback.t ; mode : Jbuild.Rule.Mode.t
; locks : Path.t list ; locks : Path.t list
; loc : Loc.t option ; loc : Loc.t option
; dir : Path.t
} }
let make ?(sandbox=false) ?(fallback=Jbuild.Rule.Fallback.Not_possible) let make ?(sandbox=false) ?(mode=Jbuild.Rule.Mode.Not_a_rule_stanza)
?context ?(locks=[]) ?loc build = ?context ?(locks=[]) ?loc build =
let targets = targets build in
let dir =
match targets with
| [] ->
invalid_arg "Build_interpret.Rule.make: rule has no targets"
| x :: l ->
let dir = Path.parent (Target.path x) in
List.iter l ~f:(fun target ->
let path = Target.path target in
if Path.parent path <> dir then
Sexp.code_error "rule has targets in different directories"
[ "dir", Path.sexp_of_t dir
; "targets", Sexp.To_sexp.list Path.sexp_of_t
(List.map (x :: l) ~f:Target.path)
]);
dir
in
{ context { context
; build ; build
; targets = targets build ; targets
; sandbox ; sandbox
; fallback ; mode
; locks ; locks
; loc ; loc
; dir
} }
end end

View File

@ -15,14 +15,16 @@ module Rule : sig
; build : (unit, Action.t) Build.t ; build : (unit, Action.t) Build.t
; targets : Target.t list ; targets : Target.t list
; sandbox : bool ; sandbox : bool
; fallback : Jbuild.Rule.Fallback.t ; mode : Jbuild.Rule.Mode.t
; locks : Path.t list ; locks : Path.t list
; loc : Loc.t option ; loc : Loc.t option
; (** Directory where all the targets are produced *)
dir : Path.t
} }
val make val make
: ?sandbox:bool : ?sandbox:bool
-> ?fallback:Jbuild.Rule.Fallback.t -> ?mode:Jbuild.Rule.Mode.t
-> ?context:Context.t -> ?context:Context.t
-> ?locks:Path.t list -> ?locks:Path.t list
-> ?loc:Loc.t -> ?loc:Loc.t
@ -40,7 +42,7 @@ end
(* must be called first *) (* must be called first *)
val static_deps val static_deps
: (_, _) Build.t : (_, _) Build.t
-> all_targets_by_dir:Path.Set.t Path.Map.t Lazy.t -> all_targets:(dir:Path.t -> Path.Set.t)
-> Static_deps.t -> Static_deps.t
val lib_deps val lib_deps

File diff suppressed because it is too large Load Diff

View File

@ -4,15 +4,137 @@ open! Import
type t type t
(** {1 Creation} *)
(** Create a new build system. [file_tree] represent the source
tree. *)
val create val create
: contexts:Context.t list : contexts:Context.t list
-> file_tree:File_tree.t -> file_tree:File_tree.t
-> rules:Build_interpret.Rule.t list
-> t -> t
val is_target : t -> Path.t -> bool type extra_sub_directories_to_keep =
| All
| These of String_set.t
(** Set the rule generators callback. There must be one callback per
build context name.
Each callback is used to generate the rules for a given directory
in the corresponding build context. It receive the directory for
which to generate the rules and the splitted part of the path after
the build context. It must return an additional list of
sub-directories to keep. This is in addition to the ones that are
present in the source tree and the ones that already contain rules.
It is expected that [f] only generate rules whose targets are
descendant of [dir]. *)
val set_rule_generators : t -> (dir:Path.t -> string list -> extra_sub_directories_to_keep) String_map.t -> unit
(** {1 Primitive for rule generations} *)
(** Add a rule to the system. This function must be called from the [gen_rules]
callback. All the target of the rule must be in the same directory.
Assuming that [gen_rules ~dir:a] calls [add_rule r] where [r.dir] is [Some b], one of
the following assumption must hold:
- [a] and [b] are the same
- [gen_rules ~dir:b] calls [load_dir ~dir:a]
The call to [load_dir ~dir:a] from [gen_rules ~dir:b] declares a directory dependency
from [b] to [a]. There must be no cyclic directory dependencies.
*)
val add_rule : t -> Build_interpret.Rule.t -> unit
(** [eval_glob t ~dir re ~f] returns the list of files in [dir] that matches [re] to
[f]. The list of files includes the list of targets. *)
val eval_glob : t -> dir:Path.t -> Re.re -> string list
(** Returns the set of targets in the given directory. *)
val targets_of : t -> dir:Path.t -> Path.Set.t
(** Load the rules for this directory. *)
val load_dir : t -> dir:Path.t -> unit
(** [on_load_dir ~dir ~f] remembers to run [f] when loading the rules for [dir]. *)
val on_load_dir : t -> dir:Path.t -> f:(unit -> unit) -> unit
(** Stamp file that depends on all files of [dir] with extension [ext]. *)
val stamp_file_for_files_of : t -> dir:Path.t -> ext:string -> Path.t
(** {1 Aliases} *)
module Alias : sig
type build_system = t
type t
val pp : t Fmt.t
val make : string -> dir:Path.t -> t
val of_path : Path.t -> t
(** The following always holds:
{[
make (name t) ~dir:(dir t) = t
]}
*)
val name : t -> string
val dir : t -> Path.t
val fully_qualified_name : t -> Path.t
val default : dir:Path.t -> t
val runtest : dir:Path.t -> t
val install : dir:Path.t -> t
val doc : dir:Path.t -> t
val lint : dir:Path.t -> t
(** Return the underlying stamp file *)
val stamp_file : t -> Path.t
(** [dep t = Build.path (stamp_file t)] *)
val dep : t -> ('a, 'a) Build.t
(** Implements [(alias_rec ...)] in dependency specification *)
val dep_rec
: t
-> loc:Loc.t
-> file_tree:File_tree.t
-> (unit, unit) Build.t
(** Implements [@alias] on the command line *)
val dep_rec_multi_contexts
: dir:Path.t
-> name:string
-> file_tree:File_tree.t
-> contexts:string list
-> (unit, unit) Build.t
(** [add_deps store alias deps] arrange things so that all [deps]
are built as part of the build of alias [alias]. *)
val add_deps : build_system -> t -> Path.t list -> unit
(** [add_action store alias ~stamp action] arrange things so that
[action] is executed as part of the build of alias
[alias]. [stamp] is any S-expression that is unique and
persistent S-expression.
*)
val add_action
: build_system
-> t
-> ?locks:Path.t list
-> stamp:Sexp.t
-> (unit, Action.t) Build.t
-> unit
end with type build_system := t
(** {1 Building} *)
module Build_error : sig module Build_error : sig
(** Exception raised in case of build error *)
type t type t
val backtrace : t -> Printexc.raw_backtrace val backtrace : t -> Printexc.raw_backtrace
@ -32,6 +154,10 @@ val do_build_exn
-> request:(unit, unit) Build.t -> request:(unit, unit) Build.t
-> unit Future.t -> unit Future.t
(** {1 Other queries} *)
val is_target : t -> Path.t -> bool
(** Return all the library dependencies (as written by the user) (** Return all the library dependencies (as written by the user)
needed to build this request *) needed to build this request *)
val all_lib_deps val all_lib_deps
@ -49,6 +175,14 @@ val all_lib_deps_by_context
(** List of all buildable targets *) (** List of all buildable targets *)
val all_targets : t -> Path.t list val all_targets : t -> Path.t list
(** Return the list of files that were created in the source tree and
needs to be deleted *)
val files_in_source_tree_to_delete
: unit
-> Path.t list
(** {1 Build rules} *)
(** A fully built rule *) (** A fully built rule *)
module Rule : sig module Rule : sig
module Id : sig module Id : sig
@ -75,8 +209,7 @@ val build_rules
-> request:(unit, unit) Build.t -> request:(unit, unit) Build.t
-> Rule.t list Future.t -> Rule.t list Future.t
val all_targets_ever_built (** {1 Misc} *)
: unit
-> Path.t list
val dump_trace : t -> unit (** Dump various databases on disk *)
val finalize : t -> unit

View File

@ -12,3 +12,4 @@ let capture_outputs = ref true
let debug_backtraces = ref false let debug_backtraces = ref false
let diff_command = ref None let diff_command = ref None
let auto_promote = ref false let auto_promote = ref false
let force = ref false

View File

@ -41,3 +41,6 @@ val diff_command : string option ref
(** Automatically promote files *) (** Automatically promote files *)
val auto_promote : bool ref val auto_promote : bool ref
(** Force re-running actions associated to aliases *)
val force : bool ref

View File

@ -13,6 +13,19 @@ module Dir = struct
let sub_dirs t = t.sub_dirs let sub_dirs t = t.sub_dirs
let ignored t = t.ignored let ignored t = t.ignored
let file_paths t =
Path.Set.of_string_set t.files ~f:(Path.relative t.path)
let sub_dir_names t =
String_map.fold t.sub_dirs ~init:String_set.empty
~f:(fun ~key:s ~data:_ acc ->
String_set.add s acc)
let sub_dir_paths t =
String_map.fold t.sub_dirs ~init:Path.Set.empty
~f:(fun ~key:s ~data:_ acc ->
Path.Set.add (Path.relative t.path s) acc)
let rec fold t ~traverse_ignored_dirs ~init:acc ~f = let rec fold t ~traverse_ignored_dirs ~init:acc ~f =
if not traverse_ignored_dirs && t.ignored then if not traverse_ignored_dirs && t.ignored then
acc acc
@ -89,6 +102,12 @@ let fold t ~traverse_ignored_dirs ~init ~f =
let find_dir t path = let find_dir t path =
Path.Map.find path t.dirs Path.Map.find path t.dirs
let files_of t path =
match find_dir t path with
| None -> Path.Set.empty
| Some dir ->
Path.Set.of_string_set (Dir.files dir) ~f:(Path.relative path)
let file_exists t path fn = let file_exists t path fn =
match Path.Map.find path t.dirs with match Path.Map.find path t.dirs with
| None -> false | None -> false

View File

@ -5,7 +5,10 @@ module Dir : sig
val path : t -> Path.t val path : t -> Path.t
val files : t -> String_set.t val files : t -> String_set.t
val file_paths : t -> Path.Set.t
val sub_dirs : t -> t String_map.t val sub_dirs : t -> t String_map.t
val sub_dir_paths : t -> Path.Set.t
val sub_dir_names : t -> String_set.t
(** Whether this directory is ignored by a [jbuild-ignore] file in (** Whether this directory is ignored by a [jbuild-ignore] file in
one of its ancestor directories. *) one of its ancestor directories. *)
@ -34,6 +37,8 @@ val root : t -> Dir.t
val find_dir : t -> Path.t -> Dir.t option val find_dir : t -> Path.t -> Dir.t option
val files_of : t -> Path.t -> Path.Set.t
val exists : t -> Path.t -> bool val exists : t -> Path.t -> bool
val file_exists : t -> Path.t -> string -> bool val file_exists : t -> Path.t -> string -> bool

View File

@ -387,30 +387,15 @@ module Scheduler = struct
let rec split_paths targets_acc ctxs_acc = function let rec split_paths targets_acc ctxs_acc = function
| [] -> List.rev targets_acc, String_set.(elements (of_list ctxs_acc)) | [] -> List.rev targets_acc, String_set.(elements (of_list ctxs_acc))
| path :: rest -> | path :: rest ->
match Path.extract_build_context path with let add_ctx ctx acc = if ctx = "default" then acc else ctx :: acc in
| None -> match Utils.analyse_target path with
| Other path ->
split_paths (Path.to_string path :: targets_acc) ctxs_acc rest split_paths (Path.to_string path :: targets_acc) ctxs_acc rest
| Some ("default", filename) -> | Regular (ctx, filename) ->
split_paths (Path.to_string filename :: targets_acc) ctxs_acc rest split_paths (Path.to_string filename :: targets_acc) (add_ctx ctx ctxs_acc) rest
| Some (".aliases", filename) -> | Alias (ctx, name) ->
let ctxs_acc, filename = split_paths (("alias " ^ Path.to_string name) :: targets_acc) (add_ctx ctx ctxs_acc) rest
match Path.extract_build_context filename with in
| None -> ctxs_acc, Path.to_string filename
| Some (ctx, fn) ->
let strip_digest fn =
let fn = Path.to_string fn in
match String.rsplit2 fn ~on:'-' with
| None -> assert false
| Some (name, digest) ->
assert (String.length digest = 32);
name
in
let ctxs_acc =
if ctx = "default" then ctxs_acc else ctx :: ctxs_acc in
ctxs_acc, strip_digest fn in
split_paths (("alias " ^ filename) :: targets_acc) ctxs_acc rest
| Some (ctx, filename) ->
split_paths (Path.to_string filename :: targets_acc) (ctx :: ctxs_acc) rest in
let target_names, contexts = split_paths [] [] targets in let target_names, contexts = split_paths [] [] targets in
let target_names_grouped_by_prefix = let target_names_grouped_by_prefix =
List.map target_names ~f:Filename.split_extension_after_dot List.map target_names ~f:Filename.split_extension_after_dot

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,11 @@
open! Import open! Import
open Jbuild open Jbuild
(* Generate rules. Returns evaluated jbuilds per context names. *)
val gen val gen
: contexts:Context.t list : contexts:Context.t list
-> build_system:Build_system.t
-> ?filter_out_optional_stanzas_with_missing_deps:bool (* default: true *) -> ?filter_out_optional_stanzas_with_missing_deps:bool (* default: true *)
-> ?only_packages:String_set.t -> ?only_packages:String_set.t
-> ?unlink_aliases:string list
-> Jbuild_load.conf -> Jbuild_load.conf
-> (Build_interpret.Rule.t list * -> (Path.t * Scope.t * Stanzas.t) list String_map.t Future.t
(* Evaluated jbuilds per context names *)
(Path.t * Scope.t * Stanzas.t) list String_map.t) Future.t

View File

@ -2,7 +2,29 @@ include Jbuilder_re
module Array = StdLabels.Array module Array = StdLabels.Array
module Bytes = StdLabels.Bytes module Bytes = StdLabels.Bytes
module Set = MoreLabels.Set
module Set = struct
module type OrderedType = MoreLabels.Set.OrderedType
module type S = sig
include MoreLabels.Set.S
val map : f:(elt -> elt) -> t -> t
end
module Make(Elt : OrderedType) : S with type elt = Elt.t = struct
module M = MoreLabels.Set.Make(Elt)
include struct
[@@@warning "-32"]
(* [map] is only available since 4.04 *)
let map ~f t =
M.elements t
|> List.map f
|> M.of_list
end
include M
end
end
external reraise : exn -> _ = "%reraise" external reraise : exn -> _ = "%reraise"

View File

@ -66,3 +66,6 @@ let copy_file ~src ~dst =
~finally:close_out ~finally:close_out
~f:(fun oc -> ~f:(fun oc ->
copy_channels ic oc)) copy_channels ic oc))
(* TODO: diml: improve this *)
let compare_files fn1 fn2 = String.compare (read_file fn1) (read_file fn2)

View File

@ -16,6 +16,8 @@ val lines_of_file : string -> string list
val read_file : string -> string val read_file : string -> string
val write_file : string -> string -> unit val write_file : string -> string -> unit
val compare_files : string -> string -> int
val copy_channels : in_channel -> out_channel -> unit val copy_channels : in_channel -> out_channel -> unit
val copy_file : src:string -> dst:string -> unit val copy_file : src:string -> dst:string -> unit

View File

@ -727,18 +727,21 @@ module Rule = struct
| Infer | Infer
end end
module Fallback = struct
module Mode = struct
type t = type t =
| Yes | Standard
| No | Fallback
| Not_possible | Promote
| Promote_but_delete_on_clean
| Not_a_rule_stanza
end end
type t = type t =
{ targets : Targets.t { targets : Targets.t
; deps : Dep_conf.t list ; deps : Dep_conf.t list
; action : Action.Unexpanded.t ; action : Action.Unexpanded.t
; fallback : Fallback.t ; mode : Mode.t
; locks : String_with_vars.t list ; locks : String_with_vars.t list
; loc : Loc.t ; loc : Loc.t
} }
@ -749,7 +752,7 @@ module Rule = struct
{ targets = Infer { targets = Infer
; deps = [] ; deps = []
; action = Action.Unexpanded.t sexp ; action = Action.Unexpanded.t sexp
; fallback = No ; mode = Standard
; locks = [] ; locks = []
; loc = Loc.none ; loc = Loc.none
} }
@ -763,7 +766,7 @@ module Rule = struct
return { targets = Static targets return { targets = Static targets
; deps ; deps
; action ; action
; fallback = if fallback then Yes else No ; mode = if fallback then Fallback else Standard
; locks ; locks
; loc = Loc.none ; loc = Loc.none
}) })
@ -785,7 +788,7 @@ module Rule = struct
; S.virt_var __POS__ "@" ; S.virt_var __POS__ "@"
; S.virt_var __POS__"<" ; S.virt_var __POS__"<"
])) ]))
; fallback = Not_possible ; mode = Not_a_rule_stanza
; locks = [] ; locks = []
; loc ; loc
}) })
@ -801,7 +804,7 @@ module Rule = struct
(S.virt_var __POS__ "ROOT", (S.virt_var __POS__ "ROOT",
Run (S.virt_text __POS__ "ocamlyacc", Run (S.virt_text __POS__ "ocamlyacc",
[S.virt_var __POS__ "<"])) [S.virt_var __POS__ "<"]))
; fallback = Not_possible ; mode = Not_a_rule_stanza
; locks = [] ; locks = []
; loc ; loc
}) })
@ -841,7 +844,7 @@ module Menhir = struct
(S.virt_var __POS__ "ROOT", (S.virt_var __POS__ "ROOT",
Run (S.virt_text __POS__ "menhir", Run (S.virt_text __POS__ "menhir",
t.flags @ [S.virt_var __POS__ "<"])) t.flags @ [S.virt_var __POS__ "<"]))
; fallback = Not_possible ; mode = Not_a_rule_stanza
; locks = [] ; locks = []
; loc ; loc
}) })
@ -861,7 +864,7 @@ module Menhir = struct
; t.flags ; t.flags
; [ S.virt_var __POS__ "^" ] ; [ S.virt_var __POS__ "^" ]
])) ]))
; fallback = Not_possible ; mode = Not_a_rule_stanza
; locks = [] ; locks = []
; loc ; loc
}] }]

View File

@ -203,20 +203,25 @@ module Rule : sig
| Infer | Infer
end end
module Fallback : sig module Mode : sig
type t = type t =
| Yes | Standard
| No (** Only use this rule if the source files don't exist. *)
| Not_possible | Fallback
(** It is not possible to add a [(fallback)] field to the rule. For instance for (** Silently promote the targets to the source tree. *)
[ocamllex], ... *) | Promote
(** Same as [Promote] but [jbuilder clean] must delete the file *)
| Promote_but_delete_on_clean
(** Same as [Standard] however this is not a rule stanza, so it is not possible to
add a [(fallback)] field to the rule. *)
| Not_a_rule_stanza
end end
type t = type t =
{ targets : Targets.t { targets : Targets.t
; deps : Dep_conf.t list ; deps : Dep_conf.t list
; action : Action.Unexpanded.t ; action : Action.Unexpanded.t
; fallback : Fallback.t ; mode : Mode.t
; locks : String_with_vars.t list ; locks : String_with_vars.t list
; loc : Loc.t ; loc : Loc.t
} }

View File

@ -101,36 +101,34 @@ let build_cm sctx ~scope ~dir ~js_of_ocaml ~src =
js_of_ocaml_rule ~sctx ~dir ~flags:(fun flags -> As flags) ~spec ~target ] js_of_ocaml_rule ~sctx ~dir ~flags:(fun flags -> As flags) ~spec ~target ]
else [] else []
let setup_separate_compilation_rules sctx = let setup_separate_compilation_rules sctx components =
if separate_compilation_enabled () if separate_compilation_enabled ()
then then
let ctx = SC.context sctx in match components with
let all_pkg = | [] | _ :: _ :: _ -> ()
List.map | [pkg] ->
(Findlib.all_packages ctx.findlib) let ctx = SC.context sctx in
~f:(fun pkg -> match Findlib.find ctx.findlib pkg ~required_by:[] with
| None -> ()
| Some pkg ->
let pkg =
(* Special case for the stdlib because it is not referenced in the META *) (* Special case for the stdlib because it is not referenced in the META *)
let pkg = match pkg.Findlib.name with
if pkg.Findlib.name = "stdlib" | "stdlib" -> Findlib.stdlib_with_archives ctx.findlib
then Findlib.stdlib_with_archives ctx.findlib | _ -> pkg
else pkg in
in let archives = Mode.Dict.get pkg.Findlib.archives Mode.Byte in
let archives = Mode.Dict.get pkg.Findlib.archives Mode.Byte in List.iter archives ~f:(fun fn ->
pkg.Findlib.name, pkg.dir, archives)
in
List.concat_map all_pkg
~f:(fun (pkg_name,pkg_dir,archives) ->
List.map archives ~f:(fun fn ->
let name = Path.basename fn in let name = Path.basename fn in
let src = Path.relative pkg_dir name in let src = Path.relative pkg.dir name in
let target = in_build_dir ~ctx [ pkg_name; sprintf "%s.js" name] in let target = in_build_dir ~ctx [ pkg.name; sprintf "%s.js" name] in
let dir = in_build_dir ~ctx [ pkg_name ] in let dir = in_build_dir ~ctx [ pkg.name ] in
let spec = Arg_spec.Dep src in let spec = Arg_spec.Dep src in
Build.return (standard ()) SC.add_rule sctx
>>> (Build.return (standard ())
js_of_ocaml_rule ~sctx ~dir ~flags:(fun flags -> As flags) ~spec ~target >>>
)) js_of_ocaml_rule ~sctx ~dir ~flags:(fun flags -> As flags) ~spec ~target)
else [] )
let build_exe sctx ~dir ~js_of_ocaml ~src = let build_exe sctx ~dir ~js_of_ocaml ~src =
let {Jbuild.Js_of_ocaml.javascript_files; _} = js_of_ocaml in let {Jbuild.Js_of_ocaml.javascript_files; _} = js_of_ocaml in

View File

@ -19,6 +19,7 @@ val build_exe
val setup_separate_compilation_rules val setup_separate_compilation_rules
: Super_context.t : Super_context.t
-> (unit, Action.t) Build.t list -> string list
-> unit
val standard : unit -> string list val standard : unit -> string list

View File

@ -15,7 +15,7 @@ let package_install_file { packages; _ } pkg =
| Some p -> | Some p ->
Ok (Path.relative p.path (Utils.install_file ~package:p.name ~findlib_toolchain:None)) Ok (Path.relative p.path (Utils.install_file ~package:p.name ~findlib_toolchain:None))
let setup ?(log=Log.no_log) ?unlink_aliases let setup ?(log=Log.no_log)
?filter_out_optional_stanzas_with_missing_deps ?filter_out_optional_stanzas_with_missing_deps
?workspace ?(workspace_file="jbuild-workspace") ?workspace ?(workspace_file="jbuild-workspace")
?(use_findlib=true) ?(use_findlib=true)
@ -55,14 +55,15 @@ let setup ?(log=Log.no_log) ?unlink_aliases
let contexts = List.concat contexts in let contexts = List.concat contexts in
List.iter contexts ~f:(fun (ctx : Context.t) -> List.iter contexts ~f:(fun (ctx : Context.t) ->
Log.infof log "@[<1>Jbuilder context:@,%a@]@." Sexp.pp (Context.sexp_of_t ctx)); Log.infof log "@[<1>Jbuilder context:@,%a@]@." Sexp.pp (Context.sexp_of_t ctx));
let build_system =
Build_system.create ~contexts ~file_tree:conf.file_tree
in
Gen_rules.gen conf Gen_rules.gen conf
~build_system
~contexts ~contexts
?unlink_aliases
?only_packages ?only_packages
?filter_out_optional_stanzas_with_missing_deps ?filter_out_optional_stanzas_with_missing_deps
>>= fun (rules, stanzas) -> >>= fun stanzas ->
let build_system = Build_system.create ~contexts
~file_tree:conf.file_tree ~rules in
return { build_system return { build_system
; stanzas ; stanzas
; contexts ; contexts
@ -211,7 +212,6 @@ let ignored_during_bootstrap =
(* Called by the script generated by ../build.ml *) (* Called by the script generated by ../build.ml *)
let bootstrap () = let bootstrap () =
Ansi_color.setup_err_formatter_colors (); Ansi_color.setup_err_formatter_colors ();
let pkg = "jbuilder" in
let main () = let main () =
let anon s = raise (Arg.Bad (Printf.sprintf "don't know what to do with %s\n" s)) in let anon s = raise (Arg.Bad (Printf.sprintf "don't know what to do with %s\n" s)) in
let subst () = let subst () =
@ -228,13 +228,14 @@ let bootstrap () =
Clflags.debug_dep_path := true; Clflags.debug_dep_path := true;
let log = Log.create () in let log = Log.create () in
Future.Scheduler.go ~log Future.Scheduler.go ~log
(setup ~log ~workspace:{ merlin_context = Some "default"; contexts = [Default [Native]] } (setup ~log ~workspace:{ merlin_context = Some "default"
; contexts = [Default [Native]] }
~use_findlib:false ~use_findlib:false
~extra_ignored_subtrees:ignored_during_bootstrap ~extra_ignored_subtrees:ignored_during_bootstrap
() ()
>>= fun { build_system = bs; _ } -> >>= fun { build_system = bs; _ } ->
Build_system.do_build_exn bs Build_system.do_build_exn bs
~request:(Build.path (Path.(relative root) (pkg ^ ".install")))) ~request:(Build.path (Path.of_string "_build/default/jbuilder.install")))
in in
try try
main () main ()

View File

@ -17,7 +17,6 @@ val package_install_file : setup -> string -> (Path.t, unit) result
it. *) it. *)
val setup val setup
: ?log:Log.t : ?log:Log.t
-> ?unlink_aliases:string list
-> ?filter_out_optional_stanzas_with_missing_deps:bool -> ?filter_out_optional_stanzas_with_missing_deps:bool
-> ?workspace:Workspace.t -> ?workspace:Workspace.t
-> ?workspace_file:string -> ?workspace_file:string

View File

@ -12,10 +12,10 @@ type t =
; source_dirs: Path.Set.t ; source_dirs: Path.Set.t
} }
let ppx_flags sctx ~dir ~src_dir:_ { preprocess; libname; _ } = let ppx_flags sctx ~dir:_ ~src_dir:_ { preprocess; libname; _ } =
match preprocess with match preprocess with
| Pps { pps; flags } -> | Pps { pps; flags } ->
let exe = SC.PP.get_ppx_driver sctx pps ~dir ~dep_kind:Optional in let exe = SC.PP.get_ppx_driver sctx pps in
let command = let command =
List.map (Path.to_absolute_filename exe List.map (Path.to_absolute_filename exe
:: "--as-ppx" :: "--as-ppx"
@ -28,14 +28,10 @@ let ppx_flags sctx ~dir ~src_dir:_ { preprocess; libname; _ } =
| _ -> [] | _ -> []
let dot_merlin sctx ~dir ({ requires; flags; _ } as t) = let dot_merlin sctx ~dir ({ requires; flags; _ } as t) =
match Path.extract_build_context dir with match Path.drop_build_context dir with
| Some (_, remaindir) -> | Some remaindir ->
let path = Path.relative remaindir ".merlin" in let merlin_file = Path.relative dir ".merlin" in
SC.add_rule sctx SC.add_rule sctx ~mode:Promote_but_delete_on_clean (
(Build.path path
>>>
Build.write_file (Path.relative dir ".merlin-exists") "");
SC.add_rule sctx (
requires &&& flags requires &&& flags
>>^ (fun (libs, flags) -> >>^ (fun (libs, flags) ->
let ppx_flags = ppx_flags sctx ~dir ~src_dir:remaindir t in let ppx_flags = ppx_flags sctx ~dir ~src_dir:remaindir t in
@ -82,7 +78,7 @@ let dot_merlin sctx ~dir ({ requires; flags; _ } as t) =
|> List.map ~f:(Printf.sprintf "%s\n") |> List.map ~f:(Printf.sprintf "%s\n")
|> String.concat ~sep:"") |> String.concat ~sep:"")
>>> >>>
Build.write_file_dyn path Build.write_file_dyn merlin_file
) )
| _ -> | _ ->
() ()

View File

@ -155,7 +155,7 @@ let setup_library_rules sctx (lib : Library.t) ~dir ~modules ~requires
~f:(fun (m, _) -> m.Module.name = main_module_name) ~f:(fun (m, _) -> m.Module.name = main_module_name)
else else
modules_and_odoc_files modules_and_odoc_files
in*) in*)
let html_files = let html_files =
List.map modules_and_odoc_files ~f:(fun (m, odoc_file) -> List.map modules_and_odoc_files ~f:(fun (m, odoc_file) ->
to_html sctx m odoc_file ~doc_dir ~odoc ~dir ~includes ~lib to_html sctx m odoc_file ~doc_dir ~odoc ~dir ~includes ~lib
@ -165,7 +165,7 @@ let setup_library_rules sctx (lib : Library.t) ~dir ~modules ~requires
lib_index sctx ~dir ~lib ~lib_unique_name ~lib_name ~doc_dir lib_index sctx ~dir ~lib ~lib_unique_name ~lib_name ~doc_dir
~modules ~includes ~odoc ~modules ~includes ~odoc
in in
Alias.add_deps (SC.aliases sctx) (Alias.doc ~dir) SC.add_alias_deps sctx (Build_system.Alias.doc ~dir)
(css_file ~doc_dir (css_file ~doc_dir
:: toplevel_index ~doc_dir :: toplevel_index ~doc_dir
:: lib_index_html :: lib_index_html
@ -227,3 +227,13 @@ let setup_toplevel_index_rule sctx =
let context = SC.context sctx in let context = SC.context sctx in
let doc_dir = doc_dir ~context in let doc_dir = doc_dir ~context in
SC.add_rule sctx @@ Build.write_file (toplevel_index ~doc_dir) html SC.add_rule sctx @@ Build.write_file (toplevel_index ~doc_dir) html
let gen_rules sctx ~dir rest =
match rest with
| [] ->
setup_css_rule sctx;
setup_toplevel_index_rule sctx
| lib :: _ ->
match Lib_db.find (SC.libs sctx) ~from:dir lib with
| None | Some (External _) -> ()
| Some (Internal (dir, _)) -> SC.load_dir sctx ~dir

View File

@ -12,6 +12,4 @@ val setup_library_rules
-> dep_graph:Ocamldep.dep_graph -> dep_graph:Ocamldep.dep_graph
-> unit -> unit
val setup_css_rule : Super_context.t -> unit val gen_rules : Super_context.t -> dir:Path.t -> string list -> unit
val setup_toplevel_index_rule: Super_context.t -> unit

View File

@ -223,7 +223,9 @@ let compare = String.compare
module Set = struct module Set = struct
include String_set include String_set
let sexp_of_t t = Sexp.To_sexp.(list string) (String_set.elements t) let sexp_of_t t = Sexp.To_sexp.(list string) (String_set.elements t)
let of_string_set = map
end end
module Map = String_map module Map = String_map
module Kind = struct module Kind = struct
@ -346,9 +348,16 @@ let parent t =
let build_prefix = "_build/" let build_prefix = "_build/"
let build_dir = "_build"
let is_in_build_dir t = let is_in_build_dir t =
String.is_prefix t ~prefix:build_prefix String.is_prefix t ~prefix:build_prefix
let is_in_source_tree t = is_local t && not (is_in_build_dir t)
let is_alias_stamp_file t =
String.is_prefix t ~prefix:"_build/.aliases/"
let extract_build_context t = let extract_build_context t =
if String.is_prefix t ~prefix:build_prefix then if String.is_prefix t ~prefix:build_prefix then
let i = String.length build_prefix in let i = String.length build_prefix in
@ -380,11 +389,39 @@ let extract_build_context_dir t =
let drop_build_context t = let drop_build_context t =
Option.map (extract_build_context t) ~f:snd Option.map (extract_build_context t) ~f:snd
let drop_build_context_exn t =
match extract_build_context t with
| None -> Sexp.code_error "Path.drop_build_context_exn" [ "t", sexp_of_t t ]
| Some (_, t) -> t
let drop_optional_build_context t = let drop_optional_build_context t =
match extract_build_context t with match extract_build_context t with
| None -> t | None -> t
| Some (_, t) -> t | Some (_, t) -> t
let split_first_component t =
if is_local t && not (is_root t)then
match String.index t '/' with
| None -> Some (t, root)
| Some i ->
Some
(String.sub t ~pos:0 ~len:i,
String.sub t ~pos:(i + 1) ~len:(String.length t - i - 1))
else
None
let explode t =
if is_local t then
Some (String.split t ~on:'/')
else
None
let explode_exn t =
if is_local t then
String.split t ~on:'/'
else
Sexp.code_error "Path.explode_exn" ["path", Atom t]
let exists t = Sys.file_exists (to_string t) let exists t = Sys.file_exists (to_string t)
let readdir t = Sys.readdir (to_string t) |> Array.to_list let readdir t = Sys.readdir (to_string t) |> Array.to_list
let is_directory t = let is_directory t =
@ -405,13 +442,10 @@ let insert_after_build_dir_exn =
] ]
in in
fun a b -> fun a b ->
if not (is_local a && is_local b) then error a b; if not (is_local a) || String.contains b '/' then error a b;
match String.lsplit2 a ~on:'/' with match String.lsplit2 a ~on:'/' with
| Some ("_build", rest) -> | Some ("_build", rest) ->
if is_root b then sprintf "_build/%s/%s" b rest
a
else
sprintf "_build/%s/%s" b rest
| _ -> | _ ->
error a b error a b

View File

@ -42,9 +42,12 @@ val compare : t -> t -> int
module Set : sig module Set : sig
include Set.S with type elt = t include Set.S with type elt = t
val sexp_of_t : t Sexp.To_sexp.t val sexp_of_t : t Sexp.To_sexp.t
val of_string_set : f:(string -> elt) -> String_set.t -> t
end end
module Map : Map.S with type key = t module Map : Map.S with type key = t
val kind : t -> Kind.t val kind : t -> Kind.t
val of_string : ?error_loc:Loc.t -> string -> t val of_string : ?error_loc:Loc.t -> string -> t
@ -99,13 +102,29 @@ val extract_build_context_dir : t -> (t * t) option
(** Drop the "_build/blah" prefix *) (** Drop the "_build/blah" prefix *)
val drop_build_context : t -> t option val drop_build_context : t -> t option
val drop_build_context_exn : t -> t
(** Drop the "_build/blah" prefix if present, return [t] otherwise *) (** Drop the "_build/blah" prefix if present, return [t] otherwise *)
val drop_optional_build_context : t -> t val drop_optional_build_context : t -> t
val explode : t -> string list option
val explode_exn : t -> string list
(** The build directory *)
val build_dir : t
(** [is_in_build_dir t = is_descendant t ~of:build_dir] *)
val is_in_build_dir : t -> bool val is_in_build_dir : t -> bool
val insert_after_build_dir_exn : t -> t -> t (** [is_in_build_dir t = is_local t && not (is_in_build_dir t)] *)
val is_in_source_tree : t -> bool
val is_alias_stamp_file : t -> bool
(** Split after the first component if [t] is local *)
val split_first_component : t -> (string * t) option
val insert_after_build_dir_exn : t -> string -> t
val exists : t -> bool val exists : t -> bool
val readdir : t -> string list val readdir : t -> string list

View File

@ -3,6 +3,7 @@ open Jbuild
module A = Action module A = Action
module Pset = Path.Set module Pset = Path.Set
module Alias = Build_system.Alias
module Dir_with_jbuild = struct module Dir_with_jbuild = struct
type t = type t =
@ -13,72 +14,36 @@ module Dir_with_jbuild = struct
} }
end end
module External_dir = struct
(* Files in the directory, grouped by extension *)
type t = Path.t list String_map.t
let create ~dir : t =
match Path.readdir dir with
| exception _ -> String_map.empty
| files ->
List.map files ~f:(fun fn -> Filename.extension fn, Path.relative dir fn)
|> String_map.of_alist_multi
(* CR-someday jdimino: when we can have dynamic targets:
{[
|> String_map.mapi ~f:(fun ext files ->
lazy (
let alias =
Alias.make ~dir:Path.root (sprintf "external-files-%s%s" hash ext)
in
Alias.add_deps aliases alias files;
alias
))
]}
*)
let files t ~ext = String_map.find_default ext t ~default:[]
end
type t = type t =
{ context : Context.t { context : Context.t
; libs : Lib_db.t ; build_system : Build_system.t
; stanzas : Dir_with_jbuild.t list ; libs : Lib_db.t
; packages : Package.t String_map.t ; stanzas : Dir_with_jbuild.t list
; aliases : Alias.Store.t ; packages : Package.t String_map.t
; file_tree : File_tree.t ; file_tree : File_tree.t
; artifacts : Artifacts.t ; artifacts : Artifacts.t
; mutable rules : Build_interpret.Rule.t list ; stanzas_to_consider_for_install : (Path.t * Stanza.t) list
; stanzas_to_consider_for_install : (Path.t * Stanza.t) list ; libs_vfile : (module Vfile_kind.S with type t = Lib.t list)
; mutable known_targets_by_src_dir_so_far : String_set.t Path.Map.t ; cxx_flags : string list
; libs_vfile : (module Vfile_kind.S with type t = Lib.t list) ; vars : Action.Var_expansion.t String_map.t
; cxx_flags : string list ; ppx_dir : Path.t
; vars : Action.Var_expansion.t String_map.t ; chdir : (Action.t, Action.t) Build.t
; ppx_dir : Path.t ; host : t option
; ppx_drivers : (string, Path.t) Hashtbl.t
; external_dirs : (Path.t, External_dir.t) Hashtbl.t
; chdir : (Action.t, Action.t) Build.t
; host : t option
} }
let context t = t.context let context t = t.context
let aliases t = t.aliases
let stanzas t = t.stanzas let stanzas t = t.stanzas
let packages t = t.packages let packages t = t.packages
let artifacts t = t.artifacts let artifacts t = t.artifacts
let file_tree t = t.file_tree let file_tree t = t.file_tree
let rules t = t.rules
let stanzas_to_consider_for_install t = t.stanzas_to_consider_for_install let stanzas_to_consider_for_install t = t.stanzas_to_consider_for_install
let cxx_flags t = t.cxx_flags let cxx_flags t = t.cxx_flags
let libs t = t.libs
let host_sctx t = Option.value t.host ~default:t let host_sctx t = Option.value t.host ~default:t
let expand_var_no_root t var = String_map.find var t.vars let expand_var_no_root t var = String_map.find var t.vars
let get_external_dir t ~dir =
Hashtbl.find_or_add t.external_dirs dir ~f:(fun dir ->
External_dir.create ~dir)
let expand_vars t ~scope ~dir s = let expand_vars t ~scope ~dir s =
String_with_vars.expand s ~f:(fun _loc -> function String_with_vars.expand s ~f:(fun _loc -> function
| "ROOT" -> Some (Path.reach ~from:dir t.context.build_dir) | "ROOT" -> Some (Path.reach ~from:dir t.context.build_dir)
@ -98,12 +63,12 @@ let resolve_program t ?hint bin =
let create let create
~(context:Context.t) ~(context:Context.t)
?host ?host
~aliases
~scopes ~scopes
~file_tree ~file_tree
~packages ~packages
~stanzas ~stanzas
~filter_out_optional_stanzas_with_missing_deps ~filter_out_optional_stanzas_with_missing_deps
~build_system
= =
let stanzas = let stanzas =
List.map stanzas List.map stanzas
@ -203,61 +168,55 @@ let create
in in
{ context { context
; host ; host
; build_system
; libs ; libs
; stanzas ; stanzas
; packages ; packages
; aliases
; file_tree ; file_tree
; rules = []
; stanzas_to_consider_for_install ; stanzas_to_consider_for_install
; known_targets_by_src_dir_so_far = Path.Map.empty
; libs_vfile = (module Libs_vfile) ; libs_vfile = (module Libs_vfile)
; artifacts ; artifacts
; cxx_flags ; cxx_flags
; vars ; vars
; ppx_drivers = Hashtbl.create 32
; ppx_dir = Path.relative context.build_dir ".ppx" ; ppx_dir = Path.relative context.build_dir ".ppx"
; external_dirs = Hashtbl.create 1024
; chdir = Build.arr (fun (action : Action.t) -> ; chdir = Build.arr (fun (action : Action.t) ->
match action with match action with
| Chdir _ -> action | Chdir _ -> action
| _ -> Chdir (context.build_dir, action)) | _ -> Chdir (context.build_dir, action))
} }
let add_rule t ?sandbox ?fallback ?locks ?loc build = let add_rule t ?sandbox ?mode ?locks ?loc build =
let build = Build.O.(>>>) build t.chdir in
Build_system.add_rule t.build_system
(Build_interpret.Rule.make ?sandbox ?mode ?locks ?loc
~context:t.context build)
let add_rule_get_targets t ?sandbox ?mode ?locks ?loc build =
let build = Build.O.(>>>) build t.chdir in let build = Build.O.(>>>) build t.chdir in
let rule = let rule =
Build_interpret.Rule.make ?sandbox ?fallback ?locks ?loc Build_interpret.Rule.make ?sandbox ?mode ?locks ?loc
~context:t.context build ~context:t.context build
in in
t.rules <- rule :: t.rules; Build_system.add_rule t.build_system rule;
t.known_targets_by_src_dir_so_far <- List.map rule.targets ~f:Build_interpret.Target.path
List.fold_left rule.targets ~init:t.known_targets_by_src_dir_so_far
~f:(fun acc target ->
match Path.extract_build_context (Build_interpret.Target.path target) with
| None -> acc
| Some (_, path) ->
let dir = Path.parent path in
let fn = Path.basename path in
let files =
match Path.Map.find dir acc with
| None -> String_set.singleton fn
| Some set -> String_set.add fn set
in
Path.Map.add acc ~key:dir ~data:files)
let add_rules t ?sandbox builds = let add_rules t ?sandbox builds =
List.iter builds ~f:(add_rule t ?sandbox) List.iter builds ~f:(add_rule t ?sandbox)
let sources_and_targets_known_so_far t ~src_path = let add_alias_deps t alias deps =
let sources = Alias.add_deps t.build_system alias deps
match File_tree.find_dir t.file_tree src_path with
| None -> String_set.empty let add_alias_action t alias ?locks ~stamp action =
| Some dir -> File_tree.Dir.files dir Alias.add_action t.build_system alias ?locks ~stamp action
in
match Path.Map.find src_path t.known_targets_by_src_dir_so_far with let eval_glob t ~dir re = Build_system.eval_glob t.build_system ~dir re
| None -> sources let load_dir t ~dir = Build_system.load_dir t.build_system ~dir
| Some set -> String_set.union sources set let on_load_dir t ~dir ~f = Build_system.on_load_dir t.build_system ~dir ~f
let source_files t ~src_path =
match File_tree.find_dir t.file_tree src_path with
| None -> String_set.empty
| Some dir -> File_tree.Dir.files dir
let unique_library_name t lib = let unique_library_name t lib =
Lib_db.unique_library_name t.libs lib Lib_db.unique_library_name t.libs lib
@ -363,14 +322,7 @@ module Libs = struct
in in
let requires = let requires =
if t.context.merlin && has_dot_merlin then if t.context.merlin && has_dot_merlin then
(* We don't depend on the dot_merlin directly, otherwise everytime it changes we Build.path (Path.relative dir ".merlin")
would have to rebuild everything.
.merlin-exists depends on the .merlin and is an empty file. Depending on it
forces the generation of the .merlin but not recompilation when it
changes. Maybe one day we should add [Build.path_exists] to do the same in
general. *)
Build.path (Path.relative dir ".merlin-exists")
>>> >>>
real_requires real_requires
else else
@ -394,24 +346,21 @@ module Libs = struct
Alias.make (sprintf "lib-%s%s-all" lib.name ext) ~dir Alias.make (sprintf "lib-%s%s-all" lib.name ext) ~dir
let setup_file_deps_alias t lib ~ext files = let setup_file_deps_alias t lib ~ext files =
Alias.add_deps t.aliases (lib_files_alias lib ~ext) files add_alias_deps t (lib_files_alias lib ~ext) files
let setup_file_deps_group_alias t lib ~exts = let setup_file_deps_group_alias t lib ~exts =
setup_file_deps_alias t lib setup_file_deps_alias t lib
~ext:(String.concat exts ~sep:"-and-") ~ext:(String.concat exts ~sep:"-and-")
(List.map exts ~f:(fun ext -> Alias.file (lib_files_alias lib ~ext))) (List.map exts ~f:(fun ext -> Alias.stamp_file (lib_files_alias lib ~ext)))
let file_deps t ~ext = let file_deps t ~ext =
Build.dyn_paths (Build.arr (fun libs -> Build.dyn_paths (Build.arr (fun libs ->
List.fold_left libs ~init:[] ~f:(fun acc (lib : Lib.t) -> List.fold_left libs ~init:[] ~f:(fun acc (lib : Lib.t) ->
match lib with match lib with
| External pkg -> begin | External pkg ->
List.rev_append Build_system.stamp_file_for_files_of t.build_system ~dir:pkg.dir ~ext :: acc
(External_dir.files (get_external_dir t ~dir:pkg.dir) ~ext)
acc
end
| Internal lib -> | Internal lib ->
Alias.file (lib_files_alias lib ~ext) :: acc))) Alias.stamp_file (lib_files_alias lib ~ext) :: acc)))
let static_file_deps ~ext lib = let static_file_deps ~ext lib =
Alias.dep (lib_files_alias lib ~ext) Alias.dep (lib_files_alias lib ~ext)
@ -852,12 +801,31 @@ module PP = struct
; Dyn (Lib.link_flags ~mode) ; Dyn (Lib.link_flags ~mode)
]) ])
let get_ppx_driver sctx pps ~dir ~dep_kind = let gen_rules sctx components =
let driver, names = match components with
| [key] ->
let ppx_dir = Path.relative sctx.ppx_dir key in
let exe = Path.relative ppx_dir "ppx.exe" in
let names =
match key with
| "+none+" -> []
| _ -> String.split key ~on:'+'
in
let driver, names =
match List.rev names with
| [] -> (None, [])
| driver :: rest ->
(Some driver, List.sort rest ~cmp:String.compare @ [driver])
in
build_ppx_driver sctx names ~dir:ppx_dir ~dep_kind:Required ~target:exe ~driver
| _ -> ()
let get_ppx_driver sctx pps =
let names =
match List.rev_map pps ~f:Pp.to_string with match List.rev_map pps ~f:Pp.to_string with
| [] -> (None, []) | [] -> []
| driver :: rest -> | driver :: rest ->
(Some driver, List.sort rest ~cmp:String.compare @ [driver]) List.sort rest ~cmp:String.compare @ [driver]
in in
let key = let key =
match names with match names with
@ -865,14 +833,8 @@ module PP = struct
| _ -> String.concat names ~sep:"+" | _ -> String.concat names ~sep:"+"
in in
let sctx = host_sctx sctx in let sctx = host_sctx sctx in
match Hashtbl.find sctx.ppx_drivers key with let ppx_dir = Path.relative sctx.ppx_dir key in
| Some x -> x Path.relative ppx_dir "ppx.exe"
| None ->
let ppx_dir = Path.relative sctx.ppx_dir key in
let exe = Path.relative ppx_dir "ppx.exe" in
build_ppx_driver sctx names ~dir ~dep_kind ~target:exe ~driver;
Hashtbl.add sctx.ppx_drivers ~key ~data:exe;
exe
let target_var = String_with_vars.virt_var __POS__ "@" let target_var = String_with_vars.virt_var __POS__ "@"
let root_var = String_with_vars.virt_var __POS__ "ROOT" let root_var = String_with_vars.virt_var __POS__ "ROOT"
@ -933,12 +895,11 @@ module PP = struct
~dep_kind ~lint ~lib_name ~scope = ~dep_kind ~lint ~lib_name ~scope =
let alias = Alias.lint ~dir in let alias = Alias.lint ~dir in
let add_alias fn build = let add_alias fn build =
add_rule sctx Alias.add_action sctx.build_system alias build
(Alias.add_build (aliases sctx) alias build ~stamp:(List [ Atom "lint"
~stamp:(List [ Atom "lint" ; Sexp.To_sexp.(option string) lib_name
; Sexp.To_sexp.(option string) lib_name ; Atom fn
; Atom fn ])
]))
in in
match Preprocess_map.find source.name lint with match Preprocess_map.find source.name lint with
| No_preprocessing -> () | No_preprocessing -> ()
@ -957,7 +918,7 @@ module PP = struct
~scope) ~scope)
) )
| Pps { pps; flags } -> | Pps { pps; flags } ->
let ppx_exe = get_ppx_driver sctx pps ~dir ~dep_kind in let ppx_exe = get_ppx_driver sctx pps in
Module.iter ast ~f:(fun kind src -> Module.iter ast ~f:(fun kind src ->
let src_path = Path.relative dir src.name in let src_path = Path.relative dir src.name in
let args = let args =
@ -1020,7 +981,7 @@ module PP = struct
lint_module ~ast ~source:m; lint_module ~ast ~source:m;
ast ast
| Pps { pps; flags } -> | Pps { pps; flags } ->
let ppx_exe = get_ppx_driver sctx pps ~dir ~dep_kind in let ppx_exe = get_ppx_driver sctx pps in
let ast = setup_reason_rules sctx ~dir m in let ast = setup_reason_rules sctx ~dir m in
lint_module ~ast ~source:m; lint_module ~ast ~source:m;
let uses_ppx_driver = uses_ppx_driver ~pps in let uses_ppx_driver = uses_ppx_driver ~pps in

View File

@ -23,41 +23,65 @@ type t
val create val create
: context:Context.t : context:Context.t
-> ?host:t -> ?host:t
-> aliases:Alias.Store.t
-> scopes:Scope.t list -> scopes:Scope.t list
-> file_tree:File_tree.t -> file_tree:File_tree.t
-> packages:Package.t String_map.t -> packages:Package.t String_map.t
-> stanzas:(Path.t * Scope.t * Stanzas.t) list -> stanzas:(Path.t * Scope.t * Stanzas.t) list
-> filter_out_optional_stanzas_with_missing_deps:bool -> filter_out_optional_stanzas_with_missing_deps:bool
-> build_system:Build_system.t
-> t -> t
val context : t -> Context.t val context : t -> Context.t
val aliases : t -> Alias.Store.t
val stanzas : t -> Dir_with_jbuild.t list val stanzas : t -> Dir_with_jbuild.t list
val packages : t -> Package.t String_map.t val packages : t -> Package.t String_map.t
val file_tree : t -> File_tree.t val file_tree : t -> File_tree.t
val artifacts : t -> Artifacts.t val artifacts : t -> Artifacts.t
val stanzas_to_consider_for_install : t -> (Path.t * Stanza.t) list val stanzas_to_consider_for_install : t -> (Path.t * Stanza.t) list
val cxx_flags : t -> string list val cxx_flags : t -> string list
val libs : t -> Lib_db.t
val expand_vars : t -> scope:Scope.t -> dir:Path.t -> String_with_vars.t -> string val expand_vars : t -> scope:Scope.t -> dir:Path.t -> String_with_vars.t -> string
val add_rule val add_rule
: t : t
-> ?sandbox:bool -> ?sandbox:bool
-> ?fallback:Jbuild.Rule.Fallback.t -> ?mode:Jbuild.Rule.Mode.t
-> ?locks:Path.t list -> ?locks:Path.t list
-> ?loc:Loc.t -> ?loc:Loc.t
-> (unit, Action.t) Build.t -> (unit, Action.t) Build.t
-> unit -> unit
val add_rule_get_targets
: t
-> ?sandbox:bool
-> ?mode:Jbuild.Rule.Mode.t
-> ?locks:Path.t list
-> ?loc:Loc.t
-> (unit, Action.t) Build.t
-> Path.t list
val add_rules val add_rules
: t : t
-> ?sandbox:bool -> ?sandbox:bool
-> (unit, Action.t) Build.t list -> (unit, Action.t) Build.t list
-> unit -> unit
val rules : t -> Build_interpret.Rule.t list val add_alias_deps
: t
-> Build_system.Alias.t
-> Path.t list
-> unit
val add_alias_action
: t
-> Build_system.Alias.t
-> ?locks:Path.t list
-> stamp:Sexp.t
-> (unit, Action.t) Build.t
-> unit
val sources_and_targets_known_so_far : t -> src_path:Path.t -> String_set.t (** See [Build_system for details] *)
val eval_glob : t -> dir:Path.t -> Re.re -> string list
val load_dir : t -> dir:Path.t -> unit
val on_load_dir : t -> dir:Path.t -> f:(unit -> unit) -> unit
val source_files : t -> src_path:Path.t -> String_set.t
(** [prog_spec t ?hint name] resolve a program. [name] is looked up in the (** [prog_spec t ?hint name] resolve a program. [name] is looked up in the
workspace, if it is not found in the tree is is looked up in the PATH. If it workspace, if it is not found in the tree is is looked up in the PATH. If it
@ -174,16 +198,13 @@ module PP : sig
-> Module.t String_map.t -> Module.t String_map.t
(** Get a path to a cached ppx driver *) (** Get a path to a cached ppx driver *)
val get_ppx_driver val get_ppx_driver : t -> Pp.t list -> Path.t
: t
-> Pp.t list
-> dir:Path.t
-> dep_kind:Build.lib_dep_kind
-> Path.t
(** [cookie_library_name lib_name] is ["--cookie"; lib_name] if [lib_name] is not (** [cookie_library_name lib_name] is ["--cookie"; lib_name] if [lib_name] is not
[None] *) [None] *)
val cookie_library_name : string option -> string list val cookie_library_name : string option -> string list
val gen_rules : t -> string list -> unit
end end
val expand_and_eval_set val expand_and_eval_set

View File

@ -73,19 +73,44 @@ let jbuild_name_in ~dir =
(Path.to_string_maybe_quoted (Path.relative dir "jbuild")) (Path.to_string_maybe_quoted (Path.relative dir "jbuild"))
ctx_name ctx_name
let describe_target fn = type target_kind =
| Regular of string * Path.t
| Alias of string * Path.t
| Other of Path.t
let analyse_target fn =
match Path.extract_build_context fn with match Path.extract_build_context fn with
| Some (".aliases", fn) -> | Some (".aliases", sub) -> begin
let name = match Path.split_first_component sub with
let fn = Path.to_string fn in | None -> Other fn
match String.rsplit2 fn ~on:'-' with | Some (ctx, fn) ->
| None -> assert false if Path.is_root fn then
| Some (name, digest) -> Other fn
assert (String.length digest = 32); else
name let basename =
in match String.rsplit2 (Path.basename fn) ~on:'-' with
sprintf "alias %s" (maybe_quoted name) | None -> assert false
| _ -> | Some (name, digest) ->
assert (String.length digest = 32);
name
in
Alias (ctx, Path.relative (Path.parent fn) basename)
end
| Some (ctx, sub) -> Regular (ctx, sub)
| None ->
Other fn
let describe_target fn =
let ctx_suffix = function
| "default" -> ""
| ctx -> sprintf " (context %s)" ctx
in
match analyse_target fn with
| Alias (ctx, p) ->
sprintf "alias %s%s" (Path.to_string_maybe_quoted p) (ctx_suffix ctx)
| Regular (ctx, fn) ->
sprintf "%s%s" (Path.to_string_maybe_quoted fn) (ctx_suffix ctx)
| Other fn ->
Path.to_string_maybe_quoted fn Path.to_string_maybe_quoted fn
let program_not_found ?context ?hint prog = let program_not_found ?context ?hint prog =

View File

@ -18,6 +18,14 @@ val jbuild_name_in : dir:Path.t -> string
(** Nice description of a target *) (** Nice description of a target *)
val describe_target : Path.t -> string val describe_target : Path.t -> string
type target_kind =
| Regular of string (* build context *) * Path.t
| Alias of string (* build context *) * Path.t
| Other of Path.t
(** Return the name of an alias from its stamp file *)
val analyse_target : Path.t -> target_kind
(** Raise an error about a program not found in the PATH or in the tree *) (** Raise an error about a program not found in the PATH or in the tree *)
val program_not_found val program_not_found
: ?context:string : ?context:string

View File

@ -12,11 +12,11 @@
running in src/foo/baz running in src/foo/baz
running in src running in src
$ $JBUILDER build -j1 --root . @plop $ $JBUILDER build -j1 --root . @plop
File "<command-line>", line 1, characters 0-0: From the command line:
Error: This alias is empty. Error: Alias plop is empty.
Alias "plop" is not defined in . or any of its descendants. It is not defined in . or any of its descendants.
[1] [1]
$ $JBUILDER build -j1 --root . @truc/x $ $JBUILDER build -j1 --root . @truc/x
File "<command-line>", line 1, characters 0-0: From the command line:
Error: Don't know about directory truc! Error: Don't know about directory truc!
[1] [1]

View File

@ -23,5 +23,4 @@
(alias (alias
((name runtest) ((name runtest)
(deps (META.foobar)) (action (echo "${read:META.foobar}"))))
(action (echo "${read:META.foobar}"))))