1*55e87721SMatt Gilbride# Copyright 2018 Google LLC 2*55e87721SMatt Gilbride# 3*55e87721SMatt Gilbride# Licensed under the Apache License, Version 2.0 (the "License"); 4*55e87721SMatt Gilbride# you may not use this file except in compliance with the License. 5*55e87721SMatt Gilbride# You may obtain a copy of the License at 6*55e87721SMatt Gilbride# 7*55e87721SMatt Gilbride# https://www.apache.org/licenses/LICENSE-2.0 8*55e87721SMatt Gilbride# 9*55e87721SMatt Gilbride# Unless required by applicable law or agreed to in writing, software 10*55e87721SMatt Gilbride# distributed under the License is distributed on an "AS IS" BASIS, 11*55e87721SMatt Gilbride# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*55e87721SMatt Gilbride# See the License for the specific language governing permissions and 13*55e87721SMatt Gilbride# limitations under the License. 14*55e87721SMatt Gilbride 15*55e87721SMatt Gilbrideimport glob 16*55e87721SMatt Gilbrideimport os 17*55e87721SMatt Gilbrideimport xml.etree.ElementTree as ET 18*55e87721SMatt Gilbrideimport re 19*55e87721SMatt Gilbrideimport requests 20*55e87721SMatt Gilbrideimport yaml 21*55e87721SMatt Gilbrideimport synthtool as s 22*55e87721SMatt Gilbrideimport synthtool.gcp as gcp 23*55e87721SMatt Gilbridefrom synthtool import cache, shell 24*55e87721SMatt Gilbridefrom synthtool.gcp import common, partials, pregenerated, samples, snippets 25*55e87721SMatt Gilbridefrom synthtool.log import logger 26*55e87721SMatt Gilbridefrom pathlib import Path 27*55e87721SMatt Gilbridefrom typing import Any, Optional, Dict, Iterable, List 28*55e87721SMatt Gilbride 29*55e87721SMatt GilbrideJAR_DOWNLOAD_URL = "https://github.com/google/google-java-format/releases/download/google-java-format-{version}/google-java-format-{version}-all-deps.jar" 30*55e87721SMatt GilbrideDEFAULT_FORMAT_VERSION = "1.7" 31*55e87721SMatt GilbrideGOOD_LICENSE = """/* 32*55e87721SMatt Gilbride * Copyright 2020 Google LLC 33*55e87721SMatt Gilbride * 34*55e87721SMatt Gilbride * Licensed under the Apache License, Version 2.0 (the "License"); 35*55e87721SMatt Gilbride * you may not use this file except in compliance with the License. 36*55e87721SMatt Gilbride * You may obtain a copy of the License at 37*55e87721SMatt Gilbride * 38*55e87721SMatt Gilbride * https://www.apache.org/licenses/LICENSE-2.0 39*55e87721SMatt Gilbride * 40*55e87721SMatt Gilbride * Unless required by applicable law or agreed to in writing, software 41*55e87721SMatt Gilbride * distributed under the License is distributed on an "AS IS" BASIS, 42*55e87721SMatt Gilbride * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 43*55e87721SMatt Gilbride * See the License for the specific language governing permissions and 44*55e87721SMatt Gilbride * limitations under the License. 45*55e87721SMatt Gilbride */ 46*55e87721SMatt Gilbride""" 47*55e87721SMatt GilbridePROTOBUF_HEADER = "// Generated by the protocol buffer compiler. DO NOT EDIT!" 48*55e87721SMatt GilbrideBAD_LICENSE = """/\\* 49*55e87721SMatt Gilbride \\* Copyright \\d{4} Google LLC 50*55e87721SMatt Gilbride \\* 51*55e87721SMatt Gilbride \\* Licensed under the Apache License, Version 2.0 \\(the "License"\\); you may not use this file except 52*55e87721SMatt Gilbride \\* in compliance with the License. You may obtain a copy of the License at 53*55e87721SMatt Gilbride \\* 54*55e87721SMatt Gilbride \\* http://www.apache.org/licenses/LICENSE-2.0 55*55e87721SMatt Gilbride \\* 56*55e87721SMatt Gilbride \\* Unless required by applicable law or agreed to in writing, software distributed under the License 57*55e87721SMatt Gilbride \\* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 58*55e87721SMatt Gilbride \\* or implied. See the License for the specific language governing permissions and limitations under 59*55e87721SMatt Gilbride \\* the License. 60*55e87721SMatt Gilbride \\*/ 61*55e87721SMatt Gilbride""" 62*55e87721SMatt GilbrideDEFAULT_MIN_SUPPORTED_JAVA_VERSION = 8 63*55e87721SMatt Gilbride 64*55e87721SMatt Gilbride 65*55e87721SMatt Gilbridedef format_code( 66*55e87721SMatt Gilbride path: str, version: str = DEFAULT_FORMAT_VERSION, times: int = 2 67*55e87721SMatt Gilbride) -> None: 68*55e87721SMatt Gilbride """ 69*55e87721SMatt Gilbride Runs the google-java-format jar against all .java files found within the 70*55e87721SMatt Gilbride provided path. 71*55e87721SMatt Gilbride """ 72*55e87721SMatt Gilbride jar_name = f"google-java-format-{version}.jar" 73*55e87721SMatt Gilbride jar = cache.get_cache_dir() / jar_name 74*55e87721SMatt Gilbride if not jar.exists(): 75*55e87721SMatt Gilbride _download_formatter(version, jar) 76*55e87721SMatt Gilbride 77*55e87721SMatt Gilbride # Find all .java files in path and run the formatter on them 78*55e87721SMatt Gilbride files = list(glob.iglob(os.path.join(path, "**/*.java"), recursive=True)) 79*55e87721SMatt Gilbride 80*55e87721SMatt Gilbride # Run the formatter as a jar file 81*55e87721SMatt Gilbride logger.info("Running java formatter on {} files".format(len(files))) 82*55e87721SMatt Gilbride for _ in range(times): 83*55e87721SMatt Gilbride shell.run(["java", "-jar", str(jar), "--replace"] + files) 84*55e87721SMatt Gilbride 85*55e87721SMatt Gilbride 86*55e87721SMatt Gilbridedef _download_formatter(version: str, dest: Path) -> None: 87*55e87721SMatt Gilbride logger.info("Downloading java formatter") 88*55e87721SMatt Gilbride url = JAR_DOWNLOAD_URL.format(version=version) 89*55e87721SMatt Gilbride response = requests.get(url) 90*55e87721SMatt Gilbride response.raise_for_status() 91*55e87721SMatt Gilbride with open(dest, "wb") as fh: 92*55e87721SMatt Gilbride fh.write(response.content) 93*55e87721SMatt Gilbride 94*55e87721SMatt Gilbride 95*55e87721SMatt GilbrideHEADER_REGEX = re.compile("\\* Copyright \\d{4} Google LLC") 96*55e87721SMatt Gilbride 97*55e87721SMatt Gilbride 98*55e87721SMatt Gilbridedef _file_has_header(path: Path) -> bool: 99*55e87721SMatt Gilbride """Return true if the file already contains a license header.""" 100*55e87721SMatt Gilbride with open(path, "rt") as fp: 101*55e87721SMatt Gilbride for line in fp: 102*55e87721SMatt Gilbride if HEADER_REGEX.search(line): 103*55e87721SMatt Gilbride return True 104*55e87721SMatt Gilbride return False 105*55e87721SMatt Gilbride 106*55e87721SMatt Gilbride 107*55e87721SMatt Gilbridedef _filter_no_header(paths: Iterable[Path]) -> Iterable[Path]: 108*55e87721SMatt Gilbride """Return a subset of files that do not already have a header.""" 109*55e87721SMatt Gilbride for path in paths: 110*55e87721SMatt Gilbride anchor = Path(path.anchor) 111*55e87721SMatt Gilbride remainder = str(path.relative_to(path.anchor)) 112*55e87721SMatt Gilbride for file in anchor.glob(remainder): 113*55e87721SMatt Gilbride if not _file_has_header(file): 114*55e87721SMatt Gilbride yield file 115*55e87721SMatt Gilbride 116*55e87721SMatt Gilbride 117*55e87721SMatt Gilbridedef fix_proto_headers(proto_root: Path) -> None: 118*55e87721SMatt Gilbride """Helper to ensure that generated proto classes have appropriate license headers. 119*55e87721SMatt Gilbride 120*55e87721SMatt Gilbride If the file does not already contain a license header, inject one at the top of the file. 121*55e87721SMatt Gilbride Some resource name classes may contain malformed license headers. In those cases, replace 122*55e87721SMatt Gilbride those with our standard license header. 123*55e87721SMatt Gilbride """ 124*55e87721SMatt Gilbride s.replace( 125*55e87721SMatt Gilbride _filter_no_header([proto_root / "src/**/*.java"]), 126*55e87721SMatt Gilbride PROTOBUF_HEADER, 127*55e87721SMatt Gilbride f"{GOOD_LICENSE}{PROTOBUF_HEADER}", 128*55e87721SMatt Gilbride ) 129*55e87721SMatt Gilbride # https://github.com/googleapis/gapic-generator/issues/3074 130*55e87721SMatt Gilbride s.replace( 131*55e87721SMatt Gilbride [proto_root / "src/**/*Name.java", proto_root / "src/**/*Names.java"], 132*55e87721SMatt Gilbride BAD_LICENSE, 133*55e87721SMatt Gilbride GOOD_LICENSE, 134*55e87721SMatt Gilbride ) 135*55e87721SMatt Gilbride 136*55e87721SMatt Gilbride 137*55e87721SMatt Gilbridedef fix_grpc_headers(grpc_root: Path, package_name: str = "unused") -> None: 138*55e87721SMatt Gilbride """Helper to ensure that generated grpc stub classes have appropriate license headers. 139*55e87721SMatt Gilbride 140*55e87721SMatt Gilbride If the file does not already contain a license header, inject one at the top of the file. 141*55e87721SMatt Gilbride """ 142*55e87721SMatt Gilbride s.replace( 143*55e87721SMatt Gilbride _filter_no_header([grpc_root / "src/**/*.java"]), 144*55e87721SMatt Gilbride "^package (.*);", 145*55e87721SMatt Gilbride f"{GOOD_LICENSE}package \\1;", 146*55e87721SMatt Gilbride ) 147*55e87721SMatt Gilbride 148*55e87721SMatt Gilbride 149*55e87721SMatt Gilbridedef latest_maven_version(group_id: str, artifact_id: str) -> Optional[str]: 150*55e87721SMatt Gilbride """Helper function to find the latest released version of a Maven artifact. 151*55e87721SMatt Gilbride 152*55e87721SMatt Gilbride Fetches metadata from Maven Central and parses out the latest released 153*55e87721SMatt Gilbride version. 154*55e87721SMatt Gilbride 155*55e87721SMatt Gilbride Args: 156*55e87721SMatt Gilbride group_id (str): The groupId of the Maven artifact 157*55e87721SMatt Gilbride artifact_id (str): The artifactId of the Maven artifact 158*55e87721SMatt Gilbride 159*55e87721SMatt Gilbride Returns: 160*55e87721SMatt Gilbride The latest version of the artifact as a string or None 161*55e87721SMatt Gilbride """ 162*55e87721SMatt Gilbride group_path = "/".join(group_id.split(".")) 163*55e87721SMatt Gilbride url = ( 164*55e87721SMatt Gilbride f"https://repo1.maven.org/maven2/{group_path}/{artifact_id}/maven-metadata.xml" 165*55e87721SMatt Gilbride ) 166*55e87721SMatt Gilbride response = requests.get(url) 167*55e87721SMatt Gilbride if response.status_code >= 400: 168*55e87721SMatt Gilbride return "0.0.0" 169*55e87721SMatt Gilbride 170*55e87721SMatt Gilbride return version_from_maven_metadata(response.text) 171*55e87721SMatt Gilbride 172*55e87721SMatt Gilbride 173*55e87721SMatt Gilbridedef version_from_maven_metadata(metadata: str) -> Optional[str]: 174*55e87721SMatt Gilbride """Helper function to parse the latest released version from the Maven 175*55e87721SMatt Gilbride metadata XML file. 176*55e87721SMatt Gilbride 177*55e87721SMatt Gilbride Args: 178*55e87721SMatt Gilbride metadata (str): The XML contents of the Maven metadata file 179*55e87721SMatt Gilbride 180*55e87721SMatt Gilbride Returns: 181*55e87721SMatt Gilbride The latest version of the artifact as a string or None 182*55e87721SMatt Gilbride """ 183*55e87721SMatt Gilbride root = ET.fromstring(metadata) 184*55e87721SMatt Gilbride latest = root.find("./versioning/latest") 185*55e87721SMatt Gilbride if latest is not None: 186*55e87721SMatt Gilbride return latest.text 187*55e87721SMatt Gilbride 188*55e87721SMatt Gilbride return None 189*55e87721SMatt Gilbride 190*55e87721SMatt Gilbride 191*55e87721SMatt Gilbridedef _common_generation( 192*55e87721SMatt Gilbride service: str, 193*55e87721SMatt Gilbride version: str, 194*55e87721SMatt Gilbride library: Path, 195*55e87721SMatt Gilbride package_pattern: str, 196*55e87721SMatt Gilbride suffix: str = "", 197*55e87721SMatt Gilbride destination_name: str = None, 198*55e87721SMatt Gilbride cloud_api: bool = True, 199*55e87721SMatt Gilbride diregapic: bool = False, 200*55e87721SMatt Gilbride preserve_gapic: bool = False, 201*55e87721SMatt Gilbride): 202*55e87721SMatt Gilbride """Helper function to execution the common generation cleanup actions. 203*55e87721SMatt Gilbride 204*55e87721SMatt Gilbride Fixes headers for protobuf classes and generated gRPC stub services. Copies 205*55e87721SMatt Gilbride code and samples to their final destinations by convention. Runs the code 206*55e87721SMatt Gilbride formatter on the generated code. 207*55e87721SMatt Gilbride 208*55e87721SMatt Gilbride Args: 209*55e87721SMatt Gilbride service (str): Name of the service. 210*55e87721SMatt Gilbride version (str): Service API version. 211*55e87721SMatt Gilbride library (Path): Path to the temp directory with the generated library. 212*55e87721SMatt Gilbride package_pattern (str): Package name template for fixing file headers. 213*55e87721SMatt Gilbride suffix (str, optional): Suffix that the generated library folder. The 214*55e87721SMatt Gilbride artman output differs from bazel's output directory. Defaults to "". 215*55e87721SMatt Gilbride destination_name (str, optional): Override the service name for the 216*55e87721SMatt Gilbride destination of the output code. Defaults to the service name. 217*55e87721SMatt Gilbride preserve_gapic (bool, optional): Whether to preserve the gapic directory 218*55e87721SMatt Gilbride prefix. Default False. 219*55e87721SMatt Gilbride """ 220*55e87721SMatt Gilbride 221*55e87721SMatt Gilbride if destination_name is None: 222*55e87721SMatt Gilbride destination_name = service 223*55e87721SMatt Gilbride 224*55e87721SMatt Gilbride cloud_prefix = "cloud-" if cloud_api else "" 225*55e87721SMatt Gilbride package_name = package_pattern.format(service=service, version=version) 226*55e87721SMatt Gilbride fix_proto_headers( 227*55e87721SMatt Gilbride library / f"proto-google-{cloud_prefix}{service}-{version}{suffix}" 228*55e87721SMatt Gilbride ) 229*55e87721SMatt Gilbride fix_grpc_headers( 230*55e87721SMatt Gilbride library / f"grpc-google-{cloud_prefix}{service}-{version}{suffix}", package_name 231*55e87721SMatt Gilbride ) 232*55e87721SMatt Gilbride 233*55e87721SMatt Gilbride if preserve_gapic: 234*55e87721SMatt Gilbride s.copy( 235*55e87721SMatt Gilbride [library / f"gapic-google-{cloud_prefix}{service}-{version}{suffix}/src"], 236*55e87721SMatt Gilbride f"gapic-google-{cloud_prefix}{destination_name}-{version}/src", 237*55e87721SMatt Gilbride required=True, 238*55e87721SMatt Gilbride ) 239*55e87721SMatt Gilbride else: 240*55e87721SMatt Gilbride s.copy( 241*55e87721SMatt Gilbride [library / f"gapic-google-{cloud_prefix}{service}-{version}{suffix}/src"], 242*55e87721SMatt Gilbride f"google-{cloud_prefix}{destination_name}/src", 243*55e87721SMatt Gilbride required=True, 244*55e87721SMatt Gilbride ) 245*55e87721SMatt Gilbride 246*55e87721SMatt Gilbride s.copy( 247*55e87721SMatt Gilbride [library / f"grpc-google-{cloud_prefix}{service}-{version}{suffix}/src"], 248*55e87721SMatt Gilbride f"grpc-google-{cloud_prefix}{destination_name}-{version}/src", 249*55e87721SMatt Gilbride # For REST-only clients, like java-compute, gRPC artifact does not exist 250*55e87721SMatt Gilbride required=(not diregapic), 251*55e87721SMatt Gilbride ) 252*55e87721SMatt Gilbride s.copy( 253*55e87721SMatt Gilbride [library / f"proto-google-{cloud_prefix}{service}-{version}{suffix}/src"], 254*55e87721SMatt Gilbride f"proto-google-{cloud_prefix}{destination_name}-{version}/src", 255*55e87721SMatt Gilbride required=True, 256*55e87721SMatt Gilbride ) 257*55e87721SMatt Gilbride 258*55e87721SMatt Gilbride if preserve_gapic: 259*55e87721SMatt Gilbride format_code(f"gapic-google-{cloud_prefix}{destination_name}-{version}/src") 260*55e87721SMatt Gilbride else: 261*55e87721SMatt Gilbride format_code(f"google-{cloud_prefix}{destination_name}/src") 262*55e87721SMatt Gilbride format_code(f"grpc-google-{cloud_prefix}{destination_name}-{version}/src") 263*55e87721SMatt Gilbride format_code(f"proto-google-{cloud_prefix}{destination_name}-{version}/src") 264*55e87721SMatt Gilbride 265*55e87721SMatt Gilbride 266*55e87721SMatt Gilbridedef gapic_library( 267*55e87721SMatt Gilbride service: str, 268*55e87721SMatt Gilbride version: str, 269*55e87721SMatt Gilbride config_pattern: str = "/google/cloud/{service}/artman_{service}_{version}.yaml", 270*55e87721SMatt Gilbride package_pattern: str = "com.google.cloud.{service}.{version}", 271*55e87721SMatt Gilbride gapic: gcp.GAPICGenerator = None, 272*55e87721SMatt Gilbride destination_name: str = None, 273*55e87721SMatt Gilbride diregapic: bool = False, 274*55e87721SMatt Gilbride preserve_gapic: bool = False, 275*55e87721SMatt Gilbride **kwargs, 276*55e87721SMatt Gilbride) -> Path: 277*55e87721SMatt Gilbride """Generate a Java library using the gapic-generator via artman via Docker. 278*55e87721SMatt Gilbride 279*55e87721SMatt Gilbride Generates code into a temp directory, fixes missing header fields, and 280*55e87721SMatt Gilbride copies into the expected locations. 281*55e87721SMatt Gilbride 282*55e87721SMatt Gilbride Args: 283*55e87721SMatt Gilbride service (str): Name of the service. 284*55e87721SMatt Gilbride version (str): Service API version. 285*55e87721SMatt Gilbride config_pattern (str, optional): Path template to artman config YAML 286*55e87721SMatt Gilbride file. Defaults to "/google/cloud/{service}/artman_{service}_{version}.yaml" 287*55e87721SMatt Gilbride package_pattern (str, optional): Package name template for fixing file 288*55e87721SMatt Gilbride headers. Defaults to "com.google.cloud.{service}.{version}". 289*55e87721SMatt Gilbride gapic (GAPICGenerator, optional): Generator instance. 290*55e87721SMatt Gilbride destination_name (str, optional): Override the service name for the 291*55e87721SMatt Gilbride destination of the output code. Defaults to the service name. 292*55e87721SMatt Gilbride preserve_gapic (bool, optional): Whether to preserve the gapic directory 293*55e87721SMatt Gilbride prefix. Default False. 294*55e87721SMatt Gilbride **kwargs: Additional options for gapic.java_library() 295*55e87721SMatt Gilbride 296*55e87721SMatt Gilbride Returns: 297*55e87721SMatt Gilbride The path to the temp directory containing the generated client. 298*55e87721SMatt Gilbride """ 299*55e87721SMatt Gilbride if gapic is None: 300*55e87721SMatt Gilbride gapic = gcp.GAPICGenerator() 301*55e87721SMatt Gilbride 302*55e87721SMatt Gilbride library = gapic.java_library( 303*55e87721SMatt Gilbride service=service, 304*55e87721SMatt Gilbride version=version, 305*55e87721SMatt Gilbride config_path=config_pattern.format(service=service, version=version), 306*55e87721SMatt Gilbride artman_output_name="", 307*55e87721SMatt Gilbride include_samples=True, 308*55e87721SMatt Gilbride diregapic=diregapic, 309*55e87721SMatt Gilbride **kwargs, 310*55e87721SMatt Gilbride ) 311*55e87721SMatt Gilbride 312*55e87721SMatt Gilbride _common_generation( 313*55e87721SMatt Gilbride service=service, 314*55e87721SMatt Gilbride version=version, 315*55e87721SMatt Gilbride library=library, 316*55e87721SMatt Gilbride package_pattern=package_pattern, 317*55e87721SMatt Gilbride destination_name=destination_name, 318*55e87721SMatt Gilbride diregapic=diregapic, 319*55e87721SMatt Gilbride preserve_gapic=preserve_gapic, 320*55e87721SMatt Gilbride ) 321*55e87721SMatt Gilbride 322*55e87721SMatt Gilbride return library 323*55e87721SMatt Gilbride 324*55e87721SMatt Gilbride 325*55e87721SMatt Gilbridedef bazel_library( 326*55e87721SMatt Gilbride service: str, 327*55e87721SMatt Gilbride version: str, 328*55e87721SMatt Gilbride package_pattern: str = "com.google.cloud.{service}.{version}", 329*55e87721SMatt Gilbride gapic: gcp.GAPICBazel = None, 330*55e87721SMatt Gilbride destination_name: str = None, 331*55e87721SMatt Gilbride cloud_api: bool = True, 332*55e87721SMatt Gilbride diregapic: bool = False, 333*55e87721SMatt Gilbride preserve_gapic: bool = False, 334*55e87721SMatt Gilbride **kwargs, 335*55e87721SMatt Gilbride) -> Path: 336*55e87721SMatt Gilbride """Generate a Java library using the gapic-generator via bazel. 337*55e87721SMatt Gilbride 338*55e87721SMatt Gilbride Generates code into a temp directory, fixes missing header fields, and 339*55e87721SMatt Gilbride copies into the expected locations. 340*55e87721SMatt Gilbride 341*55e87721SMatt Gilbride Args: 342*55e87721SMatt Gilbride service (str): Name of the service. 343*55e87721SMatt Gilbride version (str): Service API version. 344*55e87721SMatt Gilbride package_pattern (str, optional): Package name template for fixing file 345*55e87721SMatt Gilbride headers. Defaults to "com.google.cloud.{service}.{version}". 346*55e87721SMatt Gilbride gapic (GAPICBazel, optional): Generator instance. 347*55e87721SMatt Gilbride destination_name (str, optional): Override the service name for the 348*55e87721SMatt Gilbride destination of the output code. Defaults to the service name. 349*55e87721SMatt Gilbride preserve_gapic (bool, optional): Whether to preserve the gapic directory 350*55e87721SMatt Gilbride prefix. Default False. 351*55e87721SMatt Gilbride **kwargs: Additional options for gapic.java_library() 352*55e87721SMatt Gilbride 353*55e87721SMatt Gilbride Returns: 354*55e87721SMatt Gilbride The path to the temp directory containing the generated client. 355*55e87721SMatt Gilbride """ 356*55e87721SMatt Gilbride if gapic is None: 357*55e87721SMatt Gilbride gapic = gcp.GAPICBazel() 358*55e87721SMatt Gilbride 359*55e87721SMatt Gilbride library = gapic.java_library( 360*55e87721SMatt Gilbride service=service, version=version, diregapic=diregapic, **kwargs 361*55e87721SMatt Gilbride ) 362*55e87721SMatt Gilbride 363*55e87721SMatt Gilbride _common_generation( 364*55e87721SMatt Gilbride service=service, 365*55e87721SMatt Gilbride version=version, 366*55e87721SMatt Gilbride library=library / f"google-cloud-{service}-{version}-java", 367*55e87721SMatt Gilbride package_pattern=package_pattern, 368*55e87721SMatt Gilbride suffix="-java", 369*55e87721SMatt Gilbride destination_name=destination_name, 370*55e87721SMatt Gilbride cloud_api=cloud_api, 371*55e87721SMatt Gilbride diregapic=diregapic, 372*55e87721SMatt Gilbride preserve_gapic=preserve_gapic, 373*55e87721SMatt Gilbride ) 374*55e87721SMatt Gilbride 375*55e87721SMatt Gilbride return library 376*55e87721SMatt Gilbride 377*55e87721SMatt Gilbride 378*55e87721SMatt Gilbridedef pregenerated_library( 379*55e87721SMatt Gilbride path: str, 380*55e87721SMatt Gilbride service: str, 381*55e87721SMatt Gilbride version: str, 382*55e87721SMatt Gilbride destination_name: str = None, 383*55e87721SMatt Gilbride cloud_api: bool = True, 384*55e87721SMatt Gilbride) -> Path: 385*55e87721SMatt Gilbride """Generate a Java library using the gapic-generator via bazel. 386*55e87721SMatt Gilbride 387*55e87721SMatt Gilbride Generates code into a temp directory, fixes missing header fields, and 388*55e87721SMatt Gilbride copies into the expected locations. 389*55e87721SMatt Gilbride 390*55e87721SMatt Gilbride Args: 391*55e87721SMatt Gilbride path (str): Path in googleapis-gen to un-versioned generated code. 392*55e87721SMatt Gilbride service (str): Name of the service. 393*55e87721SMatt Gilbride version (str): Service API version. 394*55e87721SMatt Gilbride destination_name (str, optional): Override the service name for the 395*55e87721SMatt Gilbride destination of the output code. Defaults to the service name. 396*55e87721SMatt Gilbride cloud_api (bool, optional): Whether or not this is a cloud API (for naming) 397*55e87721SMatt Gilbride 398*55e87721SMatt Gilbride Returns: 399*55e87721SMatt Gilbride The path to the temp directory containing the generated client. 400*55e87721SMatt Gilbride """ 401*55e87721SMatt Gilbride generator = pregenerated.Pregenerated() 402*55e87721SMatt Gilbride library = generator.generate(path) 403*55e87721SMatt Gilbride 404*55e87721SMatt Gilbride cloud_prefix = "cloud-" if cloud_api else "" 405*55e87721SMatt Gilbride _common_generation( 406*55e87721SMatt Gilbride service=service, 407*55e87721SMatt Gilbride version=version, 408*55e87721SMatt Gilbride library=library / f"google-{cloud_prefix}{service}-{version}-java", 409*55e87721SMatt Gilbride package_pattern="unused", 410*55e87721SMatt Gilbride suffix="-java", 411*55e87721SMatt Gilbride destination_name=destination_name, 412*55e87721SMatt Gilbride cloud_api=cloud_api, 413*55e87721SMatt Gilbride ) 414*55e87721SMatt Gilbride 415*55e87721SMatt Gilbride return library 416*55e87721SMatt Gilbride 417*55e87721SMatt Gilbride 418*55e87721SMatt Gilbridedef _merge_release_please(destination_text: str): 419*55e87721SMatt Gilbride config = yaml.safe_load(destination_text) 420*55e87721SMatt Gilbride if "handleGHRelease" in config: 421*55e87721SMatt Gilbride return destination_text 422*55e87721SMatt Gilbride 423*55e87721SMatt Gilbride config["handleGHRelease"] = True 424*55e87721SMatt Gilbride 425*55e87721SMatt Gilbride if "branches" in config: 426*55e87721SMatt Gilbride for branch in config["branches"]: 427*55e87721SMatt Gilbride branch["handleGHRelease"] = True 428*55e87721SMatt Gilbride return yaml.dump(config) 429*55e87721SMatt Gilbride 430*55e87721SMatt Gilbride 431*55e87721SMatt Gilbridedef _merge_common_templates( 432*55e87721SMatt Gilbride source_text: str, destination_text: str, file_path: Path 433*55e87721SMatt Gilbride) -> str: 434*55e87721SMatt Gilbride # keep any existing pom.xml 435*55e87721SMatt Gilbride if file_path.match("pom.xml") or file_path.match("sync-repo-settings.yaml"): 436*55e87721SMatt Gilbride logger.debug(f"existing pom file found ({file_path}) - keeping the existing") 437*55e87721SMatt Gilbride return destination_text 438*55e87721SMatt Gilbride 439*55e87721SMatt Gilbride if file_path.match("release-please.yml"): 440*55e87721SMatt Gilbride return _merge_release_please(destination_text) 441*55e87721SMatt Gilbride 442*55e87721SMatt Gilbride # by default return the newly generated content 443*55e87721SMatt Gilbride return source_text 444*55e87721SMatt Gilbride 445*55e87721SMatt Gilbride 446*55e87721SMatt Gilbridedef _common_template_metadata() -> Dict[str, Any]: 447*55e87721SMatt Gilbride metadata = {} # type: Dict[str, Any] 448*55e87721SMatt Gilbride repo_metadata = common._load_repo_metadata() 449*55e87721SMatt Gilbride if repo_metadata: 450*55e87721SMatt Gilbride metadata["repo"] = repo_metadata 451*55e87721SMatt Gilbride group_id, artifact_id = repo_metadata["distribution_name"].split(":") 452*55e87721SMatt Gilbride 453*55e87721SMatt Gilbride metadata["latest_version"] = latest_maven_version( 454*55e87721SMatt Gilbride group_id=group_id, artifact_id=artifact_id 455*55e87721SMatt Gilbride ) 456*55e87721SMatt Gilbride 457*55e87721SMatt Gilbride metadata["latest_bom_version"] = latest_maven_version( 458*55e87721SMatt Gilbride group_id="com.google.cloud", 459*55e87721SMatt Gilbride artifact_id="libraries-bom", 460*55e87721SMatt Gilbride ) 461*55e87721SMatt Gilbride 462*55e87721SMatt Gilbride metadata["samples"] = samples.all_samples(["samples/**/src/main/java/**/*.java"]) 463*55e87721SMatt Gilbride metadata["snippets"] = snippets.all_snippets( 464*55e87721SMatt Gilbride ["samples/**/src/main/java/**/*.java", "samples/**/pom.xml"] 465*55e87721SMatt Gilbride ) 466*55e87721SMatt Gilbride if repo_metadata and "min_java_version" in repo_metadata: 467*55e87721SMatt Gilbride metadata["min_java_version"] = repo_metadata["min_java_version"] 468*55e87721SMatt Gilbride else: 469*55e87721SMatt Gilbride metadata["min_java_version"] = DEFAULT_MIN_SUPPORTED_JAVA_VERSION 470*55e87721SMatt Gilbride 471*55e87721SMatt Gilbride return metadata 472*55e87721SMatt Gilbride 473*55e87721SMatt Gilbride 474*55e87721SMatt Gilbridedef common_templates( 475*55e87721SMatt Gilbride excludes: List[str] = [], template_path: Optional[Path] = None, **kwargs 476*55e87721SMatt Gilbride) -> None: 477*55e87721SMatt Gilbride """Generate common templates for a Java Library 478*55e87721SMatt Gilbride 479*55e87721SMatt Gilbride Fetches information about the repository from the .repo-metadata.json file, 480*55e87721SMatt Gilbride information about the latest artifact versions and copies the files into 481*55e87721SMatt Gilbride their expected location. 482*55e87721SMatt Gilbride 483*55e87721SMatt Gilbride Args: 484*55e87721SMatt Gilbride excludes (List[str], optional): List of template paths to ignore 485*55e87721SMatt Gilbride **kwargs: Additional options for CommonTemplates.java_library() 486*55e87721SMatt Gilbride """ 487*55e87721SMatt Gilbride metadata = _common_template_metadata() 488*55e87721SMatt Gilbride kwargs["metadata"] = metadata 489*55e87721SMatt Gilbride 490*55e87721SMatt Gilbride # Generate flat to tell this repository is a split repo that have migrated 491*55e87721SMatt Gilbride # to monorepo. The owlbot.py in the monorepo sets monorepo=True. 492*55e87721SMatt Gilbride monorepo = kwargs.get("monorepo", False) 493*55e87721SMatt Gilbride split_repo = not monorepo 494*55e87721SMatt Gilbride repo_metadata = metadata["repo"] 495*55e87721SMatt Gilbride repo_short = repo_metadata["repo_short"] 496*55e87721SMatt Gilbride # Special libraries that are not GAPIC_AUTO but in the monorepo 497*55e87721SMatt Gilbride special_libs_in_monorepo = [ 498*55e87721SMatt Gilbride "java-translate", 499*55e87721SMatt Gilbride "java-dns", 500*55e87721SMatt Gilbride "java-notification", 501*55e87721SMatt Gilbride "java-resourcemanager", 502*55e87721SMatt Gilbride ] 503*55e87721SMatt Gilbride kwargs["migrated_split_repo"] = split_repo and ( 504*55e87721SMatt Gilbride repo_metadata["library_type"] == "GAPIC_AUTO" 505*55e87721SMatt Gilbride or (repo_short and repo_short in special_libs_in_monorepo) 506*55e87721SMatt Gilbride ) 507*55e87721SMatt Gilbride logger.info( 508*55e87721SMatt Gilbride "monorepo: {}, split_repo: {}, library_type: {}," 509*55e87721SMatt Gilbride " repo_short: {}, migrated_split_repo: {}".format( 510*55e87721SMatt Gilbride monorepo, 511*55e87721SMatt Gilbride split_repo, 512*55e87721SMatt Gilbride repo_metadata["library_type"], 513*55e87721SMatt Gilbride repo_short, 514*55e87721SMatt Gilbride kwargs["migrated_split_repo"], 515*55e87721SMatt Gilbride ) 516*55e87721SMatt Gilbride ) 517*55e87721SMatt Gilbride 518*55e87721SMatt Gilbride templates = gcp.CommonTemplates(template_path=template_path).java_library(**kwargs) 519*55e87721SMatt Gilbride 520*55e87721SMatt Gilbride # skip README generation on Kokoro (autosynth) 521*55e87721SMatt Gilbride if os.environ.get("KOKORO_ROOT") is not None: 522*55e87721SMatt Gilbride # README.md is now synthesized separately. This prevents synthtool from deleting the 523*55e87721SMatt Gilbride # README as it's no longer generated here. 524*55e87721SMatt Gilbride excludes.append("README.md") 525*55e87721SMatt Gilbride 526*55e87721SMatt Gilbride s.copy([templates], excludes=excludes, merge=_merge_common_templates) 527*55e87721SMatt Gilbride 528*55e87721SMatt Gilbride 529*55e87721SMatt Gilbridedef custom_templates(files: List[str], **kwargs) -> None: 530*55e87721SMatt Gilbride """Generate custom template files 531*55e87721SMatt Gilbride 532*55e87721SMatt Gilbride Fetches information about the repository from the .repo-metadata.json file, 533*55e87721SMatt Gilbride information about the latest artifact versions and copies the files into 534*55e87721SMatt Gilbride their expected location. 535*55e87721SMatt Gilbride 536*55e87721SMatt Gilbride Args: 537*55e87721SMatt Gilbride files (List[str], optional): List of template paths to include 538*55e87721SMatt Gilbride **kwargs: Additional options for CommonTemplates.render() 539*55e87721SMatt Gilbride """ 540*55e87721SMatt Gilbride kwargs["metadata"] = _common_template_metadata() 541*55e87721SMatt Gilbride kwargs["metadata"]["partials"] = partials.load_partials() 542*55e87721SMatt Gilbride for file in files: 543*55e87721SMatt Gilbride template = gcp.CommonTemplates().render(file, **kwargs) 544*55e87721SMatt Gilbride s.copy([template]) 545*55e87721SMatt Gilbride 546*55e87721SMatt Gilbride 547*55e87721SMatt Gilbridedef remove_method(filename: str, signature: str): 548*55e87721SMatt Gilbride """Helper to remove an entire method. 549*55e87721SMatt Gilbride 550*55e87721SMatt Gilbride Goes line-by-line to detect the start of the block. Determines 551*55e87721SMatt Gilbride the end of the block by a closing brace at the same indentation 552*55e87721SMatt Gilbride level. This requires the file to be correctly formatted. 553*55e87721SMatt Gilbride 554*55e87721SMatt Gilbride Example: consider the following class: 555*55e87721SMatt Gilbride 556*55e87721SMatt Gilbride class Example { 557*55e87721SMatt Gilbride public void main(String[] args) { 558*55e87721SMatt Gilbride System.out.println("Hello World"); 559*55e87721SMatt Gilbride } 560*55e87721SMatt Gilbride 561*55e87721SMatt Gilbride public String foo() { 562*55e87721SMatt Gilbride return "bar"; 563*55e87721SMatt Gilbride } 564*55e87721SMatt Gilbride } 565*55e87721SMatt Gilbride 566*55e87721SMatt Gilbride To remove the `main` method above, use: 567*55e87721SMatt Gilbride 568*55e87721SMatt Gilbride remove_method('path/to/file', 'public void main(String[] args)') 569*55e87721SMatt Gilbride 570*55e87721SMatt Gilbride Args: 571*55e87721SMatt Gilbride filename (str): Path to source file 572*55e87721SMatt Gilbride signature (str): Full signature of the method to remove. Example: 573*55e87721SMatt Gilbride `public void main(String[] args)`. 574*55e87721SMatt Gilbride """ 575*55e87721SMatt Gilbride lines = [] 576*55e87721SMatt Gilbride leading_regex = None 577*55e87721SMatt Gilbride with open(filename, "r") as fp: 578*55e87721SMatt Gilbride line = fp.readline() 579*55e87721SMatt Gilbride while line: 580*55e87721SMatt Gilbride # for each line, try to find the matching 581*55e87721SMatt Gilbride regex = re.compile("(\\s*)" + re.escape(signature) + ".*") 582*55e87721SMatt Gilbride match = regex.match(line) 583*55e87721SMatt Gilbride if match: 584*55e87721SMatt Gilbride leading_regex = re.compile(match.group(1) + "}") 585*55e87721SMatt Gilbride line = fp.readline() 586*55e87721SMatt Gilbride continue 587*55e87721SMatt Gilbride 588*55e87721SMatt Gilbride # not in a ignore block - preserve the line 589*55e87721SMatt Gilbride if not leading_regex: 590*55e87721SMatt Gilbride lines.append(line) 591*55e87721SMatt Gilbride line = fp.readline() 592*55e87721SMatt Gilbride continue 593*55e87721SMatt Gilbride 594*55e87721SMatt Gilbride # detect the closing tag based on the leading spaces 595*55e87721SMatt Gilbride match = leading_regex.match(line) 596*55e87721SMatt Gilbride if match: 597*55e87721SMatt Gilbride # block is closed, resume capturing content 598*55e87721SMatt Gilbride leading_regex = None 599*55e87721SMatt Gilbride 600*55e87721SMatt Gilbride line = fp.readline() 601*55e87721SMatt Gilbride 602*55e87721SMatt Gilbride with open(filename, "w") as fp: 603*55e87721SMatt Gilbride for line in lines: 604*55e87721SMatt Gilbride # print(line) 605*55e87721SMatt Gilbride fp.write(line) 606*55e87721SMatt Gilbride 607*55e87721SMatt Gilbride 608*55e87721SMatt Gilbridedef copy_and_rename_method(filename: str, signature: str, before: str, after: str): 609*55e87721SMatt Gilbride """Helper to make a copy an entire method and rename it. 610*55e87721SMatt Gilbride 611*55e87721SMatt Gilbride Goes line-by-line to detect the start of the block. Determines 612*55e87721SMatt Gilbride the end of the block by a closing brace at the same indentation 613*55e87721SMatt Gilbride level. This requires the file to be correctly formatted. 614*55e87721SMatt Gilbride The method is copied over and renamed in the method signature. 615*55e87721SMatt Gilbride The calls to both methods are separate and unaffected. 616*55e87721SMatt Gilbride 617*55e87721SMatt Gilbride Example: consider the following class: 618*55e87721SMatt Gilbride 619*55e87721SMatt Gilbride class Example { 620*55e87721SMatt Gilbride public void main(String[] args) { 621*55e87721SMatt Gilbride System.out.println("Hello World"); 622*55e87721SMatt Gilbride } 623*55e87721SMatt Gilbride 624*55e87721SMatt Gilbride public String foo() { 625*55e87721SMatt Gilbride return "bar"; 626*55e87721SMatt Gilbride } 627*55e87721SMatt Gilbride } 628*55e87721SMatt Gilbride 629*55e87721SMatt Gilbride To copy and rename the `main` method above, use: 630*55e87721SMatt Gilbride 631*55e87721SMatt Gilbride copy_and_rename_method('path/to/file', 'public void main(String[] args)', 632*55e87721SMatt Gilbride 'main', 'foo1') 633*55e87721SMatt Gilbride 634*55e87721SMatt Gilbride Args: 635*55e87721SMatt Gilbride filename (str): Path to source file 636*55e87721SMatt Gilbride signature (str): Full signature of the method to remove. Example: 637*55e87721SMatt Gilbride `public void main(String[] args)`. 638*55e87721SMatt Gilbride before (str): name of the method to be copied 639*55e87721SMatt Gilbride after (str): new name of the copied method 640*55e87721SMatt Gilbride """ 641*55e87721SMatt Gilbride lines = [] 642*55e87721SMatt Gilbride method = [] 643*55e87721SMatt Gilbride leading_regex = None 644*55e87721SMatt Gilbride with open(filename, "r") as fp: 645*55e87721SMatt Gilbride line = fp.readline() 646*55e87721SMatt Gilbride while line: 647*55e87721SMatt Gilbride # for each line, try to find the matching 648*55e87721SMatt Gilbride regex = re.compile("(\\s*)" + re.escape(signature) + ".*") 649*55e87721SMatt Gilbride match = regex.match(line) 650*55e87721SMatt Gilbride if match: 651*55e87721SMatt Gilbride leading_regex = re.compile(match.group(1) + "}") 652*55e87721SMatt Gilbride lines.append(line) 653*55e87721SMatt Gilbride method.append(line.replace(before, after)) 654*55e87721SMatt Gilbride line = fp.readline() 655*55e87721SMatt Gilbride continue 656*55e87721SMatt Gilbride 657*55e87721SMatt Gilbride lines.append(line) 658*55e87721SMatt Gilbride # not in a ignore block - preserve the line 659*55e87721SMatt Gilbride if leading_regex: 660*55e87721SMatt Gilbride method.append(line) 661*55e87721SMatt Gilbride else: 662*55e87721SMatt Gilbride line = fp.readline() 663*55e87721SMatt Gilbride continue 664*55e87721SMatt Gilbride 665*55e87721SMatt Gilbride # detect the closing tag based on the leading spaces 666*55e87721SMatt Gilbride match = leading_regex.match(line) 667*55e87721SMatt Gilbride if match: 668*55e87721SMatt Gilbride # block is closed, resume capturing content 669*55e87721SMatt Gilbride leading_regex = None 670*55e87721SMatt Gilbride lines.append("\n") 671*55e87721SMatt Gilbride lines.extend(method) 672*55e87721SMatt Gilbride 673*55e87721SMatt Gilbride line = fp.readline() 674*55e87721SMatt Gilbride 675*55e87721SMatt Gilbride with open(filename, "w") as fp: 676*55e87721SMatt Gilbride for line in lines: 677*55e87721SMatt Gilbride # print(line) 678*55e87721SMatt Gilbride fp.write(line) 679*55e87721SMatt Gilbride 680*55e87721SMatt Gilbride 681*55e87721SMatt Gilbridedef add_javadoc(filename: str, signature: str, javadoc_type: str, content: List[str]): 682*55e87721SMatt Gilbride """Helper to add a javadoc annoatation to a method. 683*55e87721SMatt Gilbride 684*55e87721SMatt Gilbride Goes line-by-line to detect the start of the block. 685*55e87721SMatt Gilbride Then finds the existing method comment (if it exists). If the 686*55e87721SMatt Gilbride comment already exists, it will append the javadoc annotation 687*55e87721SMatt Gilbride to the javadoc block. Otherwise, it will create a new javadoc 688*55e87721SMatt Gilbride comment block. 689*55e87721SMatt Gilbride 690*55e87721SMatt Gilbride Example: consider the following class: 691*55e87721SMatt Gilbride 692*55e87721SMatt Gilbride class Example { 693*55e87721SMatt Gilbride public void main(String[] args) { 694*55e87721SMatt Gilbride System.out.println("Hello World"); 695*55e87721SMatt Gilbride } 696*55e87721SMatt Gilbride 697*55e87721SMatt Gilbride public String foo() { 698*55e87721SMatt Gilbride return "bar"; 699*55e87721SMatt Gilbride } 700*55e87721SMatt Gilbride } 701*55e87721SMatt Gilbride 702*55e87721SMatt Gilbride To add a javadoc annotation the `main` method above, use: 703*55e87721SMatt Gilbride 704*55e87721SMatt Gilbride add_javadoc('path/to/file', 'public void main(String[] args)', 705*55e87721SMatt Gilbride 'deprecated', 'Please use foo instead.') 706*55e87721SMatt Gilbride 707*55e87721SMatt Gilbride Args: 708*55e87721SMatt Gilbride filename (str): Path to source file 709*55e87721SMatt Gilbride signature (str): Full signature of the method to remove. Example: 710*55e87721SMatt Gilbride `public void main(String[] args)`. 711*55e87721SMatt Gilbride javadoc_type (str): The type of javadoc annotation. Example: `deprecated`. 712*55e87721SMatt Gilbride content (List[str]): The javadoc lines 713*55e87721SMatt Gilbride """ 714*55e87721SMatt Gilbride lines: List[str] = [] 715*55e87721SMatt Gilbride annotations: List[str] = [] 716*55e87721SMatt Gilbride with open(filename, "r") as fp: 717*55e87721SMatt Gilbride line = fp.readline() 718*55e87721SMatt Gilbride while line: 719*55e87721SMatt Gilbride # for each line, try to find the matching 720*55e87721SMatt Gilbride regex = re.compile("(\\s*)" + re.escape(signature) + ".*") 721*55e87721SMatt Gilbride match = regex.match(line) 722*55e87721SMatt Gilbride if match: 723*55e87721SMatt Gilbride leading_spaces = len(line) - len(line.lstrip()) 724*55e87721SMatt Gilbride indent = leading_spaces * " " 725*55e87721SMatt Gilbride last_line = lines.pop() 726*55e87721SMatt Gilbride while last_line.lstrip() and last_line.lstrip()[0] == "@": 727*55e87721SMatt Gilbride annotations.append(last_line) 728*55e87721SMatt Gilbride last_line = lines.pop() 729*55e87721SMatt Gilbride if last_line.strip() == "*/": 730*55e87721SMatt Gilbride first = True 731*55e87721SMatt Gilbride for content_line in content: 732*55e87721SMatt Gilbride if first: 733*55e87721SMatt Gilbride lines.append( 734*55e87721SMatt Gilbride indent 735*55e87721SMatt Gilbride + " * @" 736*55e87721SMatt Gilbride + javadoc_type 737*55e87721SMatt Gilbride + " " 738*55e87721SMatt Gilbride + content_line 739*55e87721SMatt Gilbride + "\n" 740*55e87721SMatt Gilbride ) 741*55e87721SMatt Gilbride first = False 742*55e87721SMatt Gilbride else: 743*55e87721SMatt Gilbride lines.append(indent + " * " + content_line + "\n") 744*55e87721SMatt Gilbride lines.append(last_line) 745*55e87721SMatt Gilbride else: 746*55e87721SMatt Gilbride lines.append(last_line) 747*55e87721SMatt Gilbride lines.append(indent + "/**\n") 748*55e87721SMatt Gilbride first = True 749*55e87721SMatt Gilbride for content_line in content: 750*55e87721SMatt Gilbride if first: 751*55e87721SMatt Gilbride lines.append( 752*55e87721SMatt Gilbride indent 753*55e87721SMatt Gilbride + " * @" 754*55e87721SMatt Gilbride + javadoc_type 755*55e87721SMatt Gilbride + " " 756*55e87721SMatt Gilbride + content_line 757*55e87721SMatt Gilbride + "\n" 758*55e87721SMatt Gilbride ) 759*55e87721SMatt Gilbride first = False 760*55e87721SMatt Gilbride else: 761*55e87721SMatt Gilbride lines.append(indent + " * " + content_line + "\n") 762*55e87721SMatt Gilbride lines.append(indent + " */\n") 763*55e87721SMatt Gilbride lines.extend(annotations[::-1]) 764*55e87721SMatt Gilbride lines.append(line) 765*55e87721SMatt Gilbride line = fp.readline() 766*55e87721SMatt Gilbride 767*55e87721SMatt Gilbride with open(filename, "w") as fp: 768*55e87721SMatt Gilbride for line in lines: 769*55e87721SMatt Gilbride # print(line) 770*55e87721SMatt Gilbride fp.write(line) 771*55e87721SMatt Gilbride 772*55e87721SMatt Gilbride 773*55e87721SMatt Gilbridedef annotate_method(filename: str, signature: str, annotation: str): 774*55e87721SMatt Gilbride """Helper to add an annotation to a method. 775*55e87721SMatt Gilbride 776*55e87721SMatt Gilbride Goes line-by-line to detect the start of the block. 777*55e87721SMatt Gilbride Then adds the annotation above the found method signature. 778*55e87721SMatt Gilbride 779*55e87721SMatt Gilbride Example: consider the following class: 780*55e87721SMatt Gilbride 781*55e87721SMatt Gilbride class Example { 782*55e87721SMatt Gilbride public void main(String[] args) { 783*55e87721SMatt Gilbride System.out.println("Hello World"); 784*55e87721SMatt Gilbride } 785*55e87721SMatt Gilbride 786*55e87721SMatt Gilbride public String foo() { 787*55e87721SMatt Gilbride return "bar"; 788*55e87721SMatt Gilbride } 789*55e87721SMatt Gilbride } 790*55e87721SMatt Gilbride 791*55e87721SMatt Gilbride To add an annotation the `main` method above, use: 792*55e87721SMatt Gilbride 793*55e87721SMatt Gilbride annotate_method('path/to/file', 'public void main(String[] args)', 794*55e87721SMatt Gilbride '@Generated()') 795*55e87721SMatt Gilbride 796*55e87721SMatt Gilbride Args: 797*55e87721SMatt Gilbride filename (str): Path to source file 798*55e87721SMatt Gilbride signature (str): Full signature of the method to remove. Example: 799*55e87721SMatt Gilbride `public void main(String[] args)`. 800*55e87721SMatt Gilbride annotation (str): Full annotation. Example: `@Deprecated` 801*55e87721SMatt Gilbride """ 802*55e87721SMatt Gilbride lines: List[str] = [] 803*55e87721SMatt Gilbride with open(filename, "r") as fp: 804*55e87721SMatt Gilbride line = fp.readline() 805*55e87721SMatt Gilbride while line: 806*55e87721SMatt Gilbride # for each line, try to find the matching 807*55e87721SMatt Gilbride regex = re.compile("(\\s*)" + re.escape(signature) + ".*") 808*55e87721SMatt Gilbride match = regex.match(line) 809*55e87721SMatt Gilbride if match: 810*55e87721SMatt Gilbride leading_spaces = len(line) - len(line.lstrip()) 811*55e87721SMatt Gilbride indent = leading_spaces * " " 812*55e87721SMatt Gilbride lines.append(indent + annotation + "\n") 813*55e87721SMatt Gilbride lines.append(line) 814*55e87721SMatt Gilbride line = fp.readline() 815*55e87721SMatt Gilbride 816*55e87721SMatt Gilbride with open(filename, "w") as fp: 817*55e87721SMatt Gilbride for line in lines: 818*55e87721SMatt Gilbride # print(line) 819*55e87721SMatt Gilbride fp.write(line) 820*55e87721SMatt Gilbride 821*55e87721SMatt Gilbride 822*55e87721SMatt Gilbridedef deprecate_method(filename: str, signature: str, alternative: str): 823*55e87721SMatt Gilbride """Helper to deprecate a method. 824*55e87721SMatt Gilbride 825*55e87721SMatt Gilbride Goes line-by-line to detect the start of the block. 826*55e87721SMatt Gilbride Then adds the deprecation comment before the method signature. 827*55e87721SMatt Gilbride The @Deprecation annotation is also added. 828*55e87721SMatt Gilbride 829*55e87721SMatt Gilbride Example: consider the following class: 830*55e87721SMatt Gilbride 831*55e87721SMatt Gilbride class Example { 832*55e87721SMatt Gilbride public void main(String[] args) { 833*55e87721SMatt Gilbride System.out.println("Hello World"); 834*55e87721SMatt Gilbride } 835*55e87721SMatt Gilbride 836*55e87721SMatt Gilbride public String foo() { 837*55e87721SMatt Gilbride return "bar"; 838*55e87721SMatt Gilbride } 839*55e87721SMatt Gilbride } 840*55e87721SMatt Gilbride 841*55e87721SMatt Gilbride To deprecate the `main` method above, use: 842*55e87721SMatt Gilbride 843*55e87721SMatt Gilbride deprecate_method('path/to/file', 'public void main(String[] args)', 844*55e87721SMatt Gilbride DEPRECATION_WARNING.format(new_method="foo")) 845*55e87721SMatt Gilbride 846*55e87721SMatt Gilbride Args: 847*55e87721SMatt Gilbride filename (str): Path to source file 848*55e87721SMatt Gilbride signature (str): Full signature of the method to remove. Example: 849*55e87721SMatt Gilbride `public void main(String[] args)`. 850*55e87721SMatt Gilbride alternative: DEPRECATION WARNING: multiline javadoc comment with user 851*55e87721SMatt Gilbride specified leading open/close comment tags 852*55e87721SMatt Gilbride """ 853*55e87721SMatt Gilbride add_javadoc(filename, signature, "deprecated", alternative.splitlines()) 854*55e87721SMatt Gilbride annotate_method(filename, signature, "@Deprecated") 855