diff --git a/ROADMAP.org b/ROADMAP.org index 97bcb14f..c74a8e94 100644 --- a/ROADMAP.org +++ b/ROADMAP.org @@ -116,3 +116,26 @@ Load a configuration file from =~/.config/jbuilder/config.sexp= where the user can define preferences such as colors. ** Code improvements *** Delete the global variables in Clflags +*** Consolidate the S-expression parser + +It doesn't follow the specification given in the readme of +[[https://github.com/janestreet/parsexp][parsexp]]. This need to be fixed. +** Make =Jbuild_plugin= a library + +Currently Jbuilder generates a wrapper script containing the source +code of the =Jbuild_plugin= followed by the user script. While this +method is trivial to implement, it is not great if users want to write +libraries for jbuild plugins. + +What we should do instead is create a proper =jbuild_plugin= library +that is installed. This library should read a file containing the +build context details generated by Jbuilder and passed as +=Sys.argv.(1)=. + +We need to refactor things a bit to make this happen, in particular +the library will propably need to know how to parse s-expression. We +can create a =jbuild_common= library to put the parts that are common +between =jbuild_plugin= and =jbuilder=. + +Note that =doc/jbuild= is an OCaml script. To simplify the bootstrap, +we should just convert it back to a static =jbuild= file. diff --git a/doc/manual.org b/doc/manual.org index 06864fdb..6a077f05 100644 --- a/doc/manual.org +++ b/doc/manual.org @@ -39,7 +39,8 @@ it from the crowd: a great choice for multi-project development - cross-platform: as long as your code is portable, Jbuilder will be - able to cross-compile it automatically + able to cross-compile it (note that Jbuilder is designed internally + to make this easy but the actual support is not implemented yet) - release directly from any revision: Jbuilder needs no setup stage. To release your project, you can simply point to a specific @@ -55,7 +56,7 @@ format and the third one describes how to use the =jbuilder= command. - *package*: a package is a set of libraries, executables, ... that are built and installed as one by opam -- *project*: project is a source tree, maybe containing one or more +- *project*: a project is a source tree, maybe containing one or more packages - *root*: the root is the directory from where Jbuilder can build @@ -72,31 +73,34 @@ format and the third one describes how to use the =jbuilder= command. - *build context*: a build context is a subdirectory of the =/_build= directory. It contains all the build artifacts of - the workspace built against a specific configuration. The is always - a =default= build context, which correspond to the environemnt in - which Jbuilder is executed. Unless a =jbuild-workspace= file exists in - a parent directory and defines more build contexts, =default= is the - only build context available + the workspace built against a specific configuration. Without + specific configuration from the user, there is always a =default= + build context, which correspond to the environemnt in which Jbuilder + is executed. Build contexts can be specified by writing a + [[jbuild-workspace]] file + +- *build context root*: the root of a build context named =foo= is + =/_build/= - *alias*: an alias is a build target that doesn't produce any file and has configurable dependencies. Alias are per-directory and some are recursive; asking an alias to be built in a given directrory will trigger the construction of the alias in all children - directories recursively. The most interesting one is =runtest=, - which will run user defined tests + directories recursively. The most interesting ones are: + + =runtest= which runs user defined tests + + =install= which depends on everything that should be installed * Jbuilder project layout and metadata specification A typical jbuilder project will have one or more =.opam= file -at toplevel as well as =jbuild= files at toplevel and wherever -interesting things are: libraries, executables, tests, documents to -install, etc... +at toplevel as well as =jbuild= files wherever interesting things are: +libraries, executables, tests, documents to install, etc... It is recommemded to organize your project so that you have exactly one library per directory. You can have several executables in the same directory, as long as they share the same build configuration. -The rest of these section describe the format of Jbuilder metadata +The rest of these sections describe the format of Jbuilder metadata files. Note that the Jbuilder metadata format is versionned in order to @@ -113,24 +117,23 @@ latest one will be used. ** Metadata format -All configuration files read by Jbuilder are using S-expression +Most configuration files read by Jbuilder are using the S-expression syntax, which is very simple. Everything is either an atom or a list. The exact specification of S-expressions is described in the documentation of the [[https://github.com/janestreet/parsexp][parsexp]] library. -Note that the format is completely static. If you have a pattern -that's repeated a lot, you can use [[https://github.com/janestreet/cinaps][cinaps]] to generate the boilerplate. +Note that the format is completely static. However you can do +meta-programming on jbuilds files by writing them in [[OCaml syntax]]. ** .opam files -Jbuilder doesn't read =.opam= files, however when a -=.opam= is present, Jbuilder will knows that the package -named == exists. It will know how to construct a -=.install= file in the same directory, to handle installation -via [[https://opam.ocaml.org/][opam]]. Jbuilder also defines the recursive =install= alias, which depends -on all the buildable =.install= files in the workspace. So -for instance to build everything that is installable in a workspace, -run at the root: +When a =.opam= file is present, Jbuilder will knows that the +package named == exists. It will know how to construct a +=.install= file in the same directory to handle installation +via [[https://opam.ocaml.org/][opam]]. Jbuilder also defines the recursive =install= alias, which +depends on all the buildable =.install= files in the +workspace. So for instance to build everything that is installable in +a workspace, run at the root: #+begin_src $ jbuilder build @install @@ -148,14 +151,13 @@ this is where =opam pin ...= will look for them. *** Package version -Note that when installing a package, Jbuilder will write down the -version of the package in the installation directory. While Jbuilder -makes no use of it at the moment, it can be use by external tools such -as [[http://projects.camlcity.org/projects/findlib.html][ocamlfind]]. +Note that Jbuilder will try to determine the version number of +packages defined in the workspace. While Jbuilder itself makes no use +of version numbers, it can be use by external tools such as [[http://projects.camlcity.org/projects/findlib.html][ocamlfind]]. Jbuilder determines the version of a package by first looking in the -=.opam= contains a =version= variable. If not, it will try to -read the first line of a version file in the same directory as the +=.opam= for a =version= variable. If not found, it will try +to read the first line of a version file in the same directory as the =.opam= file. The version file is any file whose name is, in order in which they are looked for: @@ -163,12 +165,9 @@ order in which they are looked for: - =version= - =VERSION= -The version file can be generated by a rule. +The version file can be generated by a user rule. -Note that it is not mandatory to specify a version number and if -Jbuilder cannot determine the package version, then it just won't -assign one. Managing versions should be left to package managers -anyway. +If the version can't be determined, Jbuilder just won't assign one. *** Odig conventions @@ -177,8 +176,8 @@ README*, CHANGE*, HISTORY* and LICENSE* files in the same directory as the =.opam= file to a location where odig will find them. Note that this include files present in the source tree as well as -generated files. So for instance a changelog generated by a rule will -be automatically installed as well. +generated files. So for instance a changelog generated by a user rule +will be automatically installed as well. ** jbuild @@ -201,9 +200,7 @@ The script can use the directive =#require= to access libraries: #+end_src Note that any library required by a =jbuild= file must be part of the -installed world. This mean that your project won't be compilable in -the same workspace as the required library, unless the library has -been previously installed. +installed world. If you don't like the S-expression syntax, then this method gives you a way to use whatever else you want. For instance you could have an @@ -218,6 +215,10 @@ let () = library "foo" ~modules:["plop"; "bidule"] #+end_src +Currently the =Jbuild_plugin= module is only available inside +plugins. It is however planned to make it a proper library, see [[../ROADMAP.org][the +roadmap]] for details. + *** Specification =jbuild= files are composed of stanzas. For instance a typical @@ -273,7 +274,7 @@ the library and you are free to expose only the modules you want. field, the library will not be installed by Jbuilder. The public name must start by the package name it is part of and optionally followed by a dot and anything else you want. The package name must - be one of the package that Jbuilder knows about, as determined by + be one of the packages that Jbuilder knows about, as determined by the [[package.opam][.opam files]] - =(synopsis )= should give a one-line description of the @@ -305,7 +306,7 @@ the library and you are free to expose only the modules you want. toplevel namespace will make your library unusable with other libraries if there is a module name clash. This option is only intended for libraries that manually prefix all their modules by the - library name + library name and to ease porting of existing projects to Jbuilder - =(preprocess )= specifies how to pre-process files if needed. The default is =no_processing=. Other options are @@ -327,7 +328,7 @@ the library and you are free to expose only the modules you want. - =(cxx_names ())= is the same as =c_names= but for C++ stubs -- =(install_c_headers ())= if your libraries has public C +- =(install_c_headers ())=, if your libraries has public C header files that must be installed, you must list them in this field, with the =.h= extension @@ -340,9 +341,9 @@ the library and you are free to expose only the modules you want. =ppx_type_conv_plugin= and must be set when the library is intended to be used as a ppx rewriter or a =[@@deriving ...]= plugin -- =(ppx_runtime_libraries ())= when the library is a - ppx rewriter or a =[@@deriving ...]= plugin and has runtime - dependencies, you can specify them here +- =(ppx_runtime_libraries ())= is for when the library + is a ppx rewriter or a =[@@deriving ...]= plugin and has runtime + dependencies. You need to specify these runtime dependencies them here - =(virtual_deps ()=. Sometimes opam packages enable a specific feature only if another package is installed. This is for @@ -359,7 +360,7 @@ the library and you are free to expose only the modules you want. to =ocamlc= and =ocamlopt= when building the library archive files. You can use this to specify =-linkall= for instance. == is a list of strings supporting [[Variables expansion][variables - expansion]]. + expansion]] - =(c_flags )= specifies the compilation flags for C stubs, using the [[Ordered set language][ordered set language]]. This field supports =(:include ...)= @@ -403,7 +404,7 @@ Note that in case native compilation is not available, =.exe= will in fact be a custom byte-code executable. Custom in the sense of =ocamlc -custom=, meaning that it is a native executable that embeds the =ocamlrun= virtual machine as well as the byte code. As such you -can always rely on =.exe= begin available. +can always rely on =.exe= being available. == are: @@ -428,9 +429,9 @@ can always rely on =.exe= begin available. **** rule -The =rule= stanza is used to create custom rules. It tells Jbuilder -how to generate a specific set of files from a specific set of -dependencies. +The =rule= stanza is used to create custom user rules. It tells +Jbuilder how to generate a specific set of files from a specific set +of dependencies. The syntax is as follow: @@ -473,7 +474,6 @@ dependencies. See the [[User actions][actions section]] for more details. (action (run ocamlyacc ${<})))) #+end_src - **** alias The =alias= stanza lets you add dependencies to an alias, or specify @@ -516,7 +516,7 @@ See the [[runtest][section about running tests]] for details. The =provides= stanza allows you to globally name a file, either a source file or a target. This is especially important for build tools; by using the =provides= mechanism, you don't need to know whether the -binary is in the tree or installed. +binary is in the workspace or installed. The syntax is as follow: @@ -546,7 +546,7 @@ in points to using the special forms =${bin:}= or Note that any file referred by a =provides= stanza should probably be installed as well, using an [[install]] stanza. If the file is meant to be installed in a library directory, then its name should be of the form -=:=. It is meant to be installed in the +=:=. If it is meant to be installed in the =bin= directory, then its name should be the program name. **** install @@ -597,7 +597,7 @@ manual. The following sections are available: ***** Ordered set language -A few fields takes as argument a ordered set and can be specified +A few fields takes as argument am ordered set and can be specified using a small DSL. This DSL is interpreted by jbuilder into an ordered set of strings diff --git a/src/jbuild b/src/jbuild index 44139a2a..abbf3c45 100644 --- a/src/jbuild +++ b/src/jbuild @@ -4,6 +4,6 @@ ((name jbuilder) (public_name jbuilder) (libraries (unix jbuilder_re)) - (synopsis "Internal Jbuilder library, do not use!")) + (synopsis "Internal Jbuilder library, do not use!"))) (ocamllex (sexp_lexer meta_lexer glob_lexer)) diff --git a/src/jbuild_load.ml b/src/jbuild_load.ml index 7560d19a..bec1236d 100644 --- a/src/jbuild_load.ml +++ b/src/jbuild_load.ml @@ -5,6 +5,7 @@ module Jbuilds = struct type script = { dir : Path.t ; visible_packages : Package.t String_map.t + ; closest_packages : Package.t list } type one = @@ -69,6 +70,7 @@ end return (path, stanzas) | Script { dir ; visible_packages + ; closest_packages } -> let file = Path.relative dir "jbuild" in let generated_jbuild = @@ -77,7 +79,8 @@ end let wrapper = Path.extend_basename generated_jbuild ~suffix:".ml" in ensure_parent_dir_exists generated_jbuild; let requires = - create_plugin_wrapper context ~exec_dir:dir ~plugin:file ~wrapper ~target:generated_jbuild + create_plugin_wrapper context ~exec_dir:dir ~plugin:file ~wrapper + ~target:generated_jbuild in let pkgs = List.map requires ~f:(Findlib.find_exn context.findlib) @@ -115,7 +118,7 @@ end args >>= fun () -> let sexps = Sexp_load.many (Path.to_string generated_jbuild) in - return (dir, Stanzas.parse sexps ~dir ~visible_packages)) + return (dir, Stanzas.parse sexps ~dir ~visible_packages ~closest_packages)) |> Future.all end @@ -126,15 +129,16 @@ type conf = ; packages : Package.t String_map.t } -let load ~dir ~visible_packages = +let load ~dir ~visible_packages ~closest_packages = let file = Path.relative dir "jbuild" in match Sexp_load.many_or_ocaml_script (Path.to_string file) with | Sexps sexps -> - Jbuilds.Literal (dir, Stanzas.parse sexps ~dir ~visible_packages) + Jbuilds.Literal (dir, Stanzas.parse sexps ~dir ~visible_packages ~closest_packages) | Ocaml_script -> Script { dir ; visible_packages + ; closest_packages } let load () = @@ -175,20 +179,21 @@ let load () = |> List.map ~f:(fun pkg -> (pkg.Package.path, pkg)) |> Path.Map.of_alist_multi in - let rec walk dir jbuilds visible_packages = + let rec walk dir jbuilds visible_packages closest_packages = let path = File_tree.Dir.path dir in let files = File_tree.Dir.files dir in let sub_dirs = File_tree.Dir.sub_dirs dir in - let visible_packages = + let visible_packages, closest_packages = match Path.Map.find path packages_per_dir with - | None -> visible_packages + | None -> (visible_packages, closest_packages) | Some pkgs -> - List.fold_left pkgs ~init:visible_packages ~f:(fun acc pkg -> - String_map.add acc ~key:pkg.Package.name ~data:pkg) + (List.fold_left pkgs ~init:visible_packages ~f:(fun acc pkg -> + String_map.add acc ~key:pkg.Package.name ~data:pkg), + pkgs) in let jbuilds = if String_set.mem "jbuild" files then - let jbuild = load ~dir:path ~visible_packages in + let jbuild = load ~dir:path ~visible_packages ~closest_packages in jbuild :: jbuilds else jbuilds @@ -207,13 +212,13 @@ let load () = let children, jbuilds = String_map.fold sub_dirs ~init:([], jbuilds) ~f:(fun ~key:_ ~data:dir (children, jbuilds) -> - let child, jbuilds = walk dir jbuilds visible_packages in + let child, jbuilds = walk dir jbuilds visible_packages closest_packages in (child :: children, jbuilds)) in (Alias.Node (path, children), jbuilds) in let root = File_tree.root ftree in - let tree, jbuilds = walk root [] String_map.empty in + let tree, jbuilds = walk root [] String_map.empty [] in { file_tree = ftree ; tree ; jbuilds diff --git a/src/jbuild_types.ml b/src/jbuild_types.ml index 4ab6634c..03cbbd7c 100644 --- a/src/jbuild_types.ml +++ b/src/jbuild_types.ml @@ -729,15 +729,16 @@ end module Stanzas = struct type t = Stanza.t list - let resolve_packages ts ~dir ~(visible_packages : Package.t String_map.t) = + let resolve_packages ts ~dir + ~(visible_packages : Package.t String_map.t) + ~(closest_packages : Package.t list) = let error fmt = Loc.fail (Loc.in_file (Path.to_string (Path.relative dir "jbuild"))) fmt in - let known_packages () = - let visible_packages = String_map.values visible_packages in - let longest_pkg = List.longest_map visible_packages ~f:(fun p -> p.name) in + let package_listing packages = + let longest_pkg = List.longest_map packages ~f:(fun p -> p.Package.name) in String.concat ~sep:"\n" - (List.map visible_packages ~f:(fun pkg -> + (List.map packages ~f:(fun pkg -> sprintf "- %-*s (because of %s)" longest_pkg pkg.Package.name (Path.to_string (Path.relative pkg.path (pkg.name ^ ".opam"))))) in @@ -748,18 +749,19 @@ module Stanzas = struct %s%s" pkg (Path.to_string dir) - (known_packages ()) + (package_listing (String_map.values visible_packages)) (hint pkg (String_map.keys visible_packages)) in let default () = - match String_map.keys visible_packages with + match closest_packages with | [pkg] -> pkg | [] -> error "no packages are defined here" | _ :: _ :: _ -> - error "there is more than one package visible here:\n\ + error "I can't determine automatically which package your (install ...) \ + stanzas are for in this directory. I have the choice between these ones:\n\ %s\n\ You need to add a (package ...) field in your (install ...) stanzas" - (known_packages ()) + (package_listing closest_packages) in List.map ts ~f:(fun (stanza : Stanza.t) -> match stanza with @@ -771,7 +773,7 @@ module Stanzas = struct check pkg; stanza | Install ({ package = None; _ } as install) -> - Install { install with package = Some (default ()) } + Install { install with package = Some (default ()).name } | _ -> stanza) let parse sexps ~dir ~visible_packages =