From 31f94c2e2871644f4ae80cc599cdd5b9ed1c9a5a Mon Sep 17 00:00:00 2001 From: attila Date: Fri, 10 Feb 2023 19:05:11 +0100 Subject: [PATCH] Projucer: Add embedded Linux subprocess for WebView support --- extras/Projucer/CMakeLists.txt | 4 +- extras/Projucer/Projucer.jucer | 4 + .../BinaryData/juce_SimpleBinaryBuilder.cpp | 375 ++++++++++++++++++ .../jucer_ProjectExport_CodeBlocks.h | 199 +++++++++- .../ProjectSaving/jucer_ProjectExport_Make.h | 140 ++++++- .../ProjectSaving/jucer_ProjectExporter.cpp | 143 ++++++- .../ProjectSaving/jucer_ProjectExporter.h | 36 ++ 7 files changed, 878 insertions(+), 23 deletions(-) create mode 100644 extras/Projucer/Source/BinaryData/juce_SimpleBinaryBuilder.cpp 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 {