<<>> <> <> <> <> <> <> <> <> <<>> DIRECTORY Atom, BasicTime USING [GMT, Period, ToNSTime], CommandTool USING [CurrentWorkingDirectory], FS USING [ComponentPositions, Error, ExpandName, StreamOpen], IO, MakeDo USING [Action, ActionClass, ActionClassRep, AddFinder, fileClass, From, GetNode, GetProp, InnerGetCreated, Node, NodeList, notExistTime, PublicPartsOfAction, PublicPartsOfNode, SetProp, Time, Warning], MakeDoParsers, MakeDoPrivate USING [MDInstall], MakeDoPorting USING [logPath], MobDefs USING [Base, FTHandle, FTIndex, FTNull, FTRecord, FTSelf, MobBase, NameRecord, NameString, NullVersion, VersionID, VersionStamp], Mobery USING [FlushCache, StampAndNameFromFile], MobListerUtils USING [FreeMob, MobErr, PrintVersion, ReadMob], PBasics USING [IsBound], RefTab, Rope, SimpleFeedback, UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Boolean]; MimosaAndCinderDeps: CEDAR MONITOR IMPORTS Atom, BasicTime, CommandTool, FS, IO, MakeDo, MakeDoParsers, MakeDoPrivate, MakeDoPorting, Mobery, MobListerUtils, PBasics, RefTab, Rope, SimpleFeedback, UserProfile = <> <> BEGIN OPEN MakeDo, MDPs:MakeDoParsers; ShouldNotHappen: ERROR = CODE; ROPE: TYPE = Rope.ROPE; CachedMobData: TYPE = REF CachedMobDataRep; CachedMobDataRep: TYPE = RECORD [ created: BasicTime.GMT, <> stamp: MobDefs.VersionStamp, <> sourceStamp: MobDefs.VersionStamp, <> dependList: RefTab.Ref <> ]; SourceData: TYPE = REF SourceDataRep; SourceDataRep: TYPE = RECORD [ mobName, cName, shortName: ROPE, sourceType: SourceType _ Unknown, resultType: ResultType _ Unknown, supports: RefTab.Ref, mesaNode, cedarNode, configNode, sourceNode, switchesNode, mobNode, cNode: Node, sourceStamp, mobStamp: MobDefs.VersionStamp _ MobDefs.NullVersion, <> <> mobCreateTime, cCreateTime: BasicTime.GMT, mobReadable, cReadable: BOOL _ FALSE, supportsInvalid: BOOL _ FALSE, cmd: ROPE _ NIL ]; SourceType: TYPE = MDPs.SourceType[Unknown .. Config]; ResultType: TYPE = MDPs.ResultType[Unknown .. MobOnly]; Support: TYPE = REF SupportRep; SupportRep: TYPE = RECORD [ node: Node, version: MobDefs.VersionStamp]; <> installDir: ROPE ~ CommandTool.CurrentWorkingDirectory[]; installed, badInstall: BOOL _ FALSE; dolog: BOOL _ FALSE; myRouterName: ATOM ~ Atom.MakeAtom["MakeDo.MimosaAndCinderDeps"]; <> cinderDefaultSwitch: ROPE _ " -m~g "; -- used to be /l - bj mimosaDefaultSwitch: ROPE _ " -lc "; cTail: ROPE = ".c2c.c"; cTailLength: CARDINAL = cTail.Length[]; mobExt: ROPE = "mob"; mobTail: ROPE = Rope.Cat[".", mobExt]; mesaTail: ROPE = ".mesa"; cedarTail: ROPE = ".cedar"; configTail: ROPE = ".config"; switchesTail: ROPE = ".switches"; MimosaAndCinderClass: ActionClass _ NEW [ActionClassRep _ [ CheckConsistency: CheckConsistency, Rederive: RederiveSource, EnumHiddenDeps: EnumHiddenDeps, ClearCaches: ClearCaches ]]; ClearCaches: PROC [ac: ActionClass] ~ { Mobery.FlushCache[]; MDPs.FlushCache[]; installed _ badInstall _ FALSE; }; SourceFind: PROC [resultName: ROPE, finderData: REF ANY] RETURNS [found: BOOLEAN, sought: Node, makes, cmdFrom: NodeList, from: From, cmd: ROPE, class: ActionClass, foundData: REF ANY] -- FinderProc -- ~ { <> <> mobExpanded, baseName, shortName, mobName, cName: ROPE; mobCP: FS.ComponentPositions; mobNode, cNode: Node; isMob, isC2C: BOOL _ FALSE; md: SourceData; found _ TRUE; IF badInstall THEN {found _ FALSE; RETURN}; [mobExpanded, mobCP, ] _ FS.ExpandName[resultName !FS.Error => {found _ FALSE; CONTINUE}]; IF NOT found THEN RETURN; isMob _ mobExpanded.EqualSubstrs[start1: mobCP.ext.start, len1: mobCP.ext.length, s2: mobExt, case: FALSE]; IF mobCP.ext.start + mobCP.ext.length >= cTailLength THEN { isC2C _ mobExpanded.EqualSubstrs[start1: mobCP.ext.start+mobCP.ext.length-cTailLength, len1: cTailLength, s2: cTail, case: FALSE]; } ELSE { isC2C _ FALSE; }; IF NOT (found _ isMob OR isC2C) THEN RETURN; <> <<>> IF NOT installed THEN InstallMe[]; found _ NOT badInstall; IF NOT found THEN RETURN; baseName _ mobExpanded.Substr[start: 0, len: mobCP.base.start + mobCP.base.length]; shortName _ mobExpanded.Substr[start: mobCP.base.start, len: mobCP.base.length]; IF isC2C THEN { <> baseName _ Rope.Substr[baseName, 0, baseName.Length[]-(cTailLength-2)]; shortName _ Rope.Substr[shortName, 0, shortName.Length[]-(cTailLength-2)]; }; mobNode _ GetNode[mobName _ baseName.Cat[mobTail], fileClass]; cNode _ GetNode[cName _ baseName.Concat[cTail], fileClass]; foundData _ md _ NEW [SourceDataRep _ [ mobName: mobName, cName: cName, shortName: shortName, supports: RefTab.Create[], mesaNode: GetNode[baseName.Concat[mesaTail], fileClass], cedarNode: GetNode[baseName.Concat[cedarTail], fileClass], configNode: GetNode[baseName.Concat[configTail], fileClass], cNode: cNode, sourceNode: NIL, switchesNode: GetNode[mobName.Concat[switchesTail], fileClass], mobNode: mobNode, mobCreateTime: MakeDo.notExistTime, cCreateTime: MakeDo.notExistTime ]]; cmdFrom _ LIST[md.cedarNode, md.mesaNode, md.configNode, md.switchesNode]; sought _ IF isC2C THEN md.cNode ELSE md.mobNode; class _ MimosaAndCinderClass; [from, cmd] _ RederiveWork[md]; makes _ LIST[md.cNode, md.mobNode]; <> <> RETURN}; GetSwitches: PROC [mobName: ROPE, default: ROPE _ NIL] RETURNS [switches: ROPE] = BEGIN ss: IO.STREAM _ NIL; ss _ FS.StreamOpen[mobName.Cat[switchesTail] !FS.Error => CONTINUE]; IF ss = NIL THEN RETURN [default]; [] _ ss.SkipWhitespace[]; IF ss.EndOf[] THEN { ss.Close[]; RETURN [default] } ; switches _ ss.GetTokenRope[IO.IDProc].token; ss.Close[]; END; SupportIsLoaded: PROC [] RETURNS [BOOL] ~ INLINE { moberies: BOOL; moblisteries: BOOL; moberies _ PBasics.IsBound[LOOPHOLE[Mobery.StampAndNameFromFile]]; moblisteries _ PBasics.IsBound[LOOPHOLE[MobListerUtils.PrintVersion]] AND PBasics.IsBound[LOOPHOLE[MobListerUtils.ReadMob]]; <> RETURN [moberies AND moblisteries]; }; InstallMe: PROC ~ { foundFile, failed: BOOL; IF SupportIsLoaded[] THEN { installed _ TRUE; RETURN; }; [foundFile, failed] _ MakeDoPrivate.MDInstall[installDir, "MimosaAndCinderDepsSupport"]; badInstall _ failed OR NOT foundFile; installed _ TRUE; IF NOT foundFile THEN Warning[Rope.Cat["Couldn't install MimosaAndCinder support because couldn't find ", installDir, "MimosaAndCinderDepsSupport.Install (see ", MakeDoPorting.logPath, "MimosaAndCinderDepsSupport.InstallLog); proceeding without knowledge of MimosaAndCinder"]] ELSE IF failed THEN Warning[Rope.Cat["Couldn't install MimosaAndCinder support because of error in install file (", installDir, "MimosaAndCinderDepsSupport.Install) (see ", MakeDoPorting.logPath, "MimosaAndCinderDepsSupport.InstallLog); proceeding without knowledge of MimosaAndCinder"]]; RETURN}; CheckConsistency: PROC [a: Action, result: Node] RETURNS [consistent: BOOL, reason: ROPE] --ConsistencyChecker-- ~ { <> md: SourceData = NARROW[a.PublicPartsOfAction[].foundData]; switchesCreate: BasicTime.GMT = InnerGetCreated[md.switchesNode]; resultName: ROPE ~ result.PublicPartsOfNode[].name; resultCreateTime: Time = InnerGetCreated[result]; resultExists: BOOL ~ resultCreateTime # MakeDo.notExistTime; curMobStamp: MobDefs.VersionStamp; -- Stamp from the current Mob file of the action -- supportFound: BOOL _ FALSE; seekMob: BOOL ~ result = md.mobNode; seekC: BOOL ~ result = md.cNode; CheckSupport: PROC [key, val: REF ANY] RETURNS [stop: BOOL _ FALSE] --RefTab.EachPairAction-- ~ { <> <> <> s: Support = NARROW[val]; thisTime: Time = InnerGetCreated[s.node]; thisMobStamp: MobDefs.VersionStamp = CurMobStamp[s.node]; thisName: Rope.ROPE ~ s.node.PublicPartsOfNode[].name; extension, fullName: Rope.ROPE; cp: FS.ComponentPositions; [fullFName: fullName, cp: cp] _ FS.ExpandName[thisName ! FS.Error => { ERROR ShouldNotHappen}]; extension _ Rope.Substr[fullName, cp.ext.start, cp.ext.length]; SELECT TRUE FROM Rope.Equal[s1: extension, s2: mobExt, case: FALSE] => { <> IF thisMobStamp = MobDefs.NullVersion THEN RETURN [FALSE]; supportFound _ TRUE; IF NOT resultExists THEN RETURN [supportFound]; IF thisMobStamp # s.version THEN { out: IO.STREAM _ IO.ROS[]; out.PutRope["last used version "]; TRUSTED {MobListerUtils.PrintVersion[s.version, out, FALSE]}; out.PutF[ " of %g, but current version is ", [rope[thisName]] ]; TRUSTED {MobListerUtils.PrintVersion[thisMobStamp, out, FALSE]}; consistent _ FALSE; reason _ out.RopeFromROS[]; RETURN [TRUE]; } ELSE RETURN [NOT resultExists]; }; ENDCASE => { <> IF thisTime = MakeDo.notExistTime THEN RETURN [FALSE]; supportFound _ TRUE; IF NOT resultExists THEN RETURN [supportFound]; IF BasicTime.Period[thisTime, resultCreateTime] < 0 THEN { out: IO.STREAM _ IO.ROS[]; out.PutRope["File created in "]; TRUSTED {MobListerUtils.PrintVersion[MobStampFromTime[resultCreateTime], out, TRUE]}; out.PutF[ " but %g was created in ", [rope[thisName]] ]; TRUSTED {MobListerUtils.PrintVersion[MobStampFromTime[thisTime], out, TRUE]}; consistent _ FALSE; reason _ out.RopeFromROS[]; RETURN [TRUE]; } ELSE RETURN [NOT resultExists]; }; }; UpdateSupport[md]; SELECT md.sourceType FROM Mesa, Config => NULL; Unknown => RETURN [TRUE, "No source is known for this result"]; ENDCASE => ERROR ShouldNotHappen; IF md.resultType = MobOnly AND seekC THEN IF md.cCreateTime = MakeDo.notExistTime THEN RETURN [TRUE, "no amount of recompilation will derive a .c2c.c file from a .mesa interface"] ELSE RETURN [FALSE, "C2C file should not exist for a .mesa interface"]; SELECT TRUE FROM seekC => IF resultExists AND NOT md.cReadable THEN RETURN [FALSE, "c not readable"]; seekMob => IF resultExists AND NOT md.mobReadable THEN RETURN [FALSE, "mob not readable"]; ENDCASE => ERROR ShouldNotHappen; {curSourceStamp: MobDefs.VersionStamp ~ CurSourceStamp[md.sourceNode]; IF curSourceStamp # MobDefs.NullVersion THEN { supportFound _ TRUE; IF md.mobReadable AND resultExists AND curSourceStamp # md.sourceStamp THEN { out: IO.STREAM _ IO.ROS[]; out.PutRope["last used source "]; TRUSTED {MobListerUtils.PrintVersion[md.sourceStamp, out, TRUE]}; out.PutRope[", but current source is "]; TRUSTED {MobListerUtils.PrintVersion[curSourceStamp, out, TRUE]}; RETURN [FALSE, out.RopeFromROS[]]; }; }; }; curMobStamp _ CurMobStamp[md.mobNode]; IF seekMob AND resultExists THEN { IF curMobStamp = MobDefs.NullVersion THEN RETURN [FALSE, "New compiler version"]; <> }; IF seekC AND resultExists THEN { IF md.mobStamp # MobDefs.NullVersion THEN { <> IF curMobStamp # md.mobStamp THEN { out: IO.STREAM _ IO.ROS[]; out.PutRope["last used mob "]; TRUSTED {MobListerUtils.PrintVersion[md.mobStamp, out, TRUE]}; out.PutRope[", but current mob is "]; TRUSTED {MobListerUtils.PrintVersion[curMobStamp, out, TRUE]}; RETURN [FALSE, out.RopeFromROS[]]; }; } ELSE { <> IF BasicTime.Period[md.cCreateTime, md.mobCreateTime] > 0 THEN RETURN [FALSE, "C2C is older than MobFile file"]; } }; IF switchesCreate # MakeDo.notExistTime THEN { supportFound _ TRUE; IF BasicTime.Period[resultCreateTime, switchesCreate] > 0 THEN RETURN [FALSE, IO.PutFR["switches file created later than result %g", [rope[resultName]]]]; }; consistent _ TRUE; reason _ "all version stamps match"; [] _ md.supports.Pairs[CheckSupport]; IF NOT supportFound THEN RETURN [TRUE, "no inputs exist"]; IF NOT resultExists THEN RETURN [FALSE, IO.PutFR["some inputs exist, but not output %g", [rope[resultName]]]]; RETURN}; UpdateSupport: PROC [md: SourceData] = { <> oldMob: Time = md.mobCreateTime; oldC: Time = md.cCreateTime; md.mobCreateTime _ InnerGetCreated[md.mobNode]; md.cCreateTime _ InnerGetCreated[md.cNode]; IF md.mobCreateTime = MakeDo.notExistTime THEN RETURN; IF md.mobCreateTime = oldMob AND md.cCreateTime = oldC AND NOT md.supportsInvalid THEN RETURN; MakeSupports[md]; }; CurSourceStamp: PROC [node: Node] RETURNS [mobStamp: MobDefs.VersionStamp] = <> BEGIN time: Time _ InnerGetCreated[node]; RETURN [MobStampFromTime[time]]; END; MobStampFromTime: PROC [time: Time] RETURNS [mobStamp: MobDefs.VersionStamp] = <> BEGIN mobStamp _ MobDefs.NullVersion; IF time = MakeDo.notExistTime THEN RETURN [mobStamp]; mobStamp[0] _ BasicTime.ToNSTime[time]; END; CurMobStamp: PROC [node: Node] RETURNS [stamp: MobDefs.VersionStamp] ~ { sr: CachedMobData _ CurMobData[node]; RETURN [sr.stamp]; }; CurMobData: ENTRY PROC [node: Node] RETURNS [sr: CachedMobData] = <> BEGIN ENABLE UNWIND => {}; sr: CachedMobData _ NARROW[node.GetProp[$MobData]]; created: BasicTime.GMT _ InnerGetCreated[node]; mob: MobDefs.MobBase _ NIL; IF sr = NIL THEN node.SetProp[ prop: $MobData, val: sr _ NEW [CachedMobDataRep _ [ created: notExistTime, stamp: MobDefs.NullVersion, sourceStamp: MobDefs.NullVersion, dependList: RefTab.Create[]]] ]; IF created = sr.created THEN RETURN [sr]; sr.created _ created; IF created = MakeDo.notExistTime THEN { sr.stamp _ MobDefs.NullVersion; sr.sourceStamp _ MobDefs.NullVersion; sr.dependList _ RefTab.Create[]; } ELSE TRUSTED { fileName: ROPE ~ node.PublicPartsOfNode[].name; mob _ MobListerUtils.ReadMob[fileName ! MobListerUtils.MobErr => CONTINUE]; IF mob = NIL OR mob.versionIdent # MobDefs.VersionID THEN { sr.stamp _ MobDefs.NullVersion; sr.sourceStamp _ MobDefs.NullVersion; sr.dependList _ RefTab.Create[]; } ELSE { InsertInDependList: PROC [name: ROPE, version: MobDefs.VersionStamp] = TRUSTED { extName: ROPE = FS.ExpandName[name.Cat[mobTail]].fullFName; node: Node = GetNode[extName, fileClass]; s: Support _ NARROW[sr.dependList.Fetch[node].val]; IF s # NIL THEN s.version _ version ELSE { s _ NEW [SupportRep _ [node, version]]; IF NOT sr.dependList.Insert[node, s] THEN ERROR; }; RETURN}; sr.stamp _ mob.version; sr.sourceStamp _ mob.sourceVersion; EnumerateFiles[mob: mob, to: InsertInDependList]; }; IF mob # NIL THEN MobListerUtils.FreeMob[mob]; }; RETURN [sr]; END; EnumerateFiles: PROC [mob: MobDefs.MobBase, to: PROC [name: ROPE, version: MobDefs.VersionStamp]] = TRUSTED BEGIN fti: MobDefs.FTIndex _ MobDefs.FTIndex.FIRST; ssb: MobDefs.NameString = LOOPHOLE[mob + mob.ssOffset.units]; ftb: MobDefs.Base = LOOPHOLE[mob + mob.ftOffset.units]; UNTIL fti = mob.ftLimit DO SELECT fti FROM MobDefs.FTNull => NULL; MobDefs.FTSelf => NULL; ENDCASE => { ftr: MobDefs.FTHandle = @ftb[fti]; name: ROPE = NameToRope[ftr.name, ssb]; to[name: name, version: ftr.version]}; fti _ fti + MobDefs.FTRecord.SIZE; IF LOOPHOLE[fti, CARD] > LOOPHOLE[mob.ftLimit, CARD] THEN ERROR; ENDLOOP; END; NameToRope: PROC [n: MobDefs.NameRecord, ssb: MobDefs.NameString] RETURNS [ROPE] = TRUSTED { CharSeq: TYPE = RECORD[PACKED SEQUENCE COMPUTED CARDINAL OF CHAR]; ss: LONG POINTER TO CharSeq = LOOPHOLE[ssb]; index: CARDINAL = n+4; len: CARDINAL = ss[index]-0C; ros: IO.STREAM = IO.ROS[]; FOR i: NAT IN [index+1..index+len] DO IO.PutChar[ros, ss[i]]; ENDLOOP; RETURN [IO.RopeFromROS[ros]]; }; MakeSupports: PROC [md: SourceData] = { <> NoteSupport: PROC [key, val: REF ANY] RETURNS [stop: BOOL _ FALSE] = --RefTab.EachPairAction-- { mobSupport: Support = NARROW[val]; node: Node = mobSupport.node; version: MobDefs.VersionStamp ~ mobSupport.version; s: Support = NARROW[md.supports.Fetch[node].val]; IF s # NIL THEN s.version _ version; RETURN}; stamp: MobDefs.VersionStamp; name: ROPE; md.sourceStamp _ MobDefs.NullVersion; { sr: CachedMobData _ CurMobData[md.mobNode]; IF sr.created = notExistTime THEN { md.mobReadable _ FALSE; md.cReadable _ FALSE; RETURN; }; md.sourceStamp _ sr.sourceStamp; md.mobReadable _ TRUE; IF sr.dependList.Pairs[NoteSupport] THEN ERROR; }; md.cReadable _ TRUE; [stamp: stamp, name: name] _ Mobery.StampAndNameFromFile[md.cName! FS.Error => {md.cReadable _ FALSE; Log["C file %g not readable (1)\n", [rope[md.cName]] ]; CONTINUE}]; IF NOT md.cReadable THEN RETURN; IF NOT Rope.Equal[name, md.shortName] THEN { md.cReadable _ FALSE; Log["name %g is different from expected %g\n", IO.rope[name], IO.rope[md.shortName]]; }; IF NOT md.cReadable THEN RETURN; md.mobStamp _ stamp; md.supportsInvalid _ FALSE; RETURN}; EnumHiddenDeps: PROC [a: Action, Consume: PROC [Node]] = { md: SourceData ~ NARROW[a.PublicPartsOfAction[].foundData]; CheckSupport: PROC [key, val: REF ANY] RETURNS [stop: BOOL _ FALSE] --RefTab.EachPairAction-- ~ { mobSupport: Support = NARROW[val]; node: Node = mobSupport.node; IF NOT md.supports.Fetch[node].found THEN Consume[node]; RETURN}; SELECT md.sourceType FROM Mesa => NULL; Config => RETURN; ENDCASE => ERROR; { sr: CachedMobData _ CurMobData[md.mobNode]; IF sr.created = notExistTime THEN { RETURN; }; IF sr.dependList.Pairs[CheckSupport] THEN ERROR; }; RETURN}; RederiveSource: PROC [a: Action] RETURNS [from: From, cmd: ROPE] --RederiveProc-- = { md: SourceData ~ NARROW[a.PublicPartsOfAction[].foundData]; RETURN RederiveWork[md]}; <> <<>> RederiveWork: PROC [md: SourceData] RETURNS [from: From, cmd: ROPE] ~ { <> NoteDep: PROC [fileName: ROPE] ~ { <> n: Node = GetNode[fileName, fileClass]; s: Support _ NARROW[md.supports.Fetch[n].val]; IF s = NIL THEN { s _ NEW [SupportRep _ [n, MobDefs.NullVersion]]; IF NOT md.supports.Insert[n, s] THEN ERROR; <> }; from.mustHave _ CONS[n, from.mustHave]; RETURN}; switches: Rope.ROPE; exists: BOOL; dbxDebug: BOOL = UserProfile.Boolean[key: "MakeDo.dbxDebug", default: FALSE]; from _ [mustHave: NIL, optional: LIST[md.switchesNode]]; md.supports.Erase[]; md.sourceNode _ md.mesaNode; md.sourceType _ Mesa; [exists: exists, resultType: md.resultType] _ EnumerateDependancies[md.mesaNode, Mesa, NoteDep]; IF exists THEN NULL ELSE { [exists: exists, resultType: md.resultType] _ EnumerateDependancies[md.cedarNode, Mesa, NoteDep]; IF exists THEN NULL ELSE { [exists: exists, resultType: md.resultType] _ EnumerateDependancies[md.configNode, Config, NoteDep]; IF exists THEN { md.sourceNode _ md.configNode; md.sourceType _ Config; } ELSE { md.sourceType _ Unknown; md.resultType _ Unknown; }}}; md.supportsInvalid _ TRUE; UpdateSupport[md]; from.mustHave _ CONS[md.sourceNode, from.mustHave]; SELECT md.sourceType FROM Mesa => { switches _ GetSwitches[md.mobName, mimosaDefaultSwitch]; <> cmd _ Rope.Cat["Mimosa ", IF dbxDebug THEN "-a " ELSE NIL, switches, " ", md.shortName]; SELECT md.resultType FROM Unknown, MobAndC => NULL; MobOnly => cmd _ Rope.Cat[cmd, "; Delete ", md.shortName, cTail]; ENDCASE => ERROR; }; Config => { switches _ GetSwitches[md.mobName, cinderDefaultSwitch]; cmd _ Rope.Cat["Cind ", switches, " ", md.shortName]; }; Unknown => cmd _ NIL; ENDCASE => ERROR ShouldNotHappen; md.cmd _ cmd; RETURN}; EnumerateDependancies: PROC [sourceNode: Node, sourceType: SourceType, consume: PROC [fileName: ROPE]] RETURNS [exists: BOOLEAN, resultType: ResultType _ Unknown] ~ { <> pd: MDPs.ParseData ~ MDPs.GetParseData[sourceNode, sourceType]; IF pd=NIL THEN RETURN [FALSE, Unknown]; MDPs.EnumerateWithSuffix[pd.refdModules, ".mob", consume]; RETURN [TRUE, pd.resultType]}; Log: PROC [fmt: ROPE, v1, v2: IO.Value _ [null[]] ] ~ { IF NOT dolog THEN RETURN; SimpleFeedback.PutF[myRouterName, oneLiner, $Log, fmt, v1, v2]; RETURN}; OnProfileChange: UserProfile.ProfileChangedProc ~ { dolog _ UserProfile.Boolean[key: "MimosaAndCinderDeps.Talk", default: FALSE]; }; AddFinder[["Mimosa and Cinder", SourceFind], front]; UserProfile.CallWhenProfileChanges[OnProfileChange]; END.