1# SynthTool (for client libraries) 2 3 4 5This tool helps to generate and layout cloud client libraries. Synthtool runs the [GAPIC (Generated API Client) Generator][GAPIC] via [Google API Artifact Manager (artman)][artman]. 6 7[GAPIC]: https://github.com/googleapis/gapic-generator 8[artman]: https://github.com/googleapis/artman 9 10## Prerequisites 11 121. **Linux** This tool runs on Linux only. No other platforms are supported. 13 142. **Python 3.6** Either install it from [python.org][python_downloads] or use 15[pyenv][] to get 3.6. 16 173. **Bazel** can be downloaded from [bazel.build](https://bazel.build/). 18 194. **Docker** Some synth.py files require [Docker] to generate code. 20 215. Clone this repository and install this library with pip: 22 23 ``` 24 cd synthtool 25 python3 -m pip install -e . 26 ``` 27 28 29[python_downloads]: https://www.python.org/downloads/ 30[pyenv]: https://github.com/pyenv/pyenv 31[Docker]: https://docs.docker.com/v17.09/engine/installation/#desktop 32 33 34## Basic usage 35To start the process of generation, clone the destination repository. 36``` 37git clone [email protected]:googleapis/python-tasks.git 38cd python-tasks/ 39``` 40 41### Running `synthtool` 42If a `synth.py` script is not present, create a new one. 43 44You can create one from scratch or copy one from another library. 45 - e.g. the `synth.py` for the Cloud Tasks API for [Python][python_tasks_synth_py], 46[Java][java_tasks_synth_py], [Node.js][node_tasks_synth_py], [PHP][php_tasks_synth_py], 47or [Ruby][ruby_tasks_synth_py]. 48 49Run `synthtool`: 50 51``` 52python3 -m synthtool 53``` 54 55After `synthtool` runs successfully: 56 - Investigate the changes it made 57 - Run the library tests 58 - Commit and push the changes to a branch and open a Pull Request 59 60Find examples below in different programming languages (Cloud Tasks API used as an example). 61 62### Python 63- Clone the destination repository: 64 ``` 65 git clone [email protected]:googleapis/python-tasks.git 66 cd python-tasks/ 67 ``` 68- Run `synthtool` to generate using the existing [`synth.py`][python_tasks_synth_py] 69 file for the [Python Client for Cloud Tasks API][python_tasks_library]: 70 ``` 71 python3 -m synthtool 72 ``` 73- See the Python [Contributing Guide][python_contributing] 74 or instructions to install dependencies, run tests, and submit a contribution. 75 76[python_tasks_library]: https://github.com/googleapis/python-tasks 77[python_tasks_synth_py]: https://github.com/googleapis/python-tasks/blob/master/synth.py 78[python_contributing]: https://github.com/googleapis/python-tasks/blob/master/CONTRIBUTING.rst 79 80### Java 81- Clone the destination repository: 82 ``` 83 git clone [email protected]:googleapis/java-tasks.git 84 cd java-tasks/ 85 ``` 86- Run `synthtool` to generate using the existing [`synth.py`][java_tasks_synth_py] 87 file for the [Google Cloud Java Client for Cloud Tasks][java_tasks_library]: 88 ``` 89 python3 -m synthtool 90 ``` 91- See the Java [Contributing Guide][java_contributing] 92 or instructions to install dependencies, run tests, and submit a contribution. 93 94[java_tasks_library]: https://github.com/googleapis/java-tasks 95[java_tasks_synth_py]: https://github.com/googleapis/java-tasks/blob/master/synth.py 96[java_contributing]: https://github.com/googleapis/java-tasks/blob/master/CONTRIBUTING.md 97 98### Node.js 99- Clone the destination repository: 100 ``` 101 git clone [email protected]:googleapis/nodejs-tasks.git 102 cd nodejs-tasks/ 103 ``` 104- Run `synthtool` to generate using the existing [`synth.py`][node_tasks_synth_py] 105 file for the [Google Cloud Tasks Node.js Client][node_tasks_library]: 106 ``` 107 python3 -m synthtool 108 ``` 109- See the Node.js [Contributing Guide][node_tasks_contributing] 110 or instructions to install dependencies, run tests, and submit a contribution. 111 112[node_tasks_library]: https://github.com/googleapis/nodejs-tasks 113[node_tasks_synth_py]: https://github.com/googleapis/nodejs-tasks/blob/master/synth.py 114[node_tasks_contributing]: https://github.com/googleapis/nodejs-tasks/blob/master/CONTRIBUTING.md 115 116### PHP 117- Clone the destination repository: 118 ``` 119 git clone [email protected]:googleapis/google-cloud-php.git 120 cd google-cloud-php/ 121 ``` 122- Navigate to the destination directory: 123 ``` 124 cd Tasks/ 125 ``` 126- Run `synthtool` to generate using the existing [`synth.py`][php_tasks_synth_py] 127 file for the [Google Cloud Tasks client for PHP][php_tasks_library]: 128 ``` 129 python3 -m synthtool 130 ``` 131- See the PHP [Contributing Guide][php_contributing] 132 or instructions to install dependencies, run tests, and submit a contribution. 133 134[php_tasks_library]: https://github.com/googleapis/google-cloud-php/tree/master/Tasks 135[php_tasks_synth_py]: https://github.com/googleapis/google-cloud-php/blob/master/Tasks/synth.py 136[php_contributing]: https://github.com/googleapis/google-cloud-php/blob/master/CONTRIBUTING.md 137 138### Ruby 139- Clone the destination repository: 140 ``` 141 git clone [email protected]:googleapis/google-cloud-ruby.git 142 cd google-cloud-ruby/ 143 ``` 144- Navigate to the destination directory: 145 ``` 146 cd google-cloud-tasks/ 147 ``` 148- Run `synthtool` to generate using the existing [`synth.py`][ruby_tasks_synth_py] 149 file for the [Ruby Client for Cloud Tasks API][ruby_tasks_library]: 150 ``` 151 python3 -m synthtool 152 ``` 153- See the Ruby [Contributing Guide][ruby_contributing] 154 or instructions to install dependencies, run tests, and submit a contribution. 155 156[ruby_tasks_library]: https://github.com/googleapis/google-cloud-ruby/tree/master/google-cloud-tasks 157[ruby_tasks_synth_py]: https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-tasks/synth.py 158[ruby_contributing]: https://github.com/googleapis/google-cloud-ruby/blob/master/.github/CONTRIBUTING.md 159 160## Features 161 162### Common transforms 163 164Functions in synthtool make it easier to copy files, merge files, etc. 165See the [pydocs](https://htmlpreview.github.io/?https://github.com/googleapis/synthtool/blob/master/synthtool/pydoc.html) for more details. 166 167### Templating 168SynthTool supports template files using [Jinja](http://jinja.pocoo.org/). 169 170Templates are found in subdirectories of [`synthtool/gcp/templates/`](gcp/templates/) 171for each language, 172 - e.g. the template directories for [Python][python_templates], 173[Node.js][node_templates], [PHP][php_templates], or [Ruby][ruby_templates]. 174 175[python_templates]: gcp/templates/python_library/ 176[node_templates]: gcp/templates/node_library/ 177[php_templates]: gcp/templates/php_library/ 178[ruby_templates]: gcp/templates/ruby_library/ 179 180You can generate and copy templates using `gcp.CommonTemplates` in your `synth.py`: 181```py 182common_templates = gcp.CommonTemplates() 183 184templates = common_templates.node_library() 185s.copy(templates) 186``` 187 188You can provide variables to templates as keyword arguments to the library generation method: 189 190```py 191common_templates = gcp.CommonTemplates() 192 193templates = common_templates.node_library(version=5, show_version=True, previous_versions=[1,2,3,4]) 194 195s.copy(templates) 196``` 197 198Template files can access any values provided, e.g. 199 - `README.md.j2` 200 ```py 201 {% if show_version %} 202 The version is {{ version }} 203 204 {% if previous versions is defined %} 205 Previous versions: 206 {% for ver in previous_versions %} 207 - {{ ver }} 208 {% endfor %} 209 {% endif %} 210 {% endif %} 211 ``` 212 213For more information on how to use Synthtool templating for Python Samples, view [/py_templating_instructions](./py_templating_instructions) 214 215You can learn more about Jinga templating in the 216[Template Designer Documentation](http://jinja.pocoo.org/docs/templates/). 217 218### googleapis-private 219SynthTool supports generation from googleapis/googleapis-private. 220 221```py 222gapic = gcp.GAPICGenerator() 223 224library = gapic.node_library('speech', 'v1', private=True) 225``` 2262FA is required to clone a private repo. 227 228* **Using SSH:** Before running Synthtool, set the environment variable `AUTOSYNTH_USE_SSH` to `true`. 229 230The repo is cloned using SSH. 231* **Using HTTPS:** Generate a [GitHub Personal Access Token](https://github.com/settings/tokens) with scope `repo`. 232Run `synthtool`. 233 234When GitHub prompts for your GitHub password, provide the access token instead. 235 236``` 237synthtool > Cloning googleapis-private. 238Username for 'https://github.com': busunkim96 239Password for 'https://[email protected]': 240``` 241 242### Artman Version 243SynthTool uses the latest version of the [Artman Docker image](https://hub.docker.com/r/googleapis/artman). 244You can change this by setting the environment variable `SYNTHTOOL_ARTMAN_VERSION` to the desired version tag. 245 246``` 247export SYNTHTOOL_ARTMAN_VERSION=0.16.2 248``` 249 250### GAPIC Generator Python Version 251SynthTool uses the latest version of [gcr.io/gapic-images/gapic-generator-python](https://gcr.io/gapic-images/gapic-generator-python). You can change this by 252setting the environment variable `SYNTHTOOL_GAPIC_GENERATOR_PYTHON_VERSION` to the desired version tag. 253 254``` 255export SYNTHTOOL_GAPIC_GENERATOR_PYTHON_VERSION=0.22.0 256``` 257 258Alternatively you can set the generator version by passing it to `gapic.py_library`. 259 260```python 261import synthtool as s 262import synthtool.gcp as gcp 263 264gapic = gcp.GAPICMicrogenerator() 265 266library = gapic.py_library( 267 "bigquery/connection", "v1beta1", generator_version="0.22.0" 268) 269``` 270 271### Local Googleapis 272SynthTool supports generation from a local copy of googleapis. 273Specify the path to `googleapis` in the environment variable `SYNTHTOOL_GOOGLEAPIS`. 274 275``` 276export SYNTHTOOL_GOOGLEAPIS=path/to/local/googleapis 277``` 278 279### Local GAPIC Generator 280SynthTool supports generation from a local copy of [gapic-generator](https://github.com/googleapis/gapic-generator). 281Specify the path to `gapic-generator` in the environment variable `SYNTHTOOL_GENERATOR`. 282 283``` 284export SYNTHTOOL_GENERATOR=path/to/local/gapic-generator 285``` 286 287Don't forget to compile `gapic-generator` before running SynthTool. 288 289``` 290cd path/to/local/gapic-generator 291./gradlew fatJar 292``` 293 294### Local Template Files 295SynthTool supports specifying a local directory of templates. Specify the path to the root 296template directory (not a SynthTool clone) in the environment variable `SYNTHTOOL_TEMPLATES`. 297 298``` 299export SYNTHTOOL_TEMPLATES=path/to/local/templates 300``` 301 302### Include .proto files 303SynthTool supports copying .proto API definition files from googleapis. 304 305```py 306gapic = gcp.GAPICGenerator() 307 308library = gapic.node_library('speech', 'v1', include_protos=True) 309``` 310 311## Context-Aware Commits 312 313Autosynth runs synthtool on your `synth.py` nightly or more frequently. 314By default, it runs synthtool once, and if the generated code differs, 315creates a PR with the differences. 316 317Autosynth can also find which changes in upstream repositories triggered changes 318in the generated code. To enable this behavior (context-aware commits), 319set one or both of the following flags in you synth.py file: 320 321```py 322AUTOSYNTH_MULTIPLE_COMMITS 323AUTOSYNTH_MULTIPLE_PRS 324``` 325 326### Example 327 328Assume that since the library source code was last generated, A, B and X, Y 329were committed to googleapis and synthtool respectively, and they all triggered 330changes in the generated library code. 331 332| [googleapis](https://github.com/googleapis/googleapis) | [synthtool (templates)](gcp/templates) | 333| :--------: | :-------------------: | 334| A | X | 335| B | Y | 336 337 338Here's what autosynth generates for each flag setting. 339 340```py 341AUTOSYNTH_MULTIPLE_COMMITS = True 342``` 343 344Autosynth creates one PR, with a single commit for each original commit: 345| PR | 346| - | 347| A | 348| B | 349| X | 350| Y | 351 352*** 353 354```py 355AUTOSYNTH_MULTIPLE_COMMITS = True 356AUTOSYNTH_MULTIPLE_PRS = True 357``` 358 359Autosynth creates two PRs, with a single commit for each original commit: 360| PR1 | 361| - | 362| A | 363| B | 364 365| PR2 | 366| - | 367| X | 368| Y | 369 370 371*** 372 373```py 374AUTOSYNTH_MULTIPLE_PRS = True 375``` 376 377Autosynth creates two PRs, with a single commit combining all the 378original commits. 379 380| PR1 | 381| - | 382| AB | 383 384| PR2 | 385| - | 386| XY | 387 388 389## Helpful tips 390### Where does the generated code go? 391SynthTool runs [Artman](https://hub.docker.com/r/googleapis/artman) which creates generated code that 392can be found at `~/.cache/synthtool/googleapis<-private>/artman_genfiles`. This is useful for figuring out 393what it is you need to copy for your specific library. 394 395### Warning: Don't lint manually-written code in synth.py! 396 397Ben had the misfortune to discover a corner case where autosynth deleted a file that Ben never intended or expected it to delete. 398 399Here is what happened: 400 4011. Autosynth cannot directly observe which files your synth.py generates, because synth.py could literally do anything, including launch the space shuttle. So, Autosynth figures out which files were generated by examining all the file system reads and writes that happened while synth.py was executing. Any file that is written to or copied to is deemed to have been generated by synth.py and recorded as a generatedFiles in synth.metadata. 402 4032. NodeJS's synth.py ran the linter on manually-written sample files in the repo. A new version of the linter was pulled in, which modified a manually-written sample file. Autosynth observed the write and concluded that the manually-written file was a generated file, and listed it in generatedFiles in synth.metadata. The next time Autosynth ran, the linter made no changes, and the manually-written sample file was not written to. Autosynth concluded the manually-written sample file was no longer being generated, and deleted it. 404 405#### Lesson Learned: 406Make sure your synth.py does not touch any manually-written files in the repo. 407