Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Java.Interop.Tools.Maven] Initial commit. #1179

Merged
merged 8 commits into from
Mar 11, 2024
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
13 changes: 13 additions & 0 deletions Java.Interop.sln
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressions-Tests", "tests\Java.Interop.Tools.Expressions-Tests\Java.Interop.Tools.Expressions-Tests.csproj", "{211BAA88-66B1-41B2-88B2-530DBD8DF702}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Maven", "src\Java.Interop.Tools.Maven\Java.Interop.Tools.Maven.csproj", "{DA458F90-218B-4FE3-995F-AF4B27895FA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.Maven-Tests", "tests\Java.Interop.Tools.Maven-Tests\Java.Interop.Tools.Maven-Tests.csproj", "{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello-NativeAOTFromJNI", "samples\Hello-NativeAOTFromJNI\Hello-NativeAOTFromJNI.csproj", "{8DB3842B-73D7-491C-96F9-EBC863E2C917}"
EndProject
Global
Expand Down Expand Up @@ -322,6 +325,14 @@ Global
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.ActiveCfg = Release|Any CPU
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.Build.0 = Release|Any CPU
{DA458F90-218B-4FE3-995F-AF4B27895FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA458F90-218B-4FE3-995F-AF4B27895FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA458F90-218B-4FE3-995F-AF4B27895FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA458F90-218B-4FE3-995F-AF4B27895FA2}.Release|Any CPU.Build.0 = Release|Any CPU
{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D}.Release|Any CPU.Build.0 = Release|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -380,6 +391,8 @@ Global
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{211BAA88-66B1-41B2-88B2-530DBD8DF702} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{DA458F90-218B-4FE3-995F-AF4B27895FA2} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{6BC04C7F-949E-4F93-BF1F-E3B1DF0B888D} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{8DB3842B-73D7-491C-96F9-EBC863E2C917} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
8 changes: 8 additions & 0 deletions build-tools/automation/templates/core-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ steps:
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Xamarin.SourceWriter-Tests.dll
continueOnError: true

- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop.Tools.Maven'
inputs:
command: test
testRunTitle: Java.Interop.Tools.Maven (${{ parameters.platformName }})
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop.Tools.Maven-Tests.dll
continueOnError: true

- task: DotNetCoreCLI@2
displayName: 'Tests: Java.Interop'
condition: or(eq('${{ parameters.runNativeDotnetTests }}', 'true'), eq('${{ parameters.runNativeTests }}', 'true'))
Expand Down
23 changes: 23 additions & 0 deletions src/Java.Interop.Tools.Maven/DefaultProjectResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using Java.Interop.Tools.Maven.Models;

namespace Java.Interop.Tools.Maven;

public class DefaultProjectResolver : IProjectResolver
{
readonly Dictionary<string, Project> poms = new ();

public void Register (Project project)
{
poms.Add (project.VersionedArtifactString, project);
}

public virtual Project Resolve (Artifact artifact)
{
if (poms.TryGetValue (artifact.VersionedArtifactString, out var project))
return project;

throw new InvalidOperationException ($"No POM registered for {artifact}");
}
}
45 changes: 45 additions & 0 deletions src/Java.Interop.Tools.Maven/Extensions/PropertyStack.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml.Linq;
using Java.Interop.Tools.Maven.Models;

namespace Java.Interop.Tools.Maven.Extensions;

class PropertyStack
{
// Why go to this trouble?
// A property can be specified in both a child POM and its parent POM.
// Even if the property is being consumed in the parent POM, the property in
// the child POM takes precedence.
readonly List<List<KeyValuePair<string, string>>> stack = new ();

public void Push (ModelProperties? properties)
{
// We add a new list to the stack, even if it's empty, so that the Pop works later
var list = new List<KeyValuePair<string, string>> ();

if (properties?.Any is Collection<XElement> props)
foreach (var prop in props)
list.Add (new KeyValuePair<string, string> (prop.Name.LocalName, prop.Value));

stack.Add (list);
}

public void Pop ()
{
stack.RemoveAt (stack.Count - 1);
}

public string Apply (string value)
{
if (stack.Count == 0 || !value.Contains ("${"))
return value;

foreach (var property_set in stack) {
foreach (var prop in property_set)
value = value.Replace ($"${{{prop.Key}}}", prop.Value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives me some concern, if only because this could produce lots of garbage, all depending on stack.

I think I'd prefer a "optimization check" + use of StringBuilder.Replace():

if (stack.Count == 0 || !value.Contains("${") {
    return value;
}
var s = new StringBuilder (value);
foreach (var property_set in stack) {
    foreach (var prop in property_set) {
        s.Replace ($"${{{prop.Key}}}", prop.Value);
    }
}
return s.ToString ();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the optimization check. 👍

From my research looking at POM files, this code will rarely be hit with the optimization check, and when it is hit it will generally be for small numbers of properties. The general guidance seems to be that string.Replace is faster than StringBuilder.Replace for small numbers of replaces, despite the extra garbage, as it avoids the overhead of creating the StringBuilder and converting it ToString at the end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall reading the guidance about "string.Replace is faster … for small numbers of replace". Would be interesting to read more.

}

return value;
}
}
84 changes: 84 additions & 0 deletions src/Java.Interop.Tools.Maven/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Diagnostics.CodeAnalysis;

namespace Java.Interop.Tools.Maven.Extensions;

static class StringExtensions
{
/// <summary>
/// Shortcut for !string.IsNullOrWhiteSpace (s)
/// </summary>
public static bool HasValue ([NotNullWhen (true)] this string? s) => !string.IsNullOrWhiteSpace (s);

/// <summary>
/// Shortcut for s ?? string.Empty
/// </summary>
public static string OrEmpty (this string? str) => str ?? string.Empty;

/// <summary>
/// Removes the first subset of a delimited string. ("127.0.0.1" -> "0.0.1")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? ChompFirst (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.IndexOf (separator);

if (index < 0)
return string.Empty;

return s.Substring (index + 1);
}

/// <summary>
/// Removes the final subset of a delimited string. ("127.0.0.1" -> "127.0.0")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? ChompLast (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.LastIndexOf (separator);

if (index < 0)
return string.Empty;

return s.Substring (0, index);
}

/// <summary>
/// Returns the first subset of a delimited string. ("127.0.0.1" -> "127")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? FirstSubset (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.IndexOf (separator);

if (index < 0)
return s;

return s.Substring (0, index);
}

/// <summary>
/// Returns the final subset of a delimited string. ("127.0.0.1" -> "1")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? LastSubset (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.LastIndexOf (separator);

if (index < 0)
return s;

return s.Substring (index + 1);
}
}
8 changes: 8 additions & 0 deletions src/Java.Interop.Tools.Maven/IProjectResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Java.Interop.Tools.Maven.Models;

namespace Java.Interop.Tools.Maven;

public interface IProjectResolver
{
Project Resolve (Artifact artifact);
}
28 changes: 28 additions & 0 deletions src/Java.Interop.Tools.Maven/Java.Interop.Tools.Maven.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<DefineConstants>INTERNAL_NULLABLE_ATTRIBUTES</DefineConstants>
<Nullable>enable</Nullable>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<Import Project="..\..\TargetFrameworkDependentValues.props" />

<PropertyGroup>
<OutputPath>$(UtilityOutputFullPath)</OutputPath>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\utils\NullableAttributes.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>

<Import Project="Java.Interop.Tools.Maven.targets" />
</Project>
42 changes: 42 additions & 0 deletions src/Java.Interop.Tools.Maven/Java.Interop.Tools.Maven.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<Project>
<UsingTask AssemblyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\Java.Interop.BootstrapTasks.dll" TaskName="Java.Interop.BootstrapTasks.ReplaceFileContents" />

<!-- This target generates the Project model from the Maven POM schema.
Prerequisites:
- Install dotnet tool: 'dotnet tool install -g dotnet-xscgen'
- Download new schema to this directory (eg: https://maven.apache.org/xsd/maven-4.0.0.xsd)
Run 'dotnet build -t:UpdateProjectSchema -p:MavenXsd=maven-4.0.0.xsd' -->
<Target Name="UpdateProjectSchema">

<PropertyGroup>
<MavenXsd Condition=" '$(MavenXsd)' == '' ">maven-4.0.0.xsd</MavenXsd>
</PropertyGroup>

<ItemGroup>
<_XscgenOpt Include="$(MavenXsd)" />
<_XscgenOpt Include="--namespace http://maven.apache.org/POM/4.0.0=Java.Interop.Tools.Maven.Models" />
<_XscgenOpt Include="--typeNameSubstitute T:Model=Project" />
<_XscgenOpt Include="--nullable" />
<_XscgenOpt Include="--pcl" />
<_XscgenOpt Include="--netCore" />
<_XscgenOpt Include="--nullableReferenceAttributes" />
<_XscgenOpt Include="-o &quot;Models&quot;" />
</ItemGroup>

<Exec Command="xscgen @(_XscgenOpt, ' ')" />

<!-- Remove the Namespace from eg: [System.Xml.Serialization.XmlRootAttribute("project", Namespace="http://maven.apache.org/POM/4.0.0")]
This allows us to import POM files that do not specify an XML namespace (xmlns). -->
<ItemGroup>
<Replacements Include=', Namespace="http://maven.apache.org/POM/4.0.0"' Replacement="" />
</ItemGroup>

<ReplaceFileContents
TemplateFile="Models/Java.Interop.Tools.Maven.Models.cs"
OutputFile="Models/Project.cs"
Replacements="@(Replacements)"
/>

<Delete Files="Models/Java.Interop.Tools.Maven.Models.cs" />
</Target>
</Project>
49 changes: 49 additions & 0 deletions src/Java.Interop.Tools.Maven/Models/Artifact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Java.Interop.Tools.Maven.Models;

public class Artifact
{
public string GroupId { get; }

public string Id { get; }

public string Version { get; }

public string ArtifactString => $"{GroupId}:{Id}";

// Format should match Project.ArtifactString for comparisons.
public string VersionedArtifactString => $"{GroupId}:{Id}:{Version}";

public Artifact (string groupId, string artifactId, string version)
{
Id = artifactId;
GroupId = groupId;
Version = version;
}

public static Artifact Parse (string value)
{
if (TryParse (value, out var artifact))
return artifact;

throw new ArgumentException ($"Invalid artifact format: {value}");
}

public static bool TryParse (string value, [NotNullWhen (true)]out Artifact? artifact)
{
artifact = null;

var parts = value.Split (':');

if (parts.Length != 3)
return false;

artifact = new Artifact (parts [0], parts [1], parts [2]);

return true;
}

public override string ToString () => VersionedArtifactString;
}
9 changes: 9 additions & 0 deletions src/Java.Interop.Tools.Maven/Models/Dependency.Partial.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Java.Interop.Tools.Maven.Extensions;

namespace Java.Interop.Tools.Maven.Models;

public partial class Dependency
{
public Artifact ToArtifact ()
=> new Artifact (GroupId.OrEmpty (), ArtifactId.OrEmpty (), Version.OrEmpty ());
}
Loading