feisty meow concerns codebase  2.140
write_build_config.cpp
Go to the documentation of this file.
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 
19 #include <basis/functions.h>
21 #include <filesystem/byte_filer.h>
22 #include <filesystem/filename.h>
23 #include <loggers/console_logger.h>
25 #include <structures/set.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;
43 
44 const int MAX_HEADER_FILE = 128 * KILOBYTE;
46 
47 const char *DEFINITIONS_STATEMENT = "DEFINITIONS";
49 
50 const char *EXPORT_STATEMENT = "export ";
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 
63  _end_matter(new astring),
64  _ver(new version),
65  _nesting(0)
66 {}
67 
69 {
70  WHACK(_end_matter);
71  WHACK(_ver);
72 }
73 
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 
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 
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 
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 
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 
167  (const astring &embedded_value, astring &accumulator)
168 {
169  FUNCDEF("output_definition_macro");
170 //LOG(astring("into output def with: ") + embedded_value);
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 
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 
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()) {
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 
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  return 0;
387 }
388 
390 
391 #ifdef __BUILD_STATIC_APPLICATION__
392  // static dependencies found by buildor_gen_deps.sh:
396  #include <basis/astring.cpp>
397  #include <basis/common_outcomes.cpp>
398  #include <basis/environment.cpp>
399  #include <basis/guards.cpp>
400  #include <basis/mutex.cpp>
401  #include <basis/utf_conversion.cpp>
408  #include <filesystem/byte_filer.cpp>
409  #include <filesystem/directory.cpp>
410  #include <filesystem/filename.cpp>
411  #include <loggers/combo_logger.cpp>
412  #include <loggers/console_logger.cpp>
413  #include <loggers/critical_events.cpp>
414  #include <loggers/file_logger.cpp>
416  #include <structures/bit_vector.cpp>
417  #include <structures/checksums.cpp>
421  #include <structures/string_table.cpp>
423  #include <textual/byte_formatter.cpp>
424  #include <textual/parser_bits.cpp>
426  #include <timely/earth_time.cpp>
427  #include <timely/time_stamp.cpp>
428  #include <versions/version_ini.cpp>
429 #endif // __BUILD_STATIC_APPLICATION__
430 
#define read
Definition: Xos2defs.h:38
The application_shell is a base object for console programs.
const contents * observe() const
Returns a pointer to the underlying C array of data.
Definition: array.h:172
Provides a dynamically resizable ASCII character string.
Definition: astring.h:35
bool t() const
t() is a shortcut for the string being "true", as in non-empty.
Definition: astring.h:97
virtual void zap(int start, int end)
Deletes the characters between "start" and "end" inclusively.
Definition: astring.cpp:521
void strip_spaces(how_to_strip way=FROM_BOTH_SIDES)
removes excess space characters from string's beginning, end or both.
Definition: astring.h:325
bool equal_to(const char *that) const
returns true if "that" is equal to this.
Definition: astring.cpp:159
int end() const
returns the index of the last (non-null) character in the string.
Definition: astring.h:86
bool compare(const astring &to_compare, int start_first, int start_second, int count, bool case_sensitive) const
Compares "this" string with "to_compare".
Definition: astring.cpp:810
bool contains(const astring &to_find) const
Returns true if "to_find" is contained in this string or false if not.
Definition: astring.cpp:162
A very common template for a dynamic array of bytes.
Definition: byte_array.h:36
Outcomes describe the state of completion for an operation.
Definition: outcome.h:31
Manages a bank of textual definitions of variables.
const structures::string_table & table() const
provides a constant peek at the string_table holding the values.
int symbols() const
returns the number of entries in the variable_tokenizer.
bool parse(const basis::astring &to_tokenize)
parses the string using our established sentinel characters.
Provides file managment services using the standard I/O support.
Definition: byte_filer.h:32
int getline(basis::abyte *buffer, int desired_size)
reads a line of text (terminated by a return) into the "buffer".
Definition: byte_filer.cpp:201
int write(const basis::abyte *buffer, int buffer_size)
writes "buffer_size" bytes into the file from "buffer".
Definition: byte_filer.cpp:126
const basis::astring & name() const
returns the file name that the object is operating on.
Definition: byte_filer.cpp:82
int read(basis::abyte *buffer, int buffer_size)
reads "buffer_size" bytes from the file into "buffer".
Definition: byte_filer.cpp:123
bool eof()
returns true if the cursor is at (or after) the end of the file.
Definition: byte_filer.cpp:121
bool good()
returns true if the file seems to be in the appropriate desired state.
Definition: byte_filer.cpp:103
Provides operations commonly needed on file names.
Definition: filename.h:64
A simple object that wraps a templated set of strings.
Definition: set.h:168
const basis::astring & name(int index) const
returns the name held at the "index".
Definition: symbol_table.h:272
Holds a file's version identifier.
void set_component(int index, const basis::astring &to_set)
sets the component at "index" to "to_set".
basis::astring flex_text_form(version_style style=DOTS, int including=-1) const
returns a textual form of the version number.
const structures::string_set & exclusions()
returns the set of symbols that we will not include in the header.
bool write_output_file(const basis::astring &filename, const basis::astring &contents)
writes "contents" to "filename" if it differs from current contents.
basis::outcome output_macro(const basis::astring &symbol, const basis::astring &value, basis::astring &accumulator)
sends a macro definition for "symbol" with "value" to "accumulator".
basis::outcome output_definition_macro(const basis::astring &embedded_value, basis::astring &accumulator)
parses a 'name=value' pair out of "embedded_value" and writes a macro.
bool check_nesting(const basis::astring &to_check)
if "to_check" is a make conditional, the nesting level is adjusted.
basis::outcome output_decorated_macro(const basis::astring &symbol, const basis::astring &value, basis::astring &cfg_accumulator, basis::astring &ver_accumulator)
produces a new macro by adding a uniquifying string to "symbol".
int execute()
performs the main action of creating our build configuration header.
bool process_version_parts(const basis::astring &symbol, const basis::astring &value)
checks on "symbol" to see if it's a version component. stores if so.
#define SETUP_CONSOLE_LOGGER
< a macro that retasks the program-wide logger as a console_logger.
#define continuable_error(c, f, i)
#define non_continuable_error(c, f, i)
an extra piece of information used, if available, in bounds_halt below.
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
Provides macros that implement the 'main' program of an application.
#define HOOPLE_MAIN(obj_name, obj_args)
options that should work for most unix and linux apps.
Definition: hoople_main.h:61
Implements an application lock to ensure only one is running at once.
The guards collection helps in testing preconditions and reporting errors.
Definition: array.h:30
void WHACK(contents *&ptr)
deletion with clearing of the pointer.
Definition: functions.h:121
const int KILOBYTE
Number of bytes in a kilobyte.
Definition: definitions.h:134
A platform independent way to obtain the timestamp of a file.
Definition: byte_filer.cpp:37
A logger that sends to the console screen using the standard output device.
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55
#define static_class_name()
Aids in achievement of platform independence.
const char * EXPORT_STATEMENT
a tag we see on variables to be inherited by subshells
const int MAX_HEADER_FILE
an excessively long allowance for the maximum generated header size.
const char * DEFINITIONS_STATEMENT
the tag we see in the config file for directly compatible macros.
const char * IFNEQ_STATEMENT
const char * ENDIF_STATEMENT
#define LOG(to_print)
const int MAX_LINE_SIZE
we should never see an ini line longer than this.
#define ADD_COMMENT_RETURN(sym, val)
const char * IFEQ_STATEMENT