File: verify_api.inc.sh

package info (click to toggle)
keyman 18.0.245-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,316 kB
  • sloc: python: 52,784; cpp: 21,278; sh: 7,633; ansic: 4,823; xml: 3,617; perl: 959; makefile: 139; javascript: 138
file content (315 lines) | stat: -rw-r--r-- 10,116 bytes parent folder | download | duplicates (2)
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
#!/usr/bin/env bash
# shellcheck disable=SC2154 # (variables are set in build-utils.sh)

output_log() {
  echo "$1" >&2
  builder_echo "$1"
}

output_ok() {
  echo ":heavy_check_mark: $1" >&2
  builder_echo green "OK: $1"
}

output_warning() {
  echo ":warning: $1" >&2
  builder_echo warning "WARNING: $1"
}

output_error() {
  echo ":x: $1" >&2
  builder_echo error "ERROR: $1"
}

check_api_not_changed() {
  if [[ -z "${BIN_PKG:-}" ]]; then
    output_warning "Skipping check for API change because binary Debian package not specified"
    return
  fi
  # Checks that the API did not change compared to what's documented in the .symbols file
  tmpDir=$(mktemp -d)
  # shellcheck disable=SC2064
  trap "rm -rf \"${tmpDir}\"" ERR
  dpkg -x "${BIN_PKG}" "${tmpDir}"
  mkdir -p debian/tmp/DEBIAN
  if dpkg-gensymbols -v"${VERSION}" -p"${PKG_NAME}" -e"${tmpDir}"/usr/lib/x86_64-linux-gnu/"${LIB_NAME}".so* -c4; then
    output_ok "${LIB_NAME} API didn't change"
  else
    output_error "${LIB_NAME} API changed"
    EXIT_CODE=4
  fi
  cd "${REPO_ROOT}/linux"
  rm -rf "${tmpDir}"
  trap ERR
}

#
# Compare the SHA of the base and head commits for changes to the .symbols file
#
is_symbols_file_changed() {
  local CHANGED_REF CHANGED_BASE
  CHANGED_REF=$(git rev-parse "${GIT_SHA}":"linux/debian/${PKG_NAME}.symbols")
  CHANGED_BASE=$(git rev-parse "${GIT_BASE}":"linux/debian/${PKG_NAME}.symbols")
  if [[ "${CHANGED_REF}" == "${CHANGED_BASE}" ]]; then
    return 1
  fi
  return 0
}

get_changes() {
  local WHAT_CHANGED
  WHAT_CHANGED=$(git diff -I "^${LIB_NAME}.so" "$1".."$2" | diffstat -m -t | grep "${PKG_NAME}.symbols" )

  IFS=',' read -r -a CHANGES <<< "${WHAT_CHANGED:-0,0,0}"
}

check_updated_version_number() {
  # Checks that the package version number got updated in the .symbols file if it got changed
  # shellcheck disable=SC2310
  if is_symbols_file_changed; then
    # .symbols file changed, now check if the package version got updated as well
    # Note: We don't check that ALL changes in that file have an updated package version -
    # we hope this gets flagged in code review.
    # Note: This version number check may not match the actual released version, if the branch
    # is out of date when it is merged to the release branch (master/beta/stable-x.y). If this
    # is considered important, then make sure the branch is up to date, and wait for test
    # builds to complete, before merging.
    get_changes "${GIT_BASE}" "${GIT_SHA}"
    INSERTED="${CHANGES[0]}"
    DELETED="${CHANGES[1]}"
    MODIFIED="${CHANGES[2]}"

    if (( DELETED > 0 )) && (( MODIFIED == 0 )) && (( INSERTED == 0)); then
        # If only lines got removed we basically skip this test. A later check will
        # test that the API version got updated.
        output_ok "${PKG_NAME}.symbols file did change but only removed lines"
    else
      local version_base version_head
      version_head=$(get_highest_version_in_symbols_file "${GIT_SHA}")
      version_base=$(get_highest_version_in_symbols_file "${GIT_BASE}")
      if (( $(compare_versions "${version_head}" "${version_base}") > 0 )); then
        output_ok "${PKG_NAME}.symbols file got updated with package version number"
      else
        output_error "${PKG_NAME}.symbols file got changed without changing the package version number of the symbol"
        EXIT_CODE=1
      fi
    fi
  else
    output_ok "${PKG_NAME}.symbols file didn't change"
  fi
}

compare_versions() {
  local first_parts second_parts
  IFS='.' read -r -a first_parts <<< "$1"
  IFS='.' read -r -a second_parts <<< "$2"
  if (( first_parts[0] < second_parts[0] )); then
    echo -1
  elif (( first_parts[0] > second_parts[0] )); then
    echo 1
  elif (( first_parts[1] < second_parts[1] )); then
    echo -1
  elif (( first_parts[1] > second_parts[1] )); then
    echo 1
  elif (( first_parts[2] < second_parts[2] )); then
    echo -1
  elif (( first_parts[2] > second_parts[2] )); then
    echo 1
  else
    echo 0
  fi
}

get_highest_version_in_symbols_file() {
  local sha="$1"
  local symbol_lines line line_version
  local max_version=0

  tmpfile=$(mktemp)
  if ! git cat-file blob "${sha}:linux/debian/${PKG_NAME}.symbols" > "${tmpfile}" 2>/dev/null; then
    rm "${tmpfile}"
    return 1
  fi

  # Start with fourth line which is where symbols start
  mapfile -s 3 symbol_lines < "${tmpfile}"
  for line in "${symbol_lines[@]}"; do
    #  km_core_actions_dispose@Base 17.0.197
    line_version=${line##* }
    if (( $(compare_versions "${line_version}" "${max_version}") > 0 )); then
      max_version="${line_version}"
    fi
  done

  echo "${max_version}"

  rm "${tmpfile}"
  return 0
}

get_api_version_in_symbols_file() {
  # Retrieve symbols file at commit $1 and extract "1" from
  # "libkeymancore.so.1 libkeymancore1 #MINVER#"
  local firstline tmpfile
  local sha="$1"

  tmpfile=$(mktemp)
  if ! git cat-file blob "${sha}:linux/debian/${PKG_NAME}.symbols" > "${tmpfile}" 2>/dev/null; then
    rm "${tmpfile}"
    echo "-1"
    return
  fi

  firstline="$(head -1 "${tmpfile}")"
  firstline="${firstline#"${LIB_NAME}".so.}"
  firstline="${firstline%% *}"

  rm "${tmpfile}"
  echo "${firstline}"
}

get_api_version_from_core() {
  # Retrieve CORE_API_VERSION.md from commit $1 and extract major version
  # number ("1") from "1.0.0"
  local api_version tmpfile
  local sha="$1"
  tmpfile=$(mktemp)

  if ! git cat-file blob "${sha}:core/CORE_API_VERSION.md" > "${tmpfile}" 2>/dev/null; then
    rm "${tmpfile}"
    echo "-1"
    return
  fi

  api_version=$(cat "${tmpfile}")
  api_version=${api_version%%.*}

  rm "${tmpfile}"
  echo "${api_version}"
}

# Check if the API version got updated
# Returns:
#   0 - if the API version got updated
#   1 - the .symbols file got changed but the API version didn't get updated
#   2 - if we're in the alpha tier and the API version got updated since
#       the last stable version
# NOTE: it is up to the caller to check if this is a major version
# change that requires an API version update.
# Check if the API version got updated
# Returns:
#   0 - if the API version got updated
#   1 - the .symbols file got changed but the API version didn't get updated
#   2 - if we're in the alpha tier and the API version got updated since
#       the last stable version
# NOTE: it is up to the caller to check if this is a major version
# change that requires an API version update.
is_api_version_updated() {
  local OLD_API_VERSION NEW_API_VERSION TIER
  OLD_API_VERSION=$(get_api_version_in_symbols_file "${GIT_BASE}")
  NEW_API_VERSION=$(get_api_version_in_symbols_file "${GIT_SHA}")
  if (( NEW_API_VERSION > OLD_API_VERSION )); then
    echo "0"
    return
  fi

  # API version didn't change. However, that might be ok if we're in alpha
  # and a major change happened previously.
  TIER=$(cat "${REPO_ROOT}/TIER.md")
  case ${TIER} in
    alpha)
      local STABLE_VERSION STABLE_API_VERSION STABLE_BRANCH
      STABLE_VERSION=$((${VERSION%%.*} - 1))
      STABLE_BRANCH="origin/stable-${STABLE_VERSION}.0"
      STABLE_API_VERSION=$(get_api_version_in_symbols_file "${STABLE_BRANCH}")
      if (( STABLE_API_VERSION == -1 )); then
        # .symbols file doesn't exist in stable branch, so let's check CORE_API_VERSION.md. That
        # doesn't exist in 16.0 but appeared in 17.0.
        STABLE_API_VERSION=$(get_api_version_from_core "${STABLE_BRANCH}")
        if (( STABLE_API_VERSION == -1 )); then
          # CORE_API_VERSION.md doesn't exist either
          if (( NEW_API_VERSION > 0 )); then
            # .symbols and CORE_API_VERSION.md file don't exist in stable branch; however, we
            # incremented the version number compared to 16.0, so that's ok
            echo "2"
            return
          fi
        fi
      fi
      if (( NEW_API_VERSION > STABLE_API_VERSION )); then
        echo "2"
        return
      fi ;;
    *)
      ;;
  esac

  echo "1"
}

check_for_major_api_changes() {
  # Checks that API version number gets updated if API changes
  local WHAT_CHANGED CHANGES INSERTED DELETED MODIFIED UPDATED

  # shellcheck disable=2310
  if ! is_symbols_file_changed; then
    output_ok "No major API change"
    return
  fi

  get_changes "${GIT_BASE}" "${GIT_SHA}"
  INSERTED="${CHANGES[0]}"
  DELETED="${CHANGES[1]}"
  MODIFIED="${CHANGES[2]}"

  if (( DELETED > 0 )) || (( MODIFIED > 0 )); then
    builder_echo "Major API change: ${DELETED} lines deleted and ${MODIFIED} lines modified"
    UPDATED=$(is_api_version_updated)
    if [[ ${UPDATED} == 1 ]]; then
      output_error "Major API change without updating API version number in ${PKG_NAME}.symbols file"
      EXIT_CODE=2
    elif [[ ${UPDATED} == 2 ]]; then
      output_ok "API version number got previously updated in ${PKG_NAME}.symbols file after major API change; no change within alpha necessary"
    else
      output_ok "API version number got updated in ${PKG_NAME}.symbols file after major API change"
    fi
  elif (( INSERTED > 0 )); then
    output_ok "Minor API change: ${INSERTED} lines added"
    # We currently don't check version number for minor API changes
  else
    output_ok "No major API change"
  fi
}

check_for_api_version_consistency() {
  # Checks that the (major) API version number in the .symbols file and
  # in CORE_API_VERSION.md are the same
  local symbols_version api_version
  symbols_version=$(get_api_version_in_symbols_file "HEAD")
  api_version=$(get_api_version_from_core "HEAD")

  if (( symbols_version == api_version )); then
    output_ok "API version in .symbols file and in CORE_API_VERSION.md is the same"
  else
    output_error "API version in .symbols file and in CORE_API_VERSION.md is different"
    EXIT_CODE=3
  fi
}

verify_api_action() {
  local SONAME
  SONAME=$(get_api_version_from_core "HEAD")
  LIB_NAME=libkeymancore
  PKG_NAME="${LIB_NAME}${SONAME}"
  if [[ ! -f debian/${PKG_NAME}.symbols ]]; then
    output_error "Missing ${PKG_NAME}.symbols file"
    exit 0
  fi

  EXIT_CODE=0
  check_api_not_changed
  check_updated_version_number
  check_for_major_api_changes
  check_for_api_version_consistency
  exit "${EXIT_CODE}"
}