├── .gitattributes ├── .gitignore ├── .gitreview ├── .zuul.yaml ├── Containerfile ├── Statement.dhall ├── Type.dhall ├── add.dhall ├── arg.dhall ├── concatSep.dhall ├── copy.dhall ├── copyFrom.dhall ├── emptyLine.dhall ├── entrypoint.dhall ├── env.dhall ├── exec.dhall ├── expose.dhall ├── exposeTcp.dhall ├── exposeUdp.dhall ├── from.dhall ├── label.dhall ├── optionalStatements.dhall ├── optionalTexts.dhall ├── package.dhall ├── render.dhall ├── render │ ├── Env.dhall │ ├── Label.dhall │ ├── Statement.dhall │ ├── prefixMapText.dhall │ ├── textEntry.dhall │ └── textList.dhall ├── run.dhall ├── user.dhall ├── volume.dhall └── workdir.dhall ├── Prelude.dhall ├── README.md ├── Shakefile.hs ├── examples ├── demo.dhall └── ffmpeg.dhall └── package.dhall /.gitattributes: -------------------------------------------------------------------------------- 1 | package.dhall linguist-generated=true 2 | Containerfile/package.dhall linguist-generated=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /_build -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=softwarefactory-project.io 3 | port=29418 4 | project=software-factory/dhall-containerfile 5 | defaultbranch=master 6 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - project: 2 | check: &jobs 3 | jobs: 4 | - shake-factory-test 5 | - shake-factory-docs 6 | gate: *jobs 7 | -------------------------------------------------------------------------------- /Containerfile/Statement.dhall: -------------------------------------------------------------------------------- 1 | {-| 2 | Based on [Dockerfile format](https://docs.docker.com/engine/reference/builder/#format) 3 | -} 4 | let Prelude = ../Prelude.dhall 5 | 6 | in < From : Text 7 | | Run : Text 8 | | Cmd : List Text 9 | | Exec : List Text 10 | | Label : Prelude.Map.Type Text Text 11 | | Expose : Text 12 | | Env : Prelude.Map.Type Text Text 13 | | Add : List Text 14 | | Copy : List Text 15 | | CopyFrom : { from : Text, files : List Text } 16 | | Entrypoint : List Text 17 | | Volume : List Text 18 | | User : Text 19 | | Workdir : Text 20 | | Arg : Prelude.Map.Type Text Text 21 | | Shell : List Text 22 | | Comment : Text 23 | | Empty 24 | > 25 | : Type 26 | -------------------------------------------------------------------------------- /Containerfile/Type.dhall: -------------------------------------------------------------------------------- 1 | --| A Containerfile is a list of ./Statement.dhall 2 | List ./Statement.dhall : Type 3 | -------------------------------------------------------------------------------- /Containerfile/add.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create from 2 | let Statement = ./Statement.dhall 3 | 4 | let add 5 | : List Text -> List Statement 6 | = \(files : List Text) -> [ Statement.Add files ] 7 | 8 | in add 9 | -------------------------------------------------------------------------------- /Containerfile/arg.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../Prelude.dhall 2 | 3 | let Statement = ./Statement.dhall 4 | 5 | let arg 6 | : Prelude.Map.Type Text Text -> List Statement 7 | = \(args : Prelude.Map.Type Text Text) -> [ Statement.Arg args ] 8 | 9 | in arg 10 | -------------------------------------------------------------------------------- /Containerfile/concatSep.dhall: -------------------------------------------------------------------------------- 1 | --| A re-export of the Prelude.Text.concatSep function 2 | https://prelude.dhall-lang.org/Text/concatSep.dhall sha256:e4401d69918c61b92a4c0288f7d60a6560ca99726138ed8ebc58dca2cd205e58 3 | : forall (separator : Text) -> forall (elements : List Text) -> Text 4 | -------------------------------------------------------------------------------- /Containerfile/copy.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create copy 2 | let Statement = ./Statement.dhall 3 | 4 | let copy 5 | : List Text -> List Statement 6 | = \(files : List Text) -> [ Statement.Copy files ] 7 | 8 | in copy 9 | -------------------------------------------------------------------------------- /Containerfile/copyFrom.dhall: -------------------------------------------------------------------------------- 1 | let Statement = ./Statement.dhall 2 | 3 | let copyFrom = 4 | \(from : Text) -> 5 | \(files : List Text) -> 6 | [ Statement.CopyFrom { from, files } ] 7 | 8 | in copyFrom 9 | -------------------------------------------------------------------------------- /Containerfile/emptyLine.dhall: -------------------------------------------------------------------------------- 1 | [ (./Statement.dhall).Empty ] 2 | -------------------------------------------------------------------------------- /Containerfile/entrypoint.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create entrypoint 2 | let Statement = ./Statement.dhall 3 | 4 | let entrypoint 5 | : List Text -> List Statement 6 | = \(entrypoint : List Text) -> [ Statement.Entrypoint entrypoint ] 7 | 8 | let example0 = 9 | assert 10 | : entrypoint [ "/bin/bash" ] === [ Statement.Entrypoint [ "/bin/bash" ] ] 11 | 12 | in entrypoint 13 | -------------------------------------------------------------------------------- /Containerfile/env.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../Prelude.dhall 2 | 3 | let Statement = ./Statement.dhall 4 | 5 | let env 6 | : Prelude.Map.Type Text Text -> List Statement 7 | = \(env : Prelude.Map.Type Text Text) -> [ Statement.Env env ] 8 | 9 | let example0 = 10 | assert 11 | : env (toMap { HOME = "/root" }) 12 | === [ Statement.Env (toMap { HOME = "/root" }) ] 13 | 14 | in env 15 | -------------------------------------------------------------------------------- /Containerfile/exec.dhall: -------------------------------------------------------------------------------- 1 | --| The RUN `exec` form 2 | let Prelude = ../Prelude.dhall 3 | 4 | let Statement = ./Statement.dhall 5 | 6 | let exec 7 | : Text -> List Text -> List Statement 8 | = \(comment : Text) -> 9 | \(args : List Text) -> 10 | [ Statement.Comment comment, Statement.Exec args, Statement.Empty ] 11 | 12 | let example0 = 13 | assert 14 | : exec "Install emacs" [ "dnf install -y emacs", "dnf clean all" ] 15 | === [ Statement.Comment "Install emacs" 16 | , Statement.Exec [ "dnf install -y emacs", "dnf clean all" ] 17 | , Statement.Empty 18 | ] 19 | 20 | in exec 21 | -------------------------------------------------------------------------------- /Containerfile/expose.dhall: -------------------------------------------------------------------------------- 1 | let Statement = ./Statement.dhall 2 | 3 | let expose 4 | : Text -> List Statement 5 | = \(port : Text) -> [ Statement.Expose port ] 6 | 7 | in expose 8 | -------------------------------------------------------------------------------- /Containerfile/exposeTcp.dhall: -------------------------------------------------------------------------------- 1 | \(port : Natural) -> ./expose.dhall (Natural/show port) 2 | -------------------------------------------------------------------------------- /Containerfile/exposeUdp.dhall: -------------------------------------------------------------------------------- 1 | \(port : Natural) -> ./expose.dhall "${Natural/show port}/udp" 2 | -------------------------------------------------------------------------------- /Containerfile/from.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create from 2 | let Statement = ./Statement.dhall 3 | 4 | let from 5 | : Text -> List Statement 6 | = \(from : Text) -> [ Statement.From from ] 7 | 8 | let example0 = assert : from "fedora" === [ Statement.From "fedora" ] 9 | 10 | in from 11 | -------------------------------------------------------------------------------- /Containerfile/label.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../Prelude.dhall 2 | 3 | let Statement = ./Statement.dhall 4 | 5 | let label 6 | : Prelude.Map.Type Text Text -> List Statement 7 | = \(label : Prelude.Map.Type Text Text) -> [ Statement.Label label ] 8 | 9 | in label 10 | -------------------------------------------------------------------------------- /Containerfile/optionalStatements.dhall: -------------------------------------------------------------------------------- 1 | --| Returns optional list of Statement 2 | let Prelude = ../Prelude.dhall 3 | 4 | let Statement = ./Statement.dhall 5 | 6 | let optionalStatements 7 | : Bool -> List Statement -> List Statement 8 | = \(predicate : Bool) -> 9 | \(statements : List Statement) -> 10 | if predicate then statements else Prelude.List.empty Statement 11 | 12 | let example0 = 13 | assert 14 | : optionalStatements True [ Statement.Run "make" ] 15 | === [ Statement.Run "make" ] 16 | 17 | in optionalStatements 18 | -------------------------------------------------------------------------------- /Containerfile/optionalTexts.dhall: -------------------------------------------------------------------------------- 1 | --| Returns optional list of Text 2 | let Prelude = ../Prelude.dhall 3 | 4 | let optionalTexts 5 | : Bool -> List Text -> List Text 6 | = \(predicate : Bool) -> 7 | \(texts : List Text) -> 8 | if predicate then texts else Prelude.List.empty Text 9 | 10 | let example0 = 11 | assert : optionalTexts True [ "emacs", "vim" ] === [ "emacs", "vim" ] 12 | 13 | let example1 = 14 | assert 15 | : optionalTexts False [ "emacs", "vim" ] === Prelude.List.empty Text 16 | 17 | in optionalTexts 18 | -------------------------------------------------------------------------------- /Containerfile/package.dhall: -------------------------------------------------------------------------------- 1 | { Statement = ./Statement.dhall 2 | , Type = ./Type.dhall 3 | , add = ./add.dhall 4 | , arg = ./arg.dhall 5 | , concatSep = ./concatSep.dhall 6 | , copy = ./copy.dhall 7 | , copyFrom = ./copyFrom.dhall 8 | , emptyLine = ./emptyLine.dhall 9 | , entrypoint = ./entrypoint.dhall 10 | , env = ./env.dhall 11 | , exec = ./exec.dhall 12 | , expose = ./expose.dhall 13 | , exposeTcp = ./exposeTcp.dhall 14 | , exposeUdp = ./exposeUdp.dhall 15 | , from = ./from.dhall 16 | , label = ./label.dhall 17 | , optionalStatements = ./optionalStatements.dhall 18 | , optionalTexts = ./optionalTexts.dhall 19 | , render = ./render.dhall 20 | , run = ./run.dhall 21 | , user = ./user.dhall 22 | , volume = ./volume.dhall 23 | , workdir = ./workdir.dhall 24 | } 25 | -------------------------------------------------------------------------------- /Containerfile/render.dhall: -------------------------------------------------------------------------------- 1 | --| Render a Containerfile as Text 2 | let Prelude = ../Prelude.dhall 3 | 4 | let Containerfile = { Type = ./Type.dhall } 5 | 6 | let Statement = { Type = ./Statement.dhall } 7 | 8 | let render 9 | : Containerfile.Type -> Text 10 | = \(statements : Containerfile.Type) -> 11 | Prelude.Text.concatMapSep 12 | "\n" 13 | Statement.Type 14 | ./render/Statement.dhall 15 | statements 16 | ++ "\n" 17 | 18 | let example0 = 19 | assert 20 | : render 21 | [ Statement.Type.From "fedora:latest" 22 | , Statement.Type.Empty 23 | , Statement.Type.Comment "A comment" 24 | , Statement.Type.Env (toMap { HOME = "/root", TEST = "a space" }) 25 | , Statement.Type.Empty 26 | , Statement.Type.Run "dnf install -y emacs && dnf clean --all" 27 | ] 28 | === '' 29 | FROM fedora:latest 30 | 31 | # A comment 32 | ENV HOME /root 33 | ENV TEST a space 34 | 35 | RUN dnf install -y emacs && dnf clean --all 36 | '' 37 | 38 | in render 39 | -------------------------------------------------------------------------------- /Containerfile/render/Env.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../../Prelude.dhall 2 | 3 | let renderEnvEntry 4 | : Prelude.Map.Entry Text Text -> Text 5 | = \(entry : Prelude.Map.Entry Text Text) -> 6 | "ENV ${entry.mapKey} ${entry.mapValue}" 7 | 8 | let renderEnv 9 | : Prelude.Map.Type Text Text -> Text 10 | = \(map : Prelude.Map.Type Text Text) -> 11 | Prelude.Text.concatSep 12 | "\n" 13 | ( Prelude.List.map 14 | (Prelude.Map.Entry Text Text) 15 | Text 16 | renderEnvEntry 17 | map 18 | ) 19 | 20 | let example0 = 21 | assert 22 | : renderEnv 23 | ( toMap 24 | { HOME = "/root" 25 | , TEST = "a space" 26 | , PATH = "/usr/bin:\$PATH" 27 | } 28 | ) 29 | ++ "\n" 30 | === '' 31 | ENV HOME /root 32 | ENV PATH /usr/bin:$PATH 33 | ENV TEST a space 34 | '' 35 | 36 | in renderEnv 37 | -------------------------------------------------------------------------------- /Containerfile/render/Label.dhall: -------------------------------------------------------------------------------- 1 | let renderLabel = ./prefixMapText.dhall "LABEL" 2 | 3 | let example0 = 4 | assert 5 | : renderLabel (toMap { Version = "1.0", description = "Test" }) 6 | ++ "\n" 7 | === '' 8 | LABEL Version="1.0" 9 | LABEL description="Test" 10 | '' 11 | 12 | in renderLabel 13 | -------------------------------------------------------------------------------- /Containerfile/render/Statement.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../../Prelude.dhall 2 | 3 | let Statement = { Type = ../Statement.dhall } 4 | 5 | let prefixText = \(prefix : Text) -> \(text : Text) -> "${prefix} ${text}" 6 | 7 | let prefixTextList = 8 | \(prefix : Text) -> 9 | \(list : List Text) -> 10 | "${prefix} " ++ ./textList.dhall list 11 | 12 | let renderStatement = 13 | \(statement : Statement.Type) -> 14 | merge 15 | { From = prefixText "FROM" 16 | , Comment = prefixText "#" 17 | , Workdir = prefixText "WORKDIR" 18 | , Run = prefixText "RUN" 19 | , User = prefixText "USER" 20 | , Cmd = prefixTextList "CMD" 21 | , Exec = prefixTextList "RUN" 22 | , Expose = prefixText "EXPOSE" 23 | , Add = prefixTextList "ADD" 24 | , Copy = prefixTextList "COPY" 25 | , CopyFrom = 26 | \(copy : { from : Text, files : List Text }) -> 27 | prefixTextList "COPY --from=${copy.from}" copy.files 28 | , Volume = prefixTextList "VOLUME" 29 | , Entrypoint = prefixTextList "ENTRYPOINT" 30 | , Env = ./Env.dhall 31 | , Label = ./Label.dhall 32 | , Arg = ./prefixMapText.dhall "ARG" 33 | , Shell = prefixTextList "SHELL" 34 | , Empty = "" 35 | } 36 | statement 37 | 38 | in renderStatement 39 | -------------------------------------------------------------------------------- /Containerfile/render/prefixMapText.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../../Prelude.dhall 2 | 3 | let Entry = { mapKey : Text, mapValue : Text } 4 | 5 | let Map = List Entry 6 | 7 | let renderPrefixMapText 8 | : Text -> Map -> Text 9 | = \(prefix : Text) -> 10 | Prelude.Text.concatMapSep 11 | "\n" 12 | Entry 13 | (\(entry : Entry) -> "${prefix} " ++ ./textEntry.dhall entry) 14 | 15 | in renderPrefixMapText 16 | -------------------------------------------------------------------------------- /Containerfile/render/textEntry.dhall: -------------------------------------------------------------------------------- 1 | \(entry : { mapKey : Text, mapValue : Text }) -> 2 | "${entry.mapKey}=${Text/show entry.mapValue}" 3 | -------------------------------------------------------------------------------- /Containerfile/render/textList.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../../Prelude.dhall 2 | 3 | let renderTextList 4 | : List Text -> Text 5 | = \(args : List Text) -> 6 | "[" 7 | ++ Prelude.Text.concatSep 8 | ", " 9 | (Prelude.List.map Text Text Text/show args) 10 | ++ "]" 11 | 12 | let example0 = 13 | assert 14 | : renderTextList [ "echo", "hello world" ] 15 | === "[\"echo\", \"hello world\"]" 16 | 17 | in renderTextList 18 | -------------------------------------------------------------------------------- /Containerfile/run.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../Prelude.dhall 2 | 3 | let Statement = ./Statement.dhall 4 | 5 | let run 6 | : Text -> List Text -> List Statement 7 | = \(comment : Text) -> 8 | \(commands : List Text) -> 9 | [ Statement.Comment comment 10 | , Statement.Run (Prelude.Text.concatSep " && " commands) 11 | , Statement.Empty 12 | ] 13 | 14 | let example0 = 15 | assert 16 | : run "Install emacs" [ "dnf install -y emacs", "dnf clean all" ] 17 | === [ Statement.Comment "Install emacs" 18 | , Statement.Run "dnf install -y emacs && dnf clean all" 19 | , Statement.Empty 20 | ] 21 | 22 | in run 23 | -------------------------------------------------------------------------------- /Containerfile/user.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = ../Prelude.dhall 2 | 3 | let Statement = ./Statement.dhall 4 | 5 | let user 6 | : Text -> List Statement 7 | = \(user : Text) -> [ Statement.User user ] 8 | 9 | in user 10 | -------------------------------------------------------------------------------- /Containerfile/volume.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create volume 2 | let Statement = ./Statement.dhall 3 | 4 | let volume 5 | : List Text -> List Statement 6 | = \(volumes : List Text) -> [ Statement.Volume volumes ] 7 | 8 | in volume 9 | -------------------------------------------------------------------------------- /Containerfile/workdir.dhall: -------------------------------------------------------------------------------- 1 | --| A convenient function to create workdir 2 | let Statement = ./Statement.dhall 3 | 4 | let workdir 5 | : Text -> List Statement 6 | = \(workdir : Text) -> [ Statement.Workdir workdir ] 7 | 8 | let example0 = assert : workdir "/tmp" === [ Statement.Workdir "/tmp" ] 9 | 10 | in workdir 11 | -------------------------------------------------------------------------------- /Prelude.dhall: -------------------------------------------------------------------------------- 1 | {-| 2 | This file provides a central `Prelude` import for the rest of the library to 3 | use so that the integrity check only needs to be updated in one place 4 | whenever upgrading the interpreter. 5 | -} 6 | env:DHALL_PRELUDE 7 | ? https://prelude.dhall-lang.org/v17.0.0/package.dhall sha256:10db3c919c25e9046833df897a8ffe2701dc390fa0893d958c3430524be5a43e 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dhall-containerfile 2 | 3 | Manage your Containerfile with [Dhall][dhall-lang]. 4 | 5 | Main [documentation](https://docs.softwarefactory-project.io/dhall-containerfile/) 6 | 7 | ## Usage 8 | 9 | The package provides convenient function to create the statements: 10 | 11 | ```dhall 12 | -- ./examples/demo.dhall 13 | let Containerfile = ../package.dhall 14 | 15 | in Containerfile.from "fedora" 16 | # Containerfile.emptyLine 17 | # Containerfile.run 18 | "Install emacs" 19 | [ "dnf update -y", "dnf install -y emacs-nox", "dnf clean all" ] 20 | # Containerfile.volume [ "/data" ] 21 | # Containerfile.label 22 | ( toMap 23 | { description = "a text editor" 24 | , maintainer = "tdecacqu@redhat.com" 25 | } 26 | ) 27 | # Containerfile.emptyLine 28 | # Containerfile.entrypoint [ "emacs" ] 29 | 30 | ``` 31 | 32 | ``` 33 | # dhall text <<< '(./package.dhall).render ./examples/demo.dhall' 34 | FROM fedora 35 | 36 | # Install emacs 37 | RUN dnf update -y && dnf install -y emacs-nox && dnf clean all 38 | 39 | VOLUME ["/data"] 40 | LABEL description="a text editor" 41 | LABEL maintainer="tdecacqu@redhat.com" 42 | 43 | ENTRYPOINT ["emacs"] 44 | 45 | ``` 46 | 47 | ## Reference 48 | 49 | The package implements the [Containerfile reference][ref] with these changes: 50 | 51 | * `Exec` is the `RUN` exec form 52 | * `Cmd` only supports the preferred exec form 53 | * `Empty` denotes an empty lines 54 | * `Add` only supports the list form without chown argument 55 | * `Copy` only supports the list form without chown argument 56 | * `Entrypoint` only supports the prefered exec form 57 | 58 | ```dhall 59 | -- ./Containerfile/Statement.dhall 60 | {-| 61 | Based on [Dockerfile format](https://docs.docker.com/engine/reference/builder/#format) 62 | -} 63 | let Prelude = ../Prelude.dhall 64 | 65 | in < From : Text 66 | | Run : Text 67 | | Cmd : List Text 68 | | Exec : List Text 69 | | Label : Prelude.Map.Type Text Text 70 | | Expose : Text 71 | | Env : Prelude.Map.Type Text Text 72 | | Add : List Text 73 | | Copy : List Text 74 | | CopyFrom : { from : Text, files : List Text } 75 | | Entrypoint : List Text 76 | | Volume : List Text 77 | | User : Text 78 | | Workdir : Text 79 | | Arg : Prelude.Map.Type Text Text 80 | | Shell : List Text 81 | | Comment : Text 82 | | Empty 83 | > 84 | : Type 85 | 86 | ``` 87 | 88 | ## Example 89 | 90 | A complete example to build an ffmpeg image with some options: 91 | 92 | ```dhall 93 | -- ./examples/ffmpeg.dhall 94 | let Containerfile = ../package.dhall 95 | 96 | let FfmpegOption = 97 | { Type = { x264 : Bool, xcb : Bool } 98 | , default = { x264 = True, xcb = True } 99 | } 100 | 101 | let buildLib = 102 | \(name : Text) -> 103 | \(url : Text) -> 104 | \(build-commands : List Text) -> 105 | Containerfile.run 106 | "${name}: ${Containerfile.concatSep " " build-commands}" 107 | ( [ "cd /usr/local/src" 108 | , "git clone --depth 1 ${url}" 109 | , "cd ${name}" 110 | ] 111 | # build-commands 112 | # [ "make", "make install" ] 113 | ) 114 | 115 | let make 116 | : FfmpegOption.Type -> Containerfile.Type 117 | = \(options : FfmpegOption.Type) -> 118 | let build-reqs = 119 | [ "autoconf" 120 | , "automake" 121 | , "cmake" 122 | , "freetype-devel" 123 | , "gcc" 124 | , "gcc-c++" 125 | , "git" 126 | , "libtool" 127 | , "make" 128 | , "nasm" 129 | , "pkgconfig" 130 | , "zlib-devel" 131 | , "numactl-devel" 132 | ] 133 | # Containerfile.optionalTexts options.xcb [ "libxcb-devel" ] 134 | 135 | let runtime-reqs = 136 | [ "numactl" ] 137 | # Containerfile.optionalTexts options.xcb [ "libxcb" ] 138 | 139 | let x264-build = 140 | Containerfile.optionalStatements 141 | options.x264 142 | ( buildLib 143 | "x264" 144 | "https://code.videolan.org/videolan/x264.git" 145 | [ "./configure --prefix=\"/usr/local\" --enable-static" ] 146 | ) 147 | 148 | let yasm-build = 149 | buildLib 150 | "yasm" 151 | "git://github.com/yasm/yasm.git" 152 | [ "autoreconf -fiv", "./configure --prefix=\"/usr/local\"" ] 153 | 154 | let ffmpeg-build = 155 | buildLib 156 | "ffmpeg" 157 | "git://source.ffmpeg.org/ffmpeg" 158 | [ Containerfile.concatSep 159 | " " 160 | ( [ "PKG_CONFIG_PATH=\"/usr/local/lib/pkgconfig\"" 161 | , "./configure" 162 | , "--prefix=\"/usr/local\"" 163 | , "--extra-cflags=\"-I/usr/local/include\"" 164 | , "--extra-ldflags=\"-L/usr/local/lib\"" 165 | , "--pkg-config-flags=\"--static\"" 166 | , "--enable-gpl" 167 | , "--enable-nonfree" 168 | ] 169 | # Containerfile.optionalTexts 170 | options.x264 171 | [ "--enable-libx264" ] 172 | # Containerfile.optionalTexts 173 | options.xcb 174 | [ "--enable-libxcb" ] 175 | ) 176 | ] 177 | 178 | let bootstrap = 179 | Containerfile.run 180 | "Install build and runtime reqs" 181 | [ "dnf install -y " 182 | ++ Containerfile.concatSep " " (build-reqs # runtime-reqs) 183 | ] 184 | 185 | let cleanup = 186 | Containerfile.run 187 | "Cleanup" 188 | [ "rm -Rf /usr/local/src/*" 189 | , "dnf clean all" 190 | , "dnf erase -y " ++ Containerfile.concatSep " " build-reqs 191 | ] 192 | 193 | in Containerfile.from "fedora:latest" 194 | # Containerfile.emptyLine 195 | # bootstrap 196 | # yasm-build 197 | # x264-build 198 | # ffmpeg-build 199 | # cleanup 200 | # Containerfile.entrypoint [ "/usr/local/bin/ffmpeg" ] 201 | 202 | in { Containerfile = Containerfile.render (make FfmpegOption.default) 203 | , make 204 | , buildLib 205 | } 206 | 207 | ``` 208 | 209 | ```text 210 | # dhall text <<< '(./examples/ffmpeg.dhall).Containerfile' 211 | FROM fedora:latest 212 | 213 | # Install build and runtime reqs 214 | RUN dnf install -y autoconf automake cmake freetype-devel gcc gcc-c++ git libtool make nasm pkgconfig zlib-devel numactl-devel libxcb-devel numactl libxcb 215 | 216 | # yasm: autoreconf -fiv ./configure --prefix="/usr/local" 217 | RUN cd /usr/local/src && git clone --depth 1 git://github.com/yasm/yasm.git && cd yasm && autoreconf -fiv && ./configure --prefix="/usr/local" && make && make install 218 | 219 | # x264: ./configure --prefix="/usr/local" --enable-static 220 | RUN cd /usr/local/src && git clone --depth 1 https://code.videolan.org/videolan/x264.git && cd x264 && ./configure --prefix="/usr/local" --enable-static && make && make install 221 | 222 | # ffmpeg: PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" ./configure --prefix="/usr/local" --extra-cflags="-I/usr/local/include" --extra-ldflags="-L/usr/local/lib" --pkg-config-flags="--static" --enable-gpl --enable-nonfree --enable-libx264 --enable-libxcb 223 | RUN cd /usr/local/src && git clone --depth 1 git://source.ffmpeg.org/ffmpeg && cd ffmpeg && PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" ./configure --prefix="/usr/local" --extra-cflags="-I/usr/local/include" --extra-ldflags="-L/usr/local/lib" --pkg-config-flags="--static" --enable-gpl --enable-nonfree --enable-libx264 --enable-libxcb && make && make install 224 | 225 | # Cleanup 226 | RUN rm -Rf /usr/local/src/* && dnf clean all && dnf erase -y autoconf automake cmake freetype-devel gcc gcc-c++ git libtool make nasm pkgconfig zlib-devel numactl-devel libxcb-devel 227 | 228 | ENTRYPOINT ["/usr/local/bin/ffmpeg"] 229 | 230 | ``` 231 | 232 | ## Changes 233 | 234 | Frozen package are available in the tag commit. 235 | 236 | ### 0.4.0 237 | 238 | - Add `user` helper 239 | 240 | ### 0.3.0 241 | 242 | - Initial release 243 | 244 | [dhall-lang]: https://dhall-lang.org 245 | [ref]: https://docs.docker.com/engine/reference/builder/ 246 | -------------------------------------------------------------------------------- /Shakefile.hs: -------------------------------------------------------------------------------- 1 | -- Interpret using this command: 2 | -- podman run -it --rm -v $(pwd):/data:Z quay.io/software-factory/shake-factory 3 | -- 4 | -- Learn more at: https://softwarefactory-project.io/cgit/software-factory/shake-factory/tree/README.md 5 | 6 | import Development.Shake 7 | import ShakeFactory 8 | import ShakeFactory.Dhall 9 | 10 | main = shakeMain $ do 11 | want ["README.md", "package.dhall"] 12 | "README.md" %> dhallReadmeAction 13 | "package.dhall" %> dhallTopLevelPackageAction "./Containerfile/package.dhall" 14 | "//package.dhall" %> dhallPackageAction 15 | dhallDocsRules "dhall-containerfile" 16 | dhallReleaseRules "./Containerfile/package.dhall" 17 | cleanRules 18 | -------------------------------------------------------------------------------- /examples/demo.dhall: -------------------------------------------------------------------------------- 1 | let Containerfile = ../package.dhall 2 | 3 | in Containerfile.from "fedora" 4 | # Containerfile.emptyLine 5 | # Containerfile.run 6 | "Install emacs" 7 | [ "dnf update -y", "dnf install -y emacs-nox", "dnf clean all" ] 8 | # Containerfile.volume [ "/data" ] 9 | # Containerfile.label 10 | ( toMap 11 | { description = "a text editor" 12 | , maintainer = "tdecacqu@redhat.com" 13 | } 14 | ) 15 | # Containerfile.emptyLine 16 | # Containerfile.entrypoint [ "emacs" ] 17 | -------------------------------------------------------------------------------- /examples/ffmpeg.dhall: -------------------------------------------------------------------------------- 1 | let Containerfile = ../package.dhall 2 | 3 | let FfmpegOption = 4 | { Type = { x264 : Bool, xcb : Bool } 5 | , default = { x264 = True, xcb = True } 6 | } 7 | 8 | let buildLib = 9 | \(name : Text) -> 10 | \(url : Text) -> 11 | \(build-commands : List Text) -> 12 | Containerfile.run 13 | "${name}: ${Containerfile.concatSep " " build-commands}" 14 | ( [ "cd /usr/local/src" 15 | , "git clone --depth 1 ${url}" 16 | , "cd ${name}" 17 | ] 18 | # build-commands 19 | # [ "make", "make install" ] 20 | ) 21 | 22 | let make 23 | : FfmpegOption.Type -> Containerfile.Type 24 | = \(options : FfmpegOption.Type) -> 25 | let build-reqs = 26 | [ "autoconf" 27 | , "automake" 28 | , "cmake" 29 | , "freetype-devel" 30 | , "gcc" 31 | , "gcc-c++" 32 | , "git" 33 | , "libtool" 34 | , "make" 35 | , "nasm" 36 | , "pkgconfig" 37 | , "zlib-devel" 38 | , "numactl-devel" 39 | ] 40 | # Containerfile.optionalTexts options.xcb [ "libxcb-devel" ] 41 | 42 | let runtime-reqs = 43 | [ "numactl" ] 44 | # Containerfile.optionalTexts options.xcb [ "libxcb" ] 45 | 46 | let x264-build = 47 | Containerfile.optionalStatements 48 | options.x264 49 | ( buildLib 50 | "x264" 51 | "https://code.videolan.org/videolan/x264.git" 52 | [ "./configure --prefix=\"/usr/local\" --enable-static" ] 53 | ) 54 | 55 | let yasm-build = 56 | buildLib 57 | "yasm" 58 | "git://github.com/yasm/yasm.git" 59 | [ "autoreconf -fiv", "./configure --prefix=\"/usr/local\"" ] 60 | 61 | let ffmpeg-build = 62 | buildLib 63 | "ffmpeg" 64 | "git://source.ffmpeg.org/ffmpeg" 65 | [ Containerfile.concatSep 66 | " " 67 | ( [ "PKG_CONFIG_PATH=\"/usr/local/lib/pkgconfig\"" 68 | , "./configure" 69 | , "--prefix=\"/usr/local\"" 70 | , "--extra-cflags=\"-I/usr/local/include\"" 71 | , "--extra-ldflags=\"-L/usr/local/lib\"" 72 | , "--pkg-config-flags=\"--static\"" 73 | , "--enable-gpl" 74 | , "--enable-nonfree" 75 | ] 76 | # Containerfile.optionalTexts 77 | options.x264 78 | [ "--enable-libx264" ] 79 | # Containerfile.optionalTexts 80 | options.xcb 81 | [ "--enable-libxcb" ] 82 | ) 83 | ] 84 | 85 | let bootstrap = 86 | Containerfile.run 87 | "Install build and runtime reqs" 88 | [ "dnf install -y " 89 | ++ Containerfile.concatSep " " (build-reqs # runtime-reqs) 90 | ] 91 | 92 | let cleanup = 93 | Containerfile.run 94 | "Cleanup" 95 | [ "rm -Rf /usr/local/src/*" 96 | , "dnf clean all" 97 | , "dnf erase -y " ++ Containerfile.concatSep " " build-reqs 98 | ] 99 | 100 | in Containerfile.from "fedora:latest" 101 | # Containerfile.emptyLine 102 | # bootstrap 103 | # yasm-build 104 | # x264-build 105 | # ffmpeg-build 106 | # cleanup 107 | # Containerfile.entrypoint [ "/usr/local/bin/ffmpeg" ] 108 | 109 | in { Containerfile = Containerfile.render (make FfmpegOption.default) 110 | , make 111 | , buildLib 112 | } 113 | -------------------------------------------------------------------------------- /package.dhall: -------------------------------------------------------------------------------- 1 | ./Containerfile/package.dhall 2 | --------------------------------------------------------------------------------