1# SCons build recipe for the GPSD project 2 3# Important targets: 4# 5# build - build the software (default) 6# dist - make distribution tarball (requires GNU tar) 7# install - install programs, libraries, and manual pages 8# uninstall - undo an install 9# 10# check - run regression and unit tests. 11# audit - run code-auditing tools 12# testbuild - test-build the code from a tarball 13# website - refresh the website 14# release - ship a release 15# 16# --clean - clean all normal build targets 17# 18# Setting the DESTDIR environment variable will prefix the install destinations 19# without changing the --prefix prefix. 20 21# Unfinished items: 22# * Out-of-directory builds: see http://www.scons.org/wiki/UsingBuildDir 23# * Coveraging mode: gcc "-coverage" flag requires a hack 24# for building the python bindings 25# * Python 3 compatibility in this recipe 26 27# Since SCons 3.0.0 forces print_function on us, it needs to be unconditional. 28# This is recognized to be a bug in SCons, but we need to live with it for now, 29# and we'll need this for eventual Python 3 compatibility, anyway. 30# Python requires this to precede any non-comment code. 31from __future__ import print_function 32 33import ast 34import functools 35import glob 36import imp # for imp.find_module('gps'), imp deprecated in 3.4 37import operator 38import os 39import pickle 40import re 41# replacement for functions from the commands module, which is deprecated. 42import subprocess 43import sys 44import time 45from distutils import sysconfig 46import SCons 47 48 49# Release identification begins here. 50# 51# Actual releases follow the normal X.Y or X.Y.Z scheme. The version 52# number in git between releases has the form X.Y~dev, when it is 53# expected that X.Y will be the next actual release. As an example, 54# when 3.20 is the last release, and 3.20.1 is the expected next 55# release, the version in git will be 3.20.1~dev. Note that ~ is used, 56# because there is some precedent, ~ is an allowed version number in 57# the Debian version rules, and it does not cause confusion with 58# whether - separates components of the package name, separates the 59# name from the version, or separates version componnents. 60# 61# Keep in sync with gps/__init__.py 62# There are about 16 files with copies of the version number; make 63# sure to update all of them. 64# 65# package version 66gpsd_version = "3.20" 67# client library version 68libgps_version_current = 25 69libgps_version_revision = 0 70libgps_version_age = 0 71libgps_version = "%d.%d.%d" % (libgps_version_current, libgps_version_age, 72 libgps_version_revision) 73# 74# Release identification ends here 75 76# Hosting information (mainly used for templating web pages) begins here 77# Each variable foo has a corresponding @FOO@ expanded in .in files. 78# There are no project-dependent URLs or references to the hosting site 79# anywhere else in the distribution; preserve this property! 80annmail = "gpsd-announce@nongnu.org" 81bugtracker = "https://gitlab.com/gpsd/gpsd/issues" 82cgiupload = "root@thyrsus.com:/var/www/cgi-bin/" 83clonerepo = "git@gitlab.com:gpsd/gpsd.git" 84devmail = "gpsd-dev@lists.nongnu.org" 85download = "http://download-mirror.savannah.gnu.org/releases/gpsd/" 86formserver = "www@thyrsus.com" 87gitrepo = "git@gitlab.com:gpsd/gpsd.git" 88ircchan = "irc://chat.freenode.net/#gpsd" 89mailman = "https://lists.nongnu.org/mailman/listinfo/" 90mainpage = "https://gpsd.io" 91projectpage = "https://gitlab.com/gpsd/gpsd" 92scpupload = "garyemiller@dl.sv.nongnu.org:/releases/gpsd/" 93sitename = "GPSD" 94sitesearch = "gpsd.io" 95tiplink = "<a href='https://www.patreon.com/esr'>" \ 96 "leave a remittance at Patreon</a>" 97tipwidget = '<p><a href="https://www.patreon.com/esr">' \ 98 'Donate here to support continuing development.</a></p>' 99usermail = "gpsd-users@lists.nongnu.org" 100webform = "http://www.thyrsus.com/cgi-bin/gps_report.cgi" 101website = "https://gpsd.io/" 102# Hosting information ends here 103 104# gpsd needs Scons version at least 2.3 105EnsureSConsVersion(2, 3, 0) 106# gpsd needs Python version at least 2.6 107EnsurePythonVersion(2, 6) 108 109 110PYTHON_SYSCONFIG_IMPORT = 'from distutils import sysconfig' 111 112# Utility productions 113 114 115def Utility(target, source, action, **kwargs): 116 target = env.Command(target=target, source=source, action=action, **kwargs) 117 env.AlwaysBuild(target) 118 env.Precious(target) 119 return target 120 121 122def UtilityWithHerald(herald, target, source, action, **kwargs): 123 if not env.GetOption('silent'): 124 action = ['@echo "%s"' % herald] + action 125 return Utility(target=target, source=source, action=action, **kwargs) 126 127 128def _getstatusoutput(cmd, nput=None, shell=True, cwd=None, env=None): 129 pipe = subprocess.Popen(cmd, shell=shell, cwd=cwd, env=env, 130 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 131 (output, errout) = pipe.communicate(input=nput) 132 status = pipe.returncode 133 return (status, output) 134 135 136def _getoutput(cmd, nput=None, shell=True, cwd=None, env=None): 137 return _getstatusoutput(cmd, nput, shell, cwd, env)[1] 138 139 140# Spawn replacement that suppresses non-error stderr 141def filtered_spawn(sh, escape, cmd, args, env): 142 proc = subprocess.Popen([sh, '-c', ' '.join(args)], 143 env=env, close_fds=True, stderr=subprocess.PIPE) 144 _, stderr = proc.communicate() 145 if proc.returncode: 146 sys.stderr.write(stderr) 147 return proc.returncode 148 149# 150# Build-control options 151# 152 153 154# without this, scons will not rebuild an existing target when the 155# source changes. 156Decider('timestamp-match') 157 158# support building with various Python versions. 159sconsign_file = '.sconsign.{}.dblite'.format(pickle.HIGHEST_PROTOCOL) 160SConsignFile(sconsign_file) 161 162# Start by reading configuration variables from the cache 163opts = Variables('.scons-option-cache') 164 165systemd_dir = '/lib/systemd/system' 166systemd = os.path.exists(systemd_dir) 167 168# Set distribution-specific defaults here 169imloads = True 170 171boolopts = ( 172 # GPS protocols 173 ("ashtech", True, "Ashtech support"), 174 ("earthmate", True, "DeLorme EarthMate Zodiac support"), 175 ("evermore", True, "EverMore binary support"), 176 ("fury", True, "Jackson Labs Fury and Firefly support"), 177 ("fv18", True, "San Jose Navigation FV-18 support"), 178 ("garmin", True, "Garmin kernel driver support"), 179 ("garmintxt", True, "Garmin Simple Text support"), 180 ("geostar", True, "Geostar Protocol support"), 181 ("greis", True, "Javad GREIS support"), 182 ("itrax", True, "iTrax hardware support"), 183 ("mtk3301", True, "MTK-3301 support"), 184 ("navcom", True, "Navcom NCT support"), 185 ("nmea0183", True, "NMEA0183 support"), 186 ("nmea2000", True, "NMEA2000/CAN support"), 187 ("oncore", True, "Motorola OnCore chipset support"), 188 ("sirf", True, "SiRF chipset support"), 189 ("skytraq", True, "Skytraq chipset support"), 190 ("superstar2", True, "Novatel SuperStarII chipset support"), 191 ("tnt", True, "True North Technologies support"), 192 ("tripmate", True, "DeLorme TripMate support"), 193 ("tsip", True, "Trimble TSIP support"), 194 ("ublox", True, "u-blox Protocol support"), 195 # Non-GPS protocols 196 ("aivdm", True, "AIVDM support"), 197 ("gpsclock", True, "GPSClock support"), 198 ("isync", True, "Spectratime iSync LNRClok/GRCLOK support"), 199 ("ntrip", True, "NTRIP support"), 200 ("oceanserver", True, "OceanServer support"), 201 ("passthrough", True, "build support for passing through JSON"), 202 ("rtcm104v2", True, "rtcm104v2 support"), 203 ("rtcm104v3", True, "rtcm104v3 support"), 204 # Time service 205 ("oscillator", True, "Disciplined oscillator support"), 206 # Export methods 207 ("dbus_export", True, "enable DBUS export support"), 208 ("shm_export", True, "export via shared memory"), 209 ("socket_export", True, "data export over sockets"), 210 # Communication 211 ("bluez", True, "BlueZ support for Bluetooth devices"), 212 ("netfeed", True, "build support for handling TCP/IP data sources"), 213 ('usb', True, "libusb support for USB devices"), 214 # Other daemon options 215 ("control_socket", True, "control socket for hotplug notifications"), 216 ("force_global", False, "force daemon to listen on all addressses"), 217 ("systemd", systemd, "systemd socket activation"), 218 # Client-side options 219 ("clientdebug", True, "client debugging support"), 220 ("libgpsmm", True, "build C++ bindings"), 221 ("ncurses", True, "build with ncurses"), 222 ("qt", True, "build Qt bindings"), 223 # Daemon options 224 ("controlsend", True, "allow gpsctl/gpsmon to change device settings"), 225 ("reconfigure", True, "allow gpsd to change device settings"), 226 ("squelch", False, "squelch gpsd_log/gpsd_hexdump to save cpu"), 227 # Build control 228 ("coveraging", False, "build with code coveraging enabled"), 229 ("debug", False, "include debug information in build"), 230 ("gpsdclients", True, "gspd client programs"), 231 ("gpsd", True, "gpsd itself"), 232 ("implicit_link", imloads, "implicit linkage is supported in shared libs"), 233 ("magic_hat", sys.platform.startswith('linux'), 234 "special Linux PPS hack for Raspberry Pi et al"), 235 ("manbuild", True, "build help in man and HTML formats"), 236 ("minimal", False, "turn off every option not set on the command line"), 237 ("nostrip", False, "don't symbol-strip binaries at link time"), 238 ("profiling", False, "build with profiling enabled"), 239 ("python", True, "build Python support and modules."), 240 ("shared", True, "build shared libraries, not static"), 241 ("timeservice", False, "time-service configuration"), 242 ("xgps", True, "include xgps and xgpsspeed."), 243 # Test control 244 ("slow", False, "run tests with realistic (slow) delays"), 245) 246 247# now step on the boolopts just read from '.scons-option-cache' 248for (name, default, helpd) in boolopts: 249 opts.Add(BoolVariable(name, helpd, default)) 250 251# Gentoo, Fedora, opensuse systems use uucp for ttyS* and ttyUSB* 252if os.path.exists("/etc/gentoo-release"): 253 def_group = "uucp" 254else: 255 def_group = "dialout" 256 257nonboolopts = ( 258 ("gpsd_group", def_group, "privilege revocation group"), 259 ("gpsd_user", "nobody", "privilege revocation user",), 260 ("max_clients", '64', "maximum allowed clients"), 261 ("max_devices", '4', "maximum allowed devices"), 262 ("prefix", "/usr/local", "installation directory prefix"), 263 ("python_coverage", "coverage run", "coverage command for Python progs"), 264 ("python_libdir", "", "Python module directory prefix"), 265 ("qt_versioned", "", "version for versioned Qt"), 266 ("sysroot", "", "cross-development system root"), 267 ("target", "", "cross-development target"), 268 ("target_python", "python", "target Python version as command"), 269) 270 271# now step on the non boolopts just read from '.scons-option-cache' 272for (name, default, helpd) in nonboolopts: 273 opts.Add(name, helpd, default) 274 275pathopts = ( 276 ("bindir", "bin", "application binaries directory"), 277 ("docdir", "share/doc", "documents directory"), 278 ("includedir", "include", "header file directory"), 279 ("libdir", "lib", "system libraries"), 280 ("mandir", "share/man", "manual pages directory"), 281 ("pkgconfig", "$libdir/pkgconfig", "pkgconfig file directory"), 282 ("sbindir", "sbin", "system binaries directory"), 283 ("sysconfdir", "etc", "system configuration directory"), 284 ("udevdir", "/lib/udev", "udev rules directory"), 285) 286 287# now step on the path options just read from '.scons-option-cache' 288for (name, default, helpd) in pathopts: 289 opts.Add(PathVariable(name, helpd, default, PathVariable.PathAccept)) 290 291# 292# Environment creation 293# 294import_env = ( 295 # Variables used by programs invoked during the build 296 "DISPLAY", # Required for dia to run under scons 297 "GROUPS", # Required by gpg 298 "HOME", # Required by gpg 299 "LOGNAME", # LOGNAME is required for the flocktest production. 300 'PATH', # Required for ccache and Coverity scan-build 301 'CCACHE_DIR', # Required for ccache 302 'CCACHE_RECACHE', # Required for ccache (probably there are more) 303 # pkg-config (required for crossbuilds at least, and probably pkgsrc) 304 'PKG_CONFIG_LIBDIR', 305 'PKG_CONFIG_PATH', 306 'PKG_CONFIG_SYSROOT_DIR', 307 # Variables for specific packaging/build systems 308 "MACOSX_DEPLOYMENT_TARGET", # MacOSX 10.4 (and probably earlier) 309 'STAGING_DIR', # OpenWRT and CeroWrt 310 'STAGING_PREFIX', # OpenWRT and CeroWrt 311 'CWRAPPERS_CONFIG_DIR', # pkgsrc 312 # Variables used in testing 313 'WRITE_PAD', # So we can test WRITE_PAD values on the fly. 314) 315 316envs = {} 317for var in import_env: 318 if var in os.environ: 319 envs[var] = os.environ[var] 320envs["GPSD_HOME"] = os.getcwd() 321 322env = Environment(tools=["default", "tar", "textfile"], options=opts, ENV=envs) 323 324# Minimal build turns off every option not set on the command line, 325if ARGUMENTS.get('minimal'): 326 for (name, default, helpd) in boolopts: 327 # Ensure gpsd and gpsdclients are always enabled unless explicitly 328 # turned off. 329 if ((default is True and 330 not ARGUMENTS.get(name) and 331 name not in ("gpsd", "gpsdclients"))): 332 env[name] = False 333 334# Time-service build = stripped-down with some diagnostic tools 335if ARGUMENTS.get('timeservice'): 336 timerelated = ("gpsd", 337 "ipv6", 338 "magic_hat", 339 "mtk3301", # For the Adafruit HAT 340 "ncurses", 341 "nmea0183", # For generic hats of unknown type. 342 "oscillator", 343 "socket_export", 344 "ublox", # For the Uputronics board 345 ) 346 for (name, default, helpd) in boolopts: 347 if ((default is True and 348 not ARGUMENTS.get(name) and 349 name not in timerelated)): 350 env[name] = False 351 352# Many drivers require NMEA0183 - in case we select timeserver/minimal 353# followed by one of these. 354for driver in ('ashtech', 355 'earthmate', 356 'fury', 357 'fv18', 358 'gpsclock', 359 'mtk3301', 360 'oceanserver', 361 'skytraq', 362 'tnt', 363 'tripmate', ): 364 if env[driver]: 365 env['nmea0183'] = True 366 break 367 368 369# iSync uses ublox underneath, so we force to enable it 370if env['isync']: 371 env['ublox'] = True 372 373opts.Save('.scons-option-cache', env) 374 375for (name, default, helpd) in pathopts: 376 env[name] = env.subst(env[name]) 377 378env['VERSION'] = gpsd_version 379env['SC_PYTHON'] = sys.executable # Path to SCons Python 380 381# Set defaults from environment. Note that scons doesn't cope well 382# with multi-word CPPFLAGS/LDFLAGS/SHLINKFLAGS values; you'll have to 383# explicitly quote them or (better yet) use the "=" form of GNU option 384# settings. 385# Scons also uses different internal names than most other build-systems. 386# So we rely on MergeFlags/ParseFlags to do the right thing for us. 387env['STRIP'] = "strip" 388env['PKG_CONFIG'] = "pkg-config" 389for i in ["AR", "CC", "CXX", "LD", 390 "PKG_CONFIG", "STRIP", "TAR"]: 391 if i in os.environ: 392 j = i 393 if i == "LD": 394 i = "SHLINK" 395 env[i] = os.getenv(j) 396for i in ["ARFLAGS", "CFLAGS", "CXXFLAGS", "LDFLAGS", "SHLINKFLAGS", 397 "CPPFLAGS", "CCFLAGS", "LINKFLAGS"]: 398 if i in os.environ: 399 env.MergeFlags(Split(os.getenv(i))) 400 401 402# Keep scan-build options in the environment 403for key, value in os.environ.items(): 404 if key.startswith('CCC_'): 405 env.Append(ENV={key: value}) 406 407# Placeholder so we can kluge together something like VPATH builds. 408# $SRCDIR replaces occurrences for $(srcdir) in the autotools build. 409# scons can get confused if this is not a full path 410env['SRCDIR'] = os.getcwd() 411 412# We may need to force slow regression tests to get around race 413# conditions in the pty layer, especially on a loaded machine. 414if env["slow"]: 415 env['REGRESSOPTS'] = "-S" 416else: 417 env['REGRESSOPTS'] = "" 418 419if env.GetOption("silent"): 420 env['REGRESSOPTS'] += " -Q" 421 422 423def announce(msg): 424 if not env.GetOption("silent"): 425 print(msg) 426 427 428# DESTDIR environment variable means user prefix the installation root. 429DESTDIR = os.environ.get('DESTDIR', '') 430 431 432def installdir(idir, add_destdir=True): 433 # use os.path.join to handle absolute paths properly. 434 wrapped = os.path.join(env['prefix'], env[idir]) 435 if add_destdir: 436 wrapped = os.path.normpath(DESTDIR + os.path.sep + wrapped) 437 wrapped.replace("/usr/etc", "/etc") 438 wrapped.replace("/usr/lib/systemd", "/lib/systemd") 439 return wrapped 440 441 442# Honor the specified installation prefix in link paths. 443if env["sysroot"]: 444 env.Prepend(LIBPATH=[env["sysroot"] + installdir('libdir', 445 add_destdir=False)]) 446 447# Give deheader a way to set compiler flags 448if 'MORECFLAGS' in os.environ: 449 env.Append(CFLAGS=Split(os.environ['MORECFLAGS'])) 450 451# Don't change CCFLAGS if already set by environment. 452if 'CCFLAGS' in os.environ: 453 announce('Warning: CCFLAGS from environment overriding scons settings') 454else: 455 # Should we build with profiling? 456 if env['profiling']: 457 env.Append(CCFLAGS=['-pg']) 458 env.Append(LDFLAGS=['-pg']) 459 # Should we build with coveraging? 460 if env['coveraging']: 461 env.Append(CFLAGS=['-coverage']) 462 env.Append(LDFLAGS=['-coverage']) 463 env.Append(LINKFLAGS=['-coverage']) 464 # Should we build with debug symbols? 465 if env['debug']: 466 env.Append(CCFLAGS=['-g3']) 467 # Should we build with optimisation? 468 if env['debug'] or env['coveraging']: 469 env.Append(CCFLAGS=['-O0']) 470 else: 471 env.Append(CCFLAGS=['-O2']) 472 473# Cross-development 474 475devenv = (("ADDR2LINE", "addr2line"), 476 ("AR", "ar"), 477 ("AS", "as"), 478 ("CXX", "c++"), 479 ("CXXFILT", "c++filt"), 480 ("CPP", "cpp"), 481 ("GXX", "g++"), 482 ("CC", "gcc"), 483 ("GCCBUG", "gccbug"), 484 ("GCOV", "gcov"), 485 ("GPROF", "gprof"), 486 ("LD", "ld"), 487 ("NM", "nm"), 488 ("OBJCOPY", "objcopy"), 489 ("OBJDUMP", "objdump"), 490 ("RANLIB", "ranlib"), 491 ("READELF", "readelf"), 492 ("SIZE", "size"), 493 ("STRINGS", "strings"), 494 ("STRIP", "strip")) 495 496if env['target']: 497 for (name, toolname) in devenv: 498 env[name] = env['target'] + '-' + toolname 499 500if env['sysroot']: 501 env.MergeFlags({"CFLAGS": ["--sysroot=%s" % env['sysroot']]}) 502 env.MergeFlags({"LINKFLAGS": ["--sysroot=%s" % env['sysroot']]}) 503 504 505# Build help 506def cmp(a, b): 507 return (a > b) - (a < b) 508 509 510Help("""Arguments may be a mixture of switches and targets in any order. 511Switches apply to the entire build regardless of where they are in the order. 512Important switches include: 513 514 prefix=/usr probably what packagers want 515 516Options are cached in a file named .scons-option-cache and persist to later 517invocations. The file is editable. Delete it to start fresh. Current option 518values can be listed with 'scons -h'. 519""" + opts.GenerateHelpText(env, sort=cmp)) 520 521# Configuration 522 523 524def CheckPKG(context, name): 525 context.Message('Checking pkg-config for %s... ' % name) 526 ret = context.TryAction('%s --exists \'%s\'' 527 % (env['PKG_CONFIG'], name))[0] 528 context.Result(ret) 529 return ret 530 531 532# Stylesheet URLs for making HTML and man pages from DocBook XML. 533docbook_url_stem = 'http://docbook.sourceforge.net/release/xsl/current/' 534docbook_man_uri = docbook_url_stem + 'manpages/docbook.xsl' 535docbook_html_uri = docbook_url_stem + 'html/docbook.xsl' 536 537 538def CheckXsltproc(context): 539 context.Message('Checking that xsltproc can make man pages... ') 540 ofp = open("man/xmltest.xml", "w") 541 ofp.write(''' 542 <refentry id="foo.1"> 543 <refmeta> 544 <refentrytitle>foo</refentrytitle> 545 <manvolnum>1</manvolnum> 546 <refmiscinfo class='date'>9 Aug 2004</refmiscinfo> 547 </refmeta> 548 <refnamediv id='name'> 549 <refname>foo</refname> 550 <refpurpose>check man page generation from docbook source</refpurpose> 551 </refnamediv> 552 </refentry> 553''') 554 ofp.close() 555 probe = ("xsltproc --encoding UTF-8 --output man/foo.1 --nonet " 556 "--noout '%s' man/xmltest.xml" % (docbook_man_uri,)) 557 ret = context.TryAction(probe)[0] 558 os.remove("man/xmltest.xml") 559 if os.path.exists("man/foo.1"): 560 os.remove("man/foo.1") 561 else: 562 # failed to create output 563 ret = False 564 context.Result(ret) 565 return ret 566 567 568def CheckCompilerOption(context, option): 569 context.Message('Checking if compiler accepts %s... ' % (option,)) 570 old_CFLAGS = context.env['CFLAGS'][:] # Get a *copy* of the old list 571 context.env.Append(CFLAGS=option) 572 ret = context.TryLink(""" 573 int main(int argc, char **argv) { 574 (void) argc; (void) argv; 575 return 0; 576 } 577 """, '.c') 578 if not ret: 579 context.env.Replace(CFLAGS=old_CFLAGS) 580 context.Result(ret) 581 return ret 582 583 584def CheckHeaderDefines(context, file, define): 585 context.Message('Checking if %s supplies %s... ' % (file, define)) 586 ret = context.TryLink(""" 587 #include <%s> 588 #ifndef %s 589 #error %s is not defined 590 #endif 591 int main(int argc, char **argv) { 592 (void) argc; (void) argv; 593 return 0; 594 } 595 """ % (file, define, define), '.c') 596 context.Result(ret) 597 return ret 598 599 600def CheckSizeOf(context, type): 601 """Check sizeof 'type'""" 602 context.Message('Checking size of ' + type + '... ') 603 604 program = """ 605#include <stdlib.h> 606#include <stdio.h> 607 608/* 609 * The CheckSizeOf function does not have a way for the caller to 610 * specify header files to be included to provide the type being 611 * checked. As a workaround until that is remedied, include the 612 * header required for time_t, which is the sole current use of this 613 * function. 614 */ 615#include <time.h> 616 617int main() { 618 printf("%d", (int)sizeof(""" + type + """)); 619 return 0; 620} 621""" 622 623 # compile it 624 ret = context.TryCompile(program, '.c') 625 if 0 == ret: 626 announce('ERROR: TryCompile failed\n') 627 # fall back to sizeof(time_t) is 8 628 return '8' 629 630 # run it 631 ret = context.TryRun(program, '.c') 632 context.Result(ret[0]) 633 return ret[1] 634 635 636def CheckCompilerDefines(context, define): 637 context.Message('Checking if compiler supplies %s... ' % (define,)) 638 ret = context.TryLink(""" 639 #ifndef %s 640 #error %s is not defined 641 #endif 642 int main(int argc, char **argv) { 643 (void) argc; (void) argv; 644 return 0; 645 } 646 """ % (define, define), '.c') 647 context.Result(ret) 648 return ret 649 650# Check if this compiler is C11 or better 651 652 653def CheckC11(context): 654 context.Message('Checking if compiler is C11... ') 655 ret = context.TryLink(""" 656 #if (__STDC_VERSION__ < 201112L) 657 #error Not C11 658 #endif 659 int main(int argc, char **argv) { 660 (void) argc; (void) argv; 661 return 0; 662 } 663 """, '.c') 664 context.Result(ret) 665 return ret 666 667 668def GetPythonValue(context, name, imp, expr, brief=False): 669 context.Message('Obtaining Python %s... ' % name) 670 context.sconf.cached = 0 # Avoid bogus "(cached)" 671 if not env['target_python']: 672 status, value = 0, str(eval(expr)) 673 else: 674 command = [target_python_path, '-c', '%s; print(%s)' % (imp, expr)] 675 try: 676 status, value = _getstatusoutput(command, shell=False) 677 except OSError: 678 status = -1 679 if status == 0: 680 value = value.strip() 681 else: 682 value = '' 683 announce('Python command "%s" failed - disabling Python.\n' 684 'Python components will NOT be installed' % 685 command[2]) 686 env['python'] = False 687 context.Result('failed' if status else 'ok' if brief else value) 688 return value 689 690 691def GetLoadPath(context): 692 context.Message("Getting system load path... ") 693 694 695cleaning = env.GetOption('clean') 696helping = env.GetOption('help') 697 698# Always set up LIBPATH so that cleaning works properly. 699env.Prepend(LIBPATH=[os.path.realpath(os.curdir)]) 700 701# from scons 3.0.5, any changes to env after this, until after 702# config.Finish(), will be lost. Use config.env until then. 703 704# CheckXsltproc works, but result is incorrectly saved as "no" 705config = Configure(env, custom_tests={ 706 'CheckC11': CheckC11, 707 'CheckCompilerDefines': CheckCompilerDefines, 708 'CheckCompilerOption': CheckCompilerOption, 709 'CheckHeaderDefines': CheckHeaderDefines, 710 'CheckPKG': CheckPKG, 711 'CheckSizeOf': CheckSizeOf, 712 'CheckXsltproc': CheckXsltproc, 713 'GetPythonValue': GetPythonValue, 714 }) 715 716# Use print, rather than announce, so we see it in -s mode. 717print("This system is: %s" % sys.platform) 718 719libgps_flags = [] 720if cleaning or helping: 721 bluezflags = [] 722 confdefs = [] 723 dbusflags = [] 724 htmlbuilder = False 725 manbuilder = False 726 ncurseslibs = [] 727 rtlibs = [] 728 mathlibs = [] 729 tiocmiwait = True # For cleaning, which works on any OS 730 usbflags = [] 731else: 732 733 # OS X aliases gcc to clang 734 # clang accepts -pthread, then warns it is unused. 735 if not config.CheckCC(): 736 announce("ERROR: CC doesn't work") 737 738 if ((config.CheckCompilerOption("-pthread") and 739 not sys.platform.startswith('darwin'))): 740 config.env.MergeFlags("-pthread") 741 742 confdefs = ["/* gpsd_config.h generated by scons, do not hand-hack. */\n"] 743 744 confdefs.append('#ifndef GPSD_CONFIG_H\n') 745 746 confdefs.append('#define VERSION "%s"\n' % gpsd_version) 747 748 confdefs.append('#define GPSD_URL "%s"\n' % website) 749 750 # TODO: Move these into an if block only on systems with glibc. 751 # needed for isfinite(), pselect(), etc. 752 # for strnlen() before glibc 2.10 753 # glibc 2.10+ needs 200908L (or XOPEN 700+) for strnlen() 754 # on newer glibc _DEFAULT_SOURCE resets _POSIX_C_SOURCE 755 # we set it just in case 756 confdefs.append('#if !defined(_POSIX_C_SOURCE)') 757 confdefs.append('#define _POSIX_C_SOURCE 200809L') 758 confdefs.append('#endif\n') 759 # for daemon(), cfmakeraw(), strsep() and setgroups() 760 # on glibc 2.19+ 761 # may also be added by pkg_config 762 # on linux this eventually sets _USE_XOPEN 763 confdefs.append('#if !defined(_DEFAULT_SOURCE)') 764 confdefs.append('#define _DEFAULT_SOURCE') 765 confdefs.append('#endif\n') 766 767 # sys/un.h, and more, needs __USE_MISC with glibc and osX 768 # __USE_MISC is set by _DEFAULT_SOURCE or _BSD_SOURCE 769 770 # TODO: Many of these are now specified by POSIX. Check if 771 # defining _XOPEN_SOURCE is necessary, and limit to systems where 772 # it is. 773 # 500 means X/Open 1995 774 # getsid(), isascii(), nice(), putenv(), strdup(), sys/ipc.h need 500 775 # 600 means X/Open 2004 776 # Ubuntu and OpenBSD isfinite() needs 600 777 # 700 means X/Open 2008 778 # glibc 2.10+ needs 700+ for strnlen() 779 # Python.h wants 600 or 700 780 781 # removed 2 Jul 2019 to see if anything breaks... 782 # confdefs.append('#if !defined(_XOPEN_SOURCE)') 783 # confdefs.append('#define _XOPEN_SOURCE 700') 784 # confdefs.append('#endif\n') 785 # Reinstated for FreeBSD (below) 16-Aug-2019 786 787 if sys.platform.startswith('linux'): 788 # for cfmakeraw(), strsep(), etc. on CentOS 7 789 # glibc 2.19 and before 790 # sets __USE_MISC 791 confdefs.append('#if !defined(_BSD_SOURCE)') 792 confdefs.append('#define _BSD_SOURCE') 793 confdefs.append('#endif\n') 794 # for strnlen() and struct ifreq 795 # glibc before 2.10, deprecated in 2.10+ 796 confdefs.append('#if !defined(_GNU_SOURCE)') 797 confdefs.append('#define _GNU_SOURCE 1') 798 confdefs.append('#endif\n') 799 elif sys.platform.startswith('darwin'): 800 # strlcpy() and SIGWINCH need _DARWIN_C_SOURCE 801 confdefs.append('#if !defined(_DARWIN_C_SOURCE)') 802 confdefs.append('#define _DARWIN_C_SOURCE 1\n') 803 confdefs.append('#endif\n') 804 # vsnprintf() needs __DARWIN_C_LEVEL >= 200112L 805 # snprintf() needs __DARWIN_C_LEVEL >= 200112L 806 # _DARWIN_C_SOURCE forces __DARWIN_C_LEVEL to 900000L 807 # see <sys/cdefs.h> 808 809 # set internal lib versions at link time. 810 libgps_flags = ["-Wl,-current_version,%s" % libgps_version, 811 "-Wl,-compatibility_version,%s" % libgps_version, 812 "-Wl,-install_name,%s/$TARGET" % 813 installdir('libdir', add_destdir=False)] 814 elif sys.platform.startswith('freebsd') or sys.platform.startswith('dragonfly'): 815 # for isascii(), putenv(), nice(), strptime() 816 confdefs.append('#if !defined(_XOPEN_SOURCE)') 817 confdefs.append('#define _XOPEN_SOURCE 700') 818 confdefs.append('#endif\n') 819 # required to define u_int in sys/time.h 820 confdefs.append('#if !defined(_BSD_SOURCE)') 821 confdefs.append("#define _BSD_SOURCE 1\n") 822 confdefs.append('#endif\n') 823 # required to get strlcpy(), and more, from string.h 824 confdefs.append('#if !defined(__BSD_VISIBLE)') 825 confdefs.append("#define __BSD_VISIBLE 1\n") 826 confdefs.append('#endif\n') 827 elif sys.platform.startswith('openbsd'): 828 # required to define u_int in sys/time.h 829 confdefs.append('#if !defined(_BSD_SOURCE)') 830 confdefs.append("#define _BSD_SOURCE 1\n") 831 confdefs.append('#endif\n') 832 # required to get strlcpy(), and more, from string.h 833 confdefs.append('#if !defined(__BSD_VISIBLE)') 834 confdefs.append("#define __BSD_VISIBLE 1\n") 835 confdefs.append('#endif\n') 836 elif sys.platform.startswith('netbsd'): 837 # required to get strlcpy(), and more, from string.h 838 confdefs.append('#if !defined(_NETBSD_SOURCE)') 839 confdefs.append("#define _NETBSD_SOURCE 1\n") 840 confdefs.append('#endif\n') 841 842 cxx = config.CheckCXX() 843 if not cxx: 844 announce("C++ doesn't work, suppressing libgpsmm and Qt build.") 845 config.env["libgpsmm"] = False 846 config.env["qt"] = False 847 848 # define a helper function for pkg-config - we need to pass 849 # --static for static linking, too. 850 # 851 # Using "--libs-only-L --libs-only-l" instead of "--libs" avoids 852 # a superfluous "-rpath" option in some FreeBSD cases, and the resulting 853 # scons crash. 854 # However, it produces incorrect results for Qt5Network in OSX, so 855 # it can't be used unconditionally. 856 def pkg_config(pkg, shared=env['shared'], rpath_hack=False): 857 libs = '--libs-only-L --libs-only-l' if rpath_hack else '--libs' 858 if not shared: 859 libs += ' --static' 860 return ['!%s --cflags %s %s' % (env['PKG_CONFIG'], libs, pkg)] 861 862 # The actual distinction here is whether the platform has ncurses in the 863 # base system or not. If it does, pkg-config is not likely to tell us 864 # anything useful. FreeBSD does, Linux doesn't. Most likely other BSDs 865 # are like FreeBSD. 866 ncurseslibs = [] 867 if config.env['ncurses']: 868 if sys.platform.startswith('dragonfly'): 869 ncurseslibs= [ '-L/usr/local/lib', '-lncurses' ] 870 elif sys.platform.startswith('freebsd'): 871 ncurseslibs = ['-lncurses'] 872 elif sys.platform.startswith('openbsd'): 873 ncurseslibs = ['-lcurses'] 874 elif sys.platform.startswith('darwin'): 875 ncurseslibs = ['-lcurses'] 876 else: 877 announce('Turning off ncurses support, library not found.') 878 config.env['ncurses'] = False 879 880 if config.env['usb']: 881 # In FreeBSD except version 7, USB libraries are in the base system 882 if config.CheckPKG('libusb-1.0'): 883 confdefs.append("#define HAVE_LIBUSB 1\n") 884 try: 885 usbflags = pkg_config('libusb-1.0') 886 except OSError: 887 announce("pkg_config is confused about the state " 888 "of libusb-1.0.") 889 usbflags = [] 890 elif sys.platform.startswith("dragonfly"): 891 confdefs.append("#define HAVE_LIBUSB 1\n") 892 usbflags = [ "-lusb"] 893 elif sys.platform.startswith("freebsd"): 894 confdefs.append("#define HAVE_LIBUSB 1\n") 895 usbflags = ["-lusb"] 896 else: 897 confdefs.append("/* #undef HAVE_LIBUSB */\n") 898 usbflags = [] 899 else: 900 confdefs.append("/* #undef HAVE_LIBUSB */\n") 901 usbflags = [] 902 config.env["usb"] = False 903 904 if config.CheckLib('librt'): 905 confdefs.append("#define HAVE_LIBRT 1\n") 906 # System library - no special flags 907 rtlibs = ["-lrt"] 908 else: 909 confdefs.append("/* #undef HAVE_LIBRT */\n") 910 rtlibs = [] 911 912 # The main reason we check for libm explicitly is to set up the config 913 # environment for CheckFunc for sincos(). But it doesn't hurt to omit 914 # the '-lm' when it isn't appropriate. 915 if config.CheckLib('libm'): 916 mathlibs = ['-lm'] 917 else: 918 mathlibs = [] 919 920 # FreeBSD uses -lthr for pthreads 921 if config.CheckLib('libthr'): 922 confdefs.append("#define HAVE_LIBTHR 1\n") 923 # System library - no special flags 924 rtlibs += ["-lthr"] 925 else: 926 confdefs.append("/* #undef HAVE_LIBTHR */\n") 927 928 if config.env['dbus_export'] and config.CheckPKG('dbus-1'): 929 confdefs.append("#define HAVE_DBUS 1\n") 930 dbusflags = pkg_config("dbus-1") 931 config.env.MergeFlags(dbusflags) 932 else: 933 confdefs.append("/* #undef HAVE_DBUS */\n") 934 dbusflags = [] 935 if config.env["dbus_export"]: 936 announce("Turning off dbus-export support, library not found.") 937 config.env["dbus_export"] = False 938 939 if config.env['bluez'] and config.CheckPKG('bluez'): 940 confdefs.append("#define ENABLE_BLUEZ 1\n") 941 bluezflags = pkg_config('bluez') 942 else: 943 confdefs.append("/* #undef ENABLE_BLUEZ */\n") 944 bluezflags = [] 945 if config.env["bluez"]: 946 announce("Turning off Bluetooth support, library not found.") 947 config.env["bluez"] = False 948 949 # in_port_t is not defined on Android 950 if not config.CheckType("in_port_t", "#include <netinet/in.h>"): 951 announce("Did not find in_port_t typedef, assuming unsigned short int") 952 confdefs.append("typedef unsigned short int in_port_t;\n") 953 954 # SUN_LEN is not defined on Android 955 if ((not config.CheckDeclaration("SUN_LEN", "#include <sys/un.h>") and 956 not config.CheckDeclaration("SUN_LEN", "#include <linux/un.h>"))): 957 announce("SUN_LEN is not system-defined, using local definition") 958 confdefs.append("#ifndef SUN_LEN\n") 959 confdefs.append("#define SUN_LEN(ptr) " 960 "((size_t) (((struct sockaddr_un *) 0)->sun_path) " 961 "+ strlen((ptr)->sun_path))\n") 962 confdefs.append("#endif /* SUN_LEN */\n") 963 964 if config.CheckHeader(["linux/can.h"]): 965 confdefs.append("#define HAVE_LINUX_CAN_H 1\n") 966 announce("You have kernel CANbus available.") 967 else: 968 confdefs.append("/* #undef HAVE_LINUX_CAN_H */\n") 969 announce("You do not have kernel CANbus available.") 970 config.env["nmea2000"] = False 971 972 # check for C11 or better, and __STDC__NO_ATOMICS__ is not defined 973 # before looking for stdatomic.h 974 if ((config.CheckC11() and 975 not config.CheckCompilerDefines("__STDC_NO_ATOMICS__") and 976 config.CheckHeader("stdatomic.h"))): 977 confdefs.append("#define HAVE_STDATOMIC_H 1\n") 978 else: 979 confdefs.append("/* #undef HAVE_STDATOMIC_H */\n") 980 if config.CheckHeader("libkern/OSAtomic.h"): 981 confdefs.append("#define HAVE_OSATOMIC_H 1\n") 982 else: 983 confdefs.append("/* #undef HAVE_OSATOMIC_H */\n") 984 announce("No memory barriers - SHM export and time hinting " 985 "may not be reliable.") 986 987 # endian.h is required for rtcm104v2 unless the compiler defines 988 # __ORDER_BIG_ENDIAN__, __ORDER_LITTLE_ENDIAN__ and __BYTE_ORDER__ 989 if config.CheckCompilerDefines("__ORDER_BIG_ENDIAN__") \ 990 and config.CheckCompilerDefines("__ORDER_LITTLE_ENDIAN__") \ 991 and config.CheckCompilerDefines("__BYTE_ORDER__"): 992 confdefs.append("#define HAVE_BUILTIN_ENDIANNESS 1\n") 993 confdefs.append("/* #undef HAVE_ENDIAN_H */\n") 994 confdefs.append("/* #undef HAVE_SYS_ENDIAN_H */\n") 995 announce("Your compiler has built-in endianness support.") 996 else: 997 confdefs.append("/* #undef HAVE_BUILTIN_ENDIANNESS\n */") 998 if config.CheckHeader("endian.h"): 999 confdefs.append("#define HAVE_ENDIAN_H 1\n") 1000 confdefs.append("/* #undef HAVE_SYS_ENDIAN_H */\n") 1001 confdefs.append("/* #undef HAVE_MACHINE_ENDIAN_H */\n") 1002 elif config.CheckHeader("sys/endian.h"): 1003 confdefs.append("/* #undef HAVE_ENDIAN_H */\n") 1004 confdefs.append("#define HAVE_SYS_ENDIAN_H 1\n") 1005 confdefs.append("/* #undef HAVE_MACHINE_ENDIAN_H */\n") 1006 elif config.CheckHeader("machine/endian.h"): 1007 confdefs.append("/* #undef HAVE_ENDIAN_H */\n") 1008 confdefs.append("/* #undef HAVE_SYS_ENDIAN_H */\n") 1009 confdefs.append("#define HAVE_MACHINE_ENDIAN_H 1\n") 1010 else: 1011 confdefs.append("/* #undef HAVE_ENDIAN_H */\n") 1012 confdefs.append("/* #undef HAVE_SYS_ENDIAN_H */\n") 1013 confdefs.append("/* #undef HAVE_MACHINE_ENDIAN_H */\n") 1014 announce("You do not have the endian.h header file. " 1015 "RTCM V2 support disabled.") 1016 config.env["rtcm104v2"] = False 1017 1018 for hdr in ("arpa/inet", 1019 "netdb", 1020 "netinet/in", 1021 "netinet/ip", 1022 "sys/sysmacros", # for major(), on linux 1023 "sys/socket", 1024 "sys/un", 1025 "syslog", 1026 "termios", 1027 "winsock2" 1028 ): 1029 if config.CheckHeader(hdr + ".h"): 1030 confdefs.append("#define HAVE_%s_H 1\n" 1031 % hdr.replace("/", "_").upper()) 1032 elif "termios" == hdr: 1033 announce("ERROR: %s.h not found" % hdr) 1034 else: 1035 confdefs.append("/* #undef HAVE_%s_H */\n" 1036 % hdr.replace("/", "_").upper()) 1037 1038 sizeof_time_t = config.CheckSizeOf("time_t") 1039 confdefs.append("#define SIZEOF_TIME_T %s\n" % sizeof_time_t) 1040 announce("sizeof(time_t) is %s" % sizeof_time_t) 1041 if 4 >= int(sizeof_time_t): 1042 announce("WARNING: time_t is too small. It will fail in 2038") 1043 1044 # check function after libraries, because some function require libraries 1045 # for example clock_gettime() require librt on Linux glibc < 2.17 1046 for f in ("cfmakeraw", "clock_gettime", "daemon", "fcntl", "fork", 1047 "gmtime_r", "inet_ntop", "strlcat", "strlcpy", "strptime"): 1048 if config.CheckFunc(f): 1049 confdefs.append("#define HAVE_%s 1\n" % f.upper()) 1050 else: 1051 confdefs.append("/* #undef HAVE_%s */\n" % f.upper()) 1052 1053 # Apple may supply sincos() as __sincos(), or not at all 1054 if config.CheckFunc('sincos'): 1055 confdefs.append('#define HAVE_SINCOS\n') 1056 elif config.CheckFunc('__sincos'): 1057 confdefs.append('#define sincos __sincos\n#define HAVE_SINCOS\n') 1058 else: 1059 confdefs.append('/* #undef HAVE_SINCOS */\n') 1060 1061 if config.CheckHeader(["sys/types.h", "sys/time.h", "sys/timepps.h"]): 1062 confdefs.append("#define HAVE_SYS_TIMEPPS_H 1\n") 1063 kpps = True 1064 else: 1065 kpps = False 1066 if config.env["magic_hat"]: 1067 announce("Forcing magic_hat=no since RFC2783 API is unavailable") 1068 config.env["magic_hat"] = False 1069 tiocmiwait = config.CheckHeaderDefines("sys/ioctl.h", "TIOCMIWAIT") 1070 if not tiocmiwait and not kpps: 1071 announce("Neither TIOCMIWAIT nor RFC2783 API is available)") 1072 if config.env["timeservice"]: 1073 announce("ERROR: timeservice specified, but no PPS available") 1074 Exit(1) 1075 1076 # Map options to libraries required to support them that might be absent. 1077 optionrequires = { 1078 "bluez": ["libbluetooth"], 1079 "dbus_export": ["libdbus-1"], 1080 } 1081 1082 keys = list(map(lambda x: (x[0], x[2]), boolopts)) \ 1083 + list(map(lambda x: (x[0], x[2]), nonboolopts)) \ 1084 + list(map(lambda x: (x[0], x[2]), pathopts)) 1085 keys.sort() 1086 for (key, helpd) in keys: 1087 value = config.env[key] 1088 if value and key in optionrequires: 1089 for required in optionrequires[key]: 1090 if not config.CheckLib(required): 1091 announce("%s not found, %s cannot be enabled." 1092 % (required, key)) 1093 value = False 1094 break 1095 1096 confdefs.append("/* %s */" % helpd) 1097 if isinstance(value, bool): 1098 if value: 1099 confdefs.append("#define %s_ENABLE 1\n" % key.upper()) 1100 else: 1101 confdefs.append("/* #undef %s_ENABLE */\n" % key.upper()) 1102 elif value in (0, "", "(undefined)"): 1103 confdefs.append("/* #undef %s */\n" % key.upper()) 1104 else: 1105 if value.isdigit(): 1106 confdefs.append("#define %s %s\n" % (key.upper(), value)) 1107 else: 1108 confdefs.append("#define %s \"%s\"\n" % (key.upper(), value)) 1109 1110 # Simplifies life on hackerboards like the Raspberry Pi 1111 if config.env['magic_hat']: 1112 confdefs.append('''\ 1113/* Magic device which, if present, means to grab a static /dev/pps0 for KPPS */ 1114#define MAGIC_HAT_GPS "/dev/ttyAMA0" 1115/* Generic device which, if present, means: */ 1116/* to grab a static /dev/pps0 for KPPS */ 1117#define MAGIC_LINK_GPS "/dev/gpsd0" 1118''') 1119 1120 confdefs.append('''\ 1121 1122#define GPSD_CONFIG_H 1123#endif /* GPSD_CONFIG_H */ 1124''') 1125 1126 manbuilder = htmlbuilder = None 1127 if config.env['manbuild']: 1128 if config.CheckXsltproc(): 1129 build = ("xsltproc --encoding UTF-8 --output $TARGET" 1130 " --nonet %s $SOURCE") 1131 htmlbuilder = build % docbook_html_uri 1132 manbuilder = build % docbook_man_uri 1133 elif WhereIs("xmlto"): 1134 xmlto = "xmlto -o `dirname $TARGET` %s $SOURCE" 1135 htmlbuilder = xmlto % "html-nochunks" 1136 manbuilder = xmlto % "man" 1137 else: 1138 announce("Neither xsltproc nor xmlto found, documentation " 1139 "cannot be built.") 1140 else: 1141 announce("Build of man and HTML documentation is disabled.") 1142 if manbuilder: 1143 # 18.2. Attaching a Builder to a Construction Environment 1144 config.env.Append(BUILDERS={"Man": Builder(action=manbuilder, 1145 src_suffix=".xml")}) 1146 config.env.Append(BUILDERS={"HTML": Builder(action=htmlbuilder, 1147 src_suffix=".xml", 1148 suffix=".html")}) 1149 1150 # Determine if Qt network libraries are present, and 1151 # if not, force qt to off 1152 if config.env["qt"]: 1153 qt_net_name = 'Qt%sNetwork' % config.env["qt_versioned"] 1154 qt_network = config.CheckPKG(qt_net_name) 1155 if not qt_network: 1156 config.env["qt"] = False 1157 announce('Turning off Qt support, library not found.') 1158 1159 # If supported by the compiler, enable all warnings except uninitialized 1160 # and missing-field-initializers, which we can't help triggering because 1161 # of the way some of the JSON-parsing code is generated. 1162 # Also not including -Wcast-qual and -Wimplicit-function-declaration, 1163 # because we can't seem to keep scons from passing these to g++. 1164 # 1165 # Do this after the other config checks, to keep warnings out of them. 1166 for option in ('-Wall', 1167 '-Wcast-align', 1168 '-Wextra', 1169 # -Wimplicit-fallthrough same as 1170 # -Wimplicit-fallthrough=3, except osX hates the 1171 # second flavor 1172 '-Wimplicit-fallthrough', 1173 '-Wmissing-declarations', 1174 '-Wmissing-prototypes', 1175 '-Wno-missing-field-initializers', 1176 '-Wno-uninitialized', 1177 '-Wpointer-arith', 1178 '-Wreturn-type', 1179 '-Wstrict-prototypes', 1180 '-Wvla', 1181 ): 1182 if option not in config.env['CFLAGS']: 1183 config.CheckCompilerOption(option) 1184 1185# OSX needs to set the ID for installed shared libraries. See if this is OSX 1186# and whether we have the tool. 1187 1188# Set up configuration for target Python 1189 1190PYTHON_LIBDIR_CALL = 'sysconfig.get_python_lib()' 1191 1192PYTHON_CONFIG_NAMES = ['CC', 'CXX', 'OPT', 'BASECFLAGS', 1193 'CCSHARED', 'LDSHARED', 'SO', 'INCLUDEPY', 'LDFLAGS'] 1194PYTHON_CONFIG_QUOTED = ["'%s'" % s for s in PYTHON_CONFIG_NAMES] 1195PYTHON_CONFIG_CALL = ('sysconfig.get_config_vars(%s)' 1196 % ', '.join(PYTHON_CONFIG_QUOTED)) 1197 1198 1199# ugly hack from http://www.catb.org/esr/faqs/practical-python-porting/ 1200# handle python2/3 strings 1201def polystr(o): 1202 if isinstance(o, str): 1203 return o 1204 if isinstance(o, bytes): 1205 return str(o, encoding='latin-1') 1206 raise ValueError 1207 1208 1209if helping: 1210 1211 # If helping just get usable config info from the local Python 1212 target_python_path = '' 1213 py_config_text = str(eval(PYTHON_CONFIG_CALL)) 1214 python_libdir = str(eval(PYTHON_LIBDIR_CALL)) 1215 1216else: 1217 1218 if config.env['python'] and config.env['target_python']: 1219 try: 1220 config.CheckProg 1221 except AttributeError: # Older scons versions don't have CheckProg 1222 target_python_path = config.env['target_python'] 1223 else: 1224 target_python_path = config.CheckProg(config.env['target_python']) 1225 if not target_python_path: 1226 announce("Target Python doesn't exist - disabling Python.") 1227 config.env['python'] = False 1228 if config.env['python']: 1229 # Maximize consistency by using the reported sys.executable 1230 target_python_path = config.GetPythonValue('exe path', 1231 'import sys', 1232 'sys.executable', 1233 brief=cleaning) 1234 if config.env['python_libdir']: 1235 python_libdir = config.env['python_libdir'] 1236 else: 1237 python_libdir = config.GetPythonValue('lib dir', 1238 PYTHON_SYSCONFIG_IMPORT, 1239 PYTHON_LIBDIR_CALL, 1240 brief=cleaning) 1241 # follow FHS, put in /usr/local/libXX, not /usr/libXX 1242 # may be lib, lib32 or lib64 1243 python_libdir = polystr(python_libdir) 1244 python_libdir = python_libdir.replace("/usr/lib", 1245 "/usr/local/lib") 1246 1247 py_config_text = config.GetPythonValue('config vars', 1248 PYTHON_SYSCONFIG_IMPORT, 1249 PYTHON_CONFIG_CALL, 1250 brief=True) 1251 1252 # aiogps is only available on Python >= 3.6 1253 # FIXME check target_python, not current python 1254 if sys.version_info < (3, 6): 1255 config.env['aiogps'] = False 1256 announce("WARNING: Python too old: " 1257 "gps/aiogps.py will not be installed\n") 1258 else: 1259 config.env['aiogps'] = True 1260 1261 # check for pyserial 1262 #try: 1263 # imp.find_module('serial') 1264 # announce("Python module serial (pyserial) found.") 1265 #except ImportError: 1266 # # no pycairo, don't build xgps, xgpsspeed 1267 # announce("WARNING: Python module serial (pyserial) not found.") 1268 # config.env['xgps'] = False 1269 1270 if config.env['xgps']: 1271 # check for pycairo 1272 #try: 1273 # imp.find_module('cairo') 1274 # announce("Python module cairo (pycairo) found.") 1275 #except ImportError: 1276 # # no pycairo, don't build xgps, xgpsspeed 1277 # announce("WARNING: Python module cairo (pycairo) not found.") 1278 # config.env['xgps'] = False 1279 1280 # check for pygobject 1281 #try: 1282 # imp.find_module('gi') 1283 # announce("Python module gi (pygobject) found.") 1284 #except ImportError: 1285 # # no pygobject, don't build xgps, xgpsspeed 1286 # announce("WARNING: Python module gi (pygobject) not found.") 1287 # config.env['xgps'] = False 1288 1289 if not config.CheckPKG('gtk+-3.0'): 1290 config.env['xgps'] = False 1291 1292 1293if config.env['python']: # May have been turned off by error 1294 config.env['PYTHON'] = polystr(target_python_path) 1295 # For regress-driver 1296 config.env['ENV']['PYTHON'] = polystr(target_python_path) 1297 py_config_vars = ast.literal_eval(py_config_text.decode()) 1298 py_config_vars = [[] if x is None else x for x in py_config_vars] 1299 python_config = dict(zip(PYTHON_CONFIG_NAMES, py_config_vars)) 1300 announce(python_config) 1301 1302 1303env = config.Finish() 1304# All configuration should be finished. env can now be modified. 1305# NO CONFIG TESTS AFTER THIS POINT! 1306 1307if not (cleaning or helping): 1308 1309 # Be explicit about what we're doing. 1310 changelatch = False 1311 for (name, default, helpd) in boolopts + nonboolopts + pathopts: 1312 if env[name] != env.subst(default): 1313 if not changelatch: 1314 announce("Altered configuration variables:") 1315 changelatch = True 1316 announce("%s = %s (default %s): %s" 1317 % (name, env[name], env.subst(default), helpd)) 1318 if not changelatch: 1319 announce("All configuration flags are defaulted.") 1320 1321 # Gentoo systems can have a problem with the Python path 1322 if os.path.exists("/etc/gentoo-release"): 1323 announce("This is a Gentoo system.") 1324 announce("Adjust your PYTHONPATH to see library directories " 1325 "under /usr/local/lib") 1326 1327# Should we build the Qt binding? 1328if env["qt"] and env["shared"]: 1329 qt_env = env.Clone() 1330 qt_env.MergeFlags('-DUSE_QT') 1331 qt_env.Append(OBJPREFIX='qt-') 1332 if not (cleaning or helping): 1333 try: 1334 qt_env.MergeFlags(pkg_config(qt_net_name)) 1335 except OSError: 1336 announce("pkg_config is confused about the state of %s." 1337 % qt_net_name) 1338 qt_env = None 1339else: 1340 qt_env = None 1341 1342# Set up for Python coveraging if needed 1343if env['coveraging'] and env['python_coverage'] and not (cleaning or helping): 1344 pycov_default = opts.options[opts.keys().index('python_coverage')].default 1345 pycov_current = env['python_coverage'] 1346 pycov_list = pycov_current.split() 1347 if env.GetOption('num_jobs') > 1 and pycov_current == pycov_default: 1348 pycov_list.append('--parallel-mode') 1349 # May need absolute path to coveraging tool if 'PythonXX' is prefixed 1350 pycov_path = env.WhereIs(pycov_list[0]) 1351 if pycov_path: 1352 pycov_list[0] = pycov_path 1353 env['PYTHON_COVERAGE'] = ' '.join(pycov_list) 1354 env['ENV']['PYTHON_COVERAGE'] = ' '.join(pycov_list) 1355 else: 1356 announce('Python coverage tool not found - disabling Python coverage.') 1357 env['python_coverage'] = '' # So we see it in the options 1358 1359# Two shared libraries provide most of the code for the C programs 1360 1361# gpsd client library 1362libgps_sources = [ 1363 "ais_json.c", 1364 "bits.c", 1365 "gpsdclient.c", 1366 "gps_maskdump.c", 1367 "gpsutils.c", 1368 "hex.c", 1369 "json.c", 1370 "libgps_core.c", 1371 "libgps_dbus.c", 1372 "libgps_json.c", 1373 "libgps_shm.c", 1374 "libgps_sock.c", 1375 "netlib.c", 1376 "os_compat.c", 1377 "rtcm2_json.c", 1378 "rtcm3_json.c", 1379 "shared_json.c", 1380 "timespec_str.c", 1381] 1382 1383if env['libgpsmm']: 1384 libgps_sources.append("libgpsmm.cpp") 1385 1386# gpsd server library 1387libgpsd_sources = [ 1388 "bsd_base64.c", 1389 "crc24q.c", 1390 "driver_ais.c", 1391 "driver_evermore.c", 1392 "driver_garmin.c", 1393 "driver_garmin_txt.c", 1394 "driver_geostar.c", 1395 "driver_greis.c", 1396 "driver_greis_checksum.c", 1397 "driver_italk.c", 1398 "driver_navcom.c", 1399 "driver_nmea0183.c", 1400 "driver_nmea2000.c", 1401 "driver_oncore.c", 1402 "driver_rtcm2.c", 1403 "driver_rtcm3.c", 1404 "drivers.c", 1405 "driver_sirf.c", 1406 "driver_skytraq.c", 1407 "driver_superstar2.c", 1408 "driver_tsip.c", 1409 "driver_ubx.c", 1410 "driver_zodiac.c", 1411 "geoid.c", 1412 "gpsd_json.c", 1413 "isgps.c", 1414 "libgpsd_core.c", 1415 "matrix.c", 1416 "net_dgpsip.c", 1417 "net_gnss_dispatch.c", 1418 "net_ntrip.c", 1419 "ntpshmread.c", 1420 "ntpshmwrite.c", 1421 "packet.c", 1422 "ppsthread.c", 1423 "pseudoais.c", 1424 "pseudonmea.c", 1425 "serial.c", 1426 "subframe.c", 1427 "timebase.c", 1428] 1429 1430if not env["shared"]: 1431 def Library(env, target, sources, version, parse_flags=None): 1432 return env.StaticLibrary(target, 1433 [env.StaticObject(s) for s in sources], 1434 parse_flags=parse_flags) 1435 1436 def LibraryInstall(env, libdir, sources, version): 1437 return env.Install(libdir, sources) 1438else: 1439 def Library(env, target, sources, version, parse_flags=None): 1440 # Note: We have a possibility of getting either Object or file 1441 # list for sources, so we run through the sources and try to make 1442 # them into SharedObject instances. 1443 obj_list = [] 1444 for s in Flatten(sources): 1445 if isinstance(s, str): 1446 obj_list.append(env.SharedObject(s)) 1447 else: 1448 obj_list.append(s) 1449 return env.SharedLibrary(target=target, 1450 source=obj_list, 1451 parse_flags=parse_flags, 1452 SHLIBVERSION=version) 1453 1454 def LibraryInstall(env, libdir, sources, version): 1455 # note: osX lib name s/b libgps.VV.dylib 1456 # where VV is libgps_version_current 1457 inst = env.InstallVersionedLib(libdir, sources, SHLIBVERSION=version) 1458 return inst 1459 1460libgps_shared = Library(env=env, 1461 target="gps", 1462 sources=libgps_sources, 1463 version=libgps_version, 1464 parse_flags=rtlibs + libgps_flags) 1465env.Clean(libgps_shared, "gps_maskdump.c") 1466 1467libgps_static = env.StaticLibrary("gps_static", 1468 [env.StaticObject(s) 1469 for s in libgps_sources], rtlibs) 1470 1471static_gpsdlib = env.StaticLibrary( 1472 target="gpsd", 1473 source=[env.StaticObject(s, parse_flags=usbflags + bluezflags) 1474 for s in libgpsd_sources], 1475 parse_flags=usbflags + bluezflags) 1476 1477libraries = [libgps_shared] 1478 1479# Only attempt to create the qt library if we have shared turned on 1480# otherwise we have a mismash of objects in library 1481if qt_env: 1482 qtobjects = [] 1483 qt_flags = qt_env['CFLAGS'] 1484 for c_only in ('-Wmissing-prototypes', '-Wstrict-prototypes', 1485 '-Wmissing-declarations'): 1486 if c_only in qt_flags: 1487 qt_flags.remove(c_only) 1488 # Qt binding object files have to be renamed as they're built to avoid 1489 # name clashes with the plain non-Qt object files. This prevents the 1490 # infamous "Two environments with different actions were specified 1491 # for the same target" error. 1492 for src in libgps_sources: 1493 if src not in ('ais_json.c', 'json.c', 'libgps_json.c', 1494 'rtcm2_json.c', 'rtcm3_json.c', 'shared_json.c', 1495 'timespec_str.c'): 1496 compile_with = qt_env['CXX'] 1497 compile_flags = qt_flags 1498 else: 1499 compile_with = qt_env['CC'] 1500 compile_flags = qt_env['CFLAGS'] 1501 qtobjects.append(qt_env.SharedObject(src, 1502 CC=compile_with, 1503 CFLAGS=compile_flags)) 1504 compiled_qgpsmmlib = Library(qt_env, "Qgpsmm", qtobjects, libgps_version) 1505 libraries.append(compiled_qgpsmmlib) 1506 1507# The libraries have dependencies on system libraries 1508# libdbus appears multiple times because the linker only does one pass. 1509 1510gpsflags = mathlibs + rtlibs + dbusflags 1511gpsdflags = usbflags + bluezflags + gpsflags 1512 1513# Source groups 1514 1515gpsd_sources = [ 1516 'dbusexport.c', 1517 'gpsd.c', 1518 'shmexport.c', 1519 'timehint.c' 1520] 1521 1522if env['systemd']: 1523 gpsd_sources.append("sd_socket.c") 1524 1525gpsmon_sources = [ 1526 'gpsmon.c', 1527 'monitor_garmin.c', 1528 'monitor_italk.c', 1529 'monitor_nmea0183.c', 1530 'monitor_oncore.c', 1531 'monitor_sirf.c', 1532 'monitor_superstar2.c', 1533 'monitor_tnt.c', 1534 'monitor_ubx.c', 1535] 1536 1537# Production programs 1538 1539gpsd = env.Program('gpsd', gpsd_sources, 1540 LIBS=['gpsd', 'gps_static'], 1541 parse_flags=gpsdflags + gpsflags) 1542gpsdecode = env.Program('gpsdecode', ['gpsdecode.c'], 1543 LIBS=['gpsd', 'gps_static'], 1544 parse_flags=gpsdflags + gpsflags) 1545gpsctl = env.Program('gpsctl', ['gpsctl.c'], 1546 LIBS=['gpsd', 'gps_static'], 1547 parse_flags=gpsdflags + gpsflags) 1548gpsmon = env.Program('gpsmon', gpsmon_sources, 1549 LIBS=['gpsd', 'gps_static'], 1550 parse_flags=gpsdflags + gpsflags + ncurseslibs) 1551gpsdctl = env.Program('gpsdctl', ['gpsdctl.c'], 1552 LIBS=['gps_static'], 1553 parse_flags=gpsflags) 1554gpspipe = env.Program('gpspipe', ['gpspipe.c'], 1555 LIBS=['gps_static'], 1556 parse_flags=gpsflags) 1557gpsrinex = env.Program('gpsrinex', ['gpsrinex.c'], 1558 LIBS=['gps_static'], 1559 parse_flags=gpsflags) 1560gps2udp = env.Program('gps2udp', ['gps2udp.c'], 1561 LIBS=['gps_static'], 1562 parse_flags=gpsflags) 1563gpxlogger = env.Program('gpxlogger', ['gpxlogger.c'], 1564 LIBS=['gps_static'], 1565 parse_flags=gpsflags) 1566lcdgps = env.Program('lcdgps', ['lcdgps.c'], 1567 LIBS=['gps_static'], 1568 parse_flags=gpsflags) 1569cgps = env.Program('cgps', ['cgps.c'], 1570 LIBS=['gps_static'], 1571 parse_flags=gpsflags + ncurseslibs) 1572ntpshmmon = env.Program('ntpshmmon', ['ntpshmmon.c'], 1573 LIBS=['gpsd', 'gps_static'], 1574 parse_flags=gpsflags) 1575ppscheck = env.Program('ppscheck', ['ppscheck.c'], 1576 LIBS=['gps_static'], 1577 parse_flags=gpsflags) 1578 1579bin_binaries = [] 1580sbin_binaries = [] 1581if env["gpsd"]: 1582 sbin_binaries += [gpsd] 1583 1584if env["gpsdclients"]: 1585 sbin_binaries += [gpsdctl] 1586 bin_binaries += [ 1587 gps2udp, 1588 gpsctl, 1589 gpsdecode, 1590 gpspipe, 1591 gpsrinex, 1592 gpxlogger, 1593 lcdgps 1594 ] 1595 1596if env["timeservice"] or env["gpsdclients"]: 1597 bin_binaries += [ntpshmmon] 1598 if tiocmiwait: 1599 bin_binaries += [ppscheck] 1600 1601if env["ncurses"] and (env["timeservice"] or env["gpsdclients"]): 1602 bin_binaries += [cgps, gpsmon] 1603else: 1604 announce("WARNING: not building cgps or gpsmon") 1605 1606# Test programs - always link locally and statically 1607test_bits = env.Program('tests/test_bits', ['tests/test_bits.c'], 1608 LIBS=['gps_static']) 1609test_float = env.Program('tests/test_float', ['tests/test_float.c']) 1610test_geoid = env.Program('tests/test_geoid', ['tests/test_geoid.c'], 1611 LIBS=['gpsd', 'gps_static'], 1612 parse_flags=gpsdflags) 1613test_gpsdclient = env.Program('tests/test_gpsdclient', 1614 ['tests/test_gpsdclient.c'], 1615 LIBS=['gps_static', 'm']) 1616test_matrix = env.Program('tests/test_matrix', ['tests/test_matrix.c'], 1617 LIBS=['gpsd', 'gps_static'], 1618 parse_flags=gpsdflags) 1619test_mktime = env.Program('tests/test_mktime', ['tests/test_mktime.c'], 1620 LIBS=['gps_static'], parse_flags=mathlibs + rtlibs) 1621test_packet = env.Program('tests/test_packet', ['tests/test_packet.c'], 1622 LIBS=['gpsd', 'gps_static'], 1623 parse_flags=gpsdflags) 1624test_timespec = env.Program('tests/test_timespec', ['tests/test_timespec.c'], 1625 LIBS=['gpsd', 'gps_static'], 1626 parse_flags=gpsdflags) 1627test_trig = env.Program('tests/test_trig', ['tests/test_trig.c'], 1628 parse_flags=mathlibs) 1629# test_libgps for glibc older than 2.17 1630test_libgps = env.Program('tests/test_libgps', ['tests/test_libgps.c'], 1631 LIBS=['gps_static'], 1632 parse_flags=mathlibs + rtlibs + dbusflags) 1633 1634if not env['socket_export']: 1635 announce("test_json not building because socket_export is disabled") 1636 test_json = None 1637else: 1638 test_json = env.Program( 1639 'tests/test_json', ['tests/test_json.c'], 1640 LIBS=['gps_static'], 1641 parse_flags=mathlibs + rtlibs + usbflags + dbusflags) 1642 1643# duplicate below? 1644test_gpsmm = env.Program('tests/test_gpsmm', ['tests/test_gpsmm.cpp'], 1645 LIBS=['gps_static'], 1646 parse_flags=mathlibs + rtlibs + dbusflags) 1647testprogs = [test_bits, 1648 test_float, 1649 test_geoid, 1650 test_gpsdclient, 1651 test_libgps, 1652 test_matrix, 1653 test_mktime, 1654 test_packet, 1655 test_timespec, 1656 test_trig] 1657if env['socket_export']: 1658 testprogs.append(test_json) 1659if env["libgpsmm"]: 1660 testprogs.append(test_gpsmm) 1661 1662# Python programs 1663if not env['python']: 1664 python_built_extensions = [] 1665 python_manpages = [] 1666 python_misc = [] 1667 python_progs = [] 1668 python_targets = [] 1669else: 1670 # installed python programs 1671 python_progs = ["gegps", "gpscat", "gpsfake", "gpsprof", "ubxtool", "zerk"] 1672 python_deps = {'gpscat': 'packet'} 1673 1674 # python misc helpers and stuff 1675 python_misc = [ 1676 "gpscap.py", 1677 "gpssim.py", 1678 "jsongen.py", 1679 "maskaudit.py", 1680 "test_clienthelpers.py", 1681 "test_misc.py", 1682 "test_xgps_deps.py", 1683 "valgrind-audit.py" 1684 ] 1685 1686 if not helping and env['aiogps']: 1687 python_misc.extend(["example_aiogps.py", "example_aiogps_run"]) 1688 1689 python_manpages = { 1690 "man/gegps.1": "man/gps.xml", 1691 "man/gpscat.1": "man/gpscat.xml", 1692 "man/gpsfake.1": "man/gpsfake.xml", 1693 "man/gpsprof.1": "man/gpsprof.xml", 1694 "man/ubxtool.1": "man/ubxtool.xml", 1695 "man/zerk.1": "man/zerk.xml", 1696 } 1697 1698 if env['xgps']: 1699 python_progs.extend(["xgps", "xgpsspeed"]) 1700 python_manpages.update({ 1701 "man/xgps.1": "man/gps.xml", 1702 "man/xgpsspeed.1": "man/gps.xml", 1703 }) 1704 else: 1705 announce("WARNING: xgps and xgpsspeed will not be installed") 1706 1707 # Glob() has to be run after all buildable objects defined 1708 python_modules = Glob('gps/*.py', strings=True) 1709 1710 # Remove the aiogps module if not configured 1711 # Don't use Glob's exclude option, since it may not be available 1712 if helping or not env['aiogps']: 1713 try: 1714 python_modules.remove('gps/aiogps.py') 1715 except ValueError: 1716 pass 1717 1718 # Build Python binding 1719 # 1720 python_extensions = { 1721 "gps" + os.sep + "packet": ["crc24q.c", 1722 "driver_greis_checksum.c", 1723 "driver_rtcm2.c", 1724 "gpspacket.c", 1725 "hex.c", 1726 "isgps.c", 1727 "os_compat.c", 1728 "packet.c", 1729 ] 1730 } 1731 1732 python_env = env.Clone() 1733 # FIXME: build of python wrappers doesn't pickup flags set for coveraging, 1734 # manually add them here 1735 if env['coveraging']: 1736 python_config['BASECFLAGS'] += ' -coverage' 1737 python_config['LDFLAGS'] += ' -coverage' 1738 python_config['LDSHARED'] += ' -coverage' 1739 # in case CC/CXX was set to the scan-build wrapper, 1740 # ensure that we build the python modules with scan-build, too 1741 if env['CC'] is None or env['CC'].find('scan-build') < 0: 1742 python_env['CC'] = python_config['CC'] 1743 # As we seem to be changing compilers we must assume that the 1744 # CCFLAGS are incompatible with the new compiler. If we should 1745 # use other flags, the variable or the variable for this 1746 # should be predefined. 1747 if python_config['CC'].split()[0] != env['CC']: 1748 python_env['CCFLAGS'] = '' 1749 else: 1750 python_env['CC'] = (' '.join([env['CC']] + 1751 python_config['CC'].split()[1:])) 1752 if env['CXX'] is None or env['CXX'].find('scan-build') < 0: 1753 python_env['CXX'] = python_config['CXX'] 1754 # As we seem to be changing compilers we must assume that the 1755 # CCFLAGS or CXXFLAGS are incompatible with the new 1756 # compiler. If we should use other flags, the variable or the 1757 # variable for this should be predefined. 1758 if python_config['CXX'].split()[0] != env['CXX']: 1759 python_env['CCFLAGS'] = '' 1760 python_env['CXXFLAGS'] = '' 1761 else: 1762 python_env['CXX'] = (' '.join([env['CXX']] + 1763 python_config['CXX'].split()[1:])) 1764 1765 ldshared = python_config['LDSHARED'] 1766 ldshared = ldshared.replace('-fPIE', '') 1767 ldshared = ldshared.replace('-pie', '') 1768 python_env.Replace(SHLINKFLAGS=[], 1769 LDFLAGS=python_config['LDFLAGS'], 1770 LINK=ldshared, 1771 SHLIBPREFIX="", 1772 SHLIBSUFFIX=python_config['SO'], 1773 CPPPATH=[python_config['INCLUDEPY']], 1774 CPPFLAGS=python_config['OPT'], 1775 CFLAGS=python_config['BASECFLAGS'], 1776 CXXFLAGS=python_config['BASECFLAGS']) 1777 1778 python_objects = {} 1779 python_compiled_libs = {} 1780 for ext, sources in python_extensions.items(): 1781 python_objects[ext] = [] 1782 for src in sources: 1783 python_objects[ext].append( 1784 python_env.NoCache( 1785 python_env.SharedObject( 1786 src.split(".")[0] + '-py_' + 1787 '_'.join(['%s' % (x) for x in sys.version_info]) + 1788 python_config['SO'], src 1789 ) 1790 ) 1791 ) 1792 python_compiled_libs[ext] = python_env.SharedLibrary( 1793 ext, python_objects[ext]) 1794 1795 # Make sure we know about compiled dependencies 1796 for prog, dep in python_deps.items(): 1797 env.Depends(prog, python_compiled_libs['gps' + os.sep + dep]) 1798 1799 # Make PEP 241 Metadata 1.0. 1800 # Why not PEP 314 (V1.1) or PEP 345 (V1.2)? 1801 # V1.2 and V1.2 require a Download-URL to an installable binary 1802 python_egg_info_source = """Metadata-Version: 1.0 1803Name: gps 1804Version: %s 1805Summary: Python libraries for the gpsd service daemon 1806Home-page: %s 1807Author: the GPSD project 1808Author-email: %s 1809License: BSD 1810Keywords: GPS 1811Description: The gpsd service daemon can monitor one or more GPS devices \ 1812connected to a host computer, making all data on the location and movements \ 1813of the sensors available to be queried on TCP port 2947. 1814Platform: UNKNOWN 1815""" % (gpsd_version, website, devmail) 1816 python_egg_info = python_env.Textfile(target="gps-%s.egg-info" 1817 % (gpsd_version, ), 1818 source=python_egg_info_source) 1819 python_built_extensions = list(python_compiled_libs.values()) 1820 python_targets = python_built_extensions + [python_egg_info] 1821 1822 1823env.Command(target="packet_names.h", source="packet_states.h", 1824 action=""" 1825 rm -f $TARGET &&\ 1826 sed -e '/^ *\\([A-Z][A-Z0-9_]*\\),/s// \"\\1\",/' <$SOURCE >$TARGET &&\ 1827 chmod a-w $TARGET""") 1828 1829env.Textfile(target="gpsd_config.h", source=confdefs) 1830 1831env.Command(target="gps_maskdump.c", 1832 source=["maskaudit.py", "gps.h", "gpsd.h"], 1833 action=''' 1834 rm -f $TARGET &&\ 1835 $SC_PYTHON $SOURCE -c $SRCDIR >$TARGET &&\ 1836 chmod a-w $TARGET''') 1837 1838env.Command(target="ais_json.i", source="jsongen.py", action='''\ 1839 rm -f $TARGET &&\ 1840 $SC_PYTHON $SOURCE --ais --target=parser >$TARGET &&\ 1841 chmod a-w $TARGET''') 1842 1843generated_sources = ['packet_names.h', "ais_json.i", 1844 'gps_maskdump.c', 'revision.h', 'gpsd.php', 1845 'gpsd_config.h'] 1846 1847# Helper functions for revision hackery 1848 1849 1850def GetMtime(file): 1851 """Get mtime of given file, or 0.""" 1852 try: 1853 return os.stat(file).st_mtime 1854 except OSError: 1855 return 0 1856 1857 1858def FileList(patterns, exclusions=None): 1859 """Get list of files based on patterns, minus excluded files.""" 1860 files = functools.reduce(operator.add, map(glob.glob, patterns), []) 1861 for file in exclusions: 1862 try: 1863 files.remove(file) 1864 except ValueError: 1865 pass 1866 return files 1867 1868 1869# generate revision.h 1870if 'dev' in gpsd_version: 1871 (st, rev) = _getstatusoutput('git describe --tags') 1872 if st != 0: 1873 # Use timestamp from latest relevant file 1874 files = FileList(['*.c', '*.cpp', '*.h', '*.in', 'SConstruct'], 1875 generated_sources) 1876 timestamps = map(GetMtime, files) 1877 if timestamps: 1878 from datetime import datetime 1879 latest = datetime.fromtimestamp(sorted(timestamps)[-1]) 1880 rev = '%s-%s' % (gpsd_version, latest.isoformat()) 1881 else: 1882 rev = gpsd_version # Paranoia 1883else: 1884 rev = gpsd_version 1885revision = '''/* Automatically generated file, do not edit */ 1886#define REVISION "%s" 1887''' % (polystr(rev.strip()),) 1888env.Textfile(target="revision.h", source=[revision]) 1889 1890if env['systemd']: 1891 udevcommand = 'TAG+="systemd", ENV{SYSTEMD_WANTS}="gpsdctl@%k.service"' 1892else: 1893 udevcommand = 'RUN+="%s/gpsd.hotplug"' % (env['udevdir'], ) 1894 1895 1896# Instantiate some file templates. We'd like to use the Substfile builtin 1897# but it doesn't seem to work in scons 1.20 1898def substituter(target, source, env): 1899 substmap = ( 1900 ('@ANNOUNCE@', annmail), 1901 ('@BUGTRACKER@', bugtracker), 1902 ('@CGIUPLOAD@', cgiupload), 1903 ('@CLONEREPO@', clonerepo), 1904 ('@DATE@', time.asctime()), 1905 ('@DEVMAIL@', devmail), 1906 ('@DOWNLOAD@', download), 1907 ('@FORMSERVER@', formserver), 1908 ('@GITREPO@', gitrepo), 1909 ('@includedir@', installdir('includedir', add_destdir=False)), 1910 ('@IRCCHAN@', ircchan), 1911 ('@libdir@', installdir('libdir', add_destdir=False)), 1912 ('@LIBGPSVERSION@', libgps_version), 1913 ('@MAILMAN@', mailman), 1914 ('@MAINPAGE@', mainpage), 1915 ('@MASTER@', 'DO NOT HAND_HACK! THIS FILE IS GENERATED'), 1916 ('@prefix@', env['prefix']), 1917 ('@PROJECTPAGE@', projectpage), 1918 ('@QTVERSIONED@', env['qt_versioned']), 1919 ('@SCPUPLOAD@', scpupload), 1920 ('@SITENAME@', sitename), 1921 ('@SITESEARCH@', sitesearch), 1922 ('@TIPLINK@', tiplink), 1923 ('@TIPWIDGET@', tipwidget), 1924 ('@udevcommand@', udevcommand), 1925 ('@USERMAIL@', usermail), 1926 ('@VERSION@', gpsd_version), 1927 ('@WEBFORM@', webform), 1928 ('@WEBSITE@', website), 1929 ) 1930 1931 sfp = open(str(source[0])) 1932 content = sfp.read() 1933 sfp.close() 1934 for (s, t) in substmap: 1935 content = content.replace(s, t) 1936 m = re.search("@[A-Z]+@", content) 1937 if m and m.group(0) not in map(lambda x: x[0], substmap): 1938 print("Unknown subst token %s in %s." % (m.group(0), sfp.name), 1939 file=sys.stderr) 1940 tfp = open(str(target[0]), "w") 1941 tfp.write(content) 1942 tfp.close() 1943 1944 1945templated = glob.glob("*.in") + glob.glob("*/*.in") + glob.glob("*/*/*.in") 1946 1947# ignore files in subfolder called 'debian' - the Debian packaging 1948# tools will handle them. 1949templated = [x for x in templated if not x.startswith('debian/')] 1950 1951 1952for fn in templated: 1953 builder = env.Command(source=fn, target=fn[:-3], action=substituter) 1954 env.AddPostAction(builder, 'chmod -w $TARGET') 1955 if fn.endswith(".py.in"): 1956 env.AddPostAction(builder, 'chmod +x $TARGET') 1957 1958# Documentation 1959 1960base_manpages = { 1961 "man/cgps.1": "man/gps.xml", 1962 "man/gps.1": "man/gps.xml", 1963 "man/gps2udp.1": "man/gps2udp.xml", 1964 "man/gpsctl.1": "man/gpsctl.xml", 1965 "man/gpsd.8": "man/gpsd.xml", 1966 "man/gpsdctl.8": "man/gpsdctl.xml", 1967 "man/gpsdecode.1": "man/gpsdecode.xml", 1968 "man/gpsd_json.5": "man/gpsd_json.xml", 1969 "man/gpsinit.8": "man/gpsinit.xml", 1970 "man/gpsmon.1": "man/gpsmon.xml", 1971 "man/gpspipe.1": "man/gpspipe.xml", 1972 "man/gpsrinex.1": "man/gpsrinex.xml", 1973 "man/gpxlogger.1": "man/gpxlogger.xml", 1974 "man/lcdgps.1": "man/gps.xml", 1975 "man/libgps.3": "man/libgps.xml", 1976 "man/libgpsmm.3": "man/libgpsmm.xml", 1977 "man/libQgpsmm.3": "man/libgpsmm.xml", 1978 "man/srec.5": "man/srec.xml", 1979} 1980 1981if env["timeservice"] or env["gpsdclients"]: 1982 base_manpages.update({ 1983 "man/ntpshmmon.1": "man/ntpshmmon.xml", 1984 }) 1985 1986if tiocmiwait: 1987 base_manpages.update({ 1988 "man/ppscheck.8": "man/ppscheck.xml", 1989 }) 1990 1991all_manpages = list(base_manpages.keys()) 1992other_manpages = [ 1993 "man/gegps.1", 1994 "man/xgps.1", 1995 "man/xgpsspeed.1", 1996 ] 1997 1998if python_manpages: 1999 all_manpages += list(python_manpages.keys()) 2000 2001man_env = env.Clone() 2002if man_env.GetOption('silent'): 2003 man_env['SPAWN'] = filtered_spawn # Suppress stderr chatter 2004manpage_targets = [] 2005if manbuilder: 2006 items = list(base_manpages.items()) 2007 if python_manpages: 2008 items += list(python_manpages.items()) 2009 2010 for (man, xml) in items: 2011 manpage_targets.append(man_env.Man(source=xml, target=man)) 2012 2013# Where it all comes together 2014 2015build = env.Alias('build', 2016 [libraries, sbin_binaries, bin_binaries, python_targets, 2017 "gpsd.php", manpage_targets, 2018 "libgps.pc", "gpsd.rules"]) 2019 2020if qt_env: 2021 # duplicate above? 2022 test_qgpsmm = env.Program('tests/test_qgpsmm', ['tests/test_gpsmm.cpp'], 2023 LIBPATH=['.'], 2024 OBJPREFIX='qt-', 2025 LIBS=['Qgpsmm']) 2026 build_qt = qt_env.Alias('build', [compiled_qgpsmmlib, test_qgpsmm]) 2027 qt_env.Default(*build_qt) 2028 testprogs.append(test_qgpsmm) 2029 2030if env['python']: 2031 build_python = python_env.Alias('build', python_targets) 2032 python_env.Default(*build_python) 2033 2034# Installation and deinstallation 2035 2036# Not here because too distro-specific: udev rules, desktop files, init scripts 2037 2038# It's deliberate that we don't install gpsd.h. It's full of internals that 2039# third-party client programs should not see. 2040headerinstall = [env.Install(installdir('includedir'), x) 2041 for x in ("libgpsmm.h", "gps.h")] 2042 2043binaryinstall = [] 2044binaryinstall.append(env.Install(installdir('sbindir'), sbin_binaries)) 2045binaryinstall.append(env.Install(installdir('bindir'), bin_binaries)) 2046binaryinstall.append(LibraryInstall(env, installdir('libdir'), libgps_shared, 2047 libgps_version)) 2048# Work around a minor bug in InstallSharedLib() link handling 2049env.AddPreAction(binaryinstall, 'rm -f %s/libgps.*' % (installdir('libdir'), )) 2050 2051if qt_env: 2052 binaryinstall.append(LibraryInstall(qt_env, installdir('libdir'), 2053 compiled_qgpsmmlib, libgps_version)) 2054 2055if ((not env['debug'] and not env['profiling'] and not env['nostrip'] and 2056 not sys.platform.startswith('darwin'))): 2057 env.AddPostAction(binaryinstall, '$STRIP $TARGET') 2058 2059if env['python']: 2060 python_module_dir = str(python_libdir) + os.sep + 'gps' 2061 python_extensions_install = python_env.Install(DESTDIR + python_module_dir, 2062 python_built_extensions) 2063 if ((not env['debug'] and not env['profiling'] and 2064 not env['nostrip'] and not sys.platform.startswith('darwin'))): 2065 python_env.AddPostAction(python_extensions_install, '$STRIP $TARGET') 2066 2067 python_modules_install = python_env.Install(DESTDIR + python_module_dir, 2068 python_modules) 2069 2070 python_progs_install = python_env.Install(installdir('bindir'), 2071 python_progs) 2072 2073 python_egg_info_install = python_env.Install(DESTDIR + str(python_libdir), 2074 python_egg_info) 2075 python_install = [python_extensions_install, 2076 python_modules_install, 2077 python_progs_install, 2078 python_egg_info_install, 2079 # We don't need the directory explicitly for the 2080 # install, but we do need it for the uninstall 2081 Dir(DESTDIR + python_module_dir)] 2082 2083 # Check that Python modules compile properly 2084 python_all = python_misc + python_modules + python_progs + ['SConstruct'] 2085 check_compile = [] 2086 for p in python_all: 2087 # split in two lines for readability 2088 check_compile.append('cp %s tmp.py; %s -tt -m py_compile tmp.py;' % 2089 (p, sys.executable)) 2090 check_compile.append('rm tmp.py*') 2091 2092 python_compilation_regress = Utility('python-compilation-regress', 2093 python_all, check_compile) 2094 2095 # Sanity-check Python code. 2096 # Bletch. We don't really want to suppress W0231 E0602 E0611 E1123, 2097 # but Python 3 syntax confuses a pylint running under Python 2. 2098 # There's an internal error in astroid that requires we disable some 2099 # auditing. This is irritating as hell but there's no help for it short 2100 # of an upstream fix. 2101 python_lint = python_misc + python_modules + python_progs + ['SConstruct'] 2102 2103 pylint = Utility( 2104 "pylint", python_lint, 2105 ['''pylint --rcfile=/dev/null --dummy-variables-rgx='^_' ''' 2106 '''--msg-template=''' 2107 '''"{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" ''' 2108 '''--reports=n --disable=F0001,C0103,C0111,C1001,C0301,C0122,C0302,''' 2109 '''C0322,C0324,C0323,C0321,C0330,C0411,C0413,E1136,R0201,R0204,''' 2110 '''R0801,''' 2111 '''R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,W0110,W0201,''' 2112 '''W0121,W0123,W0231,W0232,W0234,W0401,W0403,W0141,W0142,W0603,''' 2113 '''W0614,W0640,W0621,W1504,E0602,E0611,E1101,E1102,E1103,E1123,''' 2114 '''F0401,I0011 ''' + " ".join(python_lint)]) 2115 2116 # Additional Python readability style checks 2117 pep8 = Utility("pep8", python_lint, 2118 ['pycodestyle --ignore=W602,E122,E241 ' + 2119 " ".join(python_lint)]) 2120 2121 flake8 = Utility("flake8", python_lint, 2122 ['flake8 --ignore=E501,W602,E122,E241,E401 ' + 2123 " ".join(python_lint)]) 2124 2125 # get version from each python prog 2126 # this ensures they can run and gps_versions match 2127 vchk = '' 2128 verenv = env['ENV'].copy() 2129 verenv['DISPLAY'] = '' # Avoid launching X11 in X11 progs 2130 pp = [] 2131 for p in python_progs: 2132 pp.append("$PYTHON $SRCDIR/%s -V" % p) 2133 python_versions = Utility('python-versions', python_progs, pp, ENV=verenv) 2134 2135else: 2136 python_install = [] 2137 python_compilation_regress = None 2138 python_versions = None 2139 2140pc_install = [env.Install(installdir('pkgconfig'), 'libgps.pc')] 2141if qt_env: 2142 pc_install.append(qt_env.Install(installdir('pkgconfig'), 'Qgpsmm.pc')) 2143 pc_install.append(qt_env.Install(installdir('libdir'), 'libQgpsmm.prl')) 2144 2145 2146maninstall = [] 2147for manpage in all_manpages: 2148 if not manbuilder and not os.path.exists(manpage): 2149 continue 2150 section = manpage.split(".")[1] 2151 dest = os.path.join(installdir('mandir'), "man" + section, 2152 os.path.basename(manpage)) 2153 maninstall.append(env.InstallAs(source=manpage, target=dest)) 2154install = env.Alias('install', binaryinstall + maninstall + python_install + 2155 pc_install + headerinstall) 2156 2157 2158def Uninstall(nodes): 2159 deletes = [] 2160 for node in nodes: 2161 if node.__class__ == install[0].__class__: 2162 deletes.append(Uninstall(node.sources)) 2163 else: 2164 deletes.append(Delete(str(node))) 2165 return deletes 2166 2167 2168uninstall = env.Command('uninstall', '', 2169 Flatten(Uninstall(Alias("install"))) or "") 2170env.AlwaysBuild(uninstall) 2171env.Precious(uninstall) 2172 2173# Target selection for '.' is badly broken. This is a general scons problem, 2174# not a glitch in this particular recipe. Avoid triggering the bug. 2175 2176 2177def error_action(target, source, env): 2178 raise SCons.Error.UserError("Target selection for '.' is broken.") 2179 2180 2181AlwaysBuild(Alias(".", [], error_action)) 2182 2183 2184# Putting in all these -U flags speeds up cppcheck and allows it to look 2185# at configurations we actually care about. 2186Utility("cppcheck", ["gpsd.h", "packet_names.h"], 2187 "cppcheck -U__UNUSED__ -UUSE_QT -U__COVERITY__ -U__future__ " 2188 "-ULIMITED_MAX_CLIENTS -ULIMITED_MAX_DEVICES -UAF_UNSPEC -UINADDR_ANY " 2189 "-U_WIN32 -U__CYGWIN__ " 2190 "-UPATH_MAX -UHAVE_STRLCAT -UHAVE_STRLCPY -UIPTOS_LOWDELAY " 2191 "-UIPV6_TCLASS -UTCP_NODELAY -UTIOCMIWAIT --template gcc " 2192 "--enable=all --inline-suppr --suppress='*:driver_proto.c' " 2193 "--force $SRCDIR") 2194 2195# Check with clang analyzer 2196Utility("scan-build", ["gpsd.h", "packet_names.h"], 2197 "scan-build scons") 2198 2199 2200# Check the documentation for bogons, too 2201Utility("xmllint", glob.glob("man/*.xml"), 2202 "for xml in $SOURCES; do xmllint --nonet --noout --valid $$xml; done") 2203 2204# Use deheader to remove headers not required. If the statistics line 2205# ends with other than '0 removed' there's work to be done. 2206Utility("deheader", generated_sources, [ 2207 'deheader -x cpp -x contrib -x gpspacket.c ' 2208 '-x monitor_proto.c -i gpsd_config.h -i gpsd.h ' 2209 '-m "MORECFLAGS=\'-Werror -Wfatal-errors -DDEBUG \' scons -Q"', 2210]) 2211 2212# Perform all local code-sanity checks (but not the Coverity scan). 2213audit = env.Alias('audit', 2214 ['cppcheck', 2215 'pylint', 2216 'scan-build', 2217 'valgrind-audit', 2218 'xmllint', 2219 ]) 2220 2221# 2222# Regression tests begin here 2223# 2224# Note that the *-makeregress targets re-create the *.log.chk source 2225# files from the *.log source files. 2226 2227# Unit-test the bitfield extractor 2228bits_regress = Utility('bits-regress', [test_bits], [ 2229 '$SRCDIR/tests/test_bits --quiet' 2230]) 2231 2232# Unit-test the deg_to_str() converter 2233bits_regress = Utility('deg-regress', [test_gpsdclient], [ 2234 '$SRCDIR/tests/test_gpsdclient' 2235]) 2236 2237# Unit-test the bitfield extractor 2238matrix_regress = Utility('matrix-regress', [test_matrix], [ 2239 '$SRCDIR/tests/test_matrix --quiet' 2240]) 2241 2242# using regress-drivers requires socket_export being enabled. 2243if not env['socket_export'] or not env['python']: 2244 announce("GPS regression tests suppressed because socket_export " 2245 "or python is off.") 2246 gps_regress = None 2247 gpsfake_tests = None 2248else: 2249 # Regression-test the daemon. 2250 # But first dump the platform and its delay parameters. 2251 # The ":;" in this production and the later one forestalls an attempt by 2252 # SCons to install up to date versions of gpsfake and gpsctl if it can 2253 # find older versions of them in a directory on your $PATH. 2254 gps_herald = Utility('gps-herald', [gpsd, gpsctl, python_built_extensions], 2255 ':; $PYTHON $PYTHON_COVERAGE $SRCDIR/gpsfake -T') 2256 gps_log_pattern = os.path.join('test', 'daemon', '*.log') 2257 gps_logs = glob.glob(gps_log_pattern) 2258 gps_names = [os.path.split(x)[-1][:-4] for x in gps_logs] 2259 gps_tests = [] 2260 for gps_name, gps_log in zip(gps_names, gps_logs): 2261 gps_tests.append(Utility( 2262 'gps-regress-' + gps_name, gps_herald, 2263 '$SRCDIR/regress-driver -q -o -t $REGRESSOPTS ' + gps_log)) 2264 gps_regress = env.Alias('gps-regress', gps_tests) 2265 2266 # Run the passthrough log in all transport modes for better coverage 2267 gpsfake_log = os.path.join('test', 'daemon', 'passthrough.log') 2268 gpsfake_tests = [] 2269 for name, opts in [['pty', ''], ['udp', '-u'], ['tcp', '-o -t']]: 2270 gpsfake_tests.append(Utility('gpsfake-' + name, gps_herald, 2271 '$SRCDIR/regress-driver' 2272 ' $REGRESSOPTS -q %s %s' 2273 % (opts, gpsfake_log))) 2274 env.Alias('gpsfake-tests', gpsfake_tests) 2275 2276 # Build the regression tests for the daemon. 2277 # Note: You'll have to do this whenever the default leap second 2278 # changes in gpsd.h. Many drivers rely on the default until they 2279 # get the current leap second. 2280 gps_rebuilds = [] 2281 for gps_name, gps_log in zip(gps_names, gps_logs): 2282 gps_rebuilds.append(Utility('gps-makeregress-' + gps_name, gps_herald, 2283 '$SRCDIR/regress-driver -bq -o -t ' 2284 '$REGRESSOPTS ' + gps_log)) 2285 if GetOption('num_jobs') <= 1: 2286 Utility('gps-makeregress', gps_herald, 2287 '$SRCDIR/regress-driver -b $REGRESSOPTS %s' % gps_log_pattern) 2288 else: 2289 env.Alias('gps-makeregress', gps_rebuilds) 2290 2291# To build an individual test for a load named foo.log, put it in 2292# test/daemon and do this: 2293# regress-driver -b test/daemon/foo.log 2294 2295# Regression-test the RTCM decoder. 2296if not env["rtcm104v2"]: 2297 announce("RTCM2 regression tests suppressed because rtcm104v2 is off.") 2298 rtcm_regress = None 2299else: 2300 rtcm_regress = Utility('rtcm-regress', [gpsdecode], [ 2301 '@echo "Testing RTCM decoding..."', 2302 '@for f in $SRCDIR/test/*.rtcm2; do ' 2303 ' echo "\tTesting $${f}..."; ' 2304 ' TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2305 ' $SRCDIR/gpsdecode -u -j <$${f} >$${TMPFILE}; ' 2306 ' diff -ub $${f}.chk $${TMPFILE} || echo "Test FAILED!"; ' 2307 ' rm -f $${TMPFILE}; ' 2308 'done;', 2309 '@echo "Testing idempotency of JSON dump/decode for RTCM2"', 2310 '@TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2311 '$SRCDIR/gpsdecode -u -e -j <test/synthetic-rtcm2.json >$${TMPFILE}; ' 2312 ' grep -v "^#" test/synthetic-rtcm2.json | diff -ub - $${TMPFILE} ' 2313 ' || echo "Test FAILED!"; ' 2314 ' rm -f $${TMPFILE}; ', 2315 ]) 2316 2317# Rebuild the RTCM regression tests. 2318Utility('rtcm-makeregress', [gpsdecode], [ 2319 'for f in $SRCDIR/test/*.rtcm2; do ' 2320 ' $SRCDIR/gpsdecode -j <$${f} >$${f}.chk; ' 2321 'done' 2322]) 2323 2324# Regression-test the AIVDM decoder. 2325if not env["aivdm"]: 2326 announce("AIVDM regression tests suppressed because aivdm is off.") 2327 aivdm_regress = None 2328else: 2329 # FIXME! Does not return a proper fail code 2330 aivdm_regress = Utility('aivdm-regress', [gpsdecode], [ 2331 '@echo "Testing AIVDM decoding w/ CSV format..."', 2332 '@for f in $SRCDIR/test/*.aivdm; do ' 2333 ' echo "\tTesting $${f}..."; ' 2334 ' TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2335 ' $SRCDIR/gpsdecode -u -c <$${f} >$${TMPFILE}; ' 2336 ' diff -ub $${f}.chk $${TMPFILE} || echo "Test FAILED!"; ' 2337 ' rm -f $${TMPFILE}; ' 2338 'done;', 2339 '@echo "Testing AIVDM decoding w/ JSON unscaled format..."', 2340 '@for f in $SRCDIR/test/*.aivdm; do ' 2341 ' echo "\tTesting $${f}..."; ' 2342 ' TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2343 ' $SRCDIR/gpsdecode -u -j <$${f} >$${TMPFILE}; ' 2344 ' diff -ub $${f}.ju.chk $${TMPFILE} || echo "Test FAILED!"; ' 2345 ' rm -f $${TMPFILE}; ' 2346 'done;', 2347 '@echo "Testing AIVDM decoding w/ JSON scaled format..."', 2348 '@for f in $SRCDIR/test/*.aivdm; do ' 2349 ' echo "\tTesting $${f}..."; ' 2350 ' TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2351 ' $SRCDIR/gpsdecode -j <$${f} >$${TMPFILE}; ' 2352 ' diff -ub $${f}.js.chk $${TMPFILE} || echo "Test FAILED!"; ' 2353 ' rm -f $${TMPFILE}; ' 2354 'done;', 2355 '@echo "Testing idempotency of unscaled JSON dump/decode for AIS"', 2356 '@TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2357 '$SRCDIR/gpsdecode -u -e -j <$SRCDIR/test/sample.aivdm.ju.chk ' 2358 ' >$${TMPFILE}; ' 2359 ' grep -v "^#" $SRCDIR/test/sample.aivdm.ju.chk ' 2360 ' | diff -ub - $${TMPFILE} || echo "Test FAILED!"; ' 2361 ' rm -f $${TMPFILE}; ', 2362 # Parse the unscaled json reference, dump it as scaled json, 2363 # and finally compare it with the scaled json reference 2364 '@echo "Testing idempotency of scaled JSON dump/decode for AIS"', 2365 '@TMPFILE=`mktemp -t gpsd-test.chk-XXXXXXXXXXXXXX`; ' 2366 '$SRCDIR/gpsdecode -e -j <$SRCDIR/test/sample.aivdm.ju.chk ' 2367 ' >$${TMPFILE};' 2368 ' grep -v "^#" $SRCDIR/test/sample.aivdm.js.chk ' 2369 ' | diff -ub - $${TMPFILE} || echo "Test FAILED!"; ' 2370 ' rm -f $${TMPFILE}; ', 2371 ]) 2372 2373# Rebuild the AIVDM regression tests. 2374Utility('aivdm-makeregress', [gpsdecode], [ 2375 'for f in $SRCDIR/test/*.aivdm; do ' 2376 ' $SRCDIR/gpsdecode -u -c <$${f} > $${f}.chk; ' 2377 ' $SRCDIR/gpsdecode -u -j <$${f} > $${f}.ju.chk; ' 2378 ' $SRCDIR/gpsdecode -j <$${f} > $${f}.js.chk; ' 2379 'done', ]) 2380 2381# Regression-test the packet getter. 2382packet_regress = UtilityWithHerald( 2383 'Testing detection of invalid packets...', 2384 'packet-regress', [test_packet], [ 2385 '$SRCDIR/tests/test_packet | ' 2386 ' diff -u $SRCDIR/test/packet.test.chk -', ]) 2387 2388# Rebuild the packet-getter regression test 2389Utility('packet-makeregress', [test_packet], [ 2390 '$SRCDIR/tests/test_packet >$SRCDIR/test/packet.test.chk', ]) 2391 2392# Regression-test the geoid and variation tester. 2393geoid_regress = UtilityWithHerald( 2394 'Testing the geoid and variation models...', 2395 'geoid-regress', [test_geoid], ['$SRCDIR/tests/test_geoid']) 2396 2397# Regression-test the calendar functions 2398time_regress = Utility('time-regress', [test_mktime], [ 2399 '$SRCDIR/tests/test_mktime' 2400]) 2401 2402if not env['python']: 2403 unpack_regress = None 2404 misc_regress = None 2405else: 2406 # Regression test the unpacking code in libgps 2407 unpack_regress = UtilityWithHerald( 2408 'Testing the client-library sentence decoder...', 2409 'unpack-regress', [test_libgps], [ 2410 '$SRCDIR/regress-driver $REGRESSOPTS -c' 2411 ' $SRCDIR/test/clientlib/*.log', ]) 2412 # Unit-test the bitfield extractor 2413 misc_regress = Utility('misc-regress', [], [ 2414 '{} $SRCDIR/test_clienthelpers.py'.format(target_python_path.decode()), 2415 '{} $SRCDIR/test_misc.py'.format(target_python_path.decode()) 2416 ]) 2417 2418 2419# Build the regression test for the sentence unpacker 2420Utility('unpack-makeregress', [test_libgps], [ 2421 '@echo "Rebuilding the client sentence-unpacker tests..."', 2422 '$SRCDIR/regress-driver $REGRESSOPTS -c -b $SRCDIR/test/clientlib/*.log' 2423]) 2424 2425# Unit-test the JSON parsing 2426if not env['socket_export']: 2427 json_regress = None 2428else: 2429 json_regress = Utility('json-regress', [test_json], 2430 ['$SRCDIR/tests/test_json']) 2431 2432# Unit-test timespec math 2433timespec_regress = Utility('timespec-regress', [test_timespec], [ 2434 '$SRCDIR/tests/test_timespec' 2435]) 2436 2437# Unit-test float math 2438float_regress = Utility('float-regress', [test_float], [ 2439 '$SRCDIR/tests/test_float' 2440]) 2441 2442# Unit-test trig math 2443trig_regress = Utility('trig-regress', [test_trig], [ 2444 '$SRCDIR/tests/test_trig' 2445]) 2446 2447# consistency-check the driver methods 2448method_regress = UtilityWithHerald( 2449 'Consistency-checking driver methods...', 2450 'method-regress', [test_packet], [ 2451 '$SRCDIR/tests/test_packet -c >/dev/null', ]) 2452 2453# Test the xgps/xgpsspeed dependencies 2454if not env['python'] or not env['xgps']: 2455 test_xgps_deps = None 2456else: 2457 test_xgps_deps = UtilityWithHerald( 2458 'Testing xgps/xgpsspeed dependencies (since xgps=yes)...', 2459 'test-xgps-deps', [], [ 2460 '$PYTHON $SRCDIR/test_xgps_deps.py']) 2461 2462# Run a valgrind audit on the daemon - not in normal tests 2463valgrind_audit = Utility('valgrind-audit', [ 2464 '$SRCDIR/valgrind-audit.py', python_built_extensions, gpsd], 2465 '$PYTHON $SRCDIR/valgrind-audit.py' 2466) 2467 2468# Run test builds on remote machines 2469flocktest = Utility("flocktest", [], "cd devtools; ./flocktest " + gitrepo) 2470 2471 2472# Run all normal regression tests 2473describe = UtilityWithHerald( 2474 'Run normal regression tests for %s...' % rev.strip(), 2475 'describe', [], []) 2476 2477# Delete all test programs 2478test_exes = [str(p) for p in Flatten(testprogs)] 2479test_objs = [p + '.o' for p in test_exes] 2480testclean = Utility('testclean', [], 2481 'rm -f %s' % ' '.join(test_exes + test_objs)) 2482 2483test_nondaemon = [ 2484 aivdm_regress, 2485 bits_regress, 2486 describe, 2487 float_regress, 2488 geoid_regress, 2489 json_regress, 2490 matrix_regress, 2491 method_regress, 2492 misc_regress, 2493 packet_regress, 2494 python_compilation_regress, 2495 python_versions, 2496 rtcm_regress, 2497 test_xgps_deps, 2498 time_regress, 2499 timespec_regress, 2500 # trig_regress, # not ready 2501 unpack_regress, 2502] 2503if env['socket_export']: 2504 test_nondaemon.append(test_json) 2505if env['libgpsmm']: 2506 test_nondaemon.append(test_gpsmm) 2507if qt_env: 2508 test_nondaemon.append(test_qgpsmm) 2509 2510test_quick = test_nondaemon + [gpsfake_tests] 2511test_noclean = test_quick + [gps_regress] 2512 2513env.Alias('test-nondaemon', test_nondaemon) 2514env.Alias('test-quick', test_quick) 2515check = env.Alias('check', test_noclean) 2516env.Alias('testregress', check) 2517env.Alias('build-tests', testprogs) 2518build_all = env.Alias('build-all', build + testprogs) 2519 2520# Remove all shared-memory segments. Normally only needs to be run 2521# when a segment size changes. 2522Utility('shmclean', [], ["ipcrm -M 0x4e545030;" 2523 "ipcrm -M 0x4e545031;" 2524 "ipcrm -M 0x4e545032;" 2525 "ipcrm -M 0x4e545033;" 2526 "ipcrm -M 0x4e545034;" 2527 "ipcrm -M 0x4e545035;" 2528 "ipcrm -M 0x4e545036;" 2529 "ipcrm -M 0x47505345;" 2530 ]) 2531 2532# The website directory 2533# 2534# None of these productions are fired by default. 2535# The content they handle is the GPSD website, not included in 2536# release tarballs. 2537 2538# asciidoc documents 2539if env.WhereIs('asciidoc'): 2540 adocfiles = ['AIVDM', 2541 'client-howto', 2542 'gpsd-time-service-howto', 2543 'NMEA', 2544 'ppp-howto', 2545 'protocol-evolution', 2546 'protocol-transition', 2547 'time-service-intro', 2548 ] 2549 asciidocs = ["www/" + stem + ".html" for stem in adocfiles] \ 2550 + ["www/installation.html"] + ["www/README.html"] 2551 for stem in adocfiles: 2552 env.Command('www/%s.html' % stem, 'www/%s.adoc' % stem, 2553 ['asciidoc -b html5 -a toc -o www/%s.html www/%s.adoc' 2554 % (stem, stem)]) 2555 env.Command("www/installation.html", 2556 "INSTALL.adoc", 2557 ["asciidoc -o www/installation.html INSTALL.adoc"]) 2558 env.Command("www/README.html", 2559 "README.adoc", 2560 ["asciidoc -o www/README.html README.adoc"]) 2561else: 2562 announce("Part of the website build requires asciidoc, not installed.") 2563 asciidocs = [] 2564 2565# Non-asciidoc webpages only 2566htmlpages = Split(''' 2567 www/gps2udp.html 2568 www/gpscat.html 2569 www/gpsctl.html 2570 www/gpsdctl.html 2571 www/gpsdecode.html 2572 www/gpsd.html 2573 www/gpsd_json.html 2574 www/gpsfake.html 2575 www/gps.html 2576 www/gpsinit.html 2577 www/gpsmon.html 2578 www/gpspipe.html 2579 www/gpsprof.html 2580 www/gpsrinex.html 2581 www/gpxlogger.html 2582 www/hardware.html 2583 www/internals.html 2584 www/libgps.html 2585 www/libgpsmm.html 2586 www/ntpshmmon.html 2587 www/performance/performance.html 2588 www/ppscheck.html 2589 www/replacing-nmea.html 2590 www/srec.html 2591 www/ubxtool.html 2592 www/writing-a-driver.html 2593 www/zerk.html 2594 ''') 2595 2596webpages = htmlpages + asciidocs + list(map(lambda f: f[:-3], 2597 glob.glob("www/*.in"))) 2598 2599www = env.Alias('www', webpages) 2600 2601# Paste 'scons --quiet validation-list' to a batch validator such as 2602# http://htmlhelp.com/tools/validator/batch.html.en 2603 2604 2605def validation_list(target, source, env): 2606 for page in glob.glob("www/*.html"): 2607 if '-head' not in page: 2608 fp = open(page) 2609 if "Valid HTML" in fp.read(): 2610 print(os.path.join(website, os.path.basename(page))) 2611 fp.close() 2612 2613 2614Utility("validation-list", [www], validation_list) 2615 2616# How to update the website. Assumes a logal GitLab pages setup. 2617# See .gitlab-ci.yml 2618upload_web = Utility("website", [www], 2619 ['rsync --exclude="*.in" -avz www/ ' + 2620 os.environ.get('WEBSITE', '.public'), 2621 'cp TODO NEWS ' + 2622 os.environ.get('WEBSITE', '.public')]) 2623 2624# When the URL declarations change, so must the generated web pages 2625for fn in glob.glob("www/*.in"): 2626 env.Depends(fn[:-3], "SConstruct") 2627 2628if htmlbuilder: 2629 # Manual pages 2630 for xml in glob.glob("man/*.xml"): 2631 env.HTML('www/%s.html' % os.path.basename(xml[:-4]), xml) 2632 2633 # DocBook documents 2634 for stem in ['writing-a-driver', 'performance/performance', 2635 'replacing-nmea']: 2636 env.HTML('www/%s.html' % stem, 'www/%s.xml' % stem) 2637 2638 # The internals manual. 2639 # Doesn't capture dependencies on the subpages 2640 env.HTML('www/internals.html', '$SRCDIR/doc/internals.xml') 2641 2642# The hardware page 2643env.Command('www/hardware.html', ['gpscap.py', 2644 'www/hardware-head.html', 2645 'gpscap.ini', 2646 'www/hardware-tail.html'], 2647 ['(cat www/hardware-head.html && PYTHONIOENCODING=utf-8 ' 2648 '$SC_PYTHON gpscap.py && cat www/hardware-tail.html) ' 2649 '>www/hardware.html']) 2650 2651# The diagram editor dia is required in order to edit the diagram masters 2652Utility("www/cycle.svg", ["www/cycle.dia"], 2653 ["dia -e www/cycle.svg www/cycle.dia"]) 2654 2655# Experimenting with pydoc. Not yet fired by any other productions. 2656# scons www/ dies with this 2657 2658# # if env['python']: 2659# # env.Alias('pydoc', "www/pydoc/index.html") 2660# # 2661# # # We need to run epydoc with the Python version the modules built for. 2662# # # So we define our own epydoc instead of using /usr/bin/epydoc 2663# # EPYDOC = "python -c 'from epydoc.cli import cli; cli()'" 2664# # env.Command('www/pydoc/index.html', python_progs + glob.glob("*.py") 2665# # + glob.glob("gps/*.py"), [ 2666# # 'mkdir -p www/pydoc', 2667# # EPYDOC + " -v --html --graph all -n GPSD $SOURCES -o www/pydoc", 2668# # ]) 2669 2670# Productions for setting up and performing udev tests. 2671# 2672# Requires root. Do "udev-install", then "tail -f /var/log/syslog" in 2673# another window, then run 'scons udev-test', then plug and unplug the 2674# GPS ad libitum. All is well when you get fix reports each time a GPS 2675# is plugged in. 2676# 2677# In case you are a systemd user you might also need to watch the 2678# journalctl output. Instead of the hotplug script the gpsdctl@.service 2679# unit will handle hotplugging together with the udev rules. 2680# 2681# Note that a udev event can be triggered with an invocation like: 2682# udevadm trigger --sysname-match=ttyUSB0 --action add 2683 2684if env['systemd']: 2685 systemdinstall_target = [env.Install(DESTDIR + systemd_dir, 2686 "systemd/%s" % (x,)) for x in 2687 ("gpsdctl@.service", "gpsd.service", 2688 "gpsd.socket")] 2689 systemd_install = env.Alias('systemd_install', systemdinstall_target) 2690 systemd_uninstall = env.Command( 2691 'systemd_uninstall', '', 2692 Flatten(Uninstall(Alias("systemd_install"))) or "") 2693 2694 env.AlwaysBuild(systemd_uninstall) 2695 env.Precious(systemd_uninstall) 2696 hotplug_wrapper_install = [] 2697else: 2698 hotplug_wrapper_install = [ 2699 'cp $SRCDIR/gpsd.hotplug ' + DESTDIR + env['udevdir'], 2700 'chmod a+x ' + DESTDIR + env['udevdir'] + '/gpsd.hotplug' 2701 ] 2702 2703udev_install = Utility('udev-install', 'install', [ 2704 'mkdir -p ' + DESTDIR + env['udevdir'] + '/rules.d', 2705 'cp $SRCDIR/gpsd.rules ' + DESTDIR + env['udevdir'] + 2706 '/rules.d/25-gpsd.rules', ] + hotplug_wrapper_install) 2707 2708if env['systemd']: 2709 env.Requires(udev_install, systemd_install) 2710 2711if env['systemd'] and not env["sysroot"]: 2712 systemctl_daemon_reload = Utility('systemctl-daemon-reload', '', 2713 ['systemctl daemon-reload || true']) 2714 env.AlwaysBuild(systemctl_daemon_reload) 2715 env.Precious(systemctl_daemon_reload) 2716 env.Requires(systemctl_daemon_reload, systemd_install) 2717 env.Requires(udev_install, systemctl_daemon_reload) 2718 2719 2720Utility('udev-uninstall', '', [ 2721 'rm -f %s/gpsd.hotplug' % env['udevdir'], 2722 'rm -f %s/rules.d/25-gpsd.rules' % env['udevdir'], 2723]) 2724 2725Utility('udev-test', '', ['$SRCDIR/gpsd -N -n -F /var/run/gpsd.sock -D 5', ]) 2726 2727# Cleanup 2728 2729# Dummy target for cleaning misc files 2730clean_misc = env.Alias('clean-misc') 2731# Since manpage targets are disabled in clean mode, we cover them here 2732env.Clean(clean_misc, all_manpages + other_manpages) 2733# Clean compiled Python 2734env.Clean(clean_misc, 2735 glob.glob('*.pyc') + glob.glob('gps/*.pyc') + 2736 glob.glob('gps/*.so') + ['gps/__pycache__']) 2737# Clean coverage and profiling files 2738env.Clean(clean_misc, glob.glob('*.gcno') + glob.glob('*.gcda')) 2739# Clean Python coverage files 2740env.Clean(clean_misc, glob.glob('.coverage*') + ['htmlcov/']) 2741# Clean Qt stuff 2742env.Clean(clean_misc, ['libQgpsmm.prl', 'Qgpsmm.pc']) 2743# Clean shared library files 2744env.Clean(clean_misc, glob.glob('*.so') + glob.glob('*.so.*')) 2745# Clean old location man page files 2746env.Clean(clean_misc, glob.glob('*.[0-8]')) 2747# Other misc items 2748env.Clean(clean_misc, ['config.log', 'contrib/ppscheck', 'contrib/clock_test', 2749 'TAGS']) 2750# Clean scons state files 2751env.Clean(clean_misc, ['.sconf_temp', '.scons-option-cache', 'config.log']) 2752 2753# Default targets 2754 2755if cleaning: 2756 env.Default(build_all, audit, clean_misc) 2757 announce("You must manually remove {}".format(sconsign_file)) 2758else: 2759 env.Default(build) 2760 2761# Tags for Emacs and vi 2762misc_sources = ['cgps.c', 2763 'gps2udp.c', 2764 'gpsctl.c', 2765 'gpsdctl.c', 2766 'gpsdecode.c', 2767 'gpspipe.c', 2768 'gpxlogger.c', 2769 'ntpshmmon.c', 2770 'ppscheck.c', 2771 ] 2772sources = libgpsd_sources + libgps_sources + gpsd_sources + gpsmon_sources + \ 2773 misc_sources 2774env.Command('TAGS', sources, ['etags ' + " ".join(sources)]) 2775 2776# Release machinery begins here 2777# 2778# We need to be in the actual project repo (i.e. not doing a -Y build) 2779# for these productions to work. 2780 2781if os.path.exists("gpsd.c") and os.path.exists(".gitignore"): 2782 distfiles = _getoutput(r"git ls-files | grep -v '^www/'").split() 2783 # for some reason distfiles is now a mix of byte strings and char strings 2784 distfiles = [polystr(d) for d in distfiles] 2785 2786 if ".gitignore" in distfiles: 2787 distfiles.remove(".gitignore") 2788 distfiles += generated_sources 2789 distfiles += all_manpages 2790 if "packaging/rpm/gpsd.spec" not in distfiles: 2791 distfiles.append("packaging/rpm/gpsd.spec") 2792 2793 # How to build a zip file. 2794 # Perversely, if the zip exists, it is modified, not replaced. 2795 # So delete it first. 2796 dozip = env.Command('zip', distfiles, [ 2797 'rm -f gpsd-${VERSION}.zip', 2798 '@zip -ry gpsd-${VERSION}.zip $SOURCES -x contrib/ais-samples/\\*', 2799 '@ls -l gpsd-${VERSION}.zip', 2800 ]) 2801 env.Clean(dozip, ["gpsd-${VERSION}.zip", "packaging/rpm/gpsd.spec"]) 2802 2803 # How to build a tarball. 2804 # The command assume the non-portable GNU tar extension 2805 # "--transform", and will thus fail if ${TAR} is not GNU tar. 2806 # scons in theory has code to cope with this, but in practice this 2807 # is not working. On BSD-derived systems, install GNU tar and 2808 # pass TAR=gtar in the environment. 2809 # make a .tar.gz and a .tar.xz 2810 dist = env.Command('dist', distfiles, [ 2811 '@${TAR} --transform "s:^:gpsd-${VERSION}/:S" ' 2812 ' -czf gpsd-${VERSION}.tar.gz --exclude contrib/ais-samples $SOURCES', 2813 '@${TAR} --transform "s:^:gpsd-${VERSION}/:S" ' 2814 ' -cJf gpsd-${VERSION}.tar.xz --exclude contrib/ais-samples $SOURCES', 2815 '@ls -l gpsd-${VERSION}.tar.gz', 2816 ]) 2817 env.Clean(dist, ["gpsd-${VERSION}.tar.gz", "packaging/rpm/gpsd.spec"]) 2818 2819 # Make RPM from the specfile in packaging 2820 Utility('dist-rpm', dist, 'rpmbuild -ta gpsd-${VERSION}.tar.gz') 2821 2822 # Make sure build-from-tarball works. 2823 testbuild = Utility('testbuild', [dist], [ 2824 '${TAR} -xzvf gpsd-${VERSION}.tar.gz', 2825 'cd gpsd-${VERSION}; scons', 2826 'rm -fr gpsd-${VERSION}', 2827 ]) 2828 2829 releasecheck = env.Alias('releasecheck', [ 2830 testbuild, 2831 check, 2832 audit, 2833 flocktest, 2834 ]) 2835 2836 # The chmod copes with the fact that scp will give a 2837 # replacement the permissions of the *original*... 2838 upload_release = Utility('upload-release', [dist], [ 2839 'rm -f gpsd-*tar*sig', 2840 'gpg -b gpsd-${VERSION}.tar.gz', 2841 'gpg -b gpsd-${VERSION}.tar.xz', 2842 'chmod ug=rw,o=r gpsd-${VERSION}.tar.*', 2843 'scp gpsd-${VERSION}.tar.* ' + scpupload, 2844 ]) 2845 2846 # How to tag a release 2847 tag_release = Utility('tag-release', [], [ 2848 'git tag -s -m "Tagged for external release ${VERSION}" \ 2849 release-${VERSION}']) 2850 upload_tags = Utility('upload-tags', [], ['git push --tags']) 2851 2852 # Local release preparation. This production will require Internet access, 2853 # but it doesn't do any uploads or public repo mods. 2854 # 2855 # Note that tag_release has to fire early, otherwise the value of REVISION 2856 # won't be right when revision.h is generated for the tarball. 2857 releaseprep = env.Alias("releaseprep", 2858 [Utility("distclean", [], ["rm -f revision.h"]), 2859 tag_release, 2860 dist]) 2861 # Undo local release preparation 2862 Utility("undoprep", [], ['rm -f gpsd-${VERSION}.tar.gz;', 2863 'git tag -d release-${VERSION};']) 2864 2865 # All a buildup to this. 2866 env.Alias("release", [releaseprep, 2867 upload_release, 2868 upload_tags, 2869 upload_web]) 2870 2871 # Experimental release mechanics using shipper 2872 # This will ship a freecode metadata update 2873 Utility("ship", [dist, "control"], 2874 ['shipper version=%s | sh -e -x' % gpsd_version]) 2875 2876# The following sets edit modes for GNU EMACS 2877# Local Variables: 2878# mode:python 2879# End: 2880# vim: set expandtab shiftwidth=4 2881