From 885168568d18fa7a82f9bfccc7497cabae2d37d7 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Wed, 10 Oct 2018 10:23:09 +0100 Subject: [PATCH] Projucer: Enabled adding Xcode subprojects --- .../MacOSX/Projucer.xcodeproj/project.pbxproj | 8 + .../VisualStudio2013/Projucer_App.vcxproj | 1 + .../Projucer_App.vcxproj.filters | 3 + .../VisualStudio2015/Projucer_App.vcxproj | 1 + .../Projucer_App.vcxproj.filters | 3 + .../VisualStudio2017/Projucer_App.vcxproj | 1 + .../Projucer_App.vcxproj.filters | 3 + extras/Projucer/Projucer.jucer | 2 + .../ProjectSaving/jucer_ProjectExport_Xcode.h | 226 +++++++++++++--- .../ProjectSaving/jucer_XcodeProjectParser.h | 242 ++++++++++++++++++ .../Source/Utility/Helpers/jucer_PresetIDs.h | 1 + 11 files changed, 459 insertions(+), 32 deletions(-) create mode 100644 extras/Projucer/Source/ProjectSaving/jucer_XcodeProjectParser.h diff --git a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj index fafb894a96..b19c31b5ea 100644 --- a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj +++ b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj @@ -374,6 +374,13 @@ path = "../../Source/Application/jucer_CommandLine.cpp"; sourceTree = "SOURCE_ROOT"; }; + 044478BB994878E35D30154A = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = "jucer_XcodeProjectParser.h"; + path = "../../Source/ProjectSaving/jucer_XcodeProjectParser.h"; + sourceTree = "SOURCE_ROOT"; + }; 0462692BAA9CD1BE6DFBCC33 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; @@ -2985,6 +2992,7 @@ 9EE3141E20C9CE3EA182FA04, E13A54A6D3A1895EACE53E51, 25BE1265FE6C6EA3473A3A0A, + 044478BB994878E35D30154A, ); name = ProjectSaving; sourceTree = ""; diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj index d9cf2e63cc..50e4c16b06 100644 --- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj @@ -1611,6 +1611,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters index 192f61833c..fb387692d1 100644 --- a/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2013/Projucer_App.vcxproj.filters @@ -2283,6 +2283,9 @@ Projucer\ProjectSaving + + Projucer\ProjectSaving + Projucer\Settings diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj index ba68825b5f..ff8d7f5306 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj @@ -1611,6 +1611,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters index ae41cd3d61..6ee51a73ef 100644 --- a/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2015/Projucer_App.vcxproj.filters @@ -2283,6 +2283,9 @@ Projucer\ProjectSaving + + Projucer\ProjectSaving + Projucer\Settings diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index dc44c7d0b6..103fb3eeec 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -1613,6 +1613,7 @@ + diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 7839a4f1c9..db060707d2 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -2283,6 +2283,9 @@ Projucer\ProjectSaving + + Projucer\ProjectSaving + Projucer\Settings diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index 22b65a3121..fc8483a550 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -608,6 +608,8 @@ file="Source/ProjectSaving/jucer_ResourceFile.cpp"/> + pbxBuildFiles, pbxFileReferences, pbxGroups, misc, projectConfigs, targetConfigs; mutable StringArray resourceIDs, sourceIDs, targetIDs; - mutable StringArray frameworkFileIDs, rezFileIDs, resourceFileRefs; + mutable StringArray frameworkFileIDs, embeddedFrameworkIDs, rezFileIDs, resourceFileRefs, subprojectFileIDs; + mutable Array> subprojectReferences; mutable File menuNibFile, iconFile; mutable StringArray buildProducts; const bool iOS; - ValueWithDefault customPListValue, pListPrefixHeaderValue, pListPreprocessValue, extraFrameworksValue, frameworkSearchPathsValue, extraCustomFrameworksValue, embeddedFrameworksValue, - postbuildCommandValue, prebuildCommandValue, duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue, + ValueWithDefault customPListValue, pListPrefixHeaderValue, pListPreprocessValue, + subprojectsValue, + extraFrameworksValue, frameworkSearchPathsValue, extraCustomFrameworksValue, embeddedFrameworksValue, + postbuildCommandValue, prebuildCommandValue, + duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue, iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue, microphonePermissionNeededValue, microphonePermissionsTextValue, cameraPermissionNeededValue, cameraPermissionTextValue, uiFileSharingEnabledValue, uiSupportsDocumentBrowserValue, uiStatusBarHiddenValue, documentExtensionsValue, iosInAppPurchasesValue, @@ -1803,9 +1817,14 @@ private: { prepareTargets(); + // Must be called before adding embedded frameworks, as we want to + // embed any frameworks found in subprojects. + addSubprojects(); + addFrameworks(); addCustomFrameworks(); addEmbeddedFrameworks(); + addCustomResourceFolders(); addPlistFileReferences(); @@ -1948,6 +1967,13 @@ private: addGroup (productsGroupID, "Products", buildProducts); topLevelGroupIDs.add (productsGroupID); } + + if (! subprojectFileIDs.isEmpty()) + { + auto subprojectLibrariesGroupID = createID ("__subprojects"); + addGroup (subprojectLibrariesGroupID, "Subprojects", subprojectFileIDs); + topLevelGroupIDs.add (subprojectLibrariesGroupID); + } } void addBuildPhases() const @@ -2479,12 +2505,9 @@ private: void addEmbeddedFrameworks() const { - auto frameworks = getEmbeddedFrameworks(); - - if (frameworks.isEmpty()) - return; - - StringArray embeddedFrameworkIDs; + StringArray frameworks; + frameworks.addTokens (getEmbeddedFrameworksString(), true); + frameworks.trim(); for (auto& framework : frameworks) { @@ -2498,17 +2521,9 @@ private: } } - for (auto& target : targets) - target->addCopyFilesPhase ("Embed Frameworks", embeddedFrameworkIDs, kFrameworksFolder); - } - - StringArray getEmbeddedFrameworks() const - { - StringArray frameworks; - frameworks.addTokens (getEmbeddedFrameworksString(), true); - frameworks.trim(); - - return frameworks; + if (! embeddedFrameworkIDs.isEmpty()) + for (auto& target : targets) + target->addCopyFilesPhase ("Embed Frameworks", embeddedFrameworkIDs, kFrameworksFolder); } void addCustomResourceFolders() const @@ -2523,6 +2538,104 @@ private: addCustomResourceFolder (crf); } + void addSubprojects() const + { + auto subprojectLines = StringArray::fromLines (getSubprojectsString()); + subprojectLines.removeEmptyStrings (true); + + Array> subprojects; + + for (auto& line : subprojectLines) + { + String subprojectName (line.upToFirstOccurrenceOf (":", false, false)); + StringArray requestedBuildProducts (StringArray::fromTokens (line.fromFirstOccurrenceOf (":", false, false), ",;|", "\"'")); + requestedBuildProducts.trim(); + subprojects.add ({ subprojectName, requestedBuildProducts }); + } + + for (const auto& subprojectInfo : subprojects) + { + String subprojectPath (subprojectInfo.first); + + if (! subprojectPath.endsWith (".xcodeproj")) + subprojectPath += ".xcodeproj"; + + File subprojectFile; + + if (File::isAbsolutePath (subprojectPath)) + { + subprojectFile = subprojectPath; + } + else + { + subprojectFile = getProject().getProjectFolder().getChildFile (subprojectPath); + + RelativePath p (subprojectPath, RelativePath::projectFolder); + subprojectPath = p.rebased (getProject().getProjectFolder(), getTargetFolder(), RelativePath::buildTargetFolder).toUnixStyle(); + } + + if (! subprojectFile.isDirectory()) + continue; + + auto availableBuildProducts = XcodeProjectParser::parseBuildProducts (subprojectFile); + + // If no build products have been specified then we'll take everything + if (! subprojectInfo.second.isEmpty()) + { + auto newEnd = std::remove_if (availableBuildProducts.begin(), availableBuildProducts.end(), + [&subprojectInfo](const std::pair &item) + { + return ! subprojectInfo.second.contains (item.first); + }); + availableBuildProducts.erase (newEnd, availableBuildProducts.end()); + } + + if (availableBuildProducts.empty()) + continue; + + auto subprojectFileType = getFileType (RelativePath (subprojectPath, RelativePath::projectFolder)); + auto subprojectFileID = addFileOrFolderReference (subprojectPath, "", subprojectFileType); + subprojectFileIDs.add (subprojectFileID); + + StringArray proxyIDs; + + for (auto& buildProduct : availableBuildProducts) + { + auto buildProductFileType = getFileType (RelativePath (buildProduct.second, RelativePath::projectFolder)); + + auto containerID = addContainerItemProxy (subprojectFileID, buildProduct.first); + auto proxyID = addReferenceProxy (containerID, buildProduct.second, buildProductFileType); + proxyIDs.add (proxyID); + + if (buildProductFileType == "archive.ar" || buildProductFileType == "wrapper.framework") + { + auto buildFileID = addBuildFile (buildProduct.second, proxyID, false, true); + + for (auto& target : targets) + target->frameworkIDs.add (buildFileID); + + if (buildProductFileType == "wrapper.framework") + { + auto fileID = createID (buildProduct.second + "buildref"); + + auto* v = new ValueTree (fileID); + v->setProperty ("isa", "PBXBuildFile", nullptr); + v->setProperty ("fileRef", proxyID, nullptr); + v->setProperty ("settings", "{ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }", nullptr); + pbxBuildFiles.add (v); + + embeddedFrameworkIDs.add (fileID); + } + } + } + + auto productGroupID = createFileRefID (subprojectPath + "_products"); + addGroup (productGroupID, "Products", proxyIDs); + + subprojectReferences.add ({ productGroupID, subprojectFileID }); + } + } + void addXcassets() const { auto customXcassetsPath = getCustomXcassetsFolderString(); @@ -2635,17 +2748,8 @@ private: return addFileOrFolderReference (pathString, sourceTree, fileType); } - String addFileOrFolderReference (String pathString, String sourceTree, String fileType) const + void checkAndAddFileReference (std::unique_ptr v) const { - auto fileRefID = createFileRefID (pathString); - - std::unique_ptr v (new ValueTree (fileRefID)); - v->setProperty ("isa", "PBXFileReference", nullptr); - v->setProperty ("lastKnownFileType", fileType, nullptr); - v->setProperty (Ids::name, pathString.fromLastOccurrenceOf ("/", false, false), nullptr); - v->setProperty ("path", pathString, nullptr); - v->setProperty ("sourceTree", sourceTree, nullptr); - auto existing = pbxFileReferences.indexOfSorted (*this, v.get()); if (existing >= 0) @@ -2657,6 +2761,53 @@ private: { pbxFileReferences.addSorted (*this, v.release()); } + } + + String addFileOrFolderReference (const String& pathString, String sourceTree, String fileType) const + { + auto fileRefID = createFileRefID (pathString); + + std::unique_ptr v (new ValueTree (fileRefID)); + v->setProperty ("isa", "PBXFileReference", nullptr); + v->setProperty ("lastKnownFileType", fileType, nullptr); + v->setProperty (Ids::name, pathString.fromLastOccurrenceOf ("/", false, false), nullptr); + v->setProperty ("path", pathString, nullptr); + v->setProperty ("sourceTree", sourceTree, nullptr); + + checkAndAddFileReference (std::move (v)); + + return fileRefID; + } + + String addContainerItemProxy (const String& subprojectID, const String& itemName) const + { + auto uniqueString = subprojectID + "_" + itemName; + auto fileRefID = createFileRefID (uniqueString); + + std::unique_ptr v (new ValueTree (fileRefID)); + v->setProperty ("isa", "PBXContainerItemProxy", nullptr); + v->setProperty ("containerPortal", subprojectID, nullptr); + v->setProperty ("proxyType", 2, nullptr); + v->setProperty ("remoteGlobalIDString", createFileRefID (uniqueString + "_global"), nullptr); + v->setProperty ("remoteInfo", itemName, nullptr); + + checkAndAddFileReference (std::move (v)); + + return fileRefID; + } + + String addReferenceProxy (const String& containerItemID, const String& proxyPath, const String& fileType) const + { + auto fileRefID = createFileRefID (containerItemID + "_" + proxyPath); + + std::unique_ptr v (new ValueTree (fileRefID)); + v->setProperty ("isa", "PBXReferenceProxy", nullptr); + v->setProperty ("fileType", fileType, nullptr); + v->setProperty ("path", proxyPath, nullptr); + v->setProperty ("remoteRef", containerItemID, nullptr); + v->setProperty ("sourceTree", "BUILT_PRODUCTS_DIR", nullptr); + + checkAndAddFileReference (std::move (v)); return fileRefID; } @@ -2996,6 +3147,17 @@ private: v->setProperty ("hasScannedForEncodings", (int) 0, nullptr); v->setProperty ("mainGroup", createID ("__mainsourcegroup"), nullptr); v->setProperty ("projectDirPath", "\"\"", nullptr); + + if (! subprojectReferences.isEmpty()) + { + StringArray projectReferences; + + for (auto& reference : subprojectReferences) + projectReferences.add (indentBracedList ({ "ProductGroup = " + reference.first, "ProjectRef = " + reference.second }, 1)); + + v->setProperty ("projectReferences", indentParenthesisedList (projectReferences), nullptr); + } + v->setProperty ("projectRoot", "\"\"", nullptr); auto targetString = "(" + targetIDs.joinIntoString (", ") + ")"; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_XcodeProjectParser.h b/extras/Projucer/Source/ProjectSaving/jucer_XcodeProjectParser.h new file mode 100644 index 0000000000..46e5adeb83 --- /dev/null +++ b/extras/Projucer/Source/ProjectSaving/jucer_XcodeProjectParser.h @@ -0,0 +1,242 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 ("/\\*.*?\\*/"); + content = (std::regex_replace (content, comments, "")); + + std::regex whitespace ("\\s+"); + content = (std::regex_replace (content, whitespace, " ")); + + 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)); + } + } + + 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; + } +}; diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h index 9fa8d20750..f9f55ed47b 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h @@ -125,6 +125,7 @@ namespace Ids DECLARE_ID (osxCompatibility); DECLARE_ID (osxArchitecture); DECLARE_ID (iosCompatibility); + DECLARE_ID (xcodeSubprojects); DECLARE_ID (extraFrameworks); DECLARE_ID (frameworkSearchPaths); DECLARE_ID (extraCustomFrameworks);