prometheansacrifice

Running notes. Daily logs

Logs

This tip about redirecting eshell to org-capture was interesting

Package managers have to resilient to tarball formats

Mongo aggregation framework: Why does it exist? How is it different from queries?

It's an attempt to have analytics in db itself. Spark and Hadoop dont have storage and transfer from db to hadoop/spark can be slow. Why not have it in the db itself?

Querries are simple filtering. Aggregation brings more computational power to queries

After aggregation, usually a smaller amount of data moves to application

Krisp noise supression and Deep Noise Suppression

sources of a package in ocaml

  1. Stdlib: eg unix
  2. Opam repo of packages
  3. Pinned: git URLs or local paths

Then parse the command args

ocaml/merlin/src/frontend/ocamlmerlin/new/new_merlin.ml::main@{2024-09-10}::87]

let config, command_args =
  Mconfig.parse_arguments
    ~wd:(Sys.getcwd ()) ~warning:(fun w -> fails := w :: !fails)
    (List.map snd spec) raw_args Mconfig.initial command_args

esy binary wrappers and statically linked binaries

:PUBDATE: 2024-08-27 Tue 17:13

CI pipeline used `–no-env` for Linux pipeline and re-used most of the setup for other platforms.

It appears, now, statically linked esy binaries have to be published without the sandbox wrapper. We see problems when the sandbox wrapper tries to launch the esy process - it uses `execve` specifies the environment. If we don't specify the environment, as in `–no-env`, and directly exec, the running esy binary directly assumes the bash processes environment variables.

bash --(fork)--> esy wrapper --(exec)--> real binary +
  |                                                  |
  +-------(exec)--+-----(exec)-----------------------+

This breaks how we resolve esySolveCudfCommand and other executables - we resolve them relative to $_ -> /proc/self/exe -> Sys.argv

More importantly, --no-env has no purpose with the wrapper program. It would make the wrapper a no-op binary wrapper.

We have to be careful while providing sandbox environment (TODO document this)

If all the dependencies of esy are not present in the sandbox (git and curl are not), then things may not work reliably. We saw this when we were using esy release ourselves the wrapped esy, curl spawned by esy needed `musl` library but was of course not available in the environment (because we didn't package it). It looks as if –no-env is not the best trick to be able to use the existing pipeline infrastructure at the same time ship static binaries.

All this could be real pain points for users coming on to Reason/OCaml and not being familiar with native compilation and devops associated. esy should ideally provide better guidelines and framework for CI setups

I was wondering, whats the difference between using t('a) and t(a) where a is instantiated by a Functor application

Should it be,

module type APP = {
  type route = list(string);
  type session;
  type action('view);
  let isValidSession: session => bool;
  let route: (session, route) => action('view);
};

Or,

module type APP = {
  type route = list(string);
  type session;
  type view;
  type action(view);
  let isValidSession: session => bool;
  let route: (session, route) => action('view);
};

Where, view is type later instantiated by the Functor application.

module App = Make(blah...) with type view  = someFunctorisedType.

Answer: a separate type view

Because if you dont, and if you do, action('view), when you create App without the functor, you'll end up with

module App: APP = {
  type route = list(string);
  type session = bool;
  type action(string) =
    | UI(string)
    | Redirect(route);
  let isValidSession = session => session;
  let route = (session, route) => {
    switch((isValidSession(session), route)) {
    | (true, []) => UI("home")
    | (false, []) => Redirect(["login"])
    }
  };
}

… which makes no sense.

This happeneded because, you initially started out trying to functorise away view Not just make action polymorphic.

Polymorphic action signifies that action is a shape whose details dont matter. Functorised action isn't like this.

Server React and client react dont agree on Suspense signature

module Suspense = struct
  let or_react_null = function None -> null | Some x -> x

  let make ?fallback ?children () =
    Suspense
      { fallback = or_react_null fallback; children = or_react_null children }
end
module Suspense: {
  [@mel.obj]
  external makeProps:
    (~children: element=?, ~fallback: element=?, unit) =>
    {
      .
      "children": option(element),
      "fallback": option(element),
    };
  [@mel.module "react"]
  external make:
    component({
      .
      "children": option(element),
      "fallback": option(element),
    }) =
    "Suspense";
};

Example of type signature of function with optional param

file:/dream-web-server-sessions/bin/main.re::(~error:string?, unit) > React.element #+BEGINSRC (~error:string=?, unit) => React.element#+ENDSRC

Disabling Dream webserver's deprecation

file:dream-web-server-sessions/bin/main.re::\[@alert "-all--all+deprecated"\] // For Dream]]

[@alert "-all--all+deprecated"];
// For Dream

Guest wifi login modal pages dont open because of DNS settings

https://zapier.com/blog/open-wifi-login-page/

Removing/reset alternate DNS addresses helps

Caching incorrectly can lead to security bugs

See https://bugzilla.redhat.com/show_bug.cgi?id=1990415

TLDR; a directory path gets cached, then a malicious actor replaces the directory entry with a symlink with same name and violates security assumptions

uninterpreter extension mel.obj

File "server/server.re", line 40, characters 35-59: 40 | dangerouslySetInnerHTML={{"_html": globalStyles}} ^^^^^^^^^^^^^^^^^^^^^^^^

Add melange.ppx in the dune file

esy internals: given a path to npm package, how to know if it's a JS package or native

/**

   Figure if a package is JS or esy package

   Context: Packages from NPM could contain, not just JS, but any natively compiled library.

*/

let* packageJson = NpmPackageJson.ofDir(src);
switch (packageJson |> Option.bind(~f=NpmPackageJson.esy)) {
| Some(_) =>
  let* () =
    RunAsync.ofLwt @@
    Esy_logs_lwt.debug(m =>
      m(
        "NodeModuleLinker: skipping %a because it's package.json contains 'esy' field",
        Path.pp,
        src,
      )
    );
  RunAsync.return();
| None => Fs.hardlinkPath(~src, ~dst)
};

Notes:

  1. No easy way to turn a path to InstallManifest.re since seen in Solver.re that seems to know if a package is esy or npmjs
  2. childNode.source isn't useful as it only tells if a package is opam or not

esy internals: So source types have an additional opam field to tell if a package is from opam or not

"source": {
  "type": "install",
  "source": [
    "archive:https://opam.ocaml.org/cache/sha256/59/59f2f1abbfc8a7ccbdbf608894e5c75e8a76006e34899254446f83e200dfb4f9#sha256:59f2f1abbfc8a7ccbdbf608894e5c75e8a76006e34899254446f83e200dfb4f9",
    "archive:https://github.com/ocaml-community/yojson/releases/download/2.1.2/yojson-2.1.2.tbz#sha256:59f2f1abbfc8a7ccbdbf608894e5c75e8a76006e34899254446f83e200dfb4f9"
  ],
  "opam": {
    "name": "yojson",
    "version": "2.1.2",
    "path": "esy.lock/opam/yojson.2.1.2"
  }

Explains the following then,

type t =
  | Link({
      path: DistPath.t,
      manifest: option(ManifestSpec.t),
      kind: Source.linkKind,
    })
  | Install({
      source: (Dist.t, list(Dist.t)),
      opam: option(opam),
    })

and why opam is an option. It could be missing from solution file, index.json

esy internals: So OpamManifest.t is turned into InstallManifest.t

let toInstallManifest = (~source=?, ~name, ~version, manifest) => {

This is what I'm looking for

I could use this to figure if an NPIM package should be installed with no module linkers or not. git:~/development/esy/esy/esy-solve/Solver.re::prometheansacrifice/pnpm-inspired-linker@{2024-05-01}::513

if (!Universe.mem(~pkg=manifest, universe^)) {
  switch (manifest.kind) {
  | InstallManifest.Esy =>
    universe := Universe.add(~pkg=manifest, universe^);
    let* dependencies =
      RunAsync.ofRun(evalDependencies(solver, manifest));
    let* () =
      RunAsync.contextf(
        addDependencies(dependencies),
        "resolving %a",
        InstallManifest.pp,
        manifest,
      );

    universe := Universe.add(~pkg=manifest, universe^);
    return();
  | InstallManifest.Npm => return()
  };
} else {
  return();
}

esy internals: How are manifests created in esy?

I need them to figure if a package being installed from NPM is meant for node.js or esy Packages built with esy have esy field in them

[2024-05-01 Wed] Dependencies.t only tell if the package has npm formula (package.json) or opam (.opam file)

and addDependencies = (dependencies: Dependencies.t) =>
  switch (dependencies) {
  | Dependencies.NpmFormula(reqs) =>
    let f = (req: Req.t) => addDependency(req);
    RunAsync.List.mapAndWait(~f, reqs);

  | Dependencies.OpamFormula(_) =>
    let f = (req: Req.t) => addDependency(req);
    let reqs = Dependencies.toApproximateRequests(dependencies);
    RunAsync.List.mapAndWait(~f, reqs);
  }

Github Actions: Not use "deploy from branch" but "Github actions" as source for github pages

This is how I prevented the additional deployment (pages-build-and-deployment) which was expecting the repo to be jekyll

GPT: Unused variables in common lisp

CMU's common lisp book

Learning about conditional directives with GPT

Emacs eshell: because it's possible to mix bash with elisp

Google Cloud has a way to provide justification or reason for key access when keys are external

prismic to create landing pages

Summary: Generic programming in OCaml

Ref: https://arxiv.org/pdf/1812.11665.pdf

Introduces Generic programming in OCaml with,

  1. extensible variant based GADT
  2. Records as a workaround to implement extension functions
  3. Hashtable trick to make avoid order of declaration problem
  4. Workaround for higher kinded types

Rest of the paper introduced strategies for Generic Views and type indexed functions.

After that, it mentioned reimplementations of Haskell libraries Uniplate, multiplate etc and using them to solve the original binary tree traversal with generics. Also discusses effectful computations

Everytime you ask a user to click, you lose them - Andrew Chen

Google's in-house writer on how to write with AI

Low level understanding of ios applications

Understanding how ios apps are built without Xcode

Stopping all docker containers with one command

docker container stop $(docker ps -a -q)

While creating a hello world cargo/rust program with esy I ran into linker errors

= note: ld: multiple errors: archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libpanic_unwind-b9303f5dcd4c8d61.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libaddr2line-074193e7ccb12f2d.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_demangle-592dc2260cf64a27.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libhashbrown-95abce77d407cda5.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_std_workspace_alloc-6d0f3b01c36286cc.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcfg_if-e33a663a2dcce97d.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libobject-ae5454bb02d34cb7.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libunwind-07ad8f4801703872.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/liblibc-d02e2e94e82428e3.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libgimli-ba7e4c687a24d092.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_std_workspace_core-f2cc3399f2e93551.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/liballoc-9911d63dc36d4937.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libstd-a77d2ee571f558e4.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcompiler_builtins-d8b74547953a18ba.rlib'; archive member 'lib.rmeta' not a mach-o file in '/Users/manas/.esy/3__________________________________________________________________/i/esy_rustup-29f35d7f/.rustup/toolchains/1.52-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcore-e12e04ef43bf5ffa.rlib'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

It was because Rust edition was 2018 Upgrading to 2021 with the esy-rustup@1.56 (unpublished to npm) fixed the issue. I used the following resolution.

{
  "resolutions": {
    "esy-rustup": "esy-packages/esy-rustup:esy.json#16c0801743a43f86d13f228ff0b2167015ffd2af"
  }
}

ComSpec on Windows must always use backslashes.

Fwd slashes and back slashes are interchangeable is a myth

https://github.com/esy/esy/commit/0e956cabd682ae9fd0f6f38c053fb220a86f92d9

Fixes broken system() from CRT

Error message: The syntax of the command is incorrect.

With recent commit replacing \ to / in the exported environment, COMSPEC has / instead of \, which breaks system/wsystem stdlib functions, which breaks OCaml's Sys.command. This, for instance, breaks compiler's -pp argument which calls external commands with Sys.command

This is why we see the gawk command failing on CI and noticed none of the reason programs compiling because compile shells out with Sys.command with -pp argument

There's an RFC to control increased centralisation of internet

Classical Hindley-Milner type system cannot directly work with GADTs

:PUBDATE: 2023-12-06 Wed 16:56

From Generic Programming in OCaml

To implement show we need another extension to OCAML type system introduced in version 4.00: locally abstract types. This type annotation is necessary to help the type checker while pattern matching over a GADT, since the type indices of a GADT may be instantiated to different concrete types depending on the constructor case, which is not possible with the classical Hindley-Milner algorithm

There is a tool to extract meta information from react components

Converting HEIC files to JPG on macos

OCaml 5.2.0 will make a change wrt how function expressions are represented

OCaml 5.1.0 has two different helper APIs to create a function expression AST

val fun_: ?loc:loc -> ?attrs:attrs -> arg_label -> expression option
          -> pattern -> expression -> expression
val function_: ?loc:loc -> ?attrs:attrs -> case list -> expression

ref

With this PR, specifically this commit, these helpers have been merged into one - function_

val function_ : ?loc:loc -> ?attrs:attrs -> function_param list
               -> type_constraint option -> function_body
               -> expression

This helper needs the following types

and function_param = { pparam_loc : Location.t;
    pparam_desc : function_param_desc;
  }
and function_param_desc =
  | Pparam_val of arg_label * expression option * pattern

and type_constraint =
  | Pconstraint of core_type
  | Pcoerce of core_type option * core_type
and function_body =
  | Pfunction_body of expression
  | Pfunction_cases of case list * Location.t * attributes

An example illustrating usage of the new API, comparing with the old would look like this.

@@ -358,11 +372,28 @@ let check_phrase phrase =
           let open Ast_helper in
           with_default_loc loc
             (fun () ->
+#if OCAML_VERSION >= (5, 2, 0)
+              let function_params = [ { pparam_loc = loc; pparam_desc = Pparam_val (Nolabel, None, (Pat.construct unit None)) } ] in
+              Str.eval
+                (Exp.function_
+                  ~loc
+                  ~attrs:[] 
+                   function_params
+                   None
+                   (Pfunction_body (Exp.letmodule
+                                     ~attrs:[]
+                                     ~loc
+                                     (with_loc loc (Some "_"))
+                                     (Mod.structure (item :: items))
+                                     (Exp.construct unit None))))
+#else
                Str.eval
                  (Exp.fun_ Nolabel None (Pat.construct unit None)
                    (Exp.letmodule (with_loc loc (Some "_"))
                       (Mod.structure (item :: items))
-                      (Exp.construct unit None))))
+                      (Exp.construct unit None)))
+#endif
+            )
         in
         let check_phrase = Ptop_def [top_def] in
         try

Ref: https://github.com/ocaml-community/utop/compare/master...DiningPhilosophersCo:utop:prometheansacrifice%40ocaml-5-2.patch

Trunk OCaml compiler usually has bad tooling supports

I was working with a fork of OCaml compiler on the utop source tree and noticed merlin-libs doesn't compile. Not surprising.

Just a reminder that working with OCaml trunk means not tooling :(

"devDependencies": {
  "@opam/ocaml-lsp-server": "*",
  "@opam/ocamlformat": "*"
},
(cd _build/default && /Users/manas/.esy/3__________________________________________________________________/i/ocaml-1844970f/bin/ocamlc.opt -w -40 -g -bin-annot -I src/config/.merlin_config.objs/byte -no-alias-deps -o src/config/.merlin_config.objs/byte/merlin_config.cmo -c -impl src/config/merlin_config.ml)
File "src/config/merlin_config.ml", line 8, characters 54-66:
8 |   | `OCaml_4_14_0 | `OCaml_5_0_0  | `OCaml_5_1_0  ] = `OCaml_5_2_0
                                                          ^^^^^^^^^^^^
Error: This expression has type "[> `OCaml_5_2_0 ]"
       but an expression was expected of type
         "[ `OCaml_4_02_0
         | `OCaml_4_02_1
         | `OCaml_4_02_2
         | `OCaml_4_02_3
         | `OCaml_4_03_0
         | `OCaml_4_04_0
         | `OCaml_4_05_0
         | `OCaml_4_06_0
         | `OCaml_4_07_0
         | `OCaml_4_07_1
         | `OCaml_4_08_0
         | `OCaml_4_09_0
         | `OCaml_4_10_0
         | `OCaml_4_11_0
         | `OCaml_4_12_0
         | `OCaml_4_13_0
         | `OCaml_4_14_0
         | `OCaml_5_0_0
         | `OCaml_5_1_0 ]"
       The second variant type does not allow tag(s) "`OCaml_5_2_0"
error: command failed: 'dune' 'build' '-p' 'merlin-lib' '-j' '4' (exited with 1)
esy-build-package: exiting with errors above...

Command to compiler a single Reason file without Dune - ie. with just the compiler

ocamlopt -verbose -pp 'refmt --print binary' -impl hello.re -o hello-reason

ocaml - the toplevel command - doesn't have a -pp option

$ esy ocaml -pp 'refmt --print binary'
ocaml: unknown option '-pp'.
Usage: ocaml <options> <files>
Try 'ocaml --help' for more information.#+END_SRC

This means, it's not possible to run Reason expressions off the ocaml toplevel. I can see why they'd just add support for utop - a new toplevel has to be written for Reason anyways, and while at it, why not pick the one with better CLI experience.

Path resolution of a command can fail if the executable bit is unset

When working with esy release of a project that is just a bash script, path resolution kept failing even if the script was on $PATH

Reason: it didn't have executable bit set on it's file permissions

Edebug can be used to instrument elisp. And likely, to figure how undocumented elisp code works

ref

Using edebug to figure how to write an org-babel language backend was suggested in the docs for org-babel

Script to repeatedly run rtop and find missing runtime opam dependency and add it to esy.json

:PUBDATE: 2023-11-09 Thu 22:53

# Hacky script to find missing opam dep and add it to esy.json

MISSING_PACKAGE="start" # Just a starting value to get the loop started
while [ ! -z "$MISSING_PACKAGE" ]
do
    npm r -g @prometheansacrifice/reason-cli; rm -rf _release && esy npm-release && cd _release && npm pack && npm i -g ./prometheansacrifice-reason-cli-0.0.0.tgz --force  && cd ../

    MISSING_PACKAGE=$(rtop 2>&1  | grep -o -E '"([^"]+)"' | grep -v required | sed 's/"//g')

    QUERY=".esy.release.includePackages += [\"@opam/$MISSING_PACKAGE\"]"
    jq "$QUERY" esy.json > esy.json.tmp
    mv esy.json.tmp esy.json
done

echo Done

HTTP 303 redirect

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303

For redirects that dont link to the resource, but to another interstitial, say a paywall/auth etc

Constant binary expressions in JS are usually unintentional and can be avoided

https://jordaneldredge.com/blog/interesting-bugs-caught-by-eslints-no-constant-binary-expression/

It seems developers rarely write constant binary expressions and when they do it's unintentional bugs. This article lists possible bugs with real world examples that end-up being unintentional binary expressions

Go runtime is not aware of the CPU limits set on the docker container

Reading this article made me realise docker may not necessarily respect --cpu

docker run --cpus=4 -p 8080:8080 $(ko build -L main.go)

This is because GO runtime is still unaware of this setting and it's GC would still employ all the CPU cores.

To address this, add environment variable, GOMAXPROCS to the run command.

docker run --cpus=4 -e GOMAXPROCS=4 -p 8080:8080 $(ko build -L main.go)

The new -I +str while using ocamlopt with ocaml 5

I kept getting,

ocamlopt -c fs.cmx esy_installer.mli esy_installer.ml # only dependencies newer than target need to be built
File "_none_", line 1:                                                                                                                                        
Alert ocaml_deprecated_auto_include:                                                                                                                          
OCaml's lib directory layout changed in 5.0. The str subdirectory has been                                                                                    
automatically added to the search path, but you should add -I +str to the                                                                                     
command-line to silence this alert (e.g. by adding str to the list of                                                                                         libraries in your dune file, or adding use_str to your _tags file for                                                                                         
ocamlbuild, or using -package str for ocamlfind).                                                                                                             

Following the advice fixes it. Below is an example.

esy_installer.$(OCAML_OBJECT_EXT): fs.$(OCAML_OBJECT_EXT) esy_installer.mli esy_installer.ml
        $(OCAML_COMPILER) -c -I +str $? # only dependencies newer than target need to be built

Example of how order of cmo files matter when compiling with ocamlopt

Before,

OCAML_OBJECTS = src/lexer.$(OCAML_OBJECT_EXT) src/parser.$(OCAML_OBJECT_EXT) src/esy_installer.$(OCAML_OBJECT_EXT) src/fs.$(OCAML_OBJECT_EXT)

Error

File "_none_", line 1:
Error: No implementations provided for the following modules:
         Fs referenced from src/esy_installer.cmx
make: ***  Error 2
#+PROPERTY: PUBDATE esy-installer

After,

OCAML_OBJECTS = src/lexer.$(OCAML_OBJECT_EXT) src/parser.$(OCAML_OBJECT_EXT) src/fs.$(OCAML_OBJECT_EXT) src/esy_installer.$(OCAML_OBJECT_EXT)

Compiling an OCaml module from another directory with ocamlopt

I was working on esy-boot-installer and wanted to write a test, fs_test.ml that depends on a module in src/

I kept running into the following

ocamlopt -c fs.cmx fs_test.ml                                                                                                                                 
File "fs_test.ml", line 2, characters 2-11:                                                                                                                   
2 |   Fs.mkdirp "./foo/bar";
Error: Unbound module Fs                                                       

Despite providing the module cmx, the compiler couldn't compile fs_test.ml because it was missing the interface file cmi To fix it, I had to add -I ../src

fs_test.$(OCAML_OBJECT_EXT): ../src/fs.$(OCAML_OBJECT_EXT) fs_test.ml 
        $(OCAML_COMPILER) -I ../src -c $^

Created: 2025-05-24 Sat 10:55