CMake构建学习笔记25-SpatiaLite库的构建

1. 引言

尝试使用CMake构建SpatiaLite及其依赖库,但是没有成功。因为SpatiaLite及其依赖库很多都是老牌的C库,这种库由于年代的原因一般都不提供CMake的构建方式,在Windows下提供的构建方式一般是基于nmake的。这意味着难以实现通过一个配置来实现跨平台构建,不过笔者也没有纠结这个问题,这种问题只能交给时间来解决,比如GDAL、GEOS这样库最开始都是nmake构建,后来陆续都升级为使用CMake构建。在这里就记录一下笔者使用nmake构建SpatiaLite库的过程。

SpatiaLite是一个为SQLite数据库引擎扩展空间数据存储与分析功能的开源库,使其能够支持地理信息系统(GIS)操作。

2. nmake构建

在具体总结SpatiaLite库的构建之前,最好需要了解下Windows下使用namke构建的一般步骤。
在Windows下构建程序最方便的当然是直接使用Visual Studio,通过GUI界面来构建程序。不过GUI方式也有很麻烦的地方,比如CICD需要通过脚本来实现代码到产品的过程,而从终端批处理的方式就是通过nmake来提供的。

2.1 步骤

具体来说,就是在安装Visual Studio之后,开始菜单中打开相应的开发命令行环境。以Visual Studio 2019为例,就是x64 Native Tools Command Prompt for VS 2019工具。这个工具本质上就是一个CMD,只不过已经内置了开发命令行环境,如下所示:

CMake构建学习笔记25-SpatiaLite库的构建

打开这个VS终端之后,通常输入如下指令来构建程序:

nmake -f makefile.vc 

这个makefile.vc就是代码项目的主构建脚本,定义了这个项目如何编译、链接。为了找到这个文件,需要先通过CD指令跳转到代码项目包含这个文件的目录下。除了makefile.vc这个文件之外,还可能有nmake.opt这个文件;这是一个自定义的配置文件,用于覆盖默认编译选项(如宏定义、包含路径、库路径、优化选项等)。通常makefile.vc中的内容不用修改,通过修改nmake.opt中的内容来实现自定义配置。

除了上述指令之外,为了区分构建步骤,以下步骤也很常见,例如清理构建过程的文件:

nmake -f makefile.vc clean 

将构建结果进行安装:

nmake -f makefile.vc install 

不过cleaninstall需要看makefile.vcnmake.opt中是否提供了这个构建目标。如果没有提供,那么上述指令就不起作用。类似的构建还有DEBUG和RELEASE版本,也需要看makefile.vcnmake.opt中是否存在相关的参数。

2.2 缺点

了解了上述指令之后,大概感觉跟CMake的构建指令差不多。其实差别还是很大的,跟现代构建系统相比,nmake还缺失一个关键的步骤——配置。nmake其实只是一个简单的make系统,对应的就是Linux下的Makefile,可以进行编译、链接,也可以把构建参数提取出来,但是无法动态去生成构建参数。具体来说,nmake的配置参数文件是nmake.opt,但是只有这个文件还不够,还需要根据构建环境不同,动态生成这个nmake.opt文件。

所以使用nmake构建项目的麻烦就在这里,无法根据需要动态地生成项目配置文件,只能手动去修改它。比如在之前的文章《CMake构建学习笔记19-OpenSSL库的构建》中就是如此,也是通过nmake来构建OpenSSL库,但是不能动态生成配置怎么办呢?OpenSSL的维护者引入了perl来解决这个问题。嗯,不得不说,C/C++项目的构建系统太混乱了,简直是各显神通;希望以后的项目都能升级成CMake的构建方式吧,不是CMake有多好,先把构建的行为统一了再说。

3. 构建SpatiaLite

3.1 主项目

在理解了使用nmake构建的一般步骤之后,构建具体的SpatiaLite项目反而比较简单了。从官网上下载最新的5.1.0版本,解压到本地,可以看到SpatiaLite提供了四个版本的构建配置,如下图所示:

CMake构建学习笔记25-SpatiaLite库的构建

其中:

  1. makefile.vc是32位构建配置。
  2. makefile64.vc是64位构建配置。
  3. makefile_mod.vc是32位模块化构建配置。
  4. makefile_mod64.vc是64位模块化构建配置。

这里也可以看到使用nmake构建的缺陷,应对不同的构建需求,需要提供多个配置文件。如果是使用CMake构建,使用一个CMakeList.txt即可。现在一般都是64位系统,选择makefile64.vcnmake64.opt进行构建。不过为了能正确构建,修改nmake64.opt中的内容如下:

# Directory tree where SpatiaLite will be installed. INSTDIR=C:Work3rdparty  # Uncomment the first for an optimized build, or the second for debug. OPTFLAGS=	/source-charset:windows-1252 /nologo /Ox /fp:precise /W4 /MD /D_CRT_SECURE_NO_WARNINGS  		/DDLL_EXPORT /DYY_NO_UNISTD_H #OPTFLAGS=	/nologo /Zi /MD /Fdspatialite.pdb /DDLL_EXPORT  # Set the version number for the DLL.  Normally we leave this blank since # we want software that is dynamically loading the DLL to have no problem # with version numbers. VERSION= 

主要修改了两点:

  1. INSTDIR表示构建后安装的目录,按需进行修改。
  2. /source-charset:windows-1252是额外增加的编译选项,用于设置代码文件的数据集。

修改makefile64.vc中的内容为:

# $Id: $ # # NMAKE Makefile to build libspatialite on Windows # !INCLUDE nmake64.opt  LIBOBJ = srcgaiaauxgg_sqlaux.obj srcgaiaauxgg_utf8.obj  	srcgaiaexifgaia_exif.obj srcgaiageogg_advanced.obj  	srcgaiageogg_endian.obj srcgaiageogg_ewkt.obj  	srcgaiageogg_geodesic.obj srcgaiageogg_geoJSON.obj  	srcgaiageogg_geometries.obj srcgaiageogg_geoscvt.obj  	srcgaiageogg_gml.obj srcgaiageogg_kml.obj  	srcgaiageogg_relations.obj srcgaiageogg_shape.obj  	srcgaiageogg_transform.obj srcgaiageogg_vanuatu.obj  	srcgaiageogg_wkb.obj srcgaiageogg_wkt.obj  	srcgaiageogg_extras.obj srcgaiageogg_xml.obj  	srcgaiageogg_voronoj.obj srcgaiageogg_matrix.obj  	srcgaiageogg_relations_ext.obj srcgaiageogg_rttopo.obj  	src/connection_cache/alloc_cache.obj src/connection_cache/gg_sequence.obj  	srcspatialitembrcache.obj srcshapefilesshapefiles.obj  	srcspatialitespatialite.obj srcspatialitevirtualdbf.obj  	srcspatialitevirtualfdo.obj srcspatialitevirtualnetwork.obj  	srcspatialitevirtualshape.obj srcspatialitevirtualspatialindex.obj  	srcspatialitestatistics.obj srcspatialitemetatables.obj  	srcspatialitevirtualXL.obj srcspatialiteextra_tables.obj  	srcspatialitevirtualxpath.obj srcspatialitevirtualbbox.obj  	srcspatialitespatialite_init.obj srcspatialitese_helpers.obj  	srcspatialitesrid_aux.obj srcspatialitetable_cloner.obj  	srcspatialitevirtualelementary.obj srcspatialitevirtualgeojson.obj  	srcspatialitevirtualrouting.obj srcspatialitecreate_routing.obj  	srcspatialitedbobj_scopes.obj srcspatialitepause.obj  	srcwfswfs_in.obj srcsrsinitsrs_init.obj srcspatialitevirtualgpkg.obj  	srcdxfdxf_parser.obj srcdxfdxf_loader.obj srcdxfdxf_writer.obj  	srcdxfdxf_load_distinct.obj srcdxfdxf_load_mixed.obj  	srcshapefilesvalidator.obj srcmd5md5.obj srcmd5gaia_md5.obj  	srcsrsinitepsg_inlined_00.obj srcsrsinitepsg_inlined_01.obj  	srcsrsinitepsg_inlined_02.obj srcsrsinitepsg_inlined_03.obj  	srcsrsinitepsg_inlined_04.obj srcsrsinitepsg_inlined_05.obj  	srcsrsinitepsg_inlined_06.obj srcsrsinitepsg_inlined_07.obj  	srcsrsinitepsg_inlined_08.obj srcsrsinitepsg_inlined_09.obj  	srcsrsinitepsg_inlined_10.obj srcsrsinitepsg_inlined_11.obj  	srcsrsinitepsg_inlined_12.obj srcsrsinitepsg_inlined_13.obj  	srcsrsinitepsg_inlined_14.obj srcsrsinitepsg_inlined_15.obj  	srcsrsinitepsg_inlined_16.obj srcsrsinitepsg_inlined_17.obj  	srcsrsinitepsg_inlined_18.obj srcsrsinitepsg_inlined_19.obj  	srcsrsinitepsg_inlined_20.obj srcsrsinitepsg_inlined_21.obj  	srcsrsinitepsg_inlined_22.obj srcsrsinitepsg_inlined_23.obj  	srcsrsinitepsg_inlined_24.obj srcsrsinitepsg_inlined_25.obj  	srcsrsinitepsg_inlined_26.obj srcsrsinitepsg_inlined_27.obj  	srcsrsinitepsg_inlined_28.obj srcsrsinitepsg_inlined_29.obj  	srcsrsinitepsg_inlined_30.obj srcsrsinitepsg_inlined_31.obj  	srcsrsinitepsg_inlined_32.obj srcsrsinitepsg_inlined_33.obj  	srcsrsinitepsg_inlined_34.obj srcsrsinitepsg_inlined_35.obj  	srcsrsinitepsg_inlined_36.obj srcsrsinitepsg_inlined_37.obj  	srcsrsinitepsg_inlined_38.obj srcsrsinitepsg_inlined_39.obj  	srcsrsinitepsg_inlined_40.obj srcsrsinitepsg_inlined_41.obj  	srcsrsinitepsg_inlined_42.obj srcsrsinitepsg_inlined_43.obj  	srcsrsinitepsg_inlined_44.obj srcsrsinitepsg_inlined_45.obj  	srcsrsinitepsg_inlined_46.obj srcsrsinitepsg_inlined_47.obj  	srcsrsinitepsg_inlined_48.obj srcsrsinitepsg_inlined_49.obj  	srcsrsinitepsg_inlined_50.obj srcsrsinitepsg_inlined_51.obj  	srcsrsinitepsg_inlined_52.obj srcsrsinitepsg_inlined_53.obj  	srcsrsinitepsg_inlined_54.obj srcsrsinitepsg_inlined_55.obj  	srcsrsinitepsg_inlined_56.obj srcsrsinitepsg_inlined_57.obj  	srcsrsinitepsg_inlined_58.obj srcsrsinitepsg_inlined_59.obj  	srcsrsinitepsg_inlined_60.obj srcsrsinitepsg_inlined_61.obj  	srcsrsinitepsg_inlined_62.obj srcsrsinitepsg_inlined_63.obj  	srcsrsinitepsg_inlined_extra.obj srcsrsinitepsg_inlined_prussian.obj  	srcsrsinitepsg_inlined_wgs84_00.obj srcsrsinitepsg_inlined_wgs84_01.obj  	srcversioninfoversion.obj srcvirtualtextvirtualtext.obj  	srccuttergaia_cutter.obj  	srcspatialitevirtualknn.obj srcspatialitevirtualknn2.obj  	srccontrol_pointsgaia_control_points.obj  	srccontrol_pointsgrass_crs3d.obj srccontrol_pointsgrass_georef_tps.obj  	srccontrol_pointsgrass_georef.obj srcstored_proceduresstored_procedures.obj  	srcgeopackagegaia_cvt_gpkg.obj  	srcgeopackagegpkgAddGeometryColumn.obj  	srcgeopackagegpkg_add_geometry_triggers.obj  	srcgeopackagegpkg_add_spatial_index.obj  	srcgeopackagegpkg_add_tile_triggers.obj  	srcgeopackagegpkgBinary.obj  	srcgeopackagegpkgCreateBaseTables.obj  	srcgeopackagegpkgCreateTilesTable.obj  	srcgeopackagegpkgCreateTilesZoomLevel.obj  	srcgeopackagegpkgGetImageType.obj  	srcgeopackagegpkg_get_normal_row.obj  	srcgeopackagegpkg_get_normal_zoom.obj  	srcgeopackagegpkgInsertEpsgSRID.obj  	srcgeopackagegpkgMakePoint.obj  	srctopologygaia_auxnet.obj srctopologygaia_auxtopo.obj  	srctopologygaia_auxtopo_table.obj srctopologygaia_netstmts.obj  	srctopologygaia_network.obj srctopologygaia_topology.obj  	srctopologygaia_topostmts.obj srctopologylwn_network.obj  	srctopologynet_callbacks.obj srctopologytopo_callbacks.obj  SPATIALITE_DLL = spatialite$(VERSION).dll  CFLAGS = /nologo -I.srcheaders -I.srctopology  	-I. -IC:Work3rdpartyinclude -IC:Work3rdpartyincludelibxml2 $(OPTFLAGS)  default:	all  all: spatialite.lib spatialite_i.lib #$(EXIF_LOADER_EXE)  spatialite.lib:	$(LIBOBJ) 	if exist spatialite.lib del spatialite.lib 	lib /out:spatialite.lib $(LIBOBJ)  $(SPATIALITE_DLL):	spatialite_i.lib  spatialite_i.lib:     $(LIBOBJ) 	link /dll /out:$(SPATIALITE_DLL)  		/implib:spatialite_i.lib $(LIBOBJ)  		C:Work3rdpartylibproj.lib C:Work3rdpartylibgeos_c.lib  		C:Work3rdpartylibfreexl_i.lib C:Work3rdpartylibiconv.lib  		C:Work3rdpartylibsqlite3.lib C:Work3rdpartylibzlib.lib  		C:Work3rdpartyliblibxml2.lib C:Work3rdpartyliblibrttopo.lib 	if exist $(SPATIALITE_DLL).manifest mt -manifest  		$(SPATIALITE_DLL).manifest -outputresource:$(SPATIALITE_DLL);2 		 .c.obj: 	$(CC) $(CFLAGS) /c $*.c /Fo$@ 	 clean: 	del *.dll 	del *.exp 	del *.manifest 	del *.lib 	del srcconnection_cache*.obj 	del srccutter*.obj 	del srcgaiaaux*.obj 	del srcgaiaexif*.obj 	del srcgaiageo*.obj 	del srcshapefiles*.obj 	del srcspatialite*.obj 	del srcsrsinit*.obj 	del srcversioninfo*.obj 	del srcvirtualtext*.obj 	del srcwfs*.obj 	del srcdxf*.obj 	del srcmd5*.obj 	del srctopology*.obj 	del srcstored_procedures*.obj 	del *.pdb  install: all 	-mkdir $(INSTDIR) 	-mkdir $(INSTDIR)bin 	-mkdir $(INSTDIR)lib 	-mkdir $(INSTDIR)include 	-mkdir $(INSTDIR)includespatialite 	copy *.dll $(INSTDIR)bin 	copy *.lib $(INSTDIR)lib 	copy srcheadersspatialite.h $(INSTDIR)include 	copy srcheadersspatialite*.h $(INSTDIR)includespatialite 

主要修改了以下两点:

  1. 修改include文件:
CFLAGS = /nologo -I.srcheaders -I.srctopology  	-I. -IC:Work3rdpartyinclude -IC:Work3rdpartyincludelibxml2 $(OPTFLAGS) 
  1. 修改依赖库文件:
spatialite_i.lib:     $(LIBOBJ) 	link /dll /out:$(SPATIALITE_DLL)  		/implib:spatialite_i.lib $(LIBOBJ)  		C:Work3rdpartylibproj.lib C:Work3rdpartylibgeos_c.lib  		C:Work3rdpartylibfreexl_i.lib C:Work3rdpartylibiconv.lib  		C:Work3rdpartylibsqlite3.lib C:Work3rdpartylibzlib.lib  		C:Work3rdpartyliblibxml2.lib C:Work3rdpartyliblibrttopo.lib 	if exist $(SPATIALITE_DLL).manifest mt -manifest  		$(SPATIALITE_DLL).manifest -outputresource:$(SPATIALITE_DLL);2 

都修改完成之后,执行:

nmake -f makefile.vc  #构建 nmake -f makefile.vc install  #安装 nmake -f makefile.vc clean	#清理 

3.2 依赖项

需要注意的是,makefile64.vc中的内容需要根据依赖库的安装地址来进行修改。SpatiaLite的依赖项有:proj、geos、freexl、iconv、sqlite3、zlib、libxml2以及librttopo,这些依赖项都需要提前安装好,笔者是安装到C:Work3rdparty目录中。至于具体的构建安装过程,大部分依赖库可参看如下文章:

  1. 《CMake构建学习笔记24-使用通用脚本构建PROJ和GEOS》
  2. 《CMake构建学习笔记20-iconv库的构建》
  3. 《CMake构建学习笔记23-SQLite库的构建》
  4. 《CMake构建学习笔记2-zlib库的构建》
  5. 《CMake构建学习笔记22-libxml2库的构建》

剩下的就只有freexl和librttopo两个依赖性的构建了,正好这两个也是依赖于nmake构建的库。

3.2.1 构建libexpat

在构建freexl之前,需要先构建libexpat库。libexpat是一个用C语言编写的、开源的、高效的XML解析库,已经支持使用CMake构建。那么可以使用以下脚本:

param(         [string]$Name = "libexpat-R_2_7_0",     [string]$SourceDir = "../Source",     [string]$Generator,     [string]$MSBuild,     [string]$InstallDir,       [string]$SymbolDir  )  # 根据 $Name 动态构建路径 $zipFilePath = Join-Path -Path $SourceDir -ChildPath "$Name.zip" $SourcePath = Join-Path -Path $SourceDir -ChildPath $Name $BuildDir = Join-Path -Path "." -ChildPath $Name  # 检查是否已经安装(通过目标 DLL) $TargetDll = "$InstallDir/bin/libexpat.dll" if (-not $Force -and $TargetDll -and (Test-Path $TargetDll)) {     Write-Output "Library already installed: $TargetDll"     exit 0 }  # 确保源码目录存在:解压 ZIP if (!(Test-Path $SourcePath)) {     if (!(Test-Path $zipFilePath)) {         Write-Error "Archive not found: $zipFilePath"         exit 1     }     Write-Output "Extracting $zipFilePath to $SourceDir..."     Expand-Archive -LiteralPath $zipFilePath -DestinationPath $SourceDir -Force }  # 如果是强制构建,且构建目录已存在,先删除旧的构建目录(确保干净构建) if ($Force -and (Test-Path $BuildDir)) {     Write-Output "Force mode enabled. Removing previous build directory: $BuildDir"     Remove-Item $BuildDir -Recurse -Force -ErrorAction SilentlyContinue }  # # 复制符号库 $PdbFiles = @(      "$BuildDir/RelWithDebInfo/libexpat.pdb" )   # 额外构建参数 $CMakeCacheVariables = @{     EXPAT_BUILD_DOCS = "OFF"     EXPAT_BUILD_EXAMPLES = "OFF"     EXPAT_BUILD_TESTS = "OFF" }  # 调用通用 CMake 构建脚本 $cmakeSourcePath = Join-Path -Path $SourcePath -ChildPath "expat" Write-Output "Starting build for $Name..." . ./cmake-build.ps1 -SourceLocalPath $cmakeSourcePath `     -BuildDir $BuildDir `     -Generator $Generator `     -InstallDir $InstallDir `     -SymbolDir $SymbolDir `     -PdbFiles $PdbFiles `     -CMakeCacheVariables $CMakeCacheVariables `     -MultiConfig $true    if ($LASTEXITCODE -ne 0) {     Write-Error "Build failed for $Name."     exit $LASTEXITCODE }  # 构建成功后,根据 Cleanup 开关决定是否删除 if ($Cleanup) {     Write-Output "Build succeeded. Cleaning up temporary directories..."     if (Test-Path $SourcePath) {          Remove-Item $SourcePath -Recurse -Force -ErrorAction SilentlyContinue          Write-Output "Removed source directory: $SourcePath"     }     if (Test-Path $BuildDir) {          Remove-Item $BuildDir -Recurse -Force -ErrorAction SilentlyContinue          Write-Output "Removed build directory: $BuildDir"     } }  Write-Output "Build completed for $Name." 

脚本cmake-build.ps1笔者已经在《CMake构建学习笔记21-通用的CMake构建脚本》这篇文章中介绍过。

3.2.1 freexl构建

freexl是一个开源库,主要用于读取Microsoft Excel的.xls文件格式。构建freexl与前面介绍nmake构建的一般方法基本一致:

nmake -f makefile.vc  #构建 nmake -f makefile.vc install  #安装 nmake -f makefile.vc clean	#清理 

当然nmake64.optmakefile64.vc中的内容需要根据自己的情况按需修改。对于nmake64.opt,修改的内容为:

#INSTDIR=C:OSGeo4W64 INSTDIR=C:OSGeo4W64 

而对于makefile64.vc,修改的内容为:

# CFLAGS	=	/nologo -I. -Iheaders -IC:OSGeo4W64include $(OPTFLAGS) CFLAGS	=	/nologo -I. -Iheaders -IC:Work3rdpartyinclude $(OPTFLAGS)  #freexl_i.lib:	$(LIBOBJ) #	link /debug /dll /out:$(FREEXL_DLL)  #		/implib:freexl_i.lib $(LIBOBJ)  #		C:Work3rdpartylibiconv.lib  #		C:Work3rdpartyliblibexpat.lib  #		C:Work3rdpartylibminizip.lib  #		C:Work3rdpartylibzlib.lib #	if exist $(FREEXL_DLL).manifest mt -manifest  #		$(FREEXL_DLL).manifest -outputresource:$(FREEXL_DLL);2  freexl_i.lib:	$(LIBOBJ) 	link /debug /dll /out:$(FREEXL_DLL)  		/implib:freexl_i.lib $(LIBOBJ)  		C:OSGeo4w64libiconv.lib  		C:OSGeo4W64liblibexpat.lib  		C:OSGeo4W64liblibminizip.lib  		C:OSGeo4w64libzlib.lib 	if exist $(FREEXL_DLL).manifest mt -manifest  		$(FREEXL_DLL).manifest -outputresource:$(FREEXL_DLL);2 

3.2.1 librttopo构建

librttopo是一个开源的地理空间操作库,是PostGIS项目的一部分,提供了一系列用于处理和分析地理数据的功能,比如几何图形的操作、空间关系判断、坐标转换等。librttopo使用nmake构建有点麻烦,主要原因就是跨平台兼容性有点问题,在Windows下有些文件没有生成导致编译出错。

可以使用这篇文章中提供了一个源代码版本,然后按需修改nmake64.opt

#INSTDIR=C:OSGeo4W64 INSTDIR=C:Work3rdparty 

同样修改makefile64.vc

#CFLAGS	=	/nologo -IC:OSGeo4W64include -I. -Iheaders $(OPTFLAGS) CFLAGS	=	/nologo -IC:Work3rdpartyinclude -I. -Iheaders $(OPTFLAGS)  #librttopo_i.lib:	$(LIBOBJ) #	link /debug /dll /out:$(LIBRTTOPO_DLL)  #		/implib:librrttopo_i.lib $(LIBOBJ)  #		C:OSGeo4W64libgeos_c.lib #	if exist $(LIBRTTOPO_DLL).manifest mt -manifest  #		$(LIBRTTOPO_DLL).manifest -outputresource:$(LIBRTTOPO_DLL);2 librttopo_i.lib:	$(LIBOBJ) 	link /debug /dll /out:$(LIBRTTOPO_DLL)  		/implib:librrttopo_i.lib $(LIBOBJ)  		C:Work3rdpartylibgeos_c.lib 	if exist $(LIBRTTOPO_DLL).manifest mt -manifest  		$(LIBRTTOPO_DLL).manifest -outputresource:$(LIBRTTOPO_DLL);2 

三个nmake构建库修改的配置文件内容大致差不多,都是修改安装目录,修改依赖库include目录,修改链接的库文件,然后执行:

nmake -f makefile.vc  #构建 nmake -f makefile.vc install  #安装 nmake -f makefile.vc clean	#清理 

4. 问题

如文中所说,这种nmake构建的方式有个很大的问题就是总是需要临时修改nmake64.optmakefile64.vc来应对不同的环境,这对于使用脚本自动批处理的方式来说不太友好。说白了Windows下所有的逻辑都是基于GUI的,nmake不过是个临时补丁。不过理论上也可以考虑使用环境变量的方式来避免这个问题,就留待后续解决了。

发表评论

评论已关闭。

相关文章