void ProcessTPN(SortedDictionary <string, ThirdPartyNotice> licenses, ThirdPartyNotice tpn, bool includeExternalDeps, bool includeBuildDeps)
        {
            if (tpn == null)
            {
                throw new ArgumentNullException(nameof(tpn));
            }

            tpn.EnsureValid();
            if (!tpn.Include(includeExternalDeps, includeBuildDeps))
            {
                return;
            }

            Log.StatusLine($"  {Context.Instance.Characters.Bullet} Processing: ", tpn.Name, ConsoleColor.Gray, ConsoleColor.White);

            if (licenses.ContainsKey(tpn.Name))
            {
                Log.WarningLine($"Duplicate Third Party Notice '{tpn.Name}' (old class: {licenses [tpn.Name]}; new class: {tpn})");
                return;
            }

            licenses.Add(tpn.Name, tpn);
        }
#pragma warning restore CS1998

        void GenerateThirdPartyNotices(string outputPath, ThirdPartyLicenseType licenseType, bool includeExternalDeps, bool includeBuildDeps)
        {
            List <Type> types = Utilities.GetTypesWithCustomAttribute <TPNAttribute> ();

            if (types.Count == 0)
            {
                Log.StatusLine("No Third Party Notice entries found", ConsoleColor.Gray);
                return;
            }

            var licenses = new SortedDictionary <string, ThirdPartyNotice> (StringComparer.OrdinalIgnoreCase);

            foreach (Type type in types)
            {
                EnsureValidTPNType(type);

                if (type.IsSubclassOf(typeof(ThirdPartyNoticeGroup)))
                {
                    ProcessTPN(licenses, Activator.CreateInstance(type) as ThirdPartyNoticeGroup, includeExternalDeps, includeBuildDeps);
                    continue;
                }

                if (type.IsSubclassOf(typeof(ThirdPartyNotice)))
                {
                    ProcessTPN(licenses, Activator.CreateInstance(type) as ThirdPartyNotice, includeExternalDeps, includeBuildDeps);
                    continue;
                }

                throw new NotSupportedException($"ThirdPartyNotice type {type.FullName} not supported");
            }

            if (licenses.Count == 0)
            {
                return;
            }

            string blurb;

            if (!tpnBlurbs.TryGetValue(licenseType, out blurb))
            {
                throw new InvalidOperationException($"Unknown license type {licenseType}");
            }

            using (StreamWriter sw = Utilities.OpenStreamWriter(outputPath)) {
                Log.StatusLine($" Generating: {Utilities.GetRelativePath (BuildPaths.XamarinAndroidSourceRoot, outputPath)}", ConsoleColor.Gray);
                Log.DebugLine($"Full path: {outputPath}");

                sw.WriteLine(blurb);
                sw.WriteLine();

                uint i   = 0;
                int  pad = licenses.Count >= 10 ? 4 : 3;
                foreach (var kvp in licenses)
                {
                    string           name = kvp.Key;
                    ThirdPartyNotice tpn  = kvp.Value;

                    sw.Write($"{++i}.".PadRight(pad));
                    sw.WriteLine($"{name} ({tpn.SourceUrl})");
                }
                sw.WriteLine();

                foreach (string key in licenses.Keys)
                {
                    ThirdPartyNotice tpn = licenses [key];

                    string heading   = $"%% {tpn.Name} NOTICES AND INFORMATION BEGIN HERE";
                    string underline = "=".PadRight(heading.Length, '=');
                    sw.WriteLine(heading);
                    sw.WriteLine(underline);
                    if (tpn.LicenseText != null)
                    {
                        sw.WriteLine(tpn.LicenseText.TrimStart());
                    }
                    else
                    {
                        sw.WriteLine(FetchTPNLicense(tpn.LicenseFile));
                    }
                    sw.WriteLine();
                    sw.WriteLine(underline);
                    sw.WriteLine($"END OF {tpn.Name} NOTICES AND INFORMATION");
                    sw.WriteLine();
                }

                sw.Flush();
            }
        }