static void GenerateProjects(string apiRoot, ApiMetadata api, HashSet <string> apiNames)
        {
            // We assume the source directories already exist, either because they've just
            // been generated or because they were already there. We infer the type of each
            // project based on the directory name. Expected suffixes:
            // - None: main API
            // - .Snippets: snippets (manual and generated)
            // - .Tests: unit tests
            // - .IntegrationTests: integration tests

            // Anything else will be ignored for now...
            var projectDirectories = Directory.GetDirectories(apiRoot)
                                     .Where(pd => Path.GetFileName(pd).StartsWith(api.Id))
                                     .ToList();

            foreach (var dir in projectDirectories)
            {
                string suffix = Path.GetFileName(dir).Substring(api.Id.Length);
                switch (suffix)
                {
                case "":
                    GenerateMainProject(api, dir, apiNames);
                    break;

                case ".IntegrationTests":
                case ".Snippets":
                case ".Tests":
                    GenerateTestProject(api, dir, apiNames);
                    GenerateCoverageFile(api, dir);
                    break;
                }
            }

            // TODO: Updates for unknown project types? Tricky...
        }
Beispiel #2
0
        /// <summary>
        /// Generates a metadata file (currently .repo-metadata.json; may change name later) with
        /// all the information that language-agnostic tools require.
        /// </summary>
        public static void GenerateMetadataFile(string apiRoot, ApiMetadata api)
        {
            string metadataPath             = Path.Combine(apiRoot, ".repo-metadata.json");
            var    version                  = api.StructuredVersion;
            string versionBasedReleaseLevel =
                // Version "1.0.0-beta00" hasn't been released at all, so we don't have a package to talk about.
                (version.Prerelease ?? "").EndsWith("00") && version.Major == 1 && version.Minor == 0 ? "none"
                // If it's not a prerelease now, or it's ever got to 1.0, it's generally "ga"
                : version.Major > 1 || version.Minor > 0 || version.Prerelease == null ? "ga"
                : version.Prerelease.StartsWith("beta") ? "beta" : "alpha";

            string releaseLevel = api.ReleaseLevelOverride ?? versionBasedReleaseLevel;

            if (releaseLevel == "none")
            {
                // If we have temporarily set the version to (say) beta01 and then reset it to beta00,
                // make sure we don't have an obsolete metadata file.
                File.Delete(metadataPath);
                return;
            }
            var metadata = new
            {
                distribution_name    = api.Id,
                release_level        = releaseLevel,
                client_documentation = $"https://googleapis.dev/dotnet/{api.Id}/latest",
            };
            string json = JsonConvert.SerializeObject(metadata, Formatting.Indented);

            File.WriteAllText(metadataPath, json);
        }
        private static void GenerateSynthConfiguration(string apiRoot, ApiMetadata api)
        {
            if (api.Generator == GeneratorType.None)
            {
                return;
            }
            var synthFile = Path.Combine(apiRoot, "synth.py");

            // Currently all APIs use the exact same synth file, so we can just replace it every time.
            // We may need something more sophisticated in the future.
            string content =
                @" # GENERATED BY Google.Cloud.Tools.ProjectGenerator - DO NOT EDIT!

import sys
from synthtool import shell
from pathlib import Path

# Parent of the script is the API-specific directory
# Parent of the API-specific directory is the apis directory
# Parent of the apis directory is the repo root
root = Path(__file__).parent.parent.parent
package = Path(__file__).parent.name

bash = '/bin/bash'
if sys.platform == 'win32':
  bash = 'C:\\Program Files\\Git\\bin\\bash.exe'

shell.run(
  (bash, 'generateapis.sh', '--check_compatibility', package),
  cwd = root,
  hide_output = False)
";

            File.WriteAllText(synthFile, content);
        }
Beispiel #4
0
        private static void GenerateTestProject(ApiMetadata api, string directory)
        {
            var dependencies = new SortedList <string, string>(api.TestDependencies)
            {
                { $@"..\{api.Id}\{api.Id}.csproj", "" }, // Main project
                { "Microsoft.NET.Test.Sdk", "15.0.0" },
                { "xunit", "2.3.0-beta1-build3642" },
                { "xunit.runner.visualstudio", "2.3.0-beta1-build1309 " },
                { "Moq", "4.7.8" }
            };
            var propertyGroup =
                new XElement("PropertyGroup",
                             new XElement("TargetFrameworks", api.TestTargetFrameworks ?? api.TargetFrameworks ?? "netcoreapp1.0;net452"),
                             new XElement("Features", "IOperation"),
                             new XElement("IsPackable", false),
                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                             new XElement("SignAssembly", true),
                             new XElement("PublicSign", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), true),
                             new XElement("TreatWarningsAsErrors", true),
                             // 1701, 1702 and 1705 are disabled by default.
                             // 4014 is required as snippets for streaming samples call Task.Run and don't await the result.
                             // See https://github.com/googleapis/toolkit/issues/1271 - when that's fixed, we can remove this.
                             new XElement("NoWarn", "1701;1702;1705;4014")
                             );
            var itemGroup = CreateDependenciesElement(dependencies, api.IsReleaseVersion);

            // Allow test projects to use dynamic...
            itemGroup.Add(new XElement("Reference",
                                       new XAttribute("Condition", "'$(TargetFramework)' == 'net452'"),
                                       new XAttribute("Include", "Microsoft.CSharp")));
            // Test service... it keeps on getting added by Visual Studio, so let's just include it everywhere.
            itemGroup.Add(new XElement("Service", new XAttribute("Include", "{82a7f48d-3b50-4b1e-b82e-3ada8210c358}")));
            WriteProjectFile(api, directory, propertyGroup, itemGroup, null);
        }
        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)
                {
                    GenerateProjects(Path.Combine(root, "apis", api.Id), api, apiNames);
                }
                foreach (var api in apis)
                {
                    GenerateSolutionFiles(Path.Combine(root, "apis", api.Id), api);
                }
                foreach (var api in apis)
                {
                    GenerateDocumentationStub(Path.Combine(root, "apis", api.Id), api);
                }
                return(0);
            }
            catch (UserErrorException e)
            {
                Console.WriteLine($"Configuration error: {e.Message}");
                return(1);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed: {e}");
                return(1);
            }
        }
Beispiel #6
0
        private static void GenerateCoverageFile(ApiMetadata api, string directory)
        {
            // Don't generate a coverage file if we've got a placeholder directory
            if (Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories).Length == 0)
            {
                return;
            }
            var filters = new XElement("Filters", new XElement("IncludeFilters",
                                                               new XElement("FilterEntry", new XElement("ModuleMask", api.Id)),
                                                               // Allow tests to contribute coverage to project dependencies, but not package dependencies
                                                               // (as the latter will be covering code we're not reporting on).
                                                               api.Dependencies
                                                               .Where(dep => dep.Value == ProjectVersionValue)
                                                               .Select(dep => new XElement("FilterEntry", new XElement("ModuleMask", dep.Key)))
                                                               ));
            var attributeFilters = new XElement("AttributeFilters",
                                                new XElement("AttributeFilterEntry",
                                                             "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute"),
                                                new XElement("AttributeFilterEntry", "System.Diagnostics.DebuggerNonUserCodeAttribute")
                                                );
            var output = new XElement("Output", $"../../../coverage/{Path.GetFileName(directory)}.dvcr");

            var doc = new XElement("CoverageParams",
                                   filters,
                                   attributeFilters,
                                   output
                                   );

            using (var stream = File.Create(Path.Combine(directory, "coverage.xml")))
            {
                doc.Save(stream);
            }
        }
        static void GenerateDocumentationStub(string apiRoot, ApiMetadata api)
        {
            string file = Path.Combine(apiRoot, "docs", "index.md");

            if (!File.Exists(file) && api.ProductName != null && api.ProductUrl != null)
            {
                Directory.CreateDirectory(Path.GetDirectoryName(file));
                File.WriteAllText(file,
                                  @"{{title}}

{{description}}

{{version}}

{{installation}}

{{auth}}

# Getting started

{{client-classes}}

{{client-construction}}
");
                Console.WriteLine($"Generated documentation stub for {api.Id}");
            }
        }
        private static void GenerateCoverageFile(ApiMetadata api, string directory)
        {
            var targetExecutable = new XElement("TargetExecutable", "/Program Files/dotnet/dotnet.exe");
            var targetArguments  = new XElement("TargetArguments",
                                                $"test --no-build -c Release");
            var filters = new XElement("Filters", new XElement("IncludeFilters",
                                                               new XElement("FilterEntry", new XElement("ModuleMask", api.Id)),
                                                               // Allow tests to contribute coverage to project dependencies, but not package dependencies
                                                               // (as the latter will be covering code we're not reporting on).
                                                               api.Dependencies
                                                               .Where(dep => dep.Value == ProjectVersionValue)
                                                               .Select(dep => new XElement("FilterEntry", new XElement("ModuleMask", dep.Key)))
                                                               ));
            var attributeFilters = new XElement("AttributeFilters",
                                                new XElement("AttributeFilterEntry",
                                                             "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute"),
                                                new XElement("AttributeFilterEntry", "System.Diagnostics.DebuggerNonUserCodeAttribute")
                                                );
            var output     = new XElement("Output", $"../../../coverage/{Path.GetFileName(directory)}.dvcr");
            var workingDir = new XElement("TargetWorkingDir", ".");

            var doc = new XElement("CoverageParams",
                                   targetExecutable,
                                   targetArguments,
                                   filters,
                                   attributeFilters,
                                   workingDir,
                                   output
                                   );

            using (var stream = File.Create(Path.Combine(directory, "coverage.xml")))
            {
                doc.Save(stream);
            }
        }
        static void GenerateDocumentationStub(string apiRoot, ApiMetadata api)
        {
            string file = Path.Combine(apiRoot, "docs", "index.md");

            if (!File.Exists(file) && api.ProductName != null && api.ProductUrl != null)
            {
                Directory.CreateDirectory(Path.GetDirectoryName(file));
                File.WriteAllText(file,
                                  @"{{title}}

{{description}}

{{installation}}

{{auth}}

# Getting started

TODO: Add a link to the client classes here, and introductory text.

# Sample code

TODO: Add snippet references here.
");
                Console.WriteLine($"Generated documentation stub for {api.Id}");
            }
        }
Beispiel #10
0
        static void GenerateSolutionFiles(string apiRoot, ApiMetadata api)
        {
            var projectDirectories = Directory.GetDirectories(apiRoot)
                                     .Where(pd => Path.GetFileName(pd).StartsWith(api.Id))
                                     .ToList();

            HashSet <string> projects = new HashSet <string>();

            // We want to include all the project files, and all the project references
            // from those project files, being aware that the solution file is already one directory
            // higher than the project file...
            foreach (var dir in projectDirectories)
            {
                string projectName = Path.GetFileName(dir);
                string projectFile = Path.Combine(dir, $"{projectName}.csproj");
                if (File.Exists(projectFile))
                {
                    projects.Add($"{projectName}/{projectName}.csproj");
                    XDocument doc = XDocument.Load(projectFile);
                    var       projectReferences = doc.Descendants("ProjectReference")
                                                  .Select(x => x.Attribute("Include").Value.Replace("\\", "/"))
                                                  .Select(x => x.StartsWith("../") ? x.Substring(3) : x);
                    foreach (var reference in projectReferences)
                    {
                        projects.Add(reference);
                    }
                }
            }

            var    solutionFileName = $"{api.Id}.sln";
            string fullFile         = Path.Combine(apiRoot, solutionFileName);
            string beforeHash       = GetFileHash(fullFile);

            if (!File.Exists(fullFile))
            {
                Processes.RunDotnet(apiRoot, "new", "sln", "-n", api.Id);
            }
            else
            {
                // Optimization: don't run "dotnet sln add" if we can find project entries for all the relevant project
                // references already. This is crude, but speeds up the overall process significantly.
                var projectLines = File.ReadAllLines(fullFile).Where(line => line.StartsWith("Project(")).ToList();
                if (projects.Select(p => $"\"{p.Replace("/", "\\")}\"")
                    .All(expectedProject => projectLines.Any(pl => pl.Contains(expectedProject))))
                {
                    return;
                }
            }

            // It's much faster to run a single process than to run it once per project.
            Processes.RunDotnet(apiRoot, new[] { "sln", solutionFileName, "add" }.Concat(projects).ToArray());

            string afterHash = GetFileHash(fullFile);

            if (beforeHash != afterHash)
            {
                Console.WriteLine($"{(beforeHash == null ? "Created" : "Modified")} solution file for {api.Id}");
            }
        }
Beispiel #11
0
        private static void GenerateTestProject(ApiMetadata api, string directory, HashSet <string> apiNames, bool isForAnalyzers = false)
        {
            // Don't generate a project file if we've got a placeholder directory
            if (Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories).Length == 0)
            {
                return;
            }
            var dependencies = new SortedList <string, string>(CommonTestDependencies);

            if (isForAnalyzers)
            {
                dependencies.Remove("Google.Cloud.ClientTesting");
                dependencies.Add("Google.Cloud.AnalyzersTesting", ProjectVersionValue);
            }

            dependencies.Add(api.Id, "project");

            // Deliberately not using Add, so that a project can override the defaults.
            foreach (var dependency in api.TestDependencies)
            {
                dependencies[dependency.Key] = dependency.Value;
            }

            var testTargetFrameworks = GetTestTargetFrameworks(api, isForAnalyzers);
            var propertyGroup        =
                new XElement("PropertyGroup",
                             new XElement("TargetFrameworks", testTargetFrameworks),
                             new XElement("TargetFrameworks", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), AnyDesktopFramework.Replace(testTargetFrameworks, "")),
                             new XElement("LangVersion", "latest"),
                             new XElement("IsPackable", false),
                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                             new XElement("SignAssembly", true),
                             new XElement("TreatWarningsAsErrors", true),
                             // 1701, 1702 and 1705 are disabled by default.
                             // xUnit2004 prevents Assert.Equal(true, value) etc, preferring Assert.True and Assert.False, but
                             //   Assert.Equal is clearer (IMO) for comparing values rather than conditions.
                             // xUnit2013 prevents simple checks for the number of items in a collection
                             // AD0001 is the error for an analyzer throwing an exception. We can remove this when
                             // the fix for https://github.com/xunit/xunit/issues/1409 is in NuGet.
                             new XElement("NoWarn", "1701;1702;1705;xUnit2004;xUnit2013;AD0001")
                             );
            string project             = Path.GetFileName(directory);
            var    dependenciesElement = CreateDependenciesElement(project, dependencies, api.IsReleaseVersion, testProject: true, apiNames: apiNames);

            // Include dotCover CLI tool for dotnet
            dependenciesElement.Add(new XElement("DotNetCliToolReference",
                                                 new XAttribute("Include", "JetBrains.dotCover.CommandLineTools"),
                                                 new XAttribute("Version", "2018.2.0")));
            // Allow test projects to use dynamic...
            dependenciesElement.Add(new XElement("Reference",
                                                 new XAttribute("Condition", "'$(TargetFramework)' == 'net452'"),
                                                 new XAttribute("Include", "Microsoft.CSharp")));
            // Test service... it keeps on getting added by Visual Studio, so let's just include it everywhere.
            dependenciesElement.Add(new XElement("Service", new XAttribute("Include", "{82a7f48d-3b50-4b1e-b82e-3ada8210c358}")));
            WriteProjectFile(api, directory, propertyGroup, dependenciesElement);
        }
Beispiel #12
0
        private static string GetPreferredCoverageFramework(ApiMetadata api)
        {
            var targetFrameworks = GetTestTargetFrameworks(api);

            if (targetFrameworks.Contains(TargetFrameworkClassic))
            {
                return(TargetFrameworkClassic);
            }
            // Otherwise, return the first one found.
            return(targetFrameworks.Split(';').FirstOrDefault());
        }
Beispiel #13
0
        private static void WriteProjectFile(
            ApiMetadata api, string directory, XElement propertyGroup, XElement dependenciesItemGroup, XElement packingElement)
        {
            var      file       = Path.Combine(directory, $"{Path.GetFileName(directory)}.csproj");
            string   beforeHash = GetFileHash(file);
            XElement doc;

            // If the file already exists, load it and replace the elements (leaving any further PropertyGroup and ItemGroup elements).
            // Make sure there's an appropriate import for stripping desktop builds on non-Windows platforms.
            if (File.Exists(file))
            {
                doc = XElement.Load(file);
                doc.Elements("Import").Where(x => (string)x.Attribute("Project") == @"..\..\StripDesktopOnNonWindows.xml").Remove();
                doc.Elements("PropertyGroup").First().ReplaceWith(propertyGroup);
                doc.Elements("ItemGroup").First().ReplaceWith(dependenciesItemGroup);
                doc.Elements("ItemGroup").Where(x => (string)x.Attribute("Label") == DotnetPackInstructionsLabel).Remove();
                doc.Elements("ItemGroup").First().AddAfterSelf(packingElement);

                if (!doc.Elements("Import").Any(x => (string)x.Attribute("Project") == StripDesktopOnNonWindows))
                {
                    doc.Add(new XElement("Import", new XAttribute("Project", StripDesktopOnNonWindows)));
                }
            }
            // Otherwise, create a new one
            else
            {
                doc = new XElement("Project",
                                   new XAttribute("Sdk", "Microsoft.NET.Sdk"),
                                   propertyGroup,
                                   dependenciesItemGroup,
                                   packingElement,
                                   new XElement("Import", new XAttribute("Project", StripDesktopOnNonWindows))
                                   );
            }

            // Don't use File.CreateText as that omits the byte order mark.
            // While byte order marks are nasty, Visual Studio will add it back any time a project file is
            // manually edited, so it's best if we follow suit.
            using (var stream = File.Create(Path.Combine(directory, $"{Path.GetFileName(directory)}.csproj")))
            {
                doc.Save(stream);
            }
            string afterHash = GetFileHash(file);

            if (beforeHash != afterHash)
            {
                Console.WriteLine($"{(beforeHash == null ? "Created" : "Modified")} project file {Path.GetFileName(file)}");
            }
        }
        private static void GenerateSynthConfiguration(string apiRoot, ApiMetadata api)
        {
            if (api.Generator == GeneratorType.None)
            {
                return;
            }
            var synthFile = Path.Combine(apiRoot, "synth.py");

            // Currently all APIs use the exact same synth file, so we can just replace it every time.
            // We may need something more sophisticated in the future.
            string content =
                @"# GENERATED BY Google.Cloud.Tools.ProjectGenerator - DO NOT EDIT!

import json
import sys
from synthtool import shell
import synthtool.metadata
from pathlib import Path

AUTOSYNTH_MULTIPLE_COMMITS = True

# Parent of the script is the API-specific directory
# Parent of the API-specific directory is the apis directory
# Parent of the apis directory is the repo root
root = Path(__file__).parent.parent.parent
package = Path(__file__).parent.name

bash = '/bin/bash'
if sys.platform == 'win32':
  bash = 'C:\\Program Files\\Git\\bin\\bash.exe'

shell.run(
  (bash, 'generateapis.sh', '--check_compatibility', package),
  cwd = root,
  hide_output = False)

# Load the synth.metadata that generateapis.sh has written, and
# re-add all the sources for the in-memory version that synthtool
# is about to write out. This is a pretty ugly hack, but it works for now.
# (We assume every source is a git source.)
with open('synth.metadata') as generated_metadata_file:
  generated_metadata = json.load(generated_metadata_file)
  for source in generated_metadata['sources']:
    synthtool.metadata.get().sources.add(git=source['git'])
";

            File.WriteAllText(synthFile, content);
        }
Beispiel #15
0
        static void GenerateSolutionFiles(string apiRoot, ApiMetadata api)
        {
            var projectDirectories = Directory.GetDirectories(apiRoot)
                                     .Where(pd => Path.GetFileName(pd).StartsWith(api.Id))
                                     .ToList();

            HashSet <string> projects = new HashSet <string>();

            // We want to include all the project files, and all the project references
            // from those project files, being aware that the solution file is already one directory
            // higher than the project file...
            foreach (var dir in projectDirectories)
            {
                string projectName = Path.GetFileName(dir);
                string projectFile = Path.Combine(dir, $"{projectName}.csproj");
                if (File.Exists(projectFile))
                {
                    projects.Add($"{projectName}/{projectName}.csproj");
                    XDocument doc = XDocument.Load(projectFile);
                    var       projectReferences = doc.Descendants("ProjectReference")
                                                  .Select(x => x.Attribute("Include").Value.Replace("\\", "/"))
                                                  .Select(x => x.StartsWith("../") ? x.Substring(3) : x);
                    foreach (var reference in projectReferences)
                    {
                        projects.Add(reference);
                    }
                }
            }

            var    solutionFileName = $"{api.Id}.sln";
            string fullFile         = Path.Combine(apiRoot, solutionFileName);
            string beforeHash       = GetFileHash(fullFile);

            if (!File.Exists(fullFile))
            {
                Processes.RunDotnet(apiRoot, "new", "sln", "-n", api.Id);
            }
            // It's much faster to run a single process than to run it once per project.
            Processes.RunDotnet(apiRoot, new[] { "sln", solutionFileName, "add" }.Concat(projects).ToArray());

            string afterHash = GetFileHash(fullFile);

            if (beforeHash != afterHash)
            {
                Console.WriteLine($"{(beforeHash == null ? "Created" : "Modified")} solution file for {api.Id}");
            }
        }
Beispiel #16
0
        private static void GenerateSmokeTestProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            // Don't generate a project file if we've got a placeholder directory
            if (Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories).Length == 0)
            {
                return;
            }
            var propertyGroup =
                new XElement("PropertyGroup",
                             new XElement("TargetFramework", "netcoreapp2.1"),
                             new XElement("OutputType", "Exe"),
                             new XElement("IsPackable", false));
            var dependenciesElement =
                new XElement("ItemGroup",
                             CreateDependencyElement(Path.GetFileName(directory), api.Id, ProjectVersionValue, stableRelease: false, testProject: true, apiNames));

            WriteProjectFile(api, directory, propertyGroup, dependenciesElement);
        }
Beispiel #17
0
        private static void GenerateTestProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            var dependencies = new SortedList <string, string>(CommonTestDependencies);

            dependencies.Add(api.Id, "project");

            // Deliberately not using Add, so that a project can override the defaults.
            foreach (var dependency in api.TestDependencies)
            {
                dependencies[dependency.Key] = dependency.Value;
            }

            var propertyGroup =
                new XElement("PropertyGroup",
                             new XElement("TargetFrameworks", GetTestTargetFrameworks(api)),
                             new XElement("TargetFrameworks", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), AnyDesktopFramework.Replace(GetTestTargetFrameworks(api), "")),
                             new XElement("LangVersion", "latest"),
                             new XElement("Features", "IOperation"),
                             new XElement("IsPackable", false),
                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                             new XElement("SignAssembly", true),
                             new XElement("PublicSign", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), true),
                             new XElement("TreatWarningsAsErrors", true),
                             // 1701, 1702 and 1705 are disabled by default.
                             // xUnit2004 prevents Assert.Equal(true, value) etc, preferring Assert.True and Assert.False, but
                             //   Assert.Equal is clearer (IMO) for comparing values rather than conditions.
                             // xUnit2013 prevents simple checks for the number of items in a collection
                             // AD0001 is the error for an analyzer throwing an exception. We can remove this when
                             // the fix for https://github.com/xunit/xunit/issues/1409 is in NuGet.
                             new XElement("NoWarn", "1701;1702;1705;xUnit2004;xUnit2013;AD0001")
                             );
            string project             = Path.GetFileName(directory);
            var    dependenciesElement = CreateDependenciesElement(project, dependencies, api.IsReleaseVersion, testProject: true, apiNames: apiNames);

            // Allow test projects to use dynamic...
            dependenciesElement.Add(new XElement("Reference",
                                                 new XAttribute("Condition", "'$(TargetFramework)' == 'net452'"),
                                                 new XAttribute("Include", "Microsoft.CSharp")));
            // Test service... it keeps on getting added by Visual Studio, so let's just include it everywhere.
            dependenciesElement.Add(new XElement("Service", new XAttribute("Include", "{82a7f48d-3b50-4b1e-b82e-3ada8210c358}")));
            WriteProjectFile(api, directory, propertyGroup, dependenciesElement, null);
        }
Beispiel #18
0
 static int Main()
 {
     try
     {
         var root = DirectoryLayout.DetermineRootDirectory();
         foreach (var api in ApiMetadata.LoadApis())
         {
             GenerateProjects(Path.Combine(root, "apis", api.Id), api);
         }
         foreach (var api in ApiMetadata.LoadApis())
         {
             GenerateSolutionFiles(Path.Combine(root, "apis", api.Id), api);
         }
         return(0);
     }
     catch (Exception e)
     {
         Console.WriteLine($"Failed: {e}");
         return(1);
     }
 }
Beispiel #19
0
        private static void GenerateSampleProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            // Don't generate a project file if we've got a placeholder directory
            if (Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories).Length == 0)
            {
                return;
            }
            var dependencies = new SortedList <string, string>(CommonSampleDependencies);

            dependencies.Add(api.Id, "project");
            var propertyGroup =
                new XElement("PropertyGroup",
                             new XElement("TargetFramework", "netcoreapp2.1"),
                             new XElement("OutputType", "Exe"),
                             new XElement("LangVersion", "latest"),
                             new XElement("IsPackable", false));

            string project             = Path.GetFileName(directory);
            var    dependenciesElement = CreateDependenciesElement(project, dependencies, api.IsReleaseVersion, testProject: true, apiNames: apiNames);

            WriteProjectFile(api, directory, propertyGroup, dependenciesElement);
        }
Beispiel #20
0
        private static void GenerateTestProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            var dependencies = new SortedList <string, string>(CommonTestDependencies);

            dependencies.Add(api.Id, "project");

            // Deliberately not using Add, so that a project can override the defaults.
            foreach (var dependency in api.TestDependencies)
            {
                dependencies[dependency.Key] = dependency.Value;
            }

            var propertyGroup =
                new XElement("PropertyGroup",
                             new XElement("TargetFrameworks", GetTestTargetFrameworks(api)),
                             new XElement("LangVersion", "latest"),
                             new XElement("Features", "IOperation"),
                             new XElement("IsPackable", false),
                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                             new XElement("SignAssembly", true),
                             new XElement("PublicSign", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), true),
                             new XElement("TreatWarningsAsErrors", true),
                             // 1701, 1702 and 1705 are disabled by default.
                             // 4014 is required as snippets for streaming samples call Task.Run and don't await the result.
                             // See https://github.com/googleapis/toolkit/issues/1271 - when that's fixed, we can remove this.
                             new XElement("NoWarn", "1701;1702;1705;4014")
                             );
            string project             = Path.GetFileName(directory);
            var    dependenciesElement = CreateDependenciesElement(project, dependencies, api.IsReleaseVersion, testProject: true, apiNames: apiNames);

            // Allow test projects to use dynamic...
            dependenciesElement.Add(new XElement("Reference",
                                                 new XAttribute("Condition", "'$(TargetFramework)' == 'net452'"),
                                                 new XAttribute("Include", "Microsoft.CSharp")));
            // Test service... it keeps on getting added by Visual Studio, so let's just include it everywhere.
            dependenciesElement.Add(new XElement("Service", new XAttribute("Include", "{82a7f48d-3b50-4b1e-b82e-3ada8210c358}")));
            WriteProjectFile(api, directory, propertyGroup, dependenciesElement, null);
        }
Beispiel #21
0
        /// <summary>
        /// Generates a metadata file (currently .repo-metadata.json; may change name later) with
        /// all the information that language-agnostic tools require.
        /// </summary>
        private static void GenerateMetadataFile(string apiRoot, ApiMetadata api)
        {
            var version = api.StructuredVersion;

            // Version "1.0.0-beta00" hasn't been released at all, so we don't have a package to talk about.
            // TODO: Check that this is actually appropriate.
            if ((version.Prerelease ?? "").EndsWith("00") && version.Major == 1 && version.Minor == 0)
            {
                return;
            }
            string releaseLevel =
                // If it's not a prerelease now, or it's ever got to 1.0, it's generally "ga"
                version.Major > 1 || version.Minor > 0 || version.Prerelease == null ? "ga"
                : version.Prerelease.StartsWith("beta") ? "beta" : "alpha";
            var metadata = new
            {
                distribution_name = api.Id,
                release_level     = releaseLevel
            };
            string json = JsonConvert.SerializeObject(metadata, Formatting.Indented);

            File.WriteAllText(Path.Combine(apiRoot, ".repo-metadata.json"), json);
        }
Beispiel #22
0
        private static void WriteProjectFile(
            ApiMetadata api, string directory, XElement propertyGroup, XElement dependenciesItemGroup)
        {
            var      file       = Path.Combine(directory, $"{Path.GetFileName(directory)}.csproj");
            string   beforeHash = GetFileHash(file);
            XElement doc;

            // If the file already exists, load it and replace the elements (leaving any further PropertyGroup and ItemGroup elements).
            if (File.Exists(file))
            {
                doc = XElement.Load(file);
                doc.Elements("PropertyGroup").First().ReplaceWith(propertyGroup);
                doc.Elements("ItemGroup").First().ReplaceWith(dependenciesItemGroup);
            }
            // Otherwise, create a new one
            else
            {
                doc = new XElement("Project",
                                   new XAttribute("Sdk", "Microsoft.NET.Sdk"),
                                   propertyGroup,
                                   dependenciesItemGroup);
            }

            // Don't use File.CreateText as that omits the byte order mark.
            // While byte order marks are nasty, Visual Studio will add it back any time a project file is
            // manually edited, so it's best if we follow suit.
            using (var stream = File.Create(Path.Combine(directory, $"{Path.GetFileName(directory)}.csproj")))
            {
                doc.Save(stream);
            }
            string afterHash = GetFileHash(file);

            if (beforeHash != afterHash)
            {
                Console.WriteLine($"{(beforeHash == null ? "Created" : "Modified")} project file {Path.GetFileName(file)}");
            }
        }
        static int Main()
        {
            try
            {
                ValidateCommonHiddenProductionDependencies();
                var root = DirectoryLayout.DetermineRootDirectory();
                var apis = ApiMetadata.LoadApis();
                Console.WriteLine($"API catalog contains {apis.Count} entries");
                // Now we know we can parse the API catalog, let's reformat it.
                ReformatApiCatalog();
                RewriteReadme(apis);
                RewriteDocsRootIndex(apis);
                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);
            }
        }
Beispiel #24
0
 private static string GetTestTargetFrameworks(ApiMetadata api, bool isForAnalyzers) =>
 isForAnalyzers
         ? AnalyzersTestTargetFramework
         : api.TestTargetFrameworks ?? api.TargetFrameworks ?? DefaultTestTargetFrameworks;
Beispiel #25
0
        private static void GenerateMainProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            if (api.Version == null)
            {
                throw new UserErrorException($"No version specified for {api.Id}");
            }
            string targetFrameworks = api.TargetFrameworks;

            SortedList <string, string> dependencies;

            if (api.Type == ApiType.Analyzers)
            {
                if (targetFrameworks != AnalyzersTargetFramework)
                {
                    throw new UserErrorException($"Analyzers are expected to use {AnalyzersTargetFramework}");
                }

                dependencies = new SortedList <string, string>(CommonAnalyzerDependencies);

                // Note: If support is added here for using additional dependencies, we need to resolve
                //       the packaging issues and make sure the onus won't be on the user to add the
                //       dependency references.
            }
            else
            {
                dependencies = new SortedList <string, string>(CommonHiddenProductionDependencies);

                switch (api.Type)
                {
                case ApiType.Rest:
                    dependencies.Add("Google.Api.Gax.Rest", DefaultVersionValue);
                    targetFrameworks = targetFrameworks ?? DefaultRestTargetFrameworks;
                    break;

                case ApiType.Grpc:
                    dependencies.Add("Google.Api.Gax.Grpc", DefaultVersionValue);
                    dependencies.Add("Grpc.Core", DefaultVersionValue);
                    targetFrameworks = targetFrameworks ?? DefaultGrpcTargetFrameworks;
                    break;
                }

                // Deliberately not using Add, so that a project can override the defaults.
                // In particular, stable releases *must* override versions of GRPC and GAX.
                foreach (var dependency in api.Dependencies)
                {
                    dependencies[dependency.Key] = dependency.Value;
                }
            }

            var propertyGroup = new XElement("PropertyGroup",
                                                                                   // Build-related properties
                                             new XElement("Version", api.Version), // TODO: Version, or VersionPrefix/VersionSuffix?
                                             new XElement("TargetFrameworks", targetFrameworks),
                                             new XElement("TargetFrameworks", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), AnyDesktopFramework.Replace(targetFrameworks, "")),
                                             new XElement("LangVersion", "latest"),
                                             new XElement("GenerateDocumentationFile", api.Type != ApiType.Analyzers),
                                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                                             new XElement("SignAssembly", true),
                                             new XElement("Deterministic", true),
                                             new XElement("TreatWarningsAsErrors", true),
                                             // Package-related properties
                                             new XElement("Description", api.Description),
                                             new XElement("PackageTags", string.Join(";", api.Tags.Concat(new[] { "Google", "Cloud" }))),
                                             new XElement("Copyright", $"Copyright {DateTime.UtcNow.Year} Google LLC"),
                                             new XElement("Authors", "Google Inc."),
                                             new XElement("PackageIconUrl", "https://cloud.google.com/images/gcp-icon-64x64.png"),
                                             new XElement("PackageLicenseFile", "LICENSE"),
                                             new XElement("PackageProjectUrl", "https://github.com/googleapis/google-cloud-dotnet"),
                                             new XElement("RepositoryType", "git"),
                                             new XElement("RepositoryUrl", "https://github.com/googleapis/google-cloud-dotnet")
                                             );

            if (dependencies.ContainsKey(GrpcPackage))
            {
                propertyGroup.Add(new XElement("CodeAnalysisRuleSet", "..\\..\\..\\grpc.ruleset"));
            }
            var dependenciesElement = CreateDependenciesElement(api.Id, dependencies, api.IsReleaseVersion, testProject: false, apiNames: apiNames);

            // Pack the license file; this element isn't a dependency, but it still belongs in an ItemGroup...
            dependenciesElement.Add(new XElement("None",
                                                 new XAttribute("Include", "../../../LICENSE"),
                                                 new XAttribute("Pack", true),
                                                 new XAttribute("PackagePath", ""))); // Note: not $(PackageLicenseFile) as suggested in docs, due to us using a file with no extension

            if (api.Type == ApiType.Analyzers)
            {
                propertyGroup.Add(new XElement("IncludeBuildOutput", false));

                void AddPackFile(string includePath, string packagePath)
                {
                    dependenciesElement.Add(
                        new XElement("None",
                                     new XAttribute("Include", includePath),
                                     new XAttribute("Pack", "true"),
                                     new XAttribute("PackagePath", packagePath),
                                     new XAttribute("Visible", "false")));
                }

                AddPackFile(
                    $"$(OutputPath)\\{AnalyzersTargetFramework}\\$(AssemblyName).dll",
                    "analyzers/dotnet/cs");

                // Add install scripts as per
                // https://docs.microsoft.com/en-us/nuget/reference/analyzers-conventions#install-and-uninstall-scripts
                // Name each file rather than using a wildcard so 'dotnet pack' will error out if the files are missing
                // for some reason.
                AddPackFile(
                    @"..\..\..\analyzerScripts\install.ps1",
                    "tools");
                AddPackFile(
                    @"..\..\..\analyzerScripts\uninstall.ps1",
                    "tools");
            }
            WriteProjectFile(api, directory, propertyGroup, dependenciesElement);
        }
Beispiel #26
0
        private static void GenerateMainProject(ApiMetadata api, string directory)
        {
            if (api.Version == null)
            {
                throw new Exception($"No version specified for {api.Id}");
            }
            string targetFrameworks = api.TargetFrameworks;
            var    dependencies     = new SortedList <string, string>(api.Dependencies)
            {
                { ConfigureAwaitAnalyzer, "1.0.0-beta4" },
                { SourceLinkPackage, "2.1.2" }
            };

            // If Grpc.Core is ever specified explicitly (e.g. for "other" projects),
            // but without a version number, fill it in.
            if (dependencies.ContainsKey("Grpc.Core") && dependencies["Grpc.Core"] == "")
            {
                dependencies["Grpc.Core"] = GrpcVersion;
            }
            switch (api.Type)
            {
            case "rest":
                dependencies.Add("Google.Api.Gax.Rest", api.IsReleaseVersion ? StableGaxVersion : PrereleaseGaxVersion);
                targetFrameworks = targetFrameworks ?? "netstandard1.3;net45";
                break;

            case "grpc":
                dependencies.Add("Google.Api.Gax.Grpc", api.IsReleaseVersion ? StableGaxVersion : PrereleaseGaxVersion);
                dependencies.Add("Grpc.Core", GrpcVersion);
                targetFrameworks = targetFrameworks ?? "netstandard1.5;net45";
                break;
            }

            var propertyGroup = new XElement("PropertyGroup",
                                                                                   // Build-related properties
                                             new XElement("Version", api.Version), // TODO: Version, or VersionPrefix/VersionSuffix?
                                             new XElement("TargetFrameworks", targetFrameworks),
                                             new XElement("Features", "IOperation"),
                                             new XElement("GenerateDocumentationFile", true),
                                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                                             new XElement("SignAssembly", true),
                                             new XElement("Deterministic", true),
                                             new XElement("PublicSign", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), true),
                                             new XElement("TreatWarningsAsErrors", true),
                                             // Package-related properties
                                             new XElement("Description", api.Description),
                                             new XElement("PackageTags", string.Join(";", api.Tags.Concat(new[] { "Google", "Cloud" }))),
                                             new XElement("Copyright", "Copyright 2017 Google Inc."),
                                             new XElement("Authors", "Google Inc."),
                                             new XElement("PackageIconUrl", "https://cloud.google.com/images/gcp-icon-64x64.png"),
                                             new XElement("PackageLicenseUrl", "http://www.apache.org/licenses/LICENSE-2.0"),
                                             new XElement("PackageProjectUrl", "https://github.com/GoogleCloudPlatform/google-cloud-dotnet"),
                                             new XElement("RepositoryType", "git"),
                                             new XElement("RepositoryUrl", "https://github.com/GoogleCloudPlatform/google-cloud-dotnet")
                                             );
            var packingElement = new XElement("ItemGroup",
                                              new XAttribute("Label", DotnetPackInstructionsLabel),
                                              targetFrameworks.Split(';').Select(tfm => new XElement("Content",
                                                                                                     new XAttribute("Include", $@"$(OutputPath){tfm}\$(PackageId).pdb"),
                                                                                                     new XElement("Pack", true),
                                                                                                     new XElement("PackagePath", $"lib/{tfm}")
                                                                                                     ))
                                              );

            WriteProjectFile(api, directory, propertyGroup, CreateDependenciesElement(dependencies, api.IsReleaseVersion), packingElement);
        }
Beispiel #27
0
        private static void GenerateMainProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            if (api.Version == null)
            {
                throw new UserErrorException($"No version specified for {api.Id}");
            }
            string targetFrameworks = api.TargetFrameworks;

            SortedList <string, string> dependencies;

            if (api.Type == ApiType.Analyzers)
            {
                if (targetFrameworks != AnalyzersTargetFramework)
                {
                    throw new UserErrorException($"Analyzers are expected to use {AnalyzersTargetFramework}");
                }

                dependencies = new SortedList <string, string>(CommonAnalyzerDependencies, StringComparer.Ordinal);

                // Note: If support is added here for using additional dependencies, we need to resolve
                //       the packaging issues and make sure the onus won't be on the user to add the
                //       dependency references.
            }
            else
            {
                dependencies = new SortedList <string, string>(CommonHiddenProductionDependencies, StringComparer.Ordinal);

                switch (api.Type)
                {
                case ApiType.Rest:
                    dependencies.Add("Google.Api.Gax.Rest", DefaultVersionValue);
                    targetFrameworks = targetFrameworks ?? DefaultRestTargetFrameworks;
                    break;

                case ApiType.Grpc:
                    dependencies.Add("Google.Api.Gax.Grpc.GrpcCore", DefaultVersionValue);
                    dependencies.Add("Grpc.Core", DefaultVersionValue);
                    targetFrameworks = targetFrameworks ?? DefaultGrpcTargetFrameworks;
                    break;
                }

                // Deliberately not using Add, so that a project can override the defaults.
                // In particular, stable releases *must* override versions of GRPC and GAX.
                foreach (var dependency in api.Dependencies)
                {
                    dependencies[dependency.Key] = dependency.Value;
                }
            }

            var propertyGroup = new XElement("PropertyGroup",
                                                                                   // Build-related properties
                                             new XElement("Version", api.Version), // TODO: Version, or VersionPrefix/VersionSuffix?
                                             new XElement("TargetFrameworks", targetFrameworks),
                                             new XElement("GenerateDocumentationFile", api.Type != ApiType.Analyzers),
                                             // Package-related properties
                                             new XElement("Description", api.Description),
                                             new XElement("PackageTags", string.Join(";", api.Tags.Concat(new[] { "Google", "Cloud" }))),
                                             new XElement("Copyright", $"Copyright {DateTime.UtcNow.Year} Google LLC")
                                             );

            if (dependencies.ContainsKey(GrpcPackage))
            {
                propertyGroup.Add(new XElement("CodeAnalysisRuleSet", "..\\..\\..\\grpc.ruleset"));
            }
            var dependenciesElement = CreateDependenciesElement(api.Id, dependencies, api.IsReleaseVersion, testProject: false, apiNames: apiNames);

            if (api.Type == ApiType.Analyzers)
            {
                propertyGroup.Add(new XElement("IncludeBuildOutput", false));

                void AddPackFile(string includePath, string packagePath)
                {
                    dependenciesElement.Add(
                        new XElement("None",
                                     new XAttribute("Include", includePath),
                                     new XAttribute("Pack", "true"),
                                     new XAttribute("PackagePath", packagePath),
                                     new XAttribute("Visible", "false")));
                }

                AddPackFile(
                    $"$(OutputPath)\\{AnalyzersTargetFramework}\\$(AssemblyName).dll",
                    "analyzers/dotnet/cs");

                // Add install scripts as per
                // https://docs.microsoft.com/en-us/nuget/reference/analyzers-conventions#install-and-uninstall-scripts
                // Name each file rather than using a wildcard so 'dotnet pack' will error out if the files are missing
                // for some reason.
                AddPackFile(
                    @"..\..\..\analyzerScripts\install.ps1",
                    "tools");
                AddPackFile(
                    @"..\..\..\analyzerScripts\uninstall.ps1",
                    "tools");
            }
            WriteProjectFile(api, directory, propertyGroup, dependenciesElement);
        }
Beispiel #28
0
        private static void GenerateMainProject(ApiMetadata api, string directory, HashSet <string> apiNames)
        {
            if (api.Version == null)
            {
                throw new UserErrorException($"No version specified for {api.Id}");
            }
            string targetFrameworks = api.TargetFrameworks;

            var dependencies = new SortedList <string, string>(CommonHiddenProductionDependencies);

            switch (api.Type)
            {
            case "rest":
                dependencies.Add("Google.Api.Gax.Rest", DefaultVersionValue);
                targetFrameworks = targetFrameworks ?? DefaultRestTargetFrameworks;
                break;

            case "grpc":
                dependencies.Add("Google.Api.Gax.Grpc", DefaultVersionValue);
                dependencies.Add("Grpc.Core", DefaultVersionValue);
                targetFrameworks = targetFrameworks ?? DefaultGrpcTargetFrameworks;
                break;
            }

            // Deliberately not using Add, so that a project can override the defaults.
            // In particular, stable releases *must* override versions of GRPC and GAX.
            foreach (var dependency in api.Dependencies)
            {
                dependencies[dependency.Key] = dependency.Value;
            }

            var propertyGroup = new XElement("PropertyGroup",
                                                                                   // Build-related properties
                                             new XElement("Version", api.Version), // TODO: Version, or VersionPrefix/VersionSuffix?
                                             new XElement("TargetFrameworks", targetFrameworks),
                                             new XElement("TargetFrameworks", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), AnyDesktopFramework.Replace(targetFrameworks, "")),
                                             new XElement("LangVersion", "latest"),
                                             new XElement("Features", "IOperation"),
                                             new XElement("GenerateDocumentationFile", true),
                                             new XElement("AssemblyOriginatorKeyFile", "../../GoogleApis.snk"),
                                             new XElement("SignAssembly", true),
                                             new XElement("Deterministic", true),
                                             new XElement("PublicSign", new XAttribute("Condition", " '$(OS)' != 'Windows_NT' "), true),
                                             new XElement("TreatWarningsAsErrors", true),
                                             // Package-related properties
                                             new XElement("Description", api.Description),
                                             new XElement("PackageTags", string.Join(";", api.Tags.Concat(new[] { "Google", "Cloud" }))),
                                             new XElement("Copyright", "Copyright 2017 Google Inc."),
                                             new XElement("Authors", "Google Inc."),
                                             new XElement("PackageIconUrl", "https://cloud.google.com/images/gcp-icon-64x64.png"),
                                             new XElement("PackageLicenseUrl", "http://www.apache.org/licenses/LICENSE-2.0"),
                                             new XElement("PackageProjectUrl", "https://github.com/GoogleCloudPlatform/google-cloud-dotnet"),
                                             new XElement("RepositoryType", "git"),
                                             new XElement("RepositoryUrl", "https://github.com/GoogleCloudPlatform/google-cloud-dotnet")
                                             );

            if (dependencies.ContainsKey(GrpcPackage))
            {
                propertyGroup.Add(new XElement("CodeAnalysisRuleSet", "..\\..\\..\\grpc.ruleset"));
            }
            var packingElement = new XElement("ItemGroup",
                                              new XAttribute("Label", DotnetPackInstructionsLabel),
                                              targetFrameworks.Split(';').Select(tfm => new XElement("Content",
                                                                                                     new XAttribute("Include", $@"$(OutputPath){tfm}\$(PackageId).pdb"),
                                                                                                     new XElement("Pack", true),
                                                                                                     new XElement("PackagePath", $"lib/{tfm}")
                                                                                                     ))
                                              );
            var dependenciesElement = CreateDependenciesElement(api.Id, dependencies, api.IsReleaseVersion, testProject: false, apiNames: apiNames);

            WriteProjectFile(api, directory, propertyGroup, dependenciesElement, packingElement);
        }
Beispiel #29
0
 private static string GetTestTargetFrameworks(ApiMetadata api) => api.TestTargetFrameworks ??
 api.TargetFrameworks ?? DefaultTestTargetFrameworks;