├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── _tags
├── manual.md
├── src
├── META
├── descr
├── generators.ml
├── generators.mli
├── modules.ml
├── modules.mli
├── namespaces.ml
├── namespaces.mli
├── opam
├── rules.ml
├── rules.mli
└── summary.png
└── test
├── .gitignore
├── Makefile
├── _tags
├── test_basic.ml
├── test_helpers.ml
├── test_helpers.mli
├── test_library.ml
├── test_main.ml
└── test_oasis.ml
/.gitignore:
--------------------------------------------------------------------------------
1 | _build/
2 | scratch/
3 | *.docdir
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | env:
2 | - PPA=avsm/ocaml42+opam12
3 |
4 | before_script:
5 | - sudo add-apt-repository -y ppa:$PPA
6 | - sudo apt-get update
7 | - sudo apt-get install ocaml-nox opam
8 | - ocaml -version
9 | - opam init -y
10 | - eval `opam config env`
11 |
12 | script:
13 | - opam install -y ocamlfind
14 | - make build
15 | - opam install -y ounit process oasis
16 | - make test
17 | - make clean
18 | - opam remove -y ocamlfind ounit process oasis
19 | - ln -s src/opam
20 | - opam pin add -y .
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PACKAGE := namespaces
2 | TARGETS := src/namespaces.cma src/namespaces.cmxa src/namespaces.cmi
3 |
4 | INSTALL := \
5 | $(foreach target,$(TARGETS),_build/$(target)) src/namespaces.mli \
6 | _build/src/namespaces.cmt _build/src/namespaces.cmti
7 |
8 | CFLAGS := -bin-annot
9 | OCAMLBUILD := ocamlbuild -use-ocamlfind -cflags $(CFLAGS)
10 |
11 | .PHONY : build
12 | build :
13 | $(OCAMLBUILD) src/namespaces.cma src/namespaces.cmxa
14 |
15 | .PHONY : install
16 | install : uninstall build
17 | ocamlfind install $(PACKAGE) src/META $(INSTALL) _build/src/namespaces.a
18 |
19 | .PHONY : uninstall
20 | uninstall :
21 | ocamlfind remove $(PACKAGE)
22 |
23 | .PHONY : test
24 | test :
25 | make -C test
26 |
27 | .PHONY : clean
28 | clean :
29 | ocamlbuild -clean
30 | make -C test clean
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | *This project is **not supported** for the time being. Similar behaviour may be
2 | possible using [codept][codept] [v0.10][codept-0.10]'s `-nested` feature.*
3 |
4 | [codept]: https://github.com/octachron/codept
5 | [codept-0.10]: https://github.com/Octachron/codept/blob/master/Changelog.md#version-010
6 |
7 | # Namespaces [![version 0.5.1][version]][releases] [![BSD license][license-img]][license] [![Travis status][travis-img]][travis]
8 |
9 | [version]: https://img.shields.io/badge/version-0.5.1-blue.svg
10 | [releases]: https://github.com/aantron/namespaces/releases
11 | [license-img]: https://img.shields.io/badge/license-BSD-blue.svg
12 | [travis]: https://travis-ci.org/aantron/namespaces/branches
13 | [travis-img]: https://img.shields.io/travis/aantron/namespaces/master.svg
14 |
15 | Ever wish `src/server/foo.ml` was module `Server.Foo` and not just `Foo`?
16 |
17 | Who likes to name it `src/server/server_foo.ml`, just to avoid naming conflicts?
18 |
19 | *Namespaces* is an Ocamlbuild plugin that turns directories in your source tree
20 | into scoped OCaml modules. You get a nice, logical, and predictable structure,
21 | and you don't have to worry about duplicate filenames anymore. It's the sane
22 | file naming convention that most other languages have always had.
23 |
24 | If you have the directory structure on the left, you will get the OCaml modules
25 | on the right:
26 |
27 | ![Effect summary][summary]
28 |
29 | [summary]: https://github.com/aantron/namespaces/blob/master/src/summary.png
30 |
31 | The module structure works as if you had written:
32 |
33 | ```ocaml
34 | module Server =
35 | struct
36 | module Foo = (* server/foo.ml *)
37 | module Bar = (* server/bar.ml *)
38 | end
39 |
40 | module Client =
41 | struct
42 | module Foo = (* client/foo.ml *)
43 | module Bar = (* client/bar.ml *)
44 |
45 | module Ui =
46 | struct
47 | module Reactive = (* client/ui/reactive.ml *)
48 | end
49 |
50 | include (* client/client.ml *)
51 | end
52 | ```
53 |
54 | There is no conflict between `server/foo.ml` and `client/foo.ml`, because there
55 | is no globally-visible module `Foo`.
56 |
57 | Within a module, you don't have to (indeed, must not) qualify sibling module
58 | names: from `Server.Foo`, you access `Server.Bar` as just `Bar`. You can access
59 | children and nephews, but cannot access parents.
60 |
61 | The usual dependency restrictions between OCaml modules apply: there must not be
62 | any dependency cycles. Siblings, if they depend on each other, must depend on
63 | each other in some order, and each parent module depends on all of its child
64 | modules.
65 |
66 | The modules are composed with `-no-alias-deps`, so depending on a directory
67 | module does not automatically pull in all of its descendants, unless they are
68 | actually used. This also means that Namespaces requires OCaml 4.02 or higher.
69 |
70 |
71 |
72 |
73 |
74 | ## Instructions
75 |
76 | 1. Install Namespaces:
77 |
78 | opam install namespaces
79 |
80 | 2. Add it to your build system.
81 |
82 | - If using Ocamlbuild, create `myocamlbuild.ml`, and make it look like this:
83 |
84 | let () = Ocamlbuild_plugin.dispatch Namespaces.handler
85 |
86 | Invoke Ocamlbuild with:
87 |
88 | ocamlbuild -use-ocamlfind -plugin-tag "package(namespaces)"
89 |
90 | If you already have `myocamlbuild.ml`, call `Namespaces.handler` before or
91 | after your hook that you pass to `dispatch`.
92 |
93 | - If using OASIS, find the bottom of your `myocamlbuild.ml` file, and replace
94 | the call to `dispatch` with this:
95 |
96 | let () =
97 | dispatch
98 | (MyOCamlbuildBase.dispatch_combine
99 | [MyOCamlbuildBase.dispatch_default conf package_default;
100 | Namespaces.handler])
101 |
102 | Then, add this to the top of your OASIS file:
103 |
104 | OCamlVersion: >= 4.02
105 | AlphaFeatures: ocamlbuild_more_args
106 | XOCamlbuildPluginTags: package(namespaces)
107 |
108 | and re-run `oasis setup`.
109 |
110 | 3. Tag the directories that you want to become namespaces with the `namespace`
111 | tag. For example, to make all directories in `src/` into namespaces, you
112 | can add this to your `_tags` file:
113 |
114 | : namespace
115 | : include
116 |
117 | 4. If you are building a library, see additional instructions in
118 | [`manual.md`][manual]. See also that file if you have generated files in your
119 | project.
120 |
121 | 5. Enjoy!
122 |
123 | [manual]: https://github.com/aantron/namespaces/blob/master/manual.md
124 |
125 |
126 |
127 |
128 |
129 | ## The Future
130 |
131 | An Ocamlbuild plugin is by no means the best way, ideally, to deal with the
132 | problem of structuring modules in OCaml. I am releasing this, in part, to
133 | advance the discussion. My hope is that after some time, we will have a more
134 | conclusive solution integrated into the OCaml tool chain.
135 |
136 |
137 |
138 |
139 |
140 | ## License
141 |
142 | Namespaces is distributed under the terms of the 2-clause
143 | [BSD license][license].
144 |
145 | [license]: https://opensource.org/licenses/BSD-2-Clause
146 |
--------------------------------------------------------------------------------
/_tags:
--------------------------------------------------------------------------------
1 | : safe_string, package(ocamlbuild)
2 | "test" or "scratch": -traverse
3 |
--------------------------------------------------------------------------------
/manual.md:
--------------------------------------------------------------------------------
1 | # Namespaces
2 |
3 | - [Building libraries](#Libraries)
4 | - [With Ocamlbuild](#LibrariesOcamlbuild)
5 | - [With OASIS](#LibrariesOASIS)
6 | - [Generated files](#Generated)
7 | - [Effect on separate compilation](#SeparateCompilation)
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Building libraries
15 |
16 | These instructions are in addition to the main instructions in
17 | [README.md][readme-instructions]. Follow those first, then the ones in this
18 | file.
19 |
20 | [readme-instructions]: https://github.com/aantron/namespaces#instructions
21 |
22 |
23 | #### With Ocamlbuild
24 |
25 | To build one or more libraries with Namespaces and Ocamlbuild:
26 |
27 | 1. Tag your library directories with the additional tag `namespace_lib`, like
28 | this:
29 |
30 | "src/lib_1" or "src/lib_2": namespace_lib
31 |
32 | That gives the tag structure:
33 |
34 | src/
35 | |-- lib_1/ namespace, namespace_lib
36 | | |-- namespace/ namespace
37 | | | |-- foo.ml
38 | | | +-- bar.ml
39 | | |
40 | | +-- other_namespace/ namespace
41 | | +-- foo.ml
42 | |
43 | +-- lib_2/ namespace, namespace_lib
44 | +-- namespace/ namespace
45 | |-- foo.ml
46 | +-- bar.ml
47 |
48 | These tags tell Namespaces to generate the requisite `.mllib` files for
49 | Ocamlbuild.
50 |
51 | 2. You can now build the library targets `lib_1.cma`, `lib_1.cmxa`, `lib_2.cma`,
52 | `lib_2.cmxa`.
53 |
54 | 3. To install the libraries with Findlib, describe the libraries as normal in a
55 | `META` file. Then, install it, together with the library files. For example,
56 | in `Makefile` syntax:
57 |
58 | TO_INSTALL := \
59 | META \
60 | _build/src/lib_1.cma \
61 | _build/src/lib_1.cmxa \
62 | _build/src/lib_1.a \
63 | _build/src/lib_2.cma \
64 | _build/src/lib_2.cmxa \
65 | _build/src/lib_2.a \
66 | $(shell find _build -name *.cmi) \
67 | $(shell find _build -name *.cmt) \
68 | $(shell find _build -name *.cmti)
69 |
70 | install :
71 | ocamlfind install $(PACKAGE) $(TO_INSTALL)
72 |
73 | See the [library test][libtest] for a small example.
74 |
75 | [libtest]: https://github.com/aantron/namespaces/tree/master/test/3-library
76 |
77 |
78 | #### With OASIS
79 |
80 | To build libraries with Namespaces and OASIS:
81 |
82 | 1. Tag your library directories with `namespace_lib`, as described in step (1)
83 | above for Ocamlbuild.
84 |
85 | 2. Namespaces will now generate its own `.mllib` files, but these will conflict
86 | with the ones that OASIS generates from each `Library` section in `_oasis`.
87 | To fix that, the OASIS `.mllib` files need to be deleted. Make sure your
88 | project doesn't have any `.mllib` files that you want to keep, and then add
89 | the following to the bottom of your `myocamlbuild.ml`:
90 |
91 | let () = Namespaces.delete_mllib_files ()
92 |
93 | This is an unpleasant hack, and I hope to remove it in a future version of
94 | Namespaces.
95 |
96 | 3. When using Namespaces with OASIS, the `_oasis` file is essentially only for
97 | assembling the `META` file – at least with respect to these instructions. The
98 | `_oasis` file will look something like this:
99 |
100 | OASISFormat: 0.4
101 | Name: my_lib
102 | Version: 0.1
103 | Synopsis: Description
104 | Authors: Me
105 | License: BSD-2-clause
106 | BuildTools: ocamlbuild
107 | Plugins: META (0.3)
108 |
109 | OCamlVersion: >= 4.02
110 | AlphaFeatures: ocamlbuild_more_args
111 | XOCamlbuildPluginTags: package(namespaces)
112 |
113 | Library lib_1
114 | FindlibName: my_lib
115 | Path: src
116 | Modules: Lib_1
117 | XMETADescription: lib_1
118 |
119 | Library lib_2
120 | FindlibParent: lib_1
121 | Path: src
122 | Modules: Lib_2
123 | XMETADescription: lib_2
124 |
125 | 4. Do not use `ocaml setup.ml -install` to install your library. Instead,
126 | install as in step (3) of the Ocamlbuild instructions above, but change the
127 | path `META` to `src/META`, `lib/META`, or whatever subdirectory your
128 | libraries are located in.
129 |
130 | See the [OASIS library test][oasis-libtest] for an example.
131 |
132 | [oasis-libtest]: https://github.com/aantron/namespaces/tree/master/test/4-oasis-library
133 |
134 |
135 |
136 |
137 |
138 |
139 | ## Generated files
140 |
141 | Namespaces has trouble with generated files, such as `.ml` files generated from
142 | `.mll` files by `ocamllex`. If you have such files in your project, you need to
143 | tell Namespaces about the generator. For example, if you have a program that
144 | generates `.ml` and `.mli` files from `.rpc` files,
145 |
146 | let () =
147 | Ocamlbuild_plugin.dispatch
148 | (Namespaces.handler ~generators:["%.rpc", ["%.ml"; "%.mli"]])
149 |
150 | The default value of `~generators` is `Namespaces.builtin_generators`, which has
151 | rules for `ocamllex` and `ocamlyacc`. See [`namespaces.mli`][mli] for details.
152 |
153 | [mli]: https://github.com/aantron/namespaces/blob/master/src/namespaces.mli#L37
154 |
155 |
156 |
157 |
158 |
159 |
160 | ## Effect on separate compilation
161 |
162 | As currently implemented, grouping modules in a namespace causes recompilation
163 | of everything that depends on that namespace, each time one of the namespace
164 | child modules changes. However, only the children that are actually referenced
165 | by depending code are linked with that code. To illustrate, suppose there are
166 | modules
167 |
168 | ```
169 | Namespace.Foo
170 | Namespace.Bar
171 | Main
172 | ```
173 |
174 | and `Main` refers only to `Namespace.Foo`. `Main` could be in the same project,
175 | or `Namespace` might be packaged as a library, with `Main` in a different
176 | project.
177 |
178 | When an executable with `Main` is linked, only `Namespace.Foo` is included.
179 | However, if `Main` is in the same project as `Namespace`, then when *either*
180 | `Namespace.Foo` or `Namespace.Bar` is changed, `Main` is recompiled – even
181 | though that is not strictly necessary in the case `Namespace.Bar` is changed.
182 | This is a result of an imprecision in the current dependency analysis.
183 |
--------------------------------------------------------------------------------
/src/META:
--------------------------------------------------------------------------------
1 | version = "0.5.1"
2 | description = "Directory-based module hierarchy"
3 | requires = "str ocamlbuild"
4 | archive(byte) = "namespaces.cma"
5 | archive(native) = "namespaces.cmxa"
6 |
--------------------------------------------------------------------------------
/src/descr:
--------------------------------------------------------------------------------
1 | Turn directories into OCaml modules
2 |
3 | An Ocamlbuild plugin that turns directories tagged with "namespace" into a
4 | nested module hierarchy. Each directory becomes a module. Filenames become
5 | scoped, so you can have the same filename in multiple directories. So, if you
6 | have
7 |
8 | server
9 | |-- foo.ml
10 | +-- bar.ml
11 | client
12 | |-- foo.ml
13 | |-- bar.ml
14 | |-- ui
15 | | +-- reactive.ml
16 | +-- client.ml
17 |
18 | It is as if you had written
19 |
20 | module Server =
21 | struct
22 | module Foo = (* server/foo.ml *)
23 | module Bar = (* server/bar.ml *)
24 | end
25 |
26 | module Client =
27 | struct
28 | module Foo = (* client/foo.ml *)
29 | module Bar = (* client/bar.ml *)
30 | module Ui =
31 | struct
32 | module Reactive = (* client/ui/reactive.ml *)
33 | end
34 |
35 | include (* client/client.ml *)
36 | end
37 |
--------------------------------------------------------------------------------
/src/generators.ml:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | type t = string * string list
5 |
6 | let identity = "%.ml", ["%.ml"]
7 | let signature = "%.mli", ["%.mli"]
8 | let ocamllex = "%.mll", ["%.ml"]
9 | let ocamlyacc = "%.mly", ["%.ml"]
10 | let builtin = [ocamllex; ocamlyacc]
11 |
12 | type parsed = string -> string list
13 |
14 | let parse_single =
15 | let wildcard = Str.regexp_string "%" in
16 | fun (dep, prods) ->
17 | let dep_prefix, dep_suffix =
18 | match Str.split_delim wildcard dep with
19 | | [p; s] -> p, s
20 | | _ ->
21 | failwith "Generator dependency must contain exactly one wildcard" in
22 | let dep_regexp =
23 | "^" ^ (Str.quote dep_prefix) ^ "\\(.+\\)" ^
24 | (Str.quote dep_suffix) ^ "$"
25 | |> Str.regexp in
26 |
27 | let substitute prod =
28 | let prod_fragments = Str.split_delim wildcard prod in
29 | fun stem -> String.concat stem prod_fragments in
30 | let substitute_all = List.map substitute prods in
31 |
32 | let parsed_generator file =
33 | try
34 | Str.search_forward dep_regexp file 0 |> ignore;
35 | let stem = Str.matched_group 1 file in
36 | List.map (fun f -> f stem) substitute_all
37 | with Not_found -> [] in
38 |
39 | parsed_generator
40 |
41 | let parse generators =
42 | let parsed_generators =
43 | List.map parse_single (identity::signature::generators) in
44 | fun file ->
45 | List.map (fun g -> g file) parsed_generators |> List.concat
46 |
--------------------------------------------------------------------------------
/src/generators.mli:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | (** Generator specification parser. *)
5 |
6 | type t = string * string list
7 |
8 | val ocamllex : t
9 | val ocamlyacc : t
10 | val builtin : t list
11 |
12 | type parsed = string -> string list
13 | val parse : t list -> parsed
14 |
--------------------------------------------------------------------------------
/src/modules.ml:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | open Ocamlbuild_plugin
5 |
6 | let sprintf = Printf.sprintf
7 | let bprintf = Printf.bprintf
8 |
9 | (* User tags. *)
10 | let namespace_tag = "namespace"
11 | let namespace_with_name_tag = "namespace_with_name"
12 | let namespace_level_tag = "namespace_level"
13 | let namespace_library_tag = "namespace_lib"
14 |
15 | let namespace_tag_regexp =
16 | Str.regexp
17 | (sprintf "%s$\\|%s\\((\\([^)]*\\))\\)$"
18 | namespace_tag namespace_with_name_tag)
19 |
20 | let library_tag_regexp =
21 | Str.regexp (sprintf "%s\\((\\([^)]*\\))\\)?$" namespace_library_tag)
22 |
23 | (* Internal use tags. *)
24 | let alias_file_tag = "namespace_alias_file"
25 | let ordered_open_tag = "namespace_open"
26 | let namespace_library_dependency_tag lib =
27 | sprintf "namespace_library_dependency_%s" lib
28 | let dummy_tag = "namespaces_dummy"
29 |
30 | let namespace_library_title = "_namespaces"
31 |
32 | type file =
33 | {original_name : string;
34 | renamed_name : string;
35 | prefixed_name : string;
36 | directory : string list;
37 | namespace : string list}
38 |
39 | type namespace_members =
40 | {modules : file list;
41 | interfaces : file list;
42 | namespaces : namespace list}
43 | and namespace = file * namespace_members
44 |
45 | let empty_members = {modules = []; interfaces = []; namespaces = []}
46 |
47 | let tree : namespace_members ref = ref empty_members
48 |
49 | let path_to_string (path : string list) =
50 | match path with
51 | [] -> Filename.current_dir_name
52 | | _ -> String.concat Filename.dir_sep path
53 |
54 | let original_module {original_name; _} =
55 | module_name_of_pathname original_name
56 |
57 | let original_path {original_name; directory; _} =
58 | directory @ [original_name] |> path_to_string
59 |
60 | let final_path {prefixed_name; directory; _} =
61 | directory @ [prefixed_name] |> path_to_string
62 |
63 | let module_path {renamed_name; namespace; _} =
64 | namespace @ [module_name_of_pathname renamed_name]
65 |
66 | let member_module_files {modules; namespaces; _} =
67 | (List.map fst namespaces) @ modules
68 |
69 | let digest_file base_name =
70 | sprintf "%s.digest" base_name
71 |
72 | let alias_container_module base_name =
73 | sprintf "%s__aliases_" (module_name_of_pathname base_name)
74 |
75 | let alias_group_module base_name for_module =
76 | sprintf "%s__aliases__for_%s_"
77 | (module_name_of_pathname base_name) (module_name_of_pathname for_module)
78 |
79 | let alias_export_module base_name =
80 | sprintf "%s__aliases__export_" (module_name_of_pathname base_name)
81 |
82 | let alias_container_file base_name =
83 | sprintf "%s__aliases_.ml" base_name
84 |
85 | let namespace_file base_name =
86 | sprintf "%s.ml" base_name
87 |
88 | let library_list_file base_name =
89 | sprintf "%s.mllib" base_name
90 |
91 | (* Recursively traverses the source tree. Keeps track of the current filesystem
92 | path as a list of strings. Keeps track of the current namespace path in the
93 | same way. The namespace path is extended when a directory is encountered that
94 | has the "namespace" tag set.
95 |
96 | Notes all [.ml] and [.mli] files and "namespace" directories found by
97 | creating records of type [file] for them. Organizes these into a forest of
98 | records of type [namespace], represented by a single top-level record of type
99 | [namespace_members]. This top-level record represents the top-level modules
100 | and namespaces in the project.
101 |
102 | Generated [.ml] and [.mli] files are part of the project, but cannot be found
103 | in the source tree. These are discovered through the rules passed in the
104 | [generators] argument to [scan_tree]. *)
105 | let scan_tree :
106 | Generators.parsed -> (string -> string option) ->
107 | namespace_members * (string, string list) Hashtbl.t =
108 |
109 | (* If the given directory is tagged with "namespace", evaluates to Some None.
110 | If it is tagged with "namespace(foo)", evaluates to Some (Some "foo").
111 | Otherwise, evaluates to None. *)
112 | let is_namespace directory_path =
113 | let rec scan_for_namespace_tag namespace_tag_found = function
114 | | [] -> if namespace_tag_found then Some None else None
115 | | tag::more_tags ->
116 | if not @@ Str.string_match namespace_tag_regexp tag 0 then
117 | scan_for_namespace_tag namespace_tag_found more_tags
118 | else
119 | try Some (Some (Str.matched_group 2 tag))
120 | with Not_found -> scan_for_namespace_tag true more_tags in
121 |
122 | directory_path
123 | |> tags_of_pathname
124 | |> Tags.elements
125 | |> scan_for_namespace_tag false in
126 |
127 | (* If the given path is tagged with "namespace_lib(foo)", evaluates to
128 | Some "foo". Otherwise, evaluates to None. *)
129 | let is_for_library path =
130 | let rec scan_for_library_tag = function
131 | | [] -> None
132 | | tag::more_tags ->
133 | if not @@ Str.string_match library_tag_regexp tag 0 then
134 | scan_for_library_tag more_tags
135 | else
136 | try Some (Str.matched_group 2 tag)
137 | with Not_found ->
138 | sprintf
139 | "The %s tag requires an argument (library name)"
140 | namespace_library_tag
141 | |> failwith in
142 |
143 | path
144 | |> tags_of_pathname
145 | |> Tags.elements
146 | |> scan_for_library_tag in
147 |
148 | (* Constructs a [file] record when given the current filesystem and module
149 | paths, and the basename of a file or directory. *)
150 | let make_file_record directory namespace original_name maybe_renamed_name =
151 | let renamed_name =
152 | match maybe_renamed_name with
153 | | None -> original_name
154 | | Some other_name -> other_name in
155 | let prefixed_name = String.concat "__" (namespace @ [renamed_name]) in
156 | {original_name;
157 | renamed_name;
158 | prefixed_name;
159 | directory;
160 | namespace} in
161 |
162 | (* Given a [namespace_members], eliminates duplicates in its [modules] and
163 | [interfaces] fields. *)
164 | let unique {modules; interfaces; namespaces} =
165 | let compare_files {original_name = name; _} {original_name = name'; _} =
166 | String.compare name name' in
167 | {modules = List.sort_uniq compare_files modules;
168 | interfaces = List.sort_uniq compare_files interfaces;
169 | namespaces} in
170 |
171 | fun generators filter ->
172 | let libraries = Hashtbl.create 32 in
173 | let add_to_library library_name module_file =
174 | let modules =
175 | try Hashtbl.find libraries library_name
176 | with Not_found -> [] in
177 | Hashtbl.replace libraries library_name (module_file::modules) in
178 |
179 | let rec traverse library directory namespace members_acc =
180 | path_to_string directory
181 | |> Sys.readdir
182 | |> Array.fold_left
183 | (fun members_acc entry ->
184 | let entry_path = directory @ [entry] in
185 | let entry_path_string = path_to_string entry_path in
186 |
187 | let library =
188 | match is_for_library entry_path_string with
189 | | None -> library
190 | | Some library_name -> Some library_name in
191 |
192 | (* Directories. *)
193 | if Sys.is_directory entry_path_string then
194 | let absolute_path =
195 | Filename.concat
196 | (Sys.getcwd ()) (String.concat Filename.dir_sep directory) in
197 | if absolute_path = !Options.build_dir then members_acc
198 | else
199 | match is_namespace entry_path_string with
200 | | None -> traverse library entry_path namespace members_acc
201 | | Some rename ->
202 | let new_namespace =
203 | create_namespace
204 | library directory namespace entry entry_path rename in
205 | {members_acc
206 | with namespaces = new_namespace::members_acc.namespaces}
207 |
208 | (* Files. *)
209 | else
210 | create_modules library directory namespace entry members_acc)
211 | members_acc
212 |
213 | and create_namespace library directory namespace entry entry_path rename =
214 | let file = make_file_record directory namespace entry rename in
215 |
216 | let library_name =
217 | match library with
218 | | None -> namespace_library_title
219 | | Some name -> name in
220 |
221 | add_to_library library_name (final_path file);
222 | add_to_library library_name (alias_container_file (final_path file));
223 |
224 | let nested_namespace =
225 | namespace @ [module_name_of_pathname file.renamed_name] in
226 | let members =
227 | traverse
228 | (Some library_name) entry_path nested_namespace empty_members in
229 | (file, unique members)
230 |
231 | and create_modules library directory namespace entry members_acc =
232 | generators entry
233 | |> List.map filter
234 | |> List.fold_left
235 | (fun accumulator -> function
236 | | None -> accumulator
237 | | Some name -> name::accumulator)
238 | []
239 | |> List.fold_left
240 | (fun members_acc generated_file ->
241 | let file = make_file_record directory namespace generated_file None in
242 | if Filename.check_suffix generated_file ".ml" then
243 | ((match library with
244 | | None -> ()
245 | | Some library_name ->
246 | add_to_library library_name (final_path file));
247 | {members_acc with modules = file::members_acc.modules})
248 | else if Filename.check_suffix generated_file ".mli" then
249 | {members_acc with interfaces = file::members_acc.interfaces}
250 | else members_acc)
251 | members_acc in
252 |
253 | let top_level_members = traverse None [] [] empty_members in
254 |
255 | unique top_level_members, libraries
256 |
257 | let iter f =
258 | let rec traverse members =
259 | List.iter (fun file -> f (`Interface, file)) members.interfaces;
260 | List.iter (fun file -> f (`Module, file)) members.modules;
261 | List.iter
262 | (fun (file, members') -> f (`Namespace, file); traverse members')
263 | members.namespaces in
264 | traverse !tree
265 |
266 | (* Maps namespaced file paths to [file] records. For example, for
267 | [server/foo.ml], the key is ["server/server__foo.ml"]. *)
268 | let renamed_files : (string, file) Hashtbl.t =
269 | Hashtbl.create 512
270 |
271 | (* Maps namespaced namespace directories to [namespace] records. For example,
272 | for [server/api], the key is ["server/server__api"]. *)
273 | let namespace_directory_map : (string, namespace) Hashtbl.t =
274 | Hashtbl.create 32
275 |
276 | (** Maps namespace directory namespace paths to [namespace] records. For
277 | example, for [server/api], the key is [["Server"; "Api"]]. *)
278 | let namespace_module_map : (string list, namespace) Hashtbl.t =
279 | Hashtbl.create 32
280 |
281 | let namespace_libraries : (string, string list) Hashtbl.t ref =
282 | ref (Hashtbl.create 1)
283 |
284 | (* For each [.ml] or [.mli] file found during the source tree scan, if its
285 | namespaced name differs from its original name, adds it to [renamed_files].
286 | The condition holds for files inside namespaces, and doesn't hold for files
287 | representing top-level modules that have not been renamed with
288 | [namespace_with_name]. *)
289 | let index_renamed_files () =
290 | iter
291 | (function
292 | | `Namespace, _ -> ()
293 | | (`Interface | `Module), file ->
294 | if file.prefixed_name <> file.original_name then
295 | Hashtbl.add renamed_files (final_path file) file)
296 |
297 | let file_by_renamed_path s =
298 | try Some (Hashtbl.find renamed_files s)
299 | with Not_found -> None
300 |
301 | (* Populates [namespace_directory_map] and [namespace_module_map]. *)
302 | let index_namespaces () =
303 | let rec traverse members =
304 | members.namespaces |> List.iter
305 | (fun ((file, members') as namespace) ->
306 | Hashtbl.add namespace_directory_map (final_path file) namespace;
307 | Hashtbl.add namespace_module_map (module_path file) namespace;
308 | traverse members') in
309 | traverse !tree;
310 |
311 | (* Silence warnings about unused tags. *)
312 | pflag [dummy_tag] namespace_with_name_tag (fun _ -> N);
313 | pflag [dummy_tag] namespace_library_tag (fun _ -> N);
314 | mark_tag_used namespace_tag;
315 | mark_tag_used namespace_level_tag
316 |
317 | let namespace_by_final_directory s =
318 | try Some (Hashtbl.find namespace_directory_map s)
319 | with Not_found -> None
320 |
321 | let extension s =
322 | try
323 | let index = (String.rindex s '.') + 1 in
324 | let remainder_length = (String.length s) - index in
325 | String.sub s index remainder_length
326 | with Not_found -> ""
327 |
328 | let build_native_and_or_bytecode () =
329 | !Options.targets
330 | |> List.fold_left
331 | (fun (native, bytecode) target ->
332 | match extension target with
333 | | "native" | "cmx" | "cmxa" -> true, bytecode
334 | | "byte" | "cmo" | "cma" -> native, true
335 | | _ -> native, bytecode)
336 | (false, false)
337 |
338 | let digest_dependencies (_, members) =
339 | let build_native, build_bytecode = build_native_and_or_bytecode () in
340 |
341 | let module_files = List.map final_path members.modules in
342 | let namespace_files =
343 | members.namespaces
344 | |> List.map (fun (file, _) -> final_path file |> namespace_file) in
345 | let ml_files = module_files @ namespace_files in
346 |
347 | let native_objects =
348 | if build_native then
349 | ml_files |> List.map (fun name -> (Filename.chop_extension name) ^ ".cmx")
350 | else [] in
351 | let bytecode_objects =
352 | if build_bytecode then
353 | ml_files |> List.map (fun name -> (Filename.chop_extension name) ^ ".cmo")
354 | else [] in
355 |
356 | native_objects @ bytecode_objects
357 | |> List.sort_uniq String.compare
358 |
359 | let alias_file_contents (namespace_file, members) =
360 | let buffer = Buffer.create 4096 in
361 | let member_modules = member_module_files members in
362 |
363 | member_modules |> List.iter
364 | (fun member ->
365 | bprintf buffer "module %s =\nstruct\n"
366 | (alias_group_module namespace_file.prefixed_name member.renamed_name);
367 | member_modules |> List.iter
368 | (fun member' ->
369 | if member' <> member then
370 | bprintf buffer " module %s = %s\n"
371 | (module_name_of_pathname member'.renamed_name)
372 | (module_name_of_pathname member'.prefixed_name));
373 | bprintf buffer "end\n\n");
374 |
375 | bprintf buffer "module %s =\nstruct\n"
376 | (alias_export_module namespace_file.prefixed_name);
377 | member_modules |> List.iter
378 | (fun member ->
379 | bprintf buffer " module %s = %s\n"
380 | (module_name_of_pathname member.renamed_name)
381 | (module_name_of_pathname member.prefixed_name));
382 | bprintf buffer "end\n";
383 |
384 | Buffer.contents buffer
385 |
386 | let namespace_file_contents (namespace_file, members) digest =
387 | let buffer = Buffer.create 4096 in
388 | let alias_module = alias_container_module namespace_file.prefixed_name in
389 | let export_module = alias_export_module namespace_file.prefixed_name in
390 | let included_members =
391 | member_module_files members
392 | |> List.fold_left
393 | (fun names member ->
394 | let tags = tags_of_pathname (original_path member) in
395 | if Tags.does_match tags (Tags.of_list [namespace_level_tag]) then
396 | (module_name_of_pathname member.prefixed_name)::names
397 | else names)
398 | [] in
399 |
400 | bprintf buffer "open %s\n" alias_module;
401 | bprintf buffer "include %s\n" export_module;
402 | included_members |> List.iter (bprintf buffer "include %s\n");
403 | bprintf buffer "\nlet _digest_%s = ()\n" digest;
404 |
405 | Buffer.contents buffer
406 |
407 | let library_contents_by_final_base_path path =
408 | try Some (Hashtbl.find !namespace_libraries path)
409 | with Not_found -> None
410 |
411 | let resolve (referrer : file) (referent : string) : string =
412 | let rec loop self = function
413 | | [] -> referent
414 | | (_::rest) as reversed_namespace ->
415 | let namespace_file, members =
416 | List.rev reversed_namespace |> Hashtbl.find namespace_module_map in
417 | let member_modules = member_module_files members in
418 | try
419 | let matching_module =
420 | member_modules |> List.find
421 | (fun {renamed_name; _} ->
422 | referent = (module_name_of_pathname renamed_name))
423 | |> fun {prefixed_name; _} -> module_name_of_pathname prefixed_name in
424 | if matching_module = self then raise_notrace Not_found
425 | else matching_module
426 | with Not_found ->
427 | loop (module_name_of_pathname namespace_file.prefixed_name) rest in
428 |
429 | loop
430 | (module_name_of_pathname referrer.prefixed_name)
431 | (List.rev referrer.namespace)
432 |
433 | let tag_namespace_files () =
434 | let for_namespace base_path (_, members) =
435 | let alias_file_path = alias_container_file base_path in
436 |
437 | tag_file alias_file_path [alias_file_tag; "no_alias_deps"];
438 |
439 | member_module_files members
440 | |> List.iter
441 | (fun {prefixed_name; _} ->
442 | non_dependency
443 | alias_file_path (module_name_of_pathname prefixed_name)) in
444 |
445 | Hashtbl.iter for_namespace namespace_directory_map;
446 |
447 | flag ["ocaml"; "compile"; alias_file_tag] (S [A "-w"; A "-49"])
448 |
449 | let add_open_tags () =
450 | let tag_file final_path file =
451 | let rec list_modules_to_open accumulator enclosing_namespace_path = function
452 | | [] -> failwith "impossible"
453 | | [_] -> List.rev accumulator
454 | | c::c'::rest ->
455 | let enclosing_namespace_path = enclosing_namespace_path @ [c] in
456 | let namespace_file, _ =
457 | Hashtbl.find namespace_module_map enclosing_namespace_path in
458 |
459 | let alias_container =
460 | alias_container_module namespace_file.prefixed_name in
461 | let alias_group =
462 | alias_group_module namespace_file.prefixed_name c' in
463 |
464 | list_modules_to_open
465 | (alias_group::alias_container::accumulator)
466 | enclosing_namespace_path
467 | (c'::rest) in
468 |
469 | let tag =
470 | list_modules_to_open [] [] (module_path file)
471 | |> String.concat ","
472 | |> sprintf "%s(%s)" ordered_open_tag in
473 |
474 | tag_file final_path [tag];
475 | tag_file (final_path ^ ".depends") [tag] in
476 |
477 | Hashtbl.iter tag_file renamed_files;
478 |
479 | let open_tag_to_flags modules_string =
480 | modules_string
481 | |> Str.split (Str.regexp ",")
482 | |> List.map (fun m -> S [A "-open"; A m])
483 | |> fun options -> S options in
484 |
485 | pflag ["ocaml"; "compile"] ordered_open_tag open_tag_to_flags;
486 | pflag ["ocamldep"] ordered_open_tag open_tag_to_flags
487 |
488 | (* Makes each executable target depend on all the libraries in the current
489 | project. *)
490 | let create_library_tags () =
491 | Hashtbl.iter
492 | (fun base_path _ ->
493 | let tag_name = namespace_library_dependency_tag base_path in
494 | ocaml_lib ~tag_name base_path)
495 | !namespace_libraries
496 |
497 | let tag_executable_with_libraries filename =
498 | Hashtbl.iter
499 | (fun base_path _ ->
500 | let tag_name = namespace_library_dependency_tag base_path in
501 | tag_file filename [tag_name])
502 | !namespace_libraries
503 |
504 | (* For each module [Foo] that is not a top-level module, hides the module from
505 | Ocamlbuild by extending [Options.ignore_list]. *)
506 | let hide_original_nested_modules () =
507 | let top_level_names =
508 | member_module_files !tree
509 | |> List.map (fun {renamed_name; _} ->
510 | module_name_of_pathname renamed_name) in
511 |
512 | let hide_if_nested file =
513 | match file.namespace with
514 | | [] -> ()
515 | | _ ->
516 | let renamed_module_name = module_name_of_pathname file.renamed_name in
517 | if not (List.mem renamed_module_name top_level_names) then
518 | Options.ignore_list := renamed_module_name::!Options.ignore_list in
519 |
520 | Hashtbl.iter (fun _ file -> hide_if_nested file) renamed_files;
521 | Hashtbl.iter (fun _ (file, _) -> hide_if_nested file) namespace_directory_map
522 |
523 | let scan generators filter =
524 | let namespace_trees, libraries = scan_tree generators filter in
525 | tree := namespace_trees;
526 | namespace_libraries := libraries;
527 |
528 | index_renamed_files ();
529 | index_namespaces ();
530 | tag_namespace_files ();
531 | add_open_tags ();
532 | hide_original_nested_modules ();
533 | create_library_tags ()
534 |
--------------------------------------------------------------------------------
/src/modules.mli:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | (** Module scanning and index. *)
5 |
6 | type file =
7 | {original_name : string;
8 | (** The original basename of the file or directory, including extension, if
9 | any. For example, [server/foo.ml] has this set to ["foo.ml"]. *)
10 | renamed_name : string;
11 | (** For namespaces tagged with [namespace_with_name(n)], this is [n].
12 | Otherwise, the same as [original_name]. *)
13 | prefixed_name : string;
14 | (** The prefixed name of the file or directory. For example, [server/foo.ml]
15 | has this field set to [server__foo.ml]. *)
16 | directory : string list;
17 | (** The path, relative to the project root, in which this file or directory
18 | was found. For example, [server/foo.ml] has this set to [["server"]]. *)
19 | namespace : string list
20 | (** The module path in which this file's or directory's module is aliased.
21 | For example, [server/foo] is has this set to ["Server"]. *)}
22 |
23 | type namespace
24 |
25 | val scan : Generators.parsed -> (string -> string option) -> unit
26 | (** Scans the source tree for directories to become namespaces and files to
27 | become namespace members, and notes the results as values of type [file] in
28 | a state variable inside this module. *)
29 |
30 | val iter : ([ `Module | `Interface | `Namespace ] * file -> unit) -> unit
31 |
32 | val namespace_level_tag : string
33 |
34 | (** File by its final path. Used for symlink rules and [ocamldep] output
35 | rewriting. *)
36 | val file_by_renamed_path : string -> file option
37 |
38 | val original_path : file -> string
39 |
40 | (** Namespace by its final directory. *)
41 | val namespace_by_final_directory : string -> namespace option
42 |
43 | (** Digest file by namespace final directory. Simply appends [.digest]. *)
44 | val digest_file : string -> string
45 |
46 | (** List of [.cmi] files, one for each member of the given namespace. *)
47 | val digest_dependencies : namespace -> string list
48 |
49 | (** Alias file by namespace final directory. Appends [__aliases_.ml]. *)
50 | val alias_container_file : string -> string
51 |
52 | (** Contents of the alias file. For each module [M] of the namespace ([N]),
53 | creates a module (in the alias file) called [N__aliases__for_M_], which
54 | contains aliases for all the members of [N] besides [M]. These are the
55 | siblings of [M]. The module is opened while compiling [M] to make the
56 | siblings accessible by their short paths.
57 |
58 | Also creates a module in the alias file called [N__aliases__export_], which
59 | contains aliases for all the members of [N]. This module is included in the
60 | namespace file, which makes the members of [N] accessible from modules
61 | outside the namespace. *)
62 | val alias_file_contents : namespace -> string
63 |
64 | (** Contents of the namespace file. This file includes the export module from
65 | the alias file, and any namespace members that are tagged with
66 | [namespace_level]. The string argument gives the digest, which is a dummy
67 | value used to force recompilation of any file depending on the namespace
68 | file when the interfaces inside the namespace change. *)
69 | val namespace_file_contents : namespace -> string -> string
70 |
71 | (** Library module list file by namespace final directory. Appends [.mllib]. *)
72 | val library_list_file : string -> string
73 |
74 | (** Lists the module file names in the given namespace library. *)
75 | val library_contents_by_final_base_path : string -> string list option
76 |
77 | (** Makes the given executable depend on all namespace libraries. *)
78 | val tag_executable_with_libraries : string -> unit
79 |
80 | (** Given a file (the referrer) and the short name of a module it refers to,
81 | tries to resolve the module name to a namespaced module. *)
82 | val resolve : file -> string -> string
83 |
--------------------------------------------------------------------------------
/src/namespaces.ml:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | open Ocamlbuild_plugin
5 |
6 | type generator = Generators.t
7 |
8 | let ocamllex = Generators.ocamllex
9 | let ocamlyacc = Generators.ocamlyacc
10 | let builtin_generators = Generators.builtin
11 |
12 | let plugin_filter = function
13 | | "myocamlbuild.ml" -> None
14 | | "setup.ml" -> None
15 | | s -> Some s
16 |
17 | type file = Modules.file =
18 | {original_name : string;
19 | renamed_name : string;
20 | prefixed_name : string;
21 | directory : string list;
22 | namespace : string list}
23 |
24 | let iter = Modules.iter
25 |
26 | let rec innermost_namespace = function
27 | | [] -> None
28 | | [n] -> Some n
29 | | _::namespaces -> innermost_namespace namespaces
30 |
31 | let include_files_sharing_namespace_title () =
32 | iter
33 | (function
34 | | `Module, file ->
35 | (match innermost_namespace file.namespace with
36 | | Some n when n = (module_name_of_pathname file.original_name) ->
37 | tag_file (Modules.original_path file) [Modules.namespace_level_tag]
38 | | _ -> ())
39 | | _ -> ())
40 |
41 | let handler ?(generators = Generators.builtin) ?(filter = plugin_filter) =
42 | function
43 | | After_rules ->
44 | Modules.scan (Generators.parse generators) filter;
45 | Rules.add_all ();
46 | include_files_sharing_namespace_title ()
47 | | _ -> ()
48 |
49 | let delete_mllib_files () =
50 | let rec traverse path =
51 | path
52 | |> Sys.readdir
53 | |> Array.iter (fun entry ->
54 | let path = Filename.concat path entry in
55 | if Sys.is_directory path then
56 | traverse path
57 | else
58 | if Filename.check_suffix entry ".mllib" then
59 | Sys.remove path)
60 | in
61 | traverse Filename.current_dir_name
62 |
--------------------------------------------------------------------------------
/src/namespaces.mli:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | (** [ocamlbuild] namespaces plugin. This plugin turns directories in the source
5 | tree into namespace modules. For example:
6 |
7 | The directory tree
8 | {v
9 | main.ml
10 | foo/
11 | bar/
12 | some.ml v}
13 |
14 | results in modules [Main], [Foo], [Foo.Bar], and [Foo.Bar.Some], provided
15 | the directories [foo/] and [bar/] are tagged with [namespace]. The easiest
16 | way to do this is to add [<**/*>: namespace] to your [_tags] file.
17 |
18 | To include this plugin in your build process, call [Namespaces.handler] from
19 | your [myocamlbuild.ml] file, and invoke ocamlbuild using
20 | [ocamlbuild -use-ocamlfind -plugin-tag "package(namespaces)" [your_target]].
21 | A minimal [myocamlbuild.ml] file using this plugin looks like this:
22 |
23 | {v
24 | open Ocamlbuild_plugin
25 | let () = dispatch Namespaces.handler v}
26 |
27 | For more information on [ocamlbuild], [_tags], and [myocamlbuild.ml], see
28 | the
29 | {{:http://caml.inria.fr/pub/docs/manual-ocaml/ocamlbuild.html}
30 | ocamlbuild manual}.
31 | *)
32 |
33 | open Ocamlbuild_plugin
34 |
35 |
36 |
37 | (** {1 Generated files} *)
38 |
39 | (** The plugin does not automatically detect generated [.ml] and [.mli] files in
40 | namespaces. If some of your files are generated, you must describe the
41 | generator to the plugin. The syntax is the same as for [ocamlbuild] rule
42 | dependencies and products. For example, the description of [ocamllex] is
43 | ["%.mll", ["%.ml"]]. Note that the outer brackets are part of `ocamldoc`
44 | code style syntax, not OCaml list syntax!
45 | *)
46 | type generator = string * string list
47 |
48 | (** ["%.mll", ["%.ml"]]. *)
49 | val ocamllex : generator
50 |
51 | (** ["%.mly", ["%.ml"]]. *)
52 | val ocamlyacc : generator
53 |
54 | (** The list [[ocamllex; ocamlyacc]]. *)
55 | val builtin_generators : generator list
56 |
57 |
58 |
59 | (** {1 Plugin} *)
60 |
61 | (** Scans the source tree and creates namespaces, as described above. If the
62 | [generators] parameter is not specified, it is equal to
63 | [builtin_generators]. The [filter] parameter allows transformation of the
64 | detected filenames or the omission of the files. *)
65 | val handler :
66 | ?generators:generator list ->
67 | ?filter:(string -> string option) -> hook -> unit
68 |
69 | (** Deletes all [.mllib] files in the source tree. This function is a workaround
70 | for building libraries with OASIS. OASIS generates its own [.mllib] files.
71 | To prevent them from being used, they should be deleted on each build by
72 | calling this function in `myocamlbuild.ml`, e.g.:
73 |
74 | {v
75 | let () = Namespaces.delete_mllib_files () v} *)
76 | val delete_mllib_files : unit -> unit
77 |
78 |
79 |
80 | (** {1 Debugging} *)
81 |
82 | (** Type of a file that has been indexed by the plugin during its scan of the
83 | source tree. [file] can represent a module, interface, or namespace. In the
84 | first two cases, [original_name] and [prefixed_name] end with [.ml] or
85 | [.mli], respectively. If the file is a namespace, [original_name] and
86 | [prefixed_name] are directory names without suffix. *)
87 | type file =
88 | {original_name : string;
89 | (** File or directory name as it appears in the source tree. *)
90 | renamed_name : string;
91 | (** If the file is a namespace tagged with [namespace_with_name(n)], this is
92 | [n]. Otherwise, it is equal to [original_name]. *)
93 | prefixed_name : string;
94 | (** File or directory name after prefixing with its namespace path. *)
95 | directory : string list;
96 | (** List of path components giving the directory containing the file. *)
97 | namespace : string list
98 | (** Module path of the namespace containing the module resulting from the
99 | file. *)}
100 |
101 | (** Calls the given function for each [.ml] file, [.mli] file, and namespace in
102 | the source tree. Must be called in the [After_rules] hook. *)
103 | val iter : ([ `Module | `Interface | `Namespace ] * file -> unit) -> unit
104 |
--------------------------------------------------------------------------------
/src/opam:
--------------------------------------------------------------------------------
1 | opam-version: "1.2"
2 | name: "namespaces"
3 | version: "0.5.1"
4 | maintainer: "Anton Bachin "
5 | authors: "Anton Bachin "
6 | homepage: "https://github.com/aantron/namespaces"
7 | bug-reports: "https://github.com/aantron/namespaces/issues"
8 | dev-repo: "https://github.com/aantron/namespaces.git"
9 | license: "BSD"
10 | build: [make "build"]
11 | install: [make "install"]
12 | remove: ["ocamlfind" "remove" "namespaces"]
13 | depends: [
14 | "ocamlfind" {build}
15 | "ocamlbuild" {build}
16 | ]
17 | available: [
18 | ocaml-version >= "4.02"
19 | ]
20 |
--------------------------------------------------------------------------------
/src/rules.ml:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | open Ocamlbuild_plugin
5 |
6 | let sprintf = Printf.sprintf
7 |
8 | let fail =
9 | let exceptions : (string, exn) Hashtbl.t = Hashtbl.create 512 in
10 | let space = Str.regexp " " in
11 | fun file message ->
12 | let transformed =
13 | sprintf "___%s___%s___" file (Str.global_replace space "_" message) in
14 | fun build ->
15 | try Hashtbl.find exceptions transformed |> raise
16 | with Not_found ->
17 | match build [[transformed]] with
18 | | [Outcome.Bad e] -> Hashtbl.add exceptions transformed e; raise e
19 | | _ ->
20 | failwith "Couldn't capture solver failure exception"
21 |
22 | let make_directory for_file =
23 | Command.execute (Cmd (S [A "mkdir"; A "-p"; A (Filename.dirname for_file)]))
24 |
25 | let get_namespace env build =
26 | match Modules.namespace_by_final_directory (env "%") with
27 | | Some n -> n
28 | | None -> fail (env "%") "not a namespace" build
29 |
30 | let digest_file_rule () =
31 | let name = sprintf "namespace: directory -> %s" (Modules.digest_file "") in
32 | let prod = Modules.digest_file "%" in
33 | rule name ~stamp:prod
34 | begin
35 | fun env build ->
36 | get_namespace env build
37 | |> Modules.digest_dependencies
38 | |> List.map (fun f -> [f])
39 | |> build
40 | |> List.map Outcome.ignore_good |> ignore;
41 | Nop
42 | end
43 |
44 | let alias_file_generation_rule () =
45 | let name =
46 | sprintf "namespace: directory -> %s" (Modules.alias_container_file "") in
47 | let prod = Modules.alias_container_file "%" in
48 | rule name ~prod
49 | begin
50 | fun env build ->
51 | let namespace = get_namespace env build in
52 | make_directory (env prod);
53 | Echo ([Modules.alias_file_contents namespace], env prod)
54 | end
55 |
56 | let namespace_file_generation_rule () =
57 | let prod = "%.ml" in
58 | let dep = "%.digest" in
59 | rule "namespace: directory -> ml" ~dep ~prod
60 | begin
61 | fun env build ->
62 | let namespace = get_namespace env build in
63 | let digest = Pathname.read (env dep) in
64 | make_directory (env prod);
65 | Echo ([Modules.namespace_file_contents namespace digest], env prod)
66 | end
67 |
68 | let long_name_rules () =
69 | let for_extension extension =
70 | let name =
71 | sprintf "namespace: %s -> %s (long name)" extension extension in
72 | let prod = "%." ^ extension in
73 | rule name ~prod
74 | begin
75 | fun env build ->
76 | let file =
77 | match Modules.file_by_renamed_path (env prod) with
78 | | Some f -> f
79 | | None -> fail (env prod) "not a namespaced file" build in
80 |
81 | build [[Modules.original_path file]]
82 | |> List.map Outcome.ignore_good |> ignore;
83 |
84 | (* Transfer tags from the target to the link. *)
85 | Modules.original_path file
86 | |> tags_of_pathname
87 | |> Tags.elements
88 | |> tag_file (env prod);
89 |
90 | ln_s file.Modules.original_name (env prod)
91 | end in
92 |
93 | for_extension "ml";
94 | for_extension "mli"
95 |
96 | let dependency_filter_rules () =
97 | let whitespace = Str.regexp "[ \n\t\r]+" in
98 | let for_extension extension =
99 | let name = sprintf "namespace dependencies %s" extension in
100 | let prod = sprintf "%%.%s.depends" extension in
101 | let dep = sprintf "%%.%s" extension in
102 | rule name ~prod ~dep ~insert:`top
103 | begin
104 | fun env build ->
105 | let file =
106 | match Modules.file_by_renamed_path (env dep) with
107 | | Some f -> f
108 | | None -> fail (env prod) "not a namespaced file" build in
109 |
110 | let tags =
111 | (tags_of_pathname (env prod)) ++ "ocamldep" ++ "pp:dep" in
112 |
113 | let command =
114 | S [A "ocamlfind"; A "ocamldep"; T tags; A "-modules";
115 | A (env dep)] in
116 |
117 | let dependency_string =
118 | Command.string_of_command_spec command |> run_and_read in
119 |
120 | let colon_index = String.index dependency_string ':' in
121 | let remainder =
122 | String.length dependency_string - (colon_index + 1) in
123 | let dependencies =
124 | String.sub dependency_string (colon_index + 1) remainder
125 | |> Str.split whitespace
126 | |> List.filter (fun s -> String.length s > 0) in
127 |
128 | let resolved_dependencies =
129 | dependencies |> List.map (Modules.resolve file) in
130 |
131 | let text =
132 | ((env dep) ^ ":")::resolved_dependencies
133 | |> String.concat " "
134 | |> fun s -> s ^ "\n" in
135 |
136 | Echo ([text], env prod)
137 | end in
138 |
139 | for_extension "ml";
140 | for_extension "mli"
141 |
142 | let dependencies_from_log =
143 | let newline = Str.regexp_string "\n" in
144 | let compiled =
145 | Str.regexp "ocamlfind ocaml\\(c\\|opt\\).* \\([^ ]+\\.ml\\)$" in
146 |
147 | fun () ->
148 | if not @@ Sys.file_exists "_log" then
149 | failwith "_log not present; are you running with -quiet?";
150 |
151 | "cat _log"
152 | |> run_and_read
153 | |> Str.split newline
154 | |> List.fold_left (fun acc line ->
155 | if not @@ Str.string_match compiled line 0 then acc
156 | else (Str.matched_group 2 line)::acc) []
157 | |> List.rev_map module_name_of_pathname
158 |
159 | let library_rule () =
160 | let name = "namespace: * -> mllib" in
161 | let prod = Modules.library_list_file "%" in
162 | rule name ~prod
163 | begin
164 | fun env build ->
165 | let module_files =
166 | match Modules.library_contents_by_final_base_path (env "%") with
167 | | Some s -> s
168 | | None -> fail (env prod) "not a namespace library" build in
169 |
170 | (* Build the library contents to get a topological sort according to the
171 | internal dependency relation. *)
172 | module_files
173 | |> List.map (fun file ->
174 | let file =
175 | if Filename.check_suffix file ".ml" then file
176 | else file ^ ".ml" in
177 | [file ^ ".depends"])
178 | |> build
179 | |> List.map Outcome.ignore_good
180 | |> ignore;
181 |
182 | let order_snapshot = dependencies_from_log () in
183 |
184 | let order_index module_name =
185 | let rec scan n = function
186 | | [] -> -1
187 | | name::rest ->
188 | if module_name = name then n else scan (n + 1) rest in
189 | scan 0 order_snapshot in
190 |
191 | let text =
192 | module_files
193 | |> List.map (fun file ->
194 | let name = module_name_of_pathname file in
195 | (name, order_index name))
196 | |> List.sort (fun (_, index) (_, index') -> compare index index')
197 | |> List.map fst
198 | |> String.concat "\n" in
199 |
200 | make_directory (env prod);
201 | Echo ([text], env prod)
202 | end
203 |
204 | (* A side-effecting rule for each executable, that makes each executable depend
205 | on all the libraries in the same project. It is not possible to use
206 | [Options.targets] for this purpose, because that contains the literal names
207 | of each target, as given on the command line. If the user gives a target such
208 | as [main.byte], when the "real" target is [src/main.byte], Ocamlbuild will
209 | build [src/main.byte], [Options.targets] will contain [main.byte], and
210 | Ocamlbuild will ignore tags of [main.byte]. *)
211 | let executable_rules () =
212 | let for_extension extension =
213 | let name = sprintf "executable dependencies %s" extension in
214 | let prod = sprintf "%%.%s" extension in
215 | rule name ~prod ~insert:`top
216 | begin
217 | fun env build ->
218 | Modules.tag_executable_with_libraries (env prod);
219 | fail (env prod) "__dummy_target__" build
220 | end in
221 |
222 | for_extension "byte";
223 | for_extension "native"
224 |
225 |
226 |
227 | let add_all () =
228 | digest_file_rule ();
229 | alias_file_generation_rule ();
230 | namespace_file_generation_rule ();
231 | long_name_rules ();
232 | dependency_filter_rules ();
233 | library_rule ();
234 | executable_rules ()
235 |
--------------------------------------------------------------------------------
/src/rules.mli:
--------------------------------------------------------------------------------
1 | (* This file is part of Namespaces, distributed under the terms of the 2-clause
2 | BSD license. See https://github.com/aantron/namespaces. *)
3 |
4 | (** [ocamlbuild] rules. *)
5 |
6 | val add_all : unit -> unit
7 |
--------------------------------------------------------------------------------
/src/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aantron/namespaces/6974b5b8b93e4da3d0a9cc7b4d663518da7184eb/src/summary.png
--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | _*/
2 |
--------------------------------------------------------------------------------
/test/Makefile:
--------------------------------------------------------------------------------
1 | OCAMLBUILD := ocamlbuild -use-ocamlfind -no-links
2 |
3 | .PHONY : unit
4 | unit : rebuild
5 | $(OCAMLBUILD) test_main.native -- -runner sequential
6 |
7 | .PHONY : one
8 | one : rebuild
9 | $(OCAMLBUILD) test_main.native -- -runner sequential -only-test $(NAME)
10 |
11 | .PHONY : clean
12 | clean :
13 | ocamlbuild -clean
14 | rm -rf _plugin_findlib _project _project_findlib _depending
15 |
16 | .PHONY : rebuild
17 | rebuild :
18 | make -C ..
19 |
--------------------------------------------------------------------------------
/test/_tags:
--------------------------------------------------------------------------------
1 | <**/*>: package(oUnit)
2 | "test_helpers.ml": package(process), package(str)
3 | <*.native> or <*.byte>: package(process), package(str)
4 | <_*>: -traverse
5 |
--------------------------------------------------------------------------------
/test/test_basic.ml:
--------------------------------------------------------------------------------
1 | open OUnit2
2 | open Test_helpers
3 |
4 | let basic_build_system () =
5 | myocamlbuild_ml ();
6 | tags [": namespace"]
7 |
8 | let build_and_test ?(target = "main") expect =
9 | basic_build_system ();
10 |
11 | let byte_target = target ^ ".byte" in
12 | let native_target = target ^ ".native" in
13 | ocamlbuild [byte_target; native_target];
14 |
15 | let expect = Printf.sprintf "%i\n" expect in
16 | let byte_product =
17 | Filename.concat Filename.current_dir_name (Filename.basename byte_target) in
18 | let native_product =
19 | Filename.concat
20 | Filename.current_dir_name (Filename.basename native_target) in
21 |
22 | assert_equal (run byte_product []) expect;
23 | assert_equal (run native_product []) expect
24 |
25 | let tests = "basic" >::: [
26 | test "namespacing" begin fun () ->
27 | project begin fun () ->
28 | file "src/namespace/foo.ml" ["let v = 1"];
29 | file "src/main.ml" ["Namespace.Foo.v |> string_of_int |> print_endline"];
30 |
31 | build_and_test ~target:"src/main" 1
32 | end
33 | end;
34 |
35 | test "inferred_target" begin fun () ->
36 | project begin fun () ->
37 | file "src/namespace/foo.ml" ["let v = 1"];
38 | file "src/main.ml" ["Namespace.Foo.v |> string_of_int |> print_endline"];
39 |
40 | build_and_test 1
41 | end
42 | end;
43 |
44 | test "sibling_not_linked" begin fun () ->
45 | project begin fun () ->
46 | file "src/namespace/foo.ml" ["let v = 1"];
47 | file "src/namespace/bar.ml" ["let () = exit 1"];
48 | file "src/main.ml" ["Namespace.Foo.v |> string_of_int |> print_endline"];
49 |
50 | build_and_test 1
51 | end;
52 |
53 | project begin fun () ->
54 | file "src/namespace/foo.ml" ["let v = 1"];
55 | file "src/namespace/bar.ml" ["let v = 2"];
56 | file "src/main.ml"
57 | ["(Namespace.Foo.v + Namespace.Bar.v)";
58 | "|> string_of_int |> print_endline"];
59 |
60 | build_and_test 3
61 | end
62 | end;
63 |
64 | test "nested_namespace" begin fun () ->
65 | project begin fun () ->
66 | file "src/namespace/a/foo.ml" ["let v = 1"];
67 | file "src/main.ml"
68 | ["Namespace.A.Foo.v |> string_of_int |> print_endline"];
69 |
70 | build_and_test 1
71 | end
72 | end;
73 |
74 | test "submodules_hidden" begin fun () ->
75 | project begin fun () ->
76 | file "src/namespace/foo.ml" ["let v = 1"];
77 | file "src/main.ml" ["Foo.v |> string_of_int |> print_endline"];
78 |
79 | basic_build_system ();
80 |
81 | ocamlbuild ~fails_with:"Unbound module Foo" ["main.native"]
82 | end
83 | end;
84 |
85 | test "sibling_reference" begin fun () ->
86 | project begin fun () ->
87 | file "src/namespace/foo.ml" ["let v = 1"];
88 | file "src/namespace/bar.ml" ["let v = Foo.v + 1"];
89 | file "src/main.ml" ["Namespace.Bar.v |> string_of_int |> print_endline"];
90 |
91 | build_and_test 2
92 | end
93 | end;
94 |
95 | test "cousin_reference" begin fun () ->
96 | project begin fun () ->
97 | file "src/namespace_1/foo.ml" ["let v = 1"];
98 | file "src/namespace_2/foo.ml" ["let v = Namespace_1.Foo.v + 1"];
99 | file "src/main.ml"
100 | ["Namespace_2.Foo.v |> string_of_int |> print_endline"];
101 |
102 | build_and_test 2
103 | end
104 | end;
105 |
106 | test "cousin_reference_reversed" begin fun () ->
107 | project begin fun () ->
108 | file "src/namespace_1/foo.ml" ["let v = Namespace_2.Foo.v + 1"];
109 | file "src/namespace_2/foo.ml" ["let v = 1"];
110 | file "src/main.ml"
111 | ["Namespace_1.Foo.v |> string_of_int |> print_endline"];
112 |
113 | build_and_test 2
114 | end
115 | end
116 | ]
117 |
--------------------------------------------------------------------------------
/test/test_helpers.ml:
--------------------------------------------------------------------------------
1 | open OUnit2
2 |
3 | let _plugin_install_directory = "_plugin_findlib"
4 | let _plugin_install_directory_absolute =
5 | Filename.concat (Sys.getcwd ()) _plugin_install_directory
6 |
7 | let _project_install_directory = "_project_findlib"
8 | let _project_install_directory_absolute =
9 | Filename.concat (Sys.getcwd ()) _project_install_directory
10 |
11 | let _project_directory = "_project"
12 | let _depending_directory = "_depending"
13 |
14 | let _package_name = "test-library"
15 |
16 | let run command arguments =
17 | let open Process in
18 | let result = run command (Array.of_list arguments) in
19 | if result.Output.exit_status <> Exit.Exit 0 then
20 | let report =
21 | Printf.sprintf
22 | "Command failed (%s):\n%s %s\n\nSTDERR:\n\n%s\n\nSTDOUT:\n\n%s"
23 | (Exit.to_string result.Output.exit_status)
24 | command (String.concat " " arguments)
25 | (String.concat "\n" result.Output.stderr)
26 | (String.concat "\n" result.Output.stdout)
27 | in
28 | assert_failure report
29 | else
30 | String.concat "\n" result.Output.stdout
31 |
32 | let _run command arguments = run command arguments |> ignore
33 |
34 | let _fresh_dir name =
35 | _run "rm" ["-rf"; name];
36 | _run "mkdir" ["-p"; name]
37 |
38 | let install_plugin () =
39 | _fresh_dir _plugin_install_directory;
40 |
41 | Unix.putenv "OCAMLFIND_DESTDIR" _plugin_install_directory_absolute;
42 | _run "make" ["-C"; ".."; "install"];
43 | Unix.putenv "OCAMLFIND_DESTDIR" _project_install_directory_absolute;
44 |
45 | Printf.sprintf "%s:%s"
46 | _plugin_install_directory_absolute _project_install_directory_absolute
47 | |> Unix.putenv "OCAMLPATH"
48 |
49 | let _project directory f =
50 | _fresh_dir directory;
51 |
52 | let cwd = Sys.getcwd () in
53 | let project_directory = Filename.concat cwd directory in
54 | Sys.chdir project_directory;
55 |
56 | try
57 | f ();
58 | Sys.chdir cwd
59 |
60 | with e ->
61 | Sys.chdir cwd;
62 | raise e
63 |
64 | let project = _project _project_directory
65 |
66 | let depending = _project _depending_directory
67 |
68 | let file name lines =
69 | _run "mkdir" ["-p"; (Filename.dirname name)];
70 |
71 | let channel = open_out name in
72 | try
73 | lines |> List.iter (fun s ->
74 | output_string channel s; output_char channel '\n');
75 | close_out_noerr channel
76 |
77 | with e ->
78 | close_out_noerr channel;
79 | raise e
80 |
81 | let myocamlbuild_ml () =
82 | file "myocamlbuild.ml"
83 | ["let () = Ocamlbuild_plugin.dispatch Namespaces.handler"]
84 |
85 | let tags lines =
86 | file "_tags" ("<**/*>: include"::lines)
87 |
88 | let oasis_myocamlbuild_ml () =
89 | file "myocamlbuild.ml"
90 | ["(* OASIS_START *)";
91 | "(* OASIS_STOP *)";
92 | "";
93 | "let () =";
94 | " dispatch";
95 | " (MyOCamlbuildBase.dispatch_combine";
96 | " [MyOCamlbuildBase.dispatch_default conf package_default;";
97 | " Namespaces.handler])"]
98 |
99 | let oasis_tags lines =
100 | file "_tags"
101 | (["# OASIS_START";
102 | "# OASIS_STOP";
103 | "";
104 | "<**/*>: include"] @ lines)
105 |
106 | let oasis_file lines =
107 | file "_oasis"
108 | (["OASISFormat: 0.4";
109 | "Name: namespaces-oasis-test";
110 | "Version: 0.1";
111 | "Synopsis: OASIS test";
112 | "Authors: Anton Bachin";
113 | "License: BSD-2-clause";
114 | "OCamlVersion: >= 4.02";
115 | "AlphaFeatures: ocamlbuild_more_args";
116 | "XOCamlbuildPluginTags: package(namespaces)";
117 | ""] @ lines)
118 |
119 | let ocamlbuild ?fails_with targets =
120 | let arguments =
121 | ["-use-ocamlfind"; "-plugin-tag"; "package(namespaces)"; "-cflags";
122 | "-bin-annot"] @ targets
123 | in
124 |
125 | match fails_with with
126 | | None -> _run "ocamlbuild" arguments
127 |
128 | | Some message ->
129 | let open Process in
130 | let result = run "ocamlbuild" (Array.of_list arguments) in
131 | if result.Output.exit_status = Exit.Exit 0 then
132 | let report =
133 | Printf.sprintf "Command did not fail:\n%s %s"
134 | "ocamlbuild" (String.concat " " arguments)
135 | in
136 | assert_failure report
137 | else
138 | let stdout = String.concat "\n" result.Output.stdout in
139 | let regexp = Str.regexp_string message in
140 | try Str.search_forward regexp stdout 0 |> ignore
141 | with Not_found ->
142 | let report = Printf.sprintf
143 | "Command did not fail with expected message:\n%s %s\n\nSTDOUT:\n\n%s"
144 | "ocamlbuild" (String.concat " " arguments)
145 | stdout
146 | in
147 | assert_failure report
148 |
149 | let oasis () =
150 | _run "oasis" ["setup"];
151 | _run "ocaml" ["setup.ml"; "-configure"];
152 | _run "ocaml" ["setup.ml"; "-build"]
153 |
154 | let test name f = name >:: fun context -> f ()
155 |
156 | let install_project =
157 | let whitespace = Str.regexp "[ \t\r\n]+" in
158 |
159 | fun () ->
160 | _fresh_dir _project_install_directory_absolute;
161 |
162 | let find_files extension =
163 | run "find" ["_build"; "-name"; "*." ^ extension]
164 | |> Str.split whitespace
165 | in
166 |
167 | let files =
168 | ["cma"; "cmxa"; "a"; "cmi"; "cmt"; "cmti"]
169 | |> List.map find_files
170 | |> List.flatten
171 | |> List.filter ((<>) "_build/myocamlbuild.cmi")
172 | in
173 |
174 | _run "ocamlfind" (["install"; _package_name; "META"] @ files)
175 |
--------------------------------------------------------------------------------
/test/test_helpers.mli:
--------------------------------------------------------------------------------
1 | val install_plugin : unit -> unit
2 |
3 | val project : (unit -> unit) -> unit
4 | val depending : (unit -> unit) -> unit
5 |
6 | val file : string -> string list -> unit
7 |
8 | val myocamlbuild_ml : unit -> unit
9 | val tags : string list -> unit
10 |
11 | val oasis_myocamlbuild_ml : unit -> unit
12 | val oasis_tags : string list -> unit
13 | val oasis_file : string list -> unit
14 |
15 | val run : string -> string list -> string
16 | val ocamlbuild : ?fails_with:string -> string list -> unit
17 | val oasis : unit -> unit
18 |
19 | val install_project : unit -> unit
20 |
21 | val test : string -> (unit -> unit) -> OUnit2.test
22 |
--------------------------------------------------------------------------------
/test/test_library.ml:
--------------------------------------------------------------------------------
1 | open OUnit2
2 | open Test_helpers
3 |
4 | let prepare_library () =
5 | project begin fun () ->
6 | file "src/foo.ml" ["let v = 1"];
7 | file "src/optional/bar.ml" ["let v = Foo.v + 1"];
8 | file "src/optional_bar.ml" ["include Optional.Bar"];
9 |
10 | file "src/baz.ml" ["exit 1"];
11 | file "src/optional/quux.ml" ["exit 1"];
12 |
13 | tags
14 | [": namespace";
15 | "\"src\": namespace_with_name(Test_library), namespace_lib(library)";
16 | "\"src/optional\": namespace_lib(optional)";
17 | "\"src/optional_bar.ml\": namespace_lib(optional)"];
18 | myocamlbuild_ml ();
19 |
20 | ocamlbuild
21 | ["library.cma"; "library.cmxa"; "optional.cma"; "optional.cmxa"];
22 |
23 | file "META"
24 | ["archive(byte) = \"library.cma\"";
25 | "archive(native) = \"library.cmxa\"";
26 | "";
27 | "package \"optional\" (";
28 | " requires = \"test-library\"";
29 | " archive(byte) = \"optional.cma\"";
30 | " archive(native) = \"optional.cmxa\"";
31 | ")"];
32 |
33 | install_project ()
34 | end
35 |
36 | let depend ?(optional = false) code expect =
37 | depending begin fun () ->
38 | let package =
39 | if optional then "test-library.optional" else "test-library" in
40 |
41 | file "src/main.ml" code;
42 | tags [": package(" ^ package ^ ")"];
43 | ocamlbuild ["main.byte"; "main.native"];
44 |
45 | let expect = Printf.sprintf "%i\n" expect in
46 | assert_equal (run "./main.byte" []) expect;
47 | assert_equal (run "./main.native" []) expect
48 | end
49 |
50 | let tests = "library" >::: [
51 | test "basic" begin fun () ->
52 | prepare_library ();
53 | depend ["Test_library.Foo.v |> string_of_int |> print_endline"] 1
54 | end;
55 |
56 | test "optional" begin fun () ->
57 | prepare_library ();
58 | depend ~optional:true
59 | ["Test_library.Optional.Bar.v |> string_of_int |> print_endline"] 2
60 | end;
61 |
62 | test "optional_legacy" begin fun () ->
63 | prepare_library ();
64 | depend ~optional:true
65 | ["Test_library.Optional_bar.v |> string_of_int |> print_endline"] 2
66 | end
67 | ]
68 |
--------------------------------------------------------------------------------
/test/test_main.ml:
--------------------------------------------------------------------------------
1 | open OUnit2
2 |
3 | let tests = "namespaces" >::: [
4 | Test_basic.tests;
5 | Test_oasis.tests;
6 | Test_library.tests
7 | ]
8 |
9 | let () =
10 | Test_helpers.install_plugin ();
11 | run_test_tt_main tests
12 |
--------------------------------------------------------------------------------
/test/test_oasis.ml:
--------------------------------------------------------------------------------
1 | open OUnit2
2 | open Test_helpers
3 |
4 | let tests = "oasis" >::: [
5 | test "binary" begin fun () ->
6 | project begin fun () ->
7 | file "src/namespace/foo.ml" ["let v = 1"];
8 | file "src/main.ml" ["Namespace.Foo.v |> string_of_int |> print_endline"];
9 |
10 | oasis_myocamlbuild_ml ();
11 | oasis_tags [": namespace"];
12 |
13 | oasis_file
14 | ["Executable main";
15 | " Path: src";
16 | " BuildTools: ocamlbuild";
17 | " MainIs: main.ml"];
18 |
19 | oasis ();
20 |
21 | assert_equal (run "./main.byte" []) "1\n"
22 | end
23 | end
24 | ]
25 |
--------------------------------------------------------------------------------