summaryrefslogtreecommitdiffstats
path: root/ship/build
blob: 5863e32565a23b7e76364f027f3dd3797776602a (plain)
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
#! /bin/sh
set -euf

## SYNOPSIS
# [debug=true] build compile SRCFILE DSTFILE
# [debug=true] build deps SRCFILE...
build() {

  ## Load macro definitions.
  defmacro_pattern='## usage: \(.*\) -> \([^ ]\+\) \(.*\)'
  script='s/^'"$defmacro_pattern"'$/\2_macro='"'"'\1'"'"'/p' \
    setf defmacros '$(sed -n "$script" "$%s")' 0
  eval "$defmacros"

  ## Dispatch.
  case "$1" in
    compile) build_compile "$2" "$3";;
    deps) shift; build_deps "$@";;
    *) echo "build: $1: bad command" >&2; return 23;;
  esac
}

###
### macros
###

## usage: #@include \([0-9A-Za-z_]\+\) -> build_include \1 \2
build_include() {
  if buildcache_has "#@include:$2"; then
    printf '%da\\\n##include %s: already done\n' $1 $2
  else
    buildcache_add "#@include:$2"
    cat<<EOF
$1a\\
# begin $2
$1r$(build_resolve $2)
$1a\\
# end $2
EOF
  fi
}

## usage: #@info -> build_info \1
build_info() { cat<<EOF
$1a\\
# this file was generated by //ship/build\\
#   build date: $(date -u --rfc-3339=s)\\
#   git describe: $(git describe --always --dirty --abbrev=0)
EOF
}

###
### main subroutines
###

## usage: build_compile SRCFILE DSTFILE
build_compile() {

  script='s/^'"$defmacro_pattern"'$/\2_macro/p' \
    setf macro_names '$(sed -n "$script" "$%s")' 0

  setf unexpanded_macros_pattern \
    '$(make_unexpanded_macros_pattern $%s)' macro_names

  script='
      s/^'"$defmacro_pattern"'$/s:^ *\\([0-9]\\+\\) \1$:\2 \3:/p
      $a\
t;s:^ *\\([0-9]\\+\\) .*:echo \\1p:
      ' \
    setf input_parser '$(sed -n "$script" "$%s")' 0

  SRCFILE="$1" setf src '$(cat "$%s")' SRCFILE

  buildcache_initialize "$2"

  while echo "$src" | grep -q "$unexpanded_macros_pattern"; do
    setf sedgen '$(echo "$%s" | nl -b a -s \  | sed "$%s")' src input_parser
    setf sedscript '$(eval "$%s")' sedgen
    setf src '$(echo "$%s" | sed -n "$%s")' src sedscript
  done

  buildcache_finalize

  echo "$src" > "$2"
  chmod +x "$2"
}

## usage: build_deps SRCFILE...
# Print all the dependencies of SRCFILE... to stdout. (alphabetic order)
build_deps() {
  while test $# -gt 0; do
    deps="$(
      for f; do
        for d in $(sed -n 's:^'"$build_include_macro"'$:\1:p' "$f"); do
          build_resolve $d
        done
      done
    )"
    set -- $deps
    if test $# -gt 0; then
      echo "$deps"
    fi
  done | sort | uniq
}

###
### misc utilities
###

## usage: build_resolve LIBNAME
build_resolve() {
  echo "$BUILD_PATH" | tr : \\n |
    xargs -I: printf '%s/%s\n' : "$1" |
    xargs -I: ls -d : 2>/dev/null |
    head -n 1 |
    grep . ||
  {
    echo "build resolve: $1: library not found" >&2
    return 23
  }
}

## usage: make_unexpanded_macros_pattern BUILD_DIRECTIVES...
make_unexpanded_macros_pattern() {
  echo "^\\($(
    for macro; do
      eval echo \"\$$macro\"
    done |
      tr \\n \| |
      sed 's/|/\\|/'
  )\\)$"
}

## usage: setf NAME FMT [ARG...]
setf() {
  value_script="$(shift; printf "$@")"

  eval "$1=$value_script"

  if is_debug_mode; then
    eval 'echo "$1=\"$value_script\""'
    eval 'echo "'"\$$1"'"' | nl -b a
  fi >&2
}

## usage: is_debug_mode
is_debug_mode() {
  test "${debug-false}" = true
}

###
### buildcache utilities
###

## usage: buildcache_initialize DESTFILE
buildcache_initialize() {
  buildcache="$1.buildcache"
  cat /dev/null > "$buildcache"
}

## usage: buildcache_finalize
buildcache_finalize() {
  if is_debug_mode; then
    rm "$buildcache"
  fi
}

## usage: buildcache_has BRE
# Check if buildcache contains a line matching BRE.
buildcache_has() {
  grep -q "^$1\$" "$buildcache"
}

## usage: buildcache_add LINE
# Add LINE to buildcache.
buildcache_add() {
  echo "$1" >> "$buildcache"
}

###
### main invocation
###

if echo "$0" | grep -q '^\(.*/\)\?build$'; then
  build "$@"
fi