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