new home directory
[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 version_record &to_write)
273 {
274   astring new_version_entry(version_rc_template);
275
276   // $file_ver -> w, x, y, z for version of the file.
277   REPLACE("$file_ver", to_write.file_version.flex_text_form(version::COMMAS));
278
279   // $prod_ver -> w, x, y, z for version of the product.
280   REPLACE("$prod_ver", to_write.product_version.flex_text_form
281       (version::COMMAS));
282
283   // $company -> name of company.
284   REPLACE("$company", to_write.company_name);
285
286   // $file_desc -> description of file.
287   astring description_release = to_write.description;
288   REPLACE("$file_desc", description_release);
289   astring description_debug = to_write.description
290       + astring(" -- Debug Version");
291   REPLACE("$file_desc", description_debug);
292
293   // $file_txt_ver -> file version in form w.x.y.z.
294   REPLACE("$file_txt_ver", to_write.file_version.flex_text_form(version::DOTS));
295
296   // $internal -> internal name of the library, without extensions?
297   REPLACE("$internal", to_write.internal_name);
298
299   // $copyright -> copyright held by us.
300   REPLACE("$copyright", to_write.copyright);
301
302   // $legal_tm -> the legal trademarks that must be included, e.g. windows?
303   REPLACE("$legal_tm", to_write.trademarks);
304
305   // $original_name -> the file's name before possible renamings.
306   REPLACE("$original_name", to_write.original_name);
307
308   // $prod_name -> the name of the product that this belongs to.
309   REPLACE("$prod_name", to_write.product_name);
310
311   // $prod_txt_ver -> product version in form w.x.y.z.
312   REPLACE("$prod_txt_ver", to_write.product_version
313       .flex_text_form(version::DOTS, version::MINOR));
314
315   astring special_filler;  // nothing by default.
316   if (ole_auto_registering())
317     special_filler = "VALUE \"OLESelfRegister\", \"\\0\"";
318   REPLACE("$special_ole_flag", special_filler);
319
320   astring root_part = "/";
321   root_part += _ini->load(VERSION_SECTION, ROOT, "");
322
323   astring rc_filename(astring(_path_name->dirname()) + root_part
324       + "_version.rc");
325
326   filename(rc_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
327     // make sure we can write to the file.
328
329   byte_filer rc_file(rc_filename, "w");
330   if (!rc_file.good()) return false;
331   rc_file.write((abyte *)new_version_entry.s(), new_version_entry.length());
332   rc_file.close();
333   return true;
334 }
335
336 //////////////
337
338 const astring version_header_template = "\
339 #ifndef $lib_prefix_VERSION_HEADER\n\
340 #define $lib_prefix_VERSION_HEADER\n\
341 \n\
342 /*****************************************************************************\\\n\
343 *                                                                             *\n\
344 *  Name   : Version header for $lib_name\n\
345 *  Author : Automatically generated by version_stamper                        *\n\
346 *                                                                             *\n\
347 \\*****************************************************************************/\n\
348 \n\
349 #include <__build_version.h>\n\
350 #include <__build_configuration.h>\n\
351 #include <basis/version_checker.h>\n\
352 #include <basis/version_record.h>\n\
353 \n\
354 #ifdef __WIN32__\n\
355 \n\
356 // this macro can be used to check that the current version of the\n\
357 // $lib_name library is the same version as expected.  to use it, check\n\
358 // whether it returns true or false.  if false, the version is incorrect.\n\
359 #define CHECK_$lib_prefix() (version_checker(astring(\"$lib_prefix\")\\\n\
360     + astring(\".dll\"), version(__build_SYSTEM_VERSION),\\\n\
361       astring(\"Please contact $company_name for the latest DLL and \"\\\n\
362         \"Executable files ($web_address).\")).good_version())\n\
363 \n\
364 #else\n\
365 \n\
366 // null checking for embedded or other platforms without versions.\n\
367 \n\
368 #define CHECK_$lib_prefix() 1\n\
369 \n\
370 #endif //__WIN32__\n\
371 \n\
372 #endif\n\
373 \n";
374
375 //////////////
376
377 bool version_ini::write_code(const version_record &to_write)
378 {
379   astring root_part = _ini->load(VERSION_SECTION, ROOT, "");
380   astring root = root_part.upper();  // make upper case for naming sake.
381   astring name = _ini->load(VERSION_SECTION, NAME, "");
382   // replace the macros here also.
383   name.replace_all("$product_name", to_write.product_name);
384
385   astring new_version_entry(version_header_template);
386
387 //some of the replacements are no longer needed.
388
389   // $lib_prefix -> the first part of the library's name.
390   REPLACE("$lib_prefix", root);
391   REPLACE("$lib_prefix", root);
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
398   // $lib_name -> the name of the library, as it thinks of itself.
399   REPLACE("$lib_name", name);
400   REPLACE("$lib_name", name);
401   REPLACE("$lib_name", name);
402
403   // $lib_version -> the current version for this library.
404   REPLACE("$lib_version", to_write.file_version.flex_text_form(version::COMMAS));
405
406   // $company_name -> the company that produces the library.
407   REPLACE("$company_name", to_write.company_name);
408
409   // $web_address -> the web site for the company.  not actually stored.
410   REPLACE("$web_address", to_write.web_address);
411
412   astring header_filename(_path_name->dirname().raw() + "/" + root_part
413       + astring("_version.h"));
414
415   filename(header_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
416     // make sure we can write to the file.
417
418   byte_filer header(header_filename, "w");
419   if (!header.good()) return false;
420   header.write((abyte *)new_version_entry.s(), new_version_entry.length());
421   header.close();
422   return true;
423 }
424
425
426 // returns true if manipulated the full_string to replace its version
427 bool replace_version_entry(astring &full_string, const astring &look_for,
428     const astring &new_ver)
429 {
430   bool to_return = false;
431   int posn = 0;  // where are we looking for this?
432
433   while (posn < full_string.length()) {
434     int ver_posn = full_string.find(look_for, posn);
435     if (ver_posn < 0) break;  // nothing to modify.
436     int quote_posn = full_string.find("\"", ver_posn);
437     if (quote_posn < 0) break;  // malformed assembly we will not touch.
438     int second_quote_posn = full_string.find("\"", quote_posn + 1);
439     if (second_quote_posn < 0) break;  // more malformage.
440     full_string.zap(quote_posn + 1, second_quote_posn - 1);
441     full_string.insert(quote_posn + 1, new_ver);
442     to_return = true;  // found a match.
443     // skip to the next place.
444     posn = quote_posn + new_ver.length();
445   }
446
447   return to_return;
448 }
449
450 bool version_ini::write_assembly(const version_record &to_write,
451     bool do_logging)
452 {
453   FUNCDEF("write_assembly");
454   filename just_dir = _path_name->dirname();
455 //LOG(astring("dir is set to: ") + just_dir);
456   directory dir(just_dir);
457   filename to_patch;
458 //LOG(astring("dir has: ") + dir.files().text_form());
459   if (non_negative(dir.files().find("AssemblyInfo.cpp")))
460     to_patch = just_dir.raw() + "/AssemblyInfo.cpp";
461   else if (non_negative(dir.files().find("AssemblyInfo.cs")))
462     to_patch = just_dir.raw() + "/AssemblyInfo.cs";
463   if (!to_patch.raw()) {
464     // no assembly file yet.  see if there's one in a properties subdirectory.
465     directory dir2(just_dir + "/Properties");
466     if (non_negative(dir2.files().find("AssemblyInfo.cpp")))
467       to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cpp";
468     else if (non_negative(dir2.files().find("AssemblyInfo.cs")))
469       to_patch = just_dir.raw() + "/Properties/AssemblyInfo.cs";
470   }
471
472   if (to_patch.raw().t()) {
473     // we have a filename to work on.
474     filename(to_patch).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
475       // make sure we can write to the file.
476     byte_filer modfile(to_patch, "r+b");
477     astring contents;
478     modfile.read(contents, 1000000);  // read any file size up to that.
479     while (contents[contents.end()] == '\032') {
480       // erase any stray eof characters that may happen to be present.     
481       contents.zap(contents.end(), contents.end());
482     }
483 //LOG(astring("file contents are: \n") + contents);
484
485 //here's where to fixit.
486
487     astring ver_string = to_write.file_version.flex_text_form(version::DOTS);
488     bool did_replace = replace_version_entry(contents, "AssemblyVersionAttribute", ver_string);
489     if (!did_replace) {
490       did_replace = replace_version_entry(contents, "AssemblyVersion", ver_string);
491     }
492     if (!did_replace) return true;  // nothing to modify?
493     did_replace = replace_version_entry(contents, "AssemblyFileVersion", ver_string);
494     if (!did_replace) {
495       did_replace = replace_version_entry(contents, "AssemblyFileVersionAttribute", ver_string);
496     }
497     // if we got to here, we at least replaced something...
498
499 /*
500     int ver_posn = contents.find("AssemblyVersionAttribute", 0);
501     // try again if that seek failed.
502     if (ver_posn < 0)
503       ver_posn = contents.find("AssemblyVersion", 0);
504     if (ver_posn < 0) return true;  // nothing to modify.
505 //LOG(astring("found assembly version: ") + to_patch);
506     int quote_posn = contents.find("\"", ver_posn);
507     if (quote_posn < 0) return true;  // malformed assembly we will not touch.
508 //LOG(astring("found quote: ") + to_patch);
509     int second_quote_posn = contents.find("\"", quote_posn + 1);
510     if (second_quote_posn < 0) return true;  // more malformage.
511 //LOG(astring("found quote: ") + to_patch);
512     contents.zap(quote_posn + 1, second_quote_posn - 1);
513     contents.insert(quote_posn + 1, ver_string);
514 */
515
516 //LOG(astring("writing new output file: ") + to_patch);
517     modfile.seek(0);
518     modfile.write(contents);
519     modfile.truncate();  // chop off anything left from previous versions.
520     if (do_logging) {
521       // let the people know about this...
522       filename dirbase = filename(modfile.name()).dirname().basename();
523       filename just_base = filename(modfile.name()).basename();
524       program_wide_logger::get().log(astring("    patching: ") + dirbase
525           + "/" + just_base, basis::ALWAYS_PRINT);
526     }
527   }
528
529   return true;
530 }
531
532 bool version_ini::one_stop_version_stamp(const astring &path,
533     const astring &source_version, bool do_logging)
534 {
535   astring path_name = path;
536   if (path_name.equal_to("."))
537     path_name = application_configuration::current_directory();
538
539   // load the version record in from the ini file and cache it.
540   version_ini source(path_name);
541   source.get_record();
542
543   if (source_version.t()) {
544     // get the version structure from the passed file.
545     version_ini main_version(source_version);
546     version version_to_use = main_version.get_version();
547
548     // stuff the version from the main source into this particular file.
549     source.set_version(version_to_use, false);
550
551     // stuff the other volatile records from the main version.
552     version_record main = main_version.get_record();
553     source.access_record().company_name = main.company_name;
554     source.access_record().web_address = main.web_address;
555     source.access_record().copyright = main.copyright;
556     source.access_record().trademarks = main.trademarks;
557     source.access_record().product_name = main.product_name;
558
559     source.access_record().internal_name.replace("$product_name",
560         source.get_record().product_name);
561   }
562
563   if (do_logging) {
564     // report the current state.
565    program_wide_logger::get().log(source.get_record().internal_name + " version "
566        + source.get_version().text_form() + ".", ALWAYS_PRINT);
567   }
568
569   version_ini verini(path_name);
570   verini.set_record(source.get_record(), false);
571
572 //  LOG(a_sprintf("The file \"%s\" contains this version information:",
573 //      path_name.s()));
574 //  LOG(verini.get_record().text_form());
575
576   if (!verini.write_rc(verini.get_record())) {
577     critical_events::alert_message(a_sprintf("Could not write the RC file in \"%s\".",
578         filename(path_name).basename().raw().s()));
579     return false;
580   }
581
582   if (verini.library() && !verini.write_code(verini.get_record())) {
583     critical_events::alert_message(astring("Could not write the C++ header file for "
584         "the directory \"")
585         + filename(path_name).basename() + astring("\".\n"));
586     return false;
587   }
588
589   if (!verini.write_assembly(verini.get_record(), do_logging)) {
590     critical_events::alert_message(astring("Could not write the Assembly info file for "
591         "the directory \"")
592         + filename(path_name).basename() + astring("\".\n"));
593     return false;
594   }
595
596   return true;
597 }
598
599 } //namespace.