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);