【问题标题】:Arduino-like Makefile with dependencies...?具有依赖项的类似 Arduino 的 Makefile ......?
【发布时间】:2017-06-29 15:32:39
【问题描述】:

我目前正在尝试使用SDCC OS compilerSTM8 microcontroller 开发C 库和项目模板。我的目标是类似于 Arduino 的(几乎)兼容 NOOB 的设置 - 但使用 make+shellscripts 而不是 IDE(我的野心是有限度的......)

目前我正在努力使用 make 来自动检测依赖项。在 Arduino 中,用户只包含相关的标头,例如“#include LCD-lib”,构建机制自动检测依赖关系并链接各个库。无需手动将其添加到 IDE 或 Makefile。

我喜欢它的简单性,但到目前为止,我在创建相应的 Makefile 方面失败了。基本上这是 Makefile 应该实现的目标:

  1. 扫描项目根目录中的 *.c 文件以查找包含的头文件。请注意,这些文件位于不同的 lib 文件夹中
  2. 将所有包含的头文件和(如果存在)相应的 C 文件添加到构建过程中
  3. 为了最大限度地减少编译时间和大小,在构建过程中必须跳过 lib 文件夹中未使用的 C 文件

我相信 make 可以做到以上所有 - 但不是在我的 make 经验水平... :-(

这是我想到的文件夹结构:

├── Library
│   ├── Base
│   │   ├── general STM8 sources and headers 
│   ├── STM8S_Discovery
│   │   └── board specific sources and headers
│   └── User
│       └── optional user library sources and headers
├── Projects
│   ├── Examples (to be filled)
│   │   └── Basic_Project
│   │       ├── compile_upload.sh  --> double-click to build and upload
│   │       ├── config.h
│   │       ├── main.c
│   │       └── Makefile           --> should detect dependencies in ./*.c and ./*.h
│   └── User_Projects (still empty)
└── Tools
    ├── programmer.py              --> for programming (already works from make)
    └── terminal.py                --> for serial terminal (already works from make)

我知道有很多问题要问,但方便的 Makefile 是我的主要障碍。任何帮助都非常感谢!!!提前非常感谢!

问候, Georg Icking-Konert

【问题讨论】:

  • 您是否考虑过下载另一个 IDE,例如 Simplicity Studio 等基于 Eclipse 的 IDE,构建一个具有概述结构的简单项目并查看 IDE 如何生成 Makefile?这将是一个很好的起点。
  • 请注意,在 Arduino 环境中,用户界面和/或库生成的额外代码会在每个编译单元中添加到用户 .ino 代码之前。您也可以使用 Makefiles 来执行此操作,为处理添加一个额外的步骤。 (一种方法是使用预处理器将预处理的源文件创建到构建目录中,为每个编译单元提供单独的部分。)您需要在这里做的是手动完成整个构建过程(并且写出来),包括不同的选项处理;然后,将其用作编写 Makefile 的大纲。

标签: c makefile build arduino dependencies


【解决方案1】:

您可以找到关于 GNU make here 的自动依赖生成方法的讨论以及示例实现。你没有说你在使用 GNU make,所以我只是假设。

我不知道这对你来说是否足够;从您对您的要求的陈述中并不清楚。

【讨论】:

  • 感谢您的快速响应,我会看看。是的,我正在使用 GNU make。很抱歉没有提及。如果这符合我的需求和理解水平,我会告诉你...
【解决方案2】:

注意:我意识到这个答案并不能满足您的所有要求,实际上这种方法仍然需要您列出您在项目中使用的相关 Arduino 库的名称,以及应该包含在项目中的目录的路径。但是,这个解决方案是我能想到的最接近您的要求的解决方案,它可能仍然有助于其他人在以后阅读这个问题。


我为此使用 Arduino Makefile

  1. Makefile.master 放在您的主工作空间目录中
  2. 当您开始一个新的 Arduino 项目时,您将其创建为工作区中的子目录
    • 创建一个带有.pde/.ino 扩展名的文件,其中包含setup() 和`loop() 方法
    • 将剩余逻辑放入.c/.cpp/.h/.hpp文件中
  3. 添加一个项目 Makefile,在这个子目录中设置项目优化设置,例如:

    # Your Arduino environment.
    ARD_HOME = /usr/share/arduino
    ARD_BIN = $(ARD_HOME)/hardware/tools/avr/bin
    
    # Monitor Baudrate
    MON_SPEED = 4800
    
    # Board settings.
    BOARD = uno
    PORT = /dev/ttyACM0
    PROGRAMMER = stk500v2
    
    # Where to find header files and libraries.
    INC_DIRS =
    MY_LIB_DIRS =
    LIBS =
    LIB_DIRS = $(addprefix $(ARD_HOME)/libraries/, $(LIBS)) $(MY_LIB_DIRS)
    
    include ../Makefile.master
    
  4. 使用make allmake uploadmake monitor等编译和运行

确保您在 Unix/Linux 机器(或同等设备)上安装了 picocom 作为控制台串行监视器。在 MAC-OS 上,您可以通过相应地设置 MON_CMD 变量来使用 screen


Makefile.master:

Makefile.masterAlan Burlison编写,Matthieu Weber修改,可在here找到。

我做了一些更改以使其适合我的配置,特别是我添加了以下代码行:

### DEBUG Compilation ###
ifeq ($(DEBUG), 1)
    ARD_FLAGS += -DDEBUG_PROJ
    C_FLAGS += -g
    CXX_FLAGS += -g
else
    ARD_FLAGS += -DNDEBUG_PROJ
endif

随后从 Makefile.master 中的默认 C/CXX _FLAGS 条目中删除了 -g 选项。这样符号信息是不会在release代码中添加的,只有在使用DEBUG=1编译的时候才会被

屏蔽的代码
#ifdef DEBUG_PROJ
    /* debug code here */
#endif
// or
#ifndef NDEBUG_PROJ
    /* debug code here */
#endif

找到进入二进制文件的途径,从而产生更小的发布可执行文件。

您可以在此处找到我自己的版本的Makefile.master

#
# Copyright 2011 Alan Burlison, alan@bleaklow.com.  All rights reserved.
# Subsequently modified by Matthieu Weber, matthieu.weber@jyu.fi.
# Subsequently modified by Patrick Trentin, patrick.trentin.88@gmail.com
# Use is subject to license terms.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  1. Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#
#  2. Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY ALAN BURLISON "AS IS" AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL ALAN BURLISON OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Makefile for building Arduino projects outside of the Arduino environment
#
# This makefile should be included into a per-project Makefile of the following
# form:
#
# ----------
# BOARD = mega
# PORT = /dev/term/0
# INC_DIRS = ../common
# LIB_DIRS = ../libraries/Task ../../libraries/VirtualWire
# include ../../Makefile.master
# ----------
#
# Where:
#   BOARD    : Arduino board type, from $(ARD_HOME)/hardware/boards.txt
#   PORT     : USB port
#   INC_DIRS : List pf directories containing header files
#   LIB_DIRS : List of directories containing library source
#
# Before using this Makefile you can adjust the following macros to suit
# your environment, either by editing this file directly or by defining them in
# the Makefile that includes this one, in which case they will override the
# definitions below:
#   ARD_REV      : arduino software revision, e.g. 0017, 0018
#   ARD_HOME     : installation directory of the Arduino software.
#   ARD_BIN      : location of compiler binaries
#   AVRDUDE      : location of avrdude executable
#   AVRDUDE_CONF : location of avrdude configuration file
#   PROGRAMMER   : avrdude programmer type
#   MON_TERM     : terminal command for serial monitor
#   MON_CMD      : serial monitor command
#   MON_SPEED    : serial monitor speed
#

# Global configuration.
ARD_REV ?= 100
ARD_HOME ?= /usr/local/arduino
ARD_BIN ?= /usr/bin
AVRDUDE ?= $(ARD_HOME)/hardware/tools/avrdude
AVRDUDE_CONF ?= $(ARD_HOME)/hardware/tools/avrdude.conf
MON_TERM ?= xterm
MON_SPEED ?= 57600
MON_CMD ?= picocom
PORT ?= $(HOME)/dev/arduino
BOARD ?= atmega328

### Nothing below here should require editing. ###

# Check for the required definitions.

ifndef BOARD
    $(error $$(BOARD) not defined)
endif
ifndef PORT
    $(error $$(PORT) not defined)
endif

# Version-specific settings
ARD_BOARDS = $(ARD_HOME)/hardware/arduino/boards.txt
ARD_SRC_DIR = $(ARD_HOME)/hardware/arduino/cores/arduino
ARD_MAIN = $(ARD_SRC_DIR)/main.cpp

# Standard macros.
SKETCH = $(notdir $(CURDIR))
BUILD_DIR = build
VPATH = $(LIB_DIRS)

# Macros derived from boards.txt
MCU := $(shell sed -n 's/$(BOARD)\.build\.mcu=\(.*\)/\1/p' < $(ARD_BOARDS))
F_CPU := $(shell sed -n 's/$(BOARD)\.build\.f_cpu=\(.*\)/\1/p' < $(ARD_BOARDS))
UPLOAD_SPEED := \
    $(shell sed -n 's/$(BOARD)\.upload\.speed=\(.*\)/\1/p' < $(ARD_BOARDS))
PROGRAMMER := \
    $(shell sed -n 's/$(BOARD)\.upload\.protocol=\(.*\)/\1/p' < $(ARD_BOARDS))
ARD_VAR := \
    $(shell sed -n 's/$(BOARD)\.build\.variant=\(.*\)/\1/p' < $(ARD_BOARDS))

# More Version-specific settings
ARD_VAR_DIR = $(ARD_HOME)/hardware/arduino/variants/$(ARD_VAR)

# Build tools.
CC = $(ARD_BIN)/avr-gcc
CXX = $(ARD_BIN)/avr-g++
CXXFILT = $(ARD_BIN)/avr-c++filt
OBJCOPY = $(ARD_BIN)/avr-objcopy
OBJDUMP = $(ARD_BIN)/avr-objdump
AR = $(ARD_BIN)/avr-ar
SIZE = $(ARD_BIN)/avr-size
NM = $(ARD_BIN)/avr-nm
MKDIR = mkdir -p
RM = rm -rf
MV = mv -f
LN = ln -f

# Compiler flags.
INC_FLAGS = \
    $(addprefix -I,$(INC_DIRS)) $(addprefix -I,$(LIB_DIRS)) -I$(ARD_SRC_DIR) -I$(ARD_VAR_DIR)
ARD_FLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -DARDUINO=$(ARD_REV)
C_CXX_FLAGS = \
    -Wall -Wextra -Wundef -Wno-unused-parameter \
    -fdiagnostics-show-option -Wa,-adhlns=$(BUILD_DIR)/$*.lst
C_FLAGS = \
    $(C_CXX_FLAGS) -std=gnu99 -Wstrict-prototypes -Wno-old-style-declaration
CXX_FLAGS = $(C_CXX_FLAGS)

### DEBUG Compilation ###
ifeq ($(DEBUG), 1)
    ARD_FLAGS += -DDEBUG_PROJ
    C_FLAGS += -g
    CXX_FLAGS += -g
else
    ARD_FLAGS += -DNDEBUG_PROJ
endif

# Optimiser flags.
#     optimise for size, unsigned by default, pack data.
#     separate sections, drop unused ones, shorten branches, jumps.
#     don't inline, vectorise loops. no exceptions.
#     no os preamble, use function calls in prologues.
# http://gcc.gnu.org/onlinedocs/gcc-4.3.5/gcc/
# http://www.tty1.net/blog/2008-04-29-avr-gcc-optimisations_en.html
OPT_FLAGS = \
     -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \
    -ffunction-sections -fdata-sections -Wl,--gc-sections,--relax \
    -fno-inline-small-functions -fno-tree-scev-cprop -fno-exceptions \
    -ffreestanding -mcall-prologues

# Build parameters.
IMAGE = $(BUILD_DIR)/$(SKETCH)
ARD_C_SRC = $(wildcard $(ARD_SRC_DIR)/*.c)
ARD_CXX_SRC = $(wildcard $(ARD_SRC_DIR)/*.cpp)
ARD_C_OBJ = $(patsubst %.c,%.o,$(notdir $(ARD_C_SRC)))
ARD_CXX_OBJ = $(patsubst %.cpp,%.o,$(notdir $(ARD_CXX_SRC)))
ARD_LIB = arduino
ARD_AR = $(BUILD_DIR)/lib$(ARD_LIB).a
ARD_AR_OBJ = $(ARD_AR)($(ARD_C_OBJ) $(ARD_CXX_OBJ))
ARD_LD_FLAG = -l$(ARD_LIB)

# Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
$(ARD_AR)(Tone.o) : CXX_FLAGS += -w

# Sketch libraries.
LIB_C_SRC = $(foreach ld,$(LIB_DIRS),$(wildcard $(ld)/*.c))
LIB_CXX_SRC = $(foreach ld,$(LIB_DIRS),$(wildcard $(ld)/*.cpp))
LIB_SRC = $(LIB_C_SRC) $(LIB_CXX_SRC)
ifneq "$(strip $(LIB_C_SRC) $(LIB_CXX_SRC))" ""
    LIB_C_OBJ = $(patsubst %.c,%.o,$(notdir $(LIB_C_SRC)))
    LIB_CXX_OBJ = $(patsubst %.cpp,%.o,$(notdir $(LIB_CXX_SRC)))
    LIB_LIB = library
    LIB_AR = $(BUILD_DIR)/lib$(LIB_LIB).a
    LIB_AR_OBJ = $(LIB_AR)($(LIB_C_OBJ) $(LIB_CXX_OBJ))
    LIB_LD_FLAG = -l$(LIB_LIB)
endif

# Sketch PDE source.
SKT_PDE_SRC = $(wildcard *.pde *.ino)
ifneq "$(strip $(SKT_PDE_SRC))" ""
    SKT_PDE_OBJ = $(BUILD_DIR)/$(SKETCH)_pde.o
endif

# C and C++ source.
SKT_C_SRC = $(wildcard *.c)
SKT_CXX_SRC = $(wildcard *.cpp)
ifneq "$(strip $(SKT_C_SRC) $(SKT_CXX_SRC))" ""
    SKT_C_OBJ = $(patsubst %.c,%.o,$(SKT_C_SRC))
    SKT_CXX_OBJ = $(patsubst %.cpp,%.o,$(SKT_CXX_SRC))
    SKT_LIB = sketch
    SKT_AR = $(BUILD_DIR)/lib$(SKT_LIB).a
    SKT_AR_OBJ = $(SKT_AR)/($(SKT_C_OBJ) $(SKT_CXX_OBJ))
    SKT_LD_FLAG = -l$(SKT_LIB)
endif

# Definitions.
define run-cc
    @ $(CC) $(ARD_FLAGS) $(INC_FLAGS) -M -MT '$@($%)' -MF $@_$*.dep $<
    $(CC) -c $(C_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) $(INC_FLAGS) \
        $< -o $(BUILD_DIR)/$%
    @ $(AR) rc $@ $(BUILD_DIR)/$%
    @ $(RM) $(BUILD_DIR)/$%
    @ $(CXXFILT) < $(BUILD_DIR)/$*.lst > $(BUILD_DIR)/$*.lst.tmp
    @ $(MV) $(BUILD_DIR)/$*.lst.tmp $(BUILD_DIR)/$*.lst
endef

define run-cxx
    @ $(CXX) $(ARD_FLAGS) $(INC_FLAGS) -M -MT '$@($%)' -MF $@_$*.dep $<
    $(CXX) -c $(CXX_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) $(INC_FLAGS) \
        $< -o $(BUILD_DIR)/$%
    @ $(AR) rc $@ $(BUILD_DIR)/$%
    @ $(RM) $(BUILD_DIR)/$%
    @ $(CXXFILT) < $(BUILD_DIR)/$*.lst > $(BUILD_DIR)/$*.lst.tmp
    @ $(MV) $(BUILD_DIR)/$*.lst.tmp $(BUILD_DIR)/$*.lst
endef

# Rules.
.PHONY : all clean upload monitor upload_monitor

all : $(BUILD_DIR) $(IMAGE).hex

clean :
    $(RM) $(BUILD_DIR)

$(BUILD_DIR) :
    $(MKDIR) $@

$(SKT_PDE_OBJ) : $(SKT_PDE_SRC)
    if [ $(ARD_REV) -ge 100 ]; then \
    echo '#include "Arduino.h"' > $(BUILD_DIR)/$(SKETCH)_pde.cpp; \
    else \
    echo '#include "WProgram.h"' > $(BUILD_DIR)/$(SKETCH)_pde.cpp; \
    fi
    echo '#include "$(SKT_PDE_SRC)"' >> $(BUILD_DIR)/$(SKETCH)_pde.cpp
    $(LN) $(SKT_PDE_SRC) $(BUILD_DIR)/$(SKT_PDE_SRC)
    cd $(BUILD_DIR) && $(CXX) -c $(subst build/,,$(CXX_FLAGS)) \
        $(OPT_FLAGS) $(ARD_FLAGS) -I.. \
        $(patsubst -I..%,-I../..%,$(INC_FLAGS)) \
        $(SKETCH)_pde.cpp -o $(@F)

(%.o) : $(ARD_SRC_DIR)/%.c
    $(run-cc)

(%.o) : $(ARD_SRC_DIR)/%.cpp
    $(run-cxx)

(%.o) : %.c
    $(run-cc)

(%.o) : %.cpp
    $(run-cxx)

$(BUILD_DIR)/%.d : %.c
    $(run-cc-d)

$(BUILD_DIR)/%.d : %.cpp
    $(run-cxx-d)

$(IMAGE).hex : $(ARD_AR_OBJ) $(LIB_AR_OBJ) $(SKT_AR_OBJ) $(SKT_PDE_OBJ)
    $(CC) $(CXX_FLAGS) $(OPT_FLAGS) $(ARD_FLAGS) -L$(BUILD_DIR) \
        $(SKT_PDE_OBJ) $(SKT_LD_FLAG) $(LIB_LD_FLAG) $(ARD_LD_FLAG) -lm \
        -o $(IMAGE).elf
    $(OBJCOPY) -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load \
        --no-change-warnings --change-section-lma .eeprom=0 $(IMAGE).elf \
        $(IMAGE).eep
    $(OBJCOPY) -O ihex -R .eeprom $(IMAGE).elf $(IMAGE).hex
    $(OBJDUMP) -h -S $(IMAGE).elf | $(CXXFILT) -t > $(IMAGE).lst
    $(SIZE) $(IMAGE).hex

upload : all
    - pkill -f '$(MON_CMD).*$(PORT)'
    - sleep 1
    - stty -F $(PORT) hupcl
    - $(AVRDUDE) -V -C$(AVRDUDE_CONF) -p$(MCU) -c$(PROGRAMMER) -P$(PORT) \
        -b$(UPLOAD_SPEED) -D -Uflash:w:$(IMAGE).hex:i

monitor :
    LD_LIBRARY_PATH= LD_PRELOAD= \
        $(MON_TERM) -title '$(BOARD) $(PORT)' \
        -e '$(MON_CMD) -b $(MON_SPEED) $(PORT)' &

upload_monitor : upload monitor

-include $(wildcard $(BUILD_DIR)/*.dep))
# vim:ft=make

使用示例:

给定一个 dir-tree,如下所示:

Base_Dir
├── Library
│   ├── Base
│   │   ├── general STM8 sources and headers 
│   ├── STM8S_Discovery
│   │   └── board specific sources and headers
│   └── User
│       └── optional user library sources and headers
├── Projects
│   ├── Examples (to be filled)
│   │   └── Basic_Project
│   │       ├── config.h
│   │       ├── example.ino
│   │       └── Makefile           --> should detect dependencies in ./*.c and ./*.h
...

您可以将Makefile.master 放在Projects 中,然后假设:

  • 此项目只需要 Library/BaseLibrary/User 中的内容
  • 您需要在您的项目中使用 LiquidCrystal Arduino 库

然后您将以下Makefile 添加到Basic Project

# Your Arduino environment.
BASE_DIR = /path/to/Base_Dir              # to edit
ARD_HOME = /usr/share/arduino             # to edit, maybe
ARD_BIN = $(ARD_HOME)/hardware/tools/avr/bin

# Monitor Baudrate
MON_SPEED = 4800

# Board settings.
BOARD = uno
PORT = /dev/ttyACM0
PROGRAMMER = stk500v2

# Where to find header files and libraries.
INC_DIRS =
MY_LIB_DIRS= $(BASE_DIR)/Library/Base $(BASE_DIR)/Library/User 
LIBS= LiquidCrystal
LIB_DIRS = $(addprefix $(ARD_HOME)/libraries/, $(LIBS)) $(MY_LIB_DIRS)

include ../../Makefile.master

注意common.h应该会被自动检测到,因为它位于.中,应该不需要将后者添加到INC_DIRS中。


最后说明: 上次我测试此配置时,我使用的是 Arduino 源代码的 1.0.5 版本,它运行完美。

【讨论】:

    【解决方案3】:

    首先非常感谢大家的快速和实质性的支持!我应该早点问...

    现在回到我的(不再是)问题。我现在明白我实际上问了两个不同的问题:

    • 对标头的依赖 -> 由 Patrick 或 MadScientist 的提议解决
    • 检测例如需要什么库分析 main.o 中的调用,或通过 main.c 中的 #includes ...?

    我知道从内部实现第二个要困难得多...!?但是 Patrick 的 Makefile 使得手动配置非常方便。所以这对我来说没关系:-)

    [add timeslip...] 好的,考虑了更多,下面的工作/有意义吗?

    1. 调用例如Makefile 中的 gawk 或 python 脚本,用于将所有 #included 标头与项目目录中的 *.c*.h 隔离李>
    2. 对于每个包含的 xyz.h,检查对应的 make_xyz 是否存在于其中一个 Lib 目录中(将是 Lib 的一部分)
    3. 如果是,则将该 makefile 包含在主 Makefile 中进行编译

    这是否有意义...?再次感谢,祝您有美好的一天,无论您身在何处!

    问候,Georg Icking-Konert

    【讨论】:

    • 这个空间是为真正解决您问题的答案保留的,您应该按问题上的edit按钮来添加更多信息创建一个答案。话虽如此,是的,这是一种可行的方法,如果您碰巧实现了 1. 点,在此处发布该代码将是您自己问题的一个很好的答案。 :)
    • 嗨帕特里克,感谢您的建议。作为一个新手,我不知道这个规则。希望这个现在是正确的...... ;-)
    • 再次嗨,好的,我将尝试将上述 1. 实现为 python 脚本,因为我也将 python 用于串行终端。希望不会花太长时间。我是否应该通过具有标准化名称的文件传递所需的 make_xyz 列表,该文件静态包含在 master-Makefile 中...?
    • 你可以让你的脚本直接生成Makefile,这是最简单的
    【解决方案4】:

    再次感谢您的支持!

    按照上面的建议,我编写了一个小的 python 脚本,它使用 gcc(或者实际上是 sdcc)依赖生成器。以下脚本使用 gcc 扫描所有项目 .c 文件以查找 #included 标头。然后在项目和库文件夹中搜索相应的头文件。如果存在相应的 .c 文件(与标题相同的路径和名称),则将其添加到 Makefile。重复此过程,直到不再找到新的标头。

    结果是一个 Makefile,它只构建项目 .c 文件中的 #included 模块 - 就像在 Arduino IDE 中一样。它可能并不优雅,但工作:-)

    脚本中的第 95-106 行是编译器和项目特定的,必须进行相应调整。玩得开心,再次感谢!

    #!/usr/bin/python
    
    '''
     automatically create a Makefile with dependencies from
     all .c files in a starting directory 
    '''
    
    # required modules
    import sys
    import os
    import platform
    import shlex
    from subprocess import Popen, PIPE
    
    # set OS specific
    OS = platform.system()
    if OS == 'Windows':
      MAKE      = 'mingw32-make.exe'
    else:
      MAKE      = 'make'
    
    
    ##################
    # helper functions
    ##################
    
    #########
    def getchar():
      """
       python equivalent of getchar()
      """
      ch = 0
      if OS == 'Windows':
        import msvcrt as m
        ch = m.getch()
        sys.stdio.flush()
        sys.stderr.flush()
      else:
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
      return ch
      # end getchar()
    
    
    #########
    def listFiles( start='.', pattern='.c' ):
      """
       return set of matching files in project folder incl. subfolders
      """
      result = set()
      for root, dirs, files in os.walk(start):
        for file in files:
          if file.endswith(pattern):
            #print(os.path.join(root, file))
            result.add(os.path.join(root, file))
      return result
      # end listFiles()
    
    
    #########
    def listSubdirs( start='.' ):
      """
       return set of subdirectories in given folder
      """
      result = set()
      for root, dirs, files in os.walk(start):
        for dir in dirs:
          #print(os.path.join(root, dir))
          result.add(os.path.join(root, dir))
      return result
      # end listFiles()
    
    
    #########
    def get_exitcode_stdout_stderr(cmd):
      """
       execute the external command and get its exitcode, stdout and stderr.
      """
      args = shlex.split(cmd)
      proc = Popen(args, stdout=PIPE, stderr=PIPE)
      out, err = proc.communicate()
      exitcode = proc.returncode
      return exitcode, out, err
    
    
    
    ##################
    # main program
    ##################
    
    # set compile search paths
    ROOT_DIR = '../../../'
    TOOL_DIR = ROOT_DIR + 'Tools/'
    LIB_ROOT = ROOT_DIR + 'Library/'
    PRJ_ROOT = '.'
    OBJDIR   = 'output'
    TARGET   = 'main.ihx'
    
    # set command for creating dependencies and set search paths 
    CC       = 'sdcc '
    CFLAGS   = '-mstm8 --std-sdcc99 --std-c99 '
    LFLAGS   = '-mstm8 -lstm8 --out-fmt-ihx '
    DEPEND   = '-MM '
    INCLUDE  = '-I. '
    for dir in listSubdirs(PRJ_ROOT):
      INCLUDE += '-I' + dir + ' '
    for dir in listSubdirs(LIB_ROOT):
      INCLUDE += '-I' + dir + ' '
    
    # get set of .c files in project folder incl. subdirectories
    source_todo = listFiles(PRJ_ROOT,".c")
    source_done = set()
    header_done = set()
    object_done = set()
    
    
    # print message
    sys.stdout.write('start Makefile creation ... ')
    sys.stdout.flush()
    
    
    # generate generic Makefile header
    Makefile = open('Makefile', 'wb')
    Makefile.write('OBJDIR   = '+OBJDIR+'\n')
    Makefile.write('TARGET   = '+TARGET+'\n\n')
    Makefile.write('.PHONY: clean all default objects\n\n')
    Makefile.write('.PRECIOUS: $(TARGET)\n\n')
    Makefile.write('default: $(OBJDIR) $(OBJDIR)/$(TARGET)\n\n')
    Makefile.write('all: default\n\n')
    Makefile.write('# create output folder\n')
    Makefile.write('$(OBJDIR):\n')
    Makefile.write('    mkdir -p $(OBJDIR)\n')
    Makefile.write('    rm -fr -- -p\n\n')
    
    # iteratively add project sources to Makefile
    while (len(source_todo) > 0):
    
      # get next pending source and mark as done
      source = source_todo.pop()
      source_done.add(source)
    
      # convert Windows path to POSIX for Makefile
      if OS == 'Windows':
        source = source.replace('\\','/')
    
      # use compiler generate dependency list
      cmd = CC+DEPEND+CFLAGS+INCLUDE+source
      #print cmd
      exitcode, out, err = get_exitcode_stdout_stderr(cmd)
      if (exitcode != 0):
        print 'error: ' + err
        getchar()
        exit()
    
      # append .c file with dependency and compile instruction to Makefile
      Makefile.write('$(OBJDIR)/'+out)
      #print(out)
      Makefile.write('\t'+CC+CFLAGS+INCLUDE+'-c $< -o $@\n\n')
    
      # extract file list including object[0], source[1] and headers[2..N]
      out = out.replace(':', '')
      out = out.replace('\\', '')
      out = out.replace('\n', '')
      out = out.split()
      #print out
    
      # for all files returned by compiler...
      for next in out:
    
        # append object files for linker
        if next.endswith('.rel'):
          object_done.add(next)
    
        # if corresponding source to header exists, add to pending sources
        if next.endswith('.h'):
          if next not in header_done:           # not yet in list
            header_done.add(next)                 # add to treated headers
            if (os.path.isfile(next[:-1]+'c')):   # if corresponding .c exists, add to todo list
              source_todo.add(next[:-1]+'c')
    
    
    # link project object files
    Makefile.write('$(OBJDIR)/$(TARGET): ')
    for next in object_done:
      Makefile.write('$(OBJDIR)/'+next+' ')
    Makefile.write('\n')
    Makefile.write('\t'+CC+LFLAGS)
    for next in object_done:
      Makefile.write('$(OBJDIR)/'+next+' ')
    Makefile.write(' -o $@\n')
    
    # close Makefile.dep
    Makefile.close()
    
    
    print('done\n')
    sys.stdout.write('press any key to exit')
    getchar()
    exit()
    
    # END OF MODULE
    

    【讨论】:

      猜你喜欢
      • 2011-07-26
      • 2012-02-08
      • 1970-01-01
      • 2016-09-14
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      • 2012-09-28
      相关资源
      最近更新 更多