Skip to content

Commit 544a544

Browse files
author
steven varga
committed
Merge branch 'staging' into release
2 parents b8f8dfc + 84a8ddd commit 544a544

20 files changed

Lines changed: 771 additions & 594 deletions

.github/workflows/package.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,45 @@ jobs:
228228
build/h5cpp-compiler-*.exe
229229
build/h5cpp-compiler-*.pkg
230230
231+
musl:
232+
name: musl-static / x86_64
233+
runs-on: ubuntu-24.04
234+
if: startsWith(github.ref, 'refs/tags/')
235+
236+
steps:
237+
- name: Checkout
238+
uses: actions/checkout@v6
239+
with:
240+
fetch-depth: 0
241+
242+
- name: Resolve version from tag
243+
shell: bash
244+
run: |
245+
ref="${GITHUB_REF_NAME}"
246+
echo "H5CPP_VERSION=${ref#v}" >> "$GITHUB_ENV"
247+
248+
- name: Build musl static binary
249+
shell: bash
250+
run: |
251+
set -euxo pipefail
252+
docker build -f Dockerfile.musl -t h5cpp-musl .
253+
mkdir -p dist
254+
docker create --name extract h5cpp-musl
255+
docker cp extract:/src/build/h5cpp "dist/h5cpp"
256+
docker rm extract
257+
chmod +x "dist/h5cpp"
258+
tar czf "dist/h5cpp-compiler-${H5CPP_VERSION}-Linux-musl-x86_64.tar.gz" -C dist h5cpp
259+
260+
- name: Upload artifact
261+
uses: actions/upload-artifact@v7
262+
with:
263+
name: h5cpp-compiler-musl
264+
if-no-files-found: error
265+
path: dist/*.tar.gz
266+
231267
publish:
232268
name: Publish Release
233-
needs: package
269+
needs: [package, musl]
234270
runs-on: ubuntu-latest
235271
if: startsWith(github.ref, 'refs/tags/')
236272

@@ -261,7 +297,7 @@ jobs:
261297
# Collect installer artifacts only. Defensive against stray
262298
# CMake-generated files (e.g. h5cpp-compiler-dev.sln on Windows).
263299
shopt -s globstar nullglob
264-
files=( artifacts/**/*.deb artifacts/**/*.rpm artifacts/**/*.exe artifacts/**/*.pkg )
300+
files=( artifacts/**/*.deb artifacts/**/*.rpm artifacts/**/*.exe artifacts/**/*.pkg artifacts/**/*.tar.gz )
265301
if (( ${#files[@]} == 0 )); then
266302
echo "no installer artifacts found under artifacts/" >&2
267303
exit 1

Dockerfile.musl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Alpine edge with musl — fully static h5cpp-compiler build
2+
FROM docker.io/library/alpine:edge
3+
4+
RUN apk update && apk add --no-cache \
5+
build-base \
6+
cmake \
7+
ninja \
8+
llvm20-dev \
9+
llvm20-static \
10+
clang20-dev \
11+
clang20-static \
12+
zlib-dev \
13+
zlib-static \
14+
zstd-dev \
15+
zstd-static \
16+
libxml2-dev \
17+
libxml2-static \
18+
libffi-dev \
19+
linux-headers
20+
21+
# Alpine's llvm20-dev / clang20-dev CMake exports reference every target LLVM
22+
# can build, but the -static subpackage only ships the libraries needed for
23+
# normal linking. We create empty archives for the missing testing libs and
24+
# downgrade the existence-check FATAL_ERROR to WARNING so configure succeeds.
25+
RUN for lib in LLVMTestingAnnotations LLVMTestingSupport llvm_gtest llvm_gtest_main LTO LLVMgold LLVM Remarks; do \
26+
ar rcs /usr/lib/llvm20/lib/lib${lib}.a; \
27+
done && \
28+
for f in /usr/lib/llvm20/lib/cmake/llvm/LLVMExports.cmake \
29+
/usr/lib/llvm20/lib/cmake/llvm/LLVMExports-release.cmake \
30+
/usr/lib/llvm20/lib/cmake/clang/ClangTargets.cmake \
31+
/usr/lib/llvm20/lib/cmake/clang/ClangTargets-release.cmake; do \
32+
[ -f "$f" ] && sed -i 's/message(FATAL_ERROR/message(WARNING/g' "$f"; \
33+
done
34+
35+
WORKDIR /src
36+
COPY . .
37+
38+
RUN cmake -S . -B build -G Ninja \
39+
-DCMAKE_BUILD_TYPE=Release \
40+
-DH5CPP_STATIC_LINK_LLVM=ON \
41+
-DCMAKE_EXE_LINKER_FLAGS="-static" \
42+
-DLLVM_DIR=/usr/lib/llvm20/lib/cmake/llvm \
43+
-DClang_DIR=/usr/lib/llvm20/lib/cmake/clang
44+
45+
RUN cmake --build build --parallel
46+
47+
# Verify it is static
48+
RUN file build/h5cpp && (ldd build/h5cpp 2>/dev/null || echo "Not a dynamic executable (good)")

src/consumer.hpp

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
template <typename Producer> class H5TemplateCallback : public clang::ast_matchers::MatchFinder::MatchCallback {
2121
public :
2222

23-
H5TemplateCallback(const std::string& path){
24-
io.open( path );
23+
H5TemplateCallback(const std::string& path) : path_(path) {
2524
producer.file_begin();
2625
}
2726

2827
~H5TemplateCallback(){
2928
producer.file_end();
29+
io.open(path_);
3030
io << producer;
3131
io.close();
3232
}
@@ -56,6 +56,18 @@ public :
5656
if( tier == utils::tier::pod ){
5757
// --- tier 1: standard register_struct emission ---
5858
topological_order( node );
59+
// collect header files for all records before emitting preamble
60+
if( Result.Context ) {
61+
for( const auto& item : store ) {
62+
if( item.first == utils::type::record ) {
63+
auto rec = utils::as<const clang::CXXRecordDecl*>( item.second );
64+
std::string hdr = get_header_name(rec, Result.Context->getSourceManager());
65+
if( !hdr.empty() )
66+
producer.add_include(hdr);
67+
}
68+
}
69+
}
70+
5971
producer.template_decl( rn, doc, alias, version );
6072

6173
std::string var, type;
@@ -74,6 +86,11 @@ public :
7486
break;
7587
case utils::type::record:
7688
re = utils::as<const clang::CXXRecordDecl*>( node.second );
89+
if( Result.Context ) {
90+
std::string hdr = get_header_name(re, Result.Context->getSourceManager());
91+
if( !hdr.empty() )
92+
producer.add_include(hdr);
93+
}
7794
var = producer.record_decl( utils::type_name( re ) );
7895
for(clang::FieldDecl* fld: re->fields() ){
7996
// Issue #32: skip fields annotated with [[h5::ignore]]
@@ -98,7 +115,7 @@ public :
98115
default:
99116
break;
100117
}
101-
store.pop();
118+
store.pop_front();
102119
}
103120
producer.type_release();
104121
producer.return_type( var );
@@ -150,6 +167,11 @@ public :
150167
compress_algo = compress_strs.empty() ? "gzip" : compress_strs[0];
151168
}
152169

170+
if (Result.Context) {
171+
std::string hdr = get_header_name(node, Result.Context->getSourceManager());
172+
if (!hdr.empty())
173+
producer.add_include(hdr);
174+
}
153175
producer.scatter_type(rn, fields, chunk_size, compress_algo, compress_level, doc, alias, version, on_missing);
154176
}
155177
}
@@ -160,7 +182,7 @@ public :
160182
topological_order( utils::as<const clang::QualType>( fld ) );
161183
auto it = unique.insert( node );
162184
if( it.second )
163-
store.push( {utils::type::record, node} );
185+
store.push_back( {utils::type::record, node} );
164186
}
165187

166188
void topological_order(const clang::QualType& qt){
@@ -173,7 +195,7 @@ public :
173195
topological_order( ar->getElementType() );
174196
it = unique.insert( ar );
175197
if( it.second )
176-
store.push({utils::type::array, ar});
198+
store.push_back({utils::type::array, ar});
177199
break;
178200

179201
case utils::type::record:
@@ -184,8 +206,23 @@ public :
184206
}
185207
}
186208

209+
static std::string get_header_name(const clang::CXXRecordDecl* decl, const clang::SourceManager& sm){
210+
if( !decl ) return "";
211+
clang::SourceLocation loc = decl->getLocation();
212+
if( !loc.isValid() ) return "";
213+
llvm::StringRef path = sm.getFilename(loc);
214+
if( path.size() < 3 ) return "";
215+
if( !(path.ends_with(".h") || path.ends_with(".hpp") || path.ends_with(".hxx") || path.ends_with(".hh")) )
216+
return "";
217+
size_t pos = path.rfind('/');
218+
if( pos == llvm::StringRef::npos )
219+
return path.str();
220+
return path.substr(pos + 1).str();
221+
}
222+
187223
std::ofstream io;
224+
std::string path_;
188225
Producer producer;
189226
std::set<const void*> unique, nodes;
190-
std::queue<std::pair<utils::type, const void*>> store;
227+
std::deque<std::pair<utils::type, const void*>> store;
191228
};

src/h5cpp.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ clang::ast_matchers::StatementMatcher h5templateMatcher = clang::ast_matchers::c
3636
clang::ast_matchers::functionDecl( clang::ast_matchers::allOf(
3737
clang::ast_matchers::eachOf(
3838
clang::ast_matchers::hasName("h5::write"), clang::ast_matchers::hasName("h5::create"), clang::ast_matchers::hasName("h5::read"), clang::ast_matchers::hasName("h5::append"),
39-
clang::ast_matchers::hasName("h5::awrite"), clang::ast_matchers::hasName("h5::acreate"), clang::ast_matchers::hasName("h5::aread")
39+
clang::ast_matchers::hasName("h5::awrite"), clang::ast_matchers::hasName("h5::acreate"), clang::ast_matchers::hasName("h5::aread"),
40+
clang::ast_matchers::hasName("h5::scatter"), clang::ast_matchers::hasName("h5::gather")
4041
),
4142
clang::ast_matchers::hasTemplateArgument(0, clang::ast_matchers::refersToType( clang::ast_matchers::qualType( clang::ast_matchers::eachOf(
4243
clang::ast_matchers::hasDeclaration( clang::ast_matchers::cxxRecordDecl(clang::ast_matchers::isStruct()).bind("cxxRecordDecl")),
@@ -128,6 +129,17 @@ int main(int argc, const char **argv) {
128129
clang::tooling::ClangTool Tool(OptionsParser.getCompilations(),
129130
OptionsParser.getSourcePathList());
130131

132+
// During the AST-scan pass that produces generated.h, the user's
133+
// H5CPP_REGISTER_STRUCT macros haven't fired yet (generated.h is the
134+
// empty virtual stub mapped below). The h5cpp headers gate their
135+
// "storage_representation_v<T> == unsupported" static_asserts on
136+
// H5CPP_BUILDING_TYPE_INFO so they don't fire in this bootstrap context;
137+
// they remain active for regular user compilation where generated.h exists.
138+
Tool.appendArgumentsAdjuster(
139+
clang::tooling::getInsertArgumentAdjuster(
140+
"-DH5CPP_BUILDING_TYPE_INFO",
141+
clang::tooling::ArgumentInsertPosition::BEGIN));
142+
131143
// Issue #32: rewrite [[h5::xxx(...)]] → [[clang::annotate("h5::xxx", ...)]]
132144
// for each source path before Clang sees it.
133145
std::vector<std::string> _h5_attr_storage;
@@ -140,6 +152,18 @@ int main(int argc, const char **argv) {
140152
pb_attr_translator::install_virtual_files(
141153
Tool, OptionsParser.getSourcePathList(), _pb_attr_storage);
142154

155+
// Issue #34 follow-up: break the chicken-and-egg between the source's
156+
// `#include "generated.h"` and the consumer (which writes the file at
157+
// destructor time, per 76c38d8). Without this, clang's preprocessor errors
158+
// out before the AST matchers ever fire and no output is produced.
159+
// Install an empty virtual header at OutputFile; the real content overwrites
160+
// the on-disk file when the consumer destructs.
161+
std::string _empty_generated_storage;
162+
if (!OutputFile.empty()) {
163+
_empty_generated_storage = "#pragma once\n";
164+
Tool.mapVirtualFile(OutputFile, _empty_generated_storage);
165+
}
166+
143167
std::string work_path = OutputFile;
144168
if (CheckMode) {
145169
work_path = OutputFile + ".h5cpp-check";

src/producer.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ struct Producer {
7272
static_cast<Derived*>(this)->type_release_impl();
7373
}
7474

75+
void add_include(const std::string& path){
76+
static_cast<Derived*>(this)->add_include_impl(path);
77+
}
78+
7579
// Issue #32: scatter/gather emission for tier-2 types.
7680
struct scatter_field_t {
7781
std::string cpp_name; // C++ field name

0 commit comments

Comments
 (0)