Skip to content

Commit e70ae00

Browse files
authored
[Xamarin.Android.Build.Tasks] %(JavaArtifact) is a list (#9112)
Fixes: #9013 Context: 4696bf3 Today, our Java Dependency Verification feature (4696bf3) is built around our recommended practice of one binding library per project. This is reflected in the fact that `%(JavaArtifact)` and `%(JavaVersion)` only support a single library: <PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.7.10" JavaArtifact="org.jetbrains.kotlin:kotlin-stdlib" JavaVersion="1.7.10" /> However a user may choose to place multiple Java libraries into a single package (or project), and we have no way to express that. Instead of using `%(JavaArtifact)`/`%(JavaVersion)` to specify a Java dependency, we will only use `%(JavaArtifact)` and we will update the specification to include the artifact version, e.g. `org.jetbrains.kotlin:kotlin-stdlib:1.7.10`. Additionally, this attribute now allows multiple Java artifacts to be specified by separating them with a comma, semicolon, or newline. This change affects `@(PackageReference)`, `@(ProjectReference)`, `@(AndroidLibrary)`, and `@(AndroidIgnoredJavaDependency)`. Examples: <PackageReference Include="Xamarin.Kotlin.StdLib" Version="1.7.10" JavaArtifact="org.jetbrains.kotlin:kotlin-stdlib:1.7.10" /> <ProjectReference Include="..\My.Other.Binding\My.Other.Binding.csproj" JavaArtifact="my.other.binding:mylib:1.0.0,my.other.binding:helperlib:1.0.0" /> <AndroidLibrary Include="mydependency.jar" JavaArtifact="my.library:dependency-library:1.0.0" /> <AndroidIgnoredJavaDependency Include="com.google.errorprone:error_prone_annotations:2.15.0" /> Existing support for looking at [NuGet package tags][0] to automatically find Java libraries contained within has also been updated to support multiple `artifact` tags. Note that separate tags are expected, not a single tag with multiple artifacts specified: <PropertyGroup> <!-- Correct --> <PackageTags> artifact=my.other.binding:mylib:1.0.0 artifact=my.other.binding:helperlib:1.0.0 </PackageTags> <!-- Incorrect --> <PackageTags> artifact=my.other.binding:mylib:1.0.0,my.other.binding:helperlib:1.0.0 </PackageTags> </PropertyGroup> [0]: https://learn.microsoft.com/en-us/nuget/reference/nuspec#tags
1 parent c978f35 commit e70ae00

File tree

8 files changed

+159
-104
lines changed

8 files changed

+159
-104
lines changed

src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Xamarin.Android.Build.Tasks/Properties/Resources.resx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,4 +1067,9 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
10671067
{2} - shared library file name
10681068
</comment>
10691069
</data>
1070+
<data name="XA4249" xml:space="preserve">
1071+
<value>Maven artifact specification '{0}' is invalid. The correct format is 'group_id:artifact_id:version'.</value>
1072+
<comment>The following are literal names and should not be translated: Maven, group_id, artifact_id
1073+
{0} - A Maven artifact specification</comment>
1074+
</data>
10701075
</root>

src/Xamarin.Android.Build.Tasks/Tasks/JavaDependencyVerification.cs

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,12 @@ public void AddAndroidLibraries (ITaskItem []? tasks)
149149
{
150150
foreach (var task in tasks.OrEmpty ()) {
151151
var id = task.GetMetadataOrDefault ("JavaArtifact", string.Empty);
152-
var version = task.GetMetadataOrDefault ("JavaVersion", string.Empty);
153152

154-
// TODO: Should raise an error if JavaArtifact is specified but JavaVersion is not
155-
if (!id.HasValue () || !version.HasValue ())
156-
continue;
157-
158-
if (version != null && MavenExtensions.TryParseArtifactWithVersion (id, version, log, out var art)) {
159-
log.LogMessage ("Found Java dependency '{0}:{1}' version '{2}' from AndroidLibrary '{3}'", art.GroupId, art.Id, art.Version, task.ItemSpec);
160-
artifacts.Add (art.ArtifactString, art);
153+
if (MavenExtensions.TryParseArtifacts (id, log, out var parsed)) {
154+
foreach (var art in parsed) {
155+
log.LogMessage ("Found Java dependency '{0}:{1}' version '{2}' from AndroidLibrary '{3}'", art.GroupId, art.Id, art.Version, task.ItemSpec);
156+
artifacts.Add (art.ArtifactString, art);
157+
}
161158
}
162159
}
163160
}
@@ -166,20 +163,22 @@ public void AddPackageReferences (ITaskItem []? tasks)
166163
{
167164
foreach (var task in tasks.OrEmpty ()) {
168165

169-
// See if JavaArtifact/JavaVersion overrides were used
170-
if (task.TryParseJavaArtifactAndJavaVersion ("PackageReference", log, out var explicit_artifact, out var attributes_specified)) {
171-
artifacts.Add (explicit_artifact.ArtifactString, explicit_artifact);
166+
// See if JavaArtifact override was used
167+
if (task.TryParseJavaArtifacts ("PackageReference", log, out var explicit_artifacts, out var attributes_specified)) {
168+
foreach (var explicit_artifact in explicit_artifacts)
169+
artifacts.Add (explicit_artifact.ArtifactString, explicit_artifact);
170+
172171
continue;
173172
}
174173

175-
// If user tried to specify JavaArtifact or JavaVersion, but did it incorrectly, we do not perform any fallback
174+
// If user tried to specify JavaArtifact, but did it incorrectly, we do not perform any fallback
176175
if (attributes_specified)
177176
continue;
178177

179178
// Try parsing the NuGet metadata for Java version information instead
180-
var artifact = finder?.GetJavaInformation (task.ItemSpec, task.GetMetadataOrDefault ("Version", string.Empty), log);
179+
var metadata_artifacts = finder?.GetArtifactsInNugetPackage (task.ItemSpec, task.GetMetadataOrDefault ("Version", string.Empty), log);
181180

182-
if (artifact != null) {
181+
foreach (var artifact in metadata_artifacts ?? []) {
183182
log.LogMessage ("Found Java dependency '{0}:{1}' version '{2}' from PackageReference '{3}'", artifact.GroupId, artifact.Id, artifact.Version, task.ItemSpec);
184183
artifacts.Add (artifact.ArtifactString, artifact);
185184

@@ -193,13 +192,15 @@ public void AddPackageReferences (ITaskItem []? tasks)
193192
public void AddProjectReferences (ITaskItem []? tasks)
194193
{
195194
foreach (var task in tasks.OrEmpty ()) {
196-
// See if JavaArtifact/JavaVersion overrides were used
197-
if (task.TryParseJavaArtifactAndJavaVersion ("ProjectReference", log, out var explicit_artifact, out var attributes_specified)) {
198-
artifacts.Add (explicit_artifact.ArtifactString, explicit_artifact);
195+
// See if JavaArtifact override was used
196+
if (task.TryParseJavaArtifacts ("ProjectReference", log, out var explicit_artifacts, out var attributes_specified)) {
197+
foreach (var explicit_artifact in explicit_artifacts)
198+
artifacts.Add (explicit_artifact.ArtifactString, explicit_artifact);
199+
199200
continue;
200201
}
201202

202-
// If user tried to specify JavaArtifact or JavaVersion, but did it incorrectly, we do not perform any fallback
203+
// If user tried to specify JavaArtifact, but did it incorrectly, we do not perform any fallback
203204
if (attributes_specified)
204205
continue;
205206

@@ -212,14 +213,12 @@ public void AddIgnoredDependencies (ITaskItem []? tasks)
212213
{
213214
foreach (var task in tasks.OrEmpty ()) {
214215
var id = task.ItemSpec;
215-
var version = task.GetRequiredMetadata ("AndroidIgnoredJavaDependency", "Version", log);
216216

217-
if (version is null)
218-
continue;
219-
220-
if (version != null && MavenExtensions.TryParseArtifactWithVersion (id, version, log, out var art)) {
221-
log.LogMessage ("Ignoring Java dependency '{0}:{1}' version '{2}'", art.GroupId, art.Id, art.Version);
222-
artifacts.Add (art.ArtifactString, art);
217+
if (MavenExtensions.TryParseArtifacts (id, log, out var parsed)) {
218+
foreach (var art in parsed) {
219+
log.LogMessage ("Ignoring Java dependency '{0}'", art.VersionedArtifactString);
220+
artifacts.Add (art.ArtifactString, art);
221+
}
223222
}
224223
}
225224
}
@@ -263,7 +262,7 @@ public MSBuildLoggingPomResolver (TaskLoggingHelper logger)
263262

264263
Artifact? RegisterFromTaskItem (ITaskItem item, string itemName, string filename)
265264
{
266-
item.TryParseJavaArtifactAndJavaVersion (itemName, logger, out var artifact, out var _);
265+
item.TryParseJavaArtifact (itemName, logger, out var artifact, out var _);
267266

268267
if (!File.Exists (filename)) {
269268
logger.LogCodedError ("XA4245", Properties.Resources.XA4245, filename);
@@ -349,9 +348,7 @@ public class Package
349348
public class NuGetPackageVersionFinder
350349
{
351350
readonly LockFile lock_file;
352-
readonly Dictionary<string, Artifact> cache = new Dictionary<string, Artifact> ();
353-
readonly Regex tag = new Regex ("artifact_versioned=(?<GroupId>.+)?:(?<ArtifactId>.+?):(?<Version>.+)\\s?", RegexOptions.Compiled);
354-
readonly Regex tag2 = new Regex ("artifact=(?<GroupId>.+)?:(?<ArtifactId>.+?):(?<Version>.+)\\s?", RegexOptions.Compiled);
351+
readonly static Regex tag = new Regex (@"artifact(?:_versioned)?=(?<GroupId>[^:\s;,]+):(?<ArtifactId>[^:\s;,]+):(?<Version>[^:\s;,]+)", RegexOptions.Compiled, TimeSpan.FromSeconds (5));
355352

356353
NuGetPackageVersionFinder (LockFile lockFile)
357354
{
@@ -370,59 +367,62 @@ public class NuGetPackageVersionFinder
370367
}
371368
}
372369

373-
public Artifact? GetJavaInformation (string library, string version, TaskLoggingHelper log)
370+
public List<Artifact> GetArtifactsInNugetPackage (string library, string version, TaskLoggingHelper log)
374371
{
375-
// Check if we already have this one in the cache
376-
var dictionary_key = $"{library.ToLowerInvariant ()}:{version}";
377-
378-
if (cache.TryGetValue (dictionary_key, out var artifact))
379-
return artifact;
372+
var artifacts = new List<Artifact> ();
380373

381374
// Find the LockFileLibrary
382375
var nuget = lock_file.GetLibrary (library, new NuGet.Versioning.NuGetVersion (version));
383376

384377
if (nuget is null) {
385378
log.LogCodedError ("XA4248", Properties.Resources.XA4248, library, version);
386-
return null;
379+
return artifacts;
387380
}
388381

389382
foreach (var path in lock_file.PackageFolders)
390-
if (CheckFilePath (path.Path, nuget) is Artifact art) {
391-
cache.Add (dictionary_key, art);
392-
return art;
393-
}
383+
AddArtifactsFromNuspec (artifacts, path.Path, nuget);
394384

395-
return null;
385+
return artifacts;
396386
}
397387

398-
Artifact? CheckFilePath (string nugetPackagePath, LockFileLibrary package)
388+
void AddArtifactsFromNuspec (List<Artifact> artifacts, string nugetPackagePath, LockFileLibrary package)
399389
{
400390
// Check NuGet tags
401391
var nuspec = package.Files.FirstOrDefault (f => f.EndsWith (".nuspec", StringComparison.OrdinalIgnoreCase));
402392

403393
if (nuspec is null)
404-
return null;
394+
return;
405395

406396
nuspec = Path.Combine (nugetPackagePath, package.Path, nuspec);
407397

408398
if (!File.Exists (nuspec))
409-
return null;
399+
return;
410400

411401
var reader = new NuGet.Packaging.NuspecReader (nuspec);
412402
var tags = reader.GetTags ();
413403

414-
// Try the first tag format
415-
var match = tag.Match (tags);
404+
AddArtifactsFromNuspecTags (artifacts, tags);
416405

417-
// Try the second tag format
418-
if (!match.Success)
419-
match = tag2.Match (tags);
406+
// TODO: Define a well-known file that can be included in the package like "java-package.txt"
407+
}
420408

421-
if (!match.Success)
422-
return null;
409+
public static void AddArtifactsFromNuspecTags (List<Artifact> artifacts, string tags)
410+
{
411+
// Try the first tag format
412+
try {
413+
var matches = tag.Matches (tags);
423414

424-
// TODO: Define a well-known file that can be included in the package like "java-package.txt"
415+
if (matches is null || matches.Count == 0)
416+
return;
417+
418+
foreach (Match match in matches) {
419+
var artifact = new Artifact (match.Groups ["GroupId"].Value, match.Groups ["ArtifactId"].Value, match.Groups ["Version"].Value);
425420

426-
return new Artifact (match.Groups ["GroupId"].Value, match.Groups ["ArtifactId"].Value, match.Groups ["Version"].Value);
421+
if (!artifacts.Any (a => a.VersionedArtifactString == artifact.VersionedArtifactString))
422+
artifacts.Add (artifact);
423+
}
424+
} catch (RegexMatchTimeoutException) {
425+
return;
426+
}
427427
}
428428
}

src/Xamarin.Android.Build.Tasks/Tasks/MavenDownload.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
9797

9898
var result = new TaskItem (artifact_file);
9999

100-
result.SetMetadata ("JavaArtifact", $"{artifact.GroupId}:{artifact.Id}");
101-
result.SetMetadata ("JavaVersion", artifact.Version);
100+
result.SetMetadata ("JavaArtifact", artifact.VersionedArtifactString);
102101

103102
// Allow user to opt out of dependency verification
104103
if (string.Compare (item.GetMetadataOrDefault ("VerifyDependencies", "true"), "false", true) == 0)
@@ -121,8 +120,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
121120
var pom_item = new TaskItem (kv.Value);
122121
var pom_artifact = Artifact.Parse (kv.Key);
123122

124-
pom_item.SetMetadata ("JavaArtifact", $"{pom_artifact.GroupId}:{pom_artifact.Id}");
125-
pom_item.SetMetadata ("JavaVersion", pom_artifact.Version);
123+
pom_item.SetMetadata ("JavaArtifact", pom_artifact.VersionedArtifactString);
126124

127125
additionalPoms.Add (pom_item);
128126

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -888,14 +888,9 @@ public void AndroidMavenLibrary_AllDependenciesAreVerified ()
888888
var collection = new XamarinAndroidBindingProject ();
889889

890890
// Dependencies ignored by <AndroidIgnoredJavaDependency>
891-
var concurrent = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.concurrent:concurrent-futures");
892-
concurrent.Metadata.Add ("Version", "1.1.0");
893-
894-
var lifecycle = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.lifecycle:lifecycle-runtime");
895-
lifecycle.Metadata.Add ("Version", "2.6.2");
896-
897-
var parcelable = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.versionedparcelable:versionedparcelable");
898-
parcelable.Metadata.Add ("Version", "1.2.0");
891+
var concurrent = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.concurrent:concurrent-futures:1.1.0");
892+
var lifecycle = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.lifecycle:lifecycle-runtime:2.6.2");
893+
var parcelable = new BuildItem ("AndroidIgnoredJavaDependency", "androidx.versionedparcelable:versionedparcelable:1.2.0");
899894

900895
var proj = new XamarinAndroidBindingProject {
901896
Jars = { item, annotations_experimental_androidlib },
@@ -905,8 +900,7 @@ public void AndroidMavenLibrary_AllDependenciesAreVerified ()
905900

906901
proj.AddReference (collection);
907902
var collection_proj = proj.References.First ();
908-
collection_proj.Metadata.Add ("JavaArtifact", "androidx.collection:collection");
909-
collection_proj.Metadata.Add ("JavaVersion", "1.3.0");
903+
collection_proj.Metadata.Add ("JavaArtifact", "androidx.collection:collection:1.3.0");
910904

911905
using var a = CreateDllBuilder ();
912906
using var b = CreateDllBuilder ();

0 commit comments

Comments
 (0)