├── examples ├── foo │ ├── .gitignore │ ├── src │ │ └── main.ml │ └── Alice.toml ├── hello │ ├── .gitignore │ ├── src │ │ ├── foo.ml │ │ ├── bar.ml │ │ └── main.ml │ └── Alice.toml ├── depend_on_hello │ ├── .gitignore │ ├── src │ │ └── main.ml │ └── Alice.toml ├── hello_library │ ├── src │ │ ├── foo.ml │ │ ├── foo.mli │ │ └── lib.ml │ └── Alice.toml └── hello_project │ ├── src │ ├── foo.mli │ ├── foo.ml │ ├── main.ml │ └── lib.ml │ └── Alice.toml ├── ui ├── README.md └── src │ ├── dune │ ├── alice_ui.mli │ └── alice_ui.ml ├── .dockerignore ├── .ocamlformat ├── dune.lock ├── base-unix.base.pkg ├── ISO8601.0.2.6.pkg ├── sexplib0.v0.17.0.pkg ├── menhirCST.20250912.pkg ├── menhirLib.20250912.pkg ├── menhirSdk.20250912.pkg ├── ppxlib_jane.v0.17.4.pkg ├── ocaml-compiler-libs.v0.17.0.pkg ├── ppx_optcomp.v0.17.1.pkg ├── ppx_derivers.1.2.1.pkg ├── menhir.20250912.pkg ├── ocaml_intrinsics_kernel.v0.17.1.pkg ├── ppx_globalize.v0.17.2.pkg ├── stdio.v0.17.0.pkg ├── base.v0.17.3.pkg ├── ppx_inline_test.v0.17.1.pkg ├── ppx_sexp_conv.v0.17.1.pkg ├── ppx_expect.v0.17.3.pkg ├── ppx_cold.v0.17.0.pkg ├── ppx_here.v0.17.0.pkg ├── stdlib-shims.0.3.0.pkg ├── jane-street-headers.v0.17.0.pkg ├── ppx_compare.v0.17.0.pkg ├── re.1.14.0.pkg ├── ppx_enumerate.v0.17.0.pkg ├── jst-config.v0.17.0.pkg ├── ppx_hash.v0.17.0.pkg ├── ppx_assert.v0.17.0.pkg ├── time_now.v0.17.0.pkg ├── ocaml-system.5.3.1+relocatable.pkg ├── climate.0.9.0.pkg ├── pp.2.0.0.pkg ├── csexp.1.5.2.pkg ├── toml.7.1.0.pkg ├── ppx_base.v0.17.0.pkg ├── ppxlib.0.37.0.pkg ├── xdg.3.20.2.pkg ├── ordering.3.20.2.pkg ├── dyn.3.20.2.pkg ├── dune-configurator.3.20.2.pkg ├── ocaml.5.3.1+relocatable.pkg ├── ocaml-config.3.pkg ├── sha.1.15.4.pkg ├── fileutils.0.6.6.pkg └── lock.dune ├── print ├── src │ ├── alice_print.ml │ ├── dune │ ├── raw.mli │ └── raw.ml └── README.md ├── integration_tests ├── import-module-pattern.t │ ├── .gitignore │ ├── src │ │ ├── lib.ml │ │ ├── import.ml │ │ ├── foo.mli │ │ └── foo.ml │ ├── Alice.toml │ └── run.t ├── compiler-specific │ ├── multi-package.t │ │ ├── a │ │ │ ├── .gitignore │ │ │ ├── Alice.toml │ │ │ └── src │ │ │ │ └── lib.ml │ │ ├── app │ │ │ ├── .gitignore │ │ │ ├── Alice.toml │ │ │ └── src │ │ │ │ └── main.ml │ │ ├── b │ │ │ ├── .gitignore │ │ │ ├── Alice.toml │ │ │ └── src │ │ │ │ └── lib.ml │ │ ├── c │ │ │ ├── .gitignore │ │ │ ├── Alice.toml │ │ │ └── src │ │ │ │ └── lib.ml │ │ ├── d │ │ │ ├── .gitignore │ │ │ ├── src │ │ │ │ ├── foo.mli │ │ │ │ ├── lib.ml │ │ │ │ └── foo.ml │ │ │ └── Alice.toml │ │ └── run.t │ └── dune ├── dune ├── lib-interface-file.t │ ├── lib_with_interface │ │ ├── src │ │ │ ├── lib.ml │ │ │ └── lib.mli │ │ └── Alice.toml │ ├── client │ │ ├── src │ │ │ └── main.ml │ │ └── Alice.toml │ └── run.t ├── new-lib.t ├── run-argv.t └── new.t ├── dag ├── README.md └── src │ ├── dune │ └── alice_dag.mli ├── alice ├── src │ ├── build.mli │ ├── clean.mli │ ├── dot.mli │ ├── new.mli │ ├── run.mli │ ├── tools.mli │ ├── dune │ ├── internal.mli │ ├── clean.ml │ ├── build.ml │ ├── common.mli │ ├── remote_tarballs.mli │ ├── run.ml │ ├── target.mli │ ├── dot.ml │ ├── alice.ml │ └── new.ml └── README.md ├── engine ├── README.md └── src │ ├── alice_engine.ml │ ├── public_interface_to_open.mli │ ├── profile.mli │ ├── dune │ ├── dot_merlin.mli │ ├── module_name.mli │ ├── scheduler.mli │ ├── profile.ml │ ├── project.mli │ ├── ocamldep_cache.mli │ ├── build_graph.mli │ ├── module_name.ml │ ├── build_dir.mli │ ├── dot_merlin.ml │ ├── build_dir.ml │ └── ocamldep_cache.ml ├── env ├── README.md └── src │ ├── dune │ ├── alice_env.mli │ └── alice_env.ml ├── error ├── README.md └── src │ ├── dune │ ├── alice_error.mli │ └── alice_error.ml ├── io ├── README.md └── src │ ├── uname.mli │ ├── infer_linux_distro.mli │ ├── extract.mli │ ├── dune │ ├── alice_io.ml │ ├── fetch.mli │ ├── read_hierarchy.mli │ ├── temp_dir.mli │ ├── extract.ml │ ├── uname.ml │ ├── infer_linux_distro.ml │ ├── process.mli │ ├── fetch.ml │ ├── temp_dir.ml │ ├── file_ops.mli │ ├── read_hierarchy.ml │ └── process.ml ├── package ├── README.md └── src │ ├── alice_package.ml │ ├── dune │ ├── dependency_graph.mli │ ├── package.mli │ └── package.ml ├── log ├── README.md └── src │ ├── dune │ ├── alice_log.mli │ └── alice_log.ml ├── graphviz ├── README.md └── src │ ├── dune │ ├── alice_graphviz.mli │ └── alice_graphviz.ml ├── ocaml_compiler ├── src │ ├── alice_ocaml_compiler.ml │ ├── dune │ └── ocaml_compiler.mli └── README.md ├── which ├── README.md └── src │ ├── alice_which.mli │ ├── dune │ └── alice_which.ml ├── manifest ├── README.md └── src │ ├── dune │ ├── dependencies.mli │ ├── package_id.mli │ ├── package.mli │ ├── alice_manifest.mli │ ├── dependency.mli │ ├── fields.mli │ ├── alice_manifest.ml │ ├── dependencies.ml │ ├── package.ml │ ├── package_id.ml │ ├── dependency.ml │ └── fields.ml ├── installation ├── README.md └── src │ ├── dune │ ├── alice_installation.mli │ └── alice_installation.ml ├── stdlib ├── src │ ├── compare.ml │ ├── dune │ ├── type_bool.mli │ ├── seq.ml │ ├── option.ml │ ├── list.mli │ ├── hashtbl.mli │ ├── seq.mli │ ├── string.mli │ ├── set.mli │ ├── option.mli │ ├── command.mli │ ├── compare.mli │ ├── env.mli │ ├── list.ml │ ├── set.ml │ ├── hashtbl.ml │ ├── nonempty_list.mli │ ├── map.mli │ ├── alice_stdlib.ml │ ├── string.ml │ ├── command.ml │ ├── result.mli │ ├── result.ml │ ├── type_bool.ml │ ├── ansi_style.mli │ ├── nonempty_list.ml │ ├── map.ml │ ├── filename.mli │ ├── env.ml │ └── ansi_style.ml ├── REAMDE.md └── tests │ ├── dune │ └── filename.ml ├── hierarchy ├── src │ └── dune └── README.md ├── .gitignore ├── package_meta ├── src │ ├── dune │ ├── dependency_source.mli │ ├── dependency.mli │ ├── alice_package_meta.ml │ ├── dependencies.mli │ ├── dependency_source.ml │ ├── package_name.mli │ ├── package_id.mli │ ├── dependency.ml │ ├── dependencies.ml │ ├── package_meta.mli │ ├── semantic_version.mli │ ├── package_meta.ml │ ├── package_id.ml │ └── package_name.ml ├── README.md └── tests │ ├── dune │ └── semantic_version.ml ├── scripts ├── alice ├── generate_minified_bash_completion_script.sh ├── build-alice-x86_64-linux-musl-static.dockerfile ├── build-alice-aarch64-linux-musl-static.dockerfile └── build-alice-aarch64-linux-gnu.dockerfile ├── alice.opam.template ├── flake.lock ├── shell.nix ├── alice_tests.opam ├── flake.nix ├── dune-project ├── dune-workspace ├── LICENSE ├── alice.opam ├── CHANGES.md ├── assets ├── packages.svg └── artifacts.svg ├── packaging └── replace-compiler-version-with-template-in-lockdir.patch └── default.nix /examples/foo/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # alice_ui 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | _build 2 | out 3 | -------------------------------------------------------------------------------- /examples/hello/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | profile=janestreet 2 | -------------------------------------------------------------------------------- /examples/depend_on_hello/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /dune.lock/base-unix.base.pkg: -------------------------------------------------------------------------------- 1 | (version base) 2 | -------------------------------------------------------------------------------- /examples/hello/src/foo.ml: -------------------------------------------------------------------------------- 1 | let foo = "foo" 2 | -------------------------------------------------------------------------------- /print/src/alice_print.ml: -------------------------------------------------------------------------------- 1 | module Raw = Raw 2 | -------------------------------------------------------------------------------- /examples/hello_library/src/foo.ml: -------------------------------------------------------------------------------- 1 | let x = "foo" 2 | -------------------------------------------------------------------------------- /examples/hello_library/src/foo.mli: -------------------------------------------------------------------------------- 1 | val x : string 2 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /examples/hello_project/src/foo.mli: -------------------------------------------------------------------------------- 1 | val message : string 2 | -------------------------------------------------------------------------------- /dag/README.md: -------------------------------------------------------------------------------- 1 | # Dag 2 | 3 | A generic directed acyclic graph. 4 | -------------------------------------------------------------------------------- /examples/foo/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = print_endline "Hello, World!" 2 | -------------------------------------------------------------------------------- /examples/hello/src/bar.ml: -------------------------------------------------------------------------------- 1 | let bar = String.cat "bar" Foo.foo 2 | -------------------------------------------------------------------------------- /examples/hello_library/src/lib.ml: -------------------------------------------------------------------------------- 1 | let text = "Hello, World!" 2 | -------------------------------------------------------------------------------- /examples/hello_project/src/foo.ml: -------------------------------------------------------------------------------- 1 | let message = "Hello, World!" 2 | -------------------------------------------------------------------------------- /alice/src/build.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /alice/src/clean.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /alice/src/dot.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /alice/src/new.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /alice/src/run.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /alice/src/tools.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | -------------------------------------------------------------------------------- /engine/README.md: -------------------------------------------------------------------------------- 1 | # Build Plan 2 | 3 | How to build alice packages. 4 | -------------------------------------------------------------------------------- /env/README.md: -------------------------------------------------------------------------------- 1 | # Env 2 | 3 | Utility for manipulating environments. 4 | -------------------------------------------------------------------------------- /error/README.md: -------------------------------------------------------------------------------- 1 | # Error 2 | 3 | Errors for the alice build system. 4 | -------------------------------------------------------------------------------- /examples/hello_project/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = print_endline Foo.message 2 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/a/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/app/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/b/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/c/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/d/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /io/README.md: -------------------------------------------------------------------------------- 1 | # IO 2 | 3 | IO operations for the alice build system. 4 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | A package which can be built. 4 | -------------------------------------------------------------------------------- /examples/foo/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "foo" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /examples/hello_project/src/lib.ml: -------------------------------------------------------------------------------- 1 | module Foo = Foo 2 | 3 | let x = "xa" 4 | -------------------------------------------------------------------------------- /log/README.md: -------------------------------------------------------------------------------- 1 | # Log 2 | 3 | Logging library for the alice build system. 4 | -------------------------------------------------------------------------------- /examples/depend_on_hello/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = print_endline Hello.Foo.message 2 | -------------------------------------------------------------------------------- /examples/hello/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/src/lib.ml: -------------------------------------------------------------------------------- 1 | let message = Foo.message 2 | -------------------------------------------------------------------------------- /print/README.md: -------------------------------------------------------------------------------- 1 | # Print 2 | 3 | Printing utilities for the alice build system. 4 | -------------------------------------------------------------------------------- /examples/hello_library/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /examples/hello_project/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /graphviz/README.md: -------------------------------------------------------------------------------- 1 | # Graphviz 2 | 3 | Helpers for generating graphviz source files. 4 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/src/import.ml: -------------------------------------------------------------------------------- 1 | let sprintf = Printf.sprintf 2 | -------------------------------------------------------------------------------- /ocaml_compiler/src/alice_ocaml_compiler.ml: -------------------------------------------------------------------------------- 1 | module Ocaml_compiler = Ocaml_compiler 2 | -------------------------------------------------------------------------------- /which/README.md: -------------------------------------------------------------------------------- 1 | # Which 2 | 3 | Library for looking up the location of executables. 4 | -------------------------------------------------------------------------------- /alice/README.md: -------------------------------------------------------------------------------- 1 | # alice 2 | 3 | The CLI executable for running the alice build system. 4 | -------------------------------------------------------------------------------- /examples/hello/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = print_endline (Printf.sprintf "Hello, %s!" Bar.bar) 2 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/d/src/foo.mli: -------------------------------------------------------------------------------- 1 | val message : string 2 | -------------------------------------------------------------------------------- /integration_tests/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps %{bin:alice}) 3 | (applies_to :whole_subtree)) 4 | -------------------------------------------------------------------------------- /io/src/uname.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | val uname : Env.t -> [ `M | `S ] -> string 4 | -------------------------------------------------------------------------------- /manifest/README.md: -------------------------------------------------------------------------------- 1 | # Manifest 2 | 3 | Manifest parsing logic for the alice build system. 4 | -------------------------------------------------------------------------------- /installation/README.md: -------------------------------------------------------------------------------- 1 | # Install Dir 2 | 3 | Logic for handling the alice install directory. 4 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/d/src/lib.ml: -------------------------------------------------------------------------------- 1 | module Foo_exported = Foo 2 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/src/foo.mli: -------------------------------------------------------------------------------- 1 | open! Import 2 | 3 | val message : string 4 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/lib_with_interface/src/lib.ml: -------------------------------------------------------------------------------- 1 | let add lhs rhs = lhs + rhs 2 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/lib_with_interface/src/lib.mli: -------------------------------------------------------------------------------- 1 | val add : int -> int -> int 2 | -------------------------------------------------------------------------------- /ocaml_compiler/README.md: -------------------------------------------------------------------------------- 1 | # OCaml Compiler 2 | 3 | Library for interacting with the OCaml compiler. 4 | -------------------------------------------------------------------------------- /print/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_print) 4 | (libraries alice_stdlib)) 5 | -------------------------------------------------------------------------------- /stdlib/src/compare.ml: -------------------------------------------------------------------------------- 1 | let ( let= ) i f = 2 | match i with 3 | | 0 -> f () 4 | | _ -> i 5 | ;; 6 | -------------------------------------------------------------------------------- /dag/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_dag) 4 | (libraries alice_stdlib alice_error)) 5 | -------------------------------------------------------------------------------- /graphviz/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_graphviz) 4 | (libraries alice_stdlib)) 5 | -------------------------------------------------------------------------------- /engine/src/alice_engine.ml: -------------------------------------------------------------------------------- 1 | module Scheduler = Scheduler 2 | module Project = Project 3 | module Profile = Profile 4 | -------------------------------------------------------------------------------- /error/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_error) 4 | (libraries alice_stdlib alice_print)) 5 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/c/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "c" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /stdlib/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_stdlib) 4 | (libraries dyn fileutils pp re xdg)) 5 | -------------------------------------------------------------------------------- /hierarchy/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_hierarchy) 4 | (libraries alice_stdlib alice_error)) 5 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (enabled_if 3 | (= %{ocaml-config:version} "5.3.1+relocatable"))) 4 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "import_module_pattern" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /env/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_env) 4 | (libraries alice_stdlib alice_hierarchy alice_error)) 5 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/src/foo.ml: -------------------------------------------------------------------------------- 1 | open! Import 2 | 3 | let message = sprintf "%s %s" "hello" "world" 4 | -------------------------------------------------------------------------------- /io/src/infer_linux_distro.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | val current_distro_requires_statically_linked_tools : unit -> bool 4 | -------------------------------------------------------------------------------- /log/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_log) 4 | (libraries alice_stdlib alice_print alice_package_meta)) 5 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/c/src/lib.ml: -------------------------------------------------------------------------------- 1 | module Message = struct 2 | let message = "Hello, World!" 3 | end 4 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/lib_with_interface/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lib_with_interface" 3 | version = "0.1.0" 4 | -------------------------------------------------------------------------------- /package/src/alice_package.ml: -------------------------------------------------------------------------------- 1 | include Alice_package_meta 2 | module Package = Package 3 | module Dependency_graph = Dependency_graph 4 | -------------------------------------------------------------------------------- /stdlib/REAMDE.md: -------------------------------------------------------------------------------- 1 | # Stdlib 2 | 3 | A wrapper of the OCaml `stdlib` module that adds some ergonomics and additional 4 | functionality. 5 | -------------------------------------------------------------------------------- /ui/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_ui) 4 | (libraries alice_stdlib alice_hierarchy alice_print alice_env)) 5 | -------------------------------------------------------------------------------- /installation/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_installation) 4 | (libraries alice_stdlib alice_hierarchy alice_env)) 5 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/d/src/foo.ml: -------------------------------------------------------------------------------- 1 | let message = String.concat " " [ C.Message.message; B.another_message ] 2 | -------------------------------------------------------------------------------- /which/src/alice_which.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | val ocamlopt : Alice_env.Os_type.t -> Env.t -> Alice_ocaml_compiler.Ocaml_compiler.t 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | _build 3 | dev-tools.locks 4 | *.tar.gz 5 | build 6 | *.out 7 | *.cmx 8 | *.cmi 9 | *.o 10 | out 11 | result 12 | -------------------------------------------------------------------------------- /package_meta/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_package_meta) 4 | (libraries alice_stdlib alice_error alice_hierarchy)) 5 | -------------------------------------------------------------------------------- /package_meta/README.md: -------------------------------------------------------------------------------- 1 | # Package Meta 2 | 3 | Data structures representing information about package, agnostic of how 4 | packages are parsed or built. 5 | -------------------------------------------------------------------------------- /scripts/alice: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Shell script to run alice via `dune exec` 3 | DUNE_CONFIG__PKG_BUILD_PROGRESS=disabled 4 | dune exec --display=quiet alice -- $@ 5 | -------------------------------------------------------------------------------- /examples/depend_on_hello/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "depend_on_hello" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | hello = { path = "../hello_project" } 7 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/a/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "a" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | b = { path = "../b" } 7 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/b/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "b" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | c = { path = "../c" } 7 | -------------------------------------------------------------------------------- /graphviz/src/alice_graphviz.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type string_graph = String.Set.t String.Map.t 4 | 5 | val dot_src_of_string_graph : string_graph -> string 6 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/client/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | let x = Lib_with_interface.add 40 2 in 3 | print_endline (Printf.sprintf "Hello, %d!" x) 4 | ;; 5 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/d/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | b = { path = "../b" } 7 | c = { path = "../c" } 8 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/app/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | a = { path = "../a" } 7 | d = { path = "../d" } 8 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/client/Alice.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | lib_with_interface = { path = "../lib_with_interface" } 7 | -------------------------------------------------------------------------------- /stdlib/src/type_bool.mli: -------------------------------------------------------------------------------- 1 | (** Type-level booleans *) 2 | 3 | type true_t = private True_t 4 | type false_t = private False_t 5 | 6 | type _ t = 7 | | True : true_t t 8 | | False : false_t t 9 | -------------------------------------------------------------------------------- /stdlib/tests/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name alice_stdlib_unit_tests) 3 | (package alice_tests) 4 | (inline_tests) 5 | (preprocess 6 | (pps ppx_inline_test ppx_expect)) 7 | (libraries alice_stdlib)) 8 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/b/src/lib.ml: -------------------------------------------------------------------------------- 1 | module C = C 2 | module Another_alias_of_c = C 3 | 4 | let message_from_c = C.Message.message 5 | let another_message = "blah blah blah" 6 | -------------------------------------------------------------------------------- /io/src/extract.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val extract 5 | : Env.t 6 | -> tarball_file:Absolute_path.non_root_t 7 | -> output_dir:Absolute_path.non_root_t 8 | -> unit 9 | -------------------------------------------------------------------------------- /ocaml_compiler/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_ocaml_compiler) 4 | (libraries 5 | alice_stdlib 6 | alice_error 7 | alice_hierarchy 8 | alice_io 9 | alice_ui)) 10 | -------------------------------------------------------------------------------- /engine/src/public_interface_to_open.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t 4 | 5 | val of_package_with_deps : (_, _) Alice_package.Dependency_graph.Package_with_deps.t -> t 6 | val source_code : t -> string 7 | -------------------------------------------------------------------------------- /io/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_io) 4 | (libraries 5 | alice_stdlib 6 | alice_error 7 | alice_hierarchy 8 | alice_log 9 | alice_env 10 | alice_ui 11 | unix)) 12 | -------------------------------------------------------------------------------- /manifest/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_manifest) 4 | (libraries 5 | alice_stdlib 6 | alice_error 7 | alice_hierarchy 8 | alice_package_meta 9 | toml 10 | alice_io)) 11 | -------------------------------------------------------------------------------- /stdlib/src/seq.ml: -------------------------------------------------------------------------------- 1 | open! Stdlib 2 | include Seq 3 | 4 | let map t ~f = map f t 5 | let filter_map t ~f = filter_map f t 6 | let flat_map t ~f = flat_map f t 7 | let filter t ~f = filter f t 8 | let iter t ~f = iter f t 9 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/a/src/lib.ml: -------------------------------------------------------------------------------- 1 | module C = B.C 2 | module C_again = B.Another_alias_of_c 3 | 4 | module B_plus = struct 5 | include B 6 | 7 | let yet_another_message = "foo bar baz" 8 | end 9 | -------------------------------------------------------------------------------- /package_meta/tests/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name alice_package_meta_unit_tests) 3 | (package alice_tests) 4 | (inline_tests) 5 | (preprocess 6 | (pps ppx_inline_test ppx_expect)) 7 | (libraries alice_stdlib alice_package_meta)) 8 | -------------------------------------------------------------------------------- /stdlib/src/option.ml: -------------------------------------------------------------------------------- 1 | open! Stdlib 2 | include Option 3 | 4 | let map t ~f = map f t 5 | let bind t ~f = bind t f 6 | let iter t ~f = iter f t 7 | let equal a b ~eq = Option.equal eq a b 8 | let compare ~cmp a b = Option.compare cmp a b 9 | -------------------------------------------------------------------------------- /which/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_which) 4 | (libraries 5 | alice_stdlib 6 | alice_hierarchy 7 | alice_io 8 | alice_log 9 | alice_installation 10 | alice_env 11 | alice_ocaml_compiler)) 12 | -------------------------------------------------------------------------------- /package/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_package) 4 | (libraries 5 | alice_stdlib 6 | alice_error 7 | alice_hierarchy 8 | alice_package_meta 9 | alice_manifest 10 | alice_dag 11 | alice_graphviz)) 12 | -------------------------------------------------------------------------------- /stdlib/src/list.mli: -------------------------------------------------------------------------------- 1 | include module type of Stdlib.ListLabels 2 | 3 | val filter_opt : 'a option t -> 'a t 4 | val last : 'a t -> 'a option 5 | val split_last : 'a t -> ('a t * 'a) option 6 | val all : bool t -> bool 7 | val any : bool t -> bool 8 | -------------------------------------------------------------------------------- /engine/src/profile.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_ocaml_compiler 3 | 4 | type t 5 | 6 | val ocaml_compiler_command : t -> Ocaml_compiler.t -> args:string list -> Command.t 7 | val debug : t 8 | val release : t 9 | val name : t -> string 10 | -------------------------------------------------------------------------------- /package_meta/src/dependency_source.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | type t = 5 | | Local_directory of Either_path.t (** The dependency is in a directory at this path *) 6 | 7 | val equal : t -> t -> bool 8 | val to_dyn : t -> Dyn.t 9 | -------------------------------------------------------------------------------- /stdlib/src/hashtbl.mli: -------------------------------------------------------------------------------- 1 | module type S = sig 2 | include MoreLabels.Hashtbl.S 3 | 4 | val find_or_add : 'a t -> key -> f:(key -> 'a) -> 'a 5 | end 6 | 7 | module Make (Key : sig 8 | include MoreLabels.Hashtbl.HashedType 9 | end) : S with type key = Key.t 10 | -------------------------------------------------------------------------------- /engine/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (package alice) 3 | (name alice_engine) 4 | (libraries 5 | alice_stdlib 6 | alice_error 7 | alice_hierarchy 8 | alice_ui 9 | alice_dag 10 | alice_package 11 | alice_which 12 | alice_ocaml_compiler 13 | alice_graphviz)) 14 | -------------------------------------------------------------------------------- /io/src/alice_io.ml: -------------------------------------------------------------------------------- 1 | module Fetch = Fetch 2 | module File_ops = File_ops 3 | module Extract = Extract 4 | module Infer_linux_distro = Infer_linux_distro 5 | module Process = Process 6 | module Read_hierarchy = Read_hierarchy 7 | module Temp_dir = Temp_dir 8 | module Uname = Uname 9 | -------------------------------------------------------------------------------- /package_meta/src/dependency.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t 4 | 5 | val create : name:Package_name.t -> source:Dependency_source.t -> t 6 | val equal : t -> t -> bool 7 | val to_dyn : t -> Dyn.t 8 | val name : t -> Package_name.t 9 | val source : t -> Dependency_source.t 10 | -------------------------------------------------------------------------------- /manifest/src/dependencies.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | include module type of Alice_package_meta.Dependencies 3 | 4 | val of_toml 5 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 6 | -> Toml.Types.table 7 | -> t 8 | 9 | val to_toml : t -> Toml.Types.table 10 | -------------------------------------------------------------------------------- /manifest/src/package_id.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package_meta 3 | include module type of Package_id 4 | 5 | val of_toml 6 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 7 | -> Toml.Types.table 8 | -> t 9 | 10 | val to_toml : t -> Toml.Types.table 11 | -------------------------------------------------------------------------------- /stdlib/src/seq.mli: -------------------------------------------------------------------------------- 1 | include module type of Stdlib.Seq 2 | 3 | val map : 'a t -> f:('a -> 'b) -> 'b t 4 | val filter_map : 'a t -> f:('a -> 'b option) -> 'b t 5 | val flat_map : 'a t -> f:('a -> 'b t) -> 'b t 6 | val filter : 'a t -> f:('a -> bool) -> 'a t 7 | val iter : 'a t -> f:('a -> unit) -> unit 8 | -------------------------------------------------------------------------------- /stdlib/src/string.mli: -------------------------------------------------------------------------------- 1 | include module type of StdLabels.String 2 | module Set : Set.S with type elt = string 3 | module Map : Map.S with type key = string 4 | 5 | val is_empty : t -> bool 6 | val lsplit2 : t -> on:char -> (t * t) option 7 | val split_on_char_nonempty : t -> sep:char -> t Nonempty_list.t 8 | -------------------------------------------------------------------------------- /package_meta/src/alice_package_meta.ml: -------------------------------------------------------------------------------- 1 | module Dependencies = Dependencies 2 | module Dependency = Dependency 3 | module Dependency_source = Dependency_source 4 | module Package_meta = Package_meta 5 | module Package_id = Package_id 6 | module Package_name = Package_name 7 | module Semantic_version = Semantic_version 8 | -------------------------------------------------------------------------------- /stdlib/src/set.mli: -------------------------------------------------------------------------------- 1 | include module type of MoreLabels.Set 2 | 3 | module type S = sig 4 | include S 5 | 6 | val to_dyn : t -> Dyn.t 7 | end 8 | 9 | module type Ord = sig 10 | include OrderedType 11 | 12 | val to_dyn : t -> Dyn.t 13 | end 14 | 15 | module Make (Ord : Ord) : S with type elt = Ord.t 16 | -------------------------------------------------------------------------------- /stdlib/src/option.mli: -------------------------------------------------------------------------------- 1 | include module type of Stdlib.Option 2 | 3 | val map : 'a t -> f:('a -> 'b) -> 'b t 4 | val bind : 'a t -> f:('a -> 'b t) -> 'b t 5 | val iter : 'a t -> f:('a -> unit) -> unit 6 | val equal : 'a t -> 'a t -> eq:('a -> 'a -> bool) -> bool 7 | val compare : cmp:('a -> 'a -> int) -> 'a t -> 'a t -> int 8 | -------------------------------------------------------------------------------- /integration_tests/import-module-pattern.t/run.t: -------------------------------------------------------------------------------- 1 | Test that alice can handle the pattern of having an Import module with no mli 2 | file which is opened in all other files (mli or ml). 3 | 4 | $ alice build 5 | Compiling import_module_pattern v0.1.0 6 | Finished debug build of package: 'import_module_pattern v0.1.0' 7 | -------------------------------------------------------------------------------- /io/src/fetch.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val curl : Env.t -> url:string -> output_file:Absolute_path.non_root_t -> Command.t 5 | val wget : Env.t -> url:string -> output_file:Absolute_path.non_root_t -> Command.t 6 | val fetch : Env.t -> url:string -> output_file:Absolute_path.non_root_t -> unit 7 | -------------------------------------------------------------------------------- /manifest/src/package.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | include module type of Alice_package_meta.Package_meta 3 | 4 | val to_dyn : t -> Dyn.t 5 | 6 | val of_toml 7 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 8 | -> Toml.Types.table 9 | -> t 10 | 11 | val to_toml : t -> Toml.Types.table 12 | -------------------------------------------------------------------------------- /package_meta/src/dependencies.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t 4 | 5 | val empty : t 6 | val equal : t -> t -> bool 7 | val to_dyn : t -> Dyn.t 8 | val names : t -> Package_name.t list 9 | val to_list : t -> Dependency.t list 10 | val of_list : Dependency.t list -> (t, [ `Duplicate_name of Package_name.t ]) result 11 | -------------------------------------------------------------------------------- /integration_tests/lib-interface-file.t/run.t: -------------------------------------------------------------------------------- 1 | Test that packages can have a lib.mli file. 2 | 3 | $ alice run --normalize-paths --manifest-path client/Alice.toml 4 | Compiling lib_with_interface v0.1.0 5 | Compiling client v0.1.0 6 | Running client/build/packages/client-0.1.0/debug/executable/client 7 | 8 | Hello, 42! 9 | -------------------------------------------------------------------------------- /integration_tests/new-lib.t: -------------------------------------------------------------------------------- 1 | Exercise initializing a new library project. 2 | 3 | $ alice new --lib hello --normalize-paths 4 | Creating new library package "hello" in hello 5 | $ cd hello 6 | 7 | $ alice build --normalize-paths 8 | Compiling hello v0.1.0 9 | Finished debug build of package: 'hello v0.1.0' 10 | -------------------------------------------------------------------------------- /io/src/read_hierarchy.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | open Alice_hierarchy 4 | 5 | val read : Absolute_path.non_root_t -> (File_non_root.t, [ `Not_found ]) result 6 | val read_dir : Absolute_path.non_root_t -> (File_non_root.dir, User_error.t) result 7 | val read_dir_exn : Absolute_path.non_root_t -> File_non_root.dir 8 | -------------------------------------------------------------------------------- /stdlib/src/command.mli: -------------------------------------------------------------------------------- 1 | type t = 2 | { prog : string 3 | ; args : string list 4 | ; env : Env.t 5 | } 6 | 7 | val create : string -> args:string list -> Env.t -> t 8 | val equal : t -> t -> bool 9 | val to_dyn : t -> Dyn.t 10 | val to_string_ignore_env : t -> string 11 | val to_string_ignore_env_backticks : t -> string 12 | -------------------------------------------------------------------------------- /io/src/temp_dir.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | (** Make a new directory in the system's temporary directory calling [f] on its 5 | path, returning the result of [f] and deleting the temporary directory 6 | after [f] returns. *) 7 | val with_ : prefix:string -> suffix:string -> f:(Absolute_path.non_root_t -> 'a) -> 'a 8 | -------------------------------------------------------------------------------- /manifest/src/alice_manifest.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val manifest_name : Basename.t 5 | val read_package_dir : dir_path:_ Absolute_path.t -> Package.t 6 | val read_package_manifest : manifest_path:Absolute_path.non_root_t -> Package.t 7 | val write_package_manifest : manifest_path:Absolute_path.non_root_t -> Package.t -> unit 8 | -------------------------------------------------------------------------------- /manifest/src/dependency.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package_meta 3 | include module type of Dependency 4 | 5 | val of_toml 6 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 7 | -> name:Package_name.t 8 | -> Toml.Types.value 9 | -> t 10 | 11 | val to_toml : t -> Toml.Types.Table.Key.t * Toml.Types.value 12 | -------------------------------------------------------------------------------- /engine/src/dot_merlin.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package.Dependency_graph 4 | 5 | val basename : Basename.t 6 | val dot_merlin_text_initial : unit -> string 7 | 8 | val dot_merlin_text 9 | : (_, _) Package_with_deps.t 10 | -> Build_dir.t 11 | -> Profile.t 12 | -> ocamllib_path:Absolute_path.non_root_t 13 | -> string 14 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/app/src/main.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | print_endline A.C.Message.message; 3 | print_endline A.C_again.Message.message; 4 | print_endline A.B_plus.Another_alias_of_c.Message.message; 5 | print_endline A.B_plus.another_message; 6 | print_endline A.B_plus.yet_another_message; 7 | print_endline D.Foo_exported.message 8 | ;; 9 | -------------------------------------------------------------------------------- /dune.lock/ISO8601.0.2.6.pkg: -------------------------------------------------------------------------------- 1 | (version 0.2.6) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (dune base-unix ocaml))) 9 | 10 | (source 11 | (fetch 12 | (url https://github.com/ocaml-community/ISO8601.ml/archive/0.2.6.tar.gz) 13 | (checksum md5=a460f01d409d51b7d537429881bfa276))) 14 | -------------------------------------------------------------------------------- /alice/src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (public_name alice) 3 | (package alice) 4 | (libraries 5 | alice_engine 6 | alice_error 7 | alice_print 8 | alice_io 9 | alice_package_meta 10 | alice_hierarchy 11 | alice_log 12 | alice_installation 13 | alice_env 14 | climate 15 | sha)) 16 | 17 | (env 18 | (static 19 | (link_flags 20 | (:standard -cclib -static)))) 21 | -------------------------------------------------------------------------------- /dune.lock/sexplib0.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/sexplib0/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum md5=abafe8fd1d6302e55a315f4d78960d2a))) 15 | -------------------------------------------------------------------------------- /hierarchy/README.md: -------------------------------------------------------------------------------- 1 | # Hierarchy 2 | 3 | Representation of a directory hierarchy. To avoid the need to perform IO while 4 | generating the build plan, alice will scan the source directory into a data 5 | structure which will be used in lieu of a real file system. Note that this 6 | package doesn't provide a way of scanning the source directory. This is taken 7 | care of by the `alice_io` package. 8 | -------------------------------------------------------------------------------- /package_meta/src/dependency_source.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | type t = Local_directory of Either_path.t 5 | 6 | let equal a b = 7 | match a, b with 8 | | Local_directory a, Local_directory b -> Either_path.equal a b 9 | ;; 10 | 11 | let to_dyn = function 12 | | Local_directory path -> Dyn.variant "Local_directory" [ Either_path.to_dyn path ] 13 | ;; 14 | -------------------------------------------------------------------------------- /dune.lock/menhirCST.20250912.pkg: -------------------------------------------------------------------------------- 1 | (version 20250912) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://gitlab.inria.fr/fpottier/menhir/-/archive/20250912/archive.tar.gz) 14 | (checksum md5=b8f83df02226419f99e49f1b637dcb11))) 15 | -------------------------------------------------------------------------------- /dune.lock/menhirLib.20250912.pkg: -------------------------------------------------------------------------------- 1 | (version 20250912) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://gitlab.inria.fr/fpottier/menhir/-/archive/20250912/archive.tar.gz) 14 | (checksum md5=b8f83df02226419f99e49f1b637dcb11))) 15 | -------------------------------------------------------------------------------- /dune.lock/menhirSdk.20250912.pkg: -------------------------------------------------------------------------------- 1 | (version 20250912) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://gitlab.inria.fr/fpottier/menhir/-/archive/20250912/archive.tar.gz) 14 | (checksum md5=b8f83df02226419f99e49f1b637dcb11))) 15 | -------------------------------------------------------------------------------- /dune.lock/ppxlib_jane.v0.17.4.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.4) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppxlib_jane/archive/refs/tags/v0.17.4.tar.gz) 14 | (checksum md5=d572c6d6c3b4da9e480c65ba85d3c50a))) 15 | -------------------------------------------------------------------------------- /alice.opam.template: -------------------------------------------------------------------------------- 1 | build: [ 2 | ["dune" "subst"] {dev} 3 | [ 4 | "dune" 5 | "build" 6 | "-p" 7 | name 8 | "-j" 9 | jobs 10 | "@install" 11 | # Tests depend on the compile error format, so only run tests for a single 12 | # compiler version. 13 | "@runtest" {with-test & ocaml:version >= "5.3" & ocaml:version < "5.4"} 14 | "@doc" {with-doc} 15 | ] 16 | ] 17 | -------------------------------------------------------------------------------- /dune.lock/ocaml-compiler-libs.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ocaml-compiler-libs/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum md5=aaf66efea8752475c25a942443579b41))) 15 | -------------------------------------------------------------------------------- /dune.lock/ppx_optcomp.v0.17.1.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.1) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base stdio dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_optcomp/archive/refs/tags/v0.17.1.tar.gz) 14 | (checksum md5=0bf43393409414c655c4473d79480fdf))) 15 | -------------------------------------------------------------------------------- /stdlib/src/compare.mli: -------------------------------------------------------------------------------- 1 | (** Convenient way to build comparisons for 2 | records/tuples by chaining together multiple 3 | comparisons. 4 | 5 | For example: 6 | 7 | let compare t { foo; bar } = 8 | let open Compare in 9 | let= () = Foo.compare t.foo foo in 10 | let= () = Bar.compare t.bar bar in 11 | 0 12 | *) 13 | val ( let= ) : int -> (unit -> int) -> int 14 | -------------------------------------------------------------------------------- /dune.lock/ppx_derivers.1.2.1.pkg: -------------------------------------------------------------------------------- 1 | (version 1.2.1) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url https://github.com/ocaml-ppx/ppx_derivers/archive/1.2.1.tar.gz) 13 | (checksum 14 | sha256=b6595ee187dea792b31fc54a0e1524ab1e48bc6068d3066c45215a138cc73b95))) 15 | -------------------------------------------------------------------------------- /stdlib/src/env.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | (** Array of "=" pairs *) 4 | type raw = string array 5 | 6 | val empty : t 7 | val equal : t -> t -> bool 8 | val to_dyn : t -> Dyn.t 9 | val of_raw : raw -> t 10 | val to_raw : t -> raw 11 | val get_opt : t -> name:string -> string option 12 | val find_name_opt : t -> f:(string -> bool) -> string option 13 | val set : t -> name:string -> value:string -> t 14 | -------------------------------------------------------------------------------- /dune.lock/menhir.20250912.pkg: -------------------------------------------------------------------------------- 1 | (version 20250912) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune menhirLib menhirSdk menhirCST))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://gitlab.inria.fr/fpottier/menhir/-/archive/20250912/archive.tar.gz) 14 | (checksum md5=b8f83df02226419f99e49f1b637dcb11))) 15 | -------------------------------------------------------------------------------- /dune.lock/ocaml_intrinsics_kernel.v0.17.1.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.1) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ocaml_intrinsics_kernel/archive/refs/tags/v0.17.1.tar.gz) 14 | (checksum md5=56ed7d0b0331e5bcfa4e016515c0267d))) 15 | -------------------------------------------------------------------------------- /dune.lock/ppx_globalize.v0.17.2.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.2) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppxlib_jane dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_globalize/archive/refs/tags/v0.17.2.tar.gz) 14 | (checksum md5=9df1288f1113c1daffd13cfced63a77e))) 15 | -------------------------------------------------------------------------------- /dune.lock/stdio.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base dune))) 9 | 10 | (source 11 | (fetch 12 | (url https://github.com/janestreet/stdio/archive/refs/tags/v0.17.0.tar.gz) 13 | (checksum 14 | sha256=e7cb473d4bffcf419f307c658cf2599fab03a2b4fe655bfd0be699f8f7af176e))) 15 | -------------------------------------------------------------------------------- /stdlib/src/list.ml: -------------------------------------------------------------------------------- 1 | include Stdlib.ListLabels 2 | 3 | let filter_opt t = filter_map ~f:Fun.id t 4 | 5 | let rec last = function 6 | | [] -> None 7 | | [ x ] -> Some x 8 | | _ :: xs -> last xs 9 | ;; 10 | 11 | let rec split_last = function 12 | | [] -> None 13 | | [ x ] -> Some ([], x) 14 | | _ :: xs -> split_last xs 15 | ;; 16 | 17 | let all t = for_all t ~f:Fun.id 18 | let any t = exists t ~f:Fun.id 19 | -------------------------------------------------------------------------------- /dune.lock/base.v0.17.3.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.3) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml ocaml_intrinsics_kernel sexplib0 dune dune-configurator))) 9 | 10 | (source 11 | (fetch 12 | (url https://github.com/janestreet/base/archive/refs/tags/v0.17.3.tar.gz) 13 | (checksum md5=2100b0ed13fecf43be86ed45c5b2cc4d))) 14 | -------------------------------------------------------------------------------- /dune.lock/ppx_inline_test.v0.17.1.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.1) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base time_now dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_inline_test/archive/refs/tags/v0.17.1.tar.gz) 14 | (checksum md5=38196081de2fab8321b71addbe769b73))) 15 | -------------------------------------------------------------------------------- /dune.lock/ppx_sexp_conv.v0.17.1.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.1) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppxlib_jane sexplib0 dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_sexp_conv/archive/refs/tags/v0.17.1.tar.gz) 14 | (checksum md5=acbe8a2727a29c8f2fa8da42046f5861))) 15 | -------------------------------------------------------------------------------- /alice/src/internal.mli: -------------------------------------------------------------------------------- 1 | val subcommand : unit Climate.Command.subcommand 2 | 3 | (** Ref cell for the Alice command. This is a ref cell to break a circular 4 | dependency, since the "internal" subcommand is part of the command object, 5 | but the command object is required to generate the completion script which 6 | is part of the "internal" subcommand. *) 7 | val command_for_completion_script : unit Climate.Command.t option ref 8 | -------------------------------------------------------------------------------- /dune.lock/ppx_expect.v0.17.3.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.3) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppx_here ppx_inline_test stdio dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_expect/archive/refs/tags/v0.17.3.tar.gz) 14 | (checksum md5=a7daa59114638fd80f52b6adbb0db7ed))) 15 | -------------------------------------------------------------------------------- /package_meta/src/package_name.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | 4 | type t 5 | 6 | module Set : Set.S with type elt = t 7 | module Map : Map.S with type key = t 8 | 9 | val to_dyn : t -> Dyn.t 10 | val equal : t -> t -> bool 11 | val compare : t -> t -> int 12 | val of_string_res : string -> t user_result 13 | 14 | (** Raises a user error *) 15 | val of_string_exn : string -> t 16 | 17 | val to_string : t -> string 18 | -------------------------------------------------------------------------------- /print/src/raw.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | val pp_print : Ansi_style.t Pp.t -> unit 4 | val pp_println : Ansi_style.t Pp.t -> unit 5 | val pps_print : Ansi_style.t Pp.t list -> unit 6 | val pps_println : Ansi_style.t Pp.t list -> unit 7 | val pp_eprint : Ansi_style.t Pp.t -> unit 8 | val pp_eprintln : Ansi_style.t Pp.t -> unit 9 | val pps_eprint : Ansi_style.t Pp.t list -> unit 10 | val pps_eprintln : Ansi_style.t Pp.t list -> unit 11 | -------------------------------------------------------------------------------- /dune.lock/ppx_cold.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_cold/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=670ee6f4efef2020a4bedf91b72cc2cd97ea0d74b47dad2f8f6b72d722a7452d))) 16 | -------------------------------------------------------------------------------- /dune.lock/ppx_here.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_here/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=27ac69db34a5ff0efbf6e3c52d52dda46d1e5d5db4d14fb4d8c20370b932a913))) 16 | -------------------------------------------------------------------------------- /dune.lock/stdlib-shims.0.3.0.pkg: -------------------------------------------------------------------------------- 1 | (version 0.3.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (dune ocaml))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/ocaml/stdlib-shims/releases/download/0.3.0/stdlib-shims-0.3.0.tbz) 14 | (checksum 15 | sha256=babf72d3917b86f707885f0c5528e36c63fccb698f4b46cf2bab5c7ccdd6d84a))) 16 | -------------------------------------------------------------------------------- /alice/src/clean.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_engine 3 | open Climate 4 | 5 | let clean = 6 | let open Arg_parser in 7 | let+ () = Common.set_globals_from_flags 8 | and+ project = Common.parse_project in 9 | Project.clean project 10 | ;; 11 | 12 | let subcommand = 13 | let open Command in 14 | subcommand 15 | "clean" 16 | ~aliases:[ "c" ] 17 | (singleton ~doc:"Delete all generated build artifacts." clean) 18 | ;; 19 | -------------------------------------------------------------------------------- /dune.lock/jane-street-headers.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/jane-street-headers/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=78fa6084cd067b7a7d930d1fe1cb7eb9dcd1a90c73017e570213b47a3762eb4f))) 16 | -------------------------------------------------------------------------------- /dune.lock/ppx_compare.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppxlib_jane dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_compare/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=f0b23eb78082ef4dc71a66939bbc63c6b0cc2cf6a4744a906b7a2c016cbe3098))) 16 | -------------------------------------------------------------------------------- /dune.lock/re.1.14.0.pkg: -------------------------------------------------------------------------------- 1 | (version 1.14.0) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 9 | 10 | (depends 11 | (all_platforms 12 | (dune ocaml))) 13 | 14 | (source 15 | (fetch 16 | (url https://github.com/ocaml/ocaml-re/archive/refs/tags/1.14.0.tar.gz) 17 | (checksum md5=03f4a83100cb9229a796b85c698076e1))) 18 | -------------------------------------------------------------------------------- /dune.lock/ppx_enumerate.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppxlib_jane dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_enumerate/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=a27f1797b1315bdf7678fde783dff493bd348f1c5b644d7616b660bd295dad36))) 16 | -------------------------------------------------------------------------------- /dune.lock/jst-config.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppx_assert dune dune-configurator))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/jst-config/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=2cf345e33bed0ee4c325667e77dfc5bee8f12afd56318b7c9acf81ec875ecf6e))) 16 | -------------------------------------------------------------------------------- /package_meta/src/package_id.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t = 4 | { name : Package_name.t 5 | ; version : Semantic_version.t 6 | } 7 | 8 | module Set : Set.S with type elt = t 9 | module Map : Map.S with type key = t 10 | 11 | val to_dyn : t -> Dyn.t 12 | val equal : t -> t -> bool 13 | val name : t -> Package_name.t 14 | val version : t -> Semantic_version.t 15 | val name_dash_version_string : t -> string 16 | val name_v_version_string : t -> string 17 | -------------------------------------------------------------------------------- /dune.lock/ppx_hash.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppx_compare ppx_sexp_conv ppxlib_jane dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_hash/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=8c8acae276a349d412eab9112cc3afa996d26ad4a01f2882121fc0adee0dd05e))) 16 | -------------------------------------------------------------------------------- /dune.lock/ppx_assert.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base ppx_cold ppx_compare ppx_here ppx_sexp_conv dune ppxlib))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/ppx_assert/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=94c47289a6393642b1cca7d2cdb8decdbf387c3cee4faf50d9b00efc871cce8b))) 16 | -------------------------------------------------------------------------------- /dune.lock/time_now.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml base jane-street-headers jst-config ppx_base ppx_optcomp dune))) 9 | 10 | (source 11 | (fetch 12 | (url 13 | https://github.com/janestreet/time_now/archive/refs/tags/v0.17.0.tar.gz) 14 | (checksum 15 | sha256=fc85d6e46c4eb9370de9385f7bbfa6d57b4e48a9e96b20009007226b73f9530c))) 16 | -------------------------------------------------------------------------------- /stdlib/src/set.ml: -------------------------------------------------------------------------------- 1 | include MoreLabels.Set 2 | 3 | module type S = sig 4 | include S 5 | 6 | val to_dyn : t -> Dyn.t 7 | end 8 | 9 | module type Ord = sig 10 | include OrderedType 11 | 12 | val to_dyn : t -> Dyn.t 13 | end 14 | 15 | module Make (Ord : Ord) = struct 16 | include MoreLabels.Set.Make (struct 17 | type t = Ord.t 18 | 19 | let compare = Ord.compare 20 | end) 21 | 22 | let to_dyn t = Dyn.Set (to_list t |> List.map ~f:Ord.to_dyn) 23 | end 24 | -------------------------------------------------------------------------------- /dune.lock/ocaml-system.5.3.1+relocatable.pkg: -------------------------------------------------------------------------------- 1 | (version 5.3.1+relocatable) 2 | 3 | (build 4 | (all_platforms ((action (run ocaml gen_ocaml_config.ml))))) 5 | 6 | (extra_sources 7 | (gen_ocaml_config.ml 8 | (fetch 9 | (url 10 | https://raw.githubusercontent.com/alicecaml/alice-opam-repo/refs/heads/main/packages/ocaml-system/ocaml-system.5.3.1%2Brelocatable/gen_ocaml_config.ml) 11 | (checksum 12 | sha256=8b09a982824d7f2407867c24a7609eafc3eb57a19bcc5c66330a629f0d68e468)))) 13 | -------------------------------------------------------------------------------- /scripts/generate_minified_bash_completion_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DUNE_CONFIG__PKG_BUILD_PROGRESS=disabled 3 | dune exec --display=quiet alice -- internal completions bash \ 4 | --program-name=alice \ 5 | --program-exe-for-reentrant-query=alice \ 6 | --global-symbol-prefix=__alice \ 7 | --no-command-hash-in-function-names \ 8 | --no-comments \ 9 | --no-whitespace \ 10 | --minify-global-names \ 11 | --minify-local-variables \ 12 | --optimize-case-statements 13 | -------------------------------------------------------------------------------- /dune.lock/climate.0.9.0.pkg: -------------------------------------------------------------------------------- 1 | (version 0.9.0) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 9 | 10 | (depends 11 | (all_platforms 12 | (dune ocaml))) 13 | 14 | (source 15 | (fetch 16 | (url https://github.com/gridbugs/climate/archive/refs/tags/0.9.0.tar.gz) 17 | (checksum 18 | sha256=b291ef5c30eb11d81571fd3146bd404ab91698bd1606bf41c79cd0a5bfccd80c))) 19 | -------------------------------------------------------------------------------- /dune.lock/pp.2.0.0.pkg: -------------------------------------------------------------------------------- 1 | (version 2.0.0) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 9 | 10 | (depends 11 | (all_platforms 12 | (dune ocaml))) 13 | 14 | (source 15 | (fetch 16 | (url https://github.com/ocaml-dune/pp/releases/download/2.0.0/pp-2.0.0.tbz) 17 | (checksum 18 | sha256=8651351518b092b4a2def4e08171c276152f92fb6a84a8b19b6b929ccdb44419))) 19 | -------------------------------------------------------------------------------- /stdlib/src/hashtbl.ml: -------------------------------------------------------------------------------- 1 | module type S = sig 2 | include MoreLabels.Hashtbl.S 3 | 4 | val find_or_add : 'a t -> key -> f:(key -> 'a) -> 'a 5 | end 6 | 7 | module Make (H : sig 8 | include MoreLabels.Hashtbl.HashedType 9 | end) = 10 | struct 11 | include MoreLabels.Hashtbl.Make (H) 12 | 13 | let find_or_add t key ~f = 14 | match find_opt t key with 15 | | Some x -> x 16 | | None -> 17 | let x = f key in 18 | replace t ~key ~data:x; 19 | x 20 | ;; 21 | end 22 | -------------------------------------------------------------------------------- /dune.lock/csexp.1.5.2.pkg: -------------------------------------------------------------------------------- 1 | (version 1.5.2) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 9 | 10 | (depends 11 | (all_platforms 12 | (dune ocaml))) 13 | 14 | (source 15 | (fetch 16 | (url 17 | https://github.com/ocaml-dune/csexp/releases/download/1.5.2/csexp-1.5.2.tbz) 18 | (checksum 19 | sha256=1a14dd04bb4379a41990248550628c77913a9c07f3c35c1370b6960e697787ff))) 20 | -------------------------------------------------------------------------------- /graphviz/src/alice_graphviz.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type string_graph = String.Set.t String.Map.t 4 | 5 | let dot_src_of_string_graph string_graph = 6 | let lines = 7 | String.Map.to_list string_graph 8 | |> List.map ~f:(fun (name, deps) -> 9 | let deps_str = 10 | String.Set.to_list deps |> List.map ~f:(sprintf "%S") |> String.concat ~sep:", " 11 | in 12 | sprintf " \"%s\" -> {%s}" name deps_str) 13 | in 14 | String.concat ~sep:"\n" lines |> sprintf "digraph {\n%s\n}" 15 | ;; 16 | -------------------------------------------------------------------------------- /stdlib/src/nonempty_list.mli: -------------------------------------------------------------------------------- 1 | type 'a t = ( :: ) of ('a * 'a list) 2 | 3 | val singleton : 'a -> 'a t 4 | val of_list_opt : 'a list -> 'a t option 5 | val to_list : 'a t -> 'a list 6 | val to_dyn : 'a Dyn.builder -> 'a t Dyn.builder 7 | val cons : 'a -> 'a t -> 'a t 8 | val rev : 'a t -> 'a t 9 | val map : 'a t -> f:('a -> 'b) -> 'b t 10 | val equal : eq:('a -> 'a -> bool) -> 'a t -> 'a t -> bool 11 | val compare : cmp:('a -> 'a -> int) -> 'a t -> 'a t -> int 12 | val last : 'a t -> 'a 13 | val split_last : 'a t -> 'a list * 'a 14 | -------------------------------------------------------------------------------- /dune.lock/toml.7.1.0.pkg: -------------------------------------------------------------------------------- 1 | (version 7.1.0) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (withenv 7 | ((= TZ "")) 8 | (progn 9 | (when %{pkg-self:dev} (run dune subst)) 10 | (run dune build -p %{pkg-self:name} -j %{jobs} @install))))))) 11 | 12 | (depends 13 | (all_platforms 14 | (dune ocaml menhir ISO8601))) 15 | 16 | (source 17 | (fetch 18 | (url https://github.com/ocaml-toml/to.ml/archive/7.1.0.tar.gz) 19 | (checksum 20 | sha256=1d4e9c16ed9e24d46dd757ce94adc7fc8b2068eb5ff7cd2a70fce08135a752ef))) 21 | -------------------------------------------------------------------------------- /package_meta/src/dependency.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t = 4 | { name : Package_name.t 5 | ; source : Dependency_source.t 6 | } 7 | 8 | let create ~name ~source = { name; source } 9 | 10 | let equal t { name; source } = 11 | Package_name.equal t.name name && Dependency_source.equal t.source source 12 | ;; 13 | 14 | let to_dyn { name; source } = 15 | Dyn.record 16 | [ "name", Package_name.to_dyn name; "source", Dependency_source.to_dyn source ] 17 | ;; 18 | 19 | let name { name; _ } = name 20 | let source { source; _ } = source 21 | -------------------------------------------------------------------------------- /dune.lock/ppx_base.v0.17.0.pkg: -------------------------------------------------------------------------------- 1 | (version v0.17.0) 2 | 3 | (build 4 | (all_platforms ((action (run dune build -p %{pkg-self:name} -j %{jobs}))))) 5 | 6 | (depends 7 | (all_platforms 8 | (ocaml 9 | ppx_cold 10 | ppx_compare 11 | ppx_enumerate 12 | ppx_globalize 13 | ppx_hash 14 | ppx_sexp_conv 15 | dune 16 | ppxlib))) 17 | 18 | (source 19 | (fetch 20 | (url 21 | https://github.com/janestreet/ppx_base/archive/refs/tags/v0.17.0.tar.gz) 22 | (checksum 23 | sha256=80e7e6c6a704114d1d0989ee9bc01bca45278096c0caf3f2c4ef28d3c12ae61c))) 24 | -------------------------------------------------------------------------------- /dune.lock/ppxlib.0.37.0.pkg: -------------------------------------------------------------------------------- 1 | (version 0.37.0) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 9 | 10 | (depends 11 | (all_platforms 12 | (dune ocaml ocaml-compiler-libs ppx_derivers sexplib0 stdlib-shims))) 13 | 14 | (source 15 | (fetch 16 | (url 17 | https://github.com/ocaml-ppx/ppxlib/releases/download/0.37.0/ppxlib-0.37.0.tbz) 18 | (checksum 19 | sha256=2e223837e7cecc3bc84a432432c0a72f4e1d5be9165c9c33772f156db85db0b3))) 20 | -------------------------------------------------------------------------------- /installation/src/alice_installation.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | type t 5 | 6 | val create : Alice_env.Os_type.t -> Env.t -> t 7 | val local_bin : t -> Absolute_path.non_root_t 8 | 9 | (** Path to where the file that will contain the bash completion script for 10 | Alice. *) 11 | val bash_completion_script_path : t -> Absolute_path.non_root_t 12 | 13 | val roots : t -> Absolute_path.non_root_t 14 | val current : t -> Absolute_path.non_root_t 15 | val current_bin : t -> Absolute_path.non_root_t 16 | val env : t -> Absolute_path.non_root_t 17 | -------------------------------------------------------------------------------- /dune.lock/xdg.3.20.2.pkg: -------------------------------------------------------------------------------- 1 | (version 3.20.2) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run rm -rf vendor/csexp) 9 | (run rm -rf vendor/pp) 10 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 11 | 12 | (depends 13 | (all_platforms 14 | (dune ocaml))) 15 | 16 | (source 17 | (fetch 18 | (url 19 | https://github.com/ocaml/dune/releases/download/3.20.2/dune-3.20.2.tbz) 20 | (checksum 21 | sha256=b1a86b2d60bdb4a8b9bb6861bdf2f9f28a6e7cb5d833ce81afecceb9ef9ca549))) 22 | -------------------------------------------------------------------------------- /dune.lock/ordering.3.20.2.pkg: -------------------------------------------------------------------------------- 1 | (version 3.20.2) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run rm -rf vendor/csexp) 9 | (run rm -rf vendor/pp) 10 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 11 | 12 | (depends 13 | (all_platforms 14 | (dune ocaml))) 15 | 16 | (source 17 | (fetch 18 | (url 19 | https://github.com/ocaml/dune/releases/download/3.20.2/dune-3.20.2.tbz) 20 | (checksum 21 | sha256=b1a86b2d60bdb4a8b9bb6861bdf2f9f28a6e7cb5d833ce81afecceb9ef9ca549))) 22 | -------------------------------------------------------------------------------- /dune.lock/dyn.3.20.2.pkg: -------------------------------------------------------------------------------- 1 | (version 3.20.2) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run rm -rf vendor/csexp) 9 | (run rm -rf vendor/pp) 10 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 11 | 12 | (depends 13 | (all_platforms 14 | (dune ocaml ordering pp))) 15 | 16 | (source 17 | (fetch 18 | (url 19 | https://github.com/ocaml/dune/releases/download/3.20.2/dune-3.20.2.tbz) 20 | (checksum 21 | sha256=b1a86b2d60bdb4a8b9bb6861bdf2f9f28a6e7cb5d833ce81afecceb9ef9ca549))) 22 | -------------------------------------------------------------------------------- /integration_tests/run-argv.t: -------------------------------------------------------------------------------- 1 | Test that programs run with `alice run` see the expected arguments via argv. 2 | 3 | $ alice new --normalize-paths --exe echo 4 | Creating new executable package "echo" in echo 5 | 6 | $ cd echo 7 | 8 | $ cat > src/main.ml < let () = 10 | > let args = Array.to_list Sys.argv |> List.tl in 11 | > print_endline (String.concat " " args) 12 | > EOF 13 | 14 | $ alice run --normalize-paths -- foo bar baz 15 | Compiling echo v0.1.0 16 | Running build/packages/echo-0.1.0/debug/executable/echo 17 | 18 | foo bar baz 19 | -------------------------------------------------------------------------------- /stdlib/src/map.mli: -------------------------------------------------------------------------------- 1 | include module type of MoreLabels.Map 2 | 3 | module type S = sig 4 | include S 5 | 6 | val to_dyn : 'a Dyn.builder -> 'a t -> Dyn.t 7 | val of_list : (key * 'a) list -> ('a t, key * 'a * 'a) Result.t 8 | 9 | (** Raises [Invalid_argument] if the list contains duplicate keys *) 10 | val of_list_exn : (key * 'a) list -> 'a t 11 | 12 | val keys : 'a t -> key list 13 | val values : 'a t -> 'a list 14 | end 15 | 16 | module type Key = sig 17 | include OrderedType 18 | 19 | val to_dyn : t -> Dyn.t 20 | end 21 | 22 | module Make (Key : Key) : S with type key = Key.t 23 | -------------------------------------------------------------------------------- /dune.lock/dune-configurator.3.20.2.pkg: -------------------------------------------------------------------------------- 1 | (version 3.20.2) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run rm -rf vendor/csexp) 9 | (run rm -rf vendor/pp) 10 | (run dune build -p %{pkg-self:name} -j %{jobs} @install)))))) 11 | 12 | (depends 13 | (all_platforms 14 | (dune ocaml base-unix csexp))) 15 | 16 | (source 17 | (fetch 18 | (url 19 | https://github.com/ocaml/dune/releases/download/3.20.2/dune-3.20.2.tbz) 20 | (checksum 21 | sha256=b1a86b2d60bdb4a8b9bb6861bdf2f9f28a6e7cb5d833ce81afecceb9ef9ca549))) 22 | -------------------------------------------------------------------------------- /alice/src/build.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_engine 3 | open Climate 4 | 5 | let build = 6 | let open Arg_parser in 7 | let+ () = Common.set_globals_from_flags 8 | and+ project = Common.parse_project 9 | and+ profile = Common.parse_profile in 10 | let env = Alice_env.current_env () in 11 | let os_type = Alice_env.Os_type.current () in 12 | let ocamlopt = Alice_which.ocamlopt os_type env in 13 | Project.build project profile os_type ocamlopt 14 | ;; 15 | 16 | let subcommand = 17 | let open Command in 18 | subcommand "build" ~aliases:[ "b" ] (singleton ~doc:"Build a project." build) 19 | ;; 20 | -------------------------------------------------------------------------------- /alice/src/common.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_engine 4 | open Climate 5 | 6 | (** [parse_absolute_path ?dac names] returns a named argument parser of 7 | absolute path *) 8 | val parse_absolute_path 9 | : ?doc:string 10 | -> string list 11 | -> Absolute_path.Root_or_non_root.t option Arg_parser.t 12 | 13 | val parse_project : Project.t Arg_parser.t 14 | val parse_profile : Profile.t Arg_parser.t 15 | 16 | (** Parse the "--verbose" and "--quiet" and have the side effect of setting the 17 | global log level and print mode. *) 18 | val set_globals_from_flags : unit Arg_parser.t 19 | -------------------------------------------------------------------------------- /stdlib/src/alice_stdlib.ml: -------------------------------------------------------------------------------- 1 | module Ansi_style = Ansi_style 2 | module Command = Command 3 | module Compare = Compare 4 | module Dyn = Dyn 5 | module Env = Env 6 | module Filename = Filename 7 | module Fileutils = FileUtil 8 | module List = List 9 | module Hashtbl = Hashtbl 10 | module Map = Map 11 | module Nonempty_list = Nonempty_list 12 | module Option = Option 13 | module Ordering = Ordering 14 | module Pp = Pp 15 | module Re = Re 16 | module Result = Result 17 | module Seq = Seq 18 | module Set = Set 19 | module String = String 20 | module Type_bool = Type_bool 21 | module Xdg = Xdg 22 | 23 | let sprintf = Printf.sprintf 24 | -------------------------------------------------------------------------------- /alice/src/remote_tarballs.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t 4 | 5 | module Root_5_3_1 : sig 6 | val aarch64_linux_musl_static_5_3_1 : t 7 | val aarch64_linux_gnu_5_3_1 : t 8 | val aarch64_macos_5_3_1 : t 9 | val x86_64_linux_musl_static_5_3_1 : t 10 | val x86_64_linux_gnu_5_3_1 : t 11 | val x86_64_macos_5_3_1 : t 12 | val x86_64_windows_5_3_1 : t 13 | end 14 | 15 | val install_all 16 | : t 17 | -> Alice_stdlib.Env.t 18 | -> dst:'a Alice_hierarchy.Absolute_path.t 19 | -> unit 20 | 21 | val install_compiler 22 | : t 23 | -> Alice_stdlib.Env.t 24 | -> dst:'a Alice_hierarchy.Absolute_path.t 25 | -> unit 26 | -------------------------------------------------------------------------------- /stdlib/src/string.ml: -------------------------------------------------------------------------------- 1 | module T = struct 2 | include StdLabels.String 3 | 4 | let to_dyn = Dyn.string 5 | end 6 | 7 | include T 8 | module Set = Set.Make (T) 9 | module Map = Map.Make (T) 10 | 11 | let is_empty s = length s == 0 12 | 13 | let lsplit2 s ~on = 14 | match index_opt s on with 15 | | None -> None 16 | | Some i -> Some (sub s ~pos:0 ~len:i, sub s ~pos:(i + 1) ~len:(length s - i - 1)) 17 | ;; 18 | 19 | let split_on_char_nonempty s ~sep = 20 | match Nonempty_list.of_list_opt (split_on_char s ~sep) with 21 | | None -> 22 | (* [split_on_char] never returns empty lists *) 23 | failwith "unreachable" 24 | | Some l -> l 25 | ;; 26 | -------------------------------------------------------------------------------- /io/src/extract.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | let tar env ~tarball_file ~output_dir = 5 | let args = 6 | [ "-x" 7 | ; "-C" 8 | ; Absolute_path.to_filename output_dir 9 | ; "-f" 10 | ; Absolute_path.to_filename tarball_file 11 | ] 12 | in 13 | Command.create "tar" ~args env 14 | ;; 15 | 16 | let extract env ~tarball_file ~output_dir = 17 | match tar env ~tarball_file ~output_dir |> Process.Blocking.run_command with 18 | | Ok (Process.Status.Exited 0) -> () 19 | | _ -> 20 | Alice_error.panic 21 | [ Pp.textf "Unable to extract tarball: %s" (Absolute_path.to_filename tarball_file) 22 | ] 23 | ;; 24 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1762363567, 6 | "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /package_meta/src/dependencies.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t = Dependency.t list 4 | 5 | let empty = [] 6 | let equal = List.equal ~eq:Dependency.equal 7 | let to_dyn = Dyn.list Dependency.to_dyn 8 | let names = List.map ~f:Dependency.name 9 | let to_list t = t 10 | 11 | let of_list t = 12 | let rec loop seen remaining = 13 | match remaining with 14 | | [] -> Ok t 15 | | x :: xs -> 16 | let name = Dependency.name x in 17 | if Package_name.Set.mem name seen 18 | then Error (`Duplicate_name name) 19 | else ( 20 | let seen = Package_name.Set.add name seen in 21 | loop seen xs) 22 | in 23 | loop Package_name.Set.empty t 24 | ;; 25 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | # When developing alice, use the musl toolchain. The development 3 | # environment (ocamllsp, ocamlopt, etc) can then be managed by alice 4 | # itself, since alice can install tools which have been pre-compiled 5 | # against musl. This lets us dogfood alice's tool installation 6 | # mechanism, and allows alice to be built with dune package 7 | # management on NixOS, where the OCaml compiler is treated as a 8 | # package and thus a specific version unknown to nix is fixed in the 9 | # lockdir. 10 | let muslPkgs = pkgs.pkgsMusl; 11 | in muslPkgs.mkShell { 12 | nativeBuildInputs = [ pkgs.graphviz ]; 13 | buildIpnuts = [ muslPkgs.musl ]; 14 | } 15 | -------------------------------------------------------------------------------- /engine/src/module_name.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package 3 | open Alice_hierarchy 4 | 5 | type t 6 | 7 | val to_string_uppercase_first_letter : t -> string 8 | val basename_without_extension : t -> Basename.t 9 | val of_package_name : Package_name.t -> t 10 | 11 | (** The name of the module packing all internal modules of a package. Part of 12 | Alice's packaging protocol. *) 13 | val internal_modules : Package_name.t -> t 14 | 15 | (** The name of the module containing the public interface to a package which 16 | will be opened when compiling code which depends on the package. Part of 17 | Alice's packaging protocol. *) 18 | val public_interface_to_open : Package_name.t -> t 19 | -------------------------------------------------------------------------------- /stdlib/src/command.ml: -------------------------------------------------------------------------------- 1 | type t = 2 | { prog : string 3 | ; args : string list 4 | ; env : Env.t 5 | } 6 | 7 | let create prog ~args env = { prog; args; env } 8 | 9 | let equal t { prog; args; env } = 10 | String.equal t.prog prog 11 | && List.equal ~eq:String.equal t.args args 12 | && Env.equal t.env env 13 | ;; 14 | 15 | let to_dyn { prog; args; env } = 16 | Dyn.record 17 | [ "prog", Dyn.string prog; "args", Dyn.list Dyn.string args; "env", Env.to_dyn env ] 18 | ;; 19 | 20 | let to_string_ignore_env { prog; args; env = _ } = String.concat ~sep:" " (prog :: args) 21 | 22 | let to_string_ignore_env_backticks t = 23 | String.cat (String.cat "`" (to_string_ignore_env t)) "`" 24 | ;; 25 | -------------------------------------------------------------------------------- /dune.lock/ocaml.5.3.1+relocatable.pkg: -------------------------------------------------------------------------------- 1 | (version 5.3.1+relocatable) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (withenv 7 | ((= CAML_LD_LIBRARY_PATH "") 8 | (= LSAN_OPTIONS detect_leaks=0,exitcode=0) 9 | (= ASAN_OPTIONS detect_leaks=0,exitcode=0)) 10 | (run 11 | ocaml 12 | %{pkg:ocaml-config:share}/gen_ocaml_config.ml 13 | 5.3.1 14 | %{pkg-self:name})))))) 15 | 16 | (depends 17 | (all_platforms 18 | (ocaml-config ocaml-system))) 19 | 20 | (exported_env 21 | (+= OCAMLTOP_INCLUDE_PATH "\%{toplevel}%") 22 | (= CAML_LD_LIBRARY_PATH "\%{_:stubsdir}%") 23 | (+= CAML_LD_LIBRARY_PATH "\%{lib}%/stublibs") 24 | (= OCAML_TOPLEVEL_PATH "\%{toplevel}%")) 25 | -------------------------------------------------------------------------------- /engine/src/scheduler.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package 3 | open Alice_ocaml_compiler 4 | 5 | module Package_built : sig 6 | type t 7 | 8 | val any_rebuilt : t list -> bool 9 | end 10 | 11 | module Sequential : sig 12 | (** Evaluate a list of build plans for a single package. There may be 13 | multiple build plans for a package, such as if there's a library and 14 | executable to build. Build plans are evaluated in order. *) 15 | val eval_build_plans 16 | : Build_graph.Build_plan.t list 17 | -> (_, _) Dependency_graph.Package_with_deps.t 18 | -> Profile.t 19 | -> Build_dir.t 20 | -> Ocaml_compiler.t 21 | -> any_dep_rebuilt:bool 22 | -> Package_built.t 23 | end 24 | -------------------------------------------------------------------------------- /stdlib/src/result.mli: -------------------------------------------------------------------------------- 1 | include module type of Stdlib.Result 2 | 3 | val map : ('a, 'e) t -> f:('a -> 'b) -> ('b, 'e) t 4 | val bind : ('a, 'e) t -> f:('a -> ('b, 'e) t) -> ('b, 'e) t 5 | 6 | module O : sig 7 | val ( >>| ) : ('a, 'error) t -> ('a -> 'b) -> ('b, 'error) t 8 | val ( >>= ) : ('a, 'error) t -> ('a -> ('b, 'error) t) -> ('b, 'error) t 9 | val ( let* ) : ('a, 'error) t -> ('a -> ('b, 'error) t) -> ('b, 'error) t 10 | val ( and+ ) : ('a, 'error) t -> ('b, 'error) t -> ('a * 'b, 'error) t 11 | val ( let+ ) : ('a, 'error) t -> ('a -> 'b) -> ('b, 'error) t 12 | end 13 | 14 | module List : sig 15 | type ('a, 'error) t = ('a, 'error) result list 16 | 17 | val all : ('a, 'error) t -> ('a list, 'error) result 18 | end 19 | -------------------------------------------------------------------------------- /manifest/src/fields.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | val parse_field_opt 4 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 5 | -> Toml.Types.Table.Key.t 6 | -> Toml.Types.table 7 | -> f:(Toml.Types.value -> [ `Ok of 'a | `Expected of string ]) 8 | -> 'a option 9 | 10 | val parse_field 11 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 12 | -> Toml.Types.Table.Key.t 13 | -> Toml.Types.table 14 | -> f:(Toml.Types.value -> [ `Ok of 'a | `Expected of string ]) 15 | -> 'a 16 | 17 | val check_for_extraneous_fields 18 | : manifest_path_for_messages:Alice_hierarchy.Absolute_path.non_root_t 19 | -> all_keys:Toml.Types.Table.Key.t list 20 | -> Toml.Types.table 21 | -> unit 22 | -------------------------------------------------------------------------------- /stdlib/src/result.ml: -------------------------------------------------------------------------------- 1 | open! Stdlib 2 | include Result 3 | 4 | let map t ~f = map f t 5 | let bind t ~f = bind t f 6 | 7 | let both a b = 8 | match a with 9 | | Error e -> Error e 10 | | Ok a -> 11 | (match b with 12 | | Error e -> Error e 13 | | Ok b -> Ok (a, b)) 14 | ;; 15 | 16 | module O = struct 17 | let ( >>= ) t f = bind t ~f 18 | let ( >>| ) t f = map t ~f 19 | let ( let* ) = ( >>= ) 20 | let ( let+ ) = ( >>| ) 21 | let ( and+ ) = both 22 | end 23 | 24 | module List = struct 25 | type ('a, 'error) t = ('a, 'error) result list 26 | 27 | let rec all = function 28 | | [] -> Ok [] 29 | | Ok x :: xs -> map (all xs) ~f:(fun xs -> x :: xs) 30 | | Error error :: _xs -> Error error 31 | ;; 32 | end 33 | -------------------------------------------------------------------------------- /alice_tests.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | maintainer: ["Stephen Sherratt "] 4 | authors: ["Stephen Sherratt "] 5 | license: "MIT" 6 | homepage: "https://alicecaml.org" 7 | bug-reports: "https://github.com/alicecaml/alice/issues" 8 | depends: [ 9 | "dune" {>= "3.20"} 10 | "ppx_inline_test" 11 | "ppx_expect" 12 | "odoc" {with-doc} 13 | ] 14 | build: [ 15 | ["dune" "subst"] {dev} 16 | [ 17 | "dune" 18 | "build" 19 | "-p" 20 | name 21 | "-j" 22 | jobs 23 | "@install" 24 | "@runtest" {with-test} 25 | "@doc" {with-doc} 26 | ] 27 | ] 28 | dev-repo: "git+https://github.com/alicecaml/alice.git" 29 | x-maintenance-intent: ["(latest)"] 30 | -------------------------------------------------------------------------------- /alice/src/run.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_engine 3 | open Climate 4 | 5 | let run_ = 6 | let open Arg_parser in 7 | let+ () = Common.set_globals_from_flags 8 | and+ project = Common.parse_project 9 | and+ profile = Common.parse_profile 10 | and+ args = 11 | pos_all string ~doc:"Arguments to pass to the executable." ~value_name:"ARGS" 12 | in 13 | let env = Alice_env.current_env () in 14 | let os_type = Alice_env.Os_type.current () in 15 | let ocamlopt = Alice_which.ocamlopt os_type env in 16 | Project.run project profile os_type ocamlopt ~args 17 | ;; 18 | 19 | let subcommand = 20 | let open Command in 21 | subcommand 22 | "run" 23 | ~aliases:[ "r" ] 24 | (singleton ~doc:"Build a project and run its executable." run_) 25 | ;; 26 | -------------------------------------------------------------------------------- /dune.lock/ocaml-config.3.pkg: -------------------------------------------------------------------------------- 1 | (version 3) 2 | 3 | (build 4 | (all_platforms 5 | ((action (substitute gen_ocaml_config.ml.in gen_ocaml_config.ml))))) 6 | 7 | (depends 8 | (all_platforms (ocaml-system))) 9 | 10 | (extra_sources 11 | (gen_ocaml_config.ml.in 12 | (fetch 13 | (url 14 | https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/ocaml-config/gen_ocaml_config.ml.in.3) 15 | (checksum 16 | sha256=a9ad8d84a08961159653a978db92d10f694510182b206cacb96d5c9f63b5121e))) 17 | (ocaml-config.install 18 | (fetch 19 | (url 20 | https://raw.githubusercontent.com/ocaml/opam-source-archives/main/patches/ocaml-config/ocaml-config.install) 21 | (checksum 22 | sha256=6e4fd93f4cce6bad0ed3c08afd0248dbe7d7817109281de6294e5b5ef5597051)))) 23 | -------------------------------------------------------------------------------- /stdlib/src/type_bool.ml: -------------------------------------------------------------------------------- 1 | (* Note that without disabling warning 37 the compiler warns about unused 2 | constructors. While [true_t] and [false_t] would ideally be phantom types 3 | (with no constructors), if the constructors are not exposed in the interface 4 | then the compiler can't refute impossible patterns of GADTs involving these 5 | types. An alternative solution would be to expose the constructors without 6 | using the "private" keyword, however this would allow client code to 7 | construct values of [true_t] and [false_t] which is pointless as they are 8 | ideally phantom types. *) 9 | 10 | type true_t = True_t [@@warning "-37"] 11 | type false_t = False_t [@@warning "-37"] 12 | 13 | type _ t = 14 | | True : true_t t 15 | | False : false_t t 16 | -------------------------------------------------------------------------------- /dune.lock/sha.1.15.4.pkg: -------------------------------------------------------------------------------- 1 | (version 1.15.4) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run 9 | dune 10 | build 11 | -p 12 | %{pkg-self:name} 13 | -j 14 | %{jobs} 15 | --promote-install-files=false 16 | @install) 17 | (run 18 | dune 19 | install 20 | -p 21 | %{pkg-self:name} 22 | --create-install-files 23 | %{pkg-self:name})))))) 24 | 25 | (depends 26 | (all_platforms 27 | (dune ocaml stdlib-shims))) 28 | 29 | (source 30 | (fetch 31 | (url 32 | https://github.com/djs55/ocaml-sha/releases/download/v1.15.4/sha-1.15.4.tbz) 33 | (checksum 34 | sha256=6de5b12139b1999ce9df4cc78a5a31886c2a547c9d448bf2853f8b53bcf1f1b1))) 35 | -------------------------------------------------------------------------------- /package_meta/src/package_meta.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t 4 | 5 | val to_dyn : t -> Dyn.t 6 | val equal : t -> t -> bool 7 | 8 | (** The [dependencies] argument is optional so that the presence of an empty 9 | dependencies list can be distinguished from a lack af a dependencies list 10 | so that a package manifest can be round tripped via [t]. *) 11 | val create : id:Package_id.t -> dependencies:Dependencies.t option -> t 12 | 13 | val id : t -> Package_id.t 14 | val name : t -> Package_name.t 15 | val version : t -> Semantic_version.t 16 | val dependencies : t -> Dependencies.t 17 | 18 | (** Like [dependencies] but exposes the optional value passed to [create]. Use 19 | this when serializing a [t] to round-trip. *) 20 | val dependencies_ : t -> Dependencies.t option 21 | -------------------------------------------------------------------------------- /dune.lock/fileutils.0.6.6.pkg: -------------------------------------------------------------------------------- 1 | (version 0.6.6) 2 | 3 | (build 4 | (all_platforms 5 | ((action 6 | (progn 7 | (when %{pkg-self:dev} (run dune subst)) 8 | (run 9 | dune 10 | build 11 | -p 12 | %{pkg-self:name} 13 | -j 14 | %{jobs} 15 | --promote-install-files=false 16 | @install) 17 | (run 18 | dune 19 | install 20 | -p 21 | %{pkg-self:name} 22 | --create-install-files 23 | %{pkg-self:name})))))) 24 | 25 | (depends 26 | (all_platforms 27 | (dune base-unix ocaml))) 28 | 29 | (source 30 | (fetch 31 | (url 32 | https://github.com/gildor478/ocaml-fileutils/releases/download/v0.6.6/fileutils-0.6.6.tbz) 33 | (checksum 34 | sha256=796d5791e2bf7b3bff200cf5057a7a1878439ebcd74ed0f1088cf86756d52be6))) 35 | -------------------------------------------------------------------------------- /engine/src/profile.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_ocaml_compiler 3 | 4 | type t = 5 | { optimization_level : [ `O2 | `O3 ] option 6 | ; debug : bool 7 | ; name : string 8 | } 9 | 10 | let debug = { optimization_level = None; debug = true; name = "debug" } 11 | let release = { optimization_level = Some `O2; debug = false; name = "release" } 12 | let name { name; _ } = name 13 | 14 | let ocaml_compiler_command t ocaml_compiler ~args = 15 | let prog = Ocaml_compiler.filename ocaml_compiler in 16 | let env = Ocaml_compiler.env ocaml_compiler in 17 | let args = 18 | (if t.debug then [ "-g" ] else []) 19 | @ (match t.optimization_level with 20 | | None -> [] 21 | | Some `O2 -> [ "-O2" ] 22 | | Some `O3 -> [ "-O3" ]) 23 | @ args 24 | in 25 | Command.create prog ~args env 26 | ;; 27 | -------------------------------------------------------------------------------- /io/src/uname.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | 4 | let run_uname env args = 5 | let command = Command.create "uname" ~args env in 6 | match Process.Blocking.run_command_capturing_stdout_lines command with 7 | | Ok (Process.Status.Exited 0, [ output ]) -> output 8 | | Ok (Process.Status.Exited 0, []) -> 9 | panic 10 | [ Pp.textf "No output from %s" (Command.to_string_ignore_env_backticks command) ] 11 | | Ok (Process.Status.Exited 0, _) -> 12 | panic 13 | [ Pp.textf 14 | "Multiple lines output from %s expectedly" 15 | (Command.to_string_ignore_env_backticks command) 16 | ] 17 | | _ -> 18 | panic [ Pp.textf "Failed to run %s" (Command.to_string_ignore_env_backticks command) ] 19 | ;; 20 | 21 | let uname env arg = 22 | match arg with 23 | | `M -> run_uname env [ "-m" ] 24 | | `S -> run_uname env [ "-s" ] 25 | ;; 26 | -------------------------------------------------------------------------------- /log/src/alice_log.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type level = 4 | [ `Debug 5 | | `Info 6 | | `Warn 7 | | `Error 8 | ] 9 | 10 | type package_id := Alice_package_meta.Package_id.t 11 | 12 | val set_level : level -> unit 13 | 14 | (** The type of __POS__ (the current file position) for convenience passing the 15 | value of __POS__ to logging functions. *) 16 | type pos = string * int * int * int 17 | 18 | val log 19 | : ?pos:pos 20 | -> ?package_id:package_id 21 | -> level:level 22 | -> Ansi_style.t Pp.t list 23 | -> unit 24 | 25 | val debug : ?pos:pos -> ?package_id:package_id -> Ansi_style.t Pp.t list -> unit 26 | val info : ?pos:pos -> ?package_id:package_id -> Ansi_style.t Pp.t list -> unit 27 | val warn : ?pos:pos -> ?package_id:package_id -> Ansi_style.t Pp.t list -> unit 28 | val error : ?pos:pos -> ?package_id:package_id -> Ansi_style.t Pp.t list -> unit 29 | -------------------------------------------------------------------------------- /package_meta/src/semantic_version.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | 4 | (** A version number following the semantic versioning spec 5 | (https://semver.org/) *) 6 | type t 7 | 8 | val to_dyn : t -> Dyn.t 9 | val equal : t -> t -> bool 10 | val compare : t -> t -> int 11 | val to_string : t -> string 12 | 13 | (** The version number with a leading "v" *) 14 | val to_string_v : t -> string 15 | 16 | val pre_release_string : t -> string option 17 | val metadata_string : t -> string option 18 | 19 | (** Returns an error if the argument isn't a valid semantic version. *) 20 | val of_string : string -> t user_result 21 | 22 | (** Raises a user error if the argument isn't a valid semantic version. *) 23 | val of_string_exn : string -> t 24 | 25 | (** Compares according to the semver precedence rules which ignore metadata. *) 26 | val compare_for_precedence : t -> t -> int 27 | -------------------------------------------------------------------------------- /engine/src/project.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package 3 | open Alice_hierarchy 4 | open Alice_ocaml_compiler 5 | 6 | (** Builds are orchestrated in the context of a project. This determines things 7 | like where build artifacts will go, and which package is the root of the 8 | package dependency graph. *) 9 | type t 10 | 11 | val of_package : Package.t -> t 12 | val build : t -> Profile.t -> Alice_env.Os_type.t -> Ocaml_compiler.t -> unit 13 | 14 | val run 15 | : t 16 | -> Profile.t 17 | -> Alice_env.Os_type.t 18 | -> Ocaml_compiler.t 19 | -> args:string list 20 | -> unit 21 | 22 | val clean : t -> unit 23 | val dot_build_artifacts : t -> Alice_env.Os_type.t -> Ocaml_compiler.t -> string 24 | val dot_dependencies : t -> string 25 | val build_dir_path_relative_to_project_root : Basename.t 26 | val write_dot_merlin_initial : t -> unit 27 | val write_dot_gitignore : t -> unit 28 | -------------------------------------------------------------------------------- /stdlib/src/ansi_style.mli: -------------------------------------------------------------------------------- 1 | module Color : sig 2 | type t = 3 | [ `Black 4 | | `Red 5 | | `Green 6 | | `Yellow 7 | | `Blue 8 | | `Magenta 9 | | `Cyan 10 | | `White 11 | | `Bright_black 12 | | `Bright_red 13 | | `Bright_green 14 | | `Bright_yellow 15 | | `Bright_blue 16 | | `Bright_magenta 17 | | `Bright_cyan 18 | | `Bright_white 19 | ] 20 | end 21 | 22 | type t = 23 | { bold : bool 24 | ; dim : bool 25 | ; underline : bool 26 | ; color : Color.t option 27 | } 28 | 29 | val create : ?bold:bool -> ?dim:bool -> ?underline:bool -> ?color:Color.t -> unit -> t 30 | val default : t 31 | val default_with_color : Color.t -> t 32 | 33 | (** Call a given function on a formatter with a given style. It is not safe to 34 | call [pp_with_style] inside [f]. *) 35 | val pp_with_style : t -> Format.formatter -> f:(Format.formatter -> unit) -> unit 36 | -------------------------------------------------------------------------------- /stdlib/src/nonempty_list.ml: -------------------------------------------------------------------------------- 1 | type 'a t = ( :: ) of ('a * 'a list) 2 | 3 | let singleton x = [ x ] 4 | 5 | let of_list_opt = function 6 | | [] -> None 7 | | x :: xs -> Some (x :: xs) 8 | ;; 9 | 10 | let to_list (x :: xs) = List.(x :: xs) 11 | let to_dyn f t = Dyn.list f (to_list t) 12 | let append (x :: xs) (y :: ys) = x :: List.concat [ xs; [ y ]; ys ] 13 | let cons x xs = x :: to_list xs 14 | 15 | let rev (x :: xs) = 16 | match List.rev xs with 17 | | [] -> [ x ] 18 | | y :: ys -> append (y :: ys) [ x ] 19 | ;; 20 | 21 | let map t ~f = 22 | match List.map (to_list t) ~f with 23 | | [] -> failwith "unreachable" 24 | | x :: xs -> x :: xs 25 | ;; 26 | 27 | let equal ~eq a b = List.equal ~eq (to_list a) (to_list b) 28 | let compare ~cmp a b = List.compare ~cmp (to_list a) (to_list b) 29 | let last (x :: xs) = List.last xs |> Option.value ~default:x 30 | let split_last (x :: xs) = List.split_last xs |> Option.value ~default:([], x) 31 | -------------------------------------------------------------------------------- /alice/src/target.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Climate 3 | 4 | module Os : sig 5 | type t = 6 | | Macos 7 | | Linux 8 | | Windows 9 | 10 | val to_dyn : t -> Dyn.t 11 | val to_string : t -> string 12 | end 13 | 14 | module Arch : sig 15 | type t = 16 | | Aarch64 17 | | X86_64 18 | 19 | val to_dyn : t -> Dyn.t 20 | val to_string : t -> string 21 | end 22 | 23 | module Linked : sig 24 | type t = 25 | | Dynamic 26 | | Static 27 | 28 | val to_dyn : t -> Dyn.t 29 | val to_string : t -> string 30 | end 31 | 32 | type t = 33 | { os : Os.t 34 | ; arch : Arch.t 35 | ; linked : Linked.t 36 | } 37 | 38 | val to_dyn : t -> Dyn.t 39 | val create : os:Os.t -> arch:Arch.t -> linked:Linked.t -> t 40 | val to_string : t -> string 41 | 42 | module Set : Set.S with type elt = t 43 | module Map : Map.S with type key = t 44 | 45 | val poll : Alice_env.Os_type.t -> Env.t -> t 46 | val arg_parser : t Arg_parser.t 47 | -------------------------------------------------------------------------------- /error/src/alice_error.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | (** Print an error message and exit the program. Use for unrecoverable errors 4 | caused by a bug. For errors caused by user's actions, use the [User_error] 5 | module instead. *) 6 | val panic : Ansi_style.t Pp.t list -> 'a 7 | 8 | (** [panic] but it returns a [unit] so you can write [panic_u [ ... ];] on a 9 | single line. *) 10 | val panic_u : Ansi_style.t Pp.t list -> unit 11 | 12 | module User_error : sig 13 | type t = Ansi_style.t Pp.t list 14 | type nonrec 'a result = ('a, t) result 15 | 16 | exception E of t 17 | 18 | val eprint : t -> unit 19 | 20 | (** Raises an [E] exception on the error case *) 21 | val get : 'a result -> 'a 22 | 23 | (** Panics on the error case *) 24 | val get_or_panic : 'a result -> 'a 25 | end 26 | 27 | type 'a user_result = 'a User_error.result 28 | 29 | (** Raise a [User_error.E] exception. *) 30 | val user_exn : User_error.t -> 'a 31 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = 3 | "Alice is a radical, experimental OCaml build system, package manager, and toolchain manager for Windows and Unix-based OSes."; 4 | 5 | inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; 6 | 7 | outputs = { self, nixpkgs }: 8 | let 9 | systems = 10 | [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ]; 11 | mapSystemAttrs = f: 12 | builtins.listToAttrs (map (system: { 13 | name = system; 14 | value = f system; 15 | }) systems); 16 | getPkgs = system: builtins.getAttr system nixpkgs.legacyPackages; 17 | makePackage = system: (getPkgs system).callPackage ./default.nix { }; 18 | makeDevShell = 19 | system: ({ default = import ./shell.nix { pkgs = getPkgs system; }; }); 20 | in { 21 | packages = mapSystemAttrs makePackage; 22 | devShells = mapSystemAttrs makeDevShell; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /installation/src/alice_installation.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | type t = { xdg : Xdg.t } 5 | 6 | let create os_type env = { xdg = Alice_env.Xdg.create os_type env } 7 | let home { xdg } = Xdg.home_dir xdg |> Absolute_path.of_filename_assert_non_root 8 | let data { xdg } = Xdg.data_dir xdg |> Absolute_path.of_filename_assert_non_root 9 | let local_bin t = home t / Basename.of_filename ".local" / Basename.of_filename "bin" 10 | 11 | let bash_completion_script_path t = 12 | data t 13 | / Basename.of_filename "bash-completions" 14 | / Basename.of_filename "completions" 15 | / Basename.of_filename "alice" 16 | ;; 17 | 18 | let alice_data t = data t / Basename.of_filename "alice" 19 | let roots t = alice_data t / Basename.of_filename "roots" 20 | let current t = alice_data t / Basename.of_filename "current" 21 | let current_bin t = current t / Basename.of_filename "bin" 22 | let env t = alice_data t / Basename.of_filename "env" 23 | -------------------------------------------------------------------------------- /dune.lock/lock.dune: -------------------------------------------------------------------------------- 1 | (lang package 0.1) 2 | 3 | (dependency_hash 3fb344d89d769c6be5b338aef056fe39) 4 | 5 | (ocaml ocaml-system) 6 | 7 | (repositories 8 | (complete true) 9 | (used 10 | ((source 11 | https://github.com/ocaml/opam-repository.git#e4e631bfa1de346c5c18d3bb460fd03366bcb0e6)) 12 | ((source 13 | https://github.com/ocaml-dune/opam-overlays.git#2a9543286ff0e0656058fee5c0da7abc16b8717d)) 14 | ((source 15 | https://github.com/alicecaml/alice-opam-repo#781db10863f3b7a3507842e88d0d3beeebd264ad)))) 16 | 17 | (solved_for_platforms 18 | ((arch x86_64) 19 | (os linux) 20 | (sys-ocaml-version 5.3.1+relocatable)) 21 | ((arch arm64) 22 | (os linux) 23 | (sys-ocaml-version 5.3.1+relocatable)) 24 | ((arch x86_64) 25 | (os macos) 26 | (sys-ocaml-version 5.3.1+relocatable)) 27 | ((arch arm64) 28 | (os macos) 29 | (sys-ocaml-version 5.3.1+relocatable)) 30 | ((arch x86_64) 31 | (os win32) 32 | (sys-ocaml-version 5.3.1+relocatable))) 33 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.20) 2 | 3 | (generate_opam_files) 4 | 5 | (name alice) 6 | 7 | (source 8 | (github alicecaml/alice)) 9 | 10 | (homepage https://alicecaml.org) 11 | 12 | (license MIT) 13 | 14 | (authors "Stephen Sherratt ") 15 | 16 | (maintainers "Stephen Sherratt ") 17 | 18 | (cram enable) 19 | 20 | (package 21 | (name alice) 22 | (synopsis "Radical OCaml build system and environment manager") 23 | (description 24 | "Alice is a radical, experimental OCaml build system, package manager, and toolchain manager for Windows, macOS, and Linux. Its goal is to allow anyone to program in OCaml with as little friction as possible.") 25 | (depends 26 | (ocaml 27 | (>= "5.1")) 28 | (climate 29 | (>= "0.9")) 30 | sha 31 | dyn 32 | fileutils 33 | pp 34 | (re 35 | (>= "1.12")) 36 | xdg 37 | (toml 38 | (>= "7")))) 39 | 40 | (package 41 | (name alice_tests) 42 | (depends ppx_inline_test ppx_expect)) 43 | -------------------------------------------------------------------------------- /engine/src/ocamldep_cache.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package 4 | open Alice_ocaml_compiler 5 | 6 | type dep_table = Ocaml_compiler.Deps.t Absolute_path.Non_root_map.t 7 | 8 | (** Cache which is serialized in the build directory to avoid running ocamldep 9 | when it's output is guaranteed to be the same as the previous time it was 10 | run on some file. *) 11 | type t 12 | 13 | (** Load the cache for the given package from the build directory if it exists, 14 | otherwise return an empty cache. *) 15 | val load : Build_dir.t -> Package_id.t -> t 16 | 17 | (** Overwrite the cache in the build directory with an updated dep table. *) 18 | val store : t -> dep_table -> unit 19 | 20 | (** Look up the deps of a given source file. The ocamldep executable will be 21 | run in the event of a cache miss. *) 22 | val get_deps 23 | : t 24 | -> Ocaml_compiler.t 25 | -> source_path:Absolute_path.non_root_t 26 | -> Ocaml_compiler.Deps.t 27 | -------------------------------------------------------------------------------- /package_meta/src/package_meta.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type t = 4 | { id : Package_id.t 5 | ; dependencies : Dependencies.t option 6 | (** This is an [_ option] so that a manifest with an empty dependencies 7 | list and a manifest with no dependencies list can both round trip via 8 | this type. *) 9 | } 10 | 11 | let to_dyn { id; dependencies } = 12 | Dyn.record 13 | [ "id", Package_id.to_dyn id 14 | ; "dependencies", Dyn.option Dependencies.to_dyn dependencies 15 | ] 16 | ;; 17 | 18 | let equal t { id; dependencies } = 19 | Package_id.equal t.id id 20 | && Option.equal t.dependencies dependencies ~eq:Dependencies.equal 21 | ;; 22 | 23 | let create ~id ~dependencies = { id; dependencies } 24 | let id { id; _ } = id 25 | let name t = (id t).name 26 | let version t = (id t).version 27 | 28 | let dependencies { dependencies; _ } = 29 | Option.value dependencies ~default:Dependencies.empty 30 | ;; 31 | 32 | let dependencies_ { dependencies; _ } = dependencies 33 | -------------------------------------------------------------------------------- /env/src/alice_env.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val initial_cwd : Absolute_path.Root_or_non_root.t 5 | val current_env : unit -> Env.t 6 | 7 | module Os_type : sig 8 | type t 9 | 10 | val current : unit -> t 11 | val is_windows : t -> bool 12 | val filename_add_exe_extension_on_windows : t -> Filename.t -> Filename.t 13 | val basename_add_exe_extension_on_windows : t -> Basename.t -> Basename.t 14 | end 15 | 16 | module Path_variable : sig 17 | type t = Absolute_path.Root_or_non_root.t list 18 | 19 | val to_dyn : t -> Dyn.t 20 | val get_or_empty : Os_type.t -> Env.t -> t 21 | val get_result : Os_type.t -> Env.t -> (t, [ `Variable_not_defined ]) result 22 | val contains : t -> Absolute_path.Root_or_non_root.t -> bool 23 | 24 | (** [set t os env] replaces the PATH variable in [env] with [t]. *) 25 | val set : t -> Os_type.t -> Env.t -> Env.t 26 | end 27 | 28 | module Xdg : sig 29 | include module type of Xdg 30 | 31 | val create : Os_type.t -> Env.t -> Xdg.t 32 | end 33 | -------------------------------------------------------------------------------- /error/src/alice_error.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | let tag_error = Ansi_style.create ~color:`Red ~bold:true () 4 | 5 | let panic pps = 6 | Alice_print.Raw.pps_eprint (Pp.newline :: Pp.tag tag_error (Pp.text "Panic: ") :: pps); 7 | let backtrace = Printexc.get_callstack Int.max_int in 8 | Alice_print.Raw.pps_eprint 9 | [ Pp.newline 10 | ; Pp.newline 11 | ; Pp.tag tag_error (Pp.text "Backtrace:") 12 | ; Pp.newline 13 | ; Pp.text (Printexc.raw_backtrace_to_string backtrace) 14 | ]; 15 | exit 1 16 | ;; 17 | 18 | let panic_u = panic 19 | 20 | module User_error = struct 21 | type t = Ansi_style.t Pp.t list 22 | type nonrec 'a result = ('a, t) result 23 | 24 | exception E of t 25 | 26 | let eprint t = Alice_print.Raw.pps_eprint (Pp.newline :: t) 27 | let get = Result.get_ok 28 | 29 | let get_or_panic = function 30 | | Ok t -> t 31 | | Error pps -> panic pps 32 | ;; 33 | end 34 | 35 | type 'a user_result = 'a User_error.result 36 | 37 | let user_exn pps = raise (User_error.E pps) 38 | -------------------------------------------------------------------------------- /dune-workspace: -------------------------------------------------------------------------------- 1 | (lang dune 3.20) 2 | 3 | (env 4 | (dev 5 | (flags :standard -warn-error -26-27-32-33-34-37-39-23-38-69-67-9))) 6 | 7 | (repository 8 | (name alice_frozen) 9 | (url 10 | git+https://github.com/alicecaml/alice-opam-repo#781db10863f3b7a3507842e88d0d3beeebd264ad)) 11 | 12 | (repository 13 | (name upstream_frozen) 14 | (url 15 | git+https://github.com/ocaml/opam-repository.git#e4e631bfa1de346c5c18d3bb460fd03366bcb0e6)) 16 | 17 | (repository 18 | (name overlay_frozen) 19 | (url 20 | git+https://github.com/ocaml-dune/opam-overlays.git#2a9543286ff0e0656058fee5c0da7abc16b8717d)) 21 | 22 | (lock_dir 23 | (constraints 24 | (ocaml-system 25 | (= 5.3.1+relocatable))) 26 | (repositories upstream_frozen overlay_frozen alice_frozen) 27 | (solver_env 28 | (sys-ocaml-version 5.3.1+relocatable)) 29 | (solve_for_platforms 30 | ((arch x86_64) 31 | (os linux)) 32 | ((arch arm64) 33 | (os linux)) 34 | ((arch x86_64) 35 | (os macos)) 36 | ((arch arm64) 37 | (os macos)) 38 | ((arch x86_64) 39 | (os win32)))) 40 | -------------------------------------------------------------------------------- /engine/src/build_graph.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package 3 | open Alice_hierarchy 4 | open Alice_ocaml_compiler 5 | open Type_bool 6 | 7 | module Build_plan : sig 8 | type t 9 | 10 | val deps : t -> t list 11 | val op : t -> Typed_op.t 12 | val source_input : t -> Absolute_path.non_root_t option 13 | val generated_inputs : t -> Typed_op.Generated_file.t list 14 | val outputs : t -> Typed_op.Generated_file.Set.t 15 | val transitive_closure_outputs : t -> Typed_op.Generated_file.Set.t 16 | end 17 | 18 | (** A DAG that knows how to build a collection of interdependent files and the 19 | dependencies between each file. *) 20 | type ('exe, 'lib) t 21 | 22 | val to_dyn : (_, _) t -> Dyn.t 23 | 24 | val create 25 | : ('exe, 'lib) Package.Typed.t 26 | -> Build_dir.t 27 | -> Alice_env.Os_type.t 28 | -> Ocaml_compiler.t 29 | -> ('exe, 'lib) t 30 | 31 | val plan_exe : (true_t, _) t -> Build_plan.t 32 | val plan_lib : (_, true_t) t -> Build_plan.t 33 | val plan_lsp : (_, true_t) t -> Build_plan.t 34 | val dot : (_, _) t -> string 35 | -------------------------------------------------------------------------------- /alice/src/dot.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_engine 3 | open Climate 4 | 5 | let dot_artifacts = 6 | let open Arg_parser in 7 | let+ () = Common.set_globals_from_flags 8 | and+ project = Common.parse_project in 9 | let env = Alice_env.current_env () in 10 | let os_type = Alice_env.Os_type.current () in 11 | let ocamlopt = Alice_which.ocamlopt os_type env in 12 | print_endline @@ Project.dot_build_artifacts project os_type ocamlopt 13 | ;; 14 | 15 | let dot_packages = 16 | let open Arg_parser in 17 | let+ () = Common.set_globals_from_flags 18 | and+ project = Common.parse_project in 19 | print_endline @@ Project.dot_dependencies project 20 | ;; 21 | 22 | let subcommand = 23 | let open Command in 24 | subcommand 25 | "dot" 26 | (group 27 | ~doc:"Print graphviz source files." 28 | [ subcommand "artifacts" (singleton ~doc:"Visualize the build plan." dot_artifacts) 29 | ; subcommand 30 | "packages" 31 | (singleton ~doc:"Visualize the package dependency graph." dot_packages) 32 | ]) 33 | ;; 34 | -------------------------------------------------------------------------------- /io/src/infer_linux_distro.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | module Log = Alice_log 4 | 5 | let read_etc_issue () = 6 | let etc_issue = Absolute_path.of_filename_assert_non_root "/etc/issue" in 7 | if File_ops.exists etc_issue then Some (File_ops.read_text_file etc_issue) else None 8 | ;; 9 | 10 | (* Chooses statically-linked tools on Alpine and NixOS. 11 | XXX Is there a more robust way to choose whether statically-linked tools 12 | should be used than the OS distro? *) 13 | let current_distro_requires_statically_linked_tools () = 14 | match read_etc_issue () with 15 | | None -> false 16 | | Some etc_issue -> 17 | let re_nixos = Re.str "NixOS" |> Re.compile in 18 | let re_alpine = Re.str "Alpine" |> Re.compile in 19 | if not (Re.all re_nixos etc_issue |> List.is_empty) 20 | then ( 21 | Log.debug [ Pp.text "Detected distro as NixOS." ]; 22 | true) 23 | else if not (Re.all re_alpine etc_issue |> List.is_empty) 24 | then ( 25 | Log.debug [ Pp.text "Detected distro as Alpine." ]; 26 | true) 27 | else false 28 | ;; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Stephen Sherratt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /manifest/src/alice_manifest.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_error 4 | 5 | let manifest_name = Basename.of_filename "Alice.toml" 6 | 7 | let read_table path = 8 | let filename = Absolute_path.to_filename path in 9 | let channel = In_channel.open_text filename in 10 | let toml_result = Toml.Parser.parse (Lexing.from_channel channel) filename in 11 | In_channel.close channel; 12 | match toml_result with 13 | | `Ok table -> table 14 | | `Error (message, { source; line; column = _; position = _ }) -> 15 | user_exn 16 | [ Pp.text "Failed to parse toml file!\n"; Pp.textf "%s:%d: %s" source line message ] 17 | ;; 18 | 19 | let read_package_manifest ~manifest_path = 20 | read_table manifest_path |> Package.of_toml ~manifest_path_for_messages:manifest_path 21 | ;; 22 | 23 | let read_package_dir ~dir_path = 24 | read_package_manifest ~manifest_path:(dir_path / manifest_name) 25 | ;; 26 | 27 | let write_package_manifest ~manifest_path package = 28 | let package_string = Package.to_toml package |> Toml.Printer.string_of_table in 29 | Alice_io.File_ops.write_text_file manifest_path package_string 30 | ;; 31 | -------------------------------------------------------------------------------- /manifest/src/dependencies.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | open Alice_hierarchy 4 | include Alice_package_meta.Dependencies 5 | 6 | let of_toml ~manifest_path_for_messages toml = 7 | Toml.Types.Table.to_list toml 8 | |> List.map ~f:(fun (key, value) -> 9 | let package_name = 10 | match 11 | Alice_package_meta.Package_name.of_string_res (Toml.Types.Table.Key.to_string key) 12 | with 13 | | Ok package_name -> package_name 14 | | Error pps -> 15 | user_exn 16 | (Pp.textf 17 | "Error while parsing toml file %S:\n" 18 | (Absolute_path.to_filename manifest_path_for_messages) 19 | :: pps) 20 | in 21 | Dependency.of_toml ~manifest_path_for_messages ~name:package_name value) 22 | |> of_list 23 | |> function 24 | | Ok t -> t 25 | | Error (`Duplicate_name name) -> 26 | user_exn 27 | [ Pp.textf 28 | "Duplicate package name in dependencies: %s" 29 | (Alice_package_meta.Package_name.to_string name) 30 | ] 31 | ;; 32 | 33 | let to_toml t = Toml.Types.Table.of_list (to_list t |> List.map ~f:Dependency.to_toml) 34 | -------------------------------------------------------------------------------- /package_meta/src/package_id.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | module T = struct 4 | type t = 5 | { name : Package_name.t 6 | ; version : Semantic_version.t 7 | } 8 | 9 | let to_dyn { name; version } = 10 | Dyn.record 11 | [ "name", Package_name.to_dyn name; "version", Semantic_version.to_dyn version ] 12 | ;; 13 | 14 | let equal t { name; version } = 15 | Package_name.equal t.name name && Semantic_version.equal t.version version 16 | ;; 17 | 18 | let compare t { name; version } = 19 | let open Compare in 20 | let= () = Package_name.compare t.name name in 21 | let= () = Semantic_version.compare t.version version in 22 | 0 23 | ;; 24 | end 25 | 26 | include T 27 | module Set = Set.Make (T) 28 | module Map = Map.Make (T) 29 | 30 | let name { name; _ } = name 31 | let version { version; _ } = version 32 | 33 | let name_dash_version_string { name; version } = 34 | String.concat 35 | ~sep:"-" 36 | [ Package_name.to_string name; Semantic_version.to_string version ] 37 | ;; 38 | 39 | let name_v_version_string { name; version } = 40 | sprintf "%s v%s" (Package_name.to_string name) (Semantic_version.to_string version) 41 | ;; 42 | -------------------------------------------------------------------------------- /io/src/process.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | module Status : sig 4 | type t = 5 | | Exited of int 6 | | Signaled of int 7 | | Stopped of int 8 | 9 | val to_dyn : t -> Dyn.t 10 | val panic_unless_exit_0 : t -> unit 11 | end 12 | 13 | module Blocking : sig 14 | val run 15 | : ?stdin:Unix.file_descr 16 | -> ?stdout:Unix.file_descr 17 | -> ?stderr:Unix.file_descr 18 | -> string 19 | -> args:string list 20 | -> env:Env.t 21 | -> (Status.t, [ `Prog_not_available ]) result 22 | 23 | val run_capturing_stdout_lines 24 | : ?stdin:Unix.file_descr 25 | -> ?stderr:Unix.file_descr 26 | -> string 27 | -> args:string list 28 | -> env:Env.t 29 | -> (Status.t * string list, [ `Prog_not_available ]) result 30 | 31 | val run_command 32 | : ?stdin:Unix.file_descr 33 | -> ?stdout:Unix.file_descr 34 | -> ?stderr:Unix.file_descr 35 | -> Command.t 36 | -> (Status.t, [ `Prog_not_available ]) result 37 | 38 | val run_command_capturing_stdout_lines 39 | : ?stdin:Unix.file_descr 40 | -> ?stderr:Unix.file_descr 41 | -> Command.t 42 | -> (Status.t * string list, [ `Prog_not_available ]) result 43 | end 44 | -------------------------------------------------------------------------------- /ui/src/alice_ui.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val set_mode : [ `Standard | `Quiet ] -> unit 5 | 6 | (** Indicate that when converting paths to strings with this module's 7 | [path_to_string] function, separate path components with "/" rather than 8 | the current system's path separator. *) 9 | val set_normalized_paths : unit -> unit 10 | 11 | type message 12 | 13 | val print : message -> unit 14 | val println : message -> unit 15 | 16 | (** Returns a thunk that prints a message the first time it's called. *) 17 | val println_once : message -> unit -> unit 18 | 19 | val print_newline : unit -> unit 20 | 21 | module Styles : sig 22 | val success : Ansi_style.t 23 | end 24 | 25 | val absolute_path_to_string : _ Absolute_path.t -> string 26 | val basename_to_string : Basename.t -> string 27 | val raw_message : ?style:Ansi_style.t -> string -> message 28 | 29 | type verb = 30 | [ `Fetching 31 | | `Unpacking 32 | | `Compiling 33 | | `Running 34 | | `Creating 35 | | `Removing 36 | | `Finished 37 | ] 38 | 39 | val verb_message : ?verb_style:Ansi_style.t -> verb -> string -> message 40 | val done_message : ?style:Ansi_style.t -> unit -> message 41 | -------------------------------------------------------------------------------- /engine/src/module_name.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package 3 | open Alice_hierarchy 4 | 5 | type t = string 6 | 7 | let to_string_uppercase_first_letter t = t 8 | 9 | let string_split_on_first string = 10 | let length = String.length string in 11 | let first = String.sub string ~pos:0 ~len:1 in 12 | let rest = String.sub string ~pos:1 ~len:(length - 1) in 13 | first, rest 14 | ;; 15 | 16 | let basename_without_extension t = 17 | let first, rest = string_split_on_first t in 18 | let first_lower = String.lowercase_ascii first in 19 | String.cat first_lower rest |> Basename.of_filename 20 | ;; 21 | 22 | let of_package_name package_name = 23 | let package_name_s = Package_name.to_string package_name in 24 | let first, rest = string_split_on_first package_name_s in 25 | let first_upper = String.uppercase_ascii first in 26 | String.cat first_upper rest 27 | ;; 28 | 29 | let internal_modules package_name = 30 | let package_name_s = Package_name.to_string package_name in 31 | String.cat "Internal_modules_of_" package_name_s 32 | ;; 33 | 34 | let public_interface_to_open package_name = 35 | let package_name_s = Package_name.to_string package_name in 36 | String.cat "Public_interface_to_open_of_" package_name_s 37 | ;; 38 | -------------------------------------------------------------------------------- /alice.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Radical OCaml build system and environment manager" 4 | description: 5 | "Alice is a radical, experimental OCaml build system, package manager, and toolchain manager for Windows, macOS, and Linux. Its goal is to allow anyone to program in OCaml with as little friction as possible." 6 | maintainer: ["Stephen Sherratt "] 7 | authors: ["Stephen Sherratt "] 8 | license: "MIT" 9 | homepage: "https://alicecaml.org" 10 | bug-reports: "https://github.com/alicecaml/alice/issues" 11 | depends: [ 12 | "dune" {>= "3.20"} 13 | "ocaml" {>= "5.1"} 14 | "climate" {>= "0.9"} 15 | "sha" 16 | "dyn" 17 | "fileutils" 18 | "pp" 19 | "re" {>= "1.12"} 20 | "xdg" 21 | "toml" {>= "7"} 22 | "odoc" {with-doc} 23 | ] 24 | dev-repo: "git+https://github.com/alicecaml/alice.git" 25 | x-maintenance-intent: ["(latest)"] 26 | build: [ 27 | ["dune" "subst"] {dev} 28 | [ 29 | "dune" 30 | "build" 31 | "-p" 32 | name 33 | "-j" 34 | jobs 35 | "@install" 36 | # Tests depend on the compile error format, so only run tests for a single 37 | # compiler version. 38 | "@runtest" {with-test & ocaml:version >= "5.3" & ocaml:version < "5.4"} 39 | "@doc" {with-doc} 40 | ] 41 | ] 42 | -------------------------------------------------------------------------------- /io/src/fetch.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | module Log = Alice_log 4 | 5 | let curl env ~url ~output_file = 6 | let args = 7 | [ "-L" (* --location: Handle the case when a page has moved. *) 8 | ; "-S" (* --show-error: Show an error when the download fails. *) 9 | ; "-#" (* --progress-bar: Show a progress bar *) 10 | ; "-o" (* --output: Store the result in a file *) 11 | ; Absolute_path.to_filename output_file 12 | ; "--" 13 | ; url 14 | ] 15 | in 16 | Command.create "curl" ~args env 17 | ;; 18 | 19 | let wget env ~url ~output_file = 20 | let args = [ "-O"; Absolute_path.to_filename output_file; url ] in 21 | Command.create "wget" ~args env 22 | ;; 23 | 24 | let fetch env ~url ~output_file = 25 | Log.info [ Pp.textf "Downloading %s to %s" url (Absolute_path.to_filename output_file) ]; 26 | match curl env ~url ~output_file |> Process.Blocking.run_command with 27 | | Ok (Process.Status.Exited 0) -> 28 | assert (Sys.file_exists (Absolute_path.to_filename output_file)); 29 | () 30 | | _ -> 31 | (match wget env ~url ~output_file |> Process.Blocking.run_command with 32 | | Ok (Process.Status.Exited 0) -> 33 | assert (Sys.file_exists (Absolute_path.to_filename output_file)); 34 | () 35 | | _ -> Alice_error.panic [ Pp.textf "Unable to download: %s" url ]) 36 | ;; 37 | -------------------------------------------------------------------------------- /stdlib/src/map.ml: -------------------------------------------------------------------------------- 1 | include MoreLabels.Map 2 | 3 | module type S = sig 4 | include S 5 | 6 | val to_dyn : 'a Dyn.builder -> 'a t -> Dyn.t 7 | val of_list : (key * 'a) list -> ('a t, key * 'a * 'a) Result.t 8 | val of_list_exn : (key * 'a) list -> 'a t 9 | val keys : 'a t -> key list 10 | val values : 'a t -> 'a list 11 | end 12 | 13 | module type Key = sig 14 | include OrderedType 15 | 16 | val to_dyn : t -> Dyn.t 17 | end 18 | 19 | module Make (Key : Key) : S with type key = Key.t = struct 20 | include MoreLabels.Map.Make (struct 21 | type t = Key.t 22 | 23 | let compare = Key.compare 24 | end) 25 | 26 | let to_dyn f t = Dyn.Map (to_list t |> List.map ~f:(fun (k, v) -> Key.to_dyn k, f v)) 27 | 28 | let of_list = 29 | let rec loop acc = function 30 | | [] -> Result.Ok acc 31 | | (k, v) :: l -> 32 | (match find_opt k acc with 33 | | None -> loop (add acc ~key:k ~data:v) l 34 | | Some v_old -> Error (k, v_old, v)) 35 | in 36 | fun l -> loop empty l 37 | ;; 38 | 39 | let of_list_exn list = 40 | match of_list list with 41 | | Ok t -> t 42 | | Error _ -> raise (Invalid_argument "list contains duplicate key") 43 | ;; 44 | 45 | let keys t = to_seq t |> Seq.map ~f:fst |> List.of_seq 46 | let values t = to_seq t |> Seq.map ~f:snd |> List.of_seq 47 | end 48 | -------------------------------------------------------------------------------- /io/src/temp_dir.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | let rng = lazy (Random.State.make_self_init ()) 5 | 6 | (* Make a new directory in the system's temporary directory returning its 7 | path. Does not attempt to clean up after itself. *) 8 | let mkdir ~prefix ~suffix = 9 | let perms = 0o755 in 10 | let rng = Lazy.force rng in 11 | let temp_dir_base = Filename.get_temp_dir_name () in 12 | let rec loop () = 13 | let max_8_hex_digit_int = 14 | (* Don't use 0xFFFFFFFF because on 32-bit machines OCaml uses 31-bit 15 | integers. *) 16 | 0x7FFFFFFF 17 | in 18 | let rand_int = Random.State.bits rng land max_8_hex_digit_int in 19 | let dir_name = sprintf "%s%08x%s" prefix rand_int suffix in 20 | let path = Filename.concat temp_dir_base dir_name in 21 | if Sys.file_exists path then loop () else path 22 | in 23 | let path = Absolute_path.of_filename_assert_non_root (loop ()) in 24 | (* Convert to a path before creating it in case the path is invalid. This 25 | would indicate a bug, but we'd rather crash before creating the directory 26 | than after. *) 27 | Unix.mkdir (Absolute_path.to_filename path) perms; 28 | path 29 | ;; 30 | 31 | let with_ ~prefix ~suffix ~f = 32 | let path = mkdir ~prefix ~suffix in 33 | let ret = f path in 34 | File_ops.rm_rf path; 35 | ret 36 | ;; 37 | -------------------------------------------------------------------------------- /alice/src/alice.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Climate 3 | module Tools = Tools 4 | 5 | let version = "0.3-dev" 6 | 7 | let command = 8 | let tagline = "Alice is a build system for OCaml projects." in 9 | let default_arg_parser = 10 | let open Arg_parser in 11 | let+ version_ = flag [ "version" ] ~doc:"Print the version and exit." in 12 | if version_ 13 | then print_endline (sprintf "Alice %s" version) 14 | else ( 15 | print_endline 16 | (sprintf 17 | {|%s 18 | 19 | Run `alice --help` for usage information.|} 20 | tagline); 21 | exit 1) 22 | in 23 | let open Command in 24 | group 25 | ~doc:tagline 26 | ~default_arg_parser 27 | [ Build.subcommand 28 | ; Clean.subcommand 29 | ; Dot.subcommand 30 | ; New.subcommand 31 | ; Tools.subcommand 32 | ; Run.subcommand 33 | ; subcommand "help" help 34 | ; Internal.subcommand 35 | ] 36 | ;; 37 | 38 | Internal.command_for_completion_script := Some command 39 | 40 | let () = 41 | let help_style = 42 | let open Help_style in 43 | { default with margin = Some 100 } 44 | in 45 | match Command.run command ~program_name:(Literal "alice") ~version ~help_style with 46 | | () -> () 47 | | exception Alice_error.User_error.E error -> 48 | Alice_error.User_error.eprint (error @ [ Pp.newline ]); 49 | exit 1 50 | ;; 51 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ### Changed 6 | 7 | - Follow the XDG convention when installing tools. 8 | 9 | ## 0.2.0 10 | 11 | ### Added 12 | 13 | - Generate files necessary to support ocamllsp. 14 | 15 | ### Changed 16 | 17 | - Switch to github for hosting tool archives. 18 | - Display progress bar while downloading tools. 19 | 20 | ## 0.1.3 21 | 22 | ### Added 23 | 24 | - Add a patch that replaces the locked compiler version with a template string to simplify packaging 25 | 26 | ## 0.1.2 27 | 28 | ### Added 29 | 30 | - Release a zip archive on windows with just alice.exe 31 | 32 | ## 0.1.1 33 | 34 | ### Fixed 35 | 36 | - External commands are run in an environment containing the OCaml toolchain if 37 | Alice has installed an OCaml toolchain. This fixes an issue where the OCaml 38 | compiler couldn't find flexlink on Windows unless the current Alice root was 39 | in the PATH variable. (#2, fixes #1) 40 | - Fixed compile error on 32-bit machines due to unrepresentable integer literal. 41 | - Wrap text in help messages. 42 | 43 | ## 0.1.0 44 | 45 | ### Added 46 | 47 | - Initial release of Alice. Multi-file packages with dependencies specified 48 | within the local filesystem can be built and incrementally rebuilt. 49 | Dependency and build graphs can be visualized with graphviz. The OCaml 50 | toolchain and some development tools can be installed user-wide. 51 | -------------------------------------------------------------------------------- /ocaml_compiler/src/ocaml_compiler.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | type t 5 | 6 | (** [create filename env] creates a [t] representing a compiler whose 7 | executable is at [filename]. When the compiler is executed it will run in 8 | the specified environment [env]. This allows changes to the PATH variable 9 | to captured in [t]. *) 10 | val create : Filename.t -> Env.t -> t 11 | 12 | val filename : t -> Filename.t 13 | val env : t -> Env.t 14 | val command : t -> args:string list -> Command.t 15 | 16 | module Deps : sig 17 | type t = 18 | { output : Basename.t (** The path to the compiled output of a file. *) 19 | ; inputs : Basename.t list 20 | (** The files which must be generated before compiling a file. *) 21 | } 22 | 23 | val to_dyn : t -> Dyn.t 24 | end 25 | 26 | (** [depends_native file] takes the path to a source or interface file and returns 27 | the path to file which will contain its compiled output ([Deps.output]), as 28 | well as the files which must be generated before compiling the given file 29 | ([Deps.inputs]). Returned paths are relative to the directory containing 30 | [file]. *) 31 | val depends_native : t -> Absolute_path.non_root_t -> Deps.t 32 | 33 | (** Returns the "standard_library" path from the output of running the compiler 34 | with "-config". *) 35 | val standard_library : t -> Absolute_path.non_root_t 36 | -------------------------------------------------------------------------------- /integration_tests/new.t: -------------------------------------------------------------------------------- 1 | Exercise initializing a new executable project. 2 | 3 | $ alice new hello --normalize-paths 4 | Creating new executable package "hello" in hello 5 | $ cd hello 6 | 7 | $ alice run --normalize-paths 8 | Compiling hello v0.1.0 9 | Running build/packages/hello-0.1.0/debug/executable/hello 10 | 11 | Hello, World! 12 | 13 | Test some error cases for `alice new`: 14 | $ alice new hello --path . --normalize-paths 15 | Creating new executable package "hello" in . 16 | 17 | Refusing to create project because destination directory exists and contains project manifest (Alice.toml). 18 | Delete this file before proceeding. 19 | [1] 20 | 21 | $ rm Alice.toml 22 | 23 | $ alice new hello --path . --normalize-paths 24 | Creating new executable package "hello" in . 25 | 26 | Refusing to create project because destination directory exists and contains src directory (src). 27 | Delete this directory before proceeding. 28 | [1] 29 | 30 | $ rm -r src 31 | $ touch src 32 | 33 | $ alice new hello --path . --normalize-paths 34 | Creating new executable package "hello" in . 35 | 36 | Refusing to create project because destination directory exists and contains a file named "src" (src). 37 | Delete this file before proceeding. 38 | [1] 39 | 40 | $ rm src 41 | 42 | $ alice new hello --path . --normalize-paths 43 | Creating new executable package "hello" in . 44 | -------------------------------------------------------------------------------- /stdlib/src/filename.mli: -------------------------------------------------------------------------------- 1 | include module type of Stdlib.Filename 2 | 3 | type t = string 4 | 5 | val to_dyn : t -> Dyn.t 6 | 7 | module Set : Set.S with type elt = t 8 | module Map : Map.S with type key = t 9 | 10 | val equal : t -> t -> bool 11 | val compare : t -> t -> int 12 | val has_extension : t -> ext:string -> bool 13 | 14 | (** Returns [None] if the path doesn't have an extension. *) 15 | val replace_extension : t -> ext:string -> t option 16 | 17 | val add_extension : t -> ext:string -> t 18 | 19 | module Components : sig 20 | type nonrec t = 21 | | Relative of t list 22 | | Absolute of 23 | { root : t 24 | ; rest : t list 25 | } 26 | end 27 | 28 | (** Split a path into the sequence of names that make it up. The sequence of 29 | components will never be empty, either begining with the filesystem root or 30 | the current directory. *) 31 | val to_components : t -> Components.t 32 | 33 | (** The two paths are made up of equal components, even if they differ on their 34 | path separators. *) 35 | val equal_components : t -> t -> bool 36 | 37 | val chop_prefix_opt : prefix:t -> t -> t option 38 | val chop_prefix : prefix:t -> t -> t 39 | 40 | (** Is this a filesystem root. On unix this is true only for the system's root 41 | directory. On windows this is true for the root of any drive. *) 42 | val is_root : t -> bool 43 | 44 | val normalize : t -> (t, [ `Would_traverse_beyond_the_start_of_absolute_path ]) result 45 | -------------------------------------------------------------------------------- /io/src/file_ops.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | val rm_rf : Absolute_path.non_root_t -> unit 5 | val mkdir_p : _ Absolute_path.t -> unit 6 | 7 | (** [src] and [dst] must be paths to existing directories. Each file under 8 | [src] is moved (renamed) to the same relative path under [dst], creating 9 | intermediate directories as needed. The existing directory structure under 10 | [dst] is replaced, though files will be replaced if they are at the same 11 | location as a correspondingly-named file under [src]. *) 12 | val recursive_move_between_dirs 13 | : src:Absolute_path.non_root_t 14 | -> dst:_ Absolute_path.t 15 | -> unit 16 | 17 | val cp_rf : src:Absolute_path.non_root_t -> dst:_ Absolute_path.t -> unit 18 | val cp_f : src:Absolute_path.non_root_t -> dst:_ Absolute_path.t -> unit 19 | val exists : _ Absolute_path.t -> bool 20 | val is_directory : _ Absolute_path.t -> bool 21 | 22 | val with_out_channel 23 | : Absolute_path.non_root_t 24 | -> mode:[ `Text | `Bin ] 25 | -> f:(out_channel -> 'a) 26 | -> 'a 27 | 28 | val write_text_file : Absolute_path.non_root_t -> string -> unit 29 | 30 | val with_in_channel 31 | : Absolute_path.non_root_t 32 | -> mode:[ `Text | `Bin ] 33 | -> f:(in_channel -> 'a) 34 | -> 'a 35 | 36 | val read_text_file : Absolute_path.non_root_t -> string 37 | val mtime : Absolute_path.non_root_t -> float 38 | val symlink : src:Absolute_path.non_root_t -> dst:Absolute_path.non_root_t -> unit 39 | -------------------------------------------------------------------------------- /scripts/build-alice-x86_64-linux-musl-static.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.22.0 AS builder 2 | 3 | RUN apk update && apk add \ 4 | build-base \ 5 | musl-dev \ 6 | curl \ 7 | wget \ 8 | git \ 9 | bash \ 10 | ; 11 | 12 | # Install the OCaml compiler (via alice) 13 | RUN curl -fsSL https://github.com/alicecaml/alice-install/releases/download/v5/install.sh | sh -s -- 0.3.0-alpha2 --global /usr --no-prompt --install-tools --no-update-shell-config --install-compiler-only 14 | 15 | # Install Dune 16 | RUN curl -4fsSL https://github.com/ocaml-dune/dune-bin-install/releases/download/v3/install.sh | sh -s 3.20.2 --install-root /usr --no-update-shell-config 17 | 18 | RUN mkdir /app 19 | WORKDIR /app 20 | COPY --chmod=0755 . . 21 | 22 | ENV DUNE_PROFILE=static 23 | RUN dune build 24 | 25 | RUN (git describe --exact-match --tags || git rev-parse HEAD) | cat > version.txt 26 | RUN uname -m > arch.txt 27 | RUN echo alice-$(cat version.txt)-$(cat arch.txt)-linux-musl-static > name.txt 28 | RUN cp -rvL _build/install/default $(cat name.txt) 29 | RUN chmod a+w $(cat name.txt)/bin/alice 30 | RUN strip $(cat name.txt)/bin/alice 31 | RUN chmod a-w $(cat name.txt)/bin/alice 32 | RUN mkdir -p $(cat name.txt)/share/bash-completion/completions 33 | RUN scripts/generate_minified_bash_completion_script.sh > $(cat name.txt)/share/bash-completion/completions/alice 34 | RUN tar czf $(cat name.txt).tar.gz $(cat name.txt) 35 | RUN dune clean 36 | 37 | FROM scratch 38 | COPY --from=builder /app . 39 | -------------------------------------------------------------------------------- /package/src/dependency_graph.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_package_meta 3 | 4 | (** The type arguments determine what type of package is at the root of the 5 | dependency graph. This is mostly for convenience, since then a dependency 6 | graph completely captures all the things that need to be built in order to 7 | build the root package. *) 8 | type ('exe, 'lib) t 9 | 10 | val to_dyn : (_, _) t -> Dyn.t 11 | val compute : ('exe, 'lib) Package.Typed.t -> ('exe, 'lib) t 12 | val dot : (_, _) t -> string 13 | 14 | module Package_with_deps : sig 15 | type ('exe, 'lib) t 16 | type lib_only_t = (Type_bool.false_t, Type_bool.true_t) t 17 | 18 | val package_typed : ('exe, 'lib) t -> ('exe, 'lib) Package.Typed.t 19 | val package : (_, _) t -> Package.t 20 | val name : (_, _) t -> Package_name.t 21 | val id : (_, _) t -> Package_id.t 22 | val immediate_deps_in_dependency_order : (_, _) t -> lib_only_t list 23 | 24 | val transitive_dependency_closure_excluding_package 25 | : (_, _) t 26 | -> Package.Typed.lib_only_t list 27 | end 28 | 29 | (** Returns the transitive closure of dependencies excluding the package at the 30 | root of the dependency graph. This lets us statically know that each 31 | dependency is a library package, while the root package may not be. *) 32 | val transitive_dependency_closure_in_dependency_order 33 | : (_, _) t 34 | -> Package_with_deps.lib_only_t list 35 | 36 | val root_package_with_deps : ('exe, 'lib) t -> ('exe, 'lib) Package_with_deps.t 37 | -------------------------------------------------------------------------------- /assets/packages.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | hello v0.1.0 14 | 15 | hello v0.1.0 16 | 17 | 18 | 19 | foo v0.1.0 20 | 21 | foo v0.1.0 22 | 23 | 24 | 25 | hello v0.1.0->foo v0.1.0 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /print/src/raw.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | let tag_handler fmt tag pp = 4 | Ansi_style.pp_with_style tag fmt ~f:(fun fmt -> Pp.to_fmt fmt pp) 5 | ;; 6 | 7 | let pp_print_gen fmt pp = 8 | Format.open_box 0; 9 | Pp.to_fmt_with_tags fmt pp ~tag_handler; 10 | Format.pp_print_flush fmt (); 11 | Format.close_box () 12 | ;; 13 | 14 | let pps_print_gen fmt pps = 15 | Format.open_box 0; 16 | List.iter pps ~f:(fun pp -> Pp.to_fmt_with_tags fmt pp ~tag_handler); 17 | Format.pp_print_flush fmt (); 18 | Format.close_box () 19 | ;; 20 | 21 | let pp_println_gen fmt pp = 22 | Format.open_box 0; 23 | Pp.to_fmt_with_tags fmt pp ~tag_handler; 24 | Format.pp_print_newline fmt (); 25 | Format.pp_print_flush fmt (); 26 | Format.close_box () 27 | ;; 28 | 29 | let pps_println_gen fmt pps = 30 | Format.open_box 0; 31 | List.iter pps ~f:(fun pp -> Pp.to_fmt_with_tags fmt pp ~tag_handler); 32 | Format.pp_print_newline fmt (); 33 | Format.pp_print_flush fmt (); 34 | Format.close_box () 35 | ;; 36 | 37 | let formatter = 38 | lazy 39 | (let f = Format.std_formatter in 40 | Format.pp_set_margin f 9999; 41 | f) 42 | ;; 43 | 44 | let pp_print = pp_print_gen (Lazy.force formatter) 45 | let pp_println = pp_println_gen (Lazy.force formatter) 46 | let pps_print = pps_print_gen (Lazy.force formatter) 47 | let pps_println = pps_println_gen (Lazy.force formatter) 48 | let pp_eprint = pp_print_gen (Lazy.force formatter) 49 | let pp_eprintln = pp_println_gen (Lazy.force formatter) 50 | let pps_eprint = pps_print_gen (Lazy.force formatter) 51 | let pps_eprintln = pps_println_gen (Lazy.force formatter) 52 | -------------------------------------------------------------------------------- /manifest/src/package.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | include Alice_package_meta.Package_meta 3 | 4 | module Keys = struct 5 | module Key = Toml.Types.Table.Key 6 | 7 | let package = Key.of_string "package" 8 | let dependencies = Key.of_string "dependencies" 9 | let all = [ package; dependencies ] 10 | end 11 | 12 | let of_toml ~manifest_path_for_messages toml_table = 13 | Fields.check_for_extraneous_fields 14 | ~manifest_path_for_messages 15 | ~all_keys:Keys.all 16 | toml_table; 17 | let metadata_table = 18 | (* General metadata is under a key "package" in the manifet *) 19 | Fields.parse_field ~manifest_path_for_messages Keys.package toml_table ~f:(function 20 | | Toml.Types.TTable table -> `Ok table 21 | | _ -> `Expected "table") 22 | in 23 | let id = Package_id.of_toml ~manifest_path_for_messages metadata_table in 24 | let dependencies = 25 | Fields.parse_field_opt 26 | ~manifest_path_for_messages 27 | Keys.dependencies 28 | toml_table 29 | ~f:(function 30 | | Toml.Types.TTable dependencies -> 31 | `Ok (Dependencies.of_toml ~manifest_path_for_messages dependencies) 32 | | _ -> `Expected "table") 33 | in 34 | create ~id ~dependencies 35 | ;; 36 | 37 | let to_toml t = 38 | let fields = [ Keys.package, Toml.Types.TTable (Package_id.to_toml (id t)) ] in 39 | let fields = 40 | match dependencies_ t with 41 | | Some dependencies -> 42 | fields 43 | @ [ Keys.dependencies, Toml.Types.TTable (Dependencies.to_toml dependencies) ] 44 | | None -> fields 45 | in 46 | Toml.Types.Table.of_list fields 47 | ;; 48 | -------------------------------------------------------------------------------- /stdlib/src/env.ml: -------------------------------------------------------------------------------- 1 | module Variable = struct 2 | type t = 3 | { name : string 4 | ; value : string 5 | } 6 | 7 | let parse s = 8 | match String.lsplit2 s ~on:'=' with 9 | | None -> { name = s; value = "" } 10 | | Some (name, value) -> { name; value } 11 | ;; 12 | 13 | let to_string { name; value } = Printf.sprintf "%s=%s" name value 14 | let equal t { name; value } = String.equal t.name name && String.equal t.value value 15 | 16 | let to_dyn { name; value } = 17 | Dyn.record [ "name", Dyn.string name; "value", Dyn.string value ] 18 | ;; 19 | end 20 | 21 | type t = Variable.t list 22 | type raw = string array 23 | 24 | let empty = [] 25 | let equal = List.equal ~eq:Variable.equal 26 | let to_dyn = Dyn.list Variable.to_dyn 27 | let of_raw raw = Array.to_list raw |> List.map ~f:Variable.parse 28 | let to_raw t = List.map t ~f:Variable.to_string |> Array.of_list 29 | 30 | let get_opt t ~name = 31 | List.find_map t ~f:(fun (variable : Variable.t) -> 32 | if String.equal variable.name name then Some variable.value else None) 33 | ;; 34 | 35 | let find_name_opt t ~f = 36 | List.find_map t ~f:(fun (variable : Variable.t) -> 37 | if f variable.name then Some variable.value else None) 38 | ;; 39 | 40 | let contains t ~name = 41 | List.exists t ~f:(fun (variable : Variable.t) -> String.equal variable.name name) 42 | ;; 43 | 44 | let set t ~name ~value = 45 | let variable = { Variable.name; value } in 46 | if contains t ~name 47 | then 48 | List.map t ~f:(fun (v : Variable.t) -> 49 | if String.equal v.name name then variable else v) 50 | else variable :: t 51 | ;; 52 | -------------------------------------------------------------------------------- /manifest/src/package_id.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | open Alice_hierarchy 4 | open Alice_package_meta 5 | include Package_id 6 | 7 | module Keys = struct 8 | module Key = Toml.Types.Table.Key 9 | 10 | let name = Key.of_string "name" 11 | let version = Key.of_string "version" 12 | let all = [ name; version ] 13 | end 14 | 15 | let of_toml ~manifest_path_for_messages toml_table = 16 | let error pps = 17 | user_exn 18 | (Pp.textf 19 | "Error while parsing toml file %S:\n" 20 | (Absolute_path.to_filename manifest_path_for_messages) 21 | :: pps) 22 | in 23 | Fields.check_for_extraneous_fields 24 | ~manifest_path_for_messages 25 | ~all_keys:Keys.all 26 | toml_table; 27 | let name = 28 | Fields.parse_field ~manifest_path_for_messages Keys.name toml_table ~f:(function 29 | | Toml.Types.TString name -> `Ok name 30 | | _ -> `Expected "string") 31 | in 32 | let name = 33 | match Package_name.of_string_res name with 34 | | Ok name -> name 35 | | Error pps -> error pps 36 | in 37 | let version = 38 | Fields.parse_field ~manifest_path_for_messages Keys.version toml_table ~f:(function 39 | | Toml.Types.TString version -> `Ok version 40 | | _ -> `Expected "string") 41 | in 42 | let version = Semantic_version.of_string_exn version in 43 | { name; version } 44 | ;; 45 | 46 | let to_toml { name; version } = 47 | let fields = 48 | [ Keys.name, Toml.Types.TString (Package_name.to_string name) 49 | ; Keys.version, Toml.Types.TString (Semantic_version.to_string version) 50 | ] 51 | in 52 | Toml.Types.Table.of_list fields 53 | ;; 54 | -------------------------------------------------------------------------------- /manifest/src/dependency.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | open Alice_hierarchy 4 | open Alice_package_meta 5 | include Dependency 6 | 7 | module Keys = struct 8 | module Key = Toml.Types.Table.Key 9 | 10 | let path = Key.of_string "path" 11 | let all = [ path ] 12 | end 13 | 14 | let of_toml ~manifest_path_for_messages ~name toml_value = 15 | match (toml_value : Toml.Types.value) with 16 | | TTable toml_table -> 17 | Fields.check_for_extraneous_fields 18 | ~manifest_path_for_messages 19 | ~all_keys:Keys.all 20 | toml_table; 21 | let path = 22 | Fields.parse_field ~manifest_path_for_messages Keys.path toml_table ~f:(function 23 | | Toml.Types.TString path -> `Ok (Either_path.of_filename path) 24 | | _ -> `Expected "string") 25 | in 26 | let source = Dependency_source.Local_directory path in 27 | Dependency.create ~name ~source 28 | | other -> 29 | user_exn 30 | [ Pp.textf 31 | "Error while parsing toml file %S:\n" 32 | (Absolute_path.to_filename manifest_path_for_messages) 33 | ; Pp.text "Expected dependency to be a table or string, but instead found:\n" 34 | ; Pp.text (Toml.Printer.string_of_value other) 35 | ] 36 | ;; 37 | 38 | let to_toml t = 39 | let name = Dependency.name t in 40 | let source = Dependency.source t in 41 | let (Dependency_source.Local_directory path) = source in 42 | let table = 43 | [ Keys.path, Toml.Types.TString (Either_path.to_filename path) ] 44 | |> Toml.Types.Table.of_key_values 45 | in 46 | let rhs = Toml.Types.TTable table in 47 | let lhs = Toml.Types.Table.Key.of_string (Package_name.to_string name) in 48 | lhs, rhs 49 | ;; 50 | -------------------------------------------------------------------------------- /scripts/build-alice-aarch64-linux-musl-static.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.22.0 AS builder 2 | 3 | RUN apk update && apk add \ 4 | build-base \ 5 | musl-dev \ 6 | curl \ 7 | wget \ 8 | git \ 9 | bash \ 10 | opam \ 11 | ; 12 | 13 | # Install the OCaml compiler (via alice) 14 | RUN curl -fsSL https://github.com/alicecaml/alice-install/releases/download/v5/install.sh | sh -s -- 0.3.0-alpha2 --global /usr --no-prompt --install-tools --no-update-shell-config --install-compiler-only 15 | 16 | RUN mkdir /app 17 | WORKDIR /app 18 | COPY --chmod=0755 . . 19 | 20 | RUN opam init --disable-sandbox --auto-setup --bare 21 | 22 | # There's no Dune binary distro available for aarch64 linux, so install it with Opam instead. 23 | RUN opam switch create . --empty 24 | RUN opam repo add alice git+https://github.com/alicecaml/alice-opam-repo --all-switches 25 | RUN opam update 26 | RUN opam install -y ocaml-system.5.3.1+relocatable dune 27 | ENV DUNE_PROFILE=static 28 | RUN opam exec dune build 29 | 30 | RUN (git describe --exact-match --tags || git rev-parse HEAD) | cat > version.txt 31 | RUN uname -m > arch.txt 32 | RUN echo alice-$(cat version.txt)-$(cat arch.txt)-linux-musl-static > name.txt 33 | RUN cp -rvL _build/install/default $(cat name.txt) 34 | RUN chmod a+w $(cat name.txt)/bin/alice 35 | RUN strip $(cat name.txt)/bin/alice 36 | RUN chmod a-w $(cat name.txt)/bin/alice 37 | RUN mkdir -p $(cat name.txt)/share/bash-completion/completions 38 | RUN opam exec scripts/generate_minified_bash_completion_script.sh > $(cat name.txt)/share/bash-completion/completions/alice 39 | RUN tar czf $(cat name.txt).tar.gz $(cat name.txt) 40 | RUN opam exec dune clean 41 | 42 | FROM scratch 43 | COPY --from=builder /app . 44 | -------------------------------------------------------------------------------- /engine/src/build_dir.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package 4 | 5 | type t 6 | 7 | val of_path : Absolute_path.non_root_t -> t 8 | val path : t -> Absolute_path.non_root_t 9 | val package_ocamldeps_cache_file : t -> Package_id.t -> Absolute_path.non_root_t 10 | val package_base_dir : t -> Package_id.t -> Profile.t -> Absolute_path.non_root_t 11 | 12 | val package_generated_source_dir 13 | : t 14 | -> Package_id.t 15 | -> Profile.t 16 | -> Absolute_path.non_root_t 17 | 18 | val package_private_dir : t -> Package_id.t -> Profile.t -> Absolute_path.non_root_t 19 | val package_public_dir : t -> Package_id.t -> Profile.t -> Absolute_path.non_root_t 20 | 21 | (** Directory for files generated for consumption by LSP. If package internals 22 | were made visible to LSP then it would suggest completions that aren't 23 | valid due to package hygiene. Instead, just the public interface to a 24 | library is made visible to LSP by way of this directory. The regular public 25 | directory can't be used for this purpose because of the generated public 26 | interface. *) 27 | val package_public_for_lsp_dir 28 | : t 29 | -> Package_id.t 30 | -> Profile.t 31 | -> Absolute_path.non_root_t 32 | 33 | val package_executable_dir : t -> Package_id.t -> Profile.t -> Absolute_path.non_root_t 34 | val package_dirs : t -> Package_id.t -> Profile.t -> Absolute_path.non_root_t list 35 | 36 | val package_generated_file_compiled 37 | : t 38 | -> Package_id.t 39 | -> Profile.t 40 | -> Typed_op.Generated_file.Compiled.t 41 | -> Absolute_path.non_root_t 42 | 43 | val package_generated_file 44 | : t 45 | -> Package_id.t 46 | -> Profile.t 47 | -> Typed_op.Generated_file.t 48 | -> Absolute_path.non_root_t 49 | -------------------------------------------------------------------------------- /package_meta/src/package_name.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | 4 | module T = struct 5 | include StdLabels.String 6 | 7 | let to_dyn = Dyn.string 8 | end 9 | 10 | include T 11 | 12 | let equal = String.equal 13 | let compare = String.compare 14 | 15 | module Set = Set.Make (T) 16 | module Map = Map.Make (T) 17 | 18 | let validate s = 19 | let is_valid_first_char = function 20 | | 'a' .. 'z' -> true 21 | | _ -> false 22 | in 23 | let is_valid_later_char = function 24 | | 'a' .. 'z' | '0' .. '9' | '_' -> true 25 | | _ -> false 26 | in 27 | if String.is_empty s 28 | then Error [ Pp.text "Package name may not be empty!" ] 29 | else ( 30 | let first_char = String.get s 0 in 31 | match is_valid_first_char first_char with 32 | | false -> 33 | Error 34 | [ Pp.textf "Package names must start with a lowercase letter. Got: %c" first_char 35 | ] 36 | | true -> 37 | let invalid_char = 38 | String.fold_left s ~init:None ~f:(fun invalid_char char -> 39 | match invalid_char with 40 | | Some _ -> invalid_char 41 | | None -> if is_valid_later_char char then invalid_char else Some char) 42 | in 43 | (match invalid_char with 44 | | Some invalid_char -> 45 | Error 46 | [ Pp.textf 47 | "Package names may consist of only lowercase letters, digits, and \ 48 | underscores. Got: %c" 49 | invalid_char 50 | ] 51 | | None -> Ok ())) 52 | ;; 53 | 54 | let of_string_res s = validate s |> Result.map ~f:(fun () -> s) 55 | 56 | let of_string_exn s = 57 | match validate s with 58 | | Error pps -> user_exn pps 59 | | Ok () -> s 60 | ;; 61 | 62 | let to_string t = t 63 | -------------------------------------------------------------------------------- /stdlib/tests/filename.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Filename 3 | 4 | let%test "is_root" = 5 | if Sys.win32 6 | then List.all [ is_root "C:\\"; is_root "D:\\"; is_root "/" ] 7 | else is_root "/" 8 | ;; 9 | 10 | let%test "normalize_error" = 11 | let f t = 12 | match normalize t with 13 | | Error `Would_traverse_beyond_the_start_of_absolute_path -> true 14 | | _ -> false 15 | in 16 | [ [ f "/.."; f "/a/b/../../.."; f "/../a"; f "/a/b/../../../c" ] 17 | ; (if Sys.win32 then [ f "C:\\.."; f "C:\\a\\..\\.." ] else []) 18 | ] 19 | |> List.concat 20 | |> List.all 21 | ;; 22 | 23 | let%test "normalize" = 24 | let f a b = 25 | let norm = normalize a |> Result.get_ok in 26 | if equal_components norm b 27 | then true 28 | else ( 29 | print_endline 30 | (sprintf "%S should normalize to %S but instead it normalizes to %S" a b norm); 31 | false) 32 | in 33 | List.all 34 | [ f "" "." 35 | ; f "a" "a" 36 | ; f "/" "/" 37 | ; f "/a" "/a" 38 | ; f "a/b" "a/b" 39 | ; f ".." ".." 40 | ; f "." "." 41 | ; f "../.." "../.." 42 | ; f "../a" "../a" 43 | ; f "/." "/" 44 | ; f "a/./b" "a/b" 45 | ; f "a/../b" "b" 46 | ; f "/a/.." "/" 47 | ] 48 | ;; 49 | 50 | let%test "chop_prefix" = 51 | let f ~prefix a b = 52 | let chopped = chop_prefix a ~prefix in 53 | if equal_components chopped b 54 | then true 55 | else ( 56 | print_endline 57 | (sprintf "[chop_prefix %S ~prefix:%S], expected %S, got %S" a prefix b chopped); 58 | false) 59 | in 60 | List.all 61 | [ f "/a" ~prefix:"/" "a" 62 | ; f "/a/b" ~prefix:"/a" "b" 63 | ; f "a/b" ~prefix:"a" "b" 64 | ; f "a" ~prefix:"a" "." 65 | ; f "../a" ~prefix:".." "a" 66 | ; f "a/b/c/d" ~prefix:"a/b" "c/d" 67 | ] 68 | ;; 69 | -------------------------------------------------------------------------------- /manifest/src/fields.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_error 3 | open Alice_hierarchy 4 | module Table = Toml.Types.Table 5 | 6 | let parse_field_opt ~manifest_path_for_messages key toml_table ~f = 7 | Table.find_opt key toml_table 8 | |> Option.map ~f:(fun value -> 9 | match f value with 10 | | `Ok x -> x 11 | | `Expected expected -> 12 | user_exn 13 | [ Pp.textf 14 | "Error while parsing toml file %S:\n" 15 | (Absolute_path.to_filename manifest_path_for_messages) 16 | ; Pp.textf 17 | "Expected field %S to contain a %s, but instead found:\n" 18 | (Table.Key.to_string key) 19 | expected 20 | ; Pp.text (Toml.Printer.string_of_value value) 21 | ]) 22 | ;; 23 | 24 | let parse_field ~manifest_path_for_messages key toml_table ~f = 25 | match parse_field_opt ~manifest_path_for_messages key toml_table ~f with 26 | | Some value -> value 27 | | None -> 28 | user_exn 29 | [ Pp.textf 30 | "Error while parsing toml file %S:\nCan't find required field %S." 31 | (Absolute_path.to_filename manifest_path_for_messages) 32 | (Table.Key.to_string key) 33 | ] 34 | ;; 35 | 36 | let check_for_extraneous_fields ~manifest_path_for_messages ~all_keys toml_table = 37 | let all_keys = List.map all_keys ~f:Table.Key.to_string |> String.Set.of_list in 38 | Table.iter 39 | (fun key _ -> 40 | let key = Table.Key.to_string key in 41 | match String.Set.mem key all_keys with 42 | | false -> 43 | user_exn 44 | [ Pp.textf 45 | "Error while parsing toml file %S:\n" 46 | (Absolute_path.to_filename manifest_path_for_messages) 47 | ; Pp.textf "Unexpected key: %s" key 48 | ] 49 | | true -> ()) 50 | toml_table 51 | ;; 52 | -------------------------------------------------------------------------------- /scripts/build-alice-aarch64-linux-gnu.dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble-20250529 AS builder 2 | 3 | RUN apt-get update -y && apt-get upgrade -y && apt-get install -y \ 4 | build-essential \ 5 | curl \ 6 | wget \ 7 | git \ 8 | bash \ 9 | unzip \ 10 | ; 11 | 12 | # Install the OCaml compiler (via alice) 13 | RUN curl -fsSL https://github.com/alicecaml/alice-install/releases/download/v5/install.sh | sh -s -- 0.3.0-alpha2 --global /usr --no-prompt --install-tools --no-update-shell-config --install-compiler-only 14 | 15 | # Install Opam. Don't use the apt package because it would interfere with alice's OCaml binary distribution. 16 | RUN curl -fsSL https://opam.ocaml.org/install.sh > install_opam.sh && yes '' | sh install_opam.sh 17 | 18 | RUN mkdir /app 19 | WORKDIR /app 20 | COPY --chmod=0755 . . 21 | 22 | RUN opam init --disable-sandbox --auto-setup --bare 23 | 24 | # There's no Dune binary distro available for aarch64 linux, so install it with Opam instead. 25 | RUN opam switch create . --empty 26 | RUN opam repo add alice git+https://github.com/alicecaml/alice-opam-repo --all-switches 27 | RUN opam update 28 | RUN opam install -y ocaml-system.5.3.1+relocatable dune 29 | RUN opam exec dune build 30 | 31 | RUN (git describe --exact-match --tags || git rev-parse HEAD) | cat > version.txt 32 | RUN uname -m > arch.txt 33 | RUN echo alice-$(cat version.txt)-$(cat arch.txt)-linux-gnu > name.txt 34 | RUN cp -rvL _build/install/default $(cat name.txt) 35 | RUN chmod a+w $(cat name.txt)/bin/alice 36 | RUN strip $(cat name.txt)/bin/alice 37 | RUN chmod a-w $(cat name.txt)/bin/alice 38 | RUN mkdir -p $(cat name.txt)/share/bash-completion/completions 39 | RUN opam exec scripts/generate_minified_bash_completion_script.sh > $(cat name.txt)/share/bash-completion/completions/alice 40 | RUN tar czf $(cat name.txt).tar.gz $(cat name.txt) 41 | RUN opam exec dune clean 42 | 43 | FROM scratch 44 | COPY --from=builder /app . 45 | -------------------------------------------------------------------------------- /io/src/read_hierarchy.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | let entries dir_path = 5 | let dir_handle = Unix.opendir dir_path in 6 | let ret = 7 | Seq.of_dispenser (fun () -> 8 | match Unix.readdir dir_handle with 9 | | entry -> Some entry 10 | | exception End_of_file -> None) 11 | |> List.of_seq 12 | in 13 | Unix.closedir dir_handle; 14 | ret 15 | ;; 16 | 17 | (* Lists the names (not the paths) of files from the given directory. *) 18 | let entries_without_current_or_parent dir_path = 19 | entries dir_path 20 | |> List.filter ~f:(function 21 | | "." | ".." -> false 22 | | _ -> true) 23 | ;; 24 | 25 | let read base_path = 26 | match Sys.file_exists (Absolute_path.to_filename base_path) with 27 | | false -> Error `Not_found 28 | | true -> 29 | let rec loop path = 30 | let stat = Unix.lstat (Absolute_path.to_filename path) in 31 | let kind = 32 | match stat.st_kind with 33 | | S_REG -> File_non_root.Regular 34 | | S_LNK -> Link 35 | | S_DIR -> 36 | let files = 37 | entries_without_current_or_parent (Absolute_path.to_filename path) 38 | |> List.map ~f:(fun name -> 39 | let basename = Basename.of_filename name in 40 | loop (path / basename)) 41 | in 42 | Dir files 43 | | _ -> Unknown 44 | in 45 | { path; kind } 46 | in 47 | Ok (loop base_path) 48 | ;; 49 | 50 | let read_dir path = 51 | match read path with 52 | | Error `Not_found -> 53 | Error [ Pp.textf "Directory not found: %s" (Alice_ui.absolute_path_to_string path) ] 54 | | Ok file -> 55 | (match File_non_root.as_dir file with 56 | | Some dir -> Ok dir 57 | | None -> 58 | Error [ Pp.textf "%S is not a directory" (Alice_ui.absolute_path_to_string path) ]) 59 | ;; 60 | 61 | let read_dir_exn path = 62 | match read_dir path with 63 | | Ok x -> x 64 | | Error pps -> Alice_error.user_exn pps 65 | ;; 66 | -------------------------------------------------------------------------------- /log/src/alice_log.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | type level = 4 | [ `Debug 5 | | `Info 6 | | `Warn 7 | | `Error 8 | ] 9 | 10 | let current_level : level ref = ref `Error 11 | let set_level level = current_level := level 12 | 13 | let to_int = function 14 | | `Debug -> 0 15 | | `Info -> 1 16 | | `Warn -> 2 17 | | `Error -> 3 18 | ;; 19 | 20 | let tag_debug = Ansi_style.create ~color:`Magenta () 21 | let tag_info = Ansi_style.create ~color:`Blue () 22 | let tag_warn = Ansi_style.create ~color:`Yellow () 23 | let tag_error = Ansi_style.create ~color:`Red () 24 | let tag_package = Ansi_style.create ~color:`Cyan () 25 | 26 | type pos = string * int * int * int 27 | 28 | let pp_pos (file, lnum, _cnum, _enum) = Pp.textf "%s:%d" file lnum 29 | 30 | let log ?pos ?package_id ~level message = 31 | if to_int level >= to_int !current_level 32 | then ( 33 | let prefix = 34 | match level with 35 | | `Debug -> Pp.tag tag_debug (Pp.text "[DEBUG] ") 36 | | `Info -> Pp.tag tag_info (Pp.text " [INFO] ") 37 | | `Warn -> Pp.tag tag_warn (Pp.text " [WARN] ") 38 | | `Error -> Pp.tag tag_error (Pp.text "[ERROR] ") 39 | in 40 | let message = 41 | match package_id with 42 | | None -> message 43 | | Some package_id -> 44 | Pp.tag 45 | tag_package 46 | (Pp.textf 47 | "[%s] " 48 | (Alice_package_meta.Package_id.name_v_version_string package_id)) 49 | :: message 50 | in 51 | let message = 52 | match pos with 53 | | None -> message 54 | | Some pos -> pp_pos pos :: message 55 | in 56 | Alice_print.Raw.pps_eprintln (prefix :: message)) 57 | ;; 58 | 59 | let debug ?pos ?package_id message = log ?pos ?package_id ~level:`Debug message 60 | let info ?pos ?package_id message = log ?pos ?package_id ~level:`Info message 61 | let warn ?pos ?package_id message = log ?pos ?package_id ~level:`Warn message 62 | let error ?pos ?package_id message = log ?pos ?package_id ~level:`Error message 63 | -------------------------------------------------------------------------------- /stdlib/src/ansi_style.ml: -------------------------------------------------------------------------------- 1 | module Color = struct 2 | type t = 3 | [ `Black 4 | | `Red 5 | | `Green 6 | | `Yellow 7 | | `Blue 8 | | `Magenta 9 | | `Cyan 10 | | `White 11 | | `Bright_black 12 | | `Bright_red 13 | | `Bright_green 14 | | `Bright_yellow 15 | | `Bright_blue 16 | | `Bright_magenta 17 | | `Bright_cyan 18 | | `Bright_white 19 | ] 20 | end 21 | 22 | type t = 23 | { bold : bool 24 | ; dim : bool 25 | ; underline : bool 26 | ; color : Color.t option 27 | } 28 | 29 | let create ?(bold = false) ?(dim = false) ?(underline = false) ?color () = 30 | { bold; dim; underline; color } 31 | ;; 32 | 33 | (* This is what the style will be after the terminal is reset. *) 34 | let default = { bold = false; dim = false; underline = false; color = None } 35 | let default_with_color color = { default with color = Some color } 36 | let reset = "\x1b[0m" 37 | 38 | let is_default { bold; dim; underline; color } = 39 | (not bold) && (not dim) && (not underline) && Option.is_none color 40 | ;; 41 | 42 | let escape { bold; dim; underline; color } = 43 | let effects = 44 | List.concat 45 | [ (if bold then [ ";1" ] else []) 46 | ; (if dim then [ ";2" ] else []) 47 | ; (if underline then [ ";4" ] else []) 48 | ] 49 | in 50 | let color_code = 51 | match (color : Color.t option) with 52 | | None -> 0 53 | | Some `Black -> 30 54 | | Some `Red -> 31 55 | | Some `Green -> 32 56 | | Some `Yellow -> 33 57 | | Some `Blue -> 34 58 | | Some `Magenta -> 35 59 | | Some `Cyan -> 36 60 | | Some `White -> 37 61 | | Some `Bright_black -> 90 62 | | Some `Bright_red -> 91 63 | | Some `Bright_green -> 92 64 | | Some `Bright_yellow -> 93 65 | | Some `Bright_blue -> 94 66 | | Some `Bright_magenta -> 95 67 | | Some `Bright_cyan -> 96 68 | | Some `Bright_white -> 97 69 | in 70 | Printf.sprintf "\x1b[%d%sm" color_code (String.concat ~sep:"" effects) 71 | ;; 72 | 73 | let pp_with_style t ppf ~f = 74 | if not (is_default t) then Format.pp_print_string ppf (escape t); 75 | f ppf; 76 | if not (is_default t) then Format.pp_print_string ppf reset 77 | ;; 78 | -------------------------------------------------------------------------------- /packaging/replace-compiler-version-with-template-in-lockdir.patch: -------------------------------------------------------------------------------- 1 | diff --git i/dune.lock/lock.dune w/dune.lock/lock.dune 2 | index fb60208..e5c012d 100644 3 | --- i/dune.lock/lock.dune 4 | +++ w/dune.lock/lock.dune 5 | @@ -17,16 +17,16 @@ 6 | (solved_for_platforms 7 | ((arch x86_64) 8 | (os linux) 9 | - (sys-ocaml-version 5.3.1+relocatable)) 10 | + (sys-ocaml-version %%COMPILER_VERSION%%)) 11 | ((arch arm64) 12 | (os linux) 13 | - (sys-ocaml-version 5.3.1+relocatable)) 14 | + (sys-ocaml-version %%COMPILER_VERSION%%)) 15 | ((arch x86_64) 16 | (os macos) 17 | - (sys-ocaml-version 5.3.1+relocatable)) 18 | + (sys-ocaml-version %%COMPILER_VERSION%%)) 19 | ((arch arm64) 20 | (os macos) 21 | - (sys-ocaml-version 5.3.1+relocatable)) 22 | + (sys-ocaml-version %%COMPILER_VERSION%%)) 23 | ((arch x86_64) 24 | (os win32) 25 | - (sys-ocaml-version 5.3.1+relocatable))) 26 | + (sys-ocaml-version %%COMPILER_VERSION%%))) 27 | diff --git i/dune.lock/ocaml-system.5.3.1+relocatable.pkg w/dune.lock/ocaml-system.5.3.1+relocatable.pkg 28 | index 752572d..5ee08d1 100644 29 | --- i/dune.lock/ocaml-system.5.3.1+relocatable.pkg 30 | +++ w/dune.lock/ocaml-system.5.3.1+relocatable.pkg 31 | @@ -1,12 +1 @@ 32 | -(version 5.3.1+relocatable) 33 | - 34 | -(build 35 | - (all_platforms ((action (run ocaml gen_ocaml_config.ml))))) 36 | - 37 | -(extra_sources 38 | - (gen_ocaml_config.ml 39 | - (fetch 40 | - (url 41 | - https://raw.githubusercontent.com/alicecaml/alice-opam-repo/refs/heads/main/packages/ocaml-system/ocaml-system.5.3.1%2Brelocatable/gen_ocaml_config.ml) 42 | - (checksum 43 | - sha256=8b09a982824d7f2407867c24a7609eafc3eb57a19bcc5c66330a629f0d68e468)))) 44 | +(version %%COMPILER_VERSION%%) 45 | diff --git i/dune.lock/ocaml.5.3.1+relocatable.pkg w/dune.lock/ocaml.5.3.1+relocatable.pkg 46 | index d30db21..b4ec95d 100644 47 | --- i/dune.lock/ocaml.5.3.1+relocatable.pkg 48 | +++ w/dune.lock/ocaml.5.3.1+relocatable.pkg 49 | @@ -1,4 +1,4 @@ 50 | -(version 5.3.1+relocatable) 51 | +(version %%COMPILER_VERSION%%) 52 | 53 | (build 54 | (all_platforms 55 | @@ -10,7 +10,7 @@ 56 | (run 57 | ocaml 58 | %{pkg:ocaml-config:share}/gen_ocaml_config.ml 59 | - 5.3.1 60 | + %%COMPILER_VERSION%% 61 | %{pkg-self:name})))))) 62 | 63 | (depends 64 | -------------------------------------------------------------------------------- /engine/src/dot_merlin.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package.Dependency_graph 4 | 5 | let basename = Basename.of_filename ".merlin" 6 | 7 | let header_comment = 8 | {|# Generated by Alice. 9 | # This file is necessary for LSP to work in Alice projects. 10 | # It will be automatically updated when the project is built. 11 | # Don't check this file into version control. 12 | # See https://www.alicecaml.org/lsp for more info. 13 | |} 14 | ;; 15 | 16 | let dot_merlin_text_initial () = 17 | let source_rel_path = Relative_path.of_basename Alice_package.Package.src in 18 | sprintf "%s\nS %s" header_comment (Relative_path.to_filename source_rel_path) 19 | ;; 20 | 21 | let dot_merlin_text package_with_deps build_dir profile ~ocamllib_path = 22 | let build_abs_paths_for_deps = 23 | Package_with_deps.immediate_deps_in_dependency_order package_with_deps 24 | |> List.concat_map ~f:(fun dep -> 25 | let dep_id = Package_with_deps.id dep in 26 | [ Build_dir.package_public_dir build_dir dep_id profile 27 | ; Build_dir.package_public_for_lsp_dir build_dir dep_id profile 28 | ]) 29 | in 30 | let build_abs_paths = 31 | Build_dir.package_private_dir 32 | build_dir 33 | (Package_with_deps.id package_with_deps) 34 | profile 35 | :: build_abs_paths_for_deps 36 | in 37 | let to_relative_paths ~prefix = 38 | List.map build_abs_paths ~f:(Absolute_path.chop_prefix ~prefix) 39 | in 40 | let build_rel_paths = 41 | match Package_with_deps.package package_with_deps |> Alice_package.Package.root with 42 | | `Root prefix -> to_relative_paths ~prefix 43 | | `Non_root prefix -> to_relative_paths ~prefix 44 | in 45 | let source_rel_path = Relative_path.of_basename Alice_package.Package.src in 46 | let ocamllib_path_line = 47 | (* It's probably more correct to specify the OCaml library path with 48 | "FLG -ocamllib-path" however this doesn't seem to work on Windows, so 49 | just use a "B" directive instead. *) 50 | sprintf "B %s" (Absolute_path.to_filename ocamllib_path) 51 | in 52 | let lines = 53 | sprintf "S %s" (Relative_path.to_filename source_rel_path) 54 | :: ocamllib_path_line 55 | :: List.map build_rel_paths ~f:(fun build_rel_path -> 56 | sprintf "B %s" (Relative_path.to_filename build_rel_path)) 57 | in 58 | let text = String.concat lines ~sep:"\n" in 59 | sprintf "%s\n%s" header_comment text 60 | ;; 61 | -------------------------------------------------------------------------------- /package_meta/tests/semantic_version.ml: -------------------------------------------------------------------------------- 1 | open Alice_stdlib 2 | open Alice_package_meta.Semantic_version 3 | 4 | let of_string_exn s = 5 | match of_string s with 6 | | Ok t -> t 7 | | Error e -> 8 | Alice_error.User_error.eprint e; 9 | exit 1 10 | ;; 11 | 12 | let%test "basic" = Result.is_ok (of_string "1.2.3") 13 | let%test "multiple digits" = Result.is_ok (of_string "14.25.36") 14 | 15 | let%test "pre_release" = 16 | match pre_release_string @@ of_string_exn "9.8.7-beta" with 17 | | None -> false 18 | | Some pre_release -> String.equal pre_release "beta" 19 | ;; 20 | 21 | let%test "metadata" = 22 | match metadata_string @@ of_string_exn "1.0.0+foo" with 23 | | None -> false 24 | | Some metadata -> String.equal metadata "foo" 25 | ;; 26 | 27 | let%test "pre_release and metadata" = 28 | let t = of_string_exn "2.2.2-rc1+blah" in 29 | let pre_release = pre_release_string t |> Option.get in 30 | let metadata = metadata_string t |> Option.get in 31 | String.equal pre_release "rc1" && String.equal metadata "blah" 32 | ;; 33 | 34 | let%test "metadata contains dash" = 35 | let metadata = of_string_exn "2.3.4+foo-bar" |> metadata_string |> Option.get in 36 | String.equal metadata "foo-bar" 37 | ;; 38 | 39 | let%test "invalid versions" = 40 | let versions = 41 | [ "" 42 | ; "foo" 43 | ; "4" 44 | ; "4.2" 45 | ; "1.2.3.4" 46 | ; "1.2.3-" 47 | ; "1.3.4+" 48 | ; "1.3.4-+" 49 | ; "1.2.3-/" 50 | ; "1.2.3-beta+" 51 | ; "1.2.3-beta+/" 52 | ; "1.3.4+/" 53 | ] 54 | in 55 | List.for_all versions ~f:(fun version -> 56 | match of_string version with 57 | | Ok _ -> 58 | print_endline (sprintf "Should be invalid but was accepted: %s" version); 59 | false 60 | | Error _ -> true) 61 | ;; 62 | 63 | let%test "precedence rules" = 64 | let lt a b = 65 | match compare_for_precedence (of_string_exn a) (of_string_exn b) with 66 | | -1 -> true 67 | | _ -> 68 | print_endline (sprintf "%s should be less than %s but it is not." a b); 69 | false 70 | in 71 | List.all 72 | [ lt "0.0.0" "0.0.1" 73 | ; lt "0.1.0" "0.2.0" 74 | ; lt "1.0.0" "2.0.0" 75 | ; lt "0.1.0" "0.1.2" 76 | ; lt "0.2.4" "0.4.2" 77 | ; lt "0.1.0" "1.0.0" 78 | ; lt "1.0.0-alpha" "1.0.0" 79 | ; lt "1.0.0-alpha" "1.0.0-alpha.1" 80 | ; lt "1.0.0-alpha.1" "1.0.0-alpha.2" 81 | ] 82 | ;; 83 | 84 | let%test "metadata doesn't affect precedence" = 85 | compare_for_precedence (of_string_exn "1.0.0") (of_string_exn "1.0.0+foo") == 0 86 | ;; 87 | -------------------------------------------------------------------------------- /engine/src/build_dir.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package 4 | 5 | type t = { path : Absolute_path.non_root_t } 6 | 7 | let of_path path = { path } 8 | let path { path } = path 9 | 10 | let package_dir t package_id = 11 | t.path 12 | / Basename.of_filename "packages" 13 | / Basename.of_filename (Package_id.name_dash_version_string package_id) 14 | ;; 15 | 16 | let package_ocamldeps_cache_file t package_id = 17 | package_dir t package_id / Basename.of_filename "ocamldeps_cache.marshal" 18 | ;; 19 | 20 | let package_base_dir t package_id profile = 21 | package_dir t package_id / Basename.of_filename (Profile.name profile) 22 | ;; 23 | 24 | let package_generated_source_dir t package_id profile = 25 | package_base_dir t package_id profile / Basename.of_filename "generated" 26 | ;; 27 | 28 | let package_private_dir t package_id profile = 29 | package_base_dir t package_id profile / Basename.of_filename "private" 30 | ;; 31 | 32 | let package_public_dir t package_id profile = 33 | package_base_dir t package_id profile / Basename.of_filename "public" 34 | ;; 35 | 36 | let package_public_for_lsp_dir t package_id profile = 37 | package_base_dir t package_id profile / Basename.of_filename "public_for_lsp" 38 | ;; 39 | 40 | let package_executable_dir t package_id profile = 41 | package_base_dir t package_id profile / Basename.of_filename "executable" 42 | ;; 43 | 44 | let package_dirs t package_id profile = 45 | [ package_generated_source_dir t package_id profile 46 | ; package_public_dir t package_id profile 47 | ; package_public_for_lsp_dir t package_id profile 48 | ; package_private_dir t package_id profile 49 | ; package_executable_dir t package_id profile 50 | ] 51 | ;; 52 | 53 | let package_generated_file_compiled t package_id profile compiled = 54 | let open Typed_op.Generated_file in 55 | let base = 56 | match Compiled.visibility compiled with 57 | | Private -> package_private_dir t package_id profile 58 | | Public -> package_public_dir t package_id profile 59 | | Public_for_lsp -> package_public_for_lsp_dir t package_id profile 60 | in 61 | base / Compiled.path compiled 62 | ;; 63 | 64 | let package_generated_file t package_id profile generated_file = 65 | let open Typed_op.Generated_file in 66 | match (generated_file : t) with 67 | | Generated_source path -> package_generated_source_dir t package_id profile / path 68 | | Compiled compiled -> package_generated_file_compiled t package_id profile compiled 69 | | Linked_library linked_library -> 70 | package_public_dir t package_id profile / Linked_library.path linked_library 71 | | Linked_executable path -> package_executable_dir t package_id profile / path 72 | ;; 73 | -------------------------------------------------------------------------------- /package/src/package.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Type_bool 3 | open Alice_hierarchy 4 | open Alice_package_meta 5 | 6 | type t 7 | 8 | val to_dyn : t -> Dyn.t 9 | val equal : t -> t -> bool 10 | val create : root:Absolute_path.Root_or_non_root.t -> meta:Package_meta.t -> t 11 | val read_root : Absolute_path.Root_or_non_root.t -> t 12 | val root : t -> Absolute_path.Root_or_non_root.t 13 | val meta : t -> Package_meta.t 14 | val id : t -> Package_id.t 15 | val name : t -> Package_name.t 16 | val version : t -> Semantic_version.t 17 | val dependencies : t -> Dependencies.t 18 | val dependency_names : t -> Package_name.t list 19 | 20 | (** The file inside the source directory containing the entry point for the 21 | executable. *) 22 | val exe_root_ml : Basename.t 23 | 24 | (** The file inside the source directory containing the entry point for the 25 | library. *) 26 | val lib_root_ml : Basename.t 27 | 28 | val src : Basename.t 29 | 30 | (** The path of the directory inside a package where the source code is located *) 31 | val src_dir_path : t -> Absolute_path.non_root_t 32 | 33 | val src_dir_exn : t -> File_non_root.dir 34 | 35 | module Typed : sig 36 | type package := t 37 | 38 | type ('exe, 'lib) type_ = 39 | | Exe_only : (true_t, false_t) type_ 40 | | Lib_only : (false_t, true_t) type_ 41 | | Exe_and_lib : (true_t, true_t) type_ 42 | 43 | (** A package with type-level boolean type annotations indicating whether it 44 | contains an executable or a library or both. *) 45 | type ('exe, 'lib) t 46 | 47 | type lib_only_t = (false_t, true_t) t 48 | type exe_only_t = (true_t, false_t) t 49 | type exe_and_lib_t = (true_t, true_t) t 50 | 51 | val to_dyn : (_, _) t -> Dyn.t 52 | val equal : ('exe, 'lib) t -> ('exe, 'lib) t -> bool 53 | 54 | (** Ignore the presence of a library in a package containing both a library 55 | and an executable. *) 56 | val limit_to_exe_only : exe_and_lib_t -> exe_only_t 57 | 58 | (** Ignore the presence of an executable in a package containing both an 59 | executable and a library. *) 60 | val limit_to_lib_only : exe_and_lib_t -> lib_only_t 61 | 62 | val package : (_, _) t -> package 63 | val name : (_, _) t -> Package_name.t 64 | val type_ : ('exe, 'lib) t -> ('exe, 'lib) type_ 65 | val contains_exe : ('exe, _) t -> 'exe Type_bool.t 66 | val contains_lib : (_, 'lib) t -> 'lib Type_bool.t 67 | end 68 | 69 | val typed 70 | : t 71 | -> [ `Exe_only of (true_t, false_t) Typed.t 72 | | `Lib_only of (false_t, true_t) Typed.t 73 | | `Exe_and_lib of (true_t, true_t) Typed.t 74 | ] 75 | 76 | type 'a with_typed = { f : 'exe 'lib. ('exe, 'lib) Typed.t -> 'a } 77 | 78 | val with_typed : 'a with_typed -> t -> 'a 79 | -------------------------------------------------------------------------------- /assets/artifacts.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | hello 14 | 15 | hello 16 | 17 | 18 | 19 | main.cmx 20 | 21 | main.cmx 22 | 23 | 24 | 25 | hello->main.cmx 26 | 27 | 28 | 29 | 30 | 31 | main.ml 32 | 33 | main.ml 34 | 35 | 36 | 37 | main.cmx->main.ml 38 | 39 | 40 | 41 | 42 | 43 | main.cmi 44 | 45 | main.cmi 46 | 47 | 48 | 49 | main.cmi->main.ml 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /engine/src/ocamldep_cache.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_package 4 | module File_ops = Alice_io.File_ops 5 | module Log = Alice_log 6 | open Alice_ocaml_compiler 7 | 8 | type dep_table = Ocaml_compiler.Deps.t Absolute_path.Non_root_map.t 9 | 10 | type t = 11 | { dep_table : dep_table 12 | ; mtime : float 13 | ; package_id : Package_id.t 14 | ; build_dir : Build_dir.t 15 | } 16 | 17 | let load build_dir package_id = 18 | let path = Build_dir.package_ocamldeps_cache_file build_dir package_id in 19 | if File_ops.exists path 20 | then ( 21 | Log.info 22 | ~package_id 23 | [ Pp.textf 24 | "Loading ocamldeps cache from: %s" 25 | (Alice_ui.absolute_path_to_string path) 26 | ]; 27 | let dep_table = 28 | File_ops.with_in_channel path ~mode:`Bin ~f:(fun channel -> 29 | Marshal.from_channel channel) 30 | in 31 | let mtime = File_ops.mtime path in 32 | { dep_table; mtime; build_dir; package_id }) 33 | else 34 | { dep_table = Absolute_path.Non_root_map.empty 35 | ; mtime = Float.neg_infinity 36 | ; build_dir 37 | ; package_id 38 | } 39 | ;; 40 | 41 | let store t (dep_table : dep_table) = 42 | let path = Build_dir.package_ocamldeps_cache_file t.build_dir t.package_id in 43 | File_ops.mkdir_p 44 | (Absolute_path.parent path |> Absolute_path.Root_or_non_root.assert_non_root); 45 | File_ops.with_out_channel path ~mode:`Bin ~f:(fun channel -> 46 | Marshal.to_channel channel dep_table []) 47 | ;; 48 | 49 | let get_deps t ocaml_compiler ~source_path = 50 | let path = Build_dir.package_ocamldeps_cache_file t.build_dir t.package_id in 51 | let source_mtime = File_ops.mtime source_path in 52 | let run_ocamldep () = 53 | Log.info 54 | ~package_id:t.package_id 55 | [ Pp.textf 56 | "Analyzing dependencies of file: %s" 57 | (Alice_ui.absolute_path_to_string source_path) 58 | ]; 59 | Ocaml_compiler.depends_native ocaml_compiler source_path 60 | in 61 | if source_mtime > t.mtime 62 | then 63 | (* Source file is newer than the cache so we need to run ocamldep. *) 64 | run_ocamldep () 65 | else ( 66 | match Absolute_path.Non_root_map.find_opt source_path t.dep_table with 67 | | None -> 68 | (* Source file is absent from the cache. This is unusual because the 69 | source file is older than the cache. Run ocamldep to compute the 70 | result anyway. *) 71 | Log.warn 72 | ~package_id:t.package_id 73 | [ Pp.textf 74 | "The ocamldeps cache (%s) is newer than source file %S, however there is no \ 75 | entry in the ocamldeps cache for that source file." 76 | (Alice_ui.absolute_path_to_string path) 77 | (Alice_ui.absolute_path_to_string source_path) 78 | ]; 79 | run_ocamldep () 80 | | Some deps -> deps) 81 | ;; 82 | -------------------------------------------------------------------------------- /dag/src/alice_dag.mli: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | 3 | module type Node = sig 4 | module Name : sig 5 | type t 6 | 7 | val to_dyn : t -> Dyn.t 8 | 9 | module Set : Set.S with type elt = t 10 | module Map : Map.S with type key = t 11 | end 12 | 13 | (** A node in a DAG. Nodes have names which are used to link nodes to their 14 | dependencies in the graph. *) 15 | type t 16 | 17 | val to_dyn : t -> Dyn.t 18 | val equal : t -> t -> bool 19 | val name : t -> Name.t 20 | val dep_names : t -> Name.Set.t 21 | 22 | (** How to display this node when visualizing the graph with graphviz. *) 23 | val show : t -> string 24 | end 25 | 26 | module Make (Node : Node) : sig 27 | type t 28 | 29 | val empty : t 30 | val to_dyn : t -> Dyn.t 31 | val nodes : t -> Node.t list 32 | val roots : t -> Node.t list 33 | val to_string_graph : t -> String.Set.t String.Map.t 34 | 35 | (** Returns a list containing all nodes in the transitive dependency closure 36 | of the node [start] where each node appears a single time in the list, 37 | and a node will appear earlier than any dependant nodes in the list. If 38 | [include_start] is true then the final node will always be the one named 39 | in [start]. Otherwise [start] will not appear in the output. *) 40 | val transitive_closure_in_dependency_order 41 | : t 42 | -> start:Node.Name.t 43 | -> include_start:bool 44 | -> Node.t list 45 | 46 | (** Returns a list where each node in [t] appears exactly once, in such an 47 | order than a node will appear earlier than any dependant nodes. *) 48 | val all_nodes_in_dependency_order : t -> Node.t list 49 | 50 | module Traverse : sig 51 | type dag := t 52 | 53 | (** Helper for traversing a DAG. Traversals begin at output nodes. A 54 | traversal is a node in the DAG which knows how to expand the 55 | dependencies of the node. Doesn't attempt to prevent visiting nodes 56 | multiple times. *) 57 | type t 58 | 59 | val node : t -> Node.t 60 | val name : t -> Node.Name.t 61 | val dag : t -> dag 62 | val deps : t -> t list 63 | val transitive_closure : t -> Node.t list 64 | end 65 | 66 | (** [traverse t ~name] returns a traversal of [t] starting at the node named 67 | [name], or panics if no such node exists. *) 68 | val traverse : t -> name:Node.Name.t -> Traverse.t 69 | 70 | module Staging : sig 71 | type dag := t 72 | 73 | (** A graph which allows an incomplete representation. Use to construct 74 | DAGs one node at a time. An incomplete graph contains nodes with deps 75 | which are not present in the graph. *) 76 | type t 77 | 78 | val empty : t 79 | val to_dyn : t -> Dyn.t 80 | 81 | (** Add a node to the graph. The deps of the new node don't all need to be 82 | present in the graph. *) 83 | val add : t -> Node.Name.t -> Node.t -> (t, [ `Conflict of Node.t ]) result 84 | 85 | (** Returns a DAG provided that the staging graph is complete and free of 86 | cycles. *) 87 | val finalize 88 | : t 89 | -> (dag, [ `Dangling of Node.Name.t | `Cycle of Node.Name.t list ]) result 90 | end 91 | 92 | val restage : t -> Staging.t 93 | end 94 | -------------------------------------------------------------------------------- /io/src/process.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | module Log = Alice_log 4 | 5 | module Status = struct 6 | type t = 7 | | Exited of int 8 | | Signaled of int 9 | | Stopped of int 10 | 11 | let to_dyn t = 12 | match (t : t) with 13 | | Exited x -> Dyn.variant "Exited" [ Dyn.int x ] 14 | | Signaled x -> Dyn.variant "Signaled" [ Dyn.int x ] 15 | | Stopped x -> Dyn.variant "Stopped" [ Dyn.int x ] 16 | ;; 17 | 18 | let of_unix (process_status : Unix.process_status) = 19 | match process_status with 20 | | WEXITED x -> Exited x 21 | | WSIGNALED x -> Signaled x 22 | | WSTOPPED x -> Stopped x 23 | ;; 24 | 25 | let panic_unless_exit_0 = function 26 | | Exited 0 -> () 27 | | Exited x -> 28 | Alice_error.panic [ Pp.textf "Process exited with non-zero status: %d" x ] 29 | | Signaled x -> 30 | Alice_error.panic [ Pp.textf "Process exited due to an unhandled signal: %d" x ] 31 | | Stopped x -> Alice_error.panic [ Pp.textf "Process was stopped by a signal: %d" x ] 32 | ;; 33 | end 34 | 35 | module Blocking = struct 36 | let run 37 | ?(stdin = Unix.stdin) 38 | ?(stdout = Unix.stdout) 39 | ?(stderr = Unix.stderr) 40 | prog 41 | ~args 42 | ~env 43 | = 44 | let env = Env.to_raw env in 45 | let env = Array.to_list env in 46 | (*print_endline (sprintf "%s" (Dyn.list Dyn.string env |> Dyn.to_string)); *) 47 | let env = Array.of_list env in 48 | let args = Array.of_list (prog :: args) in 49 | try 50 | Log.debug 51 | [ Pp.textf "Running command: %s" (String.concat ~sep:" " (Array.to_list args)) ]; 52 | let pid = Unix.create_process_env prog args env stdin stdout stderr in 53 | let _, status = Unix.waitpid [] pid in 54 | Ok (Status.of_unix status) 55 | with 56 | | Unix.Unix_error (Unix.ENOENT, _, _) -> Error `Prog_not_available 57 | ;; 58 | 59 | let run_capturing_stdout_lines 60 | ?(stdin = Unix.stdin) 61 | ?(stderr = Unix.stderr) 62 | prog 63 | ~args 64 | ~env 65 | = 66 | Temp_dir.with_ ~prefix:"alice." ~suffix:".stdout" ~f:(fun dir -> 67 | let path = dir / Basename.of_filename "stdout" in 68 | let perms = 0o755 in 69 | let output_file_desc = 70 | Unix.openfile (Absolute_path.to_filename path) [ O_CREAT; O_RDWR ] perms 71 | in 72 | let result = run ~stdin ~stdout:output_file_desc ~stderr prog ~args ~env in 73 | let result = 74 | Result.map result ~f:(fun status -> 75 | let _ = Unix.lseek output_file_desc 0 SEEK_SET in 76 | let channel = Unix.in_channel_of_descr output_file_desc in 77 | let lines = In_channel.input_lines channel in 78 | status, lines) 79 | in 80 | Unix.close output_file_desc; 81 | result) 82 | ;; 83 | 84 | let run_command 85 | ?(stdin = Unix.stdin) 86 | ?(stdout = Unix.stdout) 87 | ?(stderr = Unix.stderr) 88 | { Command.prog; args; env } 89 | = 90 | run ~stdin ~stdout ~stderr prog ~args ~env 91 | ;; 92 | 93 | let run_command_capturing_stdout_lines 94 | ?(stdin = Unix.stdin) 95 | ?(stderr = Unix.stderr) 96 | { Command.prog; args; env } 97 | = 98 | run_capturing_stdout_lines ~stdin ~stderr prog ~args ~env 99 | ;; 100 | end 101 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { ocaml, ocamlformat_0_27_0, ocamlPackages, fetchgit, symlinkJoin }: 2 | let 3 | tools = symlinkJoin { 4 | name = "alice-ocaml-tools"; 5 | paths = [ 6 | ocaml 7 | ocamlPackages.ocaml-lsp 8 | ocamlPackages.dot-merlin-reader 9 | ocamlformat_0_27_0 10 | ]; 11 | }; 12 | deps = with ocamlPackages; [ 13 | sha 14 | xdg 15 | toml 16 | re 17 | fileutils 18 | pp 19 | (dyn.overrideAttrs (_: { 20 | # Since alice depends on pp and dyn, modify dyn to reuse the common 21 | # pp rather than vendoring it. This avoids a module conflict 22 | # between pp and dyn's vendored copy of pp when building alice. 23 | buildInputs = [ pp ]; 24 | patchPhase = '' 25 | rm -rf vendor/pp 26 | ''; 27 | })) 28 | (buildDunePackage { 29 | pname = "climate"; 30 | version = "0.9.0"; 31 | src = fetchgit { 32 | url = "https://github.com/gridbugs/climate"; 33 | rev = "refs/tags/0.9.0"; 34 | hash = "sha256-WRhWNWQ4iTUVpJlp7isJs3+0n/D0gYXTxRcCTJZ1o8U="; 35 | }; 36 | }) 37 | ]; 38 | make = { version, src }: 39 | let 40 | packageBare = (ocamlPackages.buildDunePackage { 41 | inherit src; 42 | pname = "alice"; 43 | version = version; 44 | buildInputs = deps; 45 | postInstall = '' 46 | mkdir -p $out/share/bash-completion/completions 47 | $out/bin/alice internal completions bash \ 48 | --program-name=alice \ 49 | --program-exe-for-reentrant-query=alice \ 50 | --global-symbol-prefix=__alice \ 51 | --no-command-hash-in-function-names \ 52 | --no-comments \ 53 | --no-whitespace \ 54 | --minify-global-names \ 55 | --minify-local-variables \ 56 | --optimize-case-statements > $out/share/bash-completion/completions/alice 57 | ''; 58 | }); 59 | packageWithTools = symlinkJoin { 60 | name = "alice-${version}-and-ocaml-tools"; 61 | version = version; 62 | paths = [ packageBare tools ]; 63 | }; 64 | in packageWithTools // { bare = packageBare; }; 65 | makeGithub = { version, hash }: 66 | make { 67 | inherit version; 68 | src = fetchgit { 69 | url = "https://github.com/alicecaml/alice"; 70 | rev = "refs/tags/${version}"; 71 | hash = hash; 72 | }; 73 | }; 74 | versioned = { 75 | alice_0_1_0 = makeGithub { 76 | version = "0.1.0"; 77 | hash = "sha256-Ax9qbFzgHPH0EYQrgA+1bEAlFinc4egNKIn/ZrxV5K4="; 78 | }; 79 | alice_0_1_1 = makeGithub { 80 | version = "0.1.1"; 81 | hash = "sha256-4T6YyyN4ttFcqSeBWNfff8bL7bYWYhLMxqRN7KCAp3c="; 82 | }; 83 | alice_0_1_2 = makeGithub { 84 | version = "0.1.2"; 85 | hash = "sha256-05EXQxosue5XEwAUtkI/2VObKJzUTzrZfVH3WELHACk="; 86 | }; 87 | alice_0_1_3 = makeGithub { 88 | version = "0.1.3"; 89 | hash = "sha256-PkZbzqjlWswJ/8wBJikj45royPUEyUWG/bRqB47qkXg="; 90 | }; 91 | alice_0_2_0 = makeGithub { 92 | version = "0.2.0"; 93 | hash = "sha256-QNAPIccp3K6w0s35jmEWodwvac0YoWUZr0ffXptfLGs="; 94 | }; 95 | }; 96 | latest = versioned.alice_0_2_0; 97 | dev = make { 98 | version = "0.3-dev"; 99 | src = ./.; 100 | }; 101 | in { 102 | inherit versioned latest tools dev; 103 | default = latest; 104 | } 105 | -------------------------------------------------------------------------------- /ui/src/alice_ui.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | module Raw = Alice_print.Raw 4 | 5 | let mode : [ `Standard | `Quiet ] ref = ref `Standard 6 | let set_mode mode' = mode := mode' 7 | let path_style : [ `Native | `Normalized ] ref = ref `Native 8 | let set_normalized_paths () = path_style := `Normalized 9 | 10 | type message = Ansi_style.t Pp.t list 11 | 12 | let print pps = 13 | match !mode with 14 | | `Standard -> Raw.pps_print pps 15 | | `Quiet -> () 16 | ;; 17 | 18 | let println pps = 19 | match !mode with 20 | | `Standard -> Raw.pps_println pps 21 | | `Quiet -> () 22 | ;; 23 | 24 | let print_newline () = println [] 25 | 26 | module Styles = struct 27 | let success = Ansi_style.create ~bold:true ~color:`Green () 28 | end 29 | 30 | let absolute_path_to_string : type is_root. is_root Absolute_path.t -> string = 31 | fun path -> 32 | match !path_style with 33 | | `Native -> Absolute_path.to_filename path 34 | | `Normalized -> 35 | (match Absolute_path.is_root path with 36 | | True -> "/" 37 | | False -> 38 | let path = 39 | if Absolute_path.has_extension path ~ext:".exe" 40 | then Absolute_path.remove_extension path 41 | else path 42 | in 43 | let filename = 44 | Absolute_path.to_filename path 45 | |> Filename.chop_prefix 46 | ~prefix:(Absolute_path.Root_or_non_root.to_filename Alice_env.initial_cwd) 47 | in 48 | (* The start of the path will always be a period. Skip it. *) 49 | let components = 50 | match Filename.to_components filename with 51 | | Absolute { root = _; rest } | Relative rest -> rest 52 | in 53 | if List.is_empty components then "." else String.concat ~sep:"/" components) 54 | ;; 55 | 56 | let basename_to_string basename = 57 | match !path_style with 58 | | `Native -> Basename.to_filename basename 59 | | `Normalized -> 60 | let basename = 61 | if Basename.has_extension basename ~ext:".exe" 62 | then Basename.remove_extension basename 63 | else basename 64 | in 65 | Basename.to_filename basename 66 | ;; 67 | 68 | let raw_message ?(style = Ansi_style.default) raw = [ Pp.text raw |> Pp.tag style ] 69 | let default_verb_style = Ansi_style.create ~bold:true ~color:`Green () 70 | 71 | type verb = 72 | [ `Fetching 73 | | `Unpacking 74 | | `Compiling 75 | | `Running 76 | | `Creating 77 | | `Removing 78 | | `Finished 79 | ] 80 | 81 | let verb_to_string_padded = function 82 | | `Fetching -> " Fetching" 83 | | `Unpacking -> " Unpacking" 84 | | `Compiling -> " Compiling" 85 | | `Running -> " Running" 86 | | `Creating -> " Creating" 87 | | `Removing -> " Removing" 88 | | `Finished -> " Finished" 89 | ;; 90 | 91 | let verb_message ?(verb_style = default_verb_style) verb object_ = 92 | [ Pp.text (verb_to_string_padded verb) |> Pp.tag verb_style; Pp.space; Pp.text object_ ] 93 | ;; 94 | 95 | let default_done_style = Ansi_style.create ~bold:true ~color:`Green () 96 | let done_message ?(style = default_done_style) () = [ Pp.text "Done!" |> Pp.tag style ] 97 | 98 | (* Returns a thunk than invokes a given think the first time it's called. *) 99 | let once f = 100 | let called = ref false in 101 | fun () -> 102 | if not !called 103 | then ( 104 | called := true; 105 | f ()) 106 | ;; 107 | 108 | let println_once pps = once (fun () -> println pps) 109 | -------------------------------------------------------------------------------- /which/src/alice_which.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Alice_env 4 | 5 | let find_in_search_path exe_name search_paths = 6 | List.find_map search_paths ~f:(fun search_path -> 7 | let exe_path = Absolute_path.Root_or_non_root.concat_basename search_path exe_name in 8 | if Alice_io.File_ops.exists exe_path then Some exe_path else None) 9 | ;; 10 | 11 | (* Add the current root's bin dir from the Alice installation to the end of the 12 | PATH variable if such a directory exists and isn't already present in PATH. 13 | This modified environment will be queried to find the location of 14 | executebles, and executables will run in the modified environment. This way, 15 | if the user doesn't already have an OCaml toolchain installed, the toolchain 16 | installed by Alice will be used, and commands from the toolchain can call 17 | each other. *) 18 | let add_installation_to_path_variable os_type env = 19 | let installation = Alice_installation.create os_type env in 20 | let installation_bin = Alice_installation.current_bin installation in 21 | let path_variable = 22 | match Path_variable.get_result os_type env with 23 | | Error `Variable_not_defined -> [] 24 | | Ok path_variable -> path_variable 25 | in 26 | if Path_variable.contains path_variable (`Non_root installation_bin) 27 | then 28 | (* The bin dir from the Alice installation is already in the PATH 29 | variable, so there's nothing to do. *) 30 | env 31 | else if 32 | Alice_io.File_ops.exists installation_bin 33 | && Alice_io.File_ops.is_directory installation_bin 34 | then ( 35 | (* Only update the PATH variable if the bin dir from the Alice 36 | installation exists. *) 37 | let path_variable = path_variable @ [ `Non_root installation_bin ] in 38 | Path_variable.set path_variable os_type env) 39 | else env 40 | ;; 41 | 42 | let which os_type env exe_name = 43 | let exe_name = 44 | if Os_type.is_windows os_type && not (Filename.has_extension exe_name ~ext:".exe") 45 | then ( 46 | let exe_name_with_extension = Filename.add_extension exe_name ~ext:".exe" in 47 | Alice_log.warn 48 | [ Pp.textf 49 | "Looking up location of program %S which lacks \".exe\" extension. Assuming \ 50 | you meant %S." 51 | exe_name 52 | exe_name_with_extension 53 | ]; 54 | exe_name_with_extension) 55 | else exe_name 56 | in 57 | let search_paths = 58 | match Path_variable.get_result os_type env with 59 | | Ok search_paths -> search_paths 60 | | Error `Variable_not_defined -> 61 | Alice_log.warn 62 | [ Pp.text 63 | "Can't determine program search paths because the PATH variable is not \ 64 | defined." 65 | ]; 66 | [] 67 | in 68 | find_in_search_path (Basename.of_filename exe_name) search_paths 69 | ;; 70 | 71 | let try_which os_type env exe_name = 72 | match which os_type env exe_name with 73 | | Some path -> Absolute_path.to_filename path 74 | | None -> 75 | (* Couldn't find the executable in PATH, so just return its name just in 76 | case the OS has some tricks up its sleeve for finding executables. *) 77 | exe_name 78 | ;; 79 | 80 | let ocamlopt_name os_type = 81 | Alice_env.Os_type.filename_add_exe_extension_on_windows os_type "ocamlopt.opt" 82 | ;; 83 | 84 | let ocamlopt os_type env = 85 | let env = add_installation_to_path_variable os_type env in 86 | let filename = try_which os_type env (ocamlopt_name os_type) in 87 | Alice_ocaml_compiler.Ocaml_compiler.create filename env 88 | ;; 89 | -------------------------------------------------------------------------------- /env/src/alice_env.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | 4 | let initial_cwd = Absolute_path.of_filename (Sys.getcwd ()) 5 | let current_env () = Unix.environment () |> Env.of_raw 6 | 7 | module Os_type = struct 8 | type t = 9 | | Windows 10 | | Unix 11 | 12 | let current () = if Sys.win32 then Windows else Unix 13 | 14 | let is_windows = function 15 | | Windows -> true 16 | | Unix -> false 17 | ;; 18 | 19 | let filename_add_exe_extension_on_windows t filename_without_extension = 20 | if Filename.has_extension filename_without_extension ~ext:".exe" 21 | then 22 | Alice_error.panic 23 | [ Pp.textf "File %S already has .exe extension" filename_without_extension ]; 24 | match t with 25 | | Windows -> Filename.add_extension filename_without_extension ~ext:".exe" 26 | | Unix -> filename_without_extension 27 | ;; 28 | 29 | let basename_add_exe_extension_on_windows t basename_without_extension = 30 | if Basename.has_extension basename_without_extension ~ext:".exe" 31 | then 32 | Alice_error.panic 33 | [ Pp.textf 34 | "File %S already has .exe extension" 35 | (Basename.to_filename basename_without_extension) 36 | ]; 37 | match t with 38 | | Windows -> Basename.add_extension basename_without_extension ~ext:".exe" 39 | | Unix -> basename_without_extension 40 | ;; 41 | 42 | let path_delimiter = function 43 | | Windows -> ';' 44 | | Unix -> ':' 45 | ;; 46 | end 47 | 48 | module Path_variable = struct 49 | type t = Absolute_path.Root_or_non_root.t list 50 | 51 | let to_dyn = Dyn.list Absolute_path.Root_or_non_root.to_dyn 52 | 53 | let of_raw os_type raw = 54 | String.split_on_char ~sep:(Os_type.path_delimiter os_type) raw 55 | |> List.filter ~f:(Fun.negate String.is_empty) 56 | |> List.map ~f:(fun filename -> 57 | if Filename.is_relative filename 58 | then 59 | Absolute_path.Root_or_non_root.concat_relative_exn 60 | initial_cwd 61 | (Relative_path.of_filename filename) 62 | else Absolute_path.of_filename filename) 63 | ;; 64 | 65 | let to_raw os_type t = 66 | List.map t ~f:Absolute_path.Root_or_non_root.to_filename 67 | |> String.concat ~sep:(String.make 1 (Os_type.path_delimiter os_type)) 68 | ;; 69 | 70 | let is_path_variable_name os_type name = 71 | if Os_type.is_windows os_type 72 | then String.equal (String.uppercase_ascii name) "PATH" 73 | else String.equal name "PATH" 74 | ;; 75 | 76 | let get_opt os_type env = 77 | Env.find_name_opt ~f:(is_path_variable_name os_type) env 78 | |> Option.map ~f:(of_raw os_type) 79 | ;; 80 | 81 | let get_or_empty os_type env = get_opt os_type env |> Option.value ~default:[] 82 | 83 | let get_result os_type env = 84 | match get_opt os_type env with 85 | | None -> Error `Variable_not_defined 86 | | Some t -> Ok t 87 | ;; 88 | 89 | let contains t path = List.exists t ~f:(Absolute_path.Root_or_non_root.equal path) 90 | 91 | let set t os_type env = 92 | (* Set the PATH variable. Note that on Windows, the PATH variable is 93 | sometimes referred to in non-uppercase, however it's case insensitive on 94 | Windows so setting it by an all uppercase name should be fine. *) 95 | Env.set env ~name:"PATH" ~value:(to_raw os_type t) 96 | ;; 97 | end 98 | 99 | module Xdg = struct 100 | include Alice_stdlib.Xdg 101 | 102 | let create os_type env = 103 | let env name = Env.get_opt env ~name in 104 | create ~win32:(Os_type.is_windows os_type) ~env () 105 | ;; 106 | end 107 | -------------------------------------------------------------------------------- /integration_tests/compiler-specific/multi-package.t/run.t: -------------------------------------------------------------------------------- 1 | Build a package with dependencies. 2 | 3 | $ sanitize() { 4 | > cat | sed -E '/Command failed: /d' | sed -E '/File .*, line [0-9]+, characters/d' | sed -E 's/Unbound module "([^ ]*)"/Unbound module \1/' 5 | > } 6 | 7 | $ alice run --normalize-paths --manifest-path app/Alice.toml 8 | Compiling c v0.1.0 9 | Compiling b v0.1.0 10 | Compiling a v0.1.0 11 | Compiling d v0.1.0 12 | Compiling app v0.1.0 13 | Running app/build/packages/app-0.1.0/debug/executable/app 14 | 15 | Hello, World! 16 | Hello, World! 17 | Hello, World! 18 | blah blah blah 19 | foo bar baz 20 | Hello, World! blah blah blah 21 | 22 | $ alice dot packages --normalize-paths --manifest-path app/Alice.toml 23 | digraph { 24 | "a v0.1.0" -> {"b v0.1.0"} 25 | "app v0.1.0" -> {"a v0.1.0", "d v0.1.0"} 26 | "b v0.1.0" -> {"c v0.1.0"} 27 | "d v0.1.0" -> {"b v0.1.0", "c v0.1.0"} 28 | } 29 | 30 | Make a new package to test some things which are not allowed: 31 | $ alice new --normalize-paths --exe bad 32 | Creating new executable package "bad" in bad 33 | $ cat >> bad/Alice.toml << EOF 34 | > [dependencies] 35 | > a = { path = "../a" } 36 | > EOF 37 | $ alice build --normalize-paths --manifest-path bad/Alice.toml 38 | Compiling c v0.1.0 39 | Compiling b v0.1.0 40 | Compiling a v0.1.0 41 | Compiling bad v0.1.0 42 | Finished debug build of package: 'bad v0.1.0' 43 | 44 | Even though the package "c" is in the transitive dependency closure of "bad", 45 | "bad" cannot refer directly to "c". 46 | $ cat > bad/src/main.ml << EOF 47 | > let () = print_endline C.Message.message 48 | > EOF 49 | $ alice build --normalize-paths --manifest-path bad/Alice.toml 2>&1 | sanitize 50 | Compiling bad v0.1.0 51 | 1 | let () = print_endline C.Message.message 52 | ^^^^^^^^^^^^^^^^^ 53 | Error: Unbound module C 54 | 55 | 56 | The package protocol creates a module "internal_modules_of_" which is 57 | publically visible to client code, however the module is shadowed with an empty 58 | module and generates a warning. Check that this works as expected by attempting 59 | to access the transitive dependency "c" from "bad" via this module. 60 | $ cat > bad/src/main.ml << EOF 61 | > let () = print_endline Internal_modules_of_c.Lib.Message.message 62 | > EOF 63 | $ alice build --normalize-paths --manifest-path bad/Alice.toml 2>&1 | sanitize 64 | Compiling bad v0.1.0 65 | 1 | let () = print_endline Internal_modules_of_c.Lib.Message.message 66 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 67 | Alert deprecated: module Public_interface_to_open_of_a.Internal_modules_of_c 68 | This module is an empty shadow of another module intended for internal use only. 69 | 70 | 1 | let () = print_endline Internal_modules_of_c.Lib.Message.message 71 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 72 | Error: Unbound module Internal_modules_of_c.Lib 73 | 74 | 75 | The package protocol also creates a module 76 | "public_interface_to_open_of_". This module should be inaccessible to 77 | client code, even when the package is an immediate dependency. 78 | 79 | $ cat > bad/src/main.ml << EOF 80 | > let () = print_endline Public_interface_to_open_of_a.A.C.Message.message 81 | > EOF 82 | $ alice build --normalize-paths --manifest-path bad/Alice.toml 2>&1 | sanitize 83 | Compiling bad v0.1.0 84 | 1 | let () = print_endline Public_interface_to_open_of_a.A.C.Message.message 85 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 86 | Alert deprecated: module Public_interface_to_open_of_a.Public_interface_to_open_of_a 87 | This module is an empty shadow of another module intended for internal use only. 88 | 89 | 1 | let () = print_endline Public_interface_to_open_of_a.A.C.Message.message 90 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 91 | Error: Unbound module Public_interface_to_open_of_a.A 92 | 93 | 94 | -------------------------------------------------------------------------------- /alice/src/new.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Alice_hierarchy 3 | open Climate 4 | module File_ops = Alice_io.File_ops 5 | open Alice_error 6 | open Alice_package 7 | module Project = Alice_engine.Project 8 | 9 | let make_project name path kind = 10 | let src_path = Absolute_path.Root_or_non_root.concat_basename path Package.src in 11 | let manifest_path = 12 | Absolute_path.Root_or_non_root.concat_basename path Alice_manifest.manifest_name 13 | in 14 | if File_ops.exists manifest_path 15 | then 16 | user_exn 17 | [ Pp.textf 18 | "Refusing to create project because destination directory exists and contains \ 19 | project manifest (%s).\n" 20 | (Alice_ui.absolute_path_to_string manifest_path) 21 | ; Pp.text "Delete this file before proceeding." 22 | ]; 23 | if File_ops.exists src_path 24 | then 25 | if File_ops.is_directory src_path 26 | then 27 | user_exn 28 | [ Pp.textf 29 | "Refusing to create project because destination directory exists and \ 30 | contains src directory (%s).\n" 31 | (Alice_ui.absolute_path_to_string src_path) 32 | ; Pp.text "Delete this directory before proceeding." 33 | ] 34 | else 35 | user_exn 36 | [ Pp.textf 37 | "Refusing to create project because destination directory exists and \ 38 | contains a file named %S (%s).\n" 39 | (Basename.to_filename Package.src) 40 | (Alice_ui.absolute_path_to_string src_path) 41 | ; Pp.text "Delete this file before proceeding." 42 | ]; 43 | let manifest = 44 | Package_meta.create 45 | ~id:{ name; version = Semantic_version.of_string_exn "0.1.0" } 46 | ~dependencies:None 47 | in 48 | File_ops.mkdir_p src_path; 49 | (match kind with 50 | | `Exe -> 51 | File_ops.write_text_file 52 | (src_path / Package.exe_root_ml) 53 | "let () = print_endline \"Hello, World!\"" 54 | | `Lib -> 55 | File_ops.write_text_file 56 | (src_path / Package.lib_root_ml) 57 | "let add lhs rhs = lhs + rhs"); 58 | Alice_manifest.write_package_manifest ~manifest_path manifest; 59 | let project = Project.of_package (Package.read_root path) in 60 | Project.write_dot_merlin_initial project; 61 | Project.write_dot_gitignore project 62 | ;; 63 | 64 | let new_ = 65 | let open Arg_parser in 66 | let+ () = Common.set_globals_from_flags 67 | and+ name = pos_req 0 string ~doc:"Name of the project" ~value_name:"NAME" 68 | and+ path = 69 | Common.parse_absolute_path 70 | ~doc:"Initialize the new project in this directory (must not already exist)" 71 | [ "path"; "p" ] 72 | and+ exe = 73 | flag [ "exe" ] ~doc:"Create a project containing an executable package (default)" 74 | and+ lib = flag [ "lib" ] ~doc:"Create a project containing a library package" in 75 | let package_name = Alice_package_meta.Package_name.of_string_exn name in 76 | let path = 77 | match path with 78 | | Some path -> path 79 | | None -> 80 | `Non_root 81 | (Absolute_path.Root_or_non_root.concat_basename 82 | Alice_env.initial_cwd 83 | (Basename.of_filename name)) 84 | in 85 | let kind = 86 | match exe, lib with 87 | | false, false | true, false -> `Exe 88 | | false, true -> `Lib 89 | | true, true -> Alice_error.user_exn [ Pp.text "Can't specify both --exe and --lib" ] 90 | in 91 | let kind_string = 92 | match kind with 93 | | `Exe -> "executable" 94 | | `Lib -> "library" 95 | in 96 | let open Alice_ui in 97 | let path_string = 98 | match path with 99 | | `Root p -> absolute_path_to_string p 100 | | `Non_root p -> absolute_path_to_string p 101 | in 102 | println 103 | (verb_message 104 | `Creating 105 | (sprintf "new %s package %S in %s" kind_string name path_string)); 106 | make_project package_name path kind 107 | ;; 108 | 109 | let subcommand = 110 | let open Command in 111 | subcommand "new" ~aliases:[ "n" ] (singleton ~doc:"Create a new alice project." new_) 112 | ;; 113 | -------------------------------------------------------------------------------- /package/src/package.ml: -------------------------------------------------------------------------------- 1 | open! Alice_stdlib 2 | open Type_bool 3 | open Alice_package_meta 4 | open Alice_hierarchy 5 | open Alice_error 6 | open Alice_io.Read_hierarchy 7 | module File_ops = Alice_io.File_ops 8 | 9 | type t = 10 | { root : Absolute_path.Root_or_non_root.t 11 | ; meta : Package_meta.t 12 | } 13 | 14 | let to_dyn { root; meta } = 15 | Dyn.record 16 | [ "root", Absolute_path.Root_or_non_root.to_dyn root 17 | ; "meta", Package_meta.to_dyn meta 18 | ] 19 | ;; 20 | 21 | let equal t { root; meta } = 22 | Absolute_path.Root_or_non_root.equal t.root root && Package_meta.equal t.meta meta 23 | ;; 24 | 25 | let create ~root ~meta = { root; meta } 26 | 27 | let read_root root = 28 | let meta = 29 | match root with 30 | | `Root dir_path -> Alice_manifest.read_package_dir ~dir_path 31 | | `Non_root dir_path -> Alice_manifest.read_package_dir ~dir_path 32 | in 33 | create ~root ~meta 34 | ;; 35 | 36 | let root { root; _ } = root 37 | let meta { meta; _ } = meta 38 | let id { meta; _ } = Package_meta.id meta 39 | let name { meta; _ } = Package_meta.name meta 40 | let version { meta; _ } = Package_meta.version meta 41 | let dependencies { meta; _ } = Package_meta.dependencies meta 42 | let dependency_names t = dependencies t |> Dependencies.names 43 | let exe_root_ml = Basename.of_filename "main.ml" 44 | let lib_root_ml = Basename.of_filename "lib.ml" 45 | let src = Basename.of_filename "src" 46 | let src_dir_path t = Absolute_path.Root_or_non_root.concat_basename (root t) src 47 | let src_dir_exn t = src_dir_path t |> read_dir_exn 48 | let contains_exe t = File_ops.exists (src_dir_path t / exe_root_ml) 49 | let contains_lib t = File_ops.exists (src_dir_path t / lib_root_ml) 50 | 51 | module Typed = struct 52 | type ('exe, 'lib) type_ = 53 | | Exe_only : (true_t, false_t) type_ 54 | | Lib_only : (false_t, true_t) type_ 55 | | Exe_and_lib : (true_t, true_t) type_ 56 | 57 | type nonrec ('exe, 'lib) t = 58 | { package : t 59 | ; type_ : ('exe, 'lib) type_ 60 | } 61 | 62 | type lib_only_t = (false_t, true_t) t 63 | type exe_only_t = (true_t, false_t) t 64 | type exe_and_lib_t = (true_t, true_t) t 65 | 66 | let to_dyn : type exe lib. (exe, lib) t -> Dyn.t = 67 | fun { package; type_ } -> 68 | let type_ = 69 | match type_ with 70 | | Exe_only -> "Exe_only" 71 | | Lib_only -> "Lib_only" 72 | | Exe_and_lib -> "Exe_and_lib" 73 | in 74 | Dyn.record [ "package", to_dyn package; "type_", Dyn.variant type_ [] ] 75 | ;; 76 | 77 | let equal t { package; type_ = _ } = equal t.package package 78 | 79 | let limit_to_exe_only : (true_t, true_t) t -> (true_t, false_t) t = 80 | fun { package; _ } -> { package; type_ = Exe_only } 81 | ;; 82 | 83 | let limit_to_lib_only : (true_t, true_t) t -> (false_t, true_t) t = 84 | fun { package; _ } -> { package; type_ = Lib_only } 85 | ;; 86 | 87 | let package { package; _ } = package 88 | let name t = package t |> name 89 | let type_ { type_; _ } = type_ 90 | 91 | let contains_exe : type exe lib. (exe, lib) t -> exe Type_bool.t = 92 | fun t -> 93 | match t.type_ with 94 | | Exe_only -> True 95 | | Lib_only -> False 96 | | Exe_and_lib -> True 97 | ;; 98 | 99 | let contains_lib : type exe lib. (exe, lib) t -> lib Type_bool.t = 100 | fun t -> 101 | match t.type_ with 102 | | Exe_only -> False 103 | | Lib_only -> True 104 | | Exe_and_lib -> True 105 | ;; 106 | end 107 | 108 | let typed t = 109 | let package = t in 110 | match contains_exe package, contains_lib package with 111 | | false, false -> 112 | user_exn 113 | [ Pp.textf 114 | "Package %S defines contains neither an executable nor a library." 115 | (Package_id.name_v_version_string (id package)) 116 | ] 117 | | true, false -> `Exe_only { Typed.package; type_ = Exe_only } 118 | | false, true -> `Lib_only { Typed.package; type_ = Lib_only } 119 | | true, true -> `Exe_and_lib { Typed.package; type_ = Exe_and_lib } 120 | ;; 121 | 122 | type 'a with_typed = { f : 'exe 'lib. ('exe, 'lib) Typed.t -> 'a } 123 | 124 | let with_typed with_typed t = 125 | match typed t with 126 | | `Exe_only pt -> with_typed.f pt 127 | | `Lib_only pt -> with_typed.f pt 128 | | `Exe_and_lib pt -> with_typed.f pt 129 | ;; 130 | --------------------------------------------------------------------------------