Fix NuGet fallback folder packages

- Creates a `Godot.Offline.Config` file to configurate NuGet with
Godot's fallback folder. This is easier because now we can assume we can
override the entire file since user config will likely be in the default
`NuGet.Config` file or an additional `*.config` file.
- Ensure the NuGet fallback folder is created at the same time it is
added to the NuGet configuration so future builds don't fail.
- Add `GodotSharp` and `GodotSharpEditor` packages to the fallback folder.
- Add `.nupkg.metadata` file to packages in fallback folder.
- Refer to `Godot.SourceGenerators` using the specific non-floating version
since floating versions don't seem to work with fallbackPackageFolders.
This commit is contained in:
Raul Santos 2022-09-07 19:21:13 +02:00
parent 20d6672846
commit 02bd0724f5
No known key found for this signature in database
GPG key ID: B532473AE3A803E4
4 changed files with 46 additions and 92 deletions

View file

@ -17,7 +17,7 @@
<!-- C# source generators --> <!-- C# source generators -->
<ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
<PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" /> <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" />
</ItemGroup> </ItemGroup>
<!-- Godot API references --> <!-- Godot API references -->

View file

@ -21,10 +21,13 @@
Outputs="$(GeneratedGodotNupkgsVersionsFile)"> Outputs="$(GeneratedGodotNupkgsVersionsFile)">
<PropertyGroup> <PropertyGroup>
<GenerateGodotNupkgsVersionsCode><![CDATA[ <GenerateGodotNupkgsVersionsCode><![CDATA[
namespace $(RootNamespace) { namespace $(RootNamespace)
public class GeneratedGodotNupkgsVersions { {
public class GeneratedGodotNupkgsVersions
{
public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b
public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b
public const string GodotSharp = "$(PackageVersion_GodotSharp)"%3b
} }
} }
]]></GenerateGodotNupkgsVersionsCode> ]]></GenerateGodotNupkgsVersionsCode>

View file

@ -22,71 +22,13 @@ namespace GodotTools.Build
public static string GodotFallbackFolderPath public static string GodotFallbackFolderPath
=> Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder"); => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder");
private static void AddFallbackFolderToNuGetConfig(string nuGetConfigPath, string name, string path)
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(nuGetConfigPath);
const string nuGetConfigRootName = "configuration";
var rootNode = xmlDoc.DocumentElement;
if (rootNode == null)
{
// No root node, create it
rootNode = xmlDoc.CreateElement(nuGetConfigRootName);
xmlDoc.AppendChild(rootNode);
// Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well
XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add");
nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org";
nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value =
"https://api.nuget.org/v3/index.json";
nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3";
rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry);
}
else
{
// Check that the root node is the expected one
if (rootNode.Name != nuGetConfigRootName)
throw new FormatException("Invalid root Xml node for NuGet.Config. " +
$"Expected '{nuGetConfigRootName}' got '{rootNode.Name}'.");
}
var fallbackFoldersNode = rootNode["fallbackPackageFolders"] ??
rootNode.AppendChild(xmlDoc.CreateElement("fallbackPackageFolders"));
// Check if it already has our fallback package folder
for (var xmlNode = fallbackFoldersNode.FirstChild; xmlNode != null; xmlNode = xmlNode.NextSibling)
{
if (xmlNode.NodeType != XmlNodeType.Element)
continue;
var xmlElement = (XmlElement)xmlNode;
if (xmlElement.Name == "add" &&
xmlElement.Attributes["key"]?.Value == name &&
xmlElement.Attributes["value"]?.Value == path)
{
return;
}
}
XmlElement newEntry = xmlDoc.CreateElement("add");
newEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = name;
newEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = path;
fallbackFoldersNode.AppendChild(newEntry);
xmlDoc.Save(nuGetConfigPath);
}
/// <summary> /// <summary>
/// Returns all the paths where the user NuGet.Config files can be found. /// Returns all the paths where the Godot.Offline.Config files can be found.
/// Does not determine whether the returned files exist or not. /// Does not determine whether the returned files exist or not.
/// </summary> /// </summary>
private static string[] GetAllUserNuGetConfigFilePaths() private static string[] GetAllGodotNuGetConfigFilePaths()
{ {
// Where to find 'NuGet/NuGet.Config': // Where to find 'NuGet/config/Godot.Offline.Config':
// //
// - Mono/.NETFramework (standalone NuGet): // - Mono/.NETFramework (standalone NuGet):
// Uses Environment.SpecialFolder.ApplicationData // Uses Environment.SpecialFolder.ApplicationData
@ -98,10 +40,12 @@ namespace GodotTools.Build
string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
const string configFileName = "Godot.Offline.Config";
if (Utils.OS.IsWindows) if (Utils.OS.IsWindows)
{ {
// %APPDATA% for both // %APPDATA% for both
return new[] { Path.Combine(applicationData, "NuGet", "NuGet.Config") }; return new[] { Path.Combine(applicationData, "NuGet", "config", configFileName) };
} }
var paths = new string[2]; var paths = new string[2];
@ -111,20 +55,20 @@ namespace GodotTools.Build
string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME");
if (!string.IsNullOrEmpty(dotnetCliHome)) if (!string.IsNullOrEmpty(dotnetCliHome))
{ {
paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config"); paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "config", configFileName);
} }
else else
{ {
string home = Environment.GetEnvironmentVariable("HOME"); string home = Environment.GetEnvironmentVariable("HOME");
if (string.IsNullOrEmpty(home)) if (string.IsNullOrEmpty(home))
throw new InvalidOperationException("Required environment variable 'HOME' is not set."); throw new InvalidOperationException("Required environment variable 'HOME' is not set.");
paths[0] = Path.Combine(home, ".nuget", "NuGet", "NuGet.Config"); paths[0] = Path.Combine(home, ".nuget", "NuGet", "config", configFileName);
} }
// Mono/.NETFramework (standalone NuGet) // Mono/.NETFramework (standalone NuGet)
// ApplicationData is $HOME/.config on Linux/macOS // ApplicationData is $HOME/.config on Linux/macOS
paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config"); paths[1] = Path.Combine(applicationData, "NuGet", "config", configFileName);
return paths; return paths;
} }
@ -141,29 +85,27 @@ namespace GodotTools.Build
// The nuspec is not lower case inside the nupkg but must be made lower case when extracted. // The nuspec is not lower case inside the nupkg but must be made lower case when extracted.
/// <summary> /// <summary>
/// Adds the specified fallback folder to the user NuGet.Config files, /// Adds the specified fallback folder to the Godot.Offline.Config files,
/// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet.
/// </summary> /// </summary>
public static void AddFallbackFolderToUserNuGetConfigs(string name, string path) public static void AddFallbackFolderToGodotNuGetConfigs(string name, string path)
{ {
foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths()) // Make sure the fallback folder exists to avoid error:
// MSB4018: The "ResolvePackageAssets" task failed unexpectedly.
System.IO.Directory.CreateDirectory(path);
foreach (string nuGetConfigPath in GetAllGodotNuGetConfigFilePaths())
{ {
if (!System.IO.File.Exists(nuGetConfigPath)) string defaultConfig = @$"<?xml version=""1.0"" encoding=""utf-8""?>
{
// It doesn't exist, so we create a default one
const string defaultConfig = @"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration> <configuration>
<packageSources> <fallbackPackageFolders>
<add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" /> <add key=""{name}"" value=""{path}"" />
</packageSources> </fallbackPackageFolders>
</configuration> </configuration>
"; ";
System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath));
System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM
} }
AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path);
}
} }
private static void AddPackageToFallbackFolder(string fallbackFolder, private static void AddPackageToFallbackFolder(string fallbackFolder,
@ -189,6 +131,7 @@ namespace GodotTools.Build
string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower);
string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg");
string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512");
string nupkgMetadataDestPath = Path.Combine(destDir, ".nupkg.metadata");
if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath))
return; // Already added (for speed we don't check if every file is properly extracted) return; // Already added (for speed we don't check if every file is properly extracted)
@ -197,12 +140,18 @@ namespace GodotTools.Build
// Generate .nupkg.sha512 file // Generate .nupkg.sha512 file
using (var alg = SHA512.Create()) byte[] hash = SHA512.HashData(File.ReadAllBytes(nupkgPath));
{ string base64Hash = Convert.ToBase64String(hash);
alg.ComputeHash(File.ReadAllBytes(nupkgPath));
string base64Hash = Convert.ToBase64String(alg.Hash);
File.WriteAllText(nupkgSha512DestPath, base64Hash); File.WriteAllText(nupkgSha512DestPath, base64Hash);
}
// Generate .nupkg.metadata file
// Spec: https://github.com/NuGet/Home/wiki/Nupkg-Metadata-File
File.WriteAllText(nupkgMetadataDestPath, @$"{{
""version"": 2,
""contentHash"": ""{base64Hash}"",
""source"": null
}}");
// Extract nupkg // Extract nupkg
ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); ExtractNupkg(destDir, nupkgPath, packageId, packageVersion);
@ -251,7 +200,7 @@ namespace GodotTools.Build
entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) || entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) ||
entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) || entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) ||
// Nuspec at root level. We already extracted it previously but in lower case. // Nuspec at root level. We already extracted it previously but in lower case.
entryFullName.IndexOf('/') == -1 && entryFullName.EndsWith(".nuspec")) !entryFullName.Contains('/') && entryFullName.EndsWith(".nuspec"))
{ {
continue; continue;
} }
@ -297,6 +246,8 @@ namespace GodotTools.Build
{ {
("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
("GodotSharp", GeneratedGodotNupkgsVersions.GodotSharp),
("GodotSharpEditor", GeneratedGodotNupkgsVersions.GodotSharp),
}; };
} }
} }

View file

@ -123,7 +123,7 @@ namespace GodotTools
try try
{ {
string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; string fallbackFolder = NuGetUtils.GodotFallbackFolderPath;
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
fallbackFolder); fallbackFolder);
NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder);
} }
@ -497,7 +497,7 @@ namespace GodotTools
try try
{ {
// At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included
NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
NuGetUtils.GodotFallbackFolderPath); NuGetUtils.GodotFallbackFolderPath);
} }
catch (Exception e) catch (Exception e)