first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / applications / bundler / unpacker_stub.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : unpacker stub program                                             *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2006-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "common_bundle.h"
16
17 #include <application/command_line.h>
18 #include <application/hoople_main.h>
19 #include <application/window_classist.h>
20 #include <basis/array.h>
21 #include <basis/byte_array.h>
22 #include <basis/environment.h>
23 #include <basis/guards.h>
24 #include <basis/utf_conversion.h>
25 #include <configuration/application_configuration.h>
26 #include <configuration/variable_tokenizer.h>
27 #include <filesystem/byte_filer.h>
28 #include <filesystem/directory.h>
29 #include <filesystem/filename.h>
30 #include <filesystem/file_time.h>
31 #include <filesystem/heavy_file_ops.h>
32 #include <loggers/console_logger.h>
33 #include <loggers/critical_events.h>
34 #include <loggers/file_logger.h>
35 #include <processes/launch_process.h>
36 #include <structures/static_memory_gremlin.h>
37 #include <structures/string_table.h>
38 #include <textual/parser_bits.h>
39
40 #include <stdio.h>
41 #include <sys/stat.h>
42 #include <zlib.h>
43 #ifdef __UNIX__
44   #include <utime.h>
45 #endif
46 #ifdef __WIN32__
47   #include <direct.h>
48   #include <io.h>
49   #include <shlobj.h>
50   #include <sys/utime.h>
51 #endif
52
53 using namespace application;
54 using namespace basis;
55 using namespace configuration;
56 using namespace filesystem;
57 using namespace loggers;
58 using namespace processes;
59 using namespace structures;
60 using namespace textual;
61
62 const int CHUNKING_SIZE = 64 * KILOBYTE;
63   // we'll read this big a chunk from a source file at a time.
64
65 const astring TARGET_WORD = "TARGET";
66 const astring LOGDIR_WORD = "LOGDIR";
67
68 #define BASE_LOG(to_print) program_wide_logger::get().log(to_print, ALWAYS_PRINT)
69 #define LOG(to_print) STAMPED_EMERGENCY_LOG(program_wide_logger::get(), to_print)
70
71 //#define DEBUG_STUB
72   // uncomment for noisier version.
73
74 const char *ERROR_TITLE = "An Error Caused Incomplete Installation";
75   // used in error messages as the title.
76
77 ////////////////////////////////////////////////////////////////////////////
78
79 class unpacker_stub : public application_shell
80 {
81 public:
82   unpacker_stub() : application_shell(), _app_name(filename(_global_argv[0]).basename()) {}
83   DEFINE_CLASS_NAME("unpacker_stub");
84
85   int print_instructions();
86
87   virtual int execute();
88
89 private:
90   astring _app_name;
91   array<manifest_chunk> _manifest;  //!< the list of chunks to unpack.
92   string_table _variables;  //!< our list of variable overrides.
93 };
94
95 ////////////////////////////////////////////////////////////////////////////
96
97 void show_message(const astring &msg, const astring &title)
98 {
99 #ifndef __WIN32__
100   BASE_LOG(title);
101   BASE_LOG(astring('-', title.length()));
102   BASE_LOG(msg);
103 #else
104   MessageBox(0, to_unicode_temp(msg), to_unicode_temp(title),
105       MB_OK | MB_ICONINFORMATION);
106 #endif
107 }
108
109 int unpacker_stub::print_instructions()
110 {
111   a_sprintf msg("\
112     %s: This program unpacks its contents into the locations\n\
113  specified at packing time.  The --target flag can be used to specify a\n\
114 different %s directory for the installation (for components that use\n\
115 the default %s variable to specify their install folder).\n\
116     One can also pass a --keyword flag to specify a keyword; the files in the\n\
117 bundle that are marked with that keyword will be installed, but files that\n\
118 are missing the keyword will not be.\n\
119     Further, variables can be overridden on the command line in the\n\
120 form: X=Y.\n\n\
121 The line below uses all these parameters as an example:\n\n\
122   %s --target c:\\Program Files\\gubernator --keyword dlls_only SILENT=true\n\
123 \n\
124 Additional Notes:\n\
125 \n\
126     One helpful variable is \"%s\".  This is where the unpacking log file\n\
127 will be written to.  The default is \"~/logs\" (or \"$TMP/logs\" on win32)\n\
128 until overridden.\n\
129 \n", _app_name.s(), TARGET_WORD.s(), TARGET_WORD.s(), _app_name.s(), LOGDIR_WORD.s());
130   show_message(msg, "Unpacking Instructions");
131   return 12;
132 }
133
134 // creates a unique backup file name, if it can.
135 // we assume that this file already exists, but we want to check for
136 // our backup file naming scheme in case we already backed this up
137 // some previous time.
138 astring find_unique_backup_name(astring original_file)
139 {
140 const int MAXIMUM_BACKUPS = 200;
141
142   for (int i = 0; i < MAXIMUM_BACKUPS; i++) {
143     filename target_file = original_file + a_sprintf(".%04d", i);
144     if (target_file.exists()) {
145 //BASE_LOG(astring("bkup already here: ") + target_file);
146       continue;
147     }
148     // this file is okay to use.
149     return target_file;
150   }
151   return "";  // nothing found.
152 }
153
154  
155 // the string embedded into the array is not mentioned anywhere else in this
156 // program, which should allow the packer to find it and fix the manifest
157 // size.  the keyword is: "muftiloc", and the first bytes that can be
158 // overwritten are its beginning offset plus its length of 8 chars.  there
159 // is room for 8 bytes after the tag, but currently the first 4 are used as
160 // a 32 bit offset.
161 abyte MANIFEST_OFFSET_ARRAY[]
162     = { 'm', 'u', 'f', 't', 'i', 'l', 'o', 'c', 0, 0, 0, 0, 0, 0, 0, 0 };
163
164 int unpacker_stub::execute()
165 {
166 #ifdef ADMIN_CHECK
167   #ifdef __WIN32__
168     if (IsUserAnAdmin()) {
169       ::MessageBox(0, to_unicode_temp("IS admin in bundler"), to_unicode_temp("bundler"), MB_OK);
170     } else {
171       ::MessageBox(0, to_unicode_temp("NOT admin in bundler"), to_unicode_temp("bundler"), MB_OK);
172     }
173   #endif
174 #endif
175
176   command_line cmds(_global_argc, _global_argv);
177
178   int indy = 0;
179   if (cmds.find('?', indy)) return print_instructions();
180   if (cmds.find("?", indy)) return print_instructions();
181
182   // make sure we provide the same services as the bundle creator for the
183   // default set of variables.
184 #ifndef __WIN32__
185   environment::set("EXE_END", "");  // executable file ending.
186   environment::set("DLL_START", "lib");  // dll file prefix.
187   environment::set("DLL_END", ".so");  // dll file ending.
188 #else
189   environment::set("EXE_END", ".exe");
190   environment::set("DLL_START", "");
191   environment::set("DLL_END", ".dll");
192 #endif
193
194   // set TARGET directory if passed on the command line,
195   bool provided_target = false;  // true if the command line specified target.
196   astring target;
197   cmds.get_value("target", target);
198   if (!target) {
199 /* no, this is wrong headed.  make them supply a target if there is
200  * no default target provided in the bundle.
201     // a bogus default is provided if they don't specify the destination.
202     target = environment::get("TMP") + "/unbundled";
203 */
204     provided_target = false;
205   } else {
206 //LOG(astring("target is now ") + target);
207     environment::set(TARGET_WORD, target);
208     provided_target = true;
209   }
210
211   {
212     astring logdir = environment::get(LOGDIR_WORD);
213 #ifdef __WIN32__
214     if (!logdir) {
215       logdir = environment::get("TMP") + "/logs";
216       environment::set(LOGDIR_WORD, logdir);
217     }
218 #else
219     if (!logdir) {
220       astring homedir = environment::get("HOME");
221       logdir = homedir + "/logs";
222       environment::set(LOGDIR_WORD, logdir);
223     }
224 #endif
225   }
226
227   astring keyword;  // set if we were given a keyword on cmd line.
228   cmds.get_value("keyword", keyword);
229
230   astring vars_set;  // we will document the variables we saw and show later.
231
232   for (int x = 0; x < cmds.entries(); x++) {
233     command_parameter curr = cmds.get(x);
234     if (curr.type() != command_parameter::VALUE) continue;  // skip it.
235     if (curr.text().find('=', 0) < 0) continue;  // no equals character.
236     variable_tokenizer t;
237     t.parse(curr.text());
238     if (!t.symbols()) continue;  // didn't parse right.
239     astring var = t.table().name(0);
240     astring value = t.table()[0];
241     vars_set += astring("variable set: ") + var + "=" + value
242         + parser_bits::platform_eol_to_chars();
243     if (var == TARGET_WORD) {
244       provided_target = true;
245     }
246 //hmmm: handle LOGDIR passed as variable this way also!
247     _variables.add(var, value);
248     environment::set(var, value);
249   }
250
251   // get the most up to date version of the variable now.
252   astring logdir = environment::get(LOGDIR_WORD);
253
254   astring appname = filename(application_configuration::application_name()).rootname();
255
256   astring logname = logdir + "/" + appname + ".log";
257 //  log_base *old_log = set_PW_logger_for_combo(logname);
258   standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname));
259   WHACK(old_log);
260
261   BASE_LOG(astring('#', 76));
262   BASE_LOG(appname + " command-line parameters:");
263   BASE_LOG(cmds.text_form());
264   BASE_LOG(astring('#', 76));
265
266   BASE_LOG(vars_set);
267
268 #ifdef __WIN32__
269   // create a window so that installshield won't barf.  this is only needed
270   // on windows when using this as a prerequisite for installshield.
271   window_handle f_window = create_simplistic_window("temp_stubby_class",
272       "stubby window title");
273 #endif
274
275   // read position for manifest offset out of our array.
276   byte_array temp_packed(2 * sizeof(int), MANIFEST_OFFSET_ARRAY + 8);
277   un_int manifest_offset;
278   if (!structures::obscure_detach(temp_packed, manifest_offset)) {
279     show_message(astring("could not read manifest offset in: ") + _global_argv[0],
280         ERROR_TITLE);
281     return 24;
282   }
283   
284   filename this_exe(_global_argv[0]);
285   if (!this_exe.exists()) {
286     show_message(astring("could not access this exe image: ") + this_exe.raw(),
287         ERROR_TITLE);
288     return 23;
289   }
290
291   // start reading the manifest...
292   byte_filer our_exe(this_exe, "rb");
293   our_exe.seek(manifest_offset);  // go to where the manifest starts.
294
295   // get number of chunks in manifest as the first bytes.
296   if (our_exe.read(temp_packed, 2 * sizeof(int)) <= 0) {
297     show_message(astring("could not read the manifest length in: ")
298         + this_exe.raw(), ERROR_TITLE);
299     return 26;
300   }
301   un_int item_count;
302   structures::obscure_detach(temp_packed, item_count);
303 //check result of detach!
304   _manifest.insert(0, item_count);  // add enough spaces for our item list.
305
306   // read each item definition out of the manifest now.
307   for (int i = 0; i < (int)item_count; i++) {
308     manifest_chunk &curr = _manifest[i];
309     bool worked = manifest_chunk::read_manifest(our_exe, curr);
310
311 #ifdef DEBUG_STUB
312     astring tmpork;
313     curr.text_form(tmpork);
314     LOG(a_sprintf("item %d: ", i) + tmpork);
315 #endif
316
317     if (!worked) {
318       show_message(a_sprintf("could not read chunk for item #%d [%s]: ", i,
319           curr._payload.s())
320           + this_exe.raw(), ERROR_TITLE);
321       return 86;
322     }
323   }
324
325 #ifdef DEBUG_STUB
326   LOG("read the following info from manifest:");
327   astring temp;
328   for (int i = 0; i < _manifest.length(); i++) {
329     manifest_chunk &curr = _manifest[i];
330     temp += a_sprintf("(%d) size %d, %s\n", i, curr._size,
331           curr._payload.s());
332   }
333   critical_events::alert_message(temp, "manifest contents");
334 #endif
335
336   // now we should be just after the last byte of the manifest, at the
337   // first piece of data.  we should read each chunk of data out and store
338   // it where it's supposed to go.
339   for (int festdex = 0; festdex < _manifest.length(); festdex++) {
340     manifest_chunk &curr = _manifest[festdex];
341     int size_left = curr._size;
342
343     // patch in our environment variables.
344     curr._payload = parser_bits::substitute_env_vars(curr._payload, false);
345     curr._parms = parser_bits::substitute_env_vars(curr._parms, false);
346 #ifdef DEBUG_STUB
347     BASE_LOG(astring("processing ") + curr._payload
348         + a_sprintf(", size=%d, flags=%d", curr._size, curr._flags));
349     if (!!curr._parms)
350       BASE_LOG(astring("   parms: ") + curr._parms);
351 #endif
352
353     // see if they specified a keyword on the command line.
354     bool keyword_good = true;
355     if (!!keyword && !curr._keywords.member(keyword)) {
356       // their keyword choice didn't match what we were pickled with.
357       keyword_good = false;
358 //BASE_LOG(astring("skipping ") + curr._payload + " for wrong keyword " + keyword);
359     }
360
361     if (curr._flags & SET_VARIABLE) {
362       if (keyword_good) {
363         // this is utterly different from a real target.  we just set the
364         // variable and move on.
365         if (provided_target && (curr._payload == TARGET_WORD) ) {
366           BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms
367               + ": was provided explicitly as " + target);
368         } else if (_variables.find(curr._payload)) {
369           BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms
370               + ": was provided on command line.");
371         } else {
372           BASE_LOG(astring("setting ") + curr._payload + "=" + curr._parms);
373           environment::set(curr._payload, curr._parms);
374
375           // special code for changing logging directory midstream.
376           if (curr._payload == LOGDIR_WORD) {
377             astring logdir = curr._parms;
378             astring appname = filename(application_configuration::application_name()).rootname();
379             astring logname = logdir + "/" + appname + ".log";
380             standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname));
381 ///            log_base *old_log = set_PW_logger_for_combo(logname);
382             WHACK(old_log);
383           }
384           if (curr._payload == TARGET_WORD)  {
385             // well we've now seen this defined.
386             provided_target = true;
387           }
388         }
389       }
390       continue;
391     } else if (curr._flags & TEST_VARIABLE_DEFINED) {
392       if (keyword_good) {
393         astring var_value = environment::get(curr._payload);
394         if (var_value.empty()) {
395           BASE_LOG(astring("assertion failed: ") + curr._payload + " is not defined!");
396           show_message(a_sprintf("failed test for variable %s: it is "
397               "*not* defined, for item #%d.", curr._payload.s(), festdex),
398               ERROR_TITLE);
399           return 98;
400         }
401         BASE_LOG(astring("assertion succeeded: ") + curr._payload + " defined as " + var_value);
402       }
403       continue;
404     }
405
406     if (! (curr._flags & OMIT_PACKING) ) {
407       // this one has a payload, so install it now if appropriate.
408       if (!provided_target) {
409 //error!
410         BASE_LOG(astring("No TARGET has been specified; please provide one on the command line.") + parser_bits::platform_eol_to_chars());
411         return print_instructions();
412       }
413
414       // make sure that the directories needed are present for the outputs.
415       filename target_dir = filename(curr._payload).dirname();
416
417       if (keyword_good && !target_dir.exists()
418           && !directory::recursive_create(target_dir)) {
419         LOG(a_sprintf("failed to create directory %s for item #%d: ",
420             target_dir.raw().s(), festdex) + curr._payload);
421       }
422
423       // test whether they wanted to allow overwriting.
424       if (curr._flags & NO_OVERWRITE) {
425         filename target_file(curr._payload);
426         if (target_file.exists()) {
427           BASE_LOG(astring("not overwriting existing ") + curr._payload);
428           keyword_good = false;
429         }
430       }
431
432       // see if this is supposed to be backed up before installation.
433       if (curr._flags & MAKE_BACKUP_FILE) {
434         filename target_file(curr._payload);
435         if (target_file.exists()) {
436           astring new_file_name = find_unique_backup_name(curr._payload);
437           if (!new_file_name) {
438             BASE_LOG(astring("failed to calculate new filename for ") + curr._payload);
439             keyword_good = false;  // cancel the overwrite, couldn't backup.
440           } else {
441             // make a backup of the file by moving the old file name to the
442             // new file name, which should be unique, and then the current
443             // name is all clear to be written as a new file.
444 //            BASE_LOG(astring("backing up ") + curr._payload + " --> " + new_file_name);
445             int retval = rename(curr._payload.s(), new_file_name.s());
446             if (retval) {
447               BASE_LOG(astring("failed to rename ") + curr._payload + " as " + new_file_name);
448               keyword_good = false;  // cancel the overwrite, couldn't backup.
449             }
450           }
451         }
452       }
453
454       byte_filer *targo = NIL;
455       if (keyword_good) targo = new byte_filer(curr._payload, "wb");
456       byte_array uncompressed(256 * KILOBYTE);  // fluff it out to begin with.
457       byte_array temp(256 * KILOBYTE);
458
459       bool first_read = true;
460         // true if there haven't been any reads of the file before now.
461       bool too_tiny_complaint_already = false;
462         // becomes true if we complain about the file's size being larger than
463         // expected.  this allows us to only complain once about each file.
464
465       // read a chunk at a time out of our exe image and store it into the
466       // target file.
467       while (first_read || !our_exe.eof()) {
468         first_read = false;
469         un_int real_size = 0, packed_size = 0;
470         // read in the real size from the file.
471         bool worked = manifest_chunk::read_an_obscured_int(our_exe, real_size);
472         if (!worked) {
473           show_message(a_sprintf("failed while reading real size "
474               "for item #%d: ", festdex) + curr._payload, ERROR_TITLE);
475           return 99;
476         }
477         // read in the packed size now.
478         worked = manifest_chunk::read_an_obscured_int(our_exe, packed_size);
479         if (!worked) {
480           show_message(a_sprintf("failed while reading packed size "
481               "for item #%d: ", festdex) + curr._payload, ERROR_TITLE);
482           return 99;
483         }
484
485         // make sure we don't eat the whole package--did the file end?
486         if ( (real_size == -1) && (packed_size == -1) ) {
487           // we've hit our sentinel; we've already unpacked all of this file.
488           break;
489         }
490
491 #ifdef DEBUG_STUB
492         BASE_LOG(a_sprintf("chunk packed_size=%d, real_size=%d", packed_size,
493             real_size));
494 #endif
495
496         // now we know how big our next chunk is, so we can try reading it.
497         if (packed_size) {
498           int ret = our_exe.read(temp, packed_size);
499           if (ret <= 0) {
500             show_message(a_sprintf("failed while reading item #%d: ", festdex)
501                 + curr._payload, ERROR_TITLE);
502             return 99;
503           } else if (ret != packed_size) {
504             show_message(a_sprintf("bad trouble ahead, item #%d had different "
505                 " size on read (expected %d, got %d): ", festdex, packed_size,
506                 ret) + curr._payload, ERROR_TITLE);
507           }
508
509           uncompressed.reset(real_size + KILOBYTE);  // add some for paranoia.
510           uLongf destlen = uncompressed.length();
511           int uncomp_ret = uncompress(uncompressed.access(), &destlen,
512               temp.observe(), packed_size);
513           if (uncomp_ret != Z_OK) {
514             show_message(a_sprintf("failed while uncompressing item #%d: ",
515                 festdex) + curr._payload, ERROR_TITLE);
516             return 99;
517           }
518   
519           if (int(destlen) != real_size) {
520             LOG(a_sprintf("got a different unpacked size for item #%d: ",
521                 festdex) + curr._payload);
522           }
523
524           // update the remaining size for this data chunk.
525           size_left -= real_size;
526           if (size_left < 0) {
527             if (!too_tiny_complaint_already) {
528               LOG(a_sprintf("item #%d was larger than expected (non-fatal): ",
529                   festdex) + curr._payload);
530               too_tiny_complaint_already = true;
531             }
532           }
533           // toss the extra bytes out.
534           uncompressed.zap(real_size, uncompressed.length() - 1);
535
536           if (targo) {
537             // stuff the data we read into the target file.
538             ret = targo->write(uncompressed);
539             if (ret != uncompressed.length()) {
540               show_message(a_sprintf("failed while writing item #%d: ", festdex)
541                   + curr._payload, ERROR_TITLE);
542               return 93;
543             }
544           }
545         }
546       }
547       if (targo) targo->close();
548       WHACK(targo);
549       // the file's written, but now we slap it's old time on it too.
550       file_time t;
551       if (!t.unpack(curr.c_filetime)) {
552         show_message(astring("failed to interpret timestamp for ")
553             + curr._payload, ERROR_TITLE);
554         return 97;
555       }
556       // put the timestamp on the file.
557       t.set_time(curr._payload);
558 //      utimbuf held_time;
559 //      held_time.actime = t.raw();
560 //      held_time.modtime = t.raw();
561 //      // put the timestamp on the file.
562 //      utime(curr._payload.s(), &held_time);
563     }
564
565     // now that we're pretty sure the file exists, we can run it if needed.
566     if ( (curr._flags & TARGET_EXECUTE) && keyword_good) {    
567       // change the mode on the target file so we can execute it.
568       chmod(curr._payload.s(), 0766);
569       astring prev_dir = application_configuration::current_directory();
570
571       BASE_LOG(astring("launching ") + curr._payload);
572       if (!!curr._parms)
573         BASE_LOG(astring("  with parameters: ") + curr._parms);
574       BASE_LOG(astring('-', 76));
575
576       basis::un_int kid;
577       basis::un_int retval = launch_process::run(curr._payload, curr._parms,
578           launch_process::AWAIT_APP_EXIT | launch_process::HIDE_APP_WINDOW, kid);
579       if (retval != 0) {
580         if (! (curr._flags & IGNORE_ERRORS) ) {
581           if (curr._flags & QUIET_FAILURE) {
582             // no message box for this, but still log it.
583             LOG(astring("failed to launch process, targ=")
584                 + curr._payload + " with parms " + curr._parms
585                 + a_sprintf(" error=%d", retval));
586           } else {
587             show_message(astring("failed to launch process, targ=")
588                 + curr._payload + " with parms " + curr._parms
589                 + a_sprintf(" error=%d", retval), ERROR_TITLE);
590           }
591           return retval;  // pass along same exit value we were told.
592         } else {
593           LOG(astring("ignoring failure to launch process, targ=")
594               + curr._payload + " with parms " + curr._parms
595               + a_sprintf(" error=%d", retval));
596         }
597       }
598
599       chdir(prev_dir.s());  // reset directory pointer, just in case.
600
601       BASE_LOG(astring('-', 76));
602     }
603
604   }
605
606 #ifdef __WIN32__
607   whack_simplistic_window(f_window);
608 #endif
609
610   return 0;
611 }
612
613 ////////////////////////////////////////////////////////////////////////////
614
615 HOOPLE_MAIN(unpacker_stub, )
616
617 #ifdef __BUILD_STATIC_APPLICATION__
618   // static dependencies found by buildor_gen_deps.sh:
619   #include <application/application_shell.cpp>
620   #include <application/command_line.cpp>
621   #include <basis/astring.cpp>
622   #include <basis/common_outcomes.cpp>
623   #include <basis/environment.cpp>
624   #include <basis/mutex.cpp>
625   #include <basis/utf_conversion.cpp>
626   #include <configuration/application_configuration.cpp>
627   #include <configuration/configurator.cpp>
628   #include <configuration/ini_configurator.cpp>
629   #include <configuration/ini_parser.cpp>
630   #include <configuration/table_configurator.cpp>
631   #include <configuration/variable_tokenizer.cpp>
632   #include <filesystem/byte_filer.cpp>
633   #include <filesystem/directory.cpp>
634   #include <filesystem/file_info.cpp>
635   #include <filesystem/filename.cpp>
636   #include <filesystem/filename_list.cpp>
637   #include <filesystem/file_time.cpp>
638   #include <filesystem/heavy_file_ops.cpp>
639   #include <filesystem/huge_file.cpp>
640   #include <loggers/combo_logger.cpp>
641   #include <loggers/console_logger.cpp>
642   #include <loggers/critical_events.cpp>
643   #include <loggers/file_logger.cpp>
644   #include <loggers/program_wide_logger.cpp>
645   #include <processes/launch_process.cpp>
646   #include <structures/bit_vector.cpp>
647   #include <structures/checksums.cpp>
648   #include <structures/object_packers.cpp>
649   #include <structures/static_memory_gremlin.cpp>
650   #include <structures/string_hasher.cpp>
651   #include <structures/string_table.cpp>
652   #include <structures/version_record.cpp>
653   #include <textual/byte_formatter.cpp>
654   #include <textual/parser_bits.cpp>
655   #include <textual/string_manipulation.cpp>
656   #include <timely/earth_time.cpp>
657   #include <timely/time_control.cpp>
658   #include <timely/time_stamp.cpp>
659 #endif // __BUILD_STATIC_APPLICATION__
660