Skip to content

Commit 167f2b2

Browse files
authored
Merge ba7b04a into d299673
2 parents d299673 + ba7b04a commit 167f2b2

16 files changed

+1378
-250
lines changed

analytics/CMakeLists.txt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,15 @@ set(android_SRCS
7474
set(ios_SRCS
7575
src/analytics_ios.mm)
7676

77-
# Source files used by the stub implementation.
78-
set(stub_SRCS
79-
src/analytics_stub.cc)
77+
# Source files used by the desktop / stub implementation.
78+
set(desktop_SRCS
79+
src/analytics_desktop.cc
80+
src/analytics_desktop_dynamic.c)
81+
82+
if(WIN32)
83+
# Add Windows-specific sources for desktop builds.
84+
list(APPEND desktop_SRCS src/analytics_windows.cc)
85+
endif()
8086

8187
if(ANDROID)
8288
set(analytics_platform_SRCS
@@ -86,7 +92,7 @@ elseif(IOS)
8692
"${ios_SRCS}")
8793
else()
8894
set(analytics_platform_SRCS
89-
"${stub_SRCS}")
95+
"${desktop_SRCS}")
9096
endif()
9197

9298
add_library(firebase_analytics STATIC
@@ -98,6 +104,9 @@ set_property(TARGET firebase_analytics PROPERTY FOLDER "Firebase Cpp")
98104
# Set up the dependency on Firebase App.
99105
target_link_libraries(firebase_analytics
100106
PUBLIC firebase_app)
107+
if(WIN32)
108+
target_link_libraries(firebase_analytics PRIVATE Crypt32.lib)
109+
endif()
101110
# Public headers all refer to each other relative to the src/include directory,
102111
# while private headers are relative to the entire C++ SDK directory.
103112
target_include_directories(firebase_analytics

analytics/generate_windows_stubs.py

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
"""Generate stubs and function pointers for Windows SDK"""
1717

1818
import argparse
19+
import hashlib
1920
import os
2021
import re
2122
import sys
2223

2324
HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_"
24-
INCLUDE_PATH = "src/windows/"
25+
INCLUDE_PATH = "src/"
2526
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
2627
COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC
2728
//
@@ -39,17 +40,50 @@
3940
4041
"""
4142

42-
def generate_function_pointers(header_file_path, output_h_path, output_c_path):
43+
44+
def hash_file(filename):
45+
sha256_hash = hashlib.sha256()
46+
with open(filename, "rb") as file:
47+
while chunk := file.read(4096):
48+
sha256_hash.update(chunk)
49+
return sha256_hash.digest()
50+
51+
def generate_function_pointers(dll_file_path, header_file_path, output_h_path, output_c_path):
4352
"""
4453
Parses a C header file to generate a self-contained header with typedefs,
4554
extern function pointer declarations, and a source file with stub functions,
4655
initialized pointers, and a dynamic loading function for Windows.
4756
4857
Args:
58+
dll_file_path (str): The path to the DLL file.
4959
header_file_path (str): The path to the input C header file.
5060
output_h_path (str): The path for the generated C header output file.
5161
output_c_path (str): The path for the generated C source output file.
5262
"""
63+
print(f"Reading DLL file: {dll_file_path}")
64+
dll_hash = hash_file(dll_file_path) # This is binary
65+
66+
# --- Manage known hashes ---
67+
hash_file_path = os.path.join(os.path.dirname(dll_file_path), "known_dll_hashes.txt")
68+
known_hex_hashes = []
69+
try:
70+
with open(hash_file_path, 'r') as f:
71+
for line in f:
72+
known_hex_hashes.append(line.strip())
73+
except FileNotFoundError:
74+
print(f"Info: '{hash_file_path}' not found, will be created.")
75+
pass # File doesn't exist, list remains empty
76+
77+
current_dll_hex_hash = dll_hash.hex()
78+
if current_dll_hex_hash not in known_hex_hashes:
79+
known_hex_hashes.append(current_dll_hex_hash)
80+
81+
with open(hash_file_path, 'w') as f:
82+
for hex_hash in known_hex_hashes:
83+
f.write(hex_hash + '\n')
84+
print(f"Updated known hashes in: {hash_file_path}")
85+
# --- End of manage known hashes ---
86+
5387
print(f"Reading header file: {header_file_path}")
5488
try:
5589
with open(header_file_path, 'r', encoding='utf-8') as f:
@@ -64,7 +98,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
6498
includes = re.findall(r"#include\s+<.*?>", header_content)
6599

66100
# Find all typedefs, including their documentation comments
67-
typedefs = re.findall(r"/\*\*(?:[\s\S]*?)\*/\s*typedef[\s\S]*?;\s*", header_content)
101+
typedefs = re.findall(r"(?:/\*\*(?:[\s\S]*?)\*/\s*)?typedef[\s\S]*?;\s*", header_content)
68102

69103
# --- Extract function prototypes ---
70104
function_pattern = re.compile(
@@ -83,18 +117,18 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
83117
return_type = match.group(1).strip()
84118
function_name = match.group(2).strip()
85119
params_str = match.group(3).strip()
86-
120+
87121
cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else ""
88122
stub_name = f"Stub_{function_name}"
89123

90124
# Generate return statement for the stub
91125
if "void" in return_type:
92126
return_statement = " // No return value."
93127
elif "*" in return_type:
94-
return_statement = f' return ({return_type})(&g_stub_memory);'
128+
return_statement = f' return ({return_type})(&g_stub_memory[0]);'
95129
else: # bool, int64_t, etc.
96130
return_statement = " return 1;"
97-
131+
98132
stub_function = (
99133
f"// Stub for {function_name}\n"
100134
f"static {return_type} {stub_name}({params_str}) {{\n"
@@ -111,7 +145,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
111145

112146
pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};"
113147
pointer_initializations.append(pointer_init)
114-
148+
115149
function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl))
116150

117151
print(f"Found {len(pointer_initializations)} functions. Generating output files...")
@@ -123,13 +157,14 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
123157
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
124158
f.write(f"#ifndef {header_guard}\n")
125159
f.write(f"#define {header_guard}\n\n")
160+
f.write(f"#define ANALYTICS_API // filter out from header copy\n\n")
126161
f.write("#include <stdbool.h> // needed for bool type in pure C\n\n")
127-
162+
128163
f.write("// --- Copied from original header ---\n")
129164
f.write("\n".join(includes) + "\n\n")
130165
f.write("".join(typedefs))
131166
f.write("// --- End of copied section ---\n\n")
132-
167+
133168
f.write("#ifdef __cplusplus\n")
134169
f.write('extern "C" {\n')
135170
f.write("#endif\n\n")
@@ -139,15 +174,21 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
139174
f.write("\n\n")
140175
f.write("\n".join(macro_definitions))
141176
f.write("\n// clang-format on\n")
142-
f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n")
143-
f.write("#if defined(_WIN32)\n")
144-
f.write('#include <windows.h> // For HMODULE\n')
145-
f.write('// Load Google Analytics functions from the given DLL handle into function pointers.\n')
146-
f.write(f'// Returns the number of functions successfully loaded (out of {len(function_details_for_loader)}).\n')
147-
f.write("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n\n")
177+
f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.')
178+
f.write('\nextern const int FirebaseAnalytics_DynamicFunctionCount;\n');
179+
f.write("\n// --- Dynamic Loader Declaration for Windows ---\n")
180+
f.write("#if defined(_WIN32)\n\n")
181+
f.write('#include <windows.h>\n')
182+
f.write('\n// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n')
183+
f.write('extern const char* FirebaseAnalytics_KnownWindowsDllHashes[];\n')
184+
f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n')
185+
f.write('extern const int FirebaseAnalytics_KnownWindowsDllHashCount;\n\n')
186+
f.write('// Load Analytics functions from the given DLL handle into function pointers.\n')
187+
f.write(f'// Returns the number of functions successfully loaded.\n')
188+
f.write("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n\n")
148189
f.write('// Reset all function pointers back to stubs.\n')
149-
f.write("void FirebaseAnalytics_UnloadAnalyticsFunctions(void);\n\n")
150-
f.write("#endif // defined(_WIN32)\n")
190+
f.write("void FirebaseAnalytics_UnloadDynamicFunctions(void);\n\n")
191+
f.write("#endif // defined(_WIN32)\n")
151192
f.write("\n#ifdef __cplusplus\n")
152193
f.write("}\n")
153194
f.write("#endif\n\n")
@@ -159,18 +200,34 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
159200
with open(output_c_path, 'w', encoding='utf-8') as f:
160201
f.write(f"{COPYRIGHT_NOTICE}")
161202
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
162-
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n')
203+
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n\n')
163204
f.write('#include <stddef.h>\n\n')
164-
f.write("// clang-format off\n\n")
165-
f.write("static void* g_stub_memory = NULL;\n\n")
166-
f.write("// --- Stub Function Definitions ---\n")
205+
f.write("// A nice big chunk of stub memory that can be returned by stubbed Create methods.\n")
206+
f.write("static char g_stub_memory[256] = {0};\n\n")
207+
f.write("// clang-format off\n")
208+
f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.')
209+
f.write(f'\nconst int FirebaseAnalytics_DynamicFunctionCount = {len(function_details_for_loader)};\n\n');
210+
f.write("#if defined(_WIN32)\n")
211+
f.write('// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n')
212+
f.write('const char* FirebaseAnalytics_KnownWindowsDllHashes[] = {\n')
213+
if known_hex_hashes:
214+
for i, hex_hash in enumerate(known_hex_hashes):
215+
f.write(f' "{hex_hash}"')
216+
if i < len(known_hex_hashes) - 1:
217+
f.write(',')
218+
f.write('\n')
219+
f.write('};\n\n')
220+
f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n')
221+
f.write(f'const int FirebaseAnalytics_KnownWindowsDllHashCount = {len(known_hex_hashes)};\n')
222+
f.write("#endif // defined(_WIN32)\n")
223+
f.write("\n// --- Stub Function Definitions ---\n")
167224
f.write("\n\n".join(stub_functions))
168225
f.write("\n\n\n// --- Function Pointer Initializations ---\n")
169226
f.write("\n".join(pointer_initializations))
170227
f.write("\n\n// --- Dynamic Loader Function for Windows ---\n")
171228
loader_lines = [
172229
'#if defined(_WIN32)',
173-
'int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) {',
230+
'int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle) {',
174231
' int count = 0;\n',
175232
' if (!dll_handle) {',
176233
' return count;',
@@ -188,7 +245,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
188245
loader_lines.extend(proc_check)
189246
loader_lines.append('\n return count;')
190247
loader_lines.append('}\n')
191-
loader_lines.append('void FirebaseAnalytics_UnloadAnalyticsFunctions(void) {')
248+
loader_lines.append('void FirebaseAnalytics_UnloadDynamicFunctions(void) {')
192249
for name, ret_type, params in function_details_for_loader:
193250
loader_lines.append(f' ptr_{name} = &Stub_{name};');
194251
loader_lines.append('}\n')
@@ -203,29 +260,31 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
203260
parser = argparse.ArgumentParser(
204261
description="Generate C stubs and function pointers from a header file."
205262
)
263+
parser.add_argument(
264+
"--windows_dll",
265+
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/analytics_win.dll"),
266+
help="Path to the DLL file to calculate a hash."
267+
)
206268
parser.add_argument(
207269
"--windows_header",
208270
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"),
209-
#required=True,
210271
help="Path to the input C header file."
211272
)
212273
parser.add_argument(
213274
"--output_header",
214-
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"),
215-
#required=True,
275+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.h"),
216276
help="Path for the generated output header file."
217277
)
218278
parser.add_argument(
219279
"--output_source",
220-
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"),
221-
#required=True,
280+
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.c"),
222281
help="Path for the generated output source file."
223282
)
224-
225283
args = parser.parse_args()
226-
284+
227285
generate_function_pointers(
228-
args.windows_header,
229-
args.output_header,
286+
args.windows_dll,
287+
args.windows_header,
288+
args.output_header,
230289
args.output_source
231290
)

analytics/integration_test/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,16 @@ else()
211211
)
212212
elseif(MSVC)
213213
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32)
214+
set(ANALYTICS_WINDOWS_DLL "${FIREBASE_CPP_SDK_DIR}/analytics/windows/analytics_win.dll")
215+
216+
# For Windows, check if the Analytics DLL exists, and copy it in if so.
217+
if (EXISTS "${ANALYTICS_WINDOWS_DLL}")
218+
add_custom_command(
219+
TARGET ${integration_test_target_name} POST_BUILD
220+
COMMAND ${CMAKE_COMMAND} -E copy
221+
"${ANALYTICS_WINDOWS_DLL}"
222+
$<TARGET_FILE_DIR:${integration_test_target_name}>)
223+
endif()
214224
else()
215225
set(ADDITIONAL_LIBS pthread)
216226
endif()

0 commit comments

Comments
 (0)