Skip to content

Commit 4d4c798

Browse files
authored
chore: migrate config change functions to common module (#3311)
In this PR: - Migrate config change functions to `common` module. - Library generation will accept library names directly.
1 parent ee34704 commit 4d4c798

27 files changed

+335
-372
lines changed

.github/scripts/action.yaml

-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ runs:
6060
cd "${GITHUB_WORKSPACE}"
6161
pip install --require-hashes -r hermetic_build/common/requirements.txt
6262
pip install hermetic_build/common
63-
pip install --require-hashes -r hermetic_build/library_generation/requirements.txt
64-
pip install hermetic_build/library_generation
6563
pip install --require-hashes -r hermetic_build/release_note_generation/requirements.txt
6664
pip install hermetic_build/release_note_generation
6765
- name: Generate changed libraries

.github/scripts/hermetic_library_generation.sh

+8-2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ pushd "${api_def_dir}"
9292
git checkout "${googleapis_commitish}"
9393
popd
9494

95+
# get changed library list.
96+
changed_libraries=$(python hermetic_build/common/cli/get_changed_libraries.py create \
97+
--baseline-generation-config-path="${baseline_generation_config}" \
98+
--current-generation-config-path="${generation_config}")
99+
echo "Changed libraries are: ${changed_libraries:-"No changed library"}."
100+
95101
# run hermetic code generation docker image.
96102
docker run \
97103
--rm \
@@ -101,8 +107,8 @@ docker run \
101107
-v "${api_def_dir}:${workspace_name}/googleapis" \
102108
-e GENERATOR_VERSION="${image_tag}" \
103109
gcr.io/cloud-devrel-public-resources/java-library-generation:"${image_tag}" \
104-
--baseline-generation-config-path="${workspace_name}/${baseline_generation_config}" \
105-
--current-generation-config-path="${workspace_name}/${generation_config}" \
110+
--generation-config-path="${workspace_name}/${generation_config}" \
111+
--library-names="${changed_libraries}" \
106112
--api-definitions-path="${workspace_name}/googleapis"
107113

108114
python hermetic_build/release_note_generation/cli/generate_release_note.py generate \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
16+
import click as click
17+
18+
from common.model.generation_config import from_yaml
19+
from common.utils.generation_config_comparator import compare_config
20+
21+
22+
@click.group(invoke_without_command=False)
23+
@click.pass_context
24+
@click.version_option(message="%(version)s")
25+
def main(ctx):
26+
pass
27+
28+
29+
@main.command()
30+
@click.option(
31+
"--baseline-generation-config-path",
32+
required=True,
33+
type=str,
34+
help="""
35+
Absolute or relative path to a generation_config.yaml.
36+
This config file is used for computing changed library list.
37+
""",
38+
)
39+
@click.option(
40+
"--current-generation-config-path",
41+
required=True,
42+
type=str,
43+
help="""
44+
Absolute or relative path to a generation_config.yaml that contains the
45+
metadata about library generation.
46+
""",
47+
)
48+
def create(
49+
baseline_generation_config_path: str,
50+
current_generation_config_path: str,
51+
) -> None:
52+
"""
53+
Compares baseline generation config with current generation config and
54+
generates changed library names (a comma separated string) based on current
55+
generation config.
56+
"""
57+
baseline_generation_config_path = os.path.abspath(baseline_generation_config_path)
58+
if not os.path.isfile(baseline_generation_config_path):
59+
raise FileNotFoundError(
60+
f"{baseline_generation_config_path} does not exist. "
61+
"A valid generation config has to be passed in as "
62+
"baseline-generation-config-path."
63+
)
64+
current_generation_config_path = os.path.abspath(current_generation_config_path)
65+
if not os.path.isfile(current_generation_config_path):
66+
raise FileNotFoundError(
67+
f"{current_generation_config_path} does not exist. "
68+
"A valid generation config has to be passed in as "
69+
"current-generation-config-path."
70+
)
71+
config_change = compare_config(
72+
baseline_config=from_yaml(baseline_generation_config_path),
73+
current_config=from_yaml(current_generation_config_path),
74+
)
75+
changed_libraries = config_change.get_changed_libraries()
76+
if changed_libraries is None:
77+
print("No changed library.")
78+
return
79+
click.echo(",".join(config_change.get_changed_libraries()))
80+
81+
82+
if __name__ == "__main__":
83+
main()

hermetic_build/library_generation/model/config_change.py renamed to hermetic_build/common/model/config_change.py

+18-24
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
import os
15-
import shutil
14+
import tempfile
1615
from enum import Enum
1716
from typing import Optional
1817
from git import Commit, Repo
19-
2018
from common.model.gapic_inputs import parse_build_str
2119
from common.model.generation_config import GenerationConfig
2220
from common.model.library_config import LibraryConfig
23-
from library_generation.utils.utilities import sh_util
24-
from library_generation.utils.proto_path_utils import find_versioned_proto_path
21+
from common.utils.proto_path_utils import find_versioned_proto_path
2522

2623
INSERTIONS = "insertions"
2724
LINES = "lines"
@@ -109,25 +106,22 @@ def get_qualified_commits(
109106
:param repo_url: the repository contains the commit history.
110107
:return: QualifiedCommit objects.
111108
"""
112-
tmp_dir = sh_util("get_output_folder")
113-
shutil.rmtree(tmp_dir, ignore_errors=True)
114-
os.mkdir(tmp_dir)
115-
# we only need commit history, thus shadow clone is enough.
116-
repo = Repo.clone_from(url=repo_url, to_path=tmp_dir, filter=["blob:none"])
117-
commit = repo.commit(self.current_config.googleapis_commitish)
118-
proto_paths = self.current_config.get_proto_path_to_library_name()
119-
qualified_commits = []
120-
while str(commit.hexsha) != self.baseline_config.googleapis_commitish:
121-
qualified_commit = ConfigChange.__create_qualified_commit(
122-
proto_paths=proto_paths, commit=commit
123-
)
124-
if qualified_commit is not None:
125-
qualified_commits.append(qualified_commit)
126-
commit_parents = commit.parents
127-
if len(commit_parents) == 0:
128-
break
129-
commit = commit_parents[0]
130-
shutil.rmtree(tmp_dir, ignore_errors=True)
109+
with tempfile.TemporaryDirectory() as tmp_dir:
110+
# we only need commit history, thus a shadow clone is enough.
111+
repo = Repo.clone_from(url=repo_url, to_path=tmp_dir, filter=["blob:none"])
112+
commit = repo.commit(self.current_config.googleapis_commitish)
113+
proto_paths = self.current_config.get_proto_path_to_library_name()
114+
qualified_commits = []
115+
while str(commit.hexsha) != self.baseline_config.googleapis_commitish:
116+
qualified_commit = ConfigChange.__create_qualified_commit(
117+
proto_paths=proto_paths, commit=commit
118+
)
119+
if qualified_commit is not None:
120+
qualified_commits.append(qualified_commit)
121+
commit_parents = commit.parents
122+
if len(commit_parents) == 0:
123+
break
124+
commit = commit_parents[0]
131125
return qualified_commits
132126

133127
def __get_library_names_from_qualified_commits(self) -> list[str]:

hermetic_build/common/requirements.in

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
black==24.8.0
2+
GitPython==3.1.43
23
parameterized==0.9.0
34
PyYAML==6.0.2

hermetic_build/common/requirements.txt

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ click==8.1.7 \
3232
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
3333
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
3434
# via black
35+
gitdb==4.0.11 \
36+
--hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \
37+
--hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b
38+
# via gitpython
39+
gitpython==3.1.43 \
40+
--hash=sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c \
41+
--hash=sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff
42+
# via -r hermetic_build/common/requirements.in
3543
mypy-extensions==1.0.0 \
3644
--hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \
3745
--hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782
@@ -107,3 +115,7 @@ pyyaml==6.0.2 \
107115
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
108116
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
109117
# via -r hermetic_build/common/requirements.in
118+
smmap==5.0.1 \
119+
--hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \
120+
--hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da
121+
# via gitdb

hermetic_build/common/tests/cli/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
from click.testing import CliRunner
16+
import unittest
17+
18+
from common.cli.get_changed_libraries import create
19+
20+
script_dir = os.path.dirname(os.path.realpath(__file__))
21+
test_resource_dir = os.path.join(script_dir, "..", "resources", "cli")
22+
23+
24+
class GetChangedLibrariesTest(unittest.TestCase):
25+
def test_entry_point_without_baseline_config_raise_system_exception(self):
26+
os.chdir(script_dir)
27+
runner = CliRunner()
28+
# noinspection PyTypeChecker
29+
result = runner.invoke(create)
30+
self.assertEqual(2, result.exit_code)
31+
self.assertEqual(SystemExit, result.exc_info[0])
32+
33+
def test_entry_point_without_current_config_raise_system_exception(self):
34+
os.chdir(script_dir)
35+
runner = CliRunner()
36+
# noinspection PyTypeChecker
37+
result = runner.invoke(
38+
create, ["--baseline-generation-config-path=/invalid/path/file"]
39+
)
40+
self.assertEqual(2, result.exit_code)
41+
self.assertEqual(SystemExit, result.exc_info[0])
42+
43+
def test_entry_point_with_invalid_baseline_config_raise_file_exception(self):
44+
os.chdir(script_dir)
45+
runner = CliRunner()
46+
# noinspection PyTypeChecker
47+
result = runner.invoke(
48+
create,
49+
[
50+
"--baseline-generation-config-path=/invalid/path/file",
51+
"--current-generation-config-path=/invalid/path/file",
52+
],
53+
)
54+
self.assertEqual(1, result.exit_code)
55+
self.assertEqual(FileNotFoundError, result.exc_info[0])
56+
self.assertRegex(result.exception.args[0], "baseline-generation-config-path")
57+
58+
def test_entry_point_with_invalid_current_config_raise_file_exception(self):
59+
os.chdir(script_dir)
60+
runner = CliRunner()
61+
# noinspection PyTypeChecker
62+
result = runner.invoke(
63+
create,
64+
[
65+
f"--baseline-generation-config-path={test_resource_dir}/empty_config.yaml",
66+
"--current-generation-config-path=/invalid/path/file",
67+
],
68+
)
69+
self.assertEqual(1, result.exit_code)
70+
self.assertEqual(FileNotFoundError, result.exc_info[0])
71+
self.assertRegex(result.exception.args[0], "current-generation-config-path")

hermetic_build/library_generation/tests/model/config_change_unit_tests.py renamed to hermetic_build/common/tests/model/config_change_unit_tests.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
# limitations under the License.
1414
import unittest
1515

16-
from library_generation.model.config_change import ChangeType
17-
from library_generation.model.config_change import ConfigChange
18-
from library_generation.model.config_change import LibraryChange
16+
from common.model.config_change import ChangeType
17+
from common.model.config_change import ConfigChange
18+
from common.model.config_change import LibraryChange
1919
from common.model.gapic_config import GapicConfig
2020
from common.model.generation_config import GenerationConfig
2121
from common.model.library_config import LibraryConfig

hermetic_build/common/tests/resources/cli/empty_config.yaml

Whitespace-only changes.

hermetic_build/common/tests/utils/__init__.py

Whitespace-only changes.
+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from common.model.gapic_config import GapicConfig
1717
from common.model.generation_config import GenerationConfig
1818
from common.model.library_config import LibraryConfig
19-
from library_generation.utils.generation_config_comparator import ChangeType
20-
from library_generation.utils.generation_config_comparator import compare_config
19+
from common.utils.generation_config_comparator import ChangeType
20+
from common.utils.generation_config_comparator import compare_config
2121

2222

2323
class GenerationConfigComparatorTest(unittest.TestCase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import os
16+
import unittest
17+
from pathlib import Path
18+
from common.utils.proto_path_utils import find_versioned_proto_path
19+
20+
script_dir = os.path.dirname(os.path.realpath(__file__))
21+
resources_dir = os.path.join(script_dir, "..", "resources")
22+
test_config_dir = Path(os.path.join(resources_dir, "test-config")).resolve()
23+
24+
25+
class ProtoPathsUtilsTest(unittest.TestCase):
26+
def test_find_versioned_proto_path_nested_version_success(self):
27+
proto_path = "google/cloud/aiplatform/v1/schema/predict/params/image_classification.proto"
28+
expected = "google/cloud/aiplatform/v1"
29+
self.assertEqual(expected, find_versioned_proto_path(proto_path))
30+
31+
def test_find_versioned_proto_path_success(self):
32+
proto_path = "google/cloud/asset/v1p2beta1/assets.proto"
33+
expected = "google/cloud/asset/v1p2beta1"
34+
self.assertEqual(expected, find_versioned_proto_path(proto_path))
35+
36+
def test_find_versioned_proto_without_version_return_itself(self):
37+
proto_path = "google/type/color.proto"
38+
expected = "google/type/color.proto"
39+
self.assertEqual(expected, find_versioned_proto_path(proto_path))

hermetic_build/library_generation/utils/generation_config_comparator.py renamed to hermetic_build/common/utils/generation_config_comparator.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from typing import Any
1616
from typing import Dict
1717
from typing import List
18-
from library_generation.model.config_change import ChangeType
19-
from library_generation.model.config_change import ConfigChange
20-
from library_generation.model.config_change import LibraryChange
21-
from library_generation.model.config_change import HashLibrary
18+
from common.model.config_change import ChangeType
19+
from common.model.config_change import ConfigChange
20+
from common.model.config_change import LibraryChange
21+
from common.model.config_change import HashLibrary
2222
from common.model.gapic_config import GapicConfig
2323
from common.model.generation_config import GenerationConfig
2424
from common.model.library_config import LibraryConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import re
16+
17+
18+
def find_versioned_proto_path(proto_path: str) -> str:
19+
"""
20+
Returns a versioned proto_path from a given proto_path; or proto_path itself
21+
if it doesn't contain a versioned proto_path.
22+
:param proto_path: a proto file path
23+
:return: the versioned proto_path
24+
"""
25+
version_regex = re.compile(r"^v[1-9].*")
26+
directories = proto_path.split("/")
27+
for directory in directories:
28+
result = version_regex.search(directory)
29+
if result:
30+
version = result[0]
31+
idx = proto_path.find(version)
32+
return proto_path[:idx] + version
33+
return proto_path

0 commit comments

Comments
 (0)