Skip to content

Commit e90b2fd

Browse files
feat: add function to register an array of strings in env
1 parent a3134b8 commit e90b2fd

4 files changed

Lines changed: 119 additions & 24 deletions

File tree

includes/core.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ void instrument_hooks_set_feature(instrument_hooks_feature_t feature,
6666
uint8_t instrument_hooks_set_environment(InstrumentHooks*,
6767
const char* section_name,
6868
const char* key, const char* value);
69+
uint8_t instrument_hooks_set_environment_list(InstrumentHooks*,
70+
const char* section_name,
71+
const char* key,
72+
const char* const* values,
73+
uint32_t count);
6974
uint8_t instrument_hooks_write_environment(InstrumentHooks*, uint32_t pid);
7075

7176
// Header functions that will be inlined. This can be used by languages that

scripts/stub.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ uint8_t instrument_hooks_set_environment(InstrumentHooks* hooks,
5252
return 0;
5353
}
5454

55+
uint8_t instrument_hooks_set_environment_list(InstrumentHooks* hooks,
56+
const char* section_name,
57+
const char* key,
58+
const char* const* values,
59+
uint32_t count) {
60+
return 0;
61+
}
62+
5563
uint8_t instrument_hooks_write_environment(InstrumentHooks* hooks,
5664
uint32_t pid) {
5765
return 0;

src/c.zig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,27 @@ pub export fn instrument_hooks_set_environment(
121121
return 1;
122122
}
123123

124+
pub export fn instrument_hooks_set_environment_list(
125+
hooks: ?*InstrumentHooks,
126+
section_name: [*c]const u8,
127+
key: [*c]const u8,
128+
values: [*c]const [*c]const u8,
129+
count: u32,
130+
) u8 {
131+
if (section_name == null or key == null or values == null) return 1;
132+
if (hooks) |h| {
133+
const slices = allocator.alloc([]const u8, count) catch return 1;
134+
defer allocator.free(slices);
135+
for (0..count) |i| {
136+
if (values[i] == null) return 1;
137+
slices[i] = std.mem.span(values[i]);
138+
}
139+
h.environment.setIntegrationEnvironmentList(std.mem.span(section_name), std.mem.span(key), slices) catch return 1;
140+
return 0;
141+
}
142+
return 1;
143+
}
144+
124145
pub export fn instrument_hooks_write_environment(hooks: ?*InstrumentHooks, pid: u32) u8 {
125146
if (hooks) |h| {
126147
return h.environment.writeEnvironment(pid);

src/environment/root.zig

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@ const linked_libraries = @import("linked_libraries/root.zig");
55

66
extern "c" fn getenv(name: [*:0]const u8) ?[*:0]const u8;
77

8+
/// A value in the integration environment: either a single string or a list of strings.
9+
pub const EnvironmentValue = union(enum) {
10+
string: []const u8,
11+
list: []const []const u8,
12+
13+
pub fn jsonStringify(self: @This(), jw: anytype) !void {
14+
switch (self) {
15+
.string => |s| try jw.write(s),
16+
.list => |l| try jw.write(l),
17+
}
18+
}
19+
};
20+
821
/// Self-reported environment information provided by integrations (e.g. compiler version, runtime details).
9-
const IntegrationEnvironmentEntries = std.json.ArrayHashMap([]const u8);
22+
const IntegrationEnvironmentEntries = std.json.ArrayHashMap(EnvironmentValue);
1023
const IntegrationEnvironmentMap = std.json.ArrayHashMap(IntegrationEnvironmentEntries);
1124

1225
const LinkedLibrariesMap = std.json.ArrayHashMap(linked_libraries.LibraryEntry);
@@ -36,7 +49,7 @@ pub const Environment = struct {
3649
var entry_it = int_entry.value_ptr.map.iterator();
3750
while (entry_it.next()) |kv| {
3851
self.allocator.free(kv.key_ptr.*);
39-
self.allocator.free(kv.value_ptr.*);
52+
self.freeEnvironmentValue(kv.value_ptr.*);
4053
}
4154
int_entry.value_ptr.map.deinit(self.allocator);
4255
self.allocator.free(int_entry.key_ptr.*);
@@ -53,6 +66,33 @@ pub const Environment = struct {
5366
}
5467

5568
pub fn setIntegrationEnvironment(self: *Self, integration_name: []const u8, key: []const u8, value: []const u8) !void {
69+
try self.setIntegrationEnvironmentValue(integration_name, key, .{ .string = try self.allocator.dupe(u8, value) });
70+
}
71+
72+
pub fn setIntegrationEnvironmentList(self: *Self, integration_name: []const u8, key: []const u8, values: []const []const u8) !void {
73+
const duped = try self.allocator.alloc([]const u8, values.len);
74+
var i: usize = 0;
75+
errdefer {
76+
for (duped[0..i]) |item| self.allocator.free(item);
77+
self.allocator.free(duped);
78+
}
79+
while (i < values.len) : (i += 1) {
80+
duped[i] = try self.allocator.dupe(u8, values[i]);
81+
}
82+
try self.setIntegrationEnvironmentValue(integration_name, key, .{ .list = duped });
83+
}
84+
85+
fn freeEnvironmentValue(self: *Self, val: EnvironmentValue) void {
86+
switch (val) {
87+
.string => |s| self.allocator.free(s),
88+
.list => |l| {
89+
for (l) |item| self.allocator.free(item);
90+
self.allocator.free(l);
91+
},
92+
}
93+
}
94+
95+
fn setIntegrationEnvironmentValue(self: *Self, integration_name: []const u8, key: []const u8, value: EnvironmentValue) !void {
5696
const int_gop = try self.data.integration_environment.map.getOrPut(self.allocator, integration_name);
5797
if (!int_gop.found_existing) {
5898
int_gop.key_ptr.* = try self.allocator.dupe(u8, integration_name);
@@ -61,11 +101,11 @@ pub const Environment = struct {
61101

62102
const entry_gop = try int_gop.value_ptr.map.getOrPut(self.allocator, key);
63103
if (entry_gop.found_existing) {
64-
self.allocator.free(entry_gop.value_ptr.*);
104+
self.freeEnvironmentValue(entry_gop.value_ptr.*);
65105
} else {
66106
entry_gop.key_ptr.* = try self.allocator.dupe(u8, key);
67107
}
68-
entry_gop.value_ptr.* = try self.allocator.dupe(u8, value);
108+
entry_gop.value_ptr.* = value;
69109
}
70110

71111
fn populateLinkedLibraries(self: *Self) !void {
@@ -154,7 +194,7 @@ test "overwrite existing entry" {
154194
try env.setIntegrationEnvironment("gcc", "version", "14.2.0");
155195

156196
try std.testing.expectEqual(@as(usize, 1), env.data.integration_environment.map.count());
157-
try std.testing.expectEqualStrings("14.2.0", env.data.integration_environment.map.get("gcc").?.map.get("version").?);
197+
try std.testing.expectEqualStrings("14.2.0", env.data.integration_environment.map.get("gcc").?.map.get("version").?.string);
158198
}
159199

160200
test "json serialization" {
@@ -168,15 +208,9 @@ test "json serialization" {
168208
const json = try std.json.stringifyAlloc(std.testing.allocator, env.data, .{ .whitespace = .indent_2 });
169209
defer std.testing.allocator.free(json);
170210

171-
const parsed = try std.json.parseFromSlice(EnvironmentJson, std.testing.allocator, json, .{});
172-
defer parsed.deinit();
173-
174-
const gcc = parsed.value.integration_environment.map.get("gcc").?;
175-
try std.testing.expectEqualStrings("14.2.0", gcc.map.get("version").?);
176-
try std.testing.expectEqualStrings("g++ (Ubuntu 14.2.0)", gcc.map.get("build").?);
177-
178-
const clang = parsed.value.integration_environment.map.get("clang").?;
179-
try std.testing.expectEqualStrings("18.1.0", clang.map.get("version").?);
211+
try std.testing.expect(std.mem.indexOf(u8, json, "\"version\": \"14.2.0\"") != null);
212+
try std.testing.expect(std.mem.indexOf(u8, json, "\"build\": \"g++ (Ubuntu 14.2.0)\"") != null);
213+
try std.testing.expect(std.mem.indexOf(u8, json, "\"version\": \"18.1.0\"") != null);
180214
}
181215

182216
test "empty sections" {
@@ -186,10 +220,12 @@ test "empty sections" {
186220
const json = try std.json.stringifyAlloc(std.testing.allocator, env.data, .{ .whitespace = .indent_2 });
187221
defer std.testing.allocator.free(json);
188222

189-
const parsed = try std.json.parseFromSlice(EnvironmentJson, std.testing.allocator, json, .{});
190-
defer parsed.deinit();
191-
192-
try std.testing.expectEqual(@as(usize, 0), parsed.value.integration_environment.map.count());
223+
try std.testing.expectEqualStrings(
224+
\\{
225+
\\ "integration_environment": {},
226+
\\ "linked_libraries": {}
227+
\\}
228+
, json);
193229
}
194230

195231
test "json escaping" {
@@ -201,11 +237,8 @@ test "json escaping" {
201237
const json = try std.json.stringifyAlloc(std.testing.allocator, env.data, .{ .whitespace = .indent_2 });
202238
defer std.testing.allocator.free(json);
203239

204-
const parsed = try std.json.parseFromSlice(EnvironmentJson, std.testing.allocator, json, .{});
205-
defer parsed.deinit();
206-
207-
const test_sec = parsed.value.integration_environment.map.get("test").?;
208-
try std.testing.expectEqualStrings("C:\\Program Files\\gcc", test_sec.map.get("path").?);
240+
// Backslashes should be escaped in JSON
241+
try std.testing.expect(std.mem.indexOf(u8, json, "C:\\\\Program Files\\\\gcc") != null);
209242
}
210243

211244
test "merge preserves existing and adds new" {
@@ -237,7 +270,35 @@ test "new entries override existing on merge" {
237270
try env.setIntegrationEnvironment("python", "version", "3.13.0");
238271

239272
try std.testing.expectEqual(@as(usize, 1), env.data.integration_environment.map.count());
240-
try std.testing.expectEqualStrings("3.13.0", env.data.integration_environment.map.get("python").?.map.get("version").?);
273+
try std.testing.expectEqualStrings("3.13.0", env.data.integration_environment.map.get("python").?.map.get("version").?.string);
274+
}
275+
276+
test "list environment value" {
277+
var env = Environment.init(std.testing.allocator);
278+
defer env.deinit();
279+
280+
try env.setIntegrationEnvironmentList("python", "sys_path", &.{ "/usr/lib/python3.13", "/home/user/.venv/lib" });
281+
try env.setIntegrationEnvironment("python", "version", "3.13.0");
282+
283+
const json = try std.json.stringifyAlloc(std.testing.allocator, env.data, .{ .whitespace = .indent_2 });
284+
defer std.testing.allocator.free(json);
285+
286+
try std.testing.expect(std.mem.indexOf(u8, json, "\"version\": \"3.13.0\"") != null);
287+
try std.testing.expect(std.mem.indexOf(u8, json, "\"sys_path\": [") != null);
288+
try std.testing.expect(std.mem.indexOf(u8, json, "\"/usr/lib/python3.13\"") != null);
289+
try std.testing.expect(std.mem.indexOf(u8, json, "\"/home/user/.venv/lib\"") != null);
290+
}
291+
292+
test "overwrite string with list" {
293+
var env = Environment.init(std.testing.allocator);
294+
defer env.deinit();
295+
296+
try env.setIntegrationEnvironment("python", "paths", "old_value");
297+
try env.setIntegrationEnvironmentList("python", "paths", &.{ "/a", "/b" });
298+
299+
try std.testing.expectEqual(@as(usize, 1), env.data.integration_environment.map.get("python").?.map.count());
300+
const val = env.data.integration_environment.map.get("python").?.map.get("paths").?;
301+
try std.testing.expectEqual(@as(usize, 2), val.list.len);
241302
}
242303

243304
test "linked libraries serialization" {

0 commit comments

Comments
 (0)