77using System . Threading ;
88using Microsoft . Build . Framework ;
99using Microsoft . Build . Utilities ;
10- using Pchp . CodeAnalysis . CommandLine ;
1110
1211namespace Peachpie . NET . Sdk . Tools
1312{
@@ -16,6 +15,12 @@ namespace Peachpie.NET.Sdk.Tools
1615 /// </summary>
1716 public class BuildTask : Task , ICancelableTask // TODO: ToolTask
1817 {
18+ /// <summary>
19+ /// Path to <c>Peachpie.CodeAnalysis</c> executable.
20+ /// </summary>
21+ [ Required ]
22+ public string PeachpieCompilerFullPath { get ; set ; }
23+
1924 /// <summary></summary>
2025 [ Required ]
2126 public string OutputPath { get ; set ; }
@@ -28,6 +33,11 @@ public class BuildTask : Task, ICancelableTask // TODO: ToolTask
2833 [ Required ]
2934 public string TempOutputPath { get ; set ; }
3035
36+ /// <summary>
37+ /// Optional <c>.rsp</c> file to be created.
38+ /// </summary>
39+ public string ResponseFilePath { get ; set ; }
40+
3141 /// <summary></summary>
3242 [ Required ]
3343 public string TargetFramework { get ; set ; }
@@ -183,14 +193,14 @@ public override bool Execute()
183193 AddNoEmpty ( args , "sourcelink" , SourceLink ) ;
184194 AddNoEmpty ( args , "codepage" , CodePage ) ;
185195 AddNoEmpty ( args , "subdir" , PhpRelativePath ) ;
186-
187- if ( DefineConstants != null )
188- {
189- foreach ( var d in DefineConstants )
190- {
191- args . Add ( "/d:" + d ) ;
192- }
193- }
196+
197+ if ( DefineConstants != null )
198+ {
199+ foreach ( var d in DefineConstants )
200+ {
201+ args . Add ( "/d:" + d ) ;
202+ }
203+ }
194204
195205 if ( ReferencePath != null && ReferencePath . Length != 0 )
196206 {
@@ -224,16 +234,14 @@ public override bool Execute()
224234 ) ;
225235
226236 // /attr:FQN("value1","value2","value3")
227- args . Add ( $@ "/attr:{ attr . ItemSpec } ({
228- string . Join (
237+ args . Add ( $@ "/attr:{ attr . ItemSpec } ({ string . Join (
229238 "," ,
230239 Enumerable . Range ( 1 , 128 )
231240 . Select ( n => $ "_Parameter{ n } ")
232241 . TakeWhile ( prop => props . Contains ( prop ) )
233242 . Select ( prop => attr . GetMetadata ( prop ) )
234243 . Select ( value => $ "\" { value . Replace ( "\" " , "\\ \" " ) } \" ")
235- )
236- } )") ;
244+ ) } )" ) ;
237245 }
238246 }
239247
@@ -278,17 +286,26 @@ public override bool Execute()
278286 args . AddRange ( Compile . Distinct ( StringComparer . InvariantCulture ) ) ;
279287 }
280288
281- #if DEBUG
282- // save the arguments as .rsp file for debugging purposes:
283- try
289+ if ( DebuggerAttach )
284290 {
285- File . WriteAllText ( Path . Combine ( TempOutputPath , "dotnet-php.rsp" ) , string . Join ( Environment . NewLine , args ) ) ;
291+ args . Add ( "/attach" ) ;
292+ Debugger . Launch ( ) ;
286293 }
287- catch ( Exception ex )
294+
295+ // save the arguments as .rsp file for debugging purposes:
296+ if ( ! string . IsNullOrEmpty ( ResponseFilePath ) )
288297 {
289- this . Log . LogWarningFromException ( ex ) ;
298+ try
299+ {
300+ File . WriteAllText (
301+ Path . Combine ( BasePath , ResponseFilePath ) , string . Join ( Environment . NewLine , args . Select ( line => $ "\" { line . Replace ( "\\ " , "\\ \\ " ) } \" ") )
302+ ) ;
303+ }
304+ catch ( Exception ex )
305+ {
306+ this . Log . LogWarningFromException ( ex ) ;
307+ }
290308 }
291- #endif
292309
293310 //
294311 // run the compiler:
@@ -299,28 +316,46 @@ public override bool Execute()
299316 return false ;
300317 }
301318
302- // Debugger.Launch
303- if ( DebuggerAttach )
304- {
305- Debugger . Launch ( ) ;
306- }
307-
308- // compile
319+ // compile using out-of-process compiler:
309320 try
310321 {
311- var resultCode = PhpCompilerDriver . Run (
312- PhpCommandLineParser . Default ,
313- null ,
314- args : args . ToArray ( ) ,
315- clientDirectory : null ,
316- baseDirectory : BasePath ,
317- sdkDirectory : NetFrameworkPath ,
318- additionalReferenceDirectories : libs ,
319- analyzerLoader : new SimpleAnalyzerAssemblyLoader ( ) ,
320- output : new LogWriter ( this . Log ) ,
321- cancellationToken : _cancellation . Token ) ;
322-
323- return resultCode == 0 ;
322+ //var resultCode = PhpCompilerDriver.Run(
323+ // PhpCommandLineParser.Default,
324+ // null,
325+ // args: args.ToArray(),
326+ // clientDirectory: null,
327+ // baseDirectory: BasePath,
328+ // sdkDirectory: NetFrameworkPath,
329+ // additionalReferenceDirectories: libs,
330+ // analyzerLoader: new SimpleAnalyzerAssemblyLoader(),
331+ // output: new LogWriter(this.Log),
332+ // cancellationToken: _cancellation.Token);
333+
334+ var compilerArgs = new List < string > ( )
335+ {
336+ $ "/baseDirectory:{ BasePath } ",
337+ $ "/sdkDirectory:{ NetFrameworkPath } ",
338+ $ "/additionalReferenceDirectories:{ libs } ",
339+ } ;
340+
341+ if ( ResponseFilePath != null )
342+ {
343+ compilerArgs . Add (
344+ $ "/responseFile:{ ResponseFilePath } "
345+ ) ;
346+ }
347+ else
348+ {
349+ // pass all arguments instead of a single .rsp file
350+ compilerArgs . AddRange (
351+ args
352+ ) ;
353+ }
354+
355+ return RunCompiler (
356+ compilerArgs . ToArray ( ) ,
357+ _cancellation . Token
358+ ) ;
324359 }
325360 catch ( Exception ex )
326361 {
@@ -329,6 +364,51 @@ public override bool Execute()
329364 }
330365 }
331366
367+ bool RunCompiler ( string [ ] args , CancellationToken cancellation = default ( CancellationToken ) )
368+ {
369+ cancellation . ThrowIfCancellationRequested ( ) ;
370+
371+ Debug . Assert ( File . Exists ( PeachpieCompilerFullPath ) ) ;
372+
373+ //
374+ var pi = new ProcessStartInfo ( PeachpieCompilerFullPath )
375+ {
376+ Arguments = FlatternArgs ( args ) ,
377+ CreateNoWindow = true ,
378+ RedirectStandardError = true ,
379+ RedirectStandardOutput = true ,
380+ StandardOutputEncoding = Encoding . UTF8 ,
381+ StandardErrorEncoding = Encoding . UTF8 ,
382+ UseShellExecute = false ,
383+ } ;
384+
385+ //
386+ var process = Process . Start ( pi ) ;
387+ var dataReceived = new DataReceivedEventHandler ( ( o , e ) =>
388+ {
389+ this . Log . LogMessageFromText ( e . Data , MessageImportance . High ) ;
390+ } ) ;
391+
392+ process . OutputDataReceived += dataReceived ;
393+ process . ErrorDataReceived += dataReceived ;
394+
395+ using ( var cancellationHandler = cancellation . Register ( ( ) =>
396+ {
397+ if ( process . HasExited == false )
398+ {
399+ this . Log . LogMessageFromText ( "Cancelled by user" , MessageImportance . High ) ;
400+ // TODO: send signal first
401+ process . Kill ( ) ;
402+ }
403+ } ) )
404+ {
405+ process . WaitForExit ( ) ;
406+ process . StandardOutput . ReadToEnd ( ) ;
407+ //
408+ return process . ExitCode == 0 ;
409+ }
410+ }
411+
332412 void LogException ( Exception ex )
333413 {
334414 if ( ex is AggregateException aex && aex . InnerExceptions != null )
@@ -344,7 +424,7 @@ void LogException(Exception ex)
344424 }
345425 }
346426
347- bool AddNoEmpty ( List < string > args , string optionName , string optionValue )
427+ static bool AddNoEmpty ( List < string > args , string optionName , string optionValue )
348428 {
349429 if ( string . IsNullOrEmpty ( optionValue ) )
350430 {
@@ -355,7 +435,37 @@ bool AddNoEmpty(List<string> args, string optionName, string optionValue)
355435 return true ;
356436 }
357437
358- private string FormatArgFromItem ( ITaskItem item , string switchName , params string [ ] metadataNames )
438+ static string FlatternArgs ( string [ ] args )
439+ {
440+ var sb = new StringBuilder ( args . Length * 8 ) ;
441+
442+ for ( int i = 0 ; i < args . Length ; i ++ )
443+ {
444+ var arg = args [ i ] ;
445+ if ( string . IsNullOrEmpty ( arg ) )
446+ {
447+ continue ;
448+ }
449+
450+ if ( sb . Length != 0 )
451+ {
452+ sb . Append ( ' ' ) ;
453+ }
454+
455+ // sanitize {arg}
456+ sb . Append ( '\" ' ) ;
457+ sb . Append ( arg . Trim ( )
458+ //.Replace("\\", "\\\\")
459+ //.Replace("\"", "\\\"")
460+ ) ;
461+ sb . Append ( '\" ' ) ;
462+ }
463+
464+ //
465+ return sb . ToString ( ) ;
466+ }
467+
468+ static string FormatArgFromItem ( ITaskItem item , string switchName , params string [ ] metadataNames )
359469 {
360470 var arg = new StringBuilder ( $ "/{ switchName } :{ item . ItemSpec } ") ;
361471
0 commit comments