1 /*****************************************************************************\
3 * Name : write_build_config *
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 "write_build_config.h"
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>
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;
41 const int MAX_LINE_SIZE = 2048;
42 //!< we should never see an ini line longer than this.
44 const int MAX_HEADER_FILE = 128 * KILOBYTE;
45 //!< an excessively long allowance for the maximum generated header size.
47 const char *DEFINITIONS_STATEMENT = "DEFINITIONS";
48 //!< the tag we see in the config file for directly compatible macros.
50 const char *EXPORT_STATEMENT = "export ";
51 //!< a tag we see on variables to be inherited by subshells
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";
59 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
61 write_build_config::write_build_config()
62 : application_shell(),
63 _end_matter(new astring),
68 write_build_config::~write_build_config()
74 const string_set &write_build_config::exclusions()
76 static string_set _hidden;
77 static bool _initted = false;
80 _hidden += "OPTIMIZE";
81 _hidden += "STRICT_WARNINGS";
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; \
92 outcome write_build_config::output_macro(const astring &symbol,
93 const astring &value, astring &accumulator)
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;
104 accumulator += " #define ";
105 accumulator += symbol;
106 accumulator += " \"";
107 accumulator += value;
108 accumulator += "\"\n";
109 accumulator += " #endif\n";
113 bool write_build_config::process_version_parts(const astring &symbol,
114 const astring &value)
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; }
127 bool write_build_config::check_nesting(const astring &to_check)
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)) {
134 if (to_check.compare(ENDIF_STATEMENT, 0, 0, int(strlen(ENDIF_STATEMENT)), true)) {
141 outcome write_build_config::output_decorated_macro(const astring &symbol_in,
142 const astring &value, astring &cfg_accumulator, astring &ver_accumulator)
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);
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;
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);
166 outcome write_build_config::output_definition_macro
167 (const astring &embedded_value, astring &accumulator)
169 FUNCDEF("output_definition_macro");
170 //LOG(astring("into output def with: ") + embedded_value);
171 variable_tokenizer t;
172 t.parse(embedded_value);
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]);
178 ADD_COMMENT_RETURN(t.table().name(0), t.table()[0]);
179 return output_macro(t.table().name(0), t.table()[0], accumulator);
182 bool write_build_config::write_output_file(const astring &filename,
183 const astring &new_contents)
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?");
194 astring found(astring::UNTERMINATED, (char *)file_contents.observe(),
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;
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.
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());
214 build_header.write(new_contents);
215 LOG(astring(static_class_name()) + ": wrote config to "
216 + build_header.name());
219 // nothing has changed.
220 // LOG(astring(static_class_name()) + ": config already up to date in "
226 int write_build_config::execute()
229 SETUP_CONSOLE_LOGGER; // override the file_logger from app_shell.
231 // find our build ini file.
232 astring repodir = environment::get("FEISTY_MEOW_APEX");
234 // the below code should never be needed for a properly configured build.
236 if (!repodir) repodir = "l:";
237 #else // unix and other locations.
239 repodir = environment::get("HOME") + "/hoople";
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";
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
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."));
259 // these are very specific paths, but they really are where we expect to
262 astring cfg_header_filename = versions_directory + "/__build_configuration.h";
263 astring ver_header_filename = versions_directory + "/__build_version.h";
265 // open the ini file for reading.
266 byte_filer ini(fname, "r");
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!
272 // now we build strings that represents the output files we want to create.
273 astring cfg_accumulator;
274 astring ver_accumulator;
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";
290 // iterate through the entries we read in earlier and generate our header.
291 astring symbol, value;
294 int chars = ini.getline(buffer, MAX_LINE_SIZE);
295 if (!chars) continue; // hmmm: what does no chars mean?
297 variable_tokenizer t;
299 if (!t.symbols()) continue; // not so good.
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);
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);
312 if (symbol[0] == '#') continue; // toss out any comments.
314 if (!symbol) continue; // seems like that one didn't work out so well.
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));
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
325 output_definition_macro(value, cfg_accumulator);
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);
333 // write some calculated macros now.
334 ver_accumulator += "\n";
335 ver_accumulator += " // calculated macros are dropped in here.\n\n";
337 // we write our version in a couple forms. hopefully we accumulated it.
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";
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";
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";
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";
375 // finish up the version file also.
376 ver_accumulator += "\n";
377 ver_accumulator += "#endif /* outer guard */\n\n";
379 if (!write_output_file(cfg_header_filename, cfg_accumulator)) {
380 LOG(astring("failed writing output file ") + cfg_header_filename);
382 if (!write_output_file(ver_header_filename, ver_accumulator)) {
383 LOG(astring("failed writing output file ") + ver_header_filename);
389 HOOPLE_MAIN(write_build_config, )
391 #ifdef __BUILD_STATIC_APPLICATION__
392 // static dependencies found by buildor_gen_deps.sh:
393 #include <application/application_shell.cpp>
394 #include <application/command_line.cpp>
395 #include <application/windoze_helper.cpp>
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>
402 #include <configuration/application_configuration.cpp>
403 #include <configuration/configurator.cpp>
404 #include <configuration/ini_configurator.cpp>
405 #include <configuration/ini_parser.cpp>
406 #include <configuration/table_configurator.cpp>
407 #include <configuration/variable_tokenizer.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>
415 #include <loggers/program_wide_logger.cpp>
416 #include <structures/bit_vector.cpp>
417 #include <structures/checksums.cpp>
418 #include <structures/object_packers.cpp>
419 #include <structures/static_memory_gremlin.cpp>
420 #include <structures/string_hasher.cpp>
421 #include <structures/string_table.cpp>
422 #include <structures/version_record.cpp>
423 #include <textual/byte_formatter.cpp>
424 #include <textual/parser_bits.cpp>
425 #include <textual/string_manipulation.cpp>
426 #include <timely/earth_time.cpp>
427 #include <timely/time_stamp.cpp>
428 #include <versions/version_ini.cpp>
429 #endif // __BUILD_STATIC_APPLICATION__