Skip to content

Analytics changes with tests. #1730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions analytics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ set(android_SRCS
set(ios_SRCS
src/analytics_ios.mm)

# Source files used by the stub implementation.
set(stub_SRCS
src/analytics_stub.cc)
# Source files used by the desktop / stub implementation.
set(desktop_SRCS
src/analytics_desktop.cc
src/analytics_desktop_dynamic.c)

if(ANDROID)
set(analytics_platform_SRCS
Expand All @@ -86,7 +87,7 @@ elseif(IOS)
"${ios_SRCS}")
else()
set(analytics_platform_SRCS
"${stub_SRCS}")
"${desktop_SRCS}")
endif()

add_library(firebase_analytics STATIC
Expand Down
76 changes: 52 additions & 24 deletions analytics/generate_windows_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
"""Generate stubs and function pointers for Windows SDK"""

import argparse
import hashlib
import os
import re
import sys

HEADER_GUARD_PREFIX = "FIREBASE_ANALYTICS_SRC_WINDOWS_"
INCLUDE_PATH = "src/windows/"
INCLUDE_PATH = "src/"
INCLUDE_PREFIX = "analytics/" + INCLUDE_PATH
COPYRIGHT_NOTICE = """// Copyright 2025 Google LLC
//
Expand All @@ -39,17 +40,29 @@

"""

def generate_function_pointers(header_file_path, output_h_path, output_c_path):

def hash_file(filename):
sha256_hash = hashlib.sha256()
with open(filename, "rb") as file:
while chunk := file.read(4096):
sha256_hash.update(chunk)
return sha256_hash.digest()

def generate_function_pointers(dll_file_path, header_file_path, output_h_path, output_c_path):
"""
Parses a C header file to generate a self-contained header with typedefs,
extern function pointer declarations, and a source file with stub functions,
initialized pointers, and a dynamic loading function for Windows.

Args:
header_file_path (str): The path to the DLL file.
header_file_path (str): The path to the input C header file.
output_h_path (str): The path for the generated C header output file.
output_c_path (str): The path for the generated C source output file.
"""
print(f"Reading DLL file: {dll_file_path}")
dll_hash = hash_file(dll_file_path)

print(f"Reading header file: {header_file_path}")
try:
with open(header_file_path, 'r', encoding='utf-8') as f:
Expand Down Expand Up @@ -83,7 +96,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
return_type = match.group(1).strip()
function_name = match.group(2).strip()
params_str = match.group(3).strip()

cleaned_params_for_decl = re.sub(r'\s+', ' ', params_str) if params_str else ""
stub_name = f"Stub_{function_name}"

Expand All @@ -94,7 +107,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
return_statement = f' return ({return_type})(&g_stub_memory);'
else: # bool, int64_t, etc.
return_statement = " return 1;"

stub_function = (
f"// Stub for {function_name}\n"
f"static {return_type} {stub_name}({params_str}) {{\n"
Expand All @@ -111,7 +124,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):

pointer_init = f"{return_type} (*ptr_{function_name})({cleaned_params_for_decl}) = &{stub_name};"
pointer_initializations.append(pointer_init)

function_details_for_loader.append((function_name, return_type, cleaned_params_for_decl))

print(f"Found {len(pointer_initializations)} functions. Generating output files...")
Expand All @@ -124,12 +137,12 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
f.write(f"#ifndef {header_guard}\n")
f.write(f"#define {header_guard}\n\n")
f.write("#include <stdbool.h> // needed for bool type in pure C\n\n")

f.write("// --- Copied from original header ---\n")
f.write("\n".join(includes) + "\n\n")
f.write("".join(typedefs))
f.write("// --- End of copied section ---\n\n")

f.write("#ifdef __cplusplus\n")
f.write('extern "C" {\n')
f.write("#endif\n\n")
Expand All @@ -139,15 +152,20 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
f.write("\n\n")
f.write("\n".join(macro_definitions))
f.write("\n// clang-format on\n")
f.write("\n\n// --- Dynamic Loader Declaration for Windows ---\n")
f.write("\n// --- Dynamic Loader Declaration for Windows ---\n")
f.write("#if defined(_WIN32)\n")
f.write('#include <windows.h> // For HMODULE\n')
f.write('// Load Google Analytics functions from the given DLL handle into function pointers.\n')
f.write(f'// Returns the number of functions successfully loaded (out of {len(function_details_for_loader)}).\n')
f.write("int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle);\n\n")
f.write('#include <windows.h>\n')
f.write(f'\n// Google Analytics Windows DLL SHA256 hash, to be verified on load.')
f.write(f'\nextern const unsigned char FirebaseAnalytics_WindowsDllHash[{len(dll_hash)}];\n');

f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.')
f.write(f'\n#define FIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT {len(function_details_for_loader)}\n\n')
f.write('// Load Analytics functions from the given DLL handle into function pointers.\n')
f.write(f'// Returns the number of functions successfully loaded (out of\nFIREBASE_ANALYTICS_DYNAMIC_FUNCTION_COUNT).\n')
f.write("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n\n")
f.write('// Reset all function pointers back to stubs.\n')
f.write("void FirebaseAnalytics_UnloadAnalyticsFunctions(void);\n\n")
f.write("#endif // defined(_WIN32)\n")
f.write("void FirebaseAnalytics_UnloadDynamicFunctions(void);\n\n")
f.write("#endif // defined(_WIN32)\n")
f.write("\n#ifdef __cplusplus\n")
f.write("}\n")
f.write("#endif\n\n")
Expand All @@ -159,18 +177,22 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
with open(output_c_path, 'w', encoding='utf-8') as f:
f.write(f"{COPYRIGHT_NOTICE}")
f.write(f"// Generated from {os.path.basename(header_file_path)} by {os.path.basename(sys.argv[0])}\n\n")
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n')
f.write(f'#include "{INCLUDE_PREFIX}{os.path.basename(output_h_path)}"\n\n')
f.write('#include <stddef.h>\n\n')
f.write("// clang-format off\n\n")
f.write("static void* g_stub_memory = NULL;\n\n")
f.write("// clang-format off\n\n")
f.write('// Google Analytics Windows DLL SHA256 hash, to be verified on load.\n')
f.write('const unsigned char FirebaseAnalytics_WindowsDllHash[] = {\n ')
f.write(', '.join(["0x%02x" % s for s in dll_hash]))
f.write('\n};\n\n')
f.write("// --- Stub Function Definitions ---\n")
f.write("\n\n".join(stub_functions))
f.write("\n\n\n// --- Function Pointer Initializations ---\n")
f.write("\n".join(pointer_initializations))
f.write("\n\n// --- Dynamic Loader Function for Windows ---\n")
loader_lines = [
'#if defined(_WIN32)',
'int FirebaseAnalytics_LoadAnalyticsFunctions(HMODULE dll_handle) {',
'int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle) {',
' int count = 0;\n',
' if (!dll_handle) {',
' return count;',
Expand All @@ -188,7 +210,7 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
loader_lines.extend(proc_check)
loader_lines.append('\n return count;')
loader_lines.append('}\n')
loader_lines.append('void FirebaseAnalytics_UnloadAnalyticsFunctions(void) {')
loader_lines.append('void FirebaseAnalytics_UnloadDynamicFunctions(void) {')
for name, ret_type, params in function_details_for_loader:
loader_lines.append(f' ptr_{name} = &Stub_{name};');
loader_lines.append('}\n')
Expand All @@ -203,6 +225,12 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
parser = argparse.ArgumentParser(
description="Generate C stubs and function pointers from a header file."
)
parser.add_argument(
"--windows_dll",
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/analytics_win.dll"),
#required=True,
help="Path to the DLL file to calculate a hash."
)
parser.add_argument(
"--windows_header",
default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"),
Expand All @@ -211,21 +239,21 @@ def generate_function_pointers(header_file_path, output_h_path, output_c_path):
)
parser.add_argument(
"--output_header",
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.h"),
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.h"),
#required=True,
help="Path for the generated output header file."
)
parser.add_argument(
"--output_source",
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_dynamic.c"),
default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.c"),
#required=True,
help="Path for the generated output source file."
)

args = parser.parse_args()

generate_function_pointers(
args.windows_header,
args.output_header,
args.windows_dll,
args.windows_header,
args.output_header,
args.output_source
)
12 changes: 12 additions & 0 deletions analytics/integration_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ else()
)
elseif(MSVC)
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32)
set(ANALYTICS_WINDOWS_DLL "${FIREBASE_CPP_SDK_DIR}/analytics/windows/analytics_win.dll")

# For Windows, check if the Analytics DLL exists, and copy it in if so.
if (EXISTS "${ANALYTICS_WINDOWS_DLL}")
add_custom_command(
TARGET ${integration_test_target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${ANALYTICS_WINDOWS_DLL}"
$<TARGET_FILE_DIR:${integration_test_target_name}>)
else()
message(WARNING "Couldn't find ${ANALYTICS_WINDOWS_DLL}. Analytics will run in stub mode.")
endif()
else()
set(ADDITIONAL_LIBS pthread)
endif()
Expand Down
Loading
Loading