437 lines
15 KiB
OCaml
437 lines
15 KiB
OCaml
(**************************************************************************)
|
|
(* *)
|
|
(* Copyright (c) 2014 - 2016. *)
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
(* *)
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
(* *)
|
|
(**************************************************************************)
|
|
|
|
(** The OCaml compiler not being implemented with Lwt, the compilation
|
|
take place in a separated process (by using [Lwt_process.exec]).
|
|
|
|
The [main] function is the entry point for the forked process.
|
|
While [Updater.compile] is the 'forking' function to be called by
|
|
the [tezos-node] process.
|
|
|
|
*)
|
|
|
|
open Tezos_data
|
|
|
|
(* GRGR TODO: fail in the presence of "external" *)
|
|
|
|
module Backend = struct
|
|
(* See backend_intf.mli. *)
|
|
|
|
let symbol_for_global' = Compilenv.symbol_for_global'
|
|
let closure_symbol = Compilenv.closure_symbol
|
|
|
|
let really_import_approx = Import_approx.really_import_approx
|
|
let import_symbol = Import_approx.import_symbol
|
|
|
|
let size_int = Arch.size_int
|
|
let big_endian = Arch.big_endian
|
|
|
|
let max_sensible_number_of_arguments =
|
|
(* The "-1" is to allow for a potential closure environment parameter. *)
|
|
Proc.max_arguments_for_tailcalls - 1
|
|
end
|
|
let backend = (module Backend : Backend_intf.S)
|
|
|
|
let warnings = "+a-4-6-7-9-29-40..42-44-45-48"
|
|
let warn_error = "-a+8"
|
|
|
|
let () =
|
|
Clflags.unsafe_string := false ;
|
|
Clflags.native_code := true
|
|
|
|
(** Compilation environment.
|
|
|
|
[tezos_protocol_env] defines the list of [cmi] available while compiling
|
|
the protocol version. The [cmi] are packed into the [tezos-node]
|
|
binary by using [ocp-ocamlres], see the Makefile.
|
|
|
|
[register_env] defines a complementary list of [cmi] available
|
|
while compiling the generated [register.ml] file (that register
|
|
the protocol first-class module into the [Updater.versions]
|
|
hashtable).
|
|
|
|
*)
|
|
|
|
let tezos_protocol_env =
|
|
[ "camlinternalFormatBasics", Embedded_cmis.camlinternalFormatBasics_cmi ;
|
|
"proto_environment", Embedded_cmis.proto_environment_cmi ;
|
|
]
|
|
|
|
let register_env =
|
|
[ "register", Embedded_cmis.register_cmi ]
|
|
|
|
(** Helpers *)
|
|
|
|
let (//) = Filename.concat
|
|
|
|
let create_file ?(perm = 0o644) name content =
|
|
let open Unix in
|
|
let fd = openfile name [O_TRUNC; O_CREAT; O_WRONLY] perm in
|
|
ignore(write_substring fd content 0 (String.length content));
|
|
close fd
|
|
|
|
let read_md5 file =
|
|
let ic = open_in file in
|
|
let md5 = input_line ic in
|
|
close_in ic ;
|
|
md5
|
|
|
|
let rec create_dir ?(perm = 0o755) dir =
|
|
if not (Sys.file_exists dir) then begin
|
|
create_dir (Filename.dirname dir);
|
|
Unix.mkdir dir perm
|
|
end
|
|
|
|
let dump_cmi dir (file, content) =
|
|
create_file (dir // file ^ ".cmi") content
|
|
|
|
let safe_unlink file =
|
|
try Unix.unlink file
|
|
with Unix.Unix_error(Unix.ENOENT, _, _) -> ()
|
|
|
|
let unlink_cmi dir (file, _) =
|
|
safe_unlink (dir // file ^ ".cmi")
|
|
|
|
let unlink_object obj =
|
|
safe_unlink obj;
|
|
safe_unlink (Filename.chop_suffix obj ".cmx" ^ ".cmi");
|
|
safe_unlink (Filename.chop_suffix obj ".cmx" ^ ".o")
|
|
|
|
|
|
(** TEZOS_PROTOCOL files *)
|
|
|
|
module Meta = struct
|
|
let name = "TEZOS_PROTOCOL"
|
|
let config_file_encoding =
|
|
let open Data_encoding in
|
|
obj2
|
|
(opt "hash" ~description:"Used to force the hash of the protocol" Protocol_hash.encoding)
|
|
(req "modules" ~description:"Modules comprising the protocol" (list string))
|
|
|
|
let to_file dirname ?hash modules =
|
|
let config_file =
|
|
Data_encoding.Json.construct config_file_encoding (hash, modules) in
|
|
Utils.write_file ~bin:false (dirname // name) @@
|
|
Data_encoding_ezjsonm.to_string config_file
|
|
|
|
let of_file dirname =
|
|
Utils.read_file ~bin:false (dirname // name) |>
|
|
Data_encoding_ezjsonm.from_string |> function
|
|
| Error err -> Pervasives.failwith err
|
|
| Ok json -> Data_encoding.Json.destruct config_file_encoding json
|
|
end
|
|
|
|
|
|
let find_component dirname module_name =
|
|
let open Protocol in
|
|
let name_lowercase = String.uncapitalize_ascii module_name in
|
|
let implementation = dirname // name_lowercase ^ ".ml" in
|
|
let interface = implementation ^ "i" in
|
|
match Sys.file_exists implementation, Sys.file_exists interface with
|
|
| false, _ -> Pervasives.failwith @@ "Not such file: " ^ implementation
|
|
| true, false ->
|
|
let implementation = Utils.read_file ~bin:false implementation in
|
|
{ name = module_name; interface = None; implementation }
|
|
| _ ->
|
|
let interface = Utils.read_file ~bin:false interface in
|
|
let implementation = Utils.read_file ~bin:false implementation in
|
|
{ name = module_name; interface = Some interface; implementation }
|
|
|
|
let read_dir dirname =
|
|
let _hash, modules = Meta.of_file dirname in
|
|
List.map (find_component dirname) modules
|
|
|
|
(** Semi-generic compilation functions *)
|
|
|
|
let compile_mli ?(ctxt = "") ?(keep_object = false) target mli =
|
|
Printf.printf "OCAMLOPT%s %s\n%!" ctxt (Filename.basename target ^ ".cmi");
|
|
Compenv.(readenv Format.err_formatter (Before_compile mli)) ;
|
|
Optcompile.interface Format.err_formatter mli target ;
|
|
if not keep_object then
|
|
at_exit (fun () -> safe_unlink (target ^ ".cmi"))
|
|
|
|
|
|
let compile_ml ?(ctxt = "") ?(keep_object = false) ?for_pack target ml =
|
|
Printf.printf "OCAMLOPT%s %s\n%!" ctxt (Filename.basename target ^ ".cmx") ;
|
|
Compenv.(readenv Format.err_formatter (Before_compile ml));
|
|
Clflags.for_package := for_pack;
|
|
Optcompile.implementation
|
|
~backend Format.err_formatter ml target;
|
|
Clflags.for_package := None;
|
|
if not keep_object then
|
|
at_exit (fun () -> unlink_object (target ^ ".cmx")) ;
|
|
target ^ ".cmx"
|
|
|
|
let modification_date file = Unix.((stat file).st_mtime)
|
|
|
|
let compile_units
|
|
?ctxt
|
|
?(update_needed = true)
|
|
?keep_object ?for_pack ~source_dir ~build_dir units =
|
|
let compile_unit update_needed unit =
|
|
let basename = String.uncapitalize_ascii unit in
|
|
let mli = source_dir // basename ^ ".mli" in
|
|
let cmi = build_dir // basename ^ ".cmi" in
|
|
let ml = source_dir // basename ^ ".ml" in
|
|
let cmx = build_dir // basename ^ ".cmx" in
|
|
let target = build_dir // basename in
|
|
let update_needed =
|
|
update_needed
|
|
|| not (Sys.file_exists cmi)
|
|
|| ( Sys.file_exists mli
|
|
&& modification_date cmi < modification_date mli )
|
|
|| not (Sys.file_exists cmx)
|
|
|| modification_date cmx < modification_date ml in
|
|
if update_needed then begin
|
|
unlink_object cmx ;
|
|
if Sys.file_exists mli then compile_mli ?ctxt ?keep_object target mli ;
|
|
ignore (compile_ml ?ctxt ?keep_object ?for_pack target ml)
|
|
end ;
|
|
update_needed, cmx in
|
|
List.fold_left
|
|
(fun (update_needed, acc) unit->
|
|
let update_needed, output = compile_unit update_needed unit in
|
|
update_needed, output :: acc)
|
|
(update_needed, []) units
|
|
|> snd |> List.rev
|
|
|
|
let pack_objects ?(ctxt = "") ?(keep_object = false) output objects =
|
|
Printf.printf "PACK%s %s\n%!" ctxt (Filename.basename output);
|
|
Compmisc.init_path true;
|
|
Asmpackager.package_files
|
|
~backend Format.err_formatter Env.initial_safe_string objects output;
|
|
if not keep_object then at_exit (fun () -> unlink_object output) ;
|
|
Warnings.check_fatal ()
|
|
|
|
let link_shared ?(static=false) output objects =
|
|
Printf.printf "LINK %s\n%!" (Filename.basename output);
|
|
Compenv.(readenv Format.err_formatter Before_link);
|
|
Compmisc.init_path true;
|
|
if static then
|
|
Asmlibrarian.create_archive objects output
|
|
else
|
|
Asmlink.link_shared Format.err_formatter objects output;
|
|
Warnings.check_fatal ()
|
|
|
|
(** Main for the 'forked' compiler.
|
|
|
|
It expect the following arguments:
|
|
|
|
output.cmxs source_dir
|
|
|
|
where, [source_dir] should contains a TEZOS_PROTOCOL file such as:
|
|
|
|
hash = "69872d2940b7d11c9eabbc685115bd7867a94424"
|
|
modules = [Data; Main]
|
|
|
|
The [source_dir] should also contains the corresponding source
|
|
file. For instance: [data.ml], [main.ml] and optionnaly [data.mli]
|
|
and [main.mli].
|
|
|
|
*)
|
|
|
|
let create_register_file client file hash packname modules =
|
|
let unit = List.hd (List.rev modules) in
|
|
let environment_module = packname ^ ".Local_environment.Environment" in
|
|
let error_monad_module = environment_module ^ ".Error_monad" in
|
|
let context_module = environment_module ^ ".Context" in
|
|
let hash_module = environment_module ^ ".Hash" in
|
|
create_file file
|
|
(Printf.sprintf
|
|
"module Packed_protocol = struct\n\
|
|
\ let hash = (%s.Protocol_hash.of_b58check_exn %S)\n\
|
|
\ type error = %s.error = ..\n\
|
|
\ type 'a tzresult = 'a %s.tzresult\n\
|
|
\ include %s.%s\n\
|
|
\ let error_encoding = %s.error_encoding ()\n\
|
|
\ let classify_errors = %s.classify_errors\n\
|
|
\ let pp = %s.pp\n\
|
|
\ let complete_b58prefix = %s.complete
|
|
\ end\n\
|
|
\ %s\n\
|
|
"
|
|
hash_module
|
|
(Protocol_hash.to_b58check hash)
|
|
error_monad_module
|
|
error_monad_module
|
|
packname (String.capitalize_ascii unit)
|
|
error_monad_module
|
|
error_monad_module
|
|
error_monad_module
|
|
context_module
|
|
(if client then
|
|
"include Register.Make(Packed_protocol)"
|
|
else
|
|
Printf.sprintf
|
|
"let () = Register.register (%s.__cast (module Packed_protocol : %s.PACKED_PROTOCOL))" environment_module environment_module))
|
|
|
|
let mktemp_dir () =
|
|
Filename.get_temp_dir_name () //
|
|
Printf.sprintf "tezos-protocol-build-%06X" (Random.int 0xFFFFFF)
|
|
|
|
let main () =
|
|
|
|
Random.self_init () ;
|
|
Sodium.Random.stir () ;
|
|
|
|
let anonymous = ref []
|
|
and client = ref false
|
|
and build_dir = ref None
|
|
and include_dirs = ref [] in
|
|
let static = ref false in
|
|
let args_spec = [
|
|
"-static", Arg.Set static, " Build a library (.cmxa)";
|
|
"-client", Arg.Set client, " Preserve type equality with concrete node environment (used to embed protocol into the client)" ;
|
|
"-I", Arg.String (fun s -> include_dirs := s :: !include_dirs), "path Path for concrete node signatures (used to embed protocol into the client)" ;
|
|
"-bin-annot", Arg.Set Clflags.binary_annotations, " (see ocamlopt)" ;
|
|
"-g", Arg.Set Clflags.debug, " (see ocamlopt)" ;
|
|
"-build-dir", Arg.String (fun s -> build_dir := Some s), "path Reuse build dir (incremental compilation)"] in
|
|
let usage_msg = Printf.sprintf "Usage: %s <out> <src>\nOptions are:" Sys.argv.(0) in
|
|
Arg.parse args_spec (fun s -> anonymous := s :: !anonymous) usage_msg ;
|
|
|
|
let client = !client and include_dirs = !include_dirs in
|
|
let output, source_dir =
|
|
match List.rev !anonymous with
|
|
| [ output ; source_dir ] -> output, source_dir
|
|
| _ -> Arg.usage args_spec usage_msg ; Pervasives.exit 1 in
|
|
if include_dirs <> [] && not client then begin
|
|
Arg.usage args_spec usage_msg ; Pervasives.exit 1
|
|
end ;
|
|
|
|
let keep_object, build_dir, sigs_dir =
|
|
match !build_dir with
|
|
| None ->
|
|
let build_dir = mktemp_dir () in
|
|
false, build_dir, build_dir // "sigs"
|
|
| Some build_dir ->
|
|
true, build_dir, mktemp_dir () in
|
|
create_dir build_dir ;
|
|
create_dir sigs_dir ;
|
|
at_exit (fun () ->
|
|
Unix.rmdir sigs_dir ;
|
|
if not keep_object then Unix.rmdir build_dir ) ;
|
|
|
|
let hash, units = Meta.of_file source_dir in
|
|
let hash = match hash with
|
|
| Some hash -> hash
|
|
| None -> Protocol.hash @@ List.map (find_component source_dir) units
|
|
in
|
|
let packname =
|
|
if keep_object then
|
|
String.capitalize_ascii (Filename.(basename @@ chop_extension output))
|
|
else
|
|
Format.asprintf "Protocol_%a" Protocol_hash.pp hash in
|
|
let packed_objects =
|
|
if keep_object then
|
|
Filename.dirname output // String.uncapitalize_ascii packname ^ ".cmx"
|
|
else
|
|
build_dir // packname ^ ".cmx" in
|
|
let ctxt = Printf.sprintf " (%s)" (Filename.basename output) in
|
|
let logname =
|
|
if keep_object then
|
|
try
|
|
Scanf.sscanf
|
|
Filename.(basename @@ chop_extension output)
|
|
"embedded_proto_%s"
|
|
(fun s -> "proto." ^ s)
|
|
with _ ->
|
|
Filename.(basename @@ chop_extension output)
|
|
else
|
|
Format.asprintf "proto.%a" Protocol_hash.pp hash in
|
|
|
|
(* TODO proper error *)
|
|
assert (List.length units >= 1);
|
|
|
|
(* Initialize the compilers *)
|
|
Compenv.(readenv Format.err_formatter Before_args);
|
|
if not client then Clflags.no_std_include := true;
|
|
Clflags.include_dirs := build_dir :: sigs_dir :: include_dirs;
|
|
Clflags.nopervasives := true;
|
|
Warnings.parse_options false warnings;
|
|
Warnings.parse_options true warn_error;
|
|
|
|
let md5 =
|
|
if not client then
|
|
Digest.(to_hex (file Sys.executable_name))
|
|
else
|
|
try
|
|
let environment_cmi =
|
|
Misc.find_in_path_uncap !Clflags.include_dirs "environment.cmi" in
|
|
let environment_cmx =
|
|
Misc.find_in_path_uncap !Clflags.include_dirs "environment.cmx" in
|
|
Digest.(to_hex (file Sys.executable_name) ^
|
|
(to_hex (file environment_cmi)) ^
|
|
(to_hex (file environment_cmx)))
|
|
with Not_found ->
|
|
Printf.eprintf "%s: Cannot find 'environment.cmi'.\n%!" Sys.argv.(0);
|
|
Pervasives.exit 1
|
|
in
|
|
let update_needed =
|
|
not (Sys.file_exists (build_dir // ".tezos_compiler"))
|
|
|| read_md5 (build_dir // ".tezos_compiler") <> md5 in
|
|
|
|
if keep_object then
|
|
create_file (build_dir // ".tezos_compiler") (md5 ^ "\n");
|
|
|
|
(* Compile the /ad-hoc/ Error_monad. *)
|
|
List.iter (dump_cmi sigs_dir) tezos_protocol_env ;
|
|
at_exit (fun () -> List.iter (unlink_cmi sigs_dir) tezos_protocol_env ) ;
|
|
let local_environment_unit = "local_environment" in
|
|
let local_environment_ml = build_dir // local_environment_unit ^ ".ml" in
|
|
create_file local_environment_ml @@ Printf.sprintf {|
|
|
module Environment = %s.Make(struct let name = %S end)()
|
|
|}
|
|
(if client then "Environment" else "Proto_environment")
|
|
logname ;
|
|
if not keep_object then
|
|
at_exit (fun () ->
|
|
safe_unlink local_environment_ml) ;
|
|
let local_environment_object =
|
|
compile_units
|
|
~ctxt
|
|
~for_pack:packname
|
|
~keep_object
|
|
~build_dir ~source_dir:build_dir [local_environment_unit]
|
|
in
|
|
|
|
Compenv.implicit_modules :=
|
|
[ "Local_environment"; "Environment" ;
|
|
"Error_monad" ; "Hash" ; "Logging" ; "Tezos_data" ];
|
|
|
|
(* Compile the protocol *)
|
|
let objects =
|
|
compile_units
|
|
~ctxt
|
|
~update_needed
|
|
~keep_object ~for_pack:packname ~build_dir ~source_dir units in
|
|
pack_objects ~ctxt ~keep_object
|
|
packed_objects (local_environment_object @ objects) ;
|
|
|
|
(* Compiler the 'registering module' *)
|
|
List.iter (dump_cmi sigs_dir) register_env;
|
|
at_exit (fun () -> List.iter (unlink_cmi sigs_dir) register_env ) ;
|
|
let register_unit =
|
|
if client then
|
|
Filename.dirname output //
|
|
"register_" ^
|
|
Filename.(basename @@ chop_extension output)
|
|
else
|
|
build_dir // Format.asprintf "register_%s" packname in
|
|
let register_file = register_unit ^ ".ml" in
|
|
create_register_file client register_file hash packname units ;
|
|
if not keep_object then at_exit (fun () -> safe_unlink register_file) ;
|
|
if keep_object then
|
|
Clflags.include_dirs := !Clflags.include_dirs @ [Filename.dirname output] ;
|
|
let register_object =
|
|
compile_ml ~keep_object:client (register_unit) register_file in
|
|
|
|
(* Create the final [cmxs] *)
|
|
Clflags.link_everything := true ;
|
|
link_shared ~static:!static output [packed_objects; register_object]
|