NuttX 构建系统

(嵌入式 实时操作系统 rtos nuttx 7.1 makefile)

NuttX 构建系统

转载请注明出处: http://blog.csdn.net/zhumaill/article/details/24400441

1  简单介绍

NuttX 是通过 Makefile 文件组织编译的。Makefile 文件描写叙述了整个 NuttX project的编译、链接等规则,告诉 make 要编译哪些文件、如何编译以及在什么条件下编译。

NuttX 没有使用 Autoconf、 Automake、 CMake、 SCons 等自己主动化编译工具。它的 Makefile 文件全然由手工编写。比那些自己主动化编译工具所使用的编译文件更easy阅读。更easy理解软件的编译过程。

正应了那句话:试图用一种方法使事情变得简单,结果却使事情变得更复杂。

2  Makefile 文件组织结构

  • nuttx/Makefile: 顶层 Makefile 文件。内容非常easy,依据主机环境。条件包括  Makefile.unix 或 Makefile.win。
  • nuttx/Makefile.unix:Linux 环境下的 Makefile 文件。
  • nuttx/Makefile.win:Windows 环境下的 Makefile 文件。

  • nuttx/Make.defs:从 nuttx/config/<板卡>/<目标配置>/Make.defs 复制而来。

  • 各级子文件夹下的 Makefile、 Make.defs 和 Make.dep。

3  Makefile 文件包括树

(顶层文件夹是 nuttx)

`        |<--.config
         |
         |                 |<--.config
         |                 |<--tools/Config.mk
         |<--Makefile.unix-|
Makefile-|                 |             |<--.config
         |                 |<--Make.defs-|<--tools/Config.mk
         |                               |<--arch/arm/src/armv7-m/Toolchain.defs
         |
         |<--Makefile.win-(略)

能够看出,《nuttx 配置系统》中所生成的 .config 文件被包括在 Makefile 文件里。

各级子文件夹下的 Makefile、 Make.defs 和 Make.dep 并非通过 include 包括的。而是在运行 Makefile 文件里的 make 命令时调用的。

4  构建目标和选项

4.1  构建目标:

下面是在顶层 Makefile 文件里可用的构建目标的概述:

all

默认目标,按已选择的输出格式构建 NuttX 可运行文件。

clean

移除派生对象文件、静态库文件、可运行文件和暂时文件,但保留配置和上下文的文件和文件夹。还保留 Make.dep 文件。

distclean

除了完毕 “clean” 的工作之外,还包含移除全部配置和上下文的文件。本质是将文件夹结构还原为其原始的、未配置的状态。

4.2  应用程序内务处理目标

APPDIR 变量引用用户应用程序文件夹。如 NuttX 包括的 app/ 文件夹,然而,这不被看作是 NuttX 的一部分,能够被一个不同的应用程序文件夹替代。在大多数情况下,应用程序文件夹在 Makefile 脚本中被看作像不论什么其他构建文件夹。可是,为方便起见,包括了下面目标,以支持从 NuttX 构建文件夹处理用户应用程序文件夹中的内务处理功能。

apps_clean

仅对用户应用程序文件夹运行 clean 操作。

apps_distclean

仅对用户应用程序文件夹运行 distclean 操作。 apps/.config 文件被保留,所以这不是一个全然的 distclean。但多于配置复位。

export

export 目标将打包 NuttX 库和头文件到可输出包。

注意事项:(1) 须要对 KERNEL 构建做一些扩展。

(2) tools/mkexport.sh 中的逻辑仅仅支持 GCC,比如,显式地假定静态库生成器为“ar”。

download

这是一个助手目标,它将重建 NuttX 并将其下载到目标系统,仅仅需一步。

此目标的操作全然依赖于用户 Make.defs 文件里的 DOWNLOAD 命令的实现。假设没有定义 DOWNLOAD 命令。它将产生一个错误。

4.3  内部目标

下面目标是由 make 逻辑内部使用的,但假设有必要。能够在某些条件下从命令行调用。

depend

创建构建依赖关系,生成 Make.dep 文件和 .depend 文件。(注意:当前在使用 Windows 本地工具链的 Cygwin 下不支持构建依赖关系)

context

在每次目标构建时调用 context 目标以确保正确配置了 NuttX。

基本配置步骤包含在 include/nuttx 文件夹中创建 config.h 和 version.h 头文件,建立链接到配置文件夹的符号链接。

clean_context

这是 distclean 目标的一部分。它移除由 context 目标创建的全部头文件和符号链接。

4.4  构建选项

当然。不论什么 make 变量的值都能够从命令行覆盖。然而,有一个特殊的变量赋值选项可能对你很实用:

V=1

这是生成具体级别标志。假设你在命令行上指定 V=1,你将在构建时看到所使用的确切命令。当加入新的板卡或追踪编译时错误和警告时可能很实用。

5  all 目标依赖树

`                          |<--pass1dep-|<----------------------------------------╮
             |<--pass1deps-|            |<--tools/mkdeps$(HOSTEXEEXT)             |
             |     |       |                                                      |
             |  ╭--╯       |<--$(USERLIBS)(平面构建时为空)                          |
             |  |                                                                 |
             |  |           |<--pass2dep-|<---------------------------------------|
             |<---pass2deps-|            |<--tools/mkdeps$(HOSTEXEEXT)            |
             |  |  |        |                                                     |
             |  |  |        |<--$(NUTTXLIBS)(与配置有关)<--╮                       |
             |  |  |                                      |                       |
             |  |  |  ╭-----------------------------------╯                       |
             |  |  |  |                                                           |
             |  |  |  |<--lib/libsched$(LIBEXT)<--sched/libsched$(LIBEXT)<--------|
             |  |  |  |<--lib/libc$(LIBEXT)<--libc/libc$(LIBEXT)<-----------------|
             |  |  |  |<--lib/libmm$(LIBEXT)<--mm/libmm$(LIBEXT)<-----------------|
all<--$(BIN)-|  |  |  |<--lib/libarch$(LIBEXT)<--$(ARCH_SRC)/libarch$(LIBEXT)<----|
             |  |  |  |<--lib/libcxx$(LIBEXT)<--libxx/libcxx$(LIBEXT)<------------|
             |  |  |  |<--lib/libapps$(LIBEXT)<--$(APPDIR)/libapps$(LIBEXT)<------|
             |  |  |  |<--lib/libnet$(LIBEXT)<--net/libnet$(LIBEXT)<--------------|
             |  |  |  |<--lib/libfs$(LIBEXT)<--fs/libfs$(LIBEXT)<-----------------|
             |  |  |  |<--lib/libdrivers$(LIBEXT)<--drivers/libdrivers$(LIBEXT)<--|
             |  |  |  |<--lib/libbinfmt$(LIBEXT)<--binfmt/libbinfmt$(LIBEXT)<-----|
             |  |  |                                                              |
             |  |  ╰---------╮                                                    |
             |  |            |                                                    |
             |  ╰--------╮   |                                                    |
             |           |   |                                                    |
             |<--pass1<--╯   |                                                    |
             |               |                                                    |
             |<--pass2<------╯                                                    |
                                                                                  |
      ╭---------------------------------------------------------------------------╯
      |
      |           |<--check_context
      |           |
      |           |<--include/nuttx/config.h-|<--$(TOPDIR)/.config
      |           |                          |<--tools/mkconfig$(HOSTEXEEXT)
      |           |
      |           |<--include/nuttx/version.h-|<--$(TOPDIR)/.version
      |           |                           |<--tools/mkversion$(HOSTEXEEXT)
      |           |
      |<--context-|<--include/math.h<--include/nuttx/math.h
                  |<--include/float.h<--include/nuttx/float.h
                  |<--include/stdarg.h<--include/nuttx/stdarg.h
                  |
                  |            |<--include/arch<-Make.defs
                  |            |
                  |            |<--include/arch/board-|<--include/arch<--Make.defs
                  |            |                      |<--Make.defs
                  |            |
                  |<--dirlinks-|<--include/arch/chip-|<--include/arch<--Make.defs
                               |                     |<--Make.defs
                               |
                               |<--$(ARCH_SRC)/board<--Make.defs
                               |<--$(ARCH_SRC)/chip<--Make.defs
                               |<--include/apps<--Make.defs

6  all 目标编译过程

6.1  context 目标

由于 all 目标是默认目标。所以运行无參数的 make 命令即为编译 all 目标。首先会尝试编译 context 目标,当中,依据 .config 文件生成 config.h,有很多 C 文件包括了 config.h,以获得用户配置。注意当中的2个文件夹软链接,下文会用到:

$(ARCH_SRC)/board:

将 nuttx/configs/shenzhou/src 文件夹链接到 nuttx/arch/arm/src/board 文件夹。

$(ARCH_SRC)/chip:

将 nuttx/arch/arm/src/stm32 文件夹链接到 nuttx/arch/arm/src/chip 文件夹。

可是由于在安装 buildroot 时已经运行过一次 make context,并且运行 make clean 也不会删除 context 目标所生成的文件,所以这一步没做不论什么事。

6.2  pass1dep 目标

pass1dep: context tools/mkdeps$(HOSTEXEEXT)
        $(Q) for dir in $(USERDEPDIRS) ; do                 $(MAKE) -C $$dir TOPDIR="$(TOPDIR)" depend ;         done

平面构建是指将 NuttX 编译成单个二进制文件,构建的全部组件位于同样的地址空间,全部组件都能够訪问全部其他组件。

平面构建时 $(USERDEPDIRS) 为空,它也没做不论什么事。

6.3  $(USERLIBS) 目标

平面构建时 $(USERLIBS) 为空,它也没做不论什么事。

这样整个 pass1deps 目标就完毕了。

6.4  pass2dep 目标

pass2dep: context tools/mkdeps$(HOSTEXEEXT)
        $(Q) for dir in $(KERNDEPDIRS) ; do                 $(MAKE) -C $$dir TOPDIR="$(TOPDIR)" EXTRADEFINES=$(KDEFINE) depend;         done

依据 $(KERNDEPDIRS) 变量的值,在各级子文件夹下生成 Make.dep 和 .depend文件。

nuttx 文件夹下生成 Make.dep 和 .depend 文件的子文件夹有:

  • nuttx/arch/arm/src
  • nuttx/binfmt
  • nuttx/configs/shenzhou/src
  • nuttx/drivers
  • nuttx/fs
  • nuttx/libc
  • nuttx/libxx
  • nuttx/mm
  • nuttx/net
  • nuttx/sched

6.5  $(NUTTXLIBS) 目标

接下来就编译 pass2deps 目标中的 $(NUTTXLIBS),这是一个多目标。依据配置的不同,详细的目标也不同。它的工作是生成多个静态库文件。这里生成了10个静态库文件:

  • nuttx/lib/libapps.a
  • nuttx/lib/libarch.a
  • nuttx/lib/libbinfmt.a
  • nuttx/lib/libc.a
  • nuttx/lib/libcxx.a
  • nuttx/lib/libdrivers.a
  • nuttx/lib/libfs.a
  • nuttx/lib/libmm.a
  • nuttx/lib/libnet.a
  • nuttx/lib/libsched.a

以下以 libarch.a 为例。考查静态库的编译过程。

$(ARCH_SRC)/libarch$(LIBEXT): context
        $(Q) $(MAKE) -C $(ARCH_SRC) TOPDIR="$(TOPDIR)" libarch$(LIBEXT)

当中: $(ARCH_SRC) = nuttx/arch/arm/src。$(LIBEXT) = .a。传递的不是变量名,而是变量中的值。第2行的意思是先切换到文件夹 nuttx/arch/arm/src。再运行 make 命令,传递參数 TOPDIR。编译目标 libarch.a。这里的 make 调用的是文件夹 nuttx/arch/arm/src 中的 Makefile,编译的是该 Makefile 中的目标。在 nuttx/arch/arm/src/Makefile 中先定义
BIN = libarch$(LIBEXT),然后:

$(BIN) $(KBIN): $(OBJS)
        $(call ARCHIVE, [email protected], $(OBJS))

当中:OBJS = $(AOBJS) $(COBJS),而

AOBJS = $(ASRCS:.S=$(OBJEXT))

ASRCS = $(CHIP_ASRCS) $(CMN_ASRCS)

COBJS = $(CSRCS:.c=$(OBJEXT))

CSRCS = $(CHIP_CSRCS) $(CMN_CSRCS)

nuttx/arch/arm/src/Makefile 包括了 nuttx/arch/arm/src/chip/Make.defs,4个变量 CHIP_ASRCS、 CMN_ASRCS、 CHIP_CSRCS、 CMN_CSRCS 都是在 nuttx/arch/arm/src/chip/Make.defs 中定义的。多次赋值后的变量值例如以下:

CHIP_ASRCS = (空)

CMN_ASRCS = up_saveusercontext.S up_fullcontextrestore.S up_switchcontext.S vfork.S

CHIP_CSRCS = stm32_allocateheap.c stm32_start.c stm32_rcc.c stm32_lse.c stm32_lsi.c stm32_gpio.c stm32_exti_gpio.c stm32_flash.c stm32_irq.c stm32_timerisr.c stm32_dma.c stm32_lowputc.c stm32_serial.c stm32_spi.c stm32_sdio.c stm32_tim.c stm32_waste.c stm32_ccm.c
stm32_i2c.c stm32_idle.c stm32_pmstop.c stm32_pmstandby.c stm32_pmsleep.c stm32_pminitialize.c stm32_eth.c stm32_pwr.c stm32_rtc.c

CMN_CSRCS = up_assert.c up_blocktask.c up_copyfullstate.c up_createstack.c up_mdelay.c up_udelay.c up_exit.c up_initialize.c up_initialstate.c up_interruptcontext.c up_memfault.c up_modifyreg8.c up_modifyreg16.c up_modifyreg32.c up_releasepending.c up_releasestack.c
up_reprioritizertr.c up_schedulesigaction.c up_sigdeliver.c up_systemreset.c up_unblocktask.c up_usestack.c up_doirq.c up_hardfault.c up_svcall.c up_vfork.c

从这里能够查到哪些源文件被编译,哪些源文件没有被编译。

命令 $(call ARCHIVE, [email protected], $(OBJS)) 打包静态库 libarch.a。

如今回到nuttx/Makefile.unix:

lib/libarch$(LIBEXT): $(ARCH_SRC)/libarch$(LIBEXT)
        $(Q) install $(ARCH_SRC)/libarch$(LIBEXT) lib/libarch$(LIBEXT)

当中: install 是 Linux 命令。这里等同于复制。将 libarch.a 拷贝到 nuttx/lib 文件夹。

6.6  pass1 目标

pass1: pass1deps
ifeq ($(CONFIG_BUILD_2PASS),y)
        $(Q) if [ -z "$(CONFIG_PASS1_BUILDIR)" ]; then                 echo "ERROR: CONFIG_PASS1_BUILDIR not defined";                 exit 1;         fi
        $(Q) if [ ! -d "$(CONFIG_PASS1_BUILDIR)" ]; then                 echo "ERROR: CONFIG_PASS1_BUILDIR does not exist";                 exit 1;         fi
        $(Q) if [ ! -f "$(CONFIG_PASS1_BUILDIR)/Makefile" ]; then                 echo "ERROR: No Makefile in CONFIG_PASS1_BUILDIR";                 exit 1;         fi
        $(Q) $(MAKE) -C $(CONFIG_PASS1_BUILDIR) TOPDIR="$(TOPDIR)" LINKLIBS="$(LINKLIBS)" USERLIBS="$(USERLIBS)" "$(CONFIG_PASS1_TARGET)"
endif

平面构建时不做不论什么事。

6.7  pass2 目标

最后编译pass2

pass2: pass2deps
        $(Q) $(MAKE) -C $(ARCH_SRC) TOPDIR="$(TOPDIR)" EXTRA_OBJS="$(EXTRA_OBJS)" LINKLIBS="$(LINKLIBS)" EXTRADEFINES=$(KDEFINE) $(BIN)
        $(Q) if [ -w /tftpboot ] ; then                 cp -f $(BIN) /tftpboot/$(BIN).${CONFIG_ARCH};         fi
ifeq ($(CONFIG_RRLOAD_BINARY),y)
        @echo "MK: $(BIN).rr"
        $(Q) $(TOPDIR)/tools/mkimage.sh --Prefix $(CROSSDEV) $(BIN) $(BIN).rr
        $(Q) if [ -w /tftpboot ] ; then                 cp -f $(BIN).rr /tftpboot/$(BIN).rr.$(CONFIG_ARCH);         fi
endif
ifeq ($(CONFIG_INTELHEX_BINARY),y)
        @echo "CP: $(BIN).hex"
        $(Q) $(OBJCOPY) $(OBJCOPYARGS) -O ihex $(BIN) $(BIN).hex
endif
ifeq ($(CONFIG_MOTOROLA_SREC),y)
        @echo "CP: $(BIN).srec"
        $(Q) $(OBJCOPY) $(OBJCOPYARGS) -O srec $(BIN) $(BIN).srec
endif
ifeq ($(CONFIG_RAW_BINARY),y)
        @echo "CP: $(BIN).bin"
        $(Q) $(OBJCOPY) $(OBJCOPYARGS) -O binary $(BIN) $(BIN).bin
endif
        @echo "DUMP: $(BIN).out"
        $(Q) $(OBJDUMP) -x $(BIN) > $(HOME)/$(BIN).out
        @echo "DUMP: $(BIN).S"
        $(Q) $(OBJDUMP) -d -j .text -j .init_section -j .ARM.exidx -j .data -j .bss $(BIN) > $(HOME)/$(BIN).S
        cp -f $(BIN) $(BIN).hex $(BIN).bin $(HOME)

把静态库链接成 elf 格式的可运行文件。并转换成 .hex、 .bin 等其他格式的文件。

最后5行是我加上去的。生成符号文件、反汇编文件,并把一些终于文件拷贝到 Linux 用户主文件夹。由《NuttX 安装脚本》中的下面几行实现:

if !(grep -q ‘    @echo "DUMP: $(BIN).out"‘ Makefile.unix); then
  sed -i ‘/pass2:/,/^$/{
    /^$/i\    @echo "DUMP: $(BIN).out"
    /^$/i\    $(Q) $(OBJDUMP) -x $(BIN) > $(HOME)/$(BIN).out
    /^$/i\    @echo "DUMP: $(BIN).S"
    /^$/i\    $(Q) $(OBJDUMP) -d -j .text -j .init_section -j .ARM.exidx -j .data -j .bss $(BIN) > $(HOME)/$(BIN).S
    /^$/i\    cp -f $(BIN) $(BIN).hex $(BIN).bin $(HOME)
  }‘ Makefile.unix
fi

7  nuttx/Make.defs 文件

nuttx/Make.defs 文件是 Makefile 片断。从 nuttx/config/<板卡>/<目标配置>/Make.defs 复制而来。

由《NuttX 安装脚本》中的下面几行实现:

echo "nuttx配置"
cd $BASEDIR/$TOPDIR/nuttx/tools
./configure.sh $TARGETCONFIG

该 Makefile 片断提供架构和工具特定的构建选项。

它将在构建时被全部其他 Makefile 文件包括(一旦它被安装)。该 makefile 片断应定义:

  • 工具: CC、 LD、 AR、 NM、 OBJCOPY、 OBJDUMP
  • 工具选项: CFLAGS、 LDFLAGS

当该 Makefile 片断执行时,它将被传递构建根文件夹的路径 TOPDIR。该 Makefile 片断应包括:

$(TOPDIR)/.config: Nuttx 配置

$(TOPDIR)/tools/Config.mk: 一般定义

nuttx/Make.defs 文件里的定义可能依赖于 .config 文件里的一些设置。比如。假设 CONFIG_DEBUG=y。 CFLAGS将最有可能不同。

tools/Config.mk 文件包括额外的定义。这些定义可能在必要时被架构特定的 Make.defs 文件覆盖:

COMPILE、 ASSEMBLE、 ARCHIVE、 CLEAN 和 MKDEP 宏

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-10-10 21:03:28

NuttX 构建系统的相关文章

nixyx —— 一个小巧的项目工程/编译文件生成器(构建系统?)

恩..nixyx确实算不上是一个构建系统. 所谓构建系统,比如GNU的Autotools,那是一套很完整的构建体系,包括了程序的配置,编译和安装三大部分. 类似的软件还有:google的gyp.腾讯的Blade等.它们最大的好处在于,可以不考虑平台之间的差别,使用统一的配置文件和命令,做到跨平台部署. 它们往往还支持很多很高端的功能,比如集成自动测试,代码检查(Blade).. 可是我暂时不需要这些复杂的功能.我正在编写的nixy库是一个跨平台/编译器的C++库,它非常小,没必要使用大型的(或者

[Gradle] 在 Eclipse 下利用 gradle 构建系统

转载自:http://www.ibm.com/developerworks/cn/opensource/os-cn-gradle/ 构建系统时候常常要用到 Ant, Maven 等工具,对于初学者来说,它们还是过于复杂,上手还是需要时间的.本文将向读者介绍一种全新的构建项目的方式 gradle,它简单.上手快,能大大节省项目的时间和成本. 在 eclipse 下利用 gradle 构建系统 基本开发环境 操作系统:本教程使用的为 Windows Vista Enterprise, 如果您的系统是

CMake命令:CMake构建系统的骨架

CMake命令:CMake构建系统的骨架 80个命令(转载自http://www.cnblogs.com/coderfenghc/archive/2012/06/16/CMake_ch_01.html#2996205) CMD#1: add_custom_command为生成的构建系统添加一条自定义的构建规则. add_custom_command命令有两种主要的功能:第一种是为了生成输出文件,添加一条自定义命令. add_custom_command(OUTPUT output1 [outpu

Blade - 腾讯开源的构建系统 c/c++编译环境

typhoon-blade Blade is an advanced building system developed with python, majorly for C/C++ Blade 是一个现代构建系统,期望的目标是强大而好用,把程序员从构建的繁琐中解放出来. Blade主要定位于linux下的大型C++项目,密切配合研发流程,比如单元测试,持续集成,覆盖率统计等.但像unix下的文本过滤程序一样,保持相对的独立性,可以单独运行.目前重点支持i386/x86_64 Linux,未来可

基于Jenkins的自动构建系统开发_android总结

持续集成相关理论 1.1 极限编程的概述 1.1.1 极限编程的产生 2001年,为了解决许多公司的软件团队陷入不断增长的过程泥潭,一批业界专家一起概括出了一些可以让软件开发团队具有快速工作.响应变化能力的价值观和原则,他们称自己为敏捷联盟.敏捷开发过程的方法很多,主要有:SCRUM,Crystal,特征驱动软件开发(Feature Driven Development,简称FDD),自适应软件开发(Adaptive Software Development,简称ASD),以及最重要的极限编程(

打造一个全命令行的Android构建系统

IDE都是给小白程序猿的,大牛级别的程序猿一定是命令行控,终端控,你看大牛都是使用vim,emacs 就一切搞定" 这话说的尽管有些绝对.可是也不无道理.做开发这行要想效率高,自己主动化还真是缺少不了命令行工具,由于仅仅有命令行才是最佳的人机交互工具. 事实上IDE也是底层也是调用命令行工具而已,仅仅只是给普通开发人员呈现一个更友好的开发界面. 这里可不是宣扬让大家放弃IDE都改命令行,仅仅是每种事物都有他存在的理由,不管是编程语言还是工具都是一个原则 "没有最好的,仅仅有最合适的&q

进击的云安全! 让你轻松构建系统安全基线

安全基线是一个业务系统的最小安全保证,即该业务系统需要满足的最基本的安全要求.构造安全基线是系统安全工程的首要步骤,同时也是进行安全评估.发现和解决业务系统安全问题的先决条件. 从具体的内容来看,安全基线的内容主要分为三个方面:系统存在的安全漏洞.系统配置的脆弱性.系统状态的监控,这是必须满足的最小要求组成.因而在传统的环境下,即使是一个有经验的安全运维人员,完成一个系统的安全基线(加固系统环境和应用环境)平均花费的时间也要在30分钟到40分钟左右. 并且在构建的整个过程中,运维人员还将面临以下

使用maven多模块来构建系统时,spring初始化报错的问题

最近在实验maven结构的maven工程时,碰到一个问题,springbean总是初始化失败: Related cause: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper' defined in file [D:\workspace\mavenweb\mavenweb-webapp\src\main\webapp\WEB-INF

深入浅出Android Gradle构建系统(三:build task)

接上一篇  深入浅出Android Gradle构建系统(二:项目结构) 构建任务(Build Tasks) java和Android通用的任务 在build文件中使用了Android或者Java插件之后就会自动创建一系列可以运行的任务. Gradle中有如下一下默认约定的任务: 1. assemble 该任务包含了项目中的所有打包相关的任务,比如java项目中打的jar包,Android项目中打的apk 2. check 该任务包含了项目中所有验证相关的任务,比如运行测试的任务 3. buil