Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: transfer java-related synthtool code into the library_generation image #3011

Merged
merged 34 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3e1164
chore: move relevant synthtool source into hermetic docker image
diegomarquezp Jul 3, 2024
75684bd
restructure scripts
diegomarquezp Jul 3, 2024
30fa2c4
simplify gcp.common
diegomarquezp Jul 3, 2024
af71764
simplify java.py
diegomarquezp Jul 3, 2024
c61a80c
add transforms
diegomarquezp Jul 3, 2024
13a27a9
fix runtime errors
diegomarquezp Jul 3, 2024
46c841c
exclude usage of metadata
diegomarquezp Jul 3, 2024
f2fb117
fix logger creation
diegomarquezp Jul 3, 2024
420d7f4
remove unused cache and shell imports
diegomarquezp Jul 3, 2024
09af9a9
bring partials.py into gcp/common.py
diegomarquezp Jul 3, 2024
b1d33e3
use transferred partials in java.py
diegomarquezp Jul 3, 2024
fb3e399
final adjustments, add samples and snippets
diegomarquezp Jul 3, 2024
e4e1770
add requirements files
diegomarquezp Jul 3, 2024
d93093d
install java-only synthool in dockerfile
diegomarquezp Jul 3, 2024
80e008d
remove unused languages/common.py
diegomarquezp Jul 3, 2024
5fd8f9e
add changes of https://github.com/googleapis/synthtool/commit/696c4bf…
diegomarquezp Jul 3, 2024
635d92a
lint
diegomarquezp Jul 3, 2024
8dbdd90
remove unused transforms function
diegomarquezp Jul 4, 2024
b25bd60
merge setup.py files
diegomarquezp Jul 4, 2024
643f957
remove unused default_branch function
diegomarquezp Jul 4, 2024
f03aac3
merge requirements file
diegomarquezp Jul 4, 2024
3c6d02a
use base requirements file
diegomarquezp Jul 4, 2024
5d99c69
remove installation of synthool in favor of combined installation
diegomarquezp Jul 4, 2024
fd39dad
remove unused function
diegomarquezp Jul 4, 2024
35e27da
use variable names for java.py
diegomarquezp Jul 4, 2024
605266d
use variables for key references in java.py
diegomarquezp Jul 4, 2024
9530478
Merge branch 'main' into transfer-synthtool
diegomarquezp Jul 5, 2024
c7f86e2
remove duplicated dependency
diegomarquezp Jul 5, 2024
6698065
lint
diegomarquezp Jul 5, 2024
065273e
restore format of golden owlbot.py
diegomarquezp Jul 5, 2024
6186e79
reduce unnecessary dependencies
diegomarquezp Jul 5, 2024
6ab904b
pin typing extensions
diegomarquezp Jul 5, 2024
0780ad1
enrich comment of the typing-extensions dependency in requirements.in
diegomarquezp Jul 5, 2024
7c56ce0
make snippet bot to ignore the synthtool snippet script
diegomarquezp Jul 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions .cloudbuild/library_generation/library_generation.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ FROM gcr.io/cloud-devrel-public-resources/python

SHELL [ "/bin/bash", "-c" ]

ARG SYNTHTOOL_COMMITTISH=696c4bff721f5541cd75fdc97d413f8f39d2a2c1
ARG OWLBOT_CLI_COMMITTISH=ac84fa5c423a0069bbce3d2d869c9730c8fdf550
ARG PROTOC_VERSION=25.3
ENV HOME=/home
Expand All @@ -44,19 +43,11 @@ RUN ln -s $(which python3.11) /usr/local/bin/python
RUN ln -s $(which python3.11) /usr/local/bin/python3
RUN python -m pip install --upgrade pip

# install scripts as a python package
# install main scripts as a python package
WORKDIR /src
RUN python -m pip install -r requirements.txt
RUN python -m pip install .

# install synthtool
WORKDIR /tools
RUN git clone https://github.com/googleapis/synthtool
WORKDIR /tools/synthtool
RUN git checkout "${SYNTHTOOL_COMMITTISH}"
RUN python3 -m pip install --no-deps -e .
RUN python3 -m pip install -r requirements.in

# Install nvm with node and npm
ENV NODE_VERSION 20.12.0
WORKDIR /home
Expand Down
1 change: 1 addition & 0 deletions .github/snippet-bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ ignoreFiles:
- test/**
- showcase/**
- library_generation/owlbot/templates/java_library/samples/install-without-bom/pom.xml
- library_generation/owlbot/synthtool/gcp/snippets.py
34 changes: 34 additions & 0 deletions library_generation/owlbot/synthtool/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Synthtool synthesizes libraries from disparate sources."""

import sys

from synthtool.transforms import (
move,
replace,
get_staging_dirs,
remove_staging_dirs,
)

copy = move

__all__ = [
"copy",
"move",
"replace",
"get_staging_dirs",
"remove_staging_dirs",
]
39 changes: 39 additions & 0 deletions library_generation/owlbot/synthtool/_tracked_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tracked paths.

This is a bit of a hack (imported from original synthtool library).
"""

import pathlib


_tracked_paths = []


def add(path):
_tracked_paths.append(pathlib.Path(path))
# Reverse sort the list, so that the deepest paths get matched first.
_tracked_paths.sort(key=lambda s: -len(str(s)))


def relativize(path):
path = pathlib.Path(path)
for tracked_path in _tracked_paths:
try:
return path.relative_to(tracked_path)
except ValueError:
pass
raise ValueError(f"The root for {path} is not tracked.")
153 changes: 153 additions & 0 deletions library_generation/owlbot/synthtool/gcp/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import os
import re
import sys
import shutil
import yaml
from pathlib import Path
from typing import Dict, List, Optional
import jinja2
import logging

from synthtool import _tracked_paths
from synthtool.sources import templates

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)


DEFAULT_TEMPLATES_PATH = "synthtool/gcp/templates"
LOCAL_TEMPLATES: Optional[str] = os.environ.get("SYNTHTOOL_TEMPLATES")

# Originally brought from gcp/partials.py.
# These are the default locations to look up
_DEFAULT_PARTIAL_FILES = [
".readme-partials.yml",
".readme-partials.yaml",
".integration-partials.yaml",
]


class CommonTemplates:
def __init__(self, template_path: Optional[Path] = None):
if LOCAL_TEMPLATES is None:
logger.error("env var SYNTHTOOL_TEMPLATES must be set")
sys.exit(1)
logger.debug(f"Using local templates at {LOCAL_TEMPLATES}")
self._template_root = Path(LOCAL_TEMPLATES)
self._templates = templates.Templates(self._template_root)
self.excludes = [] # type: List[str]

def _generic_library(self, directory: str, relative_dir=None, **kwargs) -> Path:
# load common repo meta information (metadata that's not language specific).
if "metadata" in kwargs:
self._load_generic_metadata(kwargs["metadata"], relative_dir=relative_dir)
# if no samples were found, don't attempt to render a
# samples/README.md.
if "samples" not in kwargs["metadata"] or not kwargs["metadata"]["samples"]:
self.excludes.append("samples/README.md")

t = templates.TemplateGroup(self._template_root / directory, self.excludes)

result = t.render(**kwargs)
_tracked_paths.add(result)

return result

def java_library(self, **kwargs) -> Path:
# kwargs["metadata"] is required to load values from .repo-metadata.json
if "metadata" not in kwargs:
kwargs["metadata"] = {}
return self._generic_library("java_library", **kwargs)

def render(self, template_name: str, **kwargs) -> Path:
template = self._templates.render(template_name, **kwargs)
_tracked_paths.add(template)
return template

def _load_generic_metadata(self, metadata: Dict, relative_dir=None):
"""
loads additional meta information from .repo-metadata.json.
"""
metadata["partials"] = load_partials()

# Loads repo metadata information from the default location if it
# hasn't already been set. Some callers may have already loaded repo
# metadata, so we don't need to do it again or overwrite it. Also, only
# set the "repo" key.
if "repo" not in metadata:
metadata["repo"] = _load_repo_metadata(relative_dir=relative_dir)


def _load_repo_metadata(
relative_dir=None, metadata_file: str = "./.repo-metadata.json"
) -> Dict:
"""Parse a metadata JSON file into a Dict.

Currently, the defined fields are:
* `name` - The service's API name
* `name_pretty` - The service's API title. This will be used for generating titles on READMEs
* `product_documentation` - The product documentation on cloud.google.com
* `client_documentation` - The client library reference documentation
* `issue_tracker` - The public issue tracker for the product
* `release_level` - The release level of the client library. One of: alpha, beta,
ga, deprecated, preview, stable
* `language` - The repo language. One of dotnet, go, java, nodejs, php, python, ruby
* `repo` - The GitHub repo in the format {owner}/{repo}
* `distribution_name` - The language-idiomatic package/distribution name
* `api_id` - The API ID associated with the service. Fully qualified identifier use to
enable a service in the cloud platform (e.g. monitoring.googleapis.com)
* `requires_billing` - Whether or not the API requires billing to be configured on the
customer's acocunt

Args:
metadata_file (str, optional): Path to the metadata json file

Returns:
A dictionary of metadata. This may not necessarily include all the defined fields above.
"""
if relative_dir is not None:
if os.path.exists(Path(relative_dir, metadata_file).resolve()):
with open(Path(relative_dir, metadata_file).resolve()) as f:
return json.load(f)
elif os.path.exists(metadata_file):
with open(metadata_file) as f:
return json.load(f)
return {}


def load_partials(files: List[str] = []) -> Dict:
"""
hand-crafted artisanal markdown can be provided in a .readme-partials.yml.
The following fields are currently supported:

body: custom body to include in the usage section of the document.
samples_body: an optional body to place below the table of contents
in samples/README.md.
introduction: a more thorough introduction than metadata["description"].
title: provide markdown to use as a custom title.
deprecation_warning: a warning to indicate that the library has been
deprecated and a pointer to an alternate option
"""
result: Dict[str, Dict] = {}
cwd_path = Path(os.getcwd())
for file in files + _DEFAULT_PARTIAL_FILES:
partials_file = cwd_path / file
if os.path.exists(partials_file):
with open(partials_file) as f:
result.update(yaml.load(f, Loader=yaml.SafeLoader))
return result
91 changes: 91 additions & 0 deletions library_generation/owlbot/synthtool/gcp/samples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import glob
import logging
import re
import os
import yaml
from typing import List, Dict

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)


def _read_sample_metadata_comment(sample_file: str) -> Dict:
"""Additional meta-information can be provided through embedded comments:

// sample-metadata:
// title: ACL (Access Control)
// description: Demonstrates setting access control rules.
// usage: node iam.js --help
"""
sample_metadata = {} # type: Dict[str, str]
with open(sample_file) as f:
contents = f.read()
match = re.search(
r"(?P<metadata>// *sample-metadata:([^\n]+|\n//)+)", contents, re.DOTALL
)
if match:
# the metadata yaml is stored in a comments, remove the
# prefix so that we can parse the yaml contained.
sample_metadata_string = re.sub(r"((#|//) ?)", "", match.group("metadata"))
try:
sample_metadata = yaml.load(
sample_metadata_string, Loader=yaml.SafeLoader
)["sample-metadata"]
except yaml.scanner.ScannerError:
# warn and continue on bad metadata
logger.warning(f"bad metadata detected in {sample_file}")
return sample_metadata


def _sample_metadata(file: str) -> Dict[str, str]:
metadata = {
"title": _decamelize(os.path.splitext(os.path.basename(file))[0]),
"file": file,
}
return {**metadata, **_read_sample_metadata_comment(file)}


def all_samples(sample_globs: List[str]) -> List[Dict[str, str]]:
"""Walks samples directory and builds up samples data-structure

Args:
sample_globs: (List[str]): List of path globs to search for samples

Returns:
A list of sample metadata in the format:
{
"title": "Requester Pays",
"file": "samples/requesterPays.js"
}
The file path is the relative path from the repository root.
"""
files = []
for sample_glob in sample_globs:
for file in glob.glob(sample_glob, recursive=True):
files.append(file)
return [_sample_metadata(file) for file in sorted(files)]


def _decamelize(value: str):
"""Parser to convert fooBar.js to Foo Bar."""
if not value:
return ""
str_decamelize = re.sub("^.", value[0].upper(), value) # apple -> Apple.
str_decamelize = re.sub(
"([A-Z]+)([A-Z])([a-z0-9])", r"\1 \2\3", str_decamelize
) # ACLBatman -> ACL Batman.
return re.sub("([a-z0-9])([A-Z])", r"\1 \2", str_decamelize) # FooBar -> Foo Bar.
Loading
Loading