Skip to content

Commit

Permalink
wip: ArgumentsFromParametersFileAttribute via JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
matkoch committed Aug 22, 2023
1 parent afd2da8 commit 0036415
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 57 deletions.
8 changes: 8 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Data2": {
"type": "array",
"items": {
"type": "string"
}
},
"DiscordWebhook": {
"type": "string",
"default": "Secrets must be entered via 'nuke :secrets [profile]'"
Expand Down Expand Up @@ -130,6 +136,7 @@
"CreateGitHubRelease",
"DeletePackages",
"DownloadLicenses",
"Foo",
"GenerateGlobalSolution",
"GeneratePublicApi",
"GenerateTools",
Expand Down Expand Up @@ -180,6 +187,7 @@
"CreateGitHubRelease",
"DeletePackages",
"DownloadLicenses",
"Foo",
"GenerateGlobalSolution",
"GeneratePublicApi",
"GenerateTools",
Expand Down
16 changes: 15 additions & 1 deletion .nuke/parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,19 @@
"Solution": "nuke-common.sln",
"SignPathOrganizationId": "0fdaf334-6910-41f4-83d2-e58e4cccb087",
"SignPathProjectSlug": "nuke",
"SignPathPolicySlug": "release-signing"
"SignPathPolicySlug": "release-signing",
"Data2": [
{
"FirstName": "foo",
"Nested": {
"FirstName": "bar"
}
},
{
"FirstName": "foo2",
"Nested": {
"FirstName": "bar2"
}
}
]
}
6 changes: 6 additions & 0 deletions .teamcity/settings.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ project {
value = "Release",
options = listOf("Debug" to "Debug", "Release" to "Release"),
display = ParameterDisplay.NORMAL)
text (
"env.Data2",
label = "Data2",
value = "Build+Data Build+Data",
allowEmpty = true,
display = ParameterDisplay.NORMAL)
checkbox (
"env.IgnoreFailedSources",
label = "IgnoreFailedSources",
Expand Down
14 changes: 14 additions & 0 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ partial class Build
/// - Microsoft VSCode https://nuke.build/vscode
public static int Main() => Execute<Build>(x => ((IPack)x).Pack);

public class Data
{
public string FirstName;
public Data Nested;
}

[Parameter] readonly Data[] Data2;

Target Foo => _ => _
.Executes(() =>
{
Console.WriteLine(Data2);
});

[CI] readonly TeamCity TeamCity;
[CI] readonly AzurePipelines AzurePipelines;
[CI] readonly AppVeyor AppVeyor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,86 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using Nuke.Common.CI;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
using Nuke.Common.ValueInjection;
using Serilog;

namespace Nuke.Common.Execution;

internal class ArgumentsFromParametersFileAttribute : BuildExtensionAttributeBase, IOnBuildCreated
[PublicAPI]
public class ArgumentsFromParametersFileAttribute : BuildExtensionAttributeBase, IOnBuildCreated
{
public void OnBuildCreated(IReadOnlyCollection<ExecutableTarget> executableTargets)
{
// TODO: probably remove
if (!Directory.Exists(Constants.GetNukeDirectory(Build.RootDirectory)))
if (!Constants.GetNukeDirectory(NukeBuild.RootDirectory).DirectoryExists())
return;


// IEnumerable<string> ConvertToArguments(string profile, string name, string[] values)
// {
// var member = parameterMembers.SingleOrDefault(x => ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(name));
// var scalarType = member?.GetMemberType().GetScalarType();
// var mustDecrypt = (member?.HasCustomAttribute<SecretAttribute>() ?? false) && !BuildServerConfigurationGeneration.IsActive;
// var decryptedValues = values.Select(x => mustDecrypt ? DecryptValue(profile, name, x) : x);
// var convertedValues = decryptedValues.Select(x => ConvertValue(scalarType, x)).ToList();
// Log.Verbose("Passing value for {Member} ({Value})",
// member?.GetDisplayName() ?? "<unresolved>",
// !mustDecrypt ? convertedValues.JoinComma() : "secret");
// return new[] { $"--{ParameterService.GetParameterDashedName(name)}" }.Concat(convertedValues);
// }
//

//
// // TODO: Abstract AbsolutePath/Solution/Project etc.
// string ConvertValue(Type scalarType, string value)
// => scalarType == typeof(AbsolutePath) ||
// typeof(Solution).IsAssignableFrom(scalarType) ||
// scalarType == typeof(Project)
// ? EnvironmentInfo.WorkingDirectory.GetUnixRelativePathTo(NukeBuild.RootDirectory / value)
// : value;

var parameterMembers = ValueInjectionUtility.GetParameterMembers(Build.GetType(), includeUnlisted: true);
var passwords = new Dictionary<string, string>();
var jobjectsAndProfiles = new[] { (File: Constants.GetDefaultParametersFile(NukeBuild.RootDirectory), Profile: Constants.DefaultProfileName) }
.Where(x => File.Exists(x.File))
.Concat(NukeBuild.LoadedLocalProfiles.Select(x => (File: Constants.GetParametersProfileFile(NukeBuild.RootDirectory, x), Profile: x)))
.ForEachLazy(x => Assert.FileExists(x.File))
.Select(x => (JObject: JObject.Parse(File.ReadAllText(x.File)), x.Profile))
.Reverse();

IEnumerable<string> ConvertToArguments(string profile, string name, string[] values)
{
var member = parameterMembers.SingleOrDefault(x => ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(name));
var scalarType = member?.GetMemberType().GetScalarType();
var mustDecrypt = (member?.HasCustomAttribute<SecretAttribute>() ?? false) && !BuildServerConfigurationGeneration.IsActive;
var decryptedValues = values.Select(x => mustDecrypt ? DecryptValue(profile, name, x) : x);
var convertedValues = decryptedValues.Select(x => ConvertValue(scalarType, x)).ToList();
Log.Verbose("Passing value for {Member} ({Value})",
member?.GetDisplayName() ?? "<unresolved>",
!mustDecrypt ? convertedValues.JoinComma() : "secret");
return new[] { $"--{ParameterService.GetParameterDashedName(name)}" }.Concat(convertedValues);
}
var passwords = new Dictionary<string, string>();

string DecryptValue(string profile, string name, string value)

Check notice on line 63 in source/Nuke.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Put local function after 'return' or 'continue'

Put local function 'DecryptValue' after 'return'
=> EncryptionUtility.Decrypt(
value,
passwords[profile] = passwords.GetValueOrDefault(profile) ?? CredentialStore.GetPassword(profile, Build.RootDirectory),
name);

// TODO: Abstract AbsolutePath/Solution/Project etc.
string ConvertValue(Type scalarType, string value)
=> scalarType.IsAssignableTo(typeof(IAbsolutePathHolder))
? PathConstruction.HasPathRoot(value)
? value
: EnvironmentInfo.WorkingDirectory.GetUnixRelativePathTo(Build.RootDirectory / value)
: value;

var arguments = GetParameters().SelectMany(x => ConvertToArguments(x.Profile, x.Name, x.Values)).ToArray();
ParameterService.Instance.ArgumentsFromFilesService = new ArgumentParser(arguments);
}
ParameterService.Instance.ArgumentsFromFilesService = (parameter, destinationType) =>
{
var (property, profile) = jobjectsAndProfiles.Select(x => (Property: x.JObject.Property(parameter), x.Profile))

Check notice on line 71 in source/Nuke.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Replace with single call to FirstOrDefault(..)

Replace with single call to FirstOrDefault(..)
.Where(x => x.Property != null)
.FirstOrDefault();
if (property == null)
return null;
private IEnumerable<(string Profile, string Name, string[] Values)> GetParameters()
{
IEnumerable<string> GetValues(JProperty property)
// TODO: if property is object || property is array && array contains objects => base64
=> property.Value is JArray array
? array.Values<string>()
: property.Values<string>();
var member = parameterMembers.SingleOrDefault(x => ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(parameter));
var scalarType = member?.GetMemberType().GetScalarType();
if (scalarType == typeof(AbsolutePath) ||
typeof(Solution).IsAssignableFrom(scalarType) ||
scalarType == typeof(Project))
return NukeBuild.RootDirectory / property.Value.ToObject<string>();
IEnumerable<(string Name, string[] Values)> Load(AbsolutePath file)
{
try
{
var jobject = JObject.Parse(file.ReadAllText());
// TODO: use NukeBuild instance to match members and walk through structure to replace secrets and absolute-paths
return jobject.Properties()
.Where(x => x.Name != "$schema")
.Select(x => (x.Name, GetValues(x).ToArray()));
}
catch (Exception exception)
{
throw new Exception($"Failed parsing parameters file '{file}'.", exception);
}
}
if ((member?.HasCustomAttribute<SecretAttribute>() ?? false) &&
!BuildServerConfigurationGeneration.IsActive)
return DecryptValue(profile, parameter, property.Value.ToObject<string>());
return new[] { (File: Constants.GetDefaultParametersFile(Build.RootDirectory), Profile: Constants.DefaultProfileName) }
.Where(x => x.File.Exists())
.Concat(Build.LoadedLocalProfiles.Select(x => (File: Constants.GetParametersProfileFile(Build.RootDirectory, x), Profile: x)))
.ForEachLazy(x => Assert.FileExists(x.File))
.SelectMany(x => Load(x.File), (x, r) => (x.Profile, r.Name, r.Values));
return property.Value.ToObject(destinationType);
};
}
}
6 changes: 4 additions & 2 deletions source/Nuke.Build/Execution/ParameterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ namespace Nuke.Common;

internal partial class ParameterService
{
internal ArgumentParser ArgumentsFromFilesService;
// internal ArgumentParser ArgumentsFromFilesService;
internal Func<string, Type, object> ArgumentsFromFilesService;
internal ArgumentParser ArgumentsFromCommitMessageService;

private readonly Func<ArgumentParser> _argumentParserProvider;
Expand Down Expand Up @@ -154,7 +155,8 @@ object TryFromEnvironmentVariables() =>

// TODO: nuke <target> ?
object TryFromProfileArguments() =>

Check notice on line 157 in source/Nuke.Build/Execution/ParameterService.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Put local function after 'return' or 'continue'

Put local function 'TryFromProfileArguments' after 'return'
ArgumentsFromFilesService?.GetNamedArgument(parameterName, destinationType, separator);
// ArgumentsFromFilesService?.GetNamedArgument(parameterName, destinationType, separator);
ArgumentsFromFilesService?.Invoke(parameterName, destinationType);

object TryFromCommitMessageArguments() =>

Check notice on line 161 in source/Nuke.Build/Execution/ParameterService.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Put local function after 'return' or 'continue'

Put local function 'TryFromCommitMessageArguments' after 'return'
ArgumentsFromCommitMessageService?.GetNamedArgument(parameterName, destinationType, separator);
Expand Down

0 comments on commit 0036415

Please sign in to comment.