6eb06ad2ef3015e79806e98348a065e1eda09a0b
[feisty_meow.git] / nucleus / tools / clam_tools / write_build_config.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : write_build_config                                                *
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 "write_build_config.h"
16
17 #include <application/hoople_main.h>
18 #include <application/windoze_helper.h>
19 #include <basis/functions.h>
20 #include <configuration/variable_tokenizer.h>
21 #include <filesystem/byte_filer.h>
22 #include <filesystem/filename.h>
23 #include <loggers/console_logger.h>
24 #include <loggers/critical_events.h>
25 #include <structures/set.h>
26 #include <structures/static_memory_gremlin.h>
27 #include <structures/string_table.h>
28 #include <versions/version_ini.h>
29
30 #include <stdio.h>
31
32 using namespace application;
33 using namespace basis;
34 using namespace configuration;
35 using namespace filesystem;
36 using namespace loggers;
37 using namespace structures;
38 using namespace textual;
39 using namespace versions;
40
41 const int MAX_LINE_SIZE = 2048;
42   //!< we should never see an ini line longer than this.
43
44 const int MAX_HEADER_FILE = 128 * KILOBYTE;
45   //!< an excessively long allowance for the maximum generated header size.
46
47 const char *DEFINITIONS_STATEMENT = "DEFINITIONS";
48   //!< the tag we see in the config file for directly compatible macros.
49
50 const char *EXPORT_STATEMENT = "export ";
51   //!< a tag we see on variables to be inherited by subshells
52
53 // make conditionals that we will eat.
54 const char *IFEQ_STATEMENT = "ifeq";
55 const char *IFNEQ_STATEMENT = "ifneq";
56 const char *ENDIF_STATEMENT = "endif";
57
58 #undef LOG
59 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
60
61 write_build_config::write_build_config()
62 : application_shell(),
63   _end_matter(new astring),
64   _ver(new version),
65   _nesting(0)
66 {}
67
68 write_build_config::~write_build_config()
69 {
70   WHACK(_end_matter);
71   WHACK(_ver);
72 }
73
74 const string_set &write_build_config::exclusions() 
75 {
76   static string_set _hidden;
77   static bool _initted = false;
78   if (!_initted) {
79     _hidden += "DEBUG";
80     _hidden += "OPTIMIZE";
81     _hidden += "STRICT_WARNINGS";
82   }
83   return _hidden;
84 }
85
86 // adds some more material to dump at the end of the file.
87 #define ADD_COMMENT_RETURN(sym, val) { \
88   *_end_matter += astring("  ") + sym + " = " + val + "\n"; \
89   return common::OKAY; \
90 }
91
92 outcome write_build_config::output_macro(const astring &symbol,
93     const astring &value, astring &accumulator)
94 {
95   // drop any excluded items to avoid interfering with devstu solution.
96   if (exclusions().member(symbol))
97     ADD_COMMENT_RETURN(symbol, value);
98   // drop any malformed symbols or values.
99   if (symbol.contains("\"") || value.contains("\""))
100     ADD_COMMENT_RETURN(symbol, value);
101   accumulator += "  #ifndef ";
102   accumulator += symbol;
103   accumulator += "\n";
104   accumulator += "    #define ";
105   accumulator += symbol;
106   accumulator += " \"";
107   accumulator += value;
108   accumulator += "\"\n";
109   accumulator += "  #endif\n";
110   return common::OKAY;
111 }
112
113 bool write_build_config::process_version_parts(const astring &symbol,
114     const astring &value)
115 {
116   if (symbol.equal_to("major"))
117     { _ver->set_component(version::MAJOR, value); return true; }
118   if (symbol.equal_to("minor"))
119     { _ver->set_component(version::MINOR, value); return true; }
120   if (symbol.equal_to("revision"))
121     { _ver->set_component(version::REVISION, value); return true; }
122   if (symbol.equal_to("build"))
123     { _ver->set_component(version::BUILD, value); return true; }
124   return false;
125 }
126
127 bool write_build_config::check_nesting(const astring &to_check)
128 {
129   if (to_check.compare(IFEQ_STATEMENT, 0, 0, int(strlen(IFEQ_STATEMENT)), true)
130       || to_check.compare(IFNEQ_STATEMENT, 0, 0, int(strlen(IFNEQ_STATEMENT)), true)) {
131     _nesting++;
132     return true;
133   }
134   if (to_check.compare(ENDIF_STATEMENT, 0, 0, int(strlen(ENDIF_STATEMENT)), true)) {
135     _nesting--;
136     return true;
137   }
138   return false;
139 }
140
141 outcome write_build_config::output_decorated_macro(const astring &symbol_in,
142     const astring &value, astring &cfg_accumulator, astring &ver_accumulator)
143 {
144   // make sure we catch any conditionals.
145   if (check_nesting(symbol_in))
146     ADD_COMMENT_RETURN(symbol_in, value);
147   // toss out any exclusions.
148   if (exclusions().member(symbol_in))
149     ADD_COMMENT_RETURN(symbol_in, value);
150   if (symbol_in.contains("\"") || value.contains("\""))
151     ADD_COMMENT_RETURN(symbol_in, value);
152   if (symbol_in[0] == '[')
153     ADD_COMMENT_RETURN(symbol_in, value);
154   if (_nesting)
155     ADD_COMMENT_RETURN(symbol_in, value);
156   // switch the output stream based on whether its a version component or not.
157   astring *the_accumulator = &cfg_accumulator;
158   if (process_version_parts(symbol_in, value)) {
159     the_accumulator = &ver_accumulator;
160   }
161   // add a special tag so that we won't be colliding with other names.
162   astring symbol = astring("__build_") + symbol_in;
163   return output_macro(symbol, value, *the_accumulator);
164 }
165
166 outcome write_build_config::output_definition_macro
167     (const astring &embedded_value, astring &accumulator)
168 {
169   FUNCDEF("output_definition_macro");
170 //LOG(astring("into output def with: ") + embedded_value);
171   variable_tokenizer t;
172   t.parse(embedded_value);
173   if (!t.symbols())
174     ADD_COMMENT_RETURN("bad definition", embedded_value);
175   if (exclusions().member(t.table().name(0)))
176     ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]);
177   if (_nesting)
178     ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]);
179   return output_macro(t.table().name(0), t.table()[0], accumulator);
180 }
181
182 bool write_build_config::write_output_file(const astring &filename,
183     const astring &new_contents)
184 {
185   FUNCDEF("write_output_file");
186   // now read the soon-to-be output file so we can check its current state.
187   bool write_header = true;
188   byte_filer check_header(filename, "rb");
189   if (check_header.good()) {
190     byte_array file_contents;
191     int read = check_header.read(file_contents, MAX_HEADER_FILE);
192 if (read < 1) LOG("why is existing header contentless?");
193     if (read > 0) {
194       astring found(astring::UNTERMINATED, (char *)file_contents.observe(),
195           read);
196 //LOG(astring("got existing content:\n-----\n") + found + "\n-----\n");
197 //LOG(astring("new_content has:\n-----\n") + new_contents + "\n-----\n");
198       if (found == new_contents) {
199         write_header = false;
200       }
201     }
202   }
203   // writing only occurs when we know that the build configurations have
204   // changed.  if the file is the same, we definitely don't want to write
205   // it because pretty much all the other files depend on it.
206   if (write_header) {
207     // we actually want to blast out a new file.
208     byte_filer build_header(filename, "wb");
209     if (!build_header.good()) {
210       continuable_error(static_class_name(), func, astring("could not create "
211           "build header file in ") + build_header.name());
212       return false;
213     } else {
214       build_header.write(new_contents);
215       LOG(astring(static_class_name()) + ": wrote config to "
216           + build_header.name());
217     }
218   } else {
219     // nothing has changed.
220 //    LOG(astring(static_class_name()) + ": config already up to date in "
221 //        + filename);
222   }
223   return true;
224 }
225
226 int write_build_config::execute()
227 {
228   FUNCDEF("execute");
229   SETUP_CONSOLE_LOGGER;  // override the file_logger from app_shell.
230
231   // find our build ini file.
232   astring repodir = environment::get("FEISTY_MEOW_APEX");
233
234   // the below code should never be needed for a properly configured build.
235 #ifdef __WIN32__
236   if (!repodir) repodir = "l:";
237 #else  // unix and other locations.
238   if (!repodir)
239     repodir = environment::get("HOME") + "/hoople";
240 #endif
241
242   astring fname;
243     // where we seek out our build settings.
244   astring parmfile = environment::get("BUILD_PARAMETER_FILE");
245   if (parmfile.t()) fname = parmfile;
246   else fname = repodir + "/build.ini";
247
248   // find our storage area for the build headers.  we know a couple build
249   // configurations by now, but this should really be coming out of a config
250   // file instead.
251   astring versions_directory = environment::get("FEISTY_MEOW_GENERATED_STORE");
252   // we keep our version files one level below the top of the generated store.
253   versions_directory += "/versions";
254   if (!filename(versions_directory).good()) {
255     non_continuable_error(static_class_name(), func,
256         astring("failed to locate the library folder storing the generated files."));
257   }
258
259   // these are very specific paths, but they really are where we expect to
260   // see the headers.
261
262   astring cfg_header_filename = versions_directory + "/__build_configuration.h";
263   astring ver_header_filename = versions_directory + "/__build_version.h";
264
265   // open the ini file for reading.
266   byte_filer ini(fname, "r");
267   if (!ini.good())
268     non_continuable_error(static_class_name(), func, astring("failed to open "
269         "build configuration file for reading at ") + ini.name());
270 //hmmm: parameterize the build ini thing above!
271
272   // now we build strings that represents the output files we want to create.
273   astring cfg_accumulator;
274   astring ver_accumulator;
275
276   // odd indentation below comes from the strings being c++ code that will be
277   // written to a file.
278 //hmmm: another location to fix!!!
279   cfg_accumulator += "\
280 #ifndef BUILD_SYSTEM_CONFIGURATION\n\
281 #define BUILD_SYSTEM_CONFIGURATION\n\n\
282   // This file provides all of the code flags which were used when this build\n\
283   // was generated.  Some of the items in the build configuration have been\n\
284   // stripped out because they are not used.\n\n";
285   ver_accumulator += "\
286 #ifndef BUILD_VERSION_CONFIGURATION\n\
287 #define BUILD_VERSION_CONFIGURATION\n\n\
288   // This file provides the version macros for this particular build.\n\n";
289
290   // iterate through the entries we read in earlier and generate our header.
291   astring symbol, value;
292   astring buffer;
293   while (!ini.eof()) {
294     int chars = ini.getline(buffer, MAX_LINE_SIZE);
295     if (!chars) continue;  // hmmm: what does no chars mean?
296     
297     variable_tokenizer t;
298     t.parse(buffer);
299     if (!t.symbols()) continue;  // not so good.
300
301     // pull out the first pair we found and try to parse it.
302     symbol = t.table().name(0);
303     value = t.table()[0];
304     symbol.strip_spaces(astring::FROM_BOTH_SIDES);
305
306     // clean out + characters that can come from += declarations.
307     while ( (symbol[symbol.end()] == '+') || (symbol[symbol.end()] == ':') ) {
308       symbol.zap(symbol.end(), symbol.end());
309       symbol.strip_spaces(astring::FROM_END);
310     }
311
312     if (symbol[0] == '#') continue;  // toss out any comments.
313
314     if (!symbol) continue;  // seems like that one didn't work out so well.
315
316     if (symbol.compare(EXPORT_STATEMENT, 0, 0, int(strlen(EXPORT_STATEMENT)), true)) {
317       // clean out export statements in front of our variables.
318       symbol.zap(0, int(strlen(EXPORT_STATEMENT) - 1));
319     }
320
321     // check for a make-style macro definition.
322     if (symbol.compare(DEFINITIONS_STATEMENT, 0, 0, int(strlen(DEFINITIONS_STATEMENT)), true)) {
323       // found a macro definition.  write that up after pulling the real
324       // contents out.
325       output_definition_macro(value, cfg_accumulator);
326     } else {
327       // this one is hopefully a very tasty specialized macro.  we will
328       // show it with added text to make it unique.
329       output_decorated_macro(symbol, value, cfg_accumulator, ver_accumulator);
330     }
331   }
332
333   // write some calculated macros now.
334   ver_accumulator += "\n";
335   ver_accumulator += "  // calculated macros are dropped in here.\n\n";
336
337   // we write our version in a couple forms.  hopefully we accumulated it.
338
339   // this one is the same as the file version currently (below), but may go to
340   // a different version at some point.
341   ver_accumulator += "  #define __build_SYSTEM_VERSION \"";
342   ver_accumulator += _ver->flex_text_form();
343   ver_accumulator += "\"\n\n";
344
345   // we drop in the version as numbers also, since the version RC wants that.
346   ver_accumulator += "  #define __build_FILE_VERSION_COMMAS ";
347   ver_accumulator += _ver->flex_text_form(version::COMMAS);
348   ver_accumulator += "\n";
349   // another form of the file version for dotted notation.
350   ver_accumulator += "  #define __build_FILE_VERSION \"";
351   ver_accumulator += _ver->flex_text_form();
352   ver_accumulator += "\"\n";
353
354   // product version is just the first two parts.
355   _ver->set_component(version::REVISION, "0");
356   _ver->set_component(version::BUILD, "0");
357   // product version as a list of numbers.
358   ver_accumulator += "  #define __build_PRODUCT_VERSION_COMMAS ";
359   ver_accumulator += _ver->flex_text_form(version::COMMAS);
360   ver_accumulator += "\n";
361   // another form of the product version for use as a string.
362   ver_accumulator += "  #define __build_PRODUCT_VERSION \"";
363   ver_accumulator += _ver->flex_text_form(version::DOTS, version::MINOR);
364   ver_accumulator += "\"\n";
365
366   // write a blob of comments at the end with what we didn't use.
367   cfg_accumulator += "\n";
368   cfg_accumulator += "/*\n";
369   cfg_accumulator += "These settings were not used:\n";
370   cfg_accumulator += *_end_matter;
371   cfg_accumulator += "*/\n";
372   cfg_accumulator += "\n";
373   cfg_accumulator += "#endif /* outer guard */\n\n";
374
375   // finish up the version file also.
376   ver_accumulator += "\n";
377   ver_accumulator += "#endif /* outer guard */\n\n";
378
379   if (!write_output_file(cfg_header_filename, cfg_accumulator)) {
380     LOG(astring("failed writing output file ") + cfg_header_filename);
381   }
382   if (!write_output_file(ver_header_filename, ver_accumulator)) {
383     LOG(astring("failed writing output file ") + ver_header_filename);
384   }
385
386 //NO, not any more  // now make a copy into the library folder, for when we release a production version.
387 //  astring library_directory = repodir + "/nucleus/library";
388 //  astring cfg_header_copy = library_directory + "/__build_configuration.h";
389 //  astring ver_header_copy = library_directory + "/__build_version.h";
390 //  if (!write_output_file(cfg_header_copy, cfg_accumulator)) {
391 //    LOG(astring("skipping copy due to read-only issue on output file: ") + cfg_header_copy);
392 //  }
393 //  if (!write_output_file(ver_header_copy, ver_accumulator)) {
394 //    LOG(astring("skipping copy due to read-only issue on output file: ") + ver_header_copy);
395 //  }
396
397   return 0;
398 }
399
400 HOOPLE_MAIN(write_build_config, )
401
402 #ifdef __BUILD_STATIC_APPLICATION__
403   // static dependencies found by buildor_gen_deps.sh:
404   #include <application/application_shell.cpp>
405   #include <application/command_line.cpp>
406   #include <application/windoze_helper.cpp>
407   #include <basis/astring.cpp>
408   #include <basis/common_outcomes.cpp>
409   #include <basis/environment.cpp>
410   #include <basis/guards.cpp>
411   #include <basis/mutex.cpp>
412   #include <basis/utf_conversion.cpp>
413   #include <configuration/application_configuration.cpp>
414   #include <configuration/configurator.cpp>
415   #include <configuration/ini_configurator.cpp>
416   #include <configuration/ini_parser.cpp>
417   #include <configuration/table_configurator.cpp>
418   #include <configuration/variable_tokenizer.cpp>
419   #include <filesystem/byte_filer.cpp>
420   #include <filesystem/directory.cpp>
421   #include <filesystem/filename.cpp>
422   #include <loggers/combo_logger.cpp>
423   #include <loggers/console_logger.cpp>
424   #include <loggers/critical_events.cpp>
425   #include <loggers/file_logger.cpp>
426   #include <loggers/program_wide_logger.cpp>
427   #include <structures/bit_vector.cpp>
428   #include <structures/checksums.cpp>
429   #include <structures/object_packers.cpp>
430   #include <structures/static_memory_gremlin.cpp>
431   #include <structures/string_hasher.cpp>
432   #include <structures/string_table.cpp>
433   #include <structures/version_record.cpp>
434   #include <textual/byte_formatter.cpp>
435   #include <textual/parser_bits.cpp>
436   #include <textual/string_manipulation.cpp>
437   #include <timely/earth_time.cpp>
438   #include <timely/time_stamp.cpp>
439   #include <versions/version_ini.cpp>
440 #endif // __BUILD_STATIC_APPLICATION__
441