嵌入字体会导致Silverlight项目始终重新构建

3
我在我的Silverlight 4项目中遇到了一个奇怪的问题,尽管这是我以前见过的问题。基本上,嵌入字体会导致我的Silverlight应用程序始终重新构建,即使一切都是最新的。这有点糟糕,因为字体嵌入任务需要大量内存,并最终会导致VS崩溃。我希望能够从命令行构建项目,但无论如何,我的本地项目都已经过期,所以“运行”命令会强制重新构建。我尝试从我的msbuild日志中剪切出一些相关的日志信息。
Project "D:\Projects\Test\Test.Web\Test.Web.csproj" (10) is building "D:\Projects\Test\Test.SL\Test.SL.csproj" (2:4) on node 1 (default targets).
Building with tools version "4.0".
// Build operation starts normally (well, the dependency set on the server project is forcing the SL application to build). 
...
Target "ResolveReferences" skipped. Previously built successfully.
// A bunch of tasks are skipped (like this one)
...
Target "SubsetFontsSilverlight" in file "C:\Program Files\MSBuild\Microsoft\Expression\Blend\Silverlight\v4.0\SubsetFontSilverlight.targets" from project "D:\Projects\Test\Test.SL\Test.SL.csproj" (target "PrepareResources" depends on it):
Using "SubsetFontsSilverlight" task from assembly "C:\Program Files\MSBuild\Microsoft\Expression\Blend\Silverlight\v4.0\SubsetFontTask.dll".
Task "SubsetFontsSilverlight"
Done executing task "SubsetFontsSilverlight".
Done building target "SubsetFontsSilverlight" in project "Test.SL.csproj".
// this task never gets skipped
...
Target "MainResourcesGeneration" in file "C:\Program Files\MSBuild\Microsoft\Silverlight\v4.0\Microsoft.Silverlight.Common.targets" from project "D:\Projects\Test\Test.SL\Test.SL.csproj" (target "PrepareResources" depends on it):
Building target "MainResourcesGeneration" completely.
Input file "obj\Debug\Fonts\Fonts.zip" is newer than output file "obj\Debug\Test.SL.g.resources".
// note that the Fonts.zip file now makes the resources file out of date
...
Target "CoreCompile" in file "C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Microsoft.CSharp.Targets" from project "D:\Projects\Test\Test.SL\Test.SL.csproj" (target "Compile" depends on it):
Building target "CoreCompile" completely.
Input file "obj\Debug\Test.SL.g.resources" is newer than output file "obj\Debug\Test.SL.pdb".
// and the full recompile begins...

无论如何,如果字体文件没有改变,有没有办法停止字体任务运行?因为不断重建真的很烦人。
更新:来自项目文件的样本字体。
<BlendEmbeddedFont Include="Fonts\MyriadPro-BoldIt.otf">
  <IsSystemFont>True</IsSystemFont>
  <All>True</All>
  <AutoFill>True</AutoFill>
  <Characters>
  </Characters>
  <Uppercase>True</Uppercase>
  <Lowercase>True</Lowercase>
  <Numbers>True</Numbers>
  <Punctuation>True</Punctuation>
</BlendEmbeddedFont>

更新 2:

我想发布一个简单的复现项目,但我不确定应该把zip文件放在哪里。无论如何,可以在几秒钟内轻松创建一个。以下是步骤:

  1. 创建Silverlight应用程序(使用Blend或VS,但无论如何你需要Blend才能使其工作)

  2. 在Blend中使用字体管理器(工具 -> 字体管理器),你可能需要打开MainPage.xaml才能启用它。嵌入“Tahoma”字体。

现在,每次点击“重新生成”,都可以验证csc.exe都会运行,而不管是否有任何更改。


你选择了仅嵌入使用的字符吗?这将始终强制重新构建,因为它会再次读取所有文本以创建字符映射以进行嵌入。如果您可以发布您的MSBuild文件(包含字体的相关部分),那将非常有帮助。 - Todd Main
@Otaku:这是从Blend 4中嵌入的标准字体。我认为有一个可以做你所说的事情的设施,但这似乎是一个更广泛的子集。但无论如何,即使设置了解析使用的字符,当源文件没有改变时,为什么要这样做呢? - Egor
2个回答

2

我通过对SubsetFontSilverlight.target文件进行一些修改来解决这个问题。这不是一个完美的解决方案,也不一定适用于每个人,但对我有效。

在我的机器上,目标文件可以在C:\Program Files\MSBuild\Microsoft\Expression\Blend\Silverlight\v4.0中找到。

主要思路是使用MSBUILD目标输入/输出属性来查看是否需要更新中间的Font.zip文件。我只是硬编码了输出文件路径,因为我总是使用默认的Blend字体位置 - 但也可以从单个字体文件构建它。

我设置的条件是:如果Font.zip文件比嵌入的字体文件和项目文件更新,则跳过任务。这样,如果您添加/删除字体或替换其中一个字体,字体将重新嵌入,但如果没有更改(或仅代码更改),则不会更改字体压缩包。

如果字体没有被嵌入,重要的是手动将fonts.zip添加到资源集合中 - 这样,如果需要重新编译代码但字体可能保持不变,msbuild会记住将字体文件包括在资源中。这就是为什么我在主要嵌入任务之后添加了一个新任务。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <UsingTask AssemblyFile="SubsetFontTask.dll" TaskName="Microsoft.Expression.SubsetFontTask.SubsetFontsSilverlight" />
    <UsingTask AssemblyFile="SubsetFontTask.dll" TaskName="Microsoft.Expression.SubsetFontTask.CleanSubsetFonts" />

    <ItemGroup Condition="'$(BuildingInsideVisualStudio)'=='true'">
        <AvailableItemName Include="BlendEmbeddedFont"/>
    </ItemGroup>

  <ItemGroup>
    <!-- Files that we specify as target inputs for msbuild target timestamp check -->
    <FontEmbedRelatedFile Include="$(MSBuildProjectFile)" />
    <FontEmbedRelatedFile Include="@(BlendEmbeddedFont)" />

    <!-- Anticipated font file locations -->
    <FontOutputFile Include="$(IntermediateOutputPath)\Fonts\Fonts.zip" />
  </ItemGroup>

  <!-- 
    this task runs after the main subset task - basically it checks if the task got skipped, and if so
    it adds the projected font output files to the resources collection
   -->
  <Target Name='AfterFontEmbed' AfterTargets="SubsetFontsSilverlight"
    Condition="'@(BlendEmbeddedFont)' != '' AND '@(BlendSubsettedFont)' == ''">
    <Message Text="Adding font files to the resource collection because Font embed was skipped" Importance="normal" />
    <ItemGroup>
      <Resource Include="@(FontOutputFile)" />
    </ItemGroup>
  </Target>

    <Target Name='SubsetFontsSilverlight'  
    Inputs="@(FontEmbedRelatedFile)" Outputs="@(FontOutputFile)"
    DependsOnTargets="$(SubsetFontsDependsOn)"  
    Condition="'@(BlendEmbeddedFont)' != ''">
    <Message Text="Embedding font subsets" Importance="normal" />
        <SubsetFontsSilverlight 
            Fonts="@(BlendEmbeddedFont)" XamlFiles="@(Page)" Resources="@(Resources)"
            IntermediateFilesDirectory="$(IntermediateOutputPath)"
            >
            <Output TaskParameter="SubsettedFonts" ItemName="Resource"/>
            <!-- save our list of embedded font files for later use -->
            <Output TaskParameter="SubsettedFonts" ItemName="BlendSubsettedFont"/>
        </SubsetFontsSilverlight>
    </Target>

    <Target Name='CleanSubsetFonts' DependsOnTargets="$(CleanSubsetFontsDependsOn)" Condition="'@(BlendEmbeddedFont)' != ''">
        <CleanSubsetFonts 
            Fonts="@(BlendEmbeddedFont)"
            IntermediateFilesDirectory="$(IntermediateOutputPath)"
            />
    </Target>

    <PropertyGroup>
        <PrepareResourcesDependsOn>
            SubsetFontsSilverlight;
            $(PrepareResourcesDependsOn);
        </PrepareResourcesDependsOn>
    </PropertyGroup>

    <PropertyGroup>
        <CleanDependsOn>
            $(CleanDependsOn);
            CleanSubsetFonts;
        </CleanDependsOn>
    </PropertyGroup>

</Project>

Visual Studio在检测项目是否最新方面仍有不足,但在构建项目时,corecompile任务不会每次运行,也不会不必要地覆盖dll文件。这意味着你可以在命令行上运行编译器,在调试时将VS设置为“从不构建”,并仍然加载正确的调试符号。

我测试了大多数情况(清理前/后、缺少文件、仅代码更改、项目更改等),似乎都表现良好 - 这意味着字体存在,并且不是总是重新编译。


1

我一直在使用Egor的答案,它真的有助于减少我们的构建时间,特别是在重新构建时。然而,最近的更改破坏了一些东西 - 我们将字体从项目文件夹中的\Fonts移动到\Assets\Fonts。目标文件尝试对obj\Debug\Fonts\Fonts.zip进行最新检查,但任务正在创建obj\Debug\Assets\Fonts\Fonts.zip。

我已经修改了目标文件,以在中间输出路径的适当相对目录中检查Fonts.zip文件。这个目标文件将与包含在不同文件夹中的多个字体一起工作。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- The Blend 4 SDK will always rebuild a xap that has font embedding, rather than
       playing nice and doing up-to-date checks. This targets file replaces the default
       target from the Blend SDK with one that does.
       It checks the input font file and the project file against the fonts.zip file.
       The project file is checked so that any added or removed fonts are picked up -->

  <ItemGroup>
    <!-- Files that we specify as target inputs for msbuild target timestamp check -->
    <FontEmbedRelatedFile Include="$(MSBuildProjectFile)" />
    <FontEmbedRelatedFile Include="@(BlendEmbeddedFont)" />

    <FontOutputFiles Include="@(BlendEmbeddedFont -> '$(IntermediateOutputPath)%(RelativeDir)Fonts.zip')" />
  </ItemGroup>

  <!-- This task runs after the main subset task - basically it checks if the task got skipped, and if so
       it adds the projected font output files to the resources collection -->
  <Target Name='AfterFontEmbed' AfterTargets="SubsetFontsSilverlight"
          Condition="'@(BlendEmbeddedFont)' != '' AND '@(_BlendSubsettedFont)' == ''">
    <ItemGroup>
      <Resource Include="@(FontOutputFiles)" />
    </ItemGroup>
  </Target>

  <Target Name='SubsetFontsSilverlight'
          Inputs="@(FontEmbedRelatedFile)" Outputs="@(FontOutputFiles -> Distinct())"
          DependsOnTargets="$(SubsetFontsDependsOn)" Condition="'@(BlendEmbeddedFont)' != ''">
    <SubsetFontsSilverlight Fonts="@(BlendEmbeddedFont)" 
                            XamlFiles="@(Page)" Resources="@(Resources)" 
                            IntermediateFilesDirectory="$(IntermediateOutputPath)">
      <Output TaskParameter="SubsettedFonts" ItemName="Resource"/>
      <Output TaskParameter="SubsettedFonts" ItemName="_BlendSubsettedFont"/>
    </SubsetFontsSilverlight>
  </Target>
</Project>

刚看到这个 - 感谢您发布更新,很高兴我的解决方案能帮助到你们。 - Egor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接