我可以从 csharp 中的编译时环境变量中创建一个常量吗?

Posted

技术标签:

【中文标题】我可以从 csharp 中的编译时环境变量中创建一个常量吗?【英文标题】:Can I make a constant from a compile-time env variable in csharp? 【发布时间】:2011-05-25 21:56:26 【问题描述】:

我们使用Hudson 来构建我们的项目,并且Hudson 在编译时方便地定义了诸如“%BUILD_NUMBER%”之类的环境变量。

我想在代码中使用这个变量,这样我们就可以在运行时记录下构建的内容。但是我不能做 System.Environment.GetEnvironmentVariable 因为那是访问运行时环境,我想要的是这样的:

#define BUILD_NUM = %BUILD_NUMBER%

const string BUILD_NUM = %BUILD_NUMBER%

除非我不知道语法。有人可以指出我正确的方向吗?谢谢!

【问题讨论】:

您可以使用预构建操作/宏来更改数字,这是一种选择吗? 我希望不需要每次都修改文件,输入源文件保持不变,并将当前值插入到编译输出中。 【参考方案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 适合的地方。我用它来创建in solution weaver。这里是the details。

我将 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>

我只在调试配置上进行设置,因为我不希望在发布版本上发生替换。

weaver 类被放置在移动项目所依赖的类库项目 (Company.Mobile.Models) 中(您不需要也不应该有这种依赖关系,但 Fody 文档清楚地表明该项目包含weaver 必须在发出编织程序集的项目之前构建)。这个库项目包括 FodyHelpers Nuget 包:

<ItemGroup Condition="'$(Configuration)' == 'Debug'">
        <PackageReference Include="FodyHelpers" Version="6.2.0" />
    </ItemGroup>

weaver类定义如下:

#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 似乎可以完成这项工作,但事实并非如此。

【讨论】:

【参考方案2】:

好的,这就是我最后要做的。它不是很优雅,但它确实有效。我创建了一个如下所示的预构建步骤:

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" 我喜欢这个。如此简单,无需插件!谢谢。 我一直在寻找这个问题的标题,因为我想做与这个答案所说明的完全相同的事情。 +1 这让我对它的独创性微笑。谢谢。如果您正在解析 %DATE% 值,请务必使用 CultureInfo("en-AU")(或类似名称)以确保“17/12/2017”不会对美国人造成影响。可能值得在类的顶部添加一条通常的注释,“此代码是由工具创建的......更改将丢失......”等,以防有人想知道...... 对于那些寻找更可定制日期的人,您可以尝试echo public const string BuildTimestamp = "$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss 'GMT'"))"; &gt;&gt; "$(ProjectDir)\CiInfo.cs"【参考方案3】:

一种可能性是使用 T4 生成您的配置类,并实例化所有常量。 T4 已很好地集成到 MSVS 中,无需您自己的自定义构建步骤。

【讨论】:

非常感谢!我用 Visual Studio 开发了将近 20 年,从来没有这样的新东西存在 :)【参考方案4】:

define 不允许您像在 C/C++ 中那样在 C# 中定义内容。

来自this page:

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

如果您希望在您的 AssemblyInfo 类中反映内部版本号,大多数构建工具都支持在构建时生成该类。 MSBuild has a task 为它。 As does NAnt。不确定 Hudson 是如何做到的。

【讨论】:

【参考方案5】:

一种方法是在编译之前添加一个构建步骤,该步骤在 %BUILD_NUMBER% 的相应源文件中进行正则表达式替换。

【讨论】:

+1,虽然在过去我这样做时,我只是将.config 文件作为设置目标,然后其余代码只是从那里访​​问它。 这大致是我们目前所做的(虽然是针对配置文件,而不是源文件),但我希望我可以直接在代码中访问它,因为它相当简单,不易出错,并且赢得了'不显示为与 svn 存储库的更改。

以上是关于我可以从 csharp 中的编译时环境变量中创建一个常量吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 pandas for python 中创建虚拟变量

在 Snowflake UI 中创建环境变量文件

csharp C#中的系统环境变量

SQL中如何从两张表中创建一张表

在 NetBeans 中编译 Maven 项目时 Ant 无法找到环境变量

从PowerShell设置Visual Studio环境变量