README.md
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