Skip to content

Commit 138725b

Browse files
Add support for version in bicepconfig.json
1 parent 74551a5 commit 138725b

20 files changed

+188
-11
lines changed

src/Bicep.Cli/Commands/RootCommand.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.IO.Compression;
55
using Bicep.Cli.Arguments;
66
using Bicep.Core.Exceptions;
7+
using Bicep.Core.Utils;
8+
using Environment = System.Environment;
79

810
namespace Bicep.Cli.Commands
911
{
@@ -265,10 +267,11 @@ private void PrintThirdPartyNotices()
265267

266268
private static string GetVersionString()
267269
{
268-
var versionSplit = ThisAssembly.AssemblyInformationalVersion.Split('+');
270+
var version = BicepVersion.Instance.Value;
271+
var commitHash = BicepVersion.Instance.CommitHash;
269272

270273
// <major>.<minor>.<patch> (<commmithash>)
271-
return $"{versionSplit[0]} ({(versionSplit.Length > 1 ? versionSplit[1] : "custom")})";
274+
return $"{version} ({(commitHash is {} ? commitHash : "custom")})";
272275
}
273276

274277
private static void WriteEmbeddedResource(TextWriter writer, string streamName)

src/Bicep.Cli/Rpc/CliJsonRpcServer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Bicep.Core.Syntax;
1414
using Bicep.Core.Text;
1515
using Bicep.Core.TypeSystem;
16+
using Bicep.Core.Utils;
1617
using Bicep.Core.Workspaces;
1718
using Newtonsoft.Json.Serialization;
1819
using StreamJsonRpc;
@@ -42,7 +43,7 @@ public async Task<VersionResponse> Version(VersionRequest request, CancellationT
4243
await Task.Yield();
4344

4445
return new(
45-
ThisAssembly.AssemblyInformationalVersion.Split('+')[0]);
46+
BicepVersion.Instance.Value);
4647
}
4748

4849
/// <inheritdoc/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Bicep.Core.Diagnostics;
5+
using Bicep.Core.UnitTests.Assertions;
6+
using Bicep.Core.UnitTests.FileSystem;
7+
using Bicep.Core.UnitTests.Utils;
8+
using Bicep.Core.Utils;
9+
using FluentAssertions;
10+
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
12+
namespace Bicep.Core.IntegrationTests;
13+
14+
[TestClass]
15+
public class BicepConfigTests
16+
{
17+
[TestMethod]
18+
public async Task Version_mismatch_should_block_compilation()
19+
{
20+
var result = await CompilationHelper.RestoreAndCompile(
21+
("main.bicep", new("""
22+
output foo string = ''
23+
""")), ("../bicepconfig.json", new("""
24+
{
25+
"bicep": {
26+
"version": "0.0.1"
27+
}
28+
}
29+
""")));
30+
31+
var configPath = InMemoryFileResolver.GetFileUri("/path/bicepconfig.json");
32+
result.ExcludingLinterDiagnostics().Should().HaveDiagnostics([
33+
("BCP409", DiagnosticLevel.Error, $"""Bicep version "{BicepVersion.Instance.Value}" was used for compilation, but version "0.0.1" is required in configuration file "{configPath.LocalPath}"."""),
34+
]);
35+
}
36+
37+
[TestMethod]
38+
public async Task Correct_version_should_permit_compilation()
39+
{
40+
var result = await CompilationHelper.RestoreAndCompile(
41+
("main.bicep", new("""
42+
output foo string= ''
43+
""")), ("../bicepconfig.json", new($$"""
44+
{
45+
"bicep": {
46+
"version": "{{BicepVersion.Instance.Value}}"
47+
}
48+
}
49+
""")));
50+
51+
result.Should().NotHaveAnyDiagnostics();
52+
}
53+
}

src/Bicep.Core.UnitTests/BicepTestConstants.cs

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public static RootConfiguration CreateMockConfiguration(Dictionary<string, objec
103103
["analyzers"] = new Dictionary<string, object>(),
104104
["experimentalFeaturesEnabled"] = new Dictionary<string, bool>(),
105105
["formatting"] = new Dictionary<string, bool>(),
106+
["bicep"] = new Dictionary<string, bool>(),
106107
};
107108

108109
if (customConfigurationData is not null)

src/Bicep.Core.UnitTests/Configuration/ConfigurationManagerTests.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ public void GetBuiltInConfiguration_NoParameter_ReturnsBuiltInConfigurationWithA
117117
"insertFinalNewline": true,
118118
"indentSize": 2,
119119
"width": 120
120-
}
120+
},
121+
"bicep": {}
121122
}
122123
""");
123124
}
@@ -198,7 +199,8 @@ public void GetBuiltInConfiguration_DisableAllAnalyzers_ReturnsBuiltInConfigurat
198199
"insertFinalNewline": true,
199200
"indentSize": 2,
200201
"width": 120
201-
}
202+
},
203+
"bicep": {}
202204
}
203205
""");
204206
}
@@ -304,7 +306,8 @@ public void GetBuiltInConfiguration_DisableAnalyzers_ReturnsBuiltInConfiguration
304306
"insertFinalNewline": true,
305307
"indentSize": 2,
306308
"width": 120
307-
}
309+
},
310+
"bicep": {}
308311
}
309312
""");
310313
}
@@ -481,7 +484,8 @@ public void GetBuiltInConfiguration_EnableExperimentalFeature_ReturnsBuiltInConf
481484
"insertFinalNewline": true,
482485
"indentSize": 2,
483486
"width": 120
484-
}
487+
},
488+
"bicep": {}
485489
}
486490
""");
487491
}
@@ -729,6 +733,9 @@ public void GetConfiguration_ValidCustomConfiguration_OverridesBuiltInConfigurat
729733
"insertFinalNewline": true,
730734
"indentSize": 2,
731735
"width": 80
736+
},
737+
"bicep": {
738+
"version": "1.2.3"
732739
}
733740
}
734741
"""
@@ -846,6 +853,9 @@ public void GetConfiguration_ValidCustomConfiguration_OverridesBuiltInConfigurat
846853
"insertFinalNewline": true,
847854
"indentSize": 2,
848855
"width": 80
856+
},
857+
"bicep": {
858+
"version": "1.2.3"
849859
}
850860
}
851861
""");

src/Bicep.Core.UnitTests/Configuration/RootConfigurationTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public void RootConfiguration_LeadingTildeInCacheRootDirectory_ExpandPath(string
2323
cacheRootDirectory,
2424
BicepTestConstants.BuiltInConfiguration.ExperimentalFeaturesEnabled,
2525
BicepTestConstants.BuiltInConfiguration.Formatting,
26+
BicepTestConstants.BuiltInConfiguration.Bicep,
2627
BicepTestConstants.BuiltInConfiguration.ConfigFileUri,
2728
BicepTestConstants.BuiltInConfiguration.DiagnosticBuilders);
2829

src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseRecentApiVersionRuleTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ original.ExperimentalFeaturesEnabled with
130130
Extensibility = true,
131131
},
132132
original.Formatting,
133+
original.Bicep,
133134
null,
134135
null);
135136
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
using Bicep.Core.Utils;
4+
using FluentAssertions;
5+
using Microsoft.VisualStudio.TestTools.UnitTesting;
6+
7+
namespace Bicep.Core.UnitTests.Utils;
8+
9+
[TestClass]
10+
public class BicepVersionTests
11+
{
12+
[TestMethod]
13+
public void BicepVersion_should_return_assembly_info()
14+
{
15+
var result = BicepVersion.Instance;
16+
result.Value.Should().MatchRegex(@"^[0-9]+\.[0-9]+\.[0-9]+");
17+
if (result.CommitHash is {})
18+
{
19+
result.CommitHash.Should().MatchRegex(@"^[0-9a-f]{7,40}$");
20+
}
21+
}
22+
}

src/Bicep.Core/Configuration/AnalyzersConfigurationExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public static RootConfiguration WithAnalyzersConfiguration(this RootConfiguratio
4646
current.CacheRootDirectory,
4747
current.ExperimentalFeaturesEnabled,
4848
current.Formatting,
49+
current.Bicep,
4950
current.ConfigFileUri,
5051
current.DiagnosticBuilders);
5152

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json;
5+
using Bicep.Core.Extensions;
6+
7+
namespace Bicep.Core.Configuration;
8+
9+
public record BicepConfiguration(
10+
string? Version);
11+
12+
public class BicepConfigurationSection : ConfigurationSection<BicepConfiguration>
13+
{
14+
public BicepConfigurationSection(BicepConfiguration data)
15+
: base(data)
16+
{
17+
}
18+
19+
public static BicepConfigurationSection Bind(JsonElement element)
20+
=> new(element.ToNonNullObject<BicepConfiguration>());
21+
}

src/Bicep.Core/Configuration/ConfigurationManager.cs

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ private static RootConfiguration WithLoadDiagnostics(RootConfiguration configura
102102
configuration.CacheRootDirectory,
103103
configuration.ExperimentalFeaturesEnabled,
104104
configuration.Formatting,
105+
configuration.Bicep,
105106
configuration.ConfigFileUri,
106107
diagnostics);
107108
}

src/Bicep.Core/Configuration/ExperimentalFeaturesExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static RootConfiguration WithExperimentalFeaturesConfiguration(this RootC
1515
current.CacheRootDirectory,
1616
featuresEnabled,
1717
current.Formatting,
18+
current.Bicep,
1819
current.ConfigFileUri,
1920
current.DiagnosticBuilders);
2021

src/Bicep.Core/Configuration/ExtensionsConfigurationExtensions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static RootConfiguration WithExtensions(this RootConfiguration rootConfig
2424
rootConfiguration.CacheRootDirectory,
2525
rootConfiguration.ExperimentalFeaturesEnabled,
2626
rootConfiguration.Formatting,
27+
rootConfiguration.Bicep,
2728
rootConfiguration.ConfigFileUri,
2829
rootConfiguration.DiagnosticBuilders);
2930
}
@@ -39,6 +40,7 @@ public static RootConfiguration WithImplicitExtensions(this RootConfiguration ro
3940
rootConfiguration.CacheRootDirectory,
4041
rootConfiguration.ExperimentalFeaturesEnabled,
4142
rootConfiguration.Formatting,
43+
rootConfiguration.Bicep,
4244
rootConfiguration.ConfigFileUri,
4345
rootConfiguration.DiagnosticBuilders);
4446
}

src/Bicep.Core/Configuration/RootConfiguration.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class RootConfiguration
2828

2929
public const string FormattingKey = "formatting";
3030

31+
public const string BicepKey = "bicep";
32+
3133
public RootConfiguration(
3234
CloudConfiguration cloud,
3335
ModuleAliasesConfiguration moduleAliases,
@@ -37,6 +39,7 @@ public RootConfiguration(
3739
string? cacheRootDirectory,
3840
ExperimentalFeaturesEnabled experimentalFeaturesEnabled,
3941
FormattingConfiguration formatting,
42+
BicepConfigurationSection bicep,
4043
Uri? configFileUri,
4144
IEnumerable<DiagnosticBuilder.DiagnosticBuilderDelegate>? diagnosticBuilders)
4245
{
@@ -48,6 +51,7 @@ public RootConfiguration(
4851
this.CacheRootDirectory = ExpandCacheRootDirectory(cacheRootDirectory);
4952
this.ExperimentalFeaturesEnabled = experimentalFeaturesEnabled;
5053
this.Formatting = formatting;
54+
this.Bicep = bicep;
5155
this.ConfigFileUri = configFileUri;
5256
this.DiagnosticBuilders = diagnosticBuilders?.ToImmutableArray() ?? [];
5357
}
@@ -60,11 +64,12 @@ public static RootConfiguration Bind(JsonElement element, Uri? configFileUri = n
6064
var cacheRootDirectory = element.TryGetProperty(CacheRootDirectoryKey, out var e) ? e.GetString() : default;
6165
var experimentalFeaturesEnabled = ExperimentalFeaturesEnabled.Bind(element.GetProperty(ExperimentalFeaturesEnabledKey));
6266
var formatting = FormattingConfiguration.Bind(element.GetProperty(FormattingKey));
67+
var bicep = BicepConfigurationSection.Bind(element.GetProperty(BicepKey));
6368

6469
var extensions = ExtensionsConfiguration.Bind(element.GetProperty(ExtensionsKey));
6570
var implicitExtensions = ImplicitExtensionsConfiguration.Bind(element.GetProperty(ImplicitExtensionsKey));
6671

67-
return new(cloud, moduleAliases, extensions, implicitExtensions, analyzers, cacheRootDirectory, experimentalFeaturesEnabled, formatting, configFileUri, diagnosticBuilders);
72+
return new(cloud, moduleAliases, extensions, implicitExtensions, analyzers, cacheRootDirectory, experimentalFeaturesEnabled, formatting, bicep, configFileUri, diagnosticBuilders);
6873
}
6974

7075
public CloudConfiguration Cloud { get; }
@@ -83,6 +88,8 @@ public static RootConfiguration Bind(JsonElement element, Uri? configFileUri = n
8388

8489
public FormattingConfiguration Formatting { get; }
8590

91+
public BicepConfigurationSection Bicep { get; }
92+
8693
public Uri? ConfigFileUri { get; }
8794

8895
public ImmutableArray<DiagnosticBuilder.DiagnosticBuilderDelegate> DiagnosticBuilders { get; }
@@ -122,6 +129,9 @@ public string ToUtf8Json()
122129
writer.WritePropertyName(FormattingKey);
123130
this.Formatting.WriteTo(writer);
124131

132+
writer.WritePropertyName(BicepKey);
133+
this.Bicep.WriteTo(writer);
134+
125135
writer.WriteEndObject();
126136
}
127137

src/Bicep.Core/Configuration/bicepconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@
6565
}
6666
},
6767
"experimentalFeaturesEnabled": {},
68-
"formatting": {}
68+
"formatting": {},
69+
"bicep": {}
6970
}

src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs

+7
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,13 @@ public Diagnostic MicrosoftGraphBuiltinDeprecatedSoon(ExtensionDeclarationSyntax
18201820
public Diagnostic NameofInvalidOnUnnamedExpression() => CoreError(
18211821
"BCP408",
18221822
$"The \"{LanguageConstants.NameofFunctionName}\" function can only be used with an expression which has a name.");
1823+
1824+
public Diagnostic InvalidBicepVersion(string? configFilePath, string expectedVersion, string actualVersion) => CoreError(
1825+
"BCP409",
1826+
configFilePath switch {
1827+
{} => $"""Bicep version "{actualVersion}" was used for compilation, but version "{expectedVersion}" is required in configuration file "{configFilePath}".""",
1828+
_ => $"""Bicep version "{actualVersion}" was used for compilation, but version "{expectedVersion}" is required.""",
1829+
});
18231830
}
18241831

18251832
public static DiagnosticBuilderInternal ForPosition(TextSpan span)

src/Bicep.Core/Emit/EmitLimitationCalculator.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public static EmitLimitationInfo Calculate(SemanticModel model)
3737
ForSyntaxValidatorVisitor.Validate(model, diagnostics);
3838
FunctionPlacementValidatorVisitor.Validate(model, diagnostics);
3939
IntegerValidatorVisitor.Validate(model, diagnostics);
40-
40+
41+
BlockIncorrectBicepVersion(model, diagnostics);
4142
DetectDuplicateNames(model, diagnostics, resourceScopeData, moduleScopeData);
4243
DetectIncorrectlyFormattedNames(model, diagnostics);
4344
DetectUnexpectedResourceLoopInvariantProperties(model, diagnostics);
@@ -61,6 +62,16 @@ public static EmitLimitationInfo Calculate(SemanticModel model)
6162
return new(diagnostics.GetDiagnostics(), moduleScopeData, resourceScopeData, paramAssignments);
6263
}
6364

65+
private static void BlockIncorrectBicepVersion(SemanticModel model, IDiagnosticWriter diagnostics)
66+
{
67+
var actualVersion = ThisAssembly.AssemblyInformationalVersion.Split('+')[0];
68+
if (model.Configuration.Bicep.Data.Version is {} expectedVersion &&
69+
expectedVersion != actualVersion)
70+
{
71+
diagnostics.Write(TextSpan.TextDocumentStart, x => x.InvalidBicepVersion(model.Configuration.ConfigFileUri?.LocalPath, expectedVersion, actualVersion));
72+
}
73+
}
74+
6475
private static void DetectDuplicateNames(SemanticModel semanticModel, IDiagnosticWriter diagnosticWriter, ImmutableDictionary<DeclaredResourceMetadata, ScopeHelper.ScopeData> resourceScopeData, ImmutableDictionary<ModuleSymbol, ScopeHelper.ScopeData> moduleScopeData)
6576
{
6677
// TODO generalize or move into Az extension

src/Bicep.Core/Utils/BicepVersion.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
namespace Bicep.Core.Utils;
4+
5+
public record BicepVersion(
6+
string Value,
7+
string? CommitHash)
8+
{
9+
public static readonly BicepVersion Instance = GetVersion();
10+
11+
private static BicepVersion GetVersion()
12+
{
13+
var split = ThisAssembly.AssemblyInformationalVersion.Split('+');
14+
15+
return new(
16+
split[0],
17+
split.Length > 1 ? split[1] : null);
18+
}
19+
}

src/Bicep.Core/Utils/Environment.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ public class Environment : IEnvironment
99

1010
public IEnumerable<string> GetVariableNames()
1111
=> System.Environment.GetEnvironmentVariables().Keys.OfType<string>();
12-
}
12+
}

0 commit comments

Comments
 (0)