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