dune/bin/main.ml

416 lines
13 KiB
OCaml
Raw Normal View History

2017-02-21 15:09:58 +00:00
open Jbuilder
open Import
open Jbuilder_cmdliner.Cmdliner
(* Things in src/ don't depend on cmdliner to speed up the bootstrap, so we set this
reference here *)
let () = suggest_function := Jbuilder_cmdliner.Cmdliner_suggest.value
2017-02-21 15:09:58 +00:00
let (>>=) = Future.(>>=)
type common =
2017-02-27 15:04:49 +00:00
{ concurrency : int
; debug_rules : bool
; debug_dep_path : bool
; debug_findlib : bool
; dev_mode : bool
; workspace_file : string option
2017-02-28 07:32:15 +00:00
; root : string
; target_prefix : string
2017-02-21 15:09:58 +00:00
}
2017-02-28 07:32:15 +00:00
let prefix_target common s = common.target_prefix ^ s
2017-02-21 15:09:58 +00:00
let set_common c =
Clflags.concurrency := c.concurrency;
Clflags.debug_rules := c.debug_rules;
Clflags.debug_dep_path := c.debug_dep_path;
Clflags.debug_findlib := c.debug_findlib;
2017-02-28 07:32:15 +00:00
Clflags.dev_mode := c.dev_mode;
Printf.eprintf "Workspace root: %s\n" c.root;
if c.root <> Filename.current_dir_name then
Sys.chdir c.root
2017-02-21 15:09:58 +00:00
2017-02-27 15:04:49 +00:00
module Main = struct
include Jbuilder.Main
let setup common =
setup ?workspace_file:common.workspace_file ()
end
let create_log = Main.create_log
2017-02-28 07:32:15 +00:00
type ('a, 'b) walk_result =
| Cont of 'a
| Stop of 'b
let rec walk_parents dir ~init ~f =
match f init dir with
| Stop x -> Stop x
| Cont x ->
let parent = Filename.dirname dir in
if parent = dir then
Cont x
else
walk_parents parent ~init:x ~f
let find_root () =
let cwd = Sys.getcwd () in
let rec loop counter ~candidates ~to_cwd dir =
let files = Sys.readdir dir |> Array.to_list |> String_set.of_list in
if String_set.mem "jbuild-workspace" files then
cont counter ~candidates:((0, dir, to_cwd) :: candidates) dir ~to_cwd
else if String_set.exists files ~f:(fun fn ->
String.is_suffix fn ~suffix:".install") then
cont counter ~candidates:((1, dir, to_cwd) :: candidates) dir ~to_cwd
else if String_set.mem ".git" files || String_set.mem ".hg" files then
cont counter ~candidates:((2, dir, to_cwd) :: candidates) dir ~to_cwd
else
cont counter ~candidates dir ~to_cwd
and cont counter ~candidates ~to_cwd dir =
if counter > String.length cwd then
candidates
else
let parent = Filename.dirname dir in
if parent = dir then
candidates
else
let base = Filename.basename dir in
loop (counter + 1) parent ~candidates ~to_cwd:(base :: to_cwd)
in
match loop 0 ~candidates:[] ~to_cwd:[] cwd with
| [] -> (cwd, [])
| l ->
let lowest_priority =
List.fold_left l ~init:max_int ~f:(fun acc (prio, _, _) ->
min acc prio)
in
match List.find l ~f:(fun (prio, _, _) -> prio = lowest_priority) with
| None -> assert false
| Some (_, dir, to_cwd) -> (dir, to_cwd)
2017-02-21 15:09:58 +00:00
let copts_sect = "COMMON OPTIONS"
let help_secs =
[ `S copts_sect
; `P "These options are common to all commands."
; `S "MORE HELP"
; `P "Use `$(mname) $(i,COMMAND) --help' for help on a single command."
; `S "BUGS"
; `P "Check bug reports at https://github.com/janestreet/jbuilder/issues"
]
let common =
2017-02-28 07:32:15 +00:00
let make concurrency debug_rules debug_dep_path debug_findlib dev_mode
workspace_file root =
let root, to_cwd =
match root with
| Some dn -> (dn, [])
| None -> find_root ()
in
{ concurrency
; debug_rules
; debug_dep_path
; debug_findlib
; dev_mode
2017-02-27 15:04:49 +00:00
; workspace_file
2017-02-28 07:32:15 +00:00
; root
; target_prefix = String.concat ~sep:"" (List.map to_cwd ~f:(sprintf "%s/"))
}
in
2017-02-21 15:09:58 +00:00
let docs = copts_sect in
let concurrency =
Arg.(value & opt int !Clflags.concurrency & info ["j"] ~docs) in
let drules = Arg.(value & flag & info ["drules"] ~docs) in
let ddep_path = Arg.(value & flag & info ["ddep-path"] ~docs) in
let dfindlib = Arg.(value & flag & info ["dfindlib"] ~docs) in
let dev = Arg.(value & flag & info ["dev"] ~docs) in
2017-02-27 15:04:49 +00:00
let workspace_file =
Arg.(value
& opt (some file) None
& info ["workspace"] ~docs
2017-02-28 07:32:15 +00:00
~doc:"Use this specific workspace file instead of looking it up.")
in
let root =
Arg.(value
& opt (some dir) None
& info ["root"] ~docs
~doc:"Use this directory as workspace root instead of guessing it.\n\
Note that this option doesn't change the interpretation of \
targets given on the command line.\n\
It is only intended for scripts.")
2017-02-27 15:04:49 +00:00
in
Term.(const make
$ concurrency
$ drules
$ ddep_path
$ dfindlib
$ dev
$ workspace_file
2017-02-28 07:32:15 +00:00
$ root
2017-02-27 15:04:49 +00:00
)
2017-02-21 15:09:58 +00:00
let installed_libraries =
let doc = "Print out libraries installed on the system." in
let go common =
set_common common;
Future.Scheduler.go ~log:(create_log ())
(Context.default () >>= fun ctx ->
2017-02-28 06:01:27 +00:00
let findlib = ctx.findlib in
let pkgs = Findlib.all_packages findlib in
let max_len = List.longest_map pkgs ~f:(fun p -> p.name) in
List.iter pkgs ~f:(fun pkg ->
let ver =
match pkg.Findlib.version with
| "" -> "n/a"
| v -> v
in
Printf.printf "%-*s (version: %s)\n" max_len pkg.name ver);
Future.return ())
in
( Term.(const go
$ common)
, Term.info "installed-libraries" ~doc
)
2017-02-24 15:41:52 +00:00
let resolve_package_install setup pkg =
match Main.package_install_file setup pkg with
| Ok path -> path
| Error () ->
die "Unknown package %s!%s" pkg (hint pkg (String_map.keys setup.packages))
2017-02-27 15:04:49 +00:00
let build_package common pkg =
Future.Scheduler.go ~log:(create_log ())
2017-02-27 15:04:49 +00:00
(Main.setup common >>= fun setup ->
2017-02-24 15:41:52 +00:00
Build_system.do_build_exn setup.build_system
[resolve_package_install setup pkg])
2017-02-21 15:09:58 +00:00
let build_package =
let doc = "Build a single package in release mode." in
2017-02-23 10:23:15 +00:00
let name_ = Arg.info [] ~docv:"PACKAGE-NAME" in
2017-02-21 15:09:58 +00:00
let go common pkg =
set_common common;
2017-02-27 15:04:49 +00:00
build_package common pkg
2017-02-21 15:09:58 +00:00
in
( Term.(const go
$ common
$ Arg.(required & pos 0 (some string) None name_))
, Term.info "build-package" ~doc ~man:help_secs)
let external_lib_deps packages =
let log = create_log () in
2017-02-21 15:09:58 +00:00
let deps =
2017-02-24 13:08:37 +00:00
Path.Map.fold (Main.external_lib_deps ~log ~packages ()) ~init:String_map.empty
2017-02-21 15:09:58 +00:00
~f:(fun ~key:_ ~data:deps acc -> Build.merge_lib_deps deps acc)
in
String_map.iter deps ~f:(fun ~key:n ~data ->
match (data : Build.lib_dep_kind) with
| Required -> Printf.printf "%s\n" n
| Optional -> Printf.printf "%s (optional)\n" n)
let external_lib_deps =
let doc = "Print out external library dependencies." in
2017-02-23 10:23:15 +00:00
let name_ = Arg.info [] ~docv:"PACKAGE-NAME" in
2017-02-21 15:09:58 +00:00
let go common packages =
set_common common;
external_lib_deps packages
in
( Term.(const go
$ common
$ Arg.(non_empty & pos_all string [] name_))
, Term.info "external-lib-deps" ~doc ~man:help_secs)
2017-02-24 12:31:01 +00:00
type target =
| File of Path.t
| Alias of Path.t * Alias.t
2017-02-28 07:32:15 +00:00
let resolve_targets common (setup : Main.setup) user_targets =
match user_targets with
| [] -> []
| _ ->
2017-02-24 12:31:01 +00:00
let targets =
2017-02-25 02:14:32 +00:00
List.concat_map user_targets ~f:(fun s ->
2017-02-24 12:31:01 +00:00
if String.is_prefix s ~prefix:"@" then
let s = String.sub s ~pos:1 ~len:(String.length s - 1) in
2017-02-28 07:32:15 +00:00
let path = Path.relative Path.root (prefix_target common s) in
2017-02-24 12:31:01 +00:00
if Path.is_root path then
die "@ on the command line must be followed by a valid alias name"
else
let dir = Path.parent path in
2017-02-25 01:33:37 +00:00
let name = Path.basename path in
2017-02-25 02:14:32 +00:00
[Alias (path, Alias.make ~dir name)]
else
2017-02-28 07:32:15 +00:00
let path = Path.relative Path.root (prefix_target common s) in
2017-02-25 14:01:08 +00:00
let can't_build path =
die "Don't know how to build %s" (Path.to_string path)
in
if not (Path.is_local path) then
2017-02-25 02:14:32 +00:00
[File path]
2017-02-25 14:01:08 +00:00
else if Path.is_in_build_dir path then begin
if Build_system.is_target setup.build_system path then
[File path]
else
can't_build path
end else
2017-02-25 02:14:32 +00:00
match
2017-02-25 14:01:08 +00:00
let l =
List.filter_map setup.contexts ~f:(fun ctx ->
let path = Path.append ctx.Context.build_dir path in
if Build_system.is_target setup.build_system path then
Some (File path)
else
None)
in
if Build_system.is_target setup.build_system path ||
Path.exists path then
File path :: l
else
l
2017-02-25 02:14:32 +00:00
with
2017-02-25 14:01:08 +00:00
| [] -> can't_build path
2017-02-25 02:14:32 +00:00
| l -> l
)
in
Printf.printf "Building the following targets:\n";
2017-02-24 12:31:01 +00:00
List.iter targets ~f:(function
| File path ->
Printf.printf "- %s\n" (Path.to_string path)
| Alias (path, _) ->
Printf.printf "- alias %s\n" (Path.to_string path));
flush stdout;
2017-02-24 12:31:01 +00:00
List.map targets ~f:(function
| File path -> path
| Alias (_, alias) -> Alias.file alias)
let build_targets =
let doc = "Build targets." in
2017-02-23 10:23:15 +00:00
let name_ = Arg.info [] ~docv:"TARGET" in
2017-02-21 15:09:58 +00:00
let go common targets =
set_common common;
Future.Scheduler.go ~log:(create_log ())
2017-02-27 15:04:49 +00:00
(Main.setup common >>= fun setup ->
2017-02-28 07:32:15 +00:00
let targets = resolve_targets common setup targets in
Build_system.do_build_exn setup.build_system targets) in
2017-02-21 15:09:58 +00:00
( Term.(const go
$ common
$ Arg.(non_empty & pos_all string [] name_))
, Term.info "build" ~doc ~man:help_secs)
2017-02-21 15:09:58 +00:00
2017-02-23 13:17:25 +00:00
let runtest =
let doc = "Run tests." in
2017-02-23 13:17:25 +00:00
let name_ = Arg.info [] ~docv:"DIR" in
let go common dirs =
set_common common;
Future.Scheduler.go ~log:(create_log ())
2017-02-27 15:04:49 +00:00
(Main.setup common >>= fun setup ->
2017-02-23 13:17:25 +00:00
let targets =
List.map dirs ~f:(fun dir ->
2017-02-28 07:32:15 +00:00
let dir = Path.(relative root) (prefix_target common dir) in
2017-02-23 13:17:25 +00:00
Alias.file (Alias.runtest ~dir))
in
Build_system.do_build_exn setup.build_system targets) in
2017-02-23 13:17:25 +00:00
( Term.(const go
$ common
2017-02-24 15:41:52 +00:00
$ Arg.(value & pos_all string ["."] name_))
2017-02-23 13:17:25 +00:00
, Term.info "runtest" ~doc ~man:help_secs)
let opam_installer () =
match Bin.which "opam-installer" with
2017-02-24 15:41:52 +00:00
| None ->
die "\
Sorry, you need the opam-installer tool to be able to install or
uninstall packages.
I couldn't find the opam-installer binary :-("
| Some fn -> fn
let get_prefix context ~from_command_line =
2017-02-24 15:41:52 +00:00
match from_command_line with
| Some p -> Future.return (Path.of_string p)
| None -> Context.install_prefix context
2017-02-24 15:41:52 +00:00
let install_uninstall ~what =
2017-02-26 21:30:28 +00:00
let doc =
sprintf "%s packages using opam-installer." (String.capitalize_ascii what)
in
2017-02-24 15:41:52 +00:00
let name_ = Arg.info [] ~docv:"PACKAGE" in
let go common prefix pkgs =
set_common common;
let opam_installer = opam_installer () in
2017-02-24 15:41:52 +00:00
Future.Scheduler.go ~log:(create_log ())
2017-02-27 15:04:49 +00:00
(Main.setup common >>= fun setup ->
let pkgs =
match pkgs with
| [] -> String_map.keys setup.packages
| l -> l
in
2017-02-24 15:41:52 +00:00
let install_files, missing_install_files =
List.concat_map pkgs ~f:(fun pkg ->
2017-02-24 15:41:52 +00:00
let fn = resolve_package_install setup pkg in
List.map setup.contexts ~f:(fun ctx ->
let fn = Path.append ctx.Context.build_dir fn in
if Path.exists fn then
Inl (ctx, fn)
else
Inr fn))
|> List.partition_map ~f:(fun x -> x)
2017-02-24 15:41:52 +00:00
in
if missing_install_files <> [] then begin
die "The following <package>.install are missing:\n\
2017-02-24 15:41:52 +00:00
%s\n\
You need to run: jbuilder build @install"
2017-02-24 15:41:52 +00:00
(String.concat ~sep:"\n"
(List.map missing_install_files
~f:(fun p -> sprintf "- %s" (Path.to_string p))))
2017-02-24 15:41:52 +00:00
end;
2017-02-25 02:38:41 +00:00
(match setup.contexts, prefix with
| _ :: _ :: _, Some _ ->
die "Cannot specify --prefix when installing into multiple contexts!"
| _ -> ());
let module CMap = Map.Make(Context) in
let install_files_by_context = CMap.of_alist_multi install_files |> CMap.bindings in
2017-02-24 15:41:52 +00:00
Future.all_unit
(List.map install_files_by_context ~f:(fun (context, install_files) ->
get_prefix context ~from_command_line:prefix >>= fun prefix ->
Future.all_unit
(List.map install_files ~f:(fun path ->
Future.run Strict (Path.to_string opam_installer)
[ sprintf "-%c" what.[0]
; "--prefix"
; Path.to_string prefix
; Path.to_string path
])))))
2017-02-24 15:41:52 +00:00
in
( Term.(const go
$ common
$ Arg.(value & opt (some dir) None & info ["prefix"])
$ Arg.(value & pos_all string [] name_))
, Term.info what ~doc ~man:help_secs)
let install = install_uninstall ~what:"install"
let uninstall = install_uninstall ~what:"uninstall"
2017-02-21 15:09:58 +00:00
let all =
[ installed_libraries
2017-02-23 13:17:25 +00:00
; build_package
; external_lib_deps
; build_targets
2017-02-23 13:17:25 +00:00
; runtest
2017-02-24 15:41:52 +00:00
; install
; uninstall
2017-02-23 13:17:25 +00:00
]
2017-02-21 15:09:58 +00:00
let default =
let doc = "fast, portable and opinionated build system for OCaml" in
( Term.(ret @@ const @@ `Help (`Pager, None))
, Term.info "jbuilder" ~doc)
2017-02-21 15:09:58 +00:00
let () =
2017-02-24 11:28:30 +00:00
Ansi_color.setup_err_formatter_colors ();
2017-02-21 15:09:58 +00:00
try
match Term.eval_choice default all ~catch:false with
2017-02-21 15:09:58 +00:00
| `Error _ -> exit 1
| _ -> exit 0
with exn ->
Format.eprintf "%a@?" (Main.report_error ?map_fname:None) exn;
exit 1