368 строки
12 KiB
C#
368 строки
12 KiB
C#
/*
|
|
* Copyright 2016 Microsoft Inc
|
|
*
|
|
* Authors:
|
|
* Chris Hamons <chris.hamons@xamarin.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Xamarin.Utils;
|
|
|
|
using Profile = Mono.Tuner.Profile;
|
|
|
|
namespace Xamarin.Bundler {
|
|
|
|
public interface IFileEnumerator
|
|
{
|
|
IEnumerable<string> Files { get; }
|
|
string RootDir { get; }
|
|
}
|
|
|
|
public class FileSystemEnumerator : IFileEnumerator
|
|
{
|
|
DirectoryInfo Info;
|
|
public IEnumerable<string> Files => Info.GetFiles ().Select (x => x.FullName);
|
|
public string RootDir { get; private set; }
|
|
|
|
public FileSystemEnumerator (string path)
|
|
{
|
|
RootDir = path;
|
|
Info = new DirectoryInfo (path);
|
|
}
|
|
}
|
|
|
|
public delegate int RunCommandDelegate (string path, IList<string> args, Dictionary<string, string> env = null, StringBuilder output = null, bool suppressPrintOnErrors = false);
|
|
|
|
public enum AOTCompilerType {
|
|
Invalid,
|
|
Bundled64,
|
|
System64,
|
|
}
|
|
|
|
public enum AOTCompilationType {
|
|
Default,
|
|
None,
|
|
All,
|
|
Core,
|
|
SDK,
|
|
Explicit
|
|
}
|
|
|
|
public enum AOTKind {
|
|
Default,
|
|
Standard,
|
|
Hybrid
|
|
}
|
|
|
|
public class AOTOptions
|
|
{
|
|
public bool IsAOT => CompilationType != AOTCompilationType.Default && CompilationType != AOTCompilationType.None;
|
|
public bool IsHybridAOT => IsAOT && Kind == AOTKind.Hybrid;
|
|
|
|
public AOTCompilationType CompilationType { get; private set; } = AOTCompilationType.Default;
|
|
public AOTKind Kind { get; private set; } = AOTKind.Standard;
|
|
|
|
public List <string> IncludedAssemblies { get; private set; } = new List <string> ();
|
|
public List <string> ExcludedAssemblies { get; private set; } = new List <string> ();
|
|
|
|
public AOTOptions (string options)
|
|
{
|
|
// Syntax - all,core,sdk,none or "" if explicit then optional list of +/-'ed assemblies
|
|
// Sections seperated by ,
|
|
string [] optionParts = options.Split (',');
|
|
for (int i = 0 ; i < optionParts.Length ; ++i) {
|
|
string option = optionParts [i];
|
|
|
|
AOTKind kind = AOTKind.Default;
|
|
// Technically '|' is valid in a file name, so |hybrid.dll would be as well.
|
|
// So check the left hand side for a valid option and pass if not
|
|
if (option.Contains ("|")) {
|
|
string [] optionTypeParts = option.Split ('|');
|
|
if (optionTypeParts.Length != 2)
|
|
throw new ProductException (20, true, Errors.MX0020, "--aot", "{none, all, core, sdk}{|hybrid}, then an optional explicit list of assemblies.");
|
|
switch (optionTypeParts [0]) {
|
|
case "none":
|
|
case "core":
|
|
case "sdk":
|
|
case "all": {
|
|
option = optionTypeParts [0];
|
|
switch (optionTypeParts [1]) {
|
|
case "hybrid":
|
|
if (option != "all")
|
|
throw new ProductException (114, true, Errors.MM0114);
|
|
kind = AOTKind.Hybrid;
|
|
break;
|
|
case "standard":
|
|
kind = AOTKind.Standard;
|
|
break;
|
|
default:
|
|
throw new ProductException (20, true, Errors.MX0020, "--aot", "{none, all, core, sdk}{|hybrid}, then an optional explicit list of assemblies.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (option) {
|
|
case "none":
|
|
CompilationType = AOTCompilationType.None;
|
|
if (kind != AOTKind.Default)
|
|
Kind = kind;
|
|
continue;
|
|
case "all":
|
|
CompilationType = AOTCompilationType.All;
|
|
if (kind != AOTKind.Default)
|
|
Kind = kind;
|
|
continue;
|
|
case "sdk":
|
|
CompilationType = AOTCompilationType.SDK;
|
|
if (kind != AOTKind.Default)
|
|
Kind = kind;
|
|
continue;
|
|
case "core":
|
|
CompilationType = AOTCompilationType.Core;
|
|
if (kind != AOTKind.Default)
|
|
Kind = kind;
|
|
continue;
|
|
}
|
|
|
|
if (option.StartsWith ("+", StringComparison.Ordinal)) {
|
|
if (CompilationType == AOTCompilationType.Default)
|
|
CompilationType = AOTCompilationType.Explicit;
|
|
IncludedAssemblies.Add (option.Substring (1));
|
|
continue;
|
|
}
|
|
if (option.StartsWith ("-", StringComparison.Ordinal)) {
|
|
if (CompilationType == AOTCompilationType.Default)
|
|
CompilationType = AOTCompilationType.Explicit;
|
|
ExcludedAssemblies.Add (option.Substring (1));
|
|
continue;
|
|
}
|
|
throw new ProductException (20, true, Errors.MX0020, "--aot", "{none, all, core, sdk}{|hybrid}, then an optional explicit list of assemblies.");
|
|
}
|
|
if (CompilationType == AOTCompilationType.Default)
|
|
throw new ProductException (20, true, Errors.MX0020, "--aot", "{none, all, core, sdk}{|hybrid}, then an optional explicit list of assemblies.");
|
|
|
|
}
|
|
}
|
|
|
|
public class AOTCompiler
|
|
{
|
|
// Allows tests to stub out actual compilation and parallelism
|
|
public RunCommandDelegate RunCommand { get; set; } = Driver.RunCommand;
|
|
public ParallelOptions ParallelOptions { get; set; } = new ParallelOptions () { MaxDegreeOfParallelism = Driver.Concurrency };
|
|
|
|
string xamarin_mac_prefix;
|
|
public string XamarinMacPrefix {
|
|
get {
|
|
if (xamarin_mac_prefix == null)
|
|
xamarin_mac_prefix = Driver.GetFrameworkCurrentDirectory (Driver.App);
|
|
return xamarin_mac_prefix;
|
|
}
|
|
set {
|
|
xamarin_mac_prefix = value;
|
|
}
|
|
}
|
|
|
|
AOTOptions options;
|
|
Abi [] abis;
|
|
AOTCompilerType compilerType;
|
|
bool IsRelease;
|
|
bool IsModern;
|
|
|
|
public AOTCompiler (AOTOptions options, IEnumerable <Abi> abis, AOTCompilerType compilerType, bool isModern, bool isRelease)
|
|
{
|
|
this.options = options;
|
|
this.abis = abis.ToArray ();
|
|
this.compilerType = compilerType;
|
|
this.IsModern = isModern;
|
|
this.IsRelease = isRelease;
|
|
}
|
|
|
|
public void Compile (string path)
|
|
{
|
|
Compile (new FileSystemEnumerator (path));
|
|
}
|
|
|
|
public void Compile (IFileEnumerator files)
|
|
{
|
|
if (!options.IsAOT)
|
|
throw ErrorHelper.CreateError (0099, Errors.MX0099, $"\"AOTBundle with aot: {options.CompilationType}\" ");
|
|
|
|
var monoEnv = new Dictionary<string, string> { { "MONO_PATH", files.RootDir } };
|
|
List<string> filesToAOT = GetFilesToAOT (files);
|
|
|
|
bool needsLipo = abis.Length > 1 && filesToAOT.Count > 0;
|
|
string tempAotDir = needsLipo ? Path.GetDirectoryName (filesToAOT [0]) : null;
|
|
if (needsLipo && Directory.Exists (tempAotDir)) {
|
|
foreach (var abi in abis) {
|
|
Directory.CreateDirectory (Path.Combine (tempAotDir, "aot", abi.AsArchString ()));
|
|
}
|
|
}
|
|
|
|
Parallel.ForEach (filesToAOT.SelectMany (f => abis, (file, abi) => new Tuple <string, Abi> (file, abi)), ParallelOptions, tuple => {
|
|
var file = tuple.Item1;
|
|
var abi = tuple.Item2;
|
|
|
|
var cmd = new List <string> ();
|
|
var aotArgs = new List <string> ();
|
|
aotArgs.Add ($"mtriple={abi.AsArchString ()}");
|
|
if (options.IsHybridAOT)
|
|
aotArgs.Add ("hybrid");
|
|
if (needsLipo)
|
|
aotArgs.Add ($"outfile={Path.Combine (tempAotDir, "aot", abi.AsArchString (), Path.GetFileName (file) + ".dylib")}");
|
|
cmd.Add ($"--aot={string.Join (",", aotArgs)}");
|
|
if (IsModern)
|
|
cmd.Add ("--runtime=mobile");
|
|
cmd.Add (file);
|
|
if (RunCommand (GetMonoPath (abi), cmd, monoEnv) != 0)
|
|
throw ErrorHelper.CreateError (3001, Errors.MX3001, "AOT", file);
|
|
});
|
|
|
|
// Lipo the result
|
|
if (needsLipo) {
|
|
Parallel.ForEach (filesToAOT, ParallelOptions, file => {
|
|
string [] inputs = abis.Select (abi => Path.Combine (tempAotDir, "aot", abi.AsArchString (), Path.GetFileName (file) + ".dylib")).Where (File.Exists).ToArray ();
|
|
string output = file + ".dylib";
|
|
|
|
if (inputs.Length > 0)
|
|
Driver.RunLipoAndCreateDsym (Driver.App, output, inputs);
|
|
});
|
|
}
|
|
|
|
if (needsLipo && Directory.Exists (tempAotDir)) {
|
|
Directory.Delete (Path.Combine (tempAotDir, "aot"), true);
|
|
}
|
|
|
|
if (IsRelease && options.IsHybridAOT) {
|
|
Parallel.ForEach (filesToAOT, ParallelOptions, file => {
|
|
if (RunCommand (StripCommand, new [] { file }) != 0)
|
|
throw ErrorHelper.CreateError (3001, Errors.MX3001, "strip", file);
|
|
});
|
|
}
|
|
|
|
if (IsRelease) {
|
|
// mono --aot creates .dll.dylib.dSYM directories for each assembly AOTed
|
|
// There isn't an easy was to disable this behavior
|
|
// We move them (cheap) so they can be archived for release builds
|
|
foreach (var file in filesToAOT) {
|
|
var source = file + ".dylib.dSYM/";
|
|
if (Directory.Exists (source)) {
|
|
var dest = Path.GetFullPath (Path.Combine (source, "..", "..", "..", "..", Path.GetFileName (file) + ".dylib.dSYM/"));
|
|
if (Directory.Exists (dest))
|
|
Directory.Delete (dest, true);
|
|
Directory.Move (source, dest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<string> GetFilesToAOT (IFileEnumerator files)
|
|
{
|
|
// Make a dictionary of included/excluded files to track if we've missed some at the end
|
|
Dictionary <string, bool> includedAssemblies = new Dictionary <string, bool> ();
|
|
foreach (var item in options.IncludedAssemblies)
|
|
includedAssemblies [item] = false;
|
|
|
|
Dictionary <string, bool> excludedAssemblies = new Dictionary <string, bool> ();
|
|
foreach (var item in options.ExcludedAssemblies)
|
|
excludedAssemblies [item] = false;
|
|
|
|
var aotFiles = new List<string> ();
|
|
|
|
foreach (var file in files.Files) {
|
|
string fileName = Path.GetFileName (file);
|
|
string extension = Path.GetExtension (file);
|
|
if (extension != ".exe" && extension != ".dll")
|
|
continue;
|
|
|
|
if (excludedAssemblies.ContainsKey (fileName)) {
|
|
excludedAssemblies [fileName] = true;
|
|
continue;
|
|
}
|
|
|
|
if (includedAssemblies.ContainsKey (fileName)) {
|
|
includedAssemblies [fileName] = true;
|
|
aotFiles.Add (file);
|
|
continue;
|
|
}
|
|
|
|
switch (options.CompilationType) {
|
|
case AOTCompilationType.All:
|
|
aotFiles.Add (file);
|
|
break;
|
|
case AOTCompilationType.SDK:
|
|
string fileNameNoExtension = Path.GetFileNameWithoutExtension (fileName);
|
|
if (Profile.IsSdkAssembly (fileNameNoExtension) || fileName == "Xamarin.Mac.dll")
|
|
aotFiles.Add (file);
|
|
break;
|
|
case AOTCompilationType.Core:
|
|
if (fileName == "Xamarin.Mac.dll" || fileName == "System.dll" || fileName == "mscorlib.dll")
|
|
aotFiles.Add (file);
|
|
break;
|
|
case AOTCompilationType.Explicit:
|
|
break; // In explicit, only included includedAssemblies included
|
|
default:
|
|
throw ErrorHelper.CreateError (0099, Errors.MX0099, $"\"GetFilesToAOT with aot: {options.CompilationType}\"" );
|
|
}
|
|
}
|
|
|
|
var unusedIncludes = includedAssemblies.Where (pair => !pair.Value).Select (pair => pair.Key).ToList ();
|
|
if (unusedIncludes.Count > 0)
|
|
throw ErrorHelper.CreateError (3009, Errors.MM3009, String.Join (" ", unusedIncludes));
|
|
|
|
var unusedExcludes = excludedAssemblies.Where (pair => !pair.Value).Select (pair => pair.Key).ToList ();
|
|
if (unusedExcludes.Count > 0)
|
|
throw ErrorHelper.CreateError (3010, Errors.MM3010, String.Join (" ", unusedExcludes));
|
|
|
|
return aotFiles;
|
|
}
|
|
|
|
public const string StripCommand = "/Library/Frameworks/Mono.framework/Commands/mono-cil-strip";
|
|
|
|
string GetMonoPath (Abi abi)
|
|
{
|
|
if (compilerType == AOTCompilerType.Bundled64) {
|
|
switch (abi) {
|
|
case Abi.ARM64:
|
|
return Path.Combine (XamarinMacPrefix, "bin", "aarch64-darwin-mono-sgen");
|
|
case Abi.x86_64:
|
|
return Path.Combine (XamarinMacPrefix, "bin", "mono-sgen");
|
|
default:
|
|
throw ErrorHelper.CreateError (0099, Errors.MX0099, $"\"MonoPath with compilerType: {compilerType}\"");
|
|
}
|
|
} else if (compilerType == AOTCompilerType.System64 && abi == Abi.x86_64) {
|
|
return "/Library/Frameworks/Mono.framework/Commands/mono64";
|
|
} else {
|
|
throw ErrorHelper.CreateError (0099, Errors.MX0099, $"\"MonoPath with compilerType: {compilerType}\"");
|
|
}
|
|
}
|
|
}
|
|
}
|