diff --git a/RLBotCS/Main.cs b/RLBotCS/Main.cs index d3148fe..4808238 100644 --- a/RLBotCS/Main.cs +++ b/RLBotCS/Main.cs @@ -10,7 +10,7 @@ if (args.Length > 0 && args[0] == "--version") { Console.WriteLine( - "RLBotServer v5.0.0-rc17\n" + "RLBotServer v5.0.0-rc.18\n" + $"Bridge {BridgeVersion.Version}\n" + "@ https://www.rlbot.org & https://github.com/RLBot/core" ); diff --git a/RLBotCS/ManagerTools/ConfigParser.cs b/RLBotCS/ManagerTools/ConfigParser.cs index a5e01e1..fa9718c 100644 --- a/RLBotCS/ManagerTools/ConfigParser.cs +++ b/RLBotCS/ManagerTools/ConfigParser.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using RLBot.Flat; using RLBotCS.Model; @@ -77,6 +78,8 @@ public static class Fields public const string AgentRunCommand = "run_command"; public const string AgentRunCommandLinux = "run_command_linux"; public const string AgentEnvironment = "environment"; + public const string AgentEnvironmentWindows = "windows"; + public const string AgentEnvironmentLinux = "linux"; public const string AgentHivemind = "hivemind"; public const string LoadoutBlueTable = "blue_loadout"; @@ -261,6 +264,26 @@ private string GetRunCommand(TomlTable runnableSettings) #endif } + private static readonly Regex UnixEnvironmentVariablePattern = new( + @"\$(?:\{(?[A-Za-z_][A-Za-z0-9_]*)\}|(?[A-Za-z_][A-Za-z0-9_]*))", + RegexOptions.Compiled + ); + + private static string ExpandEnvironmentValue(string value) + { + value = Environment.ExpandEnvironmentVariables(value); + return UnixEnvironmentVariablePattern.Replace( + value, + match => + { + string name = match.Groups["braced"].Success + ? match.Groups["braced"].Value + : match.Groups["bare"].Value; + return Environment.GetEnvironmentVariable(name) ?? match.Value; + } + ); + } + private List GetEnvironment(TomlTable runnableSettings) { TomlTable environment = GetValue( @@ -269,11 +292,21 @@ private List GetEnvironment(TomlTable runnableSettings) [] ); - List variables = []; - using (_context.Begin(Fields.AgentEnvironment)) + Dictionary values = new(StringComparer.Ordinal); + + void ReadValues(TomlTable table, bool skipPlatformTables, bool expandValues) { - foreach (var (key, rawValue) in environment) + foreach (var (key, rawValue) in table) { + if ( + skipPlatformTables + && ( + key == Fields.AgentEnvironmentWindows + || key == Fields.AgentEnvironmentLinux + ) + ) + continue; + if (rawValue is not string value) { throw new InvalidCastException( @@ -281,11 +314,41 @@ private List GetEnvironment(TomlTable runnableSettings) ); } - variables.Add(new() { Name = key, Value = value }); + values[key] = expandValues ? ExpandEnvironmentValue(value) : value; + } + } + + using (_context.Begin(Fields.AgentEnvironment)) + { + ReadValues(environment, true, false); + + string? platform = + OperatingSystem.IsWindows() ? Fields.AgentEnvironmentWindows + : OperatingSystem.IsLinux() ? Fields.AgentEnvironmentLinux + : null; + + if ( + platform is not null + && environment.TryGetValue(platform, out var rawPlatformEnvironment) + ) + { + if (rawPlatformEnvironment is not TomlTable platformEnvironment) + { + throw new InvalidCastException( + $"{_context.ToStringWithEnd(platform)} has value {rawPlatformEnvironment}, but a table was expected." + ); + } + + using (_context.Begin(platform)) + { + ReadValues(platformEnvironment, false, true); + } } } - return variables; + return values + .Select(pair => new EnvironmentVariableT { Name = pair.Key, Value = pair.Value }) + .ToList(); } private ScriptConfigurationT LoadScriptConfig(string scriptConfigPath) diff --git a/RLBotCSTests/ConfigParserTest.cs b/RLBotCSTests/ConfigParserTest.cs index 853ec95..e25d9ba 100644 --- a/RLBotCSTests/ConfigParserTest.cs +++ b/RLBotCSTests/ConfigParserTest.cs @@ -200,6 +200,76 @@ public void Overrides() ); } + [TestMethod] + public void PlatformEnvironmentOverrides() + { + ConfigParser parser = new(); + MatchConfigurationT mc = parser.LoadMatchConfig("TestTomls/platform.toml"); + + string expectedShared = + OperatingSystem.IsWindows() ? "windows-bot-value" + : OperatingSystem.IsLinux() ? "linux-bot-value" + : "common-bot-value"; + string expectedScriptShared = + OperatingSystem.IsWindows() ? "windows-script-value" + : OperatingSystem.IsLinux() ? "linux-script-value" + : "common-script-value"; + + CustomBotT bot = mc.PlayerConfigurations[0].Variety.AsCustomBot(); + Assert.AreEqual( + "$HOME/common-value", + bot.Environment.Single(e => e.Name == "COMMON_ENV").Value + ); + Assert.AreEqual( + expectedShared, + bot.Environment.Single(e => e.Name == "SHARED_ENV").Value + ); + + ScriptConfigurationT script = mc.ScriptConfigurations[0]; + Assert.AreEqual( + "$HOME/common-value", + script.Environment.Single(e => e.Name == "COMMON_ENV").Value + ); + Assert.AreEqual( + expectedScriptShared, + script.Environment.Single(e => e.Name == "SHARED_ENV").Value + ); + + if (OperatingSystem.IsWindows()) + { + Assert.AreEqual( + Environment.ExpandEnvironmentVariables( + "%LOCALAPPDATA%\\RLBot5\\bots\\torch-archive" + ), + bot.Environment.Single(e => e.Name == "PLATFORM_ENV").Value + ); + Assert.AreEqual( + Environment.ExpandEnvironmentVariables( + "%LOCALAPPDATA%\\RLBot5\\bots\\torch-archive" + ), + script.Environment.Single(e => e.Name == "PLATFORM_ENV").Value + ); + } + else if (OperatingSystem.IsLinux()) + { + string expectedPlatform = + $"{Environment.GetEnvironmentVariable("HOME")}/.local/share/RLBot5/bots/torch-archive"; + Assert.AreEqual( + expectedPlatform, + bot.Environment.Single(e => e.Name == "PLATFORM_ENV").Value + ); + Assert.AreEqual( + expectedPlatform, + script.Environment.Single(e => e.Name == "PLATFORM_ENV").Value + ); + } + else + { + Assert.IsFalse(bot.Environment.Any(e => e.Name == "PLATFORM_ENV")); + Assert.IsFalse(script.Environment.Any(e => e.Name == "PLATFORM_ENV")); + } + } + [TestMethod] public void ConfigNotFound() { diff --git a/RLBotCSTests/TestTomls/platform.bot.toml b/RLBotCSTests/TestTomls/platform.bot.toml new file mode 100644 index 0000000..5369542 --- /dev/null +++ b/RLBotCSTests/TestTomls/platform.bot.toml @@ -0,0 +1,19 @@ +[settings] +agent_id = "test/platform-bot" +name = "Platform Test Bot" +run_command = "python bot.py" +run_command_linux = "python bot.py" + +[settings.environment] +COMMON_ENV = "$HOME/common-value" +SHARED_ENV = "common-bot-value" + +[settings.environment.windows] +SHARED_ENV = "windows-bot-value" +PLATFORM_ENV = "%LOCALAPPDATA%\\RLBot5\\bots\\torch-archive" + +[settings.environment.linux] +SHARED_ENV = "linux-bot-value" +PLATFORM_ENV = "$HOME/.local/share/RLBot5/bots/torch-archive" + +[details] diff --git a/RLBotCSTests/TestTomls/platform.script.toml b/RLBotCSTests/TestTomls/platform.script.toml new file mode 100644 index 0000000..af80fd4 --- /dev/null +++ b/RLBotCSTests/TestTomls/platform.script.toml @@ -0,0 +1,19 @@ +[settings] +agent_id = "test/platform-script" +name = "Platform Test Script" +run_command = "python script.py" +run_command_linux = "python script.py" + +[settings.environment] +COMMON_ENV = "$HOME/common-value" +SHARED_ENV = "common-script-value" + +[settings.environment.windows] +SHARED_ENV = "windows-script-value" +PLATFORM_ENV = "%LOCALAPPDATA%\\RLBot5\\bots\\torch-archive" + +[settings.environment.linux] +SHARED_ENV = "linux-script-value" +PLATFORM_ENV = "$HOME/.local/share/RLBot5/bots/torch-archive" + +[details] diff --git a/RLBotCSTests/TestTomls/platform.toml b/RLBotCSTests/TestTomls/platform.toml new file mode 100644 index 0000000..2146c93 --- /dev/null +++ b/RLBotCSTests/TestTomls/platform.toml @@ -0,0 +1,7 @@ +[match] + +[[cars]] +config_file = "platform.bot.toml" + +[[scripts]] +config_file = "platform.script.toml"