@@ -76,13 +76,21 @@ pub const BuildFile = struct {
7676 build_associated_config : ? std .json .Parsed (BuildAssociatedConfig ) = null ,
7777 impl : struct {
7878 mutex : std.Thread.Mutex = .{},
79+ build_runner_state : if (builtin .single_threaded ) void else BuildRunnerState = if (builtin .single_threaded ) {} else .idle ,
80+ version : u32 = 0 ,
7981 /// contains information extracted from running build.zig with a custom build runner
8082 /// e.g. include paths & packages
8183 /// TODO this field should not be nullable, callsites should await the build config to be resolved
8284 /// and then continue instead of dealing with missing information.
8385 config : ? std .json .Parsed (BuildConfig ) = null ,
8486 } = .{},
8587
88+ const BuildRunnerState = enum {
89+ idle ,
90+ running ,
91+ running_but_already_invalidated ,
92+ };
93+
8694 pub fn tryLockConfig (self : * BuildFile ) ? BuildConfig {
8795 self .impl .mutex .lock ();
8896 return if (self .impl .config ) | cfg | cfg .value else {
@@ -158,19 +166,6 @@ pub const BuildFile = struct {
158166 return true ;
159167 }
160168
161- fn setBuildConfig (self : * BuildFile , new_build_config : std .json .Parsed (BuildConfig )) void {
162- const tracy_zone = tracy .trace (@src ());
163- defer tracy_zone .end ();
164-
165- self .impl .mutex .lock ();
166- defer self .impl .mutex .unlock ();
167-
168- if (self .impl .config ) | * old_config | {
169- old_config .deinit ();
170- }
171- self .impl .config = new_build_config ;
172- }
173-
174169 fn deinit (self : * BuildFile , allocator : std.mem.Allocator ) void {
175170 allocator .free (self .uri );
176171 if (self .impl .config ) | cfg | cfg .deinit ();
@@ -882,19 +877,15 @@ pub fn invalidateBuildFile(self: *DocumentStore, build_file_uri: Uri) void {
882877 if (self .config .global_cache_dir == null ) return ;
883878 if (self .config .zig_lib_dir == null ) return ;
884879
880+ const build_file = self .getBuildFile (build_file_uri ) orelse return ;
881+
885882 if (builtin .single_threaded ) {
886- self .invalidateBuildFileWorker (build_file_uri , false );
883+ self .invalidateBuildFileWorker (build_file );
887884 return ;
888885 }
889886
890- const duped_uri = self .allocator .dupe (u8 , build_file_uri ) catch {
891- self .invalidateBuildFileWorker (build_file_uri , false );
892- return ;
893- };
894-
895- self .thread_pool .spawn (invalidateBuildFileWorker , .{ self , duped_uri , true }) catch {
896- self .allocator .free (duped_uri );
897- self .invalidateBuildFileWorker (build_file_uri , false );
887+ self .thread_pool .spawn (invalidateBuildFileWorker , .{ self , build_file }) catch {
888+ self .invalidateBuildFileWorker (build_file );
898889 return ;
899890 };
900891}
@@ -915,14 +906,14 @@ fn sendMessageToClient(allocator: std.mem.Allocator, transport: lsp.AnyTransport
915906fn notifyBuildStart (self : * DocumentStore ) void {
916907 if (! self .lsp_capabilities .supports_work_done_progress ) return ;
917908
909+ const transport = self .transport orelse return ;
910+
918911 // Atomicity note: We do not actually care about memory surrounding the
919912 // counter, we only care about the counter itself. We only need to ensure
920913 // we aren't double entering/exiting
921914 const prev = self .builds_in_progress .fetchAdd (1 , .monotonic );
922915 if (prev != 0 ) return ;
923916
924- const transport = self .transport orelse return ;
925-
926917 sendMessageToClient (
927918 self .allocator ,
928919 transport ,
@@ -959,14 +950,14 @@ const EndStatus = enum { success, failed };
959950fn notifyBuildEnd (self : * DocumentStore , status : EndStatus ) void {
960951 if (! self .lsp_capabilities .supports_work_done_progress ) return ;
961952
953+ const transport = self .transport orelse return ;
954+
962955 // Atomicity note: We do not actually care about memory surrounding the
963956 // counter, we only care about the counter itself. We only need to ensure
964957 // we aren't double entering/exiting
965958 const prev = self .builds_in_progress .fetchSub (1 , .monotonic );
966959 if (prev != 1 ) return ;
967960
968- const transport = self .transport orelse return ;
969-
970961 const message = switch (status ) {
971962 .failed = > "Failed" ,
972963 .success = > "Success" ,
@@ -987,23 +978,59 @@ fn notifyBuildEnd(self: *DocumentStore, status: EndStatus) void {
987978 };
988979}
989980
990- fn invalidateBuildFileWorker (self : * DocumentStore , build_file_uri : Uri , is_build_file_uri_owned : bool ) void {
991- defer if (is_build_file_uri_owned ) self .allocator .free (build_file_uri );
981+ fn invalidateBuildFileWorker (self : * DocumentStore , build_file : * BuildFile ) void {
982+ {
983+ build_file .impl .mutex .lock ();
984+ defer build_file .impl .mutex .unlock ();
985+
986+ switch (build_file .impl .build_runner_state ) {
987+ .idle = > build_file .impl .build_runner_state = .running ,
988+ .running = > {
989+ build_file .impl .build_runner_state = .running_but_already_invalidated ;
990+ return ;
991+ },
992+ .running_but_already_invalidated = > return ,
993+ }
994+ }
992995
993- var end_status : EndStatus = .failed ;
994996 self .notifyBuildStart ();
995- defer self .notifyBuildEnd (end_status );
996997
997- const build_config = loadBuildConfiguration (self , build_file_uri ) catch | err | {
998- log .err ("Failed to load build configuration for {s} (error: {})" , .{ build_file_uri , err });
999- return ;
1000- };
998+ while (true ) {
999+ build_file .impl .version += 1 ;
1000+ const new_version = build_file .impl .version ;
10011001
1002- const build_file = self .getBuildFile (build_file_uri ) orelse {
1003- build_config .deinit ();
1004- return ;
1005- };
1006- build_file .setBuildConfig (build_config );
1002+ const build_config = loadBuildConfiguration (self , build_file .uri , new_version ) catch | err | {
1003+ if (err != error .RunFailed ) { // already logged
1004+ log .err ("Failed to load build configuration for {s} (error: {})" , .{ build_file .uri , err });
1005+ }
1006+ self .notifyBuildEnd (.failed );
1007+ build_file .impl .mutex .lock ();
1008+ defer build_file .impl .mutex .unlock ();
1009+ build_file .impl .build_runner_state = .idle ;
1010+ return ;
1011+ };
1012+
1013+ build_file .impl .mutex .lock ();
1014+ switch (build_file .impl .build_runner_state ) {
1015+ .idle = > unreachable ,
1016+ .running = > {
1017+ build_file .impl .build_runner_state = .idle ;
1018+ build_file .impl .config = build_config ;
1019+ build_file .impl .mutex .unlock ();
1020+
1021+ if (build_file .impl .config ) | * old_config | old_config .deinit ();
1022+ self .notifyBuildEnd (.success );
1023+ break ;
1024+ },
1025+ .running_but_already_invalidated = > {
1026+ build_file .impl .build_runner_state = .running ;
1027+ build_file .impl .mutex .unlock ();
1028+
1029+ build_config .deinit ();
1030+ continue ;
1031+ },
1032+ }
1033+ }
10071034
10081035 if (self .transport ) | transport | {
10091036 if (self .lsp_capabilities .supports_semantic_tokens_refresh ) {
@@ -1029,9 +1056,6 @@ fn invalidateBuildFileWorker(self: *DocumentStore, build_file_uri: Uri, is_build
10291056 ) catch {};
10301057 }
10311058 }
1032-
1033- // Looks like a useless assignment, but alters deffered onEnd
1034- end_status = .success ;
10351059}
10361060
10371061/// The `DocumentStore` represents a graph structure where every
@@ -1252,7 +1276,7 @@ fn prepareBuildRunnerArgs(self: *DocumentStore, build_file_uri: []const u8) ![][
12521276}
12531277
12541278/// Runs the build.zig and extracts include directories and packages
1255- fn loadBuildConfiguration (self : * DocumentStore , build_file_uri : Uri ) ! std.json. Parsed (BuildConfig ) {
1279+ fn loadBuildConfiguration (self : * DocumentStore , build_file_uri : Uri , build_file_version : u32 ) ! std.json. Parsed (BuildConfig ) {
12561280 const tracy_zone = tracy .trace (@src ());
12571281 defer tracy_zone .end ();
12581282
@@ -1264,6 +1288,8 @@ fn loadBuildConfiguration(self: *DocumentStore, build_file_uri: Uri) !std.json.P
12641288 const build_file_path = try URI .parse (self .allocator , build_file_uri );
12651289 defer self .allocator .free (build_file_path );
12661290
1291+ const cwd = std .fs .path .dirname (build_file_path ).? ;
1292+
12671293 const args = try self .prepareBuildRunnerArgs (build_file_uri );
12681294 defer {
12691295 for (args ) | arg | self .allocator .free (arg );
@@ -1276,26 +1302,42 @@ fn loadBuildConfiguration(self: *DocumentStore, build_file_uri: Uri) !std.json.P
12761302 break :blk try std .process .Child .run (.{
12771303 .allocator = self .allocator ,
12781304 .argv = args ,
1279- .cwd = std . fs . path . dirname ( build_file_path ) .? ,
1305+ .cwd = cwd ,
12801306 .max_output_bytes = 16 * 1024 * 1024 ,
12811307 });
12821308 };
12831309 defer self .allocator .free (zig_run_result .stdout );
12841310 defer self .allocator .free (zig_run_result .stderr );
12851311
1286- errdefer blk : {
1287- const joined = std .mem .join (self .allocator , " " , args ) catch break :blk ;
1312+ const is_ok = switch (zig_run_result .term ) {
1313+ .Exited = > | exit_code | exit_code == 0 ,
1314+ else = > false ,
1315+ };
1316+
1317+ const diagnostic_tag : DiagnosticsCollection.Tag = tag : {
1318+ var hasher : std.hash.Wyhash = .init (47 ); // Chosen by the following prompt: Pwease give a wandom nyumbew
1319+ hasher .update (build_file_uri );
1320+ break :tag @enumFromInt (@as (u32 , @truncate (hasher .final ())));
1321+ };
1322+
1323+ if (! is_ok ) {
1324+ const joined = try std .mem .join (self .allocator , " " , args );
12881325 defer self .allocator .free (joined );
12891326
12901327 log .err (
12911328 "Failed to execute build runner to collect build configuration, command:\n {s}\n Error: {s}" ,
12921329 .{ joined , zig_run_result .stderr },
12931330 );
1294- }
12951331
1296- switch (zig_run_result .term ) {
1297- .Exited = > | exit_code | if (exit_code != 0 ) return error .RunFailed ,
1298- else = > return error .RunFailed ,
1332+ var error_bundle = try @import ("features/diagnostics.zig" ).getErrorBundleFromStderr (self .allocator , zig_run_result .stderr , false , null );
1333+ defer error_bundle .deinit (self .allocator );
1334+
1335+ try self .diagnostics_collection .pushErrorBundle (diagnostic_tag , build_file_version , cwd , error_bundle );
1336+ try self .diagnostics_collection .publishDiagnostics ();
1337+ return error .RunFailed ;
1338+ } else {
1339+ try self .diagnostics_collection .pushErrorBundle (diagnostic_tag , build_file_version , null , .empty );
1340+ try self .diagnostics_collection .publishDiagnostics ();
12991341 }
13001342
13011343 const parse_options : std.json.ParseOptions = .{
0 commit comments