dune/src/context.ml

531 lines
18 KiB
OCaml
Raw Normal View History

2016-12-02 13:54:32 +00:00
open Import
open Fiber.O
2016-12-02 13:54:32 +00:00
module Kind = struct
2017-02-26 21:28:30 +00:00
module Opam = struct
type t =
{ root : string
; switch : string
}
end
type t = Default | Opam of Opam.t
2017-03-10 11:22:01 +00:00
let sexp_of_t : t -> Sexp.t = function
| Default -> Sexp.unsafe_atom_of_string "default"
2017-03-10 11:22:01 +00:00
| Opam o ->
Sexp.To_sexp.(record [ "root" , string o.root
; "switch", string o.switch
2017-03-10 11:22:01 +00:00
])
2016-12-02 13:54:32 +00:00
end
module Env_var = struct
type t = string
let compare a b =
if Sys.win32 then
String.compare (String.lowercase a) (String.lowercase b)
else
String.compare a b
end
module Env_var_map = Map.Make(Env_var)
2016-12-02 13:54:32 +00:00
type t =
2017-02-25 00:28:10 +00:00
{ name : string
; kind : Kind.t
; merlin : bool
2016-12-02 13:54:32 +00:00
; for_host : t option
; implicit : bool
2016-12-02 13:54:32 +00:00
; build_dir : Path.t
; path : Path.t list
; toplevel_path : Path.t option
2016-12-02 13:54:32 +00:00
; ocaml_bin : Path.t
; ocaml : Path.t
; ocamlc : Path.t
; ocamlopt : Path.t option
; ocamldep : Path.t
; ocamlmklib : Path.t
; env : string array
; env_extra : string Env_var_map.t
2017-02-28 06:01:27 +00:00
; findlib : Findlib.t
; findlib_toolchain : string option
2016-12-02 13:54:32 +00:00
; arch_sixtyfour : bool
2017-02-28 06:01:27 +00:00
; opam_var_cache : (string, string) Hashtbl.t
; natdynlink_supported : bool
; ocamlc_config : Ocamlc_config.t
; version_string : string
; version : int * int * int
2016-12-02 13:54:32 +00:00
; stdlib_dir : Path.t
; ccomp_type : string
2017-03-31 16:31:55 +00:00
; c_compiler : string
; ocamlc_cflags : string list
; ocamlopt_cflags : string list
; bytecomp_c_libraries : string list
; native_c_libraries : string list
2016-12-02 13:54:32 +00:00
; native_pack_linker : string
; ranlib : string
; cc_profile : string
; architecture : string
; system : string
; ext_obj : string
; ext_asm : string
; ext_lib : string
; ext_dll : string
; ext_exe : string
2016-12-02 13:54:32 +00:00
; os_type : string
; default_executable_name : string
; host : string
; target : string
; flambda : bool
; exec_magic_number : string
; cmi_magic_number : string
; cmo_magic_number : string
; cma_magic_number : string
; cmx_magic_number : string
; cmxa_magic_number : string
; ast_impl_magic_number : string
; ast_intf_magic_number : string
; cmxs_magic_number : string
; cmt_magic_number : string
; which_cache : (string, Path.t option) Hashtbl.t
2016-12-02 13:54:32 +00:00
}
2017-03-10 11:22:01 +00:00
let sexp_of_t t =
let open Sexp.To_sexp in
let path = Path.sexp_of_t in
record
[ "name", string t.name
2017-03-10 11:22:01 +00:00
; "kind", Kind.sexp_of_t t.kind
; "merlin", bool t.merlin
; "for_host", option string (Option.map t.for_host ~f:(fun t -> t.name))
2017-03-10 11:22:01 +00:00
; "build_dir", path t.build_dir
; "toplevel_path", option path t.toplevel_path
; "ocaml_bin", path t.ocaml_bin
; "ocaml", path t.ocaml
; "ocamlc", path t.ocamlc
; "ocamlopt", option path t.ocamlopt
; "ocamldep", path t.ocamldep
; "ocamlmklib", path t.ocamlmklib
; "env", list (pair string string) (Env_var_map.to_list t.env_extra)
2017-03-10 11:22:01 +00:00
; "findlib_path", list path (Findlib.path t.findlib)
; "arch_sixtyfour", bool t.arch_sixtyfour
; "natdynlink_supported", bool t.natdynlink_supported
; "opam_vars", string_hashtbl string t.opam_var_cache
; "ocamlc_config", Ocamlc_config.sexp_of_t t.ocamlc_config
; "which", string_hashtbl (option path) t.which_cache
2017-03-10 11:22:01 +00:00
]
let compare a b = compare a.name b.name
2016-12-02 13:54:32 +00:00
let get_arch_sixtyfour stdlib_dir =
let files = ["caml/config.h"; "caml/m.h"] in
let get_arch_sixtyfour_from file =
2017-11-06 08:30:50 +00:00
let file_path = Path.to_string (Path.relative stdlib_dir file) in
if Sys.file_exists file_path then begin
List.exists (Io.lines_of_file file_path) ~f:(fun line ->
match String.extract_blank_separated_words line with
| ["#define"; "ARCH_SIXTYFOUR"] -> true
| _ -> false)
end else
false
in
List.exists ~f:get_arch_sixtyfour_from files
2016-12-02 13:54:32 +00:00
2017-02-24 15:41:52 +00:00
let opam_config_var ~env ~cache var =
match Hashtbl.find cache var with
| Some _ as x -> Fiber.return x
2017-02-24 15:41:52 +00:00
| None ->
match Bin.opam with
| None -> Fiber.return None
2017-02-24 15:41:52 +00:00
| Some fn ->
Process.run_capture (Accept All) (Path.to_string fn) ~env
["config"; "var"; var]
>>| function
| Ok s ->
let s = String.trim s in
Hashtbl.add cache var s;
Some s
| Error _ -> None
2017-02-24 15:41:52 +00:00
let get_env env var =
let rec loop i =
if i = Array.length env then
None
else
let entry = env.(i) in
match String.lsplit2 entry ~on:'=' with
| Some (key, value) when Env_var.compare key var = Eq ->
Some value
| _ -> loop (i + 1)
in
loop 0
let which ~cache ~path x =
Hashtbl.find_or_add cache x ~f:(Bin.which ~path)
2017-03-10 11:22:01 +00:00
let extend_env ~vars ~env =
if Env_var_map.is_empty vars then
2017-03-10 11:22:01 +00:00
env
else
let imported =
Array.to_list env
|> List.filter ~f:(fun s ->
match String.index s '=' with
| None -> true
| Some i ->
let key = String.sub s ~pos:0 ~len:i in
not (Env_var_map.mem vars key))
2017-03-10 11:22:01 +00:00
in
List.rev_append
(List.map (Env_var_map.to_list vars)
~f:(fun (k, v) -> sprintf "%s=%s" k v))
2017-03-10 11:22:01 +00:00
imported
|> Array.of_list
let create ~(kind : Kind.t) ~path ~base_env ~env_extra ~name ~merlin
~targets () =
2017-03-10 11:22:01 +00:00
let env = extend_env ~env:base_env ~vars:env_extra in
2017-02-24 15:41:52 +00:00
let opam_var_cache = Hashtbl.create 128 in
(match kind with
| Opam { root; _ } ->
Hashtbl.add opam_var_cache "root" root
2017-02-24 15:41:52 +00:00
| Default -> ());
2016-12-02 13:54:32 +00:00
let prog_not_found_in_path prog =
2017-03-31 16:31:55 +00:00
Utils.program_not_found prog ~context:name
2016-12-02 13:54:32 +00:00
in
let which_cache = Hashtbl.create 128 in
let which x = which ~cache:which_cache ~path x in
let findlib_config_path = lazy (
match which "ocamlfind" with
| None -> prog_not_found_in_path "ocamlfind"
| Some fn ->
(* When OCAMLFIND_CONF is set, "ocamlfind printconf" does print the contents of the
variable, but "ocamlfind printconf conf" still prints the configuration file set
at the configuration time of ocamlfind, sigh... *)
match Sys.getenv "OCAMLFIND_CONF" with
| s -> Fiber.return (Path.absolute s)
| exception Not_found ->
Process.run_capture_line ~env Strict
(Path.to_string fn) ["printconf"; "conf"]
>>| Path.absolute)
2016-12-02 13:54:32 +00:00
in
let create_one ~name ~implicit ?findlib_toolchain ?host ~merlin () =
(match findlib_toolchain with
| None -> Fiber.return None
| Some toolchain ->
Lazy.force findlib_config_path >>| fun path ->
Some (Findlib.Config.load path ~toolchain ~context:name))
>>= fun findlib_config ->
let get_tool_using_findlib_config prog =
Option.bind findlib_config ~f:(fun conf ->
match Findlib.Config.get conf prog with
Refactor library management (#516) Lib module ---------- We have a new module Lib that replaces Lib, parts of Lib_db and parts of Findlib. It is used to manage all libraries (internal and extrernal). Lib.t represent a completely resolved library, i.e. where all the dependencies have been resolved. Lib.Compile is used to provide what is necessary to build the library itself. Lib.Meta provides what is necessary to generate the META file for the library. We also have library databases represented as Lib.DB.t. A library database is simply a mapping from names to Lib.t values and and created from a resolve function that looks up a name and return a Lib.Info.t. A Lib.Info.t is the same as a Lib.t except that dependencies are not resolved. A library database can have a parent database that is used to lookup names that are not found in the current database. In practice we have the following hierarchy: 1. For every scope, we have a library database that holds all the libraries of this scope. In this DB, a library can be referred by either it's name or public name 2. the parent of each of these databases is a database that holds all the public libraries of the workspace. In this DB libraries must be referred by their public name 3. the parent of this DB is for installed libraries (1) databases are accessible via Scope.libs (Super_context.find_scope_by_{name,dir} sctx xxx) (2) is accessible via Super_context.public_libs sctx (3) is accessible via Super_context.installed_libs sctx The dependencies of a library are always resolved inside the DB it is part of. When we compute a transitive closure, we check that we don't have two libraries from two different DB with the same name. So for instance linting Base should now supported. Jbuild.Scope_info ----------------- Jbuild.Scope was renamed Jbuild.Scope_info Scope module ------------ This replaces Lib_db. A Scope.t is now just a pair of a Jbuild.Scope_info.t and a Lib.DB.t. Scope.DB.t is an object used to lookup scopes by either name or directory. We no longer have an external scope or special anonymous scope. Instead one should use Super_context.installed_libs or Super_context.public_libs depending on the context.
2018-02-20 11:46:10 +00:00
| None -> None
| Some s ->
match Filename.analyze_program_name s with
| In_path | Relative_to_current_dir -> which s
| Absolute -> Some (Path.absolute s))
in
let ocamlc =
match get_tool_using_findlib_config "ocamlc" with
| Some x -> x
| None ->
match which "ocamlc" with
| Some x -> x
| None -> prog_not_found_in_path "ocamlc"
in
let dir = Path.parent ocamlc in
let ocaml_tool_not_found prog =
die "ocamlc found in %s, but %s/%s doesn't exist (context: %s)"
(Path.to_string dir) (Path.to_string dir) prog name
in
let get_ocaml_tool prog =
match get_tool_using_findlib_config prog with
| None -> Bin.best_prog dir prog
| Some _ as x -> x
in
let get_ocaml_tool_exn prog =
match get_ocaml_tool prog with
| None -> ocaml_tool_not_found prog
| Some fn -> fn
in
let build_dir = Path.of_string (sprintf "_build/%s" name) in
let findlib_path () =
match kind, findlib_toolchain, Setup.library_path with
| Default, None, Some l ->
Fiber.return (List.map l ~f:Path.absolute)
| _ ->
(* If ocamlfind is present, it has precedence over everything else. *)
match which "ocamlfind" with
| Some fn ->
let args =
let args = ["printconf"; "path"] in
match findlib_toolchain with
| None -> args
| Some s -> "-toolchain" :: s :: args
in
Process.run_capture_lines ~env Strict (Path.to_string fn) args
>>| List.map ~f:Path.absolute
| None ->
(* If there no ocamlfind in the PATH, check if we have opam
and assume a standard opam setup *)
opam_config_var ~env ~cache:opam_var_cache "lib"
>>| function
| Some s -> [Path.absolute s]
| None ->
(* If neither opam neither ocamlfind are present, assume
that libraries are [dir ^ "/../lib"] *)
[Path.relative (Path.parent dir) "lib"]
in
Fiber.fork_and_join
findlib_path
(fun () ->
Process.run_capture_lines ~env Strict
(Path.to_string ocamlc) ["-config"]
>>| Ocamlc_config.of_lines)
>>= fun (findlib_path, ocamlc_config) ->
let version = Ocamlc_config.version ocamlc_config in
let env, env_extra =
(* See comment in ansi_color.ml for setup_env_for_colors. For
OCaml < 4.05, OCAML_COLOR is not supported so we use
OCAMLPARAM. OCaml 4.02 doesn't support 'color' in OCAMLPARAM,
so we just don't force colors with 4.02. *)
if !Clflags.capture_outputs
&& Lazy.force Colors.stderr_supports_colors
&& version >= (4, 03, 0)
&& version < (4, 05, 0) then
let value =
match get_env env "OCAMLPARAM" with
| None -> "color=always,_"
| Some s -> "color=always," ^ s
in
extend_env ~env ~vars:((Env_var_map.singleton "OCAMLPARAM" value)),
(Env_var_map.add env_extra "OCAMLPARAM" value)
else
env,env_extra
in
let stdlib_dir = Path.of_string (Ocamlc_config.stdlib_dir ocamlc_config) in
let natdynlink_supported =
Ocamlc_config.natdynlink_supported ocamlc_config
in
let version = Ocamlc_config.version ocamlc_config in
let version_string = Ocamlc_config.version_string ocamlc_config in
let get = Ocamlc_config.get ocamlc_config in
let get_strings = Ocamlc_config.get_strings ocamlc_config in
let arch_sixtyfour =
match Ocamlc_config.word_size ocamlc_config with
| Some ws -> ws = "64"
| None -> get_arch_sixtyfour stdlib_dir
in
Fiber.return
{ name
; implicit
; kind
; merlin
; for_host = host
; build_dir
; path
; toplevel_path = Option.map (get_env env "OCAML_TOPLEVEL_PATH") ~f:Path.absolute
; ocaml_bin = dir
; ocaml = (match which "ocaml" with Some p -> p | None -> prog_not_found_in_path "ocaml")
; ocamlc
; ocamlopt = get_ocaml_tool "ocamlopt"
; ocamldep = get_ocaml_tool_exn "ocamldep"
; ocamlmklib = get_ocaml_tool_exn "ocamlmklib"
; env
; env_extra
; findlib = Findlib.create ~stdlib_dir ~path:findlib_path
; findlib_toolchain
; arch_sixtyfour
; opam_var_cache
; natdynlink_supported
; stdlib_dir
; ocamlc_config
; version_string
; version
; ccomp_type = get "ccomp_type"
; c_compiler = Ocamlc_config.c_compiler ocamlc_config
; ocamlc_cflags = Ocamlc_config.ocamlc_cflags ocamlc_config
; ocamlopt_cflags = Ocamlc_config.ocamlopt_cflags ocamlc_config
; bytecomp_c_libraries = get_strings "bytecomp_c_libraries"
; native_c_libraries = get_strings "native_c_libraries"
; native_pack_linker = get "native_pack_linker"
; ranlib = get "ranlib"
; cc_profile = get "cc_profile"
; architecture = get "architecture"
; system = get "system"
; ext_obj = Ocamlc_config.ext_obj ocamlc_config
; ext_asm = Ocamlc_config.ext_asm ocamlc_config
; ext_lib = Ocamlc_config.ext_lib ocamlc_config
; ext_dll = Ocamlc_config.ext_dll ocamlc_config
; ext_exe = Ocamlc_config.ext_exe ocamlc_config
; os_type = get "os_type"
; default_executable_name = get "default_executable_name"
; host = get "host"
; target = get "target"
; flambda = Ocamlc_config.flambda ocamlc_config
; exec_magic_number = get "exec_magic_number"
; cmi_magic_number = get "cmi_magic_number"
; cmo_magic_number = get "cmo_magic_number"
; cma_magic_number = get "cma_magic_number"
; cmx_magic_number = get "cmx_magic_number"
; cmxa_magic_number = get "cmxa_magic_number"
; ast_impl_magic_number = get "ast_impl_magic_number"
; ast_intf_magic_number = get "ast_intf_magic_number"
; cmxs_magic_number = get "cmxs_magic_number"
; cmt_magic_number = get "cmt_magic_number"
; which_cache
}
with Ocamlc_config.E msg ->
die "Failed to parse the output of '%s -config':@\n\
%s"
(Path.to_string ocamlc) msg
in
let implicit = not (List.mem ~set:targets Workspace.Context.Target.Native) in
create_one () ~implicit ~name ~merlin >>= fun native ->
Fiber.parallel_map targets ~f:(function
| Native -> Fiber.return None
| Named findlib_toolchain ->
let name = sprintf "%s.%s" name findlib_toolchain in
create_one () ~implicit:false ~name ~findlib_toolchain ~host:native
~merlin:false
>>| fun x -> Some x)
>>| fun others ->
native :: List.filter_map others ~f:(fun x -> x)
2016-12-02 13:54:32 +00:00
2017-02-24 15:41:52 +00:00
let opam_config_var t var = opam_config_var ~env:t.env ~cache:t.opam_var_cache var
let initial_env = lazy (
Lazy.force Colors.setup_env_for_colors;
Unix.environment ())
2016-12-02 13:54:32 +00:00
let default ?(merlin=true) ~targets () =
2016-12-02 13:54:32 +00:00
let env = Lazy.force initial_env in
let path =
match get_env env "PATH" with
| Some s -> Bin.parse_path s
| None -> []
2016-12-02 13:54:32 +00:00
in
create ~kind:Default ~path ~base_env:env ~env_extra:Env_var_map.empty
~name:"default" ~merlin ~targets ()
2016-12-02 13:54:32 +00:00
let create_for_opam ?root ~targets ~switch ~name ?(merlin=false) () =
2016-12-02 13:54:32 +00:00
match Bin.opam with
2017-03-31 16:31:55 +00:00
| None -> Utils.program_not_found "opam"
2016-12-02 13:54:32 +00:00
| Some fn ->
(match root with
| Some root -> Fiber.return root
2016-12-02 13:54:32 +00:00
| None ->
Process.run_capture_line Strict (Path.to_string fn) ["config"; "var"; "root"])
2016-12-02 13:54:32 +00:00
>>= fun root ->
Process.run_capture Strict (Path.to_string fn)
2016-12-02 13:54:32 +00:00
["config"; "env"; "--root"; root; "--switch"; switch; "--sexp"]
>>= fun s ->
let vars =
2017-12-12 10:16:17 +00:00
Usexp.parse_string ~fname:"<opam output>" ~mode:Single s
|> Sexp.Of_sexp.(list (pair string string))
|> Env_var_map.of_list_multi
|> Env_var_map.mapi ~f:(fun var values ->
match List.rev values with
| [] -> assert false
| [x] -> x
| x :: _ ->
Format.eprintf
"@{<warning>Warning@}: variable %S present multiple times in the output of:\n\
@{<details>%s@}@."
var
(String.concat ~sep:" "
(List.map ~f:quote_for_shell
[Path.to_string fn; "config"; "env"; "--root"; root;
"--switch"; switch; "--sexp"]));
x)
2016-12-02 13:54:32 +00:00
in
let path =
match Env_var_map.find vars "PATH" with
2016-12-02 13:54:32 +00:00
| None -> Bin.path
| Some s -> Bin.parse_path s
in
let env = Lazy.force initial_env in
create ~kind:(Opam { root; switch }) ~targets
~path ~base_env:env ~env_extra:vars ~name ~merlin ()
let create ?merlin def =
match (def : Workspace.Context.t) with
| Default targets -> default ~targets ?merlin ()
| Opam { name; switch; root; targets; _ } ->
create_for_opam ?root ~switch ~name ?merlin ~targets ()
2016-12-02 13:54:32 +00:00
let which t s = which ~cache:t.which_cache ~path:t.path s
2017-02-24 15:41:52 +00:00
let install_prefix t =
opam_config_var t "prefix" >>| function
| Some x -> Path.absolute x
| None -> Path.parent t.ocaml_bin
2017-03-01 12:09:57 +00:00
let install_ocaml_libdir t =
match t.kind, t.findlib_toolchain, Setup.library_destdir with
| Default, None, Some d ->
Fiber.return (Some (Path.absolute d))
| _ ->
(* If ocamlfind is present, it has precedence over everything else. *)
match which t "ocamlfind" with
| Some fn ->
(Process.run_capture_line ~env:t.env Strict
(Path.to_string fn) ["printconf"; "destdir"]
>>| fun s ->
Some (Path.absolute s))
| None ->
Fiber.return None
2017-03-01 12:09:57 +00:00
(* CR-someday jdimino: maybe we should just do this for [t.env] directly? *)
let env_for_exec t =
let sep = if Sys.win32 then ';' else ':' in
let cwd = Sys.getcwd () in
let extend_var var v =
let v = Filename.concat cwd (Path.to_string v) in
match get_env t.env var with
| None -> (var, v)
| Some prev -> (var, sprintf "%s%c%s" v sep prev)
in
let vars =
[ extend_var "CAML_LD_LIBRARY_PATH"
(Path.relative
(Config.local_install_dir ~context:t.name)
"lib/stublibs")
; extend_var "OCAMLPATH"
(Path.relative
(Config.local_install_dir ~context:t.name)
"lib")
; extend_var "PATH"
(Config.local_install_bin_dir ~context:t.name)
; extend_var "MANPATH"
(Config.local_install_man_dir ~context:t.name)
2017-03-01 12:09:57 +00:00
]
in
extend_env ~env:t.env ~vars:(Env_var_map.of_list_exn vars)
let compiler t (mode : Mode.t) =
match mode with
| Byte -> Some t.ocamlc
| Native -> t.ocamlopt
let best_mode t : Mode.t =
match t.ocamlopt with
| Some _ -> Native
| None -> Byte
let cc_g (ctx : t) =
if !Clflags.g && ctx.ccomp_type <> "msvc" then
["-g"]
else
[]