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); } }
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); } }
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); }
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(); }
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}"); } }
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(); } } } }
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(); }
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}"); }
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)); }
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,
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)); } }
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)); }
/// <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)); }
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); }
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); } }
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)); } }
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()); }
/// <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); }
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); }
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); } }
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"); }