1
0
Fork 0
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:
Anthony Nicholls 2024-02-22 15:47:49 +00:00
parent e265be5a03
commit 89330431c4

View file

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