/// <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); }
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))); } } }
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}"); }
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); }
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()); }
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; }
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()); }
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))); } } }
/// <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}")); } }
// 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)); } }
private Release(StructuredVersion version, DateTimeOffset?releaseDate, IEnumerable <GitCommit> commits) => (Version, ReleaseDate, Commits) = (version, (releaseDate ?? DateTimeOffset.Now).UtcDateTime, commits.ToList().AsReadOnly());
public Section(StructuredVersion version, List <string> lines) { Version = version; Lines = lines; }