Skip to content

Commit

Permalink
[Java.Interop.Tools.Maven] Initial commit. (#1179)
Browse files Browse the repository at this point in the history
Context: dotnet/android#8649

Add a new `Java.Interop.Tools.Maven.dll` assembly that contains
functionality for:

  - Retrieving artifacts from Maven
  - Parsing and understanding POM files

This assembly is used by dotnet/android#8649 and replaces
our previous usage of [`MavenNet.dll`][0].

`Java.Interop.Tools.Maven.dll` contains a binding of
[`maven-4.0.0.xsd`][1], generated via [dotnet-xscgen][2].
To update the binding:

	# Install the binding utility
	% dotnet tool install -g dotnet-xscgen

	# Grab the version of `maven*.xsd` you want to bind/update to
	% curl -o maven-4.0.0.xsd https://maven.apache.org/xsd/maven-4.0.0.xsd

	# `dotnet tool install` installs into e.g. `$HOME/.dotnet/tools`, which might not be in `$PATH`…
	# Run the `UpdateProjectSchema` target
	% PATH=$HOME/.dotnet/tools:$PATH dotnet build -t:UpdateProjectSchema \
	  src/Java.Interop.Tools.Maven/Java.Interop.Tools.Maven.csproj \
	  -p:MavenXsd=`pwd`/maven-4.0.0.xsd

[0]: https://www.nuget.org/packages/MavenNet
[1]: https://maven.apache.org/xsd/maven-4.0.0.xsd
[2]: https://www.nuget.org/packages/dotnet-xscgen
  • Loading branch information
jpobst authored Mar 11, 2024
1 parent 3436a30 commit 1c9c8c9
Show file tree
Hide file tree
Showing 27 changed files with 7,578 additions and 0 deletions.
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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{40B3CE2F-B8DE-45CD-A43A-0F1A89BDB803}"
Expand Down Expand Up @@ -326,6 +329,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 @@ -388,6 +399,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}
{C2AF6ACF-04F6-4B41-95EA-97A372C075F9} = {40B3CE2F-B8DE-45CD-A43A-0F1A89BDB803}
EndGlobalSection
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);
}

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

0 comments on commit 1c9c8c9

Please sign in to comment.