Merge pull request #600 from tr8dr/master
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / GetReferenceAssemblyPaths.cs
1 //
2 // GetReferenceAssemblyPaths.cs: Gets the target framework directories corresponding
3 // to target framework moniker
4 //
5 // Author:
6 //   Ankit Jain (jankit@novell.com)
7 //
8 // Copyright 2011 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
29 using System;
30 using Microsoft.Build.Framework;
31 using System.IO;
32 using System.Reflection;
33 using System.Xml;
34 using System.Collections.Generic;
35
36 using Mono.XBuild.Utilities;
37
38 #if NET_4_0
39
40 namespace Microsoft.Build.Tasks
41 {
42         public class GetReferenceAssemblyPaths : TaskExtension
43         {
44                 static string framework_base_path;
45                 static string PathSeparatorAsString = Path.PathSeparator.ToString ();
46                 const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild-frameworks";
47
48                 public GetReferenceAssemblyPaths ()
49                 {
50                 }
51
52                 public override bool Execute ()
53                 {
54                         FrameworkMoniker moniker = null;
55                         if (!TryParseTargetFrameworkMoniker (TargetFrameworkMoniker, out moniker))
56                                 return false;
57
58                         var framework = GetFrameworkDirectoriesForMoniker (moniker);
59                         if (framework == null) {
60                                 Log.LogWarning ("Unable to find framework corresponding to the target framework moniker '{0}'. " +
61                                                 "Framework assembly references will be resolved from the GAC, which might not be " +
62                                                 "the intended behavior.", TargetFrameworkMoniker);
63                                 if (moniker.Identifier.Equals (".NETPortable"))
64                                         return CheckPclReferenceAssemblies (moniker);
65                                 return true;
66                         }
67
68                         ReferenceAssemblyPaths = FullFrameworkReferenceAssemblyPaths = framework.Directories;
69                         TargetFrameworkMonikerDisplayName = framework.DisplayName;
70
71                         return true;
72                 }
73
74                 bool CheckPclReferenceAssemblies (FrameworkMoniker moniker)
75                 {
76                         // Check for a supported profile
77                         var check = new FrameworkMoniker (".NETPortable", "v4.0", "Profile24");
78                         if (GetFrameworkDirectoriesForMoniker (check) != null)
79                                 Log.LogError ("Unsupported PCL Profile '{0}'.", moniker);
80                         else
81                                 Log.LogError ("PCL Reference Assemblies not installed.");
82                         return false;
83                 }
84
85                 Framework GetFrameworkDirectoriesForMoniker (FrameworkMoniker moniker)
86                 {
87                         string dirs = String.Join (PathSeparatorAsString, new string [] {
88                                                         Environment.GetEnvironmentVariable ("XBUILD_FRAMEWORK_FOLDERS_PATH") ?? String.Empty,
89                                                         MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
90                                                         RootPath,
91                                                         DefaultFrameworksBasePath });
92
93                         string [] paths = dirs.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries);
94                         foreach (string path in paths) {
95                                 var framework = GetFrameworkDirectoriesForMoniker (moniker, path);
96                                 if (framework != null)
97                                         return framework;
98                         }
99
100                         return null;
101                 }
102
103                 //@base_path must be absolute
104                 Framework GetFrameworkDirectoriesForMoniker (FrameworkMoniker moniker, string base_path)
105                 {
106                         if (String.IsNullOrEmpty (base_path)) {
107                                 Log.LogMessage (MessageImportance.Low, "Invalid *empty* base path, ignoring. " + Environment.StackTrace);
108                                 return null;
109                         }
110
111                         Log.LogMessage (MessageImportance.Low, "Looking for framework '{0}' in root path '{1}'",
112                                         moniker, base_path);
113                         string framework_path = Path.Combine (base_path, Path.Combine (moniker.Identifier, moniker.Version));
114                         if (!String.IsNullOrEmpty (moniker.Profile))
115                                 framework_path = Path.Combine (framework_path, "Profile", moniker.Profile);
116
117                         string redistlist_dir = Path.Combine (framework_path, "RedistList");
118                         string framework_list = Path.Combine (redistlist_dir, "FrameworkList.xml");
119                         if (!File.Exists (framework_list)) {
120                                 Log.LogMessage (MessageImportance.Low,
121                                                         "Unable to find framework definition file '{0}' for Target Framework Moniker '{1}'",
122                                                         framework_list, moniker);
123                                 return null;
124                         }
125
126                         Log.LogMessage (MessageImportance.Low, "Found framework definition list '{0}' for framework '{1}'",
127                                         framework_list, moniker);
128                         XmlReader xr = XmlReader.Create (framework_list);
129                         try {
130                                 xr.MoveToContent ();
131                                 if (xr.LocalName != "FileList") {
132                                         Log.LogMessage (MessageImportance.Low, "Invalid frameworklist '{0}', expected a 'FileList' root element.",
133                                                         framework_list);
134                                         return null;
135                                 }
136
137                                 var framework = new Framework ();
138                                 framework.DisplayName = xr.GetAttribute ("Name");
139                                 string framework_dir = xr.GetAttribute ("TargetFrameworkDirectory");
140                                 if (String.IsNullOrEmpty (framework_dir))
141                                         framework_dir = Path.Combine (redistlist_dir, "..");
142                                 else
143                                         framework_dir = Path.Combine (redistlist_dir, framework_dir);
144
145                                 var directories = new List<string> ();
146                                 directories.Add (MSBuildUtils.FromMSBuildPath (framework_dir));
147
148                                 string include = xr.GetAttribute ("IncludeFramework");
149                                 if (!String.IsNullOrEmpty (include)) {
150                                         var included_framework = GetFrameworkDirectoriesForMoniker (new FrameworkMoniker (moniker.Identifier, include, null));
151
152                                         if (included_framework != null && included_framework.Directories != null)
153                                                 directories.AddRange (included_framework.Directories);
154                                 }
155
156                                 framework.Directories = directories.ToArray ();
157
158                                 return framework;
159                         } catch (XmlException xe) {
160                                 Log.LogWarning ("Error reading framework definition file '{0}': {1}", framework_list, xe.Message);
161                                 Log.LogMessage (MessageImportance.Low, "Error reading framework definition file '{0}': {1}", framework_list,
162                                                 xe.ToString ());
163                                 return null;
164                         } finally {
165                                 if (xr != null)
166                                         ((IDisposable)xr).Dispose ();
167                         }
168                 }
169
170                 bool TryParseTargetFrameworkMoniker (string moniker_literal, out FrameworkMoniker moniker)
171                 {
172                         moniker = null;
173                         if (String.IsNullOrEmpty (moniker_literal))
174                                 throw new ArgumentException ("Empty moniker string");
175
176                         string [] parts = moniker_literal.Split (new char [] {','}, StringSplitOptions.RemoveEmptyEntries);
177
178                         if (parts.Length < 2 || parts.Length > 3) {
179                                 LogInvalidMonikerError (null, moniker_literal);
180                                 return false;
181                         }
182
183                         string identifier = parts [0];
184                         string version = null;
185                         string profile = null;
186
187                         if (!parts [1].StartsWith ("Version=")) {
188                                 LogInvalidMonikerError ("Invalid framework name", moniker_literal);
189                                 return false;
190                         }
191
192                         version = parts [1].Substring (8);
193                         if (String.IsNullOrEmpty (version)) {
194                                 LogInvalidMonikerError ("Invalid framework version", moniker_literal);
195                                 return false;
196                         }
197
198                         if (parts.Length > 2) {
199                                 if (!parts [2].StartsWith ("Profile=")) {
200                                         LogInvalidMonikerError ("Invalid framework version", moniker_literal);
201                                         return false;
202                                 }
203
204                                 profile = parts [2].Substring (8);
205                                 if (String.IsNullOrEmpty (profile)) {
206                                         LogInvalidMonikerError ("Invalid framework profile", moniker_literal);
207                                         return false;
208                                 }
209                         }
210
211                         moniker = new FrameworkMoniker (identifier, version, profile);
212                         return true;
213                 }
214
215                 void LogInvalidMonikerError (string msg, string moniker_literal)
216                 {
217                         if (msg != null)
218                                 Log.LogError ("{0} in the Target Framework Moniker '{1}'. Expected format: 'Identifier,Version=<version>[,Profile=<profile>]'. " +
219                                                         "It should have either 2 or 3 comma separated components.", msg, moniker_literal);
220                         else
221                                 Log.LogError ("Invalid Target Framework Moniker '{0}'. Expected format: 'Identifier,Version=<version>[,Profile=<profile>]'. " +
222                                                         "It should have either 2 or 3 comma separated components.", moniker_literal);
223                 }
224
225                 [Required]
226                 public string TargetFrameworkMoniker { get; set; }
227
228                 public string RootPath { get; set; }
229
230                 public bool BypassFrameworkInstallChecks { get; set; }
231
232                 [Output]
233                 public string TargetFrameworkMonikerDisplayName { get; set; }
234
235                 [Output]
236                 public string[] ReferenceAssemblyPaths { get; set; }
237
238                 [Output]
239                 public string[] FullFrameworkReferenceAssemblyPaths { get; set; }
240
241                 static string DefaultFrameworksBasePath {
242                         get {
243                                 if (framework_base_path == null)
244                                         framework_base_path = Path.Combine (Path.GetDirectoryName (typeof (object).Assembly.Location),
245                                                                 Path.Combine ("..", "xbuild-frameworks"));
246                                 return framework_base_path;
247                         }
248                 }
249         }
250
251         class FrameworkMoniker {
252                 public readonly string Identifier;
253                 public readonly string Version;
254                 public readonly string Profile;
255
256                 public FrameworkMoniker (string identifier, string version, string profile)
257                 {
258                         this.Identifier = identifier;
259                         this.Version = version;
260                         this.Profile = profile;
261                 }
262
263                 public override string ToString ()
264                 {
265                         if (String.IsNullOrEmpty (Profile))
266                                 return String.Format ("{0},Version={1}", Identifier, Version);
267                         return  String.Format ("{0},Version={1},Profile={2}", Identifier, Version, Profile);
268                 }
269         }
270
271         class Framework {
272                 public string[] Directories;
273                 public string DisplayName;
274         }
275 }
276
277 #endif