The Battle for Wesnoth  1.15.0-dev
build_info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2018 by Iris Morelle <shadowm2006@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "build_info.hpp"
18 
19 #include "desktop/version.hpp"
20 #include "game_config.hpp"
21 #include "filesystem.hpp"
22 #include "formatter.hpp"
23 #include "gettext.hpp"
25 #include "game_version.hpp"
26 
27 #include <algorithm>
28 
29 #include <SDL.h>
30 #include <SDL_image.h>
31 #include <SDL_mixer.h>
32 
33 #include <boost/version.hpp>
34 
35 #include <openssl/crypto.h>
36 #include <openssl/opensslv.h>
37 
38 #include <pango/pangocairo.h>
39 
40 #ifdef __APPLE__
41 // apple_notification.mm uses Foundation.h, which is an Objective-C header;
42 // but CoreFoundation.h is a C header which also defines these.
43 #include <CoreFoundation/CoreFoundation.h>
44 #endif
45 
46 namespace game_config
47 {
48 
49 namespace {
50 
51 struct version_table_manager
52 {
53  std::vector<std::string> compiled, linked, names;
54  std::vector<optional_feature> features;
55 
56  version_table_manager();
57 };
58 
59 const version_table_manager versions;
60 
61 #if 0
62 std::string format_version(unsigned a, unsigned b, unsigned c)
63 {
64  return formatter() << a << '.' << b << '.' << c;
65 }
66 #endif
67 
68 std::string format_version(const SDL_version& v)
69 {
70  return formatter() << static_cast<unsigned>(v.major) << '.'
71  << static_cast<unsigned>(v.minor) << '.'
72  << static_cast<unsigned>(v.patch);
73 }
74 
75 std::string format_openssl_patch_level(uint8_t p)
76 {
77  return p <= 26
78  ? std::string(1, 'a' + static_cast<char>(p) - 1)
79  : "patch" + std::to_string(p);
80 }
81 
82 std::string format_openssl_version(long v)
83 {
84  int major, minor, fix, patch, status;
85  std::ostringstream fmt;
86 
87  //
88  // The people who maintain OpenSSL are not from this world. I suppose it's
89  // only fair that I'm the one who gets to try to make sense of their version
90  // encoding scheme. -- shadowm
91  //
92 
93  if(v < 0x0930L) {
94  // Pre-0.9.3 seems simpler times overall.
95  minor = v & 0x0F00L >> 8;
96  fix = v & 0x00F0L >> 4;
97  patch = v & 0x000FL;
98 
99  fmt << "0." << minor << '.' << fix;
100  if(patch) {
101  fmt << format_openssl_patch_level(patch);
102  }
103  } else {
104  //
105  // Note that they either assume the major version will never be greater than
106  // 9, they plan to use hexadecimal digits for versions 10.x.x through
107  // 15.x.x, or they expect long to be always > 32-bits by then. Who the hell
108  // knows, really.
109  //
110  major = (v & 0xF0000000L) >> 28;
111  minor = (v & 0x0FF00000L) >> 20;
112  fix = (v & 0x000FF000L) >> 12;
113  patch = (v & 0x00000FF0L) >> 4;
114  status = (v & 0x0000000FL);
115 
116  if(v < 0x00905100L) {
117  //
118  // From wiki.openssl.org (also mentioned in opensslv.h, in the most oblique
119  // fashion possible):
120  //
121  // "Versions between 0.9.3 and 0.9.5 had a version identifier with this interpretation:
122  // MMNNFFRBB major minor fix final beta/patch"
123  //
124  // Both the wiki and opensslv.h fail to accurately list actual version
125  // numbers that ended up used in the wild -- e.g. 0.9.3a is supposedly
126  // 0x0090301f when it really was 0x00903101.
127  //
128  const uint8_t is_final = (v & 0xF00L) >> 8;
129  status = is_final ? 0xF : 0;
130  patch = v & 0xFFL;
131  } else if(v < 0x00906000L) {
132  //
133  // Quoth opensslv.h:
134  //
135  // "For continuity reasons (because 0.9.5 is already out, and is coded
136  // 0x00905100), between 0.9.5 and 0.9.6 the coding of the patch level
137  // part is slightly different, by setting the highest bit. This means
138  // that 0.9.5a looks like this: 0x0090581f. At 0.9.6, we can start
139  // with 0x0090600S..."
140  //
141  patch ^= 1 << 7;
142  }
143 
144  fmt << major << '.' << minor << '.' << fix;
145 
146  if(patch) {
147  fmt << format_openssl_patch_level(patch);
148  }
149 
150  if(status == 0x0) {
151  fmt << "-dev";
152  } else if(status < 0xF) {
153  fmt << "-beta" << status;
154  }
155  }
156 
157  return fmt.str();
158 
159 }
160 
161 version_table_manager::version_table_manager()
162  : compiled(LIB_COUNT, "")
163  , linked(LIB_COUNT, "")
164  , names(LIB_COUNT, "")
165  , features()
166 {
167  SDL_version sdl_version;
168 
169 
170  //
171  // SDL
172  //
173 
174  SDL_VERSION(&sdl_version);
175  compiled[LIB_SDL] = format_version(sdl_version);
176 
177  SDL_GetVersion(&sdl_version);
178  linked[LIB_SDL] = format_version(sdl_version);
179 
180  names[LIB_SDL] = "SDL";
181 
182  //
183  // SDL_image
184  //
185 
186  SDL_IMAGE_VERSION(&sdl_version);
187  compiled[LIB_SDL_IMAGE] = format_version(sdl_version);
188 
189  const SDL_version* sdl_rt_version = IMG_Linked_Version();
190  if(sdl_rt_version) {
191  linked[LIB_SDL_IMAGE] = format_version(*sdl_rt_version);
192  }
193 
194  names[LIB_SDL_IMAGE] = "SDL_image";
195 
196  //
197  // SDL_mixer
198  //
199 
200  SDL_MIXER_VERSION(&sdl_version);
201  compiled[LIB_SDL_MIXER] = format_version(sdl_version);
202 
203  sdl_rt_version = Mix_Linked_Version();
204  if(sdl_rt_version) {
205  linked[LIB_SDL_MIXER] = format_version(*sdl_rt_version);
206  }
207 
208  names[LIB_SDL_MIXER] = "SDL_mixer";
209 
210  //
211  // Boost
212  //
213 
214  compiled[LIB_BOOST] = BOOST_LIB_VERSION;
215  std::replace(compiled[LIB_BOOST].begin(), compiled[LIB_BOOST].end(), '_', '.');
216  names[LIB_BOOST] = "Boost";
217 
218  //
219  // OpenSSL/libcrypto
220  //
221 
222  compiled[LIB_CRYPTO] = format_openssl_version(OPENSSL_VERSION_NUMBER);
223  linked[LIB_CRYPTO] = format_openssl_version(SSLeay());
224  names[LIB_CRYPTO] = "OpenSSL/libcrypto";
225 
226  //
227  // Cairo
228  //
229 
230  compiled[LIB_CAIRO] = CAIRO_VERSION_STRING;
231  linked[LIB_CAIRO] = cairo_version_string();
232  names[LIB_CAIRO] = "Cairo";
233 
234  //
235  // Pango
236  //
237 
238  compiled[LIB_PANGO] = PANGO_VERSION_STRING;
239  linked[LIB_PANGO] = pango_version_string();
240  names[LIB_PANGO] = "Pango";
241 
242  //
243  // Features table.
244  //
245 
246  features.emplace_back(N_("feature^JPG screenshots"));
247 #ifdef SDL_IMAGE_VERSION_ATLEAST
248 #if SDL_IMAGE_VERSION_ATLEAST(2, 0, 2)
249  features.back().enabled = true;
250 #endif
251 #endif
252 
253  features.emplace_back(N_("feature^Lua console completion"));
254 #ifdef HAVE_HISTORY
255  features.back().enabled = true;
256 #endif
257 
258  features.emplace_back(N_("feature^Legacy bidirectional rendering"));
259 #ifdef HAVE_FRIBIDI
260  features.back().enabled = true;
261 #endif
262 
263 #ifdef _X11
264 
265  features.emplace_back(N_("feature^D-Bus notifications back end"));
266 #ifdef HAVE_LIBDBUS
267  features.back().enabled = true;
268 #endif
269 
270 #endif /* _X11 */
271 
272 #ifdef _WIN32
273  // Always compiled in.
274  features.emplace_back(N_("feature^Win32 notifications back end"));
275  features.back().enabled = true;
276 #endif
277 
278 #ifdef __APPLE__
279  // Always compiled in.
280  features.emplace_back(N_("feature^Cocoa notifications back end"));
281  features.back().enabled = true;
282 #endif /* __APPLE__ */
283 }
284 
285 const std::string empty_version = "";
286 
287 } // end anonymous namespace 1
288 
289 std::vector<optional_feature> optional_features_table()
290 {
291  std::vector<optional_feature> res = versions.features;
292 
293  for(std::size_t k = 0; k < res.size(); ++k) {
294  res[k].name = _(res[k].name.c_str());
295  }
296  return res;
297 }
298 
299 const std::string& library_build_version(LIBRARY_ID lib)
300 {
301  if(lib >= LIB_COUNT) {
302  return empty_version;
303  }
304 
305  return versions.compiled[lib];
306 }
307 
308 const std::string& library_runtime_version(LIBRARY_ID lib)
309 {
310  if(lib >= LIB_COUNT) {
311  return empty_version;
312  }
313 
314  return versions.linked[lib];
315 }
316 
317 const std::string& library_name(LIBRARY_ID lib)
318 {
319  if(lib >= LIB_COUNT) {
320  return empty_version;
321  }
322 
323  return versions.names[lib];
324 }
325 
326 namespace {
327 
328 bool strlen_comparator(const std::string& a, const std::string& b)
329 {
330  return a.length() < b.length();
331 }
332 
333 std::size_t max_strlen(const std::vector<std::string>& strs)
334 {
335  const std::vector<std::string>::const_iterator it =
336  std::max_element(strs.begin(), strs.end(), strlen_comparator);
337 
338  return it != strs.end() ? it->length() : 0;
339 }
340 
341 std::string report_heading(const std::string& heading_text)
342 {
343  return heading_text + '\n' + std::string(utf8::size(heading_text), '=') + '\n';
344 }
345 
346 } // end anonymous namespace 2
347 
349 {
350  std::ostringstream o;
351 
352  const std::size_t col2_start = max_strlen(versions.names) + 2;
353  const std::size_t col3_start = max_strlen(versions.compiled) + 1;
354 
355  for(unsigned n = 0; n < LIB_COUNT; ++n)
356  {
357  const std::string& name = versions.names[n];
358  const std::string& compiled = versions.compiled[n];
359  const std::string& linked = versions.linked[n];
360 
361  if(name.empty()) {
362  continue;
363  }
364 
365  o << name << ": ";
366 
367  const std::size_t pos2 = name.length() + 2;
368  if(pos2 < col2_start) {
369  o << std::string(col2_start - pos2, ' ');
370  }
371 
372  o << compiled;
373 
374  if(!linked.empty()) {
375  const std::size_t pos3 = compiled.length() + 1;
376  if(pos3 < col3_start) {
377  o << std::string(col3_start - pos3, ' ');
378  }
379  o << " (runtime " << linked << ")";
380  }
381 
382  o << '\n';
383  }
384 
385  return o.str();
386 }
387 
389 {
390  // Yes, it's for stdout/stderr but we still want the localized version so
391  // that the context prefixes are stripped.
392  const std::vector<optional_feature>& features = optional_features_table();
393 
394  std::size_t col2_start = 0;
395 
396  for(std::size_t k = 0; k < features.size(); ++k)
397  {
398  col2_start = std::max(col2_start, features[k].name.length() + 2);
399  }
400 
401  std::ostringstream o;
402 
403  for(std::size_t k = 0; k < features.size(); ++k)
404  {
405  const optional_feature& f = features[k];
406 
407  o << f.name << ": ";
408 
409  const std::size_t pos2 = f.name.length() + 2;
410  if(pos2 < col2_start) {
411  o << std::string(col2_start - pos2, ' ');
412  }
413 
414  o << (f.enabled ? "yes" : "no") << '\n';
415  }
416 
417  return o.str();
418 }
419 
420 std::string full_build_report()
421 {
422  std::ostringstream o;
423 
424  o << "The Battle for Wesnoth version " << game_config::revision << '\n'
425  << "Running on " << desktop::os_version() << '\n'
426  << '\n'
427  << report_heading("Game paths")
428  << '\n'
429  << "Data dir: " << filesystem::sanitize_path(game_config::path) << '\n'
430  << "User config dir: " << filesystem::sanitize_path(filesystem::get_user_config_dir()) << '\n'
431  << "User data dir: " << filesystem::sanitize_path(filesystem::get_user_data_dir()) << '\n'
432  << "Saves dir: " << filesystem::sanitize_path(filesystem::get_saves_dir()) << '\n'
433  << "Add-ons dir: " << filesystem::sanitize_path(filesystem::get_addons_dir()) << '\n'
434  << "Cache dir: " << filesystem::sanitize_path(filesystem::get_cache_dir()) << '\n'
435  << '\n'
436  << report_heading("Libraries")
437  << '\n'
439  << '\n'
440  << report_heading("Features")
441  << '\n'
443 
444  return o.str();
445 }
446 
447 } // end namespace game_config
std::string library_versions_report()
Produce a plain-text report of library versions suitable for stdout/stderr.
Definition: build_info.cpp:348
Interfaces for manipulating version numbers of engine, add-ons, etc.
std::string optional_features_report()
Produce a plain-text report of features suitable for stdout/stderr.
Definition: build_info.cpp:388
#define a
const char * c_str() const
Definition: tstring.hpp:187
STL namespace.
const std::string & library_build_version(LIBRARY_ID lib)
Retrieve the build-time version number of the given library.
Definition: build_info.cpp:299
std::string get_saves_dir()
#define b
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
const std::string & library_name(LIBRARY_ID lib)
Retrieve the user-visible name for the given library.
Definition: build_info.cpp:317
std::vector< optional_feature > optional_features_table()
Return a localized features table.
Definition: build_info.cpp:289
const t_string name
std::vector< optional_feature > features
Definition: build_info.cpp:54
std::ostringstream wrapper.
Definition: formatter.hpp:38
std::string get_user_data_dir()
Definition: filesystem.cpp:749
std::string path
Definition: game_config.cpp:39
std::string sanitize_path(const std::string &path)
Sanitizes a path to remove references to the user&#39;s name.
Platform identification and version information functions.
std::string get_cache_dir()
Definition: filesystem.cpp:754
std::vector< std::string > linked
Definition: build_info.cpp:53
const std::string revision
mock_party p
Game configuration data as global variables.
Definition: build_info.cpp:46
std::string os_version()
Returns a string with the running OS name and version information.
Definition: version.cpp:121
std::vector< std::string > names
Definition: build_info.cpp:53
const std::string & library_runtime_version(LIBRARY_ID lib)
Retrieve the runtime version number of the given library.
Definition: build_info.cpp:308
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:97
std::string get_user_config_dir()
Definition: filesystem.cpp:720
#define f
std::string get_addons_dir()
mock_char c
static map_location::DIRECTION n
std::vector< std::string > compiled
Definition: build_info.cpp:53
std::string full_build_report()
Produce a bug report-style info dump.
Definition: build_info.cpp:420