]> git.lyx.org Git - lyx.git/blob - src/support/package.C.in
Asgers Win32 changes to fs_extras
[lyx.git] / src / support / package.C.in
1 // -*- C++ -*-
2 /**
3  * \file package.C
4  * This file is part of LyX, the document processor.
5  * Licence details can be found in the file COPYING.
6  *
7  * \author Angus Leeming
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  * Warning! This file is autogenerated from package.C.in.
12  * All changes to this file will be lost.
13  */
14
15 #include <config.h>
16
17 #include "support/package.h"
18
19 #include "debug.h"
20 #include "gettext.h"
21
22 #include "support/filetools.h"
23 #include "support/lstrings.h"
24 #include "support/os.h"
25
26 #include <boost/assert.hpp>
27 #include <boost/filesystem/operations.hpp>
28 #include <boost/tuple/tuple.hpp>
29
30 #include <list>
31 #include <utility>
32
33 #if !defined (USE_WINDOWS_PACKAGING) && \
34     !defined (USE_MACOSX_PACKAGING) && \
35     !defined (USE_POSIX_PACKAGING)
36 #error USE_FOO_PACKAGING must be defined for FOO = WINDOWS, MACOSX or POSIX.
37 #endif
38
39 #if defined (USE_WINDOWS_PACKAGING)
40 # include <windows.h>
41 # include <shlobj.h>  // SHGetFolderPath
42
43   // Needed for MinGW:
44 # ifndef SHGFP_TYPE_CURRENT
45 #  define SHGFP_TYPE_CURRENT 0
46 # endif
47
48 #elif defined (USE_MACOSX_PACKAGING)
49 # include <CoreServices/CoreServices.h> // FSFindFolder, FSRefMakePath
50 #endif
51
52 using std::string;
53
54 namespace fs = boost::filesystem;
55
56 namespace lyx {
57 namespace support {
58
59 namespace {
60
61 Package package_;
62 bool initialised_ = false;
63
64 } // namespace anon
65
66
67 void init_package(string const & command_line_arg0,
68                   string const & command_line_system_support_dir,
69                   string const & command_line_user_support_dir)
70 {
71         // Can do so only once.
72         if (initialised_)
73                 return;
74
75         package_ = Package(command_line_arg0,
76                            command_line_system_support_dir,
77                            command_line_user_support_dir);
78         initialised_ = true;
79 }
80
81
82 Package const & package()
83 {
84         // Commented out because package().locale_dir() can be called
85         // from the message translation code in messages.C before
86         // init_package() is called. Lars is on the case...
87         // BOOST_ASSERT(initialised_);
88         return package_;
89 }
90
91
92 namespace {
93
94 string const abs_path_from_binary_name(string const & exe);
95
96 std::pair<string, string> const get_build_dirs(string const & abs_binary);
97
98 string const get_document_dir(string const & home_dir);
99
100 string const get_home_dir();
101
102 string const get_locale_dir(string const & system_support_dir);
103
104 string const get_system_support_dir(string const & abs_binary,
105                                     string const & command_line_system_support_dir);
106
107 string const get_temp_dir();
108
109 string const get_default_user_support_dir(string const & home_dir);
110
111 std::pair<string, bool> const
112 get_user_support_dir(string const & default_user_support_dir,
113                      string const & command_line_user_support_dir);
114
115 } // namespace anon
116
117
118 Package::Package(string const & command_line_arg0,
119                  string const & command_line_system_support_dir,
120                  string const & command_line_user_support_dir)
121         : explicit_user_support_dir_(false)
122 {
123         home_dir_ = get_home_dir();
124         temp_dir_ = get_temp_dir();
125         document_dir_ = get_document_dir(home_dir_);
126
127         string const abs_binary = abs_path_from_binary_name(command_line_arg0);
128         binary_dir_ = OnlyPath(abs_binary);
129
130         // Is LyX being run in-place from the build tree?
131         boost::tie(build_support_dir_, system_support_dir_) =
132                 get_build_dirs(abs_binary);
133
134         if (build_support_dir_.empty())
135                 system_support_dir_ =
136                         get_system_support_dir(abs_binary,
137                                                command_line_system_support_dir);
138
139         locale_dir_ = get_locale_dir(system_support_dir_);
140
141         string const default_user_support_dir =
142                 get_default_user_support_dir(home_dir_);
143         boost::tie(user_support_dir_, explicit_user_support_dir_) =
144                 get_user_support_dir(default_user_support_dir,
145                                      command_line_user_support_dir);
146
147         lyxerr[Debug::INIT]
148                 << "<package>\n"
149                 << "\tbinary_dir " << binary_dir() << '\n'
150                 << "\tsystem_support " << system_support() << '\n'
151                 << "\tbuild_support " << build_support() << '\n'
152                 << "\tuser_support " << user_support() << '\n'
153                 << "\tlocale_dir " << locale_dir() << '\n'
154                 << "\tdocument_dir " << document_dir() << '\n'
155                 << "\ttemp_dir " << temp_dir() << '\n'
156                 << "\thome_dir " << home_dir() << '\n'
157                 << "</package>\n" << std::endl;
158 }
159
160
161 namespace {
162
163 // These next three functions contain the stuff that is substituted at
164 // configuration-time.
165 string const top_srcdir()
166 {
167         static string const dir("%TOP_SRCDIR%");
168         return dir;
169 }
170
171
172 string const hardcoded_localedir()
173 {
174         return string("%LOCALEDIR%");
175 }
176
177
178 string const hardcoded_system_support_dir()
179 {
180         return string("%LYX_DIR%");
181 }
182
183 } // namespace anon
184
185
186 string const & Package::top_srcdir() const
187 {
188         static string const dir("%TOP_SRCDIR%");
189         return dir;
190 }
191
192
193 namespace {
194
195 bool check_command_line_dir(string const & dir,
196                             string const & file,
197                             string const & command_line_switch);
198
199 string const extract_env_var_dir(string const & env_var);
200
201 bool check_env_var_dir(string const & dir,
202                        string const & env_var);
203
204 bool check_env_var_dir(string const & dir,
205                        string const & file,
206                        string const & env_var);
207
208 string const relative_locale_dir();
209
210 string const relative_system_support_dir();
211
212
213 #if defined (USE_WINDOWS_PACKAGING)
214 // Given a folder ID, returns the folder name (in unix-style format).
215 // Eg CSIDL_PERSONAL -> "C:/Documents and Settings/USERNAME/My Documents"
216 string const win32_folder_path(int folder_id)
217 {
218         char folder_path[PATH_MAX + 1];
219         if (SUCCEEDED(SHGetFolderPath(0, folder_id, 0,
220                                       SHGFP_TYPE_CURRENT, folder_path)))
221                 return os::internal_path(folder_path);
222         return string();
223 }
224 #endif
225
226
227 std::pair<string, string> const get_build_dirs(string const & abs_binary)
228 {
229         string const check_text = "Checking whether LyX is run in place...";
230
231         // We're looking for "lyxrc.defaults" in a directory
232         //   binary_dir/../lib
233         // We're also looking for "chkconfig.ltx" in a directory
234         //   binary_dir/top_srcdir()/lib
235         // If both are found, then we're running LyX in-place.
236
237         // Note that the name of the lyx binary may be a symbolic link.
238         // If that is the case, then we follow the links too.
239         string binary = abs_binary;
240         while (true) {
241                 // Try and find "lyxrc.defaults".
242                 string const binary_dir = OnlyPath(binary);
243                 string const build_support_dir =
244                         NormalizePath(AddPath(binary_dir, "../lib"));
245
246                 if (!FileSearch(build_support_dir, "lyxrc.defaults").empty()) {
247                         // Try and find "chkconfig.ltx".
248                         string const system_support_dir =
249                                 MakeAbsPath(AddPath(top_srcdir(), "lib"),
250                                             AddPath(binary_dir, "support"));
251
252                         if (!FileSearch(system_support_dir, "chkconfig.ltx").empty()) {
253                                 lyxerr[Debug::INIT] << check_text << " yes"
254                                                     << std::endl;
255                                 return std::make_pair(build_support_dir, system_support_dir);
256                         }
257                 }
258
259                 // Check whether binary is a symbolic link.
260                 // If so, resolve it and repeat the exercise.
261                 if (!fs::symbolic_link_exists(binary))
262                         break;
263
264                 string link;
265                 if (LyXReadLink(binary, link, true)) {
266                         binary = link;
267                 } else {
268                         // Unable to resolve the link.
269                         break;
270                 }
271         }
272
273         lyxerr[Debug::INIT] << check_text << " no" << std::endl;
274         return std::make_pair(string(), string());
275 }
276
277
278 // Specification of document_dir_ may be reset by LyXRC,
279 // but the default is fixed for a given OS.
280 string const get_document_dir(string const & home_dir)
281 {
282 #if defined (USE_WINDOWS_PACKAGING)
283         (void)home_dir; // Silence warning about unused variable.
284         return win32_folder_path(CSIDL_PERSONAL);
285 #else // Posix-like.
286         return home_dir;
287 #endif
288 }
289
290
291 // The specification of home_dir_ is fixed for a given OS.
292 // A typical example on Windows: "C:/Documents and Settings/USERNAME"
293 // and on a Posix-like machine: "/home/USERNAME".
294 string const get_home_dir()
295 {
296 #if defined (USE_WINDOWS_PACKAGING)
297         string const home_dir = GetEnv("USERPROFILE");
298 #else // Posix-like.
299         string const home_dir = GetEnv("HOME");
300 #endif
301
302         return os::internal_path(home_dir);
303 }
304
305
306 // Several sources are probed to ascertain the locale directory.
307 // The only requirement is that the result is indeed a directory.
308 string const get_locale_dir(string const & system_support_dir)
309 {
310         // 1. Use the "LYX_LOCALEDIR" environment variable.
311         string path = extract_env_var_dir("LYX_LOCALEDIR");
312         if (!path.empty() && check_env_var_dir(path, "LYX_LOCALEDIR"))
313                 return path;
314
315         // 2. Search for system_support_dir / <relative locale dir>
316         // The <relative locale dir> is OS-dependent. (On Unix, it will
317         // be "../locale/".)
318         path = NormalizePath(AddPath(system_support_dir, relative_locale_dir()));
319
320         if (fs::exists(path) && fs::is_directory(path))
321                 return path;
322
323         // 3. Fall back to the hard-coded LOCALEDIR.
324         path = hardcoded_localedir();
325         if (fs::exists(path) && fs::is_directory(path))
326                 return path;
327
328         return string();
329 }
330
331
332 // Specification of temp_dir_ may be reset by LyXRC,
333 // but the default is fixed for a given OS.
334 string const get_temp_dir()
335 {
336 #if defined (USE_WINDOWS_PACKAGING)
337         // Typical example: C:/TEMP/.
338         char path[PATH_MAX + 1];
339         GetTempPath(PATH_MAX, path);
340         return os::internal_path(path);
341 #else // Posix-like.
342         return "/tmp";
343 #endif
344 }
345
346
347 // If we use a 'lazy' lyxerr in the hope of setting the locale before
348 // printing any messages, then we should ensure that it is flushed first.
349 void bail_out()
350 {
351 #ifndef CXX_GLOBAL_CSTD
352         using std::exit;
353 #endif
354         exit(1);
355 }
356
357
358 // Extracts the absolute path from the foo of "-sysdir foo" or "-userdir foo"
359 string const abs_path_from_command_line(string const & command_line)
360 {
361         if (command_line.empty())
362                 return string();
363
364         string const path = os::internal_path(command_line);
365         return os::is_absolute_path(path) ? path : MakeAbsPath(path);
366 }
367
368
369 // Does the grunt work for abs_path_from_binary_name()
370 string const get_binary_path(string const & exe)
371 {
372         string const exe_path = os::internal_path(exe);
373         if (os::is_absolute_path(exe_path))
374                 return exe_path;
375
376         // Two possibilities present themselves.
377         // 1. The binary is relative to the CWD.
378         string const abs_exe_path = MakeAbsPath(exe_path);
379         if (fs::exists(abs_exe_path))
380                 return abs_exe_path;
381
382         // 2. exe must be the name of the binary only and it
383         // can be found on the PATH.
384         string const exe_name = OnlyFilename(exe_path);
385         if (exe_name != exe_path)
386                 return string();
387
388         std::vector<string> const path = getEnvPath("PATH");
389         std::vector<string>::const_iterator it = path.begin();
390         std::vector<string>::const_iterator const end = path.end();
391         for (; it != end; ++it) {
392                 // This will do nothing if *it is already absolute.
393                 string const exe_dir = MakeAbsPath(*it);
394
395                 string const exe_path = AddName(exe_dir, exe_name);
396                 if (fs::exists(exe_path))
397                         return exe_path;
398         }
399
400         // Didn't find anything.
401         return string();
402 }
403
404
405 // Extracts the absolute path to the binary name received as argv[0].
406 string const abs_path_from_binary_name(string const & exe)
407 {
408         string const abs_binary = get_binary_path(exe);
409         if (abs_binary.empty()) {
410                 lyxerr << bformat(_("Unable to determine the path to the "
411                                     "LyX binary from the command line %1$s"),
412                                   exe)
413                        << std::endl;
414                 bail_out();
415         }
416         return abs_binary;
417 }
418
419
420 // A plethora of directories is searched to ascertain the system
421 // lyxdir which is defined as the first directory to contain
422 // "chkconfig.ltx".
423 string const
424 get_system_support_dir(string const & abs_binary,
425                   string const & command_line_system_support_dir)
426 {
427         string const chkconfig_ltx = "chkconfig.ltx";
428
429         // searched_dirs is used for diagnostic purposes only in the case
430         // that "chkconfig.ltx" is not found.
431         std::list<string> searched_dirs;
432
433         // 1. Use the -sysdir command line parameter.
434         string path = abs_path_from_command_line(command_line_system_support_dir);
435         if (!path.empty()) {
436                 searched_dirs.push_back(path);
437                 if (check_command_line_dir(path, chkconfig_ltx, "-sysdir"))
438                         return path;
439         }
440
441         // 2. Use the "LYX_DIR_13x" environment variable.
442         path = extract_env_var_dir("LYX_DIR_13x");
443         if (!path.empty()) {
444                 searched_dirs.push_back(path);
445                 if (check_env_var_dir(path, chkconfig_ltx, "LYX_DIR_13x"))
446                         return path;
447         }
448
449         // 3. Search relative to the lyx binary.
450         // We're looking for "chkconfig.ltx" in a directory
451         //   OnlyPath(abs_binary) / <relative dir> / PACKAGE /
452         // PACKAGE is hardcoded in config.h. Eg "lyx" or "lyx-1.3.6cvs".
453         // <relative dir> is OS-dependent; on Unix, it will be "../share/".
454         string const relative_lyxdir = relative_system_support_dir();
455
456         // One subtlety to be aware of. The name of the lyx binary may be
457         // a symbolic link. If that is the case, then we follow the links too.
458         string binary = abs_binary;
459         while (true) {
460                 // Try and find "chkconfig.ltx".
461                 string const binary_dir = OnlyPath(binary);
462
463                 string const lyxdir =
464                         NormalizePath(AddPath(binary_dir, relative_lyxdir));
465                 searched_dirs.push_back(lyxdir);
466
467                 if (!FileSearch(lyxdir, chkconfig_ltx).empty()) {
468                         // Success! "chkconfig.ltx" has been found.
469                         return lyxdir;
470                 }
471
472                 // Check whether binary is a symbolic link.
473                 // If so, resolve it and repeat the exercise.
474                 if (!fs::symbolic_link_exists(binary))
475                         break;
476
477                 string link;
478                 if (LyXReadLink(binary, link, true)) {
479                         binary = link;
480                 } else {
481                         // Unable to resolve the link.
482                         break;
483                 }
484         }
485
486         // 4. Repeat the exercise on the directory itself.
487         string binary_dir = OnlyPath(abs_binary);
488         while (true) {
489                 // This time test whether the directory is a symbolic link
490                 // *before* looking for "chkconfig.ltx".
491                 // (We've looked relative to the original already.)
492                 if (!fs::symbolic_link_exists(binary))
493                         break;
494
495                 string link;
496                 if (LyXReadLink(binary_dir, link, true)) {
497                         binary_dir = link;
498                 } else {
499                         // Unable to resolve the link.
500                         break;
501                 }
502
503                 // Try and find "chkconfig.ltx".
504                 string const lyxdir =
505                         NormalizePath(AddPath(binary_dir, relative_lyxdir));
506                 searched_dirs.push_back(lyxdir);
507
508                 if (!FileSearch(lyxdir, chkconfig_ltx).empty()) {
509                         // Success! "chkconfig.ltx" has been found.
510                         return lyxdir;
511                 }
512         }
513
514         // 5. In desparation, try the hard-coded system support dir.
515         path = hardcoded_system_support_dir();
516         if (!FileSearch(path, chkconfig_ltx).empty())
517                 return path;
518
519         // Everything has failed :-(
520         // So inform the user and exit.
521         string searched_dirs_str;
522         typedef std::list<string>::const_iterator iterator;
523         iterator const begin = searched_dirs.begin();
524         iterator const end = searched_dirs.end();
525         for (iterator it = begin; it != end; ++it) {
526                 if (it != begin)
527                         searched_dirs_str += "\n\t";
528                 searched_dirs_str += *it;
529         }
530
531         lyxerr << bformat(_("Unable to determine the system directory "
532                             "having searched\n"
533                             "\t%1$s\n"
534                             "Try the '-sysdir' command line parameter or "
535                             "set the environment variable LYX_DIR_13x to "
536                             "the LyX system directory containing the file "
537                             "`chkconfig.ltx'."),
538                           searched_dirs_str)
539                << std::endl;
540
541         bail_out();
542         // Keep the compiler happy.
543         return string();
544 }
545
546
547 // Returns the absolute path to the user lyxdir, together with a flag
548 // indicating whether this directory was specified explicitly (as -userdir
549 // or through an environment variable) or whether it was deduced.
550 std::pair<string, bool> const
551 get_user_support_dir(string const & default_user_support_dir,
552                      string const & command_line_user_support_dir)
553 {
554         bool explicit_userdir = true;
555
556         // 1. Use the -userdir command line parameter.
557         string path =
558                 abs_path_from_command_line(command_line_user_support_dir);
559         if (!path.empty())
560                 return std::make_pair(path, explicit_userdir);
561
562         // 2. Use the LYX_USERDIR_13x environment variable.
563         path = extract_env_var_dir("LYX_USERDIR_13x");
564         if (!path.empty())
565                 return std::make_pair(path, explicit_userdir);
566
567         // 3. Use the OS-dependent default_user_support_dir
568         explicit_userdir = false;
569         return std::make_pair(default_user_support_dir, explicit_userdir);
570 }
571
572
573 // $HOME/.lyx on all platforms but Win32 where it will be something like
574 // "C:/Documents and Settings/USERNAME/Application Data/lyx"
575 string const get_default_user_support_dir(string const & home_dir)
576 {
577 #if defined (USE_WINDOWS_PACKAGING)
578         (void)home_dir; // Silence warning about unused variable.
579
580         return AddPath(win32_folder_path(CSIDL_APPDATA), PACKAGE);
581
582 #elif defined (USE_MACOSX_PACKAGING)
583         (void)home_dir; // Silence warning about unused variable.
584
585         FSRef fsref;
586         OSErr const error_code =
587                 FSFindFolder(kUserDomain, kApplicationSupportFolderType,
588                              kDontCreateFolder, &fsref);
589         if (error_code != 0)
590                 return string();
591
592         char store[PATH_MAX + 1];
593         OSStatus const status_code =
594                 FSRefMakePath(&fsref,
595                               reinterpret_cast<UInt8*>(store), PATH_MAX);
596         if (status_code != 0)
597                 return string();
598
599         return AddPath(reinterpret_cast<char const *>(store), PACKAGE);
600
601 #else // USE_POSIX_PACKAGING
602         return AddPath(home_dir, string(".") + PACKAGE);
603 #endif
604 }
605
606
607 // Check that directory @c dir contains @c file.
608 // Else emit a warning about an invalid @c command_line_switch.
609 bool check_command_line_dir(string const & dir,
610                             string const & file,
611                             string const & command_line_switch)
612 {
613         string const abs_path = FileSearch(dir, file);
614         if (abs_path.empty()) {
615                 lyxerr << bformat(_("Invalid %1$s switch.\n"
616                                     "Directory %2$s does not contain %3$s."),
617                                   command_line_switch, dir, file)
618                        << std::endl;
619         }
620
621         return !abs_path.empty();
622 }
623
624
625 // The environment variable @c env_var expands to a (single) file path.
626 string const extract_env_var_dir(string const & env_var)
627 {
628         string const dir = os::internal_path(GetEnv(env_var));
629         return dir.empty() ? dir : MakeAbsPath(dir);
630 }
631
632
633 // Check that directory @c dir contains @c file.
634 // Else emit a warning about an invalid @c env_var.
635 bool check_env_var_dir(string const & dir,
636                        string const & file,
637                        string const & env_var)
638 {
639         string const abs_path = FileSearch(dir, file);
640         if (abs_path.empty()) {
641                 lyxerr << bformat(_("Invalid %1$s environment variable.\n"
642                                     "Directory %2$s does not contain %3$s."),
643                                   env_var, dir, file)
644                        << std::endl;
645         }
646
647         return !abs_path.empty();
648 }
649
650
651 // Check that directory @c dir is indeed a directory.
652 // Else emit a warning about an invalid @c env_var.
653 bool check_env_var_dir(string const & dir,
654                        string const & env_var)
655 {
656         bool const success = (fs::exists(dir) && fs::is_directory(dir));
657
658         if (!success) {
659                 // Put this string on a single line so that the gettext
660                 // search mechanism in po/Makefile.in.in will register
661                 // package.C.in as a file containing strings that need
662                 // translation.
663                 string const fmt =
664                 _("Invalid %1$s environment variable.\n%2$s is not a directory.");
665
666                 lyxerr << bformat(fmt, env_var, dir)
667                        << std::endl;
668         }
669
670         return success;
671 }
672
673
674 // The locale directory relative to the LyX system directory.
675 string const relative_locale_dir()
676 {
677         return "../locale/";
678 }
679
680
681 // The system lyxdir is relative to the directory containing the LyX binary.
682 string const relative_system_support_dir()
683 {
684         string result;
685
686 #if defined (USE_WINDOWS_PACKAGING) || defined (USE_MACOSX_PACKAGING)
687         result = AddPath("../Resources/", PACKAGE);
688 #else // Posix-like.
689         result = AddPath("../share/", PACKAGE);
690 #endif
691
692         return result;
693 }
694
695 } // namespace anon
696
697 } // namespace support
698 } // namespace lyx