super tasty version fixes
[feisty_meow.git] / nucleus / library / versions / version_ini.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : version_ini editing support                                       *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1995-$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 "version_ini.h"
16
17 #include <basis/functions.h>
18 #include <configuration/application_configuration.h>
19 #include <configuration/ini_configurator.h>
20 #include <filesystem/byte_filer.h>
21 #include <filesystem/directory.h>
22 #include <filesystem/filename.h>
23 #include <loggers/critical_events.h>
24 #include <loggers/program_wide_logger.h>
25 #include <structures/string_array.h>
26
27 #include <sys/stat.h>
28 #ifdef __WIN32__
29   #include <io.h>
30 #endif
31
32 using namespace basis;
33 using namespace configuration;
34 using namespace filesystem;
35 using namespace loggers;
36 using namespace structures;
37
38 namespace versions {
39
40 // the following are all strings that are sought in the version.ini file.
41 const char *version_ini::VERSION_SECTION = "version";
42   // the section that version entries are stored under in the INI file.
43 const char *version_ini::COMPANY_KEY="company";
44 const char *version_ini::COPYRIGHT_KEY="copyright";
45 const char *version_ini::LEGAL_INFO_KEY="legal_info";
46 const char *version_ini::PRODUCT_KEY="product_name";
47 const char *version_ini::WEB_SITE_KEY="web_site";
48
49 // not used anymore; now matched with the file version's first two digits.
50 //const version PRODUCT_VERSION(2, 0, 0, 0);
51   // the current version of the entire product.
52
53 // these are field names in the INI file.
54 const char *version_ini::MAJOR = "major";
55 const char *version_ini::MINOR = "minor";
56 const char *version_ini::REVISION = "revision";
57 const char *version_ini::BUILD = "build";
58 const char *version_ini::DESCRIPTION = "description";
59 const char *version_ini::ROOT = "root";
60 const char *version_ini::NAME = "name";
61 const char *version_ini::EXTENSION = "extension";
62 const char *version_ini::OLE_AUTO = "ole_auto";
63
64 // this is the default version INI file name, if no other is specified.
65 const char *VERSION_INI_FILE = "/version.ini";
66
67 #undef LOG
68 #define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t)
69
70 version_ini::version_ini(const astring &path_name)
71 : _loaded(false),
72   _path_name(new filename(path_name)),
73   _ini(new ini_configurator("", ini_configurator::RETURN_ONLY)),
74   _held_record(new version_record)
75 {
76   check_name(*_path_name);
77   _ini->name(*_path_name);
78 }
79
80 version_ini::~version_ini()
81 {
82   WHACK(_ini);
83   WHACK(_path_name);
84   WHACK(_held_record);
85 }
86
87 bool version_ini::ole_auto_registering()
88 {
89   astring extension = _ini->load(VERSION_SECTION, OLE_AUTO, "");
90   return (extension.lower().t());
91 }
92
93 bool version_ini::executable()
94 {
95   astring extension = _ini->load(VERSION_SECTION, EXTENSION, "");
96   if (extension.lower() == astring("exe")) return true;
97   return false;
98 }
99
100 bool version_ini::library() { return !executable(); }
101
102 bool version_ini::writable() { return _path_name->is_writable(); }
103
104 void version_ini::check_name(filename &to_examine)
105 {
106   // if it's just a directory name, add the file name.
107   if (to_examine.is_directory()) {
108     to_examine = astring(to_examine) + VERSION_INI_FILE;
109     to_examine.canonicalize();
110   }
111
112   // add the directory explicitly (if it's not there already) or the ini
113   // writer will get it from the windows directory.
114   if ( (to_examine.raw()[0] != '.') && (to_examine.dirname().raw().equal_to(".")) ) {
115     to_examine = astring("./") + to_examine;
116     to_examine.canonicalize();
117   }
118 }
119
120 bool version_ini::executable(const astring &path_name_in)
121 {
122   filename path_name(path_name_in);
123   check_name(path_name);
124   ini_configurator temp_ini(path_name, ini_configurator::RETURN_ONLY);
125   astring extension = temp_ini.load(VERSION_SECTION, EXTENSION, "");
126   extension.to_lower();
127   if (extension == astring("exe")) return true;
128   return false;
129 }
130
131 bool version_ini::library(const astring &path_name)
132 { return !executable(path_name); }
133
134 version version_ini::get_version()
135 {
136   if (_loaded) return _held_record->file_version;
137   get_record();
138   return _held_record->file_version;
139 }
140
141 void version_ini::set_version(const version &to_write, bool write_ini)
142 {
143   _held_record->file_version = to_write;  // copy the version we're given.
144
145   // set the product version appropriately to the file version.
146   _held_record->product_version = to_write;
147   _held_record->product_version.set_component(version::REVISION, "0");
148   _held_record->product_version.set_component(version::BUILD, "0");
149
150   if (!write_ini) return;  // they don't want a change to the file.
151   _ini->store(VERSION_SECTION, MAJOR, to_write.get_component(version::MAJOR));
152   _ini->store(VERSION_SECTION, MINOR, to_write.get_component(version::MINOR));
153   _ini->store(VERSION_SECTION, REVISION, to_write.get_component(version::REVISION));
154   _ini->store(VERSION_SECTION, BUILD, to_write.get_component(version::BUILD));
155 }
156
157 version version_ini::read_version_from_ini()
158 {
159   string_array parts;
160   parts += _ini->load(VERSION_SECTION, MAJOR, "0");
161   parts += _ini->load(VERSION_SECTION, MINOR, "0");
162   parts += _ini->load(VERSION_SECTION, REVISION, "0");
163   parts += _ini->load(VERSION_SECTION, BUILD, "0");
164   return version(parts);
165 }
166
167 version_record &version_ini::access_record() { return *_held_record; }
168
169 version_record version_ini::get_record()
170 {
171   FUNCDEF("get_record");
172   if (_loaded) return *_held_record;
173   version_record to_return;
174   to_return.description = _ini->load(VERSION_SECTION, DESCRIPTION, "");
175   to_return.file_version = read_version_from_ini();
176   to_return.internal_name = _ini->load(VERSION_SECTION, NAME, "");
177   to_return.original_name = _ini->load(VERSION_SECTION, ROOT, "");
178   to_return.original_name += ".";
179
180   // the dll type of extension is a hard default.  anything besides the exe
181   // ending gets mapped to dll.
182   astring extension = _ini->load(VERSION_SECTION, EXTENSION, "");
183   extension.to_lower();
184   if (extension.equal_to("dll")) {}
185   else if (extension.equal_to("exe")) {}
186   else extension.equal_to("dll");
187   to_return.original_name += extension;
188
189   to_return.product_version = to_return.file_version;
190   to_return.product_version.set_component(version::REVISION, "0");
191   to_return.product_version.set_component(version::BUILD, "0");
192
193   to_return.product_name = _ini->load(VERSION_SECTION, PRODUCT_KEY, "");
194   to_return.company_name = _ini->load(VERSION_SECTION, COMPANY_KEY, "");
195   to_return.copyright = _ini->load(VERSION_SECTION, COPYRIGHT_KEY, "");
196   to_return.trademarks = _ini->load(VERSION_SECTION, LEGAL_INFO_KEY, "");
197   to_return.web_address = _ini->load(VERSION_SECTION, WEB_SITE_KEY, "");
198
199   *_held_record = to_return;
200   _loaded = true;
201   return to_return;
202 }
203
204 void version_ini::set_record(const version_record &to_write, bool write_ini)
205 {
206   *_held_record = to_write;
207   if (write_ini) {
208     _ini->store(VERSION_SECTION, DESCRIPTION, to_write.description);
209     set_version(to_write.file_version, write_ini);
210     _ini->store(VERSION_SECTION, ROOT, to_write.original_name);
211     _ini->store(VERSION_SECTION, NAME, to_write.internal_name);
212   }
213   _loaded = true;  // we consider this to be the real version now.
214 }
215
216 //////////////
217
218 const astring version_rc_template = "\
219 #ifndef NO_VERSION\n\
220 #include <winver.h>\n\
221 #include <__build_version.h>\n\
222 #include <__build_configuration.h>\n\
223 #define BI_PLAT_WIN32\n\
224   // force 32 bit compile.\n\
225 1 VERSIONINFO LOADONCALL MOVEABLE\n\
226 FILEVERSION __build_FILE_VERSION_COMMAS\n\
227 PRODUCTVERSION __build_PRODUCT_VERSION_COMMAS\n\
228 FILEFLAGSMASK 0\n\
229 FILEFLAGS VS_FFI_FILEFLAGSMASK\n\
230 #if defined(BI_PLAT_WIN32)\n\
231   FILEOS VOS__WINDOWS32\n\
232 #else\n\
233   FILEOS VOS__WINDOWS16\n\
234 #endif\n\
235 FILETYPE VFT_APP\n\
236 BEGIN\n\
237   BLOCK \"StringFileInfo\"\n\
238   BEGIN\n\
239     // Language type = U.S. English(0x0409) and Character Set = Windows, Multilingual(0x04b0)\n\
240     BLOCK \"040904b0\"              // Matches VarFileInfo Translation hex value.\n\
241     BEGIN\n\
242       VALUE \"CompanyName\", __build_company \"\\000\"\n\
243 #ifndef _DEBUG\n\
244       VALUE \"FileDescription\", \"$file_desc\\000\"\n\
245 #else\n\
246       VALUE \"FileDescription\", \"$file_desc (DEBUG)\\000\"\n\
247 #endif\n\
248       VALUE \"FileVersion\", __build_FILE_VERSION \"\\000\" \n\
249       VALUE \"ProductVersion\", __build_PRODUCT_VERSION \"\\000\" \n\
250       VALUE \"InternalName\", \"$internal\\000\"\n\
251       VALUE \"LegalCopyright\", __build_copyright \"\\000\"\n\
252       VALUE \"LegalTrademarks\", __build_legal_info \"\\000\"\n\
253       VALUE \"OriginalFilename\", \"$original_name\\000\"\n\
254       VALUE \"ProductName\", __build_product_name \"\\000\"\n\
255       $special_ole_flag\n\
256     END\n\
257   END\n\
258 \n\
259   BLOCK \"VarFileInfo\"\n\
260   BEGIN\n\
261     VALUE \"Translation\", 0x0409, 0x04b0 // US English (0x0409) and win32 multilingual (0x04b0)\n\
262   END\n\
263 END\n\
264 #endif\n";
265
266 //////////////
267
268 // replaces every occurrence of the keyword in "tag" with the "replacement".
269 #define REPLACE(tag, replacement) \
270   new_version_entry.replace_all(tag, replacement); \
271
272 bool version_ini::write_rc(const astring &header_store, const version_record &to_write)
273 {
274   FUNCDEF("write_rc");
275   astring new_version_entry(version_rc_template);
276
277   // $file_ver -> w, x, y, z for version of the file.
278   REPLACE("$file_ver", to_write.file_version.flex_text_form(version::COMMAS));
279
280   // $prod_ver -> w, x, y, z for version of the product.
281   REPLACE("$prod_ver", to_write.product_version.flex_text_form
282       (version::COMMAS));
283
284   // $company -> name of company.
285   REPLACE("$company", to_write.company_name);
286
287   // $file_desc -> description of file.
288   astring description_release = to_write.description;
289   REPLACE("$file_desc", description_release);
290   astring description_debug = to_write.description
291       + astring(" -- Debug Version");
292   REPLACE("$file_desc", description_debug);
293
294   // $file_txt_ver -> file version in form w.x.y.z.
295   REPLACE("$file_txt_ver", to_write.file_version.flex_text_form(version::DOTS));
296
297   // $internal -> internal name of the library, without extensions?
298   REPLACE("$internal", to_write.internal_name);
299
300   // $copyright -> copyright held by us.
301   REPLACE("$copyright", to_write.copyright);
302
303   // $legal_tm -> the legal trademarks that must be included, e.g. windows?
304   REPLACE("$legal_tm", to_write.trademarks);
305
306   // $original_name -> the file's name before possible renamings.
307   REPLACE("$original_name", to_write.original_name);
308
309   // $prod_name -> the name of the product that this belongs to.
310   REPLACE("$prod_name", to_write.product_name);
311
312   // $prod_txt_ver -> product version in form w.x.y.z.
313   REPLACE("$prod_txt_ver", to_write.product_version
314       .flex_text_form(version::DOTS, version::MINOR));
315
316   astring special_filler;  // nothing by default.
317   if (ole_auto_registering())
318     special_filler = "VALUE \"OLESelfRegister\", \"\\0\"";
319   REPLACE("$special_ole_flag", special_filler);
320
321   astring root_part = "/";
322   root_part += _ini->load(VERSION_SECTION, ROOT, "");
323
324   astring rc_filename(header_store + "/" + root_part
325       + "_version.rc");
326
327   filename(rc_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
328     // make sure we can write to the file.
329
330   byte_filer rc_file(rc_filename, "w");
331   if (!rc_file.good()) return false;
332   rc_file.write((abyte *)new_version_entry.s(), new_version_entry.length());
333   rc_file.close();
334   return true;
335 }
336
337 //////////////
338
339 const astring version_header_template = "\
340 #ifndef $lib_prefix_VERSION_HEADER\n\
341 #define $lib_prefix_VERSION_HEADER\n\
342 \n\
343 /*****************************************************************************\\\n\
344 *                                                                             *\n\
345 *  Name   : Version header for $lib_name\n\
346 *  Author : Automatically generated by version_stamper                        *\n\
347 *                                                                             *\n\
348 \\*****************************************************************************/\n\
349 \n\
350 #include <__build_version.h>\n\
351 #include <__build_configuration.h>\n\
352 #include <basis/version_checker.h>\n\
353 #include <basis/version_record.h>\n\
354 \n\
355 #ifdef __WIN32__\n\
356 \n\
357 // this macro can be used to check that the current version of the\n\
358 // $lib_name library is the same version as expected.  to use it, check\n\
359 // whether it returns true or false.  if false, the version is incorrect.\n\
360 #define CHECK_$lib_prefix() (version_checker(astring(\"$lib_prefix\")\\\n\
361     + astring(\".dll\"), version(__build_SYSTEM_VERSION),\\\n\
362       astring(\"Please contact $company_name for the latest DLL and \"\\\n\
363         \"Executable files ($web_address).\")).good_version())\n\
364 \n\
365 #else\n\
366 \n\
367 // null checking for embedded or other platforms without versions.\n\
368 \n\
369 #define CHECK_$lib_prefix() 1\n\
370 \n\
371 #endif //__WIN32__\n\
372 \n\
373 #endif\n\
374 \n";
375
376 //////////////
377
378 bool version_ini::write_code(const astring &header_store, const version_record &to_write)
379 {
380   FUNCDEF("write_code");
381   astring root_part = _ini->load(VERSION_SECTION, ROOT, "");
382   astring root = root_part.upper();  // make upper case for naming sake.
383   astring name = _ini->load(VERSION_SECTION, NAME, "");
384   // replace the macros here also.
385   name.replace_all("$product_name", to_write.product_name);
386
387   astring new_version_entry(version_header_template);
388
389 //some of the replacements are no longer needed.
390
391   // $lib_prefix -> the first part of the library's name.
392   REPLACE("$lib_prefix", root);
393   REPLACE("$lib_prefix", root);
394   REPLACE("$lib_prefix", root);
395   REPLACE("$lib_prefix", root);
396   REPLACE("$lib_prefix", root);
397   REPLACE("$lib_prefix", root);
398   REPLACE("$lib_prefix", root);
399
400   // $lib_name -> the name of the library, as it thinks of itself.
401   REPLACE("$lib_name", name);
402   REPLACE("$lib_name", name);
403   REPLACE("$lib_name", name);
404
405   // $lib_version -> the current version for this library.
406   REPLACE("$lib_version", to_write.file_version.flex_text_form(version::COMMAS));
407
408   // $company_name -> the company that produces the library.
409   REPLACE("$company_name", to_write.company_name);
410
411   // $web_address -> the web site for the company.  not actually stored.
412   REPLACE("$web_address", to_write.web_address);
413
414   astring header_filename(header_store + "/" + root_part
415       + astring("_version.h"));
416
417   filename(header_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
418     // make sure we can write to the file.
419
420   byte_filer header(header_filename, "w");
421   if (!header.good()) return false;
422   header.write((abyte *)new_version_entry.s(), new_version_entry.length());
423   header.close();
424   return true;
425 }
426
427
428 // returns true if manipulated the full_string to replace its version
429 bool replace_version_entry(astring &full_string, const astring &look_for,
430     const astring &new_ver)
431 {
432   bool to_return = false;
433   int posn = 0;  // where are we looking for this?
434
435   while (posn < full_string.length()) {
436     int ver_posn = full_string.find(look_for, posn);
437     if (ver_posn < 0) break;  // nothing to modify.
438     int quote_posn = full_string.find("\"", ver_posn);
439     if (quote_posn < 0) break;  // malformed assembly we will not touch.
440     int second_quote_posn = full_string.find("\"", quote_posn + 1);
441     if (second_quote_posn < 0) break;  // more malformage.
442     full_string.zap(quote_posn + 1, second_quote_posn - 1);
443     full_string.insert(quote_posn + 1, new_ver);
444     to_return = true;  // found a match.
445     // skip to the next place.
446     posn = quote_posn + new_ver.length();
447   }
448
449   return to_return;
450 }
451
452 bool version_ini::write_assembly(const astring &header_store, const version_record &to_write,
453     bool do_logging)
454 {
455   FUNCDEF("write_assembly");
456   filename just_dir = filename(header_store);
457           //_path_name->dirname();
458
459 //hmmm: make condit on debug
460 LOG(astring("dir is set to: ") + just_dir);
461
462   directory dir(just_dir);
463   filename to_patch;
464 //LOG(astring("dir has: ") + dir.files().text_form());
465   if (non_negative(dir.files().find("AssemblyInfo.cpp")))
466     to_patch = just_dir.raw() + "/AssemblyInfo.cpp";
467   else if (non_negative(dir.files().find("AssemblyInfo.cs")))
468     to_patch = just_dir.raw() + "/AssemblyInfo.cs";
469   if (!to_patch.raw()) {
470     // no assembly file yet.  see if there's one in a properties subdirectory.
471     directory dir2(just_dir + "/Properties");
472     if (non_negative(dir2.files().find("AssemblyInfo.cpp")))
473       to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cpp";
474     else if (non_negative(dir2.files().find("AssemblyInfo.cs")))
475       to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cs";
476   }
477
478   if (to_patch.raw().t()) {
479     // we have a filename to work on.
480     filename(to_patch).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
481       // make sure we can write to the file.
482     byte_filer modfile(to_patch, "r+b");
483     astring contents;
484     modfile.read(contents, 1000000);  // read any file size up to that.
485     while (contents[contents.end()] == '\032') {
486       // erase any stray eof characters that may happen to be present.     
487       contents.zap(contents.end(), contents.end());
488     }
489 //LOG(astring("file contents are: \n") + contents);
490
491 //here's where to fixit.
492
493     astring ver_string = to_write.file_version.flex_text_form(version::DOTS);
494     bool did_replace = replace_version_entry(contents, "AssemblyVersionAttribute", ver_string);
495     if (!did_replace) {
496       did_replace = replace_version_entry(contents, "AssemblyVersion", ver_string);
497     }
498     if (!did_replace) return true;  // nothing to modify?
499     did_replace = replace_version_entry(contents, "AssemblyFileVersion", ver_string);
500     if (!did_replace) {
501       did_replace = replace_version_entry(contents, "AssemblyFileVersionAttribute", ver_string);
502     }
503     // if we got to here, we at least replaced something...
504
505 /*
506     int ver_posn = contents.find("AssemblyVersionAttribute", 0);
507     // try again if that seek failed.
508     if (ver_posn < 0)
509       ver_posn = contents.find("AssemblyVersion", 0);
510     if (ver_posn < 0) return true;  // nothing to modify.
511 //LOG(astring("found assembly version: ") + to_patch);
512     int quote_posn = contents.find("\"", ver_posn);
513     if (quote_posn < 0) return true;  // malformed assembly we will not touch.
514 //LOG(astring("found quote: ") + to_patch);
515     int second_quote_posn = contents.find("\"", quote_posn + 1);
516     if (second_quote_posn < 0) return true;  // more malformage.
517 //LOG(astring("found quote: ") + to_patch);
518     contents.zap(quote_posn + 1, second_quote_posn - 1);
519     contents.insert(quote_posn + 1, ver_string);
520 */
521
522 //LOG(astring("writing new output file: ") + to_patch);
523     modfile.seek(0);
524     modfile.write(contents);
525     modfile.truncate();  // chop off anything left from previous versions.
526     if (do_logging) {
527       // let the people know about this...
528       filename dirbase = filename(modfile.name()).dirname().basename();
529       filename just_base = filename(modfile.name()).basename();
530       program_wide_logger::get().log(astring("    patching: ") + dirbase
531           + "/" + just_base, basis::ALWAYS_PRINT);
532     }
533   }
534
535   return true;
536 }
537
538 bool version_ini::one_stop_version_stamp(const astring &path,
539     const astring &header_store, const astring &source_version, bool do_logging)
540 {
541   FUNCDEF("one_stop_version_stamp");
542   astring path_name = path;
543   if (path_name.equal_to("."))
544     path_name = application_configuration::current_directory();
545
546   // load the version record in from the ini file and cache it.
547   version_ini source(path_name);
548   source.get_record();
549
550   if (source_version.t()) {
551     // get the version structure from the passed file.
552     version_ini main_version(source_version);
553     version version_to_use = main_version.get_version();
554
555     // stuff the version from the main source into this particular file.
556     source.set_version(version_to_use, false);
557
558     // stuff the other volatile records from the main version.
559     version_record main = main_version.get_record();
560     source.access_record().company_name = main.company_name;
561     source.access_record().web_address = main.web_address;
562     source.access_record().copyright = main.copyright;
563     source.access_record().trademarks = main.trademarks;
564     source.access_record().product_name = main.product_name;
565
566     source.access_record().internal_name.replace("$product_name",
567         source.get_record().product_name);
568   }
569
570   if (do_logging) {
571     // report the current state.
572    program_wide_logger::get().log(source.get_record().internal_name + " version "
573        + source.get_version().text_form() + ".", ALWAYS_PRINT);
574   }
575
576   version_ini verini(path_name);
577   verini.set_record(source.get_record(), false);
578
579 //put this in debug brackets
580   LOG(a_sprintf("The file \"%s\" contains this version information:",
581       path_name.s()));
582   LOG(verini.get_record().text_form());
583 //...debug to here
584
585   if (!verini.write_rc(header_store, verini.get_record())) {
586     critical_events::alert_message(a_sprintf("Could not write the RC file in \"%s\".",
587         filename(path_name).basename().raw().s()));
588     return false;
589   }
590
591   if (verini.library() && !verini.write_code(header_store, verini.get_record())) {
592     critical_events::alert_message(astring("Could not write the C++ header file for "
593         "the directory \"")
594         + filename(path_name).basename() + astring("\".\n"));
595     return false;
596   }
597
598   if (!verini.write_assembly(header_store, verini.get_record(), do_logging)) {
599     critical_events::alert_message(astring("Could not write the Assembly info file for "
600         "the directory \"")
601         + filename(path_name).basename() + astring("\".\n"));
602     return false;
603   }
604
605   return true;
606 }
607
608 } //namespace.