Skip to content

Commit c0b5646

Browse files
feat: gapic-generator-java to perform a no-op when no services are detected (#2460)
Fixes #2050 Adds behavior to gracefully perform a NOOP if no services are contained in the generation request. ## Confimation in hermetic build scripts From `generate_library.sh` against `google/cloud/alloydb/connectors/v1` ``` + /usr/local/google/home/diegomarquezp/Desktop/sdk2/sdk3/sdk-platform-java/library_generation/output/protobuf-25.2/bin/protoc --experimental_allow_proto3_optional --plugin=protoc-gen-java_gapic=/usr/local/google/home/diegomarquezp/.pyenv/versions/3.11.0/lib/python3.11/site-packages/library_generation/gapic-generator-java-wrapper --java_gapic_out=metadata:/usr/local/google/home/diegomarquezp/Desktop/sdk2/sdk3/sdk-platform-java/library_generation/output/temp_preprocessed/java_gapic_srcjar_raw.srcjar.zip --java_gapic_opt=transport=grpc,rest-numeric-enums,grpc-service-config=,gapic-config=,api-service-config=google/cloud/alloydb/connectors/v1/connectors_v1.yaml google/cloud/alloydb/connectors/v1/resources.proto google/cloud/common_resources.proto Apr 05, 2024 9:33:22 PM com.google.api.generator.gapic.protoparser.Parser parse WARNING: No services found to generate. This will produce an empty zip file Apr 05, 2024 9:33:22 PM com.google.api.generator.gapic.composer.ClientLibraryPackageInfoComposer generatePackageInfo WARNING: Generating empty package info since no services were found + did_generate_gapic=true + zipinfo -t /usr/local/google/home/diegomarquezp/Desktop/sdk2/sdk3/sdk-platform-java/library_generation/output/temp_preprocessed/java_gapic_srcjar_raw.srcjar.zip Empty zipfile. + did_generate_gapic=false + [[ false == \t\r\u\e ]] ``` I made some changes to library_generation but I moved them to a follow up PR (#2599) so the checks can pass here.
1 parent e508ae6 commit c0b5646

File tree

10 files changed

+206
-59
lines changed

10 files changed

+206
-59
lines changed

gapic-generator-java/src/main/java/com/google/api/generator/Main.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.api.generator;
1616

17+
import static com.google.api.generator.gapic.protowriter.Writer.EMPTY_RESPONSE;
18+
1719
import com.google.api.generator.gapic.Generator;
1820
import com.google.protobuf.ExtensionRegistry;
1921
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest;
@@ -26,6 +28,8 @@ public static void main(String[] args) throws IOException {
2628
ProtoRegistry.registerAllExtensions(registry);
2729
CodeGeneratorRequest request = CodeGeneratorRequest.parseFrom(System.in, registry);
2830
CodeGeneratorResponse response = Generator.generateGapic(request);
29-
response.writeTo(System.out);
31+
if (response != EMPTY_RESPONSE) {
32+
response.writeTo(System.out);
33+
}
3034
}
3135
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/ClientLibraryPackageInfoComposer.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@
2929
import com.google.api.generator.gapic.model.GapicPackageInfo;
3030
import com.google.api.generator.gapic.model.Sample;
3131
import com.google.api.generator.gapic.model.Service;
32-
import com.google.common.base.Preconditions;
3332
import com.google.common.base.Strings;
33+
import java.util.logging.Logger;
3434
import javax.annotation.Generated;
3535

3636
public class ClientLibraryPackageInfoComposer {
37+
38+
private static final Logger LOGGER =
39+
Logger.getLogger(ClientLibraryPackageInfoComposer.class.getName());
40+
3741
private static final String DIVIDER = "=======================";
3842

3943
private static final String PACKAGE_INFO_DESCRIPTION =
@@ -44,7 +48,10 @@ public class ClientLibraryPackageInfoComposer {
4448
private static final String SERVICE_DESCRIPTION_HEADER_PATTERN = "Service Description: %s";
4549

4650
public static GapicPackageInfo generatePackageInfo(GapicContext context) {
47-
Preconditions.checkState(!context.services().isEmpty(), "No services found to generate");
51+
if (!context.containsServices()) {
52+
LOGGER.warning("Generating empty package info since no services were found");
53+
return null;
54+
}
4855
// Pick some service's package, as we assume they are all the same.
4956
String libraryPakkage = context.services().get(0).pakkage();
5057

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/Composer.java

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public static List<GapicClass> composeServiceClasses(GapicContext context) {
5353
}
5454

5555
public static GapicPackageInfo composePackageInfo(GapicContext context) {
56+
if (!context.containsServices()) {
57+
return null;
58+
}
5659
return addApacheLicense(ClientLibraryPackageInfoComposer.generatePackageInfo(context));
5760
}
5861

gapic-generator-java/src/main/java/com/google/api/generator/gapic/model/GapicContext.java

+15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Collections;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Optional;
2425
import java.util.Set;
2526
import java.util.TreeMap;
2627
import java.util.stream.Collectors;
@@ -32,6 +33,16 @@ public abstract class GapicContext {
3233
// it iteratively as we generate client methods.
3334
private GapicMetadata gapicMetadata = defaultGapicMetadata();
3435

36+
public static final GapicContext EMPTY =
37+
builder()
38+
.setServices(Collections.emptyList())
39+
.setMessages(Collections.emptyMap())
40+
.setServiceConfig(GapicServiceConfig.create(Optional.empty()))
41+
.setResourceNames(Collections.emptyMap())
42+
.setHelperResourceNames(Collections.emptySet())
43+
.setTransport(Transport.GRPC)
44+
.build();
45+
3546
// Maps the message name (as it appears in the protobuf) to Messages.
3647
public abstract ImmutableMap<String, Message> messages();
3748

@@ -59,6 +70,10 @@ public GapicMetadata gapicMetadata() {
5970
@Nullable
6071
public abstract com.google.api.Service serviceYamlProto();
6172

73+
public boolean containsServices() {
74+
return !services().isEmpty();
75+
}
76+
6277
public boolean hasServiceYamlProto() {
6378
return serviceYamlProto() != null;
6479
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/protoparser/Parser.java

+16-9
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,18 @@
7979
import java.util.HashSet;
8080
import java.util.List;
8181
import java.util.Map;
82+
import java.util.Map.Entry;
8283
import java.util.Objects;
8384
import java.util.Optional;
8485
import java.util.Set;
8586
import java.util.function.Function;
87+
import java.util.logging.Logger;
8688
import java.util.stream.Collectors;
8789
import java.util.stream.IntStream;
8890

8991
public class Parser {
92+
93+
private static final Logger LOGGER = Logger.getLogger(Parser.class.getName());
9094
private static final String COMMA = ",";
9195
private static final String COLON = ":";
9296
private static final String DEFAULT_PORT = "443";
@@ -175,7 +179,10 @@ public static GapicContext parse(CodeGeneratorRequest request) {
175179
mixinServices,
176180
transport);
177181

178-
Preconditions.checkState(!services.isEmpty(), "No services found to generate");
182+
if (services.isEmpty()) {
183+
LOGGER.warning("No services found to generate. This will cause a no-op (no files generated)");
184+
return GapicContext.EMPTY;
185+
}
179186

180187
// TODO(vam-google): Figure out whether we should keep this allowlist or bring
181188
// back the unused resource names for all APIs.
@@ -1102,7 +1109,8 @@ private static Map<String, FileDescriptor> getFilesToGenerate(CodeGeneratorReque
11021109
return fileDescriptors;
11031110
}
11041111

1105-
private static String parseServiceJavaPackage(CodeGeneratorRequest request) {
1112+
@VisibleForTesting
1113+
static String parseServiceJavaPackage(CodeGeneratorRequest request) {
11061114
Map<String, Integer> javaPackageCount = new HashMap<>();
11071115
Map<String, FileDescriptor> fileDescriptors = getFilesToGenerate(request);
11081116
for (String fileToGenerate : request.getFileToGenerateList()) {
@@ -1135,13 +1143,12 @@ private static String parseServiceJavaPackage(CodeGeneratorRequest request) {
11351143
processedJavaPackageCount = javaPackageCount;
11361144
}
11371145

1138-
String finalJavaPackage =
1139-
processedJavaPackageCount.entrySet().stream()
1140-
.max(Map.Entry.comparingByValue())
1141-
.get()
1142-
.getKey();
1143-
Preconditions.checkState(
1144-
!Strings.isNullOrEmpty(finalJavaPackage), "No service Java package found");
1146+
String finalJavaPackage = "";
1147+
Optional<Entry<String, Integer>> finalPackageEntry =
1148+
processedJavaPackageCount.entrySet().stream().max(Map.Entry.comparingByValue());
1149+
if (finalPackageEntry.isPresent()) {
1150+
finalJavaPackage = finalPackageEntry.get().getKey();
1151+
}
11451152
return finalJavaPackage;
11461153
}
11471154

gapic-generator-java/src/main/java/com/google/api/generator/gapic/protowriter/Writer.java

+33-19
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,29 @@
3636
import java.util.jar.JarOutputStream;
3737

3838
public class Writer {
39-
static class GapicWriterException extends RuntimeException {
40-
public GapicWriterException(String errorMessage) {
41-
super(errorMessage);
42-
}
4339

40+
static class GapicWriterException extends RuntimeException {
4441
public GapicWriterException(String errorMessage, Throwable cause) {
4542
super(errorMessage, cause);
4643
}
4744
}
4845

49-
public static CodeGeneratorResponse write(
46+
public static final CodeGeneratorResponse EMPTY_RESPONSE = null;
47+
48+
@VisibleForTesting
49+
protected static CodeGeneratorResponse write(
5050
GapicContext context,
5151
List<GapicClass> clazzes,
5252
GapicPackageInfo gapicPackageInfo,
5353
List<ReflectConfig> reflectConfigInfo,
54-
String outputFilePath) {
55-
ByteString.Output output = ByteString.newOutput();
54+
String outputFilePath,
55+
JarOutputStream jos,
56+
ByteString.Output output)
57+
throws IOException {
5658
JavaWriterVisitor codeWriter = new JavaWriterVisitor();
57-
JarOutputStream jos;
58-
try {
59-
jos = new JarOutputStream(output);
60-
} catch (IOException e) {
61-
throw new GapicWriterException(e.getMessage(), e);
59+
60+
if (!context.containsServices()) {
61+
return EMPTY_RESPONSE;
6262
}
6363

6464
for (GapicClass gapicClazz : clazzes) {
@@ -72,12 +72,8 @@ public static CodeGeneratorResponse write(
7272
writeMetadataFile(context, writePackageInfo(gapicPackageInfo, codeWriter, jos), jos);
7373
writeReflectConfigFile(gapicPackageInfo.packageInfo().pakkage(), reflectConfigInfo, jos);
7474

75-
try {
76-
jos.finish();
77-
jos.flush();
78-
} catch (IOException e) {
79-
throw new GapicWriterException(e.getMessage(), e);
80-
}
75+
jos.finish();
76+
jos.flush();
8177

8278
CodeGeneratorResponse.Builder response = CodeGeneratorResponse.newBuilder();
8379
response
@@ -88,6 +84,23 @@ public static CodeGeneratorResponse write(
8884
return response.build();
8985
}
9086

87+
public static CodeGeneratorResponse write(
88+
GapicContext context,
89+
List<GapicClass> clazzes,
90+
GapicPackageInfo gapicPackageInfo,
91+
List<ReflectConfig> reflectConfigInfo,
92+
String outputFilePath) {
93+
ByteString.Output output = ByteString.newOutput();
94+
CodeGeneratorResponse response;
95+
try (JarOutputStream jos = new JarOutputStream(output)) {
96+
response =
97+
write(context, clazzes, gapicPackageInfo, reflectConfigInfo, outputFilePath, jos, output);
98+
} catch (IOException e) {
99+
throw new GapicWriterException(e.getMessage(), e);
100+
}
101+
return response;
102+
}
103+
91104
@VisibleForTesting
92105
static void writeReflectConfigFile(
93106
String pakkage, List<ReflectConfig> reflectConfigInfo, JarOutputStream jos) {
@@ -167,7 +180,8 @@ private static void writeSamples(
167180
}
168181
}
169182

170-
private static String writePackageInfo(
183+
@VisibleForTesting
184+
static String writePackageInfo(
171185
GapicPackageInfo gapicPackageInfo, JavaWriterVisitor codeWriter, JarOutputStream jos) {
172186
PackageInfoDefinition packageInfo = gapicPackageInfo.packageInfo();
173187
packageInfo.accept(codeWriter);

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/ClientLibraryPackageInfoComposerTest.java

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.api.generator.gapic.composer;
1616

17+
import static org.junit.jupiter.api.Assertions.assertNull;
18+
1719
import com.google.api.generator.engine.writer.JavaWriterVisitor;
1820
import com.google.api.generator.gapic.model.GapicContext;
1921
import com.google.api.generator.gapic.model.GapicPackageInfo;
@@ -39,4 +41,9 @@ void composePackageInfo_showcase() {
3941
GoldenFileWriter.getGoldenDir(this.getClass()), "ShowcaseWithEchoPackageInfo.golden");
4042
Assert.assertCodeEquals(goldenFilePath, visitor.write());
4143
}
44+
45+
@Test
46+
void testGeneratePackageInfo_noServices_returnsNullPackageInfo() {
47+
assertNull(ClientLibraryPackageInfoComposer.generatePackageInfo(GapicContext.EMPTY));
48+
}
4249
}

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/ComposerTest.java

+33-15
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
package com.google.api.generator.gapic.composer;
1616

17-
import static org.junit.Assert.assertEquals;
18-
import static org.junit.Assert.assertFalse;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertNull;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
1921

2022
import com.google.api.generator.engine.ast.ClassDefinition;
2123
import com.google.api.generator.engine.ast.ScopeNode;
@@ -35,6 +37,7 @@
3537
import java.nio.file.Paths;
3638
import java.util.Arrays;
3739
import java.util.List;
40+
import org.junit.jupiter.api.BeforeEach;
3841
import org.junit.jupiter.api.Test;
3942

4043
class ComposerTest {
@@ -53,8 +56,13 @@ class ComposerTest {
5356
.build();
5457
private List<Sample> ListofSamples = Arrays.asList(new Sample[] {sample});
5558

59+
@BeforeEach
60+
void initialSanityCheck() {
61+
assertTrue(context.containsServices());
62+
}
63+
5664
@Test
57-
void gapicClass_addApacheLicense() {
65+
public void gapicClass_addApacheLicense_validInput_succeeds() {
5866
ClassDefinition classDef =
5967
ClassDefinition.builder()
6068
.setPackageString("com.google.showcase.v1beta1.stub")
@@ -84,14 +92,14 @@ void composeSamples_showcase() {
8492
assertFalse(composedSamples.isEmpty());
8593
for (Sample sample : composedSamples) {
8694
assertEquals(
87-
"File header should be APACHE",
8895
Arrays.asList(CommentComposer.APACHE_LICENSE_COMMENT),
89-
sample.fileHeader());
96+
sample.fileHeader(),
97+
"File header should be APACHE");
9098
assertEquals(
91-
"ApiShortName should be Localhost7469",
9299
"Localhost7469",
93-
sample.regionTag().apiShortName());
94-
assertEquals("ApiVersion should be V1Beta1", "V1Beta1", sample.regionTag().apiVersion());
100+
sample.regionTag().apiShortName(),
101+
"ApiShortName should be Localhost7469");
102+
assertEquals("V1Beta1", sample.regionTag().apiVersion(), "ApiVersion should be V1Beta1");
95103
}
96104
}
97105

@@ -120,10 +128,10 @@ void composeSamples_parseProtoPackage() {
120128

121129
for (Sample sample : composedSamples) {
122130
assertEquals(
123-
"ApiShortName should be Accessapproval",
131+
"Accessapproval",
124132
sample.regionTag().apiShortName(),
125-
"Accessapproval");
126-
assertEquals("ApiVersion should be V1", sample.regionTag().apiVersion(), "V1");
133+
"ApiShortName should be Accessapproval");
134+
assertEquals("V1", sample.regionTag().apiVersion(), "ApiVersion should be V1");
127135
}
128136

129137
protoPack = "google.cloud.vision.v1p1beta1";
@@ -136,8 +144,8 @@ void composeSamples_parseProtoPackage() {
136144
assertFalse(composedSamples.isEmpty());
137145

138146
for (Sample sample : composedSamples) {
139-
assertEquals("ApiShortName should be Vision", sample.regionTag().apiShortName(), "Vision");
140-
assertEquals("ApiVersion should be V1P1Beta1", sample.regionTag().apiVersion(), "V1P1Beta1");
147+
assertEquals("Vision", sample.regionTag().apiShortName(), "ApiShortName should be Vision");
148+
assertEquals("V1P1Beta1", sample.regionTag().apiVersion(), "ApiVersion should be V1P1Beta1");
141149
}
142150

143151
protoPack = "google.cloud.vision";
@@ -149,11 +157,21 @@ void composeSamples_parseProtoPackage() {
149157
assertFalse(composedSamples.isEmpty());
150158

151159
for (Sample sample : composedSamples) {
152-
assertEquals("ApiShortName should be Vision", sample.regionTag().apiShortName(), "Vision");
153-
assertEquals("ApiVersion should be empty", sample.regionTag().apiVersion(), "");
160+
assertEquals("Vision", sample.regionTag().apiShortName(), "ApiShortName should be Vision");
161+
assertTrue(sample.regionTag().apiVersion().isEmpty(), "ApiVersion should be empty");
154162
}
155163
}
156164

165+
@Test
166+
void testEmptyGapicContext_doesNotThrow() {
167+
assertTrue(Composer.composeServiceClasses(GapicContext.EMPTY).isEmpty());
168+
}
169+
170+
@Test
171+
void testComposePackageInfo_emptyGapicContext_returnsNull() {
172+
assertNull(Composer.composePackageInfo(GapicContext.EMPTY));
173+
}
174+
157175
private List<GapicClass> getTestClassListFromService(Service testService) {
158176
GapicClass testClass =
159177
GrpcServiceCallableFactoryClassComposer.instance()

0 commit comments

Comments
 (0)