table of contents · 2020-03-30 · work will save you hundreds of lines and hours of...
TRANSCRIPT
1.1
1.1.1
1.1.2
1.1.3
1.1.4
1.2
1.2.1
1.2.2
1.2.3
1.2.4
1.2.5
1.2.6
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.3.5
1.3.6
1.4
1.4.1
1.4.2
1.4.3
1.5
1.5.1
1.5.2
1.6
1.6.1
1.6.2
1.6.3
1.7
1.7.1
1.7.2
1.7.3
1.7.4
1.7.5
1.7.5.1
1.7.5.2
1.7.5.3
TableofContentsAnIntroductiontoModernCMake
InstallingCMake
RunningCMake
DosandDon'ts
What'snewinCMake
IntroductiontotheBasics
VariablesandtheCache
ProgramminginCMake
Communicatingwithyourcode
HowtoStructureYourProject
RunningOtherPrograms
ASimpleExample
AddingFeatures
C++11andBeyond
Smallbutcommonneeds
Utilities
Usefulmodules
IDEs
Debugging
IncludingProjects
Submodule
DownloadProject
Fetch(CMake3.11)
Testing
GoogleTest
Catch
ExportingandInstalling
Installing
Exporting
Packaging
Lookingforlibraries
CUDA
OpenMP
Boost
MPI
ROOT
UseFileExample
SimpleExample
DictionaryExample
1
1.7.6Minuit2
2
AnIntroductiontoModernCMakePeoplelovetohatebuildsystems.JustwatchthetalksfromCppCon17toseeexamplesofdevelopersmakingthestateofbuildsystemsthebruntofjokes.Thisraisesthequestion:Why?Certainlytherearenoshortageofproblemswhenbuilding.ButIthinkthat,in2020,wehaveaverygoodsolutiontoquiteafewofthoseproblems.It'sCMake.NotCMake2.8though;thatwasreleasedbeforeC++11evenexisted!NorthehorribleexamplesoutthereforCMake(eventhosepostedonKitWare'sowntutorialslist).I'mtalkingaboutModernCMake.CMake3.1+,maybeevenCMake3.17+!It'sclean,powerful,andelegant,soyoucanspendmostofyourtimecoding,notaddinglinestoanunreadable,unmaintainableMake(OrCMake2)file.AndCMake3.11+issupposedtobesignificantlyfaster,aswell!
Thisbookismeanttobealivingdocument.YoucanraiseanissueorputinamergerequestonGitLab.YoucanalsodownloadacopyasaPDF.
Inshort,herearethemostlikelyquestionsinyourmindifyouareconsideringModernCMake:
WhydoIneedagoodbuildsystem?Doanyofthefollowingapplytoyou?
Youwanttoavoidhard-codingpathsYouneedtobuildapackageonmorethanonecomputerYouwanttouseCI(continuousintegration)YouneedtosupportdifferentOSs(maybeevenjustflavorsofUnix)YouwanttosupportmultiplecompilersYouwanttouseanIDE,butmaybenotallofthetimeYouwanttodescribehowyourprogramisstructuredlogically,notflagsandcommandsYouwanttousealibraryYouwanttousetools,likeClang-Tidy,tohelpyoucodeYouwanttouseadebugger
Ifso,you'llbenefitfromaCMake-likebuildsystem.
WhymusttheanswerbeCMake?Buildsystemsisahottopic.Ofcoursetherearemanyoptions.Butevenareallygoodone,oronethatre-usesafamiliarsyntax,can'tcomeclosetoCMake.Why?Support.EveryIDEsupportsCMake(orCMakesupportsthatIDE).MorepackagesuseCMakethananyothersystem.So,ifyouusealibrarythatisdesignedtobeincludedinyourcode,youhaveachoice:Makeyourownbuildsystem,oruseoneofoftheprovidedones,andthatwillalmostalwaysincludeCMake.Andthatwillquicklybethecommondenominatorifyouincludemultipleprojects.And,ifyouneedalibrarythat'spreinstalled,thechancesofithavingafindCMakescriptorconfigCMakescriptareexcellent.
WhyuseaModernCMake?AroundCMake2.6-2.8,CMakestartedtakingover.ItwasinmostofthepackagemanagersforLinuxOS's,andwasbeingusedinlotsofpackages.
ThenPython3cameout.
Iknow,thisshouldhavenothingwhatsoevertodowithCMake.
AnIntroductiontoModernCMake
3
Butithada3.Anditfollowed2.Anditwasahard,ugly,transitionthatisstillongoinginsomeplaces,eventoday.
IbelievethatCMake3hadthebadlucktofollowPython3. EventhougheveryversionofCMakeisinsanelybackwardcompatible,the3serieswastreatedasifitwassomethingnew.Andso,you'llfindOS'slikeCentOS7withGCC4.8,withalmost-completeC++14support,andCMake2.8,whichcameoutbeforeC++11.
YoureallyshouldatleastuseaversionofCMakethatcameoutafteryourcompiler,sinceitneedstoknowcompilerflags,etc,forthatversion.And,sinceCMakewilldumbitselfdowntotheminimumrequiredversioninyourCMakefile,installinganewCMake,evensystemwide,isprettysafe.Youshouldatleastinstallitlocally.It'seasy(1-2linesinmanycases),andyou'llfindthat5minutesofworkwillsaveyouhundredsoflinesandhoursofCMakeLists.txtwriting,andwillbemucheasiertomaintaininthelongrun.
Thisbooktriestosolvetheproblemofthepoorexamplesandbestpracticesthatyou'llfindproliferatingtheweb.
Othersources
Othermaterialfromtheoriginalauthorofthisbook:
CMakeWorkshopInteractiveModernCMaketalk
Therearesomeotherplacestofindgoodinformationontheweb.Herearesomeofthem:
Theofficialhelp:Reallyamazingdocumentation.Nicelyorganized,greatsearch,andyoucantoggleversionsatthetop.Itjustdoesn'thaveagreat"bestpracticestutorial",whichiswhatthisbooktriestofillin.EffectiveModernCMake:Agreatlistofdo'sanddon'ts.EmbracingModernCMake:ApostwithgooddescriptionofthetermIt'stimetodoCMakeRight:AnicesetofbestpracticesforModernCMakeprojects.TheUltimateGuidetoModernCMake:Aslightlydatedpostwithsimilarintent.MoreModernCMake:AgreatpresentationfromMeetingC++2018thatrecommendsCMake3.12+.ThistalkmakescallsCMake3.0+"ModernCMake"andCMake3.12+"MoreModernCMake".OhNo!MoreModernCMake:ThesequeltoMoreModernCMake.toeb/moderncmake:AnicepresentationandexamplesaboutCMake3.5+,withintrotosyntaxthroughprojectorganization
Credits
ModernCMakewasoriginallywrittenbyHenrySchreiner.OthercontributorscanbefoundlistedonGitLab.
.CMake3.0alsoremovedseverallongdeprecatedfeaturesfromveryoldversionsofCMakeandmakeoneverytinybackwardsincompatiblechangetosyntaxrelatedtosquarebrackets,sothisisnotentirelyfair;theremightbesomevery,veryoldCMakefilesthatwouldstopworkingwith3.I'veneverseenone,though.↩
1
1
AnIntroductiontoModernCMake
4
InstallingCMake
YourCMakeversionshouldbenewerthanyourcompiler.Itshouldbenewerthanthelibrariesyouareusing(especiallyBoost).Newversionsworkbetterforeveryone.
IfyouhaveabuiltincopyofCMake,itisn'tspecialorcustomizedforyoursystem.Youcaneasilyinstallanewoneinstead,eitheronthesystemlevelortheuserlevel.FeelfreetoinstructyourusershereiftheycomplainaboutaCMakerequirementbeingsettoohigh.Especiallyiftheywant<3.1support.MaybeeveniftheywantCMake<3.17support...
Quicklist(moreinfooneachmethodbelow)
Orderedbyauthorpreference:
AllPip(official,sometimesdelayedslightly)Anaconda/Conda-Forge
WindowsChocolateyScoopMSYS2Downloadbinary(official)
MacOSHomebrewMacPortsDownloadbinary(official)
LinuxSnapcraft(official)APTrepository(Ubuntu/Debianonly)(official)Downloadbinary(official)
OfficialpackageYoucandownloadCMakefromKitWare.ThisishowyouwillprobablygetCMakeifyouareonWindows.It'snotabadwaytogetitonmacOSeither,butusingbrewinstallcmakeismuchnicerifyouuseHomebrew(andyoushould).Youcanalsogetitonmostotherpackagemanagers,suchasChocolateyforWindowsorMacPortsformacOS.
OnLinux,thereareseveraloptions.KitwareprovidesaDebian/Ubunutuaptrepository,aswellassnappackages.ThereareuniversalLinuxbinariesprovided,butyou'llneedtopickaninstalllocation.Ifyoualreadyuse~/.localforuser-spacepackages,thefollowingsinglelinecommand willgetCMakeforyou :
IfyoujustwantalocalfolderwithCMakeonly:
1 2
~$wget-qO-"https://cmake.org/files/v3.17/cmake-3.17.0-Linux-x86_64.tar.gz"|tar--
strip-components=1-xz-C~/.local
~$mkdir-pcmake-3.17&&wget-qO-"https://cmake.org/files/v3.17/cmake-3.17.0-Linux-
x86_64.tar.gz"|tar--strip-components=1-xz-Ccmake-3.17
~$exportPATH=`pwd`/cmake-3.17/bin:$PATH
InstallingCMake
5
You'llobviouslywanttoappendtothePATHeverytimeyoustartanewterminal,oraddittoyour.bashrcortoanLModsystem.
And,ifyouwantasysteminstall,installto/usr/local;thisisanexcellentchoiceinaDockercontainer,forexampleonGitLabCI.Donottryitonanon-containerizedsystem.
Ifyouareonasystemwithoutwget,replacewget-qO-withcurl-s.
YoucanalsobuildCMakeonanysystem,it'sprettyeasy,butbinariesarefaster.
CMakeDefaultVersions
HerearesomecommonbuildenvironmentsandtheCMakeversionyou'llfindonthem.FeelfreetoinstallCMakeyourself,it's1-2linesandthere'snothing"special"aboutthebuiltinversion.It'salsoverybackwardcompatible.
Windows
AlsoScoopisgenerallyuptodate.ThenormalinstallersfromCMake.orgarecommononWindows,too.
MacOS
HomebrewisquiteabitmorepopularnowadaysonmacOS,atleastaccordingtoGoogleTrends.
Linux
RHEL/CentOS
Thedefaulton8isnottoobad,butyoushouldnotusethedefaulton7.UsetheEPELpackageinstead.
Ubuntu
YoushouldonlyusethedefaultCMakeon18.04+;it'sanLTSreleasewithaprettydecentminimumversion!
Other
Generaltools
docker$wget-qO-"https://cmake.org/files/v3.17/cmake-3.17.0-Linux-x86_64.tar.gz"|tar
--strip-components=1-xz-C/usr/local
InstallingCMake
6
Justpipinstallcmakeonmanysystems.Add--userifyouhaveto.ManyLinux1(oldpiporOS)getsCMake3.13.3.
CI
Distribution CMakeversion Notes
TravisCIXenial 3.12.4 MidNovember2018thisimagebecamereadyforwidescaleuse.
TravisCIBionic 3.12.4 SameasXenialatthemoment.
AzureDevOps18.04 3.12.4
GitHubActions18.04 3.12.4 MostlyinsyncwithAzureDevOps
Fulllist
InstallingCMake
7
InstallingCMake
8
Alsoseepkgs.org/download/cmake.
Pip
Thisisalsoprovidedasanofficialpackage,maintainedbytheauthorsofCMakeatKitWare.It'sarathernewmethod,andmightfailonsomesystems(Alpineisn'tsupportedlastIchecked,butthathasCMake3.8),butworksreallywellwhenitworks(likeonTravisCI).Ifyouhavepip(Python'spackageinstaller),youcando:
Andaslongasabinaryexistsforyoursystem,you'llbeup-and-runningalmostimmediately.Ifabinarydoesn'texist,itwilltrytouseKitWare'sscikit-buildpackagetobuild,whichcurrentlycan'tbelistedasadependencyinthepackagingsystem,andmightevenrequire(anolder)copyofCMaketobuild.Soonlyusethissystemifbinariesexist,whichismostofthetime.
Thishasthebenefitofrespectingyourcurrentvirtualenvironment,aswell.
Personally,onLinux,IputversionsofCMakeinfolders,like/opt/cmake312or~/opt/cmake312,andthenaddthemto[LMod].Seeenvmodule_setupforhelpsettingupanLModsystemonmacOSorLinux.Ittakesabittolearn,butisagreatwaytomanagepackageandcompilerversions.
.Iassumethisisobvious,butyouaredownloadingandrunningcode,whichexposesyoutoamaninthemiddleattack.Ifyouareinacriticalenvironment,youshoulddownloadthefileandcheckthechecksum.(And,no,simplydoingthisintwostepsdoesnotmakeyouanysafer,onlyachecksumissafer).↩
.Ifyoudon'thavea.localinyourhomedirectory,it'seasytostart.Justmakethefolder,thenaddexportPATH="$HOME/.local/bin:$PATH"toyour.bashrcor.bash_profileor.profilefileinyourhomedirectory.Nowyoucaninstallanypackagesyoubuildto-DCMAKE_INSTALL_PREFIX=~/.localinsteadof/usr/local!↩
gitbook$pipinstallcmake
1
2
InstallingCMake
9
RunningCMakeBeforewritingCMake,let'smakesureyouknowhowtorunittomakethings.ThisistrueforalmostallCMakeprojects,whichisalmosteverything.
Buildingaproject
Unlessotherwisenoted,youshouldalwaysmakeabuilddirectoryandbuildfromthere.Youcantechnicallydoanin-sourcebuild,butyou'llhavetobecarefulnottooverwritefilesoraddthemtogit,sojustdon't.
Here'stheClassicCMakeBuildProcedure(TM):
Youcanreplacethemakelinewithcmake--build.ifyou'dlike,anditwillcallmakeorwhateverbuildtoolyouareusing.IfyouareusinganewerversionofCMake(whichyouusuallyshouldbe,exceptforcheckingcompatibilitywitholderCMake),youcaninsteaddothis:
Anyoneofthesecommandswillinstall:
Sosetofmethodsshouldyouuse?Aslongasyoudonotforgettotypethebuilddirectoryastheargument,stayingoutofthebuilddirectoryisshorter,andmakingsourcechangesiseasierfromthesourcedirectory.Youshouldtrytogetusedtousing--build,asthatwillfreeyoufromusingonlymaketobuild.Notethatworkingfromthebuilddirectoryishistoricallymuchmorecommon,andsometoolsandcommands(includingCTest)stillrequirerunningfromthebuilddirectory.
Justtoclarify,youcanpointCMakeateitherthesourcedirectoryfromthebuilddirectory,oratanexistingbuilddirectoryfromanywhere.
Ifyouusecmake--buildinsteadofdirectlycallingtheunderlyingbuildsystem,youcanuse-vforverbosebuilds(CMake3.14+),-jNforparallelbuildsonNcores(CMake3.12+),and--target(anyversionofCMake)or-t(CMake3.15+)topickatarget.Otherwise,thesecommandsvarybetweenbuildsystems,suchasVERBOSE=1makeandninja-v.
Pickingacompiler
~/package$mkdirbuild
~/package$cdbuild
~/package/build$cmake..
~/package/build$make
~/package$cmake-S.-Bbuild
~/package$cmake--buildbuild
#Fromthebuilddirectory(pickone)
~/package/build$makeinstall
~/package/build$cmake--build.--targetinstall
~/package/build$cmake--install.#CMake3.15+only
#Fromthesourcedirectory(pickone)
~/package$cmake--buildbuild--targetinstall
~/package$cmake--installbuild#CMake3.15+only
RunningCMake
10
Selectingacompilermustbedoneonthefirstruninanemptydirectory.It'snotCMakesyntaxperse,butyoumightnotbefamiliarwithit.TopickClang:
ThatsetstheenvironmentvariablesinbashforCCandCXX,andCMakewillrespectthosevariables.Thissetsitjustforthatoneline,butthat'stheonlytimeyou'llneedthose;afterwardsCMakecontinuestousethepathsitdeducesfromthosevalues.
PickingageneratorYoucanbuildwithavarietyoftools;makeisusuallythedefault.ToseeallthetoolsCMakeknowsaboutonyoursystem,run
Andyoucanpickatoolwith-G"MyTool"(quotesonlyneededifspacesareinthetoolname).YoushouldpickatoolonyourfirstCMakecallinadirectory,justlikethecompiler.Feelfreetohaveseveralbuilddirectories,likebuild/andbuildXcode.YoucansettheenvironmentvariableCMAKE_GENERATORtocontrolthedefaultgenerator(CMake3.15+).Notethatmakefileswillonlyruninparallelifyouexpliciltypassanumberofthreads,suchasmake-j2,whileNinjawillautomaticallyruninparallel.Youcandirectlypassaparallelizationoptionsuchas-j2tothecmake--build.commandinrecentversionsofCMake.
Settingoptions
YousetoptionsinCMakewith-D.Youcanseealistofoptionswith-L,oralistwithhuman-readablehelpwith-LH.Ifyoudon'tlistthesource/builddirectory,thelistingwillnotrerunCMake(cmake-Linsteadofcmake-L.).
Verboseandpartialbuilds
Again,notreallyCMake,butifyouareusingacommandlinebuildtoollikemake,youcangetverbosebuilds:
YoucanactuallywritemakeVERBOSE=1,andmakewillalsodotherightthing,thoughthat'safeatureofmakeandnotthecommandlineingeneral.
Youcanalsobuildjustapartofabuildbyspecifyingatarget,suchasthenameofalibraryorexecutableyou'vedefinedinCMake,andmakewilljustbuildthattarget.
Options
CMakehassupportforcachedoptions.AVariableinCMakecanbemarkedas"cached",whichmeansitwillbewrittentothecache(afilecalledCMakeCache.txtinthebuilddirectory)whenitisencountered.Youcanpreset(orchange)thevalueofacachedoptiononthecommandlinewith-D.WhenCMakelooksforacachedvariable,itwillusetheexistingvalueandwillnotoverwriteit.
Standardoptions
ThesearecommonCMakeoptionstomostpackages:
-DCMAKE_BUILD_TYPE=PickfromRelease,RelWithDebInfo,Debug,orsometimesmore.-DCMAKE_INSTALL_PREFIX=Thelocationtoinstallto.SysteminstallonUNIXwouldoftenbe/usr/local(thedefault),userdirectoriesareoften~/.local,oryoucanpickafolder.
~/package/build$CC=clangCXX=clang++cmake..
~/package/build$cmake--help
~/package/build$VERBOSE=1make
RunningCMake
11
-DBUILD_SHARED_LIBS=YoucansetthisONorOFFtocontrolthedefaultforsharedlibraries(theauthorcanpickonevs.theotherexplicitlyinsteadofusingthedefault,though)-DBUILD_TESTING=Thisisacommonnameforenablingtests,notallpackagesuseit,though,sometimeswithgoodreason.
DebuggingyourCMakefiles
We'vealreadymentionedverboseoutputforthebuild,butyoucanalsoseeverboseCMakeconfigureoutputtoo.The--traceoptionwillprinteverylineofCMakethatisrun.Sincethisisveryverbose,CMake3.7added--trace-source="filename",whichwillprintouteveryexecutedlineofjustthefileyouareinterestedinwhenitruns.Ifyouselectthenameofthefileyouareinterestedindebugging(usuallybyselectingtheparentdirectorywhendebuggingaCMakeLists.txt,sinceallofthosehavethesamename),youcanjustseethelinesthatruninthatfile.Veryuseful!
RunningCMake
12
Do'sandDon'ts
CMakeAntipatterns
ThenexttwolistsareheavilybasedontheexcellentgistEffectiveModernCMake.Thatlistismuchlongerandmoredetailed,feelfreetoreaditaswell.
Donotuseglobalfunctions:Thisincludeslink_directories,include_libraries,andsimilar.Don'taddunneededPUBLICrequirements:Youshouldavoidforcingsomethingonusersthatisnotrequired(-Wall).MakethesePRIVATEinstead.Don'tGLOBfiles:MakeoranothertoolwillnotknowifyouaddfileswithoutrerunningCMake.NotethatCMake3.12addsaCONFIGURE_DEPENDSflagthatmakesthisfarbetterifyouneedtouseit.Linktobuiltfilesdirectly:Alwayslinktotargetsifavailable.NeverskipPUBLIC/PRIVATEwhenlinking:Thiscausesallfuturelinkingtobekeyword-less.
CMakePatternsTreatCMakeascode:Itiscode.Itshouldbeascleanandreadableasallothercode.Thinkintargets:Yourtargetsshouldrepresentconcepts.Makean(IMPORTED)INTERFACEtargetforanythingthatshouldstaytogetherandlinktothat.Exportyourinterface:Youshouldbeabletorunfrombuildorinstall.WriteaConfig.cmakefile:Thisiswhatalibraryauthorshoulddotosupportclients.MakeALIAStargetstokeepusageconsistent:Usingadd_subdirectoryandfind_packageshouldprovidethesametargetsandnamespaces.Combinecommonfunctionalityintoclearlydocumentedfunctionsormacros:Functionsarebetterusually.Uselowercasefunctionnames:CMakefunctionsandmacroscanbecalledloweroruppercase.Alwaysuserlowercase.Uppercaseisforvariables.Usecmake_policyand/orrangeofversions:Policieschangeforareason.OnlypiecemealsetOLDpoliciesifyouhaveto.
DosandDon'ts
13
What'snewininCMakeThisisanabbreviatedversionoftheCMakechanglogwithjustthehighlightsforauthors.Namesforeachreleasearearbitrarilypickedbytheauthor.
CMake3.0:Interfacelibraries
TherewereatonofadditionstothisversionofCMake,primarilytofilloutthetargetinterface.SomebitsofneededfunctionalityweremissedandimplementedinCMake3.1instead.
NewdocumentationINTERFACElibrariesProjectVERSIONsupportExportingbuildtreeseasilyBracketargumentsandcommentsavailable(notwidelyused)Lotsofimprovements
CMake3.1:C++11andcompilefeatures
ThisisthefirstreleaseofCMaketosupportC++11.CombinedwithfixestothenewfeaturesofCMake3.0,thisiscurrentlyacommonminimumversionofCMakeforlibrariesthatwanttosupportoldCMakebuilds.
C++11SupportCompilefeaturessupportSourcescanbeaddedlaterwithtarget_sourcesBettersupportforgeneratorexpressionsandINTERFACEtargets
CMake3.2:UTF8Thisisasmallerrelease,withmostlysmallfeaturesandfixes.Internalchanges,likebetterWindowsandUTF8support,werethefocus.
continue()insideloopsFileanddirectorylocksadded
CMake3.3:ifIN_LIST
ThisisnotablefortheusefulIN_LISToptionforif,butitalsoaddedbetterlibrarysearchusing$PATH(SeeCMake3.6),dependenciesforINTERFACElibraries,andseveralotherusefulimprovements.TheadditionofaCOMPILE_LANGUAGEgeneratorexpressionwouldproveveryusefulinthefutureasmorelanguagesareadded.Makefilesnowproducebetteroutputinparallel.
IN_LISTaddedtoif*_INCLUDE_WHAT_YOU_USEpropertyaddedCOMPILE_LANGUAGEgeneratorexpression(limitedsupportinsomegenerators)
CMake3.4:Swift&CCacheThisreleaseaddslotsofusefultools,supportfortheSwiftlanguage,andtheusualimprovements.Italsostartedsupportingcompilerlaunchers,likeCCache.
AddedSwiftlanguage
What'snewinCMake
14
AddedBASE_DIRtoget_filename_componentif(TEST...)addedstring(APPEND...)addedCMAKE_*_COMPILER_LAUNCHERaddedformakeandninjaTARGET_MESSAGESallowmakefilestoprintmessagesaftertargetiscompletedImportedtargetsarebeginningtoshowupintheofficialFind*.cmakefiles
CMake3.5:ARM
ThisreleaseexpandedCMaketomoreplatforms,andmakewarningseasiertocontrolfromthecommandline.
Multipleinputfilessupportedforseveralofthecmake-Ecommands.cmake_parse_argumentsnowbuiltinBoost,GTest,andmorenowsupportimportedtargetsARMCCnowsupported,bettersupportforiOSXCodebackslashfix
CMake3.6:Clang-Tidy
ThisreleaseaddedClang-Tidysupport,alongwithmoreutilitiesandimprovements.Italsoremovedthesearchof$PATHonUnixsystemsduetoproblems,insteadusersshoulduse$CMAKE_PREFIX_PATH.
EXCLUDE_FROM_ALLforinstalllist(FILTERaddedCMAKE_*_STANDARD_INCLUDE_DIRECTORIESandCMAKE_*_STANDARD_LIBRARIESaddedfortoolchainsTry-compileimprovements*_CLANG_TIDYpropertyaddedExternalprojectscannowbeshallowclones,andotherimprovements
CMake3.7:Android&CMakeServerYoucannowcross-compiletoAndroid.Usefulnewifstatementoptionsreallyhelpclarifycode.AndthenewservermodewassupposedtoimproveintegrationwithIDEs(butisbeingreplacedbyadifferentsysteminCMake3.14+).SupportfortheVIMeditorwasalsoimproved.
PARSE_ARGVmodeforcmake_parse_argumentsBetter32-bitsupporton64-bitmachinesLotsofusefulnewifcomparisons,likeVERSION_GREATER_EQUAL(really,whydidittakethislong?)LINK_WHAT_YOU_USEaddedLotsofcustompropertiesrelatedtofilesanddirectoriesCMakeServeraddedAdded--trace-source="filename"tomonitorcertainfilesonly
CMake3.8:C#&CUDA
ThisaddsCUDAasalanguage,aswellascxx_std_11asacompilermeta-feature.ThenewgeneratorexpressioncouldbereallyusefulifyoucanrequireCMake3.8+!
NativesupportforC#asalanguageNativesupportforCUDAasalanguageMetafeaturescxx_std_11(and14,17)addedtry_compilehasbetterlanguagesupport
What'snewinCMake
15
BUILD_RPATHpropertyaddedCOMPILE_FLAGSnowsupportsgeneratorexpression*_CPPLINTadded$<IF:cond,true-value,false-value>added(wow!)source_group(TREEadded(finallyallowingIDE'storeflecttheprojectfolderstructure!)
CMake3.9:IPO
LotsoffixestoCUDAsupportwentintothisrelease,includingPTXsupportandMSVCgenerators.InterproceduralOptimizationsarenowsupportedproperly.Evenmoremodulesprovideimportedtargets,includingMPI.
CUDAsupportedforWindowsBetterobjectlibrarysupportinseveralsituationsDESCRIPTIONaddedtoprojectseparate_argumentsgetsNATIVE_COMMANDINTERPROCEDURAL_OPTIMIZATIONenforced(andCMAKE_*initializeradded,CheckIPOSupportedadded,ClangandGCCsupport)NewGoogleTestmoduleFindDoxygendrasticallyimproved
CMake3.10:CppCheck
CMakenowisbuiltwithC++11compilers.Lotsofusefulimprovementshelpwritecleanercode.
SupportforflangFortrancompilerCompilerlauncheraddedtoCUDAIndented#cmakedefinesnowsupportedforconfigure_fileinclude_guard()addedtoensureafilegetsincludedonlyoncestring(PREPENDadded*_CPPCHECKpropertyaddedLABELSaddedtodirectoriesFindMPIvastlyexpandedFindOpenMPimprovedDynamictestdiscoveryforGoogleTest
CMake3.11:Faster&IMPORTEDINTERFACEThisreleaseissupposedtobemuchfaster.YoucanalsofinallydirectlyaddINTERFACEtargetstoIMPORTEDlibraries(theinternalFind*.cmakescriptsshouldbecomemuchcleanereventually).
FortransupportscompilerlaunchersXcodeandVisualStudiosupportCOMPILE_LANGUAGEgeneratorexpressionsfinallyYoucannowaddINTERFACEtargetsdirectlytoIMPORTEDINTERFACElibraries(Wow!)SourcefilepropertieshavebeenexpandedFetchContentmodulenowallowsdownloadstohappenatconfiguretime(Wow)
CMake3.12:VersionrangesandCONFIGURE_DEPENDS
Verypowerfulrelease,containinglotsofsmallerlong-requestedfeatures.Oneofthesmallerbutimmediatelynoticeablechangesistheadditionofversionranges;youcannowsetboththeminimumandmaximumknownCMakeversioneasily.YoucanalsosetCONFIGURE_DEPENDSonaGLOBedsetoffiles,andthebuildsystemwillcheckthosefilesandrerunifneeded!YoucanusethegeneralPackageName_ROOTforallfind_packagesearches.Lotsofadditionstostringsandlists,moduleupdates,shinynewPythonfindmodule(2and3versionstoo),andmanymore.
What'snewinCMake
16
Supportforcmake_minimum_requiredranges(backwardcompatible)Supportfor-j,--parallelin--buildmode(passedontobuildtool)SupportforSHELL:stringsincompileoptions(notdeduplicated)NewFindPythonmodulestring(JOINandlist(JOIN,andlist(TRANSFORMfile(TOUCHandfile(GLOBCONFIGURE_DEPENDSC++20supportCUDAasalanguageimprovements:CUDA7and7.5nowsupportedSupportforOpenMPonmacOS(commandlineonly)SeveralnewpropertiesandpropertyinitializersCPackfinallyreadsCMAKE_PROJECT_VERSIONvariables
CMake3.13:Linkingcontrol
YoucannowmakesymboliclinksonWindows!LotsofnewfunctionsthatfilloutthepopularrequestsforCMake,suchasadd_link_options,target_link_directories,andtarget_link_options.Youcannowdoquiteabitmoremodificationtotargetsoutsideofthesourcedirectory,forbetterfileseparation.And,target_sourcesfinallyhandlesrelativepathsproperly(policy76).
Newctest--progressoptionforliveoutputtarget_link_optionsandadd_link_optionsaddedtarget_link_directoriesaddedSymboliclinkcreation,-Ecreate_symlink,supportedonWindowsIPOsupportedonWindowsYoucanuse-Sand-Bforsourceandbuilddirectoriestarget_link_librariesandinstallworkoutsidethecurrenttargetdirectorySTATIC_LIBRARY_OPTIONSpropertyaddedtarget_sourcesisnowrelativetothecurrentsourcedirectory(CMP0076)IfyouuseXcode,younowcanexperimentallysetschemafields
CMake3.14:Fileutilities(AKACMakeπ)
Thisreleasehaslotsofsmallcleanups,includingseveralutilitiesforfiles.Generatorexpressionsworkinafewmoreplaces,andlisthandlingisbetterwithemptyvariables.Quiteafewmorefindpackagesproducetargets.ThenewVisualStudio162019generatorisabitdifferentthanolderversions.WindowsXPandVistasupporthasbeendropped.
Thecmake--buildcommandgained-v/--verbose,touseverbosebuildsifyourbuildtoolsupportsitTheFILEcommandgainedCREATE_LINK,READ_SYMLINK,andSIZEget_filename_componentgainedLAST_EXTandNAME_WLEtoaccessjustthelastextensiononafile,whichwouldget.ziponafilesuchasversion.1.2.zip(veryhandy!)YoucanseeifavariableisdefinedintheCACHEwithDEFINEDCACHE{VAR}inanifstatement.BUILD_RPATH_USE_ORIGINandCMakeversionwereaddedtoimprovehandlingofRPathinthebuilddirectory.TheCMakeservermodeisnowbeingreplacedwithafileAPI,startinginthisrelease.WillaffectIDEsinthelongrun.
CMake3.15:CLIupgradeThisreleasehasmanysmallerpolishingchanges,includeseveralofimprovementstotheCMakecommandline,suchascontroloverthedefaultgeneratorthroughenvironmentvariables(sonowit'seasytochangethedefaultgeneratortoNinja).Multipletargetsaresupportedin--buildmode,and--installmodeadded.CMakefinallysupportsmultiplelevelsoflogging.Generatorexpressionsgainedafewhandytools.ThestillverynewFindPythonmodulecontinuestoimprove,andFindBoostisnowmoreinlinewithBoost1.70'snewCONFIGmodule.export(PACKAGE)hasdrasticallychanged;itnownolongertouches$HOME/.cmakebydefault(ifCMakeMinimumversionis3.15orhigher),andrequiresanextrastepifauserwantstouseit.Thisisgenerallylesssurprising.
What'snewinCMake
17
CMAKE_GENERATORenvironmentvariableaddedtocontroldefaultgeneratorMultipletargetsupportinbuildmode,cmake.--build--targetabShortcut-tfor--targetInstallsupport,cmake.--install,doesnotinvokethebuildsystemSupportfor--loglevelandNOTICE,VERBOSE,DEBUG,andTRACEformessageThelistcommandgainedPREPEND,POP_FRONT,andPOP_BACKexecute_processgainedCOMMAND_ECHOoption(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO)allowsyoutoautomaticallyechocommandsbeforerunningthemSeveralNinjaimprovements,includeSWIFTlanguagesupportCompilerandlistimprovementstogeneratorexpressions
CMake3.16:Unitybuilds
Anewunitybuildmodewasadded,allowingsourcefilestobemergedintoasinglebuildfile.Supportforprecompiledheaders(possiblypreparingforC++20modules,perhaps?)wasadded.Lotsofothersmallerfixeswereimplemented,especiallytonewerfeatures,suchastoFindPython,FindDoxygen,andothers.
AddedsupportforObjectiveCandObjectiveC++languagesSupportforprecompilingheaders,withtarget_precompile_headersSupportfor"Unity"or"Jumbo"builds(mergingsourcefiles)withCMAKE_UNITY_BUILDCTest:Cannowskipbasedonregex,expandlistsSeveralnewfeaturestocontrolRPath.Generatorexpressionsworkinmoreplaces,likebuildandinstallpathsFindlocationscannowbeexplicitlycontrolledthroughnewvariables
CMake3.17:MoreCUDA
AFindCUDAToolkitwasfinallyadded,whichallowsfindingandusingtheCUDAtoolkitwithoutenablingtheCUDAlanguage!CUDAnowisabitmoreconfigurable,suchaslinkingtosharedlibraries.Quiteabitmorepolishintheexpectedareas,aswell,likeFindPython.Finally,youcannowiterateovermultiplelistsatatime.
CUDA_RUNTIME_LIBRARYcanfinallybesettoShared!FindCUDAToolkitfinallyaddedcmake-ErmreplacesolderremovecommandsCUDAhasmetafeatureslikecuda_std_03,etc.Youcantrackthesearchesforpackageswith--debug-findExternalProjectcannowdisablerecursivecheckoutsFindPythonbetterintegrationwithCondaDEPRECATIONcanbeappliedtotargetsCMakegainedarmcommandSeveralnewenvironmentvariablesforeachcannowdoZIP_LISTS(multiplelistsatatime)
What'snewinCMake
18
Introductiontothebasics
MinimumVersion
Here'sthefirstlineofeveryCMakeLists.txt,whichistherequirednameofthefileCMakelooksfor:
cmake_minimum_required(VERSION3.1)
Let'smentionabitofCMakesyntax.Thecommandnamecmake_minimum_requirediscaseinsensitive,sothecommonpracticeistouselowercase. TheVERSIONisaspecialkeywordforthisfunction.Andthevalueoftheversionfollowsthekeyword.Likeeverywhereinthisbook,justclickonthecommandnametoseetheofficialdocumentation,andusethedropdowntoswitchdocumentationbetweenCMakeversions.
Thislineisspecial! TheversionofCMakewillalsodictatethepolicies,whichdefinebehaviorchanges.So,ifyousetminimum_requiredtoVERSION2.8,you'llgetthewronglinkingbehavioronmacOS,forexample,eveninthenewestCMakeversions.Ifyousetitto3.3orless,you'llgetthewronghiddensymbolsbehaviour,etc.Alistofpoliciesandversionsisavailableatpolicies.
InCMake3.12,thiswillsupportarange,suchasVERSION3.1...3.12;thismeansyousupportaslowas3.1buthavealsotesteditwiththenewpolicysettingsupto3.12.Thisismuchniceronusersthatneedthebettersettings,andduetoatrickinthesyntax,it'sbackwardcompatiblewitholderversionsofCMake(thoughactuallyrunningCMake3.2-3.11willonlysetthe3.1versionofthepoliciesinthisexample).NewversionsofpoliciestendtobemostimportantformacOSandWindowsusers,whoalsousuallyhaveaveryrecentversionofCMake.
Thisiswhatnewprojectsshoulddo:
cmake_minimum_required(VERSION3.1...3.15)
if(${CMAKE_VERSION}VERSION_LESS3.12)
cmake_policy(VERSION${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()
IfCMakeversionislessthan3.12,theifblockwillbetrue,andthepolicywillbesettothecurrentCMakeversion.IfCMakeis3.12orhigher,theifblockwillbefalse,butthenewsyntaxincmake_minimum_requiredwillberespectedandthiswillcontinuetoworkproperly!
WARNING:MSVC'sCMakeservermodeoriginallyhadabuginreadingthisformat,soifyouneedtosupportnon-commandlineWindowsbuildsforolderMSVCversions,youwillwanttodothisinstead:
cmake_minimum_required(VERSION3.1)
if(${CMAKE_VERSION}VERSION_LESS3.15)
cmake_policy(VERSION${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION3.15)
endif()
Ifyoureallyneedtosettoalowvaluehere,youcanusecmake_policytoconditionallyincreasethepolicylevelorsetaspecificpolicy.PleaseatleastdothisforyourmacOSusers!
Settingaproject
Now,everytop-levelCMakefilewillhavethenextline:
1
2
IntroductiontotheBasics
19
project(MyProjectVERSION1.0
DESCRIPTION"Veryniceproject"
LANGUAGESCXX)
Nowweseeevenmoresyntax.Stringsarequoted,whitespacedoesn'tmatter,andthenameoftheprojectisthefirstargument(positional).Allthekeywordargumentshereareoptional.Theversionsetsabunchofvariables,likeMyProject_VERSIONandPROJECT_VERSION.ThelanguagesareC,CXX,Fortran,ASM,CUDA(CMake3.8+),CSharp(3.8+),andSWIFT(CMake3.15+experimental).CCXXisthedefault.InCMake3.9,DESCRIPTIONwasaddedtosetaprojectdescription,aswell.Thedocumentationforprojectmaybehelpful.
Youcanaddcommentswiththe#character.CMakedoeshaveaninlinesyntaxforcommentstoo,butitisrarelyneeded,aswhitespacedoesn'tmatter.
There'sreallynothingspecialabouttheprojectname.Notargetsareaddedatthispoint.
Makinganexecutable
Althoughlibrariesaremuchmoreinteresting,andwe'llspendmostofourtimewiththem,let'sstartwithasimpleexecutable.
add_executable(onetwo.cppthree.h)
Thereareseveralthingstounpackhere.oneisboththenameoftheexecutablefilegenerated,andthenameoftheCMaketargetcreated(you'llhearalotmoreabouttargetssoon,Ipromise).Thesourcefilelistcomesnext,andyoucanlistasmanyasyou'dlike.CMakeissmart,andwillonlycompilesourcefileextensions.Theheaderswillbe,formostintentsandpurposes,ignored;theonlyreasontolistthemistogetthemtoshowupinIDEs.TargetsshowupasfoldersinmanyIDEs.Moreaboutthegeneralbuildsystemandtargetsisavailableatbuildsystem.
MakingalibraryMakingalibraryisdonewithadd_library,andisjustaboutassimple:
add_library(oneSTATICtwo.cppthree.h)
Yougettopickatypeoflibrary,STATIC,SHARED,orMODULE.Ifyouleavethischoiceoff,thevalueofBUILD_SHARED_LIBSwillbeusedtopickbetweenSTATICandSHARED.
Asyou'llseeinthefollowingsections,oftenyou'llneedtomakeafictionaltarget,thatis,onewherenothingneedstobecompiled,forexample,foraheader-onlylibrary.ThatiscalledanINTERFACElibrary,andisanotherchoice;theonlydifferenceisitcannotbefollowedbyfilenames.
YoucanalsomakeanALIASlibrarywithanexistinglibrary,whichsimplygivesyouanewnameforatarget.Theonebenefittothisisthatyoucanmakelibrarieswith::inthename(whichyou'llseelater).
Targetsareyourfriend
Nowwe'vespecifiedatarget,howdoweaddinformationaboutit?Forexample,maybeitneedsanincludedirectory:
target_include_directories(onePUBLICinclude)
3
IntroductiontotheBasics
20
target_include_directoriesaddsanincludedirectorytoatarget.PUBLICdoesn'tmeanmuchforanexecutable;foralibraryitletsCMakeknowthatanytargetsthatlinktothistargetmustalsoneedthatincludedirectory.OtheroptionsarePRIVATE(onlyaffectthecurrenttarget,notdependencies),andINTERFACE(onlyneededfordependencies).
Wecanthenchaintargets:
add_library(anotherSTATICanother.cppanother.h)
target_link_libraries(anotherPUBLICone)
target_link_librariesisprobablythemostusefulandconfusingcommandinCMake.Ittakesatarget(another)andaddsadependencyifatargetisgiven.Ifnotargetofthatname(one)exists,thenitaddsalinktoalibrarycalledoneonyourpath(hencethenameofthecommand).Oryoucangiveitafullpathtoalibrary.Oralinkerflag.Justtoaddafinalbitofconfusion,classicCMakeallowedyoutoskipthekeywordselectionofPUBLIC,etc.Ifthiswasdoneonatarget,you'llgetanerrorifyoutrytomixstylesfurtherdownthechain.
Focusonusingtargetseverywhere,andkeywordseverywhere,andyou'llbefine.
Targetscanhaveincludedirectories,linkedlibraries(orlinkedtargets),compileoptions,compiledefinitions,compilefeatures(seetheC++11chapter),andmore.Asyou'llseeinthetwoincludingprojectschapters,youcanoftengettargets(andalwaysmaketargets)torepresentallthelibrariesyouuse.Eventhingsthatarenottruelibraries,likeOpenMP,canberepresentedwithtargets.ThisiswhyModernCMakeisgreat!
DiveinSeeifyoucanfollowthefollowingfile.ItmakesasimpleC++11libraryandaprogramusingit.Nodependencies.I'lldiscussmoreC++standardoptionslater,usingtheCMake3.8systemfornow.
cmake_minimum_required(VERSION3.8)
project(CalculatorLANGUAGESCXX)
add_library(calclibSTATICsrc/calclib.cppinclude/calc/lib.hpp)
target_include_directories(calclibPUBLICinclude)
target_compile_features(calclibPUBLICcxx_std_11)
add_executable(calcapps/calc.cpp)
target_link_libraries(calcPUBLICcalclib)
.Inthisbook,I'llmostlyavoidshowingyouthewrongwaytodothings;youcanfindplentyofexamplesofthatonline.I'llmentionalternativesoccasionally,butthesearenotrecommendedunlesstheyareabsolutelynecessary;oftentheyarejusttheretohelpyoureadolderCMakecode.↩
.YouwillsometimesseeFATAL_ERRORhere,thatwasneededtosupportnicefailureswhenrunningthisinCMake<2.6,whichshouldnotbeaproblemanymore.↩
.The::syntaxwasoriginallyintendedforINTERFACEIMPORTEDlibraries,whichwereexplicitlysupposedtobelibrariesdefinedoutsidethecurrentproject.But,becauseofthis,mostofthetarget_*commandsdon'tworkonIMPORTEDlibraries,makingthemhardtosetupyourself.Sodon'tusetheIMPORTEDkeywordfornow,anduseanALIAStargetinstead;itwillbefineuntilyoustartexportingtargets.ThislimitationwasfixedinCMake3.11.↩
1
2
3
IntroductiontotheBasics
21
VariablesandtheCache
LocalVariables
Wewillcovervariablesfirst.Alocalvariableissetlikethis:
set(MY_VARIABLE"value")
Thenamesofvariablesareusuallyallcaps,andthevaluefollows.Youaccessavariablebyusing${},suchas${MY_VARIABLE}.CMakehastheconceptofscope;youcanaccessthevalueofthevariableafteryousetitaslongasyouareinthesamescope.Ifyouleaveafunctionorafileinasubdirectory,thevariablewillnolongerbedefined.YoucansetavariableinthescopeimmediatelyaboveyourcurrentonewithPARENT_SCOPEattheend.
Listsaresimplyaseriesofvalueswhenyousetthem:
set(MY_LIST"one""two")
whichinternallybecome;separatedvalues.Sothisisanidenticalstatement:
set(MY_LIST"one;two")
Thelist(commandhasutilitiesforworkingwithlists,andseparate_argumentswillturnaspaceseparatedstringintoalist(inplace).NotethatanunquotedvalueinCMakeisthesameasaquotedoneiftherearenospacesinit;thisallowsyoutoskipthequotesmostofthetimewhenworkingwithvaluethatyouknowcouldnotcontainspaces.
Whenavariableisexpandedusing${}syntax,allthesamerulesaboutspacesapply.Beespeciallycarefulwithpaths;pathsmaycontainaspaceatanytimeandshouldalwaysbequotedwhentheyareavariable(neverwrite${MY_PATH},alwaysshouldbe"${MY_PATH}").
CacheVariables
Ifyouwanttosetavariablefromthecommandline,CMakeoffersavariablecache.Somevariablesarealreadyhere,likeCMAKE_BUILD_TYPE.Thesyntaxfordeclaringavariableandsettingitifitisnotalreadysetis:
set(MY_CACHE_VARIABLE"VALUE"CACHESTRING"Description")
Thiswillnotreplaceanexistingvalue.ThisissothatyoucansettheseonthecommandlineandnothavethemoverriddenwhentheCMakefileexecutes.Ifyouwanttousethesevariablesasamake-shiftglobalvariable,thenyoucando:
set(MY_CACHE_VARIABLE"VALUE"CACHESTRING""FORCE)
mark_as_advanced(MY_CACHE_VARIABLE)
Thefirstlinewillcausethevaluetobesetnomatterwhat,andthesecondlinewillkeepthevariablefromshowingupinthelistofvariablesifyouruncmake-L..oruseaGUI.Thisissocommon,youcanalsousetheINTERNALtypetodothesamething(thoughtechnicallyitforcestheSTRINGtype,thiswon'taffectanyCMakecodethatdependsonthevariable):
set(MY_CACHE_VARIABLE"VALUE"CACHEINTERNAL"")
SinceBOOLissuchacommonvariabletype,youcansetitmoresuccinctlywiththeshortcut:
option(MY_OPTION"Thisissettablefromthecommandline"OFF)
1
VariablesandtheCache
22
FortheBOOLdatatype,thereareseveraldifferentwordingsforONandOFF.
Seecmake-variablesforalistingofknownvariablesinCMake.
Environmentvariables
Youcanalsoset(ENV{variable_name}value)andget$ENV{variable_name}environmentvariables,thoughitisgenerallyaverygoodideatoavoidthem.
TheCache
Thecacheisactuallyjustatextfile,CMakeCache.txt,thatgetscreatedinthebuilddirectorywhenyourunCMake.ThisishowCMakeremembersanythingyouset,soyoudon'thavetore-listyouroptionseverytimeyourerunCMake.
Properties
TheotherwayCMakestoresinformationisinproperties.Thisislikeavariable,butitisattachedtosomeotheritem,likeadirectoryoratarget.Aglobalpropertycanbeausefuluncachedglobalvariable.ManytargetpropertiesareinitializedfromamatchingvariablewithCMAKE_atthefront.SosettingCMAKE_CXX_STANDARD,forexample,willmeanthatallnewtargetscreatedwillhaveCXX_STANDARDsettothatwhentheyarecreated.Therearetwowaystosetproperties:
set_property(TARGETTargetName
PROPERTYCXX_STANDARD11)
set_target_properties(TargetNamePROPERTIES
CXX_STANDARD11)
Thefirstformismoregeneral,andcansetmultipletargets/files/testsatonce,andhasusefuloptions.Thesecondisashortcutforsettingseveralpropertiesononetarget.Andyoucangetpropertiessimilarly:
get_property(ResultVariableTARGETTargetNamePROPERTYCXX_STANDARD)
Seecmake-propertiesforalistingofallknownproperties.Youcanalsomakeyourowninsomecases.
.ifstatementsareabitoddinthattheycantakethevariablewithorwithoutthesurroundingsyntax;thisisthereforhistoricalreasons:ifpredatesthe${}syntax.↩
.Interfacetargets,forexample,mayhavelimitsoncustompropertiesthatareallowed.↩
2
1
2
VariablesandtheCache
23
ProgramminginCMake
Controlflow
CMakehasanifstatement,thoughovertheyearsithasbecomerathercomplex.Thereareaseriesofallcapskeywordsyoucanuseinsideanifstatement,andyoucanoftenrefertovariablesbyeitherdirectlybynameorusingthe${}syntax(theifstatementhistoricallypredatesvariableexpansion).Anexampleifstatement:
if(variable)
#Ifvariableis`ON`,`YES`,`TRUE`,`Y`,ornonzeronumber
else()
#Ifvariableis`0`,`OFF`,`NO`,`FALSE`,`N`,`IGNORE`,`NOTFOUND`,`""`,orendsin`-NOTFOUND`
endif()
#Ifvariabledoesnotexpandtooneoftheabove,CMakewillexpanditthentryagain
Sincethiscanbealittleconfusingifyouexplicitlyputavariableexpansion,like${variable},duetothepotentialexpansionofanexpansion,apolicy(CMP0054)wasaddedinCMake3.1+thatkeepsaquotedexpansionfrombeingexpandedyetagain.So,aslongastheminimumversionofCMakeis3.1+,youcando:
if("${variable}")
#Trueifvariableisnotfalse-like
else()
#Notethatundefinedvariableswouldbe`""`thusfalse
endif()
Thereareavarietyofkeywordsaswell,suchas:
Unary:NOT,TARGET,EXISTS(file),DEFINED,etc.Binary:STREQUAL,AND,OR,MATCHES(regularexpression),VERSION_LESS,VERSION_LESS_EQUAL(CMake3.7+),etc.Parenthesescanbeusedtogroup
generator-expressions
generator-expressionsarereallypowerful,butabitoddandspecialized.MostCMakecommandshappenatconfiguretime,includetheifstatementsseenabove.Butwhatifyouneedlogictooccuratbuildtimeoreveninstalltime?Generatorexpressionswereaddedforthispurpose. Theyareevaluatedintargetproperties.
Thesimplestgeneratorexpressionsareinformationalexpressions,andareoftheform$<KEYWORD>;theyevaluatetoapieceofinformationrelevantforthecurrentconfiguration.Theotherformis$<KEYWORD:value>,whereKEYWORDisakeywordthatcontrolstheevaluation,andvalueistheitemtoevaluate(aninformationalexpressionkeywordisallowedhere,too).IfKEYWORDisageneratorexpressionorvariablethatevaluatesto0or1,valueissubstitutedif1andnotif0.Youcannestgeneratorexpressions,andyoucanusevariablestomakereadingnestedvariablesbearable.Someexpressionsallowmultiplevalues,separatedbycommas.
IfyouwanttoputacompileflagonlyfortheDEBUGconfiguration,forexample,youcoulddo:
target_compile_options(MyTargetPRIVATE"$<$<CONFIG:Debug>:--my-flag>")
Thisisanewer,betterwaytoaddthingsthanusingspecialized*_DEBUGvariables,andgeneralizedtoallthethingsgeneratorexpressionssupport.Notethatyoushouldnever,neverusetheconfiguretimevalueforthecurrentconfiguration,becausemulti-configurationgeneratorslikeIDEsdonothavea"current"configurationatconfiguretime,onlyatbuildtimethroughgeneratorexpressionsandcustom*_<CONFIG>variables.
Othercommonusesforgeneratorexpressions:
1
2
ProgramminginCMake
24
Limitinganitemtoacertainlanguageonly,suchasCXX,toavoiditmixingwithsomethinglikeCUDA,orwrappingitsothatitisdifferentdependingontargetlanguage.Accessingconfigurationdependentproperties,liketargetfilelocation.Givingadifferentlocationforbuildandinstalldirectories.
Thatlastoneisverycommon.You'llseesomethinglikethisinalmosteverypackagethatsupportsinstalling:
target_include_directories(
MyTarget
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
MacrosandFunctions
YoucandefineyourownCMakefunctionormacroeasily.Theonlydifferencebetweenafunctionandamacroisscope;macrosdon'thaveone.So,ifyousetavariableinafunctionandwantittobevisibleoutside,you'llneedPARENT_SCOPE.Nestingfunctionsthereforeisabittricky,sinceyou'llhavetoexplicitlysetthevariablesyouwantvisibletotheoutsideworldtoPARENT_SCOPEineachfunction.But,functionsdon't"leak"alltheirvariableslikemacrosdo.Forthefollowingexamples,I'llusefunctions.
Anexampleofasimplefunctionisasfollows:
function(SIMPLEREQUIRED_ARG)
message(STATUS"Simplearguments:${REQUIRED_ARG},followedby${ARGV}")
set(${REQUIRED_ARG}"FromSIMPLE"PARENT_SCOPE)
endfunction()
simple(This)
message("Output:${This}")
Ifyouwantpositionalarguments,theyarelistedexplicitly,andallotherargumentsarecollectedinARGN(ARGVholdsallarguments,eventheonesyoulist).YouhavetoworkaroundthefactthatCMakedoesnothavereturnvaluesbysettingvariables.Intheexampleabove,youcanexplicitlygiveavariablenametoset.
ArgumentsCMakehasanamedvariablesystemthatyou'vealreadyseeninmostofthebuildinCMakefunctions.Youcanuseitwiththecmake_parse_argumentsfunction.IfyouwanttosupportaversionofCMakelessthan3.5,you'llwanttoalsoincludetheCMakeParseArgumentsmodule,whichiswhereitusedtolivebeforebecomingabuiltincommand.Hereisanexampleofhowtouseit:
function(COMPLEX)
cmake_parse_arguments(
COMPLEX_PREFIX
"SINGLE;ANOTHER"
"ONE_VALUE;ALSO_ONE_VALUE"
"MULTI_VALUES"
${ARGN}
)
endfunction()
complex(SINGLEONE_VALUEvalueMULTI_VALUESsomeothervalues)
Insidethefunctionafterthiscall,you'llfind:
COMPLEX_PREFIX_SINGLE=TRUE
COMPLEX_PREFIX_ANOTHER=FALSE
COMPLEX_PREFIX_ONE_VALUE="value"
COMPLEX_PREFIX_ALSO_ONE_VALUE=<UNDEFINED>
ProgramminginCMake
25
COMPLEX_PREFIX_MULTI_VALUES="some;other;values"
Ifyoulookattheofficialpage,you'llseeaslightlydifferentmethodusingsettoavoidexplicitlywritingthesemicolonsinthelist;feelfreetousethestructureyoulikebest.Youcanmixitwiththepositionalargumentslistedabove;anyremainingarguments(thereforeoptionalpositionalarguments)areinCOMPLEX_PREFIX_UNPARSED_ARGUMENTS.
.Theyactasiftheyareevaluatedatbuild/installtime,thoughactuallytheyareevaluatedforeachbuildconfiguration.↩
.TheCMakedocssplitsexpressionsintoInformational,Logical,andOutput.↩
1
2
ProgramminginCMake
26
Communicationwithyourcode
ConfigureFile
CMakeallowsyoutoaccessCMakevariablesfromyourcodeusingconfigure_file.Thiscommandcopiesafile(traditionallyendingin.infromoneplacetoanother,substitutingallCMakevariablesitfinds.Ifyouwanttoavoidreplacingexisting${}syntaxinyourinputfile,[email protected]'salsoaCOPY_ONLYkeywordifyouarejustusingthisasareplacementforfile(COPY.
Thisfunctionalityisusedquitefrequently;forexample,onVersion.h.in:
Version.h.in
#pragmaonce
#defineMY_VERSION_MAJOR@PROJECT_VERSION_MAJOR@
#defineMY_VERSION_MINOR@PROJECT_VERSION_MINOR@
#defineMY_VERSION_PATCH@PROJECT_VERSION_PATCH@
#defineMY_VERSION_TWEAK@PROJECT_VERSION_TWEAK@
#defineMY_VERSION"@PROJECT_VERSION@"
CMakelines:
configure_file(
"${PROJECT_SOURCE_DIR}/include/My/Version.h.in"
"${PROJECT_BINARY_DIR}/include/My/Version.h"
)
Youshouldincludethebinaryincludedirectoryaswellwhenbuildingyourproject.Ifyouwanttoputanytrue/falsevariablesinaheader,CMakehasCspecific#cmakedefineand#cmakedefine01replacementstomakeappropriatedefinelines.
Youcanalso(andoftendo)usethistoproduce.cmakefiles,suchastheconfigurefiles(seeinstalling).
Readingfiles
Theotherdirectioncanbedonetoo;youcanreadinsomething(likeaversion)fromyoursourcefiles.Ifyouhaveaheaderonlylibrarythatyou'dliketomakeavailablewithorwithoutCMake,forexample,thenthiswouldbethebestwaytohandleaversion.Thiswouldlooksomethinglikethis:
#Assumingthecanonicalversionislistedinasingleline
#ThiswouldbeinseveralpartsifpickingupfromMAJOR,MINOR,etc.
set(VERSION_REGEX"#defineMY_VERSION[\t]+\"(.+)\"")
#Readinthelinecontainingtheversion
file(STRINGS"${CMAKE_CURRENT_SOURCE_DIR}/include/My/Version.hpp"
VERSION_STRINGREGEX${VERSION_REGEX})
#Pickoutjusttheversion
string(REGEXREPLACE${VERSION_REGEX}"\\1"VERSION_STRING"${VERSION_STRING}")
#AutomaticallygettingPROJECT_VERSION_MAJOR,My_VERSION_MAJOR,etc.
project(MyLANGUAGESCXXVERSION${VERSION_STRING})
Above,file(STRINGSfile_namevariable_nameREGEXregex)pickslinesthatmatcharegex;andthesameregexisusedtothenpickouttheparenthesescapturegroupwiththeversionpart.Replaceisusedwithbacksubstitutiontooutputonlythatonegroup.
Communicatingwithyourcode
27
Communicatingwithyourcode
28
HowtostructureyourprojectThefollowinginformationisbiased.Butinagoodway,Ithink.I'mgoingtotellyouhowtostructurethedirectoriesinyourproject.Thisisbasedonconvention,butwillhelpyou:
Easilyreadotherprojectsfollowingthesamepatters,Avoidapatternthatcausesconflicts,Keepfrommuddlingandcomplicatingyourbuild.
First,thisiswhatyourfilesshouldlooklikewhenyoustartifyourprojectiscreativelycalledproject,withalibrarycalledlib,andaexecutablecalledapp:
-project
-.gitignore
-README.md
-LICENCE.md
-CMakeLists.txt
-cmake
-FindSomeLib.cmake
-something_else.cmake
-include
-project
-lib.hpp
-src
-CMakeLists.txt
-lib.cpp
-apps
-CMakeLists.txt
-app.cpp
-tests
-CMakeLists.txt
-testlib.cpp
-docs
-CMakeLists.txt
-extern
-googletest
-scripts
-helper.py
Thenamesarenotabsolute;you'llseecontentionabouttest/vs.tests/,andtheapplicationfoldermaybecalledsomethingelse(ornotexistforalibrary-onlyproject).You'llalsosometimeseeapythonfolderforpythonbindings,oracmakefolderforhelperCMakefiles,likeFind<library>.cmakefiles.Butthebasicsarethere.
Noticeafewthingsalreadyapparent;theCMakeLists.txtfilesaresplitupoverallsourcedirectories,andarenotintheincludedirectories.Thisisbecauseyoushouldbeabletocopythecontentsoftheincludedirectoryto/usr/includeorsimilardirectly(exceptforconfigurationheaders,whichIgooverinanotherchapter),andnothaveanyextrafilesorcauseanyconflicts.That'salsowhythereisadirectoryforyourprojectinsidetheincludedirectory.Useadd_subdirectorytoaddasubdirectorycontainingaCMakeLists.txt.
Youoftenwantacmakefolder,withallofyourhelpermodules.ThisiswhereyourFind*.cmakefilesgo.Ansetofsomecommonhelpersisatgithub.com/CLIUtils/cmake.ToaddthisfoldertoyourCMakepath:
set(CMAKE_MODULE_PATH"${PROJECT_SOURCE_DIR}/cmake"${CMAKE_MODULE_PATH})
Yourexternfoldershouldcontaingitsubmodulesalmostexclusively.Thatway,youcancontroltheversionofthedependenciesexplicitly,butstillupgradeeasily.SeetheTestingchapterforanexampleofaddingasubmodule.
Youshouldhavesomethinglike/build*inyour.gitignore,sothatuserscanmakebuilddirectoriesinthesourcedirectoryandusethosetobuild.Afewpackagesprohibitthis,butit'smuchbetterthandoingatrueout-of-sourcebuildandhavingtotypesomethingdifferentforeachpackageyoubuild.
HowtoStructureYourProject
29
Ifyouwanttoavoidthebuilddirectorybeinginavalidsourcedirectory,addthisnearthetopofyourCMakeLists:
###Requireout-of-sourcebuilds
file(TO_CMAKE_PATH"${PROJECT_BINARY_DIR}/CMakeLists.txt"LOC_PATH)
if(EXISTS"${LOC_PATH}")
message(FATAL_ERROR"Youcannotbuildinasourcedirectory(oranydirectorywithaCMakeLists.txtfile).Pleasemakeab
uildsubdirectory.FeelfreetoremoveCMakeCache.txtandCMakeFiles.")
endif()
Seetheextendedcodeexamplehere.
HowtoStructureYourProject
30
Runningotherprograms
Runningacommandatconfiguretime
Runningacommandatconfiguretimeisrelativelyeasy.Useexecute_processtorunaprocessandaccesstheresults.ItisgenerallyagoodideatoavoidhardcodingaprogrampathintoyourCMake;youcanuse${CMAKE_COMMAND},find_package(Git),orfind_programtogetaccesstoacommandtorun.UseRESULT_VARIABLEtocheckthereturncodeandOUTPUT_VARIABLEtogettheoutput.
Hereisanexamplethatupdatesallgitsubmodules:
find_package(GitQUIET)
if(GIT_FOUNDANDEXISTS"${PROJECT_SOURCE_DIR}/.git")
execute_process(COMMAND${GIT_EXECUTABLE}submoduleupdate--init--recursive
WORKING_DIRECTORY${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLEGIT_SUBMOD_RESULT)
if(NOTGIT_SUBMOD_RESULTEQUAL"0")
message(FATAL_ERROR"gitsubmoduleupdate--initfailedwith${GIT_SUBMOD_RESULT},pleasecheckoutsubmodules")
endif()
endif()
RunningacommandatbuildtimeBuildtimecommandsareabittricker.Themaincomplicationcomesfromthetargetsystem;whendoyouwantyourcommandtorun?Doesitproduceanoutputthatanothertargetneeds?Withthisinmind,hereisanexamplethatcallsaPythonscripttogenerateaheaderfile:
find_package(PythonInterpREQUIRED)
add_custom_command(OUTPUT"${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp"
COMMAND"${PYTHON_EXECUTABLE}""${CMAKE_CURRENT_SOURCE_DIR}/scripts/GenerateHeader.py"--argument
DEPENDSsome_target)
add_custom_target(generate_headerALL
DEPENDS"${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp")
install(FILES${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hppDESTINATIONinclude)
Here,thegenerationhappensaftersome_targetiscomplete,andhappenswhenyourunmakewithoutatarget(ALL).Ifyoumakethisadependencyofanothertargetwithadd_dependencies,youcouldavoidtheALLkeyword.Or,youcouldrequirethatauserexplicitlybuildsthegenerate_headertargetwhenmaking.
Includedcommonutilities
AusefultoolinwritingCMakebuildsthatworkcross-platformiscmake-E<mode>(seeninCMakefilesas${CMAKE_COMMAND}-E).ThismodeallowsCMaketodoavarietyofthingswithoutcallingsystemtoolsexplicitly,likecopy,make_directory,andremove.Itismostlyusedforthebuildtimecommands.Notethattheveryusefulcreate_symlinkmodeusedtobeUnixonly,butwasaddedforWindowsinCMake3.13.Seethedocs.
RunningOtherPrograms
31
AsimpleexampleThisisasimpleyetcompleteexampleofaproperCMakeLists.Forthisprogram,wehaveonelibrary(MyLibExample)withaheaderfileandasourcefile,andoneapplication,MyExample,withonesourcefile.
#AlmostallCMakefilesshouldstartwiththis
#Youshouldalwaysspecifyarangewiththenewest
#andoldesttestedversionsofCMake.Thiswillensure
#youpickupthebestpolicies.
cmake_minimum_required(VERSION3.1...3.16)
#Thisisyourprojectstatement.Youshouldalwayslistlanguages;
#Listingtheversionisniceheresinceitsetslotsofusefulvariables
project(ModernCMakeExampleVERSION1.0LANGUAGESCXX)
#IfyousetanyCMAKE_variables,thatcangohere.
#(Butusuallydon'tdothis,exceptmaybeforC++standard)
#Findpackagesgohere.
#Youshouldusuallysplitthisintofolders,butthisisasimpleexample
#Thisisa"default"library,andwillmatchthe***variablesetting.
#OthercommonchoicesareSTATIC,SHARED,andMODULE
#IncludingheaderfilesherehelpsIDEsbutisnotrequired.
#Outputlibnamematchestargetname,withtheusualextensionsonyoursystem
add_library(MyLibExamplesimple_lib.cppsimple_lib.hpp)
#Linkeachtargetwithothertargetsoraddoptions,etc.
#Addingsomethingwecanrun-Outputnamematchestargetname
add_executable(MyExamplesimple_example.cpp)
#Makesureyoulinkyourtargetswiththiscommand.Itcanalsolinklibrariesand
#evenflags,solinkingatargetthatdoesnotexistwillnotgiveaconfigure-timeerror.
target_link_libraries(MyExamplePRIVATEMyLibExample)
Thecompleteexampleisavailableinexamplesfolder.
Alarger,multi-fileexampleisalsoavailable.
ASimpleExample
32
AddingfeaturesThissectioncoversaddingcommonfeaturestoyourCMakeproject.You'lllearnhowtoaddavarietyofoptionscommonlyneededinC++projects,likeC++11support,aswellashowtosupportIDEsandmore.
Defaultbuildtype
CMakenormallydoesa"non-release,nondebug"emptybuildtype;ifyouprefertosetthedefaultbuildtypeyourself,youcanfollowthisrecipeforthedefaultbuildtypemodifiedfromtheKitwareblog:
set(default_build_type"Release")
if(NOTCMAKE_BUILD_TYPEANDNOTCMAKE_CONFIGURATION_TYPES)
message(STATUS"Settingbuildtypeto'${default_build_type}'asnonewasspecified.")
set(CMAKE_BUILD_TYPE"${default_build_type}"CACHE
STRING"Choosethetypeofbuild."FORCE)
#Setthepossiblevaluesofbuildtypeforcmake-gui
set_property(CACHECMAKE_BUILD_TYPEPROPERTYSTRINGS
"Debug""Release""MinSizeRel""RelWithDebInfo")
endif()
AddingFeatures
33
C++11andbeyondC++11issupportedbyCMake.Really.JustnotinCMake2.8,because,guesswhat,C++11didn'texistin2009when2.0wasreleased.AslongasyouareusingCMake3.1ornewer,youshouldbefine,therearetwodifferentwaystoenablesupport.Andasyou'llsoonsee,there'sevenbettersupportinCMake3.8+.I'llstartwiththat,sincethisisModernCMake.
CMake3.8+:Metacompilerfeatures
AslongasyoucanrequirethatauserinstallCMake,you'llhaveaccesstothenewestwaytoenableC++standards.Thisisthemostpowerfulway,withthenicestsyntaxandthebestsupportfornewstandards,andthebesttargetbehaviorformixingstandardsandoptions.AssumingyouhaveatargetnamedmyTarget,itlookslikethis:
target_compile_features(myTargetPUBLICcxx_std_11)
set_target_properties(myTargetPROPERTIESCXX_EXTENSIONSOFF)
Forthefirstline,wegettopickbetweencxx_std_11,cxx_std_14,andcxx_std_17.Thesecondlineisoptional,butwillavoidextensionsbeingadded;withoutityou'dgetthingslike-std=g++11replacing-std=c++11.ThefirstlineevenworksonINTERFACEtargets;onlyactualcompiledtargetscanusethesecondline.
IfatargetfurtherdownthedependencychainspecifiesahigherC++level,thisinteractsnicely.It'sreallyjustamoreadvancedversionofthefollowingmethod,soitinteractsnicelywiththat,too.
CMake3.1+:Compilerfeatures
Youcanaskforspecificcompilerfeaturestobeavailable.ThiswasmoregranularthanaskingforaC++version,thoughit'sabittrickytopickoutjustthefeaturesapackageisusingunlessyouwrotethepackageandhaveagoodmemory.SincethisultimatelychecksagainstalistofoptionsCMakeknowsyourcompilersupportsandpicksthehighestflagindicatedinthatlist,youdon'thavetospecifyalltheoptionsyouneed,justtherarestones.Thesyntaxisidenticaltothesectionabove,youjusthavealistofoptionstopickinsteadofcxx_std_*options.Seethewholelisthere.
Ifyouhaveoptionalfeatures,youcanusethelistCMAKE_CXX_COMPILE_FEATURESanduseif(...IN_LIST...)fromCMake3.3+toseeifthatfeatureissupported,andadditconditionally.Seethedocshereforotherusecases.
Arelatedfeature,WriteCompilerDetectionHeader,isworthcheckingout.Itisamodulethatletsyoumakeafilewithmacrosallowingyoutocheckandsupportoptionalfeaturesforspecificcompilers.Likeanyheadergenerator,thiswillrequirethatyoubuildwithCMakesothatyourheadercanbegeneratedaspartofthebuildprocess(onlyimportantifyoucareaboutsupportingmultiplebuildsystems,orifyouaremakingano-buildheader-onlylibrary).
CMake3.1+:Globalandpropertysettings
ThereisanotherwaythatC++standardsaresupported;aspecificsetofthreeproperties(bothglobalandtargetlevel).Theglobalpropertiesare:
set(CMAKE_CXX_STANDARD11)
set(CMAKE_CXX_STANDARD_REQUIREDON)
set(CMAKE_CXX_EXTENSIONSOFF)
ThefirstlinesetsaC++standardlevel,andthesecondtellsCMaketouseit,andthefinallineisoptionalandensures-std=c++11vs.somethinglike-std=g++11.Thismethodisn'tbadforafinalpackage,butshouldn'tbeusedbyalibrary.Youcanalsosetthesevaluesonatarget:
C++11andBeyond
34
set_target_properties(myTargetPROPERTIES
CXX_STANDARD11
CXX_STANDARD_REQUIREDYES
CXX_EXTENSIONSNO
)
Whichisbetter,butstilldoesn'thavethesortofexplicitcontrolthatcompilerfeatureshaveforpopulatingPRIVATEandINTERFACEproperties.
YoucanfindmoreinformationaboutthefinaltwomethodsonCraigScott'susefulblogpost.
Don'tsetmanualflagsyourself.You'llthenbecomeresponsibleformaintingcorrectflagsforeveryreleaseofeverycompiler,errormessagesforunsupportedcompilerswon'tbeuseful,andsomeIDEsmightnotpickupthemanualflags.
C++11andBeyond
35
AddingFeaturesTherearelotsofcompilerandlinkersettings.Whenyouneedtoaddsomethingspecial,youcouldcheckfirsttoseeifCMakesupportsit;ifitdoes,youcanavoidexplicitlytyingyourselftoacompilerversion.And,betteryet,youexplainwhatyoumeaninyourCMakeLists,ratherthanspewingflags.
ThefirstandmostcommonfeaturewasC++standardssupport,whichgotit'sownchapter.
Positionindependentcode
Thisisbestknownasthe-fPICflag.Muchofthetime,youdon'tneedtodoanything.CMakewillincludetheflagforSHAREDorMODULElibraries.Ifyoudoexplicitlyneedit:
set(CMAKE_POSITION_INDEPENDENT_CODEON)
willdoitglobally,or:
set_target_properties(lib1PROPERTIESPOSITION_INDEPENDENT_CODEON)
toexplicitlyturnitON(orOFF)foratarget.
LittlelibrariesIfyouneedtolinktothedllibrary,with-ldlonLinux,justusethebuilt-inCMakevariable${CMAKE_DL_LIBS}inatarget_link_librariescommand.Nomoduleorfind_packageneeded.(Thisaddswhateverisneededtogetdlopenanddlclose)
Unfortunately,themathlibraryisnotsolucky.Ifyouneedtoexplicitlylinktoit,youcanalwaysdotarget_link_libraries(MyTargetPUBLICm),butitmightbebettertouseCMake'sgenericfind_library:
find_library(MATH_LIBRARYm)
if(MATH_LIBRARY)
target_link_libraries(MyTargetPUBLIC${MATH_LIBRARY})
endif()
YoucanprettyeasilyfindFind*.cmake'sforthisandotherlibrariesthatyouneedwithaquicksearch;mostmajorpackageshaveahelperlibraryofCMakemodules.Seethechapteronexistingpackageinclusionformore.
InterproceduraloptimizationINTERPROCEDURAL_OPTIMIZATION,bestknownaslinktimeoptimizationandthe-fltoflag,isavailableonveryrecentversionsofCMake.YoucanturnthisonwithCMAKE_INTERPROCEDURAL_OPTIMIZATION(CMake3.9+only)ortheINTERPROCEDURAL_OPTIMIZATIONpropertyontargets.SupportforGCCandClangwasaddedinCMake3.8.Ifyousetcmake_minimum_required(VERSION3.9)orbetter(seeCMP0069),settingthistoONonatargetisanerrorifthecompilerdoesn'tsupportit.Youcanusecheck_ipo_supported(),fromthebuilt-inCheckIPOSupportedmodule,toseeifsupportisavailablebeforehand.Anexampleof3.9styleusage:
include(CheckIPOSupported)
check_ipo_supported(RESULTresult)
if(result)
set_target_properties(fooPROPERTIESINTERPROCEDURAL_OPTIMIZATIONTRUE)
endif()
Smallbutcommonneeds
36
Smallbutcommonneeds
37
CCacheandUtilitiesOvertheversions,commonutilitiesthathelpyouwritegoodcodehavehadsupportaddedtoCMake.ThisisusuallyintheformofapropertyandmatchingCMAKE_*initializationvariable.Thefeatureisnotmeanttobetiedtoonespecialprogram,butratheranyprogramthatissomewhatsimilarinbehavior.
Allofthesetake;separatedvalues(astandardlistinCMake)thatdescribetheprogramandoptionsthatyoushouldrunonthesourcefilesofthistarget.
CCache
SettheCMAKE_<LANG>_COMPILER_LAUNCHERvariableorthe<LANG>_COMPILER_LAUNCHERpropertyonatargettousesomethinglikeCCacheto"wrap"thecompilationofthetarget.SupportforCCachehasbeenexpandinginthelatestversionsofCMake.Inpractice,thistendstolooklikethis:
find_program(CCACHE_PROGRAMccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER"${CCACHE_PROGRAM}")
set(CMAKE_CUDA_COMPILER_LAUNCHER"${CCACHE_PROGRAM}")#CMake3.9+
endif()
Utilities
SetthefollowingpropertiesorCMAKE_*initializervariablestothecommandlineforthetools.MostofthemarelimitedtoCorCXXwithmakeorninjagenerators.
<LANG>_CLANG_TIDY:CMake3.6+<LANG>_CPPCHECK
<LANG>_CPPLINT
<LANG>_INCLUDE_WHAT_YOU_USE
ClangtidyHereisasimpleexampleofusingClang-Tidy:
if(CMAKE_VERSIONVERSION_GREATER3.6)
#Addclang-tidyifavailable
option(CLANG_TIDY_FIX"PerformfixesforClang-Tidy"OFF)
find_program(
CLANG_TIDY_EXE
NAMES"clang-tidy"
DOC"Pathtoclang-tidyexecutable"
)
if(CLANG_TIDY_EXE)
if(CLANG_TIDY_FIX)
set(CMAKE_CXX_CLANG_TIDY"${CLANG_TIDY_EXE}""-fix")
else()
set(CMAKE_CXX_CLANG_TIDY"${CLANG_TIDY_EXE}")
endif()
endif()
endif()
Utilities
38
The-fixpartisoptional,andwillmodifyyoursourcefilestotrytofixthetidywarningissued.Ifyouareworkinginagitrepository,thisisfairlysafeasyoucanseewhathaschanged.However,makesureyoudonotrunyourmakefile/ninjabuildinparallel!Thiswillnotworkverywellatallifittriestofixthesameheadertwice.
Ifyouwanttoexplicitlyusethetargetformtoensureyouonlycallthisonyourlocaltargets,youcansetavariable(IusuallychoseDO_CLANG_TIDY)insteadoftheCMAKE_CXX_CLANG_TIDYvariable,thenaddittoyourtargetpropertiesasyoucreatethem.
Includewhatyouuse
Thisisanexampleforusingincludewhatyouuse.First,you'llneedtohavethetool,suchasinadockercontainer:
Then,youcanpassthisintoyourbuildwithoutmodifyingthesource:
Finally,youcancollecttheoutputandapplythefixes:
LinkwhatyouuseThereisabooleantargetproperty,LINK_WHAT_YOU_USE,thatwillcheckforextraneousfileswhenlinking.
Clang-formatClang-formatdoesn'treallyhaveanintegrationwithCMake,unfortunately.Youcouldmakeacustomtarget(Seethispost,oryoucanrunitmanually.AninterestingprojectthatIhavenotreallytriedishere;itaddsaformattargetandevenmakessurethatyoucan'tcommitunformattedfiles.
Thefollowingtwolinewoulddothatinagitrepositoryinbash(assumingyouhavea.clang-formatfile):
gitbook$dockerrun--rm-ittuxity/include-what-you-use:clang_4.0
build#cmake..-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=include-what-you-use
build#make2>iwyu.out
build#fix_includes.py<iwyu.out
gitbook$gitls-files--'*.cpp''*.h'|xargsclang-format-i-style=file
gitbook$gitdiff--exit-code--color
Utilities
39
UsefulModulesThereareatonofusefulmodulesinCMake'smodulescollection;butsomeofthemaremoreusefulthanothers.Hereareafewhighlights.
CMakeDependentOption
Thisaddsacommandcmake_dependent_optionthatsetsanoptionbasedonanothersetofvariablesbeingtrue.Itlookslikethis:
include(CMakeDependentOption)
cmake_dependent_option(BUILD_TESTS"Buildyourtests"ON"VAL1;VAL2"OFF)
whichisjustashortcutforthis:
if(VAL1ANDVAL2)
set(BUILD_TESTS_DEFAULTON)
else()
set(BUILD_TESTS_DEFAULTOFF)
endif()
option(BUILD_TESTS"Buildyourtests"${BUILD_TESTS_DEFAULT})
if(NOTBUILD_TESTS_DEFAULT)
mark_as_advanced(BUILD_TESTS)
endif()
NotethatBUILD_TESTINGisabetterwaytocheckfortestingbeingenabledifyouuseinclude(CTest),sinceitisdefinedforyou.ThisisjustanexampleofCMakeDependentOption.
CMakePrintHelpers
Thismodulehasacoupleofhandyoutputfunctions.cmake_print_propertiesletsyoueasilyprintproperties.Andcmake_print_variableswillprintthenamesandvaluesofanyvariablesyougiveit.
CheckCXXCompilerFlag
Thischeckstoseeifaflagissupported.Forexample:
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-someflagOUTPUT_VARIABLE)
NotethatOUTPUT_VARIABLEwillalsoappearintheconfigurationprintout,sochooseagoodname.
Thisisjustoneofmanysimilarmodules,suchasCheckIncludeFileCXX,CheckStructHasMember,TestBigEndian,andCheckTypeSizethatallowyoutocheckforinformationaboutthesystem(andyoucancommunicatethattoyoursourcecode).
WriteCompilerDetectionHeader
Thisisanamazingmodulesimilartotheoneslistedabove,butspecialenoughtodeserveitsownsection.Itallowsyoutolookforalistoffeaturesthatsomecompilerssupport,andwriteoutaC++headerfilethatletsyouknowwhetherthatfeatureisavailable.Itevencanprovidecompatibilitymacrosforfeaturesthathavechangednames!
Usefulmodules
40
Touse:
write_compiler_detection_header(
FILEmyoutput.h
PREFIXMy
COMPILERSGNUClangMSVCIntel
FEATUREScxx_variadic_templates
)
Thissupportscompilerfeatures(definedto0or1),symbols(definedtoemptyorthesymbol),andmacrosthatsupportdifferentnames.TheywillbeprefixedwiththePREFIXyouprovide.YoucanseparatecompilersintodifferentfilesusingOUTPUT_FILES_DIR.
Thedownsideisthatyoudohavetolistthecompilersyouexpecttosupport.IfyouusetheALLOW_UNKNOWN_COMPILERSflag(s),youcankeepthisfromerroringonunknowncompilers,butitwillstillleaveallfeaturesempty.
try_compile/try_run
Thisisnotexactlyamodule,butiscrucialtomanyofthemoduleslistedabove.Youcanattempttocompile(andpossiblyrun)abitofcodeatconfiguretime.Thiscanallowyoutogetinformationaboutthecapabilitiesofyoursystem.Thebasicsyntaxis:
try_compile(
RESULT_VAR
bindir
SOURCES
source.cpp
)
Therearelotsofoptionsyoucanadd,likeCOMPILE_DEFINITIONS.InCMake3.8+,thiswillhonortheCMakeC/C++/CUDAstandardsettings.Ifyouusetry_runinstead,itwillruntheresultingprogramandgiveyoutheoutputinRUN_OUTPUT_VARIABLE.
FeatureSummaryThisisafairlyusefulbutratheroddmodule.Itallowsyoutoprintoutalistofpackageswhatweresearchedfor,aswellasanyoptionsyouexplicitymark.It'spartiallybutnotcompletelytiedintofind_package.Youfirstincludethemodule,asalways:
include(FeatureSummary)
Then,foranyfindpackagesyouhaverunorwillrun,youcanextendthedefaultinformation:
set_package_properties(OpenMPPROPERTIES
URL"http://www.openmp.org"
DESCRIPTION"Parallelcompilerdirectives"
PURPOSE"Thisiswhatitdoesinmypackage")
YoucanalsosettheTYPEofapackagetoRUNTIME,OPTIONAL,RECOMMENDED,orREQUIRED;youcan't,however,lowerthetypeofapackage;ifyouhavealreadyaddedaREQUIREDpackagethroughfind_packagebasedonanoption,you'llseeitlistedasREQUIRED.
And,youcanmarkanyoptionsaspartofthefeaturesummary.Ifyouchoosethesamenameasapackage,thetwointeractwitheachother.
add_feature_info(WITH_OPENMPOpenMP_CXX_FOUND"OpenMP(ThreadsafeFCNsonly)")
Then,youcanprintoutthesummaryoffeatures,eithertothescreenoralogfile:
if(CMAKE_PROJECT_NAMESTREQUALPROJECT_NAME)
feature_summary(WHATENABLED_FEATURESDISABLED_FEATURESPACKAGES_FOUND)
Usefulmodules
41
feature_summary(FILENAME${CMAKE_CURRENT_BINARY_DIR}/features.logWHATALL)
endif()
YoucanbuildanycollectionofWHATitemsthatyoulike,orjustuseALL.
Usefulmodules
42
SupportingIDEsIngeneral,IDEsarealreadysupportedbyastandardCMakeproject.TherearejustafewextrathingsthatcanhelpIDEsperformevenbetter.
Foldersfortargets
SomeIDEs,likeXcode,supportfolders.YouhavetomanuallyenabletheUSE_FOLDERSglobalpropertytoallowCMaketoorganizeyourfilesbyfolders:
set_property(GLOBALPROPERTYUSE_FOLDERSON)
Then,youcanaddtargetstofoldersafteryoucreatethem:
set_property(TARGETMyFilePROPERTYFOLDER"Scripts")
Folderscanbenestedwith/.
Youcancontrolhowfilesshowupineachfolderwithregularexpressionsorexplicitlistingsinsource_group:
FoldersforfilesYoucanalsocontrolhowthefoldersinsidetargetsappear.Therearetwoways,bothusingthesource_groupcommand.Thetraditionalwayis
source_group("SourceFiles\\NewDirectory"REGULAR_EXPRESSION".*\\.c[ucp]p?")
YoucanexplicitlylistfileswithFILES,oruseaREGULAR_EXPRESSION.Thiswayyouhavecompletecontroloverthefolderstructure.However,ifyouron-disklayoutiswelldesigned,youmightjustwanttomimicthat.InCMake3.8+,youcandosoveryeasilywithanewversionofthesource_groupcommand:
source_group(TREE"${CMAKE_CURRENT_SOURCE_DIR}/base/dir"PREFIX"HeaderFiles"FILES${FILE_LIST})
FortheTREEoption,youshouldusuallygiveafullpathstartingwithsomethinglike${CMAKE_CURRENT_SOURCE_DIR}/(becausethecommandinterpretspathsrelativetothebuilddirectory).TheprefixtellsyouwhereitputsitintotheIDEstructure,andtheFILESoptiontakesalistoffiles.CMakewillstriptheTREEpathfromtheFILE_LISTpath,itwilladdPREFIX,andthatwillbetheIDEfolderstructure.
Note:IfyouneedtosupportCMake<3.8,Iwouldrecommendjustprotectingtheabovecommand,andonlysupportingnicefolderlayoutonCMake3.8+.Foroldermethodstodothisfolderlayout,seethisblogpost.
RunningwithanIDE
TouseanIDE,eitherpass-G"nameofIDE"ifCMakecanproducethatIDE'sfiles(likeXcode,VisualStudio),oropentheCMakeLists.txtfilefromyourIDEifthatIDEhasbuiltinsupportforCMake(CLion,QtCreator,manyothers).
IDEs
43
DebuggingcodeYoumightneedtodebugyourCMakebuild,ordebugyourC++code.Botharecoveredhere.
CMakedebugging
First,let'slookatwaystodebugaCMakeListsorotherCMakefile.
Printingvariables
ThetimehonoredmethodofprintstatementslookslikethisinCMake:
message(STATUS"MY_VARIABLE=${MY_VARIABLE}")
However,abuiltinmodulemakesthiseveneasier:
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
Ifyouwanttoprintoutaproperty,thisismuch,muchnicer!Insteadofgettingthepropertiesonebyoneofofeachtarget(orotheritemwithproperties,suchasSOURCES,DIRECTORIES,TESTS,orCACHE_ENTRIES-globalpropertiesseemtobemissingforsomereason),youcansimplylistthemandgetthemprinteddirectly:
cmake_print_properties(
TARGETSmy_target
PROPERTIESPOSITION_INDEPENDENT_CODE
)
Tracingarun
HaveyouwantedtowatchexactlywhathappensinyourCMakefile,andwhen?The--trace-source="filename"featureisfantastic.Everylineruninthefilethatyougivewillbeechoedtothescreenwhenitisrun,lettingyoufollowexactlywhatishappening.Therearerelatedoptionsaswell,buttheytendtoburyyouinoutput.
Forexample:
cmake-S.-Bbuild--trace-source=CMakeLists.txt
Ifyouadd--trace-expand,thevariableswillbeexpandedintotheirvalues.
Buildingindebugmode
Forsingle-configurationgenerators,youcanbuildyourcodewith-DCMAKE_BUILD_TYPE=Debugtogetdebuggingflags.Inmulti-configurationgenerators,likemanyIDEs,youcanpicktheconfigurationintheIDE.Therearedistinctflagsforthismode(variablesendingin_DEBUGasopposedto_RELEASE),aswellasageneratorexpressionvalueCONFIG:DebugorCONFIG:Release.
Onceyoumakeadebugbuild,youcanrunadebugger,suchasgdborlldbonit.
Debugging
44
IncludingSmallProjectsThisiswhereagoodGitsystemplusCMakeshines.Youmightnotbeabletosolvealltheworld'sproblems,butthisisprettycloseforC++!
Thereareseveralmethodslistedinthechaptersinthissection.
IncludingProjects
45
GitSubmoduleMethodIfyouwanttoaddaGitrepositoryonthesameservice(GitHub,GitLab,BitBucket,etc),thefollowingisthecorrectGitcommandtosetthatupasasubmoduleintheexterndirectory:
Therelativepathtotherepoisimportant;itallowsyoutokeepthesameaccessmethod(sshorhttps)astheparentrepository.Thisworksverywellinmostways.Whenyouareinsidethesubmodule,youcantreatitjustlikeanormalrepo,andwhenyouareintheparentrepository,youcan"add"tochangethecurrentcommitpointer.
Butthetraditionaldownsideisthatyoueitherhavetohaveyourusersknowgitsubmodulecommands,sotheycaninitandupdatetherepo,ortheyhavetoadd--recursivewhentheyinitiallycloneyourrepo.CMakecanofferasolution:
find_package(GitQUIET)
if(GIT_FOUNDANDEXISTS"${PROJECT_SOURCE_DIR}/.git")
#Updatesubmodulesasneeded
option(GIT_SUBMODULE"Checksubmodulesduringbuild"ON)
if(GIT_SUBMODULE)
message(STATUS"Submoduleupdate")
execute_process(COMMAND${GIT_EXECUTABLE}submoduleupdate--init--recursive
WORKING_DIRECTORY${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLEGIT_SUBMOD_RESULT)
if(NOTGIT_SUBMOD_RESULTEQUAL"0")
message(FATAL_ERROR"gitsubmoduleupdate--initfailedwith${GIT_SUBMOD_RESULT},pleasecheckoutsubmodules")
endif()
endif()
endif()
if(NOTEXISTS"${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt")
message(FATAL_ERROR"Thesubmoduleswerenotdownloaded!GIT_SUBMODULEwasturnedofforfailed.Pleaseupdatesubmodules
andtryagain.")
endif()
ThefirstlinechecksforGitusingCMake'sbuiltinFindGit.cmake.Then,ifyouareinagitcheckoutofyoursource,addanoption(defaultingtoON)thatallowsdeveloperstoturnoffthefeatureiftheyneedto.Wethenrunthecommandtogetallrepositories,andfailifthatcommandfails,withaniceerrormessage.Finally,weverifythattherepositoriesexistbeforecontinuing,regardlessofthemethodusedtoobtainthem.YoucanuseORtolistseveral.
Now,youruserscanbecompletelyoblivioustotheexistenceofthesubmodules,andyoucanstillkeepupgooddevelopmentpractices!Theonlythingtowatchoutforisfordevelopers;youwillresetthesubmodulewhenyourerunCMakeifyouaredevelopinginsidethesubmodule.Justaddnewcommitstotheparentstagingarea,andyou'llbefine.
YoucanthenincludeprojectsthatprovidegoodCMakesupport:
add_subdirectory(extern/repo)
Or,youcanbuildaninterfacelibrarytargetyourselfifitisaheaderonlyproject.Or,youcanusefind_packageifthatissupported,probablypreparingtheinitialsearchdirectorytobetheoneyou'veadded(checkthedocsorthefilefortheFind*.cmakefileyouareusing).YoucanalsoincludeaCMakehelperfiledirectoryifyouappendtoyourCMAKE_MODULE_PATH,forexampletoaddpybind11'simprovedFindPython*.cmakefiles.
Bonus:Gitversionnumber
MovethistoGitsection:
execute_process(COMMAND${GIT_EXECUTABLE}rev-parse--shortHEAD
WORKING_DIRECTORY"${CMAKE_CURRENT_SOURCE_DIR}"
gitbook$gitsubmoduleadd../../owner/repo.gitextern/repo
Submodule
46
OUTPUT_VARIABLEPACKAGE_GIT_VERSION
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
Submodule
47
GoogleTest:Downloadmethod
DownloadingMethod:buildtime
UntilCMake3.11,theprimarydownloadmethodforpackageswasdoneatbuildtime.Thiscausesseveralissues;mostimportantofwhichisthatadd_subdirectorydoesn'tworkonafilethatdoesn'texistyet!Thetoolforthis,ExternalProject,hastoworkaroundthisbydoingthebuilditself.(Itcan,however,buildnon-CMakepackagesaswell).
.NotethatExternalDataisthetoolfornon-packagedata.↩
DownloadingMethod:configuretimeIfyoupreferconfiguretime,seetheCrascit/DownloadProjectrepositoryforadrop-insolution.Submodulesworksowell,though,thatI'vediscontinuedmostofthedownloadsforthingslikeGoogleTestandmovedthemtosubmodules.Autodownloadsarehardertomimicifyoudon'thaveinternetaccess,andtheyareoftenimplementedinthebuilddirectory,wastingtimeandspaceifyouhavemultiplebuilddirectories.
1
1
DownloadProject
48
FetchContent(CMake3.11+)Often,youwouldliketodoyourdownloadofdataorpackagesaspartoftheconfigureinsteadofthebuild.Thiswasinventedseveraltimesinthirdpartymodules,butwasfinallyaddedtoCMakeitselfaspartofCMake3.11astheFetchContentmodule.
TheFetchContentmodulehasexcellentdocumentationthatIwon'ttrytorepeat.Thekeyideasare:
UseFetchContent_Declare(MyName)togetdataorapackage.YoucansetURLs,Gitrepositories,andmore.UseFetchContent_GetProperties(MyName)onthenameyoupickedinthefirststeptogetMyName_*variables.CheckMyName_POPULATED,andifnotpopulated,useFetchContent_Populate(MyName)(andifapackage,add_subdirectory("${MyName_SOURCE_DIR}""${MyName_BINARY_DIR}"))
Forexample,todownloadCatch2:
FetchContent_Declare(
catch
GIT_REPOSITORYhttps://github.com/catchorg/Catch2.git
GIT_TAGv2.9.1
)
#CMake3.14+
FetchContent_MakeAvailable(catch)
Ifyoucan'tuseCMake3.14+,theclassicwaytopreparecodewas:
#CMake3.11+
FetchContent_GetProperties(catch)
if(NOTcatch_POPULATED)
FetchContent_Populate(catch)
add_subdirectory(${catch_SOURCE_DIR}${catch_BINARY_DIR})
endif()
Ofcourse,youcouldbundledthisupintoamacro:
if(${CMAKE_VERSION}VERSION_LESS3.14)
macro(FetchContent_MakeAvailableNAME)
FetchContent_GetProperties(${NAME})
if(NOT${NAME}_POPULATED)
FetchContent_Populate(${NAME})
add_subdirectory(${${NAME}_SOURCE_DIR}${${NAME}_BINARY_DIR})
endif()
endmacro()
endif()
NowyouhavetheCMake3.14+syntaxinCMake3.11+.
Fetch(CMake3.11)
49
Testing
GeneralTestingInformation
InyourmainCMakeLists.txtyouneedtoaddthefollowingfunctioncall(notinasubfolder):
if(CMAKE_PROJECT_NAMESTREQUALPROJECT_NAME)
include(CTest)
endif()
WhichwillenabletestingandsetaBUILD_TESTINGoptionsouserscanturntestingonandoff(Alongwithafewotherthings).Oryoucandothisyourselfbydirectlycallingenable_testing().
Whenyouaddyourtestfolder,youshoulddosomethinglikethis:
if(CMAKE_PROJECT_NAMESTREQUALPROJECT_NAMEANDBUILD_TESTING)
add_subdirectory(tests)
endif()
Thereasonforthisisthatifsomeoneelseincludesyourpackage,andtheyuseBUILD_TESTING,theyprobablydonotwantyourteststobuild.Intherarecasethatyoureallydowanttoenabletestingonbothpackages,youcanprovideanoverride:
if((CMAKE_PROJECT_NAMESTREQUALPROJECT_NAMEORMYPROJECT_BUILD_TESTING)ANDBUILD_TESTING)
add_subdirectory(tests)
endif()
Themainusecasefortheoverrideaboveisactuallyinthisbook'sownexamples,asthemasterCMakeprojectreallydoeswanttorunallthesubprojecttests.
Youcanregistertargetswith:
add_test(NAMETestNameCOMMANDTargetName)
IfyouputsomethingelsebesidesatargetnameafterCOMMAND,itwillregisterasacommandlinetorun.Itwouldalsobevalidtoputthegeneratorexpression:
add_test(NAMETestNameCOMMAND$<TARGET_FILE:${TESTNAME}>)
whichwouldusetheoutputlocation(thus,theexecutable)oftheproducedtarget.
Buildingaspartofatest
IfyouwanttorunCMaketobuildaprojectaspartofatest,youcandothattoo(infact,thisishowCMaketestsitself).Forexample,ifyourmasterprojectwascalledMyProjectandyouhadanexamples/simpleprojectthatcouldbuildbyitself,thiswouldlooklike:
add_test(
NAME
ExampleCMakeBuild
COMMAND
"${CMAKE_CTEST_COMMAND}"
--build-and-test"${My_SOURCE_DIR}/examples/simple"
"${CMAKE_CURRENT_BINARY_DIR}/simple"
--build-generator"${CMAKE_GENERATOR}"
--test-command"${CMAKE_CTEST_COMMAND}"
Testing
50
)
TestingFrameworksLookatthesubchaptersforrecipesforpopularframeworks.
GoogleTest:ApopularoptionfromGoogle.Developmentcanbeabitslow.Catch2:Amodern,PyTest-likeframeworkwithclevermacros.DocTest:AreplacementforCatch2thatissupposedtocompilemuchfasterandbecleaner.SeeCatch2chapterandreplacewithDocTest.
Testing
51
GoogleTest
Submodulemethod(preferred)
Tousethismethod,justcheckoutGoogleTestasasubmodule:
gitsubmoduleadd--branch=release-1.8.0../../google/googletest.gitextern/googletest
Then,inyourmainCMakeLists.txt:
option(PACKAGE_TESTS"Buildthetests"ON)
if(PACKAGE_TESTS)
enable_testing()
include(GoogleTest)
add_subdirectory(tests)
endif()
IwouldrecommendusingsomethinglikePROJECT_NAMESTREQUALCMAKE_PROJECT_NAMEtosetthedefaultforthePACKAGE_TESTSoption,sincethisshouldonlybuildbydefaultifthisisthecurrentproject.Asmentionedbefore,youhavetodotheenable_testinginyourmainCMakeLists.
Now,inyourtestsdirectory:
add_subdirectory("${PROJECT_SOURCE_DIR}/extern/googletest""extern/googletest")
IfyoudidthisinyourmainCMakeLists,youcoulduseanormaladd_subdirectory;theextrapathhereisneededtocorrectthebuildpathbecausewearecallingitfromasubdirectory.
Thenextlineisoptional,butkeepsyourCACHEcleaner:
mark_as_advanced(
BUILD_GMOCKBUILD_GTESTBUILD_SHARED_LIBS
gmock_build_testsgtest_build_samplesgtest_build_tests
gtest_disable_pthreadsgtest_force_shared_crtgtest_hide_internal_symbols
)
IfyouareinterestedinkeepingIDEsthatsupportfoldersclean,Iwouldalsoaddtheselines:
set_target_properties(gtestPROPERTIESFOLDERextern)
set_target_properties(gtest_mainPROPERTIESFOLDERextern)
set_target_properties(gmockPROPERTIESFOLDERextern)
set_target_properties(gmock_mainPROPERTIESFOLDERextern)
Then,toaddatest,I'drecommendthefollowingmacro:
macro(package_add_testTESTNAME)
#createanexectuableinwhichthetestswillbestored
add_executable(${TESTNAME}${ARGN})
#linktheGoogletestinfrastructure,mockinglibrary,andadefaultmainfuctionto
#thetestexecutable.Removeg_test_mainifwritingyourownmainfunction.
target_link_libraries(${TESTNAME}gtestgmockgtest_main)
#gtest_discover_testsreplacesgtest_add_tests,
#seehttps://cmake.org/cmake/help/v3.10/module/GoogleTest.htmlformoreoptionstopasstoit
gtest_discover_tests(${TESTNAME}
#setaworkingdirectorysoyourprojectrootsothatyoucanfindtestdataviapathsrelativetotheprojectroot
WORKING_DIRECTORY${PROJECT_DIR}
PROPERTIESVS_DEBUGGER_WORKING_DIRECTORY"${PROJECT_DIR}"
1
GoogleTest
52
)
set_target_properties(${TESTNAME}PROPERTIESFOLDERtests)
endmacro()
package_add_test(test1test1.cpp)
Thiswillallowyoutoquicklyandsimplyaddtests.Feelfreetoadjusttosuityourneeds.Ifyouhaven'tseenitbefore,ARGNis"everyargumentafterthelistedones".Modifythemacrotomeetyourneeds.Forexample,ifyou'retestinglibrariesandneedtolinkindifferentlibrariesfordifferenttests,youmightusethis:
macro(package_add_test_with_librariesTESTNAMEFILESLIBRARIESTEST_WORKING_DIRECTORY)
add_executable(${TESTNAME}${FILES})
target_link_libraries(${TESTNAME}gtestgmockgtest_main${LIBRARIES})
gtest_discover_tests(${TESTNAME}
WORKING_DIRECTORY${TEST_WORKING_DIRECTORY}
PROPERTIESVS_DEBUGGER_WORKING_DIRECTORY"${TEST_WORKING_DIRECTORY}"
)
set_target_properties(${TESTNAME}PROPERTIESFOLDERtests)
endmacro()
package_add_test_with_libraries(test1test1.cpplib_to_test"${PROJECT_DIR}/european-test-data/")
Downloadmethod
YoucanusethedownloaderinmyCMakehelperrepository,usingCMake'sincludecommand.
ThisisadownloaderforGoogleTest,basedontheexcellentDownloadProjecttool.DownloadingacopyforeachprojectistherecommendedwaytouseGoogleTest(somuchso,infact,thattheyhavedisabledtheautomaticCMakeinstalltarget),sothisrespectsthatdesigndecision.Thismethoddownloadstheprojectatconfiguretime,sothatIDE'scorrectlyfindthelibraries.Usingitissimple:
cmake_minimum_required(VERSION3.10)
project(MyProjectCXX)
list(APPENDCMAKE_MODULE_PATH${PROJECT_SOURCE_DIR}/cmake)
enable_testing()#Mustbeinmainfile
include(AddGoogleTest)#Couldbein/tests/CMakeLists.txt
add_executable(SimpleTestSimpleTest.cu)
add_gtest(SimpleTest)
Note:add_gtestisjustamacrothataddsgtest,gmock,andgtest_main,andthenrunsadd_testtocreateatestwiththesamename:
target_link_libraries(SimpleTestgtestgmockgtest_main)
add_test(SimpleTestSimpleTest)
FetchContent:CMake3.11TheexamplefortheFetchContentmoduleisGoogleTest:
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORYhttps://github.com/google/googletest.git
GIT_TAGrelease-1.8.0
)
FetchContent_GetProperties(googletest)
if(NOTgoogletest_POPULATED)
GoogleTest
53
FetchContent_Populate(googletest)
add_subdirectory(${googletest_SOURCE_DIR}${googletest_BINARY_DIR})
endif()
.HereI'veassumedthatyouareworkingonaGitHubrepositorybyusingtherelativepathtogoogletest.↩1
GoogleTest
54
CatchCatchandCatch2(C++11onlyversion)arepowerful,idomatictestingsolutionssimilarinphilosophytoPyTestforPython.TouseCatchinaCMakeproject,thereareseveraloptions.
Vendoring
IfyousimplydropinthesingleincludereleaseofCatchintoyourproject,thisiswhatyouwouldneedtoaddCatch:
#Prepare"Catch"libraryforotherexecutables
set(CATCH_INCLUDE_DIR${CMAKE_CURRENT_SOURCE_DIR}/extern/catch)
add_library(Catch2::CatchIMPORTEDINTERFACE)
set_property(Catch2::CatchPROPERTYINTERFACE_INCLUDE_DIRECTORIES"${CATCH_INCLUDE_DIR}")
Then,youwouldlinktoCatch2::Catch.ThiswouldhavebeenokayasanINTERFACEtargetsinceyouwon'tbeexportingyourtests.
DirectinclusionIfyouaddthelibraryusingExternalProject,FetchContent,orgitsubmodules,youcanalsoadd_subdirectoryCatch(CMake3.1+).
CatchalsoprovidestwoCMakemodulesthatyoucanusetoregistertheindividualtestswithCMake.
Catch
55
ExportingandInstallingTherearethreegoodwaysandonebadwaytoallowothersuseyourlibrary:
Findmodule(thebadway)
Ifyouarethelibraryauthor,don'tmakeaFind<mypackage>.cmakescript!TheseweredesignedforlibrarieswhoseauthorsdidnotsupportCMake.UseaConfig<mypackage>.cmakeinsteadaslistedbelow.
AddSubproject
Apackagecanincludeyourprojectinasubdirectory,andthenuseadd_directoryonthesubdirectory.Thisusefulforheader-onlyandquick-to-compilelibraries.Notethattheinstallcommandsmayinterferewiththeparentproject,soyoucanaddEXCLUDE_FROM_ALLtotheadd_subdirectorycommand;thetargetsyouexplicitlyusewillstillbebuilt.
Inordertosupportthisasalibraryauthor,makesureyouuseCMAKE_CURRENT_SOURCE_DIRinsteadofPROJECT_SOURCE_DIR(andlikewiseforothervariables,likebinarydirs).YoucancheckCMAKE_PROJECT_NAMESTREQUALPROJECT_NAMEtoonlyaddoptionsordefaultsthatmakesenseifthisisaproject.
Also,sincenamespacesareagoodidea,andtheusageofyourlibraryshouldbeconsistentwiththeothermethodsbelow,youshouldadd
add_library(MyLib::MyLibALIASMyLib)
tostandardisetheusageacrossallmethods.ThisALIAStargetwillnotbeexportedbelow.
ExportingThethirdwayis*Config.cmakescripts;thatwillbethetopicofthenextchapterinthissession.
ExportingandInstalling
56
InstallingInstallcommandscauseafileortargettobe"installed"intotheinstalltreewhenyoumakeinstall.Yourbasictargetinstallcommandlookslikethis:
install(TARGETSMyLib
EXPORTMyLibTargets
LIBRARYDESTINATIONlib
ARCHIVEDESTINATIONlib
RUNTIMEDESTINATIONbin
INCLUDESDESTINATIONinclude
)
Thevariousdestinationsareonlyneededifyouhavealibrary,staticlibrary,orprogramtoinstall.Theincludesdestinationisspecial;sinceatargetdoesnotinstallincludes.Itonlysetstheincludesdestinationontheexportedtarget(whichisoftenalreadysetbytarget_include_directories,sochecktheMyLibTargetsfileandmakesureyoudon'thavetheincludedirectoryincludedtwiceifyouwantcleancmakefiles).
It'susuallyagoodideatogiveCMakeaccesstotheversion,sothatfind_packagecanhaveaversionspecified.Thatlookslikethis:
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
MyLibConfigVersion.cmake
VERSION${PACKAGE_VERSION}
COMPATIBILITYAnyNewerVersion
)
Youhavetwochoicesnext.YouneedtomakeaMyLibConfig.cmake,butyoucandoiteitherbyexportingyourtargetsdirectlytoit,orbywritingitbyhand,thenincludingthetargetsfile.Thelateroptioniswhatyou'llneedifyouhaveanydependencies,evenjustOpenMP,soI'llillustratethatmethod.
First,makeaninstalltargetsfile(verysimilartotheoneyoumadeinthebuilddirectory):
install(EXPORTMyLibTargets
FILEMyLibTargets.cmake
NAMESPACEMyLib::
DESTINATIONlib/cmake/MyLib
)
Thisfilewilltakethetargetsyouexportedandputtheminafile.Ifyouhavenodependencies,justuseMyLibConfig.cmakeinsteadofMyLibTargets.cmakehere.ThenwriteacustomMyLibConfig.cmakefileinyoursourcetreesomewhere.Ifyouwanttocaptureconfiguretimevariables,youcanusea.infile,andyouwillwanttousethe@[email protected]:
include(CMakeFindDependencyMacro)
#Capturingvaluesfromconfigure(optional)
set(my-config-var@my-config-var@)
#Samesyntaxasfind_package
find_dependency(MYDEPREQUIRED)
#Anyextrasetup
#Addthetargetsfile
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
Now,youcanuseconfigurefile(ifyouuseda.infile)andtheninstalltheresultingfile.Sincewe'vemadeaConfigVersionfile,thisisagoodplacetoinstallittoo.
Installing
57
configure_file(MyLibConfig.cmake.inMyLibConfig.cmake@ONLY)
install(FILES"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATIONlib/cmake/MyLib
)
That'sit!Nowonceyouinstallapackage,therewillbefilesinlib/cmake/MyLibthatCMakewillsearchfor(specifically,MyLibConfig.cmakeandMyLibConfigVersion.cmake),andthetargetsfilethatconfigusesshouldbethereaswell.
WhenCMakesearchesforapackage,itwilllookinthecurrentinstallprefixandseveralstandardplaces.Youcanalsoaddthistoyoursearchpathmanually,includingMyLib_PATH,andCMakegivestheusernicehelpoutputiftheconfigurefileisnotfound.
Installing
58
Exporting
ThedefaultbehaviorforexportingchangedinCMake3.15.Sincechangingfilesinauser'shomedirectoryisconsidered"surprising"(anditis,whichiswhythischapterexists),itisnolongerthedefaultbehavior.IfyousetaminimumormaximumCMakeversionof3.15orbetter,thiswillnolongerhappenunlessyousetCMAKE_EXPORT_PACKAGE_REGISTRY,asmentionedbelow.
Therearethreewaystoaccessaprojectfromanotherproject:subdirectory,exportedbuilddirectories,andinstalling.Tousethebuilddirectoryofoneprojectinanotherproject,youwillneedtoexporttargets.Exportingtargetsisneededforaproperinstall,allowingthebuilddirectorytobeusedaswellisjusttwoaddedlines.ItisnotgenerallyawaytoworkthatIwouldrecommend,butcanbeusefulfordevelopmentandaswaytopreparetheinstallationprocedurediscussedlater.
Youshouldmakeanexportset,probablyneartheendofyourmainCMakeLists.txt:
export(TARGETSMyLib1MyLib2NAMESPACEMyLib::FILEMyLibTargets.cmake)
Thisputsthetargetsyouhavelistedintoafileinthebuilddirectory,andoptionallyprefixesthemwithanamespace.Now,toallowCMaketofindthispackage,exportthepackageintothe$HOME/.cmake/packagesfolder:
set(CMAKE_EXPORT_PACKAGE_REGISTRYON)
export(PACKAGEMyLib)
Now,ifyoufind_package(MyLib),CMakecanfindthebuildfolder.LookatthegeneratedMyLibTargets.cmakefiletohelpyouunderstandexactlywhatiscreated;it'sjustanormalCMakefile,withtheexportedtargets.
Notethatthere'sadownside:ifyouhaveimporteddependencies,theywillneedtobeimportedbeforeyoufind_package.Thatwillbefixedinthenextmethod.
Exporting
59
PackagingTherearetwowaystoinstructCMaketobuildyourpackage;oneistouseaCPackConfig.cmakefile,andtheotheristointegratetheCPackvariablesintoyourCMakeLists.txtfile.Sinceyouwantvariablesfromyourmainbuildtobeincluded,likeversionnumber,you'llwanttomakeaconfigurefileifyougothatroute.I'llshowyoutheintegratedversion:
#Packagingsupport
set(CPACK_PACKAGE_VENDOR"Vendorname")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY"Somesummary")
set(CPACK_PACKAGE_VERSION_MAJOR${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH${PROJECT_VERSION_PATCH})
set(CPACK_RESOURCE_FILE_LICENSE"${CMAKE_CURRENT_SOURCE_DIR}/LICENCE")
set(CPACK_RESOURCE_FILE_README"${CMAKE_CURRENT_SOURCE_DIR}/README.md")
Thesearethemostcommonvariablesyou'llneedtomakeabinarypackage.AbinarypackageusestheinstallmechanismofCMake,soanythingthatisinstalledwillbepresent.
Youcanalsomakeasourcepackage.YoushouldsetCMAKE_SOUCE_IGNORE_FILEStoregularexpressionsthatensureyoudon'tpickupanyextrafiles(likethebuilddirectoryorgitdetails);otherwisemakepackage_sourcewillbundleupliterallyeverythinginthesourcedirectory.Youcanalsosetthesourcegeneratortomakeyourfavoritetypesoffilesforsourcepackages:
set(CPACK_SOURCE_GENERATOR"TGZ;ZIP")
set(CPACK_SOURCE_IGNORE_FILES
/.git
/dist
/.*build.*
/\\\\.DS_Store
)
NotethatthiswillnotworkonWindows,butthegeneratedsourcepackagesworkonWindows.
Finally,youneedtoincludetheCPackmodule:
include(CPack)
Packaging
60
FindingPackageTherearetwowaystofindpackagesinCMake.
Lookingforlibraries
61
CUDA(inprogress)CUDAsupportisavailableintwoflavors.Thenewmethod,introducedinCMake3.8(3.9forWindows),willbewhatIfocusonfirst.Theoldmethodwillbecoveredafterwards,butasyou'llsee,it'suglierandhardertogetright.I'dsticktorequiringCMake3.8or3.9forCUDA(andCMake3.11forIDEslikeXcodeandVisualStudio).
AgoodresourceforCUDAandModernCMakeisthistalkbyCMakedeveloperRobertMaynardatGTC2017.
Method1:CUDAasaFirstClassLanguage
Thismethodisquitenew,anddoesn'tseemtohavemuchdocumentation.Thereareseveralissuesyouneedtowatchoutforwhenusingit,butoverallisshouldbeamuchnicerandcleanerwaytouseCUDA.
AddingtheCUDALanguage
TherearetwowaystoenableCUDAsupport.IfCUDAisnotoptional:
project(MY_PROJECTLANGUAGESCUDACXX)
You'llprobablywantCXXlistedherealso.And,ifCUDAisoptional,you'llwanttoputthisinsomewhereconditionally:
enable_language(CUDA)
TochecktoseeifCUDAisavailable,useCheckLanuage:
include(CheckLanguage)
check_language(CUDA)
YoucanchecktheversionoftheNVCCtoolkitwithCMAKE_CUDA_COMPILER_VERSION(fornow,onlyNVCCissupported,butjusttobesure,checkCMAKE_CUDA_COMPILER_IDSTREQUAL"NVIDIA").
VariablesforCUDA
ManyvariableswithCXXinthenamehaveaCUDAversionwithCUDAinstead.Forexample,tosettheC++standardrequiredforCUDA,
if(NOTDEFINEDCMAKE_CUDA_STANDARD)
set(CMAKE_CUDA_STANDARD11)
set(CMAKE_CUDA_STANDARD_REQUIREDON)
endif()
Addingalibrary
Thisistheeasypart;aslongasyouuse.cuforCUDAfiles,youcanjustaddlibrarieslikeyounormallywould.
Youcanalsouseseparablecompilation:
set_target_properties(mylibPROPERTIES
CUDA_SEPERABLE_COMPILATIONON)
YoucanalsodirecltymakeaPTXfilewiththeCUDA_PTX_COMPILATIONproperty.
CUDA
62
Workingwithtargets
UsingtargetsshouldworksimilarlytoCXX,butthere'saproblem.Ifyouincludeatargetthatincludescompileroptions(flags),mostofthetime,theoptionswillnotbeprotectedbythecorrectincludes(andthechancesofthemhavingthecorrectCUDAwrapperisevensmaller).Here'swhatacorrectcompileroptionslineshouldlooklike:
"$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:-fopenmp>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompiler=-fopenmp>"
However,ifyouusingalmostanyfind_package,andusingtheModernCMakemethodsoftargetsandinheritance,everythingwillbreak.I'velearnedthatthehardway.
Fornow,here'saprettyreasonablesolution,aslongasyouknowtheun-aliasedtargetname.It'safunctionthatwillfixaC++onlytargetbywrappingtheflagsifusingaCUDAcompiler:
function(CUDA_CONVERT_FLAGSEXISTING_TARGET)
get_property(old_flagsTARGET${EXISTING_TARGET}PROPERTYINTERFACE_COMPILE_OPTIONS)
if(NOT"${old_flags}"STREQUAL"")
string(REPLACE";"","CUDA_flags"${old_flags}")
set_property(TARGET${EXISTING_TARGET}PROPERTYINTERFACE_COMPILE_OPTIONS
"$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CXX>>:${old_flags}>$<$<BUILD_INTERFACE:$<COMPILE_LANGUAGE:CUDA>>:-Xcompile
r=${CUDA_flags}>"
)
endif()
endfunction()
Usefulvariables
CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES:Placeforbuilt-inThrust,etcCMAKE_CUDA_COMPILER:NVCCwithlocation
NotethatFindCUDAisdeprecated,butfornow,thefollowingfunctionsrequireFindCUDA:
CUDAversionchecks/pickingaversionArchitecturedetection(Note:3.12fixesthispartially)LinkingtoCUDAlibrariesfromnon-.cufiles
Method2:FindCUDAIfyouwanttosupportanolderversionofCMake,IrecommendatleastincludingtheFindCUDAfromCMakeversion3.9inyourcmakefolder(seetheCLIUtilsgithuborganizationforagitrepository).You'llwanttwofeaturesthatwereadded:CUDA_LINK_LIBRARIES_KEYWORDandcuda_select_nvcc_arch_flags,alongwiththenewerarchitecturesandCUDAversions.
TousetheoldCUDAsupport,youusefind_package:
find_package(CUDA7.0REQUIRED)
message(STATUS"FoundCUDA${CUDA_VERSION_STRING}at${CUDA_TOOLKIT_ROOT_DIR}")
YoucancontroltheCUDAflagswithCUDA_NVCC_FLAGS(listappend)andyoucancontrolseparablecompilationwithCUDA_SEPARABLE_COMPILATION.You'llalsowanttomakesureCUDAplaysniceandaddskeywordstothetargets(CMake3.9+):
set(CUDA_LINK_LIBRARIES_KEYWORDPUBLIC)
You'llalsomightwanttoallowausertocheckforthearchflagsoftheircurrenthardware:
cuda_select_nvcc_arch_flags(ARCH_FLAGS)#optionalargumentforarchtoadd
CUDA
63
CUDA
64
OpenMPOpenMPsupportwasdrasticallyimprovedinCMake3.9+.TheModern(TM)waytoaddOpenMPtoatargetis:
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
target_link_libraries(MyTargetPUBLICOpenMP::OpenMP_CXX)
endif()
Thisnotonlyiscleanerthantheoldmethod,itwillalsocorrectlysetthelibrarylinklinedifferentlyfromthecompilelineifneeded.InCMake3.12+,thiswillevensupportOpenMPonmacOS(ifthelibraryisavailable,suchaswithbrewinstalllibomp).However,ifyouneedtosupportolderCMake,thefollowingworksonCMake3.1+:
#ForCMake<3.9,weneedtomakethetargetourselves
if(NOTTARGETOpenMP::OpenMP_CXX)
find_package(ThreadsREQUIRED)
add_library(OpenMP::OpenMP_CXXIMPORTEDINTERFACE)
set_property(TARGETOpenMP::OpenMP_CXX
PROPERTYINTERFACE_COMPILE_OPTIONS${OpenMP_CXX_FLAGS})
#Onlyworksifthesameflagispassedtothelinker;useCMake3.9+otherwise(Intel,AppleClang)
set_property(TARGETOpenMP::OpenMP_CXX
PROPERTYINTERFACE_LINK_LIBRARIES${OpenMP_CXX_FLAGS}Threads::Threads)
endif()
target_link_libraries(MyTargetPUBLICOpenMP::OpenMP_CXX)
Warning:CMake<3.4hasabugintheThreadspackagethatrequiresyoutohavetheClanguageenabled.
OpenMP
65
BoostlibraryTheBoostlibraryisincludedinthefindpackagesthatCMakeprovides,butithasacoupleofodditiesinhowitworks.SeeFindBoostforafulldescription;thiswilljustgiveaquickoverviewandprovidearecipe.BesuretocheckthepagefortheminimumrequiredversionofCMakeyouareusingandseewhatoptionsyouhave.
First,youcancustomizethebehavioroftheBoostlibrariesselectedusingasetofvariablesthatyousetbeforesearchingforBoost.Thereareagrowingnumberofsettings,butherearethethreemostcommonones:
set(Boost_USE_STATIC_LIBSOFF)
set(Boost_USE_MULTITHREADEDON)
set(Boost_USE_STATIC_RUNTIMEOFF)
InCMake3.5,importedtargetswereadded.Thesetargetshandledependenciesforyouaswell,sotheyareaverynicewaytoaddBoostlibraries.However,CMakehasthedependencyinformationbakedintoitforallknownversionsofBoost,soCMakemustbenewerthanBoostforthesetowork.Inarecentmergerequest,CMakestartedassumingthatthedependenciesholdfromthelastversionitknowsabout,andwillusethat(alongwithgivingawarning).ThisfunctionalitywasbackportedintoCMake3.9.
TheimporttargetsareintheBoost::namespace.Boost::boostistheheaderonlypart.Theothercompiledlibrariesareavailable,andincludedependenciesasneeded.
HereisanexampleforusingtheBoost::filesystemlibrary:
set(Boost_USE_STATIC_LIBSOFF)
set(Boost_USE_MULTITHREADEDON)
set(Boost_USE_STATIC_RUNTIMEOFF)
find_package(Boost1.50REQUIREDCOMPONENTSfilesystem)
message(STATUS"Boostversion:${Boost_VERSION}")
#ThisisneededifyourBoostversionisnewerthanyourCMakeversion
#orifyouhaveanoldversionofCMake(<3.5)
if(NOTTARGETBoost::filesystem)
add_library(Boost::filesystemIMPORTEDINTERFACE)
set_property(TARGETBoost::filesystemPROPERTY
INTERFACE_INCLUDE_DIRECTORIES${Boost_INCLUDE_DIR})
set_property(TARGETBoost::filesystemPROPERTY
INTERFACE_LINK_LIBRARIES${Boost_LIBRARIES})
endif()
target_link_libraries(MyExeOrLibraryPUBLICBoost::filesystem)
Boost
66
MPIToaddMPI,likeOpenMP,you'llbebestoffwithCMake3.9+.
find_package(MPIREQUIRED)
message(STATUS"Run:${MPIEXEC}${MPIEXEC_NUMPROC_FLAG}${MPIEXEC_MAX_NUMPROCS}${MPIEXEC_PREFLAGS}EXECUTABLE${MPIEXEC_POSTF
LAGS}ARGS")
target_link_libraries(MyTargetPUBLICMPI::MPI_CXX)
However,youcanimitatethisonCMake3.1+with:
find_package(MPIREQUIRED)
#ForsupportingCMake<3.9:
if(NOTTARGETMPI::MPI_CXX)
add_library(MPI::MPI_CXXIMPORTEDINTERFACE)
set_property(TARGETMPI::MPI_CXX
PROPERTYINTERFACE_COMPILE_OPTIONS${MPI_CXX_COMPILE_FLAGS})
set_property(TARGETMPI::MPI_CXX
PROPERTYINTERFACE_INCLUDE_DIRECTORIES"${MPI_CXX_INCLUDE_PATH}")
set_property(TARGETMPI::MPI_CXX
PROPERTYINTERFACE_LINK_LIBRARIES${MPI_CXX_LINK_FLAGS}${MPI_CXX_LIBRARIES})
endif()
message(STATUS"Run:${MPIEXEC}${MPIEXEC_NUMPROC_FLAG}${MPIEXEC_MAX_NUMPROCS}${MPIEXEC_PREFLAGS}EXECUTABLE${MPIEXEC_POSTF
LAGS}ARGS")
target_link_libraries(MyTargetPUBLICMPI::MPI_CXX)
MPI
67
ROOTROOTisaC++ToolkitforHighEnergyPhysics.Itishuge.TherearereallyalotofwaystouseitinCMake,thoughmany/mostoftheexamplesyou'llfindareprobablywrong.Here'smyrecommendation.
Mostimportantly,therearelotsofimprovementsinCMakesupportinmorerecentversionsofROOT-Using6.16+ismuch,mucheasier!Ifyoureallymustsupport6.14orearlier,seethesectionattheend.
FindingROOT
ROOT6.10+supportsconfigfilediscovery,soyoucanjustdo:
find_package(ROOT6.16CONFIGREQUIRED)
toattempttofindROOT.Ifyoudon'thaveyourpathssetup,youcanpass-DROOT_DIR=$ROOTSYS/cmaketofindROOT.(But,really,youshouldsourcethisroot.sh).
Therightway(Targets)
ROOT6.12andearlierdonotaddtheincludedirectoryforimportedtargets.ROOT6.14+hascorrectedthiserror,andrequiredtargetpropertieshavebeengettingbetter.Thismethodisrapidlybecomingeasiertouse(seetheexampleattheendofthispagefortheolderROOTdetails).
Tolink,justpickthelibrariesyouwanttouse:
add_executable(RootSimpleExampleSimpleExample.cxx)
target_link_libraries(RootSimpleExamplePUBLICROOT::Physics)
Ifyou'dliketoseethedefaultlist,runroot-config--libsonthecommandline.InHomebrewROOT6.18thiswouldbe:
ROOT::Core
ROOT::Gpad
ROOT::Graf3d
ROOT::Graf
ROOT::Hist
ROOT::Imt
ROOT::MathCore
ROOT::Matrix
ROOT::MultiProc
ROOT::Net
ROOT::Physics
ROOT::Postscript
ROOT::RIO
ROOT::ROOTDataFrame
ROOT::ROOTVecOps
ROOT::Rint
ROOT::Thread
ROOT::TreePlayer
ROOT::Tree
ROOT
68
Theoldglobalway
ROOTprovidesautilitytosetupaROOTproject,whichyoucanactivateusinginclude("${ROOT_USE_FILE}").Thiswillautomaticallymakeuglydirectorylevelandglobalvariablesforyou.Itwillsaveyoualittletimesettingup,andwillwastemassiveamountsoftimelaterifyoutrytodoanythingtricky.Aslongasyouaren'tmakingalibrary,it'sprobablyfineforsimplescripts.Includesandflagsaresetglobally,butyou'llstillneedtolinkto${ROOT_LIBRARIES}yourself,alongwithpossiblyROOT_EXE_LINKER_FLAGS(Youwillhavetoseparate_argumentsfirstbeforelinkingoryouwillgetanerroriftherearemultipleflags,likeonmacOS).Also,before6.16,youhavetomanuallyfixabuginthespacing.
Here'swhatitwouldlooklike:
#Setsupglobalsettings
include("${ROOT_USE_FILE}")
#ThisisrequiredforROOT<6.16
#string(REPLACE"-L""-L"ROOT_EXE_LINKER_FLAGS"${ROOT_EXE_LINKER_FLAGS}")
#Thisisrequiredonifthereismorethanoneflag(likeonmacOS)
separate_arguments(ROOT_EXE_LINKER_FLAGS)
add_executable(RootUseFileExampleSimpleExample.cxx)
target_link_libraries(RootUseFileExamplePUBLIC${ROOT_LIBRARIES}${ROOT_EXE_LINKER_FLAGS})
ComponentsFindROOTallowsyoutospecifycomponents.Itwilladdanythingyoulistto${ROOT_LIBRARIES},soyoumightwanttobuildyourowntargetusingthattoavoidlistingthecomponentstwice.Thisdidnotsolvedependencies;itwasanerrortolistRooFitbutnotRooFitCore.IfyoulinktoROOT::RooFitinsteadof${ROOT_LIBRARIES},thenRooFitCoreisnotrequired.
DictionarygenerationDictionarygenerationisROOT'swayofworkingaroundthemissingreflectionfeatureinC++.ItallowsROOTtolearnthedetailsofyourclasssoitcansaveit,showmethodsintheClinginterpreter,etc.You'llneedthreethingsinyoursourcecodetomakeitworkforclasses:
YourclassdefinitionshouldendwithClassDef(MyClassName,1)YourclassimplementationshouldhaveClassImp(MyClassName)initYoushouldhaveafilewithanamethatendswithLinkDef.h
TheLinkDef.hfilefollowsaspecificformulaandtellsROOTwhatpartstogeneratedictionariesfor.
Togenerate,youshouldincludethefollowinginyourCMakeLists:
include("${ROOT_DIR}/modules/RootNewMacros.cmake")
#UncommentforROOTversionsthan6.16
#Theybreakifnothingisintheglobalincludelist!
#include_directories(ROOT_BUG)
ThesecondlineisduetoabugintheNewMacrosfilethatcausesdictionarygenerationtofailifthereisnotatleastoneglobalincludedirectoryoraincfolder.HereI'mincludinganon-existentdirectoryjusttomakeitwork.ThereisnoROOT_BUGdirectory.
Togenerateafile:
root_generate_dictionary(G__ExampleExample.hLINKDEFExampleLinkDef.h)
ROOT
69
Thefinalargument,listedafterLINKDEF,musthaveanamethatendsinLinkDef.h.Thiscommandwillcreatethreefiles.IfyoustartedoutputnamewithG__,thatwillberemovedfromthename,otherwiseitwillusethenamegiven;thismustmatchthefinaloutputlibrarynameyouwillsoonbecreating.Assumingthisis${NAME}:
${NAME}.cxx:Thisfileshouldbeincludedinyoursourceswhenyoumakethelibrary.lib{NAME}.rootmap(G__prefixremoved):Therootmapfileinplaintextlib{NAME}_rdict.pcm(G__prefixremoved):AROOTfile
Thefinaltwooutputfilesmustsitnexttothelibraryoutput.ThisisdonebycheckingCMAKE_LIBRARY_OUTPUT_DIRECTORY(itwillnotpickuplocaltargetsettings).Ifyouhavealibdirsetbutyoudon'thave(global)installlocationsset,you'llalsoneedtosetARG_NOINSTALLtoTRUE.
UsingOldROOTIfyoureallyhavetouseolderROOT,you'llneedsomethinglikethis:
#ROOTtargetsaremissingincludesandflagsinROOT6.10and6.12
set_property(TARGETROOT::CorePROPERTY
INTERFACE_INCLUDE_DIRECTORIES"${ROOT_INCLUDE_DIRS}")
#EarlyROOTdoesnotincludetheflagsrequiredontargets
add_library(ROOT::Flags_CXXIMPORTEDINTERFACE)
#ROOT6.14andearlierhaveaspacingbuginthelinkerflags
string(REPLACE"-L""-L"ROOT_EXE_LINKER_FLAGS"${ROOT_EXE_LINKER_FLAGS}")
#FixforROOT_CXX_FLAGSnotactuallybeingaCMakelist
separate_arguments(ROOT_CXX_FLAGS)
set_property(TARGETROOT::Flags_CXXAPPENDPROPERTY
INTERFACE_COMPILE_OPTIONS${ROOT_CXX_FLAGS})
#Adddefinitions
separate_arguments(ROOT_DEFINITIONS)
foreach(_flag${ROOT_EXE_LINKER_FLAG_LIST})
#Remove-Dor/Difpresent
string(REGEXREPLACE[=[^[-//]D]=]""_flag${_flag})
set_property(TARGETROOT::FlagsAPPENDPROPERTYINTERFACE_LINK_LIBRARIES${_flag})
endforeach()
#Thisalsofixesabuginthelinkerflags
separate_arguments(ROOT_EXE_LINKER_FLAGS)
set_property(TARGETROOT::Flags_CXXAPPENDPROPERTY
INTERFACE_LINK_LIBRARIES${ROOT_EXE_LINKER_FLAGS})
#MakesureyoulinkwithROOT::Flags_CXXtoo!
ROOT
70
ASimpleROOTProjectThisisaminimalexampleofaROOTprojectusingtheUseFilesystemandwithoutadictionary.
examples/root-usefile/CMakeLists.txt
cmake_minimum_required(VERSION3.1...3.16)
project(RootUseFileExampleLANGUAGESCXX)
find_package(ROOT6.16CONFIGREQUIRED)
#Setsupglobalsettings
include("${ROOT_USE_FILE}")
#ThisisrequiredforROOT<6.16
#string(REPLACE"-L""-L"ROOT_EXE_LINKER_FLAGS"${ROOT_EXE_LINKER_FLAGS}")
#Thisisrequiredonifthereismorethanoneflag(likeonmacOS)
separate_arguments(ROOT_EXE_LINKER_FLAGS)
add_executable(RootUseFileExampleSimpleExample.cxx)
target_link_libraries(RootUseFileExamplePUBLIC${ROOT_LIBRARIES}${ROOT_EXE_LINKER_FLAGS})
examples/root-usefile/SimpleExample.cxx
#include<TLorentzVector.h>
intmain(){
TLorentzVectorv(1,2,3,4);
v.Print();
return0;
}
UseFileExample
71
ASimpleROOTProjectThisisaminimalexampleofaROOTprojectusingthetargetsystemandwithoutadictionary.
examples/root-simple/CMakeLists.txt
cmake_minimum_required(VERSION3.1...3.16)
project(RootSimpleExampleLANGUAGESCXX)
#FindingtheROOTpackage
find_package(ROOT6.16CONFIGREQUIRED)
#AddinganexecutableprogramandlinkingtoneededROOTlibraries
add_executable(RootSimpleExampleSimpleExample.cxx)
target_link_libraries(RootSimpleExamplePUBLICROOT::Physics)
examples/root-simple/SimpleExample.cxx
#include<TLorentzVector.h>
intmain(){
TLorentzVectorv(1,2,3,4);
v.Print();
return0;
}
SimpleExample
72
DictionaryExampleThisisanexampleofbuildingamodulethatincludesadictionaryinCMake.InsteadofusingtheROOTsuggestedflags,wewillmanuallyaddthreadingviafind_package,whichistheonlyimportantflaginthelistonmostsystems.
examples/root-dict/CMakeLists.txt
cmake_minimum_required(VERSION3.4...3.16)
project(RootDictExampleLANGUAGESCXX)
set(CMAKE_CXX_STANDARD11)
set(CMAKE_CXX_STANDARD_REQUIREDON)
set(CMAKE_CXX_EXTENSIONSOFF)
set(CMAKE_PLATFORM_INDEPENDENT_CODEON)
find_package(ROOTCONFIGREQUIRED)
include("${ROOT_DIR}/modules/RootNewMacros.cmake")
root_generate_dictionary(G__DictExampleDictExample.hLINKDEFDictLinkDef.h)
add_library(DictExampleSHAREDDictExample.cxxDictExample.hG__DictExample.cxx)
target_include_directories(DictExamplePUBLIC"${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(DictExamplePUBLICROOT::Core)
SupportingfilesThisisjustasimple-as-possibleclassdefinition,withonemethod:
examples/root-dict/DictExample.cxx
#include"DictExample.h"
Double_tSimple::GetX()const{returnx;}
ClassImp(Simple)
examples/root-dict/DictExample.h
#pragmaonce
#include<TROOT.h>
classSimple{
Double_tx;
public:
Simple():x(2.5){}
Double_tGetX()const;
ClassDef(Simple,1)
};
WeneedaLinkDef.h,aswell.
examples/root-dict/DictLinkDef.h
DictionaryExample
73
//See:https://root.cern.ch/selecting-dictionary-entries-linkdefh
#ifdef__CINT__
#pragmalinkoffallglobals;
#pragmalinkoffallclasses;
#pragmalinkoffallfunctions;
#pragmalinkC++nestedclasses;
#pragmalinkC++classSimple+;
#endif
TestingitThisisanexampleofamacrothatteststhecorrectgenerationfromthefileslistedabove.
examples/root-dict/CheckLoad.C
{
gSystem->Load("libDictExample");
Simples;
cout<<s.GetX()<<endl;
TFile*_file=TFile::Open("tmp.root","RECREATE");
gDirectory->WriteObject(&s,"MyS");
Simple*MyS=nullptr;
gDirectory->GetObject("MyS",MyS);
cout<<MyS->GetX()<<endl;
_file->Close();
}
DictionaryExample
74
Minuit2Minuit2isavailableinstandalonemode,foruseincaseswhereROOTiseithernotavailableornotbuiltwithMinuit2enabled.Thiswillcoverrecommendedusages,aswellassomeaspectsofthedesign.
Usage
Minuit2canbeusedinanyofthestandardCMakeways,eitherfromtheROOTsourceorfromastandalonesourcedistribution:
#CheckforMinuit2inROOTifyouwant
#andthenlinktoROOT::Minuit2instead
add_subdirectory(minuit2)#orroot/math/minuit2
#OR
find_package(Minuit2CONFIG)#Eitherbuildorinstall
target_link_libraries(MyProgramPRIVATEMinuit2::Minuit2)
Development
Minuit2isagoodexampleofpotentialsolutionstotheproblemofintegratingamodern(CMake3.1+)buildintoanexistingframework.
TohandlethetwodifferentCMakesystems,themainCMakeLists.txtdefinescommonoptions,thencallsaStandalone.cmakefileifthisisnotbuildingaspartofROOT.
ThehardestpartintheROOTcaseisthatMinuit2requiresfilesthatareoutsidethemath/minuit2directory.Thiswassolvedbyaddingacopy_standalone.cmakefilewithafunctionthattakesafilenamelistandtheneitherreturnsalistoffilenamesinplaceintheoriginalsource,orcopiesfilesintothelocalsourceandreturnsalistofthenewlocations,orreturnsjustthelistofnewlocationsiftheoriginalsourcedoesnotexist(standalone).
#Copiesfilesintosourcedirectory
cmake/root/math/minuit2-Dminuit2-standalone=ON
#Makes.tar.gzfromsourcedirectory
makepackage_source
#Optional,cleanthesourcedirectory
makepurge
Thisisonlyintendedfordeveloperswantingtoproducesourcepackages-anormaluserdoesnotpassthisoptionandwillnotcreatesourcecopies.
Youcanusemakeinstallormakepackage(binarypackages)withoutaddingthisstandaloneoption,eitherfrominsidetheROOTsourceorfromastandalonepackage.
Minuit2
75