From 5fe8dcf2ecac5082db30f2b60761fd4dcda00658 Mon Sep 17 00:00:00 2001 From: Sara Adams <4410333+saraadams@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:36:26 +0100 Subject: [PATCH] [DataProvider] Extract Bazel version from profile, if present (#108) With Bazel release 6.1.0 the Bazel profile includes which version of Bazel generated the profile. This PR adds a DataProvider that extracts that information, if it is included. Fixes #109 --------- Signed-off-by: Sara Adams --- .../bazelprofile/BazelProfileConstants.java | 5 + .../analyzer/dataproviders/BazelVersion.java | 115 +++++++++++++++++ .../BazelVersionDataProvider.java | 78 ++++++++++++ .../dataproviders/DataProviderUtil.java | 1 + .../BazelVersionDataProviderTest.java | 118 ++++++++++++++++++ .../dataproviders/DataProvidersTestSuite.java | 1 + 6 files changed, 318 insertions(+) create mode 100644 analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersion.java create mode 100644 analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProvider.java create mode 100644 analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProviderTest.java diff --git a/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfileConstants.java b/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfileConstants.java index 285cddc..67fbf77 100644 --- a/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfileConstants.java +++ b/analyzer/java/com/engflow/bazel/invocation/analyzer/bazelprofile/BazelProfileConstants.java @@ -61,4 +61,9 @@ public class BazelProfileConstants { // InstantEvent names public static final String INSTANT_FINISHING = "Finishing"; + + // otherData key names + // See + // https://github.com/bazelbuild/bazel/blob/aab19f75cd383c4b09a6ae720f9fa436bf89d271/src/main/java/com/google/devtools/build/lib/profiler/JsonTraceFileWriter.java#L179-L183 + public static final String OTHER_DATA_BAZEL_VERSION = "bazel_version"; } diff --git a/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersion.java b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersion.java new file mode 100644 index 0000000..71b76d3 --- /dev/null +++ b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersion.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 EngFlow Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.engflow.bazel.invocation.analyzer.dataproviders; + +import com.engflow.bazel.invocation.analyzer.core.Datum; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + +/** The Bazel version used when the analyzed inputs were generated. */ +public class BazelVersion implements Datum { + private final Optional major; + private final Optional minor; + private final Optional patch; + private final Optional preReleaseAnnotation; + @Nullable private final String emptyReason; + + BazelVersion(int major, int minor, int patch, String preReleaseAnnotation) { + Preconditions.checkNotNull(preReleaseAnnotation); + this.major = Optional.of(major); + this.minor = Optional.of(minor); + this.patch = Optional.of(patch); + this.preReleaseAnnotation = Optional.of(preReleaseAnnotation); + this.emptyReason = null; + } + + BazelVersion(String emptyReason) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(emptyReason)); + this.major = Optional.empty(); + this.minor = Optional.empty(); + this.patch = Optional.empty(); + this.preReleaseAnnotation = Optional.empty(); + this.emptyReason = emptyReason; + } + + @Override + public boolean isEmpty() { + return emptyReason != null; + } + + @Override + public String getEmptyReason() { + return emptyReason; + } + + public Optional getMajor() { + return major; + } + + public Optional getMinor() { + return minor; + } + + public Optional getPatch() { + return patch; + } + + public Optional getPreReleaseAnnotation() { + return preReleaseAnnotation; + } + + @Override + public String getDescription() { + return "The Bazel version used when the Bazel profile was generated. Extracted from the Bazel" + + " profile."; + } + + public String getSummary() { + return isEmpty() + ? null + : String.format( + "release %d.%d.%d%s", + major.get(), minor.get(), patch.get(), preReleaseAnnotation.get()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BazelVersion that = (BazelVersion) o; + return major.equals(that.major) + && minor.equals(that.minor) + && patch.equals(that.patch) + && preReleaseAnnotation.equals(that.preReleaseAnnotation) + && Objects.equals(emptyReason, that.emptyReason); + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch, preReleaseAnnotation, emptyReason); + } + + @Override + public String toString() { + return isEmpty() ? emptyReason : getSummary(); + } +} diff --git a/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProvider.java b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProvider.java new file mode 100644 index 0000000..b5c741b --- /dev/null +++ b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProvider.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 EngFlow Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.engflow.bazel.invocation.analyzer.dataproviders; + +import static com.engflow.bazel.invocation.analyzer.core.DatumSupplier.memoized; + +import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfile; +import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfileConstants; +import com.engflow.bazel.invocation.analyzer.core.DataProvider; +import com.engflow.bazel.invocation.analyzer.core.DatumSupplierSpecification; +import com.engflow.bazel.invocation.analyzer.core.InvalidProfileException; +import com.engflow.bazel.invocation.analyzer.core.MissingInputException; +import com.engflow.bazel.invocation.analyzer.core.NullDatumException; +import com.google.common.annotations.VisibleForTesting; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link DataProvider} that supplies data on the Bazel version used when the Bazel profile was + * generated. + */ +public class BazelVersionDataProvider extends DataProvider { + // See https://bazel.build/release#bazel-versioning + // See + // https://github.com/bazelbuild/bazel/blob/aab19f75cd383c4b09a6ae720f9fa436bf89d271/src/main/java/com/google/devtools/build/lib/profiler/JsonTraceFileWriter.java#L179 + // See + // https://github.com/bazelbuild/bazel/blob/c637041ec145e0964982a2cbf8d5693f0d1d4be0/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java#L119 + private static final Pattern BAZEL_VERSION_PATTERN = + Pattern.compile("^release (\\d+)\\.(\\d+)\\.(\\d+)(|-.*)$"); + + @Override + public List> getSuppliers() { + return List.of( + DatumSupplierSpecification.of(BazelVersion.class, memoized(this::getBazelVersion))); + } + + public BazelVersion getBazelVersion() + throws InvalidProfileException, MissingInputException, NullDatumException { + BazelProfile bazelProfile = getDataManager().getDatum(BazelProfile.class); + String bazelVersion = + bazelProfile.getOtherData().get(BazelProfileConstants.OTHER_DATA_BAZEL_VERSION); + return parse(bazelVersion); + } + + @VisibleForTesting + static BazelVersion parse(String version) { + if (version == null) { + // The version metadata was introduced in https://github.com/bazelbuild/bazel/pull/17562 and + // added to release 6.1.0. + return new BazelVersion( + "No Bazel version was found. Bazel versions before 6.1.0 did not report the version."); + } + Matcher m = BAZEL_VERSION_PATTERN.matcher(version); + if (m.matches()) { + return new BazelVersion( + Integer.valueOf(m.group(1)), + Integer.valueOf(m.group(2)), + Integer.valueOf(m.group(3)), + m.group(4)); + } else { + return new BazelVersion( + String.format("The provided Bazel version could not be parsed: '%s'", version)); + } + } +} diff --git a/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/DataProviderUtil.java b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/DataProviderUtil.java index 61e0de4..1ac7ef1 100644 --- a/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/DataProviderUtil.java +++ b/analyzer/java/com/engflow/bazel/invocation/analyzer/dataproviders/DataProviderUtil.java @@ -33,6 +33,7 @@ public static List getAllDataProviders() { return List.of( new ActionStatsDataProvider(), new BazelPhasesDataProvider(), + new BazelVersionDataProvider(), new CriticalPathDurationDataProvider(), new EstimatedCoresDataProvider(), new GarbageCollectionStatsDataProvider(), diff --git a/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProviderTest.java b/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProviderTest.java new file mode 100644 index 0000000..953477e --- /dev/null +++ b/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/BazelVersionDataProviderTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 EngFlow Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.engflow.bazel.invocation.analyzer.dataproviders; + +import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.mainThread; +import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.metaData; +import static com.engflow.bazel.invocation.analyzer.WriteBazelProfile.trace; +import static com.google.common.truth.Truth.assertThat; + +import com.engflow.bazel.invocation.analyzer.WriteBazelProfile; +import com.engflow.bazel.invocation.analyzer.bazelprofile.BazelProfileConstants; +import com.engflow.bazel.invocation.analyzer.core.DuplicateProviderException; +import com.engflow.bazel.invocation.analyzer.core.InvalidProfileException; +import com.engflow.bazel.invocation.analyzer.core.MissingInputException; +import com.engflow.bazel.invocation.analyzer.core.NullDatumException; +import org.junit.Before; +import org.junit.Test; + +public class BazelVersionDataProviderTest extends DataProviderUnitTestBase { + private BazelVersionDataProvider provider; + + @Before + public void setupTest() throws Exception { + provider = new BazelVersionDataProvider(); + provider.register(dataManager); + super.dataProvider = provider; + } + + @Test + public void shouldReturnEmptyOnMissingBazelVersion() + throws DuplicateProviderException, + InvalidProfileException, + MissingInputException, + NullDatumException { + useProfile(metaData(), trace(mainThread())); + assertThat(provider.getBazelVersion().isEmpty()).isTrue(); + } + + @Test + public void shouldReturnEmptyOnInvalidBazelVersion() + throws DuplicateProviderException, + InvalidProfileException, + MissingInputException, + NullDatumException { + useProfile( + metaData( + WriteBazelProfile.Property.put( + BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, "invalid")), + trace(mainThread())); + assertThat(provider.getBazelVersion().isEmpty()).isTrue(); + } + + @Test + public void shouldReturnVersionWithoutPreRelease() + throws DuplicateProviderException, + InvalidProfileException, + MissingInputException, + NullDatumException { + String validBazelVersion = "release 6.1.0"; + useProfile( + metaData( + WriteBazelProfile.Property.put( + BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, validBazelVersion)), + trace(mainThread())); + + BazelVersion version = provider.getBazelVersion(); + assertThat(version.isEmpty()).isFalse(); + assertThat(version.getSummary()).isEqualTo(validBazelVersion); + } + + @Test + public void shouldReturnBazelVersionWithPreRelease() + throws DuplicateProviderException, + InvalidProfileException, + MissingInputException, + NullDatumException { + String validBazelVersion = "release 8.0.0-pre.20231030.2"; + useProfile( + metaData( + WriteBazelProfile.Property.put( + BazelProfileConstants.OTHER_DATA_BAZEL_VERSION, validBazelVersion)), + trace(mainThread())); + + BazelVersion version = provider.getBazelVersion(); + assertThat(version.isEmpty()).isFalse(); + assertThat(version.getSummary()).isEqualTo(validBazelVersion); + } + + @Test + public void parseReturnsEmpty() { + assertThat(BazelVersionDataProvider.parse("").isEmpty()).isTrue(); + assertThat(BazelVersionDataProvider.parse("1.2.3").isEmpty()).isTrue(); + assertThat(BazelVersionDataProvider.parse("1.2.3-foo").isEmpty()).isTrue(); + assertThat(BazelVersionDataProvider.parse("release 1").isEmpty()).isTrue(); + assertThat(BazelVersionDataProvider.parse("release 1.2").isEmpty()).isTrue(); + assertThat(BazelVersionDataProvider.parse("release 1.2.3foo").isEmpty()).isTrue(); + } + + @Test + public void parseReturnsNonempty() { + assertThat(BazelVersionDataProvider.parse("release 1.23.4")) + .isEqualTo(new BazelVersion(1, 23, 4, "")); + assertThat(BazelVersionDataProvider.parse("release 12.3.45-foo")) + .isEqualTo(new BazelVersion(12, 3, 45, "-foo")); + } +} diff --git a/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/DataProvidersTestSuite.java b/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/DataProvidersTestSuite.java index 880dc9e..8d3cc9e 100644 --- a/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/DataProvidersTestSuite.java +++ b/analyzer/javatests/com/engflow/bazel/invocation/analyzer/dataproviders/DataProvidersTestSuite.java @@ -22,6 +22,7 @@ ActionStatsDataProviderTest.class, BazelPhasesDataProviderTest.class, BazelPhaseDescriptionsTest.class, + BazelVersionDataProviderTest.class, BazelProfilePhaseTest.class, CriticalPathDurationDataProviderTest.class, EstimatedCoresDataProviderTest.class,