Skip to content

Commit 8312706

Browse files
authored
feat: add config change (#2604)
In this PR: - Add `config_change.py` to get: - Changed libraries, which will be passed to `generate_repo.py` to generate libraries selectedly. - Qualified commits, which will be passed to `generate_pr_description.py` to generate PR description. - Refactor part of utility methods to `proto_path_utils.py`. - Refactor `generation_config_comparator.py` to move data class to `config_change.py`. - Refactor `utilities.py` and `utilities.sh` to `utils/utilities.py` and `utils/utilities.sh`. - Add unit tests. Follow up of #2587 For the goal of series of PRs, please refer to [improvement proposal](https://docs.google.com/document/d/1JiCcG3X7lnxaJErKe0ES_JkyU7ECb40nf2Xez3gWvuo/edit?tab=t.g3vua2kd06gx#bookmark=id.72s3ukwwzevo).
1 parent 1457b7d commit 8312706

22 files changed

+726
-213
lines changed

library_generation/generate_composed_library.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import os
3131
from pathlib import Path
3232
from typing import List
33-
import library_generation.utilities as util
33+
import library_generation.utils.utilities as util
3434
from library_generation.model.generation_config import GenerationConfig
3535
from library_generation.model.gapic_config import GapicConfig
3636
from library_generation.model.gapic_inputs import GapicInputs

library_generation/generate_library.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ done
7474

7575
script_dir=$(dirname "$(readlink -f "$0")")
7676
# source utility functions
77-
source "${script_dir}"/utilities.sh
77+
source "${script_dir}"/utils/utilities.sh
7878
output_folder="$(get_output_folder)"
7979

8080
if [ -z "${gapic_generator_version}" ]; then

library_generation/generate_pr_description.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@
1919
import click
2020
from git import Commit, Repo
2121
from library_generation.model.generation_config import from_yaml
22-
from library_generation.utilities import find_versioned_proto_path
22+
from library_generation.utils.proto_path_utils import find_versioned_proto_path
2323
from library_generation.utils.commit_message_formatter import format_commit_message
24-
from library_generation.utilities import get_file_paths
25-
from library_generation.utils.commit_message_formatter import wrap_nested_commit
2624
from library_generation.utils.commit_message_formatter import wrap_override_commit
2725

2826

@@ -87,7 +85,7 @@ def generate_pr_descriptions(
8785
baseline_commit: str,
8886
) -> str:
8987
config = from_yaml(generation_config_yaml)
90-
paths = get_file_paths(config)
88+
paths = config.get_proto_path_to_library_name()
9189
return __get_commit_messages(
9290
repo_url=repo_url,
9391
latest_commit=config.googleapis_commitish,

library_generation/generate_repo.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
import library_generation.utilities as util
16+
import library_generation.utils.utilities as util
1717
import click
1818
import os
1919
from library_generation.generate_composed_library import generate_composed_library
+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Copyright 2024 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import os
15+
import shutil
16+
from enum import Enum
17+
from typing import Optional
18+
from git import Commit, Repo
19+
from library_generation.model.generation_config import GenerationConfig
20+
from library_generation.model.library_config import LibraryConfig
21+
from library_generation.utils.utilities import sh_util
22+
from library_generation.utils.proto_path_utils import find_versioned_proto_path
23+
24+
25+
class ChangeType(Enum):
26+
GOOGLEAPIS_COMMIT = 1
27+
REPO_LEVEL_CHANGE = 2
28+
LIBRARIES_ADDITION = 3
29+
# As of Mar. 2024, we decide not to produce this type of change because we
30+
# still need to manually remove the libray.
31+
# LIBRARIES_REMOVAL = 4
32+
LIBRARY_LEVEL_CHANGE = 5
33+
GAPIC_ADDITION = 6
34+
# As of Mar. 2024, we decide not to produce this type of change because we
35+
# still need to manually remove the libray.
36+
# GAPIC_REMOVAL = 7
37+
38+
39+
class HashLibrary:
40+
"""
41+
Data class to group a LibraryConfig object and its hash value together.
42+
"""
43+
44+
def __init__(self, hash_value: int, library: LibraryConfig):
45+
self.hash_value = hash_value
46+
self.library = library
47+
48+
49+
class LibraryChange:
50+
def __init__(self, changed_param: str, latest_value: str, library_name: str = ""):
51+
self.changed_param = changed_param
52+
self.latest_value = latest_value
53+
self.library_name = library_name
54+
55+
56+
class QualifiedCommit:
57+
def __init__(self, commit: Commit, libraries: set[str]):
58+
self.commit = commit
59+
self.libraries = libraries
60+
61+
62+
class ConfigChange:
63+
ALL_LIBRARIES_CHANGED = None
64+
65+
def __init__(
66+
self,
67+
change_to_libraries: dict[ChangeType, list[LibraryChange]],
68+
baseline_config: GenerationConfig,
69+
latest_config: GenerationConfig,
70+
):
71+
self.change_to_libraries = change_to_libraries
72+
self.baseline_config = baseline_config
73+
self.latest_config = latest_config
74+
75+
def get_changed_libraries(self) -> Optional[list[str]]:
76+
"""
77+
Returns a unique, sorted list of library name of changed libraries.
78+
None if there is a repository level change, which means all libraries
79+
in the latest_config will be generated.
80+
:return: library names of change libraries.
81+
"""
82+
if ChangeType.REPO_LEVEL_CHANGE in self.change_to_libraries:
83+
return ConfigChange.ALL_LIBRARIES_CHANGED
84+
library_names = set()
85+
for change_type, library_changes in self.change_to_libraries.items():
86+
if change_type == ChangeType.GOOGLEAPIS_COMMIT:
87+
library_names.update(self.__get_library_names_from_qualified_commits())
88+
else:
89+
library_names.update(
90+
[library_change.library_name for library_change in library_changes]
91+
)
92+
return sorted(list(library_names))
93+
94+
def get_qualified_commits(
95+
self,
96+
repo_url: str = "https://github.com/googleapis/googleapis.git",
97+
) -> list[QualifiedCommit]:
98+
"""
99+
Returns qualified commits from configuration change.
100+
101+
A qualified commit is a commit that changes at least one file (excluding
102+
BUILD.bazel) within a versioned proto path in the given proto_paths.
103+
:param repo_url: the repository contains the commit history.
104+
:return: QualifiedCommit objects.
105+
"""
106+
tmp_dir = sh_util("get_output_folder")
107+
shutil.rmtree(tmp_dir, ignore_errors=True)
108+
os.mkdir(tmp_dir)
109+
# we only need commit history, thus shadow clone is enough.
110+
repo = Repo.clone_from(url=repo_url, to_path=tmp_dir, filter=["blob:none"])
111+
commit = repo.commit(self.latest_config.googleapis_commitish)
112+
proto_paths = self.latest_config.get_proto_path_to_library_name()
113+
qualified_commits = []
114+
while str(commit.hexsha) != self.baseline_config.googleapis_commitish:
115+
qualified_commit = ConfigChange.__create_qualified_commit(
116+
proto_paths=proto_paths, commit=commit
117+
)
118+
if qualified_commit is not None:
119+
qualified_commits.append(qualified_commit)
120+
commit_parents = commit.parents
121+
if len(commit_parents) == 0:
122+
break
123+
commit = commit_parents[0]
124+
shutil.rmtree(tmp_dir, ignore_errors=True)
125+
return qualified_commits
126+
127+
def __get_library_names_from_qualified_commits(self) -> list[str]:
128+
qualified_commits = self.get_qualified_commits()
129+
library_names = []
130+
for qualified_commit in qualified_commits:
131+
library_names.extend(qualified_commit.libraries)
132+
return library_names
133+
134+
@staticmethod
135+
def __create_qualified_commit(
136+
proto_paths: dict[str, str], commit: Commit
137+
) -> Optional[QualifiedCommit]:
138+
"""
139+
Returns a qualified commit from the given Commit object; otherwise None.
140+
141+
:param proto_paths: a mapping from versioned proto_path to library_name
142+
:param commit: a GitHub commit object.
143+
:return: qualified commits.
144+
"""
145+
libraries = set()
146+
for file in commit.stats.files.keys():
147+
if file.endswith("BUILD.bazel"):
148+
continue
149+
versioned_proto_path = find_versioned_proto_path(file)
150+
if versioned_proto_path in proto_paths:
151+
# Even though a commit usually only changes one
152+
# library, we don't want to miss generating a
153+
# library because the commit may change multiple
154+
# libraries.
155+
libraries.add(proto_paths[versioned_proto_path])
156+
if len(libraries) == 0:
157+
return None
158+
return QualifiedCommit(commit=commit, libraries=libraries)

library_generation/model/generation_config.py

+12
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ def __init__(
4949
# monorepos have more than one library defined in the config yaml
5050
self.is_monorepo = len(libraries) > 1
5151

52+
def get_proto_path_to_library_name(self) -> dict[str, str]:
53+
"""
54+
Get versioned proto_path to library_name mapping from configuration.
55+
56+
:return: versioned proto_path to library_name mapping
57+
"""
58+
paths = {}
59+
for library in self.libraries:
60+
for gapic_config in library.gapic_configs:
61+
paths[gapic_config.proto_path] = library.get_library_name()
62+
return paths
63+
5264

5365
def from_yaml(path_to_yaml: str) -> GenerationConfig:
5466
"""

library_generation/postprocess_library.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ synthtool_commitish=$6
3434
is_monorepo=$7
3535
configuration_yaml_path=$8
3636

37-
source "${scripts_root}"/utilities.sh
37+
source "${scripts_root}"/utils/utilities.sh
3838

3939
declare -a required_inputs=("postprocessing_target" "versions_file" "owlbot_cli_image_sha" "synthtool_commitish" "is_monorepo")
4040
for required_input in "${required_inputs[@]}"; do

library_generation/setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
},
1313
package_data={
1414
"library_generation": [
15-
"*.sh",
15+
"generate_library.sh",
16+
"postprocess_library.sh",
17+
"utils/utilities.sh",
1618
"templates/*.j2",
1719
"gapic-generator-java-wrapper",
1820
"requirements.*",

library_generation/test/compare_poms.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
The only comparison points are: element path (e.g. project/dependencies) and element text
55
There is a special case for `dependency`, where the maven coordinates are prepared as well
66
"""
7-
from library_generation.utilities import eprint
7+
from library_generation.utils.utilities import eprint
88
import xml.etree.ElementTree as et
99
from collections import Counter
1010
import sys

library_generation/test/generate_library_unit_tests.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ set -xeo pipefail
55
# Unit tests against ../utilities.sh
66
script_dir=$(dirname "$(readlink -f "$0")")
77
source "${script_dir}"/test_utilities.sh
8-
source "${script_dir}"/../utilities.sh
8+
source "${script_dir}"/../utils/utilities.sh
99

1010
# Unit tests
1111
extract_folder_name_test() {

library_generation/test/integration_tests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from library_generation.generate_repo import generate_from_yaml
2929
from library_generation.model.generation_config import from_yaml, GenerationConfig
3030
from library_generation.test.compare_poms import compare_xml
31-
from library_generation.utilities import (
31+
from library_generation.utils.utilities import (
3232
sh_util as shell_call,
3333
run_process_and_print_output,
3434
)

0 commit comments

Comments
 (0)