Skip to content

Commit

Permalink
Address feedback
Browse files Browse the repository at this point in the history
Context: #9613 (comment)

A lot of the previous output was nonsensical, especially around
`version`s, in large part because I was trying to associate
the `//*/@revision` attribute in the Feed to a version.
This sometimes works, e.g. for build-tools and platform-tools,
but would fail horribly for things like `<platform/>`, resulting
in JSON fragments such as:

	{
	  "desc": "Android SDK Platform 28",
	  "sdkPackage": {
	    "id": "platforms;android-*",
	    "version": "[1,1,1,2,2,2,2,3,3,3,3,3,3,5,6]",
	    "recommendedId": "platforms;android-28",
	    "recommendedVersion": "6"
	  },
	  "optional": "false"
	},

Make to fundamental architectural changes:

 1. Instead of trying to emit JSON for *everything* in the feed,
    only emit JSON for a minimal subset of known required+optional
    pieces, such as platform-tools (required) and the emulator
    and system images (optional).

 2. Drop the idea of "version", "recommendedId", and
    "recommendedVersion".

This results in a much smaller and more sensible:

	{
	  "microsoft.net.sdk.android": {
	    "workload": {
	      "alias": [
	        "android"
	      ],
	      "version": "35.0.100"
	    },
	    "jdk": {
	      "version": "[17.0,18.0)",
	      "recommendedVersion": "17.0.12"
	    },
	    "androidsdk": {
	      "packages": [
	        {
	          "desc": "Android SDK Build-Tools 35",
	          "sdkPackage": {
	            "id": "build-tools;35.0.0"
	          },
	          "optional": "false"
	        },
	        {
	          "desc": "Android SDK Command-line Tools",
	          "sdkPackage": {
	            "id": "cmdline-tools;13.0"
	          },
	          "optional": "false"
	        },
	        {
	          "desc": "Android Emulator",
	          "sdkPackage": {
	            "id": "emulator"
	          },
	          "optional": "true"
	        },
	        {
	          "desc": "NDK (Side by side) 27.1.12297006",
	          "sdkPackage": {
	            "id": "ndk;27.1.12297006"
	          },
	          "optional": "true"
	        },
	        {
	          "desc": "Android SDK Platform 35",
	          "sdkPackage": {
	            "id": "platforms;android-35"
	          },
	          "optional": "false"
	        },
	        {
	          "desc": "Android SDK Platform Baklava (Preview)",
	          "sdkPackage": {
	            "id": "platforms;android-Baklava"
	          },
	          "optional": "true"
	        },
	        {
	          "desc": "Android SDK Platform-Tools",
	          "sdkPackage": {
	            "id": "platform-tools"
	          },
	          "optional": "false"
	        },
	        {
	          "desc": "Google APIs ARM 64 v8a System Image",
	          "sdkPackage": {
	            "id": {
	              "win-x64": "system-images;android-35;google_apis;x86_64",
	              "mac-x64": "system-images;android-35;google_apis;x86_64",
	              "linux-x64": "system-images;android-35;google_apis;x86_64",
	              "mac-arm64": "system-images;android-35;google_apis;arm64-v8a",
	              "linux-arm64": "system-images;android-35;google_apis;arm64-v8a"
	            }
	          },
	          "optional": "true"
	        }
	      ]
	    }
	  }
	}
  • Loading branch information
jonpryor committed Dec 13, 2024
1 parent 26e4019 commit 52182a0
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 119 deletions.
16 changes: 14 additions & 2 deletions build-tools/create-packs/Microsoft.NET.Sdk.Android.proj
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,22 @@ about the various Microsoft.Android workloads.
<_Feed Condition=" Exists($(_AndroidPlatformSupportFeed)) ">$(_AndroidPlatformSupportFeed)</_Feed>
<_Feed Condition=" '$(_Feed)' == '' ">https://aka.ms/AndroidManifestFeed/d17-12</_Feed>
<_Project>$(MSBuildThisFileDirectory)/../../tools/workload-dependencies/workload-dependencies.csproj</_Project>
<WorkloadDependenciesPath Condition="'$(WorkloadDependenciesPath)' == ''">$(OutputPath)workload-manifest\workload-dependencies.json</WorkloadDependenciesPath>
<WorkloadDependenciesPath Condition="'$(WorkloadDependenciesPath)' == ''">$(OutputPath)workload-manifest\WorkloadDependencies.json</WorkloadDependenciesPath>
</PropertyGroup>
<ItemGroup>
<_WorkloadDeps Include="--project &quot;$(_Project)&quot;" />
<_WorkloadDeps Include="--" />
<_WorkloadDeps Include="&quot;--feed=$(_Feed)&quot;" />
<_WorkloadDeps Include="-o &quot;$(WorkloadDependenciesPath)&quot;" />
<_WorkloadDeps Include="--build-tools-version=$(XABuildToolsFolder)" />
<_WorkloadDeps Include="--cmdline-tools-version=$(CommandLineToolsFolder)" />
<_WorkloadDeps Include="--jdk-version=$(JavaSdkVersion)" />
<_WorkloadDeps Include="--platform-version=$(AndroidLatestStableApiLevel)" />
<_WorkloadDeps Include="--preview-platform-version=Baklava" /> <!-- #9594 -->
<_WorkloadDeps Include="--workload-version=$(WorkloadVersion)" />
</ItemGroup>

<Exec Command="dotnet run --project &quot;$(_Project)&quot; -- &quot;--feed=$(_Feed)&quot; --workload-version=$(WorkloadVersion) -o &quot;$(WorkloadDependenciesPath)&quot;" />
<Exec Command="dotnet run @(_WorkloadDeps, ' ')" />

<ItemGroup>
<_PackageFiles Include="$(WorkloadManifestJsonPath)" PackagePath="data" />
Expand Down
255 changes: 138 additions & 117 deletions tools/workload-dependencies/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,52 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

const string AppName = "release-json";

var RequiredPackages = new HashSet<string> {
"platform-tools",
"cmdline-tools",
"build-tool",
"platform",
};

var help = false;
var feed = (string?) null;
var output = (string?) null;
int verbosity = 0;
var workloadVersion = (string?) null;
const string AppName = "workload-dependencies";

var help = false;
var feed = (string?) null;
var output = (string?) null;
int Verbosity = 0;
var CmdlineToolsVersion = (string?) null;
var BuildToolsVersion = (string?) null;
var JdkVersion = (string?) null;
var NdkVersion = (string?) null;
var PreviewPlatformVersion = (string?) null;
var PlatformVersion = (string?) null;
var WorkloadVersion = (string?) null;

var options = new OptionSet {
"Generate `release.json` from Feed XML file.",
{ "i|feed=",
"The {PATH} to the Feed XML file.",
"The {PATH} to the Feed XML file to process.",
v => feed = v },
{ "o|output=",
"The {PATH} to the output release.json file.",
"The {FILE_PATH} for the JSON output.\nDefault is stdout.",
v => output = v },
{ "build-tools-version=",
"The Android SDK Build-Tools {VERSION} dotnet/android is built against.",
v => BuildToolsVersion = v },
{ "cmdline-tools-version=",
"The Android SDK cmdline-tools {VERSION} dotnet/android is built against.",
v => CmdlineToolsVersion = v },
{ "jdk-version=",
"The JDK {VERSION} dotnet/android is built against.",
v => JdkVersion = v },
{ "ndk-version=",
"The Android NDK {VERSION} dotnet/android is built against.",
v => NdkVersion = v },
{ "platform-version=",
"The stable Android SDK Platform {VERSION} dotnet/android binds.",
v => PlatformVersion = v },
{ "preview-platform-version=",
"The preview Android SDK Platform {VERSION} dotnet/android binds.",
v => PreviewPlatformVersion = v },
{ "workload-version=",
"The {VERSION} of the workload to generate.",
v => workloadVersion = v },
"The {VERSION} of the dotnet/android workload.",
v => WorkloadVersion = v },
{ "v|verbose:",
"Set internal message verbosity",
(int? v) => verbosity = v.HasValue ? v.Value : verbosity + 1 },
"Set internal message Verbosity",
(int? v) => Verbosity = v.HasValue ? v.Value : Verbosity + 1 },
{ "h|help",
"Show this help message and exit",
v => help = v != null },
Expand Down Expand Up @@ -61,25 +78,28 @@
}
catch (OptionException e) {
Console.Error.WriteLine ($"{AppName}: {e.Message}");
if (verbosity > 0) {
if (Verbosity > 0) {
Console.Error.WriteLine (e.ToString ());
}
return;
}
catch (System.Xml.XmlException e) {
Console.Error.WriteLine ($"{AppName}: invalid `--feed=PATH` value. {e.Message}");
if (verbosity > 0) {
if (Verbosity > 0) {
Console.Error.WriteLine (e.ToString ());
}
return;
}

var PackageCreators = new Dictionary<string, Func<XDocument, IEnumerable<JObject>>> {
["addon"] = CreateAddonPackageEntries,
["extra"] = CreateExtraPackageEntries,
["jdk"] = doc => Array.Empty<JObject> (),
["licenses"] = doc => Array.Empty<JObject> (),
["build-tool"] = doc => CreatePackageEntries (doc, "build-tool", BuildToolsVersion),
["emulator"] = doc => CreatePackageEntries (doc, "emulator", null, optional: true),
["cmdline-tools"] = doc => CreatePackageEntries (doc, "cmdline-tools", CmdlineToolsVersion),
["ndk"] = doc => CreatePackageEntries (doc, "ndk", NdkVersion, optional: true),
["platform-tools"] = doc => CreatePackageEntries (doc, "platform-tools", null),
["platform"] = CreatePlatformPackageEntries,
["system-image"] = CreateSystemImagePackageEntries,
// ndk
};

var release = new JObject {
Expand Down Expand Up @@ -127,16 +147,19 @@ JProperty CreateWorkloadProperty (XDocument doc)
{
var contents = new JObject (
new JProperty ("alias", new JArray ("android")));
if (!string.IsNullOrEmpty (workloadVersion))
contents.Add (new JProperty ("version", workloadVersion));
if (!string.IsNullOrEmpty (WorkloadVersion))
contents.Add (new JProperty ("version", WorkloadVersion));
return new JProperty ("workload", contents);
}

JProperty CreateJdkProperty (XDocument doc)
{
var latestRevision = GetLatestRevision (doc, "jdk");
var v = new Version (JdkVersion ?? "17.0");
var start = new Version (v.Major, v.Minor);
var end = new Version (v.Major+1, 0);
var latestRevision = JdkVersion ?? GetLatestRevision (doc, "jdk");
var contents = new JObject (
new JProperty ("version", "[17.0,18.0)"));
new JProperty ("version", $"[{start},{end})"));
if (!string.IsNullOrEmpty (latestRevision))
contents.Add (new JProperty ("recommendedVersion", latestRevision));
return new JProperty ("jdk", contents);
Expand All @@ -150,7 +173,8 @@ IEnumerable<XElement> GetSupportedElements (XDocument doc, string element)
return doc.Root.Elements (element)
.Where (e =>
string.Equals ("False", e.ReqAttr ("obsolete"), StringComparison.OrdinalIgnoreCase) &&
string.Equals ("False", e.ReqAttr ("preview"), StringComparison.OrdinalIgnoreCase));
string.Equals ("False", e.ReqAttr ("preview"), StringComparison.OrdinalIgnoreCase))
;
}

IEnumerable<(XElement Element, string Revision)> GetByRevisions (XDocument doc, string element)
Expand All @@ -166,54 +190,84 @@ IEnumerable<XElement> GetSupportedElements (XDocument doc, string element)
.Revision;
}

IEnumerable<JObject> CreateAddonPackageEntries (XDocument doc)
IEnumerable<JObject> CreatePackageEntries (XDocument doc, string element, string? revision, bool optional = false)
{
var allAddons = GetSupportedElements (doc, "addon").ToList ()
.OrderBy (e => e.ReqAttr ("path"));
var paths = allAddons
.Select (e => GetEntryId (e))
.Distinct ();
foreach (var path in paths) {
var addons = allAddons
.Where (e => GetEntryId (e) == path);
var version = string.Join (",", addons.Select (e => e.ReqAttr ("revision")));
var latest = addons.Last ();
var entry = new JObject {
new JProperty ("desc", latest.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", path),
new JProperty ("version", "[" + version + "]"),
new JProperty ("recommendedId", latest.ReqAttr ("path")),
new JProperty ("recommendedVersion", latest.ReqAttr ("revision")),
}),
new JProperty ("optional", "true"),
};
yield return entry;
var item = GetElementRevision (doc, element, revision);
if (item == null) {
yield break;
}
var entry = new JObject {
new JProperty ("desc", item.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", item.ReqAttr ("path")),
}),
new JProperty ("optional", optional.ToString ().ToLowerInvariant ()),
};
yield return entry;
}

IEnumerable<JObject> CreateExtraPackageEntries (XDocument doc)
XElement? GetElementRevision (XDocument doc, string element, string? revision)
{
var allExtras = GetByRevisions (doc, "extra").ToList ();
var paths = allExtras
.Select (e => e.Element.ReqAttr ("path"))
.Distinct ();
foreach (var path in paths) {
var extras = allExtras
.Where (e => e.Element.ReqAttr ("path") == path);
var version = string.Join (",", extras.Select (e => e.Revision));
var latest = extras.Last ();
var entry = new JObject {
new JProperty ("desc", latest.Element.ReqAttr ("description")),
Version? reqVersion = revision != null ? new Version (revision) : null;;
Version? maxVersion = null;
XElement? entry = null;
foreach (var e in GetSupportedElements (doc, element)) {
var r = e.ReqAttr ("revision");
var rv = new Version (r);
if (rv == reqVersion) {
return e;
}
if (rv > maxVersion) {
maxVersion = rv;
entry = e;
}
}
return entry;
}

IEnumerable<JObject> CreatePlatformPackageEntries (XDocument doc)
{
string? reqVersion = PlatformVersion != null
? $"platforms;android-{PlatformVersion}"
: null;
string? maxVersion = null;
XElement? entry = null;
foreach (var e in GetSupportedElements (doc, "platform")) {
var path = e.ReqAttr ("path");
if (path == reqVersion) {
entry = e;
break;
}
if (string.Compare (path, maxVersion) > 0) {
maxVersion = path;
entry = e;
}
}
if (entry == null) {
yield break;
}
var platform = new JObject {
new JProperty ("desc", entry.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", entry.ReqAttr ("path")),
}),
new JProperty ("optional", "false"),
};
yield return platform;

string? previewPath = PreviewPlatformVersion != null
? $"platforms;android-{PreviewPlatformVersion}"
: null;
XElement? previewEntry = doc.Elements ("platform")
.FirstOrDefault (e => e.ReqAttr ("path") == previewPath);
if (PreviewPlatformVersion != null) {
yield return new JObject {
new JProperty ("desc", previewEntry?.ReqAttr ("description") ?? $"Android SDK Platform {PreviewPlatformVersion} (Preview)"),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", path),
new JProperty ("version", "[" + version + "]"),
new JProperty ("recommendedId", latest.Element.ReqAttr ("path")),
new JProperty ("recommendedVersion", latest.Revision),
new JProperty ("id", previewPath),
}),
new JProperty ("optional", "true"),
new JProperty ("optional", "true"),
};
yield return entry;
}
}

Expand All @@ -225,8 +279,9 @@ IEnumerable<JObject> CreateSystemImagePackageEntries (XDocument doc)
let parts = path.Split (';')
where parts.Length > 3
let targetApi = parts [1]
let apiImpl = parts [2] // google_apis or default
let apiImpl = parts [2] // google_apis or default
let targetAbi = parts [3]
where apiImpl == "google_apis" // prefer google_apis
select new {
Element = image,
Path = path,
Expand All @@ -245,21 +300,21 @@ where parts.Length > 3

var id = new JObject ();
if (x64 != null) {
id.Add (new JProperty ("win-x64", x64.Path));
id.Add (new JProperty ("mac-x64", x64.Path));
id.Add (new JProperty ("linux-x64", x64.Path));
id.Add (new JProperty ("win-x64", x64.Path));
id.Add (new JProperty ("mac-x64", x64.Path));
id.Add (new JProperty ("linux-x64", x64.Path));
}
if (arm64 != null) {
id.Add (new JProperty ("mac-arm64", arm64.Path));
id.Add (new JProperty ("linux-arm64", arm64.Path));
id.Add (new JProperty ("mac-arm64", arm64.Path));
id.Add (new JProperty ("linux-arm64", arm64.Path));
}

var entry = new JObject {
new JProperty ("desc", maxImages.First ().Element.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("desc", maxImages.First ().Element.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", id),
}),
new JProperty ("optional", "true"),
new JProperty ("optional", "true"),
};
yield return entry;
}
Expand All @@ -272,50 +327,16 @@ JArray CreatePackagesArray (XDocument doc)
.Distinct ()
.OrderBy (e => e);
foreach (var name in names) {
if (PackageCreators.TryGetValue (name, out var creator)) {
foreach (var e in creator (doc)) {
packages.Add (e);
}
if (!PackageCreators.TryGetValue (name, out var creator)) {
continue;
}
var items = GetSupportedElements (doc, name)
.OrderBy (e => e.ReqAttr ("path"));
if (!items.Any ()) {
continue;
foreach (var e in creator (doc)) {
packages.Add (e);
}
var version = string.Join (",", items.Select (e => e.ReqAttr ("revision")));
var latest = items.Last ();

var entry = new JObject {
new JProperty ("desc", latest.ReqAttr ("description")),
new JProperty ("sdkPackage", new JObject {
new JProperty ("id", GetEntryId (latest)),
new JProperty ("version", "[" + version + "]"),
new JProperty ("recommendedId", latest.ReqAttr ("path")),
new JProperty ("recommendedVersion", latest.ReqAttr ("revision")),
}),
new JProperty ("optional", (!RequiredPackages.Contains (name)).ToString ().ToLowerInvariant ()),
};

packages.Add (entry);
}
return packages;
}

string GetEntryId (XElement entry)
{
var path = entry.ReqAttr ("path");
var semic = path.LastIndexOf (';');
if (semic < 0) {
return path;
}
var hyphen = path.LastIndexOf ('-');
if (hyphen < 0) {
return path.Substring (0, semic+1) + "*";
}
return path.Substring (0, Math.Max (hyphen, semic)+1) + "*";
}

static class Extensions
{
public static string ReqAttr (this XElement e, string attribute)
Expand Down

0 comments on commit 52182a0

Please sign in to comment.