Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve assemblies from specific paths #567

Merged
merged 9 commits into from
May 24, 2019
98 changes: 43 additions & 55 deletions src/ILLink.Tasks/LinkTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ public class ILLink : ToolTask
{
/// <summary>
/// Paths to the assembly files that should be considered as
/// input to the linker. Currently the linker will
/// additionally be able to resolve any assemblies in the
/// same directory as an assembly in AssemblyPaths, but this
/// behavior should not be relied upon. Instead, work under
/// the assumption that only the AssemblyPaths given will be
/// resolved.
/// input to the linker.
/// Each path can also have an "action" metadata,
/// which will set the illink action to take for
/// that assembly.
Expand Down Expand Up @@ -88,14 +83,12 @@ private string DotNetPath
get
{
if (!String.IsNullOrEmpty (_dotnetPath))
{
return _dotnetPath;
}

_dotnetPath = Environment.GetEnvironmentVariable (DotNetHostPathEnvironmentName);
if (String.IsNullOrEmpty (_dotnetPath))
{
throw new InvalidOperationException ($"{DotNetHostPathEnvironmentName} is not set");
}

return _dotnetPath;
}
}
Expand All @@ -112,9 +105,8 @@ private string DotNetPath
public string ILLinkPath {
get {
if (!String.IsNullOrEmpty (_illinkPath))
{
return _illinkPath;
}

var taskDirectory = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location);
// The linker always runs on .NET Core, even when using desktop MSBuild to host ILLink.Tasks.
_illinkPath = Path.Combine (Path.GetDirectoryName (taskDirectory), "netcoreapp2.0", "illink.dll");
Expand All @@ -132,83 +124,79 @@ protected override string GenerateCommandLineCommands ()
{
var args = new StringBuilder ();
args.Append (Quote (ILLinkPath));
return args.ToString ();
}

protected override string GenerateResponseFileCommands ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please make the response file such that there is one argument/switch per line?
Basically add newlines at the end of each option?

This makes it easier to inspect the rsp files manually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

{
var args = new StringBuilder ();

if (RootDescriptorFiles != null) {
foreach (var rootFile in RootDescriptorFiles) {
args.Append (" -x ").Append (Quote (rootFile.ItemSpec));
}
foreach (var rootFile in RootDescriptorFiles)
args.Append ("-x ").AppendLine (Quote (rootFile.ItemSpec));
}

foreach (var assemblyItem in RootAssemblyNames) {
args.Append (" -a ").Append (Quote (assemblyItem.ItemSpec));
}
foreach (var assemblyItem in RootAssemblyNames)
args.Append ("-a ").AppendLine (Quote (assemblyItem.ItemSpec));

HashSet<string> directories = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
HashSet<string> assemblyNames = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
foreach (var assembly in AssemblyPaths) {
var assemblyPath = assembly.ItemSpec;
var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);

assemblyNames.Add (assemblyName);
// If there are multiple paths with the same assembly name, only use the first one.
if (!assemblyNames.Add (assemblyName))
continue;

var dir = Path.GetDirectoryName (assemblyPath);
if (!directories.Contains (dir)) {
directories.Add (dir);
args.Append (" -d ").Append (Quote (dir));
}
args.Append ("--ref ").AppendLine (Quote (assemblyPath));

string action = assembly.GetMetadata ("action");
if ((action != null) && (action.Length > 0)) {
args.Append (" -p ");
args.Append ("-p ");
args.Append (action);
args.Append (" ").Append (Quote (assemblyName));
args.Append (" ").AppendLine (Quote (assemblyName));
}
}

foreach (var assembly in ReferenceAssemblyPaths) {
var assemblyPath = assembly.ItemSpec;
var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
if (ReferenceAssemblyPaths != null) {
foreach (var assembly in ReferenceAssemblyPaths) {
var assemblyPath = assembly.ItemSpec;
var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);

// Don't process references for which we already have
// implementation assemblies.
if (assemblyNames.Contains (assemblyName))
continue;
// Don't process references for which we already have
// implementation assemblies.
if (assemblyNames.Contains (assemblyName))
continue;

var dir = Path.GetDirectoryName (assemblyPath);
if (!directories.Contains (dir)) {
directories.Add (dir);
args.Append (" -d ").Append (Quote (dir));
}
args.Append ("-reference ").AppendLine (Quote (assemblyPath));

// Treat reference assemblies as "skip". Ideally we
// would not even look at the IL, but only use them to
// resolve surface area.
args.Append (" -p skip ").Append (Quote (assemblyName));
// Treat reference assemblies as "skip". Ideally we
// would not even look at the IL, but only use them to
// resolve surface area.
args.Append ("-p skip ").AppendLine (Quote (assemblyName));
}
}

if (OutputDirectory != null) {
args.Append (" -out ").Append (Quote (OutputDirectory.ItemSpec));
}
if (OutputDirectory != null)
args.Append ("-out ").AppendLine (Quote (OutputDirectory.ItemSpec));

if (ClearInitLocals) {
args.Append (" -s ");
args.Append ("-s ");
// Version of ILLink.CustomSteps is passed as a workaround for msbuild issue #3016
args.Append ("LLink.CustomSteps.ClearInitLocalsStep,ILLink.CustomSteps,Version=0.0.0.0:OutputStep");
args.AppendLine ("ILLink.CustomSteps.ClearInitLocalsStep,ILLink.CustomSteps,Version=0.0.0.0:OutputStep");
if ((ClearInitLocalsAssemblies != null) && (ClearInitLocalsAssemblies.Length > 0)) {
args.Append (" -m ClearInitLocalsAssemblies ");
args.Append (ClearInitLocalsAssemblies);
args.Append ("-m ClearInitLocalsAssemblies ");
args.AppendLine (ClearInitLocalsAssemblies);
}
}

if (ExtraArgs != null) {
args.Append (" ").Append (ExtraArgs);
}
if (ExtraArgs != null)
args.AppendLine (ExtraArgs);

if (DumpDependencies)
args.Append (" --dump-dependencies");
args.AppendLine ("--dump-dependencies");

return args.ToString ();
}

}
}
50 changes: 49 additions & 1 deletion src/linker/Linker/AssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using System.Collections.Generic;
using System.IO;
using Mono.Cecil;
using Mono.Collections.Generic;

namespace Mono.Linker {

Expand All @@ -43,6 +44,8 @@ public class AssemblyResolver : BaseAssemblyResolver {
HashSet<string> _unresolvedAssemblies;
bool _ignoreUnresolved;
LinkContext _context;
readonly Collection<string> _references;


public IDictionary<string, AssemblyDefinition> AssemblyCache {
get { return _assemblies; }
Expand All @@ -56,6 +59,7 @@ public AssemblyResolver ()
public AssemblyResolver (Dictionary<string, AssemblyDefinition> assembly_cache)
{
_assemblies = assembly_cache;
_references = new Collection<string> () { };
}

public bool IgnoreUnresolved {
Expand All @@ -68,12 +72,51 @@ public AssemblyResolver (Dictionary<string, AssemblyDefinition> assembly_cache)
set { _context = value; }
}

#if !FEATURE_ILLINK
// The base class's definition of GetAssembly is visible when using DirectoryAssemblyResolver.
AssemblyDefinition GetAssembly (string file, ReaderParameters parameters)
{
if (parameters.AssemblyResolver == null)
parameters.AssemblyResolver = this;

return ModuleDefinition.ReadModule (file, parameters).Assembly;
}
#endif

AssemblyDefinition ResolveFromReferences (AssemblyNameReference name, Collection<string> references, ReaderParameters parameters)
{
var fileName = name.Name + ".dll";
foreach (var reference in references) {
if (Path.GetFileName (reference) != fileName)
continue;
try {
return GetAssembly (reference, parameters);
} catch (BadImageFormatException) {
continue;
}
}

return null;
}

public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters)
{
// Validate arguments, similarly to how the base class does it.
if (name == null)
throw new ArgumentNullException ("name");
if (parameters == null)
throw new ArgumentNullException ("parameters");

AssemblyDefinition asm = null;
if (!_assemblies.TryGetValue (name.Name, out asm) && (_unresolvedAssemblies == null || !_unresolvedAssemblies.Contains (name.Name))) {
try {
asm = base.Resolve (name, parameters);
// Any full path explicit reference takes precedence over other look up logic
asm = ResolveFromReferences (name, _references, parameters);

// Fall back to the base class resolution logic
if (asm == null)
asm = base.Resolve (name, parameters);

_assemblies [name.Name] = asm;
} catch (AssemblyResolutionException) {
if (!_ignoreUnresolved)
Expand All @@ -96,6 +139,11 @@ public virtual AssemblyDefinition CacheAssembly (AssemblyDefinition assembly)
return assembly;
}

public void AddReferenceAssembly (string referencePath)
{
_references.Add (referencePath);
}

protected override void Dispose (bool disposing)
{
foreach (var asm in _assemblies.Values) {
Expand Down
4 changes: 2 additions & 2 deletions src/linker/Linker/DirectoryAssemblyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected DirectoryAssemblyResolver ()
directories = new Collection<string> (2) { "." };
}

AssemblyDefinition GetAssembly (string file, ReaderParameters parameters)
protected AssemblyDefinition GetAssembly (string file, ReaderParameters parameters)
{
if (parameters.AssemblyResolver == null)
parameters.AssemblyResolver = this;
Expand All @@ -50,7 +50,7 @@ public virtual AssemblyDefinition Resolve (AssemblyNameReference name, ReaderPar
if (name == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't the ArgumentNullException check now redundant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're redundant in the sense that the base class performs the same check, but also necessary to prevent passing null to SearchAssemblyPaths (renamed to ResolveFromReferences).
You're right that we already deref name in Resolve, so maybe that's not strictly necessary, but I still feel that it's safer to keep both checks (moved to the beginning of Resolve).

throw new ArgumentNullException ("name");
if (parameters == null)
parameters = new ReaderParameters ();
throw new ArgumentNullException ("parameters");

var assembly = SearchDirectory (name, directories, parameters);
if (assembly != null)
Expand Down
9 changes: 7 additions & 2 deletions src/linker/Linker/Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ public void Run (ILogger customLogger = null)
disabled_optimizations.Add (opt);

continue;

case "-reference":
context.Resolver.AddReferenceAssembly (GetParam ());
continue;
}

switch (token [2]) {
Expand Down Expand Up @@ -504,15 +508,15 @@ static void Usage (string msg)
Console.WriteLine (" -r Link from a list of assemblies using roots visible outside of the assembly");
Console.WriteLine (" -x Link from XML descriptor");
Console.WriteLine (" -d <path> Specify additional directories to search in for references");
Console.WriteLine (" -reference <file> Specify additional assemblies to use as references");
Console.WriteLine (" -b Update debug symbols for each linked module. Defaults to false");
Console.WriteLine (" -v Keep members and types used by debugger. Defaults to false");
Console.WriteLine (" -l <name>,<name> List of i18n assemblies to copy to the output directory. Defaults to 'all'");
Console.WriteLine (" Valid names are 'none', 'all', 'cjk', 'mideast', 'other', 'rare', 'west'");
Console.WriteLine (" -out <path> Specify the output directory. Defaults to 'output'");
Console.WriteLine (" --about About the {0}", _linker);
Console.WriteLine (" --verbose Log messages indicating progress and warnings");
Console.WriteLine (" --version Print the version number of the {0}", _linker);
Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false");
Console.WriteLine (" -out <path> Specify the output directory. Defaults to 'output'");

Console.WriteLine ();
Console.WriteLine ("Actions");
Expand Down Expand Up @@ -542,6 +546,7 @@ static void Usage (string msg)
Console.WriteLine (" --ignore-descriptors Skips reading embedded descriptors (short -z). Defaults to false");
Console.WriteLine (" --keep-facades Keep assemblies with type-forwarders (short -t). Defaults to false");
Console.WriteLine (" --new-mvid Generate a new guid for each linked assembly (short -g). Defaults to true");
Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false");
Console.WriteLine (" --strip-resources Remove XML descriptor resources for linked assemblies. Defaults to true");
Console.WriteLine (" --strip-security Remove metadata and code related to Code Access Security. Defaults to true");
Console.WriteLine (" --used-attrs-only Any attribute is removed if the attribute type is not used. Defaults to false");
Expand Down