Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

157 rindas
5.6KB

  1. import argparse
  2. import multiprocessing
  3. import pytest
  4. from pathlib import Path
  5. from concurrent import futures
  6. import sys
  7. from typing import NoReturn, Sequence, Optional
  8. from typing_extensions import Protocol
  9. import subprocess
  10. from . import paths, toolchain
  11. from .dds import DDSWrapper
  12. from .bootstrap import BootstrapMode, get_bootstrap_exe
  13. def make_argparser() -> argparse.ArgumentParser:
  14. """Create an argument parser for the dds-ci command-line"""
  15. parser = argparse.ArgumentParser()
  16. parser.add_argument('-B',
  17. '--bootstrap-with',
  18. help='How are we to obtain a bootstrapped DDS executable?',
  19. metavar='{download,build,skip,lazy}',
  20. type=BootstrapMode,
  21. default=BootstrapMode.Lazy)
  22. parser.add_argument('--rapid', help='Run CI for fast development iterations', action='store_true')
  23. parser.add_argument('--test-toolchain',
  24. '-TT',
  25. type=Path,
  26. metavar='<toolchain-file>',
  27. help='The toolchain to use for the first build, which will be passed through the tests')
  28. parser.add_argument('--main-toolchain',
  29. '-T',
  30. type=Path,
  31. dest='toolchain',
  32. metavar='<toolchain-file>',
  33. help='The toolchain to use for the final build')
  34. parser.add_argument('--jobs',
  35. '-j',
  36. type=int,
  37. help='Number of parallel jobs to use when building and testing',
  38. default=multiprocessing.cpu_count() + 2)
  39. parser.add_argument('--clean', action='store_true', help="Don't remove prior build/deps results")
  40. parser.add_argument('--no-test',
  41. action='store_false',
  42. dest='do_test',
  43. help='Skip testing and just build the final result')
  44. return parser
  45. class CommandArguments(Protocol):
  46. """
  47. The result of parsing argv with the dds-ci argument parser.
  48. """
  49. #: Whether the user wants us to clean result before building
  50. clean: bool
  51. #: The bootstrap method the user has requested
  52. bootstrap_with: BootstrapMode
  53. #: The toolchain to use when building the 'dds' executable that will be tested.
  54. test_toolchain: Optional[Path]
  55. #: The toolchain to use when building the main 'dds' executable to publish
  56. toolchain: Optional[Path]
  57. #: The maximum number of parallel jobs for build and test
  58. jobs: int
  59. #: Whether we should run the pytest tests
  60. do_test: bool
  61. #: Rapid-CI is for 'dds' development purposes
  62. rapid: bool
  63. def parse_argv(argv: Sequence[str]) -> CommandArguments:
  64. """Parse the given dds-ci command-line argument list"""
  65. return make_argparser().parse_args(argv)
  66. def test_build(dds: DDSWrapper, args: CommandArguments) -> DDSWrapper:
  67. """
  68. Execute the build that generates the test-mode executable. Uses the given 'dds'
  69. to build the new dds. Returns a DDSWrapper around the generated test executable.
  70. """
  71. test_tc = args.test_toolchain or toolchain.get_default_test_toolchain()
  72. build_dir = paths.BUILD_DIR / '_ci-test'
  73. with toolchain.fixup_toolchain(test_tc) as new_tc:
  74. dds.build(toolchain=new_tc, root=paths.PROJECT_ROOT, build_root=build_dir, jobs=args.jobs)
  75. return DDSWrapper(build_dir / ('dds' + paths.EXE_SUFFIX))
  76. def run_pytest(dds: DDSWrapper, args: CommandArguments) -> int:
  77. """
  78. Execute pytest, testing against the given 'test_dds' executable. Returns
  79. the exit code of pytest.
  80. """
  81. basetemp = Path('/tmp/dds-ci')
  82. basetemp.mkdir(exist_ok=True, parents=True)
  83. return pytest.main([
  84. '-v',
  85. '--durations=10',
  86. '-n',
  87. str(args.jobs),
  88. f'--basetemp={basetemp}',
  89. f'--dds-exe={dds.path}',
  90. f'--junit-xml={paths.BUILD_DIR}/pytest-junit.xml',
  91. str(paths.PROJECT_ROOT / 'tests/'),
  92. ])
  93. def main_build(dds: DDSWrapper, args: CommandArguments) -> int:
  94. """
  95. Execute the main build of dds using the given 'dds' executable to build itself.
  96. """
  97. main_tc = args.toolchain or (
  98. # If we are in rapid-dev mode, use the test toolchain, which had audit/debug enabled
  99. toolchain.get_default_toolchain() if not args.rapid else toolchain.get_default_test_toolchain())
  100. with toolchain.fixup_toolchain(main_tc) as new_tc:
  101. try:
  102. dds.build(toolchain=new_tc, root=paths.PROJECT_ROOT, build_root=paths.BUILD_DIR, jobs=args.jobs)
  103. except subprocess.CalledProcessError as e:
  104. if args.rapid:
  105. return e.returncode
  106. raise
  107. return 0
  108. def ci_with_dds(dds: DDSWrapper, args: CommandArguments) -> int:
  109. """
  110. Execute CI using the given prior 'dds' executable.
  111. """
  112. if args.clean:
  113. dds.clean(build_dir=paths.BUILD_DIR)
  114. dds.catalog_json_import(paths.PROJECT_ROOT / 'old-catalog.json')
  115. pool = futures.ThreadPoolExecutor()
  116. test_fut = pool.submit(lambda: 0)
  117. if args.do_test and not args.rapid:
  118. test_dds = test_build(dds, args)
  119. test_fut = pool.submit(lambda: run_pytest(test_dds, args))
  120. main_fut = pool.submit(lambda: main_build(dds, args))
  121. for fut in futures.as_completed({test_fut, main_fut}):
  122. if fut.result():
  123. return fut.result()
  124. return 0
  125. def main(argv: Sequence[str]) -> int:
  126. args = parse_argv(argv)
  127. with get_bootstrap_exe(args.bootstrap_with) as f:
  128. return ci_with_dds(f, args)
  129. def start() -> NoReturn:
  130. sys.exit(main(sys.argv[1:]))
  131. if __name__ == "__main__":
  132. start()