-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaitbuild
executable file
·350 lines (278 loc) · 19.6 KB
/
aitbuild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#! /bin/ksh
#! @brief GNU build system wrapper to use in lieu of the conventional `autogen.sh`
#! @revision 2020-08-11 (Tue) 11:50:17
#! @requires Warning. This utility requires a valid (i.e. ksh93u+ compatible) KornShell
#! @notes - Master copy is kept on ISLEcode/ait-reference since, for now, ISLEcode/ait-dragonfly being a private repository
#! - This file is edited using Vim and the AIT vimutils package; using other configurations may be inappropriate
# @{ GLOB - Global variables all accessible in global `rc` compound variable
enum boolean=( false true ) # This should be standard in Kornshell... really
compound rc=( # Use compound variable as registry for this script
typeset progname=${0##*/} # This program's name... no reason to rename this!
typeset reponame=${PWD##*/} # Defaults to directory name, unless `config.kaml.in` in use
typeset command # The build command to enact
typeset builddir=out # The -out-of-source_ build directory, conventiolly named `out`
typeset update_url=https://bit.ly/31CDmDR # The URL to fetch the raw content of this script from GitHub
boolean kaml_havefile=false # Checks for the existence of the `config.kaml.in` special file
boolean kaml_autoconf=false # Is the `configure.ac` properly configure for the `config.kaml` file
boolean kaml_automake=false # Likewise is the `Makefile.am` file also properly configured
typeset temp_dirs=( ${_.builddir} autom4te.cache tmp var ) # Directories required for build but not in Git repository
typeset temp_files=( COPYING INSTALL configure aclocal.m4 ) # Likewise for temporary build files
boolean uses_kamlconf=false ) # Enabled when @(kaml_autoconf|kaml_automake|kaml_havefile) is true
# @}
# @{ FUNC - Global utilities and dedicated functions for each function
# Utilities
function fsdelete { # Wrap UNIX command `rm`
# First argument is optional; if specified provides a file or directory name
typeset name; [[ $1 == [!-]* ]] && { name=$1; shift; }
# Next argument MUST be an option, typically `-rf` or `-f`; determine the whether this is a file or directory
typeset kind=file options=$1; shift; [[ $options == -*r* ]] && kind=directory
typeset fsobject; for fsobject in "$@"; do
# Determine the kind of object we are handling here
if [[ $kind == file ]]; then [[ -f $fsobject ]] || continue
elif [[ $kind == directory ]]; then [[ -d $fsobject ]] || continue; fi
# Enact the deletion
rm $options "$fsobject" 2>/dev/null &&
println info "- deleted${name:+ $name} $kind '%s'" ${fsobject#./} ||
println error "- failed to delete${name:+ $name} $kind '%s'" ${fsobject#./}
# If the file was previously tracked, abandon tracking
git ls-files --error-unmatch $fsobject >/dev/null 2>&1 || continue
git commit -m 'untracking intermediate build file' $fsobject >/dev/null 2>&1
done
}
function fsmkdir { # Wrap UNIX command `mkdir`
typeset name; [[ $1 == -* ]] || { name=$1; shift; }
typeset options; [[ $1 == -* ]] && options=$1
typeset dir; for dir in "$@"; do
[[ -e $dir ]] && continue
mkdir $options "$dir" 2>/dev/null &&
println info "- created${name:+ $name} directory '%s'" ${dir#./} ||
println error "- failed to create${name:+ $name} directory '%s'" ${dir#./}
done
}
function fstouch { # Wrap UNIX command `touch`
typeset name; [[ $1 == [!-]* ]] && { name=$1; shift; }
typeset file; for file in "$@"; do
[[ -e $file ]] && continue
touch "$file" 2>/dev/null &&
println info "- created${name:+ $name} file '%s'" ${file#./} ||
println error "- failed to create${name:+ $name} file '%s'" ${file#./}
done
}
function init { # Global initialisation
# Determine whether this package/repository uses a KAML configuration file
integer lines=$(grep 'config\.kaml' configure.ac 2>/dev/null | wc -l); (( lines > 0 )) && rc.kaml_autoconf=true
integer lines=$(grep 'config\.kaml\.in' Makefile.am 2>/dev/null | wc -l); (( lines > 0 )) && rc.kaml_automake=true
[[ -f config.kaml.in ]] && rc.kaml_havefile=true
# If any is true, then we assume that the package uses a KAML configuration file
if (( rc.kaml_autoconf + rc.kaml_automake + rc.kaml_havefile > 0 )); then
# Make sure the `configure.ac` file is correctly configured
rc.uses_kamlconf=true; (( rc.kaml_autoconf == true )) || println abort '%s\n %s\n %s\n %s' \
$'The `configure.ac` file hasn\'t been properly configured; the following three lines should appear in your file:' \
'AC_SUBST(PACKAGE_TARSUFFIX,PACKAGE_TARSUFFIX)' 'PACKAGE_TARSUFFIX=noarch' 'AC_CONFIG_FILES(config.kaml)'
# Make sure the `Makefile.am` file is correctly configured
(( rc.kaml_automake == true )) || println abort '%s\n %s' \
$'The `Makefile.am` file hasn\'t been properly configured; the following line should appear in your file:' \
'EXTRA_DIST += config.kaml.in'
# If the `config.kaml.in` file is absent, create it
if (( rc.kaml_havefile == false )); then
( print 'name=@PACKAGE@'
print 'bugreport=@PACKAGE_BUGREPORT@'
print 'build=@build@'
print 'build_os=@build_os@'
print 'build_triplet=@build_triplet@'
print 'host=@host@'
print 'host_os=@host_os@'
print 'host_triplet=@host_triplet@'
print 'label="@PACKAGE_NAME@"'
print 'prefix=@prefix@'
print 'target=@target@'
print 'target_os=@target_os@'
print 'target_triplet=@target_triplet@'
print 'tarname=@PACKAGE_TARNAME@'
print 'tarsuffix=@PACKAGE_TARSUFFIX@'
print 'url=@PACKAGE_URL@'
print 'version=@PACKAGE_VERSION@'
) > config.kaml.in
println warning 'rebuilt `config.kaml.in` missing file using defaults.'
fi
fi
# We're done unless an out-of-source build has already been done
[[ -d ${rc.builddir} ]] || return
# If using the KAML configuration file, load the generated file built by GNU configure
[[ -f ${rc.builddir}/config.kaml ]] && eval $'typeset -C rc.package=(\n'"$(< ${rc.builddir}/config.kaml )"$'\n)'
}
function println { # Simple message logging utility
typeset -S fail=$'\E[0;1;31m' info=$'\E[0;1m' norm=$'\E[0m' pass=$'\E[0;1;32m' warn=$'\E[0;1;33m' weak=$'\E[0;2m'
typeset -S prog="$weak${rc.progname}$norm \E[1;3;34m${rc.reponame}$norm"
typeset kind=$1 format="$2"; shift 2
case $kind in
# Colour-distinguished message levels
info) printf "$prog ${info}info $norm $format\n" "$@" ;;
pass?(ed)) printf "$prog ${pass}passed $norm $format\n" "$@" ;;
success) printf "$prog ${pass}success$norm $format\n" "$@" ;;
warn?(ing)) printf "$prog ${warn}warning$norm $format\n" "$@" ;;
fail?(ed)) printf "$prog ${fail}failed $norm $format\n" "$@" ;;
err?(or)) printf "$prog ${fail}error $norm $format\n" "$@" ;;
# Allow termination on errors
abort) printf "$prog ${fail}error $norm $format\n" "$@"; exit 1 ;;
# Special syntax for the usage string
usage) printf "${info}usage${norm} ${rc.progname}$norm $format\n" "$@" ;;
esac
}
# Commands
function run_build { # Enact `make`
typeset buildlog=build-$(date +%Y%m%d-%H%M%S.log)
println info 'building the %s package' "${rc.package.label:-${rc.package}}"
[[ -d ${rc.builddir} ]] || println abort $'package doesn\'t seem to have been prepared; try running ./aitbuild prepare'
(cd ${rc.builddir} && PATH=/bin:/usr/bin make "$@" >$buildlog 2>&1); error=$?
(( error == 0 )) && println pass 'successful build of %s' "${rc.package.label:-${rc.package}}" ||
println pass 'the build did not complete successfully; please check %s' ${rc.builddir}/$buildlog
}
function run_check { # Enact `make distcheck`
typeset checklog=check-$(date +%Y%m%d-%H%M%S.log)
println info 'checking that package %s is ready for distribution' "${rc.package.label:-${rc.package}}"
[[ -d ${rc.builddir} ]] || println abort $'package doesn\'t seem to have been prepared; try running ./aitbuild prepare'
[[ -d ${rc.builddir} ]] && (cd ${rc.builddir} && PATH=/bin:/usr/bin make distcheck "$@" >$checklog 2>&1); error=$?
(( error == 0 )) && println pass 'package %s is ready for distributioon' "${rc.package.label:-${rc.package}}" ||
println failed 'package cannot be distributed as-is; please check %s' ${rc.builddir}/$checklog
}
function run_clean { # Enact `make clean` + custom cleaning
println info 'cleaning up the AIT build environment (this can take a couple of minutes)'
# Clean-up unnecessary 'Makefile.in' and 'Makefile' files -- provided we have an associated 'Makefile.am'
find . -name Makefile.am | while read am; do
[[ am == @(${rc.builddir}|_in|var|wip) ]] && continue || typeset in=${am%am}in
[[ -f $in ]] && fsdelete -f $in
done
# Clean-up intermediate build files
for file in ${rc.temp_files[@]}; do [[ -f $file ]] && fsdelete -f $file; done
for dir in ${rc.temp_dirs[@]}; do [[ -d $dir ]] && fsdelete -fr $dir; done
for file in AUTHORS NEWS README; do [[ -e $file || ! -s $file ]] && fsdelete -f $file; done
}
function run_distrib { # Enact `make install DESTDIR=...` + binary distribution tarball
typeset instlog=tarball-$(date +%Y%m%d-%H%M%S.log) destdir=$PWD/${rc.builddir}/tarball
typeset target=${rc.package.tarsuffix}; [[ -n $target ]] || target=target
[[ $target == target?(_@(os|triplet)) ]] && nameref target=rc.package.$target
typeset tarball=$PWD/${rc.builddir}/${rc.package.tarname}-${rc.package.version}-$target.tgz
println info 'building %s binary tarball' "${rc.package.label:-${rc.package}}"
[[ -d ${rc.builddir} ]] || println abort $'package doesn\'t seem to have been prepared; try running ./aitbuild prepare'
(cd ${rc.builddir}; make install DESTDIR=$destdir >$instlog 2>&1); error=$?
(( error == 0 )) && println pass 'successfully built distribution of %s' "${rc.package.label:-${rc.package}}" ||
println pass 'unable to build distribution; please check %s' ${rc.builddir}/$instlog
(cd $destdir/${rc.package.prefix}; tar zcf $tarball * >$destdir/../$instlog 2>&1); error=$?
(( error == 0 )) && println pass 'successfully built distribution tarball (%s)' "${rc.builddir}/${tarball##*/}" ||
println pass 'unable to build distribution tarball; please check %s' ${rc.builddir}/$instlog
}
function run_prepare { # Enact `autoreconf` + `configure`
[[ -f aclocal.m4 ]] &&
println info 'rebuilding the AIT build environment.' ||
println info 'building the AIT build environment.'
# Recreate files and directories required by GNU build system
for file in AUTHORS NEWS; do fstouch 'GNU required' $file; done
for dir in ${rc.temp_dirs[@]}; do fsmkdir 'GNU build' $dir; done
[[ -f README.md && ! -e README ]] && ln -s README.md README
# Reconfigure the build environment
: ${AUTOMAKE='automake --add-missing'}
: ${AUTOM4TE_FLAGS=' --cache=var/autom4te.cache'}
# [[ -f configure ]] || autoreconf
println info 'refreshing package build setup'
autoreconf -vvv --force --install --symlink --make --no-recursive >${rc.builddir}/autoreconf.log 2>&1 ||
{ println error 'refresh failed; please consult log: %s' ${rc.builddir}/autoreconf.log; exit 1; }
println info 'performing initial configuration'
(cd ${rc.builddir} && ../configure --quiet "$@") || { println error 'failed to configure package'; exit 1; }
}
function run_rebuild { # Enact the clean + prepare + build commands
$0 clean && $0 prepare && $0 build
}
function run_selfupdate { # Download latest version of this script
# Collect the arguments (self name and udpate URL), and initialise temporary name
typeset self=$1 url=$2 burl=${url%/master*} temp=._${self##*/}; burl=${burl#*.com/}
println info 'perfoming self-update from GitHub (%s)' $burl;
# Download new copy of ourself from GitHub repository
curl -SsLo $temp $url >/dev/null 2>&1 ||
println abort 'download failed for URL: %s' $url
# Update shbang to match this operating system's Kornshell path
typeset ksh=$(whence ksh); sed -i .bak "1s:.*:#! $ksh:" $temp >/dev/null 2>&1 && rm $temp.bak ||
println abort 'failed to update shbang (%s), remove backup %s' $ksh $temp.bak
# Adjust permissions to make the download file executable
chmod 0755 $temp >/dev/null 2>&1 || println abort 'failed to adjust permissions of downloaded file (%s)' $temp
# Make a backup copy of ourself, just in case
cp $self $self.bak || println abort 'cannot make backup copy (%s), check UNIX permissions' $self.bak
# We're done, we simply need to overwrite ourself
mv $temp $self >/dev/null 2>&1 && rm $self.bak || println abort 'failed to install update, consider using backup: %s' $temp
typeset revision=$(sed -n '/^#! @revision/{s/^#! @revision[ ]*//;p;}' $self 2>/dev/null)
[[ -n $revision ]] || println abort 'something went wrong; please try to issue the self-update command again'
println success 'successfully updated myself; new revision is %s' "${revision:-unknown!}"
# Automatically update git for the user's convenience
git commit -m "updated to revision $revision" aitbuild >/dev/null 2>/dev/null
}
# @}
# @{ MAIN - Main processing loop
# Collect the command to enact; show usage if invoked without any arguments
[[ -z $1 ]] && set -- --help || [[ ${1:0:1} != - ]] && { rc.command=$1; shift; }
# Process the command line options
while getopts -a $"${rc.progname}${rc.command:+ ${rc.command}}" $'[-]
[+NAME?'"${rc.progname}"$' -- a GNU build system wrapper for building AIT packages]
[+DESCRIPTION?The purpose of this utility is to wrap the GNU build system used in all AIT packages into a easy to remember and
systematic steps. For those of us who have lived the glorious days of Xenix, System V, SCO and all flavours of UNIX, the GNU build
system is already a highly simplified build. Nonetheless, in our modern times, this may still appear overly complex having to deal
with myriad tools and configuration files. And a hurdle commonly seen being how to have Git and the GNU build system happily
coexist.]
[+?The usual installation scenario should look like this:][\+
'"${rc.progname}"$' prepare
'"${rc.progname}"$' build
'"${rc.progname}"$' install]
[+?You may be tempted to say that this looks very much like the traditional GNU workflow:][\+
./configure [options]]
make
make install]
[+?Indeed we wanted to keep this high level simplicity. In reality the GNU build system is more complex:]{
[+\b1.?Firstly, the \bconfigure\b script doesn\'t exist out of the box; it has to be generated. Many tools are involved in the
creation of this script and all of its associated files. Depending on the particular setup of your project you will be using the
GNU \bautoconf\b, \bautomake\b and \blibtool\b utilities. This requires a lot of intermediate files which are going to pollute
unnecessarily your repository. GNU does have a utility to overcome this, \bautoreconf\b; but that utility also needs some
tweaking -- even if, most of the time, it doesn\'t. So we have an extra step, possibly complex, before the configure step in the
above workflow.]
[+\b2.?Secondly the custom built \bconfigure\b script can take many options. Some packages using the GNU build system have
conceived very complex configuration scripts with virtually far too many options. If these options exist, obviously they have a
utility; but remembering them is tricky. This is most surely one of the reasons why package managers have become so pervasive.]
[+\b3.?Once we have passed the stage configuration stage, one would expect things to be simple from there on; after all the GNU
build philosophy is one of automating the task of creating \bMakefiles\b. Run \amake(1)\a and be happy! Well, this is mostly true;
and this is why we rely on the GNU build system. Nonetheless running \amake(1)\a targets can be done in a variety of ways and we
want to enforce \bout-of₋source\b builds (see below), which is slightly more constraining than simply runing \bmake install\b.]
[+\b4.?Since 2008 Git and GitHub have taken the Open Source ecosystem by storm, offering redundant distribution handling
capabilities and features. The GNU build system addresses a wider scope and probably considers it doesn\'t need to adapt.
Further, the GNU build system focuses on building source code distribution tarballs while we require, for our GitHub releases,
binary distribution tarballs. For maintainers also we want a simple workflow:][+\
'"${rc.progname}"$' prepare
'"${rc.progname}"$' build
'"${rc.progname}"$' distrib]}
[+?The \b'"${rc.progname}"$'\b utility attempts to solve this through the following design:]{
[+\b1.?A predefined and limited set of commands (see the \bCOMMANDS\b section below). Essentially all workflows should be done in
four steps: a) prepare, b) build, c) either install or build the distribution tarball, and optionally d) cleanup the environment.
Though we allow the use of command line options, our intent is that this should be the exceptional case.]
[+\b2.?In lieu of passing command line arguments we use a configuration file, \bconfig.kaml.in\b, which can contain both
predefined configuration data, and data which is populated as an outcome of the \bconfigure\b script generated by the GNU
autotool. On our roadmap, is the intent to have this file automatically generated by the \bsw(1)\b AIT tool.]}
[+COMMANDS?]{
[+build?This performs the traditional \vmake install\v.]
[+check?This command is automatically invoked by the \vdistrib\v command and is equivalent to \vmake distcheck\v.]
[+clean?Cleanup the repository so as to maintain the Git repository clean.]
[+distrib?Build the distribution tarball which can be uploaded to GitHub when creating a new release.]
[+prepare?Do all necessary tasks to prepare the build step; this includes \vconfigure\v, but also all preceeding \vsetups\v to
recreate the necessary build scripts and build-time files]
[+rebuild?A handy shortcut to run \bprepare\b and \bbuild\b from a clean slate (i.e. after having run \bclean\b to remove all
intermediate files.]
[+self-update?Easily update this script to accomodate changes and new features. It should become a habit to run this as the
very first step once a package has been cloned through Git.]}' opt; do case $opt in
# No options... yet
*) ;;
# Options have been processed... zap them from the command line
esac; done; (( OPTIND > 1 )) && shift $(( OPTIND - 1 )); OPTIND=1
# Initialise environment and enact the requested command
init "$@"; case ${rc.command} in
@(build|check|clean|distrib|prepare|rebuild)) run_${rc.command} "$@" ;;
self?(-)update) run_selfupdate $0 ${rc.update_url} ;;
*) println usage '( clean | prepare | build | rebuild | distrib)' ;;
esac
# @}
# vim: nospell