@@ -5,8 +5,21 @@ const linked_libraries = @import("linked_libraries/root.zig");
55
66extern "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 );
1023const IntegrationEnvironmentMap = std .json .ArrayHashMap (IntegrationEnvironmentEntries );
1124
1225const 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
160200test "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
182216test "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
195231test "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
211244test "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
243304test "linked libraries serialization" {
0 commit comments