前些天试着用vscode来编写和编译wxwidgets程序,当程序只有单个源文件时该方法还是可以的。但如果一个工程有多个源文件需要编译后再链接成一个应用程序时,该方法就不哪么方便了。

  1. 必须单个选中每个源文件后运行编译任务,将源文件编译成.o文件
  2. 再创建链接任务,将生成的.o文件链接成一个应用程序

所以决定通过Makefile来组织编译工程文件。

wxWidgets.mk

wxWidgets.mk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 指定wxWidgets的根目录
wxWidgets_root_dir := D:/wxWidgets_3.1.5
# 指定wxWidgets库文件的目录名
wxWidgets_dll_dir_name := gcc810_dll

# 指定编译debug版还是release版, [debug, release]
# build_config ?= release
# 指定wxWidgets库文件的编译类型: [msvc,gcc]类型不同编译时引入的头文件路径不同
wxWidgets_dll_compile_type ?= gcc

# 通配符查找以d.a结尾的wxWidgets库文件
wxWidgets_debug_libs = $(wildcard $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/*d.a)
# 通配符查找d_*.a结尾的wxWidgets库文件
wxWidgets_debug_libs += $(wildcard $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/*d_*.a)
# 找出满足上面两模式的wxWigets库文件:libwxmsw31ud_propgrid.a
wxWidgets_debug_specific_lib = $(wildcard $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/*d_*d.a)
# 通过文本替换得出libwxmsw31ud_propgrid.a的release库libwxmsw31u_propgrid.a,注意原字符和替换字符间不能有空格!
wxWidgets_release_specific_lib = $(subst d_,_, $(wxWidgets_debug_specific_lib))
# 过滤掉上面两个特殊库文件,这儿存在递归引用需有:=赋值
wxWidgets_debug_libs := $(filter-out $(wxWidgets_debug_specific_lib) $(wxWidgets_release_specific_lib) , $(wxWidgets_debug_libs))
# 上面过滤完后需补上这个特殊的
wxWidgets_debug_libs += $(wxWidgets_debug_specific_lib)
# 通配符查找wxWidgets所有库文件
wxWidgets_all_libs = $(wildcard $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/*.a)
# 全部库过滤掉debug库就是release库
wxWidgets_release_libs = $(filter-out $(wxWidgets_debug_libs), $(wxWidgets_all_libs))
# release链接时引用的wxWidgets库文件,去文件名开头的lib添加-l参数,去文件名末尾的.a。"libwxzlib.a"需做特殊处理
wxWidgets_link_release_libs = $(subst .a,, $(subst -l.a,lib.a,$(subst lib,-l, $(notdir $(wxWidgets_release_libs)))))
# Debug链接时引用的wxWidgets库文件
wxWidgets_link_debug_libs = $(subst .a,,$(subst -ld.a,libd.a,$(subst lib,-l, $(notdir $(wxWidgets_debug_libs)))))
# wxWidgets链接时的库文件搜索目录
wxWidgets_link_lib_paths = $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)
# wxWidgets编译时的头文件目录
wxWidgets_compile_include_paths = $(wxWidgets_root_dir)/include
# 根据dll编译类型和编译配置添加不同的头文件目录
ifeq ($(wxWidgets_dll_compile_type),gcc)
# ifeq "$(build_config)" "debug"
# 改用直接在CXXFLAGS、CPPFLAGS中查找是否定义DEBUG变量来判断是debug版还是release
ifneq "$(findstring DEBUG, $(CXXFLAGS) $(CPPFLAGS) $(PREDEFINE))" ""
wxWidgets_compile_include_paths += $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/mswud
else
wxWidgets_compile_include_paths += $(wxWidgets_root_dir)/lib/$(wxWidgets_dll_dir_name)/mswu
endif
else ifeq "$(wxWidgets_dll_compile_type)" "msvc"
wxWidgets_compile_include_paths += $(wxWidgets_root_dir)/include/msvc
endif

该文件中主要定义了wxWidgets的安装目录以及dll库文件夹的名称,根据前面的配置得出:
  1. 头文件路径
  2. 链接时引入的库文件名称

Makefile

该文件中设置了5个规则:

规则名 作用 使用方法
build 编译工程 make build
clean 清理工程 make clean
rebuild 重新编译工程 make rebuild
run 运行应用程序 make run
debug 调试应用程序 make debug
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
######################################################################
# mingw32-make help <--> 显示使用方法
# 存在的bug:
# 1. 在使用多进程进行rebuild时会出错
#######################################################################

# 输出文件名
output_file_name = build/HelloWorld.exe

# 设置所有的源文件
# source_files = $(wildcard src/*.cpp) $(wildcard src/*.rc)
source_files = $(call recursive_search_files, *.cpp *.rc, . src)

# 头文件路径
include_paths =

# 库文件路径
lib_paths =

# 指定C++编译器,在多编译共存时需明确指定编译器
CXX = d:/mingw-64/i686-8.1.0-release-win32-sjlj-rt_v6-rev0/mingw32/bin/g++.exe
# 指定资源文件编译器mingw下是windres.exe
RCC = d:/mingw-64/i686-8.1.0-release-win32-sjlj-rt_v6-rev0/mingw32/bin/windres.exe

# 编译预定义变量
PREDEFINE = _UNICODE __WX__ _DEBUG
# 编译参数 windows下-fexec-charset=GBK中文才能正常显示
CXXFLAGS = -g -o0 -Wall -pipe -fexec-charset=GBK
# 链接选项
LINKOPTIONS = -mwindows

# 指定编译debug版还是release版, [debug, release]
# build_config = release
# 改用直接在CXXFLAGS、CPPFLAGS中查找是否定义DEBUG变量来判断是debug版还是release
ifneq "$(findstring DEBUG, $(CXXFLAGS) $(CPPFLAGS) $(PREDEFINE))" ""
output_file_name := $(basename $(output_file_name))_debug.exe
else
output_file_name := $(basename $(output_file_name))_release.exe
endif

# 自定一个调用函数,该函数有两个参数,
# 第1个参数:指定匹配的文件模式,如:*.cpp *.rc
# 第2个参数:指定搜索的路径,如 . src (目录后不能加/)
# 在第2个参数指定的所有目录下搜索,参数1指定的所有类型文件
define recursive_search_files =
$(foreach path, $(2), \
$(foreach file_pattern,$(1), \
$(wildcard $(path)/$(file_pattern)) \
) \
)
endef

include wxwidgets.mk

.PHONY: help clean link build rebuild run other

help:
@echo " ############ usage ############"
@echo "$(MAKE) build build project"
@echo '$(MAKE) rebuild rebuild project'
@echo "$(MAKE) clean clean project"
@echo "$(MAKE) run run application"
@echo "$(MAKE) debug debug application"

# 将所有的.rc文件编译成.rc.o文件
%.rc.o:
@echo '>>>' compile... $(filter %$(basename $(notdir $@)), $(source_files))
@$(RCC) $(addprefix -I,$(wxWidgets_compile_include_paths)) \
-i $(filter %$(basename $(notdir $@)), $(source_files)) \
-o $(dir $(output_file_name))obj/$(notdir $@)


# 将所有的.cpp和.c文件编译成.o文件
%.o:
@echo '>>>' $@ compile... $(filter %$(addsuffix .cpp, $(basename $(notdir $@))), $(source_files))
# 如果输出目录中没有obj目录,先创建目录.
# 在recipe中好像只能用$(if)不能用ifeq
$(if "$(realpath $(dir $(output_file_name))obj)" "" ,\
@mkdir -p $(dir $(output_file_name))obj,\
)
# 编译.o对应的.cpp文件
@$(CXX) $(CXXFLAGS) $(addprefix -D, $(PREDEFINE)) \
$(addprefix -I,$(wxWidgets_compile_include_paths)) \
-c $(filter %$(addsuffix .cpp, $(basename $(notdir $@))), $(source_files)) \
-o $(dir $(output_file_name))obj/$(notdir $@)

# ifeq "$(build_config)" "debug"
# 改用直接在CXXFLAGS、CPPFLAGS中查找是否定义DEBUG变量来判断是debug版还是release
ifneq "$(findstring DEBUG, $(CXXFLAGS) $(CPPFLAGS) $(PREDEFINE))" ""
wxwidgets_libs = $(wxWidgets_link_debug_libs)
else
wxwidgets_libs = $(wxWidgets_link_release_libs)
endif

# 链接.o文件生成应用程序
build: $(patsubst %.rc,%.rc.o, $(addprefix $(dir $(output_file_name))obj/, $(notdir $(source_files:.cpp=.o))))
@echo '>>>' link... $(output_file_name)
# 将依赖关系中的所有.o文件链接成应用程序
@$(CXX) -o $(output_file_name) $^ \
$(addprefix -L,$(wxWidgets_link_lib_paths) $(lib_paths)) \
$(wxwidgets_libs) \
$(LINKOPTIONS)


clean:
@-rm -rfv $(dir $(output_file_name))

rebuild: clean build

run:
# shell脚本判断目标文件是否存在,若不存在先调用 make build生成应用
@if [ -a $(output_file_name) ];then \
start $(output_file_name); \
else \
$(MAKE) build; \
start $(output_file_name); \
fi

debug:
# shell脚本判断目标文件是否存在,若不存在先调用 make build生成应用程序
@if [ ! -f $(output_file_name) ]; \
then \
$(MAKE) build ; \
fi; \
gdb $(output_file_name); \

other:
@echo '>>>>>>>>>>>>>>>>>>>>>>>>>>>'

make build

  1. output_file_name目录下创建一个obj子目录
  2. source_files中的所有.cpp文件和.rc文件全都编译生.o文件并存放在obj目录下
  3. 将obj下的所有.o文件链接成一个应用程序

make clean

  1. 删除所有.o文件和应用程序

make rebuild

  1. 先删除所有.o文件和应用程序
  2. 重新构建应用程序

make run

  1. 判断应用程序是否存在,若不存在先构建应用程序
  2. 若存在启动应用程序

make debug

  1. 判断应用程序是否存在,若不存在先构建应用程序
  2. 若存在调用gdb调试应用程序

以上Makefile存在的问题

  1. 在使用make rebuild -j4可能会报目录不存在的错
  2. 当某个源文件发生更新时,若.o文件若在make build只会重新链接不会编译

对上面两个文件的改进

wxWidgets.mk

对文件做了如下改进:

  1. 增加了wxWidgets_root_pathwxWidgets_dll_path目录的末尾/的处理。
    以满足设置目录时可以带/也可以不带
  2. 增加了对以上两个目录正确性的判断处理,如果设置的目录路径错误。提示错误信息
wxWidgets.mk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 指定wxWidgets的根目录
wxWidgets_root_path ?= D:/wxWidgets_3.1.5

# 指定wxWidgets库文件的目录名
wxWidgets_dll_path ?= $(wxWidgets_root_path)/lib/gcc810_dll

# 若目录名以/结尾,则去掉末尾的/号
wxWidgets_root_path := $(patsubst %/,%,$(wxWidgets_root_path))
wxWidgets_dll_path := $(patsubst %/,%,$(wxWidgets_dll_path))

# 判断目录是否存在的函数变量,若存在返回目录名,若不存在返回空值
check_dir_is_exsit = $(shell if [ -d $(1) ];then echo $(1);fi)
# 检查wxWidgets根目录是否正确
ifeq "$(call check_dir_is_exsit, $(wxWidgets_root_path))" ""
$(error $(wxWidgets_root_path) is not exsit ! Please reconfigure wxWidgets_root_dir)
endif
# 检查wxWidgets的dll目录是否正确
ifeq "$(call check_dir_is_exsit, $(wxWidgets_dll_path))" ""
$(error $(wxWidgets_dll_path) is not exsit ! Please reconfigure wxWidgets_dll_dir)
endif

# 指定编译debug版还是release版, [debug, release]
# build_config ?= release
# 指定wxWidgets库文件的编译类型: [msvc,gcc]类型不同编译时引入的头文件路径不同
wxWidgets_dll_compile_type ?= gcc

# 通配符查找以d.a结尾的wxWidgets库文件
wxWidgets_debug_libs = $(wildcard $(wxWidgets_dll_path)/*d.a)
# 通配符查找d_*.a结尾的wxWidgets库文件
wxWidgets_debug_libs += $(wildcard $(wxWidgets_dll_path)/*d_*.a)
# 找出满足上面两模式的wxWigets库文件:libwxmsw31ud_propgrid.a
wxWidgets_debug_specific_lib = $(wildcard $(wxWidgets_dll_path)/*d_*d.a)
# 通过文本替换得出libwxmsw31ud_propgrid.a的release库libwxmsw31u_propgrid.a,注意原字符和替换字符间不能有空格!
wxWidgets_release_specific_lib = $(subst d_,_, $(wxWidgets_debug_specific_lib))
# 过滤掉上面两个特殊库文件,这儿存在递归引用需有:=赋值
wxWidgets_debug_libs := $(filter-out $(wxWidgets_debug_specific_lib) $(wxWidgets_release_specific_lib) , $(wxWidgets_debug_libs))
# 上面过滤完后需补上这个特殊的
wxWidgets_debug_libs += $(wxWidgets_debug_specific_lib)
# 通配符查找wxWidgets所有库文件
wxWidgets_all_libs = $(wildcard $(wxWidgets_dll_path)/*.a)
# 全部库过滤掉debug库就是release库
wxWidgets_release_libs = $(filter-out $(wxWidgets_debug_libs), $(wxWidgets_all_libs))
# release链接时引用的wxWidgets库文件,去文件名开头的lib添加-l参数,去文件名末尾的.a。"libwxzlib.a"需做特殊处理
wxWidgets_link_release_libs = $(subst .a,, $(subst -l.a,lib.a,$(subst lib,-l, $(notdir $(wxWidgets_release_libs)))))
# Debug链接时引用的wxWidgets库文件
wxWidgets_link_debug_libs = $(subst .a,,$(subst -ld.a,libd.a,$(subst lib,-l, $(notdir $(wxWidgets_debug_libs)))))
# wxWidgets链接时的库文件搜索目录
wxWidgets_link_lib_paths = $(wxWidgets_dll_path)
# wxWidgets编译时的头文件目录
wxWidgets_compile_include_paths = $(wxWidgets_root_path)/include

# 改用直接在CXXFLAGS、CPPFLAGS中查找是否定义DEBUG变量来判断是debug版还是release
ifneq "$(findstring DEBUG, $(CXXFLAGS) $(CPPFLAGS) $(PREDEFINE))" ""
wxWidgets_compile_include_paths += $(wxWidgets_dll_path)/mswud
wxWidgets_link_libs = $(wxWidgets_link_debug_libs)
else
wxWidgets_compile_include_paths += $(wxWidgets_dll_path)/mswu
wxWidgets_link_libs = $(wxWidgets_link_release_libs)
endif

Makefile

对文件做了如下改进:

  1. 重构了build目标,修复原来源文件改变时,因为.o文件未更新而导致目标应用程序不更新的Bug.
  2. 重构了rebuild目录,修复了原来的make rebuild -j8出错的问题.
  3. 重构了run目录,修复当再次编译出错,目标应用程序未生成时仍启动应用程序的bug.
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# 输出文件名
output_file_name = build/HelloWorld.exe
# 设置所有的源文件
source_files = $(call pattern_recursive_files, *.cpp *.rc, ./ ./src/)
# 头文件路径
include_paths = ./ ./src
# 库文件路径
lib_paths =
# 指定C++编译器,在多编译共存时需明确指定编译器
CXX = d:/mingw-64/i686-8.1.0-release-win32-sjlj-rt_v6-rev0/mingw32/bin/g++.exe
# 指定资源文件编译器mingw下是windres.exe
RCC = d:/mingw-64/i686-8.1.0-release-win32-sjlj-rt_v6-rev0/mingw32/bin/windres.exe
# 编译预定义变量
PREDEFINE = _UNICODE __WX__ _DEBUG
# 编译参数 windows下-fexec-charset=GBK中文才能正常显示
CXXFLAGS = -g -o0 -Wall -pipe -fexec-charset=GBK
# 链接选项
LINKOPTIONS = -mwindows

# 自定一个调用函数,该函数有两个参数,
# 第1个参数:指定匹配的文件模式,如:*.cpp *.rc
# 第2个参数:指定搜索的路径,如 . src (目录后不能加/)
# 在第2个参数指定的所有目录下搜索,参数1指定的所有类型文件
define pattern_recursive_files
$(foreach path, $(2), \
$(foreach file_pattern,$(1), \
$(wildcard $(patsubst %/,%,$(path))/$(file_pattern)) \
) \
)
endef

ifneq "$(findstring DEBUG, $(CXXFLAGS) $(CPPFLAGS) $(PREDEFINE))" ""
output_file_name := $(join $(basename $(output_file_name)), _debug.exe)
else
output_file_name := $(join $(basename $(output_file_name)), _release.exe)
endif

include wxWidgets.mk

.PHONY: help build clean rebuild debug

help:
@echo " ############ usage ############"
@echo "$(MAKE) build build project"
@echo '$(MAKE) rebuild rebuild project'
@echo "$(MAKE) clean clean project"
@echo "$(MAKE) run run application"
@echo "$(MAKE) debug debug application"

build: $(output_file_name)

# :号两边同时用%会报"mingw32-make: Circular src/sample.rc.o <- src/sample.rc dependency dropped."
# 这种写法$@为"*.rc.o"
%.rc.o:
@echo compile... $(filter %$(basename $(notdir $@)), $(source_files)) '->' $@
# 判断输出目录是否存在,若不存在先创建目录
@if [ ! -d $(dir $@) ];then \
echo "mkdir -p $(dir $@)"; \
mkdir -p $(dir $@);\
fi
# 编译rc文件生成.o文件
@$(RCC) $(addprefix -I,$(wxWidgets_compile_include_paths)) \
-i $(filter %$(basename $(notdir $@)), $(source_files)) \
-o $@

# .cpp.o依赖所有源文件中的.cpp文件和头文件中的.h文件
%.cpp.o: $(filter %.cpp, $(source_files)) $(call pattern_recursive_files, *.h, $(include_paths))
@echo compile... $(filter %$(basename $(notdir $@)), $^) '->' $@
# 判断输出目录是否存在,若不存在先创建目录
@if [ ! -d $(dir $@) ];then \
echo "mkdir -p $(dir $@)"; \
mkdir -p $(dir $@);\
fi
# 编译rc文件生成.o文件
@$(CXX) $(CXXFLAGS) $(addprefix -D, $(PREDEFINE)) \
$(addprefix -I,$(wxWidgets_compile_include_paths)) \
-c $(filter %$(basename $(notdir $@)), $^) \
-o $@

$(output_file_name): $(addprefix $(join $(dir $(output_file_name)), obj/), $(addsuffix .o, $(notdir $(source_files))))
@echo link... $^ '->' $@
@$(CXX) -o $@ $^ \
$(addprefix -L,$(wxWidgets_link_lib_paths) $(lib_paths)) \
$(wxWidgets_link_libs) \
$(LINKOPTIONS)

clean:
@-rm -rfv $(dir $(output_file_name))

# rebuild: clean build
# 向上面这样写clean和build是并行的,在使用make -j8时会有问题
rebuild:
@$(MAKE) --no-print-directory clean
@$(MAKE) --no-print-directory build

run:
# shell脚本判断目标文件是否存在,若不存在先调用 make build生成应用
@if [ ! -f $(output_file_name) ];then \
$(MAKE) --no-print-directory build; \
fi; \
[ $? ] && start $(output_file_name)

debug:
# shell脚本判断目标文件是否存在,若不存在先调用 make build生成应用程序
@if [ ! -f $(output_file_name) ]; \
then \
$(MAKE) build ; \
fi; \
[ $? ] && gdb $(output_file_name); \

test:
@echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
@echo wxWidgets_debug_libs '->' $(words $(wxWidgets_debug_libs))
@echo wxWidgets_release_libs '->' $(words $(wxWidgets_release_libs))
@echo $(wxWidgets_link_libs)
@echo "---------------------------------------"
@echo $(addprefix $(join $(dir $(output_file_name)), obj/), $(addsuffix .o, $(notdir $(source_files))))

注意事项

在家中电脑运行make runmake debug时能正常启动应用程序,但在公司电脑却无法启动和调试应用。可能是因为公司电脑Msys2中也安装了mingw32的原因。最后重装msys2就正常了!