Skip to content

Commit f8581ac

Browse files
committed
program: fix computation of app path with single file
When publishing GCM as a 'single file' application, the computed path to the entry executable is no longer correct. On .NET Core 3.1, using `Assembly.Location` resolves to the temporary extracted DLL file path. On .NET 5 `Assembly.Location` always returns the empty string. Since .NET 5, published single-file apps no longer use the self- extraction model, and are real single file with all assemblies and native libraries statically linked/bundled. We now use the `Environment.GetCommandLineArgs()` method to get the raw underlying "argv" arguments, which argv[0] is the absolute file path to the entry executable. We also change how we get the application version number to look for the assembly attribute, rather than extract if from the file on disk. At app startup, also change the way we trace system information to be more readable.
1 parent 7269802 commit f8581ac

File tree

3 files changed

+37
-28
lines changed

3 files changed

+37
-28
lines changed

src/shared/Git-Credential-Manager/Program.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.IO;
55
using System.Reflection;
6+
using System.Runtime.InteropServices;
67
using Atlassian.Bitbucket;
78
using GitHub;
89
using Microsoft.AzureRepos;
@@ -36,16 +37,19 @@ public static void Main(string[] args)
3637

3738
private static string GetApplicationPath()
3839
{
39-
Assembly entryAssembly = Assembly.GetExecutingAssembly();
40-
if (entryAssembly is null)
41-
{
42-
throw new InvalidOperationException();
43-
}
40+
// Assembly::Location always returns an empty string if the application was published as a single file
41+
#pragma warning disable IL3000
42+
bool isSingleFile = string.IsNullOrEmpty(Assembly.GetEntryAssembly()?.Location);
43+
#pragma warning restore IL3000
4444

45-
string candidatePath = entryAssembly.Location;
45+
// Use "argv[0]" to get the full path to the entry executable - this is consistent across
46+
// .NET Framework and .NET >= 5 when published as a single file.
47+
string[] args = Environment.GetCommandLineArgs();
48+
string candidatePath = args[0];
4649

47-
// Strip the .dll from assembly name on Mac and Linux
48-
if (!PlatformUtils.IsWindows() && Path.HasExtension(candidatePath))
50+
// If we have not been published as a single file on .NET 5 then we must strip the ".dll" file extension
51+
// to get the default AppHost/SuperHost name.
52+
if (!isSingleFile && Path.HasExtension(candidatePath))
4953
{
5054
return Path.ChangeExtension(candidatePath, null);
5155
}

src/shared/Microsoft.Git.CredentialManager/Application.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,13 @@ protected override async Task<int> RunInternalAsync(string[] args)
7676
rootCommand.AddCommand(providerCommand);
7777
}
7878

79-
// Trace the current version and program arguments
80-
Context.Trace.WriteLine($"{Constants.GetProgramHeader()} '{string.Join(" ", args)}'");
79+
// Trace the current version, OS, runtime, and program arguments
80+
PlatformInformation info = PlatformUtils.GetPlatformInformation();
81+
Context.Trace.WriteLine($"Version: {Constants.GcmVersion}");
82+
Context.Trace.WriteLine($"Runtime: {info.ClrVersion}");
83+
Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})");
84+
Context.Trace.WriteLine($"AppPath: {_appPath}");
85+
Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}");
8186

8287
try
8388
{

src/shared/Microsoft.Git.CredentialManager/Constants.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
3-
using System.Diagnostics;
3+
using System;
44
using System.Reflection;
55

66
namespace Microsoft.Git.CredentialManager
@@ -105,35 +105,35 @@ public static class HelpUrls
105105
public const string GcmLinuxCredStores = "https://aka.ms/gcmcore-linuxcredstores";
106106
}
107107

108-
private static string _gcmVersion;
108+
private static Version _gcmVersion;
109109

110-
/// <summary>
111-
/// The current version of Git Credential Manager.
112-
/// </summary>
113-
public static string GcmVersion
110+
public static Version GcmVersion
114111
{
115112
get
116113
{
117114
if (_gcmVersion is null)
118115
{
119-
_gcmVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
116+
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
117+
var attr = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>();
118+
if (attr is null)
119+
{
120+
_gcmVersion = assembly.GetName().Version;
121+
}
122+
else if (Version.TryParse(attr.Version, out Version asmVersion))
123+
{
124+
_gcmVersion = asmVersion;
125+
}
126+
else
127+
{
128+
// Unknown version!
129+
_gcmVersion = new Version(0, 0);
130+
}
120131
}
121132

122133
return _gcmVersion;
123134
}
124135
}
125136

126-
/// <summary>
127-
/// Get standard program header title for Git Credential Manager, including the current version and OS information.
128-
/// </summary>
129-
/// <returns>Standard program header.</returns>
130-
public static string GetProgramHeader()
131-
{
132-
PlatformInformation info = PlatformUtils.GetPlatformInformation();
133-
134-
return $"Git Credential Manager version {GcmVersion} ({info.OperatingSystemType}, {info.ClrVersion})";
135-
}
136-
137137
/// <summary>
138138
/// Get the HTTP user-agent for Git Credential Manager.
139139
/// </summary>

0 commit comments

Comments
 (0)