gen_travis.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #!/usr/bin/env python3
  2. from itertools import combinations, chain
  3. from enum import Enum, auto
  4. LINUX = 'linux'
  5. OSX = 'osx'
  6. WINDOWS = 'windows'
  7. FREEBSD = 'freebsd'
  8. AMD64 = 'amd64'
  9. ARM64 = 'arm64'
  10. PPC64LE = 'ppc64le'
  11. TRAVIS_TEMPLATE = """\
  12. # This config file is generated by ./scripts/gen_travis.py.
  13. # Do not edit by hand.
  14. # We use 'minimal', because 'generic' makes Windows VMs hang at startup. Also
  15. # the software provided by 'generic' is simply not needed for our tests.
  16. # Differences are explained here:
  17. # https://docs.travis-ci.com/user/languages/minimal-and-generic/
  18. language: minimal
  19. dist: focal
  20. jobs:
  21. include:
  22. {jobs}
  23. before_install:
  24. - |-
  25. if test -f "./scripts/$TRAVIS_OS_NAME/before_install.sh"; then
  26. source ./scripts/$TRAVIS_OS_NAME/before_install.sh
  27. fi
  28. before_script:
  29. - |-
  30. if test -f "./scripts/$TRAVIS_OS_NAME/before_script.sh"; then
  31. source ./scripts/$TRAVIS_OS_NAME/before_script.sh
  32. else
  33. scripts/gen_travis.py > travis_script && diff .travis.yml travis_script
  34. autoconf
  35. # If COMPILER_FLAGS are not empty, add them to CC and CXX
  36. ./configure ${{COMPILER_FLAGS:+ CC="$CC $COMPILER_FLAGS" \
  37. CXX="$CXX $COMPILER_FLAGS"}} $CONFIGURE_FLAGS
  38. make -j3
  39. make -j3 tests
  40. fi
  41. script:
  42. - |-
  43. if test -f "./scripts/$TRAVIS_OS_NAME/script.sh"; then
  44. source ./scripts/$TRAVIS_OS_NAME/script.sh
  45. else
  46. make check
  47. fi
  48. """
  49. class Option(object):
  50. class Type:
  51. COMPILER = auto()
  52. COMPILER_FLAG = auto()
  53. CONFIGURE_FLAG = auto()
  54. MALLOC_CONF = auto()
  55. FEATURE = auto()
  56. def __init__(self, type, value):
  57. self.type = type
  58. self.value = value
  59. @staticmethod
  60. def as_compiler(value):
  61. return Option(Option.Type.COMPILER, value)
  62. @staticmethod
  63. def as_compiler_flag(value):
  64. return Option(Option.Type.COMPILER_FLAG, value)
  65. @staticmethod
  66. def as_configure_flag(value):
  67. return Option(Option.Type.CONFIGURE_FLAG, value)
  68. @staticmethod
  69. def as_malloc_conf(value):
  70. return Option(Option.Type.MALLOC_CONF, value)
  71. @staticmethod
  72. def as_feature(value):
  73. return Option(Option.Type.FEATURE, value)
  74. def __eq__(self, obj):
  75. return (isinstance(obj, Option) and obj.type == self.type
  76. and obj.value == self.value)
  77. # The 'default' configuration is gcc, on linux, with no compiler or configure
  78. # flags. We also test with clang, -m32, --enable-debug, --enable-prof,
  79. # --disable-stats, and --with-malloc-conf=tcache:false. To avoid abusing
  80. # travis though, we don't test all 2**7 = 128 possible combinations of these;
  81. # instead, we only test combinations of up to 2 'unusual' settings, under the
  82. # hope that bugs involving interactions of such settings are rare.
  83. MAX_UNUSUAL_OPTIONS = 2
  84. GCC = Option.as_compiler('CC=gcc CXX=g++')
  85. CLANG = Option.as_compiler('CC=clang CXX=clang++')
  86. CL = Option.as_compiler('CC=cl.exe CXX=cl.exe')
  87. compilers_unusual = [CLANG,]
  88. CROSS_COMPILE_32BIT = Option.as_feature('CROSS_COMPILE_32BIT')
  89. feature_unusuals = [CROSS_COMPILE_32BIT]
  90. configure_flag_unusuals = [Option.as_configure_flag(opt) for opt in (
  91. '--enable-debug',
  92. '--enable-prof',
  93. '--disable-stats',
  94. '--disable-libdl',
  95. '--enable-opt-safety-checks',
  96. '--with-lg-page=16',
  97. )]
  98. malloc_conf_unusuals = [Option.as_malloc_conf(opt) for opt in (
  99. 'tcache:false',
  100. 'dss:primary',
  101. 'percpu_arena:percpu',
  102. 'background_thread:true',
  103. )]
  104. all_unusuals = (compilers_unusual + feature_unusuals
  105. + configure_flag_unusuals + malloc_conf_unusuals)
  106. def get_extra_cflags(os, compiler):
  107. if os == FREEBSD:
  108. return []
  109. if os == WINDOWS:
  110. # For non-CL compilers under Windows (for now it's only MinGW-GCC),
  111. # -fcommon needs to be specified to correctly handle multiple
  112. # 'malloc_conf' symbols and such, which are declared weak under Linux.
  113. # Weak symbols don't work with MinGW-GCC.
  114. if compiler != CL.value:
  115. return ['-fcommon']
  116. else:
  117. return []
  118. # We get some spurious errors when -Warray-bounds is enabled.
  119. extra_cflags = ['-Werror', '-Wno-array-bounds']
  120. if compiler == CLANG.value or os == OSX:
  121. extra_cflags += [
  122. '-Wno-unknown-warning-option',
  123. '-Wno-ignored-attributes'
  124. ]
  125. if os == OSX:
  126. extra_cflags += [
  127. '-Wno-deprecated-declarations',
  128. ]
  129. return extra_cflags
  130. # Formats a job from a combination of flags
  131. def format_job(os, arch, combination):
  132. compilers = [x.value for x in combination if x.type == Option.Type.COMPILER]
  133. assert(len(compilers) <= 1)
  134. compiler_flags = [x.value for x in combination if x.type == Option.Type.COMPILER_FLAG]
  135. configure_flags = [x.value for x in combination if x.type == Option.Type.CONFIGURE_FLAG]
  136. malloc_conf = [x.value for x in combination if x.type == Option.Type.MALLOC_CONF]
  137. features = [x.value for x in combination if x.type == Option.Type.FEATURE]
  138. if len(malloc_conf) > 0:
  139. configure_flags.append('--with-malloc-conf=' + ','.join(malloc_conf))
  140. if not compilers:
  141. compiler = GCC.value
  142. else:
  143. compiler = compilers[0]
  144. extra_environment_vars = ''
  145. cross_compile = CROSS_COMPILE_32BIT.value in features
  146. if os == LINUX and cross_compile:
  147. compiler_flags.append('-m32')
  148. features_str = ' '.join([' {}=yes'.format(feature) for feature in features])
  149. stringify = lambda arr, name: ' {}="{}"'.format(name, ' '.join(arr)) if arr else ''
  150. env_string = '{}{}{}{}{}{}'.format(
  151. compiler,
  152. features_str,
  153. stringify(compiler_flags, 'COMPILER_FLAGS'),
  154. stringify(configure_flags, 'CONFIGURE_FLAGS'),
  155. stringify(get_extra_cflags(os, compiler), 'EXTRA_CFLAGS'),
  156. extra_environment_vars)
  157. job = ' - os: {}\n'.format(os)
  158. job += ' arch: {}\n'.format(arch)
  159. job += ' env: {}'.format(env_string)
  160. return job
  161. def generate_unusual_combinations(unusuals, max_unusual_opts):
  162. """
  163. Generates different combinations of non-standard compilers, compiler flags,
  164. configure flags and malloc_conf settings.
  165. @param max_unusual_opts: Limit of unusual options per combination.
  166. """
  167. return chain.from_iterable(
  168. [combinations(unusuals, i) for i in range(max_unusual_opts + 1)])
  169. def included(combination, exclude):
  170. """
  171. Checks if the combination of options should be included in the Travis
  172. testing matrix.
  173. @param exclude: A list of options to be avoided.
  174. """
  175. return not any(excluded in combination for excluded in exclude)
  176. def generate_jobs(os, arch, exclude, max_unusual_opts, unusuals=all_unusuals):
  177. jobs = []
  178. for combination in generate_unusual_combinations(unusuals, max_unusual_opts):
  179. if included(combination, exclude):
  180. jobs.append(format_job(os, arch, combination))
  181. return '\n'.join(jobs)
  182. def generate_linux(arch):
  183. os = LINUX
  184. # Only generate 2 unusual options for AMD64 to reduce matrix size
  185. max_unusual_opts = MAX_UNUSUAL_OPTIONS if arch == AMD64 else 1
  186. exclude = []
  187. if arch == PPC64LE:
  188. # Avoid 32 bit builds and clang on PowerPC
  189. exclude = (CROSS_COMPILE_32BIT, CLANG,)
  190. return generate_jobs(os, arch, exclude, max_unusual_opts)
  191. def generate_macos(arch):
  192. os = OSX
  193. max_unusual_opts = 1
  194. exclude = ([Option.as_malloc_conf(opt) for opt in (
  195. 'dss:primary',
  196. 'percpu_arena:percpu',
  197. 'background_thread:true')] +
  198. [Option.as_configure_flag('--enable-prof')] +
  199. [CLANG,])
  200. return generate_jobs(os, arch, exclude, max_unusual_opts)
  201. def generate_windows(arch):
  202. os = WINDOWS
  203. max_unusual_opts = 3
  204. unusuals = (
  205. Option.as_configure_flag('--enable-debug'),
  206. CL,
  207. CROSS_COMPILE_32BIT,
  208. )
  209. return generate_jobs(os, arch, (), max_unusual_opts, unusuals)
  210. def generate_freebsd(arch):
  211. os = FREEBSD
  212. max_unusual_opts = 4
  213. unusuals = (
  214. Option.as_configure_flag('--enable-debug'),
  215. Option.as_configure_flag('--enable-prof --enable-prof-libunwind'),
  216. Option.as_configure_flag('--with-lg-page=16 --with-malloc-conf=tcache:false'),
  217. CROSS_COMPILE_32BIT,
  218. )
  219. return generate_jobs(os, arch, (), max_unusual_opts, unusuals)
  220. def get_manual_jobs():
  221. return """\
  222. # Development build
  223. - os: linux
  224. env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug \
  225. --disable-cache-oblivious --enable-stats --enable-log --enable-prof" \
  226. EXTRA_CFLAGS="-Werror -Wno-array-bounds"
  227. # --enable-expermental-smallocx:
  228. - os: linux
  229. env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug \
  230. --enable-experimental-smallocx --enable-stats --enable-prof" \
  231. EXTRA_CFLAGS="-Werror -Wno-array-bounds"
  232. """
  233. def main():
  234. jobs = '\n'.join((
  235. generate_windows(AMD64),
  236. generate_freebsd(AMD64),
  237. generate_linux(AMD64),
  238. generate_linux(PPC64LE),
  239. generate_macos(AMD64),
  240. get_manual_jobs(),
  241. ))
  242. print(TRAVIS_TEMPLATE.format(jobs=jobs))
  243. if __name__ == '__main__':
  244. main()