mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
972 lines
32 KiB
C++
972 lines
32 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
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 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-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 "../Application/jucer_Headers.h"
|
|
#include "../Application/jucer_Application.h"
|
|
#include "../ProjectSaving/jucer_ProjectExporter.h"
|
|
#include "jucer_MessageIDs.h"
|
|
#include "jucer_CppHelpers.h"
|
|
#include "jucer_SourceCodeRange.h"
|
|
#include "jucer_ClassDatabase.h"
|
|
#include "jucer_DiagnosticMessage.h"
|
|
#include "jucer_ProjectBuildInfo.h"
|
|
#include "jucer_ClientServerMessages.h"
|
|
#include "jucer_CompileEngineClient.h"
|
|
#include "../LiveBuildEngine/jucer_CompileEngineServer.h"
|
|
|
|
#ifndef RUN_CLANG_IN_CHILD_PROCESS
|
|
#error
|
|
#endif
|
|
|
|
//==============================================================================
|
|
namespace ProjectProperties
|
|
{
|
|
const Identifier liveSettingsType ("LIVE_SETTINGS");
|
|
#if JUCE_MAC
|
|
const Identifier liveSettingsSubtype ("OSX");
|
|
#elif JUCE_WINDOWS
|
|
const Identifier liveSettingsSubtype ("WINDOWS");
|
|
#elif JUCE_LINUX
|
|
const Identifier liveSettingsSubtype ("LINUX");
|
|
#endif
|
|
|
|
static ValueTree getLiveSettings (Project& project)
|
|
{
|
|
return project.getProjectRoot().getOrCreateChildWithName (liveSettingsType, nullptr)
|
|
.getOrCreateChildWithName (liveSettingsSubtype, nullptr);
|
|
}
|
|
|
|
static const ValueTree getLiveSettingsConst (Project& project)
|
|
{
|
|
return project.getProjectRoot().getChildWithName (liveSettingsType)
|
|
.getChildWithName (liveSettingsSubtype);
|
|
}
|
|
|
|
static Value getLiveSetting (Project& p, const Identifier& i) { return getLiveSettings (p).getPropertyAsValue (i, p.getUndoManagerFor (getLiveSettings (p))); }
|
|
static var getLiveSettingVar (Project& p, const Identifier& i) { return getLiveSettingsConst (p) [i]; }
|
|
|
|
static Value getUserHeaderPathValue (Project& p) { return getLiveSetting (p, Ids::headerPath); }
|
|
static String getUserHeaderPathString (Project& p) { return getLiveSettingVar (p, Ids::headerPath); }
|
|
static Value getSystemHeaderPathValue (Project& p) { return getLiveSetting (p, Ids::systemHeaderPath); }
|
|
static String getSystemHeaderPathString (Project& p) { return getLiveSettingVar (p, Ids::systemHeaderPath); }
|
|
static Value getExtraDLLsValue (Project& p) { return getLiveSetting (p, Ids::extraDLLs); }
|
|
static String getExtraDLLsString (Project& p) { return getLiveSettingVar (p, Ids::extraDLLs); }
|
|
static Value getExtraCompilerFlagsValue (Project& p) { return getLiveSetting (p, Ids::extraCompilerFlags); }
|
|
static String getExtraCompilerFlagsString (Project& p) { return getLiveSettingVar (p, Ids::extraCompilerFlags); }
|
|
static Value getExtraPreprocessorDefsValue (Project& p) { return getLiveSetting (p, Ids::defines); }
|
|
static String getExtraPreprocessorDefsString (Project& p) { return getLiveSettingVar (p, Ids::defines); }
|
|
static Value getWindowsTargetPlatformVersionValue (Project& p) { return getLiveSetting (p, Ids::liveWindowsTargetPlatformVersion); }
|
|
static String getWindowsTargetPlatformVersionString (Project& p) { return getLiveSettingVar (p, Ids::liveWindowsTargetPlatformVersion); }
|
|
|
|
static File getProjucerTempFolder()
|
|
{
|
|
#if JUCE_MAC
|
|
return File ("~/Library/Caches/com.juce.projucer");
|
|
#else
|
|
return File::getSpecialLocation (File::tempDirectory).getChildFile ("com.juce.projucer");
|
|
#endif
|
|
}
|
|
|
|
static File getCacheLocation (Project& project)
|
|
{
|
|
String cacheFolderName = project.getProjectFilenameRoot() + "_" + project.getProjectUID();
|
|
|
|
#if JUCE_DEBUG
|
|
cacheFolderName += "_debug";
|
|
#endif
|
|
|
|
return getProjucerTempFolder()
|
|
.getChildFile ("Intermediate Files")
|
|
.getChildFile (cacheFolderName);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void LiveBuildProjectSettings::getLiveSettings (Project& project, PropertyListBuilder& props)
|
|
{
|
|
using namespace ProjectProperties;
|
|
|
|
props.addSearchPathProperty (getUserHeaderPathValue (project), "User header paths", "User header search paths.");
|
|
props.addSearchPathProperty (getSystemHeaderPathValue (project), "System header paths", "System header search paths.");
|
|
|
|
props.add (new TextPropertyComponent (getExtraPreprocessorDefsValue (project), "Preprocessor Definitions", 32768, true),
|
|
"Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas "
|
|
"to separate the items - to include a space or comma in a definition, precede it with a backslash.");
|
|
|
|
props.add (new TextPropertyComponent (getExtraCompilerFlagsValue (project), "Extra compiler flags", 2048, true),
|
|
"Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor"
|
|
" definitions in the form ${NAME_OF_DEFINITION}, which will be replaced with their values.");
|
|
|
|
props.add (new TextPropertyComponent (getExtraDLLsValue (project), "Extra dynamic libraries", 2048, true),
|
|
"Extra dynamic libs that the running code may require. Use new-lines or commas to separate the items");
|
|
|
|
static const char* targetPlatformNames[] = { "(default)", "8.1", "10.0.10240.0", "10.0.10586.0", "10.0.14393.0", "10.0.15063.0", nullptr };
|
|
const var targetPlatforms[] = { var(), "8.1", "10.0.10240.0", "10.0.10586.0", "10.0.14393.0", "10.0.15063.0" };
|
|
|
|
props.add (new ChoicePropertyComponent (getWindowsTargetPlatformVersionValue (project), "Windows Target Platform",
|
|
StringArray (targetPlatformNames), Array<var> (targetPlatforms, numElementsInArray (targetPlatforms))),
|
|
"The Windows target platform to use");
|
|
}
|
|
|
|
void LiveBuildProjectSettings::updateNewlyOpenedProject (Project&) { /* placeholder */ }
|
|
|
|
bool LiveBuildProjectSettings::isBuildDisabled (Project& p)
|
|
{
|
|
const bool defaultBuildDisabled = true;
|
|
return p.getStoredProperties().getBoolValue ("buildDisabled", defaultBuildDisabled);
|
|
}
|
|
|
|
void LiveBuildProjectSettings::setBuildDisabled (Project& p, bool b) { p.getStoredProperties().setValue ("buildDisabled", b); }
|
|
bool LiveBuildProjectSettings::areWarningsDisabled (Project& p) { return p.getStoredProperties().getBoolValue ("warningsDisabled"); }
|
|
void LiveBuildProjectSettings::setWarningsDisabled (Project& p, bool b) { p.getStoredProperties().setValue ("warningsDisabled", b); }
|
|
|
|
//==============================================================================
|
|
class ClientIPC : public MessageHandler,
|
|
private InterprocessConnection,
|
|
private Timer
|
|
{
|
|
public:
|
|
ClientIPC (CompileEngineChildProcess& cp)
|
|
: InterprocessConnection (true), owner (cp)
|
|
{
|
|
launchServer();
|
|
}
|
|
|
|
~ClientIPC()
|
|
{
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
if (childProcess.isRunning())
|
|
{
|
|
#if JUCE_DEBUG
|
|
killServerPolitely();
|
|
#else
|
|
// in release builds we don't want to wait
|
|
// for the server to clean up and shut down
|
|
killServerWithoutMercy();
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void launchServer()
|
|
{
|
|
DBG ("Client: Launching Server...");
|
|
const String pipeName ("ipc_" + String::toHexString (Random().nextInt64()));
|
|
|
|
const String command (createCommandLineForLaunchingServer (pipeName,
|
|
owner.project.getProjectUID(),
|
|
ProjectProperties::getCacheLocation (owner.project)));
|
|
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
if (! childProcess.start (command))
|
|
{
|
|
jassertfalse;
|
|
}
|
|
#else
|
|
server = createClangServer (command);
|
|
#endif
|
|
|
|
bool ok = connectToPipe (pipeName, 10000);
|
|
jassert (ok);
|
|
|
|
if (ok)
|
|
MessageTypes::sendPing (*this);
|
|
|
|
startTimer (serverKeepAliveTimeout);
|
|
}
|
|
|
|
void killServerPolitely()
|
|
{
|
|
DBG ("Client: Killing Server...");
|
|
MessageTypes::sendQuit (*this);
|
|
|
|
disconnect();
|
|
stopTimer();
|
|
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
childProcess.waitForProcessToFinish (5000);
|
|
#endif
|
|
|
|
killServerWithoutMercy();
|
|
}
|
|
|
|
void killServerWithoutMercy()
|
|
{
|
|
disconnect();
|
|
stopTimer();
|
|
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
childProcess.kill();
|
|
#else
|
|
destroyClangServer (server);
|
|
server = nullptr;
|
|
#endif
|
|
}
|
|
|
|
void connectionMade()
|
|
{
|
|
DBG ("Client: connected");
|
|
stopTimer();
|
|
}
|
|
|
|
void connectionLost()
|
|
{
|
|
DBG ("Client: disconnected");
|
|
startTimer (100);
|
|
}
|
|
|
|
bool sendMessage (const ValueTree& m)
|
|
{
|
|
return InterprocessConnection::sendMessage (MessageHandler::convertMessage (m));
|
|
}
|
|
|
|
void messageReceived (const MemoryBlock& message)
|
|
{
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
startTimer (serverKeepAliveTimeout);
|
|
#else
|
|
stopTimer();
|
|
#endif
|
|
MessageTypes::dispatchToClient (owner, MessageHandler::convertMessage (message));
|
|
}
|
|
|
|
enum { serverKeepAliveTimeout = 10000 };
|
|
|
|
private:
|
|
CompileEngineChildProcess& owner;
|
|
|
|
#if RUN_CLANG_IN_CHILD_PROCESS
|
|
ChildProcess childProcess;
|
|
#else
|
|
void* server;
|
|
#endif
|
|
|
|
void timerCallback()
|
|
{
|
|
stopTimer();
|
|
owner.handleCrash (String());
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
class CompileEngineChildProcess::ChildProcess : private ValueTree::Listener,
|
|
private Timer
|
|
{
|
|
public:
|
|
ChildProcess (CompileEngineChildProcess& proc, Project& p)
|
|
: owner (proc), project (p)
|
|
{
|
|
projectRoot = project.getProjectRoot();
|
|
|
|
restartServer();
|
|
projectRoot.addListener (this);
|
|
openedOk = true;
|
|
}
|
|
|
|
~ChildProcess()
|
|
{
|
|
projectRoot.removeListener (this);
|
|
|
|
if (isRunningApp && server != nullptr)
|
|
server->killServerWithoutMercy();
|
|
|
|
server = nullptr;
|
|
}
|
|
|
|
void restartServer()
|
|
{
|
|
server = nullptr;
|
|
server = new ClientIPC (owner);
|
|
sendRebuild();
|
|
}
|
|
|
|
void sendRebuild()
|
|
{
|
|
stopTimer();
|
|
|
|
ProjectBuildInfo build;
|
|
|
|
if (! doesProjectMatchSavedHeaderState (project))
|
|
{
|
|
MessageTypes::sendNewBuild (*server, build);
|
|
|
|
owner.errorList.resetToError ("Project structure does not match the saved headers! "
|
|
"Please re-save your project to enable compilation");
|
|
return;
|
|
}
|
|
|
|
if (areAnyModulesMissing (project))
|
|
{
|
|
MessageTypes::sendNewBuild (*server, build);
|
|
|
|
owner.errorList.resetToError ("Some of your JUCE modules can't be found! "
|
|
"Please check that all the module paths are correct");
|
|
return;
|
|
}
|
|
|
|
build.setSystemIncludes (getSystemIncludePaths());
|
|
build.setUserIncludes (getUserIncludes());
|
|
|
|
build.setGlobalDefs (getGlobalDefs (project));
|
|
build.setCompileFlags (ProjectProperties::getExtraCompilerFlagsString (project).trim());
|
|
build.setExtraDLLs (getExtraDLLs());
|
|
build.setJuceModulesFolder (EnabledModuleList::findDefaultModulesFolder (project).getFullPathName());
|
|
|
|
build.setUtilsCppInclude (project.getAppIncludeFile().getFullPathName());
|
|
|
|
build.setWindowsTargetPlatformVersion (ProjectProperties::getWindowsTargetPlatformVersionString (project));
|
|
|
|
scanForProjectFiles (project, build);
|
|
|
|
owner.updateAllEditors();
|
|
|
|
MessageTypes::sendNewBuild (*server, build);
|
|
}
|
|
|
|
void cleanAll()
|
|
{
|
|
MessageTypes::sendCleanAll (*server);
|
|
sendRebuild();
|
|
}
|
|
|
|
void reinstantiatePreviews()
|
|
{
|
|
MessageTypes::sendReinstantiate (*server);
|
|
}
|
|
|
|
bool launchApp()
|
|
{
|
|
MessageTypes::sendLaunchApp (*server);
|
|
return true;
|
|
}
|
|
|
|
ScopedPointer<ClientIPC> server;
|
|
|
|
bool openedOk = false;
|
|
bool isRunningApp = false;
|
|
|
|
private:
|
|
CompileEngineChildProcess& owner;
|
|
Project& project;
|
|
ValueTree projectRoot;
|
|
|
|
void projectStructureChanged()
|
|
{
|
|
startTimer (100);
|
|
}
|
|
|
|
void timerCallback() override
|
|
{
|
|
sendRebuild();
|
|
}
|
|
|
|
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { projectStructureChanged(); }
|
|
void valueTreeChildAdded (ValueTree&, ValueTree&) override { projectStructureChanged(); }
|
|
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { projectStructureChanged(); }
|
|
void valueTreeParentChanged (ValueTree&) override { projectStructureChanged(); }
|
|
void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
|
|
|
|
static String getGlobalDefs (Project& proj)
|
|
{
|
|
String defs (ProjectProperties::getExtraPreprocessorDefsString (proj));
|
|
|
|
for (Project::ExporterIterator exporter (proj); exporter.next();)
|
|
if (exporter->canLaunchProject())
|
|
defs << " " << exporter->getExporterIdentifierMacro() << "=1";
|
|
|
|
// Use the JUCE implementation of std::function until the live build
|
|
// engine can compile the one from the standard library
|
|
defs << " _LIBCPP_FUNCTIONAL=1";
|
|
|
|
return defs;
|
|
}
|
|
|
|
static void scanProjectItem (const Project::Item& projectItem, Array<File>& compileUnits, Array<File>& userFiles)
|
|
{
|
|
if (projectItem.isGroup())
|
|
{
|
|
for (int i = 0; i < projectItem.getNumChildren(); ++i)
|
|
scanProjectItem (projectItem.getChild(i), compileUnits, userFiles);
|
|
|
|
return;
|
|
}
|
|
|
|
if (projectItem.shouldBeCompiled())
|
|
{
|
|
const File f (projectItem.getFile());
|
|
|
|
if (f.exists())
|
|
compileUnits.add (f);
|
|
}
|
|
|
|
if (projectItem.shouldBeAddedToTargetProject() && ! projectItem.shouldBeAddedToBinaryResources())
|
|
{
|
|
const File f (projectItem.getFile());
|
|
|
|
if (f.exists())
|
|
userFiles.add (f);
|
|
}
|
|
}
|
|
|
|
void scanForProjectFiles (Project& proj, ProjectBuildInfo& build)
|
|
{
|
|
Array<File> compileUnits, userFiles;
|
|
scanProjectItem (proj.getMainGroup(), compileUnits, userFiles);
|
|
|
|
{
|
|
auto isVST3Host = project.getModules().isModuleEnabled ("juce_audio_processors")
|
|
&& project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3");
|
|
|
|
auto isPluginProject = proj.getProjectType().isAudioPlugin();
|
|
|
|
OwnedArray<LibraryModule> modules;
|
|
proj.getModules().createRequiredModules (modules);
|
|
|
|
for (Project::ExporterIterator exporter (proj); exporter.next();)
|
|
{
|
|
if (exporter->canLaunchProject())
|
|
{
|
|
for (const LibraryModule* m : modules)
|
|
{
|
|
const File localModuleFolder = proj.getModules().shouldCopyModuleFilesLocally (m->moduleInfo.getID()).getValue()
|
|
? proj.getLocalModuleFolder (m->moduleInfo.getID())
|
|
: m->moduleInfo.getFolder();
|
|
|
|
|
|
m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits,
|
|
isPluginProject || isVST3Host ? ProjectType::Target::SharedCodeTarget
|
|
: ProjectType::Target::unspecified);
|
|
|
|
if (isPluginProject || isVST3Host)
|
|
m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits, ProjectType::Target::StandalonePlugIn);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; ; ++i)
|
|
{
|
|
const File binaryDataCpp (proj.getBinaryDataCppFile (i));
|
|
if (! binaryDataCpp.exists())
|
|
break;
|
|
|
|
compileUnits.add (binaryDataCpp);
|
|
}
|
|
|
|
for (int i = compileUnits.size(); --i >= 0;)
|
|
if (compileUnits.getReference(i).hasFileExtension (".r"))
|
|
compileUnits.remove (i);
|
|
|
|
build.setFiles (compileUnits, userFiles);
|
|
}
|
|
|
|
static bool doesProjectMatchSavedHeaderState (Project& project)
|
|
{
|
|
ValueTree liveModules (project.getProjectRoot().getChildWithName (Ids::MODULES));
|
|
|
|
ScopedPointer<XmlElement> xml (XmlDocument::parse (project.getFile()));
|
|
|
|
if (xml == nullptr || ! xml->hasTagName (Ids::JUCERPROJECT.toString()))
|
|
return false;
|
|
|
|
ValueTree diskModules (ValueTree::fromXml (*xml).getChildWithName (Ids::MODULES));
|
|
|
|
return liveModules.isEquivalentTo (diskModules);
|
|
}
|
|
|
|
static bool areAnyModulesMissing (Project& project)
|
|
{
|
|
OwnedArray<LibraryModule> modules;
|
|
project.getModules().createRequiredModules (modules);
|
|
|
|
for (auto* module : modules)
|
|
if (! module->getFolder().isDirectory())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
StringArray getUserIncludes()
|
|
{
|
|
StringArray paths;
|
|
paths.add (project.getGeneratedCodeFolder().getFullPathName());
|
|
paths.addArray (getSearchPathsFromString (ProjectProperties::getUserHeaderPathString (project)));
|
|
return convertSearchPathsToAbsolute (paths);
|
|
}
|
|
|
|
StringArray getSystemIncludePaths()
|
|
{
|
|
StringArray paths;
|
|
paths.addArray (getSearchPathsFromString (ProjectProperties::getSystemHeaderPathString (project)));
|
|
|
|
auto isVST3Host = project.getModules().isModuleEnabled ("juce_audio_processors")
|
|
&& project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3");
|
|
|
|
if (project.getProjectType().isAudioPlugin() || isVST3Host)
|
|
paths.add (getAppSettings().getStoredPath (Ids::vst3Path).toString());
|
|
|
|
OwnedArray<LibraryModule> modules;
|
|
project.getModules().createRequiredModules (modules);
|
|
|
|
for (auto* module : modules)
|
|
paths.addIfNotAlreadyThere (module->getFolder().getParentDirectory().getFullPathName());
|
|
|
|
return convertSearchPathsToAbsolute (paths);
|
|
}
|
|
|
|
StringArray convertSearchPathsToAbsolute (const StringArray& paths) const
|
|
{
|
|
StringArray s;
|
|
const File root (project.getProjectFolder());
|
|
|
|
for (String p : paths)
|
|
s.add (root.getChildFile (p).getFullPathName());
|
|
|
|
return s;
|
|
}
|
|
|
|
StringArray getExtraDLLs()
|
|
{
|
|
StringArray dlls;
|
|
dlls.addTokens (ProjectProperties::getExtraDLLsString (project), "\n\r,", StringRef());
|
|
dlls.trim();
|
|
dlls.removeEmptyStrings();
|
|
return dlls;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess)
|
|
};
|
|
|
|
//==============================================================================
|
|
CompileEngineChildProcess::CompileEngineChildProcess (Project& p)
|
|
: project (p),
|
|
continuousRebuild (false)
|
|
{
|
|
ProjucerApplication::getApp().openDocumentManager.addListener (this);
|
|
|
|
createProcess();
|
|
|
|
errorList.setWarningsEnabled (! LiveBuildProjectSettings::areWarningsDisabled (project));
|
|
}
|
|
|
|
CompileEngineChildProcess::~CompileEngineChildProcess()
|
|
{
|
|
ProjucerApplication::getApp().openDocumentManager.removeListener (this);
|
|
|
|
process = nullptr;
|
|
lastComponentList.clear();
|
|
}
|
|
|
|
void CompileEngineChildProcess::createProcess()
|
|
{
|
|
jassert (process == nullptr);
|
|
process = new ChildProcess (*this, project);
|
|
|
|
if (! process->openedOk)
|
|
process = nullptr;
|
|
|
|
updateAllEditors();
|
|
}
|
|
|
|
void CompileEngineChildProcess::cleanAll()
|
|
{
|
|
if (process != nullptr)
|
|
process->cleanAll();
|
|
}
|
|
|
|
void CompileEngineChildProcess::openPreview (const ClassDatabase::Class& comp)
|
|
{
|
|
if (process != nullptr)
|
|
{
|
|
MainWindow* projectWindow = nullptr;
|
|
OwnedArray<MainWindow>& windows = ProjucerApplication::getApp().mainWindowList.windows;
|
|
|
|
for (int i = 0; i < windows.size(); ++i)
|
|
{
|
|
if (MainWindow* w = windows[i])
|
|
{
|
|
if (w->getProject() == &project)
|
|
{
|
|
projectWindow = w;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle<int> mainWindowRect;
|
|
|
|
if (projectWindow != nullptr)
|
|
mainWindowRect = projectWindow->getBounds();
|
|
|
|
MessageTypes::sendOpenPreview (*process->server, comp, mainWindowRect);
|
|
}
|
|
}
|
|
|
|
void CompileEngineChildProcess::reinstantiatePreviews()
|
|
{
|
|
if (process != nullptr)
|
|
process->reinstantiatePreviews();
|
|
}
|
|
|
|
void CompileEngineChildProcess::processActivationChanged (bool isForeground)
|
|
{
|
|
if (process != nullptr)
|
|
MessageTypes::sendProcessActivationState (*process->server, isForeground);
|
|
}
|
|
|
|
//==============================================================================
|
|
bool CompileEngineChildProcess::canLaunchApp() const
|
|
{
|
|
return process != nullptr
|
|
&& runningAppProcess == nullptr
|
|
&& activityList.getNumActivities() == 0
|
|
&& errorList.getNumErrors() == 0
|
|
&& project.getProjectType().isGUIApplication();
|
|
}
|
|
|
|
void CompileEngineChildProcess::launchApp()
|
|
{
|
|
if (process != nullptr)
|
|
process->launchApp();
|
|
}
|
|
|
|
bool CompileEngineChildProcess::canKillApp() const
|
|
{
|
|
return runningAppProcess != nullptr;
|
|
}
|
|
|
|
void CompileEngineChildProcess::killApp()
|
|
{
|
|
runningAppProcess = nullptr;
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleAppLaunched()
|
|
{
|
|
runningAppProcess = process;
|
|
runningAppProcess->isRunningApp = true;
|
|
createProcess();
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleAppQuit()
|
|
{
|
|
DBG ("handleAppQuit");
|
|
runningAppProcess = nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
struct CompileEngineChildProcess::Editor : private CodeDocument::Listener,
|
|
private Timer
|
|
{
|
|
Editor (CompileEngineChildProcess& ccp, const File& f, CodeDocument& doc)
|
|
: owner (ccp), file (f), document (doc), transactionTimer (doc)
|
|
{
|
|
sendFullUpdate();
|
|
document.addListener (this);
|
|
}
|
|
|
|
~Editor()
|
|
{
|
|
document.removeListener (this);
|
|
}
|
|
|
|
void codeDocumentTextInserted (const String& newText, int insertIndex) override
|
|
{
|
|
CodeChange (Range<int> (insertIndex, insertIndex), newText).addToList (pendingChanges);
|
|
startEditorChangeTimer();
|
|
transactionTimer.stopTimer();
|
|
|
|
owner.lastComponentList.globalNamespace
|
|
.nudgeAllCodeRanges (file.getFullPathName(), insertIndex, newText.length());
|
|
}
|
|
|
|
void codeDocumentTextDeleted (int start, int end) override
|
|
{
|
|
CodeChange (Range<int> (start, end), String()).addToList (pendingChanges);
|
|
startEditorChangeTimer();
|
|
transactionTimer.stopTimer();
|
|
|
|
owner.lastComponentList.globalNamespace
|
|
.nudgeAllCodeRanges (file.getFullPathName(), start, start - end);
|
|
}
|
|
|
|
void sendFullUpdate()
|
|
{
|
|
reset();
|
|
|
|
if (owner.process != nullptr)
|
|
MessageTypes::sendFileContentFullUpdate (*owner.process->server, file, document.getAllContent());
|
|
}
|
|
|
|
bool flushEditorChanges()
|
|
{
|
|
if (pendingChanges.size() > 0)
|
|
{
|
|
if (owner.process != nullptr && owner.process->server != nullptr)
|
|
MessageTypes::sendFileChanges (*owner.process->server, pendingChanges, file);
|
|
|
|
reset();
|
|
return true;
|
|
}
|
|
|
|
stopTimer();
|
|
return false;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
stopTimer();
|
|
pendingChanges.clear();
|
|
}
|
|
|
|
void startTransactionTimer()
|
|
{
|
|
transactionTimer.startTimer (1000);
|
|
}
|
|
|
|
void startEditorChangeTimer()
|
|
{
|
|
startTimer (200);
|
|
}
|
|
|
|
CompileEngineChildProcess& owner;
|
|
File file;
|
|
CodeDocument& document;
|
|
|
|
private:
|
|
Array<CodeChange> pendingChanges;
|
|
|
|
void timerCallback() override
|
|
{
|
|
if (owner.continuousRebuild)
|
|
flushEditorChanges();
|
|
else
|
|
stopTimer();
|
|
}
|
|
|
|
struct TransactionTimer : public Timer
|
|
{
|
|
TransactionTimer (CodeDocument& doc) : document (doc) {}
|
|
|
|
void timerCallback() override
|
|
{
|
|
stopTimer();
|
|
document.newTransaction();
|
|
}
|
|
|
|
CodeDocument& document;
|
|
};
|
|
|
|
TransactionTimer transactionTimer;
|
|
};
|
|
|
|
void CompileEngineChildProcess::editorOpened (const File& file, CodeDocument& document)
|
|
{
|
|
editors.add (new Editor (*this, file, document));
|
|
}
|
|
|
|
bool CompileEngineChildProcess::documentAboutToClose (OpenDocumentManager::Document* document)
|
|
{
|
|
for (int i = editors.size(); --i >= 0;)
|
|
{
|
|
if (document->getFile() == editors.getUnchecked(i)->file)
|
|
{
|
|
const File f (editors.getUnchecked(i)->file);
|
|
editors.remove (i);
|
|
|
|
if (process != nullptr)
|
|
MessageTypes::sendHandleFileReset (*process->server, f);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CompileEngineChildProcess::updateAllEditors()
|
|
{
|
|
for (int i = editors.size(); --i >= 0;)
|
|
editors.getUnchecked(i)->sendFullUpdate();
|
|
}
|
|
|
|
//==============================================================================
|
|
void CompileEngineChildProcess::handleCrash (const String& message)
|
|
{
|
|
Logger::writeToLog ("*** Child process crashed: " + message);
|
|
|
|
if (crashHandler != nullptr)
|
|
crashHandler (message);
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleNewDiagnosticList (const ValueTree& l) { errorList.setList (l); }
|
|
void CompileEngineChildProcess::handleActivityListChanged (const StringArray& l) { activityList.setList (l); }
|
|
|
|
void CompileEngineChildProcess::handleCloseIDE()
|
|
{
|
|
if (JUCEApplication* app = JUCEApplication::getInstance())
|
|
app->systemRequestedQuit();
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleMissingSystemHeaders()
|
|
{
|
|
if (ProjectContentComponent* p = findProjectContentComponent())
|
|
p->handleMissingSystemHeaders();
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleKeyPress (const String& className, const KeyPress& key)
|
|
{
|
|
ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager();
|
|
|
|
CommandID command = commandManager.getKeyMappings()->findCommandForKeyPress (key);
|
|
|
|
if (command == StandardApplicationCommandIDs::undo)
|
|
{
|
|
handleUndoInEditor (className);
|
|
}
|
|
else if (command == StandardApplicationCommandIDs::redo)
|
|
{
|
|
handleRedoInEditor (className);
|
|
}
|
|
else if (ApplicationCommandTarget* const target = ApplicationCommandManager::findTargetForComponent (findProjectContentComponent()))
|
|
{
|
|
commandManager.setFirstCommandTarget (target);
|
|
commandManager.getKeyMappings()->keyPressed (key, findProjectContentComponent());
|
|
commandManager.setFirstCommandTarget (nullptr);
|
|
}
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleUndoInEditor (const String& /*className*/)
|
|
{
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleRedoInEditor (const String& /*className*/)
|
|
{
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleClassListChanged (const ValueTree& newList)
|
|
{
|
|
lastComponentList = ClassDatabase::ClassList::fromValueTree (newList);
|
|
activityList.sendClassListChangedMessage (lastComponentList);
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleBuildFailed()
|
|
{
|
|
auto* mcm = ModalComponentManager::getInstance();
|
|
auto* pcc = findProjectContentComponent();
|
|
|
|
if (mcm->getNumModalComponents() > 0 || pcc == nullptr || pcc->getCurrentTabIndex() == 1)
|
|
return;
|
|
|
|
if (errorList.getNumErrors() > 0)
|
|
ProjucerApplication::getCommandManager().invokeDirectly (CommandIDs::showBuildTab, true);
|
|
|
|
ProjucerApplication::getCommandManager().commandStatusChanged();
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleChangeCode (const SourceCodeRange& location, const String& newText)
|
|
{
|
|
if (Editor* ed = getOrOpenEditorFor (location.file))
|
|
{
|
|
if (ed->flushEditorChanges())
|
|
return; // client-side editor changes were pending, so deal with them first, and discard
|
|
// the incoming change, whose position may now be wrong.
|
|
|
|
ed->document.deleteSection (location.range.getStart(), location.range.getEnd());
|
|
ed->document.insertText (location.range.getStart(), newText);
|
|
|
|
// deliberately clear the messages that we just added, to avoid these changes being
|
|
// sent to the server (which will already have processed the same ones locally)
|
|
ed->reset();
|
|
ed->startTransactionTimer();
|
|
}
|
|
}
|
|
|
|
void CompileEngineChildProcess::handlePing()
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
void CompileEngineChildProcess::setContinuousRebuild (bool b)
|
|
{
|
|
continuousRebuild = b;
|
|
}
|
|
|
|
void CompileEngineChildProcess::flushEditorChanges()
|
|
{
|
|
for (Editor* ed : editors)
|
|
ed->flushEditorChanges();
|
|
}
|
|
|
|
ProjectContentComponent* CompileEngineChildProcess::findProjectContentComponent() const
|
|
{
|
|
for (MainWindow* mw : ProjucerApplication::getApp().mainWindowList.windows)
|
|
if (mw->getProject() == &project)
|
|
return mw->getProjectContentComponent();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CompileEngineChildProcess::Editor* CompileEngineChildProcess::getOrOpenEditorFor (const File& file)
|
|
{
|
|
for (Editor* ed : editors)
|
|
if (ed->file == file)
|
|
return ed;
|
|
|
|
if (ProjectContentComponent* pcc = findProjectContentComponent())
|
|
if (pcc->showEditorForFile (file, false))
|
|
return getOrOpenEditorFor (file);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CompileEngineChildProcess::handleHighlightCode (const SourceCodeRange& location)
|
|
{
|
|
ProjectContentComponent* pcc = findProjectContentComponent();
|
|
|
|
if (pcc != nullptr && pcc->showEditorForFile (location.file, false))
|
|
{
|
|
SourceCodeEditor* sce = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent());
|
|
|
|
if (sce != nullptr && sce->editor != nullptr)
|
|
{
|
|
sce->highlight (location.range, true);
|
|
|
|
Process::makeForegroundProcess();
|
|
|
|
CodeEditorComponent& ed = *sce->editor;
|
|
ed.getTopLevelComponent()->toFront (false);
|
|
ed.grabKeyboardFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CompileEngineChildProcess::cleanAllCachedFilesForProject (Project& p)
|
|
{
|
|
File cacheFolder (ProjectProperties::getCacheLocation (p));
|
|
|
|
if (cacheFolder.isDirectory())
|
|
cacheFolder.deleteRecursively();
|
|
}
|