Native Hybrid Programming 之構建初探

最近幾周開始了 IM 項目,所以想在應用層寫一套業務隔離的,跨平臺的 SDK, 我便瞄準了 libuv 這個庫,開始了 Native Hybrid 的研究 —— 爲了寫一次代碼能夠同時在 Android 和 iOS 平臺上進行編譯/運行。html

libuv

一開始以爲編譯 libuv 很困難,我採用了 gyp 的方案,並且以前對 Android NDK Toolchain 並不瞭解,致使之前對其嘗試的時候,並無得到我想要的結果,並且就算是 link 出來的包(不正確的方式),運行出錯的信息,也不能讀懂,因此浪費了我不少時間。總之,學習 Native 編程(此處 Native 指的是使用 C/C++ 進行平臺級別的編程)或者說無論學習什麼編程,第一個要務就是不能懼怕,這個很是重要。node

那麼通過個人瞭解,GCC/Clang 編譯連接的套路都差很少,無非是指定 源文件/頭文件/連接庫 進行操做,操做的時候有一些標誌位,還有一些預約義,而後就是一些參數配置了。若是咱們知曉了這些,只要一些庫一開始作了跨平臺開發的準備,那麼使用 NDK/Android Toolchain 編譯,通常都沒有什麼問題,那麼廢話很少說,咱們主要就是指定幾個環境變量。linux

CC:C 編譯器,能夠選擇 Android Toolchain 下面的 GCC 或者 Clang
CXX: C++ 編譯器
LINKER: 連接器
CFLAGS: CC 編譯的時候,一些符號位。
CXXFLAGS: CXX 編譯的時候,所須要的符號位android

還有其餘的環境變量,去參考 https://www.gnu.org/software/...
若是須要的話,咱們還要給 GCC 指定一個 sysroot,方便咱們的編譯程序來尋找 .h 和庫文件。
那麼整個跨平臺編譯的流程就是ios

export CC=xxxxx
export CXX=xxxx
export CFLAGS=xx
export LD=xxx

而後make完事兒。git

若是沒有別的需求,那麼整個編譯流程就是這麼簡單,一開始我覺得 libuv 這個庫很複雜,後來證實 libuv 是一個很簡單的庫 = =。
固然,咱們今天的主題是跨平臺的編譯,所以在這咱們選擇了 CMake 這個工具用來生成咱們的 Makefile 和 xcodeproj。github

CMake 的官網是:https://cmake.org/編程

編寫 libuv 的 CMakeList.txt

若是咱們使用 gyp 來生成 Makefile 的時候,能夠看見 Android 下的 Makefile 內容並非不少,用到的源文件和頭文件其實也屈指可數。
具體可使用 libuv 下的 android-configure 文件來調用 gyp 生成你的 Makefile 查看。
android-configure這個文件是把 NDK 中的獨立工具鏈安裝的 libuv 目錄下面,而後導出有用的環境變量,像 CC/CXX/LD 之類的變量讓 libuv 能夠進行交叉編譯。xcode

DEFS_Debug := \
    '-D_LARGEFILE_SOURCE' \
    '-D_FILE_OFFSET_BITS=64' \
    '-DDEBUG' \
    '-D_DEBUG'

# Flags passed to all source files.
CFLAGS_Debug := \
    -Wall \
    -fvisibility=hidden \
    -g \
    --std=gnu89 \
    -pedantic \
    -Wall \
    -Wextra \
    -Wno-unused-parameter \
    -Wstrict-prototypes \
    -Wstrict-aliasing \
    -g \
    -O0 \
    -fwrapv \
    -fPIE

# Flags passed to only C files.
CFLAGS_C_Debug :=

# Flags passed to only C++ files.
CFLAGS_CC_Debug := \
    -fno-rtti \
    -fno-exceptions

INCS_Debug := \
    -I$(srcdir)/include \
    -I$(srcdir)/src

DEFS_Release := \
    '-D_LARGEFILE_SOURCE' \
    '-D_FILE_OFFSET_BITS=64' \
    '-DNDEBUG'

# Flags passed to all source files.
CFLAGS_Release := \
    -Wall \
    -fvisibility=hidden \
    -g \
    --std=gnu89 \
    -pedantic \
    -Wall \
    -Wextra \
    -Wno-unused-parameter \
    -Wstrict-prototypes \
    -Wstrict-aliasing \
    -O3 \
    -fstrict-aliasing \
    -fomit-frame-pointer \
    -fdata-sections \
    -ffunction-sections

# Flags passed to only C files.
CFLAGS_C_Release :=

# Flags passed to only C++ files.
CFLAGS_CC_Release := \
    -fno-rtti \
    -fno-exceptions

INCS_Release := \
    -I$(srcdir)/include \
    -I$(srcdir)/src

OBJS := \
    $(obj).target/$(TARGET)/src/fs-poll.o \
    $(obj).target/$(TARGET)/src/inet.o \
    $(obj).target/$(TARGET)/src/threadpool.o \
    $(obj).target/$(TARGET)/src/uv-common.o \
    $(obj).target/$(TARGET)/src/version.o \
    $(obj).target/$(TARGET)/src/unix/async.o \
    $(obj).target/$(TARGET)/src/unix/core.o \
    $(obj).target/$(TARGET)/src/unix/dl.o \
    $(obj).target/$(TARGET)/src/unix/fs.o \
    $(obj).target/$(TARGET)/src/unix/getaddrinfo.o \
    $(obj).target/$(TARGET)/src/unix/getnameinfo.o \
    $(obj).target/$(TARGET)/src/unix/loop.o \
    $(obj).target/$(TARGET)/src/unix/loop-watcher.o \
    $(obj).target/$(TARGET)/src/unix/pipe.o \
    $(obj).target/$(TARGET)/src/unix/poll.o \
    $(obj).target/$(TARGET)/src/unix/process.o \
    $(obj).target/$(TARGET)/src/unix/signal.o \
    $(obj).target/$(TARGET)/src/unix/stream.o \
    $(obj).target/$(TARGET)/src/unix/tcp.o \
    $(obj).target/$(TARGET)/src/unix/thread.o \
    $(obj).target/$(TARGET)/src/unix/timer.o \
    $(obj).target/$(TARGET)/src/unix/tty.o \
    $(obj).target/$(TARGET)/src/unix/udp.o \
    $(obj).target/$(TARGET)/src/unix/proctitle.o \
    $(obj).target/$(TARGET)/src/unix/linux-core.o \
    $(obj).target/$(TARGET)/src/unix/linux-inotify.o \
    $(obj).target/$(TARGET)/src/unix/linux-syscalls.o \
    $(obj).target/$(TARGET)/src/unix/pthread-fixes.o \
    $(obj).target/$(TARGET)/src/unix/android-ifaddrs.o \
    $(obj).target/$(TARGET)/src/unix/pthread-barrier.o \
    $(obj).target/$(TARGET)/src/unix/procfs-exepath.o \
    $(obj).target/$(TARGET)/src/unix/sysinfo-loadavg.o \
    $(obj).target/$(TARGET)/src/unix/sysinfo-memory.o

截取這麼多,能夠看到個大概了,若是想看具體的執行,咱們能夠調用make V=1來查看具體執行的flags。導出之後,咱們的CMakeList.txt大概以下:bash

cmake_minimum_required(VERSION 3.4.1)
PROJECT(uv CXX C)

INCLUDE_DIRECTORIES (${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src)

# from uv.gyp
MESSAGE(STATUS "in libuv" ${CMAKE_CURRENT_SOURCE_DIR})

SET(SRC_LIST  "${CMAKE_CURRENT_SOURCE_DIR}/src/fs-poll.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/inet.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/uv-common.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/version.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/async.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/core.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/dl.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/fs.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/getaddrinfo.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/getnameinfo.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/loop.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/loop-watcher.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/pipe.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/poll.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/process.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/signal.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/stream.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/tcp.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/thread.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/timer.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/tty.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/udp.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/proctitle.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/pthread-barrier.c")

IF (DEFINED ANDROID)

ADD_DEFINITIONS(-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64)
SET(SRC_LIST  ${SRC_LIST}
            "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/linux-core.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/linux-inotify.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/linux-syscalls.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/pthread-fixes.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/android-ifaddrs.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/procfs-exepath.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/sysinfo-loadavg.c"
              "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/sysinfo-memory.c")

IF(CMAKE_BUILD_TYPE STREQUAL "Debug")
    # from common.gypi
    MESSAGE(STATUS "in Debug")
    
    ADD_DEFINITIONS(-DDEBUG -D_DEBUG)

    SET(COMMON_C_FLAGS "-Wall \
                        -fvisibility=hidden \
                        -g \
                        --std=gnu89 \
                        -pedantic \
                        -Wextra \
                        -Wno-unused-parameter \
                        -Wstrict-prototypes \
                        -Wstrict-aliasing \
                        -g \
                        -O0 \
                        -fwrapv \
                        -fPIE")

ELSE()
    MESSAGE(STATUS "in Release")
    ADD_DEFINITIONS(-DNDEBUG)
    SET(COMMON_C_FLAGS "-Wall \
                    -fvisibility=hidden \
                    -g \
                    --std=gnu89 \
                    -pedantic \
                    -Wextra \
                    -Wno-unused-parameter \
                    -Wstrict-prototypes \
                    -Wstrict-aliasing \
                    -O3 \
                    -fstrict-aliasing \
                    -fomit-frame-pointer \
                    -fdata-sections \
                    -ffunction-sections")
ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug")

SET(CMAKE_C_FLAGS_DEBUG ${COMMON_C_FLAGS})
SET(CMAKE_CXX_FLAGS_DEBUG ${COMMON_C_FLAGS} "-fno-rtti -fno-exceptions")

SET(CMAKE_C_FLAGS_RELEASE ${COMMON_C_FLAGS})
SET(CMAKE_CXX_FLAGS_RELEASE ${COMMON_C_FLAGS} "-fno-rtti -fno-exceptions")

ELSEIF (DEFINED APPLE)
SET(SRC_LIST  ${SRC_LIST}
          "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/bsd-ifaddrs.c"
          "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/darwin-proctitle.c"
          "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/darwin.c"
          "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/fsevents.c"
          "${CMAKE_CURRENT_SOURCE_DIR}/src/unix/kqueue.c")

SET(CMAKE_CXX_FLAGS "-fobjc-abi-version=2 \ 
             -fobjc-arc \
             ${CMAKE_CXX_FLAGS}")

# end of defined android
ENDIF(DEFINED ANDROID)

ADD_LIBRARY(uv STATIC ${SRC_LIST})

這裏利用 CMake 的邏輯判斷,分別加入了 iOS 和 Android 不一樣平臺的源文件,咱們去查看這些文件會發現他們都是平臺相關的 API 實現,共有的一些源文件則是抽象的接口層。

若是想知道 CMake 指令如何使用,能夠查看相關文檔:
CMake 文檔傳送門:https://cmake.org/cmake/help/...

開始執行 CMake

固然,光有 CMake 還不夠,CMake 提供了一套工具鏈系統,iOS 和 Android 都有相應的開源工具鏈插件:

iOS: https://github.com/cristeab/i...
Android :https://github.com/taka-no-me...

指定了 Toolchain 和咱們的 CPU ABI 以後,咱們就能夠爲指定的 CPU 架構編譯出特定的二進制庫了

好比,編譯 Android 以下:

cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain/android.toolchain.cmake \
      -DCMAKE_BUILD_TYPE=Debug                     \
      -DANDROID_ABI="armeabi-v7a"                  \
      -DANDROID_NATIVE_API_LEVEL="android-21"        \
      .

編譯 iOS 以下:

cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain/darwin.toolchain.cmake \
      -DIOS_PLATFORM=OS \
      -GXcode \
      .

-G是讓 CMake 生成一個 Xcode 的構建系統,也就是 Xcode 項目文件。這樣咱們就能夠啓動 Xcode 進行編譯了。

咱們把這兩段腳本寫在一個bash文件裏,每次執行的時候,只用調用./build_android.sh./build_ios.sh就好了。
每次執行android的時候,會生成libxxx.soMakefile,執行ios的時候,會生成xxx.xcodeproj,是否是以爲特別炫酷?

相關文章
相關標籤/搜索