22// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33// See the LICENSE file in the project root for more information
44using System . IO . Abstractions ;
5- using System . Security . Cryptography ;
65using System . Text . Json ;
76using System . Text . Json . Serialization ;
87using Elastic . Markdown . IO ;
1211namespace Elastic . Markdown ;
1312
1413[ JsonSourceGenerationOptions ( WriteIndented = true ) ]
15- [ JsonSerializable ( typeof ( OutputState ) ) ]
14+ [ JsonSerializable ( typeof ( GenerationState ) ) ]
15+ [ JsonSerializable ( typeof ( LinkReference ) ) ]
16+ [ JsonSerializable ( typeof ( GitConfiguration ) ) ]
1617internal partial class SourceGenerationContext : JsonSerializerContext ;
1718
18- public class OutputState
19+ public record GenerationState
1920{
20- public DateTimeOffset LastSeenChanges { get ; set ; }
21- public string [ ] Conflict { get ; set ; } = [ ] ;
21+ [ JsonPropertyName ( "last_seen_changes" ) ]
22+ public required DateTimeOffset LastSeenChanges { get ; init ; }
23+ [ JsonPropertyName ( "invalid_files" ) ]
24+ public required string [ ] InvalidFiles { get ; init ; } = [ ] ;
25+
26+ [ JsonPropertyName ( "git" ) ]
27+ public required GitConfiguration Git { get ; init ; }
2228}
2329
2430public class DocumentationGenerator
@@ -49,18 +55,13 @@ ILoggerFactory logger
4955 _logger . LogInformation ( $ "Output directory: { docSet . OutputPath } Exists: { docSet . OutputPath . Exists } ") ;
5056 }
5157
52- public OutputState ? OutputState
58+ public GenerationState ? GetPreviousGenerationState ( )
5359 {
54- get
55- {
56- var stateFile = DocumentationSet . OutputStateFile ;
57- stateFile . Refresh ( ) ;
58- if ( ! stateFile . Exists ) return null ;
59- var contents = stateFile . FileSystem . File . ReadAllText ( stateFile . FullName ) ;
60- return JsonSerializer . Deserialize ( contents , SourceGenerationContext . Default . OutputState ) ;
61-
62-
63- }
60+ var stateFile = DocumentationSet . OutputStateFile ;
61+ stateFile . Refresh ( ) ;
62+ if ( ! stateFile . Exists ) return null ;
63+ var contents = stateFile . FileSystem . File . ReadAllText ( stateFile . FullName ) ;
64+ return JsonSerializer . Deserialize ( contents , SourceGenerationContext . Default . GenerationState ) ;
6465 }
6566
6667
@@ -69,26 +70,12 @@ public async Task ResolveDirectoryTree(Cancel ctx) =>
6970
7071 public async Task GenerateAll ( Cancel ctx )
7172 {
72- if ( Context . Force || OutputState == null )
73+ var generationState = GetPreviousGenerationState ( ) ;
74+ if ( Context . Force || generationState == null )
7375 DocumentationSet . ClearOutputDirectory ( ) ;
7476
75- _logger . LogInformation ( $ "Last write source: { DocumentationSet . LastWrite } , output observed: { OutputState ? . LastSeenChanges } ") ;
76-
77- var offendingFiles = new HashSet < string > ( OutputState ? . Conflict ?? [ ] ) ;
78- var outputSeenChanges = OutputState ? . LastSeenChanges ?? DateTimeOffset . MinValue ;
79- if ( offendingFiles . Count > 0 )
80- {
81- _logger . LogInformation ( $ "Reapplying changes since { DocumentationSet . LastWrite } ") ;
82- _logger . LogInformation ( $ "Reapplying for { offendingFiles . Count } files with errors/warnings") ;
83- }
84- else if ( DocumentationSet . LastWrite > outputSeenChanges && OutputState != null )
85- _logger . LogInformation ( $ "Using incremental build picking up changes since: { OutputState . LastSeenChanges } ") ;
86- else if ( DocumentationSet . LastWrite <= outputSeenChanges && OutputState != null )
87- {
88- _logger . LogInformation ( $ "No changes in source since last observed write { OutputState . LastSeenChanges } "
89- + "Pass --force to force a full regeneration" ) ;
77+ if ( CompilationNotNeeded ( generationState , out var offendingFiles , out var outputSeenChanges ) )
9078 return ;
91- }
9279
9380 _logger . LogInformation ( "Resolving tree" ) ;
9481 await ResolveDirectoryTree ( ctx ) ;
@@ -122,6 +109,7 @@ await Parallel.ForEachAsync(DocumentationSet.Files, ctx, async (file, token) =>
122109 Context . Collector . Channel . TryComplete ( ) ;
123110
124111 await GenerateDocumentationState ( ctx ) ;
112+ await GenerateLinkReference ( ctx ) ;
125113
126114 await Context . Collector . StopAsync ( ctx ) ;
127115
@@ -133,18 +121,58 @@ IFileInfo OutputFile(string relativePath)
133121
134122 }
135123
124+ private bool CompilationNotNeeded ( GenerationState ? generationState , out HashSet < string > offendingFiles ,
125+ out DateTimeOffset outputSeenChanges )
126+ {
127+ offendingFiles = new HashSet < string > ( generationState ? . InvalidFiles ?? [ ] ) ;
128+ outputSeenChanges = generationState ? . LastSeenChanges ?? DateTimeOffset . MinValue ;
129+ if ( generationState == null )
130+ return false ;
131+
132+ if ( Context . Git != generationState . Git )
133+ {
134+ _logger . LogInformation ( $ "Full compilation: current git context: { Context . Git } differs from previous git context: { generationState . Git } ") ;
135+ return false ;
136+ }
137+
138+ if ( offendingFiles . Count > 0 )
139+ {
140+ _logger . LogInformation ( $ "Incremental compilation. since: { DocumentationSet . LastWrite } ") ;
141+ _logger . LogInformation ( $ "Incremental compilation. { offendingFiles . Count } files with errors/warnings") ;
142+ }
143+ else if ( DocumentationSet . LastWrite > outputSeenChanges )
144+ _logger . LogInformation ( $ "Incremental compilation. since: { generationState . LastSeenChanges } ") ;
145+ else if ( DocumentationSet . LastWrite <= outputSeenChanges )
146+ {
147+ _logger . LogInformation ( $ "No compilation: no changes since last observed: { generationState . LastSeenChanges } ") ;
148+ _logger . LogInformation ( $ "No compilation: no changes since last observed: { generationState . LastSeenChanges } "
149+ + "Pass --force to force a full regeneration" ) ;
150+ return true ;
151+ }
152+
153+ return false ;
154+ }
155+
156+ private async Task GenerateLinkReference ( Cancel ctx )
157+ {
158+ var file = DocumentationSet . LinkReferenceFile ;
159+ var state = LinkReference . Create ( DocumentationSet ) ;
160+ var bytes = JsonSerializer . SerializeToUtf8Bytes ( state , SourceGenerationContext . Default . LinkReference ) ;
161+ await DocumentationSet . OutputPath . FileSystem . File . WriteAllBytesAsync ( file . FullName , bytes , ctx ) ;
162+ }
163+
136164 private async Task GenerateDocumentationState ( Cancel ctx )
137165 {
138166 var stateFile = DocumentationSet . OutputStateFile ;
139167 _logger . LogInformation ( $ "Writing documentation state { DocumentationSet . LastWrite } to { stateFile . FullName } ") ;
140168 var badFiles = Context . Collector . OffendingFiles . ToArray ( ) ;
141- var state = new OutputState
169+ var state = new GenerationState
142170 {
143171 LastSeenChanges = DocumentationSet . LastWrite ,
144- Conflict = badFiles
145-
172+ InvalidFiles = badFiles ,
173+ Git = Context . Git
146174 } ;
147- var bytes = JsonSerializer . SerializeToUtf8Bytes ( state , SourceGenerationContext . Default . OutputState ) ;
175+ var bytes = JsonSerializer . SerializeToUtf8Bytes ( state , SourceGenerationContext . Default . GenerationState ) ;
148176 await DocumentationSet . OutputPath . FileSystem . File . WriteAllBytesAsync ( stateFile . FullName , bytes , ctx ) ;
149177 }
150178
0 commit comments