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