Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Management/src/GitProperties.Build/PackageReadme.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ This command writes an extra copy of `git.properties` directly next to your proj
>
> If you deploy by pushing your source code directly, rather than a pre-built or published output (for example with Cloud Foundry's `cf push`), be careful not to *also* exclude `git.properties` from whatever gets pushed or deployed. For Cloud Foundry, that means leaving it out of `.cfignore`. `git.properties` must stay out of Git through `.gitignore`, but it still needs to be present on disk and travel along with your source code.

### Refreshing many projects at once

The command above works for one project at a time. If your solution has several projects, and only some of them use this package, running the same command on a project that doesn't use it fails with an error like this:

```
error MSB4057: The target "WriteGitPropertiesFallbackFile" does not exist in the project.
```

To safely refresh every project at once, add a `Directory.Build.targets` file that applies to all of them (your solution's root folder works well). It defines a new target that only calls the real one on projects that use this package. This makes it safe to run on every project, even ones that don't use this package:

```xml
<!-- Directory.Build.targets -->
<Project>
<Target Name="RefreshGitPropertiesFallbackFile">
<CallTarget Targets="WriteGitPropertiesFallbackFile" Condition="'$(GitExecutable)' != ''" />
</Target>
</Project>
```

Then, instead of the command shown above, run this new target for your whole solution:

```shell
dotnet build YourSolution.slnx -t:RefreshGitPropertiesFallbackFile
```

## Using this package in a shared project

Installing this package as shown under [Getting started](#getting-started), whether with the `dotnet` CLI or Visual Studio's Add Package dialog, writes `PrivateAssets="all"` into the `<PackageReference>` line automatically. This stops the reference from becoming transitive, so `git.properties` generation stays local to the project you installed it in. That's the right choice for most solutions: usually only a few host apps need `git.properties`, so keeping the reference non-transitive avoids running Git commands anywhere else.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
<GitPropertiesFile Condition="'$(GitPropertiesFile)' == ''">$(IntermediateOutputPath)git.properties</GitPropertiesFile>
</PropertyGroup>

<PropertyGroup Condition="'$(_ForceWriteGitPropertiesFallbackFile)' == 'true'">
<GenerateGitProperties>true</GenerateGitProperties>
</PropertyGroup>

<PropertyGroup Condition="'$(GenerateGitProperties)' != 'auto'">
<_GitPropertiesShouldGenerate>$(GenerateGitProperties)</_GitPropertiesShouldGenerate>
</PropertyGroup>
Expand All @@ -72,7 +76,7 @@

<PropertyGroup Condition="'$(_GitPropertiesSuppressGitRepositoryNotFound)' == ''">
<_GitPropertiesSuppressGitRepositoryNotFound>false</_GitPropertiesSuppressGitRepositoryNotFound>
<_GitPropertiesSuppressGitRepositoryNotFound Condition="Exists('$(GitPropertiesFallbackFile)')">true</_GitPropertiesSuppressGitRepositoryNotFound>
<_GitPropertiesSuppressGitRepositoryNotFound Condition="Exists('$(GitPropertiesFallbackFile)') and '$(_ForceWriteGitPropertiesFallbackFile)' != 'true'">true</_GitPropertiesSuppressGitRepositoryNotFound>
</PropertyGroup>

<FindGitRepositoryRootTask StartDirectory="$(MSBuildProjectDirectory)" EnableWarnings="$(GitPropertiesEnableWarnings)"
Expand Down Expand Up @@ -157,7 +161,7 @@
computes it later in the pipeline.
-->
<PropertyGroup>
<_GitPropertiesFallbackFileTarget Condition="'$(GitPropertiesWriteToProjectDirectory)' == 'true'">$(GitPropertiesFallbackFile)</_GitPropertiesFallbackFileTarget>
<_GitPropertiesFallbackFileTarget Condition="'$(GitPropertiesWriteToProjectDirectory)' == 'true' or '$(_ForceWriteGitPropertiesFallbackFile)' == 'true'">$(GitPropertiesFallbackFile)</_GitPropertiesFallbackFileTarget>
</PropertyGroup>

<ComposeGitPropertiesTask RepositoryRoot="$(GitRepositoryRoot)" GitExecutable="$(GitExecutable)" CacheFile="$(GitPropertiesCacheFile)"
Expand All @@ -176,12 +180,22 @@
<Target Name="WriteGitPropertiesFallbackFile">
<!--
Refreshes just the fallback file without a full build. Intended for source-based `cf push`. Runs a nested <MSBuild> re-invocation reaching
"AssignTargetPaths;PrepareForPublish" rather than depending on or invoking ComposeGitProperties directly, because that target's own Condition depends
on $(GitPropertiesCacheFile), which is set only by its own DependsOnTargets chain. That chain has not run yet when the Condition is checked (the same
rule noted on ResolveGitPropertiesPaths above). The nested build reaches the SDK's own injection points instead, without ever reaching CoreCompile.
"AssignTargetPaths;PrepareForPublish", the SDK's own injection points, instead of depending on ComposeGitProperties directly: that target's
Condition depends on $(GitPropertiesCacheFile), which isn't set until its own DependsOnTargets chain runs (same rule as ResolveGitPropertiesPaths
above). Never reaches CoreCompile.
-->
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="AssignTargetPaths;PrepareForPublish"
Properties="Configuration=$(Configuration);GenerateGitProperties=true;GitPropertiesWriteToProjectDirectory=true;_GitPropertiesSuppressGitRepositoryNotFound=false" />
Properties="Configuration=$(Configuration);_ForceWriteGitPropertiesFallbackFile=true" />
</Target>

<Target Name="_ExcludeGitPropertiesOverrideFromProjectReferences" BeforeTargets="AssignProjectConfiguration"
Condition="'$(_ForceWriteGitPropertiesFallbackFile)' == 'true'">
<!-- Prevent the global _ForceWriteGitPropertiesFallbackFile from flowing to every ProjectReference. -->
<ItemGroup>
<ProjectReference>
<GlobalPropertiesToRemove>%(ProjectReference.GlobalPropertiesToRemove);_ForceWriteGitPropertiesFallbackFile</GlobalPropertiesToRemove>
</ProjectReference>
</ItemGroup>
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;

public sealed class WriteGitPropertiesFallbackFileDoesNotForceServiceDefaultsToWriteFallbackCopyTest : GitPropertiesTestBase
{
[Fact]
public async Task Test()
{
GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);

TestProject serviceDefaults = await repository.AddTestLibraryAsync("ServiceDefaults", true, [
Workspace.FakeEndpointPackageReference,
Workspace.GetGitPropertiesPackageReferenceWithPrivateAssets("none")
]);

TestProject apiService = await repository.AddTestAppAsync("ApiService", projectReferences: [serviceDefaults]);
await apiService.BuildAsync("-t:WriteGitPropertiesFallbackFile");

apiService.FallbackGitPropertiesGenerated.Should().BeTrue();
serviceDefaults.GitPropertiesGenerated.Should().BeTrue();
serviceDefaults.FallbackGitPropertiesGenerated.Should().BeFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;

public sealed class WriteGitPropertiesFallbackFileOverridesGlobalOptOutTest : GitPropertiesTestBase
{
[Fact]
public async Task Test()
{
GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);

await Workspace.WriteFileAsync(Path.Combine(Workspace.GetPath("repo"), "Directory.Build.props"), """
<Project>
<PropertyGroup>
<GenerateGitProperties>false</GenerateGitProperties>
</PropertyGroup>
</Project>
""");

TestProject testApp = repository.TestApp;

await testApp.BuildAsync("-t:WriteGitPropertiesFallbackFile");

testApp.FallbackGitPropertiesGenerated.Should().BeTrue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;

public sealed class WriteGitPropertiesFallbackFileWrapperSupportsServiceDefaultsAndAppHostTest : GitPropertiesTestBase
{
[Fact]
public async Task Test()
{
GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);

await Workspace.WriteFileAsync(Path.Combine(Workspace.GetPath("repo"), "Directory.Build.targets"), """
<Project>
<Target Name="RefreshGitPropertiesFallbackFile">
<CallTarget Targets="WriteGitPropertiesFallbackFile" Condition="'$(GitExecutable)' != ''" />
</Target>
</Project>
""");

TestProject serviceDefaults = await repository.AddTestLibraryAsync("ServiceDefaults", false, [
Workspace.FakeEndpointPackageReference,
Workspace.GetGitPropertiesPackageReferenceWithPrivateAssets("none")
]);

TestProject apiService = await repository.AddTestAppAsync("ApiService", projectReferences: [serviceDefaults]);
await apiService.BuildAsync("-t:RefreshGitPropertiesFallbackFile");

TestProject appHost = await repository.AddTestAppAsync("AppHost");
await appHost.BuildAsync("-t:RefreshGitPropertiesFallbackFile");

serviceDefaults.FallbackGitPropertiesGenerated.Should().BeFalse();
apiService.FallbackGitPropertiesGenerated.Should().BeTrue();
appHost.FallbackGitPropertiesGenerated.Should().BeFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ namespace Steeltoe.Management.GitProperties.Build.Test;

internal static class GitRepositoryBuilder
{
private const string GitConfigAppendix = """
[user]
name = Test User
email = test@example.com
[commit]
gpgsign = false # Avoid an interactive prompt on a machine that has commit signing configured globally.
""";

private static readonly HashSet<string> DirectoryNamesExcludedInPush = new(StringComparer.OrdinalIgnoreCase)
{
".git",
Expand All @@ -17,13 +25,12 @@ public static async Task InitializeEmptyAsync(string destination)
{
Directory.CreateDirectory(destination);
await ProcessRunner.RunGitAsync(destination, "init", "--quiet", "--initial-branch=main", ".");
await File.AppendAllTextAsync(Path.Combine(destination, ".git", "config"), GitConfigAppendix, TestContext.Current.CancellationToken);
}

public static async Task InitializeAsync(string destination, int commitCount, bool includeFallbackFileInGitignore)
{
await InitializeEmptyAsync(destination);
await ProcessRunner.RunGitAsync(destination, "config", "user.name", "Test User");
await ProcessRunner.RunGitAsync(destination, "config", "user.email", "test@example.com");

string gitignoreContent = includeFallbackFileInGitignore
? """
Expand Down
Loading