Skip to content

Instantly share code, notes, and snippets.

@ssfang
Last active August 26, 2022 00:44
Show Gist options
  • Save ssfang/43bce19da2bcc4e71a0faf58f0228bc3 to your computer and use it in GitHub Desktop.
Save ssfang/43bce19da2bcc4e71a0faf58f0228bc3 to your computer and use it in GitHub Desktop.
Get NDKPath
#!/bin/bash --posix
# Avoid an error about function xx() statement on some os, e.g. on ubuntu, Syntax error: "(" unexpected
# http://ubuntuforums.org/archive/index.php/t-499045.html
#filename: ndk-path.sh
###############################################
# . ./ndk-path.sh -tla 4.9 9 mips > result.txt
###############################################
#######################################
# Get the toolchain prefix for the subdirectory under $NDK_ROOT/toolchains and for binary file prefix
# under $NDK_ROOT/toolchains/<TOOLCHAIN_NAME>/prebuilt/<NDK4OS_DIRNAME>/bin
# Globals:
# aarch64 x86_64 x86 arm*
# TOOLCHAIN_PREFIX_ARCH aarch64 x86_64 x86 arm
# TOOLCHAIN_PREFIX_OS -linux-android- - - -linux-androideabi-
# TOOLCHAIN_BIN_PREFIX_ARCH aarch64 x86_64 i686 arm
# TOOLCHAIN_BIN_PREFIX_OS linux-android linux-android linux-android linux-androideabi
# Arguments:
# arch: [arm64, x86_64, mips64, x86, mips, arm*]
# Returns:
# None
#######################################
function initToolchainPrefix(){
## TOOLCHAIN_PREFIX=$tc_os$tc_arch
## TOOLCHAIN_BIN_PREFIX=$TOOLCHAIN_BIN_PREFIX_ARCH-$TOOLCHAIN_BIN_PREFIX_OS-
local tc_os tc_arch
case "$1" in
arm64)
tc_arch='aarch64'
;;
x86_64)
tc_arch='x86_64'
tc_os=-
;;
mips64)
tc_arch='mips64el'
;;
x86) ## toolchains\x86-4.9\prebuilt\windows-x86_64\bin\i686-linux-android-gcc.exe
tc_arch='x86'
tc_os=-
TOOLCHAIN_BIN_PREFIX_ARCH='i686'
;;
mips)
tc_arch='mipsel'
;;
arm*) # # arm|armeabi|armeabi-v7a
tc_arch='arm'
tc_os='-linux-androideabi-'
TOOLCHAIN_BIN_PREFIX_OS='linux-androideabi'
;;
*)
tc_arch='unknown'
;;
esac
## Set if declared but not set or is null
TOOLCHAIN_PREFIX_ARCH=$tc_arch
: ${TOOLCHAIN_BIN_PREFIX_ARCH:=$tc_arch}
TOOLCHAIN_PREFIX_OS=${tc_os:='-linux-android-'}
: ${TOOLCHAIN_BIN_PREFIX_OS:='linux-android'}
}
## @param $1 toolchain 4.6, 4.8, 4.9 for gcc; clang3.3, clang3.4 for clang
## @global TOOLCHAIN_NAME
function initToolchainName(){
if [ -z "$1" ];then
## strip tailing dash
TOOLCHAIN_NAME=$TOOLCHAIN_PREFIX_ARCH${TOOLCHAIN_PREFIX_OS%-}
## check exist
local path=`ls -d "$NDK_ROOT/toolchains/$TOOLCHAIN_NAME-"[1-9]* | head -n1`
TOOLCHAIN_NAME=${path##*[/\\]}
elif [[ "$1" == *-* ]]; then
TOOLCHAIN_NAME=$1
else
TOOLCHAIN_NAME=$TOOLCHAIN_PREFIX_ARCH$TOOLCHAIN_PREFIX_OS$1
fi
}
#######################################
# Determine the toolchain prebuilt directory
# Globals:
# NDK4OS [darwin, linux, windows, unknown]
# Arguments:
# None
# Returns:
# the toolchain bin parent directory name: [windows, windows-x86_64, linux-x86, linux-x86_64]
#######################################
function detectNdkOsDirName(){
# $MACHTYPE machine type that identifies the system hardware.
# UNAME_M = `(uname -m) 2>/dev/null` || UNAME_M=unknown
local ndk4os_suffix
if [[ "`(uname -m) 2>/dev/null`" == *64 ]]; then
ndk4os_suffix=-x86_64
fi
case "`uname -s`" in
Darwin)
NDK4OS='darwin'
: ${ndk4os_suffix=-x86}
;;
Linux)
NDK4OS='linux'
: ${ndk4os_suffix=-x86}
;;
CYGWIN*|MINGW32*|MSYS*)
NDK4OS='windows'
;;
*)
NDK4OS='unknown'
exit 1
;;
esac
echo "$NDK4OS$ndk4os_suffix"
}
function blue_echo {
#Foreground: Sky blue / Cyan
echo && echo -en "\033[36m" && echo -n "$1" && echo -e "\033[0m"
}
#######################################
# Check the subdirectory under the NDK_ROOT directory, special when subpath=''
# @param_$1 label
# @param_$2 subpath
# @returns TRUE if exists
#######################################
function check_dir {
local path;
if [ -z "$2" ]; then
blue_echo "NDK_ROOT=$NDK_ROOT"
path=$NDK_ROOT
else
blue_echo "$1=\$NDK_ROOT/$2"
path=$NDK_ROOT/$2
fi
echo -n "Checking whether $1 exist:"
if [ -d "$path" ];then
echo -e "\033[32m Yes \033[0m"
else
echo -e "\033[31m No \033[0m" #Foreground: red
return 1
fi
}
## api_level, api_arch, toolchain
###################
## @see http://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
## @see getopt
# Supported option formats:
# -x x_value
# --long_x x_value
# -xy x_value y_value Next option must remove a prefix '-'
# -x-long_y x_value y_value
# @see http://tldp.org/LDP/abs/html/parameter-substitution.html#PARAMSUBREF
# @see http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion
###############
while [[ $# > 0 ]]
do
key="$1"
while [[ ${key+x} ]] #
do
case $key in
-l*|--level)
api_level="$2"
echo "api_level=$api_level"
;;
-a*|--arch)
api_arch="$2"
echo "api_arch=$api_arch"
;;
-t*|--toolchain*)
toolchain="$2"
;;
-h*|--help*)
echo "\
Usage: [option value] [option-list corresponding-value-list]...
For option-list, subsequent options must remove a prefix '-',
e.g. -a-level arm 12, -tla 4.9 9 x86
Options:
-l|--level NDK_API_LEVEL, default:9, ls \$NDK_ROOT/platforms
-a|--arch NDK_API_ARCH, default:arm, values:
[arm64, x86_64, mips64, x86, mips, arm*]
-t|--toolchain version of gcc or full name, ls \$NDK_ROOT/toolchains
-h|--help print short or long help message and exit
"
exit
;;
*)
# unknown option
echo Unknown option: $key #1>&2
exit 10 # either this: my preferred way to handle unknown options
break # or this: do this to signal the option has been handled (if exit isn't used)
;;
esac
shift
# prepare for next option in this key, if any
[[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
done ## end of while
shift # option(s) fully processed, proceed to next input argument
done
# 决定NDK根目录,如 /opt/android-ndk-r10c
: ${NDK_ROOT:=${ANDROID_NDK_HOME:-/opt/android-ndk}}
check_dir 'the environment variable NDK_ROOT' || exit 1
# 决定API等级和要编译二进制的架构
: ${api_level:=9}
: ${api_arch:=arm}
API_LEVEL_DIRNAME=android-$api_level
API_ARCH_DIRNAME=arch-$api_arch
# 决定工具链路径上的一些子目录名字
initToolchainPrefix $api_arch && initToolchainName $toolchain
: ${NDK4OS_DIRNAME:=`detectNdkOsDirName`}
API_SUBPATH=platforms/$API_LEVEL_DIRNAME/$API_ARCH_DIRNAME
TC_BIN_SUBPATH=toolchains/$TOOLCHAIN_NAME/prebuilt/$NDK4OS_DIRNAME/bin
check_dir 'ABI_PATH' $API_SUBPATH || exit 1
check_dir 'TOOLCHAIN_BIN_PATH' $TC_BIN_SUBPATH || exit 1
blue_echo "NDK_MAKE=$NDK_ROOT/prebuilt/$NDK4OS_DIRNAME/bin/make.exe"
# 检查windows上运行cygwin的情况,需要注意在Windows上编译可能比较慢,也许相当慢
OS_NAME=$(uname -s)
if [[ "$OS_NAME" == CYGWIN* ]];then
export PATH="`cygpath $NDK_ROOT/$TC_BIN_SUBPATH`":${oldPATH=$PATH}
##@TODO like --windows, windows form path but with regular slashes (C:/WINNT)
export NDK_ROOT=`cygpath -m $NDK_ROOT`
# ln -s (target exists) (link is made)
# mklink (link is made) (target exists)
## which make.exe to use? of NDK or cygwin...
## http://stackoverflow.com/questions/3648819/how-to-make-symbolic-link-with-cygwin-in-windows-7
## https://cygwin.com/cygwin-ug-net/using-cygwinenv.html
## https://cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks
# export CYGWIN="winsymlinks" # ln -s: The shortcut style symlinks with file extension '.lnk'
# export CYGWIN="winsymlinks:native" # ln -s: plain text file
# hook ln and propagate it to other scripts to pollute the environment of subsequently executed commands
function ln(){
if [[ "-s" == "$1" ]]; then
cmd /C mklink /H "$(cygpath -aw "$3")" "`cygpath -aw "$2"`"
else
echo -e "\033[32m >>ln $* \033[0m"
command ln "$*"
fi
}
export -f ln
else
export PATH="$NDK_ROOT/$TC_BIN_SUBPATH":${OLDPATH=$PATH}
fi
echo "OS_NAME=$OS_NAME"
# 以下是配置编译环境
export SYSROOT=$NDK_ROOT/$API_SUBPATH
echo "SYSROOT=$SYSROOT"
tc_bin_prefix=$TOOLCHAIN_BIN_PREFIX_ARCH-$TOOLCHAIN_BIN_PREFIX_OS-
export CC=${tc_bin_prefix}gcc # C编译程序。默认是"cc"
export CXX=${tc_bin_prefix}g++ # C++编译程序。默认是"g++"
export CPP=${tc_bin_prefix}cpp # C/C++预处理器。默认是"$(CC) -E"
export AR=${tc_bin_prefix}ar # 函数库打包程序,可创建静态库.a文档。默认是"ar"
export AS=${tc_bin_prefix}as # 汇编程序。默认是"as"
export NM=${tc_bin_prefix}nm
export LD=${tc_bin_prefix}ld
export RANLIB=${tc_bin_prefix}ranlib # ar -s
# export FC=${tc_bin_prefix}fc # Fortran编译器。默认是"f77"
# export PC=${tc_bin_prefix}pc # Pascal语言编译器。默认是"pc"。
# export YACC=${tc_bin_prefix}yacc # Yacc文法分析器。默认是"yacc"。
#ARFLAGS # 函数库打包程序的命令行参数。默认值是"rv"
#ASFLAGS # 汇编程序的命令行参数
#FFLAGS # Fortran编译器的命令行参数
#PFLAGS # Pascal编译器的命令行参数
#YFLAGS # Yacc文法分析器的命令行参数
TOOLCHAIN_INC=$NDK_ROOT/toolchains/$TOOLCHAIN_NAME/prebuilt/$NDK4OS_DIRNAME
LOCAL_CFLAGS="--sysroot=$SYSROOT -I${SYSROOT}/usr/include -Wall -Wextra" # -I${TOOLCHAIN_INC}/include
export CFLAGS="${oldCFLAGS=$CFLAGS} $LOCAL_CFLAGS" # C编译程序的命令行参数
export CPPFLAGS="${oldCPPFLAGS=$CPPFLAGS} $LOCAL_CFLAGS" # C/C++预处理器的命令行参数
export CXXFLAGS="${oldCXXFLAGS=$CXXFLAGS} -Os" # C++编译程序的命令行参数
export LDFLAGS="${oldLDFLAGS=$LDFLAGS} -L${SYSROOT}/usr/lib -L${TOOLCHAIN_INC}/lib" # 链接器的命令行参数
# autoreconf -vfi && ./configure && make && make install
# echo
# if [ -f "./configure" ];then
# echo "./configure found";
# else
# echo "Start updating generated configuration files..."
# autoreconf -v -f -i # remake to get configure
# echo "autoreconf done";
# fi
# 生成 Makefile 文件
# --host=HOST # 编译后的代码在哪个平台执行
# --build=BUILD # 代码在哪个平台上编译,我把它称作本机Native machine,一般就是平时写代码的pc
# --target=TARGET # 一般仅用于编译一个编译器,可选,默认=HOST
# 一般 --build 可以通过config.guess脚本探测,如我在64位Windows上装了32位的cygwin,然后运行它,输出i686-pc-cygwin。
# 比如:
# 1. 在Linux上使用NDK目录下arm-linux-androideabi工具链编译代码生成目标二进制在Android上运行
# --build=x86_64-unknown-linux-gnu --host=arm-linux-androideabi
# 2. 在i686机器上,想编译一个在 Android-arm 上运行的交叉工具链 gcc ,然而这个工具链是为了在 Android 上能编译x86的程序,这样配置也许这样:
# --build=i686-pc-linux-gnu --host=arm-linux-androideabi --target=x86-xx
# file命令看结果,里面包含的是--host信息
# System types:
# --build=BUILD configure for building on BUILD [guessed]
# --host=HOST cross-compile to build programs to run on HOST [BUILD]
# --target=TARGET configure for building compilers for TARGET [HOST]
# echo && echo "Start configuring the makefile..."
# This is just an empty directory where I want the built objects to be installed
# export CONFIGURE_PREFIX=.
# ./configure --host=arm-linux-androideabi --prefix=${CONFIGURE_PREFIX}
blue_echo "Suggestion: ./configure --host=$TOOLCHAIN_BIN_PREFIX_ARCH-$TOOLCHAIN_BIN_PREFIX_OS --prefix=$TOOLCHAIN_BIN_PREFIX_ARCH-out"
Project
  +--jni
  |   |--Application.mk (only one for each directory, optional)
  |   |--Android.mk (only one for each directory, required)
  |   |--x.h
  |   `--x.c
  +--libs (auto-generated)
  |   +--armeabi
  |   |    |-- <module_bin>
  |   |    
  |   +--armeabi-v7a
  |   +--x86
  |   `--<other>
  +--obj (auto-generated)
      +--local
          +--armeabi
          |    +--objs
          |    |   +--<module_dir>
          |    |    
          |    `--<module_file>
          |
          +--armeabi-v7a
          +--x86

进入工程目录编译:cd <project> && ndk-build

  • 其他选项使用事例 *

  • 在任意目录编译某工程: ndk-build -C <project>

  • 清理编译的目标和中间产物:ndk-build clean

  • 出错等查看详细日志:ndk-build V=1

  • 改变目标生成路径:ndk-build NDK_LIBS_OUT=./jniLibs NDK_OUT=./obj,NDK_OUT was added in revision 7c; NDK_LIBS_OUT was added in revision 9。详细查看Change ndk-build output locations

import com.android.build.gradle.tasks.NdkCompile
import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "ss.explorer"
        minSdkVersion 9
        targetSdkVersion 21
    }
    // disabled the NdkCompile task, but before buildTypes
    tasks.withType(NdkCompile) {
        compileTask -> compileTask.enabled = false
    }
    // add a customized NDK build support for an ordinal project
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles.add(file("proguard-rules.pro"))
            //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}// end of android

/* relative to source path(e.g. D:\AndroidStudio\app\src\main)*/
ext.NDK_SOURCE = 'src/main'
ext.NDK_LIBS_OUT = 'jniLibs'

def getNdkDir() {
    def ndk_dir
    if (!ndk_dir) {
        def localProperties = new Properties()
        localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
        ndk_dir = localProperties.getProperty('ndk.dir')
        //Groovy truth says that a null or empty string is false
        if (!ndk_dir) {
            ndk_dir = System.getenv('ANDROID_NDK_HOME');
            if (!ndk_dir) {
                ndk_dir = System.getenv('NDK_ROOT');
                if (!ndk_dir) {
                    throw FileNotFoundException("NDK directory not found in the follow" +
                            " locations: the property ndk.dir in the file local.properties" +
                            ", the envirenment variables ANDROID_NDK_HOME, NDK_ROOT");
                }
            }
        }
        println(ndk_dir)
        ext.ndkDir = ndk_dir
    }
    return ndk_dir
}

def getNdkBuildLocation(){
    def ndkBuildFileName = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build'
    return getNdkDir() ? getNdkDir() + File.separatorChar + ndkBuildFileName : ndkBuildFileName
}

// call regular ndk-build(.cmd) script from app directory
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
    //def ndkDir = project.plugins.findPlugin('com.android.application').getNdkFolder()
    //println(ndkDir)

    /*
        # ndk-build:
            APP_BUILD_SCRIPT path of Android.mk
            NDK_APPLICATION_MK path of Application.mk
        # undocumented, but in ndk source code(core/build-local.mk):
            NDK_APP_OUT(NDK_OUT since revision 7c) means NDK_PROJECT_PATH/obj
            NDK_APP_LIBS_OUT(NDK_LIBS_OUT since revision 9) means NDK_PROJECT_PATH/libs
        Generally NDK_PROJECT_PATH is a directory contains a sub-directory named jni.
     */
    commandLine getNdkBuildLocation(), '-C', file(NDK_SOURCE).absolutePath,
            'NDK_LIBS_OUT=jniLibs'
}

clean.dependsOn 'cleanNative'
task cleanNative(type: Exec) {
    commandLine getNdkBuildLocation(), 'clean', '-C', NDK_SOURCE
}

// Copies the libs to the apk.
//tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask ->
//    pkgTask.jniDir new File(projectDir, 'src/main/libs')
//}

//dependencies {
//    compile fileTree(dir: "libs", include: ["*.jar"])
//    compile "com.android.support:appcompat-v7:22.2.0"
//}
@ssfang
Copy link
Author

ssfang commented Apr 6, 2016

参考资料

参数

  • build 就是你现在使用的机器。
  • host 就是你编译好的程序能够运行的平台。
  • target 编译程序能够处理的平台。一般都用在构建编译本身的时候(gcc), 才用target, 也就是说平时我们所说的交叉编译用不到target.

The GNU autotools packages (i.e. autoconf, automake, and libtool) use the notion of a build platform, a host platform, and a target platform.

  1. The build platform is where the code is actually compiled.

  2. The host platform is where the compiled code will execute.

  3. The target platform usually only applies to compilers. It represents what type of object code the package itself will produce (such as cross-compiling a cross-compiler); otherwise the target platform setting is irrelevant.
    For example, consider cross-compiling a video game that will run on a Dreamcast. The machine where the game is compiled is the build platform while the Dreamcast is the host platform.

    When building cross compilation tools, there are two different systems involved: the system on which the tools will run, and the system for which the tools generate code.

  4. The system on which the tools will run is called the host system.

  5. The system for which the tools generate code is called the target system.
    For example, suppose you have a compiler which runs on a GNU/Linux system and generates ELF programs for a MIPS embedded system. In this case the GNU/Linux system is the host, and the MIPS ELF system is the target. Such a compiler could be called a GNU/Linux cross MIPS ELF compiler, or, equivalently, a ‘i386-linux-gnu’ cross ‘mips-elf’ compiler.

    Target usually have a meaning for developemt tool only.

比如: 在386的平台上编译可以运行在arm板的程序 ./configure –build=i386-linux,–host=arm-linux就可以了。
因为一般我们都是编译程序而不是编译工具。如果我们编译工具,比如gcc,这个target就有用了.如果我们需要在一个我们的机器上为arm开发板编译一个可以处理 mips程序的gcc,那么target就是mips了.

Example:

  1. ./configure --build=mipsel-linux --host=mipsel-linux --target=mipsel-linux # will build native mipsel-linux binutils on mipsel-linux.
  2. ./configure --build=i386-linux --host=mipsel-linux --target=mipsel-linux # will cross-build native mipsel-linux binutils on i386-linux.
  3. ./configure --build=i386-linux --host=i386-linux --target=mipsel-linux # will build mipsel-linux cross-binutils on i386-linux.
  4. ./configure --build=mipsel-linux --host=i386-linux --target=mipsel-linux # will cross-build mipsel-linux cross-binutils for i386-linux on mipsel-linux.

As you see, only if $build != $host a cross-compilation is performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment