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

Respect AppContext.SetData with APP_CONFIG_FILE key #56748

Merged
merged 6 commits into from Aug 5, 2021

Conversation

krwq
Copy link
Member

@krwq krwq commented Aug 2, 2021

Fixes: #931

Full framework works with following path types:

  • relative path, i.e.: myconfig.config
  • absolute path
  • file URI, i.e.: file:///c:/myconfig.config

additionally:

  • when file doesn't exists there is no error and all settings read as null
  • when file is malformed you get an error

}
else
{
ApplicationConfigUri = Path.GetFullPath(externalConfigPath);
Copy link
Member

Choose a reason for hiding this comment

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

This is implicitly prefixing with whatever the current directory is at the time the config system is initialized, I guess?

In the desktop code it is using the appbase to prefix (naming.cpp line 3241 .. assuming I'm looking in the right place)

I think in .NET Core that is AppDomain.BaseDirectory, so should this be Path.Combine(AppDomain.CurrentDomain.BaseDirectory, externalConfigPath) ? There is something similar higher up.

Copy link
Member

Choose a reason for hiding this comment

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

You can change the tests to set Environment.CurrentDirectory to something random before reading the value (I'm guessing the test will fail right now)

Copy link
Member

Choose a reason for hiding this comment

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

When you use Path.Combine you need to be careful with paths that begin with \, as you'll get the second path against the root of the current directory. Trim the start of the externalConfigPath for safety.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have used Path.GetFullPath(externalConfigPath, AppDomain.CurrentDomain.BaseDirectory) which seems to be working fine. The tests are now changing CurrentDirectory to validate we are relative to the BaseDirectory and not CurrentDirectory

Copy link
Member Author

Choose a reason for hiding this comment

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

hmm seems that overload is not available everywhere...

@danmoseley
Copy link
Member

@JeremyKuhne we don't have much familiarity with the config system -- does this seem reasonable based on your recollection of things?


private static string CreateAppConfigFileWithSetting(string key, string rawUnquotedValue)
{
string fileName = Path.GetRandomFileName() + ".config";
Copy link
Member

Choose a reason for hiding this comment

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

These files will leak. it might be easiest to derive from FileCleanupTestBase and call GetTestFileName().

Copy link
Member Author

Choose a reason for hiding this comment

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

this gets a bit tricky when I want to test @JeremyKuhne's scenario #56748 (comment)

I'll use that for everything but related test cases

Copy link
Member Author

@krwq krwq Aug 3, 2021

Choose a reason for hiding this comment

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

I have ended up modifying constructor of FileCleanupTestBase so that it allows to pickup something else than TempDirectory which in here I chose to be AppDomain.BaseDirectory - TempDirectory doesn't allow me to test relative paths correctly

@JeremyKuhne
Copy link
Member

@JeremyKuhne we don't have much familiarity with the config system -- does this seem reasonable based on your recollection of things?

@danmoseley Yes, this seems right.

@krwq krwq closed this Aug 3, 2021
@krwq krwq reopened this Aug 3, 2021
@krwq
Copy link
Member Author

krwq commented Aug 3, 2021

Please re-review since a bit changed here

{
// Uri constructor would throw for relative paths but full framework doesn't.
// We will mimic full framework behavior here.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && externalConfigPath.Length >= 2 && externalConfigPath.StartsWith('\\') && externalConfigPath[1] != '\\')
Copy link
Member

Choose a reason for hiding this comment

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

Is there some way to avoid the OS check here? We don't normally do those. What if on Unix it started with '/' ... should it trim whatever the Path.DirectorySeparatorChar is?

Copy link
Member

Choose a reason for hiding this comment

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

Just always trim any amount of leading Path.DirectorySeparatorChar and you should be good.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think for Unix we will never go into this code anyway since that would mean absolute path which should be covered by the first check. I'll go ahead and trim all separators on the left - that I think this should cover 99% of the cases

Copy link
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

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

thanks for adding this

@danmoseley
Copy link
Member

(signed off assuming you can fix the OS check as above)

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && externalConfigPath.Length >= 2 && externalConfigPath.StartsWith('\\') && externalConfigPath[1] != '\\')
{
// if path starts with a single backslash Path.Combine would combine with a root of BaseDirectory so we trim that character to avoid that
externalConfigPath = externalConfigPath.Substring(0, externalConfigPath.Length - 1);
Copy link
Member

Choose a reason for hiding this comment

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

Just wondering - does this match the .NET Framework behavior for paths starting with a single slash?

Copy link
Member

Choose a reason for hiding this comment

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

using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
         AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", @"\abc");
         Console.WriteLine(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
    }
}

It prints c:\abc, without taking BaseDirectory into account.

Copy link
Member

Choose a reason for hiding this comment

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

To keep things simple, I would make this just:

if (!string.IsNullOrEmpty(externalConfigPath))
{
    if (!Path.IsPathRooted(externalConfigPath))
    {
        path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, externalConfigPath);
    }
    path = Path.GetFullPath(path);
}
else if (!string.IsNullOrEmpty(ApplicationUri))

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll go ahead with solution suggested by @JeremyKuhne (#56748 (comment)), it's probably not covering all corner cases but it should be close enough. For the backslash starting directories I do not have any strong feeling we should support that as it's hard to describe the behavior in any docs especially since now we also work on Unix

Copy link
Member

Choose a reason for hiding this comment

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

I'll go ahead with solution suggested by @JeremyKuhne (#56748 (comment))

Is there any other existing place in dotnet/runtime that is normalizing non-fully qualified paths using this algorithm? I think it does not have very intuitive behavior, e.g. it does not match what Path.GetFullPath(string path, string basePath)
does.

it should be close enough

I would pick the simplest solution, among the "close enough" ones. It was the gist of my suggestion.

Copy link
Member

Choose a reason for hiding this comment

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

Sadly Path.IsPathRooted will get you "relative" paths. Path.IsPathFullyQualified (new) is the one that gives you accurate indication of whether or not the current working directory will impact the path.

It is unlikely that customers would actually desire paths that start with \ to be rooted to the current working directory drive. I think that is enough to be helpful here, other weird scenarios like C:foo\ don't need addressed I think.

@jkotas
Copy link
Member

jkotas commented Aug 4, 2021

Full framework works with following path types:

I do not think it is necessary to match all .NET Framework behaviors to keep things simple. It is unlikely that you would be able to match the behavior for all corner cases anyway.

The only case that is required to make the customers successful is the absolute path. The rest is optional and can be omitted.

@jeffhandley jeffhandley added this to the 6.0.0 milestone Aug 4, 2021
// We will mimic full framework behavior here.

// If path starts with directory separator Path.Combine would combine with a root of BaseDirectory so we trim that character to avoid that
// For absolute path we should already be covered by the other code path
Copy link
Member

Choose a reason for hiding this comment

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

I do not think that this is a correct statement for Linux.

Uri.IsWellFormedUriString("/home/jkotas/myconfig.xml", UriKind.Absolute) returns false on Linux.

Copy link
Member Author

Choose a reason for hiding this comment

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

you're right, I've done some combination of your solution but which supports URIs as well

@krwq krwq merged commit d329350 into dotnet:main Aug 5, 2021
@dotnet dotnet locked as resolved and limited conversation to collaborators Sep 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide a way to override the global configuration file path
5 participants