1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414import json
15+ import os
16+ import re
1517import sys
1618import subprocess
17- import os
1819from pathlib import Path
19- from typing import Any
20+ from typing import Any , Optional
2021
2122from common .model .generation_config import GenerationConfig
2223from common .model .library_config import LibraryConfig
23- from typing import List
2424from library_generation .model .repo_config import RepoConfig
25- from library_generation .utils .file_render import render
25+ from library_generation .utils .file_render import render , render_to_str
2626from library_generation .utils .proto_path_utils import remove_version_from
2727
2828script_dir = os .path .dirname (os .path .realpath (__file__ ))
2929SDK_PLATFORM_JAVA = "googleapis/sdk-platform-java"
3030
3131
32- def create_argument (arg_key : str , arg_container : object ) -> List [str ]:
32+ def create_argument (arg_key : str , arg_container : object ) -> list [str ]:
3333 """
3434 Generates a list of two elements [argument, value], or returns
3535 an empty array if arg_val is None
@@ -41,7 +41,7 @@ def create_argument(arg_key: str, arg_container: object) -> List[str]:
4141
4242
4343def run_process_and_print_output (
44- arguments : List [str ] | str , job_name : str = "Job" , exit_on_fail = True , ** kwargs
44+ arguments : list [str ] | str , job_name : str = "Job" , exit_on_fail = True , ** kwargs
4545) -> Any :
4646 """
4747 Runs a process with the given "arguments" list and prints its output.
@@ -68,7 +68,7 @@ def run_process_and_print_output(
6868
6969
7070def run_process_and_get_output_string (
71- arguments : List [str ] | str , job_name : str = "Job" , exit_on_fail = True , ** kwargs
71+ arguments : list [str ] | str , job_name : str = "Job" , exit_on_fail = True , ** kwargs
7272) -> Any :
7373 """
7474 Wrapper of run_process_and_print_output() that returns the merged
@@ -123,7 +123,7 @@ def eprint(*args, **kwargs):
123123
124124def prepare_repo (
125125 gen_config : GenerationConfig ,
126- library_config : List [LibraryConfig ],
126+ library_config : list [LibraryConfig ],
127127 repo_path : str ,
128128 language : str = "java" ,
129129) -> RepoConfig :
@@ -276,14 +276,23 @@ def generate_postprocessing_prerequisite_files(
276276 else f"{ library_path } /.github/{ owlbot_yaml_file } "
277277 )
278278 if not os .path .exists (path_to_owlbot_yaml_file ):
279- render (
279+ # 1. Render the base content
280+ generated_content = render_to_str (
280281 template_name = "owlbot.yaml.monorepo.j2" ,
281- output_name = path_to_owlbot_yaml_file ,
282282 artifact_id = artifact_id ,
283283 proto_path = remove_version_from (proto_path ),
284284 module_name = repo_metadata ["repo_short" ],
285285 api_shortname = library .api_shortname ,
286+ owlbot_yaml = library .owlbot_yaml ,
286287 )
288+ # 2. Apply additions and removals
289+ modified_content = apply_owlbot_config (generated_content , library .owlbot_yaml )
290+
291+ # 3. Write the modified content back to the file
292+ directory = os .path .dirname (path_to_owlbot_yaml_file )
293+ os .makedirs (directory , exist_ok = True )
294+ with open (path_to_owlbot_yaml_file , "w" ) as f :
295+ f .write (modified_content )
287296
288297 # generate owlbot.py
289298 py_file = "owlbot.py"
@@ -307,3 +316,127 @@ def generate_postprocessing_prerequisite_files(
307316 should_include_templates = True ,
308317 template_excludes = template_excludes ,
309318 )
319+
320+
321+ def apply_owlbot_config (
322+ content : str , owlbot_config : Optional ["OwlbotYamlConfig" ]
323+ ) -> str :
324+ """
325+ Applies the addition and removal configurations to the generated owlbot.yaml content.
326+
327+ Args:
328+ content: The generated owlbot.yaml content as a string.
329+ owlbot_config: The OwlbotYamlConfig object containing the addition and removal rules.
330+
331+ Returns:
332+ The modified owlbot.yaml content.
333+ """
334+ if not owlbot_config :
335+ return content
336+
337+ modified_content = content
338+
339+ if owlbot_config .removals :
340+ if owlbot_config .removals .deep_remove_regex :
341+ for regex_pattern in owlbot_config .removals .deep_remove_regex :
342+ modified_content = re .sub (
343+ r'- "' + regex_pattern + r'"[^\S\n]*\n' , "" , modified_content
344+ )
345+ if owlbot_config .removals .deep_preserve_regex :
346+ for regex_pattern in owlbot_config .removals .deep_preserve_regex :
347+ modified_content = re .sub (
348+ r'- "' + regex_pattern + r'"[^\S\n]*\n' , "" , modified_content
349+ )
350+ if owlbot_config .removals .deep_copy_regex :
351+ for item in owlbot_config .removals .deep_copy_regex :
352+ # Construct the regex to match both source and dest lines
353+ source_regex = r'- source: "' + re .escape (item .source ) + r'"\n'
354+ dest_regex = r'\s+dest: "' + re .escape (item .dest ) + r'"\n'
355+ removal_regex = rf"{ source_regex } { dest_regex } "
356+ modified_content = re .sub (
357+ removal_regex , "" , modified_content , flags = re .MULTILINE
358+ )
359+ if owlbot_config .additions :
360+ if owlbot_config .additions .deep_remove_regex :
361+ modified_content = _add_items_after_key (
362+ content = modified_content ,
363+ key = "deep-remove-regex:" ,
364+ items = owlbot_config .additions .deep_remove_regex ,
365+ quote = True ,
366+ )
367+ if owlbot_config .additions .deep_preserve_regex :
368+ modified_content = _add_items_after_key (
369+ content = modified_content ,
370+ key = "deep-preserve-regex:" ,
371+ items = owlbot_config .additions .deep_preserve_regex ,
372+ quote = True ,
373+ )
374+ if owlbot_config .additions .deep_copy_regex :
375+ modified_content = _add_deep_copy_regex_items (
376+ content = modified_content ,
377+ key = "deep-copy-regex:" ,
378+ items = owlbot_config .additions .deep_copy_regex ,
379+ )
380+
381+ return modified_content
382+
383+
384+ def _add_items_after_key (
385+ content : str , key : str , items : list [str ], quote : bool = False
386+ ) -> str :
387+ """
388+ Adds items after a specified key in the content, following the template pattern.
389+
390+ Args:
391+ content: The generated owlbot.yaml content.
392+ key: The key after which to add the items (e.g., "deep-remove-regex:").
393+ items: The list of items to add.
394+ quote: Whether to enclose the item in double quotes.
395+
396+ Returns:
397+ The modified content.
398+ """
399+
400+ def format_item (item : str ) -> str :
401+ if quote :
402+ return f'- "{ item } "\n '
403+ else :
404+ return f"- { item } \n "
405+
406+ pattern = re .compile (rf"^{ re .escape (key )} " , re .MULTILINE )
407+ match = pattern .search (content )
408+
409+ if match :
410+ insert_position = match .end () + 1 # Insert after the newline
411+ new_items = "" .join (format_item (item ) for item in items )
412+ return content [:insert_position ] + new_items + content [insert_position :]
413+ else :
414+ return content # Key not found, return original content
415+
416+
417+ def _add_deep_copy_regex_items (
418+ content : str , key : str , items : list ["DeepCopyRegexItem" ]
419+ ) -> str :
420+ """
421+ Adds deep_copy_regex items after a specified key in the content.
422+
423+ Args:
424+ content: The generated owlbot.yaml content.
425+ key: The key after which to add the items (e.g., "deep-copy-regex:").
426+ items: The list of DeepCopyRegexItem objects to add.
427+
428+ Returns:
429+ The modified content.
430+ """
431+
432+ pattern = re .compile (rf"^{ re .escape (key )} " , re .MULTILINE )
433+ match = pattern .search (content )
434+
435+ if match :
436+ insert_position = match .end () + 1 # Insert after the newline
437+ new_items = "" .join (
438+ f'- source: "{ item .source } "\n dest: "{ item .dest } "\n ' for item in items
439+ )
440+ return content [:insert_position ] + new_items + content [insert_position :]
441+ else :
442+ return content
0 commit comments