implement the manual properly for (install ...) stanzas

This commit is contained in:
Jeremie Dimino 2017-02-28 18:17:15 +00:00
parent efbe548612
commit 2e7d592f66
5 changed files with 108 additions and 78 deletions

View File

@ -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.

View File

@ -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
=<root>/_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
=<root>/_build/<foo>=
- *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 =<package>.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]].
** <package>.opam files
Jbuilder doesn't read =<package>.opam= files, however when a
=<package>.opam= is present, Jbuilder will knows that the package
named =<package>= exists. It will know how to construct a
=<package>.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 =<package>.install= files in the workspace. So
for instance to build everything that is installable in a workspace,
run at the root:
When a =<package>.opam= file is present, Jbuilder will knows that the
package named =<package>= exists. It will know how to construct a
=<package>.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 =<package>.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
=<package>.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
=<package>.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
=<package>.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 =<package>.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][<package>.opam files]]
- =(synopsis <string>)= 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 <preprocess-spec>)= 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 (<names>))= is the same as =c_names= but for C++ stubs
- =(install_c_headers (<names>))= if your libraries has public C
- =(install_c_headers (<names>))=, 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 (<library-names>))= when the library is a
ppx rewriter or a =[@@deriving ...]= plugin and has runtime
dependencies, you can specify them here
- =(ppx_runtime_libraries (<library-names>))= 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 (<opam-packages>)=. 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. =<flags>= is a list of strings supporting [[Variables expansion][variables
expansion]].
expansion]]
- =(c_flags <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, =<name>.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 =<name>.exe= begin available.
can always rely on =<name>.exe= being available.
=<optional-fields>= are:
@ -428,9 +429,9 @@ can always rely on =<name>.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:<name>}= 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
=<public-library-name>:<file>=. It is meant to be installed in the
=<public-library-name>:<file>=. 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

View File

@ -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))

View File

@ -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

View File

@ -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 =