Skip to content

Commit 2e056e5

Browse files
committed
Resolve attributes as cross-references
Rewrite the `attribute` role to be a domain specific cross reference that is resolved against the actual collected attributes set. While our previous practice of creating attribute links using standard references was functional, it made it somewhat awkward to elide the object context for overloaded attributes, and resulted in inconsistency between linked and unlinked attribute references in both markup and visual rendering. Generating the references ourself for improved consistency of visual presentation, as well as simplified and more standard mechanisms for specifying whether a reference to an overloaded attribute should be qualified. Additionally, resolving references against our internal attribute model (as compared to the previous behavior which effectively used only section targets) means that attributes can be qualified, whether or not they are overloaded, which makes resolution more robust.
1 parent f72f3f9 commit 2e056e5

7 files changed

Lines changed: 107 additions & 39 deletions

File tree

_extensions/cps/__init__.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
import jsb
1313

1414
from sphinx import addnodes, domains
15+
from sphinx.roles import XRefRole
1516
from sphinx.util import logging
1617
from sphinx.util.docutils import SphinxRole
17-
from sphinx.util.nodes import clean_astext
18+
from sphinx.util.nodes import clean_astext, make_refnode
1819

1920
logger = logging.getLogger(__name__)
2021

@@ -300,6 +301,19 @@ def run(self):
300301
node = nodes.reference(self.rawtext, self.text, refuri=uri)
301302
return [node], []
302303

304+
# =============================================================================
305+
class AttributeRefRole(XRefRole):
306+
refdomain = 'cps'
307+
reftype = 'attribute'
308+
classes = ['code', 'attribute']
309+
310+
# -------------------------------------------------------------------------
311+
def run(self) -> tuple[list[Node], list[system_message]]:
312+
if self.disabled:
313+
return self.create_non_xref_node()
314+
else:
315+
return self.create_xref_node()
316+
303317
# =============================================================================
304318
@dataclass
305319
class Attribute:
@@ -361,13 +375,13 @@ def __init__(self, *args, **kwargs):
361375

362376
# Site-specific roles
363377
self.roles['schema'] = SchemaRole()
378+
self.roles['attribute'] = AttributeRefRole()
364379

365380
# Additional site-specific roles (these just apply styling)
366381
self.add_role('hidden')
367382
self.add_role('applies-to')
368383
self.add_role('separator')
369384
self.add_code_role('object')
370-
self.add_code_role('attribute')
371385
self.add_code_role('feature')
372386
self.add_code_role('feature.opt', styles=['feature', 'optional'])
373387
self.add_code_role('feature.var', styles=['feature', 'var'])
@@ -428,6 +442,54 @@ def add_role(self, name, styles=None, parent=roles.generic_custom_role):
428442
def add_code_role(self, name, styles=None, parent=roles.code_role):
429443
self.add_role(name, styles, parent)
430444

445+
# -------------------------------------------------------------------------
446+
def resolve_xref(self, env, fromdocname, builder,
447+
typ, target, node, contnode):
448+
short = False
449+
if target.startswith('~'):
450+
short = True
451+
target = target[1:]
452+
453+
if '.' in target:
454+
context, _, name = target.partition('.')
455+
else:
456+
name = target
457+
context = None
458+
459+
attr = self.attributes.get(name)
460+
461+
if attr is None:
462+
logger.warning('attribute %r not found', target,
463+
location=node, type='ref', subtype='attribute')
464+
return None
465+
466+
if context is None:
467+
if len(attr.instances) > 1:
468+
logger.warning('attribute %r is ambiguous', target,
469+
location=node, type='ref', subtype='attribute')
470+
return None
471+
472+
_, docname, refnode = next(iter(attr.context.values()))
473+
qualified = False
474+
475+
else:
476+
c = attr.context.get(context)
477+
if c is None:
478+
logger.warning('overloaded attribute %r not found', target,
479+
location=node, type='ref', subtype='attribute')
480+
return None
481+
482+
_, docname, refnode = c
483+
qualified = not short and len(attr.instances) > 1
484+
485+
label = refnode['names'][0]
486+
487+
cont = nodes.literal('', name, classes=['attribute'])
488+
if qualified:
489+
ccont = nodes.inline('', f' ({context})', classes=['applies-to'])
490+
cont = [cont, ccont]
491+
492+
return make_refnode(builder, fromdocname, docname, label, cont)
431493

432494
# =============================================================================
433495
def write_schema(app, exception):

components.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ by the at-sign (``@``) and a configuration name.
2424
The special case of using the at-sign as a configuration name
2525
(e.g. ``foo:foo-core@@``) means that the named configuration
2626
is the same as the configuration in which the name appears.
27-
(For example, the component ``foo-ui`` has
28-
non-configuration-specific :attribute:`requires` :string:`":foo-core@@"`
29-
and :attribute:`configurations` :string:`"A"` and :string:`"B"`.
27+
(For example, the component ``foo-ui`` has non-configuration-specific
28+
:attribute:`~component.requires` :string:`":foo-core@@"`
29+
and :attribute:`~component.configurations` :string:`"A"` and :string:`"B"`.
3030
The :string:`"A"` configuration of ``foo-ui``
3131
therefore requires ``:foo-core@A``,
3232
and similar for other configurations.)

configurations.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ It is recommended that build systems select a configuration as follows:
3333
the build system may implement a mechanism to prefer a configuration
3434
which "matches" the consuming project's active configuration.
3535

36-
- The package's `configurations (package)`_ shall be searched.
36+
- The package's :attribute:`~package.configurations` shall be searched.
3737
The first configuration in this list
3838
which matches an available configuration of the component
3939
shall be used.
@@ -106,7 +106,8 @@ The structure of a configuration-specific CPS
106106
is the same as a common CPS, with three exceptions:
107107

108108
- The only defined :object:`package` keys are
109-
`name`_, `configuration`_, and `components <components\ (package)>`_.
109+
:attribute:`name`, :attribute:`configuration`,
110+
and :attribute:`~package.components`.
110111
The first two are required.
111112
Use of other attributes specified in the schema is ill-formed.
112113

recommendations.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ it can be addressed in one of two manners:
117117
- If a "full" dependency
118118
merely needs to be linked *after* a link-only dependency,
119119
the dependency can simply be listed twice;
120-
once in :attribute:`requires`,
120+
once in :attribute:`~component.requires`,
121121
and again in :attribute:`link_requires`.
122122
(Tools are encouraged to add link-only dependencies
123123
after "full" dependencies.)
@@ -134,8 +134,8 @@ Transitive Dependencies
134134
'''''''''''''''''''''''
135135

136136
When a package is located,
137-
it is intended that the tool would also
138-
locate any `requires (package)`_ mentioned by the package.
137+
it is intended that the tool would also locate
138+
any :attribute:`~package.requires` mentioned by the package.
139139
In some cases, however, a user may want to use
140140
only some components of a package,
141141
which may have a more limited set of dependencies

schema-supplement.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ By definition, none of the following attributes are required.
2525
:type: string
2626
:context: package
2727

28-
Specifies the `license`_ that is assumed to apply to a component,
28+
Specifies the :attribute:`license`
29+
that is assumed to apply to a component,
2930
if none is otherwise specified.
3031
This is convenient for packages
31-
that wish their `license`_ to reflect portions of the package
32+
that wish their :attribute:`license`
33+
to reflect portions of the package
3234
that are not reflected by a component (such as data files)
3335
when most or all of the compiled artifacts use the same license.
3436

@@ -65,7 +67,7 @@ By definition, none of the following attributes are required.
6567
If parts of a package use different licenses,
6668
this attribute may also be specified on a component
6769
if doing so helps to clarifying the licensing.
68-
(See also `default_license`_.)
70+
(See also :attribute:`default_license`.)
6971

7072
.. ----------------------------------------------------------------------------
7173
.. cps:attribute:: meta_comment

schema.rst

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ Attribute names are case sensitive.
9090
with which this version is compatible.
9191
This information is used when a consumer requests a specific version.
9292
If the version requested is equal to or newer
93-
than the :attribute:`compat_version`,
93+
than the :attribute:`!compat_version`,
9494
the package may be used.
9595

9696
If not specified,
9797
the package is not compatible with previous versions
98-
(i.e. :attribute:`compat_version`
99-
is implicitly equal to :attribute:`version`).
98+
(i.e. :attribute:`!compat_version`
99+
is implicitly equal to :attribute:`~package.version`).
100100

101101
.. ----------------------------------------------------------------------------
102102
.. cps:attribute:: compile_features
@@ -120,7 +120,7 @@ Attribute names are case sensitive.
120120

121121
A map may be used instead to give different values
122122
depending on the language of the consuming source file.
123-
Handling of such shall be the same as for `definitions`_.
123+
Handling of such shall be the same as for :attribute:`definitions`.
124124

125125
.. ----------------------------------------------------------------------------
126126
.. cps:attribute:: compile_requires
@@ -129,7 +129,7 @@ Attribute names are case sensitive.
129129

130130
Specifies additional components required by a component
131131
which are needed only at the compile stage.
132-
Unlike `requires (component)`_,
132+
Unlike :attribute:`component.requires`,
133133
only the required components' compilation-related attributes
134134
should be applied transitively;
135135
link requirements of the required component(s) should be ignored.
@@ -244,7 +244,8 @@ Attribute names are case sensitive.
244244
(which will be known by the tool).
245245
See also `Prefix Determination`_ for details.
246246

247-
Exactly **one** of ``cps_path`` or `prefix`_ is required.
247+
Exactly **one** of :attribute:`!cps_path` or :attribute:`prefix`
248+
is required.
248249

249250
.. ----------------------------------------------------------------------------
250251
.. cps:attribute:: cps_version
@@ -260,7 +261,7 @@ Attribute names are case sensitive.
260261
CPS version numbering follows |semver|_.
261262
That is, tools that support CPS version ``<X>.<Y>``
262263
are expected to be able to read files
263-
with :attribute:`cps_version` ``<X>.<Z>``,
264+
with :attribute:`!cps_version` ``<X>.<Z>``,
264265
even for Z > Y
265266
(with the understanding that, in such cases, the tool
266267
may miss non-critical information that the CPS provided).
@@ -314,7 +315,7 @@ Attribute names are case sensitive.
314315

315316
Specifies additional components required by a component
316317
which are needed only by the dynamic library loader.
317-
Unlike `requires (component)`_ or `link_requires`_,
318+
Unlike :attribute:`component.requires` or :attribute:`link_requires`,
318319
these are not used to resolve symbol references of the consumer,
319320
but represent "private" implementation requirements
320321
of the component on which this attribute appears.
@@ -367,7 +368,7 @@ Attribute names are case sensitive.
367368

368369
A map may be used instead to give different values
369370
depending on the language of the consuming source file.
370-
Handling of such shall be the same as for `definitions`_.
371+
Handling of such shall be the same as for :attribute:`definitions`.
371372

372373
.. ----------------------------------------------------------------------------
373374
.. cps:attribute:: isa
@@ -448,7 +449,7 @@ Attribute names are case sensitive.
448449
:default: ["c"]
449450

450451
Specifies the ABI language or languages of a static library
451-
(`type`_ :string:`"archive"`).
452+
(:attribute:`type` :string:`"archive"`).
452453
Officially supported (case-insensitive) values are
453454
:string:`"c"` (no special handling required) and
454455
:string:`"cpp"` (consuming the static library
@@ -462,7 +463,7 @@ Attribute names are case sensitive.
462463
Specifies a list of additional libraries (as paths, not components)
463464
that must be linked against when linking code that consumes the component.
464465
(Note that packages should avoid using this attribute if at all possible.
465-
Use `requires (component)`_ instead whenever possible.)
466+
Use :attribute:`component.requires` instead whenever possible.)
466467

467468
.. ----------------------------------------------------------------------------
468469
.. cps:attribute:: link_location
@@ -476,7 +477,7 @@ Attribute names are case sensitive.
476477
on platforms where the library is separated into multiple file components.
477478
For example, on Windows,
478479
this attribute shall give the location of the ``.lib``,
479-
while `location`_ shall give the location of the ``.dll``.
480+
while :attribute:`location` shall give the location of the ``.dll``.
480481

481482
If the path starts with ``@prefix@``,
482483
the package's prefix is substituted
@@ -494,7 +495,7 @@ Attribute names are case sensitive.
494495

495496
Specifies additional components required by a component
496497
which are needed only at the link stage.
497-
Unlike `requires (component)`_,
498+
Unlike :attribute:`component.requires`,
498499
only the required components' link dependencies
499500
should be applied transitively;
500501
additional properties such as compile and include attributes
@@ -514,7 +515,7 @@ Attribute names are case sensitive.
514515
such as a ``.so`` or ``.jar``.
515516
(For Windows DLL components,
516517
this should be the location of the ``.dll``.
517-
See also `link_location`_.)
518+
See also :attribute:`link_location`.)
518519

519520
If the path starts with ``@prefix@``,
520521
the package's prefix is substituted
@@ -535,8 +536,8 @@ Attribute names are case sensitive.
535536
the name of the CPS file
536537
without the ``.cps`` suffix
537538
must exactly match (including case)
538-
either :attribute:`name` as-is,
539-
or :attribute:`name` converted to lower case.
539+
either :attribute:`!name` as-is,
540+
or :attribute:`!name` converted to lower case.
540541

541542
.. ----------------------------------------------------------------------------
542543
.. cps:attribute:: platform
@@ -568,7 +569,8 @@ Attribute names are case sensitive.
568569
for non-relocatable package.
569570
See also `Prefix Determination`_.
570571

571-
Exactly **one** of `cps_path`_ or ``prefix`` is required.
572+
Exactly **one** of :attribute:`cps_path` or :attribute:`!prefix`
573+
is required.
572574

573575
.. ----------------------------------------------------------------------------
574576
.. cps:attribute:: requires
@@ -580,12 +582,12 @@ Attribute names are case sensitive.
580582
This is used, for example, to indicate transitive dependencies.
581583
Relative component names are interpreted relative to the current package.
582584
Absolute component names must refer to a package required by this package
583-
(see `requires (package)`_).
585+
(see :attribute:`package.requires`).
584586
Compile and link attributes should be applied transitively,
585587
as if the consuming component also directly consumed the components
586588
required by the component being consumed.
587589

588-
See also `link_requires`_.
590+
See also :attribute:`link_requires`.
589591

590592
.. ----------------------------------------------------------------------------
591593
.. cps:attribute:: requires
@@ -650,7 +652,7 @@ Attribute names are case sensitive.
650652
:overload:
651653

652654
Specifies the version of the package.
653-
The format of this string is determined by `version_schema`_.
655+
The format of this string is determined by :attribute:`version_schema`.
654656

655657
If not provided, the CPS will not satisfy any request
656658
for a specific version of the package.
@@ -664,7 +666,7 @@ Attribute names are case sensitive.
664666
Specifies the required version of a package.
665667
If omitted, any version of the required package is acceptable.
666668
Semantics are the same
667-
as for the :attribute:`version` attribute of a |package|.
669+
as for the :attribute:`~package.version` attribute of a |package|.
668670

669671
.. ----------------------------------------------------------------------------
670672
.. cps:attribute:: version_schema
@@ -692,7 +694,7 @@ Attribute names are case sensitive.
692694
It does not imply anything
693695
about the compatibility or incompatibility
694696
of various versions of a package.
695-
See also `compat_version`_.
697+
See also :attribute:`compat_version`.
696698

697699
- :string:`simple`
698700

searching.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ or to provide the location of a package
101101
which is not installed to any of the standard search paths.
102102

103103
When a candidate ``.cps`` file is found,
104-
the tool shall inspect the package's `platform`_.
104+
the tool shall inspect the package's :attribute:`platform`.
105105
If the package's platform does not match the target platform,
106106
the tool should ignore the ``.cps`` and continue the search.
107107
This allows for the installation of packages for different platforms
@@ -127,13 +127,14 @@ it is necessary to know the package's prefix
127127
as :var:`prefix`, above).
128128
This is accomplished in one of two ways:
129129

130-
- If a package specifies `prefix`_, that value is used.
130+
- If a package specifies :attribute:`prefix`,
131+
that value is used.
131132

132-
- If a package specifies `cps_path`_,
133+
- If a package specifies :attribute:`cps_path`,
133134
the prefix shall be determined from that value
134135
in combination with the absolute location of the ``.cps`` file.
135136

136-
A correctly specified `cps_path`_ will match the location
137+
A correctly specified :attribute:`cps_path` will match the location
137138
(that is, the path without the final ``.cps`` file name)
138139
of the ``.cps`` file.
139140
For example, ``/usr/local/lib/cps/foo/foo.cps``

0 commit comments

Comments
 (0)