mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Projucer (Xcode): Revert to copying and code signing a plugin instead of symlinking
This commit is contained in:
parent
e265be5a03
commit
89330431c4
1 changed files with 177 additions and 79 deletions
|
|
@ -34,24 +34,130 @@ constexpr auto* macOSArch_32BitUniversal = "32BitUniversal";
|
|||
constexpr auto* macOSArch_64BitUniversal = "64BitUniversal";
|
||||
constexpr auto* macOSArch_64Bit = "64BitIntel";
|
||||
|
||||
static constexpr const char* configGuardTemplate = R"(
|
||||
if test "$CONFIGURATION" = "$JUCE_CONFIG_NAME"; then :
|
||||
$JUCE_GUARDED_SCRIPT
|
||||
fi
|
||||
)";
|
||||
//==============================================================================
|
||||
inline String doubleQuoted (const String& text)
|
||||
{
|
||||
return text.quoted();
|
||||
}
|
||||
|
||||
static constexpr const char* copyPluginScriptTemplate = R"(
|
||||
if [ -e "$JUCE_INSTALL_PATH$JUCE_PRODUCT_NAME" ]; then :
|
||||
echo "Destination '$JUCE_INSTALL_PATH$JUCE_PRODUCT_NAME' exists, overwriting"
|
||||
rm -rf "$JUCE_INSTALL_PATH$JUCE_PRODUCT_NAME"
|
||||
fi
|
||||
mkdir -p "$JUCE_INSTALL_PATH"
|
||||
ln -sfhv "$JUCE_SOURCE_BUNDLE" "$JUCE_INSTALL_PATH"
|
||||
)";
|
||||
inline String singleQuoted (const String& text)
|
||||
{
|
||||
return text.quoted ('\'');
|
||||
}
|
||||
|
||||
static constexpr const char* adhocCodeSignTemplate = R"(
|
||||
xcrun codesign --verify "$JUCE_FULL_PRODUCT_PATH" || xcrun codesign -f -s - "$JUCE_FULL_PRODUCT_PATH"
|
||||
)";
|
||||
//==============================================================================
|
||||
class ScriptBuilder
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
ScriptBuilder() = default;
|
||||
explicit ScriptBuilder (int indentIn) : indent (indentIn) {}
|
||||
|
||||
//==============================================================================
|
||||
template <typename... Args>
|
||||
ScriptBuilder& run (const String& command, Args&&... args)
|
||||
{
|
||||
const auto joined = StringArray { command, std::forward<Args> (args)... }.joinIntoString (" ");
|
||||
return echo ("Running " + joined).insertLine (joined);
|
||||
}
|
||||
|
||||
ScriptBuilder& echo (const String& text)
|
||||
{
|
||||
return insertLine ("echo " + doubleQuoted (text.removeCharacters ("\"")));
|
||||
}
|
||||
|
||||
ScriptBuilder& remove (const String& path)
|
||||
{
|
||||
return run ("rm -rf", doubleQuoted (path));
|
||||
}
|
||||
|
||||
ScriptBuilder& copy (const String& src, const String& dst)
|
||||
{
|
||||
return run ("ditto", doubleQuoted (src), doubleQuoted (dst));
|
||||
}
|
||||
|
||||
ScriptBuilder& set (const String& variableName, const String& defaultValue = singleQuoted (""))
|
||||
{
|
||||
return insertLine (variableName + "=" + doubleQuoted (defaultValue));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ScriptBuilder& ifThen (const String& condition, const String& then)
|
||||
{
|
||||
jassert (then.isNotEmpty());
|
||||
return insertLine ("if [[ " + condition + " ]]; then")
|
||||
.insertScript (ScriptBuilder { indent + 1 }.insertScript (then).toString())
|
||||
.insertLine ("fi")
|
||||
.insertLine();
|
||||
}
|
||||
|
||||
ScriptBuilder& ifCompare (const String& lhs, const String& rhs, const String& comparison, const String& then)
|
||||
{
|
||||
return ifThen (StringArray { doubleQuoted (lhs), comparison, doubleQuoted (rhs) }.joinIntoString (" "), then);
|
||||
}
|
||||
|
||||
ScriptBuilder& ifEqual (const String& lhs, const String& rhs, const String& then)
|
||||
{
|
||||
return ifCompare (lhs, rhs, "==", then);
|
||||
}
|
||||
|
||||
ScriptBuilder& ifSet (const String& variable, const String& then)
|
||||
{
|
||||
return ifThen ("-n " + doubleQuoted ("${" + variable + "-}"), then);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ScriptBuilder& insertLine (const String& line = {})
|
||||
{
|
||||
constexpr auto spacesPerIndent = 2;
|
||||
script.add ((String::repeatedString (" ", spacesPerIndent * indent) + line).trimEnd());
|
||||
return *this;
|
||||
}
|
||||
|
||||
ScriptBuilder& insertLines (const StringArray& lines)
|
||||
{
|
||||
for (const auto& line : lines)
|
||||
insertLine (line);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ScriptBuilder& insertScript (const String& s)
|
||||
{
|
||||
return insertLines (StringArray::fromLines (s.trimEnd()));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isEmpty() const
|
||||
{
|
||||
return script.isEmpty();
|
||||
}
|
||||
|
||||
String toString() const
|
||||
{
|
||||
return script.joinIntoString ("\n") + "\n";
|
||||
}
|
||||
|
||||
String toStringWithShellOptions (const String& options) const
|
||||
{
|
||||
if (isEmpty())
|
||||
return {};
|
||||
|
||||
return ScriptBuilder{}.insertLine ("set " + options)
|
||||
.insertLine()
|
||||
.insertScript (toString())
|
||||
.toString();
|
||||
}
|
||||
|
||||
String toStringWithDefaultShellOptions() const
|
||||
{
|
||||
return toStringWithShellOptions ("-euo pipefail");
|
||||
}
|
||||
|
||||
private:
|
||||
StringArray script;
|
||||
int indent{};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class XcodeProjectExporter final : public ProjectExporter,
|
||||
|
|
@ -1985,17 +2091,17 @@ public:
|
|||
//==============================================================================
|
||||
void addShellScriptBuildPhase (const String& phaseName, const String& script)
|
||||
{
|
||||
if (script.trim().isNotEmpty())
|
||||
{
|
||||
auto v = addBuildPhase ("PBXShellScriptBuildPhase", {});
|
||||
v.setProperty (Ids::name, phaseName, nullptr);
|
||||
v.setProperty ("alwaysOutOfDate", 1, nullptr);
|
||||
v.setProperty ("shellPath", "/bin/sh", nullptr);
|
||||
v.setProperty ("shellScript", script.replace ("\\", "\\\\")
|
||||
.replace ("\"", "\\\"")
|
||||
.replace ("\r\n", "\\n")
|
||||
.replace ("\n", "\\n"), nullptr);
|
||||
}
|
||||
if (script.trim().isEmpty())
|
||||
return;
|
||||
|
||||
auto v = addBuildPhase ("PBXShellScriptBuildPhase", {});
|
||||
v.setProperty (Ids::name, phaseName, nullptr);
|
||||
v.setProperty ("alwaysOutOfDate", 1, nullptr);
|
||||
v.setProperty ("shellPath", "/bin/sh", nullptr);
|
||||
v.setProperty ("shellScript", script.replace ("\\", "\\\\")
|
||||
.replace ("\"", "\\\"")
|
||||
.replace ("\r\n", "\\n")
|
||||
.replace ("\n", "\\n"), nullptr);
|
||||
}
|
||||
|
||||
void addCopyFilesPhase (const String& phaseName, const StringArray& files, XcodeCopyFilesDestinationIDs dst)
|
||||
|
|
@ -2419,38 +2525,30 @@ private:
|
|||
// When building LV2 and VST3 plugins on Arm macs, we need to load and run the plugin
|
||||
// bundle during a post-build step in order to generate the plugin's supporting files.
|
||||
// Arm macs will only load shared libraries if they are signed, but Xcode runs its
|
||||
// signing step after any post-build scripts. As a workaround, we check whether the
|
||||
// plugin is signed and generate an adhoc certificate if necessary, before running
|
||||
// the manifest-generator.
|
||||
// signing step after any post-build scripts. As a workaround, we sign the plugin
|
||||
// using an adhoc certificate.
|
||||
if (target->type == XcodeTarget::VST3PlugIn || target->type == XcodeTarget::LV2PlugIn)
|
||||
{
|
||||
String script = "set -e\n";
|
||||
|
||||
// Delete manifest if it's left over from an old build
|
||||
if (target->type == XcodeTarget::VST3PlugIn)
|
||||
script << "rm -f \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME/Contents/moduleinfo.json\"\n";
|
||||
|
||||
// Sign the bundle so that it can be loaded by the manifest generator tools
|
||||
script << String { adhocCodeSignTemplate }.replace ("$JUCE_FULL_PRODUCT_PATH",
|
||||
"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME");
|
||||
auto script = ScriptBuilder{}
|
||||
.run ("codesign --verbose=4 --force --sign -", doubleQuoted ("${CONFIGURATION_BUILD_DIR}/${FULL_PRODUCT_NAME}"))
|
||||
.insertLine();
|
||||
|
||||
if (target->type == XcodeTarget::LV2PlugIn)
|
||||
{
|
||||
// Note: LV2 has a non-standard config build dir
|
||||
script << "\"$CONFIGURATION_BUILD_DIR/../"
|
||||
+ Project::getLV2FileWriterName()
|
||||
+ "\" \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\"\n";
|
||||
script.run (doubleQuoted ("${CONFIGURATION_BUILD_DIR}/../" + Project::getLV2FileWriterName()),
|
||||
doubleQuoted ("${CONFIGURATION_BUILD_DIR}/${FULL_PRODUCT_NAME}"));
|
||||
}
|
||||
else if (target->type == XcodeTarget::VST3PlugIn)
|
||||
{
|
||||
script << "\"$CONFIGURATION_BUILD_DIR/" << Project::getVST3FileWriterName() << "\" "
|
||||
"-create "
|
||||
"-version " << project.getVersionString().quoted() << " "
|
||||
"-path \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" "
|
||||
"-output \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME/Contents/Resources/moduleinfo.json\"\n";
|
||||
script.run (doubleQuoted ("${CONFIGURATION_BUILD_DIR}/" + Project::getVST3FileWriterName()),
|
||||
"-create",
|
||||
"-version", doubleQuoted (project.getVersionString()),
|
||||
"-path", doubleQuoted ("${CONFIGURATION_BUILD_DIR}/${FULL_PRODUCT_NAME}"),
|
||||
"-output", doubleQuoted ("${CONFIGURATION_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources/moduleinfo.json"));
|
||||
}
|
||||
|
||||
target->addShellScriptBuildPhase ("Update manifest", script);
|
||||
target->addShellScriptBuildPhase ("Update manifest", script.toStringWithDefaultShellOptions());
|
||||
}
|
||||
|
||||
target->addShellScriptBuildPhase ("Post-build script", getPostBuildScript());
|
||||
|
|
@ -2463,51 +2561,51 @@ private:
|
|||
&& target->type == XcodeTarget::UnityPlugIn)
|
||||
embedUnityScript();
|
||||
|
||||
StringArray copyPluginScript;
|
||||
ScriptBuilder copyPluginScript;
|
||||
|
||||
for (ConstConfigIterator config (*this); config.next();)
|
||||
{
|
||||
auto& xcodeConfig = static_cast<const XcodeBuildConfiguration&> (*config);
|
||||
auto installPath = target->getInstallPathForConfiguration (xcodeConfig);
|
||||
|
||||
if (target->xcodeCopyToProductInstallPathAfterBuild && installPath.isNotEmpty())
|
||||
if (installPath.isEmpty() || ! target->xcodeCopyToProductInstallPathAfterBuild)
|
||||
continue;
|
||||
|
||||
if (installPath.startsWith ("~"))
|
||||
installPath = installPath.replace ("~", "$(HOME)");
|
||||
|
||||
installPath = installPath.replace ("$(HOME)", "${HOME}");
|
||||
|
||||
const auto copyScript = [&]
|
||||
{
|
||||
if (installPath.startsWith ("~"))
|
||||
installPath = installPath.replace ("~", "$(HOME)");
|
||||
|
||||
installPath = installPath.replace ("$(HOME)", "$HOME");
|
||||
|
||||
const auto configGuard = String { configGuardTemplate }.replace ("$JUCE_CONFIG_NAME",
|
||||
config->getName());
|
||||
|
||||
const auto signScript = String { adhocCodeSignTemplate }.replace ("$JUCE_FULL_PRODUCT_PATH",
|
||||
"${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}");
|
||||
|
||||
const auto copyScript = [&]
|
||||
const auto generateCopyScript = [](String sourcePlugin, String destinationDir)
|
||||
{
|
||||
const auto base = String { copyPluginScriptTemplate }
|
||||
.replace ("$JUCE_CONFIG_NAME", config->getName())
|
||||
.replace ("$JUCE_INSTALL_PATH", installPath);
|
||||
return ScriptBuilder{}
|
||||
.set ("destinationPlugin", destinationDir + "/$(basename " + doubleQuoted (sourcePlugin) + ")")
|
||||
.remove ("${destinationPlugin}")
|
||||
.copy (sourcePlugin, "${destinationPlugin}")
|
||||
.insertLine()
|
||||
.ifSet ("CODE_SIGN_ENTITLEMENTS",
|
||||
R"(entitlementsArg=(--entitlements "${CODE_SIGN_ENTITLEMENTS}"))")
|
||||
.run ("codesign --verbose=4 --force --sign",
|
||||
doubleQuoted ("${CODE_SIGN_IDENTITY:--}"),
|
||||
"${entitlementsArg[*]-}",
|
||||
"${OTHER_CODE_SIGN_ARGS-}",
|
||||
doubleQuoted ("${destinationPlugin}"));
|
||||
};
|
||||
|
||||
if (target->type == XcodeTarget::Target::LV2PlugIn)
|
||||
{
|
||||
return base.replace ("$JUCE_PRODUCT_NAME", "${TARGET_BUILD_DIR##*/}")
|
||||
.replace ("$JUCE_SOURCE_BUNDLE", "${TARGET_BUILD_DIR}");
|
||||
}
|
||||
if (target->type == XcodeTarget::Target::LV2PlugIn)
|
||||
return generateCopyScript ("${TARGET_BUILD_DIR}", installPath);
|
||||
|
||||
return base.replace ("$JUCE_PRODUCT_NAME", "${FULL_PRODUCT_NAME}")
|
||||
.replace ("$JUCE_SOURCE_BUNDLE", "${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}");
|
||||
}();
|
||||
return generateCopyScript ("${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}", installPath);
|
||||
}();
|
||||
|
||||
copyPluginScript.add (configGuard.replace ("$JUCE_GUARDED_SCRIPT", signScript + copyScript));
|
||||
}
|
||||
copyPluginScript.ifEqual (doubleQuoted ("${CONFIGURATION}"), doubleQuoted (config->getName()),
|
||||
copyScript.toString());
|
||||
}
|
||||
|
||||
if (! copyPluginScript.isEmpty())
|
||||
{
|
||||
copyPluginScript.insert (0, "set -e");
|
||||
target->addShellScriptBuildPhase ("Plugin Copy Step", copyPluginScript.joinIntoString ("\n"));
|
||||
}
|
||||
target->addShellScriptBuildPhase ("Plugin Copy Step", copyPluginScript.toStringWithDefaultShellOptions());
|
||||
|
||||
addTargetObject (*target);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue