1#!/bin/bash
2# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ==============================================================================
16
17# Utility script that handles downloading, extracting, and patching third-party
18# library dependencies for TensorFlow Lite for Microcontrollers.
19# Called with four arguments:
20# 1 - URL to download from.
21# 2 - MD5 checksum to verify the package's integrity. Use md5sum to create one.
22# 3 - Path to new folder to unpack the library into.
23# 4 - Optional patching action name.
24
25set -e
26
27# Patches the Ambiq Micro SDK to work around build issues.
28patch_am_sdk() {
29  local am_dir="${1}"
30  if [ ! -f ${am_dir}/VERSION.txt ]; then
31    echo "Could not find ${am_dir}, skipping AmbiqMicro SDK patch";
32    return;
33  fi
34
35  local src_dir=${am_dir}/boards/apollo3_evb/examples/hello_world/gcc
36  local dest_dir=${am_dir}/boards/apollo3_evb/examples/hello_world/gcc_patched
37
38  rm -rf ${dest_dir}
39  mkdir ${dest_dir}
40
41  cp "${src_dir}/startup_gcc.c" "${dest_dir}/startup_gcc.c"
42  cp "${src_dir}/hello_world.ld" "${dest_dir}/apollo3evb.ld"
43
44  sed -i -e '114s/1024/1024\*20/g' "${dest_dir}/startup_gcc.c"
45  #sed -i -e 's/main/_main/g' "${dest_dir}/startup_gcc.c"
46
47  sed -i -e '3s/hello_world.ld/apollo3evb.ld/g' "${dest_dir}/apollo3evb.ld"
48  sed -i -e '3s/startup_gnu/startup_gcc/g' "${dest_dir}/apollo3evb.ld"
49  sed -i -e $'22s/\*(.text\*)/\*(.text\*)\\\n\\\n\\\t\/\* These are the C++ global constructors.  Stick them all here and\\\n\\\t \* then walk through the array in main() calling them all.\\\n\\\t \*\/\\\n\\\t_init_array_start = .;\\\n\\\tKEEP (\*(SORT(.init_array\*)))\\\n\\\t_init_array_end = .;\\\n\\\n\\\t\/\* XXX Currently not doing anything for global destructors. \*\/\\\n/g' "${dest_dir}/apollo3evb.ld"
50  sed -i -e $'70s/} > SRAM/} > SRAM\\\n    \/\* Add this to satisfy reference to symbol "end" from libnosys.a(sbrk.o)\\\n     \* to denote the HEAP start.\\\n     \*\/\\\n   end = .;/g' "${dest_dir}/apollo3evb.ld"
51
52  # Add a delay after establishing serial connection
53  sed -ir -E $'s/    with serial\.Serial\(args\.port, args\.baud, timeout=12\) as ser:/    with serial.Serial(args.port, args.baud, timeout=12) as ser:\\\n        # Patched.\\\n        import time\\\n        time.sleep(0.25)\\\n        # End patch./g' "${am_dir}/tools/apollo3_scripts/uart_wired_update.py"
54
55  # Add CPP include guards to "am_hal_iom.h"
56  sed -i -e '57a\
57  #ifdef __cplusplus // Patch\
58  extern "C" {\
59  #endif // End patch
60  ' "${am_dir}/mcu/apollo3/hal/am_hal_iom.h"
61
62  sed -i -e '836a\
63  #ifdef __cplusplus // Patch\
64  }\
65  #endif // End patch
66  ' "${am_dir}/mcu/apollo3/hal/am_hal_iom.h"
67
68  echo "Finished preparing Apollo3 files"
69}
70
71# Fixes issues with KissFFT.
72patch_kissfft() {
73  sed -i -E $'s@#ifdef FIXED_POINT@// Patched automatically by download_dependencies.sh so default is 16 bit.\\\n#ifndef FIXED_POINT\\\n#define FIXED_POINT (16)\\\n#endif\\\n// End patch.\\\n\\\n#ifdef FIXED_POINT@g' tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h
74
75  sed -i -E '/^#include <sys\/types.h>/d' tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h
76  # Fix for https://github.com/mborgerding/kissfft/issues/20
77  sed -i -E $'s@#ifdef FIXED_POINT@#ifdef FIXED_POINT\\\n#include <stdint.h> /* Patched. */@g' tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h
78
79  sed -i -E "s@#define KISS_FFT_MALLOC malloc@#define KISS_FFT_MALLOC(X) (void*)(0) /* Patched. */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h
80  sed -i -E "s@#define KISS_FFT_FREE free@#define KISS_FFT_FREE(X) /* Patched. */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h
81  sed -ir -E "s@(fprintf.*\);)@/* \1 */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c
82  sed -ir -E "s@(exit.*\);)@return; /* \1 */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c
83  echo "Finished patching kissfft"
84}
85
86build_embarc_mli() {
87  make -j 4 -C ${1}/lib/make TCF_FILE=${2}
88}
89
90setup_zephyr() {
91  command -v virtualenv >/dev/null 2>&1 || {
92    echo >&2 "The required 'virtualenv' tool isn't installed. Try 'pip install virtualenv'."; exit 1;
93  }
94  virtualenv -p python3 ${1}/venv-zephyr
95  . ${1}/venv-zephyr/bin/activate
96  python ${1}/venv-zephyr/bin/pip install -r ${1}/scripts/requirements.txt
97  west init -m https://github.com/zephyrproject-rtos/zephyr.git
98  deactivate
99}
100
101# Main function handling the download, verify, extract, and patch process.
102download_and_extract() {
103  local usage="Usage: download_and_extract URL MD5 DIR [ACTION] [ACTION_PARAM]"
104  local url="${1:?${usage}}"
105  local expected_md5="${2:?${usage}}"
106  local dir="${3:?${usage}}"
107  local action=${4}
108  local action_param1=${5}  # optional action parameter
109  local tempdir=$(mktemp -d)
110  local tempdir2=$(mktemp -d)
111  local tempfile=${tempdir}/temp_file
112  local curl_retries=5
113
114  # Destionation already downloaded.
115  if [ -d ${dir} ]; then
116      exit 0
117  fi
118
119  command -v curl >/dev/null 2>&1 || {
120    echo >&2 "The required 'curl' tool isn't installed. Try 'apt-get install curl'."; exit 1;
121  }
122
123  echo "downloading ${url}" >&2
124  mkdir -p "${dir}"
125  # We've been seeing occasional 56 errors from valid URLs, so set up a retry
126  # loop to attempt to recover from them.
127  for (( i=1; i<=$curl_retries; ++i )); do
128    # We have to use this approach because we normally halt the script when
129    # there's an error, and instead we want to catch errors so we can retry.
130    set +ex
131    curl -LsS --fail --retry 5 "${url}" > ${tempfile}
132    CURL_RESULT=$?
133    set -ex
134
135    # Was the command successful? If so, continue.
136    if [[ $CURL_RESULT -eq 0 ]]; then
137      break
138    fi
139
140    # Keep trying if we see the '56' error code.
141    if [[ ( $CURL_RESULT -ne 56 ) || ( $i -eq $curl_retries ) ]]; then
142      echo "Error $CURL_RESULT downloading '${url}'"
143      exit 1
144    fi
145    sleep 2
146  done
147
148  # Check that the file was downloaded correctly using a checksum.
149  DOWNLOADED_MD5=$(openssl dgst -md5 ${tempfile} | sed 's/.* //g')
150  if [ ${expected_md5} != ${DOWNLOADED_MD5} ]; then
151    echo "Checksum error for '${url}'. Expected ${expected_md5} but found ${DOWNLOADED_MD5}"
152    exit 1
153  fi
154
155  # delete anything after the '?' in a url that may mask true file extension
156  url=$(echo "${url}" | sed "s/\?.*//")
157
158  if [[ "${url}" == *gz ]]; then
159    tar -C "${dir}" --strip-components=1 -xzf ${tempfile}
160  elif [[ "${url}" == *tar.xz ]]; then
161    tar -C "${dir}" --strip-components=1 -xf ${tempfile}
162  elif [[ "${url}" == *bz2 ]]; then
163    curl -Ls "${url}" > ${tempdir}/tarred.bz2
164    tar -C "${dir}" --strip-components=1 -xjf ${tempfile}
165  elif [[ "${url}" == *zip ]]; then
166    unzip ${tempfile} -d ${tempdir2} 2>&1 1>/dev/null
167    # If the zip file contains nested directories, extract the files from the
168    # inner directory.
169    if [ $(find $tempdir2/* -maxdepth 0 | wc -l) = 1 ] && [ -d $tempdir2/* ]; then
170      # unzip has no strip components, so unzip to a temp dir, and move the
171      # files we want from the tempdir to destination.
172      cp -R ${tempdir2}/*/* ${dir}/
173    else
174      cp -R ${tempdir2}/* ${dir}/
175    fi
176  else
177    echo "Error unsupported archive type. Failed to extract tool after download."
178    exit 1
179  fi
180  rm -rf ${tempdir2} ${tempdir}
181
182  # Delete any potential BUILD files, which would interfere with Bazel builds.
183  find "${dir}" -type f -name '*BUILD' -delete
184
185  if [[ ${action} == "patch_am_sdk" ]]; then
186    patch_am_sdk ${dir}
187  elif [[ ${action} == "patch_kissfft" ]]; then
188    patch_kissfft ${dir}
189  elif [[ ${action} == "patch_cifar10_dataset" ]]; then
190    patch_cifar10_dataset ${dir}
191  elif [[ ${action} == "build_embarc_mli" ]]; then
192    if [[ "${action_param1}" == *.tcf ]]; then
193      cp ${action_param1} ${dir}/hw/arc.tcf
194      build_embarc_mli ${dir} ../../hw/arc.tcf
195    else
196      build_embarc_mli ${dir} ${action_param1}
197    fi
198  elif [[ ${action} == "setup_zephyr" ]]; then
199    setup_zephyr ${dir}
200  elif [[ ${action} ]]; then
201    echo "Unknown action '${action}'"
202    exit 1
203  fi
204}
205
206download_and_extract "$1" "$2" "$3" "$4" "$5"
207