Commit e1ad92fd authored by Dario Pinto's avatar Dario Pinto
Browse files

Add blog features: list by categories/authors, List categories and List...

Add blog features: list by categories/authors, List categories and List authors, link all pages together
parent a1fc79e2
type article =
{ date : int * int * int
; title : string
; authors : string list
; author : string
; tags : string list
; category : string
; content : string
; url : string
}
let string_of_file file =
let chan = open_in ("src/content/" ^ file) in
let rec aux acc chan =
match input_line chan with
| exception End_of_file -> acc
| line -> aux (acc ^ "\n" ^ line) chan
in
let res = aux "" chan in
close_in chan;
res
(** [allowed_categories] is a list of allowed categories for any article posted
on the blog *)
let allowed_categories =
[ "tooling"
; "blockchains"
; "oCamlPro"
; "formal methods"
; "trainings"
; "oCaml"
; "rust"
]
(** [raw_articles] List of all raw text in all articles in /content/blog/
subdirectory *)
let raw_articles =
List.find_all
(fun file ->
(String.length file >= 5 && String.equal (String.sub file 0 5) "blog/")
&& Filename.check_suffix file ".md" )
Content.file_list
let get_value field = List.hd (List.rev (String.split_on_char ':' field))
(** [get_meta_value field] extract the second field of meta_data required at the
beginning of the article *)
let get_meta_value field = List.hd (List.rev (String.split_on_char ':' field))
(** [extract_date date] convert date meta_data into a [(int * int * int)] type *)
let extract_date date =
match String.split_on_char '-' date with
| [ year; month; day ] ->
(int_of_string year, int_of_string month, int_of_string day)
| _ -> (0, 0, 0)
(** [article_of_string post url] convert a given raw_text article into an
[article] type *)
let article_of_string post url =
match String.split_on_char '\n' post with
| title :: authors :: date :: category :: tags :: r ->
| title :: author :: date :: category :: tags :: r ->
Some
{ date = extract_date (get_value date)
; title = get_value title
; authors =
(let authors = get_value authors in
match String.split_on_char ',' authors with
| [] -> [ "Unspecified authors!" ]
| auth -> auth )
{ date = extract_date (get_meta_value date)
; title = get_meta_value title
; author = get_meta_value author
; tags =
(let tags = get_value tags in
(let tags = get_meta_value tags in
match String.split_on_char ',' tags with
| [] -> [ "Unspecified tags!" ]
| tags -> tags )
; category = get_value category
; category =
begin
let c = get_meta_value category in
if not @@ List.mem c allowed_categories then
failwith "Category unknown";
c
end
; content = String.concat "\n" r
; url
}
| _ -> None
let post_header title authors (year, month, day) category tags =
Format.asprintf
{|<h1 id="page-title">%s</h1>
(** [get_article_data raw_articles] Returns a list of [article] types *)
let get_article_data raw_articles =
List.map
(fun article ->
match Content.read article with
| None -> failwith "invalid article data"
| Some data -> (
match
article_of_string data
(Filename.basename (Filename.chop_suffix article ".md"))
with
| None -> failwith "invalid article data"
| Some data -> data ) )
raw_articles
(** [articles_data] A list of [article] types *)
let articles_data = get_article_data raw_articles
(** [authors] The list of authors in the current pool of available articles *)
let authors =
let articles_by_authors =
List.sort_uniq (fun a1 a2 -> compare a1.author a2.author) articles_data
in
List.map (fun article -> article.author) articles_by_authors
(** [authors_count] A list of [(string * int)] when for each author [string],
his/her respective number of written articles *)
let authors_count =
let rec count acc current_author = function
| [] -> (current_author, acc)
| article :: r ->
if article.author = current_author then
count (acc + 1) current_author r
else
count acc current_author r
in
let rec map_authors_count acc = function
| [] -> acc
| current_author :: r ->
map_authors_count (count 0 current_author articles_data :: acc) r
in
List.rev (map_authors_count [] authors)
(** [categories_count] A list of [(string * int)] when for each category
[string] its corresponding number of articles *)
let categories_count =
let allowed_categories =
List.sort (fun c1 c2 -> compare c1 c2) allowed_categories
in
let rec count acc category = function
| [] -> (category, acc)
| article :: r ->
if article.category = category then
count (acc + 1) category r
else
count acc category r
in
let rec map_categories_count acc = function
| [] -> acc
| allowed_cat :: r ->
map_categories_count (count 0 allowed_cat articles_data :: acc) r
in
List.rev (map_categories_count [] allowed_categories)
let pp_cat_count fmt _categories_count = Format.fprintf fmt ""
let pp_article_excerpt fmt article =
let year, month, day = article.date in
Format.fprintf fmt
{|<div class="row">
<h3>
<a href="/blog/%s">%s</a>
</h3>
<div class="row">
<div class="col-lg-3">
<img src="/blog/assets/img/icon_person.svg" style="max-width:1em"/>
Authors: %a
<img class="icon" src="/blog/assets/img/icon_person.svg"/>
Author: %s
</div>
<div class="col-lg-3">
<img src="/blog/assets/img/icon_calendar.svg" style="max-width:1em"/>
Date: %4d-%2d-%2d
<img class="icon" src="/blog/assets/img/icon_calendar.svg"/>
Date: %4d-%02d-%02d
</div>
<div class="col-lg-3">
<img src="/blog/assets/img/icon_category.svg" style="max-width:1em"/>
<img class="icon" src="/blog/assets/img/icon_category.svg"/>
Category: %s
</div>
<div class="col-lg-3">
<img src="/blog/assets/img/icon_tags.svg" style="max-width:1em"/>
<img class="icon" src="/blog/assets/img/icon_tags.svg"/>
Tags: %a
</div>
</div>
<br/>
<br/>
<a href="/blog/%s">Read more...</a>
<hr class="featurette-divider"/>
|}
title
</div>
<br />|}
article.url article.title article.author year month day article.category
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.fprintf fmt ", ")
Format.pp_print_string )
authors year month day category
article.tags article.url
let pp_blog_posts fmt articles_data_list =
List.iter (Format.fprintf fmt "%a" pp_article_excerpt) articles_data_list
(** [specific_article_header title author (year, month, day) category tags]
prints the header for a given blog post *)
let specific_article_header title author (year, month, day) category tags =
Format.asprintf
{|<h1 id="page-title">%s</h1>
<div class="row">
<div class="col-lg-3">
<img class="icon" src="/blog/assets/img/icon_person.svg"/>
Author: %s
</div>
<div class="col-lg-3">
<img class="icon" src="/blog/assets/img/icon_calendar.svg"/>
Date: %4d-%02d-%02d
</div>
<div class="col-lg-3">
<img class="icon" src="/blog/assets/img/icon_category.svg"/>
Category: %s
</div>
<div class="col-lg-3">
<img class="icon" src="/blog/assets/img/icon_tags.svg"/>
Tags: %a
</div>
</div>
<hr class="featurette-divider"/>
|}
title author year month day category
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.fprintf fmt ", ")
Format.pp_print_string )
tags
let pp_articles fmt articles_data_list =
List.iter
(fun article ->
Format.fprintf fmt {|<a href="%s">%s</a>@.<br />|} article.url
article.title )
articles_data_list
let home_page home =
let articles =
List.find_all
(fun file ->
(String.length file >= 5 && String.equal (String.sub file 0 5) "blog/")
&& Filename.check_suffix file ".md" )
Content.file_list
(** [sub_cat category] Displays the list of articles corresponding to the
request category *)
let sub_cat category =
let articles_by_date =
List.sort (fun a1 a2 -> compare a2.date a1.date) articles_data
in
let articles_data =
List.map
(fun article ->
match Content.read article with
| None -> failwith "invalid article data"
| Some data -> (
match article_of_string data (Filename.chop_suffix article ".md") with
| None -> failwith "invalid article data"
| Some data -> data ) )
articles
let articles_of_category =
List.filter
(fun article -> String.equal article.category category)
articles_by_date
in
Format.asprintf
{|<h1 id="page-title">Articles on %s</h1>
<hr class="featurette-divider"/>
%a
|}
(String.capitalize_ascii category)
pp_blog_posts articles_of_category
(*
*
* /!\ NEED SOME PROCESSING ON AUTHOR NAMES
* REMOVE WHITESPACES FOR PROCESSING ??
*
* *)
(** [author ocp_author] Displays the list of articles written by a given
[ocp_author] *)
let author ocp_author =
let articles_by_date =
List.sort (fun a1 a2 -> compare a1.author a2.author) articles_data
in
let articles_of_author =
List.filter
(fun article -> String.equal article.author ocp_author)
articles_by_date
in
Format.asprintf
{|<h1 id="page-title">Articles on %s</h1>
<hr class="featurette-divider"/>
%a
|}
ocp_author pp_blog_posts articles_of_author
(** [category_home] List of all available categories on the Blog, along with
number of articles of given category *)
let category_home =
Format.asprintf
{|<h1 id="page-title">Blog Categories</h1>
<hr class="featurette-divider"/>
%a
|}
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.fprintf fmt "<br />")
(fun fmt (category, count) ->
Format.fprintf fmt
{|<h3><a href="/blog/category/%s">%s</a> (%d articles)</h3>|}
category
(String.capitalize_ascii category)
count ) )
categories_count
(** [authors_home] List of all available authors on the Blog, along with number
of articles for a given author *)
let authors_home =
Format.asprintf
{|<h1 id="page-title">Blog Authors</h1>
<hr class="featurette-divider"/>
%a
|}
(Format.pp_print_list
~pp_sep:(fun fmt () -> Format.fprintf fmt "<br /> ")
(fun fmt (author, count) ->
Format.fprintf fmt
{|<h3><a href="/blog/authors/%s">%s (%d articles)</h3>|} author
author count ) )
authors_count
(** [home_page] this is the home page for the blog, articles appear as excerpts
from most recent to oldest *)
let home_page =
let articles_by_date =
List.sort (fun a1 a2 -> compare a2.date a1.date) articles_data
in
Format.asprintf "%s%a" home pp_articles articles_by_date
Format.asprintf
{|<h1 id="page-title">Blog</h1>
<div class="row">
<h5>
<a href="/blog/authors">All authors here!</a>
</h5>
<h5>
<a href="/blog/category">All categories here!</a>
</h5>
</div>
<hr class="featurette-divider"/>
%a
|}
pp_blog_posts articles_by_date
......@@ -27,6 +27,10 @@ body {
max-width: 10em;
}
.icon {
max-width:1em;
}
#page-title {
text-align: center;
}
......
<h1 id="page-title">Blog</h1>
......@@ -4,84 +4,123 @@ date:2021-09-02
category:tooling
tags:static,portable,binaries
Generating static and portable executables with OCaml
Distributing OCaml software on opam is great (if I dare say so myself), but sometimes you need to provide your tools to an audience outside of the OCaml community, or just without recompilations or in a simpler way.
However, just distributing the locally generated binaries requires that the users have all the needed shared libraries installed, and a compatible libc. It’s not something you can assume in general, and even if you don’t need any C shared library or are confident enough it will be installed everywhere, the libc issue will arise for anyone using a distribution based on a different kind, or a little older than the one you used to build.
There is no built-in support for generating static executables in the OCaml compiler, and it may seem a bit tricky, but it’s not in fact too complex to do by hand, something you may be ready to do for a release that will be published. So here are a few tricks, recipes and advice that should enable you to generate truly portable executables with no external dependency whatsoever. Both Linux and macOS will be treated, but the examples will be based on Linux unless otherwise specified.
Example
I will take as an example a trivial HTTP file server based on Dream.
Sample code
The relevant part of our dune file is just:
(executable
Distributing OCaml software on opam is great (if I dare say so myself), but sometimes you need to provide your tools to an audience outside of the OCaml community, or just without recompilations or in a simpler way.
However, just distributing the locally generated binaries requires that the users have all the needed shared libraries installed, and a compatible libc. It&#8217;s not something you can assume in general, and even if you don&#8217;t need any C shared library or are confident enough it will be installed everywhere, the libc issue will arise for anyone using a distribution based on a different kind, or a little older than the one you used to build.
There is no built-in support for generating static executables in the OCaml compiler, and it may seem a bit tricky, but it&#8217;s not in fact too complex to do by hand, something you may be ready to do for a release that will be published. So here are a few tricks, recipes and advice that should enable you to generate truly portable executables with no external dependency whatsoever. Both Linux and macOS will be treated, but the examples will be based on Linux unless otherwise specified.
<h2>Example</h2>
I will take as an example a trivial HTTP file server based on <a href="https://github.com/aantron/dream">Dream</a>.
<summary>
Sample code
</summary>
<h5>
fserv.ml
</h5>
<pre><code class="language-ocaml">
let () = Dream.(run @@ logger @@ static &quot;.&quot;)
</code></pre>
<h5>
fserv.opam
</h5>
<pre><code class="language-python">
opam-version: &quot;2.0&quot;
depends: [&quot;ocaml&quot; &quot;dream&quot;]
</code></pre>
<h5>
dune-project
</h5>
<pre><code class="language-lisp">
(lang dune 2.8)
(name fserv)
</code></pre>
The relevant part of our <code>dune</code> file is just:
<pre><code class="language-lisp">(executable
(public_name fserv)
(libraries dream))
</code></pre>
This is how we check the resulting binary:
$ dune build fserv.exe
ocamlc .fserv.eobjs/byte/dune__exe__Fserv.{cmi,cmo,cmt}
<pre><code class="language-shell-session">
$ dune build fserv.exe
ocamlc .fserv.eobjs/byte/dune__exe__Fserv.{cmi,cmo,cmt}
ocamlopt .fserv.eobjs/native/dune__exe__Fserv.{cmx,o}
ocamlopt fserv.exe
$ file _build/default/fserv.exe
_build/default/fserv.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1991bb9f1d67807411c93f6fb6ec46b4a0ee8ed5, for GNU/Linux 3.2.0, with debug_info, not stripped
$ ldd _build/default/fserv.exe
$ file _build/default/fserv.exe
_build/default/fserv.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1991bb9f1d67807411c93f6fb6ec46b4a0ee8ed5, for GNU/Linux 3.2.0, with debug_info, not stripped
$ ldd _build/default/fserv.exe
linux-vdso.so.1 (0x00007ffe97690000)
libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fd6cc636000)
libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fd6cc342000)
libev.so.4 => /usr/lib/x86_64-linux-gnu/libev.so.4 (0x00007fd6cc330000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd6cc30e000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd6cc1ca000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd6cc1c4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd6cbffd000)
libssl.so.1.1 =&gt; /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fd6cc636000)
libcrypto.so.1.1 =&gt; /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fd6cc342000)
libev.so.4 =&gt; /usr/lib/x86_64-linux-gnu/libev.so.4 (0x00007fd6cc330000)
libpthread.so.0 =&gt; /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd6cc30e000)
libm.so.6 =&gt; /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd6cc1ca000)
libdl.so.2 =&gt; /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd6cc1c4000)
libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd6cbffd000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd6cced7000)
</code></pre>
(on macOS, replace ldd with otool -L; dune output is obtained with (display short) in ~/.config/dune/config)
(on macOS, replace <code>ldd</code> with <code>otool -L</code>; dune output is obtained with <code>(display short)</code> in <code>~/.config/dune/config</code>)
So lets see how to change this result. Basically, here, libev, libssl and libcrypto are required shared libraries that may not be installed on every system, while all the others are part of the core system:
So let&#8217;s see how to change this result. Basically, here, <code>libev</code>, <code>libssl</code> and <code>libcrypto</code> are required shared libraries that may not be installed on every system, while all the others are part of the core system:
linux-vdso, libdl and ld-linux are concerned with the dynamic loading of shared objects ;
libm and libpthread are extensions of the core libc that are tightly bound to it, and always installed.
<ul>
<li>
<code>linux-vdso</code>, <code>libdl</code> and <code>ld-linux</code> are concerned with the dynamic loading of shared objects ;
</li>
<li>
<code>libm</code> and <code>libpthread</code> are extensions of the core <code>libc</code> that are tightly bound to it, and always installed.
</li>
</ul>
Statically linking the libraries
<h2>
Statically linking the libraries
</h2>
In simple cases, static linking can be turned on as easily as passing the -static flag to the C compiler: through OCaml you will need to pass -cclib -static. We can add that to our dune file:
In simple cases, static linking can be turned on as asily as passing the <code>-static</code> flag to the C compiler: through OCaml you will need to pass <code>-cclib -static</code>. We can add that to our <code>dune</code> file:
<pre><code class="language-lisp">
(executable
(public_name fserv)
(flags (:standard -cclib -static))
(libraries dream))
</code></pre>
which gives:
&#8230; which gives:
<pre><code class="language-shell-session">
$ dune build fserv.exe
ocamlc .fserv.eobjs/byte/dune__exe__Fserv.{cmi,cmo,cmt}
ocamlc .fserv.eobjs/byte/dune__exe__Fserv.{cmi,cmo,cmt}
ocamlopt .fserv.eobjs/native/dune__exe__Fserv.{cmx,o}
ocamlopt fserv.exe
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/libcrypto.a(dso_dlfcn.o): in function `dlfcn_globallookup':
(.text+0x13): warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/usr/bin/ld: ~/.opam/4.11.0/lib/ocaml/libunix.a(initgroups.o): in function `unix_initgroups':
initgroups.c:(.text.unix_initgroups+0x1f): warning: Using 'initgroups' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/libcrypto.a(dso_dlfcn.o): in function `dlfcn_globallookup&#039;:
(.text+0x13): warning: Using &#039;dlopen&#039; in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/usr/bin/ld: ~/.opam/4.11.0/lib/ocaml/libunix.a(initgroups.o): in function `unix_initgroups&#039;:
initgroups.c:(.text.unix_initgroups+0x1f): warning: Using &#039;initgroups&#039; in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
[...]
$ file _build/default/fserv.exe
_build/default/fserv.exe: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=9ee3ae1c24fbc291d1f580bc7aaecba2777ee6c2, for GNU/Linux 3.2.0, with debug_info, not stripped
_build/default/fserv.exe: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=9ee3ae1c24fbc291d1f580bc7aaecba2777ee6c2, for GNU/Linux 3.2.0, with debug_info, not stripped
$ ldd _build/default/fserv.exe
not a dynamic executable
not a dynamic executable
</code></pre>
The executable was generated… and the result seems OK, but we shouldn’t skip all these ld warnings. Basically, what ld is telling us is that you shouldn’t statically link glibc (it internally uses dynlinking, to libraries that also need glibc functions, and will therefore still need to dynlink a second version from the system 🤯).
The executable was generated&#8230; and the result <em>seems</em> OK, but we shouldn&#8217;t skip all these <code>ld</code> warnings. Basically, what <code>ld</code> is telling us is that you shouldn&#8217;t statically link <code>glibc</code> (it internally uses dynlinking, to libraries that also need <code>glibc</code> functions, and will therefore <strong>still</strong> need to dynlink a second version from the system 🤯).
Indeed here, we have been statically linking a dynamic linking engine, among other things. Don&#8217;t do it.
Indeed here, we have been statically linking a dynamic linking engine, among other things. Don’t do it.
Linux solution: linking with musl instead of glibc
<h3>
Linux solution: linking with musl instead of glibc
</h3>
The easiest workaround at this point, on Linux, is to compile with musl, which is basically a glibc replacement that can be statically linked. There are some OCaml and gcc variants to automatically use musl (comments welcome if you have been successful with them!), but I have found the simplest option is to use a tiny Alpine Linux image through a Docker container. Here well use OCamlPro’s minimal Docker images but anything based on musl should do.
The easiest workaround at this point, on Linux, is to compile with <a href="http://musl.libc.org/">musl</a>, which is basically a glibc replacement that can be statically linked. There are some OCaml and gcc variants to automatically use musl (comments welcome if you have been successful with them!), but I have found the simplest option is to use a tiny Alpine Linux image through a Docker container. Here we&#8217;ll use OCamlPro&#8217;s <a href="https://hub.docker.com/r/ocamlpro/ocaml">minimal Docker images</a> but anything based on musl should do.
<pre><code class="language-shell-session">
$ docker run --rm -it ocamlpro/ocaml:4.12
[...]
~/fserv $ sudo apk add openssl-libs-static
......@@ -94,80 +133,108 @@ OK: 161 MiB in 52 packages
_build/default/fserv.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
~/fserv $ ldd _build/default/fserv.exe
/lib/ld-musl-x86_64.so.1 (0x7ff41353f000)
</code></pre>
Almost there! We see that we had to install extra packages with apk add: the static libraries might not be already installed and in this case are in a separate package (you would get bin/ld: cannot find -lssl). The last remaining dynamic loader in the output of ldd is because static PIE executable were not supported until recently. To get rid of it, we need to either replace -static with -static-pie if the version of gcc is high enough, or add -cclib -no-pie:
Almost there! We see that we had to install extra packages with <code>apk add</code>: the static libraries might not be already installed and in this case are in a separate package (you would get <code>bin/ld: cannot find -lssl</code>). The last remaining dynamic loader in the output of <code>ldd</code> is because static PIE executable were not supported <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81498#c1">until recently</a>. To get rid of it, we need to either replace <code>-static</code> with <code>-static-pie</code> if the version of gcc is high enough, or add <code>-cclib -no-pie</code>:
<pre><code class="language-lisp">
(executable
(public_name fserv)
(flags (:standard -cclib -static-pie))
(libraries dream))
</code></pre>
And we are good!
<pre><code class="language-shell-session">
~/fserv $ file _build/default/fserv.exe
_build/default/fserv.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
_build/default/fserv.exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
~/fserv $ ldd _build/default/fserv.exe
/lib/ld-musl-x86_64.so.1: _build/default/fserv.exe: Not a valid dynamic program
/lib/ld-musl-x86_64.so.1: _build/default/fserv.exe: Not a valid dynamic program
</code></pre>
Trick: short script to compile through a Docker container
<strong>Trick</strong>: short script to compile through a Docker container
Passing the context to a Docker container and getting the artefacts back can be bothersome and often causes file ownership issues, so I use the following snippet to pipe them to/from it using tar:
git ls-files -z | xargs -0 tar c | \
docker run --rm -i ocamlpro/ocaml:4.12 \
sh -uexc \
'{ tar x &&
opam switch create . ocaml-system --deps-only --locked &&
opam exec -- dune build --profile=release @install;
} >&2 && tar c -hC _build/install/default/bin .' | \
tar vx
Passing the context to a Docker container and getting the artefacts back can be bothersome and often causes file ownership issues, so I use the following snippet to pipe them to/from it using <code>tar</code>:
<pre><code class="language-bash">
git ls-files -z | xargs -0 tar c | \
docker run --rm -i ocamlpro/ocaml:4.12 \
sh -uexc \
&#039;{ tar x &amp;&amp;
opam switch create . ocaml-system --deps-only --locked &amp;&amp;
opam exec -- dune build --profile=release @install;
} &gt;&amp;2 &amp;&amp; tar c -hC _build/install/default/bin .&#039; | \
tar vx
</code></pre>
The other cases: turning to manual linking
<h3>
The other cases: turning to manual linking