Android编译器及编译工具之编译器

简介: 习惯了IDE以及各种现成的编译工具为我们提供便捷的编译方式,我们很少会操心编译工具的编译过程和原理,但是工具越高级,隐藏的细节就越多,这样编译遇到问题时我们难以定位,遇到复杂的项目(尤其跨平台项目难以用ide)时不知如何下手。所以准备写两篇关于编译器和编译工具的文章。本文先来介绍编译工具。

image.png


习惯了IDE以及各种现成的编译工具为我们提供便捷的编译方式,我们很少会操心编译工具的编译过程和原理,但是工具越高级,隐藏的细节就越多,这样编译遇到问题时我们难以定位,遇到复杂的项目(尤其跨平台项目难以用ide)时不知如何下手。所以准备写两篇关于编译器和编译工具的文章。本文先来介绍编译工具。


主要从事Android开发,本文主要介绍Android、iOS用到的编程语言及编译器。


Java/Kotlin/Groovy


这三种编程语言都是基于Java虚拟机的。由于JVM的存在,所以Java既是编译型语言,又是解释型语言。将JVM理解成操作系统,它是编译型语言;从物理操作系统的角度,它又是解释型的。JVM负责把编译成的.class解释成最终CPU理解的二进制字节。它为了实现跨平台牺牲了效率。


Java编译工具


我们还是先以一个最简单的HelloWorld开始。


public class HelloWorld{
    public static void main(String[] args){
        System.out.println("Hello, World!");
    }
}


命名成HelloWorld.java。还记得Java为我们分别提供了编译工具javac和执行工具java吗?我们使用javac编译:


javac HelloWrold.java


在与HelloWorld.java统计目录下看到生成了HelloWorld.class,我们继续执行该class文件:


qingkouwei:~/javaLinux/w1$ java HelloWorld.class
Error: Could not find or load main class HelloWorld.class


报错了,我们回想一下java的参数,传入的是main函数所在的类的名字,而不是class文件;java会根据类名自动去找class文件。我们改成java HelloWorld就可以成功看到输出结果了。


带包名类编译


上面例子太简单了,我们加上包名来一遍:


package com.qingkouwei.demo;
public class HelloWorld{
    public static void main(String[] args){
        System.out.println("Hello, World!");
    }
}


使用javac编译后在当前目录生成了HelloWorld.class,运行java HelloWorld后报错:


Error: Could not find or load main class com.qingkouwei.demo.HelloWorld


这里包名需要和文件路径相对应,创建com/qingkouwei/demo目录,将HelloWorld.class放进来,执行java com.qingkouwei.demo.HelloWorld成功输出:


Hello, World!


这里说明两点:


  1. 增加了package名,所以class名也变了,执行时要使用包名+类名的方式。
  2. Java 会根据包名对应出目录结构,并从class path搜索该目录去找class文件。由于默认的class path是当前目录,所以com.qingkouwei.demo.HelloWorld必须存储在./com/qingkouwei/demo/下。


我们还可以使用javac命令的-d参数指定编译路径:


qingkouwei@mac javac -d . HelloWorld.java 
qingkouwei@mac ls
com  HelloWorld.java
qingkouwei@mac java com.qingkouwei.demo.HelloWorld
Hello, World!


编译有依赖关系的class


我们将打印Hello World的方法封装成一个Hello工厂类HelloFactory:


package com.qingkouwei.demo;
public class HelloFactory{
    public void printHello(String name){
        System.out.println("Hello, " + name + "!");
    }
}


HelloWorld调用该方法:


package com.qingkouwei.demo;
public class HelloWorld{
    public static void main(String[] args){
        HelloFactory factory = new HelloFactory();
        factory.printHello("World");
    }
}


这样HelloWorld依赖了HelloFactory,我们先编译HelloFactory.java,再编译HelloWorld.java:


qingkouwei@mac javac -d . HelloFactory.java 
qingkouwei@mac javac -d . HelloWorld.java 
qingkouwei@mac ls
com  HelloFactory.java  HelloWorld.java
qingkouwei@mac java com.qingkouwei.demo.HelloWorld
Hello, World!


如果修改编译顺序呢:


qingkouwei@mac javac -d . HelloWorld.java 
HelloWorld.java:4: error: cannot find symbol
        HelloFactory service = new HelloFactory();
        ^
  symbol:   class HelloFactory
  location: class HelloWorld
HelloWorld.java:4: error: cannot find symbol
        HelloFactory service = new HelloFactory();
                                   ^
  symbol:   class HelloFactory
  location: class HelloWorld
2 errors


如果编译的时候,还要我们手动管理依赖关系,代价太大了,当工程复杂度上来后,几乎就是不可维护的,我们一次性将两个java文件传给javac:


qingkouwei@mac javac -d . HelloWorld.java HelloFactory.java 
qingkouwei@mac ls
com  HelloFactory.java  HelloWorld.java
qingkouwei@mac  java com.qingkouwei.demo.HelloWorld
Hello, World!


javac是可以自动管理依赖关系的。


javac命令总结


javac的语法如下:


javac [ options ] [ sourcefiles ] [ classes] [ @argfiles ]


  1. options:是一些选项,比如-cp,-d
  2. sourcefiles:就是编译的java文件,如HelloWorld.java,可以是多个,并用空格隔开
  3. classes:用来处理处理注解。
  4. @argfiles,就是包含option或java文件列表的文件路径,用@符号开头


Kotlin


Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,由 JetBrains 设计开发并开源。Kotlin 可以编译成Java字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。它要运行在JVM中的时候其实和java是一个爹,很多东西都是类似的,只是在语法上做了一些改进。


简单介绍下Kotlin命令行编译。


Kotlin 命令行编译工具下载地址:github.com/JetBrains/k… bin 目录添加到系统环境变量。bin 目录包含编译和运行 Kotlin 所需的脚本。Mac上可以直接使用brew install kotlin安装。


创建helloworld.kt文件:


fun main(args: Array<String>) {
    println("Hello, World!")
}


使用 Kotlin 编译器编译应用:


kotlinc helloworld.kt -include-runtime -d hello.jar


  • -d: 用来设置编译输出的名称,可以是 class 或 .jar 文件,也可以是目录。
  • -include-runtime : 让 .jar 文件包含 Kotlin 运行库,从而可以直接运行。


可以通过kotlinc -help查看支持的编译选项。


运行:


java -jar hello.jar
Hello, World!


kotlinc是和javac类似的作用。


Groovy


Groovy是一种基于JVM Java虚拟机的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库。具体使用不再展开。


C/C++


linux上主流的C/C++编译器是GCC与clang。


GCC


GCC是GNU(Gnu's Not Unix)编译器套装(GNU Compiler Collection,GCC),是一套编程语言编译器,以GPL及LGPL许可证所发行的自由软件,也是GNU项目的关键部分,也是GNU工具链的主要组成部分之一。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准。1985年由理查德·马修·斯托曼开始发展,现在由自由软件基金会负责维护工作。GCC原本用C开发,后来因为LLVM、Clang的崛起,它更快地将开发语言转换为C++。


gcc/g++ 在执行编译工作的时候,总共需要4步:


  1. 预处理,生成 .i 的文件[预处理器cpp]
  2. 将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
  3. 有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
  4. 连接目标代码, 生成可执行程序 [链接器ld]


gcc编译命令及示例


创建main文件:


#include <stdio.h>
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}


  1. 预处理:gcc -E hello.c > pianoapan.txt ,-E只激活预处理,不生成文件, 需要把它重定向到一个输出文件里面。
  2. 生成汇编:gcc -S hello.c -S只激活预处理和编译,就是指把文件编译成为汇编代码。
  3. 编译:gcc -c hello.c ,-c只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
  4. 连接:gcc -o hello hello.c ,一步到位编译成可使用库。 -o指定目标名称, 默认的时候, gcc 编译出来的文件是 a.out。


gcc 命令的常用选项:


选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串"1"定义 MACRO 宏。
-DMACRO=DEFN 以字符串"DEFN"定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成所有警告信息。


Clang


Clang:是一个C、C++、Objective-C和Objective-C++编程语言的编译器前端。它采用了底层虚拟机(LLVM)作为其后端。它的目标是提供一个GNU编译器套装(GCC)的替代品。作者是克里斯·拉特纳(Chris Lattner),在苹果公司的赞助支持下进行开发,而源代码授权是使用类BSD的伊利诺伊大学厄巴纳-香槟分校开源码许可。Clang主要由C++编写。


上面都提到了LLVM,LLVM是构架编译器的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。clang其实是LLVM项目的一个子项目,基于LLVM架构的C/C++/Objective-C编译器前端。


clang编译命令


1.无选项编译链接 用法:#clang hello.c作用:将hello.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。编译成功后可以看到生成了一个a.out的文件。在命令行输入./a.out 执行程序。./表示在当前目录,a.out为可执行程序文件名。


2.选项 -o 用法:#clang hello.c -o hello作用:将hello.c预处理、汇编、编译并链接形成可执行文件hello.c。-o选项用来指定输出文件的文件名。输入./hello执行程序。


3.选项 -E 用法:#clang -E hello.c -o hello.i作用:将hello.c预处理输出hello.i文件。


4.选项 -S 用法:#clang -S hello.i作用:将预处理输出文件hello.i汇编成hello.s文件。


5.选项 -c 用法:#clang -c hello.s 作用:将汇编输出文件hello.s编译输出hello.o文件。


6.无选项链接 用法:#clang hello.o -o hello作用:将编译输出文件hello.o链接成最终可执行文件hello。输入./hello执行程序。


如果想直接输入hello就运行,需要把hello复制到目录/usr/bin下


7.选项-O 用法:#clang -O1 hello.c -o hello作用:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。输入./hello执行程序。


8.编译使用C++ std库的程序


用法:#clang hello.cpp -o hello -l std c++


作用:将hello.cpp编译链接成test可执行文件。-l  std  c++指定链接std c++库。


我们可以看到clang和gcc的编译选项是类似的。


NDK


在Android开发中我们使用的是NDK工具,通常使用ndk-build来跨平台编译Android c/c++库。ndk-build是种什么编译工具呢?


我们查看下ndk-build文件:


$ cat ndk-build
#!/bin/sh
DIR="$(cd "$(dirname "$0")" && pwd)"
$DIR/build/ndk-build "$@"%


发现ndk-build只是个指向/build/ndk-build的脚本,查看后发现/build/ndk-build也是一个脚本:


$ cat build/ndk-build
#!/bin/bash
...
# Check that we have 64-bit binaries on 64-bit system, otherwise fallback
# on 32-bit ones. This gives us more freedom in packaging the NDK.
LOG_MESSAGE=
if [ $HOST_ARCH = x86_64 ]; then
  if [ ! -d $ANDROID_NDK_ROOT/prebuilt/$HOST_TAG ]; then
    HOST_ARCH=x86
    LOG_MESSAGE="(no 64-bit prebuilt binaries detected)"
  fi
fi
...
    get_build_var ()
    {
        local VAR=$1
        local FLAGS=`gen_flags`
        $GNUMAKE --no-print-dir -f $PROGDIR/core/build-local.mk $FLAGS DUMP_${VAR} | tail -1
    }
   ...
    APP_ABIS=`get_build_var APP_ABI`
    for ABI in $APP_ABIS; do
        perl ${LLVM_TOOLCHAIN_PREFIX}scan-build \
            --use-cc $ANALYZER_CC \
            --use-c++ $ANALYZER_CXX \
            --status-bugs \
            $ANALYZER_OUT_FLAG \
            $GNUMAKE -f $PROGDIR/core/build-local.mk "$@" APP_ABI=$ABI
    done
else
    $GNUMAKE -O -f $PROGDIR/core/build-local.mk "$@"
fi


我们看到最终起作用的是GNUMAKE变量,我们看到GNUMAKE=$ANDROID_NDK_ROOT/prebuilt/$HOST_TAG/bin/make,所以不管是ndk-build项目还是cmake项目,最终还是只用make编译工具来调用具体编译器来编译。


在r8c中 添加了Clang 3.1 编译器,在NDK r12中,ndk-build建议使用clang编译器,r13b中gcc不再支持,NDK_TOOLCHAIN_VERSION默认使用clang,r14b中gcc被弃用,使用gcc通过-D_ANDROID_API_=$API指定具体的API,在r18b中,移除gcc,gnustl,gabi++,stlport等。具体ndk修订历史记录可以参考官网:

developer.android.com/ndk/downloa…


总结一下,ndk是用于跨平台编译c/c++代码的工具集合,它提供了编译代码的三种方式:


  1. 基于Make的ndk-build
  2. CMake
  3. 独立工具链,用于与其他构建系统集成,或与基于 configure 的项目搭配使用。


最终它还是使用GCC/Clang编译器。


dart


Dart 是一个为全平台构建快速应用的客户端优化的编程语言。它主要用于谷歌退出的跨平台框架Flutter。Dart编译特点:


  1. Just-In-Time即时编译。在应用运行时同时执行代码编译,让flutter具备极速的开发体验。具备亚秒级的热重载(Hot Reloading)特性。
  2. Ahead-Of-Time运行前编译。将代码库直接编译成原生的ARM指令。为应用带来快速的启动速度和可预见的卓越性能。


Dart编译工具使用


dart.dev/tools/sdk/a…下载编译工具,解压配置环境变量后,可以通过dart --version查看版本。


使用dart2native构建和部署命令行程序。我们准备main.dart源文件:


main(){ 
  print('Hello World'); 
}


使用命令dart2native main.dart -o hello编译为可执行文件。


oc/swift


oc


苹果通过Xcode来学习编译Objective-C编程语言,XCode的默认编译器是clang。


准备代码main.m


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool
{
NSLog(@"Hello, World!");
}
return 0;
}


使用命令clang -fobjc-arc -framework Foundation main.m -o HelloWorld,编译完成后即可运行:./hello

注意:


  • -fobjc-arc表示编译器需要支持ARC特性
  • -framework Foundation表示引用Foundation框架
  • main.m为需要进行编译的源代码文件
  • -o HelloWord表示输出的可执行文件的文件名


swift


Swift 是一种支持多编程范式和编译式的开源编程语言,苹果于2014年WWDC(苹果开发者大会)发布,用于开发 iOS,OS X 和 watchOS 应用程序。Swift 结合了 C 和 Objective-C 的优点并且不受 C 兼容性的限制。Swift 在 Mac OS 和 iOS 平台可以和 Object-C 使用相同的运行环境。


swift的开发工具仍然为xcode,我们在命令行使用xcrun swift -emit-executable -sdk $(xcrun --show-sdk-path --sdk macosx) sample.swift编译swift程序。


总结


本文主要介绍了移动端相关的编译工具,都是基础的入门工具,但是对于我们日后面对复杂的大型项目提供帮助,特别是一些跨平台的C/C++项目,一份代码一个脚本编译出所有平台的程序,都需要我们能够熟练驾驭这些编译工具。


参考


目录
相关文章
|
XML Java 编译器
Android编译器及编译工具之编译工具
Apache Ant 是由 Java 语言开发的工具,由 Apache 软件基金会所提供。Ant 是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。Apache Ant 的配置文件写成 XML 容易维护和书写,而且结构很清晰。
391 0
|
IDE Java 编译器
Android要求编译器符合级别5.0或6.0发现“ 1.7”请使用Android工具>修复项目属性
Android要求编译器符合级别5.0或6.0发现“ 1.7”请使用Android工具>修复项目属性
304 0
DHL
|
算法 Java 网络安全
Android 利器,我开发了云同步编译工具
SyncKit 主要将本地的项目同步到远程设备,在远程设备上进行编译,然后将编译的结果拉回本地
DHL
439 0
Android 利器,我开发了云同步编译工具
|
XML 安全 Java
【Android 安装包优化】资源混淆 ( AAPT2 资源编译工具 | resources.arsc 资源映射表 工作机制 )
【Android 安装包优化】资源混淆 ( AAPT2 资源编译工具 | resources.arsc 资源映射表 工作机制 )
475 0
【Android 安装包优化】资源混淆 ( AAPT2 资源编译工具 | resources.arsc 资源映射表 工作机制 )
|
Java 编译器 Android开发
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(二)
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(二)
218 0
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(二)
|
Java 编译器 Android开发
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(一)
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(一)
247 0
【Android Protobuf 序列化】Protobuf 使用 ( protoc 编译器简介 | 下载 protoc 编译器 | 使用 protoc 编译器编译 .proto 源文件 )(一)
|
测试技术 编译器 API
【Android 高性能音频】Oboe 函数库简介 ( Oboe 简介 | Oboe 特点 | Oboe 编译工具 | Oboe 相关文档 | Oboe 测试工具 )
【Android 高性能音频】Oboe 函数库简介 ( Oboe 简介 | Oboe 特点 | Oboe 编译工具 | Oboe 相关文档 | Oboe 测试工具 )
440 0
|
Android开发 编译器 Java