Skip to content

Commit

Permalink
singularity-tools.buildImageFromDef: init
Browse files Browse the repository at this point in the history
  • Loading branch information
ShamrockLee committed Apr 5, 2023
1 parent 36432b8 commit 53a1065
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 0 deletions.
79 changes: 79 additions & 0 deletions pkgs/build-support/singularity-tools/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
, writeMultipleReferencesToFile
, writeScript
, writeShellScriptBin
, writeText
, e2fsprogs
, util-linux
, bash
, coreutils
, runtimeShell
, singularity
, storeDir ? builtins.storeDir
Expand Down Expand Up @@ -278,4 +280,81 @@ rec {

# in
# result;

inherit (import ./definition-lib.nix { inherit lib; })
knownPrimarySectionNamesDefault
knownAppSectionNamesDefault
toSingularityDef
;

contentsToDef =
{ contents ? [ ]
, definitionOverrider ? null
}:
let
layerClosure = writeMultipleReferencesToFile (contents ++ [ bash coreutils ]);
in
(
if lib.isFunction definitionOverrider then
definitionOverrider
else if builtins.isAttrs definitionOverrider then
(d: lib.recursiveUpdate d definitionOverrider)
else
lib.id
) {
header.Bootstrap = "scratch";
setup = ''
mkdir -p ''${SINGULARITY_ROOTFS}/${storeDir}
for f in $(cat ${layerClosure}) ; do
cp -r "$f" "''${SINGULARITY_ROOTFS}/${storeDir}"
done
mkdir -p "''${SINGULARITY_ROOTFS}/bin"
"${coreutils}/bin/ln" -s "${runtimeShell}" "''${SINGULARITY_ROOTFS}/bin/sh"
mkdir -p "''${SINGULARITY_ROOTFS}/usr/bin"
"${coreutils}/bin/ln" -s "${coreutils}/bin/env" "''${SINGULARITY_ROOTFS}/usr/bin/env"
'';
environment = {
PATH = "${lib.makeBinPath contents}:\${PATH:-}";
};
labels = {
inherit layerClosure;
};
};

buildImageFromDef =
args@{ name
, definition ? contentsToDef { inherit contents definitionOverrider; }
, contents ? [ ]
, definitionOverrider ? null
, executableFlags ? [ ]
, buildImageFlags ? [ ]
, singularity ? defaultSingularity
, toSingularityDefArgs ? { }
, ...
}:
let
# May be "apptainer" instead of "singularity"
projectName = singularity.projectName or "singularity";
definitionFile = writeText "${name}.def" (toSingularityDef toSingularityDefArgs definition);
# Pass for users who want to build from the command line instead of inside a VM.
buildscriptPackage = writeShellScriptBin "build-image" ''
if [ "$#" -lt 1 ]; then
echo "Expect IMAGE_PATH" >&2
exit 1
fi
pathSIF="$1"
shift
${lib.toUpper projectName}ENV_PATH="$PATH" "${singularity}/bin/${projectName}" ${toString executableFlags} build ${toString buildImageFlags} "$@" "$pathSIF" "${definitionFile}"
'';
in
(runCommand "${projectName}-image-${name}.sif"
(removeAttrs args [ "name" "contents" "definition" "definitionOverrider" ] // {
inherit executableFlags buildImageFlags;
passthru = args.passthru or { } // {
inherit singularity definition definitionFile buildscriptPackage;
layerClosure = definition.labels.layerClosure or null;
};
}) ''
"${buildscriptPackage}/bin/${buildscriptPackage.meta.mainProgram}" "$out" $buildImageFlags "''${buildImageFlagsArray[@]}"
'');
}
131 changes: 131 additions & 0 deletions pkgs/build-support/singularity-tools/definition-lib.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{ lib }:

let
# Increase the readability of the output file
# by arranging each section into the specified order
knownPrimarySectionNamesDefault = [ "pre" "setup" "files" "post" "environment" "runscript" "startscript" "test" "labels" "help" ];
knownAppSectionNamesDefault = [ "appfiles" "appinstall" "appenv" "apprun" "applabels" "apphelp" ];

# TODO: Implement a type for the Apptainer/Singularity definition

### Structurize the definition file
/**!
* Here is the structured representation of the
* demo definition file in the Apptainer/Singularity menu:
* ~~~{.nix}
* {
* # header.Bootstrap is REQUIRED
* header = {
* Bootstrap = "docker";
* From = "ubuntu";
* };
* setup = ''
* touch /file1
* touch ''${SINGULARITY_ROOTFS}/file2
* '';
* # A string instead of a list is also acceptable
* files = [
* # Element as a list will be concatenated with whitespace as the delimiter
* [ "/file1" ]
* [ "/file1" "/opt" ]
* # If the element is a string instead of a list, it would be composed directly
* "some ad-hoc stuff"
* ];
* # A string or a list instead of a set is also acceptable
* # The value part is NOT escaped as shell strings.
* # Use `lib.escapeShellArg` to escape special contents
* environment = {
* __sectionPreScript = "some ad-hoc stuff beforehand";
* LISTEN_PORT = "12345"
* LC_ALL = "C";
* # This will evaluates to
* # `export PATH=/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-hello-0.0.0/bin/hello:/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cowsay-0.0.0/bin/cowsay:${PATH:-}`
* # when writing as a definition string
* PATH = "${lib.makeBinPath [ hello cowsay ]}:\${PATH:-}"
* __sectionPostScript = "some ad-hoc stuff afterward";
* };
* post = ''
* # ...
* '';
* # ...
* # A string instead of a set is also acceptable
* labels = {
* Author = "d@sylabs.io";
* Version = "v0.0.1";
* };
* # ...
* # Sections for apps goes here
* apps.foo = {
* apprun = ''
* exec echo "RUNNING FOO"
* '';
* applabels = {
* BOOTSTRAP = "FOO";
* };
* # ...
* };
* }
* ~~~
* The original version can be found here:
* https://apptainer.org/user-docs/3.1/definition_files.html
**/
toSingularityDef =
{ dropUnknownSections ? false
, knownPrimarySectionNames ? knownPrimarySectionNamesDefault
, knownAppSectionNames ? knownAppSectionNamesDefault
, ...
}:
let
orderAs = template: listToOrder: (lib.intersectLists listToOrder template) ++ lib.optionals dropUnknownSections (lib.subtractLists template listToOrder);
sectionMappingFunction = sectionName: sectionContent:
map (s: " ${s}") (
if (builtins.typeOf sectionContent == "string") then
(ss: if (ss != [ ]) && (lib.last ss == "\n") then lib.take ((lib.count ss) - 1) ss else ss) (lib.splitString "\n" sectionContent)
else if (builtins.typeOf sectionContent == "list") then
map (lineContent: if (builtins.typeOf lineContent == "list") then builtins.concatStringsSep " " lineContent else lineContent) sectionContent
else if (builtins.typeOf sectionContent == "set") then
if sectionName == "environment" || sectionName == "appenv" then
(sectionContent.__sectionPreScript or [ ])
++ map (name: "export ${name}=${sectionContent.${name}}") (builtins.attrNames sectionContent)
++ (sectionContent.__sectionPostScript or [ ])
else
map (name: "${name} ${sectionContent.${name}}") (builtins.attrNames sectionContent)
else [ ]
);
in
definition:
builtins.concatStringsSep "\n" (
[
(builtins.concatStringsSep "\n" (
[ "Bootstrap: ${definition.header.Bootstrap}" ]
++ map
(headerName: "${headerName}: ${definition.header.${headerName}}")
(lib.subtractLists [ "Bootstrap" ] (builtins.attrNames definition.header))
))
]
++ map
(sectionName: builtins.concatStringsSep "\n" (
[ "%${sectionName}" ]
++ sectionMappingFunction sectionName definition.${sectionName}
))
(orderAs knownPrimarySectionNames (lib.subtractLists [ "header" "apps" ] (builtins.attrNames definition)))
++ lib.optionals (builtins.hasAttr "apps" definition) (builtins.concatLists (map
(appName:
map
(sectionName: builtins.concatStringsSep "\n" (
[ "%${sectionName} ${appName}" ]
++ sectionMappingFunction sectionName definition.apps.${appName}.${sectionName}
))
(orderAs knownAppSectionNames (builtins.attrNames definition.apps.${appName}))
)
(builtins.attrNames definition.apps)
))
);
in
{
inherit
knownPrimarySectionNamesDefault
knownAppSectionNamesDefault
toSingularityDef
;
}

0 comments on commit 53a1065

Please sign in to comment.