1 /*
2 * SessionTexUtils.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 #include "SessionTexUtils.hpp"
17
18 #include <boost/algorithm/string.hpp>
19
20 #include <core/system/Process.hpp>
21 #include <core/system/Environment.hpp>
22
23 #include <r/RExec.hpp>
24
25 #include <session/SessionModuleContext.hpp>
26
27 #include "SessionCompilePdfSupervisor.hpp"
28
29 using namespace rstudio::core;
30
31 namespace rstudio {
32 namespace session {
33 namespace modules {
34 namespace tex {
35 namespace utils {
36
37 namespace {
38
39 // this function attempts to emulate the behavior of tools::texi2dvi
40 // in appending extra paths to TEXINPUTS, BIBINPUTS, & BSTINPUTS
inputsEnvVar(const std::string & name,const FilePath & extraPath,bool ensureForwardSlashes)41 core::system::Option inputsEnvVar(const std::string& name,
42 const FilePath& extraPath,
43 bool ensureForwardSlashes)
44 {
45 std::string value = core::system::getenv(name);
46 if (value.empty())
47 value = ".";
48
49 // on windows tools::texi2dvi replaces \ with / when defining the TEXINPUTS
50 // environment variable (but for BIBINPUTS and BSTINPUTS)
51 #ifdef _WIN32
52 if (ensureForwardSlashes)
53 boost::algorithm::replace_all(value, "\\", "/");
54 #endif
55
56 std::string sysPath = string_utils::utf8ToSystem(extraPath.getAbsolutePath());
57 core::system::addToPath(&value, sysPath);
58 core::system::addToPath(&value, ""); // trailing : required by tex
59
60 return std::make_pair(name, value);
61 }
62
buildArgs(const shell_utils::ShellArgs & args,const FilePath & texFilePath)63 shell_utils::ShellArgs buildArgs(const shell_utils::ShellArgs& args,
64 const FilePath& texFilePath)
65 {
66 shell_utils::ShellArgs procArgs;
67 procArgs << args;
68 procArgs << texFilePath.getFilename();
69 return procArgs;
70 }
71
ignoreOutput(const std::string & output)72 void ignoreOutput(const std::string& output)
73 {
74 }
75
76 } // anonymous namespace
77
rTexmfPaths()78 RTexmfPaths rTexmfPaths()
79 {
80 // first determine the R share directory
81 std::string rHomeShare;
82 r::exec::RFunction rHomeShareFunc("R.home", "share");
83 Error error = rHomeShareFunc.call(&rHomeShare);
84 if (error)
85 {
86 LOG_ERROR(error);
87 return RTexmfPaths();
88 }
89 FilePath rHomeSharePath(rHomeShare);
90 if (!rHomeSharePath.exists())
91 {
92 LOG_ERROR(core::pathNotFoundError(rHomeShare, ERROR_LOCATION));
93 return RTexmfPaths();
94 }
95
96 // R texmf path
97 FilePath rTexmfPath(rHomeSharePath.completePath("texmf"));
98 if (!rTexmfPath.exists())
99 {
100 LOG_ERROR(core::pathNotFoundError(
101 rTexmfPath.getAbsolutePath(),
102 ERROR_LOCATION));
103 return RTexmfPaths();
104 }
105
106 // populate and return struct
107 RTexmfPaths texmfPaths;
108 texmfPaths.texInputsPath = rTexmfPath.completeChildPath("tex/latex");
109 texmfPaths.bibInputsPath = rTexmfPath.completeChildPath("bibtex/bib");
110 texmfPaths.bstInputsPath = rTexmfPath.completeChildPath("bibtex/bst");
111 return texmfPaths;
112 }
113
114
115 // build TEXINPUTS, BIBINPUTS etc. by composing any existing value in
116 // the environment (or . if none) with the R dirs in share/texmf
rTexInputsEnvVars()117 core::system::Options rTexInputsEnvVars()
118 {
119 core::system::Options envVars;
120 RTexmfPaths texmfPaths = rTexmfPaths();
121 if (!texmfPaths.empty())
122 {
123 envVars.push_back(inputsEnvVar("TEXINPUTS",
124 texmfPaths.texInputsPath,
125 true));
126 envVars.push_back(inputsEnvVar("BIBINPUTS",
127 texmfPaths.bibInputsPath,
128 false));
129 envVars.push_back(inputsEnvVar("BSTINPUTS",
130 texmfPaths.bstInputsPath,
131 false));
132 }
133 return envVars;
134 }
135
runTexCompile(const FilePath & texProgramPath,const core::system::Options & envVars,const shell_utils::ShellArgs & args,const FilePath & texFilePath,core::system::ProcessResult * pResult)136 Error runTexCompile(const FilePath& texProgramPath,
137 const core::system::Options& envVars,
138 const shell_utils::ShellArgs& args,
139 const FilePath& texFilePath,
140 core::system::ProcessResult* pResult)
141 {
142 // copy extra environment variables
143 core::system::Options env;
144 core::system::environment(&env);
145 for (const core::system::Option& var : envVars)
146 {
147 core::system::setenv(&env, var.first, var.second);
148 }
149
150 // set options
151 core::system::ProcessOptions procOptions;
152 procOptions.terminateChildren = true;
153 procOptions.redirectStdErrToStdOut = true;
154 procOptions.environment = env;
155 procOptions.workingDir = texFilePath.getParent();
156
157 // run the program
158 return core::system::runProgram(
159 string_utils::utf8ToSystem(texProgramPath.getAbsolutePath()),
160 buildArgs(args, texFilePath),
161 "",
162 procOptions,
163 pResult);
164 }
165
runTexCompile(const core::FilePath & texProgramPath,const core::system::Options & envVars,const core::shell_utils::ShellArgs & args,const core::FilePath & texFilePath,const boost::function<void (int,const std::string &)> & onExited)166 core::Error runTexCompile(
167 const core::FilePath& texProgramPath,
168 const core::system::Options& envVars,
169 const core::shell_utils::ShellArgs& args,
170 const core::FilePath& texFilePath,
171 const boost::function<void(int,const std::string&)>& onExited)
172 {
173 return compile_pdf_supervisor::runProgram(
174 texProgramPath,
175 buildArgs(args, texFilePath),
176 envVars,
177 texFilePath.getParent(),
178 ignoreOutput,
179 onExited);
180
181 }
182
183 } // namespace utils
184 } // namespace tex
185 } // namespace modules
186 } // namespace session
187 } // namespace rstudio
188
189