diff --git a/extras/Projucer/CMakeLists.txt b/extras/Projucer/CMakeLists.txt
index 012eda673f..c790f06268 100644
--- a/extras/Projucer/CMakeLists.txt
+++ b/extras/Projucer/CMakeLists.txt
@@ -147,6 +147,7 @@ juce_add_binary_data(ProjucerData SOURCES
Source/BinaryData/gradle/gradle-wrapper.jar
Source/BinaryData/gradle/gradlew
Source/BinaryData/gradle/gradlew.bat
+ Source/BinaryData/juce_SimpleBinaryBuilder.cpp
../Build/CMake/JuceLV2Defines.h.in
../Build/CMake/LaunchScreen.storyboard
@@ -156,7 +157,8 @@ juce_add_binary_data(ProjucerData SOURCES
../Build/CMake/PIPConsole.cpp.in
../Build/CMake/RecentFilesMenuTemplate.nib
../Build/CMake/UnityPluginGUIScript.cs.in
- ../Build/CMake/juce_runtime_arch_detection.cpp)
+ ../Build/CMake/juce_runtime_arch_detection.cpp
+ ../Build/CMake/juce_LinuxSubprocessHelper.cpp)
target_link_libraries(Projucer PRIVATE
ProjucerData
diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer
index f836208ca6..6b6d573d37 100644
--- a/extras/Projucer/Projucer.jucer
+++ b/extras/Projucer/Projucer.jucer
@@ -299,6 +299,10 @@
file="Source/BinaryData/colourscheme_light.xml"/>
+
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//==============================================================================
+struct FileHelpers
+{
+ static std::string getCurrentWorkingDirectory()
+ {
+ std::vector buffer (1024);
+
+ while (getcwd (buffer.data(), buffer.size() - 1) == nullptr && errno == ERANGE)
+ buffer.resize (buffer.size() * 2 / 3);
+
+ return { buffer.data() };
+ }
+
+ static bool endsWith (const std::string& s, char c)
+ {
+ if (s.length() == 0)
+ return false;
+
+ return *s.rbegin() == c;
+ };
+
+ static std::string appendedPaths (const std::string& first, const std::string& second)
+ {
+ return endsWith (first, '/') ? first + second : first + "/" + second;
+ }
+
+ static bool exists (const std::string& path)
+ {
+ return ! path.empty() && access (path.c_str(), F_OK) == 0;
+ }
+
+ static bool deleteFile (const std::string& path)
+ {
+ if (! exists (path))
+ return true;
+
+ return remove (path.c_str()) == 0;
+ }
+
+ static std::string getFilename (const std::string& path)
+ {
+ return { std::find_if (path.rbegin(), path.rend(), [] (auto c) { return c == '/'; }).base(),
+ path.end() };
+ }
+
+ static bool isDirectory (const std::string& path)
+ {
+ struct stat64 info;
+
+ return ! path.empty()
+ && stat64 (path.c_str(), &info) == 0
+ && ((info.st_mode & S_IFDIR) != 0);
+ }
+
+ static std::string getParentDirectory (const std::string& path)
+ {
+ std::string p { path.begin(),
+ std::find_if (path.rbegin(),
+ path.rend(),
+ [] (auto c) { return c == '/'; }).base() };
+
+ // Trim the ending slash, but only if not root
+ if (endsWith (p, '/') && p.length() > 1)
+ return { p.begin(), p.end() - 1 };
+
+ return p;
+ }
+
+ static bool createDirectory (const std::string& path)
+ {
+ if (isDirectory (path))
+ return true;
+
+ const auto parentDir = getParentDirectory (path);
+
+ if (path == parentDir)
+ return false;
+
+ if (createDirectory (parentDir))
+ return mkdir (path.c_str(), 0777) != -1;
+
+ return false;
+ }
+};
+
+//==============================================================================
+struct StringHelpers
+{
+ static bool isQuoteCharacter (char c)
+ {
+ return c == '"' || c == '\'';
+ }
+
+ static std::string unquoted (const std::string& str)
+ {
+ if (str.length() == 0 || (! isQuoteCharacter (str[0])))
+ return str;
+
+ return str.substr (1, str.length() - (isQuoteCharacter (str[str.length() - 1]) ? 1 : 0));
+ }
+
+ static void ltrim (std::string& s)
+ {
+ s.erase (s.begin(), std::find_if (s.begin(), s.end(), [] (int c) { return ! std::isspace (c); }));
+ }
+
+ static void rtrim (std::string& s)
+ {
+ s.erase (std::find_if (s.rbegin(), s.rend(), [] (int c) { return ! std::isspace (c); }).base(), s.end());
+ }
+
+ static std::string trimmed (const std::string& str)
+ {
+ auto result = str;
+ ltrim (result);
+ rtrim (result);
+ return result;
+ }
+
+ static std::string replaced (const std::string& str, char charToReplace, char replaceWith)
+ {
+ auto result = str;
+ std::replace (result.begin(), result.end(), charToReplace, replaceWith);
+ return result;
+ }
+};
+
+//==============================================================================
+static bool addFile (const std::string& filePath,
+ const std::string& binaryNamespace,
+ std::ofstream& headerStream,
+ std::ofstream& cppStream,
+ bool verbose)
+{
+ std::ifstream fileStream (filePath, std::ios::in | std::ios::binary | std::ios::ate);
+
+ if (! fileStream.is_open())
+ {
+ std::cerr << "Failed to open input file " << filePath << std::endl;
+ return false;
+ }
+
+ std::vector buffer ((size_t) fileStream.tellg());
+ fileStream.seekg (0);
+ fileStream.read (buffer.data(), static_cast (buffer.size()));
+
+ const auto variableName = StringHelpers::replaced (StringHelpers::replaced (FileHelpers::getFilename (filePath),
+ ' ',
+ '_'),
+ '.',
+ '_');
+
+ if (verbose)
+ {
+ std::cout << "Adding " << variableName << ": "
+ << buffer.size() << " bytes" << std::endl;
+ }
+
+ headerStream << " extern const char* " << variableName << ";" << std::endl
+ << " const int " << variableName << "Size = "
+ << buffer.size() << ";" << std::endl;
+
+ cppStream << "static const unsigned char temp0[] = {";
+
+ auto* data = (const uint8_t*) buffer.data();
+
+ for (size_t i = 0; i < buffer.size() - 1; ++i)
+ {
+ cppStream << (int) data[i] << ",";
+
+ if ((i % 40) == 39)
+ cppStream << std::endl << " ";
+ }
+
+ cppStream << (int) data[buffer.size() - 1] << ",0,0};" << std::endl;
+ cppStream << "const char* " << binaryNamespace << "::" << variableName
+ << " = (const char*) temp0" << ";" << std::endl << std::endl;
+
+ return true;
+}
+
+//==============================================================================
+class Arguments
+{
+public:
+ enum class PositionalArguments
+ {
+ sourceFile = 0,
+ targetDirectory,
+ targetFilename,
+ binaryNamespace
+ };
+
+ static std::optional create (int argc, char* argv[])
+ {
+ std::vector arguments;
+ bool verbose = false;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string arg { argv[i] };
+
+ if (arg == "-v" || arg == "--verbose")
+ verbose = true;
+ else
+ arguments.emplace_back (std::move (arg));
+ }
+
+ if (arguments.size() != static_cast (PositionalArguments::binaryNamespace) + 1)
+ return std::nullopt;
+
+ return Arguments { std::move (arguments), verbose };
+ }
+
+ std::string get (PositionalArguments argument) const
+ {
+ return arguments[static_cast (argument)];
+ }
+
+ bool isVerbose() const
+ {
+ return verbose;
+ }
+
+private:
+ Arguments (std::vector args, bool verboseIn)
+ : arguments (std::move (args)), verbose (verboseIn)
+ {
+ }
+
+ std::vector arguments;
+ bool verbose = false;
+};
+
+//==============================================================================
+int main (int argc, char* argv[])
+{
+ const auto arguments = Arguments::create (argc, argv);
+
+ if (! arguments.has_value())
+ {
+ std::cout << " Usage: SimpleBinaryBuilder [-v | --verbose] sourcefile targetdirectory targetfilename namespace"
+ << std::endl << std::endl
+ << " SimpleBinaryBuilder will encode the provided source file into" << std::endl
+ << " two files called (targetfilename).cpp and (targetfilename).h," << std::endl
+ << " which it will write into the specified target directory." << std::endl
+ << " The target directory will be automatically created if necessary. The binary" << std::endl
+ << " resource will be available in the given namespace." << std::endl << std::endl;
+
+ return 0;
+ }
+
+ const auto currentWorkingDirectory = FileHelpers::getCurrentWorkingDirectory();
+
+ using ArgType = Arguments::PositionalArguments;
+
+ const auto sourceFile = FileHelpers::appendedPaths (currentWorkingDirectory,
+ StringHelpers::unquoted (arguments->get (ArgType::sourceFile)));
+
+ if (! FileHelpers::exists (sourceFile))
+ {
+ std::cerr << "Source file doesn't exist: "
+ << sourceFile
+ << std::endl << std::endl;
+
+ return 1;
+ }
+
+ const auto targetDirectory = FileHelpers::appendedPaths (currentWorkingDirectory,
+ StringHelpers::unquoted (arguments->get (ArgType::targetDirectory)));
+
+ if (! FileHelpers::exists (targetDirectory))
+ {
+ if (! FileHelpers::createDirectory (targetDirectory))
+ {
+ std::cerr << "Failed to create target directory: " << targetDirectory << std::endl;
+ return 1;
+ }
+ }
+
+ const auto className = StringHelpers::trimmed (arguments->get (ArgType::targetFilename));
+ const auto binaryNamespace = StringHelpers::trimmed (arguments->get (ArgType::binaryNamespace));
+
+ const auto headerFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".h");
+ const auto cppFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".cpp");
+
+ if (arguments->isVerbose())
+ {
+ std::cout << "Creating " << headerFilePath
+ << " and " << cppFilePath
+ << " from file " << sourceFile
+ << "..." << std::endl << std::endl;
+ }
+
+ if (! FileHelpers::deleteFile (headerFilePath))
+ {
+ std::cerr << "Failed to remove old header file: " << headerFilePath << std::endl;
+ return 1;
+ }
+
+ if (! FileHelpers::deleteFile (cppFilePath))
+ {
+ std::cerr << "Failed to remove old source file: " << cppFilePath << std::endl;
+ return 1;
+ }
+
+ std::ofstream header (headerFilePath);
+
+ if (! header.is_open())
+ {
+ std::cerr << "Failed to open " << headerFilePath << std::endl;
+
+ return 1;
+ }
+
+ std::ofstream cpp (cppFilePath);
+
+ if (! cpp.is_open())
+ {
+ std::cerr << "Failed to open " << headerFilePath << std::endl;
+
+ return 1;
+ }
+
+ header << "/* (Auto-generated binary data file). */" << std::endl << std::endl
+ << "#pragma once" << std::endl << std::endl
+ << "namespace " << binaryNamespace << std::endl
+ << "{" << std::endl;
+
+ cpp << "/* (Auto-generated binary data file). */" << std::endl << std::endl
+ << "#include " << std::quoted (className + ".h") << std::endl << std::endl;
+
+ if (! addFile (sourceFile, binaryNamespace, header, cpp, arguments->isVerbose()))
+ return 1;
+
+ header << "}" << std::endl << std::endl;
+
+ return 0;
+}
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h
index 553bafd6dd..d90493a31b 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h
@@ -157,11 +157,15 @@ public:
addVersion (xml);
createProject (*xml.createNewChildElement ("Project"));
writeXmlOrThrow (xml, cbpFile, "UTF-8", 10, true);
+
+ linuxSubprocessHelperProperties.deployLinuxSubprocessHelperSourceFilesIfNecessary();
}
//==============================================================================
void addPlatformSpecificSettingsForProjectType (const build_tools::ProjectType&) override
{
+ linuxSubprocessHelperProperties.addToExtraSearchPathsIfNecessary();
+
// add shared code target first as order matters for Codeblocks
if (shouldBuildTargetType (build_tools::ProjectType::Target::SharedCodeTarget))
targets.add (new CodeBlocksTarget (*this, build_tools::ProjectType::Target::SharedCodeTarget));
@@ -246,6 +250,18 @@ private:
}
//==============================================================================
+ enum class IsDynamicLibrary
+ {
+ no,
+ yes
+ };
+
+ enum class ShouldBeCompiled
+ {
+ no,
+ yes
+ };
+
class CodeBlocksTarget : public build_tools::ProjectType::Target
{
public:
@@ -299,14 +315,130 @@ private:
return {};
}
- bool isDynamicLibrary() const
+ IsDynamicLibrary isDynamicLibrary() const
{
- return (type == DynamicLibrary || type == VSTPlugIn);
+ return (type == DynamicLibrary || type == VSTPlugIn) ? IsDynamicLibrary::yes
+ : IsDynamicLibrary::no;
}
const CodeBlocksProjectExporter& exporter;
};
+ //==============================================================================
+ class LinuxSubprocessHelperTarget
+ {
+ public:
+ enum class HelperType
+ {
+ linuxSubprocessHelper,
+ simpleBinaryBuilder
+ };
+
+ LinuxSubprocessHelperTarget (const CodeBlocksProjectExporter& exporter, HelperType helperTypeIn)
+ : owner (exporter),
+ helperType (helperTypeIn)
+ {
+ }
+
+ String getTargetNameForConfiguration (const BuildConfiguration& config) const
+ {
+ return getName (helperType) + " | " + config.getName();
+ }
+
+ void addTarget (XmlElement& xml, const BuildConfiguration& config) const
+ {
+ xml.setAttribute ("title", getTargetNameForConfiguration (config));
+ auto* output = xml.createNewChildElement ("Option");
+ output->setAttribute ("output", getOutput (helperType, config));
+ xml.createNewChildElement ("Option")->setAttribute ("object_output",
+ "obj/" + File::createLegalFileName (config.getName().trim()));
+ xml.createNewChildElement ("Option")->setAttribute ("type", getTypeIndex (type));
+ xml.createNewChildElement ("Option")->setAttribute ("compiler", "gcc");
+
+ const auto isDynamicLibrary = IsDynamicLibrary::no;
+
+ {
+ auto* compiler = xml.createNewChildElement ("Compiler");
+
+ for (auto flag : owner.getCompilerFlags (config, isDynamicLibrary))
+ owner.setAddOption (*compiler, "option", flag);
+ }
+
+ {
+ auto* linker = xml.createNewChildElement ("Linker");
+
+ for (auto& flag : owner.getLinkerFlags (config, isDynamicLibrary))
+ owner.setAddOption (*linker, "option", flag);
+ }
+
+ if (helperType == HelperType::simpleBinaryBuilder)
+ {
+ auto* postBuildCommands = xml.createNewChildElement ("ExtraCommands");
+
+ const auto binaryDataSource = owner.linuxSubprocessHelperProperties
+ .getLinuxSubprocessHelperBinaryDataSource();
+
+ owner.setAddOption (*postBuildCommands,
+ "after",
+ getOutput (HelperType::simpleBinaryBuilder, config)
+ + " " + getOutput (HelperType::linuxSubprocessHelper, config)
+ + " pre_build"
+ + " " + binaryDataSource.getFileNameWithoutExtension().quoted()
+ + " LinuxSubprocessHelperBinaryData");
+ }
+ }
+
+ void addCompileUnits (XmlElement& xml) const
+ {
+ const auto file = getSource (helperType);
+ auto* unit = xml.createNewChildElement ("Unit");
+ unit->setAttribute ("filename", file.toUnixStyle());
+
+ for (ConstConfigIterator config (owner); config.next();)
+ {
+ auto targetName = getTargetNameForConfiguration (*config);
+ unit->createNewChildElement ("Option")->setAttribute ("target", targetName);
+ }
+ }
+
+ private:
+ build_tools::RelativePath getSource (HelperType helperTypeForSource) const
+ {
+ if (helperTypeForSource == HelperType::linuxSubprocessHelper)
+ return owner.linuxSubprocessHelperProperties.getLinuxSubprocessHelperSource();
+
+ return owner.linuxSubprocessHelperProperties.getSimpleBinaryBuilderSource();
+ }
+
+ String getName (HelperType helperTypeForName) const
+ {
+ return LinuxSubprocessHelperProperties::getBinaryNameFromSource (getSource (helperTypeForName));
+ }
+
+ String getOutput (HelperType helperTypeForOutput, const BuildConfiguration& config) const
+ {
+ return owner.getOutputPathForConfig (config) + "/" + getName (helperTypeForOutput);
+ }
+
+ const CodeBlocksProjectExporter& owner;
+ HelperType helperType;
+ build_tools::ProjectType::Target::Type type = build_tools::ProjectType::Target::ConsoleApp;
+ };
+
+ void addSubprocessHelperBinarySourceCompileUnit (XmlElement& xml) const
+ {
+ auto* unit = xml.createNewChildElement ("Unit");
+ const auto binaryDataSource = linuxSubprocessHelperProperties.getLinuxSubprocessHelperBinaryDataSource();
+ unit->setAttribute ("filename", binaryDataSource.toUnixStyle());
+
+ for (ConstConfigIterator config (*this); config.next();)
+ {
+ const auto& target = getTargetForFile (resolveRelativePath (binaryDataSource), ShouldBeCompiled::yes);
+ const auto targetName = target.getTargetNameForConfiguration (*config);
+ unit->createNewChildElement ("Option")->setAttribute ("target", targetName);
+ }
+ }
+
//==============================================================================
void addVersion (XmlElement& xml) const
{
@@ -372,7 +504,7 @@ private:
return getCleanedStringArray (defs);
}
- StringArray getCompilerFlags (const BuildConfiguration& config, CodeBlocksTarget& target) const
+ StringArray getCompilerFlags (const BuildConfiguration& config, IsDynamicLibrary isDynamicLibrary) const
{
StringArray flags;
@@ -409,7 +541,7 @@ private:
if (config.exporter.isLinux())
{
- if (target.isDynamicLibrary() || getProject().isAudioPluginProject())
+ if (isDynamicLibrary == IsDynamicLibrary::yes || getProject().isAudioPluginProject())
flags.add ("-fPIC");
auto packages = config.exporter.getLinuxPackages (PackageDependencyType::compile);
@@ -432,7 +564,7 @@ private:
return getCleanedStringArray (flags);
}
- StringArray getLinkerFlags (const BuildConfiguration& config, CodeBlocksTarget& target) const
+ StringArray getLinkerFlags (const BuildConfiguration& config, IsDynamicLibrary isDynamicLibrary) const
{
auto flags = makefileExtraLinkerFlags;
@@ -449,7 +581,7 @@ private:
if (config.exporter.isLinux())
{
- if (target.isDynamicLibrary())
+ if (isDynamicLibrary == IsDynamicLibrary::yes)
flags.add ("-shared");
auto packages = config.exporter.getLinuxPackages (PackageDependencyType::link);
@@ -509,21 +641,23 @@ private:
return 0;
}
- String getOutputPathForTarget (CodeBlocksTarget& target, const BuildConfiguration& config) const
+ String getOutputPathForConfig (const BuildConfiguration& config) const
{
- String outputPath;
if (config.getTargetBinaryRelativePathString().isNotEmpty())
{
build_tools::RelativePath binaryPath (config.getTargetBinaryRelativePathString(), build_tools::RelativePath::projectFolder);
binaryPath = binaryPath.rebased (projectFolder, getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
- outputPath = config.getTargetBinaryRelativePathString();
- }
- else
- {
- outputPath ="bin/" + File::createLegalFileName (config.getName().trim());
+ return config.getTargetBinaryRelativePathString();
}
- return outputPath + "/" + replacePreprocessorTokens (config, config.getTargetBinaryNameString() + target.getTargetSuffix());
+ return "bin/" + File::createLegalFileName (config.getName().trim());
+ }
+
+ String getOutputPathForTarget (CodeBlocksTarget& target, const BuildConfiguration& config) const
+ {
+ return getOutputPathForConfig (config)
+ + "/"
+ + replacePreprocessorTokens (config, config.getTargetBinaryNameString() + target.getTargetSuffix());
}
String getSharedCodePath (const BuildConfiguration& config) const
@@ -584,7 +718,7 @@ private:
flags.add ("-D" + def);
}
- flags.addArray (getCompilerFlags (config, target));
+ flags.addArray (getCompilerFlags (config, target.isDynamicLibrary()));
for (auto flag : flags)
setAddOption (*compiler, "option", flag);
@@ -604,7 +738,7 @@ private:
if (getProject().isAudioPluginProject() && target.type != build_tools::ProjectType::Target::SharedCodeTarget)
setAddOption (*linker, "option", getSharedCodePath (config).quoted());
- for (auto& flag : getLinkerFlags (config, target))
+ for (auto& flag : getLinkerFlags (config, target.isDynamicLibrary()))
setAddOption (*linker, "option", flag);
const StringArray& libs = isWindows() ? mingwLibs : linuxLibs;
@@ -623,9 +757,15 @@ private:
auto* build = xml.createNewChildElement ("Build");
for (ConstConfigIterator config (*this); config.next();)
+ {
+ if (linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ for (const auto& helperTarget : helperTargets)
+ helperTarget.addTarget (*build->createNewChildElement ("Target"), *config);
+
for (auto target : targets)
if (target->type != build_tools::ProjectType::Target::AggregateTarget)
createBuildTarget (*build->createNewChildElement ("Target"), *target, *config);
+ }
}
void addVirtualTargets (XmlElement& xml) const
@@ -636,6 +776,10 @@ private:
{
StringArray allTargets;
+ if (linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ for (const auto& target : helperTargets)
+ allTargets.add (target.getTargetNameForConfiguration (*config));
+
for (auto target : targets)
if (target->type != build_tools::ProjectType::Target::AggregateTarget)
allTargets.add (target->getTargetNameForConfiguration (*config));
@@ -726,19 +870,26 @@ private:
return *targets[0];
}
- CodeBlocksTarget& getTargetForProjectItem (const Project::Item& projectItem) const
+ CodeBlocksTarget& getTargetForFile (const File& file, ShouldBeCompiled shouldBeCompiled) const
{
if (getProject().isAudioPluginProject())
{
- if (! projectItem.shouldBeCompiled())
+ if (shouldBeCompiled == ShouldBeCompiled::no)
return getTargetWithType (build_tools::ProjectType::Target::SharedCodeTarget);
- return getTargetWithType (getProject().getTargetTypeFromFilePath (projectItem.getFile(), true));
+ return getTargetWithType (getProject().getTargetTypeFromFilePath (file, true));
}
return getMainTarget();
}
+ CodeBlocksTarget& getTargetForProjectItem (const Project::Item& projectItem) const
+ {
+ return getTargetForFile (projectItem.getFile(),
+ projectItem.shouldBeCompiled() ? ShouldBeCompiled::yes
+ : ShouldBeCompiled::no);
+ }
+
void addCompileUnits (const Project::Item& projectItem, XmlElement& xml) const
{
if (projectItem.isGroup())
@@ -787,6 +938,12 @@ private:
void addCompileUnits (XmlElement& xml) const
{
+ if (linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ for (const auto& helperTarget : helperTargets)
+ helperTarget.addCompileUnits (xml);
+
+ addSubprocessHelperBinarySourceCompileUnit (xml);
+
for (int i = 0; i < getAllGroups().size(); ++i)
addCompileUnits (getAllGroups().getReference(i), xml);
@@ -825,5 +982,9 @@ private:
OwnedArray targets;
+ // The order of these targets is significant, as latter targets depend on earlier ones
+ const LinuxSubprocessHelperTarget helperTargets[2] { { *this, LinuxSubprocessHelperTarget::HelperType::linuxSubprocessHelper },
+ { *this, LinuxSubprocessHelperTarget::HelperType::simpleBinaryBuilder } };
+
JUCE_DECLARE_NON_COPYABLE (CodeBlocksProjectExporter)
};
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h
index dc3c74be76..5f9954521d 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Make.h
@@ -295,7 +295,26 @@ public:
for (auto& [path, flags] : filesToCompile)
{
- out << "$(JUCE_OBJDIR)/" << escapeQuotesAndSpaces (owner.getObjectFileFor (path)) << ": " << escapeQuotesAndSpaces (path.toUnixStyle()) << newLine
+ const auto additionalTargetDependencies = [&path = path, this]
+ {
+ if ( owner.linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper()
+ && path.getFileName().contains ("include_juce_gui_extra.cpp"))
+ {
+ return owner.linuxSubprocessHelperProperties
+ .getLinuxSubprocessHelperBinaryDataSource()
+ .toUnixStyle();
+ }
+
+ return String{};
+ }();
+
+ const auto prependedWithSpaceIfNotEmpty = [] (auto s)
+ {
+ return s.isEmpty() ? s : " " + s;
+ };
+
+ out << "$(JUCE_OBJDIR)/" << escapeQuotesAndSpaces (owner.getObjectFileFor (path)) << ": " << escapeQuotesAndSpaces (path.toUnixStyle())
+ << prependedWithSpaceIfNotEmpty (additionalTargetDependencies) << newLine
<< "\t-$(V_AT)mkdir -p $(@D)" << newLine
<< "\t@echo \"Compiling " << path.getFileName() << "\"" << newLine
<< (path.hasFileExtension ("c;s;S") ? "\t$(V_AT)$(CC) $(JUCE_CFLAGS) " : "\t$(V_AT)$(CXX) $(JUCE_CXXFLAGS) ")
@@ -530,11 +549,15 @@ public:
build_tools::overwriteFileIfDifferentOrThrow (helperDir.getChildFile ("arch_detection.cpp"),
BinaryData::juce_runtime_arch_detection_cpp);
}
+
+ linuxSubprocessHelperProperties.deployLinuxSubprocessHelperSourceFilesIfNecessary();
}
//==============================================================================
void addPlatformSpecificSettingsForProjectType (const build_tools::ProjectType&) override
{
+ linuxSubprocessHelperProperties.addToExtraSearchPathsIfNecessary();
+
callForAllSupportedTargets ([this] (build_tools::ProjectType::Target::Type targetType)
{
targets.insert (targetType == build_tools::ProjectType::Target::AggregateTarget ? 0 : -1,
@@ -886,7 +909,21 @@ private:
out << newLine;
- out << " CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)" << newLine
+ const auto preBuildDirectory = [&]() -> String
+ {
+ if (linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ {
+ using LSHP = LinuxSubprocessHelperProperties;
+ const auto dataSource = linuxSubprocessHelperProperties.getLinuxSubprocessHelperBinaryDataSource();
+
+ if (auto preBuildDir = LSHP::getParentDirectoryRelativeToBuildTargetFolder (dataSource))
+ return " " + *preBuildDir;
+ }
+
+ return "";
+ }();
+
+ out << " CLEANCMD = rm -rf $(JUCE_OUTDIR)/$(TARGET) $(JUCE_OBJDIR)" << preBuildDirectory << newLine
<< "endif" << newLine
<< newLine;
}
@@ -964,6 +1001,94 @@ private:
out << newLine;
}
+ /* These targets are responsible for building the juce_linux_subprocess_helper, the
+ juce_simple_binary_builder, and then using the binary builder to create embeddable .h and .cpp
+ files from the linux subprocess helper.
+ */
+ void writeSubprocessHelperTargets (OutputStream& out) const
+ {
+ using LSHP = LinuxSubprocessHelperProperties;
+
+ const auto ensureDirs = [] (auto& outStream, std::vector dirs)
+ {
+ for (const auto& dir : dirs)
+ outStream << "\t-$(V_AT)mkdir -p " << dir << newLine;
+ };
+
+ const auto makeTarget = [&ensureDirs] (auto& outStream, String input, String output)
+ {
+ const auto isObjectTarget = output.endsWith (".o");
+ const auto isSourceInput = input.endsWith (".cpp");
+
+ const auto targetOutput = isObjectTarget ? "$(JUCE_OBJDIR)/" + output : output;
+
+ outStream << (isObjectTarget ? "$(JUCE_OBJDIR)/" : "") << output << ": " << input << newLine;
+
+ const auto createBuildTargetRelative = [] (auto path)
+ {
+ return build_tools::RelativePath { path, build_tools::RelativePath::buildTargetFolder };
+ };
+
+ if (isObjectTarget)
+ ensureDirs (outStream, { "$(JUCE_OBJDIR)" });
+ else if (auto outputParentFolder = LSHP::getParentDirectoryRelativeToBuildTargetFolder (createBuildTargetRelative (output)))
+ ensureDirs (outStream, { *outputParentFolder });
+
+ outStream << (isObjectTarget ? "\t@echo \"Compiling " : "\t@echo \"Linking ")
+ << (isObjectTarget ? input : output) << "\"" << newLine
+ << "\t$(V_AT)$(CXX) $(JUCE_CXXFLAGS) -o " << targetOutput.quoted()
+ << " " << (isSourceInput ? "-c \"$<\"" : input.quoted());
+
+ if (! isObjectTarget)
+ outStream << " $(JUCE_LDFLAGS)";
+
+ outStream << " $(TARGET_ARCH)" << newLine << newLine;
+
+ return targetOutput;
+ };
+
+ const auto subprocessHelperSource = linuxSubprocessHelperProperties.getLinuxSubprocessHelperSource();
+
+ const auto subprocessHelperObj = makeTarget (out,
+ subprocessHelperSource.toUnixStyle(),
+ getObjectFileFor (subprocessHelperSource));
+
+ const auto subprocessHelperPath = makeTarget (out,
+ subprocessHelperObj,
+ "$(JUCE_BINDIR)/" + LSHP::getBinaryNameFromSource (subprocessHelperSource));
+
+ const auto binaryBuilderSource = linuxSubprocessHelperProperties.getSimpleBinaryBuilderSource();
+
+ const auto binaryBuilderObj = makeTarget (out,
+ binaryBuilderSource.toUnixStyle(),
+ getObjectFileFor (binaryBuilderSource));
+
+ const auto binaryBuilderPath = makeTarget (out,
+ binaryBuilderObj,
+ "$(JUCE_BINDIR)/" + LSHP::getBinaryNameFromSource (binaryBuilderSource));
+
+ const auto binaryDataSource = linuxSubprocessHelperProperties.getLinuxSubprocessHelperBinaryDataSource();
+ jassert (binaryDataSource.getRoot() == build_tools::RelativePath::buildTargetFolder);
+
+ out << binaryDataSource.toUnixStyle() << ": " << subprocessHelperPath
+ << " " << binaryBuilderPath
+ << newLine;
+
+ const auto binarySourceDir = [&]() -> String
+ {
+ if (const auto p = LSHP::getParentDirectoryRelativeToBuildTargetFolder (binaryDataSource))
+ return *p;
+
+ return ".";
+ }();
+
+ out << "\t$(V_AT)" << binaryBuilderPath.quoted() << " " << subprocessHelperPath.quoted()
+ << " " << binarySourceDir.quoted() << " " << binaryDataSource.getFileNameWithoutExtension().quoted()
+ << " LinuxSubprocessHelperBinaryData" << newLine;
+
+ out << newLine;
+ }
+
void writeMakefile (OutputStream& out) const
{
out << "# Automatically generated makefile, created by the Projucer" << newLine
@@ -1039,6 +1164,14 @@ private:
}
}
+ if (( targetType == MakefileTarget::SharedCodeTarget
+ || targetType == MakefileTarget::StaticLibrary
+ || targetType == MakefileTarget::DynamicLibrary)
+ && linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ {
+ targetFiles.emplace_back (linuxSubprocessHelperProperties.getLinuxSubprocessHelperBinaryDataSource(), "");
+ }
+
if (targetType == MakefileTarget::LV2TurtleProgram)
{
targetFiles.emplace_back (getLV2TurtleDumpProgramSource().rebased (projectFolder,
@@ -1067,6 +1200,9 @@ private:
<< "\t$(V_AT)printf \"int main() { return 0; }\" | $(CXX) -x c++ -o $(@D)/execinfo.x -lexecinfo - >/dev/null 2>&1 && printf -- \"-lexecinfo\" > \"$@\" || touch \"$@\"" << newLine
<< newLine;
+ if (linuxSubprocessHelperProperties.shouldUseLinuxSubprocessHelper())
+ writeSubprocessHelperTargets (out);
+
out << "clean:" << newLine
<< "\t@echo Cleaning " << projectName << newLine
<< "\t$(V_AT)$(CLEANCMD)" << newLine
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp
index 915e4d5bda..0ea916b194 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.cpp
@@ -234,6 +234,32 @@ build_tools::RelativePath ProjectExporter::rebaseFromProjectFolderToBuildTarget
return path.rebased (project.getProjectFolder(), getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
}
+build_tools::RelativePath ProjectExporter::rebaseFromBuildTargetToProjectFolder (const build_tools::RelativePath& path) const
+{
+ jassert (path.getRoot() == build_tools::RelativePath::buildTargetFolder);
+ return path.rebased (getTargetFolder(), project.getProjectFolder(), build_tools::RelativePath::projectFolder);
+}
+
+File ProjectExporter::resolveRelativePath (const build_tools::RelativePath& path) const
+{
+ if (path.isAbsolute())
+ return path.toUnixStyle();
+
+ switch (path.getRoot())
+ {
+ case build_tools::RelativePath::buildTargetFolder:
+ return getTargetFolder().getChildFile (path.toUnixStyle());
+
+ case build_tools::RelativePath::projectFolder:
+ return project.getProjectFolder().getChildFile (path.toUnixStyle());
+
+ case build_tools::RelativePath::unknown:
+ jassertfalse;
+ }
+
+ return path.toUnixStyle();
+}
+
bool ProjectExporter::shouldFileBeCompiledByDefault (const File& file) const
{
return file.hasFileExtension (cOrCppFileExtensions)
@@ -497,6 +523,8 @@ void ProjectExporter::addTargetSpecificPreprocessorDefs (StringPairArray& defs,
{
defs.set ("JucePlugin_Enable_ARA", "1");
}
+
+ linuxSubprocessHelperProperties.setCompileDefinitionIfNecessary (defs);
}
void ProjectExporter::addDefaultPreprocessorDefs (StringPairArray& defs) const
@@ -545,7 +573,7 @@ Project::Item& ProjectExporter::getModulesGroup()
}
//==============================================================================
-static bool isWebBrowserComponentEnabled (Project& project)
+static bool isWebBrowserComponentEnabled (const Project& project)
{
static String guiExtrasModule ("juce_gui_extra");
@@ -608,6 +636,7 @@ void ProjectExporter::addToModuleLibPaths (const build_tools::RelativePath& path
void ProjectExporter::addToExtraSearchPaths (const build_tools::RelativePath& pathFromProjectFolder, int index)
{
+ jassert (pathFromProjectFolder.getRoot() == build_tools::RelativePath::projectFolder);
addProjectPathToBuildPathList (extraSearchPaths, pathFromProjectFolder, index);
}
@@ -1083,3 +1112,115 @@ String ProjectExporter::getExternalLibraryFlags (const BuildConfiguration& confi
return {};
}
+
+//==============================================================================
+LinuxSubprocessHelperProperties::LinuxSubprocessHelperProperties (ProjectExporter& projectExporter)
+ : owner (projectExporter)
+{}
+
+bool LinuxSubprocessHelperProperties::shouldUseLinuxSubprocessHelper() const
+{
+ const auto& project = owner.getProject();
+ const auto& projectType = project.getProjectType();
+
+ return owner.isLinux()
+ && isWebBrowserComponentEnabled (project)
+ && ! (projectType.isCommandLineApp())
+ && ! (projectType.isGUIApplication());
+}
+
+void LinuxSubprocessHelperProperties::deployLinuxSubprocessHelperSourceFilesIfNecessary() const
+{
+ if (shouldUseLinuxSubprocessHelper())
+ {
+ const auto deployHelperSourceFile = [] (auto& sourcePath, auto& contents)
+ {
+ if (! sourcePath.isRoot() && ! sourcePath.getParentDirectory().exists())
+ {
+ sourcePath.getParentDirectory().createDirectory();
+ }
+
+ build_tools::overwriteFileIfDifferentOrThrow (sourcePath, contents);
+ };
+
+ const std::pair sources[]
+ {
+ { owner.resolveRelativePath (getSimpleBinaryBuilderSource()), BinaryData::juce_SimpleBinaryBuilder_cpp },
+ { owner.resolveRelativePath (getLinuxSubprocessHelperSource()), BinaryData::juce_LinuxSubprocessHelper_cpp }
+ };
+
+ for (const auto& [path, source] : sources)
+ {
+ deployHelperSourceFile (path, source);
+ }
+ }
+}
+
+build_tools::RelativePath LinuxSubprocessHelperProperties::getLinuxSubprocessHelperSource() const
+{
+ return build_tools::RelativePath { "make_helpers", build_tools::RelativePath::buildTargetFolder }
+ .getChildFile ("juce_LinuxSubprocessHelper.cpp");
+}
+
+void LinuxSubprocessHelperProperties::setCompileDefinitionIfNecessary (StringPairArray& defs) const
+{
+ if (shouldUseLinuxSubprocessHelper())
+ defs.set (useLinuxSubprocessHelperCompileDefinition, "1");
+}
+
+build_tools::RelativePath LinuxSubprocessHelperProperties::getSimpleBinaryBuilderSource() const
+{
+ return build_tools::RelativePath { "make_helpers", build_tools::RelativePath::buildTargetFolder }
+ .getChildFile ("juce_SimpleBinaryBuilder.cpp");
+}
+
+build_tools::RelativePath LinuxSubprocessHelperProperties::getLinuxSubprocessHelperBinaryDataSource() const
+{
+ return build_tools::RelativePath ("pre_build", juce::build_tools::RelativePath::buildTargetFolder)
+ .getChildFile ("juce_LinuxSubprocessHelperBinaryData.cpp");
+}
+
+void LinuxSubprocessHelperProperties::addToExtraSearchPathsIfNecessary() const
+{
+ if (shouldUseLinuxSubprocessHelper())
+ {
+ const auto subprocessHelperBinaryDir = getLinuxSubprocessHelperBinaryDataSource().getParentDirectory();
+ owner.addToExtraSearchPaths (owner.rebaseFromBuildTargetToProjectFolder (subprocessHelperBinaryDir));
+ }
+}
+
+std::optional LinuxSubprocessHelperProperties::getParentDirectoryRelativeToBuildTargetFolder (build_tools::RelativePath rp)
+{
+ jassert (rp.getRoot() == juce::build_tools::RelativePath::buildTargetFolder);
+ const auto parentDir = rp.getParentDirectory().toUnixStyle();
+ return parentDir == rp.toUnixStyle() ? std::nullopt : std::make_optional (parentDir);
+}
+
+String LinuxSubprocessHelperProperties::makeSnakeCase (const String& s)
+{
+ String result;
+ result.preallocateBytes (128);
+
+ bool previousCharacterUnderscore = false;
+
+ for (const auto c : s)
+ {
+ if ( CharacterFunctions::isUpperCase (c)
+ && result.length() != 0
+ && ! (previousCharacterUnderscore))
+ {
+ result << "_";
+ }
+
+ result << CharacterFunctions::toLowerCase (c);
+
+ previousCharacterUnderscore = c == '_';
+ }
+
+ return result;
+}
+
+String LinuxSubprocessHelperProperties::getBinaryNameFromSource (const build_tools::RelativePath& rp)
+{
+ return makeSnakeCase (rp.getFileNameWithoutExtension());
+}
diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h
index a25d1fe86b..4a896ecdb0 100644
--- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h
+++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h
@@ -32,6 +32,37 @@
class ProjectSaver;
+class LinuxSubprocessHelperProperties
+{
+public:
+ explicit LinuxSubprocessHelperProperties (ProjectExporter& projectExporter);
+
+ bool shouldUseLinuxSubprocessHelper() const;
+
+ void deployLinuxSubprocessHelperSourceFilesIfNecessary() const;
+
+ build_tools::RelativePath getLinuxSubprocessHelperSource() const;
+
+ void setCompileDefinitionIfNecessary (StringPairArray& defs) const;
+
+ build_tools::RelativePath getSimpleBinaryBuilderSource() const;
+
+ build_tools::RelativePath getLinuxSubprocessHelperBinaryDataSource() const;
+
+ void addToExtraSearchPathsIfNecessary() const;
+
+ static std::optional getParentDirectoryRelativeToBuildTargetFolder (build_tools::RelativePath rp);
+
+ static String makeSnakeCase (const String& s);
+
+ static String getBinaryNameFromSource (const build_tools::RelativePath& rp);
+
+ static constexpr const char* useLinuxSubprocessHelperCompileDefinition = "JUCE_USE_EXTERNAL_TEMPORARY_SUBPROCESS";
+
+private:
+ ProjectExporter& owner;
+};
+
//==============================================================================
class ProjectExporter : private Value::Listener
{
@@ -164,6 +195,8 @@ public:
void updateOldModulePaths();
build_tools::RelativePath rebaseFromProjectFolderToBuildTarget (const build_tools::RelativePath& path) const;
+ build_tools::RelativePath rebaseFromBuildTargetToProjectFolder (const build_tools::RelativePath& path) const;
+ File resolveRelativePath (const build_tools::RelativePath&) const;
void addToExtraSearchPaths (const build_tools::RelativePath& pathFromProjectFolder, int index = -1);
void addToModuleLibPaths (const build_tools::RelativePath& pathFromProjectFolder);
@@ -219,6 +252,9 @@ public:
StringArray extraSearchPaths;
StringArray moduleLibSearchPaths;
+ //==============================================================================
+ const LinuxSubprocessHelperProperties linuxSubprocessHelperProperties { *this };
+
//==============================================================================
class BuildConfiguration : public ReferenceCountedObject
{