Skip to content

Commit

Permalink
option to automatically call devtools load_all when files change
Browse files Browse the repository at this point in the history
  • Loading branch information
jjallaire committed Jul 11, 2012
1 parent cc9825e commit 9838afa
Show file tree
Hide file tree
Showing 18 changed files with 291 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/cpp/r/include/r/session/RSession.hpp
Expand Up @@ -119,7 +119,7 @@ struct RCallbacks
boost::function<int(const std::string&)> editFile;
boost::function<void(const std::string&)> showMessage ;
boost::function<void(bool)> busy;
boost::function<void()> deferredInit;
boost::function<void(bool)> deferredInit;
boost::function<void()> suspended;
boost::function<void()> resumed;
boost::function<bool()> handleUnsavedChanges;
Expand Down
8 changes: 4 additions & 4 deletions src/cpp/r/session/RSession.cpp
Expand Up @@ -156,11 +156,11 @@ void reportDeferredDeserializationError(const Error& error)
REprintf((errMsg + "\n").c_str());
}

void completeDeferredSessionInit()
void completeDeferredSessionInit(bool newSession)
{
// call external hook
if (s_callbacks.deferredInit)
s_callbacks.deferredInit();
s_callbacks.deferredInit(newSession);
}

void saveClientState(ClientStateCommitType commitType)
Expand Down Expand Up @@ -211,7 +211,7 @@ void deferredRestoreSuspendedSession(
reportDeferredDeserializationError(error);

// complete deferred init
completeDeferredSessionInit();
completeDeferredSessionInit(false);

}

Expand Down Expand Up @@ -280,7 +280,7 @@ void deferredRestoreNewSession()
setImageDirty(false);

// complete deferred init
completeDeferredSessionInit();
completeDeferredSessionInit(true);
}

void reportHistoryAccessError(const std::string& context,
Expand Down
9 changes: 7 additions & 2 deletions src/cpp/session/SessionMain.cpp
Expand Up @@ -469,6 +469,11 @@ void handleClientInit(const boost::function<void()>& initFunction,

sessionInfo["build_state"] = modules::build::buildStateAsJson();
sessionInfo["build_restart_context"] = modules::build::buildRestartContext();
if (!resumed)
{
sessionInfo["build_devtools_load_path"]
= modules::build::buildDevtoolsLoadPath();
}

// send response (we always set kEventsPending to false so that the client
// won't poll for events until it is ready)
Expand Down Expand Up @@ -1407,9 +1412,9 @@ Error rInit(const r::session::RInitInfo& rInitInfo)
return Success();
}

void rDeferredInit()
void rDeferredInit(bool newSession)
{
module_context::events().onDeferredInit();
module_context::events().onDeferredInit(newSession);
}

void consolePrompt(const std::string& prompt, bool addToHistory)
Expand Down
2 changes: 1 addition & 1 deletion src/cpp/session/include/session/SessionModuleContext.hpp
Expand Up @@ -199,7 +199,7 @@ struct Events : boost::noncopyable
boost::signal<void ()> onClientInit;
boost::signal<void ()> onBeforeExecute;
boost::signal<void (ChangeSource)> onDetectChanges;
boost::signal<void()> onDeferredInit;
boost::signal<void(bool)> onDeferredInit;
boost::signal<void(bool)> onBackgroundProcessing;
boost::signal<void(bool)> onShutdown;
boost::signal<void ()> onSysSleep;
Expand Down
23 changes: 21 additions & 2 deletions src/cpp/session/include/session/projects/SessionProjects.hpp
Expand Up @@ -62,7 +62,8 @@ struct RProjectBuildOptions
cleanupAfterCheck(true),
autoRoxygenizeForCheck(true),
autoRoxygenizeForBuildPackage(true),
autoRoxygenizeForBuildAndReload(false)
autoRoxygenizeForBuildAndReload(false),
autoExecuteLoadAll(false)
{
}

Expand All @@ -71,6 +72,7 @@ struct RProjectBuildOptions
bool autoRoxygenizeForCheck;
bool autoRoxygenizeForBuildPackage;
bool autoRoxygenizeForBuildAndReload;
bool autoExecuteLoadAll;
};

class ProjectContext : boost::noncopyable
Expand Down Expand Up @@ -111,6 +113,7 @@ class ProjectContext : boost::noncopyable
{
config_ = config;
updateDefaultEncoding();
updateBuildTargetPath();
}

core::Error readVcsOptions(RProjectVcsOptions* pOptions) const;
Expand All @@ -127,8 +130,21 @@ class ProjectContext : boost::noncopyable
// if necessary
std::string defaultEncoding() const;

// computed absolute path to project build target directory
const core::FilePath& buildTargetPath() const
{
return buildTargetPath_;
}

core::json::Object uiPrefs() const;

// current build options (note that these are not synchronized
// accross processes!)
const RProjectBuildOptions& buildOptions() const
{
return buildOptions_;
}

// does this project context have a file monitor? (might not have one
// if the user has disabled code indexing or if file monitoring failed
// for this path)
Expand All @@ -151,7 +167,7 @@ class ProjectContext : boost::noncopyable
// deferred init handler (this allows other modules to reliably subscribe
// to our file monitoring events with no concern that they'll miss
// onMonitoringEnabled)
void onDeferredInit();
void onDeferredInit(bool newSession);

// file monitor event handlers
void fileMonitorRegistered(core::system::file_monitor::Handle handle,
Expand All @@ -164,6 +180,7 @@ class ProjectContext : boost::noncopyable
core::Error buildOptionsFile(core::Settings* pOptionsFile) const;

void updateDefaultEncoding();
void updateBuildTargetPath();

void augmentRbuildignore();

Expand All @@ -173,6 +190,8 @@ class ProjectContext : boost::noncopyable
core::FilePath scratchPath_;
core::r_util::RProjectConfig config_;
std::string defaultEncoding_;
core::FilePath buildTargetPath_;
RProjectBuildOptions buildOptions_;

bool hasFileMonitor_;
std::vector<std::string> monitorSubscribers_;
Expand Down
153 changes: 129 additions & 24 deletions src/cpp/session/modules/SessionBuild.cpp
Expand Up @@ -28,6 +28,9 @@
#include <core/system/ShellUtils.hpp>
#include <core/r_util/RPackageInfo.hpp>

#include <r/RExec.hpp>
#include <r/session/RSessionUtils.hpp>

#include <session/projects/SessionProjects.hpp>
#include <session/SessionModuleContext.hpp>

Expand All @@ -52,6 +55,7 @@ FilePath restartContextFilePath()
}

void saveRestartContext(const FilePath& packageDir,
const std::string& devtoolsLoadPath,
const std::string& buildOutput)
{
// read package info
Expand All @@ -74,6 +78,7 @@ void saveRestartContext(const FilePath& packageDir,

restartSettings.beginUpdate();
restartSettings.set("package_name", pkgInfo.name());
restartSettings.set("devtools_load_path", devtoolsLoadPath);
restartSettings.set("build_output", buildOutput);
restartSettings.endUpdate();
}
Expand Down Expand Up @@ -102,6 +107,7 @@ json::Value collectRestartContext()

json::Object restartJson;
restartJson["package_name"] = restartSettings.get("package_name");
restartJson["devtools_load_path"] = restartSettings.get("devtools_load_path");
restartJson["build_output"] = restartSettings.get("build_output");
return restartJson;
}
Expand All @@ -111,6 +117,14 @@ json::Value collectRestartContext()
}
}

std::string computeDevtoolsLoadPath()
{
std::string loadPath = projects::projectContext().config().packagePath;
if (loadPath.empty())
loadPath = ".";
return loadPath;
}


// R command invocation -- has two representations, one to be submitted
// (shellCmd_) and one to show the user (cmdString_)
Expand Down Expand Up @@ -222,24 +236,22 @@ class Build : boost::noncopyable,
options.terminateChildren = true;
options.redirectStdErrToStdOut = true;

FilePath buildTargetPath = projects::projectContext().buildTargetPath();
const core::r_util::RProjectConfig& config = projectConfig();
if (config.buildType == r_util::kBuildTypePackage)
{
FilePath packagePath = projectPath(config.packagePath);
options.workingDir = packagePath.parent();
executePackageBuild(type, packagePath, options, cb);
options.workingDir = buildTargetPath.parent();
executePackageBuild(type, buildTargetPath, options, cb);
}
else if (config.buildType == r_util::kBuildTypeMakefile)
{
FilePath makefilePath = projectPath(config.makefilePath);
options.workingDir = makefilePath;
options.workingDir = buildTargetPath;
executeMakefileBuild(type, options, cb);
}
else if (config.buildType == r_util::kBuildTypeCustom)
{
FilePath scriptPath = projectPath(config.customScriptPath);
options.workingDir = scriptPath.parent();
executeCustomBuild(type, scriptPath, options, cb);
options.workingDir = buildTargetPath.parent();
executeCustomBuild(type, buildTargetPath, options, cb);
}
else
{
Expand Down Expand Up @@ -560,7 +572,7 @@ class Build : boost::noncopyable,
void cleanupAfterCheck(const r_util::RPackageInfo& pkgInfo)
{
// compute paths
FilePath buildPath = projectPath(projectConfig().packagePath).parent();
FilePath buildPath = projects::projectContext().buildTargetPath().parent();
FilePath srcPkgPath = buildPath.childPath(pkgInfo.sourcePackageFilename());
FilePath chkDirPath = buildPath.childPath(pkgInfo.name() + ".Rcheck");

Expand Down Expand Up @@ -613,18 +625,6 @@ class Build : boost::noncopyable,
cb);
}

FilePath projectPath(const std::string& path)
{
if (boost::algorithm::starts_with(path, "~/") ||
FilePath::isRootPath(path))
{
return module_context::resolveAliasedPath(path);
}
else
{
return projects::projectContext().directory().complete(path);
}
}

void terminateWithErrorStatus(int exitStatus)
{
Expand Down Expand Up @@ -714,8 +714,14 @@ class Build : boost::noncopyable,
// save the restart context if necessary
if ((projectConfig().buildType == r_util::kBuildTypePackage) && restartR_)
{
FilePath packagePath = projectPath(projectConfig().packagePath);
saveRestartContext(packagePath, output_);
FilePath packagePath = projects::projectContext().buildTargetPath();

// devtools load all if necessary
std::string devtoolsLoadPath;
if (options_.autoExecuteLoadAll)
devtoolsLoadPath = computeDevtoolsLoadPath();

saveRestartContext(packagePath, devtoolsLoadPath, output_);
}

ClientEvent event(client_events::kBuildCompleted, restartR_);
Expand All @@ -729,7 +735,7 @@ class Build : boost::noncopyable,

std::string buildPackageSuccessMsg(const std::string& type)
{
FilePath writtenPath = projectPath(projectConfig().packagePath).parent();
FilePath writtenPath = projects::projectContext().buildTargetPath().parent();
std::string written = module_context::createAliasedPath(writtenPath);
if (written == "~")
written = writtenPath.absolutePath();
Expand Down Expand Up @@ -791,6 +797,91 @@ Error terminateBuild(const json::JsonRpcRequest& request,
return Success();
}

void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events,
boost::shared_ptr<bool> pPackageChanged)
{
// do nothing if we aren't monitoring
if (!projects::projectContext().buildOptions().autoExecuteLoadAll)
return;

FilePath buildTargetPath = projects::projectContext().buildTargetPath();
BOOST_FOREACH(const core::system::FileChangeEvent& event, events)
{
FilePath changedFilePath(event.fileInfo().absolutePath());
if (changedFilePath.isWithin(buildTargetPath))
{
std::string relativePath =
changedFilePath.relativePath(buildTargetPath);
if (boost::algorithm::starts_with(relativePath, "DESCRIPTION") ||
boost::algorithm::starts_with(relativePath, "R") ||
boost::algorithm::starts_with(relativePath, "src") ||
boost::algorithm::starts_with(relativePath, "data"))
{
*pPackageChanged = true;
break;
}
}
}
}

void onMonitoringDisabled()
{
}

void onDeferredInit(bool newSession,
boost::shared_ptr<bool> pPackageChanged)
{
// tweak the package changed flag to force a refresh
// whenever we come back from a suspended session
if (!newSession)
*pPackageChanged = true;
}

bool executeLoadAllIfPackageChanged(boost::shared_ptr<bool> pPackageChanged)
{
if (*pPackageChanged)
{
// reset state
*pPackageChanged = false;

r::session::utils::SuppressOutputInScope suppressOutput;

r::exec::RFunction loadAll("devtools:::load_all");
loadAll.addParam(computeDevtoolsLoadPath());
Error error = loadAll.call();
if (error)
LOG_ERROR(error);
}

return true;
}

void initializeDevtoolsLoadAllMonitor()
{
// boolean which we'll update
boost::shared_ptr<bool> pPackageChanged(new bool(false));

// subscribe to file_monitor for devtools::load_all
projects::FileMonitorCallbacks cb;
cb.onFilesChanged = boost::bind(onFilesChanged, _1, pPackageChanged);
cb.onMonitoringDisabled = onMonitoringDisabled;
projects::projectContext().subscribeToFileMonitor(
"Automatic execution of devtools::load_all", cb);

// deferred init handler for startup
module_context::events().onDeferredInit.connect(
boost::bind(onDeferredInit, _1, pPackageChanged));


// schedule periodic work to check whether there are changes
// and execute devtools::load_all
module_context::schedulePeriodicWork(
boost::posix_time::milliseconds(500),
boost::bind(executeLoadAllIfPackageChanged, pPackageChanged),
true);
}


} // anonymous namespace


Expand All @@ -814,8 +905,22 @@ json::Value buildRestartContext()
return collectRestartContext();
}

json::Value buildDevtoolsLoadPath()
{
if (projects::projectContext().buildOptions().autoExecuteLoadAll)
{
return computeDevtoolsLoadPath();
}
else
{
return json::Value();
}
}

Error initialize()
{
initializeDevtoolsLoadAllMonitor();

// install rpc methods
using boost::bind;
using namespace module_context;
Expand Down

0 comments on commit 9838afa

Please sign in to comment.