Postgresql源码(140)理解PG的编译流程(make、Makefile、Makefile.global.in)

news2025/1/22 17:45:38

PG16

PG中使用的makefile看起来代码比较多,但是实际逻辑比较简单,这里做一些抽象总结。

总结

  1. Makefile.global.in的$(recurse)宏自动生成了target,可以方便的进入内存目录进行编译。
all: all-common-recurse
all-common-recurse:  submake-generated-headers
   MAKE -C common all

all: all-backend-recurse
all-backend-recurse:  submake-generated-headers
   MAKE -C backend all

install: install-common-recurse
install-common-recurse:  submake-generated-headers
   MAKE -C common install

install: install-backend-recurse
install-backend-recurse:  submake-generated-headers
   MAKE -C backend install

check: check-common-recurse
check-common-recurse:  submake-generated-headers  temp-install
   MAKE -C common check

check: check-backend-recurse
check-backend-recurse:  submake-generated-headers  temp-install
   MAKE -C backend check
  1. Makefile.global.in同时定义了对.o合.bc文件的生成规则:
%.o : %.c
	@if test ! -d $(DEPDIR); then mkdir -p $(DEPDIR); fi
	$(COMPILE.c) -o $@ $< -MMD -MP -MF $(DEPDIR)/$(*F).Po

%.o : %.cpp
	@if test ! -d $(DEPDIR); then mkdir -p $(DEPDIR); fi
	$(COMPILE.cc) -o $@ $< -MMD -MP -MF $(DEPDIR)/$(*F).Po

%.bc : %.c
	$(COMPILE.c.bc) -o $@ $<

%.bc : %.cpp
	$(COMPILE.cxx.bc) -o $@ $<
  1. 在最内层目录的Makefile定义了OBJS例如:OBJS=aset.o mcxt.o ...,然后调用common.mk文件,对OBJS中的每一个元素进行隐式编译,common.mk:
    在这里插入图片描述
    简化版内层Makefile:
OBJS = aset.o mcxt.o

# 没有OBJS生成规则,make隐式生成.o文件,调用上面定义的%.o : %.c规则
objfiles.txt: $(OBJS)
	# echo or touch objfiles
  1. 如果打开了llvm,简化版内层Makefile:
OBJS = aset.o mcxt.o

# 没有aset.bc mcxt.bc生成规则,make隐式生成.bc文件,调用上面定义的%.bc : %.c规则。
objfiles.txt: $(patsubst %.o,%.bc, $(OBJS))     ## objfiles.txt: aset.bc mcxt.bc
# 没有OBJS生成规则,make隐式生成.o文件,调用上面定义的%.o : %.c规则
$(patsubst %.o,%.bc, $(OBJS)): $(OBJS)          ## aset.bc mcxt.bc: aset.o mcxt.o

1 执行make后发生了什么?

简单工程中的makefile中可以看到很多target,依赖关系一般也比较直接,例如下面例子中:

OrcV2CBindingsAddObjectFile 依赖 OrcV2CBindingsAddObjectFile.o 依赖 OrcV2CBindingsAddObjectFile.c

all: __BUILD_DIR \
		$(BUILD_DIR)/OrcV2CBindingsAddObjectFile \
		$(BUILD_DIR)/OrcV2CBindingsBasicUsage

.PHONY: __BUILD_DIR

$(BUILD_DIR)/OrcV2CBindingsAddObjectFile: $(BUILD_DIR)/OrcV2CBindingsAddObjectFile.o
	${CXX} $< ${CXXFLAGS} ${LLVM_LD_FLAGS} -o $@ 
$(BUILD_DIR)/OrcV2CBindingsAddObjectFile.o: OrcV2CBindingsAddObjectFile.c
	${CC} -c $< ${CFLAGS} ${LLVM_CC_FLAGS} -o $@

但打开PG根目录下的Makefile:

subdir = src
top_builddir = ..
include Makefile.global

SUBDIRS = \
	common \
	port \
	timezone \
	backend \
	backend/utils/mb/conversion_procs \
	backend/snowball \
	include \
	interfaces \
	backend/replication/libpqwalreceiver \
	backend/replication/pgoutput \
	fe_utils \
	bin \
	pl \
	makefiles \
	test/regress \
	test/isolation \
	test/perl

ifeq ($(with_llvm), yes)
SUBDIRS += backend/jit/llvm
endif

# There are too many interdependencies between the subdirectories, so
# don't attempt parallel make here.
.NOTPARALLEL:

$(recurse)

...
...
  • 看不到target信息,代码的可读性比较差,因为所有target都是生成的。优点是target动态生成,代码量少,比较灵活。
  • 所有的细节都封装在$(recurse)中,具体在Makefile.global.in中定义。

2 Makefile.global.in

截取关键代码:


standard_targets = all install installdirs uninstall distprep clean distclean maintainer-clean coverage check checkprep installcheck init-po update-po
# these targets should recurse even into subdirectories not being built:
standard_always_targets = distprep clean distclean maintainer-clean

.PHONY: $(standard_targets) install-strip html man installcheck-parallel update-unicode

# make `all' the default target
all:


##########################################################################
#
# Programs and flags

# Compilers

CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
PG_SYSROOT = @PG_SYSROOT@

override CPPFLAGS := $(ICU_CFLAGS) $(CPPFLAGS)

ifdef PGXS
override CPPFLAGS := -I$(includedir_server) -I$(includedir_internal) $(CPPFLAGS)
else # not PGXS
override CPPFLAGS := -I$(top_srcdir)/src/include $(CPPFLAGS)
ifdef VPATH
override CPPFLAGS := -I$(top_builddir)/src/include $(CPPFLAGS)
endif
endif # not PGXS

CC = @CC@
GCC = @GCC@
SUN_STUDIO_CC = @SUN_STUDIO_CC@
CXX = @CXX@
CFLAGS = @CFLAGS@
CFLAGS_SL = @CFLAGS_SL@
# *_MODULE are for flags applied to extension libraries
CFLAGS_SL_MODULE = @CFLAGS_SL_MODULE@
CXXFLAGS_SL_MODULE = @CXXFLAGS_SL_MODULE@
CFLAGS_UNROLL_LOOPS = @CFLAGS_UNROLL_LOOPS@
CFLAGS_VECTORIZE = @CFLAGS_VECTORIZE@
CFLAGS_CRC = @CFLAGS_CRC@
PERMIT_DECLARATION_AFTER_STATEMENT = @PERMIT_DECLARATION_AFTER_STATEMENT@
CXXFLAGS = @CXXFLAGS@

LLVM_CPPFLAGS = @LLVM_CPPFLAGS@
LLVM_CFLAGS = @LLVM_CFLAGS@
LLVM_CXXFLAGS = @LLVM_CXXFLAGS@

# Kind-of compilers

BISON = @BISON@
BISONFLAGS = @BISONFLAGS@ $(YFLAGS)
FLEX = @FLEX@
FLEXFLAGS = @FLEXFLAGS@ $(LFLAGS)
DTRACE = @DTRACE@
DTRACEFLAGS = @DTRACEFLAGS@
ZIC = @ZIC@

# Linking

AR = @AR@
AROPT = crs
LIBS = @LIBS@
LDAP_LIBS_FE = @LDAP_LIBS_FE@
LDAP_LIBS_BE = @LDAP_LIBS_BE@
UUID_LIBS = @UUID_LIBS@
LLVM_LIBS=@LLVM_LIBS@


# Tree-wide build support

all install check installcheck: submake-generated-headers

.PHONY: submake-generated-headers

submake-generated-headers:
ifndef NO_GENERATED_HEADERS
ifeq ($(MAKELEVEL),0)
	$(MAKE) -C $(top_builddir)/src/backend generated-headers
endif
endif



##########################################################################
#
# Recursive make support

define _create_recursive_target
.PHONY: $(1)-$(2)-recurse
$(1): $(1)-$(2)-recurse
$(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install)
	$$(MAKE) -C $(2) $(3)
endef

recurse = $(foreach target,$(if $1,$1,$(standard_targets)),$(foreach subdir,$(if $2,$2,$(SUBDIRS)),$(eval $(call 

在最后面我们看到了recurse的定义:
recurse = $(foreach target,$(if $1,$1,$(standard_targets)),$(foreach subdir,$(if $2,$2,$(SUBDIRS)),$(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))))
这里调用了_create_recursive_target函数来生成所有目录的target。

PG的例子太复杂,这里抽象出来一个最小用例来继续分析。

3 $(recurse)最小DEMO

Makefile


subdir = src
top_builddir = ..
include Makefile.global

SUBDIRS = \
	common \
	port \
	timezone \
	backend

$(recurse)

Makefile.global


standard_targets = all install check
.PHONY: $(standard_targets)


all:


###############################
all install check installcheck: submake-generated-headers

.PHONY: submake-generated-headers

submake-generated-headers:
ifndef NO_GENERATED_HEADERS
ifeq ($(MAKELEVEL),0)
	$(info ------------GENERATEHEADERS------------)
endif
endif

###############################
define _create_recursive_target
.PHONY: $(1)-$(2)-recurse
$(1): $(1)-$(2)-recurse
$(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install)
	$(info ------------GENERATE TATGETS------------)
	$(info $$1: $1, $$2: $2, $$3: $3)
	$(info $(1): $(1)-$(2)-recurse)
	$(info $(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install))
	$(info MAKE -C $(2) $(3))
	$$(MAKE) -C $(2) $(3)
endef

recurse = $(foreach target,$(if $1,$1,$(standard_targets)),$(foreach subdir,$(if $2,$2,$(SUBDIRS)),$(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))))



# recurse = \
#     $(foreach target,$(if $1,$1,$(standard_targets)),\
#         $(foreach subdir,$(if $2,$2,$(SUBDIRS)),\
#             $(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))\
#         )\
#     )

执行结果:
在这里插入图片描述

4 $(recurse)最小DEMO结果分析

recurse = $(foreach target,$(if $1,$1,$(standard_targets)),$(foreach subdir,$(if $2,$2,$(SUBDIRS)),$(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))))

展开后

recurse = \
    $(foreach target,$(if $1,$1,$(standard_targets)),\
        $(foreach subdir,$(if $2,$2,$(SUBDIRS)),\
            $(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))\
        )\
    )
  • 外层循环遍历standard_targets = all install check,也就是PG定义的所有make指令,需要为每个指令定义target。
  • 内层循环遍历SUBDIRS = common backend,这个是Makefile中定义的,也就是谁include Makefile.global,谁负责定义好SUBDIRS。表示当前目录下,哪些子目录需要进行编译。

调用_create_recursive_target函数,为makefile动态定义target:

define _create_recursive_target
.PHONY: $(1)-$(2)-recurse
$(1): $(1)-$(2)-recurse
$(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install)
	$$(MAKE) -C $(2) $(3)
endef

函数执行后,生成一系列target,两个小细节:

  • 如果target是all install check installcheck集中情况,需要增加submake-generated-headers依赖,这个是生成头文件的,PG的一些头文件是编译时生成出来的。
  • 如果target是check,需要增加temp-install依赖,temp-install是负责regress测试的target。
all: all-common-recurse
all-common-recurse:  submake-generated-headers
	MAKE -C common all

all: all-backend-recurse
all-backend-recurse:  submake-generated-headers
	MAKE -C backend all

install: install-common-recurse
install-common-recurse:  submake-generated-headers
	MAKE -C common install

install: install-backend-recurse
install-backend-recurse:  submake-generated-headers
	MAKE -C backend install

check: check-common-recurse
check-common-recurse:  submake-generated-headers  temp-install
	MAKE -C common check

check: check-backend-recurse
check-backend-recurse:  submake-generated-headers  temp-install
	MAKE -C backend check

5 继续完善demo

  • 前面提到的demo只构造了target,并没有真正编译.c文件。
  • 继续增加了.c文件编译到.o的部分。
$ tree
.
├── common
│   ├── aset.c
│   └── Makefile
├── common.mk
├── Makefile
└── Makefile.global

1 directory, 5 files

common/Makefile

subdir = src/backend/utils/mmgr
top_builddir = ../../../..
include ../Makefile.global

OBJS = \
	aset.o

include ../common.mk

common.mk

subsysfilename = objfiles.txt

ifneq ($(subdir), src/backend)
all: $(subsysfilename)
endif

objfiles.txt: $(OBJS)
	$(if $(filter-out $(OBJS),$?),( $(if $(SUBDIROBJS),cat $(SUBDIROBJS); )echo $(addprefix $(subdir)/,$(OBJS)) ) >$@,touch $@)


Makefile


subdir = src
top_builddir = ..
include Makefile.global

SUBDIRS = \
	common \
	backend

$(recurse)

Makefile.global


standard_targets = all install check
.PHONY: $(standard_targets)


all:


###############################
all install check installcheck: submake-generated-headers

.PHONY: submake-generated-headers

submake-generated-headers:
ifndef NO_GENERATED_HEADERS
ifeq ($(MAKELEVEL),0)
	$(info ------------GENERATEHEADERS------------)
endif
endif

###############################
define _create_recursive_target
.PHONY: $(1)-$(2)-recurse
$(1): $(1)-$(2)-recurse
$(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install)
	$(info ------------GENERATE TATGETS------------)
	$(info $$1: $1, $$2: $2, $$3: $3)
	$(info $(1): $(1)-$(2)-recurse)
	$(info $(1)-$(2)-recurse: $(if $(filter all install check installcheck, $(3)), submake-generated-headers) $(if $(filter check, $(3)), temp-install))
	$(info MAKE -C $(2) $(3))
	$$(MAKE) -C $(2) $(3)
endef

recurse = $(foreach target,$(if $1,$1,$(standard_targets)),$(foreach subdir,$(if $2,$2,$(SUBDIRS)),$(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))))



# recurse = \
#     $(foreach target,$(if $1,$1,$(standard_targets)),\
#         $(foreach subdir,$(if $2,$2,$(SUBDIRS)),\
#             $(eval $(call _create_recursive_target,$(target),$(subdir),$(if $3,$3,$(target))))\
#         )\
#     )

这里主要需要关注的就是common.mk。每个源码目录中都有一些文件需要编译,所以都需要配一个Makefile,这些Makefile中相同的逻辑抽取到了common.mk中。

objfiles.txt: $(OBJS)
	...
	...

注意这里OBJS = aset.o,这里makefile构造objfiles.txt依赖aset.o,而aset.o触发了Makefile的隐式规则,会自动从aset.c编译为aset.o。

除了规则外,具体的编译方法也可以在Makefile中定义,例如PG的makefile.global.in中定义了对.o文件的处理方法。

%.o : %.c
	@if test ! -d $(DEPDIR); then mkdir -p $(DEPDIR); fi
	$(COMPILE.c) -o $@ $< -MMD -MP -MF $(DEPDIR)/$(*F).Po

%.o : %.cpp
	@if test ! -d $(DEPDIR); then mkdir -p $(DEPDIR); fi
	$(COMPILE.cc) -o $@ $< -MMD -MP -MF $(DEPDIR)/$(*F).Po

llvm支持:common.mk中还有一段,如果打开了llvm后,objfiles.txt会同时依赖.o和.bc文件。

ifeq ($(with_llvm), yes)
objfiles.txt: $(patsubst %.o,%.bc, $(OBJS))
$(patsubst %.o,%.bc, $(OBJS)): $(OBJS)
endif

在makefile.global.in中也定义了对.bc文件的处理方法:

%.bc : %.c
	$(COMPILE.c.bc) -o $@ $<

%.bc : %.cpp
	$(COMPILE.cxx.bc) -o $@ $<

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java数据结构——优先队列

目录 引言1. 优先队列2. 优先队列的实现2.1 堆的概念2.2 堆的创建2.2.1 堆的向下调整2.3 堆的插入2.4 堆的删除 3. 总结 引言 前面一篇文章讲了二叉树&#xff0c;本篇将讲述数据结构中的优先队列&#xff0c;优先队列则需要用到完全二叉树来实现。 1. 优先队列 队列&#x…

51c大模型~合集105

我自己的原文哦~ https://blog.51cto.com/whaosoft/13101924 #刚刚&#xff0c;ChatGPT开始有了执行力&#xff01; 现在 AI 智能体可以 24*7 小时为你打工。 2025 刚过去了半个月&#xff0c;OpenAI 在智能体领域「开大」了。 今天&#xff0c;OpenAI 正在为 ChatGPT 推出…

从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CICD)

从零到上线&#xff1a;Node.js 项目的完整部署流程&#xff08;包含 Docker 和 CI/CD&#xff09; 目录 项目初始化&#xff1a;构建一个简单的 Node.js 应用设置 Docker 环境&#xff1a;容器化你的应用配置 CI/CD&#xff1a;自动化构建与部署上线前的最后检查&#xff1a;…

《自动驾驶与机器人中的SLAM技术》ch4:基于预积分和图优化的 GINS

前言&#xff1a;预积分图优化的结构 1 预积分的图优化顶点 这里使用 《自动驾驶与机器人中的SLAM技术》ch4&#xff1a;预积分学 中提到的散装的形式来实现预积分的顶点部分&#xff0c;所以每个状态被分为位姿&#xff08;&#xff09;、速度、陀螺零偏、加计零偏四种顶点&am…

Ubuntu系统更改IP,保姆级教程

原理概述 本篇文章所用工具&#xff1a; Xshell&#xff1a;点击下载 VMware Workstation Pro&#xff1a;点击下载 密钥需要自行搜索所下载的VMware对应版本密钥。 IP 地址 IP 地址&#xff08;Internet Protocol Address&#xff09;是分配给每个连接到计算机网络的设备的…

【Linux】进程管理(一篇入门-进程:基本概念、PCB进程块、进程的创建、等待、终止、状态,exec函数族的使用)

本节主要内容&#xff1a;进程的一些基本概念&#xff0c;进程控制块PCB&#xff0c;以及如何在一个C程序里创建进程、终止进程、等待进程&#xff0c;包括孤儿进程、僵尸进程等特殊状态的进程&#xff0c;并涉及回收进程的概念。最终是exec函数族的函数使用方法及用途。 程序与…

RabbitMQ 在实际应用时要注意的问题

1. 幂等性保障 1.1 幂等性介绍 幂等性是数学和计算机科学中某些运算的性质,它们可以被多次应⽤,⽽不会改变初始应⽤的结果. 应⽤程序的幂等性介绍 在应⽤程序中,幂等性就是指对⼀个系统进⾏重复调⽤(相同参数),不论请求多少次,这些请求对系统的影响都是相同的效果. ⽐如数据库…

EXCEL+Python搞定数据处理(第一部分:Python入门-第1章:为什么要用Python为Excel编程)

参考资料&#xff1a; ExcelPython飞速搞定数据分析与处理&#xff0c;[瑞士] 费利克斯朱姆斯坦 著&#xff0c;中国工信出版社、人民邮电出版社出版(“Python for Excel, by Felix Zumstein (O’Reilly). Copyright 2021 Zoomer Analytics LLC, 978-1-492-08100-5”) 将不定…

MPLS VPN 部署与应用

一.简介 MPLS&#xff0c;称之为多协议标签交换&#xff0c;在九十年代中期被提出来&#xff0c;用于解决传统IP报文依赖查表转发而产生的瓶颈&#xff0c;现多用于VPN技术&#xff0c;MPLS报头封装在数据链路层之上&#xff0c;网络层之下。本文为结合了华为技术和新华三技术…

麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 之前咱们学习了LNMP架构&#xff0c;但是PHP对于技术来说确实是老掉牙了&#xff0c;PHP的市场占有量越来越少了&#xff0c;我认识一个10年的PHP开发工程师&#xff0c;十年工资从15k到今天的6k&am…

67,【7】buuctf web [HarekazeCTF2019]Avatar Uploader 2(未完成版)

进入靶场 和上一题一母同胞&#xff0c;先把上一题的答案拖进去看看 区别在于上一题这块直接显示了flag&#xff0c;这里并没有 看看源码 加载不出来&#xff0c;ctrlu <!-- 上传头像的提示信息&#xff0c;说明上传要求 --><p>Please upload a PNG image less th…

QD Laser携“Lantana”激光器参展SPIE光子学西部展2025,聚焦紧凑型设计

据悉&#xff0c;QD Laser公司将在2025年SPIE光子学西部展览会上展出其最新产品——世界最小一体化紧凑型可见光激光器“Lantana”。该展会将于1月28日至30日在旧金山的Moscone中心举行。 在展会期间&#xff0c;QD Laser公司将现场展示这款超小型、轻便设备—— “Lantana”。…

63,【3】buuctf web Upload-Labs-Linux 1

进入靶场 点击pass1 查看提示 既然是上传文件&#xff0c;先构造一句话木马&#xff0c;便于用蚁剑连接 <?php eval($_POST[123])?> 上传木马 文件后缀写为.php.jpg 右键复制图片地址 打开蚁剑连接 先点击测试连接&#xff0c;显示成功后&#xff0c;再点击添加即可 …

不重启JVM,替换掉已经加载的类

不重启JVM&#xff0c;替换掉已经加载的类 直接操作字节码 使用ASM框架直接操作class文件&#xff0c;在类中修改代码&#xff0c;然后retransform就可以了 下边是BTrace官方提供的一个简单例子&#xff1a; package com.sun.btrace.samples;import com.sun.btrace.annotati…

使用插件SlideVerify实现滑块验证

作者gitee地址&#xff1a;https://gitee.com/monoplasty/vue-monoplasty-slide-verify 使用步骤&#xff1a; 1、安装插件 npm install --save vue-monoplasty-slide-verify 2、在main.js中进行配置 import SlideVerify from vue-monoplasty-slide-verify; Vue.use(SlideV…

Kafka 和 MQ 的区别

1.概述 1.1.MQ简介 消息中间件&#xff0c;其实准确的叫法应该叫消息队列&#xff08;message queue&#xff09;&#xff0c;简称MQ。其本质上是个队列&#xff0c;有FIFO的性质&#xff0c;即first in first out&#xff0c;先入先出。 目前市场上主流的MQ有三款&#xff…

61,【1】BUUCTF WEB BUU XSS COURSE 11

进入靶场 左边是吐槽&#xff0c;右边是登录&#xff0c;先登录试试 admin 123456 admiin# 123456 admin"# 123456 不玩了&#xff0c;先去回顾下xss 回顾完就很尴尬了&#xff0c;我居然用SQL的知识去做xss的题 重来 吐槽这里有一个输入框&#xff0c;容易出现存储型…

CAN 网络介绍

背景 在T-Box 产品开发过程中&#xff0c;我们离不开CAN总线&#xff0c;因为CAN总线为我们提供了车身的相关数据&#xff0c;比如&#xff0c;车速、油耗、温度等。用于上报TSP平台&#xff0c;进行国标认证&#xff1b;也帮助我们进行车身控制&#xff0c;比如车门解锁/闭锁…

【C++】在线五子棋对战项目网页版

目录 1.Websocket 1.1.Websocket的简单认识 1.2.什么是轮询呢&#xff1f; 1.3.websocket协议切换过程 1.4.websocketpp库常用接口认识 1.5.websocketpp库搭建服务器流程 1.6.websocketpp库搭建服务器 2.mysqlclient库-接口认识 3.项目模块的划分&#xff1a; 4.项目…

《鸿蒙 HarmonyOS 应用开发从入门到精通(第 2 版)》学习笔记 ——HarmonyOS 环境搭建之安装DevEco Studio

作为一款开发工具&#xff0c;除了具有基本的代码开发、编译构建及调测等功能外&#xff0c;DevEco Studio还具有如下特点&#xff1a; 高效智能代码编辑&#xff1a;支持Java、XML、ArkTS、JS、C/C等语言的代码高亮、代码智能补齐、代码错误检查、代码自动跳转、代码格式化、…