... |
... |
@@ -3,72 +3,136 @@ Contributing |
3
|
3
|
Some tips and guidelines for developers hacking on BuildStream
|
4
|
4
|
|
5
|
5
|
|
6
|
|
-Feature additions
|
7
|
|
------------------
|
8
|
|
-Major feature additions should be proposed on the
|
9
|
|
-`mailing list <https://mail.gnome.org/mailman/listinfo/buildstream-list>`_
|
10
|
|
-before being considered for inclusion, we strongly recommend proposing
|
11
|
|
-in advance of commencing work.
|
|
6
|
+.. _contributing_filing_issues:
|
|
7
|
+
|
|
8
|
+Filing issues
|
|
9
|
+-------------
|
|
10
|
+If you are experiencing an issue with BuildStream, or would like to submit a patch
|
|
11
|
+to fix an issue, then you should first search the list of `open issues <https://gitlab.com/BuildStream/buildstream/issues>`_
|
|
12
|
+to see if the issue is already filed, and `open an issue <https://gitlab.com/BuildStream/buildstream/issues/new>`_
|
|
13
|
+if no issue already exists.
|
|
14
|
+
|
|
15
|
+For policies on how to submit and issue and how to use our project labels,
|
|
16
|
+we recommend that you read the `policies guide
|
|
17
|
+<https://gitlab.com/BuildStream/nosoftware/alignment/blob/master/BuildStream_policies.md>`_
|
|
18
|
+
|
12
|
19
|
|
13
|
|
-If you are experiencing an issue with BuildStream or would like to submit a small patch/feature, then
|
14
|
|
-you can `oepn an issue <https://gitlab.com/BuildStream/buildstream/issues/new?issue%5Bassignee_id%5D=&issue%5Bmilestone_id%5D=>`_
|
|
20
|
+.. _contributing_fixing_bugs:
|
15
|
21
|
|
16
|
|
-For policies on how to submit and issue and how to use our project labels, we recommend that you read the `policies guide <https://gitlab.com/BuildStream/nosoftware/alignment/blob/master/BuildStream_policies.md>`_
|
|
22
|
+Fixing bugs
|
|
23
|
+-----------
|
|
24
|
+Before fixing a bug, it is preferred that an :ref:`issue be filed <contributing_filing_issues>`
|
|
25
|
+first in order to better document the defect, however this need not be followed to the
|
|
26
|
+letter for minor fixes.
|
17
|
27
|
|
18
|
|
-New features must be well documented and tested either in our main
|
19
|
|
-test suite if possible, or otherwise in the integration tests.
|
|
28
|
+Patches which fix bugs should always come with a regression test.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+.. _contributing_adding_features:
|
|
32
|
+
|
|
33
|
+Adding new features
|
|
34
|
+-------------------
|
|
35
|
+Feature additions should be proposed on the `mailing list
|
|
36
|
+<https://mail.gnome.org/mailman/listinfo/buildstream-list>`_
|
|
37
|
+before being considered for inclusion. To save time and avoid any frustration,
|
|
38
|
+we strongly recommend proposing your new feature in advance of commencing work.
|
|
39
|
+
|
|
40
|
+Once consensus has been reached on the mailing list, then the proposing
|
|
41
|
+party should :ref:`file an issue <contributing_filing_issues>` to track the
|
|
42
|
+work. Please use the *bst_task* template for issues which represent
|
|
43
|
+feature additions.
|
|
44
|
+
|
|
45
|
+New features must be well documented and tested in our test suite.
|
20
|
46
|
|
21
|
47
|
It is expected that the individual submitting the work take ownership
|
22
|
48
|
of their feature within BuildStream for a reasonable timeframe of at least
|
23
|
49
|
one release cycle after their work has landed on the master branch. This is
|
24
|
|
-to say that the submitter is expected to address and fix any side effects and
|
25
|
|
-bugs which may have fell through the cracks in the review process, giving us
|
26
|
|
-a reasonable timeframe for identifying these.
|
|
50
|
+to say that the submitter is expected to address and fix any side effects,
|
|
51
|
+bugs or regressions which may have fell through the cracks in the review
|
|
52
|
+process, giving us a reasonable timeframe for identifying these.
|
27
|
53
|
|
28
|
54
|
|
29
|
|
-Patch submissions
|
30
|
|
------------------
|
31
|
|
-If you want to submit a patch, do ask for developer permissions on our
|
32
|
|
-IRC channel first (GitLab's button also works, but you may need to
|
33
|
|
-shout about it - we often overlook this) - for CI reasons, it's much
|
34
|
|
-easier if patches are in branches of the main repository.
|
|
55
|
+.. _contributing_submitting_patches:
|
|
56
|
+
|
|
57
|
+Submitting patches
|
|
58
|
+------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+Ask for developer access
|
|
62
|
+~~~~~~~~~~~~~~~~~~~~~~~~
|
|
63
|
+If you want to submit a patch, do ask for developer permissions, either
|
|
64
|
+by asking us directly on our public IRC channel (irc://irc.gnome.org/#buildstream)
|
|
65
|
+or by visiting our `project page on GitLab <https://gitlab.com/BuildStream/buildstream>`_
|
|
66
|
+and using the GitLab UI to ask for permission.
|
|
67
|
+
|
|
68
|
+This will make your contribution experience smoother, as you will not
|
|
69
|
+need to setup any complicated CI settings, and rebasing your branch
|
|
70
|
+against the upstream master branch will be more painless.
|
35
|
71
|
|
36
|
|
-Branches must be submitted as merge requests in gitlab. If the branch
|
37
|
|
-fixes an issue or is related to any issues, these issues must be mentioned
|
38
|
|
-in the merge request or preferably the commit messages themselves.
|
39
|
72
|
|
|
73
|
+Branch names
|
|
74
|
+~~~~~~~~~~~~
|
40
|
75
|
Branch names for merge requests should be prefixed with the submitter's
|
41
|
|
-name or nickname, e.g. ``username/implement-flying-ponies``.
|
|
76
|
+name or nickname, followed by a forward slash, and then a descriptive
|
|
77
|
+name. e.g.::
|
|
78
|
+
|
|
79
|
+ username/fix-that-bug
|
|
80
|
+
|
|
81
|
+This allows us to more easily identify which branch does what and
|
|
82
|
+belongs to whom, especially so that we can effectively cleanup stale
|
|
83
|
+branches in the upstream repository over time.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+Merge requests
|
|
87
|
+~~~~~~~~~~~~~~
|
|
88
|
+Once you have created a local branch, you can push it to the upstream
|
|
89
|
+BuildStream repository using the command line::
|
|
90
|
+
|
|
91
|
+ git push origin username/fix-that-bug:username/fix-that-bug
|
|
92
|
+
|
|
93
|
+GitLab will respond to this with a message and a link to allow
|
|
94
|
+you to create a new merge request.
|
42
|
95
|
|
43
|
96
|
You may open merge requests for the branches you create before you
|
44
|
|
-are ready to have them reviewed upstream, as long as your merge request
|
45
|
|
-is not yet ready for review then it must be prefixed with the ``WIP:``
|
46
|
|
-identifier.
|
|
97
|
+are ready to have them reviewed and considered for inclusion. As long as
|
|
98
|
+your merge request is not yet ready for review then the merge request
|
|
99
|
+title must be prefixed with the ``WIP:`` identifier.
|
|
100
|
+
|
47
|
101
|
|
|
102
|
+Organized commits
|
|
103
|
+~~~~~~~~~~~~~~~~~
|
48
|
104
|
Submitted branches must not contain a history of the work done in the
|
49
|
|
-feature branch. Please use git's interactive rebase feature in order to
|
50
|
|
-compose a clean patch series suitable for submission.
|
|
105
|
+feature branch. For example, if you had to change your approach, or
|
|
106
|
+have a later commit which fixes something in a previous commit on your
|
|
107
|
+branch, we do not want to include the history of how you came up with
|
|
108
|
+your patch in the upstream master branch.
|
|
109
|
+
|
|
110
|
+Please use git's interactive rebase feature in order to compose a clean
|
|
111
|
+patch series suitable for submission upstream.
|
|
112
|
+
|
|
113
|
+Every commit in series should pass the test suite, this is very important
|
|
114
|
+for tracking down regressions and performing git bisections in the future.
|
51
|
115
|
|
52
|
116
|
We prefer that documentation changes be submitted in separate commits from
|
53
|
|
-the code changes which they document, and new test cases are also preferred
|
54
|
|
-in separate commits.
|
|
117
|
+the code changes which they document, and newly added test cases are also
|
|
118
|
+preferred in separate commits.
|
55
|
119
|
|
56
|
120
|
If a commit in your branch modifies behavior such that a test must also
|
57
|
121
|
be changed to match the new behavior, then the tests should be updated
|
58
|
|
-with the same commit. Ideally every commit in the history of master passes
|
59
|
|
-its test cases, this makes bisections more easy to perform, but is not
|
60
|
|
-always practical with more complex branches.
|
|
122
|
+with the same commit, so that every commit in series passes it's own tests.
|
61
|
123
|
|
62
|
124
|
|
63
|
125
|
Commit messages
|
64
|
126
|
~~~~~~~~~~~~~~~
|
65
|
|
-Commit messages must be formatted with a brief summary line, optionally
|
66
|
|
-followed by an empty line and then a free form detailed description of
|
67
|
|
-the change.
|
|
127
|
+Commit messages must be formatted with a brief summary line, followed by
|
|
128
|
+an empty line and then a free form detailed description of the change.
|
68
|
129
|
|
69
|
130
|
The summary line must start with what changed, followed by a colon and
|
70
|
131
|
a very brief description of the change.
|
71
|
132
|
|
|
133
|
+If the commit fixes an issue, or is related to an issue; then the issue
|
|
134
|
+number should always be referenced in the commit message.
|
|
135
|
+
|
72
|
136
|
**Example**::
|
73
|
137
|
|
74
|
138
|
element.py: Added the frobnicator so that foos are properly frobbed.
|
... |
... |
@@ -77,14 +141,48 @@ a very brief description of the change. |
77
|
141
|
the element. Elements that are not properly frobnicated raise
|
78
|
142
|
an error to inform the user of invalid frobnication rules.
|
79
|
143
|
|
|
144
|
+ Fixes #123
|
80
|
145
|
|
81
|
|
-Coding style
|
82
|
|
-------------
|
83
|
|
-Coding style details for BuildStream
|
|
146
|
+In the case that you have a commit which necessarily modifies multiple
|
|
147
|
+components, then the summary line should still mention generally what
|
|
148
|
+changed (if possible), followed by a colon and a brief summary.
|
|
149
|
+
|
|
150
|
+In this case the free form detailed description of the change should
|
|
151
|
+contain a bullet list describing what was changed in each component
|
|
152
|
+separately.
|
|
153
|
+
|
|
154
|
+**Example**::
|
84
|
155
|
|
|
156
|
+ artifact cache: Fixed automatic expiry in the local cache
|
85
|
157
|
|
86
|
|
-Style guide
|
87
|
|
-~~~~~~~~~~~
|
|
158
|
+ o _artifactcache/artifactcache.py: Updated the API contract
|
|
159
|
+ of ArtifactCache.remove() so that something detailed is
|
|
160
|
+ explained here.
|
|
161
|
+
|
|
162
|
+ o _artifactcache/cascache.py: Adhere to the new API contract
|
|
163
|
+ dictated by the abstract ArtifactCache class.
|
|
164
|
+
|
|
165
|
+ o tests/artifactcache/expiry.py: Modified test expectations to
|
|
166
|
+ match the new behavior.
|
|
167
|
+
|
|
168
|
+ Fixes #123
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+Coding guidelines
|
|
172
|
+-----------------
|
|
173
|
+This section discusses coding style and other guidelines for hacking
|
|
174
|
+on BuildStream. This is important to read through for writing any non-trivial
|
|
175
|
+patches and especially outlines what people should watch out for when
|
|
176
|
+reviewing patches.
|
|
177
|
+
|
|
178
|
+Much of the rationale behind what is layed out in this section considers
|
|
179
|
+good traceability of lines of code with *git blame*, overall sensible
|
|
180
|
+modular structure, consistency in how we write code, and long term maintenance
|
|
181
|
+in mind.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+Approximate PEP-8 Style
|
|
185
|
+~~~~~~~~~~~~~~~~~~~~~~~
|
88
|
186
|
Python coding style for BuildStream is pep8, which is documented here: https://www.python.org/dev/peps/pep-0008/
|
89
|
187
|
|
90
|
188
|
We have a couple of minor exceptions to this standard, we dont want to compromise
|
... |
... |
@@ -93,15 +191,377 @@ code readability by being overly restrictive on line length for instance. |
93
|
191
|
The pep8 linter will run automatically when running the test suite.
|
94
|
192
|
|
95
|
193
|
|
|
194
|
+.. _contributing_documenting_symbols:
|
|
195
|
+
|
|
196
|
+Documenting symbols
|
|
197
|
+~~~~~~~~~~~~~~~~~~~
|
|
198
|
+In BuildStream, we maintain what we call a *"Public API Surface"* that
|
|
199
|
+is guaranteed to be stable and unchanging across stable releases. The
|
|
200
|
+symbols which fall into this special class are documented using Python's
|
|
201
|
+standard *docstrings*, while all other internals of BuildStream are documented
|
|
202
|
+with comments above the related symbol.
|
|
203
|
+
|
|
204
|
+When documenting the public API surface which is rendered in the reference
|
|
205
|
+manual, we always mention the major version in which the API was introduced,
|
|
206
|
+as shown in the examples below. If a public API exists without the *Since*
|
|
207
|
+annotation, this is taken to mean that it was available since the first stable
|
|
208
|
+release 1.0.
|
|
209
|
+
|
|
210
|
+Here are some examples to get the hang of the format of API documenting
|
|
211
|
+comments and docstrings.
|
|
212
|
+
|
|
213
|
+**Public API Surface method**::
|
|
214
|
+
|
|
215
|
+ def frobnicate(self, source, *, frobilicious=False):
|
|
216
|
+ """Frobnicates this element with the specified source
|
|
217
|
+
|
|
218
|
+ Args:
|
|
219
|
+ source (Source): The Source to frobnicate with
|
|
220
|
+ frobilicious (bool): Optionally specify that frobnication should be
|
|
221
|
+ performed fribiliciously
|
|
222
|
+
|
|
223
|
+ Returns:
|
|
224
|
+ (Element): The frobnicated version of this Element.
|
|
225
|
+
|
|
226
|
+ *Since: 1.2*
|
|
227
|
+ """
|
|
228
|
+ ...
|
|
229
|
+
|
|
230
|
+**Internal method**::
|
|
231
|
+
|
|
232
|
+ # frobnicate():
|
|
233
|
+ #
|
|
234
|
+ # Frobnicates this element with the specified source
|
|
235
|
+ #
|
|
236
|
+ # Args:
|
|
237
|
+ # source (Source): The Source to frobnicate with
|
|
238
|
+ # frobilicious (bool): Optionally specify that frobnication should be
|
|
239
|
+ # performed fribiliciously
|
|
240
|
+ #
|
|
241
|
+ # Returns:
|
|
242
|
+ # (Element): The frobnicated version of this Element.
|
|
243
|
+ #
|
|
244
|
+ def frobnicate(self, source, *, frobilicious=False):
|
|
245
|
+ ...
|
|
246
|
+
|
|
247
|
+**Public API Surface instance variable**::
|
|
248
|
+
|
|
249
|
+ def __init__(self, context, element):
|
|
250
|
+
|
|
251
|
+ self.name = self._compute_name(context, element)
|
|
252
|
+ """The name of this foo
|
|
253
|
+
|
|
254
|
+ *Since: 1.2*
|
|
255
|
+ """
|
|
256
|
+
|
|
257
|
+**Internal instance variable**::
|
|
258
|
+
|
|
259
|
+ def __init__(self, context, element):
|
|
260
|
+
|
|
261
|
+ self.name = self._compute_name(context, element) # The name of this foo
|
|
262
|
+
|
|
263
|
+**Internal instance variable (long)**::
|
|
264
|
+
|
|
265
|
+ def __init__(self, context, element):
|
|
266
|
+
|
|
267
|
+ # This instance variable required a longer explanation, so
|
|
268
|
+ # it is on a line above the instance variable declaration.
|
|
269
|
+ self.name = self._compute_name(context, element)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+**Public API Surface class**::
|
|
273
|
+
|
|
274
|
+ class Foo(Bar):
|
|
275
|
+ """The main Foo object in the data model
|
|
276
|
+
|
|
277
|
+ Explanation about Foo. Note that we always document
|
|
278
|
+ the constructor arguments here, and not beside the __init__
|
|
279
|
+ method.
|
|
280
|
+
|
|
281
|
+ Args:
|
|
282
|
+ context (Context): The invocation Context
|
|
283
|
+ count (int): The number to count
|
|
284
|
+
|
|
285
|
+ *Since: 1.2*
|
|
286
|
+ """
|
|
287
|
+ ...
|
|
288
|
+
|
|
289
|
+**Internal class**::
|
|
290
|
+
|
|
291
|
+ # Foo()
|
|
292
|
+ #
|
|
293
|
+ # The main Foo object in the data model
|
|
294
|
+ #
|
|
295
|
+ # Args:
|
|
296
|
+ # context (Context): The invocation Context
|
|
297
|
+ # count (int): The number to count
|
|
298
|
+ #
|
|
299
|
+ # Returns:
|
|
300
|
+ # (Foo): A newly created Foo object
|
|
301
|
+ #
|
|
302
|
+ class Foo(Bar):
|
|
303
|
+ ...
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+.. _contributing_class_order:
|
|
307
|
+
|
|
308
|
+Class structure and ordering
|
|
309
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
310
|
+When creating or modifying an object class in BuildStream, it is
|
|
311
|
+important to keep in mind the order in which symbols should appear
|
|
312
|
+and keep this consistent.
|
|
313
|
+
|
|
314
|
+Here is an example to illustrate the expected ordering of symbols
|
|
315
|
+on a python class in BuildStream::
|
|
316
|
+
|
|
317
|
+ class Foo(Bar):
|
|
318
|
+
|
|
319
|
+ # Public class-wide variables come first, if any.
|
|
320
|
+
|
|
321
|
+ # Private class-wide variables, if any
|
|
322
|
+
|
|
323
|
+ # Now we have the dunder/magic methods, always starting
|
|
324
|
+ # with the __init__() method.
|
|
325
|
+
|
|
326
|
+ def __init__(self, name):
|
|
327
|
+
|
|
328
|
+ super().__init__()
|
|
329
|
+
|
|
330
|
+ # NOTE: In the instance initializer we declare any instance variables,
|
|
331
|
+ # always declare the public instance variables (if any) before
|
|
332
|
+ # the private ones.
|
|
333
|
+ #
|
|
334
|
+ # It is preferred to avoid any public instance variables, and
|
|
335
|
+ # always expose an accessor method for it instead.
|
|
336
|
+
|
|
337
|
+ #
|
|
338
|
+ # Public instance variables
|
|
339
|
+ #
|
|
340
|
+ self.name = name # The name of this foo
|
|
341
|
+
|
|
342
|
+ #
|
|
343
|
+ # Private instance variables
|
|
344
|
+ #
|
|
345
|
+ self._count = 0 # The count of this foo
|
|
346
|
+
|
|
347
|
+ ################################################
|
|
348
|
+ # Abstract Methods #
|
|
349
|
+ ################################################
|
|
350
|
+
|
|
351
|
+ # NOTE: Abstract methods in BuildStream are allowed to have
|
|
352
|
+ # default methods.
|
|
353
|
+ #
|
|
354
|
+ # Subclasses must NEVER override any method which was
|
|
355
|
+ # not advertized as an abstract method by the parent class.
|
|
356
|
+
|
|
357
|
+ # frob()
|
|
358
|
+ #
|
|
359
|
+ # Implementors should implement this to frob this foo
|
|
360
|
+ # count times if possible.
|
|
361
|
+ #
|
|
362
|
+ # Args:
|
|
363
|
+ # count (int): The number of times to frob this foo
|
|
364
|
+ #
|
|
365
|
+ # Returns:
|
|
366
|
+ # (int): The number of times this foo was frobbed.
|
|
367
|
+ #
|
|
368
|
+ # Raises:
|
|
369
|
+ # (FooError): Implementors are expected to raise this error
|
|
370
|
+ #
|
|
371
|
+ def frob(self, count):
|
|
372
|
+
|
|
373
|
+ #
|
|
374
|
+ # An abstract method in BuildStream is allowed to have
|
|
375
|
+ # a default implementation.
|
|
376
|
+ #
|
|
377
|
+ self._count = self._do_frobbing(count)
|
|
378
|
+
|
|
379
|
+ return self._count
|
|
380
|
+
|
|
381
|
+ ################################################
|
|
382
|
+ # Implementation of abstract methods #
|
|
383
|
+ ################################################
|
|
384
|
+
|
|
385
|
+ # NOTE: Implementations of abstract methods defined by
|
|
386
|
+ # the parent class should NEVER document the API
|
|
387
|
+ # here redundantly.
|
|
388
|
+
|
|
389
|
+ def frobbish(self):
|
|
390
|
+ #
|
|
391
|
+ # Implementation of the "frobbish" abstract method
|
|
392
|
+ # defined by the parent Bar class.
|
|
393
|
+ #
|
|
394
|
+ return True
|
|
395
|
+
|
|
396
|
+ ################################################
|
|
397
|
+ # Public Methods #
|
|
398
|
+ ################################################
|
|
399
|
+
|
|
400
|
+ # NOTE: Public methods here are the ones which are expected
|
|
401
|
+ # to be called from outside of this class.
|
|
402
|
+ #
|
|
403
|
+ # These, along with any abstract methods, usually
|
|
404
|
+ # constitute the API surface of this class.
|
|
405
|
+
|
|
406
|
+ # frobnicate()
|
|
407
|
+ #
|
|
408
|
+ # Perform the frobnication process on this Foo
|
|
409
|
+ #
|
|
410
|
+ # Raises:
|
|
411
|
+ # (FrobError): In the case that a frobnication error was
|
|
412
|
+ # encountered
|
|
413
|
+ #
|
|
414
|
+ def frobnicate(self):
|
|
415
|
+ frobnicator.frobnicate(self)
|
|
416
|
+
|
|
417
|
+ # set_count()
|
|
418
|
+ #
|
|
419
|
+ # Sets the count of this foo
|
|
420
|
+ #
|
|
421
|
+ # Args:
|
|
422
|
+ # count (int): The new count to set
|
|
423
|
+ #
|
|
424
|
+ def set_count(self, count):
|
|
425
|
+
|
|
426
|
+ self._count = count
|
|
427
|
+
|
|
428
|
+ # get_count()
|
|
429
|
+ #
|
|
430
|
+ # Accessor for the count value of this foo.
|
|
431
|
+ #
|
|
432
|
+ # Returns:
|
|
433
|
+ # (int): The count of this foo
|
|
434
|
+ #
|
|
435
|
+ def set_count(self, count):
|
|
436
|
+
|
|
437
|
+ return self._count
|
|
438
|
+
|
|
439
|
+ ################################################
|
|
440
|
+ # Private Methods #
|
|
441
|
+ ################################################
|
|
442
|
+
|
|
443
|
+ # NOTE: Private methods are the ones which are internal
|
|
444
|
+ # implementation details of this class.
|
|
445
|
+ #
|
|
446
|
+ # We can be absolutely sure that nobody is ever
|
|
447
|
+ # going to call these functions from outside of
|
|
448
|
+ # this class definition.
|
|
449
|
+ #
|
|
450
|
+ # Even though these are private implementation
|
|
451
|
+ # details, they still MUST have API documenting
|
|
452
|
+ # comments on them.
|
|
453
|
+
|
|
454
|
+ # _do_frobbing()
|
|
455
|
+ #
|
|
456
|
+ # Does the actual frobbing
|
|
457
|
+ #
|
|
458
|
+ # Args:
|
|
459
|
+ # count (int): The number of times to frob this foo
|
|
460
|
+ #
|
|
461
|
+ # Returns:
|
|
462
|
+ # (int): The number of times this foo was frobbed.
|
|
463
|
+ #
|
|
464
|
+ def self._do_frobbing(self, count):
|
|
465
|
+ return count
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+.. _contributing_public_and_private:
|
|
469
|
+
|
|
470
|
+Public and private symbols
|
|
471
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
472
|
+BuildStream mostly follows the PEP-8 for defining public and private symbols
|
|
473
|
+for any given class, with some deviations. Please read the `section on inheritance
|
|
474
|
+<https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance>`_ for
|
|
475
|
+reference on how the PEP-8 defines public and non-public.
|
|
476
|
+
|
|
477
|
+* A public symbol is any symbol which you expect to be used by clients
|
|
478
|
+ of your class or module within BuildStream.
|
|
479
|
+
|
|
480
|
+ Public symbols are written without any leading underscores.
|
|
481
|
+
|
|
482
|
+* A private symbol is any symbol which is entirely internal to your class
|
|
483
|
+ or module within BuildStream. These symbols cannot ever be accessed by
|
|
484
|
+ external clients or modules.
|
|
485
|
+
|
|
486
|
+ A private symbol must be denoted by a leading underscore.
|
|
487
|
+
|
|
488
|
+* When a class can have subclasses (for example, the ``Sandbox`` or ``Platform``
|
|
489
|
+ classes which have various implementations, or the ``Element`` and ``Source``
|
|
490
|
+ classes which plugins derive from), then private sumbols should be denoted
|
|
491
|
+ by two leading underscores.
|
|
492
|
+
|
|
493
|
+ The double leading underscore naming convention invokes Python's name
|
|
494
|
+ mangling algorithm which helps prevent namespace collisions in the case
|
|
495
|
+ that subclasses might have a private symbol with the same name.
|
|
496
|
+
|
|
497
|
+In BuildStream, we have what we call a *"Public API Surface"*, as previously
|
|
498
|
+mentioned in :ref:`contributing_documenting_symbols`. In the :ref:`next section
|
|
499
|
+<contributing_public_api_surface>` we will discuss the *"Public API Surface"* and
|
|
500
|
+outline the exceptions to the rules discussed here.
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+.. _contributing_public_api_surface:
|
|
504
|
+
|
|
505
|
+Public API Surface
|
|
506
|
+~~~~~~~~~~~~~~~~~~
|
|
507
|
+BuildStream exposes what we call a *"Public API Surface"* which is stable
|
|
508
|
+and unchanging. This is for the sake of stability of the interfaces which
|
|
509
|
+plugins use, so it can also be referred to as the *"Plugin facing API"*.
|
|
510
|
+
|
|
511
|
+Any symbols which are a part of the *"Public API Surface*" are never allowed
|
|
512
|
+to change once they have landed in a stable release version of BuildStream. As
|
|
513
|
+such, we aim to keep the *"Public API Surface"* as small as possible at all
|
|
514
|
+times, and never expose any internal details to plugins inadvertently.
|
|
515
|
+
|
|
516
|
+One problem which arises from this is that we end up having symbols
|
|
517
|
+which are public according to the :ref:`rules discussed in the previous section
|
|
518
|
+<contributing_public_and_private>`, but must be hidden away from the
|
|
519
|
+*"Public API Surface"*. For example, BuildStream internal classes need
|
|
520
|
+to invoke methods on the ``Element`` and ``Source`` classes, wheras these
|
|
521
|
+methods need to be hidden from the *"Public API Surface"*.
|
|
522
|
+
|
|
523
|
+This is where BuildStream deviates from the PEP-8 standard for public
|
|
524
|
+and private symbol naming.
|
|
525
|
+
|
|
526
|
+In order to disambiguate between:
|
|
527
|
+
|
|
528
|
+* Symbols which are publicly accessible details of the ``Element`` class, can
|
|
529
|
+ be accessed by BuildStream internals, but must remain hidden from the
|
|
530
|
+ *"Public API Surface"*
|
|
531
|
+
|
|
532
|
+* Symbols which are private to the ``Element`` class, and cannot be accessed
|
|
533
|
+ from outside of the ``Element`` class at all.
|
|
534
|
+
|
|
535
|
+We denote the former category of symbols with only a single underscore, and the latter
|
|
536
|
+category of symbols with a double underscore. We often refer to this distinction
|
|
537
|
+as *"API Private"* (the former category) and *"Local Private"* (the latter category).
|
|
538
|
+
|
|
539
|
+Classes which are a part of the *"Public API Surface"* and require this disambiguation
|
|
540
|
+were not discussed in :ref:`the class ordering section <contributing_class_order>`, for
|
|
541
|
+these classes, the *"API Private"* symbols always come **before** the *"Local Private"*
|
|
542
|
+symbols in the class declaration.
|
|
543
|
+
|
|
544
|
+Modules which are not a part of the *"Public API Surface"* have their python files
|
|
545
|
+prefixed with a single underscore, and are not imported in BuildStream's the master
|
|
546
|
+``__init__.py`` which is used by plugins.
|
|
547
|
+
|
|
548
|
+.. note::
|
|
549
|
+
|
|
550
|
+ The ``utils.py`` module is public and exposes a handful of utility functions,
|
|
551
|
+ however many of the functions it provides are *"API Private"*.
|
|
552
|
+
|
|
553
|
+ In this case, the *"API Private"* functions are prefixed with a single underscore.
|
|
554
|
+
|
|
555
|
+
|
96
|
556
|
Imports
|
97
|
557
|
~~~~~~~
|
98
|
558
|
Module imports inside BuildStream are done with relative ``.`` notation
|
99
|
559
|
|
100
|
|
-Good::
|
|
560
|
+**Good**::
|
101
|
561
|
|
102
|
562
|
from .context import Context
|
103
|
563
|
|
104
|
|
-Bad::
|
|
564
|
+**Bad**::
|
105
|
565
|
|
106
|
566
|
from buildstream.context import Context
|
107
|
567
|
|
... |
... |
@@ -122,128 +582,540 @@ This makes things clear when reading code that said functions |
122
|
582
|
are not defined in the same file but come from utils.py for example.
|
123
|
583
|
|
124
|
584
|
|
125
|
|
-Policy for private symbols
|
126
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~~~
|
127
|
|
-Private symbols are expressed via a leading ``_`` single underscore, or
|
128
|
|
-in some special circumstances with a leading ``__`` double underscore.
|
|
585
|
+Instance Variables
|
|
586
|
+~~~~~~~~~~~~~~~~~~
|
|
587
|
+It is preferred that all instance state variables be declared as :ref:`private symbols
|
|
588
|
+<contributing_public_and_private>`, however in some cases, especially when the state
|
|
589
|
+is immutable for the object's life time (like an ``Element`` name for example), it
|
|
590
|
+is acceptable to save some typing by using a publicly accessible instance variable.
|
|
591
|
+
|
|
592
|
+It is never acceptable to modify the value of an instance variable from outside
|
|
593
|
+of the declaring class. In other words, the class which exposes an instance variable
|
|
594
|
+is the only one in control of the value of this variable.
|
|
595
|
+
|
|
596
|
+* If an instance variable is public and must be modified; then it must be
|
|
597
|
+ modified using a :ref:`mutator <contributing_accessor_mutator>`
|
|
598
|
+
|
|
599
|
+* Ideally for better encapsulation, all object state is declared as
|
|
600
|
+ :ref:`private instance variables <contributing_public_and_private>` and can
|
|
601
|
+ only be accessed by external classes via public :ref:`accessors and mutators
|
|
602
|
+ <contributing_accessor_mutator>`
|
|
603
|
+
|
|
604
|
+.. note::
|
|
605
|
+
|
|
606
|
+ In some cases, we may use small data structures declared as objects for the sake
|
|
607
|
+ of better readability, where the object class itself has no real supporting code.
|
|
608
|
+
|
|
609
|
+ In these exceptions, it can be acceptable to modify the instance variables
|
|
610
|
+ of these objects directly, unless they are otherwise documented to be immutable.
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+.. _contributing_accessor_mutator:
|
|
614
|
+
|
|
615
|
+Accessors and Mutators
|
|
616
|
+~~~~~~~~~~~~~~~~~~~~~~
|
|
617
|
+An accessor and mutator, are methods defined on the object class to access (get)
|
|
618
|
+or mutate (set) a value owned by the declaring class, respectively.
|
|
619
|
+
|
|
620
|
+An accessor might derive the returned value from one or more of its components,
|
|
621
|
+and a mutator might have side effects, or delegate the mutation to a component.
|
|
622
|
+
|
|
623
|
+Accessors and mutators are always :ref:`public <contributing_public_and_private>`
|
|
624
|
+(even if they might have a single leading underscore and are considered
|
|
625
|
+:ref:`API Private <contributing_public_api_surface>`), as their purpose is to
|
|
626
|
+enforce encapsulation with regards to any accesses to the state which is owned
|
|
627
|
+by the declaring class.
|
|
628
|
+
|
|
629
|
+Accessors and mutators are functions prefixed with ``get_`` and ``set_``
|
|
630
|
+respectively, e.g.::
|
|
631
|
+
|
|
632
|
+ class Foo():
|
|
633
|
+
|
|
634
|
+ def __init__(self):
|
|
635
|
+
|
|
636
|
+ # Declare some internal state
|
|
637
|
+ self._count = 0
|
|
638
|
+
|
|
639
|
+ # get_count()
|
|
640
|
+ #
|
|
641
|
+ # Gets the count of this Foo.
|
|
642
|
+ #
|
|
643
|
+ # Returns:
|
|
644
|
+ # (int): The current count of this Foo
|
|
645
|
+ #
|
|
646
|
+ def get_foo(self):
|
|
647
|
+ return self._count
|
|
648
|
+
|
|
649
|
+ # set_count()
|
|
650
|
+ #
|
|
651
|
+ # Sets the count of this Foo.
|
|
652
|
+ #
|
|
653
|
+ # Args:
|
|
654
|
+ # count (int): The new count for this Foo
|
|
655
|
+ #
|
|
656
|
+ def set_foo(self, count):
|
|
657
|
+ self._count = count
|
|
658
|
+
|
|
659
|
+.. attention::
|
|
660
|
+
|
|
661
|
+ We are aware that Python offers a facility for accessors and
|
|
662
|
+ mutators using the ``@property`` decorator instead. Do not use
|
|
663
|
+ the ``@property`` decorator.
|
|
664
|
+
|
|
665
|
+ The decision to use explicitly defined functions instead of the
|
|
666
|
+ ``@property`` feature is rather arbitrary, and there is not much
|
|
667
|
+ technical merit to preferring one technique over the other.
|
|
668
|
+ However as :ref:`discussed below <contributing_always_consistent>`,
|
|
669
|
+ it is of the utmost importance that we do not mix both techniques
|
|
670
|
+ in the same code base.
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+.. _contributing_abstract_methods:
|
|
674
|
+
|
|
675
|
+Abstract Methods
|
|
676
|
+~~~~~~~~~~~~~~~~
|
|
677
|
+In BuildStream, an *"Abstract Method"* is a bit of a misnomer and does
|
|
678
|
+not match up to how Python defines abstract methods, we need to seek out
|
|
679
|
+a new nomanclature to refer to these methods.
|
129
|
680
|
|
130
|
|
-Before understanding the naming policy, it is first important to understand
|
131
|
|
-that in BuildStream, there are two levels of privateness which need to be
|
132
|
|
-considered.
|
|
681
|
+In Python, an *"Abstract Method"* is a method which **must** be
|
|
682
|
+implemented by a subclass, whereas all methods in Python can be
|
|
683
|
+overridden.
|
133
|
684
|
|
134
|
|
-These are treated subtly differently and thus need to be understood:
|
|
685
|
+In BuildStream, we use the term *"Abstract Method"*, to refer to
|
|
686
|
+a method which **can** be overridden by a subclass, whereas it
|
|
687
|
+is **illegal** to override any other method.
|
135
|
688
|
|
136
|
|
-* API Private
|
|
689
|
+* Abstract methods are allowed to have default implementations
|
137
|
690
|
|
138
|
|
- A symbol is considered to be *API private* if it is not exposed in the *public API*.
|
|
691
|
+* Subclasses are not allowed to redefine the calling signature
|
|
692
|
+ of an abstract method, or redefine the API contract in any way
|
139
|
693
|
|
140
|
|
- Even if a symbol does not have any leading underscore, it may still be *API private*
|
141
|
|
- if the containing *class* or *module* is named with a leading underscore.
|
|
694
|
+* Subclasses are not allowed to override any other methods.
|
142
|
695
|
|
143
|
|
-* Local private
|
|
696
|
+The key here is that in BuildStream, we consider it unacceptable
|
|
697
|
+that a subclass overrides a method of it's parent class unless
|
|
698
|
+the said parent class has explicitly given permission to subclasses
|
|
699
|
+to do so, and outlined the API contract for this. No surprises
|
|
700
|
+are allowed.
|
144
|
701
|
|
145
|
|
- A symbol is considered to be *local private* if it is not intended for access
|
146
|
|
- outside of the defining *scope*.
|
147
|
702
|
|
148
|
|
- If a symbol has a leading underscore, it might not be *local private* if it is
|
149
|
|
- declared on a publicly visible class, but needs to be accessed internally by
|
150
|
|
- other modules in the BuildStream core.
|
|
703
|
+Error Handling
|
|
704
|
+~~~~~~~~~~~~~~
|
|
705
|
+In BuildStream, all non recoverable errors are expressed via
|
|
706
|
+subclasses of the ``BstError`` exception.
|
151
|
707
|
|
|
708
|
+This exception is handled deep in the core in a few places, and
|
|
709
|
+it is rarely necessary to handle a ``BstError``.
|
152
|
710
|
|
153
|
|
-Ordering
|
154
|
|
-''''''''
|
155
|
|
-For better readability and consistency, we try to keep private symbols below
|
156
|
|
-public symbols. In the case of public modules where we may have a mix of
|
157
|
|
-*API private* and *local private* symbols, *API private* symbols should come
|
158
|
|
-before *local private* symbols.
|
159
|
711
|
|
|
712
|
+Raising Exceptions
|
|
713
|
+''''''''''''''''''
|
|
714
|
+When writing code in the BuildStream core, ensure that all system
|
|
715
|
+calls are wrapped in a ``try:`` block, and raise a descriptive ``BstError``
|
|
716
|
+of the appropriate class explaining what exactly failed.
|
160
|
717
|
|
161
|
|
-Symbol naming
|
162
|
|
-'''''''''''''
|
163
|
|
-Any private symbol must start with a single leading underscore for two reasons:
|
|
718
|
+Always include the original system call error is formatted into
|
|
719
|
+your new exception, and that you use the python ``from`` semantic
|
|
720
|
+to retain the original call trace, example::
|
164
|
721
|
|
165
|
|
-* So that it does not bleed into documentation and *public API*.
|
|
722
|
+ try:
|
|
723
|
+ os.utime(self._refpath(ref))
|
|
724
|
+ except FileNotFoundError as e:
|
|
725
|
+ raise ArtifactError("Attempt to access unavailable artifact: {}".format(e)) from e
|
166
|
726
|
|
167
|
|
-* So that it is clear to developers which symbols are not used outside of the declaring *scope*
|
168
|
727
|
|
169
|
|
-Remember that with python, the modules (python files) are also symbols
|
170
|
|
-within their containing *package*, as such; modules which are entirely
|
171
|
|
-private to BuildStream are named as such, e.g. ``_thismodule.py``.
|
|
728
|
+Enhancing Exceptions
|
|
729
|
+''''''''''''''''''''
|
|
730
|
+Sometimes the ``BstError`` originates from a lower level component,
|
|
731
|
+and the source of the error did not have enough context to create
|
|
732
|
+a good and informative summary of the error for the user.
|
172
|
733
|
|
|
734
|
+In these cases it is necessary to handle the error and raise a new
|
|
735
|
+one, e.g.::
|
173
|
736
|
|
174
|
|
-Cases for double underscores
|
175
|
|
-''''''''''''''''''''''''''''
|
176
|
|
-The double underscore in python has a special function. When declaring
|
177
|
|
-a symbol in class scope which has a leading underscore, it can only be
|
178
|
|
-accessed within the class scope using the same name. Outside of class
|
179
|
|
-scope, it can only be accessed with a *cheat*.
|
|
737
|
+ try:
|
|
738
|
+ extracted_artifact = self._artifacts.extract(self, cache_key)
|
|
739
|
+ except ArtifactError as e:
|
|
740
|
+ raise ElementError("Failed to extract {} while checking out {}: {}"
|
|
741
|
+ .format(cache_key, self.name, e)) from e
|
180
|
742
|
|
181
|
|
-We use the double underscore in cases where the type of privateness can be
|
182
|
|
-ambiguous.
|
183
|
743
|
|
184
|
|
-* For private modules and classes
|
|
744
|
+Programming errors
|
|
745
|
+''''''''''''''''''
|
|
746
|
+Sometimes you are writing code and have detected an unexpected condition,
|
|
747
|
+or a broken invariant for which the code cannot be prepared to handle
|
|
748
|
+gracefully.
|
185
|
749
|
|
186
|
|
- We never need to disambiguate with a double underscore
|
|
750
|
+In these cases, do **not** raise any of the ``BstError`` class exceptions.
|
187
|
751
|
|
188
|
|
-* For private symbols declared in a public *scope*
|
|
752
|
+Instead, use the python ``assert`` statement, e.g.::
|
189
|
753
|
|
190
|
|
- In the case that we declare a private method on a public object, it
|
191
|
|
- becomes ambiguous whether:
|
|
754
|
+ assert utils._is_main_process(), \
|
|
755
|
+ "Attempted to save workspace configuration from child process"
|
192
|
756
|
|
193
|
|
- * The symbol is *local private*, and only used within the given scope
|
|
757
|
+This will result in a ``BUG`` message with the stack trace included being
|
|
758
|
+logged and reported in the frontend.
|
194
|
759
|
|
195
|
|
- * The symbol is *API private*, and will be used internally by BuildStream
|
196
|
|
- from other parts of the codebase.
|
197
|
760
|
|
198
|
|
- In this case, we use a single underscore for *API private* methods which
|
199
|
|
- are not *local private*, and we use a double underscore for *local private*
|
200
|
|
- methods declared in public scope.
|
|
761
|
+BstError parameters
|
|
762
|
+'''''''''''''''''''
|
|
763
|
+When raising ``BstError`` class exceptions, there are some common properties
|
|
764
|
+which can be useful to know about:
|
201
|
765
|
|
|
766
|
+* **message:** The brief human readable error, will be formatted on one line in the frontend
|
202
|
767
|
|
203
|
|
-Documenting private symbols
|
204
|
|
-'''''''''''''''''''''''''''
|
205
|
|
-Any symbol which is *API Private* (regardless of whether it is also
|
206
|
|
-*local private*), should have some documentation for developers to
|
207
|
|
-better understand the codebase.
|
|
768
|
+* **detail:** An optional detailed human readable message to accompany the **message** summary
|
|
769
|
+ of the error. This is often used to recommend the user some course of action, or to provide
|
|
770
|
+ additional context about the error.
|
208
|
771
|
|
209
|
|
-Contrary to many other python projects, we do not use docstrings to
|
210
|
|
-document private symbols, but prefer to keep *API Private* symbols
|
211
|
|
-documented in code comments placed *above* the symbol (or *beside* the
|
212
|
|
-symbol in some cases, such as variable declarations in a class where
|
213
|
|
-a shorter comment is more desirable), rather than docstrings placed *below*
|
214
|
|
-the symbols being documented.
|
|
772
|
+* **temporary:** Some errors are allowed to be *temporary*, this attribute is only
|
|
773
|
+ observed from child processes which fail in a temporary way. This distinction
|
|
774
|
+ is used to determine whether the task should be *retried* or not. An error is usually
|
|
775
|
+ only a *temporary* error if the cause of the error was a network timeout.
|
215
|
776
|
|
216
|
|
-Other than this detail, follow the same guidelines for documenting
|
217
|
|
-symbols as described below.
|
|
777
|
+* **reason:** A machine readable identifier for the error. This is used for the purpose
|
|
778
|
+ of regression testing, such that we check that BuildStream has errored out for the
|
|
779
|
+ expected reason in a given failure mode.
|
218
|
780
|
|
219
|
781
|
|
220
|
|
-Documenting BuildStream
|
221
|
|
------------------------
|
|
782
|
+Documenting Exceptions
|
|
783
|
+''''''''''''''''''''''
|
|
784
|
+We have already seen :ref:`some examples <contributing_class_order>` of how
|
|
785
|
+exceptions are documented in API documenting comments, but this is worth some
|
|
786
|
+additional disambiguation.
|
|
787
|
+
|
|
788
|
+* Only document the exceptions which are raised directly by the function in question.
|
|
789
|
+ It is otherwise nearly impossible to keep track of what exceptions *might* be raised
|
|
790
|
+ indirectly by calling the given function.
|
|
791
|
+
|
|
792
|
+* For a regular public or private method, your audience is a caller of the function;
|
|
793
|
+ document the exception in terms of what exception might be raised as a result of
|
|
794
|
+ calling this method.
|
|
795
|
+
|
|
796
|
+* For an :ref:`abstract method <contributing_abstract_methods>`, your audience is the
|
|
797
|
+ implementor of the method in a subclass; document the exception in terms of what
|
|
798
|
+ exception is prescribed for the implementing class to raise.
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+.. _contributing_always_consistent:
|
|
802
|
+
|
|
803
|
+Always be consistent
|
|
804
|
+~~~~~~~~~~~~~~~~~~~~
|
|
805
|
+There are various ways to define functions and classes in Python,
|
|
806
|
+which has evolved with various features over time.
|
|
807
|
+
|
|
808
|
+In BuildStream, we may not have leveraged all of the nice features
|
|
809
|
+we could have, that is okay, and where it does not break API, we
|
|
810
|
+can consider changing it.
|
|
811
|
+
|
|
812
|
+Even if you know there is a *better* way to do a given thing in
|
|
813
|
+Python when compared to the way we do it in BuildStream, *do not do it*.
|
|
814
|
+
|
|
815
|
+Consistency of how we do things in the codebase is more important
|
|
816
|
+than the actual way in which things are done, always.
|
|
817
|
+
|
|
818
|
+Instead, if you like a certain python feature and think the BuildStream
|
|
819
|
+codebase, then propose your change on the `mailing list
|
|
820
|
+<https://mail.gnome.org/mailman/listinfo/buildstream-list>`_. Chances
|
|
821
|
+are that we will reach agreement to use your preferred approach, and
|
|
822
|
+in that case, it will be important to apply the change unilaterally
|
|
823
|
+across the entire codebase, such that we continue to have a consistent
|
|
824
|
+code base.
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+Avoid tail calling
|
|
828
|
+~~~~~~~~~~~~~~~~~~
|
|
829
|
+With the exception of tail calling with simple functions from
|
|
830
|
+the standard python library, such as splitting and joining lines
|
|
831
|
+of text and encoding/decoding text; always avoid tail calling.
|
|
832
|
+
|
|
833
|
+**Good**::
|
|
834
|
+
|
|
835
|
+ # Variables that we will need declared up top
|
|
836
|
+ context = self._get_context()
|
|
837
|
+ workspaces = context.get_workspaces()
|
|
838
|
+
|
|
839
|
+ ...
|
|
840
|
+
|
|
841
|
+ # Saving the workspace configuration
|
|
842
|
+ workspaces.save_config()
|
|
843
|
+
|
|
844
|
+**Bad**::
|
|
845
|
+
|
|
846
|
+ # Saving the workspace configuration
|
|
847
|
+ self._get_context().get_workspaces().save_config()
|
|
848
|
+
|
|
849
|
+**Acceptable**::
|
|
850
|
+
|
|
851
|
+ # Decode the raw text loaded from a log file for display,
|
|
852
|
+ # join them into a single utf-8 string and strip away any
|
|
853
|
+ # trailing whitespace.
|
|
854
|
+ return '\n'.join([line.decode('utf-8') for line in lines]).rstrip()
|
|
855
|
+
|
|
856
|
+When you need to obtain a delegate object via an accessor function,
|
|
857
|
+either do it at the beginning of the function, or at the beginning
|
|
858
|
+of a code block within the function that will use that object.
|
|
859
|
+
|
|
860
|
+There are several reasons for this convention:
|
|
861
|
+
|
|
862
|
+* When observing a stack trace, it is always faster and easier to
|
|
863
|
+ determine what went wrong when all statements are on separate lines.
|
|
864
|
+
|
|
865
|
+* We always want individual lines to trace back to their origin as
|
|
866
|
+ much as possible for the purpose of tracing the history of code
|
|
867
|
+ with *git blame*.
|
|
868
|
+
|
|
869
|
+ One day, you might need the ``Context`` or ``Workspaces`` object
|
|
870
|
+ in the same function for another reason, at which point it will
|
|
871
|
+ be unacceptable to leave the existing line as written, because it
|
|
872
|
+ will introduce a redundant accessor to the same object, so the
|
|
873
|
+ line written as::
|
|
874
|
+
|
|
875
|
+ self._get_context().get_workspaces().save_config()
|
|
876
|
+
|
|
877
|
+ Will have to change at that point, meaning we lose the valuable
|
|
878
|
+ information of which commit originally introduced this call
|
|
879
|
+ when running *git blame*.
|
|
880
|
+
|
|
881
|
+* For similar reasons, we prefer delegate objects be accessed near
|
|
882
|
+ the beginning of a function or code block so that there is less
|
|
883
|
+ chance that this statement will have to move in the future, if
|
|
884
|
+ the same function or code block needs the delegate object for any
|
|
885
|
+ other reason.
|
|
886
|
+
|
|
887
|
+ Asides from this, code is generally more legible and uniform when
|
|
888
|
+ variables are declared at the beginning of function blocks.
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+Vertical stacking of modules
|
|
892
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
893
|
+For the sake of overall comprehensiveness of the BuildStream
|
|
894
|
+architecture, it is important that we retain vertical stacking
|
|
895
|
+order of the dependencies and knowledge of modules as much as
|
|
896
|
+possible.
|
|
897
|
+
|
|
898
|
+For instance, the ``Source`` objects are owned by ``Element``
|
|
899
|
+objects in the BuildStream data model, and as such the ``Element``
|
|
900
|
+will delegate some activities to the ``Source`` objects in it's
|
|
901
|
+possesion. The ``Source`` objects should however never call functions
|
|
902
|
+on the ``Element`` object, nor should the ``Source`` object itself
|
|
903
|
+have any understanding of what an ``Element`` is.
|
|
904
|
+
|
|
905
|
+If you are implementing a low level utility layer, for example
|
|
906
|
+as a part of the ``YAML`` loading code layers, it can be tempting
|
|
907
|
+to derive context from the higher levels of the codebase which use
|
|
908
|
+these low level utilities, instead of defining properly stand alone
|
|
909
|
+APIs for these utilities to work: Never do this.
|
|
910
|
+
|
|
911
|
+Unfortunately, unlike other languages where include files play
|
|
912
|
+a big part in ensuring that it is difficult to make a mess; Python,
|
|
913
|
+allows you to just call methods on arbitrary objects passed through
|
|
914
|
+a function call without having to import the module which defines
|
|
915
|
+those methods - this leads to cyclic dependencies of modules quickly
|
|
916
|
+if the developer does not take special care of ensuring this does not
|
|
917
|
+happen.
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+Use less arguments in methods
|
|
921
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
922
|
+When creating an object, or adding a new API method to an existing
|
|
923
|
+object, always strive to keep as much context as possible on the
|
|
924
|
+object itself rather than expecting callers of the methods to provide
|
|
925
|
+everything the method needs every time.
|
|
926
|
+
|
|
927
|
+If the value or object that is needed in a function call is a constant
|
|
928
|
+for the lifetime of the object which exposes the given method, then
|
|
929
|
+that value or object should be passed in the constructor instead of
|
|
930
|
+via a method call.
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+Avoid transient state on instances
|
|
934
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
935
|
+At times, it can be tempting to store transient state that is
|
|
936
|
+the result of one operation on an instance, only to be retrieved
|
|
937
|
+later via an accessor function elsewhere.
|
|
938
|
+
|
|
939
|
+As a basic rule of thumb, if the value is transient and just the
|
|
940
|
+result of one operation, which needs to be observed directly after
|
|
941
|
+by another code segment, then never store it on the instance.
|
|
942
|
+
|
|
943
|
+BuildStream is complicated in the sense that it is multi processed
|
|
944
|
+and it is not always obvious how to pass the transient state around
|
|
945
|
+as a return value or a function parameter. Do not fall prey to this
|
|
946
|
+obstacle and pollute object instances with transient state.
|
|
947
|
+
|
|
948
|
+Instead, always refactor the surrounding code so that the value
|
|
949
|
+is propagated to the desired end point via a well defined API, either
|
|
950
|
+by adding new code paths or changing the design such that the
|
|
951
|
+architecture continues to make sense.
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+Refactor the code base as needed
|
|
955
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
956
|
+Especially when implementing features, always move the BuildStream
|
|
957
|
+codebase forward as a whole.
|
|
958
|
+
|
|
959
|
+Taking a short cut is alright when prototyping, but circumventing
|
|
960
|
+existing architecture and design to get a feature implemented without
|
|
961
|
+re-designing the surrounding architecture to accommodate the new
|
|
962
|
+feature instead, is never acceptable upstream.
|
|
963
|
+
|
|
964
|
+For example, let's say that you have to implement a feature and you've
|
|
965
|
+successfully prototyped it, but it launches a ``Job`` directly from a
|
|
966
|
+``Queue`` implementation to get the feature to work, while the ``Scheduler``
|
|
967
|
+is normally responsible for dispatching ``Jobs`` for the elements on
|
|
968
|
+a ``Queue``. This means that you've proven that your feature can work,
|
|
969
|
+and now it is time to start working on a patch for upstream.
|
|
970
|
+
|
|
971
|
+Consider what the scenario is and why you are circumventing the design,
|
|
972
|
+and redesign the ``Scheduler`` and ``Queue`` objects to accommodate for
|
|
973
|
+the new feature and condition under which you need to dispatch a ``Job``,
|
|
974
|
+or how you can give the ``Queue`` implementation the additional context it
|
|
975
|
+needs.
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+Adding core plugins
|
|
979
|
+-------------------
|
|
980
|
+This is a checklist of things which need to be done when adding a new
|
|
981
|
+core plugin to BuildStream proper.
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+Update documentation index
|
|
985
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
986
|
+The documentation generating scripts will automatically pick up your
|
|
987
|
+newly added plugin and generate HTML, but will not add a link to the
|
|
988
|
+documentation of your plugin automatically.
|
|
989
|
+
|
|
990
|
+Whenever adding a new plugin, you must add an entry for it in ``doc/source/core_plugins.rst``.
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+Bump format version
|
|
994
|
+~~~~~~~~~~~~~~~~~~~
|
|
995
|
+In order for projects to assert that they have a new enough version
|
|
996
|
+of BuildStream to use the new plugin, the ``BST_FORMAT_VERSION`` must
|
|
997
|
+be incremented in the ``_versions.py`` file.
|
|
998
|
+
|
|
999
|
+Remember to include in your plugin's main documentation, the format
|
|
1000
|
+version in which the plugin was introduced, using the standard annotation
|
|
1001
|
+which we use throughout the documentation, e.g.::
|
|
1002
|
+
|
|
1003
|
+ .. note::
|
|
1004
|
+
|
|
1005
|
+ The ``foo`` plugin is available since :ref:`format version 16 <project_format_version>`
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+Add tests
|
|
1009
|
+~~~~~~~~~
|
|
1010
|
+Needless to say, all new feature additions need to be tested. For ``Element``
|
|
1011
|
+plugins, these usually need to be added to the integration tests. For ``Source``
|
|
1012
|
+plugins, the tests are added in two ways:
|
|
1013
|
+
|
|
1014
|
+* For most normal ``Source`` plugins, it is important to add a new ``Repo``
|
|
1015
|
+ implementation for your plugin in the ``tests/testutils/repo/`` directory
|
|
1016
|
+ and update ``ALL_REPO_KINDS`` in ``tests/testutils/repo/__init__.py``. This
|
|
1017
|
+ will include your new ``Source`` implementation in a series of already existing
|
|
1018
|
+ tests, ensuring it works well under normal operating conditions.
|
|
1019
|
+
|
|
1020
|
+* For other source plugins, or in order to test edge cases, such as failure modes,
|
|
1021
|
+ which are not tested under the normal test battery, add new tests in ``tests/sources``.
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+Extend the cachekey test
|
|
1025
|
+~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1026
|
+For any newly added plugins, it is important to add some new simple elements
|
|
1027
|
+in ``tests/cachekey/project/elements`` or ``tests/cachekey/project/sources``,
|
|
1028
|
+and ensure that the newly added elements are depended on by ``tests/cachekey/project/target.bst``.
|
|
1029
|
+
|
|
1030
|
+One new element should be added to the cache key test for every configuration
|
|
1031
|
+value which your plugin understands which can possibly affect the result of
|
|
1032
|
+your plugin's ``Plugin.get_unique_key()`` implementation.
|
|
1033
|
+
|
|
1034
|
+This test ensures that cache keys do not unexpectedly change or become incompatible
|
|
1035
|
+due to code changes. As such, the cache key test should have full coverage of every
|
|
1036
|
+YAML configuration which can possibly affect cache key outcome at all times.
|
|
1037
|
+
|
|
1038
|
+See the ``tests/cachekey/update.py`` file for instructions on running the updater,
|
|
1039
|
+you need to run the updater to generate the ``.expected`` files and add the new
|
|
1040
|
+``.expected`` files in the same commit which extends the cache key test.
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+Protocol buffers
|
|
1044
|
+----------------
|
|
1045
|
+BuildStream uses protobuf and gRPC for serialization and communication with
|
|
1046
|
+artifact cache servers. This requires ``.proto`` files and Python code
|
|
1047
|
+generated from the ``.proto`` files using protoc. All these files live in the
|
|
1048
|
+``buildstream/_protos`` directory. The generated files are included in the
|
|
1049
|
+git repository to avoid depending on grpcio-tools for user installations.
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+Regenerating code
|
|
1053
|
+~~~~~~~~~~~~~~~~~
|
|
1054
|
+When ``.proto`` files are modified, the corresponding Python code needs to
|
|
1055
|
+be regenerated. As a prerequisite for code generation you need to install
|
|
1056
|
+``grpcio-tools`` using pip or some other mechanism::
|
|
1057
|
+
|
|
1058
|
+ pip3 install --user grpcio-tools
|
|
1059
|
+
|
|
1060
|
+To actually regenerate the code::
|
|
1061
|
+
|
|
1062
|
+ ./setup.py build_grpc
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+Documenting
|
|
1066
|
+-----------
|
222
|
1067
|
BuildStream starts out as a documented project from day one and uses
|
223
|
1068
|
sphinx to document itself.
|
224
|
1069
|
|
|
1070
|
+This section discusses formatting policies for editing files in the
|
|
1071
|
+``doc/source`` directory, and describes the details of how the docs are
|
|
1072
|
+generated so that you can easily generate and view the docs yourself before
|
|
1073
|
+submitting patches to the documentation.
|
|
1074
|
+
|
|
1075
|
+For details on how API documenting comments and docstrings are formatted,
|
|
1076
|
+refer to the :ref:`documenting section of the coding guidelines
|
|
1077
|
+<contributing_documenting_symbols>`.
|
|
1078
|
+
|
225
|
1079
|
|
226
|
1080
|
Documentation formatting policy
|
227
|
1081
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
228
|
1082
|
The BuildStream documentation style is as follows:
|
229
|
1083
|
|
230
|
|
-* Titles and headings require two leading empty lines above them. Only the first word should be capitalized.
|
|
1084
|
+* Titles and headings require two leading empty lines above them.
|
|
1085
|
+ Only the first word in a title should be capitalized.
|
231
|
1086
|
|
232
|
|
- * If there is an ``.. _internal_link`` anchor, there should be two empty lines above the anchor, followed by one leading empty line.
|
|
1087
|
+ * If there is an ``.. _internal_link:`` anchor, there should be two empty lines
|
|
1088
|
+ above the anchor, followed by one leading empty line.
|
233
|
1089
|
|
234
|
1090
|
* Within a section, paragraphs should be separated by one empty line.
|
235
|
1091
|
|
236
|
|
-* Notes are defined using: ``.. note::`` blocks, followed by an empty line and then indented (3 spaces) text.
|
|
1092
|
+* Notes are defined using: ``.. note::`` blocks, followed by an empty line
|
|
1093
|
+ and then indented (3 spaces) text.
|
|
1094
|
+
|
|
1095
|
+ * Other kinds of notes can be used throughout the documentation and will
|
|
1096
|
+ be decorated in different ways, these work in the same way as ``.. note::`` does.
|
237
|
1097
|
|
238
|
|
-* Code blocks are defined using: ``.. code:: LANGUAGE`` blocks, followed by an empty line and then indented (3 spaces) text. Note that the default language is `python`.
|
|
1098
|
+ Feel free to also use ``.. attention::`` or ``.. important::`` to call special
|
|
1099
|
+ attention to a paragraph, ``.. tip::`` to give the reader a special tip on how
|
|
1100
|
+ to use an advanced feature or ``.. warning::`` to warn the user about a potential
|
|
1101
|
+ misuse of the API and explain it's consequences.
|
|
1102
|
+
|
|
1103
|
+* Code blocks are defined using: ``.. code:: LANGUAGE`` blocks, followed by an empty
|
|
1104
|
+ line and then indented (3 spaces) text. Note that the default language is `python`.
|
239
|
1105
|
|
240
|
1106
|
* Cross references should be of the form ``:role:`target```.
|
241
|
1107
|
|
242
|
|
- * To cross reference arbitrary locations with, for example, the anchor ``_anchor_name``, you must give the link an explicit title: ``:ref:`Link text <anchor_name>```. Note that the "_" prefix is not required.
|
|
1108
|
+ * Explicit anchors can be declared as ``.. _anchor_name:`` on a line by itself.
|
|
1109
|
+
|
|
1110
|
+ * To cross reference arbitrary locations with, for example, the anchor ``anchor_name``,
|
|
1111
|
+ always provide some explicit text in the link instead of deriving the text from
|
|
1112
|
+ the target, e.g.: ``:ref:`Link text <anchor_name>```.
|
|
1113
|
+ Note that the "_" prefix is not used when referring to the target.
|
243
|
1114
|
|
244
|
1115
|
Useful links:
|
245
|
1116
|
|
246
|
|
-For further information, please see the `Sphinx Documentation <http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
|
|
1117
|
+For further information, please see the `Sphinx Documentation
|
|
1118
|
+<http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
|
247
|
1119
|
|
248
|
1120
|
|
249
|
1121
|
Building Docs
|
... |
... |
@@ -312,32 +1184,6 @@ the ``man/`` subdirectory, which will be automatically included |
312
|
1184
|
in the buildstream distribution.
|
313
|
1185
|
|
314
|
1186
|
|
315
|
|
-Documenting conventions
|
316
|
|
-~~~~~~~~~~~~~~~~~~~~~~~
|
317
|
|
-We use the sphinx.ext.napoleon extension for the purpose of having
|
318
|
|
-a bit nicer docstrings than the default sphinx docstrings.
|
319
|
|
-
|
320
|
|
-A docstring for a method, class or function should have the following
|
321
|
|
-format::
|
322
|
|
-
|
323
|
|
- """Brief description of entity
|
324
|
|
-
|
325
|
|
- Args:
|
326
|
|
- argument1 (type): Description of arg
|
327
|
|
- argument2 (type): Description of arg
|
328
|
|
-
|
329
|
|
- Returns:
|
330
|
|
- (type): Description of returned thing of the specified type
|
331
|
|
-
|
332
|
|
- Raises:
|
333
|
|
- (SomeError): When some error occurs
|
334
|
|
- (SomeOtherError): When some other error occurs
|
335
|
|
-
|
336
|
|
- A detailed description can go here if one is needed, only
|
337
|
|
- after the above part documents the calling conventions.
|
338
|
|
- """
|
339
|
|
-
|
340
|
|
-
|
341
|
1187
|
Documentation Examples
|
342
|
1188
|
~~~~~~~~~~~~~~~~~~~~~~
|
343
|
1189
|
The examples section of the documentation contains a series of standalone
|
... |
... |
@@ -419,30 +1265,8 @@ regenerate them locally in order to build the docs. |
419
|
1265
|
command: build hello.bst
|
420
|
1266
|
|
421
|
1267
|
|
422
|
|
-Protocol Buffers
|
423
|
|
-----------------
|
424
|
|
-BuildStream uses protobuf and gRPC for serialization and communication with
|
425
|
|
-artifact cache servers. This requires ``.proto`` files and Python code
|
426
|
|
-generated from the ``.proto`` files using protoc. All these files live in the
|
427
|
|
-``buildstream/_protos`` directory. The generated files are included in the
|
428
|
|
-git repository to avoid depending on grpcio-tools for user installations.
|
429
|
|
-
|
430
|
|
-
|
431
|
|
-Regenerating code
|
432
|
|
-~~~~~~~~~~~~~~~~~
|
433
|
|
-When ``.proto`` files are modified, the corresponding Python code needs to
|
434
|
|
-be regenerated. As a prerequisite for code generation you need to install
|
435
|
|
-``grpcio-tools`` using pip or some other mechanism::
|
436
|
|
-
|
437
|
|
- pip3 install --user grpcio-tools
|
438
|
|
-
|
439
|
|
-To actually regenerate the code::
|
440
|
|
-
|
441
|
|
- ./setup.py build_grpc
|
442
|
|
-
|
443
|
|
-
|
444
|
|
-Testing BuildStream
|
445
|
|
--------------------
|
|
1268
|
+Testing
|
|
1269
|
+-------
|
446
|
1270
|
BuildStream uses pytest for regression tests and testing out
|
447
|
1271
|
the behavior of newly added components.
|
448
|
1272
|
|
... |
... |
@@ -495,6 +1319,7 @@ with:: |
495
|
1319
|
Alternatively, any IDE plugin that uses pytest should automatically
|
496
|
1320
|
detect the ``.pylintrc`` in the project's root directory.
|
497
|
1321
|
|
|
1322
|
+
|
498
|
1323
|
Adding tests
|
499
|
1324
|
~~~~~~~~~~~~
|
500
|
1325
|
Tests are found in the tests subdirectory, inside of which
|
... |
... |
@@ -521,8 +1346,9 @@ Tests that run a sandbox should be decorated with:: |
521
|
1346
|
|
522
|
1347
|
and use the integration cli helper.
|
523
|
1348
|
|
524
|
|
-Measuring BuildStream performance
|
525
|
|
----------------------------------
|
|
1349
|
+
|
|
1350
|
+Measuring performance
|
|
1351
|
+---------------------
|
526
|
1352
|
|
527
|
1353
|
|
528
|
1354
|
Benchmarking framework
|