11import ast
22from contextlib import contextmanager
3+ from collections import defaultdict
34import hashlib
45import logging
56import os
67import sys
8+ import json
9+ from typing import Any
710
811from pathlib import Path
912import re
1013
1114log = logging .getLogger (__name__ )
1215
16+ from .variant_constants import (
17+ VALIDATION_PROPERTY_REGEX ,
18+ VARIANTS_JSON_SCHEMA_KEY ,
19+ VARIANTS_JSON_SCHEMA_URL ,
20+ VARIANTS_JSON_VARIANT_DATA_KEY ,
21+ VARIANT_INFO_DEFAULT_PRIO_KEY ,
22+ VARIANT_INFO_FEATURE_KEY ,
23+ VARIANT_INFO_NAMESPACE_KEY ,
24+ VARIANT_INFO_PROPERTY_KEY ,
25+ VARIANT_INFO_PROVIDER_DATA_KEY ,
26+ VARIANT_INFO_PROVIDER_PLUGIN_API_KEY ,
27+ VARIANT_INFO_PROVIDER_ENABLE_IF_KEY ,
28+ VARIANT_INFO_PROVIDER_REQUIRES_KEY
29+ )
1330from .versionno import normalise_version
1431
1532class Module :
@@ -351,13 +368,14 @@ class Metadata:
351368 license_files = ()
352369 dynamic = ()
353370
354- variant_hash = None
355- variant_properties = ()
356- variant_requires = ()
357- variant_plugin_apis = ()
358- variant_default_namespace_priorities = ()
359- variant_default_feature_priorities = ()
360- variant_default_property_priorities = ()
371+ variant_hash : str | None = None
372+ variant_properties : list [str ] = []
373+ variant_plugins : dict [str , dict [str , list [str ] | str ]] = {}
374+ variant_default_priorities : dict [str , Any ] = {
375+ "namespace" : [],
376+ "feature" : {},
377+ "property" : {}
378+ }
361379
362380 metadata_version = "2.4"
363381
@@ -452,26 +470,74 @@ def write_metadata_file(self, fp):
452470 if self .description is not None :
453471 fp .write ('\n ' + self .description + '\n ' )
454472
473+
474+ def write_variants_json_file (self , fp ):
475+ """Write out Variant Metadata in json format"""
476+
455477 if self .variant_hash is not None :
456- fp .write ('Variant-hash: {}\n ' .format (self .variant_hash ))
457- for vprop in self .variant_properties :
458- fp .write ('Variant-property: {}\n ' .format (vprop ))
459- for vreq in self .variant_requires :
460- fp .write ('Variant-requires: {}\n ' .format (vreq ))
461- for vAPI in self .variant_plugin_apis :
462- fp .write ('Variant-plugin-api: {}\n ' .format (vAPI ))
463- if self .variant_default_namespace_priorities :
464- fp .write ('Variant-default-namespace-priorities: {}\n ' .format (
465- ', ' .join (self .variant_default_namespace_priorities )
466- ))
467- if self .variant_default_feature_priorities :
468- fp .write ('Variant-default-feature-priorities: {}\n ' .format (
469- ', ' .join (self .variant_default_feature_priorities )
470- ))
471- if self .variant_default_property_priorities :
472- fp .write ('Variant-default-property-priorities: {}\n ' .format (
473- ', ' .join (self .variant_default_property_priorities )
474- ))
478+ data = {
479+ VARIANTS_JSON_SCHEMA_KEY : VARIANTS_JSON_SCHEMA_URL ,
480+ VARIANT_INFO_DEFAULT_PRIO_KEY : {},
481+ VARIANT_INFO_PROVIDER_DATA_KEY : {},
482+ VARIANTS_JSON_VARIANT_DATA_KEY : {}
483+ }
484+
485+ # ==================== VARIANT_INFO_DEFAULT_PRIO_KEY ==================== #
486+
487+ if (ns_prio := self .variant_default_priorities ["namespace" ]):
488+ data [VARIANT_INFO_DEFAULT_PRIO_KEY ][VARIANT_INFO_NAMESPACE_KEY ] = ns_prio
489+
490+ if (feat_prio := self .variant_default_priorities ["feature" ]):
491+ data [VARIANT_INFO_DEFAULT_PRIO_KEY ][VARIANT_INFO_FEATURE_KEY ] = feat_prio
492+
493+ if (prop_prio := self .variant_default_priorities ["property" ]):
494+ data [VARIANT_INFO_DEFAULT_PRIO_KEY ][VARIANT_INFO_PROPERTY_KEY ] = prop_prio
495+
496+ if not data [VARIANT_INFO_DEFAULT_PRIO_KEY ]:
497+ # If no default priorities are set, remove the key
498+ del data [VARIANT_INFO_DEFAULT_PRIO_KEY ]
499+
500+ # ==================== VARIANT_INFO_PROVIDER_DATA_KEY ==================== #
501+
502+ variant_providers = defaultdict (dict )
503+ for ns , plugin_conf in self .variant_plugins .items ():
504+ variant_providers [ns ][VARIANT_INFO_PROVIDER_REQUIRES_KEY ] = plugin_conf .get ("requires" , [])
505+
506+ if (enable_if := plugin_conf .get ("enable_if" , None )) is not None :
507+ variant_providers [ns ][VARIANT_INFO_PROVIDER_ENABLE_IF_KEY ] = enable_if
508+
509+ if (plugin_api := plugin_conf .get ("plugin_api" , None )) is not None :
510+ variant_providers [ns ][VARIANT_INFO_PROVIDER_PLUGIN_API_KEY ] = plugin_api
511+
512+ data [VARIANT_INFO_PROVIDER_DATA_KEY ] = variant_providers
513+
514+ # ==================== VARIANTS_JSON_VARIANT_DATA_KEY ==================== #
515+
516+ variant_data = defaultdict (lambda : defaultdict (set ))
517+ for vprop_str in self .variant_properties :
518+ match = VALIDATION_PROPERTY_REGEX .match (vprop_str )
519+ if not match :
520+ raise ValueError (
521+ f"Invalid variant property '{ vprop_str } ' in variant { self .variant_hash } "
522+ )
523+ namespace = match .group ('namespace' )
524+ feature = match .group ('feature' )
525+ value = match .group ('value' )
526+ variant_data [namespace ][feature ].add (value )
527+ data [VARIANTS_JSON_VARIANT_DATA_KEY ][self .variant_hash ] = variant_data
528+
529+ def preprocess (data ):
530+ """Preprocess the data to ensure it is JSON serializable."""
531+ if isinstance (data , (defaultdict , dict )):
532+ return {k : preprocess (v ) for k , v in data .items ()}
533+ if isinstance (data , set ):
534+ return list (data )
535+ return data
536+
537+ json .dump (
538+ preprocess (data ), fp , indent = 4 , sort_keys = True , ensure_ascii = False
539+ )
540+
475541
476542 @property
477543 def supports_py2 (self ):
@@ -488,7 +554,7 @@ def make_metadata(module, ini_info):
488554 md_dict .update (ini_info .metadata )
489555 vconfig = getattr (ini_info , "variant_config" , None )
490556 if vconfig is not None :
491- md_dict .update (vconfig .to_metadata_dict ())
557+ md_dict .update (vconfig .to_variant_cfg_dict ())
492558 return Metadata (md_dict )
493559
494560
0 commit comments