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

来源:互联网 发布:导航源码 编辑:程序博客网 时间:2024/06/05 19:27

由于是开源软件,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.0MATH(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 expectedMARK_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和BisonINCLUDE(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 providerENDIF (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/2924292IF (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 availableIF (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 bindingsIF (WITH_BINDINGS)  ……############################################################## create qgsconfig.h# installed with app targetCONFIGURE_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 prefixLINK_DIRECTORIES(${CMAKE_BINARY_DIR}/src/core ${CMAKE_BINARY_DIR}/src/gui)############################################################## create qgsversion.hIF (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 coreSET (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 commandsADD_SUBDIRECTORY(postinstall)############################################################## Uninstall stuff see: http://www.vtk.org/Wiki/CMake_FAQCONFIGURE_FILE(  ……############################################################## Enable packaging……

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

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

这里写图片描述

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

总结

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

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

2 0