/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2017 - ROLI Ltd. You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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 {}; } //============================================================================== 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; } };