diff --git a/Source/Client/Core/Source/Build/BuildEngine.h b/Source/Client/Core/Source/Build/BuildEngine.h index 82e5084d8..a082d4beb 100644 --- a/Source/Client/Core/Source/Build/BuildEngine.h +++ b/Source/Client/Core/Source/Build/BuildEngine.h @@ -86,25 +86,16 @@ namespace Soup::Core { auto startTime = std::chrono::high_resolution_clock::now(); - // Load the local user config and any sdk content - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector(); - LoadLocalUserConfig(sdkParameters, sdkReadAccess); - - auto endTime = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast>(endTime - startTime); - - // std::cout << "LoadLocalUserConfig: " << std::to_string(duration.count()) << " seconds." << std::endl; - - startTime = std::chrono::high_resolution_clock::now(); + // Load user config state + auto userDataPath = GetSoupUserDataPath(); // Load the system specific state auto hostBuildGlobalParameters = ValueTable(); auto systemReadAccess = std::vector(); LoadHostSystemState(hostBuildGlobalParameters, systemReadAccess); - endTime = std::chrono::high_resolution_clock::now(); - duration = std::chrono::duration_cast>(endTime - startTime); + auto endTime = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast>(endTime - startTime); // std::cout << "LoadSystemState: " << std::to_string(duration.count()) << " seconds." << std::endl; @@ -119,6 +110,7 @@ namespace Soup::Core builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = loadEngine.Load(); @@ -144,8 +136,7 @@ namespace Soup::Core // for each individual package auto buildRunner = BuildRunner( arguments, - sdkParameters, - sdkReadAccess, + userDataPath, systemReadAccess, recipeCache, packageProvider, @@ -161,53 +152,11 @@ namespace Soup::Core } private: - /// - /// Load Local User Config and process any known state - /// - static void LoadLocalUserConfig( - ValueList& sdkParameters, - std::vector& sdkReadAccess) + static Path GetSoupUserDataPath() { - // Load the local user config - auto localUserConfigPath = System::IFileSystem::Current().GetUserProfileDirectory() + - BuildConstants::SoupLocalStoreDirectory() + - BuildConstants::LocalUserConfigFileName(); - LocalUserConfig localUserConfig = {}; - if (!LocalUserConfigExtensions::TryLoadLocalUserConfigFromFile(localUserConfigPath, localUserConfig)) - { - Log::Warning("Local User Config invalid"); - } - - // Process the SDKs - if (localUserConfig.HasSDKs()) - { - Log::Info("Checking SDKs for read access"); - auto sdks = localUserConfig.GetSDKs(); - for (auto& sdk : sdks) - { - auto sdkName = sdk.GetName(); - Log::Info("Found SDK: " + sdkName); - if (sdk.HasSourceDirectories()) - { - for (auto& sourceDirectory : sdk.GetSourceDirectories()) - { - Log::Info(" Read Access: " + sourceDirectory.ToString()); - sdkReadAccess.push_back(sourceDirectory); - } - } - - auto sdkParameter = ValueTable(); - sdkParameter.emplace("Name", Value(sdkName)); - if (sdk.HasProperties()) - { - sdkParameter.emplace( - "Properties", - RecipeBuildStateConverter::ConvertToBuildState(sdk.GetProperties())); - } - - sdkParameters.push_back(std::move(sdkParameter)); - } - } + auto result = System::IFileSystem::Current().GetUserProfileDirectory() + + BuildConstants::SoupLocalStoreDirectory(); + return result; } static void LoadHostSystemState( @@ -215,7 +164,7 @@ namespace Soup::Core std::vector& systemReadAccess) { auto system = std::string("Windows"); - hostBuildGlobalParameters.emplace("System", Value(std::string(system))); + hostBuildGlobalParameters.emplace("System", Value(system)); // Allow read access from system directories systemReadAccess.push_back( diff --git a/Source/Client/Core/Source/Build/BuildLoadEngine.h b/Source/Client/Core/Source/Build/BuildLoadEngine.h index c93d65fae..de3151b4a 100644 --- a/Source/Client/Core/Source/Build/BuildLoadEngine.h +++ b/Source/Client/Core/Source/Build/BuildLoadEngine.h @@ -48,6 +48,7 @@ namespace Soup::Core const RecipeBuildArguments& _arguments; // System Parameters + Path _userDataPath; const ValueTable& _hostBuildGlobalParameters; // Shared Runtime State @@ -72,11 +73,13 @@ namespace Soup::Core const std::map>& builtInPackageLookup, const RecipeBuildArguments& arguments, const ValueTable& hostBuildGlobalParameters, + Path userDataPath, RecipeCache& recipeCache) : _knownLanguageLookup(knownLanguageLookup), _builtInPackageLookup(builtInPackageLookup), _arguments(arguments), _hostBuildGlobalParameters(hostBuildGlobalParameters), + _userDataPath(std::move(userDataPath)), _recipeCache(recipeCache), _packageGraphLookup(), _packageLookup(), @@ -228,7 +231,7 @@ namespace Soup::Core else { // Build the global store location path - auto packageStore = GetSoupUserDataPath() + Path("packages/"); + auto packageStore = _userDataPath + Path("packages/"); auto& languageSafeName = GetLanguageSafeName(activeReference.GetLanguage()); auto activeVersionString = activeReference.GetVersion().ToString(); packagePath = packageStore + @@ -271,7 +274,7 @@ namespace Soup::Core else { // Build the global store location path - auto packageStore = GetSoupUserDataPath() + Path("locks/"); + auto packageStore = _userDataPath + Path("locks/"); auto& languageSafeName = GetLanguageSafeName(activeReference.GetLanguage()); packagePath = packageStore + Path(languageSafeName) + @@ -934,12 +937,5 @@ namespace Soup::Core return knownLanguageResult->second.LanguageSafeName; } - - Path GetSoupUserDataPath() const - { - auto result = System::IFileSystem::Current().GetUserProfileDirectory() + - BuildConstants::SoupLocalStoreDirectory(); - return result; - } }; } diff --git a/Source/Client/Core/Source/Build/BuildRunner.h b/Source/Client/Core/Source/Build/BuildRunner.h index 81078a4e1..d04b61e9a 100644 --- a/Source/Client/Core/Source/Build/BuildRunner.h +++ b/Source/Client/Core/Source/Build/BuildRunner.h @@ -34,11 +34,8 @@ namespace Soup::Core // Root arguments const RecipeBuildArguments& _arguments; - // SDK Parameters - const ValueList& _sdkParameters; - const std::vector& _sdkReadAccess; - // System Parameters + Path _userDataPath; const std::vector& _systemReadAccess; // Shared Runtime @@ -60,8 +57,7 @@ namespace Soup::Core /// BuildRunner( const RecipeBuildArguments& arguments, - const ValueList& sdkParameters, - const std::vector& sdkReadAccess, + Path userDataPath, const std::vector& systemReadAccess, RecipeCache& recipeCache, PackageProvider& packageProvider, @@ -69,8 +65,7 @@ namespace Soup::Core FileSystemState& fileSystemState, RecipeBuildLocationManager& locationManager) : _arguments(arguments), - _sdkParameters(sdkParameters), - _sdkReadAccess(sdkReadAccess), + _userDataPath(std::move(userDataPath)), _systemReadAccess(systemReadAccess), _recipeCache(recipeCache), _packageProvider(packageProvider), @@ -358,9 +353,6 @@ namespace Soup::Core // Generate the dependencies input state globalState.emplace("Dependencies", GenerateParametersDependenciesValueTable(packageInfo)); - // Pass along the sdks - globalState.emplace("SDKs", _sdkParameters); - inputTable.emplace("GlobalState", std::move(globalState)); // Build up the input state for the generate call @@ -374,14 +366,10 @@ namespace Soup::Core for (auto& [key, value] : packageAccessSet.GenerateCurrentMacros) generateMacros.emplace(key, value); - // Make subgraph macros are unique + // Make subgraph macros unique for (auto& [key, value] : packageAccessSet.GenerateSubGraphMacros) generateSubGraphMacros.emplace(key, value); - // Allow read access for all sdk directories - for (auto& value : _sdkReadAccess) - evaluateAllowedReadAccess.push_back(value.ToString()); - // Pass along the read access set for (auto& value : packageAccessSet.EvaluateCurrentReadDirectories) evaluateAllowedReadAccess.push_back(value.ToString()); @@ -399,6 +387,7 @@ namespace Soup::Core evaluateMacros.emplace(key, value); inputTable.emplace("PackageRoot", packageInfo.PackageRoot.ToString()); + inputTable.emplace("UserDataPath", _userDataPath.ToString()); inputTable.emplace("GenerateMacros", std::move(generateMacros)); inputTable.emplace("GenerateSubGraphMacros", std::move(generateSubGraphMacros)); inputTable.emplace("EvaluateReadAccess", std::move(evaluateAllowedReadAccess)); @@ -454,9 +443,13 @@ namespace Soup::Core auto generateAllowedReadAccess = std::vector(); auto generateAllowedWriteAccess = std::vector(); - // Allow read access to the generate executable folder, Windows and the DotNet install + // Allow read access to the generate executable folder generateAllowedReadAccess.push_back(generateFolder); + // Allow read access to the local user config + auto localUserConfigPath = _userDataPath + BuildConstants::LocalUserConfigFileName(); + generateAllowedReadAccess.push_back(std::move(localUserConfigPath)); + // TODO: Windows specific generateAllowedReadAccess.push_back(Path("C:/Windows/")); generateAllowedReadAccess.push_back(Path("C:/Program Files/dotnet/")); @@ -553,12 +546,6 @@ namespace Soup::Core allowedReadAccess.push_back(temporaryDirectory); allowedWriteAccess.push_back(temporaryDirectory); - // TODO: REMOVE - Allow read access from SDKs - std::copy( - _sdkReadAccess.begin(), - _sdkReadAccess.end(), - std::back_inserter(allowedReadAccess)); - // Ensure the temporary directories exists if (!System::IFileSystem::Current().Exists(temporaryDirectory)) { diff --git a/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h b/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h index 6a6a6ee2c..63bdfe227 100644 --- a/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h @@ -16,12 +16,14 @@ namespace Soup::Core::UnitTests auto builtInPackages = std::map>(); auto arguments = RecipeBuildArguments(); auto hostBuildGlobalParameters = ValueTable(); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); } @@ -32,12 +34,14 @@ namespace Soup::Core::UnitTests auto builtInPackages = std::map>(); auto arguments = RecipeBuildArguments(); auto hostBuildGlobalParameters = ValueTable(); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); } @@ -129,12 +133,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -325,12 +331,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -543,12 +551,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -838,12 +848,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -1091,12 +1103,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -1353,12 +1367,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -1643,12 +1659,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -1909,12 +1927,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -2199,12 +2219,14 @@ namespace Soup::Core::UnitTests { { "HostValue", Value(true) }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -2553,12 +2575,14 @@ namespace Soup::Core::UnitTests Value(true), }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -2908,12 +2932,14 @@ namespace Soup::Core::UnitTests Value(true), }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); @@ -3177,12 +3203,14 @@ namespace Soup::Core::UnitTests Value(true), }, }); + auto userDataPath = Path("C:/User/Soup/"); auto recipeCache = RecipeCache(); auto uut = BuildLoadEngine( knownLanguages, builtInPackages, arguments, hostBuildGlobalParameters, + userDataPath, recipeCache); auto packageProvider = uut.Load(); diff --git a/Source/Client/Core/UnitTests/Build/BuildRunnerTests.h b/Source/Client/Core/UnitTests/Build/BuildRunnerTests.h index d1b2a8604..b177a0589 100644 --- a/Source/Client/Core/UnitTests/Build/BuildRunnerTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildRunnerTests.h @@ -14,8 +14,7 @@ namespace Soup::Core::UnitTests void Initialize_Success() { auto arguments = RecipeBuildArguments(); - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector(); + auto userDataPath = Path("C:/User/Soup/"); auto systemReadAccess = std::vector(); auto recipeCache = RecipeCache(); auto packageProvider = PackageProvider(1, PackageGraphLookupMap(), PackageLookupMap()); @@ -24,10 +23,9 @@ namespace Soup::Core::UnitTests auto knownLanguages = std::map(); auto locationManager = RecipeBuildLocationManager(knownLanguages); auto uut = BuildRunner( - std::move(arguments), - std::move(sdkParameters), - std::move(sdkReadAccess), - std::move(systemReadAccess), + arguments, + userDataPath, + systemReadAccess, recipeCache, packageProvider, evaluateEngine, @@ -64,10 +62,7 @@ namespace Soup::Core::UnitTests auto arguments = RecipeBuildArguments(); arguments.HostPlatform = "TestPlatform"; arguments.WorkingDirectory = Path("C:/WorkingDirectory/MyPackage/"); - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector({ - Path("C:/FakeSDK/"), - }); + auto userDataPath = Path("C:/User/Soup/"); auto systemReadAccess = std::vector({ Path("C:/FakeSystem/"), }); @@ -115,8 +110,7 @@ namespace Soup::Core::UnitTests auto locationManager = RecipeBuildLocationManager(knownLanguages); auto uut = BuildRunner( arguments, - sdkParameters, - sdkReadAccess, + userDataPath, systemReadAccess, recipeCache, packageProvider, @@ -323,10 +317,7 @@ namespace Soup::Core::UnitTests auto arguments = RecipeBuildArguments(); arguments.HostPlatform = "TestPlatform"; arguments.WorkingDirectory = Path("C:/WorkingDirectory/MyPackage/"); - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector({ - Path("C:/FakeSDK/"), - }); + auto userDataPath = Path("C:/User/Soup/"); auto systemReadAccess = std::vector({ Path("C:/FakeSystem/"), }); @@ -423,8 +414,7 @@ namespace Soup::Core::UnitTests auto locationManager = RecipeBuildLocationManager(knownLanguages); auto uut = BuildRunner( arguments, - sdkParameters, - sdkReadAccess, + userDataPath, systemReadAccess, recipeCache, packageProvider, @@ -819,10 +809,7 @@ namespace Soup::Core::UnitTests auto arguments = RecipeBuildArguments(); arguments.HostPlatform = "TestPlatform"; arguments.WorkingDirectory = Path("C:/WorkingDirectory/MyPackage/"); - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector({ - Path("C:/FakeSDK/"), - }); + auto userDataPath = Path("C:/User/Soup/"); auto systemReadAccess = std::vector({ Path("C:/FakeSystem/"), }); @@ -940,8 +927,7 @@ namespace Soup::Core::UnitTests auto locationManager = RecipeBuildLocationManager(knownLanguages); auto uut = BuildRunner( arguments, - sdkParameters, - sdkReadAccess, + userDataPath, systemReadAccess, recipeCache, packageProvider, @@ -1560,10 +1546,7 @@ namespace Soup::Core::UnitTests auto arguments = RecipeBuildArguments(); arguments.HostPlatform = "TestPlatform"; arguments.WorkingDirectory = Path("C:/WorkingDirectory/MyPackage/"); - auto sdkParameters = ValueList(); - auto sdkReadAccess = std::vector({ - Path("C:/FakeSDK/"), - }); + auto userDataPath = Path("C:/User/Soup/"); auto systemReadAccess = std::vector({ Path("C:/FakeSystem/"), }); @@ -1656,8 +1639,7 @@ namespace Soup::Core::UnitTests auto locationManager = RecipeBuildLocationManager(knownLanguages); auto uut = BuildRunner( arguments, - sdkParameters, - sdkReadAccess, + userDataPath, systemReadAccess, recipeCache, packageProvider, diff --git a/Source/Client/Core/UnitTests/gen/Main.cpp b/Source/Client/Core/UnitTests/gen/Main.cpp index cd11075b4..6c9f9a701 100644 --- a/Source/Client/Core/UnitTests/gen/Main.cpp +++ b/Source/Client/Core/UnitTests/gen/Main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/Generate/GenerateEngine.h b/Source/Generate/GenerateEngine.h index abd22c246..f860a4a1d 100644 --- a/Source/Generate/GenerateEngine.h +++ b/Source/Generate/GenerateEngine.h @@ -33,6 +33,21 @@ namespace Soup::Core::Generate } auto packageRoot = Path(inputTable.at("PackageRoot").AsString()); + auto userDataPath = Path(inputTable.at("UserDataPath").AsString()); + + // Load the local user config and any sdk content + auto sdkParameters = ValueList(); + auto sdkReadAccess = std::vector(); + LoadLocalUserConfig(userDataPath, sdkParameters, sdkReadAccess); + + // Load the recipe file + auto recipeFile = packageRoot + BuildConstants::RecipeFileName(); + Recipe recipe; + if (!RecipeExtensions::TryLoadRecipeFromFile(recipeFile, recipe)) + { + Log::Error("Failed to load the recipe: " + recipeFile.ToString()); + throw std::runtime_error("Failed to load recipe."); + } // Load the input macro definition auto generateMacros = std::map(); @@ -59,20 +74,15 @@ namespace Soup::Core::Generate for (auto& [key, value] : inputTable.at("EvaluateMacros").AsTable()) evaluateMacros.emplace(key, value.AsString()); + // Allow read access for all sdk directories + for (auto& value : sdkReadAccess) + evaluateAllowedReadAccess.push_back(std::move(value)); + // Setup a macro manager to resolve macros auto generateMacroManager = MacroManager(generateMacros); auto generateSubGraphMacroManager = MacroManager(generateSubGraphMacros); auto evaluateMacroManager = MacroManager(evaluateMacros); - // Load the recipe file - auto recipeFile = packageRoot + BuildConstants::RecipeFileName(); - Recipe recipe; - if (!RecipeExtensions::TryLoadRecipeFromFile(recipeFile, recipe)) - { - Log::Error("Failed to load the recipe: " + recipeFile.ToString()); - throw std::runtime_error("Failed to load recipe."); - } - // Combine all the dependencies shared state auto dependenciesSharedState = LoadDependenciesSharedState( generateSubGraphMacroManager, @@ -83,9 +93,12 @@ namespace Soup::Core::Generate generateMacroManager, dependenciesSharedState); - // Start a new global state that is initialized to the recipe itself + // Start a new global state auto globalState = ValueTable(); + // Pass along the sdks + globalState.emplace("SDKs", std::move(sdkParameters)); + // Initialize the Recipe Root Table auto recipeState = RecipeBuildStateConverter::ConvertToBuildState(recipe.GetTable()); globalState.emplace("Recipe", Value(std::move(recipeState))); @@ -166,6 +179,54 @@ namespace Soup::Core::Generate } private: + /// + /// Load Local User Config and process any known state + /// + static void LoadLocalUserConfig( + const Path& userDataPath, + ValueList& sdkParameters, + std::vector& sdkReadAccess) + { + // Load the local user config + auto localUserConfigPath = userDataPath + BuildConstants::LocalUserConfigFileName(); + LocalUserConfig localUserConfig = {}; + if (!LocalUserConfigExtensions::TryLoadLocalUserConfigFromFile(localUserConfigPath, localUserConfig)) + { + Log::Warning("Local User Config invalid"); + } + + // Process the SDKs + if (localUserConfig.HasSDKs()) + { + Log::Info("Checking SDKs for read access"); + auto sdks = localUserConfig.GetSDKs(); + for (auto& sdk : sdks) + { + auto sdkName = sdk.GetName(); + Log::Info("Found SDK: " + sdkName); + if (sdk.HasSourceDirectories()) + { + for (auto& sourceDirectory : sdk.GetSourceDirectories()) + { + Log::Info(" Read Access: " + sourceDirectory.ToString()); + sdkReadAccess.push_back(sourceDirectory); + } + } + + auto sdkParameter = ValueTable(); + sdkParameter.emplace("Name", Value(sdkName)); + if (sdk.HasProperties()) + { + sdkParameter.emplace( + "Properties", + RecipeBuildStateConverter::ConvertToBuildState(sdk.GetProperties())); + } + + sdkParameters.push_back(std::move(sdkParameter)); + } + } + } + /// /// Using the parameters to resolve the dependency output folders, load up the shared state table and /// combine them into a single value table to be used as input the this generate phase. diff --git a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs index bc39ffc65..8e30bf7c7 100644 --- a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs +++ b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs @@ -78,6 +78,7 @@ public async Task Discover() "HIGH: Using VC Version: 14.33.31629", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", + "INFO: Nuget not found", "INFO: Creating directory C:/Users/Me/.soup/", }, testListener.GetMessages()); @@ -92,6 +93,8 @@ public async Task Discover() "Exists: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", + "GetUserProfileDirectory", + "Exists: C:/Users/Me/.nuget/packages", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", @@ -228,6 +231,7 @@ public async Task Discover_Prerelease() "HIGH: Using VC Version: 14.34.31823", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", + "INFO: Nuget not found", "INFO: Creating directory C:/Users/Me/.soup/", }, testListener.GetMessages()); @@ -242,6 +246,8 @@ public async Task Discover_Prerelease() "Exists: C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", + "GetUserProfileDirectory", + "Exists: C:/Users/Me/.nuget/packages", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", @@ -430,6 +436,7 @@ public async Task Discover_UpdateExisting() "HIGH: Using VC Version: 14.33.31629", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", + "INFO: Nuget not found", "INFO: Creating directory C:/Users/Me/.soup/", }, @@ -446,6 +453,8 @@ public async Task Discover_UpdateExisting() "Exists: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", + "GetUserProfileDirectory", + "Exists: C:/Users/Me/.nuget/packages", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackage.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackage.cs new file mode 100644 index 000000000..dd7d99043 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackage.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Collections.Generic; + +namespace Swhere.Core.Nuget +{ + internal class NugetPackage + { + public string Id { get; set; } = string.Empty; + + public IList Versions { get; set; } = new List(); + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageDependency.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageDependency.cs new file mode 100644 index 000000000..2489c1915 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageDependency.cs @@ -0,0 +1,13 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +namespace Swhere.Core.Nuget +{ + internal class NugetPackageDependency + { + public string Id { get; set; } = string.Empty; + + public string Version { get; set; } = string.Empty; + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageTargetFramework.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageTargetFramework.cs new file mode 100644 index 000000000..18c608c20 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageTargetFramework.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Collections.Generic; + +namespace Swhere.Core.Nuget +{ + internal class NugetPackageTargetFramework + { + public string Name { get; set; } = string.Empty; + + public IList Dependencies { get; set; } = new List(); + + public IList Libraries { get; set; } = new List(); + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageVersion.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageVersion.cs new file mode 100644 index 000000000..4cceefe22 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NugetPackageVersion.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Collections.Generic; + +namespace Swhere.Core.Nuget +{ + internal class NugetPackageVersion + { + public string Version { get; set; } = string.Empty; + + public IList TargetFrameworks { get; set; } = new List(); + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependency.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependency.cs new file mode 100644 index 000000000..a4a923ccc --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependency.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Xml.Serialization; + +namespace Swhere.Core.Nuget +{ + public class NuspecDependency : NuspecDependencyBase + { + [XmlAttribute("id")] + public string Id { get; set; } = string.Empty; + + [XmlAttribute("version")] + public string Version { get; set; } = string.Empty; + + [XmlAttribute("include")] + public string? Description { get; set; } + + [XmlAttribute("exclude")] + public string? Authors { get; set; } + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyBase.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyBase.cs new file mode 100644 index 000000000..b1e4a9d04 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyBase.cs @@ -0,0 +1,10 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +namespace Swhere.Core.Nuget +{ + public class NuspecDependencyBase + { + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyGroup.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyGroup.cs new file mode 100644 index 000000000..8bb006237 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecDependencyGroup.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Swhere.Core.Nuget +{ + public class NuspecDependencyGroup : NuspecDependencyBase + { + [XmlAttribute("targetFramework")] + public string TargetFramework { get; set; } = string.Empty; + + [XmlElement("dependency")] + public List Dependencies { get; set; } = new List(); + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NuspecMetadata.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecMetadata.cs new file mode 100644 index 000000000..01a70a7c0 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecMetadata.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Swhere.Core.Nuget +{ + public class NuspecMetadata + { + [XmlElement("id")] + public string Id { get; set; } = string.Empty; + + [XmlElement("version")] + public string Version { get; set; } = string.Empty; + + [XmlElement("description")] + public string Description { get; set; } = string.Empty; + + [XmlElement("authors")] + public string Authors { get; set; } = string.Empty; + + [XmlElement("releaseNotes")] + public string? ReleaseNotes { get; set; } + + [XmlElement("copyright")] + public string? Copyright { get; set; } + + [XmlElement("tags")] + public string? Tags { get; set; } + + [XmlArray("dependencies")] + [XmlArrayItem("group", typeof(NuspecDependencyGroup))] + [XmlArrayItem("dependency", typeof(NuspecDependency))] + public List? Dependencies { get; set; } + } +} diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NuspecPackage.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecPackage.cs new file mode 100644 index 000000000..5785ddf33 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NuspecPackage.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using System.Xml.Serialization; + +namespace Swhere.Core.Nuget +{ + [XmlRoot("package")] + public class NuspecPackage + { + [XmlElement("metadata")] + public NuspecMetadata Metadata { get; set; } = new NuspecMetadata(); + } +} diff --git a/Source/GenerateSharp/Swhere.Core/NugetSDKUtilities.cs b/Source/GenerateSharp/Swhere.Core/NugetSDKUtilities.cs new file mode 100644 index 000000000..fd78ea184 --- /dev/null +++ b/Source/GenerateSharp/Swhere.Core/NugetSDKUtilities.cs @@ -0,0 +1,223 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +using Opal; +using Opal.System; +using Swhere.Core.Nuget; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace Soup.Build.Discover +{ + internal static class NugetSDKUtilities + { + public static async Task<(bool HasNuget, Path NugetPackagesPath, IList Packages)> FindNugetPackagesAsync() + { + var fileSystem = LifetimeManager.Get(); + var nugetDirectory = fileSystem.GetUserProfileDirectory() + + new Path(".nuget"); + var nugetPackagesDirectory = nugetDirectory + + new Path("packages"); + if (fileSystem.Exists(nugetPackagesDirectory)) + { + Log.Info($"Discover Nuget Packages: {nugetPackagesDirectory}"); + var packages = new List(); + foreach (var nugetPackageDirectory in fileSystem.GetChildDirectories(nugetPackagesDirectory)) + { + var packageName = nugetPackageDirectory.Path.GetFileName(); + NugetPackage? package = null; + foreach (var nugetPackageVersionDirectory in fileSystem.GetChildDirectories(nugetPackageDirectory.Path)) + { + var (currentPackage, packageVersion) = await LoadNugetPackageAsync( + packageName, + nugetPackageVersionDirectory.Path); + if (currentPackage is not null && packageVersion is not null) + { + if (package is null) + { + package = currentPackage; + } + + // TODO: Check that they are compatible? + package.Versions.Add(packageVersion); + } + } + + if (package is not null) + { + packages.Add(package); + } + } + + return (true, nugetPackagesDirectory, packages); + } + else + { + Log.Info("Nuget not found"); + return (false, nugetPackagesDirectory, new List()); + } + } + + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "Verified all required types are referenced")] + private static async Task<(NugetPackage?, NugetPackageVersion?)> LoadNugetPackageAsync(string name, Path directory) + { + var fileSystem = LifetimeManager.Get(); + + // Load the NuSpec XML file + var nuspecFile = directory + new Path($"{name}.nuspec"); + if (!fileSystem.Exists(nuspecFile)) + { + Log.Warning($"Missing Nuspec file: {nuspecFile}"); + return (null, null); + } + + using var nuspec = fileSystem.OpenRead(nuspecFile); + using var reader = XmlReader.Create(nuspec.GetInStream(), new XmlReaderSettings() { Async = true }); + var rootNamespace = await ReadRootNamespaceAsync(reader); + if (!Regex.IsMatch(rootNamespace, @"^http://schemas.microsoft.com/packaging/\d\d\d\d/\d\d/nuspec.xsd$")) + { + Log.Warning($"Unknown Nuspec file namespace: {rootNamespace}"); + return (null, null); + } + + var serializer = new XmlSerializer(typeof(NuspecPackage), rootNamespace); + + if (!serializer.CanDeserialize(reader)) + { + Log.Warning($"Cannot deserialize Nuspec file: {nuspecFile}"); + return (null, null); + } + + var nuspecDocument = (NuspecPackage?)serializer.Deserialize(reader); + if (nuspecDocument is null) + { + Log.Warning($"Failed to parse Nuspec file: {nuspecFile}"); + return (null, null); + } + + var nugetPackage = new NugetPackage() + { + Id = nuspecDocument.Metadata.Id, + }; + var nugetPackageVersion = new NugetPackageVersion() + { + Version = nuspecDocument.Metadata.Version, + }; + + if (nuspecDocument.Metadata.Dependencies is not null) + { + foreach (var dependency in nuspecDocument.Metadata.Dependencies) + { + if (dependency is NuspecDependencyGroup dependencyGroup) + { + var tfm = GetTFM(dependencyGroup.TargetFramework); + if (tfm is not null) + { + var targetFramework = new NugetPackageTargetFramework() + { + Name = tfm, + Libraries = DiscoverLibraries(directory, tfm), + Dependencies = dependencyGroup.Dependencies.Select(value => + new NugetPackageDependency() { Id = value.Id, Version = value.Version }).ToList(), + }; + + nugetPackageVersion.TargetFrameworks.Add(targetFramework); + } + } + else + { + if (nuspecDocument is null) + { + Log.Warning($"Cannot parse depedencies not in groupd yet: {nuspecFile}"); + return (null, null); + } + } + } + } + + return (nugetPackage, nugetPackageVersion); + } + + private static async Task ReadRootNamespaceAsync(XmlReader reader) + { + while (await reader.ReadAsync()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + return reader.NamespaceURI; + } + } + + throw new InvalidOperationException("XML had no root"); + } + + private static string? GetTFM(string targetFramework) + { + var targetFrameworkMoniker = default(string); + if (targetFramework.StartsWith("net")) + { + targetFrameworkMoniker = targetFramework; + } + else + { + targetFrameworkMoniker = targetFramework switch + { + ".NETCoreApp1.0" => "netcoreapp1.0", + ".NETCoreApp1.1" => "netcoreapp1.1", + ".NETCoreApp2.0" => "netcoreapp2.0", + ".NETCoreApp2.1" => "netcoreapp2.1", + ".NETCoreApp2.2" => "netcoreapp2.2", + ".NETCoreApp3.0" => "netcoreapp3.0", + ".NETCoreApp3.1" => "netcoreapp3.1", + ".NETFramework4.5" => "net45", + ".NETFramework4.5.2" => "net452", + ".NETFramework4.6" => "net46", + ".NETFramework4.6.1" => "net461", + ".NETFramework4.6.2" => "net462", + ".NETStandard1.0" => "netstandard1.0", + ".NETStandard1.1" => "netstandard1.1", + ".NETStandard1.2" => "netstandard1.2", + ".NETStandard1.3" => "netstandard1.3", + ".NETStandard1.4" => "netstandard1.4", + ".NETStandard1.5" => "netstandard1.5", + ".NETStandard1.6" => "netstandard1.4", + ".NETStandard2.0" => "netstandard2.0", + ".NETStandard2.1" => "netstandard2.1", + _ => null + }; + } + + return targetFrameworkMoniker; + } + + private static IList DiscoverLibraries(Path directory, string targetFrameworkMoniker) + { + var fileSystem = LifetimeManager.Get(); + + var libraries = new List(); + var targetLibrariesPath = directory + new Path($"lib/{targetFrameworkMoniker}/"); + if (fileSystem.Exists(targetLibrariesPath)) + { + foreach (var file in fileSystem.GetChildFiles(targetLibrariesPath)) + { + if (file.Path.GetFileExtension() == ".dll") + { + var relativeFile = file.Path.GetRelativeTo(directory); + libraries.Add(relativeFile.ToString()); + } + } + } + + return libraries; + } + } +} \ No newline at end of file diff --git a/Source/GenerateSharp/Swhere.Core/Recipe.sml b/Source/GenerateSharp/Swhere.Core/Recipe.sml index c56eb85ee..c6d9bd589 100644 --- a/Source/GenerateSharp/Swhere.Core/Recipe.sml +++ b/Source/GenerateSharp/Swhere.Core/Recipe.sml @@ -2,7 +2,17 @@ Name: "Swhere.Core" Language: "C#|0.1" Version: "1.0.0" Source: [ + "Nuget/NugetPackage.cs" + "Nuget/NugetPackageDependency.cs" + "Nuget/NugetPackageTargetFramework.cs" + "Nuget/NugetPackageVersion.cs" + "Nuget/NuspecDependency.cs" + "Nuget/NuspecDependencyBase.cs" + "Nuget/NuspecDependencyGroup.cs" + "Nuget/NuspecMetadata.cs" + "Nuget/NuspecPackage.cs" "DotNetSDKUtilities.cs" + "NugetSDKUtilities.cs" "SwhereManager.cs" "VSWhereUtilities.cs" "WindowsSDKUtilities.cs" diff --git a/Source/GenerateSharp/Swhere.Core/SwhereManager.cs b/Source/GenerateSharp/Swhere.Core/SwhereManager.cs index c6de2e88c..6d63e84a6 100644 --- a/Source/GenerateSharp/Swhere.Core/SwhereManager.cs +++ b/Source/GenerateSharp/Swhere.Core/SwhereManager.cs @@ -17,7 +17,8 @@ public static async Task DiscoverAsync(bool includePrerelease) // Load up the Local User Config var localUserConfigPath = LifetimeManager.Get().GetUserProfileDirectory() + new Path(".soup/LocalUserConfig.sml"); - var (loadConfigResult, userConfig) = await LocalUserConfigExtensions.TryLoadLocalUserConfigFromFileAsync(localUserConfigPath); + var (loadConfigResult, userConfig) = + await LocalUserConfigExtensions.TryLoadLocalUserConfigFromFileAsync(localUserConfigPath); if (!loadConfigResult) { Log.Info("No existing local user config."); @@ -88,13 +89,61 @@ public static async Task DiscoverAsync(bool includePrerelease) { "ToolsRoot", netFXToolsPath.ToString() }, }); + var (hasNuget, nugetPackagesPath, nugetPackages) = await NugetSDKUtilities.FindNugetPackagesAsync(); + if (hasNuget) + { + var nugetSDK = userConfig.EnsureSDK("Nuget"); + nugetSDK.SourceDirectories = new List() + { + nugetPackagesPath, + }; + + nugetSDK.Properties.Values.Clear(); + + nugetSDK.Properties.AddItemWithSyntax("PackagesDirectory", nugetPackagesPath.ToString(), 3); + + var packagesTable = nugetSDK.Properties.EnsureTableWithSyntax("Packages", 3); + packagesTable.Values.Clear(); + foreach (var package in nugetPackages) + { + var packageTable = packagesTable.EnsureTableWithSyntax(package.Id, 4); + foreach (var packageVersion in package.Versions) + { + var packageVersionTable = packageTable.EnsureTableWithSyntax(packageVersion.Version, 5); + if (packageVersion.TargetFrameworks.Count > 0) + { + var targetFrameworksTable = packageVersionTable.EnsureTableWithSyntax("TargetFrameworks", 6); + foreach (var targetFramework in packageVersion.TargetFrameworks) + { + var targetFrameworkTable = targetFrameworksTable.EnsureTableWithSyntax(targetFramework.Name, 7); + + if (targetFramework.Dependencies.Count > 0) + { + var dependenciesArray = targetFrameworkTable.EnsureArrayWithSyntax("Dependencies", 8); + foreach (var dependency in targetFramework.Dependencies) + { + var dependencyTable = dependenciesArray.AddInlineTableWithSyntax(9); + dependencyTable.AddInlineItemWithSyntax("Id", dependency.Id); + dependencyTable.AddInlineItemWithSyntax("Version", dependency.Version); + } + } + + if (targetFramework.Libraries.Count > 0) + { + var librariesArray = targetFrameworkTable.EnsureArrayWithSyntax("Libraries", 8); + foreach (var library in targetFramework.Libraries) + { + librariesArray.AddItemWithSyntax(library, 9); + } + } + } + } + } + } + } + // Save the result await LocalUserConfigExtensions.SaveToFileAsync(localUserConfigPath, userConfig); } - - private static void PrintUsage() - { - Log.Info("Soup.Build.Discover.exe"); - } } } diff --git a/Source/GenerateSharp/Swhere/PackageLock.sml b/Source/GenerateSharp/Swhere/PackageLock.sml index b792e6fae..928fc9549 100644 --- a/Source/GenerateSharp/Swhere/PackageLock.sml +++ b/Source/GenerateSharp/Swhere/PackageLock.sml @@ -16,7 +16,7 @@ Closures: { Build1: { Wren: [ { Name: "Soup.CSharp", Version: "0.10.0" } - { Name: "Soup.CSharp.Nuget", Version: "0.1.0" } + { Name: "Soup.CSharp.Nuget", Version: "0.2.0" } ] } Tool0: { diff --git a/Source/GenerateSharp/Utilities/SML/Grammar/SMLExtensions.cs b/Source/GenerateSharp/Utilities/SML/Grammar/SMLExtensions.cs index 5cd3fe06a..31964b6e4 100644 --- a/Source/GenerateSharp/Utilities/SML/Grammar/SMLExtensions.cs +++ b/Source/GenerateSharp/Utilities/SML/Grammar/SMLExtensions.cs @@ -37,6 +37,13 @@ public static void AddItemWithSyntax(this SMLArray array, string value, int inde new SMLToken("\""))), new List()); + // Update the previous last item to have a comma delmiter + if (array.Values.Count > 0) + { + var lastItem = array.Values.Last(); + lastItem.Delimiter.Add(NewlineToken); + } + // Add the model to the parent table model array.Values.Add(newValue); } @@ -46,11 +53,35 @@ public static SMLArray AddArrayWithSyntax(this SMLDocument document, string name return document.Values.AddArrayWithSyntax(name, 0); } + public static SMLArray EnsureArrayWithSyntax(this SMLTable table, string name, int indentLevel) + { + if (table.Values.TryGetValue(name, out var tableValue)) + { + return tableValue.Value.AsArray(); + } + else + { + return table.AddArrayWithSyntax(name, indentLevel); + } + } + public static SMLArray AddArrayWithSyntax(this SMLTable table, string name, int indentLevel) { return table.Values.AddArrayWithSyntax(name, indentLevel); } + public static SMLTable EnsureTableWithSyntax(this SMLDocument document, string name) + { + if (document.Values.TryGetValue(name, out var tableValue)) + { + return tableValue.Value.AsTable(); + } + else + { + return document.AddTableWithSyntax(name); + } + } + public static SMLTable AddTableWithSyntax(this SMLDocument document, string name) { return document.Values.AddTableWithSyntax(name, 0); @@ -61,6 +92,18 @@ public static SMLTable AddInlineTableWithSyntax(this SMLDocument document, strin return document.Values.AddInlineTableWithSyntax(name, 0); } + public static SMLTable EnsureTableWithSyntax(this SMLTable table, string name, int indentLevel) + { + if (table.Values.TryGetValue(name, out var tableValue)) + { + return tableValue.Value.AsTable(); + } + else + { + return table.AddTableWithSyntax(name, indentLevel); + } + } + public static SMLTable AddTableWithSyntax(this SMLTable table, string name, int indentLevel) { return table.Values.AddTableWithSyntax(name, indentLevel); @@ -184,6 +227,18 @@ public static void AddItemWithSyntax(this SMLTable table, string key, long value table.Values.Add(key, CreateTableValue(keyToken, newValue)); } + public static void AddItemWithSyntax(this SMLTable table, string key, string value) + { + // Create a new item and matching syntax + var newValue = new SMLValue(new SMLStringValue(value)); + + // Tables items should be on newline + var keyToken = new SMLToken(key); + + // Add the model to the parent table model + table.Values.Add(key, CreateTableValue(keyToken, newValue)); + } + public static void AddItemWithSyntax(this SMLTable table, string key, string value, int indentLevel) { var indent = string.Concat(Enumerable.Repeat(Indent, indentLevel));