在C#中,我可以从编译时环境变量创建一个常量吗?

25
我们使用 Hudson 构建项目,并且 Hudson 会在编译时方便地定义环境变量,例如 %BUILD_NUMBER%
我想在代码中使用该变量,以便在运行时记录此构建的信息等操作。然而,我不能使用 System.Environment.GetEnvironmentVariable,因为那会访问运行时环境。我需要的是类似于:
#define BUILD_NUM = %BUILD_NUMBER%

或者:

const string BUILD_NUM = %BUILD_NUMBER%

但我不知道语法。有人可以指点我一下吗?谢谢!


你可以使用预构建的操作/宏来更改数字,这是一个选项吗? - Bobby
1
我希望有一种不需要每次修改文件的方法,使得输入源文件保持不变,并将当前值插入编译后的输出中。 - Eggplant Jeff
5个回答

23

好的,这是我最终采取的方法。虽然不太优雅,但可以工作。我创建了一个预构建步骤,代码如下:

echo namespace Some.Namespace > "$(ProjectDir)\CiInfo.cs"
echo { >> "$(ProjectDir)\CiInfo.cs"
echo     ///^<summary^>Info about the continuous integration server build that produced this binary.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo     public static class CiInfo >> "$(ProjectDir)\CiInfo.cs"
echo     { >> "$(ProjectDir)\CiInfo.cs"
echo         ///^<summary^>The current build number, such as "153"^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo         public const string BuildNumber = ("%BUILD_NUMBER%" == "" ? @"Unknown" : "%BUILD_NUMBER%"); >> "$(ProjectDir)\CiInfo.cs"
echo         ///^<summary^>String of the build number and build date/time, and other useful info.^</summary^> >> "$(ProjectDir)\CiInfo.cs"
echo         public const string BuildTag = ("%BUILD_TAG%" == "" ? @"nohudson" : "%BUILD_TAG%") + " built: %DATE%-%TIME%"; >> "$(ProjectDir)\CiInfo.cs"
echo     } >> "$(ProjectDir)\CiInfo.cs"
echo } >> "$(ProjectDir)\CiInfo.cs"

然后我将"CiInfo.cs"添加到项目中,但忽略版本控制。这样我就不必编辑或提交它,而且该项目始终具有一个常量可用,即最新的构建编号和时间。


谢谢。我会将其制作成批处理文件,然后从批处理中调用它并传递带路径的文件名,并使用%1进行重定向,如果大部分内容可以在批处理中处理,则更容易维护: $(ProjectDir)\Tools\BuildCI.bat "$(ProjectDir)\CiInfo.cs" - Charles Byrne
我喜欢这个。非常简单,没有插件!谢谢。 - arkod
我正在寻找这个问题的标题,因为我想做与这个答案所说明的完全相同的事情。+1 - Zuu
这让我对它的独创性感到微笑。谢谢。如果你正在解析%DATE%值,一定要使用,比如CultureInfo("en-AU")(或类似的)来确保“17/12/2017”不会对美国人造成影响。可能值得在类顶部添加注释,例如“此代码是由工具创建的...更改将丢失...”等,以防有人在未来想知道。 - SteveCinq
对于那些寻求更多自定义日期的人,可以尝试类似以下代码:echo public const string BuildTimestamp = "$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss 'GMT'"))"; >> "$(ProjectDir)\CiInfo.cs" - Bob_Gneu

7

一种方法是在编译之前添加构建步骤,对适当的源文件进行正则表达式替换,以替换%BUILD_NUMBER%。


+1,虽然过去我做这个的时候,我只是将.config文件作为设置进行了定位,然后代码的其余部分就从那里访问它。 - Moo-Juice
这大致是我们目前所做的(虽然是针对配置文件而不是源文件),我希望我可以直接在代码中访问它,因为这样会更简单、更少出错,并且不会显示为与 SVN 存储库的更改。 - Eggplant Jeff

6

一种可能的解决方案是使用T4生成配置类,并实例化所有常量。T4已经很好地集成到MSVS中,不需要自定义构建步骤。


非常感谢!我使用Visual Studio开发已经有20年了,从来不知道还有这样的东西存在 :) - Drolevar

1

在C#中,define不允许您像在C/C++中那样定义常量。

来自此页面:

不能使用#define指令声明常量值,如通常在C和C++中所做的那样。C#中的常量最好定义为类或结构的静态成员。如果您有多个这样的常量,请考虑创建一个单独的“Constants”类来保存它们。

如果您想要在AssemblyInfo类中反映生成号码,则大多数构建工具支持在构建时生成该类。 MSBuild有一个任务可用于此目的。 NAnt也有类似功能。不确定Hudson如何实现此功能。


1

我有一个类似的问题。

我正在开发一款带有ASP.Net后端的Xamarin移动应用程序。我有一个包含后端服务器URL的设置类:

namespace Company.Mobile
{
    public static class Settings
    {
#if DEBUG
        const string WebApplicationBaseUrl = "https://local-pc:44335/";
#else
        const string WebApplicationBaseUrl = "https://company.com/";
#endif
    }
}

它在调试和发布配置中具有不同的值。但是,当几个开发人员开始参与项目时,这种方法就行不通了。每个开发机器都有其自己的IP地址,而移动电话需要使用唯一的IP地址进行连接。
我需要在每个开发机器上从文件或环境变量设置常量值。这就是Fody的用武之地。我使用它来创建一个解决方案 weaver。这里是详细信息
我将我的Settings类放在Xamarin应用程序项目中。此项目必须包含Fody Nuget软件包:
<ItemGroup>
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Fody" Version="6.2.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
    <WeaverFiles Include="$(SolutionDir)Company.Mobile.Models\bin\Debug\netstandard2.0\Company.Mobile.Models.dll" WeaverClassNames="SetDevServerUrlWeaver" />
  </ItemGroup>

I make my setup work on Debug configuration only, because I don't want the substitution to happen on Release builds.
Weaver类位于一个类库项目(Company.Mobile.Models)中,移动项目依赖于此项目(您不需要也不应该有此依赖关系,但Fody文档明确说明包含编织器的项目必须在发出编织程序集的项目之前构建)。此库项目包括FodyHelpers Nuget软件包。
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
        <PackageReference Include="FodyHelpers" Version="6.2.0" />
    </ItemGroup>

织造机类的定义如下:

#if DEBUG

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Fody;

namespace Company.Mobile.Models
{
    public class SetDevServerUrlWeaver : BaseModuleWeaver
    {
        private const string SettingsClassName = "Settings",
            DevServerUrlFieldName = "WebApplicationBaseUrl",
            DevServerUrlSettingFileName = "devServerUrl.txt";

        public override void Execute()
        {
            var target = this.ModuleDefinition.Types.SingleOrDefault(t => t.IsClass && t.Name == SettingsClassName);

            var targetField = target.Fields.Single(f => f.Name == DevServerUrlFieldName);

            try
            {
                targetField.Constant = File.ReadAllText(Path.Combine(this.ProjectDirectoryPath, DevServerUrlSettingFileName));
            }
            catch
            {
                this.WriteError($"Place a file named {DevServerUrlSettingFileName} and place in it the dev server URL");

                throw;
            }
        }

        public override IEnumerable<string> GetAssembliesForScanning()
        {
            yield return "Company.Mobile";
        }
    }
}

#endif

这是放置在移动应用项目中的FodyWeavers.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <SetDevServerUrlWeaver />
</Weavers>

devServerUrl.txt文件只包含我的本地IP:https://192.168.1.111:44335/。此文件不应添加到源代码控制中。将其添加到源代码控制忽略文件中,以便每个开发人员都有自己的版本。

您可以轻松地从环境变量(System.Environment.GetEnvironmentVariable)或其他地方读取替换的值,而不是从文件中读取。

我希望有更好的方法来做到这一点,比如Roslyn,或者似乎可以完成工作的this attribute,但都不行。


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