Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Adopt-StarterKit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ $generated = Join-Path ([System.IO.Path]::GetTempPath()) "starterkit-$Name-$([Gu

$templateArguments = @("new", $Template, "--name", $Name, "--output", $generated)
if ($Organization) { $templateArguments += @("--organization", $Organization) }
if ($Project) { $templateArguments += @("--project", $Project) }
if ($Project) { $templateArguments += @("--projectName", $Project) }

Write-Host "Generating '$Template' for '$Name' into $generated"
& dotnet @templateArguments | Out-Null
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ dotnet new update
If you are still using Azure DevOps, we got you covered, but you need the organization and project names as well. Again, choose between a binary package or a source-only package.

```
dotnet new azdo-nuget-class-library-sln --name TheNameOfYourAwesomeLibrary --organization MyDevOpsOrganization --project MyDevOpsProject
dotnet new azdo-nuget-class-library-sln --name TheNameOfYourAwesomeLibrary --organization MyDevOpsOrganization --projectName MyDevOpsProject
```

```
dotnet new azdo-source-only-nuget-class-library-sln --name TheNameOfYourAwesomeLibrary --organization MyDevOpsOrganization --project MyDevOpsProject
dotnet new azdo-source-only-nuget-class-library-sln --name TheNameOfYourAwesomeLibrary --organization MyDevOpsOrganization --projectName MyDevOpsProject
```

The GitHub templates use [Dependabot](https://docs.github.com/en/code-security/dependabot) to keep the NuGet packages and GitHub Actions up-to-date. If you prefer [Renovate](https://docs.renovatebot.com/), add `--dependency-updater renovate`. You get a `.github/renovate.json` instead of `.github/dependabot.yml`, so you don't get duplicate pull requests. It groups related updates (such as all analyzers or all xUnit packages) into one pull request, updates the .NET SDK in `global.json`, and automatically merges patch updates of analyzers, test and build packages.
Expand Down
77 changes: 54 additions & 23 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

class Build : FalloutBuild
{
static readonly string[] TemplateVariantNames =
["Normal", "SourceOnly", "NormalOss", "SourceOnlyOss", "NormalAzdo", "SourceOnlyAzdo"];

/// Support plugins are available for:
/// - JetBrains ReSharper https://nuke.build/resharper
/// - JetBrains Rider https://nuke.build/rider
Expand Down Expand Up @@ -141,8 +144,7 @@ class Build : FalloutBuild
PackageReadme = true,
});

string[] names = ["Normal", "SourceOnly", "NormalOss", "SourceOnlyOss", "NormalAzdo", "SourceOnlyAzdo"];
foreach (string name in names)
foreach (string name in TemplateVariantNames)
{
(ArtifactsDirectory / "templates" / name / "PackageReadme.md").WriteAllText(readmeContents);
}
Expand Down Expand Up @@ -187,8 +189,7 @@ class Build : FalloutBuild
}

// Write into every template variant
string[] variantNames = ["Normal", "NormalOss", "SourceOnly", "SourceOnlyOss", "NormalAzdo", "SourceOnlyAzdo"];
foreach (string variant in variantNames)
foreach (string variant in TemplateVariantNames)
{
var skillsDir = ArtifactsDirectory / "templates" / variant / ".agents" / "skills" / "csharp-guidelines";
skillsDir.CreateOrCleanDirectory();
Expand Down Expand Up @@ -302,46 +303,66 @@ class Build : FalloutBuild
// Install the locally built package with force flag
DotNet($"new install {packageFile} --force", workingDirectory: testDirectory);

// Test each template variant by creating and building a test project
string[] templateShortNames = [
"nooss-nuget-class-library-sln",
"nooss-source-only-nuget-class-library-sln",
"oss-nuget-class-library-sln",
"oss-source-only-nuget-class-library-sln",
"oss-nuget-class-library-sln --benchmarks true",
"oss-source-only-nuget-class-library-sln --benchmarks true"
];

foreach (string templateName in templateShortNames)
// Test each template variant by creating and building a test project. The short names are read from
// the rendered template.json files, so a newly added variant cannot be forgotten here.
string[] templateShortNames = TemplateVariantNames
.Select(name => ArtifactsDirectory / "templates" / name / ".template.config" / "template.json")
.Select(templateJsonFile => JsonDocument.Parse(templateJsonFile.ReadAllText())
.RootElement.GetProperty("shortName").GetString())
.ToArray();

// The Azure DevOps templates require an organization and project, which also exercises their
// symbol replacement. The OSS templates are additionally tested with the optional benchmarks flag.
string[] templateArguments = templateShortNames
.Select(shortName => shortName.StartsWith("azdo-", StringComparison.Ordinal)
? $"{shortName} --organization TestOrganization --projectName TestProject"
: shortName)
.Concat([
"oss-nuget-class-library-sln --benchmarks true",
"oss-source-only-nuget-class-library-sln --benchmarks true"
])
.ToArray();

foreach (string templateArgument in templateArguments)
{
string shortName = templateArgument.Split(' ')[0];
bool isAzdo = shortName.StartsWith("azdo-", StringComparison.Ordinal);

var projectTestDirectory = testDirectory / $"solution";
projectTestDirectory.DeleteDirectory();
projectTestDirectory.CreateDirectory();

Information("Testing template: {Template}", templateName);
Information("Testing template: {Template}", templateArgument);

// Create project from template
DotNet($"new {templateName:nq} --name TestLibrary --force", workingDirectory: projectTestDirectory);
DotNet($"new {templateArgument:nq} --name TestLibrary --force", workingDirectory: projectTestDirectory);

// Build the generated project to ensure it compiles without errors
// Note: template uses preferNameDirectory=true, so project is created in TestLibrary subdirectory
var actualProjectDirectory = projectTestDirectory / "TestLibrary";

DotNet("build", workingDirectory: actualProjectDirectory);
Information("Successfully built project from template: {Template}", templateName);
Information("Successfully built project from template: {Template}", templateArgument);

// Check if the basic project structure was created correctly
if ((actualProjectDirectory / $"TestLibrary.slnx").FileExists() ||
(actualProjectDirectory / $"TestLibrary").DirectoryExists())
{
Information("Project structure was created successfully for template: {Template}", templateName);
Information("Project structure was created successfully for template: {Template}", templateArgument);
}
else
{
Error($"Template {templateName} failed to create proper project structure");
Error($"Template {templateArgument} failed to create proper project structure");
}

AssertDependencyUpdater(actualProjectDirectory, expectDependabot: true);
if (isAzdo)
{
AssertAzdoReplacements(actualProjectDirectory, "TestOrganization", "TestProject");
}
else
{
AssertDependencyUpdater(actualProjectDirectory, expectDependabot: true);
}
}

// Verify that choosing Renovate replaces the Dependabot configuration
Expand Down Expand Up @@ -415,8 +436,7 @@ void TestAdoptionByExistingLibrary(AbsolutePath repositoryDirectory)
.DependsOn(PrepareTemplateReadmes)
.Executes(() =>
{
string[] names = ["Normal", "SourceOnly", "NormalOss", "SourceOnlyOss", "NormalAzdo", "SourceOnlyAzdo"];
foreach (string name in names)
foreach (string name in TemplateVariantNames)
{
var templateDirectory = ArtifactsDirectory / "templates" / name;

Expand Down Expand Up @@ -464,6 +484,17 @@ static void AssertDependencyUpdater(AbsolutePath projectDirectory, bool expectDe
$"but found dependabot.yml={hasDependabot} and renovate.json={hasRenovate}");
}

static void AssertAzdoReplacements(AbsolutePath projectDirectory, string organization, string project)
{
// Build/Build.cs is present in both the normal and source-only Azdo variants, unlike the main .csproj,
// whose "Package info" property group (which contains the RepositoryUrl) is only rendered for non-source-only.
var buildScriptFile = projectDirectory / "Build" / "Build.cs";
string content = buildScriptFile.ReadAllText();

Assert.True(content.Contains($"{organization}/{project}", StringComparison.Ordinal),
$"Expected {buildScriptFile} to reference organization '{organization}' and project '{project}'");
}

bool IsPullRequest =>GitHubActions?.IsPullRequest ?? false;

bool IsTag => BranchSpec != null && BranchSpec.Contains("refs/tags", StringComparison.OrdinalIgnoreCase);
Expand Down
2 changes: 1 addition & 1 deletion templates/Source/.template.config/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"replaces": "MyOrganization",
"isRequired": true
},
"project": {
"projectName": {
"type": "parameter",
"dataType": "string",
"defaultValue": "MyProject",
Expand Down
Loading