|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//---------------------------------------------------------------------------
//
// Description: A MSBuild Task that can generate .baml file for some special
// xaml markup files that want to take some locally-defined types.
//
//---------------------------------------------------------------------------
using System;
using System.IO;
using System.Collections;
using System.Text;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using MS.Utility;
using MS.Internal;
using MS.Internal.Tasks;
// Since we disable PreSharp warnings in this file, PreSharp warning is unknown to C# compiler.
// We first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
namespace Microsoft.Build.Tasks.Windows
{
#region MarkupCompilePass2 Task class
/// <summary>
/// Class of MarkupCompilePass2 Task
/// </summary>
public sealed class MarkupCompilePass2 : Task
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public MarkupCompilePass2( ) : base(SR.SharedResourceManager)
{
// set the source directory
_sourceDir = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar;
_outputType = SharedStrings.WinExe;
_nErrors = 0;
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// Execute method in Task
/// </summary>
/// <returns></returns>
public override bool Execute()
{
TaskHelper.DisplayLogo(Log, nameof(MarkupCompilePass2));
//
// Create the TaskFileService instance here
//
_taskFileService = new TaskFileService(this) as ITaskFileService;
try
{
IsSupportedOutputType(OutputType);
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.CurrentDirectory), SourceDir);
// If wrong files are set to some properties, the task
// should stop here immediatelly.
if (_nErrors > 0)
{
Log.LogErrorWithCodeFromResources(nameof(SR.WrongPropertySetting));
}
else
{
bool hasLocalXamlFiles;
hasLocalXamlFiles = InitLocalXamlCache();
if (!hasLocalXamlFiles)
{
// There is no valid input xaml files.
// No need to do further work.
// stop here.
return true;
}
// create output directory
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
// Call the Markup Compiler to do the real compiling work
ArrayList referenceList;
FileUnit localApplicationFile;
FileUnit[] localXamlPageFileList;
// Prepare the appropriate file lists required by MarkupCompiler.
PrepareForMarkupCompilation(out localApplicationFile, out localXamlPageFileList, out referenceList);
// Do the real Pass2 compilation work here.
DoLocalReferenceMarkupCompilation(localApplicationFile, localXamlPageFileList, referenceList);
// Generate the required output items.
GenerateOutputItems();
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.CompilationDone));
}
}
#pragma warning disable 6500
catch (Exception e)
{
string message;
string errorId;
errorId = Log.ExtractMessageCode(e.Message, out message);
if (String.IsNullOrEmpty(errorId))
{
errorId = UnknownErrorID;
message = SR.Format(SR.UnknownBuildError, message);
}
Log.LogError(null, errorId, null, null, 0, 0, 0, 0, message, null);
_nErrors++;
}
catch // Non-CLS compliant errors
{
Log.LogErrorWithCodeFromResources(nameof(SR.NonClsError));
_nErrors++;
}
#pragma warning restore 6500
if (_nErrors > 0)
{
// When error counter is changed, the appropriate error message should have
// been reported.
//
// The task should cleanup all the cache files so that all the xaml files will
// get chance to recompile next time.
//
string stateFileName = OutputPath + AssemblyName +
(TaskFileService.IsRealBuild? SharedStrings.StateFile : SharedStrings.IntellisenseStateFile);
string localTypeCacheFileName = OutputPath + AssemblyName +
(TaskFileService.IsRealBuild? SharedStrings.LocalTypeCacheFile : SharedStrings.IntellisenseLocalTypeCacheFile);
if (TaskFileService.Exists(stateFileName))
{
TaskFileService.Delete(stateFileName);
}
if (TaskFileService.Exists(localTypeCacheFileName))
{
TaskFileService.Delete(localTypeCacheFileName);
}
return false;
}
else
{
// Mark Pass2 as completed in the cache
string stateFileName = OutputPath + AssemblyName +
(TaskFileService.IsRealBuild ? SharedStrings.StateFile : SharedStrings.IntellisenseStateFile);
if (TaskFileService.Exists(stateFileName))
{
CompilerState compilerState = new CompilerState(stateFileName, TaskFileService);
compilerState.LoadStateInformation();
if (compilerState.Pass2Required)
{
compilerState.Pass2Required = false;
compilerState.SaveStateInformation();
}
}
Log.LogMessageFromResources(nameof(SR.CompileSucceed_Pass2));
return true;
}
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// The Language the managed compiler supports.
/// the valid languages are C#, VB, Jscript, J#, C++
/// </summary>
[Required]
public string Language
{
get { return _language; }
set { _language = value; }
}
///<summary>
/// OutputPath
/// Directory which will contain the generated baml files.
///</summary>
[Required]
public string OutputPath
{
get { return _outputPath; }
set
{
string filePath = value;
// Get the relative path based on sourceDir
_outputPath= TaskHelper.CreateFullFilePath(filePath, SourceDir);
// Make sure OutputDir always ends with Path.DirectorySeparatorChar
if (!_outputPath.EndsWith(string.Empty + Path.DirectorySeparatorChar, StringComparison.Ordinal))
{
_outputPath += Path.DirectorySeparatorChar;
}
}
}
///<summary>
/// OutputType
/// Valid types: exe, winexe, library, netmodule
///</summary>
[Required]
public string OutputType
{
get { return _outputType; }
set { _outputType = TaskHelper.GetLowerString(value); }
}
///<summary>
/// AssemblyName
/// The short name of assembly which will be generated for this project.
///</summary>
[Required]
public string AssemblyName
{
get { return _assemblyName; }
set { _assemblyName = value; }
}
///<summary>
/// Root namespace for the classes inside the project.
/// It is also used in the Root element record of the generated baml file
/// when the corresponding markup page is not set x:Class attribute.
///</summary>
public string RootNamespace
{
get { return _rootNamespace; }
set { _rootNamespace = value; }
}
/// <summary>
/// Control whether to run the compilation in second appdomain.
/// By default, it is set to true, but project can set this property
/// to false to make markup file compilation faster.
/// </summary>
public bool AlwaysCompileMarkupFilesInSeparateDomain
{
get { return _alwaysCompileMarkupFilesInSeparateDomain; }
set { _alwaysCompileMarkupFilesInSeparateDomain = value; }
}
/// <summary>
/// Assembly References.
/// </summary>
/// <value></value>
public ITaskItem[] References
{
get { return _references; }
set { _references = value; }
}
///<summary>
///</summary>
public bool XamlDebuggingInformation
{
get { return _xamlDebuggingInformation; }
set { _xamlDebuggingInformation = value; }
}
///<summary>
/// Support custom IntermediateOutputPath and BaseIntermediateOutputPath outside the project path
///</summary>
public bool SupportCustomOutputPaths { get; set; } = false;
/// <summary>
/// Known reference paths hold referenced assemblies which are never changed during the build procedure.
/// such as references in GAC, in framework directory or framework SDK directory etc.
/// Users could add their own known reference paths in project files.
/// </summary>
public string[] KnownReferencePaths
{
get
{
return _knownReferencePaths;
}
set
{
_knownReferencePaths = value;
}
}
/// <summary>
/// A list of reference assemblies that are to change for sure during the build cycle.
///
/// Such as in VS.NET, if one project wants to reference another project's output, the
/// second project's output could be put in AssembliesGeneratedDuringBuild list.
/// Note: Once this property is set, it must contain the complete list of generated
/// assemblies in this build solution.
/// </summary>
public string[] AssembliesGeneratedDuringBuild
{
get
{
return _assembliesGeneratedDuringBuild;
}
set
{
_assembliesGeneratedDuringBuild = value;
}
}
///<summary>
/// Generated Baml files for the passed markup xaml files
///</summary>
[Output]
public ITaskItem [] GeneratedBaml
{
get
{
if (_generatedBaml == null)
_generatedBaml = Array.Empty<TaskItem>();
return _generatedBaml;
}
set
{
_generatedBaml = value;
}
}
/// <summary>
/// Controls how to generate localization information for each xaml file.
/// Valid values: None, CommentsOnly, All.
/// </summary>
public string LocalizationDirectivesToLocFile
{
get
{
string localizationDirectives = SharedStrings.Loc_None;
switch (_localizationDirectives)
{
case MS.Internal.LocalizationDirectivesToLocFile.None:
localizationDirectives = SharedStrings.Loc_None;
break;
case MS.Internal.LocalizationDirectivesToLocFile.CommentsOnly:
localizationDirectives = SharedStrings.Loc_CommentsOnly;
break;
case MS.Internal.LocalizationDirectivesToLocFile.All:
localizationDirectives = SharedStrings.Loc_All;
break;
}
return localizationDirectives;
}
set
{
string localizationDirectives = value;
if (localizationDirectives != null)
{
localizationDirectives = localizationDirectives.ToLower(CultureInfo.InvariantCulture);
}
switch (localizationDirectives)
{
case SharedStrings.Loc_None:
_localizationDirectives = MS.Internal.LocalizationDirectivesToLocFile.None;
break;
case SharedStrings.Loc_CommentsOnly:
_localizationDirectives = MS.Internal.LocalizationDirectivesToLocFile.CommentsOnly;
break;
case SharedStrings.Loc_All:
_localizationDirectives = MS.Internal.LocalizationDirectivesToLocFile.All;
break;
default:
_localizationDirectives = MS.Internal.LocalizationDirectivesToLocFile.Unknown;
break;
}
}
}
#endregion Public Properties
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
//
// TaskFileService
//
private ITaskFileService TaskFileService
{
get { return _taskFileService; }
}
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
//
// Initialze the local xaml cache file.
//
// return value:
//
// If cache doesn't exist, or both LocalAppDef and LocalXaml Pages do not exist, return false
// to indicate no further work required.
// otherwise, return true.
//
private bool InitLocalXamlCache()
{
bool hasLocalFiles = false;
_compilerLocalRefCache = new CompilerLocalReference(
OutputPath + AssemblyName + (TaskFileService.IsRealBuild? SharedStrings.LocalTypeCacheFile : SharedStrings.IntellisenseLocalTypeCacheFile),
_taskFileService);
if (_compilerLocalRefCache.CacheFileExists())
{
_compilerLocalRefCache.LoadCacheFile();
_localApplicationFile = _compilerLocalRefCache.LocalApplicationFile;
_localMarkupPages = _compilerLocalRefCache.LocalMarkupPages;
if (_localApplicationFile != null || (_localMarkupPages != null && _localMarkupPages.Length > 0))
{
hasLocalFiles = true;
//
// Initialize InternalTypeHelper file from the cache file first.
// Further handling will be taken after the xaml file compilation is done.
//
// If InternalTypeHelperFile is set in the Cache file, it means Pass1 cannot
// detect whether or not to keep the InternalTypeHelper File until the Pass2
// xaml file compilation is done.
//
_internalTypeHelperFile = _compilerLocalRefCache.InternalTypeHelperFile;
}
}
return hasLocalFiles;
}
//
// Return a new sourceDir and relative filepath for a given filePath.
// This is for supporting of fullpath or ..\ in the original FilePath.
//
private string GetResolvedFilePath(string filePath, ref string newSourceDir)
{
// Create a full path for the originalFilePath.
string fullFilePath = TaskHelper.CreateFullFilePath(filePath, SourceDir);
// Get the relative path based on sourceDir
string relPath = TaskHelper.GetRootRelativePath(SourceDir, fullFilePath);
string newRelativeFilePath;
if (relPath.Length > 0)
{
// the original file is relative to the SourceDir.
newSourceDir = SourceDir;
newRelativeFilePath = relPath;
}
else
{
// the original file is not relative to the SourceDir.
// it could have its own fullpath or contains "..\" etc.
//
// In this case, we want to put the filename as relative filepath
// and put the deepest directory that file is in as the new
// SourceDir.
//
int pathEndIndex = fullFilePath.LastIndexOf(Path.DirectorySeparatorChar);
newSourceDir = fullFilePath.Substring(0, pathEndIndex + 1);
newRelativeFilePath = TaskHelper.GetRootRelativePath(newSourceDir, fullFilePath);
}
return newRelativeFilePath;
}
//
// Generate the necessary file lists and other information required by MarkupCompiler.
//
// Output ArrayLists: localApplicationFile,
// localXamlPageFileList
// referenceList
//
private void PrepareForMarkupCompilation(out FileUnit localApplicationFile, out FileUnit[] localXamlPageFileList, out ArrayList referenceList)
{
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.PreparingCompile));
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.OutputType), OutputType);
// Initialize the output parameters
localXamlPageFileList = Array.Empty<FileUnit>();
localApplicationFile = FileUnit.Empty;
referenceList = new ArrayList();
if (_localApplicationFile != null)
{
// We don't want to support multiple application definition file per project.
localApplicationFile = new FileUnit(_localApplicationFile.FilePath, _localApplicationFile.LinkAlias, _localApplicationFile.LogicalName);
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.LocalRefAppDefFile), localApplicationFile);
}
// Generate the Xaml Markup file list
if (_localMarkupPages != null && _localMarkupPages.Length > 0)
{
int localFileNum = _localMarkupPages.Length;
localXamlPageFileList = new FileUnit[localFileNum];
for (int i = 0; i < localFileNum; i++)
{
FileUnit localPageFile = new FileUnit(_localMarkupPages[i].FilePath, _localMarkupPages[i].LinkAlias, _localMarkupPages[i].LogicalName);
localXamlPageFileList[i] = localPageFile;
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.LocalRefMarkupPage), localPageFile);
}
}
//
// Generate the assembly reference list.
// The temporary target assembly should have been added into Reference list from target file.
//
if (References != null && References.Length > 0)
{
ReferenceAssembly asmReference;
string refpath, asmname;
for (int i = 0; i < References.Length; i++)
{
refpath = References[i].ItemSpec;
refpath = TaskHelper.CreateFullFilePath(refpath, SourceDir);
asmname = Path.GetFileNameWithoutExtension(refpath);
asmReference = new ReferenceAssembly(refpath, asmname);
referenceList.Add(asmReference);
}
}
}
//
// Call MarkupCompiler to do the real compilation work.
//
private void DoLocalReferenceMarkupCompilation(FileUnit localApplicationFile, FileUnit[] localXamlPageFileList, ArrayList referenceList)
{
// When code goes here, the MarkupCompilation is really required, so don't need
// to do more further validation inside this private method.
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.DoCompilation));
AppDomain appDomain = null;
CompilerWrapper compilerWrapper = null;
try
{
compilerWrapper = TaskHelper.CreateCompilerWrapper();
if (compilerWrapper != null)
{
compilerWrapper.OutputPath = OutputPath;
compilerWrapper.TaskLogger = Log;
compilerWrapper.UnknownErrorID = UnknownErrorID;
compilerWrapper.XamlDebuggingInformation = XamlDebuggingInformation;
compilerWrapper.SupportCustomOutputPaths = SupportCustomOutputPaths;
compilerWrapper.TaskFileService = _taskFileService;
if (OutputType.Equals(SharedStrings.Exe) || OutputType.Equals(SharedStrings.WinExe))
{
compilerWrapper.ApplicationMarkup = localApplicationFile;
}
compilerWrapper.References = referenceList;
compilerWrapper.LocalizationDirectivesToLocFile = (int)_localizationDirectives;
// This is for Pass2 compilation
compilerWrapper.DoCompilation(AssemblyName, Language, RootNamespace, localXamlPageFileList, true);
//
// If no any xaml file with local-types wants to reference an internal type from
// current assembly and friend assembly, and InternalTypeHelperFile is set in the
// cache file, now it is the time to remove the content of InternalTypeHelper File.
//
// We still keep the empty file to make other parts of the build system happy.
//
if (!String.IsNullOrEmpty(_internalTypeHelperFile) && !compilerWrapper.HasInternals)
{
if (TaskFileService.Exists(_internalTypeHelperFile))
{
// Make empty content for this file.
MemoryStream memStream = new MemoryStream();
using (StreamWriter writer = new StreamWriter(memStream, new UTF8Encoding(false)))
{
writer.WriteLine(String.Empty);
writer.Flush();
TaskFileService.WriteFile(memStream.ToArray(), _internalTypeHelperFile);
}
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.InternalTypeHelperNotRequired), _internalTypeHelperFile);
}
}
}
}
finally
{
if (compilerWrapper != null && compilerWrapper.ErrorTimes > 0)
{
_nErrors += compilerWrapper.ErrorTimes;
}
if (appDomain != null)
{
// AppDomains are not supported on .NET Core. 'AppDomain.Unload' will always throw `CannotUnloadAppDomainException`.
#pragma warning disable SYSLIB0024
AppDomain.Unload(appDomain);
#pragma warning restore SYSLIB0024
compilerWrapper = null;
}
}
}
// <summary>
// Generate the required Output Items.
// </summary>
private void GenerateOutputItems( )
{
// For the rest target types,
// Create the output lists for Baml files.
ArrayList bamlFileList = new ArrayList();
string newSourceDir = SourceDir; // Just for calling GetResolvedFilePath
string relativeFile;
if (_localApplicationFile != null)
{
TaskItem bamlItem;
relativeFile = GetResolvedFilePath(_localApplicationFile.FilePath, ref newSourceDir);
bamlItem = GenerateBamlItem(relativeFile, _localApplicationFile.Localizable, _localApplicationFile.LinkAlias, _localApplicationFile.LogicalName);
if (bamlItem != null)
{
bamlFileList.Add(bamlItem);
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.LocalRefGeneratedBamlFile), bamlItem.ItemSpec);
}
}
if (_localMarkupPages != null && _localMarkupPages.Length > 0)
{
for (int i = 0; i < _localMarkupPages.Length; i++)
{
// add the baml file
LocalReferenceFile localRefFile = _localMarkupPages[i];
relativeFile = GetResolvedFilePath(localRefFile.FilePath, ref newSourceDir);
TaskItem bamlItem = GenerateBamlItem(relativeFile, localRefFile.Localizable, localRefFile.LinkAlias, localRefFile.LogicalName);
if (bamlItem != null)
{
bamlFileList.Add(bamlItem);
Log.LogMessageFromResources(MessageImportance.Low, nameof(SR.LocalRefGeneratedBamlFile), bamlItem.ItemSpec);
}
}
}
// Generate the Baml Output Item
GeneratedBaml = (ITaskItem[])bamlFileList.ToArray(typeof(ITaskItem));
}
//
// Generate a baml TaskItem for the given xaml file, and transfer the appropriate
// source task item's custom attributes to the generated baml item if necessary.
// The xaml file could be an application definition file or a Markup Page
//
// Note: the xaml file must be resolved by calling GetResolvedFilePath( ) or
// CreatFullFilePath( ) before calling this method.
//
private TaskItem GenerateBamlItem(string resolvedXamlfile, bool localizable, string linkAlias, string logicalName)
{
TaskItem bamlItem = null;
//
// For a given .xaml file (foo.xaml), there are below options for generated file:
//
// 1. A baml file with the same xaml file base name. foo.baml (such as page)
// 2. No baml file generated. such as logical component,
// or some simple Application definition xaml.
//
string bamlFileName = Path.ChangeExtension(resolvedXamlfile, SharedStrings.BamlExtension);
string bamlFile = OutputPath + bamlFileName;
if (TaskFileService.Exists(bamlFile))
{
//
// Baml file exists.
// Generate a TaskItem for it.
//
bamlItem = new TaskItem();
bamlItem.ItemSpec = bamlFile;
// Transfer the metadata value from source item to the generated baml item.
bamlItem.SetMetadata(SharedStrings.Localizable, localizable ? "True" : "False");
bamlItem.SetMetadata(SharedStrings.Link, linkAlias);
bamlItem.SetMetadata(SharedStrings.LogicalName, logicalName);
}
return bamlItem;
}
//
// Don't support local reference xaml compilation for netmodule type.
//
private bool IsSupportedOutputType(string outputType)
{
bool isSupported = false;
switch (outputType)
{
case SharedStrings.Exe :
case SharedStrings.WinExe:
case SharedStrings.Library :
isSupported = true;
break;
default:
isSupported = false;
break;
}
if (isSupported == false)
{
Log.LogErrorWithCodeFromResources(nameof(SR.TargetIsNotSupported), outputType);
// Keep the error numbers so that the task can stop immediatelly
// later when Execute( ) is called.
_nErrors++;
}
return isSupported;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
#region Private Properties
// <summary>
// The root directory for the applicaiton project.
// </summary>
private string SourceDir
{
get { return _sourceDir; }
}
#endregion Private Properties
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private ITaskItem [] _references;
private string _outputType;
private string _assemblyName;
private string[] _assembliesGeneratedDuringBuild;
private string[] _knownReferencePaths;
private string _rootNamespace = String.Empty;
private bool _xamlDebuggingInformation = false;
private bool _alwaysCompileMarkupFilesInSeparateDomain = true;
private LocalizationDirectivesToLocFile _localizationDirectives = MS.Internal.LocalizationDirectivesToLocFile.None;
private string _sourceDir;
private string _outputPath;
private string _language;
private ITaskItem [] _generatedBaml;
private int _nErrors;
private CompilerLocalReference _compilerLocalRefCache;
private LocalReferenceFile _localApplicationFile = null;
private LocalReferenceFile[] _localMarkupPages = null;
private string _internalTypeHelperFile = String.Empty;
private ITaskFileService _taskFileService;
private const string UnknownErrorID = "MC2000";
#endregion Private Fields
}
#endregion MarkupCompilePass2 Task class
}
|