-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmake-server
executable file
·244 lines (206 loc) · 7.92 KB
/
make-server
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
#!/usr/bin/env zsh
# -*- mode: sh; sh-indentation: 4; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# Copyright (c) YEAR USER_NAME
# An example of type-agnostic script/function, i.e.: the file can be run as a +x
# script or as an autoload function.
# Set the base and typically useful options
emulate -L zsh
setopt extendedglob warncreateglobal typesetsilent noshortloops \
rcquotes noautopushd multios
# Run as script? ZSH_SCRIPT is a Zsh 5.3 addition
if [[ $0 != make-server || -n $ZSH_SCRIPT ]]; then
# Handle $0 according to the Zsh Plugin Standard:
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
0=${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}
0=${${(M)0##/*}:-$PWD/$0}
# Such global variable is expected to be typeset'd -g in the plugin.zsh
# file. Here it's restored in case of the function being run as a script.
typeset -gA Plugins
Plugins[MSERV_DIR]=${0:h}
# Allow accessing other functions as scripts.
fi
{ zmodload zsh/system && zsystem supports flock
Plugins+=( MSERV_FLOCK_AVAIL $((!$?)) ); } &>/dev/null
# Set path in case of other scripts being needed.
local -aU path; local -U PATH
path+=( $Plugins[MSERV_DIR]/functions )
MSERV_CONF_DIRS=${MSERV_CONF_DIRS//(#b)((#s)|:)\~/$match[1]$HOME}
# Obtains the command to run and full path to log
# in its directory and outputs to it plus two other
# locations (/tmp and ~/.config/mksrv)
run_command()
{
# In case running bare script, without plugin manager
local ZSRV_THIS_CACHE=${ZSRV_THIS_CACHE:-${ZSH_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}}/makesrv}
local cmd=$1 prj=$2:h \
prjlog=$2 tmplog=/tmp/$2:t cachelog=$ZSRV_THIS_CACHE/$2:t
shift 2 # Now $@ contains remaining strings – command options.
if [[ $cmd = REMOVE ]]; then
command rm -f $tmplog $cachelog $prjlog
return
fi
# Run via eval to allow e.g.: cmd="env PATH=… make"
# or cmd="make -C …"
builtin eval "command $cmd $@ &>!$tmplog &>!$cachelog &>!$prjlog&"
# Save the PID of the background command
REPLY=$!
Plugins[MSERV_${(U)cmd}_PID]=$REPLY
}
local -a match mbegin mend
local MATCH; integer MBEGIN MEND
[[ -n $1 ]] && { source "$1" || return 1;}
# -T ties a comma separated scalar with an array.
local -TUx MSERV_CONF_DIRS in_src_dirs
local -TUx SRC_DIRS src_dirs
local REPLY prj tpe prev_tpe datfle
integer i count lockfd
# A hash to select target file without an if.
local -A Hash=( null .mksrv-last-null
clean .mksrv-last-clean
warn .mksrv-last-warn
err .mksrv-last-err ) \
NCnt Data Counts # Separate compilation counts per project
# and of temporary file names.
#
# Pre-process the repositories filtering out incorrect ones.
#
# Initial check for empty input.
if [[ -z ${MSERV_CONF_DIRS##[[:space:]]##} ]]; then
msg {208}Error{39}:{70} No directories of projects given, \
nothing to manage, exiting…
fi
# Iterate over given dirs.
for prj in $in_src_dirs; do
if [[ ! -d $prj || ! -r $prj ]]; then
msg {208}Error{39}:{70} Directory \`{dir}$prj{70}\` \
incorrect, skipping…
elif [[ ! -r $prj/Makefile ]]; then
msg {208}Error{39}:{70} Directory \`{dir}$prj{70}\` \
doesn\'t contain a Makefile, skipping…
else
src_dirs+=( $prj )
fi
done
# Final check.
if ((!$#src_dirs)); then
msg {208}Error{39}:{70} No suitable directories found, \
exiting the {cmd}make-server{70}…
fi
# Save the processed value and other Plugins hash fields.
Plugins[MSERV_CONF_DIRS]=$SRC_DIRS
Plugins[MSERV_LOCK_FILE]=.mksrv-upmost
Plugins[MSERV_CONF_INTERVAL]=${MSERV_CONF_INTERVAL:=5}
# Split the make args by tying it with array `args`
local -T MSERV_CONF_ARGS args
Plugins[MSERV_CONF_ARGS]=$MSERV_CONF_ARGS
Plugins[MSERV_CONF_PAUSE_AFTER]=${MSERV_CONF_PAUSE_AFTER:=30}
#
# Establish make binary to use.
#
local make=make
(($+commands[gmake]))&&make=gmake
#
# Main loop
#
# First unloack sleeps.
for prj in $src_dirs; do
command rm -f $prj/.mksrv-sleep
done
while ((1)); do
sleep $Plugins[MSERV_CONF_INTERVAL]
i=0
#
# Compile each of the projects and test their result,
# setting their private state files.
#
for prj in $src_dirs; do
i+=1
if [[ ! -f $prj/.mksrv-sleep && $NCnt[$prj] -ge Plugins[MSERV_CONF_PAUSE_AFTER] ]]; then
msg %B{55}Resuming operation "({208}requested{55})" for {69}$prj
NCnt[$prj]=0
fi
[[ -f $prj/.mksrv-sleep ]] && continue
#
# (Re-)Generate the make's temp output file name if needed.
#
if [[ -z $Data[$prj] ]]; then
count=$Counts[$prj]+1
Data[$prj]=$(mktemp /tmp/mksrv-$count-for-$prj:t.XXX)
msg %B{208}MAKE-SERVER%b%f: Project %B{70}$prj:t%f%b \
new build %B{140}\#$count%f%b initiated.
fi
datfle=$Data[$prj]:t
# Check if any explicit (-f) make is running.
while ((1)); do
lockfd=1337
if ! zsystem flock -f lockfd -t 0 $prj/$Plugins[MSERV_LOCK_FILE]&>/dev/null
then
if (($?==2)) || [[ -f $prj/.mksrv-lock ]]; then
msg {208}Warning:%f waiting for manual, non-managed \
{39}make%f to finish…
sleep 1
continue
fi
fi
break
done
#
# Run make catching its output.
#
# Three locations: /tmp, ~/.cache/…, and project dir
# via a refactored function that outputs to them.
repeat 1 { run_command "$make -C $prj" $prj/$datfle ${=args[i]} }
print ongoing $datfle $REPLY >! $prj/.mksrv-state
wait $REPLY
print finished $datfle >! $prj/.mksrv-state
#
# Examine and move the output and set status files.
#
# Use one of the files with output.
datfle=/tmp/$datfle
# No file-references and no CC? -> no compilation done
if ! grep -qE '^([^:]+:[0-9]+:[0-9]+:|[[:space:]]+CC|[[:space:]](g|)cc[[:space:]])' $datfle; then
tpe=null
# No error references? -> either a warning or a CC-only run
elif ! grep -qE '^([^:]+:[0-9]+:[0-9]+:.*error:)' $datfle; then
if grep -qE '^([^:]+:[0-9]+:[0-9]+:.*warning:)' $datfle; then
tpe=warn
else
tpe=clean
fi
else
tpe=err
fi
#
# Check for change to update counts and temp files.
#
{ prev_tpe=${$(<$prj/.mksrv-upmost)[1]};} &>/dev/null
# Update count of distinct events ignoring the null runs
# and also request a new temporary file generation by Data[]=.
if [[ $tpe != $prev_tpe && $tpe != null ]]; then
Counts[$prj]=$((Counts[$prj]+1))
Data[$prj]=
fi
#
# Save the state of the result after examination.
#
# Store final state and move the file.
command mv $datfle $prj/$Hash[$tpe]
if ! [[ $tpe == null && $prev_tpe == (warn|clean) ]]; then
print $tpe $datfle:t $Hash[$tpe]>!$prj/.mksrv-upmost
fi
msg {208}MAKE-SERVER:%f Build of {39}$prj:t%f finished with result: %B{140}$tpe%f.
# Remove the output files, whose paths are known
# to the `run_command` func.
repeat 1 { run_command REMOVE $prj/$datfle:t; }
{exec {lockfd}>&-;}&>/dev/null
# Update sleep count per project.
[[ $prev_tpe == $tpe || ! ($tpe != $prev_tpe && $tpe != null) ]] && ((NCnt[$prj]+=1))
if (( NCnt[$prj] >= $Plugins[MSERV_CONF_PAUSE_AFTER] )); then
msg %B{55}No change {208}$NCnt[$prj]{55} runs for project \
{69}$prj{55} occured, suspending it…
print $$ >! $prj/.mksrv-sleep
fi
done
done
# vim:ft=zsh:tw=80:sw=4:sts=4:et:foldmarker=[[[,]]]