Design
Goals
Re-use existing installation software when it is available.
Avoid lock-in to any existing installation software.
Use OCaml as much as possible.
Terms
- Packaging
The procedure for taking source code, doing some compiling and other translations, and ending up with an installable package (ex. setup.exe on Windows)
- Component
A logical piece of software that can be selected or deselected during installation. Components can depend on other components.
Each component has a concrete instantation as an Opam package
dkml-component-<COMPONENTNAME>
.- Installer
A single-file executable (ex.
setup.exe
) or a single-file installation bundle (ex.package-name.msi
,package-name.rpm
, etc.) that, when used by the end-user, will install all selected components.Each installer has a concreate instantation as an Opam package
dkml-installer-<INSTALLERNAME>
.- Installer Generator
A program that can create a customized installer that you can configure with your own installation instructions or an installation manifest. Examples include 0install and cpack.
- Component API
A collection of OCaml modules and module types that Component authors use to register their component.
- Installer API
A collection of OCaml modules and module types that that provide low-level boilerplate to create an installer for Installer authors.
- Component OPAM_BUILD Phase
When Opam runs the build: [instructions …] for a component, we’ll be calling that the OPAM_BUILD phase
- Component OPAM_INSTALL Phase
When Opam runs the install: [instructions …] for a component, we’ll be calling that the OPAM_INSTALL Phase
Packaging Flow
Creating the installer package
The installer can be generated directly using:
opam switch create installer-$INSTALLERNAME --empty
opam install --switch installer-$INSTALLERNAME ./dkml-installer-$INSTALLERNAME.opam
The generated installer will be available in the Opam switch’s
$OPAM_SWITCH_PREFIX/share/$INSTALLERNAME/dist/
folder.
Note
There is an GitHub Actions workflow package
that is run for
dkml-installer-<INSTALLERNAME>
whenever a commit or a tag is pushed. It does the same
opam switch create
and opam install
as above.
From now on we’ll just say installer $INSTALLERNAME
is I
.
Here is what the opam install ...
step does in detail:
Opam builds and install the Opam dependencies. Specifically Opam does the equivalent of:
Each component
C
listed inI
’sdkml-installer-<INSTALLERNAME>.opam
dependencies will go through its OPAM_BUILD and OPAM_INSTALL phases using the instructions inC
’sdkml-component-<C>.opam
.A component may choose to expose an
dkml-component-<C>-api.opam
if there are any OCaml types, constants and functions which need to be shared with the consumers of that component.The OPAM_INSTALL phase of
dkml-component-<C>.opam
is responsible for:Placing files in
<share>/static-files/
where <share> is<opamswitch>/share/dkml-component-<C>/
. These files will be used in the USER_DEPLOY_INITIAL phase.Placing executables and files in
<share>/staging-files/<target_arch>
. These files will be used in the USER_INSTALL phase
Important
Relocatable requirements
Anything inside
<share>/static-files/
and<share>/staging-files/
must be relocatable to a different location on the end-user’s hard drive. As of January 2022, many executables likeocamlc
andocamlbuild
are not relocatable.Instead the only native executable should be
ocamlrun
(provided bydkml-component-ocamlrun.opam
).Important
Target architectures
Creating universal applications for macOS or archive libraries for Android requires that more than one target architecture is available in the application. Staging files have a
<target_arch>/
subfolder that can be used to create universal applications. If the staging files are architecture agnostic then the files should go into ageneric/
subfolder.
Side-by-side copies all the
<share>/static-files/
and<share>/staging-files/
directories. It does the equivalent of the following for all componentsC
:rsync -a $OPAM_SWITCH_PREFIX/share/$C/static-files/ \ $OPAM_SWITCH_PREFIX/share/$I/static-files/$C/ rsync -a $OPAM_SWITCH_PREFIX/share/$C/staging-files/ \ $OPAM_SWITCH_PREFIX/share/$I/staging-files/$C/
Create dune_site plugin loader-based executables named
dkml-package-setup.bc
,dkml-package-uninstaller.bc
,<package>-user-runner.exe
and<package>-admin-runner.exe
that will perform the steps in User runs the installerThe last step depends on what type of installer generator has been configured. As of Apr 2022 only the Console installer generator is available, and no configuration is needed. But regardless of which installer generator is available, the Component packages should not change.
- Console Installer Generator
This installer will produce a
$OPAM_SWITCH_PREFIX/share/$I/dist/$I.zip
file or a$OPAM_SWITCH_PREFIX/share/$I/dist/$I.tar.gz
file.All of the
$OPAM_SWITCH_PREFIX/share/$I/static-files/
will go into thestatic
top-level folder of the$I.zip
archive.All of the
$OPAM_SWITCH_PREFIX/share/$I/staging-files/
will go into thestaging
top-level folder of the$I.zip
archive.The
dkml-package-setup.bc
,dkml-package-uninstaller.bc
,<package>-user-runner.exe
and<package>-admin-runner.exe
executables will be placed in the root of the$I.zip
archive.- Future Possibility: 0install
If no component needs administrative permission then 0install would be a good choice for a cross-platform installer.
- Future Possibility: cpack
cpack would be a good choice for generating a variety of installers across many platforms (
.rpm
,.msi
, etc.), although it is much harder to configure than 0install.
User runs the installer
[
dkml-package-setup.bc
] Load all the components with dune_site’sSites.Plugins.Plugins.load_all ()
:When a component (plugin)
C
is loaded, it will register itself with thedkml-install-api
registry.
[
dkml-package-setup.bc
] After all the components are registered, the components are topologically sorted based on their dependencies.[
dkml-package-setup.bc
] Ask end-user which components to install. Some components may have configuration that lets them display text (ex. license) or ask more questions.[
dkml-package-setup.bc
] Formulate command line options for<package>-user-runner.exe
and<package>-admin-runner.exe
that correspond to the end-user selections. By default the staging directory will be thestaging
directory that is in the same directory asdkml-package-setup.bc
. The same command line options will be used in both executables.Note
This is a underspecified spot in the design. There needs to be a “selections” artifact created by the Console Installer or GUI installer to describe the end-user choices. And there needs to be some mapping from that “selections” artifact into command line options for
<package>-user-runner.exe
and<package>-admin-runner.exe
. And each component should be able to influence how that selections artifact is created.A good start is by analogy to cross-platform UI component design, where you can assemble UI components into your own component hierarchy (ex. DOM), and then have them render to different graphics backends (web / native). The context record that is part of the current component implementation can have extra fields so each component can add new <input> elements to the root DOM, and other fields to convert the <input> elements into command line options. It would be the responsibility of the Text/GUI installer to render the <input> elements as questions for Text or visual choices for a GUI.
Early versions of the installer will simply have no choices.
[
dkml-package-setup.bc
] Check if there are any components that needs administrative/root privileges. The check will be like:Component.needs_admin "<end_user_installation_prefix>"
[
dkml-package-setup.bc
] ADMIN_INSTALL phase If there are any components that needs administrative/root privileges, then:[
dkml-package-setup.bc
] Spawn the<package>-admin-runner.exe
executable as an elevated Unix process:# If doas is available, especially for OpenBSD doas <package>-admin-runner # If sudo is available sudo <package>-admin-runner # Otherwise use su su root -c <package>-admin-runner
or with a Windows User Account Control Application Manifest.
An alternative for Windows is to use PowerShell to ask for Administrative privileges:
Start-Process powershell -ArgumentList '& <package>-admin-runner.exe' -verb RunAs
The options given to
<package>-admin-runner.exe
were formulated in an earlier step, plus an extra option is added for the location of thestaging
folder.[
<package>-admin-runner.exe
] In topological order call each component:Component.run_as_admin "<end_user_installation_prefix>"
[
dkml-package-setup.bc
] USER_DEPLOY_INITIAL phase: Copy thestatic/$C
folder of each componentC
from the archive to the <end_user_installation_prefix>.[
dkml-package-setup.bc
] USER_INSTALL phase:[
dkml-package-setup.bc
] Spawn<package>-user-runner.exe
with the options formulated in an earlier step, plus an option for the location of thestaging
folder.[
<package>-user-runner.exe
] In topological order call each component like:Component.run_as_user "<end_user_installation_prefix>"