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
Clisted inI’sdkml-installer-<INSTALLERNAME>.opamdependencies 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.opamif 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>.opamis 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 likeocamlcandocamlbuildare 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.exeand<package>-admin-runner.exethat 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.zipfile or a$OPAM_SWITCH_PREFIX/share/$I/dist/$I.tar.gzfile.All of the
$OPAM_SWITCH_PREFIX/share/$I/static-files/will go into thestatictop-level folder of the$I.ziparchive.All of the
$OPAM_SWITCH_PREFIX/share/$I/staging-files/will go into thestagingtop-level folder of the$I.ziparchive.The
dkml-package-setup.bc,dkml-package-uninstaller.bc,<package>-user-runner.exeand<package>-admin-runner.exeexecutables will be placed in the root of the$I.ziparchive.- 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)
Cis loaded, it will register itself with thedkml-install-apiregistry.
[
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.exeand<package>-admin-runner.exethat correspond to the end-user selections. By default the staging directory will be thestagingdirectory 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.exeand<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.exeexecutable 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.exewere formulated in an earlier step, plus an extra option is added for the location of thestagingfolder.[
<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/$Cfolder of each componentCfrom the archive to the <end_user_installation_prefix>.[
dkml-package-setup.bc] USER_INSTALL phase:[
dkml-package-setup.bc] Spawn<package>-user-runner.exewith the options formulated in an earlier step, plus an option for the location of thestagingfolder.[
<package>-user-runner.exe] In topological order call each component like:Component.run_as_user "<end_user_installation_prefix>"