#!/bin/sh
#
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

. `dirname $0`/prebuilt-common.sh
PROGDIR=`dirname $0`
PROGNAME=`basename $0`

if [ -z "$ANDROID_PRODUCT_OUT" ] ; then
    echo "ERROR: The environment variable ANDROID_PRODUCT_OUT is not defined!"
fi

# This sets HOST_TAG to linux-x86 or darwin-x86 on 64-bit systems
force_32bit_binaries

# We need to extract the auto-detected platform level to display it in
# the help. Do not barf if we can't do it right now, to keep --help working
# in this case.
#
BUILD_PROP=$ANDROID_PRODUCT_OUT/system/build.prop
if [ ! -f "$BUILD_PROP" ] ; then
    # Use this as default if the build.prop file does not exist
    # We will generate an error after the extract_parameters call
    # in this specific case, but we want --help to work before that.
    PLATFORM=9
else
    PLATFORM=`awk -F '=' '$1 == "ro.build.version.sdk" { print $2; }' $BUILD_PROP`
    if [ $? != 0 ] ; then
        dump "WARNING: Could not extract default platform level from $BUILD_PROP!"
        PLATFORM=9
        dump "Defaulting to $PLATFORM"
    fi
fi

PROGRAM_PARAMETERS="<level> [<level2>...]"
PROGRAM_DESCRIPTION=\
"This script is used to update the NDK's platform headers and system libraries
from those generated after a full build of the Android platform. Run it after
modifying and rebuilding one of the public libraries exposed through the NDK.

The first parameter must be a platform/API level. For example, to update the
NDK definitions for the 'android-9' platform, use one of:

    $PROGNAME android-9
    $PROGNAME 9

You can also use several values to update several levels at once, e.g.:

    $PROGNAME $API_LEVELS

NOTE: The currently auto-detected API level for your build is $PLATFORM,
      but this value may be incorrect if your platform has not been assigned
      a new API level yet.

This script is really in charge of the following tasks:

  1/ Import system headers from \$ANDROID/framework/base/ and other
     locations in the full system source tree.

  2/ Locate system shared libraries from \$ANDROID_PRODUCT_OUT/system/lib
     and convert them into small "shell" .so files that only export the
     same functions and variables. These can be used with the NDK at link
     time, are much smaller, and also do not require the use of special
     linker flags when used with the standalone NDK toolchain
     (i.e. -Wl,--allow-shlib-undefined)

  3/ For each shared library, also generate a list of symbols exported
     by the shell. This makes it easier to see with 'git diff' which
     symbols were added (or even removed) since the last invocation.

  4/ Copy a few system static libraries (libc.a, libm.a, etc...) used
     to generate static executables. As well as a few key object files
     required by the C runtime (e.g. crtbegin_dynamic.o), when needed.

By default, all files are placed under \$ANDROID/development/ndk
but you can override this with the --out-dir=<path> option.

By default, the build-specific platform/API level is autodetected, and
only the files under \$ANDROID_ROOT/development/ndk/platforms/android-<level>/
will be affected. This ensures you don't accidentally overwrite files
corresponding to previous releases.
"

ARCH=arm
register_var_option "--arch=<name>" ARCH "Specify architecture name."

DEVDIR="$ANDROID_NDK_ROOT/../development/ndk"
if [ -d "$DEVDIR" ] ; then
    OUT_DIR=`cd $DEVDIR && pwd`
else
    OUT_DIR=
fi
register_var_option "--out-dir=<path>" OUT_DIR "Specify output directory."

extract_parameters "$@"

if [ -z "$PARAMETERS" ] ; then
    dump "ERROR: Missing required API/platform level. See --help for usage instructions."
    exit 1
fi

# Normalize platform names, i.e. get rid of android- prefix in a list of
# platform levels
#
#  3 android-4 foo-5 -> 3 4 foo-5  (foo-5 is invalid name)
#
# $1: list of platform levels
#
normalize_platforms ()
{
    echo "$@" | tr ' ' '\n' | sed -e 's!android-!!g' | tr '\n' ' '
}

PLATFORMS=`normalize_platforms $PARAMETERS`
log "Target platform levels: $PLATFORMS"

# Check that a given platform level was listed on the command line
# $1: Platform numerical level (e.g. '3')
# returns true if the platform is listed
platform_check ()
{
    echo "$PLATFORMS" | tr ' ' '\n' | fgrep -q "$1"
    if [ $? != 0 ] ; then
        # Not listed, return an error code for 'if'
        return 1
    else
        PLATFORM_ROOT="$OUT_DIR/platforms/android-$1/arch-$ARCH"
        log "Platform root: $PLATFORM_ROOT"
        return 0
    fi
}

# Determine Android build tree root
ANDROID_ROOT=`cd $ANDROID_PRODUCT_OUT/../../../.. && pwd`
log "Android build tree root: $ANDROID_ROOT"
log "Android product out: $ANDROID_PRODUCT_OUT"

case $ARCH in
    arm)
        TOOLCHAIN=arm-linux-androideabi-4.4.3
        PREFIX=arm-linux-androideabi
        ;;
    x86)
        TOOLCHAIN=x86-4.2.1
        PREFIX=i686-android-linux-gnu
        ;;
    *)
        echo "ERROR: Unsupported architecture: $ARCH"
        exit 1
esac


TOOLCHAIN_PREFIX=$ANDROID_NDK_ROOT/toolchains/$TOOLCHAIN/prebuilt/$HOST_TAG/bin/$PREFIX
log "Toolchain prefix: $TOOLCHAIN_PREFIX"

if [ -z "$OUT_DIR" ] ; then
    dump "ERROR: Could not find valid output directory (e.g. \$NDK/../development/ndk)."
    dump "Please use --out-dir=<path> to specify one!"
    exit 1
fi

# Check the platform value and set PLATFORM_ROOT
#

# Normalize the value: android-3 -> android-3   3 -> android-3
PLATFORM=`echo $PLATFORM | sed -e 's!android-!!g'`
PLATFORM=android-$PLATFORM


# Temp file used to list shared library symbol exclusions
# See set_symbol_excludes and extract_shared_library_xxxx functions below
SYMBOL_EXCLUDES=/tmp/ndk-symbol-excludes.txt

# Temp file used to list shared library symbol inclusions, these
# are essentially overrides to the content of SYMBOL_EXCLUDES
SYMBOL_INCLUDES=/tmp/ndk-symbol-includes.txt

# Reset the symbol exclusion list to its default
reset_symbol_excludes ()
{
    # By default, do not export C++ mangled symbol, which all start with _Z
    echo '^_Z' > $SYMBOL_EXCLUDES
    > $SYMBOL_INCLUDES
}

# Add new exclusion patterns to SYMBOL_EXCLUDES
set_symbol_excludes ()
{
    (echo "$@" | tr ' ' '\n') >> $SYMBOL_EXCLUDES
}

# Add new inclusion patterns to SYMBOL_INCLUDES
set_symbol_includes ()
{
    (echo "$@" | tr ' ' '\n') >> $SYMBOL_INCLUDES
}

# Clear symbol exclusion/inclusion files
clear_symbol_excludes ()
{
    rm -f $SYMBOL_EXCLUDES $SYMBOL_INCLUDES
}

# Filter the list of symbols from a file
# $1: path to symbol list file
filter_symbols ()
{
    (grep -v -f $SYMBOL_EXCLUDES $1 && grep -f $SYMBOL_INCLUDES $1) | sort -u
}

#
# Dump the list of dynamic functions exported by a given shared library
# $1: Path to shared library object
extract_shared_library_functions ()
{
    $TOOLCHAIN_PREFIX-readelf -s -D -W $1 | awk '$5 ~ /FUNC/ && $6 ~ /GLOBAL/ && $8 !~ /UND/ { print $9; }' > $TMPC
    filter_symbols $TMPC
}

# Dump the list of global variables exposed by a given shared library
# $1: Path to shared library object
extract_shared_library_variables ()
{
    $TOOLCHAIN_PREFIX-readelf -s -D -W $1 | awk '$5 ~ /OBJECT/ && $6 ~ /GLOBAL/ && $8 !~ /UND/ { print $9; }' > $TMPC
    filter_symbols $TMPC
}

# Generate link library, i.e. a special tiny shell .so that exports the
# same symbols as a reference shared library, and can be used during
# link with the NDK toolchain.
#
# Having these shells allows two things:
#
# - Reduce the size of the NDK release package (some libs are very large)
# - Use the standalone toolchain without -Wl,--allow-shlib-undefined
#
# Note that the list of symbols for each generated library is stored
# under arch-$ARCH/symbols/<libname>.txt
#
# $1: Path to reference shared library
# $2: Path to output shared library (can be the same as $1)
#
generate_shell_library ()
{
    # First, extract the list of functions and variables exported by the
    # reference library.
    local funcs=`extract_shared_library_functions $1`
    local vars=`extract_shared_library_variables $1`
    local numfuncs=`echo $funcs | wc -w`
    local numvars=`echo $vars | wc -w`
    dump "Generating shell library for `basename $1` ($numfuncs functions + $numvars variables)"

    # Now generate a small C source file that contains similarly-named stubs
    echo "/* Auto-generated file, do not edit */" > $TMPC
    local func var
    for func in $funcs; do
        echo "void $func(void) {}" >> $TMPC
    done
    for var in $vars; do
        echo "int $var;" >> $TMPC
    done

    # Build it with our cross-compiler. It will complain about conflicting
    # types for built-in functions, so just shut it up.
    $TOOLCHAIN_PREFIX-gcc -Wl,-shared,-Bsymbolic -nostdlib -o $TMPO $TMPC 1>/dev/null 2>&1
    if [ $? != 0 ] ; then
        dump "ERROR: Can't generate shell library for: $1"
        dump "See the content of $TMPC for details."
        exit 1
    fi

    # Sanity check: the generated shared library must export the same
    # functions and variables, or something is really rotten!
    local newfuncs=`extract_shared_library_functions $TMPO`
    local newvars=`extract_shared_library_variables $TMPO`
    if [ "$newfuncs" != "$funcs" ] ; then
        dump "ERROR: mismatch in generated functions list"
        exit 1
    fi
    if [ "$newvars" != "$vars" ] ; then
        dump "ERROR: mismatch in generated variables list"
        exit 1
    fi

    # Copy to our destination now
    local libdir=`dirname "$2"`
    mkdir -p "$libdir" && cp -f $TMPO "$2"
    if [ $? != 0 ] ; then
        dump "ERROR: Can't copy shell library for: $1"
        dump "target location is: $2"
        exit 1
    fi

    # Write the functions and variables to a symbols file now
    local symdir=`dirname "$libdir"`/symbols
    local symfile="$symdir/`basename $1.txt`"

    dump "Generating symbol file: $symfile"
    mkdir -p "$symdir" && > $symfile
    (echo "$funcs" | tr ' ' '\n') >>     $symfile
    (echo "$vars" | tr ' ' '\n') >> $symfile

    # Clear the export exclusion list
    reset_symbol_excludes
}

# 'Copy' a given system library. This really reads the shared library to
# to generate a small shell version that will be installed at the destination
# $1: Library name (e.g. libEGL.so)
#
copy_system_shared_library ()
{
    local src="$ANDROID_PRODUCT_OUT/system/lib/$1.so"
    if [ ! -f "$src" ] ; then
        dump "ERROR: Missing system library: $src"
        exit 1
    fi
    local dst="$PLATFORM_ROOT/lib/$1.so"
    mkdir -p `dirname "$dst"` &&
    generate_shell_library "$src" "$dst"
}

copy_system_static_library ()
{
    local src="$ANDROID_PRODUCT_OUT/obj/STATIC_LIBRARIES/$1_intermediates/$1.a"
    if [ ! -f "$src" ] ; then
        dump "ERROR: Missing system static library: $src"
        exit 1
    fi
    local dst="$PLATFORM_ROOT/lib/$1.a"
    dump "Copying system static library: $1.a"
    mkdir -p `dirname "$dst"` && cp -f "$src" "$dst"
}

copy_system_object_file ()
{
    local src="$ANDROID_PRODUCT_OUT/obj/lib/$1.o"
    if [ ! -f "$src" ] ; then
        dump "ERROR: Missing system object file: $src"
        exit 1
    fi
    local dst="$PLATFORM_ROOT/lib/$1.o"
    dump "Copying system object file: $1.o"
    mkdir -p `dirname "$dst"` &&
    cp -f "$src" "$dst"
}

# Copy the content of a given directory to $SYSROOT/usr/include
# $1: Source directory
# $2+: List of headers
copy_system_headers ()
{
    local srcdir="$1"
    shift
    local header
    dump "Copying system headers from: $srcdir"
    for header; do
        dump "  $header"
        local src="$srcdir/$header"
        local dst="$PLATFORM_ROOT/../include/$header"
        if [ ! -f "$srcdir/$header" ] ; then
            dump "ERROR: Missing system header: $srcdir/$header"
            exit 1
        fi
        mkdir -p `dirname "$dst"` && cp -f "$src" "$dst"
        if [ $? != 0 ] ; then
            dump "ERROR: Could not copy system header: $src"
            dump "Target location: $dst"
            exit 1
        fi
    done
}

# Copy all headers found under $1
# $1: source directory
copy_system_headers_from ()
{
    local headers=`cd "$1" && find . -type f | sed -e 's!\./!!g'`
    copy_system_headers $1 $headers
}

# Same as copy_system_headers, but for arch-specific files
# $1: Source directory
# $2+: List of headers
copy_arch_system_headers ()
{
    local srcdir="$1"
    shift
    local header
    for header; do
        dump "Copying $arch system header: $header from $srcdir"
        local src="$srcdir/$header"
        local dst="$PLATFORM_ROOT/include/$header"
        if [ ! -f "$srcdir/$header" ] ; then
            dump "ERROR: Missing $ARCH system header: $srcdir/$header"
            exit 1
        fi
        mkdir -p `dirname "$dst"` && cp -f "$src" "$dst"
        if [ $? != 0 ] ; then
            dump "ERROR: Could not copy $ARCH system header: $src"
            dump "Target location: $dst"
            exit 1
        fi
    done
}


# Now do the work

reset_symbol_excludes

# API level 3
if platform_check 3; then
    # Remove a few internal symbols that should not be exposed
    # from the C library (we plan to clean that up soon by using the
    # "hidden" visibility attribute in the near future).
    #
    set_symbol_excludes \
        '^the_' '^dns_' 'load_domain_search_list' 'res_get_dns_changed' \
        '^_resolv_cache' '^_dns_getht' '^_thread_atexit' \
        '^free_malloc_leak_info' 'fake_gmtime_r' 'fake_localtime_r' \
        '^gAllocationsMutex' '^gHashTable' '^gMallocLeakZygoteChild' \

    copy_system_shared_library libc
    copy_system_static_library libc
    copy_system_headers_from $ANDROID_ROOT/bionic/libc/include
    copy_system_headers_from $ANDROID_ROOT/bionic/libc/arch-$ARCH/include

    copy_system_object_file crtbegin_dynamic
    copy_system_object_file crtbegin_static
    copy_system_object_file crtend_android

    copy_system_shared_library libm
    copy_system_static_library libm
    copy_system_headers $ANDROID_ROOT/bionic/libm/include math.h
    copy_arch_system_headers $ANDROID_ROOT/bionic/libm/$ARCH fenv.h

    # The <dlfcn.h> header was already copied from bionic/libc/include
    copy_system_shared_library libdl
    # There is no libdl.a at the moment, we might need one in
    # the future to build gdb-7.1.x though.

    copy_system_shared_library libz
    copy_system_static_library libz
    copy_system_headers $ANDROID_ROOT/external/zlib zconf.h zlib.h

    set_symbol_excludes '^.*'         # exclude everything
    set_symbol_includes '^__android_' # except __android_xxxx functions
    copy_system_shared_library liblog
    copy_system_headers $ANDROID_ROOT/system/core/include android/log.h

    # NOTE: We do not copy the C++ headers, they are part of the NDK
    #        under $NDK/source/cxx-stl. They were separated from the rest
    #        of the platform headers in order to make room for other STL
    #        implementations (e.g. STLport or GNU Libstdc++-v3)
    #
    copy_system_shared_library libstdc++
    copy_system_static_library libstdc++

    # We link gdbserver statically with libthreadb, so there is no point
    # in copying the shared library (which by the way has an unstable ABI
    # anyway).
    copy_system_static_library libthread_db
    copy_system_headers $ANDROID_ROOT/bionic/libthread_db/include thread_db.h

    copy_system_headers $ANDROID_ROOT/dalvik/libnativehelper/include/nativehelper jni.h
fi

# API level 4
if platform_check 4; then
    copy_system_shared_library libGLESv1_CM
    copy_system_headers $ANDROID_ROOT/frameworks/base/opengl/include \
        GLES/gl.h \
        GLES/glext.h \
        GLES/glplatform.h

    copy_system_headers $ANDROID_ROOT/frameworks/base/opengl/include \
        KHR/khrplatform.h
fi

# API level 5
if platform_check 5; then
    copy_system_shared_library libGLESv2
    copy_system_headers $ANDROID_ROOT/frameworks/base/opengl/include \
        GLES2/gl2.h \
        GLES2/gl2ext.h \
        GLES2/gl2platform.h
fi

# API level 8
if platform_check 8; then
    copy_system_shared_library libandroid
    copy_system_headers $ANDROID_ROOT/frameworks/base/native/include \
        android/bitmap.h
fi

# API level 9
if platform_check 9; then
    copy_system_object_file crtbegin_so
    copy_system_object_file crtend_so

    copy_system_shared_library libandroid
    copy_system_headers $ANDROID_ROOT/frameworks/base/native/include \
        android/asset_manager.h \
        android/asset_manager_jni.h \
        android/configuration.h \
        android/input.h \
        android/keycodes.h \
        android/looper.h \
        android/native_activity.h \
        android/native_window.h \
        android/native_window_jni.h \
        android/obb.h \
        android/rect.h \
        android/sensor.h \
        android/storage_manager.h \
        android/window.h

    copy_system_shared_library libEGL
    copy_system_headers $ANDROID_ROOT/frameworks/base/opengl/include \
        EGL/egl.h \
        EGL/eglext.h \
        EGL/eglplatform.h

    set_symbol_excludes '^_' '^MPH_' # remove MPH_to_xxx definitions
    copy_system_shared_library libOpenSLES
    copy_system_headers $ANDROID_ROOT/system/media/opensles/include \
        SLES/OpenSLES.h \
        SLES/OpenSLES_Android.h \
        SLES/OpenSLES_AndroidConfiguration.h \
        SLES/OpenSLES_Platform.h
fi

clear_symbol_excludes

dump "Done!"