QGis二次开发 -- 源码编译终极篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/deirjie/article/details/63713033

由于是开源软件,QGis版本迭代比较快,在保持long term release版本的基础上,每个月都会有一个monthly release的新版本发布。源码工程变化快速,给想要上手编译开发的新人朋友带来了一些困惑。

我之前分别写过QGis1.8版本和QGis2.9版本的源码编译指南,我相信还是帮助到了一部分人。但是现在回过头来看,文章中用到的QGis版本又过时了。

显然,我写博客的速度赶不及QGis大大小小的版本发布速度,也没有必要每一个版本,都来写一遍编译指南,这是很笨拙的做法。况且,我发现,有很大一部分朋友在向我提问题的时候,他们是并没有仔仔细细地把博客内容学习过的,大多数是走马观花。还有一部分朋友确实是学习过博客内容了,并且一步一步依照准则进行,但是一旦遇到点错误,就不知所措,急病乱投医。

俗话说,授人以鱼不如授人以渔。这次我想抛开QGis的任何一个特定版本,跟大家谈谈源码编译的根本方法,结合我之前写的两篇QGis的编译指南,希望能够就此终结源码编译的噩梦。

注意:
本文只讲工程组织的一些原理,如果需要具体的编译操作,请移步关于QGis1.8二次开发的环境配置 以及 QGis2.9在windows下的编译以及二次开发包下载

写在前面的一点忠告

1. 不要轻易尝试用最新版本的Qt进行编译

截止本文撰写的时间,QGis3.0还未正式发布,到那以前,QGis对于Qt5.x的支持都不会非常好。即使支持,也并没有真正用到Qt5的新特性。目前Qt5.5以下的版本,都有成功编译的例子,但更新的版本就很难了。所以,新版本,有风险。
当你想要用最新版本的Qt进行QGis编译时,先想一想,你是否真的需要最新版的功能?举个最简单的例子,你真的需要Qt Quick吗?或者,你知道Qt Quick吗?

2. 不要低估C++的难度,但也不要过分高估C++带来的阻碍

我在这里必须要单独说明这一点,很多朋友误以为C++如同C#、Python那般上手就能看懂,熟悉一下就能运用自如,这是非常错误的,尤其是你现在要自己去研究QGis这般庞大的C++源码工程的时候。记住这句话,当你看不懂编译器出错信息,不知道是哪里出问题的时候,只是因为你不懂C++。
C++是一门博大精深的语言,它的困难在于自身的灵活性,想要精通它并非一朝一夕的功夫。
然而,就我们编译QGis源码,并用它进行二次开发而言,你需要越过的C++门槛其实并不高。你只需要把基本的概念、编译链接流程等理解清楚,就足够了。

3.多在自己身上找原因

这个不用细说,只是作为提醒,不要抱怨别人的方法不行,不要责怪自己的电脑不行,更不要去怀疑自己的操作系统出了故障,大多数时候编译不成功,都是你自己的原因。

原理

源码工程的编译过程,实际上,就是将依赖库与源代码连接起来的过程。

从github下载到QGis的源码后,我们会看到如下图的文件结构:

这里写图片描述

我们来解释一下这里面的文件夹的用途。

文件名 说明
ci
cmake 工程组织说明文件,主要是依赖库的配置说明
cmake_templates cmake模板文件
debian Linux操作系统所需
doc 帮助文档
!18n 翻译所需文件
images 图片资源文件
mac 苹果Mac操作系统所需
ms-windows 微软Windows操作系统所需
postinstall 软件安装完成之后执行的脚本操作
python python脚本支持
resources 各种资源、配置文件
rpm 默认配置文件
scripts 各种脚本
src 源代码,这个是我们关注的重点
tests 各种测试代码
tools 目前这里面只有一个Qt3迁移到Qt4的工具

并且,在这一级目录下面,有一个非常重要的文件“CMakeLists.txt”,它定义了源码工程如何进行编译。这个文件代码很长,梳理一下,删掉不必要的部分,结构大致可以表示为下面这样。(请务必读一下下面的代码,关键地方我都注释在了代码里面

##############################################################
# 编译版本设置
SET(CPACK_PACKAGE_VERSION_MAJOR "2")
……

# Note the version no is Mmmpp for Major/minor/patch, 0-padded, thus '10100' for 1.1.0
MATH(EXPR QGIS_VERSION_INT "${CPACK_PACKAGE_VERSION_MAJOR}*10000+${CPACK_PACKAGE_VERSION_MINOR}*100+${CPACK_PACKAGE_VERSION_PATCH}")
MESSAGE(STATUS "QGIS version: ${COMPLETE_VERSION} ${RELEASE_NAME} (${QGIS_VERSION_INT})")

#############################################################
# CMake设置
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.6) # CMake最低要求
……

# 配置GRASS插件
FOREACH (GRASS_SEARCH_VERSION 6 7)
  ……

# 下面是各种编译选项
SET (WITH_DESKTOP TRUE CACHE BOOL "Determines whether QGIS desktop should be built")
SET (WITH_SERVER FALSE CACHE BOOL "Determines whether QGIS server should be built")
……

SET (WITH_CUSTOM_WIDGETS FALSE CACHE BOOL "Determines whether QGIS custom widgets for Qt Designer should be built")

SET (WITH_ASTYLE FALSE CACHE BOOL "If you plan to contribute you should reindent with scripts/prepare-commit.sh (using 'our' astyle)")

SET (WITH_POSTGRESQL TRUE CACHE BOOL "Determines whether POSTGRESQL support should be built")
……

SET (WITH_INTERNAL_QEXTSERIALPORT TRUE CACHE BOOL "Use internal build of Qextserialport")

SET (WITH_QSPATIALITE FALSE CACHE BOOL "Determines whether QSPATIALITE sql driver should be built")

SET (WITH_ORACLE FALSE CACHE BOOL "Determines whether Oracle support should be built")
……

# 如果你需要Python支持,关注这一块
SET (WITH_BINDINGS TRUE CACHE BOOL "Determines whether python bindings should be built")
IF (WITH_BINDINGS)
  ……
ENDIF (WITH_BINDINGS)

# Android移动端支持
IF (ANDROID)
    SET (DEFAULT_WITH_QTMOBILITY TRUE)
ELSE (ANDROID)
    SET (DEFAULT_WITH_QTMOBILITY FALSE)
ENDIF (ANDROID)
SET (WITH_QTMOBILITY ${DEFAULT_WITH_QTMOBILITY} CACHE BOOL "Determines if QtMobility related code should be build (for example internal GPS)")

# globe三维支持
SET (WITH_GLOBE FALSE CACHE BOOL "Determines whether Globe plugin should be built")
……

SET (PEDANTIC TRUE CACHE BOOL "Determines if we should compile in pedantic mode.")
SET (ENABLE_TESTS TRUE CACHE BOOL "Build unit tests?")
SET (ENABLE_COVERAGE FALSE CACHE BOOL "Perform coverage tests?")
SET (GENERATE_COVERAGE_DOCS FALSE CACHE BOOL "Generate coverage docs (requires lcov)?")

# hide this variable because building of python bindings might fail
# if set to other directory than expected
MARK_AS_ADVANCED(LIBRARY_OUTPUT_PATH)

# 这里是指定编译器类型
IF (MSVC AND CMAKE_GENERATOR MATCHES "NMake")
  # following variable is also used in qgsconfig.h
  SET (USING_NMAKE TRUE)
ENDIF (MSVC AND CMAKE_GENERATOR MATCHES "NMake")

#############################################################
# 这里是Flex和Bison

INCLUDE(Flex)

FIND_FLEX()

IF (NOT FLEX_EXECUTABLE)
  MESSAGE(FATAL_ERROR "Couldn't find Flex")
ENDIF (NOT FLEX_EXECUTABLE)

INCLUDE(Bison)

FIND_BISON()

IF (NOT BISON_EXECUTABLE)
  MESSAGE(FATAL_ERROR "Couldn't find Bison")
ENDIF (NOT BISON_EXECUTABLE)

#############################################################
# 下面就开始找依赖库了

IF(NOT WIN32 AND NOT ANDROID)
  ……

# 必须要的依赖库
FIND_PACKAGE(Proj)
FIND_PACKAGE(GEOS)
FIND_PACKAGE(GDAL)
FIND_PACKAGE(Expat REQUIRED)
FIND_PACKAGE(Spatialindex REQUIRED)
FIND_PACKAGE(Qwt REQUIRED)

IF (WITH_INTERNAL_QEXTSERIALPORT)
  SET(QEXTSERIALPORT_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/core/gps/qextserialport)
ELSE (WITH_INTERNAL_QEXTSERIALPORT)
  FIND_PACKAGE(Qextserialport REQUIRED)
ENDIF(WITH_INTERNAL_QEXTSERIALPORT)

FIND_PACKAGE(Sqlite3)
IF (NOT SQLITE3_FOUND)
  MESSAGE (SEND_ERROR "sqlite3 dependency was not found!")
ENDIF (NOT SQLITE3_FOUND)

# 可选的依赖库
IF (WITH_POSTGRESQL)
  FIND_PACKAGE(Postgres) # PostgreSQL provider
ENDIF (WITH_POSTGRESQL)

FIND_PACKAGE(SpatiaLite REQUIRED)

# spatialite的版本处理
IF(SPATIALITE_VERSION_GE_4_0_0)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSPATIALITE_VERSION_GE_4_0_0")
ENDIF(SPATIALITE_VERSION_GE_4_0_0)
IF(SPATIALITE_VERSION_G_4_1_1)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSPATIALITE_VERSION_G_4_1_1")
ENDIF(SPATIALITE_VERSION_G_4_1_1)
IF(SPATIALITE_HAS_INIT_EX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSPATIALITE_HAS_INIT_EX")
ENDIF(SPATIALITE_HAS_INIT_EX)

IF (NOT PROJ_FOUND OR NOT GEOS_FOUND OR NOT GDAL_FOUND)
  MESSAGE (SEND_ERROR "Some dependencies were not found! Proj: ${PROJ_FOUND}, Geos: ${GEOS_FOUND}, GDAL: ${GDAL_FOUND}")
ENDIF (NOT PROJ_FOUND OR NOT GEOS_FOUND OR NOT GDAL_FOUND)

IF (POSTGRES_FOUND)
  # following variable is used in qgsconfig.h
  SET (HAVE_POSTGRESQL TRUE)
ENDIF (POSTGRES_FOUND)

SET (WITH_QTWEBKIT TRUE CACHE INTERNAL "Enable QtWebkit support")
IF (WITH_QTWEBKIT)
  ADD_DEFINITIONS(-DWITH_QTWEBKIT)
ENDIF(WITH_QTWEBKIT)
#############################################################
# 找Qt4,如果设置了ENABLE_QT5会尝试去找Qt5,需要用Qt5编译的,关注这里的详细代码
SET(QT_MIN_VERSION 4.8.0)
SET (ENABLE_QT5 FALSE CACHE BOOL "If enabled will try to find Qt5 before looking for Qt4")
IF (ENABLE_QT5)
  ……

# 下面是模型测试
SET(ENABLE_MODELTEST FALSE CACHE BOOL "Enable QT ModelTest (not for production)")

IF (ENABLE_TESTS)
  ……

#############################################################
# C++11的支持
# enable use of c++11 features where available
# full c++11 support in clang 3.3+: http://clang.llvm.org/cxx_status.html
# for Mac, this is probably Apple LLVM 4.2 (based on LLVM 3.2svn, in XCode 4.6+)
#   or definitely Apple LLVM 5.0 (based on LLVM 3.3svn, in Xcode 5+):
#   https://gist.github.com/yamaya/2924292

IF (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
  EXECUTE_PROCESS(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
  IF (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)
    SET(USE_CXX_11 TRUE)
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
  ENDIF()
ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  IF ((NOT APPLE AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "3.2")
       OR (APPLE AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.1"))
    SET(USE_CXX_11 TRUE)
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-error=c++11-narrowing")
  ENDIF()
ELSEIF (MSVC AND MSVC_VERSION GREATER 1600)
  SET(USE_CXX_11 TRUE)
ELSE()
  SET(USE_CXX_11 FALSE)
ENDIF()

#allow override keyword if available
IF (NOT USE_CXX_11)
  ADD_DEFINITIONS("-Doverride=")
  ADD_DEFINITIONS("-Dnoexcept=")
  ADD_DEFINITIONS("-Dnullptr=0")
ENDIF()


#############################################################
# 设置警告信息可用

IF (PEDANTIC)
  MESSAGE (STATUS "Pedantic compiler settings enabled")
  IF(MSVC)
    ……

    # disable warnings
    SET(_warnings "${_warnings} /wd4100 ")  # unused formal parameters
   ……

ENDIF (PEDANTIC)

IF (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  ……

IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)")
 ……

IF (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
  ……

IF(MSVC)
  ……

IF(ENABLE_COVERAGE)
  ……

#############################################################
# 操作系统环境指定的一些配置

IF (WIN32)
  ……

  IF (MSVC)
    ……

  IF (APPLE)
    ……

ENDIF (WIN32)

IF (ANDROID)
    ……

# 看一看这里,是配置预编译器选项需要的设置
ADD_DEFINITIONS("-DCORE_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DGUI_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DPYTHON_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DANALYSIS_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DAPP_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DCUSTOMWIDGETS_EXPORT=${DLLIMPORT}")
ADD_DEFINITIONS("-DSERVER_EXPORT=${DLLIMPORT}")

#############################################################
# 用户可以指定的一些QGIS配置,主要针对安装好的应用程序
# user-changeable settings which can be used to customize
# layout of QGIS installation
# (default values are platform-specific)

SET (QGIS_BIN_SUBDIR     ${DEFAULT_BIN_SUBDIR}     CACHE STRING "Subdirectory where executables will be installed")
……


#############################################################
# Python的一些依赖

SET (ENABLE_PYTHON3 ${ENABLE_QT5} CACHE BOOL "If enabled will try to find Python 3 before looking for Python 2")
IF(ENABLE_PYTHON3)
  SET(PYTHON_VER 3 CACHE STRING "Python version")
ELSE(ENABLE_PYTHON3)
  SET(PYTHON_VER 2.7 CACHE STRING "Python version")
ENDIF(ENABLE_PYTHON3)

FIND_PACKAGE(PythonInterp ${PYTHON_VER} REQUIRED)

#############################################################
# Python bindings

IF (WITH_BINDINGS)
  ……

#############################################################
# create qgsconfig.h
# installed with app target

CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake_templates/qgsconfig.h.in ${CMAKE_BINARY_DIR}/qgsconfig.h)
INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR})

# Added by Jef to prevent python core and gui libs linking to other qgisCore and qgisGui libs
# that may be in the same install prefix
LINK_DIRECTORIES(${CMAKE_BINARY_DIR}/src/core ${CMAKE_BINARY_DIR}/src/gui)

#############################################################
# create qgsversion.h
IF (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
  ……

#############################################################
# 在输出目录中添加一些子文件夹

#create a variable to specify where our test data is
#so that unit tests can use TEST_DATA_DIR to locate
#the test data. See CMakeLists in test dirs for more info
#TEST_DATA_DIR is also used by QgsRenderChecker currently in core
SET (TEST_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata")

ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(doc)
ADD_SUBDIRECTORY(images)
ADD_SUBDIRECTORY(resources)
ADD_SUBDIRECTORY(i18n)

IF (WITH_BINDINGS)
  ADD_SUBDIRECTORY(python)
ENDIF (WITH_BINDINGS)

IF (ENABLE_TESTS)
  ADD_SUBDIRECTORY(tests)
  SET (CTEST_BINARY_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/bin" )
  MESSAGE (STATUS "Ctest Binary Directory set to: ${CTEST_BINARY_DIRECTORY}")
ENDIF (ENABLE_TESTS)

IF (APPLE)
  ……

INSTALL(FILES cmake/FindQGIS.cmake DESTINATION ${QGIS_DATA_DIR})

#############################################################
# Post-install commands
ADD_SUBDIRECTORY(postinstall)

#############################################################
# Uninstall stuff see: http://www.vtk.org/Wiki/CMake_FAQ
CONFIGURE_FILE(
  ……

#############################################################
# Enable packaging
……

如果你把上面的代码认真读了一遍,相信你对QGis工程的组织有一些基本的认识了。

我自己将QGis的不同模块对应的编译选项列在这里了,可以参考。(如果有遗漏请告诉我,谢谢)

这里写图片描述

针对每个不同的模块编译,可以去找不同模块子文件夹下的CMakeList.txt文件,看看自己是否在生成工程的时候哪里有缺失。

总结

通过上面的整体介绍,希望大家能够对QGis工程的编译有一点感觉,这里面的组织非常复杂,但只要细心,你一定会找到自己编译不成功的原因。

谢谢阅读,如有错误,请不吝指正!

猜你喜欢

转载自blog.csdn.net/deirjie/article/details/63713033