예제 #1
0
        private static void Canonicalize(string api)
        {
            if (api == "root")
            {
                return;
            }
            var layout = DirectoryLayout.ForApi(api);
            var site   = Path.Combine(layout.DocsOutputDirectory, "site");

            foreach (var page in Directory.GetFiles(site, "*.html", new EnumerationOptions {
                RecurseSubdirectories = true
            }))
            {
                var relative = Path.GetRelativePath(site, page).Replace('\\', '/');

                var canonicalLink = Canonicalizer.GetUrl(api, relative);
                // If the page isn't on CloudSite, skip it.
                if (canonicalLink is null)
                {
                    continue;
                }

                var canonicalElement = $"<link rel=\"canonical\" href=\"{canonicalLink}\" />";

                var html = File.ReadAllText(page);
                html = html.Replace("<head>", $"<head>{canonicalElement}");
                File.WriteAllText(page, html);
            }
        }
예제 #2
0
        static void GenerateProject(string package, string version, string framework, string workingArea)
        {
            if (File.Exists(workingArea) || Directory.Exists(workingArea))
            {
                throw new UserErrorException($"{workingArea} already exists");
            }
            Directory.CreateDirectory(workingArea);

            XElement targetReference;

            if (version == "project")
            {
                DirectoryLayout layout             = DirectoryLayout.ForApi(package);
                var             packageProjectFile = Path.Combine(layout.SourceDirectory, package, $"{package}.csproj");
                targetReference = new XElement("ProjectReference", new XAttribute("Include", packageProjectFile));
            }
            else
            {
                targetReference = new XElement("PackageReference", new XAttribute("Include", package), new XAttribute("Version", version));
            }
            var projectFile = new XDocument(
                new XElement("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"),
                             new XElement("PropertyGroup",
                                          new XElement("TargetFramework", framework)
                                          ),
                             new XElement("ItemGroup", targetReference)
                             )
                );

            using (var output = File.Create(Path.Combine(workingArea, "project.csproj")))
            {
                projectFile.Save(output);
            }
        }
예제 #3
0
        private static int MainImpl(string[] args)
        {
            if (args.Length != 1)
            {
                throw new UserErrorException("Please specify the API name");
            }
            string api         = args[0];
            var    layout      = DirectoryLayout.ForApi(api);
            var    apiMetadata = ApiMetadata.LoadApis().FirstOrDefault(x => x.Id == api);

            if (apiMetadata == null)
            {
                throw new UserErrorException($"Unable to load API metadata from apis.json for {api}");
            }

            string output = layout.DocsOutputDirectory;

            if (Directory.Exists(output))
            {
                Directory.Delete(output, true);
            }
            Directory.CreateDirectory(output);

            var apiDirectory = layout.SourceDirectory;
            var projects     = Project.LoadProjects(apiDirectory).ToList();

            CreateDocfxJson(api, projects, output);
            CopyAndGenerateArticles(apiMetadata, layout.DocsSourceDirectory, output);
            CreateToc(api, output);
            return(0);
        }
예제 #4
0
        public static void Main(string[] args)
        {
            var layout = DirectoryLayout.ForApi("Google.Cloud.Language.V1");

            // We have manually-written versions of AnalyzeEntities, AnalyzeEntitySentiment, AnalyzeSyntax and AnnotateText
            // which accept a document and automatically request UTF-16. Currently the generated methods will just omit the
            // encoding, which means no offsets are returned. At some point the service may heuristically determine that it
            // should use UTF-16, at which point we can remove the manual code - but until then, we remove the generated code.
            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Google.Cloud.Language.V1", "LanguageServiceClient.g.cs"))
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntitiesAsync", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntitiesAsync", "Document", "CancellationToken")
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntities", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntitySentimentAsync", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntitySentimentAsync", "Document", "CancellationToken")
            .RemoveMethod("LanguageServiceClient", "AnalyzeEntitySentiment", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnalyzeSyntaxAsync", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnalyzeSyntaxAsync", "Document", "CancellationToken")
            .RemoveMethod("LanguageServiceClient", "AnalyzeSyntax", "Document", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnnotateTextAsync", "Document", "Features", "CallSettings")
            .RemoveMethod("LanguageServiceClient", "AnnotateTextAsync", "Document", "Features", "CancellationToken")
            .RemoveMethod("LanguageServiceClient", "AnnotateText", "Document", "Features", "CallSettings")
            .Save();

            // Remove the generated tests for these methods. This is somewhat brittle, but it's unlikely that any more flattenings will be added.
            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Google.Cloud.Language.V1.Tests", "LanguageServiceClientTest.g.cs"))
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeEntities2")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeEntities2Async")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeEntitySentiment2")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeEntitySentiment2Async")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeSyntax2")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnalyzeSyntax2Async")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnnotateText2")
            .RemoveMethod("GeneratedLanguageServiceClientTest", "AnnotateText2Async")
            .Save();
        }
예제 #5
0
        private static string CreateClientClassesDocumentation(ApiMetadata api)
        {
            if (api.Type != ApiType.Grpc)
            {
                return("FIXME"); // No automatic templating for this API
            }
            var layout        = DirectoryLayout.ForApi(api.Id);
            var packageSource = Path.Combine(layout.SourceDirectory, api.Id);
            var sourceFiles   = Directory.GetFiles(packageSource, "*Client.cs");
            // TODO: Find a more robust way of detecting the clients.
            var clients = sourceFiles
                          .Where(file => File.ReadAllText(file).Contains(": ServiceSettingsBase")) // Check it contains a generated client
                          .Select(file => Path.GetFileName(file))                                  // Just the file name, not full path
                          .Select(file => file.Substring(0, file.Length - 3))                      // Trim .cs
                          .OrderBy(client => client)
                          .Select(client => $"[{client}](obj/api/{api.Id}.{client}.yml)")          // Markdown link to API doc
                          .ToList();

            switch (clients.Count)
            {
            case 0: return("FIXME");    // No automatic templating for this API

            case 1: return($"All operations are performed through {clients[0]}.");

            default:
                var list = string.Join("\r\n", clients.Select(client => $"- {client}"));
                return($"All operations are performed through the following client classes:\r\n\r\n{list}");
            }
        }
예제 #6
0
        static void GenerateNotes(string api, Func <string, bool> pathFilter)
        {
            var apiDirectory = $"apis\\{api}\\";
            var tagPrefix    = $"{api}-";

            Console.WriteLine($"Changes for {api}");

            var root = DirectoryLayout.DetermineRootDirectory();

            using (var repo = new Repository(root))
            {
                var diff    = repo.Diff;
                var apiTags = repo.Tags
                              .Where(tag => tag.FriendlyName.StartsWith(tagPrefix))
                              .ToList();
                var idToTagName = apiTags.ToDictionary(tag => tag.Target.Id, tag => tag.FriendlyName.Substring(tagPrefix.Length));

                foreach (var commit in repo.Branches["master"].Commits)
                {
                    if (idToTagName.TryGetValue(commit.Id, out string version))
                    {
                        Console.WriteLine();
                        Console.WriteLine($"Release: {version}");
                        Console.WriteLine($"---------{new string('-', version.Length)}");
                    }
                    if (CommitContainsApi(diff, commit, pathFilter))
                    {
                        Console.WriteLine($"https://github.com/googleapis/google-cloud-dotnet/commit/{commit.Sha.Substring(0, 7)}");
                        Console.WriteLine(commit.Message);
                        Console.WriteLine();
                    }
                }
            }
        }
예제 #7
0
        public static void Main(string[] args)
        {
            var layout = DirectoryLayout.ForApi("Grafeas.V1");

            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Grafeas.V1", "GrafeasClient.cs"))
            .RemoveProperty("GrafeasClient", "DefaultEndpoint")
            .RemoveProperty("GrafeasClient", "DefaultScopes")
            .RemoveProperty("GrafeasClient", "ChannelPool")
            .RemoveField("GrafeasClient", "s_channelPool")
            .RemoveMethod("GrafeasClient", "CreateAsync", "ServiceEndpoint", "GrafeasSettings")
            .RemoveMethod("GrafeasClient", "Create", "ServiceEndpoint", "GrafeasSettings")
            .RemoveMethod("GrafeasClient", "ShutdownDefaultChannelsAsync")
            .RemoveType("GrafeasClientBuilder")
            .Save();

            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Grafeas.V1", "ResourceNames.cs"))
            .RewriteTypeName("Grafeas.V1.ProjectName", "global::Grafeas.V1.ProjectName")
            .RewriteTypeName("Grafeas.V1.NoteName", "global::Grafeas.V1.NoteName")
            .RewriteTypeName("Grafeas.V1.OccurrenceName", "global::Grafeas.V1.OccurrenceName")
            .RewriteMemberAccess("Grafeas.V1.ProjectName.Parse", "global::Grafeas.V1.ProjectName.Parse")
            .RewriteMemberAccess("Grafeas.V1.NoteName.Parse", "global::Grafeas.V1.NoteName.Parse")
            .RewriteMemberAccess("Grafeas.V1.OccurrenceName.Parse", "global::Grafeas.V1.OccurrenceName.Parse")
            .Save();

            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Grafeas.V1.Snippets", "GrafeasClientSnippets.g.cs"))
            .Rewrite(new SnippetRewriter())
            .RewriteAlias("apis", "global::Grafeas.V1")
            .Save();

            SourceFile.Load(Path.Combine(layout.SourceDirectory, "Grafeas.V1.Tests", "GrafeasClientTest.g.cs"))
            .RewriteAlias("apis", "global::Grafeas.V1")
            .Save();
        }
예제 #8
0
        protected override void ExecuteImpl(string[] args)
        {
            ValidateCommonHiddenProductionDependencies();
            var root    = DirectoryLayout.DetermineRootDirectory();
            var catalog = ApiCatalog.Load();

            ValidateApiCatalog(catalog);
            Console.WriteLine($"API catalog contains {catalog.Apis.Count} entries");
            // Now we know we can parse the API catalog, let's reformat it.
            ReformatApiCatalog(catalog);
            RewriteReadme(catalog);
            RewriteRenovate(catalog);
            HashSet <string> apiNames = catalog.CreateIdHashSet();

            foreach (var api in catalog.Apis)
            {
                var path = Path.Combine(root, "apis", api.Id);
                GenerateProjects(path, api, apiNames);
                GenerateSolutionFiles(path, api);
                GenerateDocumentationStub(path, api);
                GenerateSynthConfiguration(path, api);
                GenerateOwlBotConfiguration(path, api);
                GenerateMetadataFile(path, api);
            }
        }
        protected override void ExecuteImpl(string[] args)
        {
            var googleapisRoot   = GetGoogleapisRoot();
            var serviceDirectory = ServiceDirectory.LoadFromGoogleapis(googleapisRoot);
            var outputDirectory  = DirectoryLayout.ForApi("ServiceDirectory").SourceDirectory;

            string directoryJson = JsonConvert.SerializeObject(serviceDirectory, Formatting.Indented);

            File.WriteAllText(Path.Combine(outputDirectory, "directory.json"), directoryJson);

            var gitCommit     = GetCommit(googleapisRoot);
            var synthMetadata = new
            {
                sources = new[] {
                    new {
                        git = new
                        {
                            name   = "googleapis",
                            remote = "https://github.com/googleapis/googleapis.git",
                            sha    = gitCommit
                        }
                    }
                }
            };
            var synthMetadataJson = JsonConvert.SerializeObject(synthMetadata, Formatting.Indented);

            File.WriteAllText(Path.Combine(outputDirectory, "synth.metadata"), synthMetadataJson);
        }
        private static void Execute(string id)
        {
            var catalog = ApiCatalog.Load();
            var api     = catalog[id];

            if (api.NoVersionHistory)
            {
                Console.WriteLine($"Skipping version history update for {id}");
                return;
            }
            string historyFilePath = HistoryFile.GetPathForPackage(id);

            var root = DirectoryLayout.DetermineRootDirectory();

            using (var repo = new Repository(root))
            {
                var releases = LoadReleases(repo, api).ToList();
                if (!File.Exists(historyFilePath))
                {
                    File.WriteAllText(historyFilePath, "# Version history\r\n\r\n");
                }
                var historyFile = HistoryFile.Load(historyFilePath);
                historyFile.MergeReleases(releases);
                historyFile.Save(historyFilePath);
            }
            var relativePath = Path.GetRelativePath(DirectoryLayout.DetermineRootDirectory(), historyFilePath)
                               .Replace('\\', '/');

            Console.WriteLine($"Updated version history file: {relativePath}");
        }
예제 #11
0
        private static void SetVersion(string id, string version)
        {
            var catalog = ApiCatalog.Load();
            var api     = catalog[id];

            string oldVersion = api.Version;

            api.Version = version;
            var layout   = DirectoryLayout.ForApi(id);
            var apiNames = catalog.CreateIdHashSet();

            ProjectGenerator.Program.GenerateMetadataFile(layout.SourceDirectory, api);
            ProjectGenerator.Program.GenerateProjects(layout.SourceDirectory, api, apiNames);
            ProjectGenerator.Program.RewriteReadme(catalog);
            ProjectGenerator.Program.RewriteDocsRootIndex(catalog);

            // Update the parsed JObject associated with the ID, and write it back to apis.json.
            api.Json["version"] = version;
            string formatted = catalog.FormatJson();

            File.WriteAllText(ApiCatalog.CatalogPath, formatted);
            Console.WriteLine("Updated apis.json");
            Console.WriteLine();
            Console.WriteLine(new ApiVersionPair(id, oldVersion, version));
        }
예제 #12
0
        protected override void ExecuteImpl(string[] args)
        {
            string id = args[0];

            var catalog = ApiCatalog.Load();

            if (catalog.Apis.Any(api => api.Id == id))
            {
                throw new UserErrorException($"API {id} already exists in the API catalog.");
            }
            var root       = DirectoryLayout.DetermineRootDirectory();
            var googleapis = Path.Combine(root, "googleapis");
            var apiIndex   = ApiIndex.V1.Index.LoadFromGoogleApis(googleapis);

            var targetApi = apiIndex.Apis.FirstOrDefault(api => api.DeriveCSharpNamespace() == id);

            if (targetApi is null)
            {
                var lowerWithoutCloud = id.Replace(".Cloud", "").ToLowerInvariant();
                var possibilities     = apiIndex.Apis
                                        .Select(api => api.DeriveCSharpNamespace())
                                        .Where(ns => ns.Replace(".Cloud", "").ToLowerInvariant() == lowerWithoutCloud);
                throw new UserErrorException(
                          $"No service found for '{id}'.{Environment.NewLine}Similar possibilities (check options?): {string.Join(", ", possibilities)}");
            }

            var api = new ApiMetadata
            {
                Id          = id,
                ProtoPath   = targetApi.Directory,
                ProductName = targetApi.Title.EndsWith(" API") ? targetApi.Title[..^ 4] : targetApi.Title,
예제 #13
0
        protected override void ExecuteImpl(string[] args)
        {
            string configFile = args[0];
            var    json       = File.ReadAllText(configFile);
            var    config     = JsonConvert.DeserializeObject <BatchReleaseConfig>(json);

            var criteria = config.GetCriteria().ToList();

            if (criteria.Count != 1)
            {
                throw new UserErrorException("Batch release config must specify exactly one criterion.");
            }

            if (!config.DryRun)
            {
                var root = DirectoryLayout.DetermineRootDirectory();
                using var repo = new Repository(root);

                if (repo.RetrieveStatus().IsDirty)
                {
                    throw new UserErrorException("In non-dry-run mode, the current branch must not have changes.");
                }
            }

            var catalog   = ApiCatalog.Load();
            var criterion = criteria[0];
            var proposals = criterion.GetProposals(catalog);

            foreach (var proposal in proposals)
            {
                // Note: This takes into account the dry-run flag.
                proposal.Execute(config);
            }
        }
        protected override void ExecuteImpl(string[] args)
        {
            var catalog       = ApiCatalog.Load();
            var root          = DirectoryLayout.DetermineRootDirectory();
            var googleapis    = Path.Combine(root, "googleapis");
            var apiIndex      = ApiIndex.V1.Index.LoadFromGoogleApis(googleapis);
            int modifiedCount = 0;

            foreach (var api in catalog.Apis)
            {
                var indexEntry = apiIndex.Apis.FirstOrDefault(x => x.DeriveCSharpNamespace() == api.Id);
                if (indexEntry is null)
                {
                    continue;
                }

                // Change this line when introducing a new field...
                api.Json.Last.AddAfterSelf(new JProperty("serviceConfigFile", indexEntry.ConfigFile));
                modifiedCount++;
            }

            Console.WriteLine($"Modified APIs: {modifiedCount}");
            string json = catalog.FormatJson();

            // Validate that we can still load it, before saving it to disk...
            ApiCatalog.FromJson(json);

            File.WriteAllText(ApiCatalog.CatalogPath, json);
        }
        public static void RewriteReadme(ApiCatalog catalog)
        {
            var root       = DirectoryLayout.DetermineRootDirectory();
            var readmePath = Path.Combine(root, "README.md");

            RewriteApiTable(readmePath, catalog, api => $"https://googleapis.dev/dotnet/{api.Id}/{api.Version}");
        }
        private static void Execute(string id)
        {
            var catalog = ApiCatalog.Load();
            var api     = catalog[id];

            if (api.NoVersionHistory)
            {
                Console.WriteLine($"Skipping version history update for {id}");
                return;
            }
            string historyFilePath = HistoryFile.GetPathForPackage(id);

            var root = DirectoryLayout.DetermineRootDirectory();

            using var repo = new Repository(root);
            var releases         = Release.LoadReleases(repo, catalog, api).ToList();
            var historyFile      = HistoryFile.Load(historyFilePath);
            var sectionsInserted = historyFile.MergeReleases(releases);

            if (sectionsInserted.Count != 0)
            {
                historyFile.Save(historyFilePath);
                var relativePath = Path.GetRelativePath(DirectoryLayout.DetermineRootDirectory(), historyFilePath)
                                   .Replace('\\', '/');
                Console.WriteLine($"Updated version history file: {relativePath}");
                Console.WriteLine("New content:");
                Console.WriteLine();
                foreach (var line in sectionsInserted.SelectMany(section => section.Lines))
                {
                    Console.WriteLine(line);
                }
            }
        }
        public static void RewriteDocsRootIndex(ApiCatalog catalog)
        {
            var root      = DirectoryLayout.ForRootDocs().DocsSourceDirectory;
            var indexPath = Path.Combine(root, "index.md");

            RewriteApiTable(indexPath, catalog, api => $"{api.Id}/index.html");
        }
        IEnumerable <ReleaseProposal> IBatchCriterion.GetProposals(ApiCatalog catalog)
        {
            var root = DirectoryLayout.DetermineRootDirectory();

            using var repo = new Repository(root);
            var pendingChangesByApi = GitHelpers.GetPendingChangesByApi(repo, catalog);

            foreach (var api in catalog.Apis)
            {
                // Don't even bother proposing package groups at the moment.
                if (api.PackageGroup is object)
                {
                    continue;
                }
                // Don't propose packages that haven't changed.
                // Note that this will also not propose a release for APIs that haven't
                // yet *been* released - which is probably fine. (We don't want to accidentally
                // launch something due to not paying attention.)
                if (pendingChangesByApi[api].Commits.Count == 0)
                {
                    continue;
                }
                var newVersion = api.StructuredVersion.AfterIncrement();

                yield return(ReleaseProposal.CreateFromHistory(repo, api.Id, newVersion));
            }
        }
예제 #19
0
        protected override void ExecuteImpl(string[] args)
        {
            string id      = args[0];
            string version = args[1];

            var catalog = ApiCatalog.Load();
            var api     = catalog[id];

            string oldVersion = api.Version;

            api.Version = version;
            var layout   = DirectoryLayout.ForApi(id);
            var apiNames = catalog.CreateIdHashSet();

            GenerateProjectsCommand.GenerateMetadataFile(layout.SourceDirectory, api);
            GenerateProjectsCommand.GenerateProjects(layout.SourceDirectory, api, apiNames);
            GenerateProjectsCommand.RewriteReadme(catalog);
            GenerateProjectsCommand.RewriteDocsRootIndex(catalog);

            // Update the parsed JObject associated with the ID, and write it back to apis.json.
            api.Json["version"] = version;
            string formatted = catalog.FormatJson();

            File.WriteAllText(ApiCatalog.CatalogPath, formatted);
            Console.WriteLine("Updated apis.json");
            Console.WriteLine();
            Console.WriteLine(new ApiVersionPair(id, oldVersion, version));
        }
예제 #20
0
        /// <summary>
        /// Loads the service directory from service config files in the "googleapis"
        /// directory under the root layout.
        /// </summary>
        public static ServiceDirectory LoadFromGoogleapis()
        {
            var root           = DirectoryLayout.DetermineRootDirectory();
            var googleapisRoot = Path.Combine(root, "googleapis");

            return(LoadFromGoogleapis(googleapisRoot));
        }
예제 #21
0
        private static int MainImpl(string[] args)
        {
            if (args.Length != 1)
            {
                throw new UserErrorException("Please specify the API name");
            }
            string api    = args[0];
            var    layout = DirectoryLayout.FromApi(api);

            string output = layout.DocsOutputDirectory;

            if (Directory.Exists(output))
            {
                Directory.Delete(output, true);
            }
            Directory.CreateDirectory(output);

            var apiDirectory = layout.ApiSourceDirectory;
            var projects     = Project.LoadProjects(apiDirectory).ToList();

            CreateDocfxJson(api, projects, output);
            CopyAndGenerateArticles(api, layout.ApiDocsSourceDirectory, output);
            CreateToc(api, output);
            return(0);
        }
예제 #22
0
        protected override void ExecuteImpl(string[] args)
        {
            var idsToCheck = new List <string>();

            foreach (var diff in FindChangedVersions())
            {
                if (diff.OldVersion is null)
                {
                    Console.WriteLine($"{diff.Id} is new; no comparison required.");
                }
                else if (diff.NewVersion is null)
                {
                    Console.WriteLine($"{diff.Id} has been deleted; no comparison required.");
                }
                else
                {
                    // Found an API to compare. Build it locally first, so we know we're up-to-date.
                    var api = diff.Id;
                    idsToCheck.Add(api);
                    Console.WriteLine($"Building {api} locally");
                    var sourceRoot = DirectoryLayout.ForApi(api).SourceDirectory;
                    Processes.RunDotnet(sourceRoot, "build", "-nologo", "-clp:NoSummary", "-v", "quiet", "-c", "Release", api);
                }
            }
            new CheckVersionCompatibilityCommand().Execute(idsToCheck.ToArray());
        }
        IEnumerable <ReleaseProposal> IBatchCriterion.GetProposals(ApiCatalog catalog)
        {
            var root = DirectoryLayout.DetermineRootDirectory();

            using var repo = new Repository(root);
            var pendingChangesByApi = GitHelpers.GetPendingChangesByApi(repo, catalog);

            foreach (var api in catalog.Apis)
            {
                var pendingChanges = pendingChangesByApi[api];
                var pendingCommits = pendingChanges.Commits.Select(commit => commit.HashPrefix);
                if (!Commits.SetEquals(pendingCommits))
                {
                    continue;
                }
                var newVersion = api.StructuredVersion.AfterIncrement();
                var proposal   = ReleaseProposal.CreateFromHistory(repo, api.Id, newVersion);

                // Potentially replace the natural history with an override
                if (!string.IsNullOrEmpty(HistoryOverride) && proposal.NewHistorySection is HistoryFile.Section newSection)
                {
                    var naturalLines  = newSection.Lines;
                    var overrideLines = HistoryOverride.Split('\n');
                    var lines         = naturalLines.Take(2).Concat(overrideLines).ToList();
                    // We always add a blank line at the end of each section.
                    lines.Add("");
                    proposal.NewHistorySection = new HistoryFile.Section(newVersion, lines);
                }
                yield return(proposal);
            }
        }
예제 #24
0
        public void Execute(string[] args)
        {
            var root    = DirectoryLayout.DetermineRootDirectory();
            var catalog = ApiCatalog.Load();
            HashSet <string> tags;

            using (var repo = new Repository(root))
            {
                tags = new HashSet <string>(repo.Tags.Select(tag => tag.FriendlyName));
            }

            List <ApiMetadata> apisToCheck = args.Length == 0
                ? catalog.Apis.Where(api => !api.Version.EndsWith("00") && !tags.Contains($"{api.Id}-{api.Version}")).ToList()
                                             // Note: this basically validates the command line arguments.
                : args.Select(arg => catalog[arg]).ToList();

            foreach (var api in apisToCheck)
            {
                if (IgnoredApis.Contains(api.Id))
                {
                    Console.WriteLine($"Skipping check for {api.Id} as it doesn't target netstandard2.0");
                    continue;
                }
                Console.WriteLine($"Checking compatibility for {api.Id} version {api.Version}");
                var prefix           = api.Id + "-";
                var previousVersions = tags
                                       .Where(tag => tag.StartsWith(prefix))
                                       .Select(tag => tag.Split(new char[] { '-' }, 2)[1])
                                       .Where(v => !v.StartsWith("0")) // We can reasonably ignore old 0.x versions
                                       .Select(StructuredVersion.FromString)
                                       .OrderBy(v => v)
                                       .ToList();

                var newVersion = api.StructuredVersion;

                // First perform a "strict" check, where necessary, failing the build if the difference
                // is inappropriate.
                var(requiredVersion, requiredLevel) = GetRequiredCompatibility(api.StructuredVersion);
                if (requiredVersion != null)
                {
                    if (!previousVersions.Contains(requiredVersion))
                    {
                        throw new UserErrorException($"Expected to check compatibility with {requiredVersion}, but no corresponding tag found");
                    }
                    var actualLevel = CheckCompatibility(api, requiredVersion);
                    if (actualLevel < requiredLevel)
                    {
                        throw new UserErrorException($"Required compatibility level: {requiredLevel}. Actual compatibility level: {actualLevel}.");
                    }
                }

                // Next log the changes compared with the previous release (if we haven't already diffed it)
                // in an informational way. (This can be used to improve or check release notes.)
                var lastRelease = previousVersions.LastOrDefault();
                if (lastRelease != null && !lastRelease.Equals(requiredVersion))
                {
                    CheckCompatibility(api, lastRelease);
                }
            }
        }
        internal void InternalExecute(string id, string version, bool quiet)
        {
            var catalog = ApiCatalog.Load();
            var api     = catalog[id];

            string oldVersion = api.Version;

            api.Version = version;
            if (api.StructuredVersion.Patch == 0)
            {
                GenerateProjectsCommand.UpdateDependencies(catalog, api);
            }
            var layout   = DirectoryLayout.ForApi(id);
            var apiNames = catalog.CreateIdHashSet();

            // This will still write output, even if "quiet" is true, but that's probably
            // okay for batch releasing.
            GenerateProjectsCommand.GenerateMetadataFile(layout.SourceDirectory, api);
            GenerateProjectsCommand.GenerateProjects(layout.SourceDirectory, api, apiNames);
            GenerateProjectsCommand.RewriteReadme(catalog);

            // Update the parsed JObject associated with the ID, and write it back to apis.json.
            api.Json["version"] = version;
            string formatted = catalog.FormatJson();

            File.WriteAllText(ApiCatalog.CatalogPath, formatted);
            if (!quiet)
            {
                Console.WriteLine("Updated apis.json");
                Console.WriteLine();
                Console.WriteLine(new ApiVersionPair(id, oldVersion, version));
            }
        }
예제 #26
0
        private static void CompareVersions(string[] args)
        {
            if (args.Length != 0)
            {
                throw new UserErrorException($"{CompareCommand} does not accept additional arguments");
            }

            var idsToCheck = new List <string>();

            foreach (var diff in FindChangedVersions())
            {
                if (diff.OldVersion is null)
                {
                    Console.WriteLine($"{diff.Id} is new; no comparison required.");
                }
                else if (diff.NewVersion is null)
                {
                    Console.WriteLine($"{diff.Id} has been deleted; no comparison required.");
                }
                else
                {
                    // Found an API to compare. Build it locally first, so we know we're up-to-date.
                    var api = diff.Id;
                    idsToCheck.Add(api);
                    Console.WriteLine($"Building {api} locally");
                    var sourceRoot = DirectoryLayout.ForApi(api).SourceDirectory;
                    Processes.RunDotnet(sourceRoot, "build", "-nologo", "-clp:NoSummary", "-v", "quiet", "-c", "Release", api);
                }
            }
            CheckVersionCompatibility.Program.Main(idsToCheck.ToArray());
        }
예제 #27
0
        /// <summary>
        /// Checks the compatibility of the locally-built API against a version on NuGet.
        /// This assumes the local package has already been built and is up-to-date.
        /// </summary>
        private static Level CheckCompatibility(ApiMetadata api, StructuredVersion version)
        {
            Console.WriteLine($"Differences from {version}");

            // TODO: Remove this try/catch when *everything* has a previous minor version on netstandard2.0.
            AssemblyDefinition oldMetadata;

            try
            {
                oldMetadata = Assemblies.LoadPackageAsync(api.Id, version.ToString(), null, null).Result;
            }
            catch (Exception e)
            {
                Console.WriteLine($"Unable to load {api.Id} version {version} from NuGet. Some possible causes:");
                Console.WriteLine("- Package was pre-netstandard2.0");
                Console.WriteLine("- Package was never published");
                Console.WriteLine("- nuget.org failure");
                Console.WriteLine($"Exception message: {e.Message}");
                Console.WriteLine($"Returning 'identical' as the change level; please check carefully before release.");
                return(Level.Identical);
            }
            var sourceAssembly = Path.Combine(DirectoryLayout.ForApi(api.Id).SourceDirectory, api.Id, "bin", "Release", "netstandard2.0", $"{api.Id}.dll");
            var newMetadata    = Assemblies.LoadFile(sourceAssembly);

            var diff = Assemblies.Compare(oldMetadata, newMetadata, null);

            diff.PrintDifferences(Level.Major, FormatDetail.Brief);
            diff.PrintDifferences(Level.Minor, FormatDetail.Brief);
            Console.WriteLine($"Diff level: {diff.Level}");
            Console.WriteLine();
            return(diff.Level);
        }
예제 #28
0
        private static int MainImpl(string[] args)
        {
            if (args.Length != 1)
            {
                throw new UserErrorException("Please specify the API name");
            }
            string api         = args[0];
            var    layout      = DirectoryLayout.ForApi(api);
            var    apiCatalog  = ApiCatalog.Load();
            var    apiMetadata = apiCatalog[api];

            string output = layout.DocsOutputDirectory;

            if (Directory.Exists(output))
            {
                Directory.Delete(output, true);
            }
            Directory.CreateDirectory(output);

            CreateGoogleApisDevDocfxJson(apiCatalog, apiMetadata, output);
            CreateDevsiteDocfxJson(apiCatalog, apiMetadata, output);
            CopyAndGenerateArticles(apiMetadata, layout.DocsSourceDirectory, output);
            CreateToc(api, output);
            return(0);
        }
예제 #29
0
        static int Main()
        {
            try
            {
                ValidateCommonHiddenProductionDependencies();
                var root = DirectoryLayout.DetermineRootDirectory();
                var apis = ApiMetadata.LoadApis();
                Console.WriteLine($"API catalog contains {apis.Count} entries");
                HashSet <string> apiNames = new HashSet <string>(apis.Select(api => api.Id));

                foreach (var api in apis)
                {
                    var path = Path.Combine(root, "apis", api.Id);
                    GenerateProjects(path, api, apiNames);
                    GenerateSolutionFiles(path, api);
                    GenerateDocumentationStub(path, api);
                    GenerateSynthConfiguration(path, api);
                    GenerateMetadataFile(path, api);
                }
                return(0);
            }
            catch (UserErrorException e)
            {
                Console.WriteLine($"Configuration error: {e.Message}");
                return(1);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed: {e}");
                return(1);
            }
        }
예제 #30
0
        public static void RewriteDocsRootIndex(List <ApiMetadata> apis)
        {
            var root      = DirectoryLayout.ForRootDocs().DocsSourceDirectory;
            var indexPath = Path.Combine(root, "index.md");

            RewriteApiTable(indexPath, apis, api => $"{api.Id}/index.html");
        }