/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once #include //============================================================================== class XcodeProjectParser { public: //============================================================================== static std::unique_ptr> parseObjects (const File& projectFile) { auto pbxprojs = projectFile.findChildFiles (File::TypesOfFileToFind::findFiles, false, "*.pbxproj"); if (pbxprojs.isEmpty()) { jassertfalse; return nullptr; } auto content = pbxprojs[0].loadFileAsString().toStdString(); std::regex comments ("/\\*.*?\\*/"); std::string empty (""); content = (std::regex_replace (content, comments, empty)); std::regex whitespace ("\\s+"); std::string space (" "); content = (std::regex_replace (content, whitespace, space)); auto objects = std::make_unique>(); std::smatch objectsStartMatch; if (! std::regex_search (content, objectsStartMatch, std::regex ("[ ;{]+objects *= *\\{"))) { jassertfalse; return nullptr; } auto strPtr = content.begin() + objectsStartMatch.position() + objectsStartMatch.length(); while (strPtr++ != content.end()) { if (*strPtr == ' ' || *strPtr == ';') continue; if (*strPtr == '}') break; auto groupReference = parseObjectID (content, strPtr); if (groupReference.empty()) { jassertfalse; return nullptr; } while (*strPtr == ' ' || *strPtr == '=') { if (++strPtr == content.end()) { jassertfalse; return nullptr; } } auto bracedContent = parseBracedContent (content, strPtr); if (bracedContent.empty()) return nullptr; objects->set (groupReference, bracedContent); } jassert (strPtr <= content.end()); return objects; } static std::pair findObjectMatching (const HashMap& objects, const std::regex& rgx) { HashMap::Iterator it (objects); std::smatch match; while (it.next()) { auto key = it.getValue(); if (std::regex_search (key, match, rgx)) return { it.getKey(), it.getValue() }; } return {}; } //============================================================================== struct BuildProduct { String name; String path; }; static std::vector parseBuildProducts (const File& projectFile) { auto objects = parseObjects (projectFile); if (objects == nullptr) return {}; auto mainObject = findObjectMatching (*objects, std::regex ("[ ;{]+isa *= *PBXProject[ ;}]+")); jassert (! mainObject.first.empty()); auto targetRefs = parseObjectItemList (mainObject.second, "targets"); jassert (! targetRefs.isEmpty()); std::vector results; for (auto& t : targetRefs) { auto targetRef = t.toStdString(); if (! objects->contains (targetRef)) { jassertfalse; continue; } auto name = parseObjectItemValue (objects->getReference (targetRef), "name"); if (name.empty()) continue; auto productRef = parseObjectItemValue (objects->getReference (targetRef), "productReference"); if (productRef.empty()) continue; if (! objects->contains (productRef)) { jassertfalse; continue; } auto path = parseObjectItemValue (objects->getReference (productRef), "path"); if (path.empty()) continue; results.push_back ({ String (name).unquoted(), String (path).unquoted() }); } return results; } private: //============================================================================== static std::string parseObjectID (std::string& content, std::string::iterator& ptr) { auto start = ptr; while (ptr != content.end() && *ptr != ' ' && *ptr != ';' && *ptr != '=') ++ptr; return ptr == content.end() ? std::string() : content.substr ((size_t) std::distance (content.begin(), start), (size_t) std::distance (start, ptr)); } //============================================================================== static std::string parseBracedContent (std::string& content, std::string::iterator& ptr) { jassert (*ptr == '{'); auto start = ++ptr; auto braceDepth = 1; while (ptr++ != content.end()) { switch (*ptr) { case '{': ++braceDepth; break; case '}': if (--braceDepth == 0) return content.substr ((size_t) std::distance (content.begin(), start), (size_t) std::distance (start, ptr)); default: break; } } jassertfalse; return {}; } //============================================================================== static std::string parseObjectItemValue (const std::string& source, const std::string& key) { std::smatch match; if (! std::regex_search (source, match, std::regex ("[ ;{]+" + key + " *= *(.*?) *;"))) { jassertfalse; return {}; } return match[1]; } //============================================================================== static StringArray parseObjectItemList (const std::string& source, const std::string& key) { std::smatch match; if (! std::regex_search (source, match, std::regex ("[ ;{]+" + key + " *= *\\((.*?)\\)"))) { jassertfalse; return {}; } auto result = StringArray::fromTokens (String (match[1]), ", ", ""); result.removeEmptyStrings(); return result; } };