由于是开源软件,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工程的编译有一点感觉,这里面的组织非常复杂,但只要细心,你一定会找到自己编译不成功的原因。
谢谢阅读,如有错误,请不吝指正!