4afed778ac91223bc51ba5b1d1047a20bda871d4
[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       non_continuable_error(static_class_name(), func, astring("failed to create "
211           "build header file in ") + build_header.name());
212     build_header.write(new_contents);
213     LOG(astring(static_class_name()) + ": wrote config to "
214         + build_header.name());
215   } else {
216     // nothing has changed.
217 //    LOG(astring(static_class_name()) + ": config already up to date in "
218 //        + filename);
219   }
220   return true;
221 }
222
223 int write_build_config::execute()
224 {
225   FUNCDEF("execute");
226   SETUP_CONSOLE_LOGGER;  // override the file_logger from app_shell.
227
228   // find our build ini file.
229   astring repodir = environment::get("FEISTY_MEOW_APEX");
230
231   // the below code should never be needed for a properly configured build.
232 #ifdef __WIN32__
233   if (!repodir) repodir = "l:";
234 #else  // unix and other locations.
235   if (!repodir)
236     repodir = environment::get("HOME") + "/hoople";
237 #endif
238
239   astring fname;
240     // where we seek out our build settings.
241   astring parmfile = environment::get("BUILD_PARAMETER_FILE");
242   if (parmfile.t()) fname = parmfile;
243   else fname = repodir + "/build.ini";
244
245   // find our storage area for the build headers.  we know a couple build
246   // configurations by now, but this should really be coming out of a config
247   // file instead.
248   astring library_directory = repodir + "/nucleus/library";
249   if (!filename(library_directory).good()) {
250     non_continuable_error(static_class_name(), func,
251         astring("failed to locate the library folder storing the generated files."));
252   }
253
254   // these are very specific paths, but they really are where we expect to
255   // see the headers.
256
257   astring cfg_header_filename = library_directory + "/"
258       "__build_configuration.h";
259   astring ver_header_filename = library_directory + "/"
260       "__build_version.h";
261
262   // open the ini file for reading.
263   byte_filer ini(fname, "r");
264   if (!ini.good())
265     non_continuable_error(static_class_name(), func, astring("failed to open "
266         "build configuration file for reading at ") + ini.name());
267 //hmmm: parameterize the build ini thing above!
268
269   // now we build strings that represents the output files we want to create.
270   astring cfg_accumulator;
271   astring ver_accumulator;
272
273   // odd indentation below comes from the strings being c++ code that will be
274   // written to a file.
275 //hmmm: another location to fix!!!
276   cfg_accumulator += "\
277 #ifndef BUILD_SYSTEM_CONFIGURATION\n\
278 #define BUILD_SYSTEM_CONFIGURATION\n\n\
279   // This file provides all of the code flags which were used when this build\n\
280   // was generated.  Some of the items in the build configuration have been\n\
281   // stripped out because they are not used.\n\n";
282   ver_accumulator += "\
283 #ifndef BUILD_VERSION_CONFIGURATION\n\
284 #define BUILD_VERSION_CONFIGURATION\n\n\
285   // This file provides the version macros for this particular build.\n\n";
286
287   // iterate through the entries we read in earlier and generate our header.
288   astring symbol, value;
289   astring buffer;
290   while (!ini.eof()) {
291     int chars = ini.getline(buffer, MAX_LINE_SIZE);
292     if (!chars) continue;  // hmmm: what does no chars mean?
293     
294     variable_tokenizer t;
295     t.parse(buffer);
296     if (!t.symbols()) continue;  // not so good.
297
298     // pull out the first pair we found and try to parse it.
299     symbol = t.table().name(0);
300     value = t.table()[0];
301     symbol.strip_spaces(astring::FROM_BOTH_SIDES);
302
303     // clean out + characters that can come from += declarations.
304     while ( (symbol[symbol.end()] == '+') || (symbol[symbol.end()] == ':') ) {
305       symbol.zap(symbol.end(), symbol.end());
306       symbol.strip_spaces(astring::FROM_END);
307     }
308
309     if (symbol[0] == '#') continue;  // toss out any comments.
310
311     if (!symbol) continue;  // seems like that one didn't work out so well.
312
313     if (symbol.compare(EXPORT_STATEMENT, 0, 0, int(strlen(EXPORT_STATEMENT)), true)) {
314       // clean out export statements in front of our variables.
315       symbol.zap(0, int(strlen(EXPORT_STATEMENT) - 1));
316     }
317
318     // check for a make-style macro definition.
319     if (symbol.compare(DEFINITIONS_STATEMENT, 0, 0, int(strlen(DEFINITIONS_STATEMENT)), true)) {
320       // found a macro definition.  write that up after pulling the real
321       // contents out.
322       output_definition_macro(value, cfg_accumulator);
323     } else {
324       // this one is hopefully a very tasty specialized macro.  we will
325       // show it with added text to make it unique.
326       output_decorated_macro(symbol, value, cfg_accumulator, ver_accumulator);
327     }
328   }
329
330   // write some calculated macros now.
331   ver_accumulator += "\n";
332   ver_accumulator += "  // calculated macros are dropped in here.\n\n";
333
334   // we write our version in a couple forms.  hopefully we accumulated it.
335
336   // this one is the same as the file version currently (below), but may go to
337   // a different version at some point.
338   ver_accumulator += "  #define __build_SYSTEM_VERSION \"";
339   ver_accumulator += _ver->flex_text_form();
340   ver_accumulator += "\"\n\n";
341
342   // we drop in the version as numbers also, since the version RC wants that.
343   ver_accumulator += "  #define __build_FILE_VERSION_COMMAS ";
344   ver_accumulator += _ver->flex_text_form(version::COMMAS);
345   ver_accumulator += "\n";
346   // another form of the file version for dotted notation.
347   ver_accumulator += "  #define __build_FILE_VERSION \"";
348   ver_accumulator += _ver->flex_text_form();
349   ver_accumulator += "\"\n";
350
351   // product version is just the first two parts.
352   _ver->set_component(version::REVISION, "0");
353   _ver->set_component(version::BUILD, "0");
354   // product version as a list of numbers.
355   ver_accumulator += "  #define __build_PRODUCT_VERSION_COMMAS ";
356   ver_accumulator += _ver->flex_text_form(version::COMMAS);
357   ver_accumulator += "\n";
358   // another form of the product version for use as a string.
359   ver_accumulator += "  #define __build_PRODUCT_VERSION \"";
360   ver_accumulator += _ver->flex_text_form(version::DOTS, version::MINOR);
361   ver_accumulator += "\"\n";
362
363   // write a blob of comments at the end with what we didn't use.
364   cfg_accumulator += "\n";
365   cfg_accumulator += "/*\n";
366   cfg_accumulator += "These settings were not used:\n";
367   cfg_accumulator += *_end_matter;
368   cfg_accumulator += "*/\n";
369   cfg_accumulator += "\n";
370   cfg_accumulator += "#endif /* outer guard */\n\n";
371
372   // finish up the version file also.
373   ver_accumulator += "\n";
374   ver_accumulator += "#endif /* outer guard */\n\n";
375
376   if (!write_output_file(cfg_header_filename, cfg_accumulator)) {
377     LOG(astring("failed writing output file ") + cfg_header_filename);
378   }
379   if (!write_output_file(ver_header_filename, ver_accumulator)) {
380     LOG(astring("failed writing output file ") + ver_header_filename);
381   }
382
383   return 0;
384 }
385
386 HOOPLE_MAIN(write_build_config, )
387
388 #ifdef __BUILD_STATIC_APPLICATION__
389   // static dependencies found by buildor_gen_deps.sh:
390   #include <application/application_shell.cpp>
391   #include <application/command_line.cpp>
392   #include <application/windoze_helper.cpp>
393   #include <basis/astring.cpp>
394   #include <basis/common_outcomes.cpp>
395   #include <basis/environment.cpp>
396   #include <basis/guards.cpp>
397   #include <basis/mutex.cpp>
398   #include <basis/utf_conversion.cpp>
399   #include <configuration/application_configuration.cpp>
400   #include <configuration/configurator.cpp>
401   #include <configuration/ini_configurator.cpp>
402   #include <configuration/ini_parser.cpp>
403   #include <configuration/table_configurator.cpp>
404   #include <configuration/variable_tokenizer.cpp>
405   #include <filesystem/byte_filer.cpp>
406   #include <filesystem/directory.cpp>
407   #include <filesystem/filename.cpp>
408   #include <loggers/combo_logger.cpp>
409   #include <loggers/console_logger.cpp>
410   #include <loggers/critical_events.cpp>
411   #include <loggers/file_logger.cpp>
412   #include <loggers/program_wide_logger.cpp>
413   #include <structures/bit_vector.cpp>
414   #include <structures/checksums.cpp>
415   #include <structures/object_packers.cpp>
416   #include <structures/static_memory_gremlin.cpp>
417   #include <structures/string_hasher.cpp>
418   #include <structures/string_table.cpp>
419   #include <structures/version_record.cpp>
420   #include <textual/byte_formatter.cpp>
421   #include <textual/parser_bits.cpp>
422   #include <textual/string_manipulation.cpp>
423   #include <timely/earth_time.cpp>
424   #include <timely/time_stamp.cpp>
425   #include <versions/version_ini.cpp>
426 #endif // __BUILD_STATIC_APPLICATION__
427