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