summaryrefslogtreecommitdiff
path: root/chromium/third_party/flatbuffers/src/samples/android/build_apk.sh
blob: 4967ee6ddf2ab391d3ca09854ba3ba1200887c7b (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
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
#!/bin/bash -eu
# Copyright (c) 2013 Google, Inc.
#
# This software is provided 'as-is', without any express or implied
# warranty.  In no event will the authors be held liable for any damages
# arising from the use of this software.
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.
#
# Build, deploy, debug / execute a native Android package based upon
# NativeActivity.

declare -r script_directory=$(dirname $0)
declare -r android_root=${script_directory}/../../../../../../
declare -r script_name=$(basename $0)
declare -r android_manifest=AndroidManifest.xml
declare -r os_name=$(uname -s)

# Minimum Android target version supported by this project.
: ${BUILDAPK_ANDROID_TARGET_MINVERSION:=10}
# Directory containing the Android SDK
# (http://developer.android.com/sdk/index.html).
: ${ANDROID_SDK_HOME:=}
# Directory containing the Android NDK
# (http://developer.android.com/tools/sdk/ndk/index.html).
: ${NDK_HOME:=}

# Display script help and exit.
usage() {
  echo "
Build the Android package in the current directory and deploy it to a
connected device.

Usage: ${script_name} \\
         [ADB_DEVICE=serial_number] [BUILD=0] [DEPLOY=0] [RUN_DEBUGGER=1] \
         [LAUNCH=0] [SWIG_BIN=swig_binary_directory] [SWIG_LIB=swig_include_directory] [ndk-build arguments ...]

ADB_DEVICE=serial_number:
  serial_number specifies the device to deploy the built apk to if multiple
  Android devices are connected to the host.
BUILD=0:
  Disables the build of the package.
DEPLOY=0:
  Disables the deployment of the built apk to the Android device.
RUN_DEBUGGER=1:
  Launches the application in gdb after it has been deployed.  To debug in
  gdb, NDK_DEBUG=1 must also be specified on the command line to build a
  debug apk.
LAUNCH=0:
  Disable the launch of the apk on the Android device.
SWIG_BIN=swig_binary_directory:
  The directory where the SWIG binary lives. No need to set this if SWIG is
  installed and point to from your PATH variable.
SWIG_LIB=swig_include_directory:
  The directory where SWIG shared include files are, usually obtainable from
  commandline with \"swig -swiglib\". No need to set this if SWIG is installed
  and point to from your PATH variable.
ndk-build arguments...:
  Additional arguments for ndk-build.  See ndk-build -h for more information.
" >&2
  exit 1
}

# Get the number of CPU cores present on the host.
get_number_of_cores() {
  case ${os_name} in
    Darwin)
      sysctl hw.ncpu | awk '{ print $2 }'
      ;;
    CYGWIN*|Linux)
      awk '/^processor/ { n=$3 } END { print n + 1 }' /proc/cpuinfo
      ;;
    *)
      echo 1
      ;;
  esac
}

# Get the package name from an AndroidManifest.xml file.
get_package_name_from_manifest() {
  xmllint --xpath 'string(/manifest/@package)' "${1}"
}

# Get the library name from an AndroidManifest.xml file.
get_library_name_from_manifest() {
  echo "\
setns android=http://schemas.android.com/apk/res/android
xpath string(/manifest/application/activity\
[@android:name=\"android.app.NativeActivity\"]/meta-data\
[@android:name=\"android.app.lib_name\"]/@android:value)" |
  xmllint --shell "${1}" | awk '/Object is a string/ { print $NF }'
}

# Get the number of Android devices connected to the system.
get_number_of_devices_connected() {
  adb devices -l | \
    awk '/^..*$/ { if (p) { print $0 } }
         /List of devices attached/ { p = 1 }' | \
    wc -l
  return ${PIPESTATUS[0]}
}

# Kill a process and its' children.  This is provided for cygwin which
# doesn't ship with pkill.
kill_process_group() {
  local parent_pid="${1}"
  local child_pid=
  for child_pid in $(ps -f | \
                     awk '{ if ($3 == '"${parent_pid}"') { print $2 } }'); do
    kill_process_group "${child_pid}"
  done
  kill "${parent_pid}" 2>/dev/null
}

# Find and run "adb".
adb() {
  local adb_path=
  for path in "$(which adb 2>/dev/null)" \
              "${ANDROID_SDK_HOME}/sdk/platform-tools/adb" \
              "${android_root}/prebuilts/sdk/platform-tools/adb"; do
    if [[ -e "${path}" ]]; then
      adb_path="${path}"
      break
    fi
  done
  if [[ "${adb_path}" == "" ]]; then
    echo -e "Unable to find adb." \
           "\nAdd the Android ADT sdk/platform-tools directory to the" \
           "PATH." >&2
    exit 1
  fi
  "${adb_path}" "$@"
}

# Find and run "android".
android() {
  local android_executable=android
  if echo "${os_name}" | grep -q CYGWIN; then
    android_executable=android.bat
  fi
  local android_path=
  for path in "$(which ${android_executable})" \
              "${ANDROID_SDK_HOME}/sdk/tools/${android_executable}" \
              "${android_root}/prebuilts/sdk/tools/${android_executable}"; do
    if [[ -e "${path}" ]]; then
      android_path="${path}"
      break
    fi
  done
  if [[ "${android_path}" == "" ]]; then
    echo -e "Unable to find android tool." \
           "\nAdd the Android ADT sdk/tools directory to the PATH." >&2
    exit 1
  fi
  # Make sure ant is installed.
  if [[ "$(which ant)" == "" ]]; then
    echo -e "Unable to find ant." \
            "\nPlease install ant and add to the PATH." >&2
    exit 1
  fi

  "${android_path}" "$@"
}

# Find and run "ndk-build"
ndkbuild() {
  local ndkbuild_path=
  for path in "$(which ndk-build 2>/dev/null)" \
              "${NDK_HOME}/ndk-build" \
              "${android_root}/prebuilts/ndk/current/ndk-build"; do
    if [[ -e "${path}" ]]; then
      ndkbuild_path="${path}"
      break
    fi
  done
  if [[ "${ndkbuild_path}" == "" ]]; then
    echo -e "Unable to find ndk-build." \
            "\nAdd the Android NDK directory to the PATH." >&2
    exit 1
  fi
  "${ndkbuild_path}" "$@"
}

# Get file modification time of $1 in seconds since the epoch.
stat_mtime() {
  local filename="${1}"
  case ${os_name} in
    Darwin) stat -f%m "${filename}" 2>/dev/null || echo 0 ;;
    *) stat -c%Y "${filename}" 2>/dev/null || echo 0 ;;
  esac
}

# Build the native (C/C++) build targets in the current directory.
build_native_targets() {
  # Save the list of output modules in the install directory so that it's
  # possible to restore their timestamps after the build is complete.  This
  # works around a bug in ndk/build/core/setup-app.mk which results in the
  # unconditional execution of the clean-installed-binaries rule.
  restore_libraries="$(find libs -type f 2>/dev/null | \
                       sed -E 's@^libs/(.*)@\1@')"

  # Build native code.
  ndkbuild -j$(get_number_of_cores) "$@"

  # Restore installed libraries.
  # Obviously this is a nasty hack (along with ${restore_libraries} above) as
  # it assumes it knows where the NDK will be placing output files.
  (
    IFS=$'\n'
    for libpath in ${restore_libraries}; do
      source_library="obj/local/${libpath}"
      target_library="libs/${libpath}"
      if [[ -e "${source_library}" ]]; then
        cp -a "${source_library}" "${target_library}"
      fi
    done
  )
}

# Select the oldest installed android build target that is at least as new as
# BUILDAPK_ANDROID_TARGET_MINVERSION.  If a suitable build target isn't found,
# this function prints an error message and exits with an error.
select_android_build_target() {
  local -r android_targets_installed=$( \
    android list targets | \
    awk -F'"' '/^id:.*android/ { print $2 }')
  local android_build_target=
  for android_target in $(echo "${android_targets_installed}" | \
                          awk -F- '{ print $2 }' | sort -n); do
    local isNumber='^[0-9]+$'
    # skip preview API releases e.g. 'android-L'
    if [[ $android_target =~ $isNumber ]]; then
      if [[ $((android_target)) -ge \
          $((BUILDAPK_ANDROID_TARGET_MINVERSION)) ]]; then
        android_build_target="android-${android_target}"
        break
      fi
    # else
      # The API version is a letter, so skip it.
    fi
  done
  if [[ "${android_build_target}" == "" ]]; then
    echo -e \
      "Found installed Android targets:" \
      "$(echo ${android_targets_installed} | sed 's/ /\n  /g;s/^/\n  /;')" \
      "\nAndroid SDK platform" \
      "android-$((BUILDAPK_ANDROID_TARGET_MINVERSION))" \
      "must be installed to build this project." \
      "\nUse the \"android\" application to install API" \
      "$((BUILDAPK_ANDROID_TARGET_MINVERSION)) or newer." >&2
    exit 1
  fi
  echo "${android_build_target}"
}

# Sign unsigned apk $1 and write the result to $2 with key store file $3 and
# password $4.
# If a key store file $3 and password $4 aren't specified, a temporary
# (60 day) key is generated and used to sign the package.
sign_apk() {
  local unsigned_apk="${1}"
  local signed_apk="${2}"
  if [[ $(stat_mtime "${unsigned_apk}") -gt \
          $(stat_mtime "${signed_apk}") ]]; then
    local -r key_alias=$(basename ${signed_apk} .apk)
    local keystore="${3}"
    local key_password="${4}"
    [[ "${keystore}" == "" ]] && keystore="${unsigned_apk}.keystore"
    [[ "${key_password}" == "" ]] && \
      key_password="${key_alias}123456"
    if [[ ! -e ${keystore} ]]; then
      keytool -genkey -v -dname "cn=, ou=${key_alias}, o=fpl" \
        -storepass ${key_password} \
        -keypass ${key_password} -keystore ${keystore} \
        -alias ${key_alias} -keyalg RSA -keysize 2048 -validity 60
    fi
    cp "${unsigned_apk}" "${signed_apk}"
    jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \
      -keystore ${keystore} -storepass ${key_password} \
      -keypass ${key_password} "${signed_apk}" ${key_alias}
  fi
}

# Build the apk $1 for package filename $2 in the current directory using the
# ant build target $3.
build_apk() {
  local -r output_apk="${1}"
  local -r package_filename="${2}"
  local -r ant_target="${3}"
  # Get the list of installed android targets and select the oldest target
  # that is at least as new as BUILDAPK_ANDROID_TARGET_MINVERSION.
  local -r android_build_target=$(select_android_build_target)
  [[ "${android_build_target}" == "" ]] && exit 1
  echo "Building ${output_apk} for target ${android_build_target}" >&2

  # Create / update build.xml and local.properties files.
  if [[ $(stat_mtime "${android_manifest}") -gt \
          $(stat_mtime build.xml) ]]; then
    android update project --target "${android_build_target}" \
                           -n ${package_filename} --path .
  fi

  # Use ant to build the apk.
  ant -quiet ${ant_target}

  # Sign release apks with a temporary key as these packages will not be
  # redistributed.
  local unsigned_apk="bin/${package_filename}-${ant_target}-unsigned.apk"
  if [[ "${ant_target}" == "release" ]]; then
    sign_apk "${unsigned_apk}" "${output_apk}" "" ""
  fi
}

# Uninstall package $1 and install apk $2 on device $3 where $3 is "-s device"
# or an empty string.  If $3 is an empty string adb will fail when multiple
# devices are connected to the host system.
install_apk() {
  local -r uninstall_package_name="${1}"
  local -r install_apk="${2}"
  local -r adb_device="${3}"
  # Uninstall the package if it's already installed.
  adb ${adb_device} uninstall "${uninstall_package_name}" 1>&2 > /dev/null || \
    true # no error check

  # Install the apk.
  # NOTE: The following works around adb not returning an error code when
  # it fails to install an apk.
  echo "Install ${install_apk}" >&2
  local -r adb_install_result=$(adb ${adb_device} install "${install_apk}")
  echo "${adb_install_result}"
  if echo "${adb_install_result}" | grep -qF 'Failure ['; then
    exit 1
  fi
}

# Launch previously installed package $1 on device $2.
# If $2 is an empty string adb will fail when multiple devices are connected
# to the host system.
launch_package() {
  (
    # Determine the SDK version of Android on the device.
    local -r android_sdk_version=$(
      adb ${adb_device} shell cat system/build.prop | \
      awk -F= '/ro.build.version.sdk/ {
                 v=$2; sub(/[ \r\n]/, "", v); print v
               }')

    # Clear logs from previous runs.
    # Note that logcat does not just 'tail' the logs, it dumps the entire log
    # history.
    adb ${adb_device} logcat -c

    local finished_msg='Displayed '"${package_name}"
    local timeout_msg='Activity destroy timeout.*'"${package_name}"
    # Maximum time to wait before stopping log monitoring.  0 = infinity.
    local launch_timeout=0
    # If this is a Gingerbread device, kill log monitoring after 10 seconds.
    if [[ $((android_sdk_version)) -le 10 ]]; then
      launch_timeout=10
    fi
    # Display logcat in the background.
    # Stop displaying the log when the app launch / execution completes or the
    # logcat
    (
      adb ${adb_device} logcat | \
        awk "
          {
            print \$0
          }

          /ActivityManager.*: ${finished_msg}/ {
            exit 0
          }

          /ActivityManager.*: ${timeout_msg}/ {
            exit 0
          }" &
      adb_logcat_pid=$!;
      if [[ $((launch_timeout)) -gt 0 ]]; then
        sleep $((launch_timeout));
        kill ${adb_logcat_pid};
      else
        wait ${adb_logcat_pid};
      fi
    ) &
    logcat_pid=$!
    # Kill adb logcat if this shell exits.
    trap "kill_process_group ${logcat_pid}" SIGINT SIGTERM EXIT

    # If the SDK is newer than 10, "am" supports stopping an activity.
    adb_stop_activity=
    if [[ $((android_sdk_version)) -gt 10 ]]; then
      adb_stop_activity=-S
    fi

    # Launch the activity and wait for it to complete.
    adb ${adb_device} shell am start ${adb_stop_activity} -n \
      ${package_name}/android.app.NativeActivity

    wait "${logcat_pid}"
  )
}

# See usage().
main() {
  # Parse arguments for this script.
  local adb_device=
  local ant_target=release
  local disable_deploy=0
  local disable_build=0
  local run_debugger=0
  local launch=1
  local build_package=1
  for opt; do
    case ${opt} in
      # NDK_DEBUG=0 tells ndk-build to build this as debuggable but to not
      # modify the underlying code whereas NDK_DEBUG=1 also builds as debuggable
      # but does modify the code
      NDK_DEBUG=1) ant_target=debug ;;
      NDK_DEBUG=0) ant_target=debug ;;
      ADB_DEVICE*) adb_device="$(\
        echo "${opt}" | sed -E 's/^ADB_DEVICE=([^ ]+)$/-s \1/;t;s/.*//')" ;;
      BUILD=0) disable_build=1 ;;
      DEPLOY=0) disable_deploy=1 ;;
      RUN_DEBUGGER=1) run_debugger=1 ;;
      LAUNCH=0) launch=0 ;;
      clean) build_package=0 disable_deploy=1 launch=0 ;;
      -h|--help|help) usage ;;
    esac
  done

  # If a target device hasn't been specified and multiple devices are connected
  # to the host machine, display an error.
  local -r devices_connected=$(get_number_of_devices_connected)
  if [[ "${adb_device}" == "" && $((devices_connected)) -gt 1 && \
        ($((disable_deploy)) -eq 0 || $((launch)) -ne 0 || \
         $((run_debugger)) -ne 0) ]]; then
    if [[ $((disable_deploy)) -ne 0 ]]; then
      echo "Deployment enabled, disable using DEPLOY=0" >&2
    fi
    if [[ $((launch)) -ne 0 ]]; then
     echo "Launch enabled." >&2
    fi
    if [[ $((disable_deploy)) -eq 0 ]]; then
      echo "Deployment enabled." >&2
    fi
    if [[ $((run_debugger)) -ne 0 ]]; then
      echo "Debugger launch enabled." >&2
    fi
    echo "
Multiple Android devices are connected to this host.  Either disable deployment
and execution of the built .apk using:
  \"${script_name} DEPLOY=0 LAUNCH=0\"

or specify a device to deploy to using:
  \"${script_name} ADB_DEVICE=\${device_serial}\".

The Android devices connected to this machine are:
$(adb devices -l)
" >&2
    exit 1
  fi

  if [[ $((disable_build)) -eq 0 ]]; then
    # Build the native target.
    build_native_targets "$@"
  fi

  # Get the package name from the manifest.
  local -r package_name=$(get_package_name_from_manifest "${android_manifest}")
  if [[ "${package_name}" == "" ]]; then
    echo -e "No package name specified in ${android_manifest},"\
            "skipping apk build, deploy"
            "\nand launch steps." >&2
    exit 0
  fi
  local -r package_basename=${package_name/*./}
  local package_filename=$(get_library_name_from_manifest ${android_manifest})
  [[ "${package_filename}" == "" ]] && package_filename="${package_basename}"

  # Output apk name.
  local -r output_apk="bin/${package_filename}-${ant_target}.apk"

  if [[ $((disable_build)) -eq 0 && $((build_package)) -eq 1 ]]; then
    # Build the apk.
    build_apk "${output_apk}" "${package_filename}" "${ant_target}"
  fi

  # Deploy to the device.
  if [[ $((disable_deploy)) -eq 0 ]]; then
    install_apk "${package_name}" "${output_apk}" "${adb_device}"
  fi

  if [[ "${ant_target}" == "debug" && $((run_debugger)) -eq 1 ]]; then
    # Start debugging.
    ndk-gdb ${adb_device} --start
  elif [[ $((launch)) -eq 1 ]]; then
    launch_package "${package_name}" "${adb_device}"
  fi
}

main "$@"