88from .languages import getLanguage , ScriptLanguage , BashLanguage , PwshLanguage
99from .pathspec import PackageSet
1010from .scm import CvsScm , GitScm , ImportScm , SvnScm , UrlScm , ScmOverride , \
11- auditFromDir , getScm , SYNTHETIC_SCM_PROPS
11+ auditFromDir , auditFromProperties , getScm , SYNTHETIC_SCM_PROPS
1212from .state import BobState
1313from .stringparser import checkGlobList , Env , DEFAULT_STRING_FUNS , IfExpression
1414from .tty import InfoOnce , Warn , WarnOnce , setColorMode , setParallelTUIThreshold
@@ -1739,13 +1739,28 @@ def getScm(self):
17391739 return self .__scm
17401740
17411741class LayerValidator :
1742+ @staticmethod
1743+ def __validateName (name ):
1744+ if name == "" :
1745+ raise schema .SchemaError ("Layer name must not be empty" )
1746+ if any ((c in name ) for c in '\\ /' ):
1747+ raise schema .SchemaError ("Invalid character in layer name" )
1748+
17421749 def validate (self , data ):
1743- if isinstance (data ,str ):
1750+ if isinstance (data , str ):
1751+ self .__validateName (data )
17441752 return LayerSpec (data )
1753+ elif not isinstance (data , dict ):
1754+ raise schema .SchemaUnexpectedTypeError ("Layer entry must be a string or a dict" , None )
1755+
17451756 if 'name' not in data :
17461757 raise schema .SchemaMissingKeyError ("Missing 'name' key in {}" .format (data ), None )
1758+ elif not isinstance (data ['name' ], str ):
1759+ raise schema .SchemaUnexpectedTypeError ("Layer name must be a string" , None )
1760+
17471761 _data = data .copy ();
17481762 name = _data .get ('name' )
1763+ self .__validateName (name )
17491764 del _data ['name' ]
17501765
17511766 return LayerSpec (name , RecipeSet .LAYERS_SCM_SCHEMA .validate (_data )[0 ])
@@ -2957,11 +2972,12 @@ class RecipeSet:
29572972 })
29582973
29592974 # We do not support the "import" SCM for layers. It just makes no sense.
2975+ # Also, all SCMs lack the "dir" and "if" attributes.
29602976 LAYERS_SCM_SCHEMA = ScmValidator ({
2961- 'git' : GitScm .SCHEMA ,
2962- 'svn' : SvnScm .SCHEMA ,
2963- 'cvs' : CvsScm .SCHEMA ,
2964- 'url' : UrlScm .SCHEMA ,
2977+ 'git' : GitScm .LAYERS_SCHEMA ,
2978+ 'svn' : SvnScm .LAYERS_SCHEMA ,
2979+ 'cvs' : CvsScm .LAYERS_SCHEMA ,
2980+ 'url' : UrlScm .LAYERS_SCHEMA ,
29652981 })
29662982
29672983 SCM_SCHEMA = ScmValidator ({
@@ -3101,7 +3117,7 @@ def __init__(self):
31013117 self .__commandConfig = {}
31023118 self .__uiConfig = {}
31033119 self .__shareConfig = {}
3104- self .__layers = []
3120+ self .__layers = {}
31053121 self .__buildHooks = {}
31063122 self .__sandboxOpts = {}
31073123 self .__scmDefaults = {}
@@ -3424,20 +3440,34 @@ async def getScmAudit(self):
34243440 try :
34253441 ret = self .__recipeScmAudit
34263442 except AttributeError :
3443+ ret = {}
34273444 try :
3428- ret = await auditFromDir ("." )
3445+ ret [ "" ] = await auditFromDir ("." )
34293446 except BobError as e :
34303447 Warn ("could not determine recipes state" ).warn (e .slogan )
3431- ret = None
3448+
3449+ # We look into every layers directory. If the Bob state knows it,
3450+ # use the SCM information from there. Otherwise make a best guess.
3451+ for layer , path in sorted (self .__layers .items ()):
3452+ state = None
3453+ try :
3454+ scmProps = (BobState ().getLayerState (path ) or {}).get ("prop" )
3455+ if scmProps is None :
3456+ state = await auditFromDir (path )
3457+ else :
3458+ state = await auditFromProperties (path , scmProps )
3459+ except BobError as e :
3460+ Warn (f"could not determine layer '{ layer } ' state" ).warn (e .slogan )
3461+ ret [layer ] = state
3462+
34323463 self .__recipeScmAudit = ret
34333464 return ret
34343465
34353466 async def getScmStatus (self ):
3436- audit = await self .getScmAudit ()
3437- if audit is None :
3438- return "unknown"
3439- else :
3440- return audit .getStatusLine ()
3467+ scmAudit = await self .getScmAudit ()
3468+ return ", " .join (( ((f"layer { name } : " if name else "" )
3469+ + ("unknown" if audit is None else audit .getStatusLine ()))
3470+ for name , audit in sorted (scmAudit .items ()) ))
34413471
34423472 def getBuildHook (self , name ):
34433473 return self .__buildHooks .get (name )
@@ -3481,7 +3511,7 @@ def __parse(self, envOverrides, platform, recipesRoot=""):
34813511 if platform not in ('cygwin' , 'darwin' , 'linux' , 'msys' , 'win32' ):
34823512 raise ParseError ("Invalid platform: " + platform )
34833513 self .__platform = platform
3484- self .__layers = []
3514+ self .__layers = {}
34853515 self .__whiteList = getPlatformEnvWhiteList (platform )
34863516 self .__pluginPropDeps = b''
34873517 self .__pluginSettingsDeps = b''
@@ -3585,7 +3615,6 @@ def __parseLayer(self, layerSpec, maxVer, recipesRoot, upperLayer):
35853615
35863616 if layer in self .__layers :
35873617 return
3588- self .__layers .append (layer )
35893618
35903619 if managedLayers :
35913620 # SCM backed layers are in build dir, regular layers are in
@@ -3602,6 +3631,8 @@ def __parseLayer(self, layerSpec, maxVer, recipesRoot, upperLayer):
36023631 for l in layer .split ("/" ) ))
36033632 if not os .path .isdir (rootDir ):
36043633 raise ParseError (f"Layer '{ layer } ' does not exist!" )
3634+
3635+ self .__layers [layer ] = rootDir
36053636 else :
36063637 rootDir = recipesRoot
36073638
0 commit comments