The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
6.1KB

  1. import os
  2. import shutil
  3. import re
  4. def get_curly_brace_scope_end(string, start_pos):
  5. """Given a string and the position of an opening curly brace, find the
  6. position of the closing brace.
  7. """
  8. if string[start_pos] != "{":
  9. raise ValueError("string must have \"{\" at start pos")
  10. string_end = len(string)
  11. bracket_counter = 1
  12. start_pos += 1
  13. while start_pos < string_end:
  14. if string[start_pos] == "{":
  15. bracket_counter += 1
  16. elif string[start_pos] == "}":
  17. bracket_counter -= 1
  18. if bracket_counter == 0:
  19. return start_pos
  20. start_pos += 1
  21. return -1
  22. def add_doxygen_group(path, group_name):
  23. """Add a Doxygen group to the file at 'path'.
  24. Namespaces cause all kinds of problems, and we need to ensure that if
  25. the classes in a source file are contained within a namespace then we
  26. also put the @weakgroup inside.
  27. """
  28. filename = os.path.basename(path)
  29. if filename.startswith("juce_") and filename.endswith(".h"):
  30. group_definition_start = ("\r\n/** @weakgroup "
  31. + group_name
  32. + "\r\n * @{\r\n */\r\n")
  33. group_definition_end = "\r\n/** @}*/\r\n"
  34. with open(path, "r") as f:
  35. content = f.read()
  36. # Put the group definitions inside all namespaces.
  37. namespace_regex = re.compile(r"\s+namespace\s+\S+\s+{")
  38. match = namespace_regex.search(content)
  39. while (match is not None):
  40. namespace_end = get_curly_brace_scope_end(content, match.end() - 1)
  41. if namespace_end == -1:
  42. raise ValueError("error finding end of namespace "
  43. + match.group()
  44. + " in "
  45. + path)
  46. content = (content[:match.end()]
  47. + group_definition_start
  48. + content[match.end():namespace_end]
  49. + group_definition_end
  50. + content[namespace_end:])
  51. search_start = (namespace_end
  52. + len(group_definition_start)
  53. + len(group_definition_end))
  54. match = namespace_regex.search(content, search_start)
  55. with open(path, "w") as f:
  56. f.write(group_definition_start)
  57. f.write(content)
  58. f.write(group_definition_end)
  59. ###############################################################################
  60. # Get the list of JUCE modules to include.
  61. juce_modules = ("juce_audio_basics", "juce_audio_devices",
  62. "juce_blocks_basics", "juce_core", "juce_events")
  63. # A temporary directory to hold our preprocessed source files.
  64. build_directory = "build"
  65. # Make sure we have a clean temporary directory.
  66. try:
  67. shutil.rmtree(build_directory)
  68. except OSError as e:
  69. if e.errno != 2:
  70. # An errno of 2 indicates that the directory does not exist, which is
  71. # fine!
  72. raise e
  73. # Copy the JUCE modules to the temporary directory, and process the source
  74. # files.
  75. module_definitions = []
  76. for module_name in juce_modules:
  77. # Copy the required modules.
  78. original_module_dir = os.path.join("..", "..", "..", "modules",
  79. module_name)
  80. module_path = os.path.join(build_directory, module_name)
  81. shutil.copytree(original_module_dir, module_path)
  82. # Parse the module header to get module information.
  83. module_header = os.path.join(module_path, module_name + ".h")
  84. with open(module_header, "r") as f:
  85. content = f.read()
  86. block_info_result = re.match(r".*BEGIN_JUCE_MODULE_DECLARATION"
  87. "(.*)"
  88. "END_JUCE_MODULE_DECLARATION.*",
  89. content,
  90. re.DOTALL)
  91. detail_lines = []
  92. for line in block_info_result.group(1).split("\n"):
  93. stripped_line = line.strip()
  94. if stripped_line:
  95. result = re.match(r"^.*?description:\s*(.*)$", stripped_line)
  96. if result:
  97. short_description = result.group(1)
  98. else:
  99. detail_lines.append(stripped_line)
  100. # The module header causes problems for Doxygen, so delete it.
  101. os.remove(module_header)
  102. # Create a Doxygen group definition for the module.
  103. module_definiton = []
  104. module_definiton.append("/** @defgroup {n} {n}".format(n=module_name))
  105. module_definiton.append(" {d}".format(d=short_description))
  106. module_definiton.append("")
  107. for line in detail_lines:
  108. module_definiton.append(" - {l}".format(l=line))
  109. module_definiton.append("")
  110. module_definiton.append(" @{")
  111. module_definiton.append("*/")
  112. # Create a list of internal directories we can use as subgroups and create
  113. # the Doxygen group hierarchy string.
  114. dir_contents = os.listdir(module_path)
  115. subdirs = [x for x in dir_contents
  116. if os.path.isdir(os.path.join(module_path, x))]
  117. module_groups = {}
  118. for subdir in subdirs:
  119. subgroup_name = "{n}-{s}".format(n=module_name, s=subdir)
  120. module_groups[subgroup_name] = os.path.join(module_path, subdir)
  121. module_definiton.append("")
  122. module_definiton.append(
  123. "/** @defgroup {tag} {n} */".format(tag=subgroup_name, n=subdir)
  124. )
  125. module_definiton.append("")
  126. module_definiton.append("/** @} */")
  127. module_definitions.append("\r\n".join(module_definiton))
  128. # Put the top level files into the main group.
  129. for filename in (set(dir_contents) - set(subdirs)):
  130. add_doxygen_group(os.path.join(module_path, filename), module_name)
  131. # Put subdirectory files into their respective groups.
  132. for group_name in module_groups:
  133. for dirpath, dirnames, filenames in os.walk(module_groups[group_name]):
  134. for filename in filenames:
  135. add_doxygen_group(os.path.join(dirpath, filename), group_name)
  136. # Create an extra header file containing the module hierarchy.
  137. with open(os.path.join(build_directory, "juce_modules.dox"), "w") as f:
  138. f.write("\r\n\r\n".join(module_definitions))