1 /*
2 * SessionAsyncCompletions.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 // #define RSTUDIO_DEBUG_LABEL "pkginfo"
17 // #define RSTUDIO_ENABLE_DEBUG_MACROS
18
19 #include "SessionAsyncPackageInformation.hpp"
20
21 #include <string>
22 #include <vector>
23 #include <sstream>
24
25 #include <shared_core/FilePath.hpp>
26 #include <shared_core/json/Json.hpp>
27 #include <core/json/JsonRpc.hpp>
28 #include <shared_core/Error.hpp>
29
30 #include <boost/format.hpp>
31 #include <boost/algorithm/string.hpp>
32
33 #include <session/SessionModuleContext.hpp>
34
35 #include <core/Macros.hpp>
36
37 namespace rstudio {
38 namespace session {
39 namespace modules {
40 namespace r_packages {
41
42 using namespace core::r_util;
43
44 // static variables
45 bool AsyncPackageInformationProcess::s_isUpdating_ = false;
46 bool AsyncPackageInformationProcess::s_updateRequested_ = false;
47 std::vector<std::string> AsyncPackageInformationProcess::s_pkgsToUpdate_;
48
49 using namespace rstudio::core;
50
51 class CompleteUpdateOnExit : public boost::noncopyable {
52
53 public:
54
~CompleteUpdateOnExit()55 ~CompleteUpdateOnExit()
56 {
57 using namespace rstudio::core::r_util;
58
59 // Give empty completions to the packages which weren't updated
60 for (std::vector<std::string>::const_iterator it = AsyncPackageInformationProcess::s_pkgsToUpdate_.begin();
61 it != AsyncPackageInformationProcess::s_pkgsToUpdate_.end();
62 ++it)
63 {
64 if (!RSourceIndex::hasInformation(*it))
65 {
66 RSourceIndex::addPackageInformation(*it, PackageInformation());
67 }
68 }
69
70 AsyncPackageInformationProcess::s_pkgsToUpdate_.clear();
71 AsyncPackageInformationProcess::s_isUpdating_ = false;
72
73 if (AsyncPackageInformationProcess::s_updateRequested_)
74 {
75 AsyncPackageInformationProcess::s_updateRequested_ = false;
76 AsyncPackageInformationProcess::update();
77 }
78 }
79
80 };
81
82 namespace {
83
fillFormalInfo(const json::Array & formalNamesJson,const json::Array & formalInfoJsonArray,FunctionInformation * pInfo)84 void fillFormalInfo(const json::Array& formalNamesJson,
85 const json::Array& formalInfoJsonArray,
86 FunctionInformation* pInfo)
87 {
88 for (std::size_t i = 0, n = formalNamesJson.getSize(); i < n; ++i)
89 {
90 std::string formalName = formalNamesJson[i].getString();
91 FormalInformation info(formalName);
92
93 const json::Array& formalInfoJson = formalInfoJsonArray[i].getArray();
94
95 bool hasDefaultValue = formalInfoJson[0].getInt() != 0;
96 bool isMissingnessHandled = formalInfoJson[1].getInt() != 0;
97 bool isUsed = formalInfoJson[2].getInt() != 0;
98
99 info.setHasDefaultValue(hasDefaultValue);
100 info.setMissingnessHandled(isMissingnessHandled);
101 info.setIsUsed(isUsed);
102
103 pInfo->addFormal(info);
104 }
105 }
106
fillFunctionInfo(const json::Object & functionObjectJson,const std::string & pkgName,std::map<std::string,FunctionInformation> * pInfo)107 bool fillFunctionInfo(const json::Object& functionObjectJson,
108 const std::string& pkgName,
109 std::map<std::string, FunctionInformation>* pInfo)
110 {
111 using namespace core::json;
112
113 for (const json::Object::Member& member : functionObjectJson)
114 {
115 const std::string& functionName = member.getName();
116 FunctionInformation info(functionName, pkgName);
117
118 const json::Value& valueJson = member.getValue();
119
120 json::Array formalNamesJson;
121 json::Array formalInfoJson;
122 int performsNse = 0;
123 Error error = json::readObject(valueJson.getObject(),
124 "formal_names", formalNamesJson,
125 "formal_info", formalInfoJson,
126 "performs_nse", performsNse);
127
128 if (error)
129 LOG_ERROR(error);
130
131 info.setPerformsNse(performsNse);
132 info.setIsPrimitive(false);
133
134 fillFormalInfo(formalNamesJson, formalInfoJson, &info);
135
136 (*pInfo)[functionName] = info;
137 }
138
139 return true;
140
141 }
142
143 } // anonymous namespace
144
onCompleted(int exitStatus)145 void AsyncPackageInformationProcess::onCompleted(int exitStatus)
146 {
147 CompleteUpdateOnExit updateScope;
148
149 DEBUG("* Completed async library lookup");
150 std::vector<std::string> splat;
151
152 std::string stdOut = stdOut_.str();
153
154 stdOut_.str(std::string());
155 stdOut_.clear();
156
157 if (stdOut == "" || stdOut == "\n")
158 {
159 DEBUG("- Received empty response");
160 return;
161 }
162
163 boost::split(splat, stdOut, boost::is_any_of("\n"));
164
165 std::size_t n = splat.size();
166 DEBUG("- Received " << n << " lines of response");
167
168 // Each line should be a JSON object with the format:
169 //
170 // {
171 // "package": <single package name>
172 // "exports": <array of object names in the namespace>,
173 // "types": <array of types (see .rs.acCompletionTypes)>,
174 // "function_info": {big ugly object with function info},
175 // "data" <array of dataset names>
176 // }
177 for (std::size_t i = 0; i < n; ++i)
178 {
179 json::Array exportsJson;
180 json::Array typesJson;
181 json::Object functionInfoJson;
182 json::Array datasetsJson;
183
184 core::r_util::PackageInformation pkgInfo;
185
186 if (splat[i].empty())
187 continue;
188
189 // The lines we wish to parse should be prefixed with
190 // the code '#!json: '.
191 if (!boost::algorithm::starts_with(splat[i], "#!json: "))
192 continue;
193
194 std::string line = splat[i].substr(::strlen("#!json: "));
195
196 json::Value value;
197 if (!value.parse(line))
198 {
199 std::string subset;
200 if (splat[i].length() > 60)
201 subset = splat[i].substr(0, 60) + "...";
202 else
203 subset = splat[i];
204
205 LOG_ERROR_MESSAGE("Failed to parse JSON: '" + subset + "'");
206 continue;
207 }
208
209 // Ensure that this parsed as an Object -- this might have parsed as
210 // something else if e.g. we got malformed output on load of a package
211 if (!json::isType<json::Object>(value))
212 continue;
213
214 Error error = json::readObject(value.getObject(),
215 "package", pkgInfo.package,
216 "exports", exportsJson,
217 "types", typesJson,
218 "function_info", functionInfoJson,
219 "datasets", datasetsJson);
220
221 if (error)
222 {
223 LOG_ERROR(error);
224 continue;
225 }
226
227 DEBUG("Adding entry for package: '" << pkgInfo.package << "'");
228
229 if (!exportsJson.toVectorString(pkgInfo.exports))
230 LOG_ERROR_MESSAGE("Failed to read JSON 'objects' array to vector");
231
232 if (!typesJson.toVectorInt(pkgInfo.types))
233 LOG_ERROR_MESSAGE("Failed to read JSON 'types' array to vector");
234
235 if (!fillFunctionInfo(functionInfoJson, pkgInfo.package, &(pkgInfo.functionInfo)))
236 LOG_ERROR_MESSAGE("Failed to read JSON 'functions' object to map");
237
238 if (!datasetsJson.toVectorString(pkgInfo.datasets))
239 LOG_ERROR_MESSAGE("Failed to read JSON 'data' array to vector");
240
241 // Update the index
242 core::r_util::RSourceIndex::addPackageInformation(pkgInfo.package, pkgInfo);
243 }
244
245 }
246
update()247 void AsyncPackageInformationProcess::update()
248 {
249 using namespace rstudio::core::r_util;
250
251 s_updateRequested_ = true;
252 if (s_isUpdating_)
253 return;
254
255 s_isUpdating_ = true;
256 s_updateRequested_ = false;
257
258 s_pkgsToUpdate_ =
259 RSourceIndex::getAllUnindexedPackages();
260
261 // alias for readability
262 const std::vector<std::string>& pkgs = s_pkgsToUpdate_;
263
264 DEBUG_BLOCK("Completions")
265 {
266 if (!pkgs.empty())
267 {
268 std::cerr << "Updating packages: [";
269 std::cerr << "'" << pkgs[0] << "'";
270 for (std::size_t i = 1; i < pkgs.size(); i++)
271 {
272 std::cerr << ", '" << pkgs[i] << "'";
273 }
274 std::cerr << "]\n";
275 }
276 else
277 {
278 std::cerr << "No packages to update; bailing out" << std::endl;
279 }
280 }
281
282 if (pkgs.empty())
283 {
284 s_isUpdating_ = false;
285 return;
286 }
287
288 std::stringstream ss;
289 ss << ".rs.getPackageInformation(";
290 ss << "'" << pkgs[0] << "'";
291
292 if (pkgs.size() > 0)
293 {
294 for (std::vector<std::string>::const_iterator it = pkgs.begin() + 1;
295 it != pkgs.end();
296 ++it)
297 {
298 ss << ",'" << *it << "'";
299 }
300 }
301 ss << ");";
302
303 std::string finalCmd = ss.str();
304 DEBUG("Running command: '" << finalCmd << "'");
305
306 boost::shared_ptr<AsyncPackageInformationProcess> pProcess(
307 new AsyncPackageInformationProcess());
308
309 std::vector<core::FilePath> sources;
310 FilePath modulesPath = session::options().modulesRSourcePath();
311 sources.push_back(modulesPath.completePath("SessionCodeTools.R"));
312 sources.push_back(modulesPath.completePath("SessionRCompletions.R"));
313
314 pProcess->start(
315 finalCmd.c_str(),
316 core::FilePath(),
317 async_r::R_PROCESS_VANILLA | async_r::R_PROCESS_AUGMENTED,
318 sources);
319
320 }
321
322 } // end namespace r_pacakges
323 } // end namespace modules
324 } // end namespace session
325 } // end namespace rstudio
326