Merge lp:~vila/bzr/doc-new-config into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Merge reported by: Vincent Ladeuil
Merged at revision: not available
Proposed branch: lp:~vila/bzr/doc-new-config
Merge into: lp:bzr
Prerequisite: lp:~vila/bzr/672382-list-values
Diff against target: 449 lines (+434/-0)
2 files modified
doc/developers/configuration.txt (+433/-0)
doc/developers/index.txt (+1/-0)
To merge this branch: bzr merge lp:~vila/bzr/doc-new-config
Reviewer Review Type Date Requested Status
Andrew Bennetts Approve
Review via email: mp+40730@code.launchpad.net

Description of the change

This is a brain dump of some thoughts about a new configuration option handling implementation.

Any kind of feedback welcome :)

I'd like to better separate this document into a user oriented one and a developer oriented one in the future so help welcome on this too.

To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

I've started looking at this. So far I can say: that is a clear and comprehensive list of the current issues. Thank you!

Revision history for this message
Andrew Bennetts (spiv) wrote :

As far as the proposed implementation goes, my thoughts are:

 * there's lots of room for bikeshedding over syntax and even features... but this seems reasonable to me
 * I'm a bit worried about performance: we should make sure the actual implementation imposes as little cost to the startup time as possible. In particular I wonder about the costs of evaluating definitions that refer to other definitions while guarding against cycles etc. It would be nice to have a configuration format that in principle doesn't require any more data to be read, parsed and validated than is relevant to the current operation.
 * To a lesser extent I'm also concerned about the performance impact of adding more kinds of config file. It'll probably be fine, but we should measure.
 * How will we manage the transition from the current config files to your proposed files?
 * I'd like more detail about what replacements for appendpath will look like. Is there going to be a magic {rest-of-path] interpolation value, or something like that?
 * In general it would be good to have more examples, perhaps even start the proposal with an example configuration file.
 * I'm not sure how the proposed name spaces work: e.g. will users need to write “bzr.debug_flags” rather than just “debug_flags” everywhere?

As far as landing this branch goes: I think land it. It should be discussed on the list, but there's no reason not to land this branch on trunk in the meantime.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :
Download full text (19.3 KiB)

Overall:

I'm ok to land it. I would somewhat prefer to put it into devnotes
rather than trunk, because it is more of working notes than
documentation, though I have to admit devnotes has not been very
active.

I agree with spiv that seeing some examples of the configuration files
and APIs would probably help a lot.

It seems like there are various aspects that can be tackled
semi-independently though with ordering constraints, perhaps starting
with the new api for getting configuration values.

On 13 November 2010 02:18, Vincent Ladeuil <email address hidden> wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/doc-new-config into lp:bzr with lp:~vila/bzr/672382-list-values as a prerequisite.
>
> Requested reviews:
>  bzr-core (bzr-core)
>
>
> This is a brain dump of some thoughts about a new configuration option handling implementation.
>
> Any kind of feedback welcome :)
>
> I'd like to better separate this document into a user oriented one and a developer oriented one in the future so help welcome on this too.

I'm glad you wrote this up.

I wonder if "here are problems we have now and proposed solutions"
should really live in the main tree, but it's better to have it there
than nowhere. In particular, the 'current' bit rarely seems to get
updated when things fixing it land.

Perhaps some of this can be recycled into user documentation or
developer documentation as the features it describes land.

One further issue is that some values seem to be safe to take from
untrustworthy data, eg remote or in-tree configuration (eg branch
nick), and others are not (eg a hook to run). I have been wondering
if we ought to make this part of the declaration of a hook, but I'm
not sure if a simple safe/unsafe split is enough.

> +Not all needs can be addressed by the default values used inside bzr and
> +bzrlib, no matter how well they are chosen (and they are ;).
> +
> +Many parts of ``bzrlib`` depends on some constants though and the user
> +should be able to customize the behavior to suit his needs so these
> +constants need to become configuration options.
> +
> +These options can be set from the command-line, in an environment variable
> +or recorded in a configuration file.
> +
> +Current issues
> +==============

I guess all of these, except perhaps the first (which is general)
should have bugs, but there may be little point manually pasting them
here.

> +
> +* Many parts of ``bzrlib`` declare constants and there is no way for the
> +  user to look at or modify them.
> +
> +* The current API requires a configuration object to create, modify or
> +  delete a configuration option in a given configuration file.  ``bzr
> +  config`` makes it almost transparent for the user. Internally though, not
> +  all cases are handled: only BranchConfig implements chained configs,
> +  nothing is provided at the repository level and too many plugins define
> +  their own section or even their own config file.

The fact that you need an object is not a problem (afaics); the fact
that we have one class per place they can be stored is distasteful.

> +
> +* ``locations.conf`` defines the options that needs to override any setting
> +  in ``branch.conf`` fo...

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (6.7 KiB)

>>>>> Andrew Bennetts <email address hidden> writes:

    > Review: Approve
    > As far as the proposed implementation goes, my thoughts are:

    > * there's lots of room for bikeshedding over syntax and even
    > features... but this seems reasonable to me

    > * I'm a bit worried about performance: we should make sure the
    > actual implementation imposes as little cost to the startup time
    > as possible. In particular I wonder about the costs of
    > evaluating definitions that refer to other definitions while
    > guarding against cycles etc.

Negligible, as long as it doesn't imply multiple IOs for the same config
file. This is not guaranteed by the current implementation as alluded in
the 'option life cycle' section.

Compared to parsing a config file, interpolating one option value should
also be negligible (this is easier to measure but requires using config
files with an "average" content to be ). Also note that the actual
implementation is using configobj which provides interpolation and is
already active by default, you can even use it today with the %(option)s
syntax.

    > It would be nice to have a configuration format that in principle
    > doesn't require any more data to be read, parsed and validated
    > than is relevant to the current operation.

That's a bit vague :)

My current thoughts are that we shouldn't try to validate the values
themselves, there are strings. period. Callers need to fallback to
default values if they can't find valid values in the files, which
includes the case where the option is not defined at all. As long as we
allow hand-editing of the config files, there is no guarantee that their
content is valid. From there, we can at best warn the user that some
content is invalid, but we can't block a bzr command because of that.

If we agree on that, then reading a config file can't fail and we can
focus on minimizing the number of times such a file should be read or
written.

Or may be you are referring to the edge case where we need to read all
config files in order to check that none of them define a given option ?
This is a concern with the actual implementation (see below) especially
if we start to use more or more config options (as in all the constants
used today in bzrlib).

    > * To a lesser extent I'm also concerned about the performance
    > impact of adding more kinds of config file. It'll probably be
    > fine, but we should measure.

So far, with the actual implementation, getting a config option value
requires reading one or several config files depending on the
context. Setting a new value requires reading and writing a config file
(whether or not we had already read it).

I'd like to change the implementation so that, during one bzrlib
"session" (hand waving, think of it as one bzr command), getting a
config value requires reading one or more config files but only once
(i.e. if we need more values, we won't read the same file
again). Setting a new value requires writing a config file (optionally
re-reading it for safety) but doing so only once by config file for
which at least one option has been modified.

So overall, we should issue the same num...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (6.5 KiB)

>>>>> Martin Pool <email address hidden> writes:

    > Overall:
    > I'm ok to land it. I would somewhat prefer to put it into devnotes
    > rather than trunk, because it is more of working notes than
    > documentation,

I'll do that.

    > though I have to admit devnotes has not been very active.

    > I agree with spiv that seeing some examples of the configuration files
    > and APIs would probably help a lot.

Sure, I'll keep working on it to turn it into proper documentation. I
wanted some rough agreement before going further and try to split it
into work items.

    > It seems like there are various aspects that can be tackled
    > semi-independently though with ordering constraints, perhaps
    > starting with the new api for getting configuration values.

I'm still unclear of the path to follow from here, my next target was a
first step in interpolation limited in scope to values defined in a
single config file.

<snip/>

    > I wonder if "here are problems we have now and proposed solutions"
    > should really live in the main tree, but it's better to have it there
    > than nowhere. In particular, the 'current' bit rarely seems to get
    > updated when things fixing it land.

Right, I try to address that by keeping it in the upper thread in my
loom, so that after I land something I keep coming to it.

    > Perhaps some of this can be recycled into user documentation or
    > developer documentation as the features it describes land.

That's the idea, I may even start with that before implementing anything.

    > One further issue is that some values seem to be safe to take from
    > untrustworthy data, eg remote or in-tree configuration (eg branch
    > nick), and others are not (eg a hook to run). I have been
    > wondering if we ought to make this part of the declaration of a
    > hook, but I'm not sure if a simple safe/unsafe split is enough.

Me neither, one thing I haven't done yet, it to list all the
actual/potential config options to get a better idea.

<snip/>

    >> +Current issues
    >> +==============

    > I guess all of these, except perhaps the first (which is general)
    > should have bugs, but there may be little point manually pasting
    > them here.

I've tried to mention the ones I was aware of in the text.

<snip/>

    > The fact that you need an object is not a problem (afaics); the fact
    > that we have one class per place they can be stored is distasteful.

Yup. The goal is that the same object should be usable in all cases and
ideally already provided in most of them (via the wt, branch, repo, etc
(hand-waving on the etc;)).

<snip/>

    >> This is not a big concern so far as very few needs required
    >> dicts as option values.

    > last sentence doesn't quite parse.

'gtk_file_commit_messages' in branch.conf use a bencoded value to store
a dict in the branch.conf file. I'm not sure I know other cases.

Is:

  In a few known cases, a bencoded dict is stored in a config value, so
  while this isn't user-friendly, not providing a better alternative
  shouldn't be a concern.

clearer ?

    > One more, for which there is a bug, is wanting a -O per-process config
    > setting. ...

Read more...

lp:~vila/bzr/doc-new-config updated
5520. By Vincent Ladeuil

Merge 672382-list-values into doc-new-config

5521. By Vincent Ladeuil

Merge bzr.dev into doc-new-config

5522. By Vincent Ladeuil

Fix some tweaks from review.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Landed in lp:~bzr-core/bzr/devnotes/.

Keep replying by mail if you have further comments though !

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'doc/developers/configuration.txt'
--- doc/developers/configuration.txt 1970-01-01 00:00:00 +0000
+++ doc/developers/configuration.txt 2010-12-02 08:39:33 +0000
@@ -0,0 +1,433 @@
1==================
2Configuring Bazaar
3==================
4
5Goal
6====
7
8Not all needs can be addressed by the default values used inside bzr and
9bzrlib, no matter how well they are chosen (and they are ;).
10
11Many parts of ``bzrlib`` depends on some constants though and the user
12should be able to customize the behavior to suit his needs so these
13constants need to become configuration options.
14
15These options can be set from the command-line, in an environment variable
16or recorded in a configuration file.
17
18Current issues
19==============
20
21* Many parts of ``bzrlib`` declare constants and there is no way for the
22 user to look at or modify them.
23
24* The current API requires a configuration object to create, modify or
25 delete a configuration option in a given configuration file. ``bzr
26 config`` makes it almost transparent for the user. Internally though, not
27 all cases are handled: only BranchConfig implements chained configs,
28 nothing is provided at the repository level and too many plugins define
29 their own section or even their own config file.
30
31* ``locations.conf`` defines the options that needs to override any setting
32 in ``branch.conf`` for both local and remotes branches. Many users want a
33 way to define default values for options that are not defined in
34 ``branch.conf``. This could be approximated today by *not* defining these
35 options in ``branch.conf`` but in ``locations.conf`` instead. This
36 workaround doesn't allow a user to define defaults in ``locations.conf``
37 and override them in ``branch.conf``.
38
39* Defining a new option requires adding a new method in the ``Config``
40 object to get access to features like:
41
42 * should the option be inherited by more specific sections,
43
44 * should the inherited value append the relative path between the
45 section one and the location it applies to.
46
47 * the default value (including calling any python code that may be
48 required to calculate this value),
49
50 * priority between sections and various config files
51
52 A related problem is that, in the actual implementation, some
53 configuration options have defined methods, other don't and this is
54 inconsistent.
55
56* Access to the 'active' configuration option value from the command line
57 doesn't give access to specific section.
58
59* Rules for configuration options are not clearly defined for remote
60 branches (they may differ between dumb and smart servers).
61
62* The features offered by the Bazaar configuration files should be easily
63 accessible to plugin authors either by supporting plugin configuration
64 options in the configuration files or allowing plugin to define their
65 own configuration files.
66
67* While the actual configuration files support sections, they are used in
68 mutually exclusive ways that make it impossible to offer the same set of
69 features to all configuration files:
70
71 * ``bazaar.conf`` use arbitrary names for sections. ``DEFAULT`` is used
72 for global options, ``ALIASES`` are used to define command aliases,
73 plugins can define their own sections, some plugins do that
74 (``bzr-bookmarks`` use ``BOOKMARKS`` for example), some other define
75 their own sections.
76
77 * ``locations.conf`` use globs as section names. This provides an easy
78 way to associate a set of options to a matching working tree or
79 branch, including remote ones.
80
81 * ``branch.conf`` doesn't use any section.
82
83* There is no easy way to get configuration options for a given repository
84 or an arbitrary path. Working trees and branches are generally organized
85 in hierarchies and being able to share the option definitions is an often
86 required feature. This can also address some needs exhibited by various
87 branch schemes like looms, pipeline, colocated branches and nested
88 trees. Being able to specify options *in* a working tree could also help
89 support conflict resolution options for a given file, directory or
90 subtree.
91
92* Since sections allow different definitions for the same option, a total
93 order should be defined between sections to select the right definition
94 for a given path. Allowing globs for section names is harmful in this
95 respect since the order is currently defined as being the lexicographical
96 one. The caveat here is that if the order is always defined for a given
97 set of sections it can change when one or several globs are modified and
98 the user may get surprising and unwanted results in these cases. The
99 lexicographical order is otherwise fine to define what section is more
100 specific than another. (This may not be a problem in real life since
101 longer globs are generally more specific than shorter ones and explicit
102 paths should also be longer than matching globs. That may leave a glob and
103 a path of equal length in a gray area but in practice using ``bzr config``
104 should give enough feedback to address them).
105
106* Internally, configuration files (and their fallbacks, ``bazaar.conf`` and
107 ``locations.conf`` for ``branch.conf``) are read every time *one* option is
108 queried. Likewise, setting or deleting a configuration option implies
109 writing the configuration file *immediately* after re-reading the file to
110 avoid racing updates.
111
112* The current implementation use a mix of transport-based and direct file
113 systems operations.
114
115* While the underlying ``ConfigObj`` implementation provides an
116 interpolation feature, the ``bzrlib`` implementation doesn't provide an
117 easy handling of templates where other configuration options can be
118 interpolated. Instead, ``locations.conf`` (and only it) allows for
119 ``appendpath`` and ``norecurse``.
120
121* Inherited list values can't be modified, a more specific configuration can
122 only redefine the whole list.
123
124* There is no easy way to define dicts (the most obvious one being to use a
125 dedicated section which is already overloaded). Using embedded sections
126 for this would not be practical either if we keep using a no-name section
127 for default values. In a few known cases, a bencoded dict is stored in a
128 config value, so while this isn't user-friendly, not providing a better
129 alternative shouldn't be a concern.
130
131
132Proposed implementation
133=======================
134
135
136Configuration files definition
137------------------------------
138
139While of course configurations files can be versioned they are not intended
140to be accessed in sync with the files they refer to (one can imagine
141handling versioned properties this way but this is *not* what the bazaar
142configuration files are targeted at). ``bzr`` will always refer to
143configuration files as they exist on disk when an option is queried or set.
144
145The configuration files are generally local to the file system but some of
146them can be accessed remotely (``branch.conf``, ``repo.conf``).
147
148
149Naming
150------
151
152The option name space is organized as follow:
153
154* Bazaar itself defines all its constants as ``bzr.option_name``.
155
156* plugins can define their own options by prefixing them with the plugin
157 name as ``svn.option_name`` for the ``svn`` plugin.
158
159Using valid python identifiers is recommended but not enforced (but we may
160do so in the future).
161
162Value
163-----
164
165All option values are text. They are provided as Unicode strings to API
166users with some refinements:
167
168* boolean values can be obtained for a set of acceptable strings (yes/no,
169 y/n, on/off, etc),
170
171* a list of strings from a value containing a comma separated list of
172 strings.
173
174Since the configuration files can be edited by the user, ``bzr`` doesn't
175expect their content to be validated. Instead, the code using options should
176be ready to handle *invalid* values by warning the user and fallback to a
177default value. Likely, if an option is not defined in any configuration
178file, the code should fallback to a default value (helpers should be
179provided by the API to handle common cases, warning the user, getting a
180particular type of value, returning a default value).
181
182This also ensures compatibility with values provided via environment
183variables or from the command line.
184
185Interpolation
186-------------
187
188Some option values can be templates and contain references to other
189options. This is especially useful to define URLs in sections shared for
190multiple branches for example. It can also be used to describe commands
191where some parameters are set by ``bzrlib`` at runtime.
192
193Since options values are text-only, and to avoid clashing with other
194interpolation syntaxes, references are enclosed with curly brackets::
195
196 push_location = lp:~{launchpad_username}/bzr/{nick}
197
198In the example above, ``launchpad_username`` is an already defined
199configuration option while ``nick`` is the branch nickname and is set when a
200configuration applies to a given branch.
201
202The interpolation implementation should accept an additional dict so that
203``bzrlib`` or plugins can define references that can be interpolated without
204being existing configuration options::
205
206 diff_command={cmd} {cmd_opts} {file_a} {file_b}
207
208There are two common errors that should be handled when handling interpolation:
209
210* loops: when a configuration value refers to itself, directly or indirectly,
211
212* undefined references: when a configuration value refers to an unknown option.
213
214One possible implementation is to report errors when such references are
215encountered.
216
217Another implementation could be envisioned though: when a loop is
218encountered, we can fall back to the less specific configurations. This
219allows list values to refer to the definition in the less specific
220configurations allowing::
221
222 bazaar.conf:
223 debug_flags = hpss
224
225 branch.conf for mybranch:
226 debug_flags = {debug_flags}, hpssdetail
227
228 $ bzr -d mybranch config debug_flags
229 hpss, hpssdetail
230
231Undefined references would still be detected if they are not defined in any
232configuration or just stay unresolved which should be enough to trigger
233errors displaying the value. Diagnosing typos should be doable in this case.
234
235Configuration file syntax
236-------------------------
237
238The configuration file is mostly an ``ini-file``. It contains ``name =
239value`` lines grouped in sections. Comments are allowed by prefixing them
240with the '#' character.
241
242A section is named by the path it should apply to (more examples below).
243
244Options defined outside of any section act as defaults when no section
245applies. This means that in the most common cases, the user doesn't need to
246define any section.
247
248When sections are used, they provide a finer grain of configuration by
249defining option sets that apply to some working trees, branches,
250repositories or part of them.
251
252The subset is defined by the common leading path or a glob.
253
254* a full url: used to described options for remote branches and
255 repositories.
256
257* local absolute path: used for working trees, branches or repositories
258 on the local disks.
259
260* relative path: the path is relative to the configuration file and can be
261 used for colocated branches or threads in a loom, i.e any working tree,
262 branch or repository that is located in a place related to the
263 configuration file path. Some configuration files may define this path
264 relationship in specific ways to make them easier to use (i.e. if a config
265 file is somewhere below ``.bzr`` and refers to threads in a loom for
266 example, the relative path would be the thread name, it doesn't have to be
267 an *exact* relative path, as long as its interpretation is unambiguous and
268 clear for the user).
269
270Whatever path is used, the options apply if the branch path starts with
271the path defining the section (or if the glob matches).
272
273The ConfigOption object
274-----------------------
275
276In addition to the configuration files, one internal configuration dict can
277contain definitions for some configuration options. This will allow a finer
278grained definition of the default values and the online help.
279
280The ConfigOption object will define:
281
282* name
283
284* default value. ``None`` is the "default" default value.
285
286* docstring used for the help
287
288* a list of config files where the option can be defined.
289
290The ConfigFile object
291---------------------
292
293This is an implementation-level object that should rarely be used directly.
294
295* it can be local or remote
296
297* locking
298
299 All lock operations should be implemented via transport objects.
300
301* option life cycle
302
303 Working trees, branches and repositories should define a config attribute
304 following the same life cycle as their lock: the associated config file is
305 read once and written once if needed. This should minimize the file system
306 accesses or the network requests. There is no known racing scenarios for
307 configuration options, changing the existing implementation to this less
308 constrained one shouldn't introduce any. Yet, in order to detect such
309 racing scenarios, we can check that the current content of the
310 configuration file is the expected one before writing the new content and
311 emit warnings if differences occurred. The checks should be performed for
312 the modified values only. As of today, the size of the configuration files
313 are small enough to be kept in memory.
314
315The Config object
316-----------------
317
318This the object that provides access to the needed features:
319
320* getting an option value,
321
322* setting an option value,
323
324* deleting an option value,
325
326* handling a list of configuration files that should be tried in the given
327 order to find an option.
328
329Depending on the files involved, a working tree, branch or repository object
330should be provided to access the corresponding configuration files. Note
331that providing a working tree object also implicitly provides the
332associated branch and repository object so only one of them is required (or
333none for configuration files specific to the user like bazaar.conf and
334locations.conf).
335
336Getting an option value
337~~~~~~~~~~~~~~~~~~~~~~~
338
339Depending on the option, there are various places where it can be defined
340and several ways to override these settings when needed.
341
342The following lists all possible places where a configuration option can
343be defined, but some options will make sense in only some of them. The
344first to define a value for an option wins (None is therefore used to
345express that an option is not set).
346
347* command-line (Not Implemented Yet)
348 ``-Ooption=value`` see bug #491196.
349
350* environment variable
351
352 ``export BZR_OPTION=value``
353
354 Some environment variables doesn't have a corresponding configuration
355 option (BZR_PLUGIN_PATH) and most configuration options doesn't have a
356 corresponding environment variable.
357
358* locations.conf
359
360 When an option is set in ``locations.conf`` it overrides any other
361 configuration file. This should be used with care as it allows setting a
362 different value than what is recommended by the project
363
364* tree.conf (Not Implemented Yet)
365
366 The options related to the working tree.
367
368 This includes all options related to commits, ignored files, junk files,
369 etc.
370
371 Note that the sections defined there can use relative paths if some
372 options should apply to a subtree or some specific files only.
373
374 See bug #430538 and bug #654998.
375
376* branch.conf
377
378 The options related to the branch.
379
380 Sections can be defined for co-located branches or loom threads.
381
382* repo.conf (Not Implemented Yet)
383
384 The options related to the repository.
385
386 Using an option to describe whether or not a repository is shared could
387 help address bug #342119 but this will probably requires a format bump).
388
389* project.conf (Not Implemented Yet)
390
391 The options common to all branches and working trees for a project.
392
393* organization.conf (Not Implemented Yet)
394
395 The options common to all branches and working trees for an organization.
396
397 See bug #419854.
398
399* bazaar.conf
400
401 The options the user has selected for the host he is using.
402
403 Sections can be defined for both remote and local branches to define
404 default values (i.e. the most common use of ``locations.conf`` today).
405
406* default (Not Implemented Yet)
407
408 The options defined in the ``bzr`` source code.
409
410 This will be implemented via the ConfigOption objects.
411
412Plugins can define additional configuration files as they see fit and
413insert them in this list, see their documentation for details.
414
415Compatibility
416=============
417
418* The ``DEFAULT`` section in bazaar.conf should still be recognized but
419 won't be mandatory anymore.
420
421* Other sections in the ``bazaar.conf`` configuration file are still
422 supported but their use is discouraged and we may deprecate them in the
423 future. Plugin authors are encouraged to migrate to the new name space
424 scheme by prefixing their options with their plugin name.
425
426* Option policies should be deprecated:
427
428 * The ``norecurse`` policy is useless, all options are recursive by
429 default. If specific values are needed for specific paths, they can just
430 be defined as such.
431
432 * The ``appendpath`` policy should be implemented via interpolation and a
433 ``relpath`` option provided by the configuration framework.
0434
=== modified file 'doc/developers/index.txt'
--- doc/developers/index.txt 2010-10-13 04:13:48 +0000
+++ doc/developers/index.txt 2010-12-02 08:39:33 +0000
@@ -38,6 +38,7 @@
3838
39 transports39 transports
40 ui40 ui
41 configuration
4142
42Releasing and Packaging43Releasing and Packaging
43=======================44=======================