1. 引入
PostGIS 项目自然无需作过多介绍,但作为一种被 SQL 语言包装到 PostgreSQL 后面的工具集,它究竟是如何工作的对于普通用户来说其实是一个黑盒。
作为一个 20 年的老开源项目,它也像 Geoserver 一般长期没有激进的功能变革了,但与后者在老框架上缝缝补补不同,PostGIS 的基底——PostgreSQL——还是有其生命力和开发潜力的。因此,有必要打开这个黑盒子看一看,以便在必要的时刻为其赋予有价值的新功能。
本文的 PostGIS 为
3.5.0dev
,编译运行环境为Ubuntu 22.04(aarch64)
。
在项目根目录下的 README.postgis
文件中,我们可以看见对不同子目录的职能描述:
子目录 | 职能 |
---|---|
deps | 第三方依赖库 |
doc | 生成文档相关的工具和脚本 |
extensions | 与 PostGIS 扩展相关的文件,包括各种扩展的控制文件( .control 文件)和 SQL 脚本 |
extras | 若干非核心脚本和工具 |
liblwgeom | 用于处理轻量几何对象(Light Weight Geometry)的库文件 |
fuzzers | 用于 liblwgeom 的模糊测试(Google fuzz)的工具和脚本等 |
libpgcommon | 用于连接 PostgreSQL 和 lwgeom 对象的公共库 |
loader | Esri Shape 数据输入(shp2pg)输出(pg2shp)工具 |
macros | Autoconf 辅助宏,包含一些来自 gettext 项目的通用宏和 PostGIS 专用宏 |
postgis | PostGIS 核心插件的源码 |
raster | PostGIS-raster 插件的源码 |
sfcgal | PostGIS-sfcgal 插件的源码 |
topology | PostGIS-topology 插件的源码 |
regress | 回归测试相关的脚本和工具 |
utils | 一些工具脚本 |
想要分析 PostGIS 的核心原理,只需要关注如下目录的内容:
- extension
- liblwgeom
- postgis
- utils
除非明确指出,后文将不再讨论包括文档(doc),测试(regress,CUnit,fuzzers)、导入导出工具(loader)等非核心功能目录中的内容。其他的非核心扩展(如 raster 扩展)将单独讨论。
2. 项目配置(configure
)阶段
要编译 PostGIS 项目首先需要运行根目录下的 configure
脚本对项目进行配置。
2.1 configure
脚本生成
PostGIS 是个经历了长期岁月的老项目,遵循着诞生时的技术路线,它使用的是现在已经不那么流行的 Autotools工具链以及 Perl 脚本。
当使用来自存档文件( tarball )的 PostGIS 源码时, configure
脚本已经存在,无需手动生成该文件。使用来自其他源头的源码编译时,根据 READDME.postgis
文档的说明,需要运行 autogen.sh
脚本生成它。
当你的系统中正确安装了 Autotools 工具链后, autogen.sh
脚本会依次执行如下命令(省略了非核心步骤):
-
libtoolize --force --copy
:Autotools 工具链一系列初始化操作。 -
aclocal -I macro
:根据根目录下 macro 目录中全部.m4 文件的内容,生成aclocal.m4
文件。 -
autoconf
:根据configure.ac
模板文件(该文件是 Autoconf 工具约定的默认文件名)生成configure
脚本,此过程将自动加载aclocal.m4
文件。
执行 configure
脚本后就可以完成 PostGIS 项目对于当前系统环境的检查与配置,若检查无误,配置完成后即可执行 make
命令进行编译。
configure
脚本是由 Autoconf 工具经由模板展开生成的,可读性较低。因此,我们从原始模板configure.ac
文件窥探脚本的大致结构,了解 PostGIS 的编译逻辑。
2.2 configure.ac
模板简析
我们可以在 PostGIS 项目的多处发现以 .in 后缀结尾的模板文件(例如 Makefile.in
)。在这些模板文件中,有若干以 @变量名@
形式编写的占位符(例如 @POSTGIS_MAJOR_VERSION@
)。
使用 Autoconf 工具的 AC_CONFIG_FILES
命令,即可将模板中的占位符用同名变量的值替换掉,最终生成它们去掉 .in 后缀的目标文件。这就是 Autoconf 模板文件的核心功能:为不同的占位符变量赋值,生成满足当前系统环境的依赖文件。
configure.ac
模板生成 configure
脚本的过程,就是统一设置各种变量并最终将这些变量填充到指定子模板中并生成对应文件的流程。
简单来说,在 configure.ac
模板中,主要涉及以下类别的参数设置:
- 与当前项目相关的参数:
- 项目版本
- 文件路径
- 检测并设置(初始化)与编译器(C/C++)相关的参数
- 检测并设置与依赖库相关的参数:
- PostgreSQL
- 输出格式库:
- libxml2(读写 KML/GML)
- json-c(读写 Geojson)
- protobuf-c(输出 MVT 等格式)
- 空间操作库:
- GEOS
- GDAL
- SFCGAL
- PROJ
最终,configure.ac
模板会根据上述参数替换下列模板文件中的占位符,生成若干 Makefile
文件(省略了非核心内容和第三方库):
- 核心构建脚本:
- GNUmakefile
- 几何数据类型库:
- liblwgeom/Makefile
- 连接辅助库
- libpgcommon/Makefile
- 扩展依赖文件:
- extensions/Makefile
- extensions/postgis/Makefile
- extensions/postgis_topology/Makefile
- extensions/postgis_tiger_geocoder/Makefile
- extensions/address_standardizer/Makefile
- extensions/postgis_raster/Makefile
- extensions/postgis_sfcgal/Makefile
- PostGIS 核心库:
- postgis/Makefile
- sfcgal/Makefile
- raster/Makefile
- raster/rt_core/Makefile
- raster/rt_pg/Makefile
- topology/Makefile
当成功执行 configure
脚本后,便可在根目录执行 make
命令,按照核心构建脚本 GNUmakefile
描述的内容按顺序构建编译。
3. 项目编译(make
)与安装(install
)阶段
GNUmakefile
的核心代码如下:
# 声明所有要编译的子目录,每个子目录都是一个子项目
SUBDIRS = liblwgeom raster
SUBDIRS+= deps libpgcommon postgis topology sfcgal utils extensions
for s in $(SUBDIRS); do \
$(MAKE) -C $${s} all || exit 1; \
done;
因为每个子目录下的 Makefile
脚本都正确生成,执行 make
命令后整个项目将递归的编译全部子项目,即执行每个子目录下 Makefile
脚本的 all
目标(target),若 all
目标不存在则运行第一个目标。
我们以最核心的 PostGIS 扩展及其依赖为例(liblwgeom、libpgcommon、extensions/postgis、postgis),分析这些子项目都是如何编译的,最终编译出哪些文件以及这些文件如何安装到数据库指定目录。
3.1 liblwgeom
liblwgeom 项目是核心的几何库,不依赖于 PostgreSQL。
项目的 all
目标递归执行了如下的编译流程:
-
all
目标依赖liblwgeom.la
目标,liblwgeom.la
目标依赖$(LT_OBJS)
目标:# 列出需要编译的文件 SA_OBJS = \ stringbuffer.o \ ... 省略 lwgeom_sfcgal.o varint.o NM_OBJS = \ lwspheroid.o # 定义libtools的目标对象(与obj对象同名的lo文件) LT_SA_OBJS = $(SA_OBJS:.o=.lo) LT_NM_OBJS = $(NM_OBJS:.o=.lo) LT_OBJS = $(LT_SA_OBJS) $(LT_NM_OBJS) all: liblwgeom.la liblwgeom.la: $(LT_OBJS) $(LIBTOOL) --tag=CC --mode=link $(CC) -rpath $(libdir) $(LT_OBJS) $(RYU_LIBPATH)\ -release $(SOVER) -version-info 0:0:0 $(LDFLAGS) -static -o $@
-
$(LT_OBJS)
目标依赖../postgis_config.h
、../postgis_revision.h
以及$(SA_HEADERS)
变量定义的所有文件的存在:# 定义依赖的头文件 SA_HEADERS = \ bytebuffer.h \ ... 省略 varint.h # 生成精确提交版本 ../postgis_revision.h: $(MAKE) -C .. postgis_revision.h # postgis_config.h 定义了其他版本信息 # 当 postgis_config.h 或 postgis_revision.h 发生变化时将重新执行 $(LT_OBJS) 目标 $(LT_OBJS): ../postgis_config.h ../postgis_revision.h $(SA_HEADERS)
-
执行
$(LT_OBJS)
目标时会触发$(LT_SA_OBJS)
目标和$(LT_NM_OBJS)
目标,将通过 libtool 工具生成一系列 .o 文件和对应的 .lo 文件。RYU_INCLUDE = -I$(srcdir)/../deps/ryu/.. srcdir = . builddir = . # 定义预处理标志,添加依赖的第三方库的头文件路径 CPPFLAGS = $(RYU_INCLUDE) -I/usr/include/libxml2 -I/usr/include -I/usr/include/json-c -DNDEBUG -I$(builddir) -I$(srcdir) # 定义编译器标志 CFLAGS = -std=gnu99 -g -O2 -fno-math-errno -fno-signed-zeros -Wall -O2 -fPIC -DPIC # 从 .c 文件生成 .lo 文件 $(LT_SA_OBJS): %.lo: %.c $(LIBTOOL) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< $(LT_NM_OBJS): %.lo: %.c $(LIBTOOL) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
-
当
$(LT_OBJS)
目标满足需求时,开始执行liblwgeom.la
目标:prefix = /usr/local exec_prefix = ${prefix} libdir = ${exec_prefix}/lib # 共享对象版本号,一般为POSTGIS_MAJOR_VERSION.POSTGIS_MINOR_VERSION SOVER = 3.5 # 定义c编译器 CC = gcc # 定义链接器标志位,指定需要链接的库(默认从 LD_LIBRARY_PATH 路径搜索需要链接的库) LDFLAGS = -lm -lgeos_c -lproj -ljson-c -L/usr/lib/aarch64-linux-gnu -lSFCGAL -lgmpxx -no-undefined RYU_LIBPATH = ../deps/ryu/libryu.la $(RYU_LIBPATH): ../deps/ryu/d2s.c $(MAKE) -C ../deps/ryu libryu.la SHELL = /bin/bash top_builddir = .. LIBTOOL = $(SHELL) $(top_builddir)/libtool liblwgeom.la: $(LT_OBJS) $(LIBTOOL) --tag=CC --mode=link $(CC) -rpath $(libdir) $(LT_OBJS) $(RYU_LIBPATH)\ -release $(SOVER) -version-info 0:0:0 $(LDFLAGS) -static -o $@
-
最终在
liblwgeom
目录编译生成了liblwgeom.la
文件,在liblwgeom/.libs
下编译生成了liblwgeom.a
文件。最终编译后的结果形如:|-- .libs | |...省略 | |-- effectivearea.o | |...省略 | |-- liblwgeom.a | |-- liblwgeom.la -> ../liblwgeom.la | |...省略 ...省略 |-- effectivearea.c |-- effectivearea.h |-- effectivearea.lo |-- effectivearea.o ...省略 |-- liblwgeom.h |-- liblwgeom.h.in |-- liblwgeom.la ...省略
libtool 工具会在
liblwgeom/.libs
目录和liblwgeom
目录下同时生成一系列同名的 .o 文件,分别为PIC对象文件和非 PIC 对象文件。
最终生成的是其他库依赖的静态库,不需要安装到 PostgreSQL 中,没有安装步骤。
3.2 libpgcommon
libpgcommon 项目是与 PostgreSQL 交互的工具库。
项目的 all
目标递归执行了如下的编译流程:
-
all
目标执行libpgcommon.a
目标,libpgcommon.a
目标依赖$(SA_OBJS)
目标和$(SA_HEADERS)
目标。# 定义依赖的头文件 SA_HEADERS = \ lwgeom_pg.h \ ... 省略 pgsql_compat.h # 列出需要编译的文件 SA_OBJS = \ gserialized_gist.o \ ... 省略 shared_gserialized.o all: libpgcommon.a libpgcommon.a: $(SA_OBJS) $(SA_HEADERS) gcc-ar rs libpgcommon.a $(SA_OBJS)
-
$(SA_OBJS)
目标依赖postgis_config.h
文件(若该文件发生变化,会触发重新执行$(SA_OBJS)
目标),同时该目标也执行了编译目标,将编译出指定的 .o 文件(猜测因为libpgcommon
库直接和 PostgreSQL 打交道,不需要链接其他的库,因此没有必要使用 libtool 管理编译逻辑)。srcdir = . top_builddir = .. CC=gcc CFLAGS= -I$(srcdir)/../liblwgeom -I$(top_builddir)/liblwgeom -I/usr/include/libxml2 -I/usr/include -I/usr/include/json-c -DNDEBUG -std=gnu99 -g -O2 -fno-math-errno -fno-signed-zeros -Wall -O2 -I/usr/include/postgresql/14/server -fPIC -DPIC $(SA_OBJS): ../postgis_config.h # 从 .c 文件生成 .o 文件 $(SA_OBJS): %.o: %.c $(CC) $(CFLAGS) -c -o $@ $<
-
若
$(SA_OBJS)
目标达成且$(SA_HEADERS)
依赖也满足,则会触发libpgcommon.a
文件的编译,最终生成libpgcommon.a
文件。
最终生成的是其他库依赖的静态库,不需要安装到 PostgreSQL 中,没有安装步骤。
3.3 extensions/postgis
extensions/postgis 项目会生成一系列扩展所依赖的脚本文件(.sql / .control)。因为涉及到部分与 PostgreSQL 直接交互的场景,因此使用了PGXS机制。
下文中的 PGXS 包含
pgxs.mk
文件及其引用的Makefile.shlib
、Makefile.global
等文件。
编译阶段
项目的all
目标递归执行了如下的流程(忽略了处理 unpackaged 模式的逻辑):
-
all
目标依赖sql/$(EXTENSION)--$(EXTVERSION).sql
和sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
两个子目标:EXTVERSION = 3.5.0dev EXTENSION = postgis all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
-
sql/$(EXTENSION)--$(EXTVERSION).sql
目标依赖 sql 文件夹以及几个 sql 文件的存在:EXTENSION_SCRIPTS = \ sql/postgis_for_extension.sql \ sql/spatial_ref_sys_config_dump.sql \ sql/spatial_ref_sys.sql # 按顺序把这些文件合并到 postgis--3.5.0dev.sql 脚本中 # 最终生成的脚本用于全新安装当前版本的 postgis 扩展 sql/$(EXTENSION)--$(EXTVERSION).sql: $(EXTENSION_SCRIPTS) | sql printf '\\echo Use "CREATE EXTENSION $(EXTENSION)" to load this file. \\quit\n' > $@ cat $^ >> $@
-
执行
sql/$(EXTENSION)--$(EXTVERSION).sql
目标:# 创建 sql 目录 sql: mkdir -p $@ # 生成 postgis_for_extension.sq l脚本 # 因为 postgis.sql.in 模板比较简单,因此使用c预处理器(c preprocessor)作为模板处理器,替换了里面的 include 和宏定义 SQLPP = /usr/bin/cpp -traditional-cpp -w -P -Upixel -Ubool sql/postgis_for_extension.sql: ../../postgis/postgis.sql.in ../../postgis_revision.h | sql $(SQLPP) -I./../../postgis $< > $@.tmp grep -v '^#' $@.tmp | \ $(PERL) -lpe \ "s'MODULE_PATHNAME'\$(MODULEPATH)'g" \ | $(PERL) -pe 's/BEGIN\;//g ; s/COMMIT\;//g' > $@ rm -f $@.tmp # 移除开头结尾的BEGIN/COMMIT sql/spatial_ref_sys.sql: ../../spatial_ref_sys.sql | sql $(PERL) -pe 's/BEGIN\;//g ; s/COMMIT\;//g' $< > $@ sql/spatial_ref_sys_config_dump.sql: ../../spatial_ref_sys.sql ../../utils/create_spatial_ref_sys_config_dump.pl | sql $(PERL) ../../utils/create_spatial_ref_sys_config_dump.pl $< > $@
-
sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
目标依赖 sql 文件夹以及几个 sql 文件的存在:EXTENSION_UPGRADE_SCRIPTS = \ ../postgis_extension_helper.sql \ sql/postgis_upgrade.sql \ ../postgis_extension_helper_uninstall.sql # 把这些文件按顺序合并到 postgis--ANY--3.5.0dev.sql 文件中 # 最终生成脚本用于从任意先前版本升级到当前版本的 postgis 扩展 sql/$(EXTENSION)--ANY--$(EXTVERSION).sql: $(EXTENSION_UPGRADE_SCRIPTS) | sql printf '\\echo Use "CREATE EXTENSION $(EXTENSION)" to load this file. \\quit\n' > $@ cat $^ >> $@
-
执行
sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
目标:sql/postgis_upgrade_for_extension.sql: ../../postgis/common_before_upgrade.sql ../../postgis/postgis_before_upgrade.sql sql/postgis_upgrade_for_extension.sql.in ../../postgis/postgis_after_upgrade.sql ../../postgis/common_after_upgrade.sql | sql cat $^ | $(PERL) -pe 's/BEGIN\;//g ; s/COMMIT\;//g' > $@ # 执行perl脚本生成指定的SQL脚本 sql/postgis_upgrade_for_extension.sql.in: sql/postgis_for_extension.sql ../../utils/create_upgrade.pl | sql $(PERL) ../../utils/create_upgrade.pl $< > $@ sql/postgis_upgrade.sql: sql/postgis_upgrade_for_extension.sql | sql $(PERL) -pe "s/BEGIN\;//g ; s/COMMIT\;//g; s/^(DROP .*)\;/SELECT postgis_extension_drop_if_exists('$(EXTENSION)', '\1');\n\1\;/" $< > $@
-
sql/$(EXTENSION)--$(EXTVERSION).sql
及sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
目标完成后即完成all
目标。 -
由于引用了
upgrade-paths-rules.mk
:include ./../upgrade-paths-rules.mk
需要执行
upgrade-paths-rules.mk
的all
目标:POSTGIS_BUILD_DATE=$(shell date $${SOURCE_DATE_EPOCH:+-d @$$SOURCE_DATE_EPOCH} -u "+%Y-%m-%d %H:%M:%S") TAG_UPGRADE=$(EXTENSION)--TEMPLATED--TO--ANY.sql # 生成 postgis--TEMPLATED--TO--ANY.sql all: sql/$(TAG_UPGRADE) # 该文件内容没有意义,或许是因为不需要处理版本间的差异 sql/$(TAG_UPGRADE): $(MAKEFILE_LIST) | sql echo '-- Just tag extension $(EXTENSION) version as "ANY"' > $@ echo '-- Installed by $(EXTENSION) $(EXTVERSION)' >> $@ echo '-- Built on $(POSTGIS_BUILD_DATE)' >> $@
-
最终在 sql 目录下生成如下文件:
postgis--3.5.0dev.sql postgis--ANY--3.5.0dev.sql postgis--TEMPLATED--TO--ANY.sql postgis_for_extension.sql postgis_upgrade.sql postgis_upgrade_for_extension.sql postgis_upgrade_for_extension.sql.in spatial_ref_sys.sql spatial_ref_sys_config_dump.sql
-
通过引用的方式,引入了 PGXS 机制:
PG_CONFIG := /usr/bin/pg_config PGXS := /usr/lib/postgresql/14/lib/pgxs/src/makefiles/pgxs.mk include $(PGXS) PERL = /usr/bin/perl
PGXS 的
all
目标会生成postgis.control
文件:all: $(DATA_built) # 最终要被复制到PostgreSQL指定目录下的文件 DATA_built = \ $(EXTENSION).control \ sql/$(EXTENSION)--$(EXTVERSION).sql \ sql/$(EXTENSION)--ANY--$(EXTVERSION).sql \ $(NULL) MODULEPATH = $$libdir/$(EXTENSION)-3 # 因为模板很简单,使用sed即可替换模板中的变量最终生成 postgis.control 文件 $(EXTENSION).control: $(EXTENSION).control.in Makefile cat $< \ | sed -e 's|@EXTVERSION@|$(EXTVERSION)|g' \ | sed -e 's|@EXTENSION@|$(EXTENSION)|g' \ | sed -e 's|@MODULEPATH@|$(MODULEPATH)|g' \ > $@
安装阶段
extensions/postgis 项目的 install
目标分为两部分:
-
源自
upgrade-paths-rules.mk
的更新文件安装:-
复制 sql 目录下的
postgis--3.5.0dev--ANY.sql
和postgis--TEMPLATED--TO--ANY.sql
到/usr/share/postgresql/14/extension/
目录下:INSTALL_DATA_MODE = 644 INSTALL = /usr/bin/install -c INSTALL_DATA = $(INSTALL) -m $(INSTALL_DATA_MODE) datadir := /usr/share/postgresql/14 datamoduledir := extension EXTDIR=$(DESTDIR)$(datadir)/$(datamoduledir) install: install-upgrade-paths install-upgrade-paths: sql/$(TAG_UPGRADE) sql/$(EXTENSION)--ANY--$(EXTVERSION).sql mkdir -p "$(EXTDIR)" $(INSTALL_DATA) "sql/$(EXTENSION)--ANY--$(EXTVERSION).sql" "$(EXTDIR)/$(EXTENSION)--ANY--$(EXTVERSION).sql" $(INSTALL_DATA) "sql/$(TAG_UPGRADE)" "$(EXTDIR)/$(TAG_UPGRADE)" ln -fs "$(TAG_UPGRADE)" "$(EXTDIR)/$(EXTENSION)--$(EXTVERSION)--ANY.sql"
-
由
GNUmakefile
调用install-extension-upgrades-from-known-versions
目标:@if test x"$@" = xinstall; then \ if test x"yes" = xyes; then \ $(MAKE) install-extension-upgrades-from-known-versions; \ endif endif
-
执行
install-extension-upgrades-from-known-versions
目标,在/usr/share/postgresql/14/extension/
目录下生成一系列软链接形式的 postgis 升级脚本:PGRADEABLE_VERSIONS = \ 2.0.0 \ 2.0.1 \ ...省略.. 3.5.0alpha2 # 以下被注释的代码为历史遗留的无用代码 # 详见:https://github.com/postgis/postgis/commit/593c7af97ea74fbe5e288af2030ab3047958c4c1 # 已被独立的upgradeable_versions.mk脚本替代 # GREP = /usr/bin/grep # MICRO_NUMBER = $(shell echo $(EXTVERSION) | \ # $(PERL) -pe 's/\d.\d.(\d+)[a-zA-Z]*\d*/$1/' # PREREL_NUMBER = $(shell echo $(EXTVERSION) | \ # $(PERL) -pe 's/\d\.\d\.(.*)/\1/' | \ # $(GREP) "[a-zA-Z]" | \ # $(PERL) -pe 's/\d+[a-zA-Z]+(\d+)/\1/' # MICRO_PREV = $(shell if test "$(MICRO_NUMBER)x" != "x"; then expr $(MICRO_NUMBER) - 1; fi) # PREREL_PREV = $(shell if test "$(PREREL_NUMBER)x" != "x"; then expr $(PREREL_NUMBER) - 1; fi) # PREREL_PREFIX = $(shell echo $(EXTVERSION) | \ # $(PERL) -pe 's/\d\.\d\.(.*)/\1/' | \ # $(GREP) "[a-zA-Z]" | \ # $(PERL) -pe 's/(\d+[a-zA-Z]+)\d*/\1/' # postgis.pl 脚本通过软链接的方式生成一系列升级文件 # 例如:postgis--2.0.0--ANY.sql (从 2.0 升级到当前版本) 实际指向 postgis--TEMPLATED--TO--ANY.sql install-extension-upgrades-from-known-versions: $(PERL) $(top_srcdir)/loader/postgis.pl \ install-extension-upgrades \ --extension $(EXTENSION) \ --pg_sharedir $(DESTDIR)$(PG_SHAREDIR) \ $(UPGRADEABLE_VERSIONS)
-
-
源自 PGXS 的其他 SQL 文件安装:
-
PGXS 的
install
目标依赖installdirs
目标:datamoduledir := extension install: installdirs $(INSTALL_DATA) $(addprefix $(srcdir)/, $(addsuffix .control, $(EXTENSION))) '$(DESTDIR)$(datadir)/extension/' $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) $(DATA_built) '$(DESTDIR)$(datadir)/$(datamoduledir)/'
-
installdirs
目标保证指定的安装目录存在:MKDIR_P = /bin/mkdir -p installdirs: $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' $(MKDIR_P) '$(DESTDIR)$(datadir)/$(datamoduledir)'
-
当实现
installdirs
目标后,将复制DATA_built
变量定义的文件到/usr/share/postgresql/14/extension/
目录下,包括:sql/postgis--ANY--3.5.0dev.sql
sql/postgis--TEMPLATED--TO--ANY.sql
postgis.control
-
3.4 postgis
该项目是 PostGIS 扩展最核心的部件,因此也深度的使用了 PGXS 机制。
编译阶段
项目的 Makefile
中没有显式定义 all
目标,因此我们需要在 PGXS 中找到真正的 all
目标作为入口。
在 PGXS 中存在多个 all
目标,忽略处理旧函数(legacy)的逻辑,可视为三大部分:
# 1. 生成 sql 文件
SQL_built=postgis.sql uninstall_postgis.sql postgis_upgrade.sql
DATA_built=$(SQL_built)
all: $(DATA_built)
# 2. 生成 LLVM-BC 文件
PG_OBJS= \
postgis_module.o \
...省略...
postgis_legacy.o
OBJS=$(PG_OBJS)
# 我所使用的环境支持llvm,因此会编译出BC文件
# 依赖与OBJS变量中定义的文件同名的.BC文件
all: $(patsubst %.o,%.bc, $(OBJS))
# 3. 生成最终的库文件
MODULE_big=postgis-3
NAME = $(MODULE_big)
include $(top_srcdir)/src/Makefile.shlib
all: all-lib
-
首先看 sql 文件的生成。生成时主要使用了通配符匹配所有 sql 对应的模板文件,对于不使用模板的部分文件,也指定了专门的生成方法:
# 使用C预处理器处理所有的SQL模板,生成指定的文件 %.sql: %.sql.in $(SQLPP) -I../libpgcommon -I. $< > $@.tmp grep -v '^#' $@.tmp | $(PERL) -lpe "s'MODULE_PATHNAME'\$(MODULEPATH)'g;s'@extschema@\.''g" > $@ rm -f $@.tmp # 按顺序将指定的sql文件合并到 postgis_upgrade.sql 中,并补全为可独立执行的sql脚本 postgis_upgrade.sql: common_before_upgrade.sql postgis_before_upgrade.sql postgis_upgrade.sql.in postgis_after_upgrade.sql common_after_upgrade.sql echo "BEGIN;" > $@ cat $^ >> $@ echo "COMMIT;" >> $@ # postgis核心脚本,可以让用户手动安装postgis # 当 postgis.sql 依赖的这些文件发生变化后会重新生成 postgis.sql: sqldefines.h geography.sql.in postgis_brin.sql.in postgis_spgist.sql.in postgis_letters.sql uninstall_postgis.sql: postgis.sql ../utils/create_uninstall.pl $(PERL) ../utils/create_uninstall.pl $< $(POSTGIS_PGSQL_VERSION) > $@
-
再看 LLVM-BC 对象的生成。使用 CLang 编译器从 c 文件编译出 bc 文件,最终生成与全部.o 文件一一对应的.bc 文件:
CLANG = /usr/bin/clang-14 BITCODE_CFLAGS = -fno-strict-aliasing -fwrapv COMPILE.c.bc = $(CLANG) -Wno-ignored-attributes $(BITCODE_CFLAGS) $(CPPFLAGS) -flto=thin -emit-llvm -c # 从同名的C文件编译出对应的BC文件 %.bc: %.c $(COMPILE.c.bc) -o $@ $<
-
库文件的生成是最核心的部分,
all
目标调用了all-lib
子目标,all-lib
子目标又调用了all-shared-lib
子目标,all-shared-lib
子目标依赖于$(OBJS)
目标,因此优先执行$(OBJS)
目标,即编译生成所有 .o 文件:all-lib: all-shared-lib # 库的名字为 postgis-3.so shlib = $(NAME)$(DLSUFFIX) all-shared-lib: $(shlib) $(shlib): $(OBJS) $(LINK.shared) -o $@ $(OBJS) $(LDFLAGS) $(SHLIB_LINK) DEPDIR = .deps # 定义编译器 CC = gcc # 定义各种编译标志位 CFLAGS = -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -moutline-atomics -g -g -O2 -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security CPPFLAGS = -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) -c # 将 OBJS 变量中定义的每个 .o 文件编译出来 %.o: %.c @if test ! -d $(DEPDIR); then mkdir -p $(DEPDIR); fi $(COMPILE.c) -o $@ $< -MMD -MP -MF $(DEPDIR)/$(*F).Po
-
当编译成功所有 OBJS 变量中定义的 .o 文件后,可以开始最终编译
postgis-3.so
文件:LDFLAGS = -lm COMPILER = $(CC) $(CFLAGS) LINK.shared = $(COMPILER) -shared WAGYU_LIBPATH = ../deps/wagyu/libwagyu.la # 这里的拼写是个历史遗留笔误 WAYGU_LIB = $(WAGYU_LIBPATH) -lstdc++ FLATGEOBUF_LIBPATH = ../deps/flatgeobuf/libflatgeobuf.la FLATGEOBUF_LIB = $(FLATGEOBUF_LIBPATH) -lstdc++ SHLIB_LINK_F = $(WAYGU_LIB) $(FLATGEOBUF_LIB) ../libpgcommon/libpgcommon.a ../liblwgeom/.libs/liblwgeom.a -lgeos_c -lproj -ljson-c -lprotobuf-c -lxml2 -L/usr/lib/aarch64-linux-gnu -lSFCGAL -lgmpxx -Wl,--exclude-libs,ALL -lm SHLIB_LINK := $(SHLIB_LINK_F) DLSUFFIX = .so $(shlib): $(OBJS) $(LINK.shared) -o $@ $(OBJS) $(LDFLAGS) $(SHLIB_LINK)
-
最终编译/生成如下文件:
# sql 文件 postgis.sql uninstall_postgis.sql postgis_upgrade.sql # bc 文件 brin_2d.bc ...省略... vector_tile.pb-c.bc # o 文件 brin_2d.o ...省略... vector_tile.pb-c.o # so 文件 postgis-3.so
安装阶段
postgis 项目的没有显式定义install
目标,因此安装阶段的逻辑来自于 PGXS 机制:
-
安装脚本文件和 BC 文件 :
MODULEDIR=contrib/postgis-3.5 datamoduledir := $(MODULEDIR) DATA=../spatial_ref_sys.sql install: all installdirs # 定义于 DATA_built 和 DATA 变量中的 SQL 文件将被复制到 /usr/share/postgresql/14/contrib/postgis-3.5/ 目录下 $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) $(DATA_built) '$(DESTDIR)$(datadir)/$(datamoduledir)/' # 将所有 BC 文件安装到 /usr/lib/postgresql/14/lib/bitcode/postgis-3 目录下 $(call install_llvm_module,$(MODULE_big),$(OBJS))
-
安装共享库文件:
INSTALL_SHLIB = $(INSTALL) $(INSTALL_SHLIB_OPTS) INSTALL_SHLIB_OPTS = -m 755 pkglibdir = /usr/lib/postgresql/14/lib install: install-lib install-lib: install-lib-shared # 将 postgis-3.so 安装到 /usr/lib/postgresql/14/lib 目录下 install-lib-shared: $(shlib) installdirs-lib $(INSTALL_SHLIB) $< '$(DESTDIR)$(pkglibdir)/$(shlib)' installdirs-lib: $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'
4. 总结
通过上述步骤,我们大致了解了 PostGIS 的编译、安装逻辑,此时 PostGIS 库及其依赖文件已经被安装到 PostgreSQL 目录中,但 PostGIS 还并未处于可用状态。下一节我们将讨论通过执行 SQL 语句创建 PostGIS 扩展的内部逻辑。
网友评论