question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Build of extensions can fail if path to project contains '#'

See original GitHub issue

Short description

During the build CreatePkgDef will fail if the path to contains a #. It doesn’t have to be in the solution folder or project folder itself. Anywhere in the path will trigger it, e.g. C:\Test#\Samples, with Samples being the solution folder.

The following versions of Microsoft.VSSDK.BuildTools are tested:

  • 17.1.9-preview1
  • 17.4.2119 (latest)

What happended

I couldn’t get https://github.com/VsixCommunity/Samples to build initially. It turned out that it was because of a # symbol in the path where I cloned the repository.

Example using C:\Test#\Samples

CreatePkgDef : error : 
  ProvideCodeBaseAttribute: Could not load specified assembly: 'Community.VisualStudio.Toolkit' 
  reason: Could not load file or assembly 'file:///C:\Community.VisualStudio.Toolkit.dll' or one of its dependencies.
  The system cannot find the file specified.

After trying everything else, I finally thought to remove the #. Then it built without a problem. I was curious why that would be the case, so I’ve spent a good amount of time tracking it down.

Cause

tldr; System.Reflection.Assembly.CodeBase is used instead of System.Reflection.Assembly.EscapedCodeBase

I’m not sure whether it’s fair to say the bug is in CreatePkgDef or ProvideCodeBaseAttribute, but the way the bug “works” is as follows:

The samples use Community.VisualStudio.Toolkit which has the assembly attribute [assembly: ProvideCodeBase(AssemblyName = “Community.VisualStudio.Toolkit”)]

For brevity, I’ve trimmed the code to show just the steps that involves the path. I’ll also use C# features not available in DotNetFramework. That’s also how it’s been decompiled in VS.

The path to the VSIX assembly is C:\Test#\Samples\InsertGuid\bin\Debug\InsertGuid.dll The path to the dll is C:\Test#\Samples\InsertGuid\bin\Debug\Community.VisualStudio.Toolkit.dll

CreatePkgDef

public static string DoCreatePkgDef( InputArguments inputArguments )
{
  using PkgDefContext pkgDefContext = new PkgDefContext( pkgDefFileHive, registerUsing, CreatePkgDef.consoleMode );
  CreatePkgDef.ProcessAssembly( inputArguments.FileName, pkgDefFileHive, pkgDefContext, true, RegistrationMode.PkgDef );
}

public static void ProcessAssembly( string fileName, Hive hive, PkgDefContext context, bool register, RegistrationMode mode )
{
  Assembly assembly = Assembly.LoadFrom(fileName); // @"C:\Test#\Samples\InsertGuid\bin\Debug\InsertGuid.dll"
  context.ComponentAssembly = assembly;

  var sortedList = new SortedList<object, List<RegistrationAttribute>>(AssemblyOrTypeComparer.Default);
  foreach ( var attr in assembly.GetCustomAttributes( true ) )
  {
    if ( attr is RegistrationAttribute )
    {
      sortedList[assembly].Add( attr );
    }
  }
  foreach ( (_,list) in sortedList )
  {
    foreach ( var attr in list )
    {
      attr.Register( context ); // attr is of type ProvideCodeBaseAttribute which is derived from ProvideDependentAssemblyAttribute
    }
  }
}

PkgDefContext

public sealed class PkgDefContext : RegistrationContext, IDisposable
{
  public Assembly ComponentAssembly { get; set; }
  public override string CodeBase => ComponentAssembly.CodeBase; // this leads to the bug
}

ProvideDependentAssemblyAttribute

public override void Register(RegistrationContext context)
{
  UpdateCurrentAssembly(context.CodeBase);                          // "file:///C:/Test#/Samples/InsertGuid/bin/Debug/InsertGuid.dll"
}
private void UpdateCurrentAssembly(string targetCodeBase)           // "file:///C:/Test#/Samples/InsertGuid/bin/Debug/InsertGuid.dll"
{
  targetCodeBase = new Uri(targetCodeBase).LocalPath;               // @"C:\Test"
  //.. 
  string directoryName = Path.GetDirectoryName(targetCodeBase);     // @"C:\"
  string path = AssemblyName + ".dll";                              // "Community.VisualStudio.Toolkit.dll"
  directoryName = Path.Combine(directoryName, path);                // @"C:\Community.VisualStudio.Toolkit.dll"
  CurrentAssembly = LoadAssembly(directoryName, out errorReason);   // this fails because of a FileNotFoundException
}

Fix

RegistrationContext.CodeBase’s value comes from Assembly.CodeBase.

Assembly.CodeBase is be functionally similar to "file:///" + path.Replace('\\','/'). Assembly.EscapedCodeBase is functionally equivalent to new Guid( new Guid( "file:///" ), path).AbsoluteUri.

assembly = Assembly.LoadFrom( @"C:\Test#\Samples\InsertGuid\bin\Debug\InsertGuid.dll" );

assembly.CodeBase        == "file:///C:/Test#/Samples/InsertGuid/bin/Debug/InsertGuid.dll"
assembly.EscapedCodeBase == "file:///C:/Test%23/Samples/InsertGuid/bin/Debug/InsertGuid.dll"

new Uri( assembly.CodeBase ).LocalPath        == @"C:\Test"
new Uri( assembly.EscapedCodeBase ).LocalPath == @"C:\Test#\Samples\InsertGuid\bin\Debug\InsertGuid.dll"

Option 1: Change PkgDefContext.CodeBase property

-    return this.ComponentAssembly.CodeBase;
+    return this.ComponentAssembly.EscapedCodeBase;

Option 2: Change ProvideDependentAssemblyAttribute.Register method

-    UpdateCurrentAssembly(context.CodeBase);
+    UpdateCurrentAssembly(context.EscapedCodeBase);

This would also require changes to RegistrationAttribute.RegistrationContext since EscapedCodeBase is not part of that class. It could be implemented there as public virtual string EscapedCodeBase => CodeBase;. PkgDefContext could then override it public override string EscapedCodeBase => ComponentAssembly.EscapedCodeBase;.

One advantage with option is that Register could then try CodeBase first, and if that fails it could retry using EscapedCodeBase.

Risks

This could break stuff, so it might be better to try CodeBase first, and if not found, try EscapedCodeBase.

Issue Analytics

  • State:closed
  • Created 9 months ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
reducktedcommented, Dec 15, 2022

@BertanAygun This is not specific to the Community Toolkit. You can create a regular VSIX project from the standard template, add the ProvideCodeBase attribute and it will fail to compile if the directory contains a #.

0reactions
dcastenholzcommented, Jan 5, 2023

This is only at build time, but unfortunately, we are not able to change the folder name at this point.

This prevents us from using the Community extensibility template extension. Just means more work for us.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Extension could not be found. Please make sure the ...
I can see that an extension is added to the experimental instance of Visual Studio when you successfully build the project (I've found...
Read more >
Build path contains project build with "Create Visual Studio ...
Question - Build path contains project build with "Create Visual Studio Solution" option, which is incompatible - Unity Forum.
Read more >
Troubleshoot broken references - Visual Studio
If projects are shared on different computers, some references might not be found when a component is located in a different directory.
Read more >
Cannot debug net6.0-macos Apps - Developer Community
When I hit debug, the following error is shown in the Terminal: Possible reasons for this include: * You misspelled a built-in dotnet...
Read more >
Creating an extension using only Visual Studio
If you decide to store your extension somewhere else, some paths in the post-build step must be changed. More about that under "Customizing...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found