Skip to content

Commit fdf6eb7

Browse files
authored
Fix resource not updating when adding or renaming resources (#918)
1 parent 7afd6dc commit fdf6eb7

3 files changed

Lines changed: 139 additions & 271 deletions

File tree

Tools.BuildTasks-2019/GenerateNanoResourceTask.cs

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -992,37 +992,45 @@ private bool ShouldRebuildResgenOutputFile(string sourceFilePath, string outputF
992992
}
993993
}
994994

995-
// TODO
996-
//// if the .resources is up to date with respect to the .resx file
997-
//// then we need to compare timestamps for each linked file inside
998-
//// the .resx file itself
999-
//if (!shouldRebuildOutputFile && resxFileInfo.LinkedFiles != null)
1000-
//{
1001-
// foreach (string linkedFilePath in resxFileInfo.LinkedFiles)
1002-
// {
1003-
// // If the linked file doesn't exist, then we want to rebuild this
1004-
// // .resources file so the user sees an error from ResGen.exe
1005-
// shouldRebuildOutputFile = !File.Exists(linkedFilePath);
1006-
1007-
// // If the linked file exists, then we need to compare the timestamp
1008-
// // for the linked resource to see if it is more recent than the
1009-
// // .resources file
1010-
// if (!shouldRebuildOutputFile)
1011-
// {
1012-
// DateTime linkedFileTimeStamp = File.GetLastWriteTime(linkedFilePath);
1013-
// shouldRebuildOutputFile = linkedFileTimeStamp > outputFileTimeStamp;
1014-
// }
1015-
1016-
// // If we found an instance where a linked file is in a state
1017-
// // that we should rebuild the .resources file, then we should
1018-
// // bail from this loop & just return since the first file that
1019-
// // forces a rebuild is enough
1020-
// if (shouldRebuildOutputFile)
1021-
// {
1022-
// break;
1023-
// }
1024-
// }
1025-
//}
995+
// if the .resources is up to date with respect to the .resx file
996+
// then we need to compare timestamps for each linked file inside
997+
// the .resx file itself
998+
if (!shouldRebuildOutputFile && resxFileInfo.LinkedFiles != null)
999+
{
1000+
// Linked file paths in the .resx may be relative. Resolve them the same
1001+
// way ResXResourceReader does: against BaseLinkedFileDirectory if set,
1002+
// otherwise against the directory containing the .resx file.
1003+
string linkedFileBaseDir = cache.BaseLinkedFileDirectory ?? Path.GetDirectoryName(sourceFilePath);
1004+
1005+
foreach (string rawLinkedFilePath in resxFileInfo.LinkedFiles)
1006+
{
1007+
string linkedFilePath = Path.IsPathRooted(rawLinkedFilePath)
1008+
? rawLinkedFilePath
1009+
: Path.GetFullPath(Path.Combine(linkedFileBaseDir, rawLinkedFilePath));
1010+
1011+
// If the linked file doesn't exist, then we want to rebuild this
1012+
// .resources file so the user sees an error from ResGen.exe
1013+
shouldRebuildOutputFile = !File.Exists(linkedFilePath);
1014+
1015+
// If the linked file exists, then we need to compare the timestamp
1016+
// for the linked resource to see if it is more recent than the
1017+
// .resources file
1018+
if (!shouldRebuildOutputFile)
1019+
{
1020+
DateTime linkedFileTimeStamp = File.GetLastWriteTime(linkedFilePath);
1021+
shouldRebuildOutputFile = linkedFileTimeStamp > outputFileTimeStamp;
1022+
}
1023+
1024+
// If we found an instance where a linked file is in a state
1025+
// that we should rebuild the .resources file, then we should
1026+
// bail from this loop & just return since the first file that
1027+
// forces a rebuild is enough
1028+
if (shouldRebuildOutputFile)
1029+
{
1030+
break;
1031+
}
1032+
}
1033+
}
10261034
}
10271035

10281036
return shouldRebuildOutputFile;

Tools.BuildTasks-2019/ProcessResourceFiles.cs

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ internal ArrayList UnsuccessfullyCreatedOutFiles
9898
/// <summary>
9999
/// Whether we successfully created the STR class
100100
/// </summary>
101-
internal bool StronglyTypedResourceSuccessfullyCreated { get; } = false;
101+
internal bool StronglyTypedResourceSuccessfullyCreated { get; private set; } = false;
102102

103103
/// <summary>
104104
/// Indicates whether the resource reader should use the source file's
@@ -423,6 +423,56 @@ public void CreateStronglyTypedResources(string inputFileName, CodeDomProvider p
423423
{
424424
throw new ApplicationException(errors[0]);
425425
}
426+
427+
StronglyTypedResourceSuccessfullyCreated = true;
428+
}
429+
430+
/// <summary>
431+
/// Generates a strongly typed resource class from in-memory .resx content.
432+
/// Used by the VS custom tool so that unsaved designer edits are reflected
433+
/// immediately without waiting for the file to be written to disk.
434+
/// </summary>
435+
public void CreateStronglyTypedResources(string inputFileName, string inputFileContent, CodeDomProvider provider, TextWriter writer, string resourceName)
436+
{
437+
Init();
438+
439+
ReadResources(inputFileName, inputFileContent, true);
440+
441+
string[] errors = null;
442+
443+
CreateStronglyTypedResources(provider, writer, resourceName, out errors);
444+
445+
if (errors != null && errors.Length > 0)
446+
{
447+
throw new ApplicationException(errors[0]);
448+
}
449+
450+
StronglyTypedResourceSuccessfullyCreated = true;
451+
}
452+
453+
/// <summary>
454+
/// Reads resources from in-memory .resx content. For ResXFileRef entries the
455+
/// linked files are still resolved from disk using the directory of
456+
/// <paramref name="filename"/> as the base path.
457+
/// </summary>
458+
public void ReadResources(String filename, string fileContent, bool shouldUseSourcePath)
459+
{
460+
Format format = GetFormat(filename);
461+
if (format == Format.XML)
462+
{
463+
ResXResourceReader resXReader = assemblyList != null
464+
? new ResXResourceReader(new StringReader(fileContent), assemblyList)
465+
: new ResXResourceReader(new StringReader(fileContent));
466+
if (shouldUseSourcePath)
467+
{
468+
resXReader.BasePath = Path.GetDirectoryName(Path.GetFullPath(filename));
469+
}
470+
ReadResources(resXReader, filename);
471+
}
472+
else
473+
{
474+
ReadResources(filename, shouldUseSourcePath);
475+
}
426476
}
427477

428478
private CodeNamespace CreateNamespace(CodeCompileUnit ccu, string ns, Hashtable tableNamespaces)
@@ -678,13 +728,22 @@ private void ReadResources(IResourceReader reader, String fileName)
678728
while (resEnum.MoveNext())
679729
{
680730
string name = (string)resEnum.Key;
681-
// Replace dot in the name with underscore.
731+
// Replace dot in the name with underscore.
682732
// 1. First reason - this is what desktop resource generator does.
683733
// 2. Second reason - Extra dots causes resource generator to create name space and enumerations.
684734
// This complicates the syntax and finally create invalid code if 2 or more dots are present.
685735
// So we just make longer name.
686736
name = name.Replace('.', '_');
687-
object value = resEnum.Value;
737+
object value;
738+
try
739+
{
740+
value = resEnum.Value;
741+
}
742+
catch (Exception ex)
743+
{
744+
logger?.LogWarning(null, fileName, 0, 0, 0, 0, "GenerateResource.CannotLoadResource", (string)resEnum.Key, ex.Message);
745+
continue;
746+
}
688747
AddResource(name, value, fileName);
689748
}
690749
}
@@ -837,11 +896,18 @@ private void WriteTextResources(String fileName)
837896
/// <param name="linePosition">Column number for messages</param>
838897
private void AddResource(string name, object value, String inputFileName, int lineNumber, int linePosition)
839898
{
899+
if (resourcesHashTable.ContainsKey(name))
900+
{
901+
logger?.LogWarning(null, inputFileName, lineNumber, linePosition, 0, 0, "GenerateResource.DuplicateResourceName", name);
902+
return;
903+
}
904+
840905
Entry entry = Entry.CreateEntry(name, value, StronglyTypedNamespace, GenerateNestedEnums ? StronglyTypedClassName : string.Empty);
841906

842907
Debug.Assert(entry.ClassName.Length > 0);
843908

844909
resources.Add(entry);
910+
resourcesHashTable[name] = entry;
845911
}
846912

847913
private void AddResource(string name, object value, String inputFileName)
@@ -944,8 +1010,7 @@ public static Entry CreateEntry(string name, object value, string defaultNamespa
9441010
// Examples - .wav
9451011
// this is a binary resource
9461012
MemoryStream msOther = (MemoryStream)value;
947-
byte[] memoryData = new byte[msOther.Length];
948-
msOther.Read(memoryData, 0, 0);
1013+
byte[] memoryData = msOther.ToArray();
9491014
entry = new BinaryEntry(name, memoryData);
9501015
break;
9511016
default:
@@ -954,7 +1019,7 @@ public static Entry CreateEntry(string name, object value, string defaultNamespa
9541019

9551020
if (entry == null)
9561021
{
957-
throw new Exception();
1022+
throw new Exception($"Resource '{name}' has unsupported type '{value.GetType().FullName}'.");
9581023
}
9591024

9601025
if (entry.Namespace.Length == 0)

0 commit comments

Comments
 (0)