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.

254 lines
7.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. #include <regex>
  20. //==============================================================================
  21. class XcodeProjectParser
  22. {
  23. public:
  24. //==============================================================================
  25. static std::unique_ptr<HashMap<std::string, std::string>> parseObjects (const File& projectFile)
  26. {
  27. auto pbxprojs = projectFile.findChildFiles (File::TypesOfFileToFind::findFiles, false, "*.pbxproj");
  28. if (pbxprojs.isEmpty())
  29. {
  30. jassertfalse;
  31. return nullptr;
  32. }
  33. auto content = pbxprojs[0].loadFileAsString().toStdString();
  34. std::regex comments ("/\\*.*?\\*/");
  35. std::string empty ("");
  36. content = (std::regex_replace (content, comments, empty));
  37. std::regex whitespace ("\\s+");
  38. std::string space (" ");
  39. content = (std::regex_replace (content, whitespace, space));
  40. auto objects = std::make_unique<HashMap<std::string, std::string>>();
  41. std::smatch objectsStartMatch;
  42. if (! std::regex_search (content, objectsStartMatch, std::regex ("[ ;{]+objects *= *\\{")))
  43. {
  44. jassertfalse;
  45. return nullptr;
  46. }
  47. auto strPtr = content.begin() + objectsStartMatch.position() + objectsStartMatch.length();
  48. while (strPtr++ != content.end())
  49. {
  50. if (*strPtr == ' ' || *strPtr == ';')
  51. continue;
  52. if (*strPtr == '}')
  53. break;
  54. auto groupReference = parseObjectID (content, strPtr);
  55. if (groupReference.empty())
  56. {
  57. jassertfalse;
  58. return nullptr;
  59. }
  60. while (*strPtr == ' ' || *strPtr == '=')
  61. {
  62. if (++strPtr == content.end())
  63. {
  64. jassertfalse;
  65. return nullptr;
  66. }
  67. }
  68. auto bracedContent = parseBracedContent (content, strPtr);
  69. if (bracedContent.empty())
  70. return nullptr;
  71. objects->set (groupReference, bracedContent);
  72. }
  73. jassert (strPtr <= content.end());
  74. return objects;
  75. }
  76. static std::pair<std::string, std::string> findObjectMatching (const HashMap<std::string, std::string>& objects,
  77. const std::regex& rgx)
  78. {
  79. HashMap<std::string, std::string>::Iterator it (objects);
  80. std::smatch match;
  81. while (it.next())
  82. {
  83. auto key = it.getValue();
  84. if (std::regex_search (key, match, rgx))
  85. return { it.getKey(), it.getValue() };
  86. }
  87. return {};
  88. }
  89. //==============================================================================
  90. struct BuildProduct
  91. {
  92. String name;
  93. String path;
  94. };
  95. static std::vector<BuildProduct> parseBuildProducts (const File& projectFile)
  96. {
  97. auto objects = parseObjects (projectFile);
  98. if (objects == nullptr)
  99. return {};
  100. auto mainObject = findObjectMatching (*objects, std::regex ("[ ;{]+isa *= *PBXProject[ ;}]+"));
  101. jassert (! mainObject.first.empty());
  102. auto targetRefs = parseObjectItemList (mainObject.second, "targets");
  103. jassert (! targetRefs.isEmpty());
  104. std::vector<BuildProduct> results;
  105. for (auto& t : targetRefs)
  106. {
  107. auto targetRef = t.toStdString();
  108. if (! objects->contains (targetRef))
  109. {
  110. jassertfalse;
  111. continue;
  112. }
  113. auto name = parseObjectItemValue (objects->getReference (targetRef), "name");
  114. if (name.empty())
  115. continue;
  116. auto productRef = parseObjectItemValue (objects->getReference (targetRef), "productReference");
  117. if (productRef.empty())
  118. continue;
  119. if (! objects->contains (productRef))
  120. {
  121. jassertfalse;
  122. continue;
  123. }
  124. auto path = parseObjectItemValue (objects->getReference (productRef), "path");
  125. if (path.empty())
  126. continue;
  127. results.push_back ({ String (name).unquoted(), String (path).unquoted() });
  128. }
  129. return results;
  130. }
  131. private:
  132. //==============================================================================
  133. static std::string parseObjectID (std::string& content, std::string::iterator& ptr)
  134. {
  135. auto start = ptr;
  136. while (ptr != content.end() && *ptr != ' ' && *ptr != ';' && *ptr != '=')
  137. ++ptr;
  138. return ptr == content.end() ? std::string()
  139. : content.substr ((size_t) std::distance (content.begin(), start),
  140. (size_t) std::distance (start, ptr));
  141. }
  142. //==============================================================================
  143. static std::string parseBracedContent (std::string& content, std::string::iterator& ptr)
  144. {
  145. jassert (*ptr == '{');
  146. auto start = ++ptr;
  147. auto braceDepth = 1;
  148. while (ptr++ != content.end())
  149. {
  150. switch (*ptr)
  151. {
  152. case '{':
  153. ++braceDepth;
  154. break;
  155. case '}':
  156. if (--braceDepth == 0)
  157. return content.substr ((size_t) std::distance (content.begin(), start),
  158. (size_t) std::distance (start, ptr));
  159. default:
  160. break;
  161. }
  162. }
  163. jassertfalse;
  164. return {};
  165. }
  166. //==============================================================================
  167. static std::string parseObjectItemValue (const std::string& source, const std::string& key)
  168. {
  169. std::smatch match;
  170. if (! std::regex_search (source, match, std::regex ("[ ;{]+" + key + " *= *(.*?) *;")))
  171. {
  172. jassertfalse;
  173. return {};
  174. }
  175. return match[1];
  176. }
  177. //==============================================================================
  178. static StringArray parseObjectItemList (const std::string& source, const std::string& key)
  179. {
  180. std::smatch match;
  181. if (! std::regex_search (source, match, std::regex ("[ ;{]+" + key + " *= *\\((.*?)\\)")))
  182. {
  183. jassertfalse;
  184. return {};
  185. }
  186. auto result = StringArray::fromTokens (String (match[1]), ", ", "");
  187. result.removeEmptyStrings();
  188. return result;
  189. }
  190. };