Skip to content

Commit 6c1525c

Browse files
committed
[build] Generate workload-dependencies.json
Context: [`Releases.json` loop][0] Context: dotnet/macios#21779 (comment) There is a desire to have the .NET Workloads have a machine readable description of what dependencies they require in order to run, in order to facilitate tooling that would check for these dependencies. Add `tools/workload-dependencies`, a new tool which parses a "Xamarin Manifest" to generate `workload-dependencies.json`. The "canonical" location for the "Xamarin Manifest" is within `external/android-platform-support/Feeds/AndroidManifestFeed_d17.12.xml`; failing that, <https://aka.ms/AndroidManifestFeed/d17-12> can be used. Output of the tool is a JSON document specifying ther required JDK and Android SDK packages which the .NET for Android workload requires: { "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;*", "version": "[30.0.2,30.0.3,31.0.0,32.0.0,33.0.0,33.0.1,33.0.2,33.0.3,34.0.0,35.0.0]", "recommendedId": "build-tools;35.0.0", "recommendedVersion": "35.0.0" }, "optional": "false" }, { "desc": "Android SDK Command-line Tools", "sdkPackage": { "id": "cmdline-tools;*", "version": "[5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0]", "recommendedId": "cmdline-tools;13.0", "recommendedVersion": "13.0" }, "optional": "false" }, { "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" }, { "desc": "Android SDK Platform-Tools", "sdkPackage": { "id": "platform-tools", "version": "[33.0.2,33.0.3,34.0.1,34.0.3,34.0.4,34.0.5,35.0.1,35.0.2]", "recommendedId": "platform-tools", "recommendedVersion": "35.0.2" }, "optional": "false" }, … ] } } } [0]: https://loop.cloud.microsoft/p/eyJ1IjoiaHR0cHM6Ly9taWNyb3NvZnQuc2hhcmVwb2ludC1kZi5jb20vc2l0ZXMvYzIyZmVjMDMtN2I4OS00OTJhLTgzNzQtZmZjMTI4YjMwMWRhP25hdj1jejBsTWtaemFYUmxjeVV5Um1NeU1tWmxZekF6TFRkaU9Ea3RORGt5WVMwNE16YzBMV1ptWXpFeU9HSXpNREZrWVNaa1BXSWxNakZXTUhSeU9XY3dRbk5WYlhVdFJWUjNRVEZNY0dOSmQwdG1VVEZUZFVFeFRuRk5XbVZ3TUhVd1dUaEhkVVpKVlRSUGIxWnlVMWxoZFRaT2RFODRTamhISm1ZOU1ERlhSelkwU0RNMU56TlZRbEpITWs1TU1rSkdTemRZV1ZCWFJqSlNTRVJQVENaalBTVXlSaVpoUFV4dmIzQkJjSEFtY0QwbE5EQm1iSFZwWkhnbE1rWnNiMjl3TFhCaFoyVXRZMjl1ZEdGcGJtVnlKbmc5SlRkQ0pUSXlkeVV5TWlVelFTVXlNbFF3VWxSVlNIaDBZVmRPZVdJelRuWmFibEYxWXpKb2FHTnRWbmRpTW14MVpFTXhhMXBwTldwaU1qRTRXV2xHVjAxSVVubFBWMk4zVVc1T1ZtSllWWFJTVmxJelVWUkdUV05IVGtwa01IUnRWVlJHVkdSVlJYaFVia1pPVjIxV2QwMUlWWGRYVkdoSVpGVmFTbFpVVWxCaU1WcDVWVEZzYUdSVVdrOWtSVGcwVTJwb1NHWkVRWGhXTUdNeVRrVm5lazU2VGtwVU1WRXdWMnQwUmxGNldrdFRSbXhaVlRCYVVWUlZhRlJXUlVaRFYyeEZKVE5FSlRJeUpUSkRKVEl5YVNVeU1pVXpRU1V5TWprNE5HUXhZMlpoTFRnMVpXUXROR1kyWXkxaU9EWmlMVFJtTXpZMU1EQXlaak5tTnlVeU1pVTNSQT09In0%3D?ct=1728330820992&
1 parent 68da9e9 commit 6c1525c

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed

build-tools/create-packs/Microsoft.NET.Sdk.Android.proj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,20 @@ about the various Microsoft.Android workloads.
4343
Replacements="@NET_PREVIOUS_VERSION@=$(AndroidNetPreviousVersion)">
4444
</ReplaceFileContents>
4545

46+
<PropertyGroup>
47+
<_AndroidPlatformSupportFeed>$(MSBuildThisFileDirectory)/../../external/android-platform-support/Feeds/AndroidManifestFeed_d17.12.xml</_AndroidPlatformSupportFeed>
48+
<_Feed Condition=" Exists($(_AndroidPlatformSupportFeed)) ">$(_AndroidPlatformSupportFeed)</_Feed>
49+
<_Feed Condition=" '$(_Feed)' == '' ">https://aka.ms/AndroidManifestFeed/d17-12</_Feed>
50+
<_Project>$(MSBuildThisFileDirectory)/../../tools/workload-dependencies/workload-dependencies.csproj</_Project>
51+
<WorkloadDependenciesPath Condition="'$(WorkloadDependenciesPath)' == ''">$(OutputPath)workload-manifest\workload-dependencies.json</WorkloadDependenciesPath>
52+
</PropertyGroup>
53+
54+
<Exec Command="dotnet run --project &quot;$(_Project)&quot; -- &quot;--feed=$(_Feed)&quot; --workload-version=$(WorkloadVersion) -o &quot;$(WorkloadDependenciesPath)&quot;" />
55+
4656
<ItemGroup>
4757
<_PackageFiles Include="$(WorkloadManifestJsonPath)" PackagePath="data" />
4858
<_PackageFiles Include="$(WorkloadManifestTargetsPath)" PackagePath="data" />
59+
<_PackageFiles Include="$(WorkloadDependenciesPath)" PackagePath="data" />
4960
</ItemGroup>
5061
</Target>
5162

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
using System.Net.Http;
2+
using System.Xml.Linq;
3+
4+
using Mono.Options;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
8+
const string AppName = "release-json";
9+
10+
var RequiredPackages = new HashSet<string> {
11+
"platform-tools",
12+
"cmdline-tools",
13+
"build-tool",
14+
"platform",
15+
};
16+
17+
var help = false;
18+
var feed = (string?) null;
19+
var output = (string?) null;
20+
int verbosity = 0;
21+
var workloadVersion = (string?) null;
22+
23+
var options = new OptionSet {
24+
"Generate `release.json` from Feed XML file.",
25+
{ "i|feed=",
26+
"The {PATH} to the Feed XML file.",
27+
v => feed = v },
28+
{ "o|output=",
29+
"The {PATH} to the output release.json file.",
30+
v => output = v },
31+
{ "workload-version=",
32+
"The {VERSION} of the workload to generate.",
33+
v => workloadVersion = v },
34+
{ "v|verbose:",
35+
"Set internal message verbosity",
36+
(int? v) => verbosity = v.HasValue ? v.Value : verbosity + 1 },
37+
{ "h|help",
38+
"Show this help message and exit",
39+
v => help = v != null },
40+
};
41+
42+
XDocument doc;
43+
44+
try {
45+
options.Parse (args);
46+
47+
if (help) {
48+
options.WriteOptionDescriptions (Console.Out);
49+
return;
50+
}
51+
52+
if (string.IsNullOrEmpty (feed)) {
53+
Console.Error.WriteLine ($"{AppName}: --feed is required.");
54+
Console.Error.WriteLine ($"{AppName}: Use --help for more information.");
55+
return;
56+
}
57+
doc = XDocument.Parse (await GetFeedContents (feed));
58+
if (doc.Root == null) {
59+
throw new InvalidOperationException ("Missing root element in XML feed.");
60+
}
61+
}
62+
catch (OptionException e) {
63+
Console.Error.WriteLine ($"{AppName}: {e.Message}");
64+
if (verbosity > 0) {
65+
Console.Error.WriteLine (e.ToString ());
66+
}
67+
return;
68+
}
69+
catch (System.Xml.XmlException e) {
70+
Console.Error.WriteLine ($"{AppName}: invalid `--feed=PATH` value. {e.Message}");
71+
if (verbosity > 0) {
72+
Console.Error.WriteLine (e.ToString ());
73+
}
74+
return;
75+
}
76+
77+
var PackageCreators = new Dictionary<string, Func<XDocument, IEnumerable<JObject>>> {
78+
["extra"] = CreateExtraPackageEntries,
79+
["addon"] = CreateAddonPackageEntries,
80+
["licenses"] = doc => Array.Empty<JObject> (),
81+
["jdk"] = doc => Array.Empty<JObject> (),
82+
};
83+
84+
var release = new JObject {
85+
new JProperty ("microsoft.net.sdk.android", new JObject {
86+
CreateWorkloadProperty (doc),
87+
CreateJdkProperty (doc),
88+
new JProperty ("androidsdk", new JObject {
89+
new JProperty ("packages", CreatePackagesArray (doc)),
90+
}),
91+
}),
92+
};
93+
94+
using var writer = CreateWriter ();
95+
release.WriteTo (writer);
96+
writer.Flush ();
97+
98+
async Task<string> GetFeedContents (string feed)
99+
{
100+
if (File.Exists (feed)) {
101+
return File.ReadAllText (feed);
102+
}
103+
if (Uri.TryCreate (feed, UriKind.Absolute, out var uri)) {
104+
return await GetFeedContentsFromUri (uri);
105+
}
106+
throw new NotSupportedException ($"Don't know what to do with --feed={feed}");
107+
}
108+
109+
async Task<string> GetFeedContentsFromUri (Uri feed)
110+
{
111+
using var client = new HttpClient ();
112+
var response = await client.GetAsync (feed);
113+
return await response.Content.ReadAsStringAsync ();
114+
}
115+
116+
JsonWriter CreateWriter ()
117+
{
118+
var w = string.IsNullOrEmpty (output)
119+
? new JsonTextWriter (Console.Out) { CloseOutput = false}
120+
: new JsonTextWriter (File.CreateText (output)) { CloseOutput = true };
121+
w.Formatting = Formatting.Indented;
122+
return w;
123+
}
124+
125+
JProperty CreateWorkloadProperty (XDocument doc)
126+
{
127+
var contents = new JObject (
128+
new JProperty ("alias", new JArray ("android")));
129+
if (!string.IsNullOrEmpty (workloadVersion))
130+
contents.Add (new JProperty ("version", workloadVersion));
131+
return new JProperty ("workload", contents);
132+
}
133+
134+
JProperty CreateJdkProperty (XDocument doc)
135+
{
136+
var latestRevision = GetLatestRevision (doc, "jdk");
137+
var contents = new JObject (
138+
new JProperty ("version", "[17.0,18.0)"));
139+
if (!string.IsNullOrEmpty (latestRevision))
140+
contents.Add (new JProperty ("recommendedVersion", latestRevision));
141+
return new JProperty ("jdk", contents);
142+
}
143+
144+
IEnumerable<XElement> GetSupportedElements (XDocument doc, string element)
145+
{
146+
if (doc.Root == null) {
147+
return Array.Empty<XElement> ();
148+
}
149+
return doc.Root.Elements (element)
150+
.Where (e =>
151+
string.Equals ("False", e.ReqAttr ("obsolete"), StringComparison.OrdinalIgnoreCase) &&
152+
string.Equals ("False", e.ReqAttr ("preview"), StringComparison.OrdinalIgnoreCase));
153+
}
154+
155+
IEnumerable<(XElement Element, string Revision)> GetByRevisions (XDocument doc, string element)
156+
{
157+
return GetSupportedElements (doc, element)
158+
.OrderByRevision ();
159+
}
160+
161+
string? GetLatestRevision (XDocument doc, string element)
162+
{
163+
return GetByRevisions (doc, element)
164+
.LastOrDefault ()
165+
.Revision;
166+
}
167+
168+
IEnumerable<JObject> CreateExtraPackageEntries (XDocument doc)
169+
{
170+
var allExtras = GetByRevisions (doc, "extra").ToList ();
171+
var paths = allExtras
172+
.Select (e => e.Element.ReqAttr ("path"))
173+
.Distinct ();
174+
foreach (var path in paths) {
175+
var extras = allExtras
176+
.Where (e => e.Element.ReqAttr ("path") == path);
177+
var version = string.Join (",", extras.Select (e => e.Revision));
178+
var latest = extras.Last ();
179+
var entry = new JObject {
180+
new JProperty ("desc", latest.Element.ReqAttr ("description")),
181+
new JProperty ("sdkPackage", new JObject {
182+
new JProperty ("id", path),
183+
new JProperty ("version", "[" + version + "]"),
184+
new JProperty ("recommendedId", latest.Element.ReqAttr ("path")),
185+
new JProperty ("recommendedVersion", latest.Revision),
186+
}),
187+
new JProperty ("optional", "true"),
188+
};
189+
yield return entry;
190+
}
191+
}
192+
193+
IEnumerable<JObject> CreateAddonPackageEntries (XDocument doc)
194+
{
195+
var allAddons = GetSupportedElements (doc, "addon").ToList ()
196+
.OrderBy (e => e.ReqAttr ("path"));
197+
var paths = allAddons
198+
.Select (e => GetEntryId (e))
199+
.Distinct ();
200+
foreach (var path in paths) {
201+
var addons = allAddons
202+
.Where (e => GetEntryId (e) == path);
203+
var version = string.Join (",", addons.Select (e => e.ReqAttr ("revision")));
204+
var latest = addons.Last ();
205+
var entry = new JObject {
206+
new JProperty ("desc", latest.ReqAttr ("description")),
207+
new JProperty ("sdkPackage", new JObject {
208+
new JProperty ("id", path),
209+
new JProperty ("version", "[" + version + "]"),
210+
new JProperty ("recommendedId", latest.ReqAttr ("path")),
211+
new JProperty ("recommendedVersion", latest.ReqAttr ("revision")),
212+
}),
213+
new JProperty ("optional", "true"),
214+
};
215+
yield return entry;
216+
}
217+
}
218+
219+
JArray CreatePackagesArray (XDocument doc)
220+
{
221+
var packages = new JArray ();
222+
var names = doc.Root!.Elements ()
223+
.Select (e => e.Name.LocalName)
224+
.Distinct ()
225+
.OrderBy (e => e);
226+
foreach (var name in names) {
227+
if (PackageCreators.TryGetValue (name, out var creator)) {
228+
foreach (var e in creator (doc)) {
229+
packages.Add (e);
230+
}
231+
continue;
232+
}
233+
var items = GetSupportedElements (doc, name)
234+
.OrderBy (e => e.ReqAttr ("path"));
235+
if (!items.Any ()) {
236+
continue;
237+
}
238+
var version = string.Join (",", items.Select (e => e.ReqAttr ("revision")));
239+
var latest = items.Last ();
240+
241+
var entry = new JObject {
242+
new JProperty ("desc", latest.ReqAttr ("description")),
243+
new JProperty ("sdkPackage", new JObject {
244+
new JProperty ("id", GetEntryId (latest)),
245+
new JProperty ("version", "[" + version + "]"),
246+
new JProperty ("recommendedId", latest.ReqAttr ("path")),
247+
new JProperty ("recommendedVersion", latest.ReqAttr ("revision")),
248+
}),
249+
new JProperty ("optional", (!RequiredPackages.Contains (name)).ToString ().ToLowerInvariant ()),
250+
};
251+
252+
packages.Add (entry);
253+
}
254+
return packages;
255+
}
256+
257+
string GetEntryId (XElement entry)
258+
{
259+
var path = entry.ReqAttr ("path");
260+
var semic = path.LastIndexOf (';');
261+
if (semic < 0) {
262+
return path;
263+
}
264+
var hyphen = path.LastIndexOf ('-');
265+
if (hyphen < 0) {
266+
return path.Substring (0, semic+1) + "*";
267+
}
268+
return path.Substring (0, Math.Max (hyphen, semic)+1) + "*";
269+
}
270+
271+
static class Extensions
272+
{
273+
public static string ReqAttr (this XElement e, string attribute)
274+
{
275+
var v = (string?) e.Attribute (attribute);
276+
if (v == null) {
277+
throw new InvalidOperationException ($"Missing required attribute `{attribute}` in: `{e}");
278+
}
279+
return v;
280+
}
281+
282+
public static IEnumerable<(XElement Element, string Revision)> OrderByRevision (this IEnumerable<XElement> elements)
283+
{
284+
return from e in elements
285+
let revision = e.ReqAttr ("revision")
286+
let version = new Version (revision.Contains (".") ? revision : revision + ".0")
287+
orderby version
288+
select (e, revision);
289+
}
290+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<RootNamespace>release_json</RootNamespace>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
13+
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
14+
</ItemGroup>
15+
</Project>

0 commit comments

Comments
 (0)