• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 13664188314

04 Mar 2025 10:28PM UTC coverage: 85.268% (+0.2%) from 85.042%
13664188314

Pull #3328

github

web-flow
Merge cb131d2bf into e2557bbe2
Pull Request #3328: NCrystal becomes runtime rather than buildtime dependency

87 of 99 new or added lines in 6 files covered. (87.88%)

1 existing line in 1 file now uncovered.

51275 of 60134 relevant lines covered (85.27%)

31499527.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.94
/src/ncrystal_load.cpp
1
#include "openmc/ncrystal_load.h"
2

3
#include <cctype>  // for isspace
4
#include <cstdlib> // for strtoul
5
#include <memory>  // for shared_ptr
6
#include <mutex>   // for mutex, lock_guard
7
#include <string>
8

9
#include <fmt/core.h>
10
#include <stdio.h> // for popen, pclose
11

12
#include "openmc/error.h"
13

14
#ifdef _WIN32
15
#ifndef WIN32_LEAN_AND_MEAN
16
#define WIN32_LEAN_AND_MEAN
17
#endif
18
#include <windows.h> // for LoadLibrary, GetProcAddress
19
#else
20
#include <dlfcn.h> // for dlopen, dlsym, dlerror
21
#endif
22

23
namespace openmc {
24
namespace {
25

26
struct NCrystalConfig {
27
  std::string shlibpath;
28
  unsigned long intversion = 0;
29
  std::string symbol_namespace;
30
};
31

32
NCrystalConfig query_ncrystal_config()
16✔
33
{
34
#ifdef _WIN32
35
  FILE* pipe = _popen("ncrystal-config --show "
36
                      "intversion shlibpath namespace",
37
    "r");
38
#else
39
  FILE* pipe = popen("ncrystal-config --show "
16✔
40
                     "intversion shlibpath namespace 2>/dev/null",
41
    "r");
42
#endif
43
  if (!pipe)
16✔
NEW
44
    return {}; // failure
×
45
  auto readLine = [pipe](std::string& tgt) -> bool {
48✔
46
    // Read line and discard trailing whitespace (including newline chars).
47
    char buffer[4096];
48
    if (fgets(buffer, sizeof(buffer), pipe) == NULL)
48✔
NEW
49
      return false;
×
50
    tgt = buffer;
48✔
51
    while (!tgt.empty() && std::isspace(tgt.back()))
96✔
52
      tgt.pop_back();
48✔
53
    return true;
48✔
54
  };
16✔
55
  auto parseIntVersion = [](const std::string& s) {
16✔
56
    char* str_end = nullptr;
16✔
57
    unsigned long v = std::strtoul(s.c_str(), &str_end, 10);
16✔
58
    return (v >= 2002000 && v < 999999999 && str_end == s.c_str() + s.size())
16✔
59
             ? v
32✔
60
             : 0;
16✔
61
  };
62

63
  NCrystalConfig res;
16✔
64
  bool all_ok(true);
16✔
65
  if (!readLine(res.shlibpath) ||
16✔
66
      !(res.intversion = parseIntVersion(res.shlibpath)) ||
16✔
67
      !readLine(res.shlibpath) || res.shlibpath.empty() ||
48✔
68
      !readLine(res.symbol_namespace)) {
16✔
NEW
69
    res.intversion = 0; // failure
×
70
  }
71

72
#ifdef _WIN32
73
  auto returnCode = _pclose(pipe);
74
#else
75
  auto returnCode = pclose(pipe);
16✔
76
#endif
77
  if (returnCode == 0 && res.intversion >= 2002000)
16✔
78
    return res;
16✔
NEW
79
  return {}; // failure
×
80
}
16✔
81

82
struct NCrystalAPIDB {
83
  std::mutex mtx;
84
  std::shared_ptr<const NCrystalAPI> api;
85
  using FctSignature = void* (*)(int);
86
  FctSignature ncrystal_access_virtapi_fct = nullptr;
87
};
88

89
void* load_virtapi_raw(unsigned interface_id, NCrystalAPIDB& db)
16✔
90
{
91
  if (!db.ncrystal_access_virtapi_fct) {
16✔
92
    auto cfg = query_ncrystal_config();
16✔
93
    if (!(cfg.intversion >= 4001000)) {
16✔
94
      // This is the most likely error message people will see:
NEW
95
      fatal_error("Could not locate a functioning and recent enough"
×
96
                  " NCrystal installation (required since geometry"
97
                  " contains NCrystal materials).");
98
    }
99
#ifdef _WIN32
100
    auto handle = LoadLibrary(cfg.shlibpath.c_str());
101
#else
102
    dlerror(); // clear previous errors
16✔
103
    void* handle = dlopen(cfg.shlibpath.c_str(), RTLD_LOCAL | RTLD_LAZY);
16✔
104
#endif
105
    if (!handle)
16✔
NEW
106
      fatal_error("Loading of the NCrystal library failed");
×
107

108
    std::string symbol =
109
      fmt::format("ncrystal{}_access_virtual_api", cfg.symbol_namespace);
13✔
110

111
#ifdef _WIN32
112
    void* addr = (void*)(intptr_t)GetProcAddress(handle, symbol.c_str());
113
    if (!addr)
114
      fatal_error("GetProcAddress("
115
                  "ncrystal_access_virtual_api) failed");
116
#else
117
    dlerror(); // clear previous errors
16✔
118
    void* addr = dlsym(handle, symbol.c_str());
16✔
119
    if (!addr)
16✔
NEW
120
      fatal_error("dlsym(ncrystal_access_virtual_api) failed");
×
121
#endif
122
    db.ncrystal_access_virtapi_fct =
16✔
123
      reinterpret_cast<NCrystalAPIDB::FctSignature>(addr);
16✔
124
  }
16✔
125

126
  void* result = (*db.ncrystal_access_virtapi_fct)(interface_id);
16✔
127
  if (!result)
16✔
NEW
128
    fatal_error("NCrystal installation does not support required interface.");
×
129

130
  return result;
16✔
131
}
132

133
NCrystalAPIDB& get_ncrystal_api_db()
16✔
134
{
135
  static NCrystalAPIDB db;
16✔
136
  return db;
16✔
137
}
138
} // namespace
139

140
std::shared_ptr<const NCrystalAPI> load_ncrystal_api()
16✔
141
{
142
  auto& db = get_ncrystal_api_db();
16✔
143
  std::lock_guard<std::mutex> lock(db.mtx);
16✔
144
  if (!db.api) {
16✔
145
    void* raw_api = load_virtapi_raw(NCrystalAPI::interface_id, db);
16✔
146
    if (!raw_api)
16✔
NEW
147
      fatal_error("Problems loading NCrystal.");
×
148
    db.api = *reinterpret_cast<std::shared_ptr<const NCrystalAPI>*>(raw_api);
16✔
149
  }
150
  return db.api;
32✔
151
}
16✔
152
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc