1 /*****************************************************************************\
3 * Name : version_ini editing support *
4 * Author : Chris Koeritz *
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 \*****************************************************************************/
15 #include "version_ini.h"
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>
32 using namespace basis;
33 using namespace configuration;
34 using namespace filesystem;
35 using namespace loggers;
36 using namespace structures;
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";
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.
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";
64 // this is the default version INI file name, if no other is specified.
65 const char *VERSION_INI_FILE = "/version.ini";
68 #define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t)
70 version_ini::version_ini(const astring &path_name)
72 _path_name(new filename(path_name)),
73 _ini(new ini_configurator("", ini_configurator::RETURN_ONLY)),
74 _held_record(new version_record)
76 check_name(*_path_name);
77 _ini->name(*_path_name);
80 version_ini::~version_ini()
87 bool version_ini::ole_auto_registering()
89 astring extension = _ini->load(VERSION_SECTION, OLE_AUTO, "");
90 return (extension.lower().t());
93 bool version_ini::executable()
95 astring extension = _ini->load(VERSION_SECTION, EXTENSION, "");
96 if (extension.lower() == astring("exe")) return true;
100 bool version_ini::library() { return !executable(); }
102 bool version_ini::writable() { return _path_name->is_writable(); }
104 void version_ini::check_name(filename &to_examine)
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();
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();
120 bool version_ini::executable(const astring &path_name_in)
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;
131 bool version_ini::library(const astring &path_name)
132 { return !executable(path_name); }
134 version version_ini::get_version()
136 if (_loaded) return _held_record->file_version;
138 return _held_record->file_version;
141 void version_ini::set_version(const version &to_write, bool write_ini)
143 _held_record->file_version = to_write; // copy the version we're given.
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");
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));
157 version version_ini::read_version_from_ini()
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);
167 version_record &version_ini::access_record() { return *_held_record; }
169 version_record version_ini::get_record()
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 += ".";
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;
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");
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, "");
199 *_held_record = to_return;
204 void version_ini::set_record(const version_record &to_write, bool write_ini)
206 *_held_record = to_write;
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);
213 _loaded = true; // we consider this to be the real version now.
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\
229 FILEFLAGS VS_FFI_FILEFLAGSMASK\n\
230 #if defined(BI_PLAT_WIN32)\n\
231 FILEOS VOS__WINDOWS32\n\
233 FILEOS VOS__WINDOWS16\n\
237 BLOCK \"StringFileInfo\"\n\
239 // Language type = U.S. English(0x0409) and Character Set = Windows, Multilingual(0x04b0)\n\
240 BLOCK \"040904b0\" // Matches VarFileInfo Translation hex value.\n\
242 VALUE \"CompanyName\", __build_company \"\\000\"\n\
244 VALUE \"FileDescription\", \"$file_desc\\000\"\n\
246 VALUE \"FileDescription\", \"$file_desc (DEBUG)\\000\"\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\
259 BLOCK \"VarFileInfo\"\n\
261 VALUE \"Translation\", 0x0409, 0x04b0 // US English (0x0409) and win32 multilingual (0x04b0)\n\
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); \
272 bool version_ini::write_rc(const astring &header_store, const version_record &to_write)
275 astring new_version_entry(version_rc_template);
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));
280 // $prod_ver -> w, x, y, z for version of the product.
281 REPLACE("$prod_ver", to_write.product_version.flex_text_form
284 // $company -> name of company.
285 REPLACE("$company", to_write.company_name);
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);
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));
297 // $internal -> internal name of the library, without extensions?
298 REPLACE("$internal", to_write.internal_name);
300 // $copyright -> copyright held by us.
301 REPLACE("$copyright", to_write.copyright);
303 // $legal_tm -> the legal trademarks that must be included, e.g. windows?
304 REPLACE("$legal_tm", to_write.trademarks);
306 // $original_name -> the file's name before possible renamings.
307 REPLACE("$original_name", to_write.original_name);
309 // $prod_name -> the name of the product that this belongs to.
310 REPLACE("$prod_name", to_write.product_name);
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));
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);
321 astring root_part = "/";
322 root_part += _ini->load(VERSION_SECTION, ROOT, "");
324 astring rc_filename(header_store + "/" + root_part
327 filename(rc_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
328 // make sure we can write to the file.
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());
339 const astring version_header_template = "\
340 #ifndef $lib_prefix_VERSION_HEADER\n\
341 #define $lib_prefix_VERSION_HEADER\n\
343 /*****************************************************************************\\\n\
345 * Name : Version header for $lib_name\n\
346 * Author : Automatically generated by version_stamper *\n\
348 \\*****************************************************************************/\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\
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\
367 // null checking for embedded or other platforms without versions.\n\
369 #define CHECK_$lib_prefix() 1\n\
371 #endif //__WIN32__\n\
378 bool version_ini::write_code(const astring &header_store, const version_record &to_write)
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);
387 astring new_version_entry(version_header_template);
389 //some of the replacements are no longer needed.
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);
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);
405 // $lib_version -> the current version for this library.
406 REPLACE("$lib_version", to_write.file_version.flex_text_form(version::COMMAS));
408 // $company_name -> the company that produces the library.
409 REPLACE("$company_name", to_write.company_name);
411 // $web_address -> the web site for the company. not actually stored.
412 REPLACE("$web_address", to_write.web_address);
414 astring header_filename(header_store + "/" + root_part
415 + astring("_version.h"));
417 filename(header_filename).chmod(filename::ALLOW_BOTH, filename::USER_RIGHTS);
418 // make sure we can write to the file.
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());
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)
432 bool to_return = false;
433 int posn = 0; // where are we looking for this?
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();
452 bool version_ini::write_assembly(const astring &header_store, const version_record &to_write,
455 FUNCDEF("write_assembly");
456 filename just_dir = filename(header_store);
457 //_path_name->dirname();
459 //hmmm: make condit on debug
460 LOG(astring("dir is set to: ") + just_dir);
462 directory dir(just_dir);
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";
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");
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());
489 //LOG(astring("file contents are: \n") + contents);
491 //here's where to fixit.
493 astring ver_string = to_write.file_version.flex_text_form(version::DOTS);
494 bool did_replace = replace_version_entry(contents, "AssemblyVersionAttribute", ver_string);
496 did_replace = replace_version_entry(contents, "AssemblyVersion", ver_string);
498 if (!did_replace) return true; // nothing to modify?
499 did_replace = replace_version_entry(contents, "AssemblyFileVersion", ver_string);
501 did_replace = replace_version_entry(contents, "AssemblyFileVersionAttribute", ver_string);
503 // if we got to here, we at least replaced something...
506 int ver_posn = contents.find("AssemblyVersionAttribute", 0);
507 // try again if that seek failed.
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);
522 //LOG(astring("writing new output file: ") + to_patch);
524 modfile.write(contents);
525 modfile.truncate(); // chop off anything left from previous versions.
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);
538 bool version_ini::one_stop_version_stamp(const astring &path,
539 const astring &header_store, const astring &source_version, bool do_logging)
541 FUNCDEF("one_stop_version_stamp");
542 astring path_name = path;
543 if (path_name.equal_to("."))
544 path_name = application_configuration::current_directory();
546 // load the version record in from the ini file and cache it.
547 version_ini source(path_name);
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();
555 // stuff the version from the main source into this particular file.
556 source.set_version(version_to_use, false);
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;
566 source.access_record().internal_name.replace("$product_name",
567 source.get_record().product_name);
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);
576 version_ini verini(path_name);
577 verini.set_record(source.get_record(), false);
579 //put this in debug brackets
580 LOG(a_sprintf("The file \"%s\" contains this version information:",
582 LOG(verini.get_record().text_form());
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()));
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 "
594 + filename(path_name).basename() + astring("\".\n"));
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 "
601 + filename(path_name).basename() + astring("\".\n"));