我们也说说Android.mk(4) - 依赖:目标编程的模式

简介: 我一直觉得,Makefile确实是C/C++程序员的良配,因为Makefile所使用的两种范式都是C/C++程序员不熟悉的,一种是函数式的思想,一种是依赖构成的目标链的模式。

我们也说说Android.mk(4) - 依赖:目标编程的模式

另一种范式

我一直觉得,Makefile确实是C/C++程序员的良配,因为Makefile所使用的两种范式都是C/C++程序员不熟悉的,一种是函数式的思想,一种是依赖构成的目标链的模式。

Makefile从最基本上来说,可以抽象成下面这样的:

    target ... : prerequisites ...
            command
            ...
            ...

如大家所熟悉的,这段的意义是:当prerequisites有更新的时候,执行command命令。如果target是一个真实的目标,也就是对应一个真实的文件,那么就生成这个文件。如果是伪目标,可以被用来做为一个入口,比如clean,也可以成为一个真实目标的依赖。
可以明显地分为两个部分:一个是target依赖链的范式,这与过程式语言的C语言非常不同。用蒋军的话讲,跟Prolog有点像。有着它自己的一套逻辑系统。
后面的command,我们前面讲了不少了,我个人是希望大家以函数式的思想来写。

我们落地到一个实际的例子中:

$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
    $(call build-systemimage-target,$@)

这个$(BUILT_SYSTEMIMAGE),是个真实的目标,对应了要生成的文件system.img,如下:

BUILT_SYSTEMIMAGE := $(systemimage_intermediates)/system.img

下面来看看,system.img所依赖目标,先看第一个,结果这一个实际上又是两个:

FULL_SYSTEMIMAGE_DEPS := $(INTERNAL_SYSTEMIMAGE_FILES) $(INTERNAL_USERIMAGES_DEPS)

然后,我们发现这个依赖在一层层地扩张:

INTERNAL_SYSTEMIMAGE_FILES := $(filter $(TARGET_OUT)/%, \
    $(ALL_PREBUILT) \
    $(ALL_COPIED_HEADERS) \
    $(ALL_GENERATED_SOURCES) \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(PDK_FUSION_SYSIMG_FILES) \
    $(RECOVERY_RESOURCE_ZIP))

扩张还在继续,比如,对于ALL_PREBUILT,各个模块不断地把自己的东西增加进去:

ALL_PREBUILT += $(TARGET_OUT)/bin/monkey

其他的目标原理相通,这里就不多浪费篇幅了。
总而言之,这一大套目标中,只要有任何一个有变化,system.img就要重新生成了。如何生成?在下一行中写着呢:调用build-systemimage-target啊。

Makefile写作指南

目标式和函数式两种范式都学好了,下面是我们如何组织材料来完成我们的工程的时候了。

  1. 先定义总目标
    Makefile的总的目的是输出一个或多个结果文件,先把总的目标定义好。

然后假设子目标都已经构建好了,下面写一个将这些中间产品变成最终目标的脚本。
比如编译一个简单的C程序,总的目标是一个可执行文件,最终加工时的材料是已经编译好的.o文件和输入的第三方的库文件等,我们先不管它们是如何编译的,假设它们已经做好了,我们只要写一个链接的脚本就好了。
像我们上面的system.img的例子,反正依赖多,就分门别类的列吧,最终我们只需要把它们打个包就好了。

  1. 层层分解,逐步完成
    然后去寻找,构成这个大目标的第一层的构件是什么,像上面我们所看过的一样,逐层扩张。

对于C文件,这时候才考虑每一个.o是如何从源文件编译的。

  1. 模块化、函数化
    上面两步都是目标模式的,这一步开始搞函数模式了。将各目标中可重用的函数抽象出来,该分文件就分文件,该整理代码就整理代码等
  2. 测试调优
    当一个工程大到一定程度的时候,Makefile的可读性会严重下降。

这时候我们还是按目标式和函数式两条主线来降低复杂度。目标是层次式的,我们可以一层一层地调试,比如先调从.c到.o的编译过程,再调将.o链接起来的总装部分.
哪一个子模块出问题,就专门调那一部分的。
对于功能部分,我们一直强调函数式思想就是希望,对于某一个确实性的输入,能有一个确定性的输出,没有副作用,这样能够将调试的难度降低,我们可以一个函数一个函数地调试。
Makefile的调试以打日志为主,还可以通过make -p来输出完整的变量和目标列表。

make -p,看看make都做了些什么

下面是我在cygwin下的make -p的输出结果的节选

Make工具的信息

首先是Make工具汇报下自己的基础情况:

The files is:main.cpp
# GNU Make 4.1
# Built for x86_64-unknown-cygwin
# Copyright (C) 1988-2014 Free Software Foundation, Inc.
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

# make 数据基础,打印在 Tue May  3 17:54:36 2016

变量

下面是变量的列表,包含我们自己定义的,也包含make自动为我们生成的。

# 变量

# 'override' directive
GNUMAKEFLAGS := 
# 自动
<D = $(patsubst %/,%,$(dir $<))
# 自动
?F = $(notdir $?)
# 默认
.SHELLFLAGS := -c
# makefile (from 'Makefile', line 30)
result_findString2 := 
# makefile
MAKEFLAGS = p
# 默认
CWEAVE = cweave
# 自动
?D = $(patsubst %/,%,$(dir $?))
# 环境
!:: = ::\
# 自动
@D = $(patsubst %/,%,$(dir $@))
# 环境
HOMEDRIVE = C:
# 自动
@F = $(notdir $@)
# 自动
^D = $(patsubst %/,%,$(dir $^))
# makefile
CURDIR := /cygdrive/d/working/codeBlocks/Hello
# makefile
SHELL = /bin/sh
# 默认
RM = rm -f
# 默认
CO = co
...

目录信息

# 目录

# SCCS:无法对其进行 stat 操作。
# . (设备 114478965,i-节点 1688849860268365):10 文件, 19 不可能.
# RCS:无法对其进行 stat 操作。

# 10 文件, 19 不可能在 3 目录中。

隐含规则信息

# 隐含规则。

%.out:

%.a:

%.ln:

%.o:

%: %.o
#  recipe to execute (内置):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.c:

%: %.c
#  recipe to execute (内置):
    $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.ln: %.c
#  recipe to execute (内置):
    $(LINT.c) -C$* $<

%.o: %.c
#  recipe to execute (内置):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

%.cc:

%: %.cc
#  recipe to execute (内置):
    $(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.cc
#  recipe to execute (内置):
    $(COMPILE.cc) $(OUTPUT_OPTION) $<

%.C:

%: %.C
#  recipe to execute (内置):
    $(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.C
#  recipe to execute (内置):
    $(COMPILE.C) $(OUTPUT_OPTION) $<

%.cpp:

%: %.cpp
#  recipe to execute (内置):
    $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.cpp
#  recipe to execute (内置):
    $(COMPILE.cpp) $(OUTPUT_OPTION) $<

%.p:

%: %.p
#  recipe to execute (内置):
    $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.p
#  recipe to execute (内置):
    $(COMPILE.p) $(OUTPUT_OPTION) $<
...

文件目标和假目标

# 文件

# 不是一个目标:
.web.p:
#  Builtin rule
#  对隐含规则的搜索尚未完成。
#  从不检查修改时间。
#  文件尚未被更新。
#  recipe to execute (内置):
    $(TANGLE) $<

# 不是一个目标:
.l.r:
#  Builtin rule
#  对隐含规则的搜索尚未完成。
#  从不检查修改时间。
#  文件尚未被更新。
#  recipe to execute (内置):
    $(LEX.l) $< > $@ 
     mv -f lex.yy.r $@

all8:
#  假目标 (.PHONY的前提)。
#  对隐含规则的搜索尚未完成。
#  文件不存在。
#  文件尚未被更新。
#  recipe to execute (from 'Makefile', line 66):
    @echo $(filter-out default interpreter jit optimizing,xoc)
    @echo $(filter-out default interpreter jit optimizing,default)

all9:
#  假目标 (.PHONY的前提)。
#  对隐含规则的搜索尚未完成。
#  文件不存在。
#  文件尚未被更新。
#  recipe to execute (from 'Makefile', line 75):
    $(eval ARCH_OF_BOOT_OAT := $(lastword $(subst /, ,$(dir $(BOOT_ART_SRC)))))
    $(eval OAT_TEMP := $(PRODUCT_OUT)/data/dalvik-cache/temp-oat/system/framework/$(ARCH_OF_BOOT_OAT))
    $(eval OAT_SRC := $(patsubst %.art,%.oat,$(BOOT_ART_SRC)))
    $(eval OAT_DIST := $(patsubst %.art,%.oat,$(BOOT_ART_DST)))
    @echo $(ARCH_OF_BOOT_OAT)
    @echo $(OAT_TEMP)
    @echo $(OAT_SRC)
    @echo $(OAT_DIST)
...
目录
相关文章
|
6月前
|
Android开发
Android Mediatek 增加Recovery模式下读cmdline的强制工厂重置选项
Android Mediatek 增加Recovery模式下读cmdline的强制工厂重置选项
57 0
|
6月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
|
6月前
|
Ubuntu 网络协议 Java
【Android平板编程】远程Ubuntu服务器code-server编程写代码
【Android平板编程】远程Ubuntu服务器code-server编程写代码
|
6月前
|
存储 Java Android开发
OpenCV3 安卓应用编程:1~6 全
OpenCV3 安卓应用编程:1~6 全
41 0
|
6月前
|
存储 算法 开发工具
OpenCV 安卓编程示例:1~6 全
OpenCV 安卓编程示例:1~6 全
148 0
|
6月前
|
Java 调度 数据库
Android 性能优化: 如何进行多线程编程以提高应用性能?
Android 性能优化: 如何进行多线程编程以提高应用性能?
110 0
|
6月前
|
XML 前端开发 测试技术
Android基础知识:解释Android的MVC和MVP模式。
Android基础知识:解释Android的MVC和MVP模式。
64 0
|
3月前
|
设计模式 Android开发 Kotlin
Android经典实战之Kotlin委托模式和by关键字
本文介绍了Kotlin中`by`关键字在类及属性委托中的运用,通过实例展示了如何利用类委托简化接口实现,以及如何借助标准与自定义属性委托管理属性的读写操作。通过`by`关键字的支持,Kotlin使得委托模式的实现更为直观且高效。
70 4
|
2月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
29 3
|
4月前
|
存储 前端开发 测试技术
Android Kotlin中使用 LiveData、ViewModel快速实现MVVM模式
使用Kotlin实现MVVM模式是Android开发的现代实践。该模式分离UI和业务逻辑,借助LiveData、ViewModel和DataBinding增强代码可维护性。步骤包括创建Model层处理数据,ViewModel层作为数据桥梁,以及View层展示UI。添加相关依赖后,Model类存储数据,ViewModel类通过LiveData管理变化,而View层使用DataBinding实时更新UI。这种架构提升代码可测试性和模块化。
170 2