87c6a7664d0ad7d823c8a295f18aed9c246722e2
[feisty_meow.git] / nucleus / 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 _MSC_VER
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     provided_target = false;
200   } else {
201 //LOG(astring("target is now ") + target);
202     environment::set(TARGET_WORD, target);
203     provided_target = true;
204   }
205
206   {
207     astring logdir = environment::get(LOGDIR_WORD);
208 #ifdef __WIN32__
209     if (!logdir) {
210       logdir = environment::TMP() + "/logs";
211       environment::set(LOGDIR_WORD, logdir);
212     }
213 #else
214     if (!logdir) {
215       astring homedir = environment::get("HOME");
216       logdir = homedir + "/logs";
217       environment::set(LOGDIR_WORD, logdir);
218     }
219 #endif
220   }
221
222   astring keyword;  // set if we were given a keyword on cmd line.
223   cmds.get_value("keyword", keyword);
224
225   astring vars_set;  // we will document the variables we saw and show later.
226
227   for (int x = 0; x < cmds.entries(); x++) {
228     command_parameter curr = cmds.get(x);
229     if (curr.type() != command_parameter::VALUE) continue;  // skip it.
230     if (curr.text().find('=', 0) < 0) continue;  // no equals character.
231     variable_tokenizer t;
232     t.parse(curr.text());
233     if (!t.symbols()) continue;  // didn't parse right.
234     astring var = t.table().name(0);
235     astring value = t.table()[0];
236     vars_set += astring("variable set: ") + var + "=" + value
237         + parser_bits::platform_eol_to_chars();
238     if (var == TARGET_WORD) {
239       provided_target = true;
240     }
241 //hmmm: handle LOGDIR passed as variable this way also!
242     _variables.add(var, value);
243     environment::set(var, value);
244   }
245
246   // get the most up to date version of the variable now.
247   astring logdir = environment::get(LOGDIR_WORD);
248
249   astring appname = filename(application_configuration::application_name()).rootname();
250
251   astring logname = logdir + "/" + appname + ".log";
252 //  log_base *old_log = set_PW_logger_for_combo(logname);
253   standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname));
254   WHACK(old_log);
255
256   BASE_LOG(astring('#', 76));
257   BASE_LOG(appname + " command-line parameters:");
258   BASE_LOG(cmds.text_form());
259   BASE_LOG(astring('#', 76));
260
261   BASE_LOG(vars_set);
262
263 #ifdef __WIN32__
264   // create a window so that installshield won't barf.  this is only needed
265   // on windows when using this as a prerequisite for installshield.
266   window_handle f_window = create_simplistic_window("temp_stubby_class",
267       "stubby window title");
268 #endif
269
270   // read position for manifest offset out of our array.
271   byte_array temp_packed(2 * sizeof(int), MANIFEST_OFFSET_ARRAY + 8);
272   un_int manifest_offset;
273   if (!structures::obscure_detach(temp_packed, manifest_offset)) {
274     show_message(astring("could not read manifest offset in: ") + _global_argv[0],
275         ERROR_TITLE);
276     return 24;
277   }
278   
279   filename this_exe(_global_argv[0]);
280   if (!this_exe.exists()) {
281     show_message(astring("could not access this exe image: ") + this_exe.raw(),
282         ERROR_TITLE);
283     return 23;
284   }
285
286   // start reading the manifest...
287   byte_filer our_exe(this_exe, "rb");
288   our_exe.seek(manifest_offset);  // go to where the manifest starts.
289
290   // get number of chunks in manifest as the first bytes.
291   if (our_exe.read(temp_packed, 2 * sizeof(int)) <= 0) {
292     show_message(astring("could not read the manifest length in: ")
293         + this_exe.raw(), ERROR_TITLE);
294     return 26;
295   }
296   un_int item_count;
297   structures::obscure_detach(temp_packed, item_count);
298 //check result of detach!
299   _manifest.insert(0, item_count);  // add enough spaces for our item list.
300
301   // read each item definition out of the manifest now.
302   for (int i = 0; i < (int)item_count; i++) {
303     manifest_chunk &curr = _manifest[i];
304     bool worked = manifest_chunk::read_manifest(our_exe, curr);
305
306 #ifdef DEBUG_STUB
307     astring tmpork;
308     curr.text_form(tmpork);
309     LOG(a_sprintf("item %d: ", i) + tmpork);
310 #endif
311
312     if (!worked) {
313       show_message(a_sprintf("could not read chunk for item #%d [%s]: ", i,
314           curr._payload.s())
315           + this_exe.raw(), ERROR_TITLE);
316       return 86;
317     }
318   }
319
320 #ifdef DEBUG_STUB
321   LOG("read the following info from manifest:");
322   astring temp;
323   for (int i = 0; i < _manifest.length(); i++) {
324     manifest_chunk &curr = _manifest[i];
325     temp += a_sprintf("(%d) size %d, %s\n", i, curr._size,
326           curr._payload.s());
327   }
328   critical_events::alert_message(temp, "manifest contents");
329 #endif
330
331   // now we should be just after the last byte of the manifest, at the
332   // first piece of data.  we should read each chunk of data out and store
333   // it where it's supposed to go.
334   for (int festdex = 0; festdex < _manifest.length(); festdex++) {
335     manifest_chunk &curr = _manifest[festdex];
336     int size_left = curr._size;
337
338     // patch in our environment variables.
339     curr._payload = parser_bits::substitute_env_vars(curr._payload, false);
340     curr._parms = parser_bits::substitute_env_vars(curr._parms, false);
341 #ifdef DEBUG_STUB
342     BASE_LOG(astring("processing ") + curr._payload
343         + a_sprintf(", size=%d, flags=%d", curr._size, curr._flags));
344     if (!!curr._parms)
345       BASE_LOG(astring("   parms: ") + curr._parms);
346 #endif
347
348     // see if they specified a keyword on the command line.
349     bool keyword_good = true;
350     if (!!keyword && !curr._keywords.member(keyword)) {
351       // their keyword choice didn't match what we were pickled with.
352       keyword_good = false;
353 //BASE_LOG(astring("skipping ") + curr._payload + " for wrong keyword " + keyword);
354     }
355
356     if (curr._flags & SET_VARIABLE) {
357       if (keyword_good) {
358         // this is utterly different from a real target.  we just set the
359         // variable and move on.
360         if (provided_target && (curr._payload == TARGET_WORD) ) {
361           BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms
362               + ": was provided explicitly as " + target);
363         } else if (_variables.find(curr._payload)) {
364           BASE_LOG(astring("skipping ") + curr._payload + "=" + curr._parms
365               + ": was provided on command line.");
366         } else {
367           BASE_LOG(astring("setting ") + curr._payload + "=" + curr._parms);
368           environment::set(curr._payload, curr._parms);
369
370           // special code for changing logging directory midstream.
371           if (curr._payload == LOGDIR_WORD) {
372             astring logdir = curr._parms;
373             astring appname = filename(application_configuration::application_name()).rootname();
374             astring logname = logdir + "/" + appname + ".log";
375             standard_log_base *old_log = program_wide_logger::set(new combo_logger(logname));
376 ///            log_base *old_log = set_PW_logger_for_combo(logname);
377             WHACK(old_log);
378           }
379           if (curr._payload == TARGET_WORD)  {
380             // well we've now seen this defined.
381             provided_target = true;
382           }
383         }
384       }
385       continue;
386     } else if (curr._flags & TEST_VARIABLE_DEFINED) {
387       if (keyword_good) {
388         astring var_value = environment::get(curr._payload);
389         if (var_value.empty()) {
390           BASE_LOG(astring("assertion failed: ") + curr._payload + " is not defined!");
391           show_message(a_sprintf("failed test for variable %s: it is "
392               "*not* defined, for item #%d.", curr._payload.s(), festdex),
393               ERROR_TITLE);
394           return 98;
395         }
396         BASE_LOG(astring("assertion succeeded: ") + curr._payload + " defined as " + var_value);
397       }
398       continue;
399     }
400
401     if (! (curr._flags & OMIT_PACKING) ) {
402       // this one has a payload, so install it now if appropriate.
403       if (!provided_target) {
404 //error!
405         BASE_LOG(astring("No TARGET has been specified; please provide one on the command line.") + parser_bits::platform_eol_to_chars());
406         return print_instructions();
407       }
408
409       // make sure that the directories needed are present for the outputs.
410       filename target_dir = filename(curr._payload).dirname();
411
412       if (keyword_good && !target_dir.exists()
413           && !directory::recursive_create(target_dir)) {
414         LOG(a_sprintf("failed to create directory %s for item #%d: ",
415             target_dir.raw().s(), festdex) + curr._payload);
416       }
417
418       // test whether they wanted to allow overwriting.
419       if (curr._flags & NO_OVERWRITE) {
420         filename target_file(curr._payload);
421         if (target_file.exists()) {
422           BASE_LOG(astring("not overwriting existing ") + curr._payload);
423           keyword_good = false;
424         }
425       }
426
427       // see if this is supposed to be backed up before installation.
428       if (curr._flags & MAKE_BACKUP_FILE) {
429         filename target_file(curr._payload);
430         if (target_file.exists()) {
431           astring new_file_name = find_unique_backup_name(curr._payload);
432           if (!new_file_name) {
433             BASE_LOG(astring("failed to calculate new filename for ") + curr._payload);
434             keyword_good = false;  // cancel the overwrite, couldn't backup.
435           } else {
436             // make a backup of the file by moving the old file name to the
437             // new file name, which should be unique, and then the current
438             // name is all clear to be written as a new file.
439 //            BASE_LOG(astring("backing up ") + curr._payload + " --> " + new_file_name);
440             int retval = rename(curr._payload.s(), new_file_name.s());
441             if (retval) {
442               BASE_LOG(astring("failed to rename ") + curr._payload + " as " + new_file_name);
443               keyword_good = false;  // cancel the overwrite, couldn't backup.
444             }
445           }
446         }
447       }
448
449       byte_filer *targo = NULL_POINTER;
450       if (keyword_good) targo = new byte_filer(curr._payload, "wb");
451       byte_array uncompressed(256 * KILOBYTE);  // fluff it out to begin with.
452       byte_array temp(256 * KILOBYTE);
453
454       bool first_read = true;
455         // true if there haven't been any reads of the file before now.
456       bool too_tiny_complaint_already = false;
457         // becomes true if we complain about the file's size being larger than
458         // expected.  this allows us to only complain once about each file.
459
460       // read a chunk at a time out of our exe image and store it into the
461       // target file.
462       while (first_read || !our_exe.eof()) {
463         first_read = false;
464         un_int real_size = 0, packed_size = 0;
465         // read in the real size from the file.
466         bool worked = manifest_chunk::read_an_obscured_int(our_exe, real_size);
467         if (!worked) {
468           show_message(a_sprintf("failed while reading real size "
469               "for item #%d: ", festdex) + curr._payload, ERROR_TITLE);
470           return 99;
471         }
472         // read in the packed size now.
473         worked = manifest_chunk::read_an_obscured_int(our_exe, packed_size);
474         if (!worked) {
475           show_message(a_sprintf("failed while reading packed size "
476               "for item #%d: ", festdex) + curr._payload, ERROR_TITLE);
477           return 99;
478         }
479
480         // make sure we don't eat the whole package--did the file end?
481         if ( (real_size == -1) && (packed_size == -1) ) {
482           // we've hit our sentinel; we've already unpacked all of this file.
483           break;
484         }
485
486 #ifdef DEBUG_STUB
487         BASE_LOG(a_sprintf("chunk packed_size=%d, real_size=%d", packed_size,
488             real_size));
489 #endif
490
491         // now we know how big our next chunk is, so we can try reading it.
492         if (packed_size) {
493           int ret = our_exe.read(temp, packed_size);
494           if (ret <= 0) {
495             show_message(a_sprintf("failed while reading item #%d: ", festdex)
496                 + curr._payload, ERROR_TITLE);
497             return 99;
498           } else if (ret != packed_size) {
499             show_message(a_sprintf("bad trouble ahead, item #%d had different "
500                 " size on read (expected %d, got %d): ", festdex, packed_size,
501                 ret) + curr._payload, ERROR_TITLE);
502           }
503
504           uncompressed.reset(real_size + KILOBYTE);  // add some for paranoia.
505           uLongf destlen = uncompressed.length();
506           int uncomp_ret = uncompress(uncompressed.access(), &destlen,
507               temp.observe(), packed_size);
508           if (uncomp_ret != Z_OK) {
509             show_message(a_sprintf("failed while uncompressing item #%d: ",
510                 festdex) + curr._payload, ERROR_TITLE);
511             return 99;
512           }
513   
514           if (int(destlen) != real_size) {
515             LOG(a_sprintf("got a different unpacked size for item #%d: ",
516                 festdex) + curr._payload);
517           }
518
519           // update the remaining size for this data chunk.
520           size_left -= real_size;
521           if (size_left < 0) {
522             if (!too_tiny_complaint_already) {
523               LOG(a_sprintf("item #%d was larger than expected (non-fatal): ",
524                   festdex) + curr._payload);
525               too_tiny_complaint_already = true;
526             }
527           }
528           // toss the extra bytes out.
529           uncompressed.zap(real_size, uncompressed.length() - 1);
530
531           if (targo) {
532             // stuff the data we read into the target file.
533             ret = targo->write(uncompressed);
534             if (ret != uncompressed.length()) {
535               show_message(a_sprintf("failed while writing item #%d: ", festdex)
536                   + curr._payload, ERROR_TITLE);
537               return 93;
538             }
539           }
540         }
541       }
542       if (targo) targo->close();
543       WHACK(targo);
544       // the file's written, but now we slap it's old time on it too.
545       file_time t;
546       if (!t.unpack(curr.c_filetime)) {
547         show_message(astring("failed to interpret timestamp for ")
548             + curr._payload, ERROR_TITLE);
549         return 97;
550       }
551       // put the timestamp on the file.
552       t.set_time(curr._payload);
553 //      utimbuf held_time;
554 //      held_time.actime = t.raw();
555 //      held_time.modtime = t.raw();
556 //      // put the timestamp on the file.
557 //      utime(curr._payload.s(), &held_time);
558     }
559
560     // now that we're pretty sure the file exists, we can run it if needed.
561     if ( (curr._flags & TARGET_EXECUTE) && keyword_good) {    
562       // change the mode on the target file so we can execute it.
563       chmod(curr._payload.s(), 0766);
564       astring prev_dir = application_configuration::current_directory();
565
566       BASE_LOG(astring("launching ") + curr._payload);
567       if (!!curr._parms)
568         BASE_LOG(astring("  with parameters: ") + curr._parms);
569       BASE_LOG(astring('-', 76));
570
571       basis::un_int kid;
572       basis::un_int retval = launch_process::run(curr._payload, curr._parms,
573           launch_process::AWAIT_APP_EXIT | launch_process::HIDE_APP_WINDOW, kid);
574       if (retval != 0) {
575         if (! (curr._flags & IGNORE_ERRORS) ) {
576           if (curr._flags & QUIET_FAILURE) {
577             // no message box for this, but still log it.
578             LOG(astring("failed to launch process, targ=")
579                 + curr._payload + " with parms " + curr._parms
580                 + a_sprintf(" error=%d", retval));
581           } else {
582             show_message(astring("failed to launch process, targ=")
583                 + curr._payload + " with parms " + curr._parms
584                 + a_sprintf(" error=%d", retval), ERROR_TITLE);
585           }
586           return retval;  // pass along same exit value we were told.
587         } else {
588           LOG(astring("ignoring failure to launch process, targ=")
589               + curr._payload + " with parms " + curr._parms
590               + a_sprintf(" error=%d", retval));
591         }
592       }
593
594       chdir(prev_dir.s());  // reset directory pointer, just in case.
595
596       BASE_LOG(astring('-', 76));
597     }
598
599   }
600
601 #ifdef __WIN32__
602   whack_simplistic_window(f_window);
603 #endif
604
605   return 0;
606 }
607
608 ////////////////////////////////////////////////////////////////////////////
609
610 HOOPLE_MAIN(unpacker_stub, )
611
612 #ifdef __BUILD_STATIC_APPLICATION__
613   // static dependencies found by buildor_gen_deps.sh:
614   #include <application/application_shell.cpp>
615   #include <application/command_line.cpp>
616   #include <basis/astring.cpp>
617   #include <basis/common_outcomes.cpp>
618   #include <basis/environment.cpp>
619   #include <basis/mutex.cpp>
620   #include <basis/utf_conversion.cpp>
621   #include <configuration/application_configuration.cpp>
622   #include <configuration/configurator.cpp>
623   #include <configuration/ini_configurator.cpp>
624   #include <configuration/ini_parser.cpp>
625   #include <configuration/table_configurator.cpp>
626   #include <configuration/variable_tokenizer.cpp>
627   #include <filesystem/byte_filer.cpp>
628   #include <filesystem/directory.cpp>
629   #include <filesystem/file_info.cpp>
630   #include <filesystem/filename.cpp>
631   #include <filesystem/filename_list.cpp>
632   #include <filesystem/file_time.cpp>
633   #include <filesystem/heavy_file_ops.cpp>
634   #include <filesystem/huge_file.cpp>
635   #include <loggers/combo_logger.cpp>
636   #include <loggers/console_logger.cpp>
637   #include <loggers/critical_events.cpp>
638   #include <loggers/file_logger.cpp>
639   #include <loggers/program_wide_logger.cpp>
640   #include <processes/launch_process.cpp>
641   #include <structures/bit_vector.cpp>
642   #include <structures/checksums.cpp>
643   #include <structures/object_packers.cpp>
644   #include <structures/static_memory_gremlin.cpp>
645   #include <structures/string_hasher.cpp>
646   #include <structures/string_table.cpp>
647   #include <structures/version_record.cpp>
648   #include <textual/byte_formatter.cpp>
649   #include <textual/parser_bits.cpp>
650   #include <textual/string_manipulation.cpp>
651   #include <timely/earth_time.cpp>
652   #include <timely/time_control.cpp>
653   #include <timely/time_stamp.cpp>
654 #endif // __BUILD_STATIC_APPLICATION__
655