Merge pull request #624 from rgrinberg/package-dep-conf

Add package dependency
This commit is contained in:
Rudi Grinberg 2018-03-19 02:04:15 +08:00 committed by GitHub
commit 37edb71204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 243 additions and 36 deletions

View File

@ -1058,6 +1058,9 @@ syntax:
universe. In any case, this is only for dependencies in the
installed world, you must still specify all dependencies that come
from the workspace.
- ``(package <pkg>)`` depend on all files installed by ``<package>``, as well
as on the transitive package dependencies of ``<package>``. This can be used
to test a command against the files that will be installed
In all these cases, the argument supports `Variables expansion`_.

View File

@ -27,13 +27,14 @@ module Repr = struct
| Split : ('a, 'b) t * ('c, 'd) t -> ('a * 'c, 'b * 'd) t
| Fanout : ('a, 'b) t * ('a, 'c) t -> ('a, 'b * 'c) t
| Paths : Pset.t -> ('a, 'a) t
| Paths_for_rule : Path.Set.t -> ('a, 'a) t
| Paths_glob : glob_state ref -> ('a, Path.t list) t
(* The reference gets decided in Build_interpret.deps *)
| If_file_exists : Path.t * ('a, 'b) if_file_exists_state ref -> ('a, 'b) t
| Contents : Path.t -> ('a, string) t
| Lines_of : Path.t -> ('a, string list) t
| Vpath : 'a Vspec.t -> (unit, 'a) t
| Dyn_paths : ('a, Path.t list) t -> ('a, 'a) t
| Dyn_paths : ('a, Path.Set.t) t -> ('a, 'a) t
| Record_lib_deps : lib_deps -> ('a, 'a) t
| Fail : fail -> (_, _) t
| Memo : 'a memo -> (unit, 'a) t
@ -134,7 +135,9 @@ let paths ps = Paths (Pset.of_list ps)
let path_set ps = Paths ps
let paths_glob ~loc ~dir re = Paths_glob (ref (G_unevaluated (loc, dir, re)))
let vpath vp = Vpath vp
let dyn_paths t = Dyn_paths t
let dyn_paths t = Dyn_paths (t >>^ Path.Set.of_list)
let dyn_path_set t = Dyn_paths t
let paths_for_rule ps = Paths_for_rule ps
let catch t ~on_error = Catch (t, on_error)

View File

@ -71,6 +71,7 @@ val files_recursively_in
(** Record dynamic dependencies *)
val dyn_paths : ('a, Path.t list) t -> ('a, 'a) t
val dyn_path_set : ('a, Path.Set.t) t -> ('a, 'a) t
val vpath : 'a Vspec.t -> (unit, 'a) t
@ -183,12 +184,13 @@ module Repr : sig
| Split : ('a, 'b) t * ('c, 'd) t -> ('a * 'c, 'b * 'd) t
| Fanout : ('a, 'b) t * ('a, 'c) t -> ('a, 'b * 'c) t
| Paths : Path.Set.t -> ('a, 'a) t
| Paths_for_rule : Path.Set.t -> ('a, 'a) t
| Paths_glob : glob_state ref -> ('a, Path.t list) t
| If_file_exists : Path.t * ('a, 'b) if_file_exists_state ref -> ('a, 'b) t
| Contents : Path.t -> ('a, string) t
| Lines_of : Path.t -> ('a, string list) t
| Vpath : 'a Vspec.t -> (unit, 'a) t
| Dyn_paths : ('a, Path.t list) t -> ('a, 'a) t
| Dyn_paths : ('a, Path.Set.t) t -> ('a, 'a) t
| Record_lib_deps : lib_deps -> ('a, 'a) t
| Fail : fail -> (_, _) t
| Memo : 'a memo -> (unit, 'a) t
@ -220,3 +222,6 @@ end
val repr : ('a, 'b) t -> ('a, 'b) Repr.t
val merge_lib_deps : lib_deps -> lib_deps -> lib_deps
(**/**)
val paths_for_rule : Path.Set.t -> ('a, 'a) t

View File

@ -63,6 +63,8 @@ let static_deps t ~all_targets ~file_tree =
| Split (a, b) -> loop a (loop b acc)
| Fanout (a, b) -> loop a (loop b acc)
| Paths fns -> { acc with action_deps = Pset.union fns acc.action_deps }
| Paths_for_rule fns ->
{ acc with rule_deps = Pset.union fns acc.rule_deps }
| Paths_glob state -> begin
match !state with
| G_evaluated l ->
@ -129,6 +131,7 @@ let lib_deps =
| Split (a, b) -> loop a (loop b acc)
| Fanout (a, b) -> loop a (loop b acc)
| Paths _ -> acc
| Paths_for_rule _ -> acc
| Vpath _ -> acc
| Paths_glob _ -> acc
| Dyn_paths t -> loop t acc
@ -156,6 +159,7 @@ let targets =
| Split (a, b) -> loop a (loop b acc)
| Fanout (a, b) -> loop a (loop b acc)
| Paths _ -> acc
| Paths_for_rule _ -> acc
| Vpath _ -> acc
| Paths_glob _ -> acc
| Dyn_paths t -> loop t acc

View File

@ -278,6 +278,10 @@ module Alias0 = struct
let doc = make "doc"
let private_doc = make "doc-private"
let lint = make "lint"
let package_install ~(context : Context.t) ~pkg =
make (sprintf ".%s-files" (Package.Name.to_string pkg))
~dir:context.build_dir
end
module Dir_status = struct
@ -297,8 +301,9 @@ module Dir_status = struct
type alias =
{ mutable deps : Pset.t
; mutable actions : alias_action list
{ mutable deps : Pset.t
; mutable dyn_deps : (unit, Pset.t) Build.t
; mutable actions : alias_action list
}
type rules_collector =
@ -349,6 +354,8 @@ type t =
; files_of : (Path.t, Files_of.t) Hashtbl.t
; mutable prefix : (unit, unit) Build.t option
; hook : hook -> unit
; (* Package files are part of *)
packages : (Path.t, Package.Name.t) Hashtbl.t
}
let string_of_paths set =
@ -442,6 +449,7 @@ module Build_exec = struct
let b = exec dyn_deps b x in
(a, b)
| Paths _ -> x
| Paths_for_rule _ -> x
| Paths_glob state -> get_glob_result_exn state
| Contents p -> Io.read_file (Path.to_string p)
| Lines_of p -> Io.lines_of_file (Path.to_string p)
@ -450,7 +458,7 @@ module Build_exec = struct
Option.value_exn file.data
| Dyn_paths t ->
let fns = exec dyn_deps t x in
dyn_deps := Pset.union !dyn_deps (Pset.of_list fns);
dyn_deps := Pset.union !dyn_deps fns;
x
| Record_lib_deps _ -> x
| Fail { fail } -> fail ()
@ -851,7 +859,7 @@ and load_dir_step2_exn t ~dir ~collector ~lazy_generators =
let alias_rules, alias_stamp_files =
let open Build.O in
String_map.foldi collector.aliases ~init:([], Pset.empty)
~f:(fun name { Dir_status. deps; actions } (rules, alias_stamp_files) ->
~f:(fun name { Dir_status. deps; dyn_deps; actions } (rules, alias_stamp_files) ->
let base_path = Path.relative alias_dir name in
let rules, deps =
List.fold_left actions ~init:(rules, deps)
@ -871,11 +879,14 @@ and load_dir_step2_exn t ~dir ~collector ~lazy_generators =
(Pre_rule.make
~context:None
(Build.path_set deps >>>
Build.action ~targets:[path]
(Redirect (Stdout,
path,
Digest_files
(Path.Set.to_list deps))))
dyn_deps >>>
Build.dyn_path_set (Build.arr (fun x -> x))
>>^ (fun dyn_deps ->
let deps = Pset.union deps dyn_deps in
Action.with_stdout_to path
(Action.digest_files (Pset.to_list deps)))
>>>
Build.action_dyn () ~targets:[path])
:: rules,
Pset.add alias_stamp_files path))
in
@ -1142,6 +1153,7 @@ let create ~contexts ~file_tree ~hook =
let t =
{ contexts
; files = Hashtbl.create 1024
; packages = Hashtbl.create 1024
; trace = Trace.load ()
; local_mkdirs = Path.Local.Set.empty
; dirs = Hashtbl.create 1024
@ -1324,7 +1336,8 @@ let build_rules_internal ?(recursive=false) t ~request =
else begin
rules_seen := Id_set.add !rules_seen ir.id;
(match ir.exec with
| Running { rule_evaluation; _ } | Evaluating_rule { rule_evaluation; _ } ->
| Running { rule_evaluation; _ }
| Evaluating_rule { rule_evaluation; _ } ->
Fiber.return rule_evaluation
| Not_started { eval_rule; exec_rule } ->
Fiber.fork (fun () ->
@ -1381,6 +1394,46 @@ let build_rules ?recursive t ~request =
entry_point t ~f:(fun () ->
build_rules_internal ?recursive t ~request)
let set_package t file package =
Hashtbl.add t.packages file package
let package_deps t pkg files =
let rules_seen = ref Id_set.empty in
let rec loop fn acc =
match Hashtbl.find_all t.packages fn with
| [] -> loop_deps fn acc
| [p] when p = pkg -> loop_deps fn acc
| pkgs ->
List.fold_left pkgs ~init:acc ~f:add_package
and add_package acc p =
if p = pkg then
acc
else
Package.Name.Set.add acc p
and loop_deps fn acc =
match Hashtbl.find t.files fn with
| None -> acc
| Some (File_spec.T { rule = ir; _ }) ->
if Id_set.mem !rules_seen ir.id then
acc
else begin
rules_seen := Id_set.add !rules_seen ir.id;
let _, dyn_deps =
match ir.exec with
| Running { rule_evaluation; _ }
| Evaluating_rule { rule_evaluation; _ } ->
Option.value_exn (Fiber.Future.peek rule_evaluation)
| Not_started _ -> assert false
in
Pset.fold (Pset.union ir.static_deps dyn_deps) ~init:acc ~f:loop
end
in
let open Build.O in
Build.paths_for_rule files >>^ fun () ->
(* We know that at this point of execution, all the relevant ivars
have been filled *)
Pset.fold files ~init:Package.Name.Set.empty ~f:loop_deps
(* +-----------------------------------------------------------------+
| Adding rules to the system |
+-----------------------------------------------------------------+ *)
@ -1472,14 +1525,27 @@ module Alias = struct
let collector = get_collector build_system ~dir:t.dir in
match String_map.find collector.aliases t.name with
| None ->
let x = { Dir_status. deps = Pset.empty; actions = [] } in
let x =
{ Dir_status.
deps = Pset.empty
; dyn_deps = Build.return Pset.empty
; actions = []
}
in
collector.aliases <- String_map.add collector.aliases t.name x;
x
| Some x -> x
let add_deps build_system t deps =
let add_deps build_system t ?dyn_deps deps =
let def = get_alias_def build_system t in
def.deps <- Pset.union def.deps (Pset.of_list deps)
def.deps <- Pset.union def.deps deps;
match dyn_deps with
| None -> ()
| Some build ->
let open Build.O in
def.dyn_deps <-
Build.fanout def.dyn_deps build >>^ fun (a, b) ->
Pset.union a b
let add_action build_system t ~context ?(locks=[]) ~stamp action =
let def = get_alias_def build_system t in

View File

@ -77,6 +77,18 @@ 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
(** Sets the package this file is part of *)
val set_package : t -> Path.t -> Package.Name.t -> unit
(** Assuming [files] is the list of files in [_build/install] that
belong to package [pkg], [package_deps t pkg files] is the set of
direct package dependencies of [package]. *)
val package_deps
: t
-> Package.Name.t
-> Path.Set.t
-> (unit, Package.Name.Set.t) Build.t
(** {2 Aliases} *)
module Alias : sig
@ -107,6 +119,10 @@ module Alias : sig
val private_doc : dir:Path.t -> t
val lint : dir:Path.t -> t
(** Alias for all the files in [_build/install] that belong to this
package *)
val package_install : context:Context.t -> pkg:Package.Name.t -> t
(** Return the underlying stamp file *)
val stamp_file : t -> Path.t
@ -128,9 +144,15 @@ module Alias : sig
-> 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_deps store alias ?dyn_deps deps] arrange things so that all
[dyn_deps] and [deps] are built as part of the build of alias
[alias]. *)
val add_deps
: build_system
-> t
-> ?dyn_deps:(unit, Path.Set.t) Build.t
-> Path.Set.t
-> unit
(** [add_action store alias ~stamp action] arrange things so that
[action] is executed as part of the build of alias

View File

@ -386,12 +386,18 @@ module Ivar = struct
| Full x -> k x
| Empty q ->
Queue.push { Handler. run = k; ctx } q
let peek t =
match t.state with
| Full x -> Some x
| Empty _ -> None
end
module Future = struct
type 'a t = 'a Ivar.t
let wait = Ivar.read
let peek = Ivar.peek
end
let fork f ctx k =

View File

@ -40,6 +40,9 @@ module Future : sig
(** Wait for the given future to yield a value. *)
val wait : 'a t -> 'a fiber
(** Return [Some x] if [t] has already returned. *)
val peek : 'a t -> 'a option
end with type 'a fiber := 'a t
(** [fork f] creates a sub-fiber and return a [Future.t] to wait its result. *)
@ -226,6 +229,9 @@ module Ivar : sig
(** Fill the ivar with the following value. This can only be called
once for a given ivar. *)
val fill : 'a t -> 'a -> unit fiber
(** Return [Some x] is [fill t x] has been called previously. *)
val peek : 'a t -> 'a option
end with type 'a fiber := 'a t
module Mutex : sig

View File

@ -708,17 +708,18 @@ module Gen(P : Install_rules.Params) = struct
List.iter Cm_kind.all ~f:(fun cm_kind ->
let files =
Module.Name.Map.fold modules ~init:[] ~f:(fun m acc ->
Module.Name.Map.fold modules ~init:Path.Set.empty ~f:(fun m acc ->
match Module.cm_file m ~obj_dir cm_kind with
| None -> acc
| Some fn -> fn :: acc)
| Some fn -> Path.Set.add acc fn)
in
SC.Libs.setup_file_deps_alias sctx ~dir lib ~ext:(Cm_kind.ext cm_kind)
files);
SC.Libs.setup_file_deps_group_alias sctx ~dir lib ~exts:[".cmi"; ".cmx"];
SC.Libs.setup_file_deps_alias sctx ~dir lib ~ext:".h"
(List.map lib.install_c_headers ~f:(fun header ->
Path.relative dir (header ^ ".h")));
Path.relative dir (header ^ ".h"))
|> Path.Set.of_list);
let top_sorted_modules =
Ocamldep.Dep_graph.top_closed_implementations dep_graphs.impl

View File

@ -203,8 +203,10 @@ module Gen(P : Install_params) = struct
let install_dir = Config.local_install_dir ~context:ctx.name in
List.map entries ~f:(fun entry ->
let dst =
Path.append install_dir (Install.Entry.relative_installed_path entry ~package)
Path.append install_dir
(Install.Entry.relative_installed_path entry ~package)
in
Build_system.set_package (SC.build_system sctx) entry.src package;
SC.add_rule sctx (Build.symlink ~src:entry.src ~dst);
Install.Entry.set_src entry dst)
@ -237,6 +239,19 @@ module Gen(P : Install_params) = struct
(Utils.install_file ~package ~findlib_toolchain:ctx.findlib_toolchain)
in
let entries = local_install_rules entries ~package in
let files = Install.files entries in
SC.add_alias_deps sctx
(Alias.package_install ~context:ctx ~pkg:package)
files
~dyn_deps:
(Build_system.package_deps (SC.build_system sctx) package files
>>^ fun packages ->
Package.Name.Set.to_list packages
|> List.map ~f:(fun pkg ->
Build_system.Alias.package_install
~context:(SC.context sctx) ~pkg
|> Build_system.Alias.stamp_file)
|> Path.Set.of_list);
SC.add_rule sctx
~mode:(if promote_install_file then
Promote_but_delete_on_clean
@ -244,7 +259,7 @@ module Gen(P : Install_params) = struct
(* We must ignore the source file since it might be
copied to the source tree by another context. *)
Ignore_source_files)
(Build.path_set (Install.files entries)
(Build.path_set files
>>^ (fun () ->
let entries =
match ctx.findlib_toolchain with
@ -299,7 +314,7 @@ module Gen(P : Install_params) = struct
let path = Path.append ctx.build_dir src_path in
let install_alias = Alias.install ~dir:path in
let install_file = Path.relative path install_fn in
SC.add_alias_deps sctx install_alias [install_file])
SC.add_alias_deps sctx install_alias (Path.Set.singleton install_file))
let init () =
init_meta ();

View File

@ -230,6 +230,7 @@ module Dep_conf = struct
| Alias_rec of String_with_vars.t
| Glob_files of String_with_vars.t
| Files_recursively_in of String_with_vars.t
| Package of String_with_vars.t
| Universe
let t =
@ -243,6 +244,7 @@ module Dep_conf = struct
; cstr_sw "alias_rec" (fun x -> Alias_rec x)
; cstr_sw "glob_files" (fun x -> Glob_files x)
; cstr_sw "files_recursively_in" (fun x -> Files_recursively_in x)
; cstr_sw "package" (fun x -> Package x)
; cstr "universe" nil Universe
]
in
@ -266,6 +268,9 @@ module Dep_conf = struct
| Files_recursively_in t ->
List [Sexp.unsafe_atom_of_string "files_recursively_in" ;
String_with_vars.sexp_of_t t]
| Package t ->
List [Sexp.unsafe_atom_of_string "package" ;
String_with_vars.sexp_of_t t]
| Universe ->
Sexp.unsafe_atom_of_string "universe"
end

View File

@ -125,6 +125,7 @@ module Dep_conf : sig
| Alias_rec of String_with_vars.t
| Glob_files of String_with_vars.t
| Files_recursively_in of String_with_vars.t
| Package of String_with_vars.t
| Universe
val t : t Sexp.Of_sexp.t

View File

@ -210,7 +210,8 @@ module Gen (S : sig val sctx : SC.t end) = struct
compile_module ~dir ~obj_dir ~includes ~dep_graphs
~doc_dir ~pkg_or_lnu)
in
Dep.setup_deps (Lib lib) (List.map modules_and_odoc_files ~f:snd)
Dep.setup_deps (Lib lib) (List.map modules_and_odoc_files ~f:snd
|> Path.Set.of_list)
let setup_css_rule () =
SC.add_rule sctx
@ -337,13 +338,13 @@ module Gen (S : sig val sctx : SC.t end) = struct
:: List.map ~f:(fun lib -> Dep.html_alias (Lib lib)) libs
) ~f:(fun alias ->
SC.add_alias_deps sctx alias
[ css_file
; toplevel_index
]
(Path.Set.of_list [ css_file
; toplevel_index
])
);
List.combine odocs html_files
|> List.iter ~f:(fun (odoc, html) ->
SC.add_alias_deps sctx odoc.html_alias [html]
SC.add_alias_deps sctx odoc.html_alias (Path.Set.singleton html)
);
end
@ -387,6 +388,7 @@ module Gen (S : sig val sctx : SC.t end) = struct
|> Lib.Set.to_list
|> List.map ~f:(fun lib -> Dep.html_alias (Lib lib)))
|> List.map ~f:Build_system.Alias.stamp_file
|> Path.Set.of_list
)
let pkg_odoc (pkg : Package.t) = Paths.odocs (Pkg pkg.name)
@ -449,7 +451,7 @@ module Gen (S : sig val sctx : SC.t end) = struct
~doc_dir:(Paths.odocs (Pkg pkg.name))
~includes:(Build.arr (fun _ -> Arg_spec.As []))
) in
Dep.setup_deps (Pkg pkg.name) odocs
Dep.setup_deps (Pkg pkg.name) (Path.Set.of_list odocs)
let init ~modules_by_lib ~mlds_of_dir =
let docs_by_package =
@ -529,6 +531,7 @@ module Gen (S : sig val sctx : SC.t end) = struct
))
|> List.map ~f:(fun (lib : Lib.t) ->
Build_system.Alias.stamp_file (Dep.alias (Lib lib)))
|> Path.Set.of_list
)
end

View File

@ -41,6 +41,7 @@ let file_tree t = t.file_tree
let stanzas_to_consider_for_install t = t.stanzas_to_consider_for_install
let cxx_flags t = t.cxx_flags
let build_dir t = t.context.build_dir
let build_system t = t.build_system
let host t = Option.value t.host ~default:t
@ -236,8 +237,8 @@ let add_rule_get_targets t ?sandbox ?mode ?locks ?loc build =
let add_rules t ?sandbox builds =
List.iter builds ~f:(add_rule t ?sandbox)
let add_alias_deps t alias deps =
Alias.add_deps t.build_system alias deps
let add_alias_deps t alias ?dyn_deps deps =
Alias.add_deps t.build_system alias ?dyn_deps deps
let add_alias_action t alias ?locks ~stamp action =
Alias.add_action t.build_system ~context:t.context alias ?locks ~stamp action
@ -302,7 +303,8 @@ module Libs = struct
~ext:(String.concat exts ~sep:"-and-")
(List.map exts ~f:(fun ext ->
Alias.stamp_file
(lib_files_alias ~dir ~name:(Library.best_name lib) ~ext)))
(lib_files_alias ~dir ~name:(Library.best_name lib) ~ext))
|> Path.Set.of_list)
let file_deps t ~ext =
Build.dyn_paths (Build.arr (fun libs ->
@ -351,6 +353,10 @@ module Deps = struct
let path = Path.relative dir (expand_vars t ~scope ~dir s) in
Build.files_recursively_in ~dir:path ~file_tree:t.file_tree
>>^ Pset.to_list
| Package p ->
let pkg = Package.Name.of_string (expand_vars t ~scope ~dir p) in
Alias.dep (Alias.package_install ~context:t.context ~pkg)
>>^ fun () -> []
| Universe ->
Build.path Build_system.universe_file
>>^ fun () -> []

View File

@ -41,6 +41,7 @@ val stanzas_to_consider_for_install : t -> (Path.t * Scope.t * Stanza.t) list
val cxx_flags : t -> string list
val build_dir : t -> Path.t
val host : t -> t
val build_system : t -> Build_system.t
(** All public libraries of the workspace *)
val public_libs : t -> Lib.DB.t
@ -97,7 +98,8 @@ val add_rules
val add_alias_deps
: t
-> Build_system.Alias.t
-> Path.t list
-> ?dyn_deps:(unit, Path.Set.t) Build.t
-> Path.Set.t
-> unit
val add_alias_action
: t
@ -150,7 +152,7 @@ module Libs : sig
-> dir:Path.t
-> ext:string
-> Library.t
-> Path.t list
-> Path.Set.t
-> unit
(** Setup an alias that depend on all files with the given extensions.

View File

@ -440,3 +440,13 @@
(progn
(run ${exe:cram.exe} run.t)
(diff? run.t run.t.corrected)))))))
(alias
((name runtest)
(deps ((files_recursively_in test-cases/package-dep)))
(action
(chdir test-cases/package-dep
(setenv JBUILDER ${bin:jbuilder}
(progn
(run ${exe:cram.exe} run.t)
(diff? run.t run.t.corrected)))))))

View File

@ -0,0 +1,33 @@
(jbuild_version 1)
(library
((name foo)
(public_name foo)
(modules (foo))))
(library
((name bar)
(public_name bar)
(libraries (foo))
(modules (bar))))
(rule
(with-stdout-to foo.ml
(echo "let x = 42")))
(rule
(with-stdout-to bar.ml
(echo "let x = string_of_int Foo.x")))
(rule
(with-stdout-to test.ml
(echo "let () = Printf.printf \"%d %s\" Foo.x Bar.x")))
(rule
((deps (test.ml (package bar)))
(targets (test.exe))
(action (run ocamlfind ocamlc -linkpkg -package bar -o test.exe test.ml))))
(alias
((name runtest)
(action (run ./test.exe))))

View File

@ -0,0 +1,16 @@
$ $JBUILDER runtest -j1 --display short --root .
ocamldep bar.ml.d
ocamldep foo.ml.d
ocamlc .foo.objs/foo.{cmi,cmo,cmt}
ocamlc .bar.objs/bar.{cmi,cmo,cmt}
ocamlc bar.cma
ocamlopt .foo.objs/foo.{cmx,o}
ocamlopt .bar.objs/bar.{cmx,o}
ocamlopt bar.{a,cmxa}
ocamlopt bar.cmxs
ocamlopt foo.{a,cmxa}
ocamlopt foo.cmxs
ocamlc foo.cma
ocamlfind test.exe
test alias runtest
42 42