dune/src/workspace.ml

232 lines
6.1 KiB
OCaml

open Import
open Stanza.Of_sexp
(* workspace files use the same version numbers as dune-project files
for simplicity *)
let syntax = Stanza.syntax
module Context = struct
module Target = struct
type t =
| Native
| Named of string
let t =
map string ~f:(function
| "native" -> Native
| s -> Named s)
let add ts x =
match x with
| None -> ts
| Some t ->
if List.mem t ~set:ts then
ts
else
ts @ [t]
end
module Name = struct
let t =
plain_string (fun ~loc name ->
if name = "" ||
String.is_prefix name ~prefix:"." ||
name = "log" ||
name = "install" ||
String.contains name '/' ||
String.contains name '\\' then
of_sexp_errorf loc
"%S is not allowed as a build context name" name;
name)
end
module Opam = struct
type t =
{ loc : Loc.t
; name : string
; profile : string
; switch : string
; root : string option
; merlin : bool
; targets : Target.t list
}
let t ~profile ~x =
field "switch" string >>= fun switch ->
field "name" Name.t ~default:switch >>= fun name ->
field "targets" (list Target.t) ~default:[Target.Native] >>= fun targets ->
field_o "root" string >>= fun root ->
field_b "merlin" >>= fun merlin ->
field "profile" string ~default:profile >>= fun profile ->
loc >>= fun loc ->
return { loc
; switch
; name
; root
; merlin
; targets = Target.add targets x
; profile
}
end
module Default = struct
type t =
{ loc : Loc.t
; profile : string
; targets : Target.t list
}
let t ~profile ~x =
field "targets" (list Target.t) ~default:[Target.Native]
>>= fun targets ->
field "profile" string ~default:profile
>>= fun profile ->
loc
>>= fun loc ->
return { loc
; targets = Target.add targets x
; profile
}
end
type t = Default of Default.t | Opam of Opam.t
let loc = function
| Default x -> x.loc
| Opam x -> x.loc
let t ~profile ~x =
sum
[ "default",
(fields (Default.t ~profile ~x) >>| fun x ->
Default x)
; "opam",
(fields (Opam.t ~profile ~x) >>| fun x ->
Opam x)
]
let t ~profile ~x =
Syntax.get_exn syntax >>= function
| (0, _) ->
(* jbuild-workspace files *)
(peek_exn >>= function
| List (_, List _ :: _) ->
Sexp.Of_sexp.record (Opam.t ~profile ~x) >>| fun x -> Opam x
| _ -> t ~profile ~x)
| _ -> t ~profile ~x
let name = function
| Default _ -> "default"
| Opam o -> o.name
let targets = function
| Default x -> x.targets
| Opam x -> x.targets
let all_names t =
let n = name t in
n :: List.filter_map (targets t) ~f:(function
| Native -> None
| Named s -> Some (n ^ "." ^ s))
let default ?x ?profile () =
Default
{ loc = Loc.of_pos __POS__
; targets = [Option.value x ~default:Target.Native]
; profile = Option.value profile
~default:Config.default_build_profile
}
end
type t =
{ merlin_context : string option
; contexts : Context.t list
}
include Versioned_file.Make(struct type t = unit end)
let () = Lang.register syntax ()
let t ?x ?profile:cmdline_profile () =
field "profile" string ~default:Config.default_build_profile
>>= fun profile ->
let profile = Option.value cmdline_profile ~default:profile in
multi_field "context" (Context.t ~profile ~x)
>>= fun contexts ->
let defined_names = ref String.Set.empty in
let { merlin_context; contexts } =
let init =
{ merlin_context = None
; contexts = []
}
in
List.fold_left contexts ~init ~f:(fun t ctx ->
let name = Context.name ctx in
if String.Set.mem !defined_names name then
Loc.fail (Context.loc ctx)
"second definition of build context %S" name;
defined_names := String.Set.union !defined_names
(String.Set.of_list (Context.all_names ctx));
match ctx, t.merlin_context with
| Opam { merlin = true; _ }, Some _ ->
Loc.fail (Context.loc ctx)
"you can only have one context for merlin"
| Opam { merlin = true; _ }, None ->
{ merlin_context = Some name; contexts = ctx :: t.contexts }
| _ ->
{ t with contexts = ctx :: t.contexts })
in
let contexts =
match contexts with
| [] -> [Context.default ?x ~profile ()]
| _ -> contexts
in
let merlin_context =
match merlin_context with
| Some _ -> merlin_context
| None ->
if List.exists contexts
~f:(function Context.Default _ -> true | _ -> false) then
Some "default"
else
None
in
return
{ merlin_context
; contexts = List.rev contexts
}
let t ?x ?profile () = fields (t ?x ?profile ())
let default ?x ?profile () =
{ merlin_context = Some "default"
; contexts = [Context.default ?x ?profile ()]
}
let load ?x ?profile p =
let x = Option.map x ~f:(fun s -> Context.Target.Named s) in
match Which_program.t with
| Dune ->
Io.with_lexbuf_from_file p ~f:(fun lb ->
if Dune_lexer.eof_reached lb then
default ?x ?profile ()
else
let first_line = Dune_lexer.first_line lb in
parse_contents lb first_line ~f:(fun _lang -> t ?x ?profile ()))
| Jbuilder ->
let sexp =
Io.Sexp.load p ~mode:Many_as_one ~lexer:Sexp.Lexer.jbuild_token
in
parse
(enter (t ?x ?profile ()))
(Univ_map.singleton (Syntax.key syntax) (0, 0))
sexp
let default ?x ?profile () =
let x = Option.map x ~f:(fun s -> Context.Target.Named s) in
default ?x ?profile ()
let filename =
match Which_program.t with
| Dune -> "dune-workspace"
| Jbuilder -> "jbuild-workspace"