Esempio n. 1
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);
        }
Esempio n. 2
0
 public Release(StructuredVersion version, Commit tagCommit, IEnumerable <GitCommit> commits)
 {
     Version = version;
     Commits = commits.ToList().AsReadOnly();
     // Assume that if a release isn't tagged yet, it's because we're releasing it today.
     ReleaseDate = tagCommit?.GetDate().UtcDateTime ?? DateTime.Today;
 }
        /// <summary>
        /// Updates the dependencies in an API for known packages, but only if the default
        /// version is later than the current one, with the same major version number.
        /// </summary>
        public static void UpdateDependencies(ApiCatalog catalog, ApiMetadata api)
        {
            // Update any previously-defaulted versions to be explicit, if the new version is GA.
            // (This only affects production dependencies, so is not performed in UpdateDependencyDictionary.)
            // Implicit dependencies are always present in DefaultPackageVersions, so we don't need to worry about
            // "internal" dependencies.
            if (api.IsReleaseVersion && PackageTypeToImplicitDependencies.TryGetValue(api.Type, out var implicitDependencies))
            {
                foreach (var implicitDependency in implicitDependencies)
                {
                    if (!api.Dependencies.ContainsKey(implicitDependency))
                    {
                        api.Dependencies[implicitDependency] = DefaultPackageVersions[implicitDependency];
                    }
                }
            }

            UpdateDependencyDictionary(api.Dependencies, "dependencies");
            UpdateDependencyDictionary(api.TestDependencies, "testDependencies");

            void UpdateDependencyDictionary(SortedDictionary <string, string> dependencies, string jsonName)
            {
                if (dependencies.Count == 0)
                {
                    return;
                }

                // We want to update any dependencies to "external" packages as listed in DefaultPackageVersions,
                // but also "internal" packages such as Google.LongRunning.
                Dictionary <string, string> allDefaultPackageVersions = DefaultPackageVersions
                                                                        .Concat(catalog.Apis.Select(api => new KeyValuePair <string, string>(api.Id, api.Version)))
                                                                        .ToDictionary(pair => pair.Key, pair => pair.Value);

                foreach (var package in dependencies.Keys.ToList())
                {
                    if (allDefaultPackageVersions.TryGetValue(package, out var defaultVersion))
                    {
                        var currentVersion = dependencies[package];
                        if (currentVersion == DefaultVersionValue ||
                            currentVersion == ProjectVersionValue ||
                            defaultVersion == currentVersion)
                        {
                            continue;
                        }
                        var structuredDefaultVersion = StructuredVersion.FromString(defaultVersion);
                        var structuredCurrentVersion = StructuredVersion.FromString(currentVersion);
                        if (structuredDefaultVersion.CompareTo(structuredCurrentVersion) > 0 &&
                            structuredDefaultVersion.Major == structuredCurrentVersion.Major)
                        {
                            dependencies[package] = defaultVersion;
                        }
                    }
                }

                if (api.Json is object)
                {
                    api.Json[jsonName] = new JObject(dependencies.Select(pair => new JProperty(pair.Key, pair.Value)));
                }
            }
        }
Esempio n. 4
0
        private static void MaybeShowLagging(Repository repo, List <Tag> allTags, ApiMetadata api)
        {
            var currentVersion = api.StructuredVersion;

            // Skip anything that is naturally pre-release (in the API), or where the current release is GA already.
            if (!api.CanHaveGaRelease || currentVersion.Prerelease is null)
            {
                return;
            }

            // Find all the existing prereleases for the expected "next GA" release.
            var    expectedGa          = StructuredVersion.FromMajorMinorPatch(currentVersion.Major, currentVersion.Minor, currentVersion.Patch, prerelease: null);
            string expectedGaPrefix    = $"{api.Id}-{expectedGa}";
            var    matchingReleaseTags = allTags.Where(tag => tag.FriendlyName.StartsWith(expectedGaPrefix, StringComparison.Ordinal)).ToList();

            // Skip if we haven't even released the current prerelease.
            if (matchingReleaseTags.Count == 0)
            {
                return;
            }

            var latest   = GitHelpers.GetDate(matchingReleaseTags.First());
            var earliest = GitHelpers.GetDate(matchingReleaseTags.Last());

            string dateRange = latest == earliest
                ? $"{latest:yyyy-MM-dd}"
                : $"{earliest:yyyy-MM-dd} - {latest:yyyy-MM-dd}";

            Console.WriteLine($"{api.Id,-50}{api.Version,-20}{dateRange}");
        }
Esempio n. 5
0
        public void Construction(string version, int major, int minor, int patch, string prerelease)
        {
            var structured = new StructuredVersion(version);

            Assert.AreEqual(major, structured.Major);
            Assert.AreEqual(minor, structured.Minor);
            Assert.AreEqual(patch, structured.Patch);
            Assert.AreEqual(prerelease, structured.Prerelease);
        }
Esempio n. 6
0
        public void Compare_Equal(string value)
        {
            StructuredVersion v1 = new StructuredVersion(value);
            StructuredVersion v2 = new StructuredVersion(value);

            Assert.AreEqual(0, v1.CompareTo(v2));
            Assert.AreEqual(v1, v2);
            Assert.AreEqual(v1.GetHashCode(), v2.GetHashCode());
        }
Esempio n. 7
0
 public Release(StructuredVersion version, Commit tagCommit, IEnumerable <GitCommit> commits)
 {
     Version = version;
     Commits = commits.ToList().AsReadOnly();
     // Assume that if a release isn't tagged yet, it's because we're releasing it today.
     ReleaseDate = tagCommit != null
         ? (tagCommit.Author ?? tagCommit.Committer).When.Date
         : DateTime.Today;
 }
Esempio n. 8
0
        public void Compare(string earlier, string later)
        {
            StructuredVersion earlierVersion = new StructuredVersion(earlier);
            StructuredVersion laterVersion   = new StructuredVersion(later);

            Assert.That(earlierVersion.CompareTo(laterVersion), Is.LessThan(0));
            Assert.That(laterVersion.CompareTo(earlierVersion), Is.GreaterThan(0));
            Assert.AreNotEqual(laterVersion, earlierVersion);
            Assert.AreNotEqual(laterVersion.GetHashCode(), earlierVersion.GetHashCode());
        }
Esempio n. 9
0
        private static IEnumerable <Release> LoadReleases(Repository repo, ApiMetadata api)
        {
            var id          = api.Id;
            var pathPrefix  = $"apis/{id}/{id}/";
            var projectFile = $"apis/{id}/{id}/{id}.csproj";
            // Some versions return forward slashes, some return backslashes :(
            Func <string, bool> pathFilter = path => path.Replace('\\', '/').StartsWith(pathPrefix) && path != projectFile;

            List <Release>    releases         = new List <Release>();
            StructuredVersion currentVersion   = StructuredVersion.FromString(api.Version);
            Commit            currentTagCommit = null;

            // "Pending" as in "haven't been yielded in a release yet"
            List <GitCommit> pendingCommits = new List <GitCommit>();

            var tagPrefix        = $"{id}-";
            var versionsCommitId = repo.Tags
                                   .Where(tag => tag.FriendlyName.StartsWith(tagPrefix))
                                   .ToDictionary(tag => tag.Target.Id, tag => tag.FriendlyName.Substring(tagPrefix.Length));

            foreach (var commit in repo.Head.Commits)
            {
                if (CommitContainsApi(commit))
                {
                    pendingCommits.Add(new GitCommit(commit));
                }
                if (versionsCommitId.TryGetValue(commit.Id, out string version) && !version.StartsWith("0."))
                {
                    yield return(new Release(currentVersion, currentTagCommit, pendingCommits));

                    // Release constructor clones the list, so we're safe to clear it.
                    pendingCommits.Clear();
                    currentTagCommit = commit;
                    currentVersion   = StructuredVersion.FromString(version);
                }
            }

            if (pendingCommits.Count != 0)
            {
                yield return(new Release(currentVersion, currentTagCommit, pendingCommits));
            }

            bool CommitContainsApi(Commit commit)
            {
                if (commit.Parents.Count() != 1)
                {
                    return(false);
                }
                var tree       = commit.Tree;
                var parentTree = commit.Parents.First().Tree;
                var comparison = repo.Diff.Compare <TreeChanges>(parentTree, tree);

                return(comparison.Select(change => change.Path).Any(pathFilter));
            }
        }
        /// <summary>
        /// Returns the appropriate version to include in a package dependency.
        /// For Google.* and Grpc.*, this is major-version pinned. For other packages, we just leave it as the version
        /// specified in the string - as some packages are fine to upgrade beyond major version boundaries.
        /// </summary>
        private static string GetDependencyVersionRange(string package, string specifiedVersion)
        {
            if (!(package.StartsWith("Google.") || package.StartsWith("Grpc.")))
            {
                return(specifiedVersion);
            }
            var structuredVersion = StructuredVersion.FromString(specifiedVersion);
            var nextMajor         = StructuredVersion.FromMajorMinorPatchBuild(structuredVersion.Major + 1, 0, 0, structuredVersion.Build is null ? default(int?) : 0, null);

            return($"[{structuredVersion}, {nextMajor})");
        }
        /// <summary>
        /// Updates the dependencies in an API for known packages, but only if the default
        /// version is later than the current one, with the same major version number.
        /// </summary>
        public static void UpdateDependencies(ApiCatalog catalog, ApiMetadata api)
        {
            UpdateDependencyDictionary(api.Dependencies, "dependencies");
            UpdateDependencyDictionary(api.TestDependencies, "testDependencies");

            void UpdateDependencyDictionary(SortedDictionary <string, string> dependencies, string jsonName)
            {
                if (dependencies.Count == 0)
                {
                    return;
                }

                // We want to update any dependencies to "external" packages as listed in DefaultPackageVersions,
                // but also "internal" packages such as Google.LongRunning.
                Dictionary <string, string> allDefaultPackageVersions = DefaultPackageVersions
                                                                        .Concat(catalog.Apis.Select(api => new KeyValuePair <string, string>(api.Id, api.Version)))
                                                                        .ToDictionary(pair => pair.Key, pair => pair.Value);

                foreach (var package in dependencies.Keys.ToList())
                {
                    if (allDefaultPackageVersions.TryGetValue(package, out var defaultVersion))
                    {
                        var currentVersion = dependencies[package];
                        if (currentVersion == DefaultVersionValue ||
                            currentVersion == ProjectVersionValue ||
                            defaultVersion == currentVersion)
                        {
                            continue;
                        }
                        var structuredDefaultVersion = StructuredVersion.FromString(defaultVersion);
                        var structuredCurrentVersion = StructuredVersion.FromString(currentVersion);
                        if (structuredDefaultVersion.CompareTo(structuredCurrentVersion) > 0 &&
                            structuredDefaultVersion.Major == structuredCurrentVersion.Major)
                        {
                            dependencies[package] = defaultVersion;
                        }
                    }
                }
                if (api.Json is object)
                {
                    api.Json[jsonName] = new JObject(dependencies.Select(pair => new JProperty(pair.Key, pair.Value)));
                }
            }
        }
Esempio n. 12
0
 /// <summary>
 /// Returns the previous version that the given new version *must* be compatible with,
 /// and the required level of compatibility. The version part is null if no compatibility
 /// check is required.
 /// </summary>
 private static (StructuredVersion version, Level level) GetRequiredCompatibility(StructuredVersion newVersion)
 {
     if (newVersion.Patch != 0)
     {
         // A patch version must be identical to major.minor.0.
         var oldVersion = new StructuredVersion(newVersion.Major, newVersion.Minor, 0, null);
         return(oldVersion, Level.Identical);
     }
     else if (newVersion.Minor == 0)
     {
         // A new major version doesn't need to check anything.
         return(null, Level.Identical);
     }
     else
     {
         // A new minor version must be compatible with the previous minor version
         var oldVersion = new StructuredVersion(newVersion.Major, newVersion.Minor - 1, 0, null);
         return(oldVersion, Level.Minor);
     }
 }
        protected override void ExecuteImpl(string[] args)
        {
            string id = args[0];

            // It's slightly inefficient that we load the API catalog once here and once later on, and the code duplication
            // is annoying too, but it's insignficant really - and at least the code is simple.
            var catalog = ApiCatalog.Load();
            var api     = catalog[id];
            var version = IncrementStructuredVersion(api.StructuredVersion).ToString();

            new SetVersionCommand().Execute(new[] { id, version });

            StructuredVersion IncrementStructuredVersion(StructuredVersion originalVersion)
            {
                // Any GA version just increments the minor version.
                if (originalVersion.Prerelease is null)
                {
                    return(StructuredVersion.FromMajorMinorPatch(originalVersion.Major, originalVersion.Minor + 1, 0, null));
                }

                // For prereleases, expect something like "beta01" which should be incremented to "beta02".
                var prereleasePattern = new Regex(@"^([^\d]*)(\d+)$");
                var match             = prereleasePattern.Match(originalVersion.Prerelease);

                if (!match.Success)
                {
                    throw new UserErrorException($"Don't know how to auto-increment version '{originalVersion}'");
                }
                var prefix = match.Groups[1].Value;
                var suffix = match.Groups[2].Value;

                if (!int.TryParse(suffix, out var counter))
                {
                    throw new UserErrorException($"Don't know how to auto-increment version '{originalVersion}'");
                }
                counter++;
                var newSuffix = counter.ToString().PadLeft(suffix.Length, '0');

                return(StructuredVersion.FromMajorMinorPatch(originalVersion.Major, originalVersion.Minor, originalVersion.Patch, $"{prefix}{newSuffix}"));
            }
        }
Esempio n. 14
0
        // Visible for testing
        static internal IEnumerable <(StructuredVersion, Level)> GetComparisons(StructuredVersion newVersion, List <StructuredVersion> taggedVersions)
        {
            // e.g. 1.0.0-beta00, 2.0.0-beta00. Initial not-even-beta libraries, and preparing for new major version.
            var isNonRelease = newVersion.IsNonRelease;

            // Whether pre-release or not, and whether new or not, if there's a previous stable version we should compare against it.
            // Exception: if the latest stable version is the new version, we don't need to compare here as we'll compare later.
            // (And the natural comparison here would be incorrect, as it would insist on an identical comparison.)
            if (newVersion.Major > 1 || newVersion.Minor > 0 || newVersion.Patch > 0)
            {
                var latestStable = taggedVersions.LastOrDefault(v => v.IsStable);
                if (latestStable is object && !latestStable.Equals(newVersion))
                {
                    var level =
                        newVersion.Major != latestStable.Major ? Level.Major
                        : newVersion.Minor != latestStable.Minor ? Level.Minor
                        : Level.Identical;
                    yield return(latestStable, level);
                }
            }

            // If the new version is already a tag, we compare with that.
            var compareWithSelf = taggedVersions.Contains(newVersion);

            if (compareWithSelf)
            {
                yield return(newVersion, newVersion.IsStable ? Level.Minor : Level.Major);
            }
            else if (!newVersion.IsNonRelease)
            {
                // This is a release PR: the version doesn't already exist, and it isn't a *-beta00 version.
                // If there's any existing release with the same major/minor/patch (which must necessarily be a pre-release), compare with that, allowing any changes.
                // (We're already checking against the appropriate stable version, if any.)
                var latestSameMajorMinorPatch = taggedVersions.LastOrDefault(v => v.Major == newVersion.Major && v.Minor == newVersion.Minor && v.Patch == newVersion.Patch);
                if (latestSameMajorMinorPatch is object)
                {
                    yield return(latestSameMajorMinorPatch, Level.Major);
                }
            }
        }
        private static IEnumerable <Release> LoadReleases(Repository repo, ApiCatalog catalog, ApiMetadata api)
        {
            var id = api.Id;
            var commitPredicate = GitHelpers.CreateCommitPredicate(repo, catalog, api);

            List <Release>    releases         = new List <Release>();
            StructuredVersion currentVersion   = StructuredVersion.FromString(api.Version);
            Commit            currentTagCommit = null;

            // "Pending" as in "haven't been yielded in a release yet"
            List <GitCommit> pendingCommits = new List <GitCommit>();

            var tagPrefix        = $"{id}-";
            var versionsCommitId = repo.Tags
                                   .Where(tag => tag.FriendlyName.StartsWith(tagPrefix))
                                   .ToDictionary(tag => tag.Target.Id, tag => tag.FriendlyName.Substring(tagPrefix.Length));

            foreach (var commit in repo.Head.Commits)
            {
                if (commitPredicate(commit))
                {
                    pendingCommits.Add(new GitCommit(commit));
                }
                if (versionsCommitId.TryGetValue(commit.Id, out string version) && !version.StartsWith("0."))
                {
                    yield return(new Release(currentVersion, currentTagCommit, pendingCommits));

                    // Release constructor clones the list, so we're safe to clear it.
                    pendingCommits.Clear();
                    currentTagCommit = commit;
                    currentVersion   = StructuredVersion.FromString(version);
                }
            }

            if (pendingCommits.Count != 0)
            {
                yield return(new Release(currentVersion, currentTagCommit, pendingCommits));
            }
        }
Esempio n. 16
0
 private Release(StructuredVersion version, DateTimeOffset?releaseDate, IEnumerable <GitCommit> commits) =>
 (Version, ReleaseDate, Commits) = (version, (releaseDate ?? DateTimeOffset.Now).UtcDateTime, commits.ToList().AsReadOnly());
Esempio n. 17
0
 public Section(StructuredVersion version, List <string> lines)
 {
     Version = version;
     Lines   = lines;
 }