fix nuget runner crash if non-local nuget.exe was used
[teamcity/dotNetPackagesSupport.git] / nuget-extensions / nuget-runner / src / NuGetRunner.cs
1 using System;\r
2 using System.Collections.Generic;\r
3 using System.Diagnostics;\r
4 using System.IO;\r
5 using System.Reflection;\r
6 using System.Linq;\r
7 using System.Text;\r
8 using System.Threading;\r
9 \r
10 namespace JetBrains.TeamCity.NuGetRunner\r
11 {\r
12   public class NuGetRunner\r
13   {\r
14     private readonly string myNuGetExe;\r
15     private readonly Assembly myNuGetAssembly;\r
16     private readonly List<EventHandler> myStartEvents = new List<EventHandler>();\r
17     private readonly List<EventHandler> myFinishEvents = new List<EventHandler>();\r
18 \r
19     public NuGetRunner(string NuGetExe)\r
20     {\r
21       myNuGetExe = NuGetExe;\r
22       if (!File.Exists(NuGetExe))\r
23         throw new NuGetLoadException("Failed to find NuGet.exe at " + myNuGetExe);\r
24 \r
25       try\r
26       {\r
27         myNuGetAssembly = Assembly.LoadFrom(myNuGetExe);\r
28       }\r
29       catch (Exception e)\r
30       {\r
31         throw new NuGetLoadException("Failed to load NuGet assembly into AppDomain. " + e.Message, e);\r
32       }\r
33 \r
34       NuGetExtensionsPath = new Lazy<string>(LocateNuGetExtensionsPath);\r
35     }\r
36 \r
37     private string LocateNuGetExtensionsPath()\r
38     {\r
39       var mi = myNuGetAssembly.EntryPoint.DeclaringType;\r
40       foreach (\r
41         var type in\r
42           new Func<Type>[] {() => mi.DeclaringType, () => myNuGetAssembly.GetType("NuGet.Program")}.Select(Compute).\r
43             Where(x => x != null))\r
44       {\r
45         var field = type.GetField("ExtensionsDirectoryRoot",\r
46                                   BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);\r
47         if (field != null && field.FieldType == typeof (string))\r
48         {\r
49           var extensionsPath = field.GetValue(null) as string;\r
50           if (extensionsPath != null)\r
51             return extensionsPath;\r
52         }\r
53       }\r
54       //This is explicit path value taken from NuGet source code\r
55       return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "NuGet", "Commands");\r
56     }\r
57 \r
58     public Lazy<string> NuGetExtensionsPath { get; private set; }\r
59 \r
60     public event EventHandler BeforeNuGetStarted { add { myStartEvents.Add(value); } remove { myStartEvents.Remove(value);  } }\r
61     public event EventHandler AfterNuGetFinished { add { myFinishEvents.Add(value); } remove { myFinishEvents.Remove(value); } }\r
62 \r
63     private void CallEvents(IEnumerable<EventHandler> handler)\r
64     {\r
65       foreach (var h in handler)\r
66       {\r
67         try\r
68         {\r
69           h(this, EventArgs.Empty);\r
70         } catch (Exception e)\r
71         {\r
72           Console.Error.WriteLine("Failed to execute event: " + e);\r
73         }\r
74       }\r
75     }\r
76 \r
77     public int Run(IEnumerable<string> argz)\r
78     {\r
79       CallEvents(myStartEvents);\r
80 \r
81       try\r
82       {\r
83         var process = Process.Start(new ProcessStartInfo\r
84                                       {\r
85                                         FileName = myNuGetExe,\r
86                                         //TODO use escapring safe escaping here.\r
87                                         Arguments = string.Join(" ", argz.Select(x=>x.IndexOfAny(" \t\n\r".ToCharArray()) >=0 ? "\"" + x + "\"" : x)),\r
88                                         UseShellExecute = false,\r
89                                         RedirectStandardInput = true, \r
90                                         RedirectStandardError = true, \r
91                                         RedirectStandardOutput = true\r
92                                       });\r
93 \r
94         process.StandardInput.Close();\r
95         Func<StreamReader, TextWriter, Thread> readOutput = (si, so) =>\r
96         {\r
97           var th = new Thread(delegate()\r
98           {\r
99             int i;\r
100             while ((i = si.Read()) >= 0) so.Write((char)i);            \r
101           }) { Name = "Process output reader " + process.Id };\r
102           th.Start();\r
103           return th;\r
104         };\r
105 \r
106         var t1 = readOutput(process.StandardOutput, Console.Out);\r
107         var t2 = readOutput(process.StandardError, Console.Error);\r
108         \r
109         process.WaitForExit();\r
110 \r
111         t1.Join(TimeSpan.FromMinutes(5));\r
112         t2.Join(TimeSpan.FromMinutes(5));\r
113 \r
114         return process.ExitCode;\r
115         /*AppDomain dom = AppDomain.CreateDomain("NuGet Launcher Domain");\r
116         var result = dom.ExecuteAssembly(myNuGetExe, argz);\r
117         return result is int ? (int) result : 0;*/\r
118       }\r
119       finally\r
120       {\r
121         CallEvents(myFinishEvents);\r
122       }\r
123     }\r
124 \r
125     private static T Compute<T>(Func<T> func) where T : class\r
126     {\r
127       try\r
128       {\r
129         return func();\r
130       }\r
131       catch\r
132       {\r
133         return null;\r
134       }\r
135     }\r
136   }\r
137 }