22 Commits

Autore SHA1 Messaggio Data
  Alois Mahdal 8c0952ceaf Bump version to 0.0.21 11 mesi fa
  Alois Mahdal d031c8c635 Update release source branch to 'main' 11 mesi fa
  Alois Mahdal 0e1d02393f Update MKit to v0.1.3 11 mesi fa
  Alois Mahdal 3d0eb8e16b Update CI config after big gladness cleanup 1 anno fa
  Alois Mahdal 330d7dba83 Bump version to 0.0.20 3 anni fa
  Alois Mahdal 0525eb079c Fix non-existent cache on first run breaking locking 3 anni fa
  Alois Mahdal 2ba6866a96 Bump version to 0.0.19 3 anni fa
  Alois Mahdal 68fda08d5d Add missing make build dependency 3 anni fa
  Alois Mahdal 26de8e00fc Bump version to 0.0.18 3 anni fa
  Alois Mahdal 278f53ca87 Add explanation of CA certificate storage logic 3 anni fa
  Alois Mahdal 122e83b51b Only use 'certificates' by default if it exists 3 anni fa
  Alois Mahdal 1dabaeba4d Allow disabling passing of cert file 3 anni fa
  Alois Mahdal 320a0fd6f6 Add missing option description in usage message 3 anni fa
  Alois Mahdal 860afc6edc Bump version to 0.0.17 4 anni fa
  Alois Mahdal b489e7d819 Set default queue size 512 for get_queue 4 anni fa
  Alois Mahdal d584e65b5e Return nothing gracefully if no sequence is provided 4 anni fa
  Alois Mahdal 693b8abf6a Bump version to 0.0.16 4 anni fa
  Alois Mahdal 78747b5460 Add head() method to get first N messages in sequence 4 anni fa
  Alois Mahdal 7a6cbb22ab Update URLs after migrating to GitLab.com 4 anni fa
  Alois Mahdal 1450174533 Enable Gladness pipeline 4 anni fa
  Alois Mahdal f2112e2588 Remove unnecessary execution bit in src 4 anni fa
  Alois Mahdal 1b3bfa7249 Update URLs after migrating from pagure.io 4 anni fa

+ 5
- 0
.gitlab-ci.yml Vedi File

@@ -0,0 +1,5 @@
1
+---
2
+include:
3
+  - project: 'vornet/infra/gladness'
4
+    ref: main
5
+    file: '/pipelines/mkit.yaml'

+ 4
- 4
mkit.ini Vedi File

@@ -1,11 +1,11 @@
1 1
 [project]
2
-    version     = 0.0.15
2
+    version     = 0.0.21
3 3
     name        = imapdomo
4 4
     pkgname     = imapdomo
5 5
     maintainer  = Alois Mahdal <netvor+imapdomo@vornet.cz>
6
-    vcs_browser = https://pagure.io/imapdomo
6
+    vcs_browser = https://gitlab.com/vornet/hacktop/imapdomo
7 7
     tagline     = imapfilter convenience wrapper
8
-    relsrc      = master
8
+    relsrc      = main
9 9
     reldst      = latest
10 10
 
11 11
 [dist]
@@ -42,4 +42,4 @@
42 42
     share   = src/imaprules.lua
43 43
     share   = src/main.lua
44 44
 
45
-#mkit version=0.0.40
45
+#mkit version=0.1.3

+ 1
- 0
packaging/debian/control Vedi File

@@ -6,6 +6,7 @@ Priority: extra
6 6
 Standards-Version: 3.9.2
7 7
 Build-Depends:
8 8
  debhelper (>= 9),
9
+ make,
9 10
 
10 11
 Package: __MKIT_PROJ_PKGNAME__
11 12
 Architecture: all

+ 1
- 0
packaging/template.spec Vedi File

@@ -8,6 +8,7 @@ URL:        __MKIT_PROJ_VCS_BROWSER__
8 8
 License:    GPLv2
9 9
 Source0:    %{name}-%{version}.tar.gz
10 10
 BuildArch:  noarch
11
+BuildRequires: make
11 12
 
12 13
 Requires: %shellfu_req
13 14
 Requires: imapfilter

+ 16
- 2
src/imapdomo.lua Vedi File

@@ -57,16 +57,17 @@ end
57 57
 -- mail getters                                                             --
58 58
 ------------------------------------------------------------------------------
59 59
 
60
-pkg.get_queue = function(acct, mbox)
60
+pkg.get_queue = function(acct, mbox, size)
61 61
     --
62 62
     -- Get queue from *mbox* from *acct* or nil (no messages)
63 63
     --
64 64
     -- If mbox is not specified, "FILTER_QUEUE" is used
65 65
     --
66 66
     mbox = mbox or "FILTER_QUEUE"
67
+    size = size or 512
67 68
     local exist = acct[mbox]:check_status()
68 69
     if exist > 0 then
69
-        return acct[mbox]:select_all()
70
+        return pkg.head(size, acct[mbox]:select_all())
70 71
     end
71 72
     return nil
72 73
 end
@@ -203,6 +204,19 @@ pkg.filter_header_saved = function(seq, name)
203 204
     return result
204 205
 end
205 206
 
207
+pkg.head = function(num, seq)
208
+    --
209
+    -- Return first *num* elements from sequence
210
+    --
211
+    if not seq then return seq end
212
+    local result = seq:is_smaller(0)    -- HACK to generate empty sequence
213
+    for idx, value in ipairs(seq) do
214
+        if idx > num then break end
215
+        table.insert(result, value)
216
+    end
217
+    return result
218
+end
219
+
206 220
 pkg.filter_part_like = function(query, seq)
207 221
     --
208 222
     -- Run MIME part query on *seq* sequence of messages

+ 12
- 1
src/imapdomo.skel Vedi File

@@ -16,9 +16,12 @@ usage() {
16 16
             "-c DIR      change to DIR before doing anything"                 \
17 17
             "-l          list handlers and exit"                              \
18 18
             "-d          turn on debugging mode"                              \
19
+            "-t CERTS    Use certificates file CERTS."                        \
20
+            "-T          Use system CA certificate storage."                  \
19 21
             "-N          turn off locking; make sure that handlers cannot"    \
20 22
             "            interfere with each other or that another locking"   \
21 23
             "            mechanism is in place.  Also see NOTE below."        \
24
+            "-V          Show imapdomo version and exit."                     \
22 25
        --                                                                     \
23 26
        "imapdomo will try to read init.lua and handlers/ACTION.lua from its"  \
24 27
        "configuration directory."                                             \
@@ -26,6 +29,12 @@ usage() {
26 29
        "See imapfilter_config(5)) for guide and API reference.  Few functions"\
27 30
        "are also available in .imapdomo/imapdomo.lua"                         \
28 31
        ""                                                                     \
32
+       "By default, imapdomo will look for 'certificates' file under the"     \
33
+       "directory (__IMAPDOMO_CONFIG_USER__), and if it exists, it will tell" \
34
+       "imapfilter to use it via -t argument.  If it does not exist, it will" \
35
+       "leave the argument out, meaning imapfilter will use system cert"      \
36
+       "storage.  Use -T to force system storage or -t to force another file."\
37
+       ""                                                                     \
29 38
        "NOTE: Be aware that it's your responsibility to ensure that filters"  \
30 39
        "don't cause performance problems. (Trust me, it's not so hard to"     \
31 40
        "\"earn\" yourself a nice server ban--I learned that the hard way)."
@@ -88,6 +97,7 @@ lock() {
88 97
     #
89 98
     debug -v NoLock
90 99
     $NoLock && return 0
100
+    mkdir -p "$IMAPDOMO_USER_CACHE"
91 101
     date +"$$@%s" > "$IMAPDOMO_USER_CACHE/lock"
92 102
     debug -f "$IMAPDOMO_USER_CACHE/lock"
93 103
 }
@@ -157,7 +167,7 @@ main() {
157 167
     CfgDir="$IMAPDOMO_CFGDIR"
158 168
     LogDir="$IMAPDOMO_USER_CACHE/logs"
159 169
     HeaderDir="$IMAPDOMO_USER_CACHE/headers"
160
-    Certs="$IMAPDOMO_CFGDIR/certificates"
170
+    test -f "$IMAPDOMO_CFGDIR/certificates" && Certs="$IMAPDOMO_CFGDIR/certificates"
161 171
     NoLock=false
162 172
     Debug=false
163 173
     #shellcheck disable=SC2034
@@ -167,6 +177,7 @@ main() {
167 177
         -t) Certs="$2"; shift 2 || usage -w "missing value to: $1" ;;
168 178
         -l) lshandlers; exit ;;
169 179
         -N) NoLock=true; shift ;;
180
+        -T) Certs=; shift ;;
170 181
         -V|--version-semver) show_semversion ;;
171 182
         --version) show_version ;;
172 183
         -*) usage -w "unknown argument: '$1'" ;;

+ 226
- 145
utils/mkit/include/build.sh Vedi File

@@ -4,9 +4,11 @@
4 4
 
5 5
 mkit_import ini
6 6
 mkit_import facts
7
+mkit_import plugin
8
+mkit_import util
7 9
 
8 10
 
9
-__build1() {
11
+build__file() {
10 12
     #
11 13
     # Process one skeleton $1 of type $3 (or guessed) to path $2
12 14
     #
@@ -14,28 +16,57 @@ __build1() {
14 16
     local dstpath=$2    # destination meaty animal path
15 17
     local ftype=$3      # file/builder type
16 18
     test -n "$dstpath"  || dstpath=${srcpath%.skel}
17
-    test -n "$ftype"    || ftype=$(__guess_ftype "$dstpath")
19
+    test -n "$ftype"    || ftype=MKIT_COMMON
18 20
     debug_var srcpath dstpath ftype
19
-    <"$srcpath" __build1_ftype "$ftype" >"$dstpath"
20
-    __rec_built "$dstpath"
21
+    <"$srcpath" __build__file_type "$ftype" >"$dstpath"
22
+    build__record "$dstpath"
21 23
 }
22 24
 
23
-__build1_ftype() {
25
+__build__file_type() {
24 26
     #
25 27
     # Build a file of type $1; fom stdin to stdout
26 28
     #
27 29
     local ftype=$1      # file/builder type
28
-    case $ftype in
29
-        MKIT_COMMON)    __expand_macros "macros" ;;
30
-        rpmstuff)       __expand_macros "macros" "rpmstuff:macros" ;;
31
-        debstuff)       __expand_macros "macros" "debstuff:macros" ;;
32
-        *)              die "unknown file type: $ftype" ;;
33
-    esac
30
+    if test "$ftype" == MKIT_COMMON; then
31
+        __build__macro_expand "build:macros"
32
+    elif plugin__isvalid "$ftype"; then
33
+        __build__macro_expand "$ftype:macros"
34
+    else
35
+        die "unknown file type: $ftype"
36
+    fi
34 37
 }
35 38
 
36
-__expand_line() {
39
+__build__macro_ls() {
37 40
     #
38
-    # Expand macro from $MacroMap in single line $1
41
+    # List known macros
42
+    #
43
+    find "${MacroDirs[@]}" -mindepth 1 -depth -printf '%P\n' \
44
+      | sed 's/[.]/:/g'
45
+}
46
+
47
+__build__macro_read() {
48
+    #
49
+    # Read macro $1
50
+    #
51
+    local key=$1
52
+    find "${MacroDirs[@]}" -mindepth 1 -depth -name "$key" \
53
+      | head -1 \
54
+      | xargs -r cat
55
+}
56
+
57
+__build__macro_put() {
58
+    #
59
+    # Write stdin as macro $1
60
+    #
61
+    local fqkey=$1
62
+    local file="$MKIT_LOCAL/data/${fqkey//:/.}"
63
+    mkdir -p "${file%/*}"
64
+    cat >"$file"
65
+}
66
+
67
+__build__macro_expand_line() {
68
+    #
69
+    # Expand macro from config in single line $1
39 70
     #
40 71
     # If macro value has multiple lines, repeat original line with
41 72
     # different substitution.
@@ -44,76 +75,47 @@ __expand_line() {
44 75
     # line `see: "__FOO__"` will expand to two lines: `see: "foo"`
45 76
     # and `see: "bar"`.
46 77
     #
47
-    local line=$1   # line to process
48
-    local mname     # macro name
49
-    local mvline    # line of macro value
50
-    local xline     # expanded line
51
-    xline=$line
52
-    for mname in "${!MacroMap[@]}"; do
53
-        if ! test "${line//$mname}" == "$line"; then
54
-            xline=$(
55
-                while IFS= read -r mvline; do
56
-                    echo "${line//$mname/$mvline}"
57
-                done <<<"${MacroMap[$mname]}"
78
+    local raw_line=$1       # line to process
79
+    local macro_name        # macro name
80
+    local macro_value_line  # line of macro value
81
+    local expanded_line     # expanded line
82
+    expanded_line=$raw_line
83
+    for macro_name in $(__build__macro_ls); do
84
+        if ! test "${raw_line//$macro_name}" == "$raw_line"; then
85
+            expanded_line=$(
86
+                while IFS= read -r macro_value_line; do
87
+                    echo "${raw_line//"$macro_name"/"$macro_value_line"}"
88
+                done <<<"$(__build__macro_read "$macro_name")"
58 89
             )
59 90
         fi
60
-        line=$xline
91
+        raw_line=$expanded_line
61 92
     done
62
-    echo "$xline"
93
+    echo "$expanded_line"
63 94
     return 1
64 95
 }
65 96
 
66
-__expand_macros() {
97
+__build__macro_expand() {
67 98
     #
68
-    # Read stdin, expanding macros from sections $@
99
+    # Read stdin, expanding macros from extra sections $@
69 100
     #
70
-    local section       # each section to expand macros from
101
+    local MacroDirs=()  # directories to lookup macros in
102
+    local mdir          # every ^^
71 103
     local line          # each line on stdin
72
-    local mname         # each macro name
73
-    local -A MacroMap   # macro value map
74
-    MacroMap[__MKIT_MKIT_VERSION__]=$MKIT_VERSION
75
-    MacroMap[__MKIT_PROJ_NAME__]=$(ini 1value project:name)
76
-    MacroMap[__MKIT_PROJ_CODENAME__]=$(ini 1value project:codename)
77
-    MacroMap[__MKIT_PROJ_LICENSE__]=$(ini 1value project:license)
78
-    MacroMap[__MKIT_PROJ_PKGNAME__]=$(ini 1value project:pkgname)
79
-    MacroMap[__MKIT_PROJ_TAGLINE__]=$(ini 1value project:tagline)
80
-    MacroMap[__MKIT_PROJ_MAINTAINER__]=$(ini 1value project:maintainer)
81
-    MacroMap[__MKIT_PROJ_VCS_BROWSER__]=$(ini 1value project:vcs_browser)
82
-    MacroMap[__MKIT_PROJ_GIT_LASTHASH__]=$(__cached git_lasthash)
83
-    MacroMap[__MKIT_PROJ_GIT_LASTSUMMARY__]=$(__cached git_lastsummary)
84
-    MacroMap[__MKIT_PROJ_VERSION__]=$(__cached semver)
85
-    for section in "$@"; do
86
-        for mname in $(ini lskeys "$section"); do
87
-            MacroMap[$mname]=$(ini values "$section:$mname")
88
-        done
104
+    local bltndata="$MKIT_LOCAL/data/MKIT_BUILTIN"
105
+    local usrdata="$MKIT_LOCAL/data/macros"
106
+    test -d "$bltndata" && MacroDirs+=("$bltndata")
107
+    test -d "$usrdata" && MacroDirs+=("$usrdata")
108
+    for extra in "$@"; do
109
+        mdir="$MKIT_LOCAL/data/${extra//:/.}"
110
+        test -d "$mdir" || continue
111
+        MacroDirs+=("$mdir")
89 112
     done
90
-    debug_var MacroMap
91 113
     while IFS= read -r line; do
92
-        __expand_line "$line"
114
+        __build__macro_expand_line "$line"
93 115
     done
94 116
 }
95 117
 
96
-__guess_ftype() {
97
-    #
98
-    # Guess file type from destination path $1
99
-    #
100
-    local dstpath=$1    # destination path
101
-    case $dstpath in
102
-        *)    echo MKIT_COMMON ;;
103
-    esac
104
-}
105
-
106
-__qfs() {
107
-    #
108
-    # Quote for our sed scipt's RHS
109
-    #
110
-    sed '
111
-        s:\\:\\\\:g
112
-        s:|:\\|:g
113
-    '
114
-}
115
-
116
-__cached() {
118
+build__cached() {
117 119
     #
118 120
     # Cached value $1 of function $1()
119 121
     #
@@ -126,7 +128,8 @@ __cached() {
126 128
     # arguments).
127 129
     #
128 130
     local name=$1
129
-    __local_get "$name" && return 0
131
+    __local_get "$name" && debug "cache hit with: $name()" && return 0
132
+    debug "cache miss with: $name()"
130 133
     "$name" | __local_putb "$name"
131 134
     __local_get "$name"
132 135
 }
@@ -136,7 +139,7 @@ __local_putb() {
136 139
     # Make file $1 in $MKIT_LOCAL from stdin and mark as built
137 140
     #
138 141
     local fpath=$1
139
-    __local_put "$fpath" && __rec_built "$MKIT_LOCAL/$fpath"
142
+    __local_put "$fpath" && build__record "$MKIT_LOCAL/$fpath"
140 143
 }
141 144
 
142 145
 __local_put() {
@@ -156,7 +159,24 @@ __local_get() {
156 159
     cat "$fpath" 2>/dev/null
157 160
 }
158 161
 
159
-__rec_built() {
162
+build__fact() {
163
+    #
164
+    # Make fact file $1 in $MKIT_LOCAL from stdin; mark as built
165
+    #
166
+    local name=$1
167
+    __local_put "facts/$name" || die
168
+    build__recordr "$MKIT_LOCAL/facts"
169
+}
170
+
171
+build__factr() {
172
+    #
173
+    # Read fact file $1 from $MKIT_LOCAL
174
+    #
175
+    local name=$1
176
+    cat "$MKIT_LOCAL/facts/$name" || die
177
+}
178
+
179
+build__record() {
160 180
     #
161 181
     # Record file $1 for deletion on `clean`
162 182
     #
@@ -165,38 +185,141 @@ __rec_built() {
165 185
     echo "1:$file" >> "$MKIT_LOCAL/built.lst"
166 186
 }
167 187
 
168
-_mkit_data() {
188
+build__recordr() {
169 189
     #
170
-    # Build sampler showing all macro values
190
+    # Record dir $1 for recursive  deletion on `clean`
191
+    #
192
+    local path=$1
193
+    mkdir -p "$MKIT_LOCAL"
194
+    echo "r:$path" >> "$MKIT_LOCAL/built.lst"
195
+}
196
+
197
+_mkit_builddata() {
198
+    #
199
+    # Build config data
171 200
     #
172 201
     local macro
173 202
     local section
174
-    local sections
175
-    sections=()
176
-    ini lskeys macros | grep -q . && sections=(macros)
177
-    sections+=( $(ini lssect | grep ':macros$') )
178
-    {
179
-        echo "(builtin):"
180
-        echo "  x_MKIT_MKIT_VERSION__ => '__MKIT_MKIT_VERSION__'"
181
-        echo "  x_MKIT_PROJ_NAME__ => '__MKIT_PROJ_NAME__'"
182
-        echo "  x_MKIT_PROJ_CODENAME__ => '__MKIT_PROJ_CODENAME__'"
183
-        echo "  x_MKIT_PROJ_LICENSE__ => '__MKIT_PROJ_LICENSE__'"
184
-        echo "  x_MKIT_PROJ_PKGNAME__ => '__MKIT_PROJ_PKGNAME__'"
185
-        echo "  x_MKIT_PROJ_TAGLINE__ => '__MKIT_PROJ_TAGLINE__'"
186
-        echo "  x_MKIT_PROJ_MAINTAINER__ => '__MKIT_PROJ_MAINTAINER__'"
187
-        echo "  x_MKIT_PROJ_VCS_BROWSER__ => '__MKIT_PROJ_VCS_BROWSER__'"
188
-        echo "  x_MKIT_PROJ_GIT_LASTHASH__ => '__MKIT_PROJ_GIT_LASTHASH__'"
189
-        echo "  x_MKIT_PROJ_GIT_LASTSUMMARY__ => '__MKIT_PROJ_GIT_LASTSUMMARY__'"
190
-        echo "  x_MKIT_PROJ_VERSION__ => '__MKIT_PROJ_VERSION__'"
191
-        for section in "${sections[@]}"; do
192
-            echo "$section:"
193
-            for macro in $(ini lskeys "$section"); do
194
-                echo "  x${macro:1} => '$macro'"
195
-            done
203
+    test -d "$MKIT_LOCAL/data/build.macros" && {
204
+        warn "mkit: using cached _mkit_builddata: $(date -Isec -r "$MKIT_LOCAL/data/build.macros")" \
205
+             "mkit:   (hint: run 'make clean' to regenerate it)"
206
+        return 0
207
+    }
208
+    build__recordr "$MKIT_LOCAL/data"
209
+    for macro in $(ini lskeys "build:macros"); do
210
+        ini values "build:macros:$macro" | __build__macro_put "build:macros/$macro"
211
+    done
212
+}
213
+
214
+_mkit_inidata() {
215
+    #
216
+    # Build INI data
217
+    #
218
+    test -d "$MKIT_LOCAL/data/ini" && {
219
+        warn "mkit: using cached _mkit_inidata: $(date -Isec -r "$MKIT_LOCAL/data/ini")" \
220
+             "mkit:   (hint: run 'make clean' to regenerate it)"
221
+        return 0
222
+    }
223
+    build__recordr "$MKIT_LOCAL/data"
224
+    local sect
225
+    local key
226
+    ini lssect \
227
+      | while read -r sect; do
228
+            mkdir -p "$MKIT_LOCAL/data/ini/$sect"
229
+            ini lskeys "$sect" \
230
+              | while read -r key; do
231
+                    ini values "$sect:$key" >"$MKIT_LOCAL/data/ini/$sect/$key"
232
+                done
196 233
         done
197
-    } \
198
-      | __expand_macros "MKIT_BUILTIN" "${sections[@]}" \
199
-      | sed '/^  x/ s|x|_|'
234
+}
235
+
236
+_mkit_metadata() {
237
+    #
238
+    # Build meta data
239
+    #
240
+    local plg_sections=()
241
+    test -d "$MKIT_LOCAL/data/MKIT_BUILTIN" && {
242
+        warn "mkit: using cached _mkit_metadata: $(date -Isec -r "$MKIT_LOCAL/data/MKIT_BUILTIN")" \
243
+             "mkit:   (hint: run 'make clean' to regenerate it)"
244
+        return 0
245
+    }
246
+    build__recordr "$MKIT_LOCAL/data"
247
+    echo "$MKIT_VERSION"            | __build__macro_put MKIT_BUILTIN/__MKIT_MKIT_VERSION__
248
+    ini 1value project:name         | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_NAME__
249
+    ini 1value project:codename     | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_CODENAME__
250
+    ini 1value project:license      | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_LICENSE__
251
+    ini 1value project:pkgname      | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_PKGNAME__
252
+    ini 1value project:tagline      | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_TAGLINE__
253
+    ini 1value project:maintainer   | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_MAINTAINER__
254
+    ini 1value project:vcs_browser  | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_VCS_BROWSER__
255
+    build__cached git_lasthash      | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_GIT_LASTHASH__
256
+    build__cached git_lastsummary   | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_GIT_LASTSUMMARY__
257
+    build__cached semver            | __build__macro_put MKIT_BUILTIN/__MKIT_PROJ_VERSION__
258
+    util__loadarr plg_sections __plugin_macro_sections
259
+    debug_var plg_sections
260
+    for section in macros "${plg_sections[@]}"; do
261
+        for macro in $(ini lskeys "$section"); do
262
+            ini values "$section:$macro" | __build__macro_put "$section/$macro"
263
+        done
264
+    done
265
+}
266
+
267
+__subdirs() {
268
+    #
269
+    # List direct sub-directories of $1
270
+    #
271
+    find "$1" -maxdepth 1 -mindepth 1 -printf '%P\n' -type d
272
+}
273
+
274
+__subfiles() {
275
+    #
276
+    # List direct sub-files of $1
277
+    #
278
+    find "$1" -maxdepth 1 -mindepth 1 -printf '%P\n' -type f
279
+}
280
+
281
+__plugin_macro_sections() {
282
+    #
283
+    # List macro sections for plugins
284
+    #
285
+    plugin__ls \
286
+      | sed 's/$/:macros/'
287
+}
288
+
289
+_mkit_show_metadata() {
290
+    #
291
+    # Show sampler of macro values
292
+    #
293
+    __show_msection MKIT_BUILTIN
294
+    __show_msection macros
295
+    __plugin_macro_sections \
296
+      | while read -r section; do
297
+            __show_msection "$section"
298
+        done
299
+}
300
+
301
+_mkit_show_builddata() {
302
+    #
303
+    # Show sampler of config values
304
+    #
305
+    __show_msection build:macros
306
+}
307
+
308
+__show_msection() {
309
+    #
310
+    # Show macros of section $1
311
+    #
312
+    local section=$1
313
+    local macro_name
314
+    local first=true
315
+    local label=$section
316
+    local secdir="$MKIT_LOCAL/data/${section//:/.}"
317
+    test -d "$secdir" || return 0
318
+    test "$section" == MKIT_BUILTIN && label="(builtin)"
319
+    for macro_name in $(__subfiles "$secdir"); do
320
+        $first && echo "$label:"; first=false
321
+        echo "    $macro_name => '$(<"$secdir/$macro_name")'"
322
+    done
200 323
 }
201 324
 
202 325
 build() {
@@ -206,7 +329,7 @@ build() {
206 329
     local srcpath   # each source path
207 330
     find . -type f -name '*.skel' \
208 331
      | while read -r srcpath; do
209
-           __build1 "$srcpath"
332
+           build__file "$srcpath"
210 333
        done
211 334
 }
212 335
 
@@ -242,38 +365,6 @@ clean() {
242 365
     true
243 366
 }
244 367
 
245
-debstuff() {
246
-    #
247
-    # Build Debian stuff (eamed tarball, debian dir)
248
-    #
249
-    local version       # package version
250
-    local debian_skel   # 'debian' folder skeleton
251
-    local dfsrc         # each source file from ^^
252
-    local dftgt         # each built packaging file
253
-    version=$(__cached semver)
254
-
255
-    # tarball - we should already have by means of 'dist'
256
-    #
257
-    mv "${MKIT_PROJ_PKGNAME}-$version.tar.gz" \
258
-       "${MKIT_PROJ_PKGNAME}_$version.orig.tar.gz" \
259
-     || die "could not rename tarball"
260
-    __rec_built "${MKIT_PROJ_PKGNAME}_$version.orig.tar.gz"
261
-
262
-    # read content of each mandatory file from debian_skel
263
-    #
264
-    debian_skel=$(ini 1value dist:debstuff)
265
-    test -n "$debian_skel" || die "dist:debstuff not specified"
266
-    test -d "$debian_skel" || die "debian directory template found: $debian_skel"
267
-    mkdir -p debian/source
268
-    find "$debian_skel" -type f \
269
-      | while read -r dfsrc; do
270
-            dftgt="debian/${dfsrc#$debian_skel}"
271
-            mkdir -p "$(dirname "$dftgt")"
272
-            __build1 "$dfsrc" "$dftgt" debstuff
273
-        done
274
-    __rec_built debian
275
-}
276
-
277 368
 dist() {
278 369
     #
279 370
     # Create distributable tarball
@@ -287,25 +378,15 @@ dist() {
287 378
     version=$(semver)
288 379
     dirname=$MKIT_PROJ_PKGNAME-$version
289 380
     git_lasthash=$(git_lasthash)
381
+    debug_var version dirname git_lasthash
290 382
     mkdir -p "$dirname"
291 383
     ini values "dist:tarball" | xargs -I DIST_ITEM cp -R DIST_ITEM "$dirname"
292 384
     mkdir -p "$dirname/.mkit"
293 385
     echo -n "$version" > "$dirname/.mkit/semver"
294 386
     echo -n "$git_lasthash" > "$dirname/.mkit/git_lasthash"
387
+    cp -r "$MKIT_LOCAL/data" "$dirname/.mkit/"
295 388
     tar -cf "$dirname.tar" "$dirname"
296 389
     gzip -f "$dirname.tar"      # see above FIXME
297
-    __rec_built "$dirname.tar.gz"
390
+    build__record "$dirname.tar.gz"
298 391
     rm -rf "$dirname"
299 392
 }
300
-
301
-rpmstuff() {
302
-    #
303
-    # Build specfile
304
-    #
305
-    local specname=$MKIT_PROJ_PKGNAME.spec      # .spec filename
306
-    local specsrc                               # source of specfile
307
-    specsrc="$(ini 1value "dist:rpmstuff")"
308
-    test -n "$specsrc" || die "dist:rpmstuff not specified"
309
-    test -f "$specsrc" || die "specfile template not found: $specsrc"
310
-    __build1 "$specsrc" "$specname" rpmstuff
311
-}

+ 13
- 12
utils/mkit/include/deploy.sh Vedi File

@@ -3,12 +3,13 @@
3 3
 # See LICENSE file for copyright and license details.
4 4
 
5 5
 mkit_import ini
6
+mkit_import build
6 7
 
7
-__deploy_item() {
8
+__deploy__item() {
8 9
     #
9 10
     # Deploy item and make it look like wanted
10 11
     #
11
-    # usage: __deploy_item src dst [mode]
12
+    # usage: __deploy__item src dst [mode]
12 13
     #
13 14
     # Both src and dst must be names of actual items[1],
14 15
     # whereas dst must not exist.  On update, dst is
@@ -37,15 +38,15 @@ __deploy_item() {
37 38
                 [[ $item =~ .skel$ ]] \
38 39
                  && grep -qe "${item%.skel}" "$MKIT_LOCAL/built.lst" \
39 40
                  && continue
40
-                __deploy_item "$item" "$dst${item#$src}" "$mode"
41
+                __deploy__item "$item" "$dst${item#"$src"}" "$mode"
41 42
             done
42 43
     else
43 44
         test "$mode" == "SRC" && mode=$(stat -c "%a" "$src")
44
-        __maybe install -DTvm "$mode" "$src" "$dst"
45
+        __deploy__maybe install -DTvm "$mode" "$src" "$dst"
45 46
     fi
46 47
 }
47 48
 
48
-__get_dst() {
49
+__deploy__dst() {
49 50
     #
50 51
     # Find out target path for src file $2 of group $1
51 52
     #
@@ -53,10 +54,10 @@ __get_dst() {
53 54
     local src=$2        # each source
54 55
     local dst=$3        # alternative destination name
55 56
     test -n "$dst" || dst=${src##*/}
56
-    echo "$(__get_root "$grp")/$dst"
57
+    echo "$(__deploy__root "$grp")/$dst"
57 58
 }
58 59
 
59
-__get_root() {
60
+__deploy__root() {
60 61
     #
61 62
     # Find out target root for group $1
62 63
     #
@@ -73,7 +74,7 @@ __get_root() {
73 74
     esac
74 75
 }
75 76
 
76
-__maybe() {
77
+__deploy__maybe() {
77 78
     #
78 79
     # Call the deploy command $1 $@ unless in dry mode
79 80
     #
@@ -102,8 +103,8 @@ install() {
102 103
             mode=$(ini 1value "modes:$group")
103 104
             ini values "files:$group" \
104 105
               | while read -r src dst; do
105
-                    dst=$(__get_dst "$group" "$src" "$dst")
106
-                    __deploy_item "$src" "$dst" "$mode"
106
+                    dst=$(__deploy__dst "$group" "$src" "$dst")
107
+                    __deploy__item "$src" "$dst" "$mode"
107 108
                 done
108 109
         done
109 110
     test -f "$MKIT_LOCAL/autoclean" && clean
@@ -123,8 +124,8 @@ uninstall() {
123 124
       | while read -r group; do
124 125
             ini values "files:$group" \
125 126
               | while read -r src dst; do
126
-                    dst=$(__get_dst "$group" "$src" "$dst")
127
-                    __maybe rm -vrf "$dst"
127
+                    dst=$(__deploy__dst "$group" "$src" "$dst")
128
+                    __deploy__maybe rm -vrf "$dst"
128 129
                 done
129 130
         done
130 131
 }

+ 51
- 23
utils/mkit/include/facts.sh Vedi File

@@ -9,7 +9,10 @@ git_bool() {
9 9
     # Get git bool (ie. exit status counts) $1
10 10
     #
11 11
     local bool_name=$1      # name of boolean to get
12
-    git_present || warn "can't give bool outside git repo: $bool_name"
12
+    git_present || {
13
+        warn "can't give bool outside git repo: $bool_name"
14
+        return 3
15
+    }
13 16
     case "$bool_name" in
14 17
         dirty_files)
15 18
             git diff-files | grep -qm 1 .
@@ -20,6 +23,13 @@ git_bool() {
20 23
         dirty)
21 24
             git_bool dirty_files || git_bool dirty_index
22 25
             ;;
26
+        async)
27
+            local status_desc   # status description (in square brackets)
28
+            status_desc=$(git status -sb | grep '^##.*' | grep -o '\[[^]]*\]$')
29
+            grep -qw behind <<<"$status_desc" && return 0
30
+            grep -qw ahead <<<"$status_desc" && return 0
31
+            return 1
32
+            ;;
23 33
         *)
24 34
             warn "unknown git bool asked: $bool_name"
25 35
             return 2
@@ -51,6 +61,9 @@ git_fact() {
51 61
         reldiff)
52 62
             git log --oneline "$(git_fact latest_tag)..HEAD" --name-only
53 63
             ;;
64
+        reldiff_brief)
65
+            git rev-list --oneline "$(git_fact latest_tag)..HEAD"
66
+            ;;
54 67
         latest_sha)
55 68
             git log -1 --pretty=format:%h HEAD
56 69
             ;;
@@ -124,6 +137,42 @@ git_lastsummary() {
124 137
       | cut -d' ' -f2-
125 138
 }
126 139
 
140
+make_bmeta() {
141
+    #
142
+    # Compose build metadata string
143
+    #
144
+    local is_tagged=T   # T if tagged (clean, no metadata), F if devel
145
+    local brname        # current branch name
146
+    local ghash         # current commit short hash
147
+    if ! git describe --tags --exact-match HEAD >&/dev/null;
148
+    then    # we are at a later commit than the last tag
149
+        is_tagged=F
150
+        brname=$(git_fact current_branch | sed 's/[^[:alnum:]]/_/g')
151
+        ghash=$(git_fact latest_sha)
152
+    fi
153
+    {
154
+        if test "$is_tagged" == F; then
155
+            test -n "$stamp" && echo "t$stamp"
156
+            echo "$brname"
157
+            echo "g$ghash"
158
+            test -n "$MKIT_UPSTREAM" && echo "u$MKIT_UPSTREAM"
159
+            git_bool async && "$MKIT_ASYNC" && echo async
160
+            "$MKIT_IN_CI" && test -z "$MKIT_UPSTREAM" && git_bool async && {
161
+                warn "branch is out-of sync with upstream; git hash might be misleading" \
162
+                     "  (hint: if this is due to CI auto-rebase, set MKIT_UPSTREAM to original shorthash)"
163
+            }
164
+        fi
165
+        git_bool dirty && echo dirty
166
+    } | paste -sd.
167
+}
168
+
169
+make_suffix() {
170
+    local bmeta
171
+    bmeta=$(make_bmeta)
172
+    test -n "$bmeta" || return 0
173
+    echo "+$bmeta"
174
+}
175
+
127 176
 semver() {
128 177
     #
129 178
     # Build proper SemVer version string
@@ -162,10 +211,6 @@ semver() {
162 211
     local xyz           # base version string
163 212
     local prerl         # pre-release keyword (from mkit.ini, eg. 'beta')
164 213
     local latest_tag    # latest git tag
165
-    local brname        # current branch name
166
-    local ghash         # current commit short hash
167
-    local is_tagged=T   # T if tagged (clean, no metadata), F if devel
168
-    local is_dirty=F    # F if dirty, T if clean
169 214
     local stamp         # timestamp or nothing (see $MKIT_TSTAMP)
170 215
     local suffix        # version suffix
171 216
     prerl=$(ini 1value project:prerl)
@@ -189,24 +234,7 @@ semver() {
189 234
         *)  warn "bad form of last tag, using base version from mkit.ini: tag is '$latest_tag'"
190 235
             xyz=$(ini 1value project:version) ;;
191 236
     esac
192
-    if ! git describe --tags --exact-match HEAD >&/dev/null;
193
-    then    # we are at a later commit than the last tag
194
-        is_tagged=F
195
-        brname=$(git_fact current_branch | sed 's/[^[:alnum:]]/_/g')
196
-        ghash=$(git_fact latest_sha)
197
-    fi
198
-    git_bool dirty && is_dirty=T
199
-    case "$is_dirty:$is_tagged:$stamp" in
200
-        F:T:*)  suffix=""                                 ;;
201
-        T:T:)   suffix="+dirty"                           ;;
202
-        T:T:*)  suffix="+t$stamp.dirty"                   ;;
203
-        F:F:)   suffix="+$brname.g$ghash"                 ;;
204
-        F:F:*)  suffix="+t$stamp.$brname.g$ghash"         ;;
205
-        T:F:)   suffix="+$brname.g$ghash.dirty"           ;;
206
-        T:F:*)  suffix="+t$stamp.$brname.g$ghash.dirty"   ;;
207
-        *)      suffix=MKIT_BUG
208
-                warn "MKIT_BUG: bad dirt/commit detection" ;;
209
-    esac
237
+    suffix=$(make_suffix)
210 238
     test -n "$prerl" && suffix="-$prerl$suffix"
211 239
     echo "$xyz$suffix"
212 240
 }

+ 10
- 32
utils/mkit/include/ini.sh Vedi File

@@ -35,9 +35,9 @@ __ini_expandln() {
35 35
         ((Depth++))
36 36
         debug_var line_todo
37 37
         test "$Depth" -le "$MaxDepth" || {
38
-            warn "expansion error: reached maximum depth: $Depth > $MaxDepth"
39
-            warn "    original line: $line_orig"
40
-            warn "    expanded line: $line_todo"
38
+            warn "expansion error: reached maximum depth: $Depth > $MaxDepth" \
39
+                 "    original line: $line_orig" \
40
+                 "    expanded line: $line_todo"
41 41
             return 3
42 42
         }
43 43
         line_done=$(__ini_expandln_once "$line_todo")
@@ -64,7 +64,7 @@ __ini_expandln_once() {
64 64
         ipath=${ref#[}; ipath=${ipath%]}
65 65
         value=$(ini 1value "$ipath")
66 66
         debug_var line ref ipath value
67
-        line=$(sed "s|\\[$ipath\\]|$value|" <<<"$line")
67
+        line=${line//"[$ipath]"/"$value"}
68 68
     done
69 69
     echo "$line"
70 70
 }
@@ -78,11 +78,10 @@ __ini_grepcmt() {
78 78
 
79 79
 __ini_grepkey() {
80 80
     #
81
-    # Read key from a section
81
+    # Read key from a normalized `key=value` section
82 82
     #
83 83
     local wnt=$1    # wanted key
84 84
     grep '.' \
85
-      | sed -e 's/ *= */=/; s/ +$//; s/^//;' \
86 85
       | grep -e "^$wnt=" \
87 86
       | cut -d= -f2- \
88 87
       | __ini_maybe_expand
@@ -99,7 +98,7 @@ __ini_greppath() {
99 98
     #
100 99
     local wnt=$1                    # wanted path
101 100
     local wntkey=${wnt##*:}         # ^^ key part
102
-    local wntsec=${wnt%:$wntkey}    # ^^ section part
101
+    local wntsec=${wnt%:"$wntkey"}  # ^^ section part
103 102
     local override                  # ENV override (only ENV section)
104 103
     if test "$wntsec" = 'ENV'; then
105 104
         override=${!wntkey}
@@ -120,13 +119,14 @@ __ini_grepsec() {
120 119
     grep '.' \
121 120
       | while read -r line; do
122 121
             case "$line" in
123
-                \[$wnt\]) ok=true;  continue ;;
124
-                \[*\])    ok=false; continue ;;
122
+                \["$wnt"\]) ok=true;  continue ;;
123
+                \[*\])      ok=false; continue ;;
125 124
             esac
126 125
             $ok || continue
127 126
             printf -- '%s\n' "$line"
128 127
         done \
129
-      | sed -e 's/ *= */=/; s/ +$//; s/^//;'
128
+      | grep '^[[:space:]]*[^[:space:]:][^:]*[[:space:]]*[=]' \
129
+      | sed -e 's/[[:space:]]*=[[:space:]]*/=/; s/[[:space:]]*$//; s/^[[:space:]]*//;'
130 130
 }
131 131
 
132 132
 __ini_lskeys() {
@@ -191,25 +191,3 @@ ini() {
191 191
     esac
192 192
     __ini_body | $fn "$arg" | $limit
193 193
 }
194
-
195
-update_version() {
196
-    #
197
-    # Change project:version in mkit.ini at path $2 to value $1
198
-    #
199
-    local version=$1    # new version
200
-    local inifile=$2    # mkit.ini path
201
-    local tmp           # mkit.ini cache
202
-    tmp=$(mktemp -t mkit.update_version.XXXXXXXX)
203
-    <"$inifile" perl -e '
204
-        my $hit = 0;
205
-        my $done = 0;
206
-        foreach (<STDIN>) {
207
-            if      ($done) { print; next; }
208
-            elsif   (m/\[project\]/) { $hit++; print; next; }
209
-            elsif   (m/\[/) { $hit = 0; print; next; }
210
-            elsif   ($hit) { s/\bversion\b( *)=( *).*/version$1=$2$ARGV[0]/ and $done++; print; }
211
-            else { print; next; }
212
-        }
213
-    ' "$version" >"$tmp" || die "failed to update version in mkit.ini"
214
-    mv "$tmp" "$inifile"
215
-}

+ 88
- 59
utils/mkit/include/mkit.sh Vedi File

@@ -2,6 +2,8 @@
2 2
 # MKit - simple install helper
3 3
 # See LICENSE file for copyright and license details.
4 4
 
5
+_MKIT_IMPORTED=""
6
+
5 7
 die() {
6 8
     #
7 9
     # Exit with message and non-zero exit status
@@ -18,40 +20,27 @@ mkit_import() {
18 20
     #
19 21
     local modname=$1
20 22
     local modpath
23
+    test "$modname" == mkit && die "cannot re-import core module: $modname"
24
+    __mkit__is_imported "$modname" && return 0
21 25
     modpath="$MKIT_DIR/include/$modname.sh"
22 26
     test -f "$modpath" || die "no such module: $modpath"
23 27
     bash -n "$modpath" || die "bad syntax: $modpath"
24
-    #shellcheck disable=SC1090
28
+    #shellcheck disable=SC1090,SC1091
25 29
     . "$modpath" || die "failed to import: $modname"
30
+    _MKIT_IMPORTED+=":$modname"
26 31
 }
27 32
 
28
-mkit_import build
29
-mkit_import deploy
30
-mkit_import release
31
-mkit_import ini
32
-
33
-__valid_targets() {
34
-    #
35
-    # List valid routes
36
-    #
37
-    echo _mkit_data
38
-    echo build
39
-    echo clean
40
-    echo debstuff
41
-    echo dist
42
-    echo install
43
-    echo release
44
-    echo release_x
45
-    echo release_y
46
-    echo release_z
47
-    echo rpmstuff
48
-    echo uninstall
49
-    echo vbump
50
-    echo vbump_x
51
-    echo vbump_y
52
-    echo vbump_z
33
+__mkit__is_imported() {
34
+    #
35
+    # True if module $1 is already imported
36
+    #
37
+    local modname=$1
38
+    grep -qw -e "$modname" <<<"$_MKIT_IMPORTED"
53 39
 }
54 40
 
41
+mkit_import ini
42
+mkit_import target
43
+
55 44
 debug() {
56 45
     #
57 46
     # Print debug message
@@ -80,27 +69,82 @@ __compver() {
80 69
     #
81 70
     # True if version $1 matches our version
82 71
     #
83
-    # If our x is 0, check first two fragments, otherwise check just
84
-    # the x.  Fragments must equal.
72
+    # We're following a particular convention described in SemVer
73
+    # issue #363:
74
+    #
75
+    #     https://github.com/semver/semver/issues/363
76
+    #
77
+    # In short: If our X is 0, check first two fragments, otherwise
78
+    # check just the x.  Fragments must equal.
85 79
     #
86 80
     local their_ver=$1      # their version
87 81
     local our_x             # our X
88 82
     local our_y             # our Y
83
+    local our_z             # our z
89 84
     local their_x           # their X
90 85
     local their_y           # their Y
91
-    their_x=${their_ver%%.*}
92
-    their_y=${their_ver##$their_x.}
93
-    their_y=${their_y%%.*}
94
-    our_x=${MKIT_VERSION%%.*}
95
-    our_y=${MKIT_VERSION##$our_x.}
96
-    our_y=${our_y%%.*}
97
-    debug_var MKIT_VERSION our_x our_y their_ver their_x their_y
98
-    test "$their_x" -eq "$our_x" || return 1
99
-    test "$our_x" -eq 0 && {
100
-        test "$their_y" = "$our_y"
101
-        return $?
86
+    local their_z           # their Z
87
+    local diff_x='='        # difference in X: '=' for equal, 'o' for different
88
+    local diff_y='='        #  ^^ ...... in Y
89
+    local diff_z='='        #  ^^ ...... in Z
90
+    read -r their_x their_y their_z <<<"$(__parse_ver_xyz "$their_ver")" || die
91
+    read -r our_x   our_y   our_z   <<<"$(__parse_ver_xyz "$MKIT_VERSION")" || die
92
+    test "$their_x" -eq "$our_x" || diff_x=o
93
+    test "$their_y" -eq "$our_y" || diff_y=o
94
+    test "$their_z" -eq "$our_z" || diff_z=o
95
+    debug_var MKIT_VERSION our_x our_y our_z their_ver their_x their_y their_z diff_x diff_y diff_z
96
+    case $our_x.$our_y:$diff_x.$diff_y.$diff_z in
97
+        *.*:=.=.=)  return 0 ;;
98
+        *.*:o.?.?)  return 1 ;;
99
+        0.0:=.=.o)  __compver_hint_async ;;
100
+        0.*:=.=.?)  __compver_hint_async ;;
101
+        0.*:=.o.?)  return 1 ;;
102
+        *.*:=.=.o)  __compver_hint_async ;;
103
+        *)          warn "MKit bug while comparing versions!" \
104
+                         " .. MKit could not handle version difference; here's dump:" \
105
+                         " ...... MKIT_VERSION=$MKIT_VERSION" \
106
+                         " ...... their_ver=$their_ver" \
107
+                         " ...... our_x=$our_x our_y=$our_y our_z=$our_z" \
108
+                         " ...... their_x=$their_x their_y=$their_y their_z=$their_z" \
109
+                         " ...... diff_x=$diff_x diff_y=$diff_y diff_z=$diff_z"
110
+                    return 1 ;;
111
+    esac
112
+}
113
+
114
+__parse_ver_xyz() {
115
+    #
116
+    # Print space-separated X, Y and Z from version $1
117
+    #
118
+    local ver=$1
119
+    local xyz
120
+    xyz=$(grep -oE '^[0-9]+[.][0-9]+[.][0-9]+' <<<"$ver")
121
+    test -n "$xyz" || {
122
+        warn "could not parse version: $ver"
102 123
     }
103
-    return 0
124
+    echo "${xyz//./ }"
125
+}
126
+
127
+__compver_hint_async() {
128
+    #
129
+    # Warn about the version being async
130
+    #
131
+    # Versions where later fragments are different are considered
132
+    # compatible, but when MKit is updated, the best practice is
133
+    # to consult the changelog and see if fixes or new features
134
+    # enable optimizing or removing workarounds from the mkit.ini
135
+    # file.
136
+    #
137
+    # For that reason we will inform the maintainer about this
138
+    # fact.
139
+    #
140
+    # Because in typical MKit usage the MKit itself is embedded
141
+    # in the same git repository as the file, it should be easy
142
+    # to maintain the synchronicity.
143
+    #
144
+    warn "hint: mkit.ini version is out of sync: MKit: $MKIT_VERSION, mkit.ini: $their_ver" \
145
+         "hint:  .. To hide this hint, consider updating mkit.ini based" \
146
+         "hint:  .. on changes between these versions and update version" \
147
+         "hint:  .. directive (\`#mkit version=..\`) to match MKit."
104 148
 }
105 149
 
106 150
 __chkiniversion() {
@@ -116,10 +160,10 @@ __chkiniversion() {
116 160
         {
117 161
             head -3 "$MKIT_INI"
118 162
             tail -3 "$MKIT_INI"
119
-        } | grep -m 1 -E '^# *mkit +version *= *v?[0-9]+\.[0-9]+\.[0-9]+'
163
+        } | grep -m 1 -E '^#mkit +version *= *v?[0-9]+\.[0-9]+\.[0-9]+'
120 164
     )
121 165
     test -n "$ver_line" \
122
-     || die "version mark ('#mkit version=x.y.z') not found in: $MKIT_INI"
166
+     || die "version directive ('#mkit version=x.y.z') not found in: $MKIT_INI"
123 167
     their_ver="$(tr -d '[:blank:]v' <<<"${ver_line##*=}")"
124 168
     __compver "$their_ver" \
125 169
      || die "bad mkit.ini version: $their_ver does not match $MKIT_VERSION"
@@ -134,29 +178,14 @@ mkit_init() {
134 178
     $MKIT_DRY && MKIT_DEBUG=true
135 179
     #shellcheck disable=SC2034
136 180
     MKIT_PROJ_PKGNAME=$(ini 1value "project:pkgname")
181
+    test -n "$MKIT_PROJ_PKGNAME" || die "failed to determine pkgname: project:pkgname in $MKIT_INI"
137 182
     test -f "$MKIT_INI" || die "cannot find mkit.ini: $MKIT_INI"
138 183
     __chkiniversion
139 184
 }
140 185
 
141
-route() {
142
-    #
143
-    # Call correct function based on $1
144
-    #
145
-    if __valid_targets | grep -qwx "^$1"; then
146
-        "$1"
147
-    else
148
-        {
149
-            echo "usage: $(basename "$0") TARGET"
150
-            echo
151
-            echo "valid targets:"
152
-            __valid_targets | sed 's/^/    /'
153
-        } >&2
154
-    fi
155
-}
156
-
157 186
 warn() {
158 187
     #
159 188
     # Print warning message
160 189
     #
161
-    echo "$@" >&2
190
+    printf '%s\n' "$@" >&2
162 191
 }

+ 195
- 0
utils/mkit/include/plugin.sh Vedi File

@@ -0,0 +1,195 @@
1
+#!/bin/bash
2
+# MKit - simple install helper
3
+# See LICENSE file for copyright and license details.
4
+
5
+mkit_import ini
6
+mkit_import util
7
+
8
+plugin__lspaths() {
9
+    #
10
+    # List existing paths from $MKIT_PLUGINPATH
11
+    #
12
+    local path
13
+    debug_var MKIT_PLUGINPATH
14
+    tr ':' '\n' <<<"$MKIT_PLUGINPATH" \
15
+      | while read -r path; do
16
+            debug_var path
17
+            test -d "$path" || continue
18
+            echo "$path"
19
+        done
20
+}
21
+
22
+plugin__ls() {
23
+    plugin__lspaths \
24
+      | while read -r path; do
25
+            debug_var path
26
+            find "$path" -mindepth 1 -maxdepth 1 -printf '%P\n'
27
+        done
28
+}
29
+
30
+plugin__option_bool() {
31
+    #
32
+    # Safely load boolean option $1
33
+    #
34
+    local name=$1
35
+    __plugin__option_safeload \
36
+        "$name" \
37
+        "util__isa_bool" \
38
+        "not a boolean (true|false)"
39
+}
40
+
41
+plugin__option_enum() {
42
+    #
43
+    # Safely load enum option $1, allowing values $2..
44
+    #
45
+    __plugin__check_call || return 5
46
+    local name=$1; shift
47
+    local allowed_values=("$@")
48
+    local value
49
+    local desc_allowed
50
+    value=$(plugin__option_single "$name")
51
+    debug_var name value
52
+    test -n "$value" || return 1
53
+    util__isa_enum "$value" "${allowed_values[@]}" || {
54
+        desc_allowed=$(util__join "|" "${allowed_values[@]}")
55
+        warn "not one of supported values ($desc_allowed): $name = $value"
56
+        return 3
57
+    }
58
+    echo "$value"
59
+}
60
+
61
+plugin__option_multi() {
62
+    #
63
+    # Print multi-line plugin option $1
64
+    #
65
+    __plugin__check_call || return 5
66
+    local name=$1
67
+    ini values "options:$_MKIT__PLUGIN__NAME:$name" \
68
+      | util__gate_printable
69
+}
70
+
71
+plugin__option_single() {
72
+    #
73
+    # Print single-line plugin option $1
74
+    #
75
+    __plugin__check_call || return 5
76
+    local name=$1
77
+    ini 1value "options:$_MKIT__PLUGIN__NAME:$name" \
78
+      | grep .
79
+}
80
+
81
+plugin__option_int() {
82
+    #
83
+    # Safely load boolean option $1
84
+    #
85
+    local name=$1
86
+    __plugin__option_safeload \
87
+        "$name" \
88
+        "util__isa_int" \
89
+        "not an integer"
90
+}
91
+
92
+plugin__option_name() {
93
+    #
94
+    # Safely load boolean option $1
95
+    #
96
+    local name=$1
97
+    __plugin__option_safeload \
98
+        "$name" \
99
+        "util__isa_name" \
100
+        "not a valid name (alphanumeric, starting with letter)"
101
+}
102
+
103
+plugin__option_posint() {
104
+    #
105
+    # Safely load boolean option $1
106
+    #
107
+    local name=$1
108
+    __plugin__option_safeload \
109
+        "$name" \
110
+        "util__isa_posint" \
111
+        "not a positive integer"
112
+}
113
+
114
+plugin__path() {
115
+    #
116
+    # Print path to plugin file $1
117
+    #
118
+    local name=$1
119
+    local binpath
120
+    plugin__lspaths \
121
+      | while read -r path; do
122
+            binpath="$path/$name"
123
+            debug_var binpath
124
+            test -x "$binpath" || continue
125
+            echo "$binpath"
126
+        done
127
+}
128
+
129
+plugin__handle() {
130
+    #
131
+    # Run method $2 of plugin $1
132
+    #
133
+    local _MKIT__PLUGIN__NAME=$1
134
+    local method=$2
135
+    local require=true
136
+    local path
137
+    local fn
138
+    test "${method:0:1}" == "." && {
139
+        require=false
140
+        method=${method:1}
141
+    }
142
+    fn=${_MKIT__PLUGIN__NAME}__${method}
143
+    path=$(plugin__path "$_MKIT__PLUGIN__NAME")
144
+    test -n "$path" || die "plugin not found: $_MKIT__PLUGIN__NAME"
145
+    bash -n "$path" || die "syntax errors in plugin file: $path"
146
+    #shellcheck disable=SC1090
147
+    . "$path" || die "error importing plugin: $_MKIT__PLUGIN__NAME at $path"
148
+    type -t "$fn" | grep -qw function || {
149
+        $require && die "missing handler: $fn() in plugin $_MKIT__PLUGIN__NAME at $path"
150
+        debug "SILENTLY SKIPPING UNDEFINED HANDLER: $fn() in $_MKIT__PLUGIN__NAME at $path"
151
+        return 0
152
+    }
153
+    debug "RUNNING HANDLER"
154
+    debug_var fn
155
+    "$fn"
156
+}
157
+
158
+plugin__isvalid() {
159
+    #
160
+    # True if plugin $1 is valid
161
+    #
162
+    local plugin=$1
163
+    plugin__ls | grep -qwxe "$plugin"
164
+}
165
+
166
+__plugin__check_call() {
167
+    #
168
+    # Check that this call is valid
169
+    #
170
+    local func=${FUNCNAME[1]}
171
+    local caller=${FUNCNAME[2]}
172
+    test -n "$_MKIT__PLUGIN__NAME" && return 0
173
+    warn "illegal call: $func() from $caller() _MKIT__PLUGIN__NAME=$_MKIT__PLUGIN__NAME" \
174
+         "  hint: are we inside plugin?"
175
+    return 3
176
+}
177
+
178
+__plugin__option_safeload() {
179
+    #
180
+    # Safely load option $1 with validator $2 and error message $3
181
+    #
182
+    __plugin__check_call || return 5
183
+    local name=$1
184
+    local validator=$2
185
+    local msg=$3
186
+    local value
187
+    value=$(plugin__option_single "$name")
188
+    debug_var name value
189
+    test -n "$value" || return 1
190
+    "$validator" "$value" || {
191
+        warn "$msg: $name=$value"
192
+        return 3
193
+    }
194
+    echo "$value"
195
+}

+ 52
- 27
utils/mkit/include/release.sh Vedi File

@@ -5,7 +5,7 @@
5 5
 mkit_import ini
6 6
 mkit_import facts
7 7
 
8
-__bump_version() {
8
+__release__bump_version() {
9 9
     #
10 10
     # Bump version on stdin by level $1 (x, y or z)
11 11
     #
@@ -30,7 +30,7 @@ __bump_version() {
30 30
     echo "$new"
31 31
 }
32 32
 
33
-__relck() {
33
+__release__check() {
34 34
     #
35 35
     # Die if blocking condition $1 is detected
36 36
     #
@@ -63,12 +63,15 @@ __relck() {
63 63
              || die "last change must be version bump in mkit.ini"
64 64
             ;;
65 65
         no_wip)
66
-            git_fact reldiff \
67
-              | grep '^....... WIP ' \
66
+            git_fact reldiff_brief \
67
+              | grep -iE \
68
+                    -e '^[^ ]+ WIP ' \
69
+                    -e '^[^ ]+ WIP$'  \
70
+                    -e '^[^ ]+ WIP:'  \
68 71
              && die "WIP commit since $(git_fact latest_tag)"
69 72
             ;;
70 73
         ini_version)
71
-            oracle=$(git_fact latest_version | __bump_version "$rlevel")
74
+            oracle=$(git_fact latest_version | __release__bump_version "$rlevel")
72 75
             ini 1value project:version  \
73 76
               | grep -qFxe "$oracle" \
74 77
              || die "new version not in mkit.ini: $oracle"
@@ -94,15 +97,15 @@ __release() {
94 97
     local relsrc        # release source branch (if any)
95 98
     local reldst        # release destination branch (if any)
96 99
 
97
-    __relck git_present
98
-    __relck at_relsrc
99
-    __relck not_dirty
100
-    __relck tags_ok
101
-    __relck vbump_hot
102
-    __relck no_wip
103
-    __relck ini_version
100
+    __release__check git_present
101
+    __release__check at_relsrc
102
+    __release__check not_dirty
103
+    __release__check tags_ok
104
+    __release__check vbump_hot
105
+    __release__check no_wip
106
+    __release__check ini_version
104 107
 
105
-    newtag=$(git_fact latest_version | __bump_version "$rlevel" | git_ver2tag )
108
+    newtag=$(git_fact latest_version | __release__bump_version "$rlevel" | git_ver2tag )
106 109
     set -e
107 110
     debug_var newtag
108 111
     $MKIT_DRY && return
@@ -121,7 +124,7 @@ __release_msg() {
121 124
     # Generate message for annotated tag
122 125
     #
123 126
     # The last commit before issuing a release must be "Bump version" commit
124
-    # suggested by _vbump_gitmsg() and  manually edited by user.  Since the
127
+    # suggested by __release__vbump_gitmsg() and  manually edited by user.  Since the
125 128
     # commit contains changelog, this function just uses the message body.
126 129
     #
127 130
     # The sort message (first line) is replaced with a nicer one (with project
@@ -133,7 +136,29 @@ __release_msg() {
133 136
       | tail -n +3
134 137
 }
135 138
 
136
-__vbump() {
139
+__release__update_ini() {
140
+    #
141
+    # Change project:version in mkit.ini at path $2 to value $1
142
+    #
143
+    local version=$1    # new version
144
+    local inifile=$2    # mkit.ini path
145
+    local tmp           # mkit.ini cache
146
+    tmp=$(mktemp -t mkit.__release__update_ini.XXXXXXXX)
147
+    <"$inifile" perl -e '
148
+        my $hit = 0;
149
+        my $done = 0;
150
+        foreach (<STDIN>) {
151
+            if      ($done) { print; next; }
152
+            elsif   (m/\[project\]/) { $hit++; print; next; }
153
+            elsif   (m/\[/) { $hit = 0; print; next; }
154
+            elsif   ($hit) { s/\bversion\b( *)=( *).*/version$1=$2$ARGV[0]/ and $done++; print; }
155
+            else { print; next; }
156
+        }
157
+    ' "$version" >"$tmp" || die "failed to update version in mkit.ini"
158
+    mv "$tmp" "$inifile"
159
+}
160
+
161
+__release__vbump() {
137 162
     #
138 163
     # Do version bump at level $1
139 164
     #
@@ -143,23 +168,23 @@ __vbump() {
143 168
     local rlevel=$1     # bump level (x, y or z)
144 169
     local nextver       # version after the bump
145 170
     local cache         # cache for the message
146
-    __relck git_present
147
-    __relck at_relsrc
148
-    __relck not_dirty
149
-    nextver=$(ini 1value project:version | __bump_version "$rlevel")
171
+    __release__check git_present
172
+    __release__check at_relsrc
173
+    __release__check not_dirty
174
+    nextver=$(ini 1value project:version | __release__bump_version "$rlevel")
150 175
     debug_var nextver
151 176
     $MKIT_DRY && return
152
-    update_version "$nextver" mkit.ini \
177
+    __release__update_ini "$nextver" mkit.ini \
153 178
       || die "failed to update version in mkit.ini"
154 179
     git add mkit.ini \
155 180
       || die "failed to add mkit.ini to the index"
156
-    cache=$(mktemp -t "mkit._vbump_gitmsg.XXXXXXXX")
157
-    _vbump_gitmsg "$nextver" > "$cache"
181
+    cache=$(mktemp -t "mkit.__release__vbump_gitmsg.XXXXXXXX")
182
+    __release__vbump_gitmsg "$nextver" > "$cache"
158 183
     git commit -e -F "$cache"   # note: reading from stdin will break vim
159 184
     rm "$cache"
160 185
 }
161 186
 
162
-_vbump_gitmsg() {
187
+__release__vbump_gitmsg() {
163 188
     #
164 189
     # Compose git message template for 'Bump version' commit
165 190
     #
@@ -208,26 +233,26 @@ vbump() {
208 233
     #
209 234
     # Perform version bump on Z level
210 235
     #
211
-    __vbump z
236
+    __release__vbump z
212 237
 }
213 238
 
214 239
 vbump_x() {
215 240
     #
216 241
     # Perform version bump on X level
217 242
     #
218
-    __vbump x
243
+    __release__vbump x
219 244
 }
220 245
 
221 246
 vbump_y() {
222 247
     #
223 248
     # Perform version bump on Y level
224 249
     #
225
-    __vbump y
250
+    __release__vbump y
226 251
 }
227 252
 
228 253
 vbump_z() {
229 254
     #
230 255
     # Perform version bump on Z level
231 256
     #
232
-    __vbump z
257
+    __release__vbump z
233 258
 }

+ 91
- 0
utils/mkit/include/target.sh Vedi File

@@ -0,0 +1,91 @@
1
+#!/bin/bash
2
+# MKit - simple install helper
3
+# See LICENSE file for copyright and license details.
4
+
5
+mkit_import ini
6
+mkit_import plugin
7
+
8
+target__ls() {
9
+    #
10
+    # List valid routes
11
+    #
12
+    target__map | cut -d' ' -f1
13
+}
14
+
15
+target__map() {
16
+    #
17
+    # List valid routes and corresponding module:functions
18
+    #
19
+    #shellcheck disable=SC2291
20
+    {
21
+        echo _mkit_builddata        build:_mkit_builddata
22
+        echo _mkit_inidata          build:_mkit_inidata
23
+        echo _mkit_metadata         build:_mkit_metadata
24
+        echo _mkit_show_builddata   build:_mkit_show_builddata
25
+        echo _mkit_show_metadata    build:_mkit_show_metadata
26
+        echo build                  build:build
27
+        echo clean                  build:clean
28
+        echo dist                   build:dist
29
+        echo install                deploy:install
30
+        echo release                release:release
31
+        echo release_x              release:release_x
32
+        echo release_y              release:release_y
33
+        echo release_z              release:release_z
34
+        echo uninstall              deploy:uninstall
35
+        echo vbump                  release:vbump
36
+        echo vbump_x                release:vbump_x
37
+        echo vbump_y                release:vbump_y
38
+        echo vbump_z                release:vbump_z
39
+    }
40
+}
41
+
42
+target__isvalid() {
43
+    #
44
+    # True if target $1 is valid
45
+    #
46
+    local target=$1
47
+    target__map | grep -qwe "^$target"
48
+}
49
+
50
+target__route() {
51
+    #
52
+    # Call correct function based on $1
53
+    #
54
+    local target=$1
55
+    local es
56
+    if target__isvalid "$target"; then
57
+        target__run "$target"; es=$?
58
+    elif plugin__isvalid "$target"; then
59
+        plugin__handle "$target" main; es=$?
60
+    else
61
+        {
62
+            echo "usage: $(basename "$0") TARGET"
63
+            echo
64
+            echo "valid targets (built-in):"
65
+            target__ls | sed 's/^/    /'
66
+            echo
67
+            echo "valid targets (from plugins):"
68
+            plugin__ls | sed 's/^/    /'
69
+        } >&2
70
+        es=2
71
+    fi
72
+    return "$es"
73
+}
74
+
75
+target__run() {
76
+    #
77
+    # Run target $1
78
+    #
79
+    local target=$1
80
+    local module
81
+    local fn
82
+    read -r module fn <<<"$(
83
+        target__map \
84
+          | tr : ' ' \
85
+          | grep -we "^$target" \
86
+          | cut -d' ' -f2-
87
+    )"
88
+    debug_var target module fn
89
+    mkit_import "$module"
90
+    "$fn"
91
+}

+ 322
- 0
utils/mkit/include/util.sh Vedi File

@@ -0,0 +1,322 @@
1
+#!/bin/bash
2
+
3
+util__gate_printable() {
4
+    #
5
+    # True if stdin had non-whitespace values
6
+    #
7
+    local value
8
+    value=$(cat)
9
+    grep -q '[[:print:]]' <<<"$value" || return 1
10
+    echo "$value"
11
+}
12
+
13
+util__isa_bool() {
14
+    #
15
+    # True if $1 is a boolean value
16
+    #
17
+    test "$1" == false && return 0
18
+    test "$1" == true && return 0
19
+    return 1
20
+}
21
+
22
+util__isa_enum() {
23
+    #
24
+    # True if $1 is one of $2..
25
+    #
26
+    local value=$1; shift
27
+    local alloweds=("$@")
28
+    local allowed
29
+    for allowed in "${alloweds[@]}"; do
30
+        test "$value" == "$allowed" && return 0
31
+    done
32
+    return 1
33
+}
34
+
35
+util__isa_int() {
36
+    #
37
+    # True if $1 is an integer
38
+    #
39
+    # Note that $1 does not have to be decimal; in POSIX shells,
40
+    # eg. 0xf1 is a valid hexadecimal integer and 0755 is a valid
41
+    # octal integer.
42
+    #
43
+    {
44
+        test -z "$1"    && return 1
45
+        test "$1" -ge 0 && return 0
46
+        return 1
47
+    } 2>/dev/null
48
+}
49
+
50
+util__isa_name() {
51
+    #
52
+    # True if $1 is a name in most languages
53
+    #
54
+    echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*'
55
+}
56
+
57
+util__isa_posint() {
58
+    #
59
+    # True if $1 is a positive integer
60
+    #
61
+    {
62
+        test -z "$1"    && return 1
63
+        test "$1" -ge 0 && return 0
64
+        return 1
65
+    } 2>/dev/null
66
+}
67
+
68
+util__join() {
69
+    #
70
+    # Use $1 to join items $2..
71
+    #
72
+    local dlm=$1; shift
73
+    local items=("$@")
74
+    local item
75
+    local head=true
76
+    for item in "${items[@]}"; do
77
+        $head || echo -n "$dlm"
78
+        $head && head=false
79
+        echo -n "$item"
80
+    done
81
+}
82
+
83
+util__loadarr() {
84
+    #
85
+    # Save output lines from command $2.. to array named $1
86
+    #
87
+    # Usage:
88
+    #     util__loadarr ARRAY CMD [ARG]..
89
+    #
90
+    # Unlike `mapfile -t <<<"$(command)", this produces zero
91
+    # items if command output was empty.
92
+    #
93
+    local __util__arr_fromcmd__oarr=$1; shift
94
+    local __util__arr_fromcmd__cmd=("$@")
95
+    local __util__arr_fromcmd__tmp
96
+    local __util__arr_fromcmd__es
97
+    __util__arr_val_oarr "$__util__arr_fromcmd__oarr" || return 2
98
+    __util__arr_fromcmd__tmp=$(mktemp /tmp/util__loadarr.__util__arr_fromcmd__tmp.XXXXX)
99
+    "${__util__arr_fromcmd__cmd[@]}" > "$__util__arr_fromcmd__tmp"; __util__arr_fromcmd__es=$?
100
+    mapfile -t "$__util__arr_fromcmd__oarr" <"$__util__arr_fromcmd__tmp"
101
+    rm "$__util__arr_fromcmd__tmp"
102
+    return $__util__arr_fromcmd__es
103
+}
104
+
105
+util__loadenv() {
106
+    #
107
+    # Safely load value from envvar $1
108
+    #
109
+    local name=$1
110
+    local value
111
+    value=${!name}
112
+    test -n "$value" || return 1
113
+    echo "$value"
114
+}
115
+
116
+util__loadenv_bool() {
117
+    #
118
+    # Safely load integer from envvar $1
119
+    #
120
+    local name=$1
121
+    __util__safe_loadenv \
122
+        "$name" \
123
+        util__isa_bool \
124
+        "not a boolean (true|false): $name=$value"
125
+}
126
+
127
+util__loadenv_enum() {
128
+    #
129
+    # Safely load enum from envvar $1, allowing values $2..
130
+    #
131
+    local name=$1; shift
132
+    local allowed_values=("$@")
133
+    local value
134
+    local desc_allowed
135
+    value=${!name}
136
+    test -n "$value" || return 1
137
+    util__isa_enum "$value" "${allowed_values[@]}" || {
138
+        desc_allowed=$(util__join "|" "${allowed_values[@]}")
139
+        warn "not one of supported values ($desc_allowed): $name = $value"
140
+        return 3
141
+    }
142
+    echo "$value"
143
+}
144
+
145
+util__loadenv_int() {
146
+    #
147
+    # Safely load integer from envvar $1
148
+    #
149
+    local name=$1
150
+    __util__safe_loadenv \
151
+        "$name" \
152
+        util__isa_int \
153
+        "not an integer"
154
+}
155
+
156
+util__loadenv_name() {
157
+    #
158
+    # Safely load positive integer from envvar $1
159
+    #
160
+    local name=$1
161
+    __util__safe_loadenv \
162
+        "$name" \
163
+        util__isa_name \
164
+        "not a valid name (alphanumeric, starting with letter)"
165
+}
166
+
167
+util__loadenv_posint() {
168
+    #
169
+    # Safely load positive integer from envvar $1
170
+    #
171
+    local name=$1
172
+    __util__safe_loadenv \
173
+        "$name" \
174
+        util__isa_posint \
175
+        "not a positive integer"
176
+}
177
+
178
+util__sort_by_length() {
179
+    #
180
+    # Sort items on stdin by length
181
+    #
182
+    while IFS= read -r item; do
183
+        echo "${#item} $item"
184
+    done \
185
+      | sort -n \
186
+      | cut -d' ' -f2-
187
+}
188
+
189
+util__warnbox() {
190
+    #
191
+    # Emit warning $2 box-decorated with char $1, follow by lines $3..
192
+    #
193
+    local char=$1; shift
194
+    local subj=$1; shift
195
+    local body=("$@")
196
+    case "${#body[*]}" in
197
+        0)  __util__warnbox1 "$char" "$subj" ;;
198
+        *)  __util__warnbox_sub "$char" "$subj" "${body[@]}" ;;
199
+    esac
200
+}
201
+
202
+__util__warnbox1() {
203
+    #
204
+    # Emit warning $2 box-decorated with char $1
205
+    #
206
+    local char=$1
207
+    local msg=$2
208
+    local line
209
+    local midline="$char$char $msg $char$char"
210
+    line=$(__util__make_hr "$char" "$midline")
211
+    warn "$line" \
212
+         "$midline" \
213
+         "$line"
214
+}
215
+
216
+__util__warnbox_sub() {
217
+    #
218
+    # Emit warning $2 box-decorated with char $1, follow by lines $3..
219
+    #
220
+    local char=$1; shift        # decoration character
221
+    local subj=$1; shift        # local of the message subject
222
+    local body=("$@")           # lines of the message body
223
+    local longest               # longest line from subject and body
224
+    local width                 # length of ^^
225
+    local hr                    # horizontal ruler
226
+    local vr="$char$char"       # vertical ruler point
227
+    local padded_body           # body lines; padded
228
+    local padded_subj           # body line; padded
229
+    local line                  # each of ^^^
230
+    local out_lines=()          # final lines
231
+    longest=$(__util__longest "$subj" "${body[@]}")
232
+    width=${#longest}
233
+    padded_subj=$(__util__rpad "${width}" "$subj")
234
+    util__loadarr padded_body __util__rpad "$width" "${body[@]}"
235
+    debug_var longest width padded_subj padded_body
236
+    hr=$(__util__make_hr "$char" "$vr $longest $vr")
237
+    out_lines+=(
238
+        "$hr"
239
+        "$vr $padded_subj $vr"
240
+        "$hr"
241
+    )
242
+    for line in "${padded_body[@]}"; do
243
+        out_lines+=("$vr $line $vr")
244
+    done
245
+    out_lines+=("$hr")
246
+    warn "${out_lines[@]}"
247
+}
248
+
249
+__util__longest() {
250
+    #
251
+    # Choose longest of $@
252
+    #
253
+    printf '%s\n' "$@" \
254
+      | util__sort_by_length \
255
+      | tail -n 1
256
+}
257
+
258
+__util__make_hr() {
259
+    #
260
+    # Repeat char $1 to the length of $2
261
+    #
262
+    local char=$1
263
+    local stuff=$2
264
+    local length=${#stuff}
265
+    __util__repchar "$char" "$length"
266
+}
267
+
268
+__util__repchar() {
269
+    #
270
+    # Repeat char $1 $2 times
271
+    #
272
+    local char=$1
273
+    local times=$2
274
+    local togo=$times
275
+    while test "$togo" -gt 0; do
276
+        echo -n "$char"
277
+        ((togo--))
278
+    done
279
+    echo
280
+}
281
+
282
+__util__rpad() {
283
+    #
284
+    # Right-pad all of $2.. to length $1 with space
285
+    #
286
+    local wid=$1; shift
287
+    local rest=("$@")
288
+    printf "%-${wid}s"'\n' "${rest[@]}"
289
+}
290
+
291
+__util__safe_loadenv() {
292
+    #
293
+    # Safely load envvar $1 with validator $2 and error message $3
294
+    #
295
+    local name=$1
296
+    local validator=$2
297
+    local msg=$3
298
+    local value
299
+    value=${!name}
300
+    debug_var name value
301
+    test -n "$value" || return 1
302
+    "$validator" "$value" || {
303
+        warn "$msg: $name=$value"
304
+        return 3
305
+    }
306
+    echo "$value"
307
+}
308
+
309
+__util__isa_name() {
310
+    #
311
+    # True if $1 is a name in most languages
312
+    #
313
+    echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*'
314
+}
315
+
316
+__util__arr_val_oarr() {
317
+    #
318
+    # Validate output array named $1
319
+    #
320
+    local name=$1
321
+    __util__isa_name "$name" || { warn "invalid array name: $name"; return 2; }
322
+}

+ 72
- 1
utils/mkit/include/vars.sh Vedi File

@@ -4,6 +4,20 @@
4 4
 # See LICENSE file for copyright and license details.
5 5
 
6 6
 
7
+#
8
+# Add 'async' tag to build meta-data (true|false)
9
+#
10
+# If 'true' and during the version computation git branch is behind
11
+# or ahead its remote counterpart, build meta-data (part after '+'
12
+# sign) will contain word 'async'.
13
+#
14
+# This is to warn testers and developers when build was done after
15
+# automatic rebase done by CI system.
16
+#
17
+# Defaults to 'true'.
18
+#
19
+MKIT_ASYNC=${MKIT_ASYNC:-true}
20
+
7 21
 #
8 22
 # Bump size (for vbump_? and release_?)
9 23
 #
@@ -26,6 +40,16 @@ MKIT_DEFAULT_MODE="644"
26 40
 #
27 41
 MKIT_DRY=${MKIT_DRY:-false}
28 42
 
43
+#
44
+# Is MKit running in CI (true|false)
45
+#
46
+# If MKit is running in CI, it's recommended to set this to 'true'
47
+# to enable one or more useful warnings.
48
+#
49
+# Defaults to 'false'.
50
+#
51
+MKIT_IN_CI=${MKIT_IN_CI:-false}
52
+
29 53
 #
30 54
 # Path to mkit.ini
31 55
 #
@@ -48,6 +72,14 @@ MKIT_INI_EXPAND=2
48 72
 #
49 73
 MKIT_LOCAL=${MKIT_LOCAL:-.mkit}
50 74
 
75
+#
76
+# Paths to MKit plugins
77
+#
78
+# Colon-separated list of paths where MKit will recognize
79
+# plug-ins.
80
+#
81
+MKIT_PLUGINPATH=${MKIT_PLUGINPATH:-$MKIT_DIR/plugins:$MKIT_LOCAL/plugins:$MKIT_DIR-plugins}
82
+
51 83
 #
52 84
 # Package name
53 85
 #
@@ -83,7 +115,46 @@ MKIT_PROJ_PKGNAME=""
83 115
 #
84 116
 MKIT_TSTAMP=${MKIT_TSTAMP:-ctime}
85 117
 
118
+#
119
+# Upstream hash to add to build meta-data
120
+#
121
+# If set to non-empty string, it must represent a short-hash of
122
+# upstream commit on which local branch was automatically rebased
123
+# before build.
124
+#
125
+# If CI system automatically rebases branches, the resulting short-hash
126
+# (appearing with `g` prefix) will be meaningless, and might be
127
+# misleading -- testers and developers might see the build as coming
128
+# from an unknown source.
129
+#
130
+# In that case, it's strongly recommended for CI to set this to the
131
+# original pushed short-hash before the rebase.  Resulting meta-data
132
+# will contain both hashes, allowing users to pair test results with
133
+# original push.  The appearance of the extra hash will also warn
134
+# about the fact that the rebase took place.
135
+#
136
+# For example, if developer pushed branch 'foo' that pointed to
137
+# `5faf551`, and CI system rebased it, altering the HEAD to
138
+# `bb4baa4`, by default the version would look like:
139
+#
140
+#     0.0.1+t202103011224.foo.gbb4baa4
141
+#
142
+# This would be meaningless for testing, as the `bb4baa4` hash is
143
+# completely temporary and unknonwn.
144
+#
145
+# However, a conformant CI should set `MKIT_UPSTREAM=5faf551`, which
146
+# will result in version like:
147
+#
148
+#     0.0.1+t202103011224.foo.gbb4baa4.u5faf551
149
+#
150
+# which still has technically correct git hash after `g`, but contains
151
+# the original hash as pushed after `u`.
152
+#
153
+# Defaults to empty value.
154
+#
155
+MKIT_UPSTREAM=${MKIT_UPSTREAM:-}
156
+
86 157
 #
87 158
 # This MKit version
88 159
 #
89
-MKIT_VERSION=0.0.40
160
+MKIT_VERSION=0.1.3

+ 0
- 34
utils/mkit/make Vedi File

@@ -1,34 +0,0 @@
1
-#!/bin/bash
2
-#shellcheck disable=SC2034
3
-# mkit - simple install helper
4
-# See LICENSE file for copyright and license details.
5
-
6
-init_core() {
7
-    #
8
-    # Load core module (or die)
9
-    #
10
-    #shellcheck disable=SC1090
11
-    . "$MKIT_DIR/include/mkit.sh" \
12
-     && . "$MKIT_DIR/include/vars.sh" \
13
-     && return 0
14
-    echo "failed to load core; check if MKIT_DIR is set properly: $MKIT_DIR" >&2
15
-    exit 9
16
-}
17
-
18
-#
19
-# Path to MKit dir (where 'include' is)
20
-#
21
-MKIT_DIR=${MKIT_DIR:-$(dirname "$0")}
22
-
23
-
24
-init_core
25
-
26
-case "$1" in
27
-    -V|--version-semver)    echo "$MKIT_VERSION"; exit 0 ;;
28
-    --version)              echo "Mkit (Simple Makefile target helper) $MKIT_VERSION"
29
-                            exit 0 ;;
30
-esac
31
-
32
-mkit_init
33
-
34
-route "$@"

+ 94
- 0
utils/mkit/mkit Vedi File

@@ -0,0 +1,94 @@
1
+#!/bin/bash
2
+#shellcheck disable=SC2034
3
+# mkit - simple install helper
4
+# See LICENSE file for copyright and license details.
5
+
6
+usage() {
7
+    print_help >&2
8
+    test $# -gt 0 && {
9
+        printf >&2 '%s\n' "" "$@"
10
+    }
11
+    exit 2
12
+}
13
+
14
+print_help() {
15
+    local self=${0##*/}
16
+    local lines=(
17
+        "usage:"
18
+        "  $self TARGET"
19
+        "  $self -i|--ini-1value PATH"
20
+        "  $self -I|--ini-values PATH"
21
+        "  $self --list-plugins"
22
+        "  $self --list-targets"
23
+        "  $self -V|--version-semver"
24
+        "  $self --version"
25
+        ""
26
+        "commands:"
27
+        "   TARGET          build target TARGET"
28
+        "   -i PATH         print single-line value at INI path PATH"
29
+        "                   (ignore all but last instance of this"
30
+        "                   value in the INI)"
31
+        "   -I PATH         like -i, but list all instances, one per line"
32
+        "                   (multi-line values)"
33
+        "   --list-plugins  print list of found plugins and exit"
34
+        "   --list-targets  print list of valid targets and exit"
35
+        "   -V|--version    print version (in SemVer format) and exit"
36
+        "   --help          print this help text and exit"
37
+        ""
38
+    )
39
+    printf '%s\n' "${lines[@]}"
40
+    echo "valid targets (built-in):"
41
+    target__ls | sed 's/^/    /'
42
+    echo
43
+    echo "valid targets (from plugins):"
44
+    plugin__ls | sed 's/^/    /'
45
+}
46
+
47
+
48
+init_core() {
49
+    #
50
+    # Load core module (or die)
51
+    #
52
+    #shellcheck disable=SC1090,SC1091
53
+    . "$MKIT_DIR/include/mkit.sh" \
54
+     && . "$MKIT_DIR/include/vars.sh" \
55
+     && return 0
56
+    echo "failed to load core; check if MKIT_DIR is set properly: $MKIT_DIR" >&2
57
+    exit 9
58
+}
59
+
60
+#
61
+# Path to MKit dir (where 'include' is)
62
+#
63
+MKIT_DIR=${MKIT_DIR:-$(dirname "$0")}
64
+
65
+just_ini() {
66
+    #
67
+    # Just do one mkit.ini operation $1
68
+    #
69
+    local op=$1
70
+    local key=$2
71
+    mkit_init || return $?
72
+    ini "$op" "$key"
73
+}
74
+
75
+main () {
76
+    init_core
77
+    case "$1" in
78
+        -V|--version)           echo "$MKIT_VERSION"; exit 0 ;;
79
+        -i|--ini-1value)        just_ini 1value "$2"; exit $? ;;
80
+        -I|--ini-values)        just_ini values "$2"; exit $? ;;
81
+        --ini-lskeys)           just_ini lskeys "$2"; exit $? ;;
82
+        --ini-lssect)           just_ini lssect "$2"; exit $? ;;
83
+        --ini-sec)              just_ini sec    "$2"; exit $? ;;
84
+        --list-targets)         mkit_init; target__ls; plugin__ls; exit 0 ;;
85
+        --list-plugins)         mkit_init; plugin__ls; exit 0 ;;
86
+        --help)                 print_help; exit 0 ;;
87
+        -*)                     usage "unknown argument: $1" ;;
88
+    esac
89
+    test "$#" -gt 0 || usage
90
+    mkit_init
91
+    target__route "$@"
92
+}
93
+
94
+main "$@"

+ 35
- 23
utils/mkit/mkit.mk Vedi File

@@ -3,54 +3,66 @@
3 3
 
4 4
 export MKIT_DIR
5 5
 
6
+plugins := $(shell "$(MKIT_DIR)"/mkit --list-plugins)
7
+
8
+
6 9
 all: build
7 10
 
8
-_mkit_data:
9
-	@"$(MKIT_DIR)"/make _mkit_data
11
+_mkit_builddata: _mkit_metadata
12
+	@"$(MKIT_DIR)"/mkit _mkit_builddata
10 13
 
11
-build:
12
-	@"$(MKIT_DIR)"/make build
14
+_mkit_inidata:
15
+	@"$(MKIT_DIR)"/mkit _mkit_inidata
13 16
 
14
-clean:
15
-	@"$(MKIT_DIR)"/make clean
17
+_mkit_metadata:
18
+	@"$(MKIT_DIR)"/mkit _mkit_metadata
19
+
20
+_mkit_show_metadata: _mkit_metadata
21
+	@"$(MKIT_DIR)"/mkit _mkit_show_metadata
16 22
 
17
-debstuff: dist
18
-	@"$(MKIT_DIR)"/make debstuff
23
+_mkit_show_builddata: _mkit_builddata
24
+	@"$(MKIT_DIR)"/mkit _mkit_show_builddata
19 25
 
20
-dist: clean
21
-	@"$(MKIT_DIR)"/make dist
26
+build: _mkit_builddata
27
+	@"$(MKIT_DIR)"/mkit build
22 28
 
23
-rpmstuff: dist
24
-	@"$(MKIT_DIR)"/make rpmstuff
29
+dist: _mkit_metadata
30
+	@"$(MKIT_DIR)"/mkit dist
31
+
32
+clean:
33
+	@"$(MKIT_DIR)"/mkit clean
25 34
 
26 35
 install: all
27
-	@"$(MKIT_DIR)"/make install
36
+	@"$(MKIT_DIR)"/mkit install
28 37
 
29 38
 release:
30
-	@"$(MKIT_DIR)"/make release
39
+	@"$(MKIT_DIR)"/mkit release
31 40
 
32 41
 release_x:
33
-	@"$(MKIT_DIR)"/make release_x
42
+	@"$(MKIT_DIR)"/mkit release_x
34 43
 
35 44
 release_y:
36
-	@"$(MKIT_DIR)"/make release_y
45
+	@"$(MKIT_DIR)"/mkit release_y
37 46
 
38 47
 release_z:
39
-	@"$(MKIT_DIR)"/make release_z
48
+	@"$(MKIT_DIR)"/mkit release_z
40 49
 
41 50
 uninstall:
42
-	@"$(MKIT_DIR)"/make uninstall
51
+	@"$(MKIT_DIR)"/mkit uninstall
43 52
 
44 53
 vbump:
45
-	@"$(MKIT_DIR)"/make vbump
54
+	@"$(MKIT_DIR)"/mkit vbump
46 55
 
47 56
 vbump_x:
48
-	@"$(MKIT_DIR)"/make vbump_x
57
+	@"$(MKIT_DIR)"/mkit vbump_x
49 58
 
50 59
 vbump_y:
51
-	@"$(MKIT_DIR)"/make vbump_y
60
+	@"$(MKIT_DIR)"/mkit vbump_y
52 61
 
53 62
 vbump_z:
54
-	@"$(MKIT_DIR)"/make vbump_z
63
+	@"$(MKIT_DIR)"/mkit vbump_z
64
+
65
+$(plugins): clean _mkit_metadata
66
+	@"$(MKIT_DIR)"/mkit $@
55 67
 
56
-.PHONY: all _mkit_data clean dist rpmstuff install uninstall release release_x release_y release_z vbump vbump_x vbump_y vbump_z
68
+.PHONY: all _mkit_builddata _mkit_inidata _mkit_metadata _mkit_show_builddata _mkit_show_metadata clean dist rpmstuff install uninstall release release_x release_y release_z vbump vbump_x vbump_y vbump_z $(plugins)

+ 39
- 0
utils/mkit/plugins/debstuff Vedi File

@@ -0,0 +1,39 @@
1
+#!/bin/bash
2
+# MKit - simple install helper
3
+# See LICENSE file for copyright and license details.
4
+
5
+mkit_import build
6
+mkit_import target
7
+
8
+debstuff__main() {
9
+    #
10
+    # Build Debian stuff (eamed tarball, debian dir)
11
+    #
12
+    local version       # package version
13
+    local debian_skel   # 'debian' folder skeleton
14
+    local dfsrc         # each source file from ^^
15
+    local dftgt         # each built packaging file
16
+    target__run dist || die
17
+    version=$(build__cached semver)
18
+
19
+    # tarball - we should already have by means of 'dist'
20
+    #
21
+    mv "${MKIT_PROJ_PKGNAME}-$version.tar.gz" \
22
+       "${MKIT_PROJ_PKGNAME}_$version.orig.tar.gz" \
23
+     || die "could not rename tarball"
24
+    build__record "${MKIT_PROJ_PKGNAME}_$version.orig.tar.gz"
25
+
26
+    # read content of each mandatory file from debian_skel
27
+    #
28
+    debian_skel=$(ini 1value dist:debstuff)
29
+    test -n "$debian_skel" || die "dist:debstuff not specified"
30
+    test -d "$debian_skel" || die "debian directory template found: $debian_skel"
31
+    mkdir -p debian/source
32
+    find "$debian_skel" -type f \
33
+      | while read -r dfsrc; do
34
+            dftgt="debian/${dfsrc#"$debian_skel"}"
35
+            mkdir -p "$(dirname "$dftgt")"
36
+            build__file "$dfsrc" "$dftgt" debstuff
37
+        done
38
+    build__recordr debian
39
+}

+ 127
- 0
utils/mkit/plugins/pystuff Vedi File

@@ -0,0 +1,127 @@
1
+#!/bin/bash
2
+# MKit - simple install helper
3
+# See LICENSE file for copyright and license details.
4
+
5
+mkit_import build
6
+mkit_import target
7
+
8
+pystuff__main() {
9
+    #
10
+    # Build setup.py and mkvenv.sh
11
+    #
12
+    local ModName           # Python module name
13
+    local ProjVersion       # MKit project version
14
+    local Skel              # setup.py skeleton
15
+    local MkvenvDir         # venv maker: target venv directory
16
+    local MkvenvScript      # venv maker: script name
17
+    local MkvenvPost        # venv maker: post commands
18
+    target__run build || die
19
+    Skel="$(ini 1value "dist:pystuff")"
20
+    MkvenvDir=$(__pystuff__load_mkvenv_dir)
21
+    MkvenvScript=$(__pystuff__load_mkvenv_script)
22
+    MkvenvPost=$(__pystuff__load_mkvenv_post)
23
+    ProjVersion=$(build__cached semver)
24
+    ModName=$(ini 1value macros:__VDK_PYLIB_MODNAME__)
25
+    build__fact "pystuff/mkvenv_dir" <<<"$MkvenvDir"
26
+    build__fact "pystuff/mkvenv_script" <<<"$MkvenvScript"
27
+    test -n "$Skel" || die "dist:pystuff not specified"
28
+    test -f "$Skel" || die "setup.py template not found: $Skel"
29
+    __pystuff__make_setup_py
30
+    __pystuff__make_mkvenv
31
+}
32
+
33
+__pystuff__find_dist() {
34
+    local candidate
35
+    candidate=$(
36
+        find "dist" -maxdepth 1 -mindepth 1 -printf '%P\n' \
37
+          | grep -m1 -e "$ModName-.*tar.gz"
38
+    )
39
+    test -f "dist/$candidate" && echo "dist/$candidate" && return 0
40
+    #shellcheck disable=SC2016
41
+    warn "could not find dist package in: dist" \
42
+         " .. hint: looking for $ModName-*.tar.gz" \
43
+         ' .. hint: did you run `make pystuff` first?'
44
+    return 3
45
+}
46
+
47
+__pystuff__make_setup_py() {
48
+    build__file "$Skel" "setup.py" pystuff
49
+    build__recordr dist
50
+    build__recordr "$ModName.egg-info"
51
+    python3 -m build
52
+}
53
+
54
+__pystuff__load_mkvenv_dir() {
55
+    #
56
+    # Safely load directory for the venv
57
+    #
58
+    util__loadenv PYSTUFF__VENV_DIR && return 0
59
+    plugin__option_single "dir" && return 0
60
+    echo venv
61
+}
62
+
63
+__pystuff__load_mkvenv_script() {
64
+    #
65
+    # Safely load command for the venv
66
+    #
67
+    util__loadenv PYSTUFF__VENV_SCRIPT && return 0
68
+    plugin__option_single "mkvenv_script" && return 0
69
+    echo mkvenv.sh
70
+}
71
+
72
+__pystuff__load_mkvenv_post() {
73
+    #
74
+    # Safely load command for the venv
75
+    #
76
+    util__loadenv PYSTUFF__VENV_POST && return 0
77
+    plugin__option_multi "mkvenv_post" && return 0
78
+}
79
+
80
+__pystuff__q() {
81
+    #
82
+    # Quote command $@
83
+    #
84
+    printf '%q ' "$@";
85
+    echo
86
+}
87
+
88
+__pystuff__make_mkvenv() {
89
+    #
90
+    # Create the mkvenv.sh script
91
+    #
92
+    local dist
93
+    local req
94
+    dist=$(__pystuff__find_dist) \
95
+     || die "could not find dist"
96
+    build__record "$MkvenvScript"
97
+    {
98
+        echo "#!/bin/sh -e"
99
+        echo "#"
100
+        echo "# created by MKit $MKIT_VERSION pystuff for $ModName-$ProjVersion"
101
+        echo "#"
102
+        echo ""
103
+        __pystuff__q python3 -m venv "$MkvenvDir"
104
+        __pystuff__q . "$MkvenvDir/bin/activate"
105
+        python3 "setup.py" --requires \
106
+          | grep . \
107
+          | while read -r req; do
108
+                __pystuff__q python3 -m pip install --force-reinstall "$req"
109
+            done
110
+        __pystuff__q python3 -m pip install --force-reinstall "$dist"
111
+        echo ""
112
+        echo "echo >&2 'Virtual environment should be ready. run following command:'"
113
+        echo "echo >&2 ''"
114
+        echo "echo >&2 '    source venv/bin/activate'"
115
+        echo "echo >&2 ''"
116
+        echo "echo >&2 'to activate it.'"
117
+        test -n "$MkvenvPost" && {
118
+            echo
119
+            echo "#"
120
+            echo "# user POST action ([options:pystuff:mkvenv_post]"
121
+            echo "#"
122
+            echo "$MkvenvPost"
123
+        }
124
+    } | sed 's/ *$//' >"$MkvenvScript"
125
+    bash -n "$MkvenvScript" || die "script has syntax errors"
126
+    chmod +x "$MkvenvScript"
127
+}

+ 57
- 0
utils/mkit/plugins/rpmstuff Vedi File

@@ -0,0 +1,57 @@
1
+#!/bin/bash
2
+# MKit - simple install helper
3
+# See LICENSE file for copyright and license details.
4
+
5
+mkit_import build
6
+mkit_import target
7
+
8
+rpmstuff__main() {
9
+    #
10
+    # Build specfile
11
+    #
12
+    local specname=$MKIT_PROJ_PKGNAME.spec      # .spec filename
13
+    local specsrc                               # source of specfile
14
+    target__run dist || die
15
+    specsrc="$(ini 1value "dist:rpmstuff")"
16
+    test -n "$specsrc" || die "dist:rpmstuff not specified"
17
+    test -f "$specsrc" || die "specfile template not found: $specsrc"
18
+    __rpmstuff__maybe_prefix_v0 "$specsrc"
19
+    build__file "$specsrc" "$specname" rpmstuff
20
+}
21
+
22
+__rpmstuff__maybe_prefix_v0() {
23
+    #
24
+    # Avoid "normalizes to zero" error
25
+    #
26
+    # Scripting on Fedora 38 contains new check, where the version
27
+    # is not allowed to be 0.0.0.
28
+    #
29
+    # We will disable this check for projects where our reported
30
+    # version is indeed 0.0.0, but only if it's also a devel version,
31
+    # in which case it has build data.
32
+    #
33
+    local file=$1
34
+    local tmp="$1.__rpmstuff__maybe_prefix_v0.tmp"
35
+    local version
36
+    version=$(build__cached semver)
37
+    case $version in
38
+        0.0.0+*)
39
+            warn "disabling zero-version rpmbuild check" \
40
+                 " .. hint: We're doing this only because we trust" \
41
+                 " .. hint: that this is an early devel version." \
42
+                 " .. hint: Make sure to bump at least to 0.0.1" \
43
+                 " .. hint: before release."
44
+            ;;
45
+        *)
46
+            return 0
47
+            ;;
48
+    esac
49
+    cp -ar "$file" "$tmp"
50
+    {
51
+        echo "%define _python_dist_allow_version_zero 1"
52
+        echo "# ^^ auto-added by mkit rpmstuff for early devel version"
53
+        echo ""
54
+        cat "$file"
55
+    } > "$tmp"
56
+    mv "$tmp" "$file"
57
+}

+ 175
- 15
utils/mkit/stub Vedi File

@@ -6,7 +6,7 @@ init_core() {
6 6
     #
7 7
     # Load core modules (or die)
8 8
     #
9
-    #shellcheck disable=SC1090
9
+    #shellcheck disable=SC1090,SC1091
10 10
     . "$MKIT_DIR/include/mkit.sh" \
11 11
      && . "$MKIT_DIR/include/vars.sh" \
12 12
      && return 0
@@ -63,6 +63,153 @@ deploy() {
63 63
             else
64 64
                 echo "(Nothing to say about this project.)"
65 65
             fi
66
+            echo ""
67
+            echo "See MAINTAINERS.md for instructions how to build and"
68
+            echo "maintain this MKit project."
69
+            ;;
70
+
71
+        MAINTAINERS.md)
72
+            local heading="$any_name - maintainer instructions"
73
+            echo "${heading^^}"
74
+            tr -c '=\n' '=' <<<"$heading"
75
+            echo ''
76
+            echo "<!--"
77
+            echo "    Note: This file has been auto-generated by MKit"
78
+            echo "    v$MKIT_VERSION when initializing the project."
79
+            echo "-->"
80
+            echo ''
81
+            echo "This project is using MKit for packaging and versioning"
82
+            echo "meta-data; this file describes how to do basic maintenance"
83
+            echo "operations."
84
+            echo ''
85
+            echo "MKit uses GNU Make as an interface, so all operations are"
86
+            echo "'make' targets and GNU Make takes care of the dependencies"
87
+            echo "between them.  For example, to install the project it's enough"
88
+            echo "to call 'make install'; GNU Make will call 'make built'"
89
+            echo "automatically if needed."
90
+            echo ""
91
+            echo ""
92
+            echo "Variables"
93
+            echo "---------"
94
+            echo ""
95
+            echo " *  \`DESTDIR\` - root of the installation destination"
96
+            echo " *  \`PREFIX\` - deployment prefix (often \`/usr\` or"
97
+            echo "    \`/usr/local\`)"
98
+            echo " *  \`MKIT_DRY\` - set to \`true\` to prevent MKit from making"
99
+            echo "    any changes"
100
+            echo " *  \`MKIT_DEBUG\` - set to \`true\` to have MKit display"
101
+            echo "    (lots of) debugging information"
102
+            echo ""
103
+            echo "See *utils/mkit/include/vars.sh* for full list oof supported"
104
+            echo "variables. (You can use \`sfdoc utils/mkit/include/vars.sh\`"
105
+            echo "if you have sfdoc installed.)"
106
+            echo ""
107
+            echo ""
108
+            echo "Building and installation"
109
+            echo "-------------------------"
110
+            echo ""
111
+            echo "These operations are allowed on any branch.  Assets built from"
112
+            echo "development branches will be marked using distinctive version"
113
+            echo "number containing timestamp, branch name and commit short-hash."
114
+            echo ""
115
+            echo " *  \`make\` - same as \`make build\`"
116
+            echo " *  \`make build\` - build project files"
117
+            echo " *  \`make install\` - install project to \`\$DESTDIR\`"
118
+            echo " *  \`make uninstall\` - uninstall project from \`\$DESTDIR\`"
119
+            echo " *  \`make rpmstuff\` - create RPM SPEC file and a source"
120
+            echo "    tarball"
121
+            echo " *  \`make debstuff\` - create 'debian' directory and a source"
122
+            echo "    tarball"
123
+            echo " *  \`make clean\` - remove any previously built assets".
124
+            echo ""
125
+            echo ""
126
+            echo "Versioning"
127
+            echo "----------"
128
+            echo ""
129
+            echo "These operations are only allowed on *release source branch*:"
130
+            echo ""
131
+            echo " *  \`make vbump\` - bump version and create bump commit"
132
+            echo " *  \`make release\` - create release tag"
133
+            echo ""
134
+            echo ""
135
+            echo "Recommended workflow"
136
+            echo "--------------------"
137
+            echo ""
138
+            echo ""
139
+            echo "### Development and testing ###"
140
+            echo ""
141
+            echo "Any branch can be used to create any kind of asset.  Assets"
142
+            echo "built from development branch will be marked using distinctive"
143
+            echo "version number in all MKit macros."
144
+            echo ""
145
+            echo "Development is done in branches as needed, but one branch"
146
+            echo "(often called *master*, *main* or *trunk*) is designated as"
147
+            echo "**release source** branch.  Only maintainer can push into this"
148
+            echo "branch and this is where maintainer creates the next release."
149
+            echo ""
150
+            echo ""
151
+            echo "### Versioning and releases ###"
152
+            echo ""
153
+            echo "After all development branches are merged and project is"
154
+            echo "considered ready for release, maintainer will create new"
155
+            echo "release.  This consists of three steps (only two of them are"
156
+            echo "assisted by MKit):"
157
+            echo ""
158
+            echo " 1. \`make vbump\` - will auto-edit [project:version] in"
159
+            echo "     mkit.ini and start creating **bump commit**."
160
+            echo ""
161
+            echo "    **Bump commit** is a special commit which consists of:"
162
+            echo ""
163
+            echo "     *  Bump of version in mkit.ini"
164
+            echo "     *  commit message providing rudimentary human"
165
+            echo "        readable summary of changes."
166
+            echo ""
167
+            echo "    \`make vbump\` target will cause bump of the *last*"
168
+            echo "    version fragment (Z, or PATCH in SemVer).  To bump other"
169
+            echo "    fragments, use \`make vbump_y\` or \`make vbump_x\` (MINOR"
170
+            echo "    or MAJOR in SemVer, respectively)."
171
+            echo ""
172
+            echo "    MKit will create a template of the message by scanning"
173
+            echo "    all commits since last release and invoke 'git commit' so"
174
+            echo "    that maintainer is presented with a window editing the"
175
+            echo "    commit message."
176
+            echo ""
177
+            echo "    Maintainer is responsible for finishing the commit message"
178
+            echo "    by:"
179
+            echo ""
180
+            echo "     *  Re-wording and merging the items as needed."
181
+            echo "     *  Adding any details, if needed."
182
+            echo "     *  Removing short hashes and file lists added by MKit"
183
+            echo "        (these are added only to make editing easier)."
184
+            echo ""
185
+            echo "    Once maintainer is happy with *bump commit* message,"
186
+            echo "    they can exit the editor and move on to next step."
187
+            echo ""
188
+            echo " 2. \`make release\` - will check if latest commit on current"
189
+            echo "    branch is a *bump commit*, and if yes, will create"
190
+            echo "    a corresponding **release tag**."
191
+            echo ""
192
+            echo "    **Release tags** are annotated tags (see *git-tag(1)*)"
193
+            echo "    formed by version number prefixed by letter \`v\` and are"
194
+            echo "    used by MKit to calculate version number when building"
195
+            echo "    assets.  Release tags contain the *bump commit* message"
196
+            echo "    as the annotation."
197
+            echo ""
198
+            echo "    If a *release destination* branch is specified as"
199
+            echo "    [project:reldst] in mkit.ini, MKit will also update that"
200
+            echo "    branch so that it points to the same commmit as the"
201
+            echo "    newly created release tag."
202
+            echo ""
203
+            echo " 3. Maintainer will push branches and commits to any shared"
204
+            echo "    remotes as needed."
205
+            echo ""
206
+            echo "    Warning: This is your last chance to fix any mistakes."
207
+            echo "    git tags are designed to be permanent, \"official\","
208
+            echo "    write-only objects and they will typically quickly spread"
209
+            echo "    to other clones.  *Don't count on getting rid of release"
210
+            echo "    tag*."
211
+            echo ""
212
+
66 213
             ;;
67 214
 
68 215
         */mkit.ini|mkit.ini)
@@ -90,6 +237,7 @@ deploy() {
90 237
                 echo "[dist]"
91 238
                 {
92 239
                     $MkLicense  && echo "tarball = LICENSE.md"
240
+                    $MkReadme   && echo "tarball = MAINTAINERS.md"
93 241
                     $MkMakefile && echo "tarball = Makefile"
94 242
                     $MkReadme   && echo "tarball = README.md"
95 243
                     echo "tarball = mkit.ini"
@@ -115,6 +263,9 @@ deploy() {
115 263
                 echo "[macros]"
116 264
                 {
117 265
                     echo "__${PackageName^^}_FOO__ = Barr.."
266
+                    echo "    __PROJECT_DESC_MAIN__ = MKIT_STUB_DESCRIPTION - replace this with your project"
267
+                    echo "    __PROJECT_DESC_MAIN__ = MKIT_STUB_DESCRIPTION .. description; this will appear"
268
+                    echo "    __PROJECT_DESC_MAIN__ = MKIT_STUB_DESCRIPTION .. in places like rpm -i"
118 269
                 } | reformat_section
119 270
                 echo ""
120 271
                 echo "[modes]"
@@ -135,18 +286,19 @@ deploy() {
135 286
             ;;
136 287
 
137 288
         packaging/template.spec)
138
-            echo 'Name:       __MKIT_PROJ_PKGNAME__'
139
-            echo 'Version:    __MKIT_PROJ_VERSION__'
140
-            echo 'Release:    1%{?dist}'
141
-            echo 'Summary:    __MKIT_PROJ_NAME__ - __MKIT_PROJ_TAGLINE__'
289
+            echo 'Name:           __MKIT_PROJ_PKGNAME__'
290
+            echo 'Version:        __MKIT_PROJ_VERSION__'
291
+            echo 'Release:        1%{?dist}'
292
+            echo 'Summary:        __MKIT_PROJ_NAME__ - __MKIT_PROJ_TAGLINE__'
142 293
             test -n "$VcsBrowser" && echo 'URL:        __MKIT_PROJ_VCS_BROWSER__'
143
-            $MkLicense && echo "License:    $License"
144
-            echo 'Source0:    %{name}-%{version}.tar.gz'
145
-            echo 'BuildArch:  noarch'
294
+            $MkLicense && echo "License:        $License"
295
+            echo 'Source0:        %{name}-%{version}.tar.gz'
296
+            echo 'BuildArch:      noarch'
297
+            echo 'BuildRequires:  make'
146 298
             echo ''
147 299
             echo 'Requires: MKIT_STUB_REQUIRES'
148 300
             echo '%description'
149
-            echo 'MKIT_STUB_DESCRIPTION'
301
+            echo '__PROJECT_DESC_MAIN__'
150 302
             echo ''
151 303
             echo '%prep'
152 304
             echo '%setup -q'
@@ -163,6 +315,7 @@ deploy() {
163 315
             echo '%changelog'
164 316
             echo ''
165 317
             echo '# specfile built with MKit __MKIT_MKIT_VERSION__'
318
+            echo "#  .. based on stub from: $MKIT_VERSION"
166 319
             ;;
167 320
 
168 321
         packaging/debian/copyright)
@@ -178,14 +331,16 @@ deploy() {
178 331
             echo 'Standards-Version: 3.9.2'
179 332
             echo 'Build-Depends:'
180 333
             echo ' debhelper (>= 9),'
334
+            echo ' make,'
181 335
             echo ''
182 336
             echo 'Package: __MKIT_PROJ_PKGNAME__'
183 337
             echo 'Architecture: all'
184 338
             echo 'Depends: MKIT_STUB_REQUIRES'
185 339
             echo 'Description: __MKIT_PROJ_NAME__ - __MKIT_PROJ_TAGLINE__'
186
-            echo ' MKIT_STUB_DESCRIPTION'
340
+            echo ' __PROJECT_DESC_MAIN__'
187 341
             echo ''
188 342
             echo '# control file built with MKit __MKIT_MKIT_VERSION__'
343
+            echo "#  .. based on stub from: $MKIT_VERSION"
189 344
             ;;
190 345
 
191 346
         packaging/debian/changelog)
@@ -205,11 +360,11 @@ deploy() {
205 360
             echo ''
206 361
             echo '%:'
207 362
             echo ''
208
-            echo '	dh $@'
363
+            echo '	PREFIX=/usr dh $@'
209 364
             echo ''
210 365
             echo 'override_dh_auto_install:'
211 366
             echo ''
212
-            echo '	make install PREFIX=/usr DESTDIR=debian/tmp'
367
+            echo '	make install DESTDIR=debian/tmp'
213 368
             ;;
214 369
 
215 370
         packaging/debian/source/format)
@@ -430,11 +585,15 @@ deploy() {
430 585
             if $MkReadme; then
431 586
             echo ""
432 587
             echo ""
433
-            echo "README.md"
434
-            echo "---------"
588
+            echo "README.md and MAINTAINERS.md"
589
+            echo "----------------------------"
435 590
             echo ""
436 591
             echo "Each serious project needs a serious README.  Which is why"
437 592
             echo "*stub* has created a 'stub' of one for you."
593
+            echo ""
594
+            echo "Furthermore MAINTAINERS.md is created to help maintainers"
595
+            echo "do basic MKit operations such as building assets and making"
596
+            echo "releases."
438 597
             fi
439 598
 
440 599
             echo ""
@@ -479,7 +638,7 @@ usage() {
479 638
         echo "    -V VERSION    initial version (default: 0.0.0)"
480 639
         echo "    -a            enable autoclean ('make clean' after"
481 640
         echo "                  each 'make install')"
482
-        echo "    -g            make git commits before and adter"
641
+        echo "    -g            make git commits before and after"
483 642
         echo "                  (implies -y)"
484 643
         echo "    -y            don't ask, just do it"
485 644
         echo "    -R            skip creating README.md"
@@ -699,6 +858,7 @@ main() {
699 858
     deploy src/"$PackageName".skel
700 859
     $MkMakefile     && deploy Makefile
701 860
     $MkReadme       && deploy README.md
861
+    $MkReadme       && deploy MAINTAINERS.md
702 862
     $MkLicense      && deploy LICENSE.md
703 863
     $AutoClean      && deploy .mkit/autoclean
704 864
     $MkPackaging    && deploy_packaging