1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Projucer: Add embedded Linux subprocess for WebView support

This commit is contained in:
attila 2023-02-10 19:05:11 +01:00 committed by Attila Szarvas
parent f9ff497978
commit 31f94c2e28
7 changed files with 878 additions and 23 deletions

View file

@ -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

View file

@ -299,6 +299,10 @@
file="Source/BinaryData/colourscheme_light.xml"/>
<FILE id="SDFoQY" name="juce_runtime_arch_detection.cpp" compile="0"
resource="1" file="../Build/CMake/juce_runtime_arch_detection.cpp"/>
<FILE id="m0ZzB2" name="juce_LinuxSubprocessHelper.cpp" compile="0"
resource="1" file="../Build/CMake/juce_LinuxSubprocessHelper.cpp"/>
<FILE id="OMFBxs" name="juce_SimpleBinaryBuilder.cpp" compile="0" resource="1"
file="Source/BinaryData/juce_SimpleBinaryBuilder.cpp"/>
</GROUP>
<GROUP id="{A5AE7471-B900-FD9D-8DE7-2FB68D11AE30}" name="CodeEditor">
<FILE id="w3ka6n" name="jucer_DocumentEditorComponent.cpp" compile="1"

View file

@ -0,0 +1,375 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <optional>
#include <vector>
//==============================================================================
struct FileHelpers
{
static std::string getCurrentWorkingDirectory()
{
std::vector<char> 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<char> buffer ((size_t) fileStream.tellg());
fileStream.seekg (0);
fileStream.read (buffer.data(), static_cast<std::streamsize> (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<Arguments> create (int argc, char* argv[])
{
std::vector<std::string> 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<size_t> (PositionalArguments::binaryNamespace) + 1)
return std::nullopt;
return Arguments { std::move (arguments), verbose };
}
std::string get (PositionalArguments argument) const
{
return arguments[static_cast<size_t> (argument)];
}
bool isVerbose() const
{
return verbose;
}
private:
Arguments (std::vector<std::string> args, bool verboseIn)
: arguments (std::move (args)), verbose (verboseIn)
{
}
std::vector<std::string> 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;
}

View file

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

View file

@ -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<String> 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

View file

@ -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<File, const char*> 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<String> 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());
}

View file

@ -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<String> 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
{