How it works(27) PostGIS源码解析(A) 项目的配置与编译

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 对象的公共库
loaderEsri Shape 数据输入(shp2pg)输出(pg2shp)工具
macrosAutoconf 辅助宏,包含一些来自 gettext 项目的通用宏和 PostGIS 专用宏
postgisPostGIS 核心插件的源码
rasterPostGIS-raster 插件的源码
sfcgalPostGIS-sfcgal 插件的源码
topologyPostGIS-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 脚本会依次执行如下命令(省略了非核心步骤):

  1. libtoolize --force --copy:Autotools 工具链一系列初始化操作。
  2. aclocal -I macro:根据根目录下 macro 目录中全部.m4 文件的内容,生成 aclocal.m4 文件。
  3. 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 模板中,主要涉及以下类别的参数设置:

  1. 与当前项目相关的参数:
    • 项目版本
    • 文件路径
  2. 检测并设置(初始化)与编译器(C/C++)相关的参数
  3. 检测并设置与依赖库相关的参数:
    • PostgreSQL
    • 输出格式库:
      • libxml2(读写 KML/GML)
      • json-c(读写 Geojson)
      • protobuf-c(输出 MVT 等格式)
    • 空间操作库:
      • GEOS
      • GDAL
      • SFCGAL
      • PROJ

最终,configure.ac 模板会根据上述参数替换下列模板文件中的占位符,生成若干 Makefile 文件(省略了非核心内容和第三方库):

  1. 核心构建脚本:
    • GNUmakefile
  2. 几何数据类型库:
    • liblwgeom/Makefile
  3. 连接辅助库
    • libpgcommon/Makefile
  4. 扩展依赖文件:
    • 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
  5. 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 目标递归执行了如下的编译流程:

  1. 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 $@
    
  2. $(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)
    
  3. 执行$(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 $@ $<
    
  4. $(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 $@
    
  5. 最终在 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 目标递归执行了如下的编译流程:

  1. 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)
    
  2. $(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 $@ $<
    
  3. $(SA_OBJS) 目标达成且$(SA_HEADERS)依赖也满足,则会触发libpgcommon.a文件的编译,最终生成libpgcommon.a文件。

最终生成的是其他库依赖的静态库,不需要安装到 PostgreSQL 中,没有安装步骤。

3.3 extensions/postgis

extensions/postgis 项目会生成一系列扩展所依赖的脚本文件(.sql / .control)。因为涉及到部分与 PostgreSQL 直接交互的场景,因此使用了PGXS机制。

下文中的 PGXS 包含pgxs.mk文件及其引用的Makefile.shlibMakefile.global等文件。

编译阶段

项目的all目标递归执行了如下的流程(忽略了处理 unpackaged 模式的逻辑):

  1. all 目标依赖 sql/$(EXTENSION)--$(EXTVERSION).sqlsql/$(EXTENSION)--ANY--$(EXTVERSION).sql 两个子目标:

    EXTVERSION    = 3.5.0dev
    EXTENSION     = postgis
    
    all: sql/$(EXTENSION)--$(EXTVERSION).sql sql/$(EXTENSION)--ANY--$(EXTVERSION).sql
    
  2. 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 $^ >> $@
    
  3. 执行 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 $< > $@
    
  4. 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 $^ >> $@
    
  5. 执行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\;/" $< > $@
    
  6. sql/$(EXTENSION)--$(EXTVERSION).sqlsql/$(EXTENSION)--ANY--$(EXTVERSION).sql 目标完成后即完成 all 目标。

  7. 由于引用了 upgrade-paths-rules.mk

    include ./../upgrade-paths-rules.mk
    

    需要执行 upgrade-paths-rules.mkall 目标:

    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)' >> $@
    
  8. 最终在 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
    
  9. 通过引用的方式,引入了 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 目标分为两部分:

  1. 源自 upgrade-paths-rules.mk 的更新文件安装:

    1. 复制 sql 目录下的 postgis--3.5.0dev--ANY.sqlpostgis--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"
      
    2. 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
      
    3. 执行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)
      
  2. 源自 PGXS 的其他 SQL 文件安装:

    1. 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)/'
      
    2. installdirs 目标保证指定的安装目录存在:

      MKDIR_P = /bin/mkdir -p
      
      installdirs:
         $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
         $(MKDIR_P) '$(DESTDIR)$(datadir)/$(datamoduledir)'
      
    3. 当实现 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
  1. 首先看 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) > $@
    
  2. 再看 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 $@ $<
    
  3. 库文件的生成是最核心的部分,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
    
  4. 当编译成功所有 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)
    
  5. 最终编译/生成如下文件:

    # 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 机制:

  1. 安装脚本文件和 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))
    
    
  2. 安装共享库文件:

    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 扩展的内部逻辑。

  • 22
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值