libraries.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. # Some notes about static linking:
  2. # There are two ways of linking to static library: using the -l command line
  3. # option or specifying the full path to the library file as one of the inputs.
  4. # When using the -l option, the library search paths will be searched for a
  5. # dynamic version of the library, if that is not found, the search paths will
  6. # be searched for a static version of the library. This means we cannot force
  7. # static linking of a library this way. It is possible to force static linking
  8. # of all libraries, but we want to control it per library.
  9. # Conclusion: We have to specify the full path to each library that should be
  10. # linked statically.
  11. from executils import captureStdout, shjoin
  12. from msysutils import msysActive, msysPathToNative
  13. from io import open
  14. from os import listdir
  15. from os.path import isdir, isfile
  16. from os import environ
  17. class Library(object):
  18. libName = None
  19. makeName = None
  20. header = None
  21. configScriptName = None
  22. dynamicLibsOption = '--libs'
  23. staticLibsOption = None
  24. function = None
  25. # TODO: A library can give an application compile time and run time
  26. # dependencies on other libraries. For example SDL_ttf depends on
  27. # FreeType only at run time, but depends on SDL both compile time
  28. # and run time, since SDL is part of its interface and FreeType is
  29. # only used by the implementation. As a result, it is possible to
  30. # compile against SDL_ttf without having the FreeType headers
  31. # installed. But our getCompileFlags() does not support this.
  32. # In pkg-config these are called private dependencies.
  33. dependsOn = ()
  34. @classmethod
  35. def isSystemLibrary(cls, platform): # pylint: disable-msg=W0613
  36. '''Returns True iff this library is a system library on the given
  37. platform.
  38. A system library is a library that is available systemwide in the
  39. minimal installation of the OS.
  40. The default implementation returns False.
  41. '''
  42. return False
  43. @classmethod
  44. def getConfigScript( # pylint: disable-msg=W0613
  45. cls, platform, linkStatic, distroRoot
  46. ):
  47. scriptName = cls.configScriptName
  48. if scriptName is None:
  49. return None
  50. elif platform == 'dingux' and cls.isSystemLibrary(platform):
  51. # TODO: A generic mechanism for locating config scripts in SDKs.
  52. # Note that distroRoot is for non-system libs only.
  53. # Trying a path relative to the compiler location would
  54. # probably work well.
  55. return '/opt/gcw0-toolchain/usr/mipsel-gcw0-linux-uclibc/sysroot/usr/bin/%s' % scriptName
  56. elif distroRoot is None:
  57. return scriptName
  58. else:
  59. return '%s/bin/%s' % (distroRoot, scriptName)
  60. @classmethod
  61. def getHeaders(cls, platform): # pylint: disable-msg=W0613
  62. header = cls.header
  63. return (header, ) if isinstance(header, str) else header
  64. @classmethod
  65. def getLibName(cls, platform): # pylint: disable-msg=W0613
  66. return cls.libName
  67. @classmethod
  68. def getCompileFlags(cls, platform, linkStatic, distroRoot):
  69. configScript = cls.getConfigScript(platform, linkStatic, distroRoot)
  70. if configScript is not None:
  71. flags = [ '`%s --cflags`' % configScript ]
  72. elif distroRoot is None or cls.isSystemLibrary(platform):
  73. flags = []
  74. else:
  75. flags = [ '-isystem %s/include' % distroRoot ]
  76. dependentFlags = [
  77. librariesByName[name].getCompileFlags(
  78. platform, linkStatic, distroRoot
  79. )
  80. for name in cls.dependsOn
  81. ]
  82. return ' '.join(flags + dependentFlags)
  83. @classmethod
  84. def getLinkFlags(cls, platform, linkStatic, distroRoot):
  85. configScript = cls.getConfigScript(platform, linkStatic, distroRoot)
  86. if configScript is not None:
  87. libsOption = (
  88. cls.dynamicLibsOption
  89. if not linkStatic or cls.isSystemLibrary(platform)
  90. else cls.staticLibsOption
  91. )
  92. if libsOption is not None:
  93. return '`%s %s`' % (configScript, libsOption)
  94. if distroRoot is None or cls.isSystemLibrary(platform):
  95. return '-l%s' % cls.getLibName(platform)
  96. else:
  97. flags = [
  98. '%s/lib/lib%s.a' % (distroRoot, cls.getLibName(platform))
  99. ] if linkStatic else [
  100. '-L%s/lib -l%s' % (distroRoot, cls.getLibName(platform))
  101. ]
  102. dependentFlags = [
  103. librariesByName[name].getLinkFlags(
  104. platform, linkStatic, distroRoot
  105. )
  106. for name in cls.dependsOn
  107. ]
  108. return ' '.join(flags + dependentFlags)
  109. @classmethod
  110. def getVersion(cls, platform, linkStatic, distroRoot):
  111. '''Returns the version of this library, "unknown" if there is no
  112. mechanism to retrieve the version, None if there is a mechanism
  113. to retrieve the version but it failed, or a callable that takes a
  114. CompileCommand and a log stream as its arguments and returns the
  115. version or None if retrieval failed.
  116. '''
  117. configScript = cls.getConfigScript(platform, linkStatic, distroRoot)
  118. if configScript is None:
  119. return 'unknown'
  120. else:
  121. return '`%s --version`' % configScript
  122. class ALSA(Library):
  123. libName = 'asound'
  124. makeName = 'ALSA'
  125. header = '<alsa/asoundlib.h>'
  126. function = 'snd_seq_open'
  127. @classmethod
  128. def isSystemLibrary(cls, platform):
  129. return True
  130. @classmethod
  131. def getLinkFlags(cls, platform, linkStatic, distroRoot):
  132. flags = super().getLinkFlags(platform, linkStatic, distroRoot)
  133. if linkStatic:
  134. flags += ' -lpthread'
  135. return flags
  136. @classmethod
  137. def getVersion(cls, platform, linkStatic, distroRoot):
  138. def execute(cmd, log):
  139. version = cmd.expand(log, cls.getHeaders(platform),
  140. 'SND_LIB_VERSION_STR'
  141. )
  142. return None if version is None else version.strip('"')
  143. return execute
  144. class FreeType(Library):
  145. libName = 'freetype'
  146. makeName = 'FREETYPE'
  147. header = ('<ft2build.h>', 'FT_FREETYPE_H')
  148. configScriptName = 'freetype-config'
  149. function = 'FT_Open_Face'
  150. @classmethod
  151. def isSystemLibrary(cls, platform):
  152. return platform in ('dingux',)
  153. @classmethod
  154. def getConfigScript(cls, platform, linkStatic, distroRoot):
  155. if platform in ('netbsd', 'openbsd'):
  156. if distroRoot == '/usr/local':
  157. # FreeType is located in the X11 tree, not the ports tree.
  158. distroRoot = '/usr/X11R6'
  159. script = super().getConfigScript(
  160. platform, linkStatic, distroRoot
  161. )
  162. # FreeType 2.9.1 no longer installs the freetype-config script
  163. # by default and expects pkg-config to be used instead.
  164. if isfile(script):
  165. return script
  166. elif distroRoot is None:
  167. return 'pkg-config freetype2'
  168. elif distroRoot.startswith('derived/'):
  169. toolsDir = '%s/../tools/bin' % distroRoot
  170. for name in listdir(toolsDir):
  171. if name.endswith('-pkg-config'):
  172. return toolsDir + '/' + name + ' freetype2'
  173. raise RuntimeError('No cross-pkg-config found in 3rdparty build')
  174. else:
  175. return '%s/bin/pkg-config freetype2' % distroRoot
  176. @classmethod
  177. def getVersion(cls, platform, linkStatic, distroRoot):
  178. configScript = cls.getConfigScript(platform, linkStatic, distroRoot)
  179. return '`%s --ftversion`' % configScript
  180. class GL(Library):
  181. libName = 'GL'
  182. makeName = 'GL'
  183. function = 'glGenTextures'
  184. @classmethod
  185. def isSystemLibrary(cls, platform):
  186. # On *BSD, OpenGL is in ports, not in the base system.
  187. return not platform.endswith('bsd')
  188. @classmethod
  189. def getHeaders(cls, platform):
  190. if platform == 'darwin':
  191. return ('<OpenGL/gl.h>', )
  192. else:
  193. return ('<GL/gl.h>', )
  194. @classmethod
  195. def getCompileFlags(cls, platform, linkStatic, distroRoot):
  196. if platform in ('netbsd', 'openbsd'):
  197. return '-isystem /usr/X11R6/include -isystem /usr/X11R7/include'
  198. else:
  199. return super().getCompileFlags(
  200. platform, linkStatic, distroRoot
  201. )
  202. @classmethod
  203. def getLinkFlags(cls, platform, linkStatic, distroRoot):
  204. if platform == 'darwin':
  205. return '-framework OpenGL'
  206. elif platform.startswith('mingw'):
  207. return '-lopengl32'
  208. elif platform in ('netbsd', 'openbsd'):
  209. return '-L/usr/X11R6/lib -L/usr/X11R7/lib -lGL'
  210. else:
  211. return super().getLinkFlags(platform, linkStatic, distroRoot)
  212. @classmethod
  213. def getVersion(cls, platform, linkStatic, distroRoot):
  214. def execute(cmd, log):
  215. versionPairs = tuple(
  216. ( major, minor )
  217. for major in range(1, 10)
  218. for minor in range(0, 10)
  219. )
  220. version = cmd.expand(log, cls.getHeaders(platform), *(
  221. 'GL_VERSION_%d_%d' % pair for pair in versionPairs
  222. ))
  223. try:
  224. return '%d.%d' % max(
  225. ver
  226. for ver, exp in zip(versionPairs, version)
  227. if exp is not None
  228. )
  229. except ValueError:
  230. return None
  231. return execute
  232. class GLEW(Library):
  233. makeName = 'GLEW'
  234. header = '<GL/glew.h>'
  235. function = 'glewInit'
  236. dependsOn = ('GL', )
  237. @classmethod
  238. def getLibName(cls, platform):
  239. if platform.startswith('mingw'):
  240. return 'glew32'
  241. else:
  242. return 'GLEW'
  243. @classmethod
  244. def getCompileFlags(cls, platform, linkStatic, distroRoot):
  245. flags = super().getCompileFlags(
  246. platform, linkStatic, distroRoot
  247. )
  248. if platform.startswith('mingw') and linkStatic:
  249. return '%s -DGLEW_STATIC' % flags
  250. else:
  251. return flags
  252. class LibPNG(Library):
  253. libName = 'png16'
  254. makeName = 'PNG'
  255. header = '<png.h>'
  256. configScriptName = 'libpng-config'
  257. dynamicLibsOption = '--ldflags'
  258. function = 'png_write_image'
  259. dependsOn = ('ZLIB', )
  260. @classmethod
  261. def isSystemLibrary(cls, platform):
  262. return platform in ('dingux',)
  263. class OGG(Library):
  264. libName = 'ogg'
  265. makeName = 'OGG'
  266. header = '<ogg/ogg.h>'
  267. function = 'ogg_stream_init'
  268. @classmethod
  269. def isSystemLibrary(cls, platform):
  270. return platform in ('dingux',)
  271. class SDL2(Library):
  272. libName = 'SDL2'
  273. makeName = 'SDL2'
  274. header = '<SDL.h>'
  275. configScriptName = 'sdl2-config'
  276. staticLibsOption = '--static-libs'
  277. function = 'SDL_Init'
  278. @classmethod
  279. def isSystemLibrary(cls, platform):
  280. return platform in ('dingux',)
  281. class SDL2_ttf(Library):
  282. libName = 'SDL2_ttf'
  283. makeName = 'SDL2_TTF'
  284. header = '<SDL_ttf.h>'
  285. function = 'TTF_OpenFont'
  286. dependsOn = ('SDL2', 'FREETYPE')
  287. @classmethod
  288. def isSystemLibrary(cls, platform):
  289. return platform in ('dingux',)
  290. @classmethod
  291. def getLinkFlags(cls, platform, linkStatic, distroRoot):
  292. flags = super().getLinkFlags(
  293. platform, linkStatic, distroRoot
  294. )
  295. if not linkStatic:
  296. # Because of the SDLmain trickery, we need SDL's link flags too
  297. # on some platforms even though we're linking dynamically.
  298. flags += ' ' + SDL2.getLinkFlags(platform, linkStatic, distroRoot)
  299. return flags
  300. @classmethod
  301. def getVersion(cls, platform, linkStatic, distroRoot):
  302. def execute(cmd, log):
  303. version = cmd.expand(log, cls.getHeaders(platform),
  304. 'SDL_TTF_MAJOR_VERSION',
  305. 'SDL_TTF_MINOR_VERSION',
  306. 'SDL_TTF_PATCHLEVEL',
  307. )
  308. return None if None in version else '%s.%s.%s' % version
  309. return execute
  310. class TCL(Library):
  311. libName = 'tcl'
  312. makeName = 'TCL'
  313. header = '<tcl.h>'
  314. function = 'Tcl_CreateInterp'
  315. @classmethod
  316. def getTclConfig(cls, platform, distroRoot):
  317. '''Tcl has a config script that is unlike the typical lib-config script.
  318. Information is gathered by sourcing the config script, instead of
  319. executing it and capturing the queried value from stdout. This script
  320. is located in a library directory, not in a directory in the PATH.
  321. Also, it does not have the executable bit set.
  322. This method returns the location of the Tcl config script, or None if
  323. it could not be found.
  324. '''
  325. if hasattr(cls, 'tclConfig'):
  326. # Return cached value.
  327. return cls.tclConfig
  328. def iterLocations():
  329. # Allow the user to specify the location we should search first,
  330. # by setting an environment variable.
  331. tclpath = environ.get('TCL_CONFIG')
  332. if tclpath is not None:
  333. yield tclpath
  334. if distroRoot is None or cls.isSystemLibrary(platform):
  335. if msysActive():
  336. roots = (msysPathToNative('/mingw32'), )
  337. else:
  338. roots = ('/usr/local', '/usr')
  339. else:
  340. roots = (distroRoot, )
  341. for root in roots:
  342. if isdir(root):
  343. for libdir in ('lib', 'lib64', 'lib/tcl'):
  344. libpath = root + '/' + libdir
  345. if isdir(libpath):
  346. yield libpath
  347. for entry in listdir(libpath):
  348. if entry.startswith('tcl8.'):
  349. tclpath = libpath + '/' + entry
  350. if isdir(tclpath):
  351. yield tclpath
  352. tclConfigs = {}
  353. with open('derived/tcl-search.log', 'w', encoding='utf-8') as log:
  354. print('Looking for Tcl...', file=log)
  355. for location in iterLocations():
  356. path = location + '/tclConfig.sh'
  357. if isfile(path):
  358. print('Config script: %s' % path, file=log)
  359. text = captureStdout(
  360. log,
  361. "sh -c '. %s && echo %s'" % (
  362. path, '$TCL_MAJOR_VERSION $TCL_MINOR_VERSION'
  363. )
  364. )
  365. if text is not None:
  366. try:
  367. # pylint: disable-msg=E1103
  368. major, minor = text.split()
  369. version = int(major), int(minor)
  370. except ValueError:
  371. pass
  372. else:
  373. print('Found: version %d.%d' % version, file=log)
  374. tclConfigs[path] = version
  375. try:
  376. # Minimum required version is 8.5.
  377. # Pick the oldest possible version to minimize the risk of
  378. # running into incompatible changes.
  379. tclConfig = min(
  380. ( version, path )
  381. for path, version in tclConfigs.items()
  382. if version >= (8, 5)
  383. )[1]
  384. except ValueError:
  385. tclConfig = None
  386. print('No suitable versions found.', file=log)
  387. else:
  388. print('Selected: %s' % tclConfig, file=log)
  389. cls.tclConfig = tclConfig
  390. return tclConfig
  391. @classmethod
  392. def evalTclConfigExpr(cls, platform, distroRoot, expr, description):
  393. tclConfig = cls.getTclConfig(platform, distroRoot)
  394. if tclConfig is None:
  395. return None
  396. with open('derived/tcl-search.log', 'a', encoding='utf-8') as log:
  397. print('Getting Tcl %s...' % description, file=log)
  398. text = captureStdout(
  399. log,
  400. shjoin([
  401. 'sh', '-c',
  402. '. %s && eval "echo \\"%s\\""' % (tclConfig, expr)
  403. ])
  404. )
  405. if text is not None:
  406. print('Result: %s' % text.strip(), file=log)
  407. return None if text is None else text.strip()
  408. @classmethod
  409. def getCompileFlags(cls, platform, linkStatic, distroRoot):
  410. wantShared = not linkStatic or cls.isSystemLibrary(platform)
  411. # The -DSTATIC_BUILD is a hack to avoid including the complete
  412. # TCL_DEFS (see 9f1dbddda2) but still being able to link on
  413. # MinGW (tcl.h depends on this being defined properly).
  414. return cls.evalTclConfigExpr(
  415. platform,
  416. distroRoot,
  417. '${TCL_INCLUDE_SPEC}' + ('' if wantShared else ' -DSTATIC_BUILD'),
  418. 'compile flags'
  419. )
  420. @classmethod
  421. def getLinkFlags(cls, platform, linkStatic, distroRoot):
  422. # Tcl can be built as a shared or as a static library, but not both.
  423. # Check whether the library type of Tcl matches the one we want.
  424. wantShared = not linkStatic or cls.isSystemLibrary(platform)
  425. tclShared = cls.evalTclConfigExpr(
  426. platform,
  427. distroRoot,
  428. '${TCL_SHARED_BUILD}',
  429. 'library type (shared/static)'
  430. )
  431. with open('derived/tcl-search.log', 'a', encoding='utf-8') as log:
  432. if tclShared == '0':
  433. if wantShared:
  434. print(
  435. 'Dynamic linking requested, but Tcl installation has '
  436. 'static library.', file=log
  437. )
  438. return None
  439. elif tclShared == '1':
  440. if not wantShared:
  441. print(
  442. 'Static linking requested, but Tcl installation has '
  443. 'dynamic library.', file=log
  444. )
  445. return None
  446. else:
  447. print(
  448. 'Unable to determine whether Tcl installation has '
  449. 'shared or static library.', file=log
  450. )
  451. return None
  452. # Now get the link flags.
  453. if wantShared:
  454. return cls.evalTclConfigExpr(
  455. platform,
  456. distroRoot,
  457. '${TCL_LIB_SPEC}',
  458. 'dynamic link flags'
  459. )
  460. else:
  461. return cls.evalTclConfigExpr(
  462. platform,
  463. distroRoot,
  464. '${TCL_EXEC_PREFIX}/lib/${TCL_LIB_FILE} ${TCL_LIBS}',
  465. 'static link flags'
  466. )
  467. @classmethod
  468. def getVersion(cls, platform, linkStatic, distroRoot):
  469. return cls.evalTclConfigExpr(
  470. platform,
  471. distroRoot,
  472. '${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_PATCH_LEVEL}',
  473. 'version'
  474. )
  475. class Theora(Library):
  476. libName = 'theoradec'
  477. makeName = 'THEORA'
  478. header = '<theora/theoradec.h>'
  479. function = 'th_decode_ycbcr_out'
  480. dependsOn = ('OGG', )
  481. @classmethod
  482. def isSystemLibrary(cls, platform):
  483. return platform in ('dingux',)
  484. class Vorbis(Library):
  485. libName = 'vorbis'
  486. makeName = 'VORBIS'
  487. header = '<vorbis/codec.h>'
  488. function = 'vorbis_synthesis_pcmout'
  489. dependsOn = ('OGG', )
  490. @classmethod
  491. def isSystemLibrary(cls, platform):
  492. return platform in ('dingux',)
  493. class ZLib(Library):
  494. libName = 'z'
  495. makeName = 'ZLIB'
  496. header = '<zlib.h>'
  497. function = 'inflate'
  498. @classmethod
  499. def isSystemLibrary(cls, platform):
  500. return platform in ('dingux',)
  501. @classmethod
  502. def getVersion(cls, platform, linkStatic, distroRoot):
  503. def execute(cmd, log):
  504. version = cmd.expand(log, cls.getHeaders(platform), 'ZLIB_VERSION')
  505. return None if version is None else version.strip('"')
  506. return execute
  507. # Build a dictionary of libraries using introspection.
  508. librariesByName = {
  509. obj.makeName: obj
  510. for obj in locals().values()
  511. if isinstance(obj, type)
  512. and issubclass(obj, Library)
  513. and obj is not Library
  514. }
  515. def allDependencies(makeNames):
  516. '''Compute the set of all directly and indirectly required libraries to
  517. build and use the given set of libraries.
  518. Returns the make names of the required libraries.
  519. '''
  520. # Compute the reflexive-transitive closure.
  521. transLibs = set()
  522. newLibs = set(makeNames)
  523. while newLibs:
  524. transLibs.update(newLibs)
  525. newLibs = set(
  526. depMakeName
  527. for makeName in newLibs
  528. for depMakeName in librariesByName[makeName].dependsOn
  529. if depMakeName not in transLibs
  530. )
  531. return transLibs