dune/src/context.ml

455 lines
15 KiB
OCaml
Raw Normal View History

2016-12-02 13:54:32 +00:00
open Import
open Future
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 -> Atom "default"
| Opam o ->
Sexp.To_sexp.(record [ "root" , string o.root
; "switch", string o.switch
])
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
; 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
; ocamllex : Path.t
; ocamlyacc : 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
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
2017-02-26 19:49:54 +00:00
; ocamlc_config : (string * string) list
2016-12-02 13:54:32 +00:00
; version : string
; stdlib_dir : Path.t
; ccomp_type : string
2017-03-31 16:31:55 +00:00
; c_compiler : string
; ocamlc_cflags : string
; ocamlopt_cflags : string
2016-12-02 13:54:32 +00:00
; bytecomp_c_libraries : string
; native_c_libraries : string
; 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
; 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
; "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))
; "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
; "ocamllex", path t.ocamllex
; "ocamlyacc", path t.ocamlyacc
; "ocamlmklib", path t.ocamlmklib
; "env", list (pair string string) (Env_var_map.bindings 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", list (pair string string) t.ocamlc_config
; "which", string_hashtbl (option path) t.which_cache
]
let compare a b = compare a.name b.name
2016-12-02 13:54:32 +00:00
let get_arch_sixtyfour stdlib_dir =
let config_h = Path.relative stdlib_dir "caml/config.h" in
List.exists (lines_of_file (Path.to_string config_h)) ~f:(fun line ->
2017-03-31 16:31:55 +00:00
match String.extract_blank_separated_words line with
2016-12-02 13:54:32 +00:00
| ["#define"; "ARCH_SIXTYFOUR"] -> true
| _ -> false)
2017-02-24 15:41:52 +00:00
let opam_config_var ~env ~cache var =
match Hashtbl.find cache var with
| Some _ as x -> return x
| None ->
match Bin.opam with
| None -> return None
| Some fn ->
Future.run_capture Strict (Path.to_string fn) ~env ["config"; "var"; var]
2017-02-24 15:41:52 +00:00
>>| fun s ->
let s = String.trim s in
Hashtbl.add cache ~key:var ~data:s;
Some s
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 = 0 ->
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 key vars))
2017-03-10 11:22:01 +00:00
in
List.rev_append
(List.map (Env_var_map.bindings 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 ~use_findlib =
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 ~key:"root" ~data:root
| 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
2016-12-02 13:54:32 +00:00
let ocamlc =
match which "ocamlc" with
| None -> prog_not_found_in_path "ocamlc"
| Some x -> x
in
let dir = Path.parent ocamlc in
let prog_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 best_prog prog = Bin.best_prog dir prog in
let get_prog prog =
match best_prog prog with
| None -> prog_not_found prog
| Some fn -> fn
in
let build_dir =
Path.of_string (sprintf "_build/%s" name)
2016-12-02 13:54:32 +00:00
in
let ocamlc_config_cmd = sprintf "%s -config" (Path.to_string ocamlc) in
let findlib_path =
if use_findlib then
(* If ocamlfind is present, it has precedence over everything else. *)
match which "ocamlfind" with
| Some fn ->
(Future.run_capture_lines ~env Strict
(Path.to_string fn) ["printconf"; "path"]
>>| 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 in
[dir ^ "/../lib"] *)
[Path.relative (Path.parent dir) "lib"]
else
return []
in
2016-12-02 13:54:32 +00:00
both
findlib_path
(Future.run_capture_lines ~env Strict (Path.to_string ocamlc) ["-config"])
2016-12-02 13:54:32 +00:00
>>= fun (findlib_path, ocamlc_config) ->
let ocamlc_config =
List.map ocamlc_config ~f:(fun line ->
match String.index line ':' with
| Some i ->
(String.sub line ~pos:0 ~len:i,
String.sub line ~pos:(i + 2) ~len:(String.length line - i - 2))
| None ->
die "unrecognized line in the output of `%s`: %s" ocamlc_config_cmd
line)
|> String_map.of_alist
|> function
| Ok x -> x
| Error (key, _, _) ->
die "variable %S present twice in the output of `%s`" key ocamlc_config_cmd
in
2017-03-31 16:31:55 +00:00
let get_opt var = String_map.find var ocamlc_config in
2017-02-26 21:28:30 +00:00
let get ?default var =
2017-03-31 16:31:55 +00:00
match get_opt var with
2016-12-02 13:54:32 +00:00
| Some s -> s
2017-02-26 21:28:30 +00:00
| None ->
match default with
| Some x -> x
| None ->
die "variable %S not found in the output of `%s`" var ocamlc_config_cmd
2016-12-02 13:54:32 +00:00
in
2017-02-26 21:28:30 +00:00
let get_bool ?default var =
match get ?default:(Option.map default ~f:string_of_bool) var with
2016-12-02 13:54:32 +00:00
| "true" -> true
| "false" -> false
| _ -> die "variable %S is neither 'true' neither 'false' in the output of `%s`"
var ocamlc_config_cmd
in
let get_path var = Path.absolute (get var) in
let stdlib_dir = get_path "standard_library" in
let natdynlink_supported = Path.exists (Path.relative stdlib_dir "dynlink.cmxa") in
let version = get "version" in
let env,env_extra =
(* We redirect the output of all commands, so by default the compiler will disable
colors. Since we support colors in the output of commands, we force it via OCAMLPARAM
if stderr supports colors. *)
let ocaml_version = Scanf.sscanf version "%u.%u.%u" (fun a b c -> a, b, c) in
if Lazy.force Ansi_color.stderr_supports_colors && ocaml_version > (4, 02, 3) 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 ~key:"OCAMLPARAM" ~data:value env_extra)
else
env,env_extra
in
2017-03-31 16:31:55 +00:00
let c_compiler, ocamlc_cflags, ocamlopt_cflags =
match get_opt "c_compiler" with
| Some c_compiler -> (* >= 4.06 *)
(c_compiler, get "ocamlc_cflags", get "ocamlopt_cflags")
| None ->
let split_prog s =
let len = String.length s in
let rec loop i =
if i = len then
(s, "")
else
match s.[i] with
| ' ' | '\t' ->
(String.sub s ~pos:0 ~len:i,
String.sub s ~pos:i ~len:(len - i))
| _ -> loop (i + 1)
in
loop 0
in
let c_compiler, ocamlc_cflags = split_prog (get "bytecomp_c_compiler") in
let _, ocamlopt_cflags = split_prog (get "native_c_compiler") in
(c_compiler, ocamlc_cflags, ocamlopt_cflags)
in
2017-02-26 21:49:41 +00:00
return
2017-02-25 00:28:10 +00:00
{ name
; kind
; merlin
2016-12-02 13:54:32 +00:00
; for_host = None
; build_dir
; path
; toplevel_path = Option.map (get_env env "OCAML_TOPLEVEL_PATH") ~f:Path.absolute
2016-12-02 13:54:32 +00:00
; ocaml_bin = dir
; ocaml = Path.relative dir "ocaml"
; ocamlc
; ocamlopt = best_prog "ocamlopt"
; ocamllex = get_prog "ocamllex"
; ocamlyacc = get_prog "ocamlyacc"
; ocamldep = get_prog "ocamldep"
; ocamlmklib = get_prog "ocamlmklib"
; env
2017-03-10 11:22:01 +00:00
; env_extra
2017-02-28 06:01:27 +00:00
; findlib = Findlib.create ~stdlib_dir ~path:findlib_path
2016-12-02 13:54:32 +00:00
; arch_sixtyfour = get_arch_sixtyfour stdlib_dir
2017-02-24 15:41:52 +00:00
; opam_var_cache
; natdynlink_supported
2016-12-02 13:54:32 +00:00
; stdlib_dir
2017-02-26 19:49:54 +00:00
; ocamlc_config = String_map.bindings ocamlc_config
; version
2016-12-02 13:54:32 +00:00
; ccomp_type = get "ccomp_type"
2017-03-31 16:31:55 +00:00
; c_compiler
; ocamlc_cflags
; ocamlopt_cflags
2016-12-02 13:54:32 +00:00
; bytecomp_c_libraries = get "bytecomp_c_libraries"
; native_c_libraries = get "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 = get "ext_obj"
; ext_asm = get "ext_asm"
; ext_lib = get "ext_lib"
; ext_dll = get "ext_dll"
; os_type = get "os_type"
; default_executable_name = get "default_executable_name"
; host = get "host"
; target = get "target"
2017-02-26 21:28:30 +00:00
; flambda = get_bool "flambda" ~default:false
2016-12-02 13:54:32 +00:00
; 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
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 Ansi_color.setup_env_for_opam_colors;
Unix.environment ())
2016-12-02 13:54:32 +00:00
let default ?(merlin=true) ?(use_findlib=true) () =
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 ~use_findlib
2016-12-02 13:54:32 +00:00
let create_for_opam ?root ~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 -> return root
| None ->
Future.run_capture_line Strict (Path.to_string fn) ["config"; "var"; "root"])
2016-12-02 13:54:32 +00:00
>>= fun root ->
Future.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 =
Sexp_lexer.single (Lexing.from_string s)
|> Sexp.Of_sexp.(list (pair string string))
|> Env_var_map.of_alist_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 "PATH" vars 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
2017-03-10 11:22:01 +00:00
create ~kind:(Opam { root; switch }) ~path ~base_env:env ~env_extra:vars
~name ~merlin ~use_findlib:true
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
(* 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")
2017-03-01 12:09:57 +00:00
; extend_var "OCAMLPATH" (Path.relative
(Config.local_install_dir ~context:t.name)
"lib")
; extend_var "PATH" (Config.local_install_bin_dir ~context:t.name)
2017-03-01 12:56:31 +00:00
; 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_alist_exn vars)