1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21 }
22 unit ModeMatrixOpts;
23 
24 {$mode objfpc}{$H+}
25 
26 interface
27 
28 uses
29   Classes, SysUtils, contnrs,
30   // LazUtils
31   LazConfigStorage, Laz2_XMLCfg, LazLoggerBase, LazUTF8,
32   // Codetools
33   FileProcs, KeywordFuncLists, CodeToolsCfgScript,
34   // IDE
35   LazarusIDEStrConsts;
36 
37 const
38   BuildMatrixProjectName = '#project';
39   BuildMatrixIDEName = '#ide';
40 type
41   TBuildMatrixOptionType = (
42     bmotCustom,  // append fpc parameters in Value
43     bmotOutDir,  // override output directory -FU of target
44     bmotIDEMacro // MacroName and Value
45     );
46   TBuildMatrixOptionTypes = set of TBuildMatrixOptionType;
47 
48 const
49   BuildMatrixOptionTypeNames: array[TBuildMatrixOptionType] of string = (
50     'Custom',
51     'OutDir',
52     'IDEMacro'
53     );
54 
55 type
56   TBuildMatrixGroupType = (
57     bmgtEnvironment,
58     bmgtProject,
59     bmgtSession
60     );
61   TBuildMatrixGroupTypes = set of TBuildMatrixGroupType;
62 const
63   bmgtAll = [low(TBuildMatrixGroupType)..high(TBuildMatrixGroupType)];
64 
65 type
onstnull66   TStrToBoolEvent = function(const Identifier: string): boolean of object;
67 
68   TBuildMatrixOptions = class;
69 
70   { TBuildMatrixOption }
71 
72   TBuildMatrixOption = class(TPersistent)
73   private
74     FID: string;
75     FList: TBuildMatrixOptions;
76     FMacroName: string;
77     FModes: string;
78     FTargets: string;
79     FTyp: TBuildMatrixOptionType;
80     FValue: string;
81     procedure SetMacroName(AValue: string);
82     procedure SetModes(AValue: string);
83     procedure SetTargets(AValue: string);
84     procedure SetTyp(AValue: TBuildMatrixOptionType);
85     procedure SetValue(AValue: string);
86   public
87     procedure Assign(Source: TPersistent); override;
88     constructor Create(aList: TBuildMatrixOptions);
89     destructor Destroy; override;
FitsTargetnull90     function FitsTarget(const Target: string): boolean;
FitsModenull91     function FitsMode(const Mode: string): boolean;
92     property List: TBuildMatrixOptions read FList;
93     property ID: string read FID write FID;
94     property Targets: string read FTargets write SetTargets;
95     property Modes: string read FModes write SetModes; // modes separated by line breaks, case insensitive
96     property Typ: TBuildMatrixOptionType read FTyp write SetTyp;
97     property MacroName: string read FMacroName write SetMacroName;
98     property Value: string read FValue write SetValue;
Equalsnull99     function Equals(Obj: TObject): boolean; override;
GetModesSeparatedByCommanull100     function GetModesSeparatedByComma(const SaveModes: TStrToBoolEvent): string;
101     procedure SetModesFromCommaSeparatedList(aList: string);
102     procedure DisableModes(const DisableModeEvent: TStrToBoolEvent);
103     procedure EnableMode(aMode: string);
104     procedure RenameMode(const OldMode, NewMode: string);
105     procedure LoadFromConfig(Cfg: TConfigStorage);
106     procedure SaveToConfig(Cfg: TConfigStorage; const SaveModes: TStrToBoolEvent);
107     procedure LoadFromXMLConfig(Cfg: TXMLConfig; const aPath: string);
108     procedure SaveToXMLConfig(Cfg: TXMLConfig; const aPath: string; const SaveModes: TStrToBoolEvent);
AsStringnull109     function AsString: string;
110   end;
111 
112   { TBuildMatrixOptions }
113 
114   TBuildMatrixOptions = class(TPersistent)
115   private
116     FChangeStep: int64;
117     fSavedChangeStep: int64;
118     fClearing: boolean;
119     fItems: TObjectList; // list of TBuildMatrixOption
120     FOnChanged: TNotifyEvent;
121     FOnChangesd: TNotifyEvent;
GetItemsnull122     function GetItems(Index: integer): TBuildMatrixOption;
GetModifiednull123     function GetModified: boolean;
124     procedure SetModified(AValue: boolean);
125   public
126     procedure Assign(Source: TPersistent); override;
127     constructor Create;
128     destructor Destroy; override;
129     procedure Clear;
Countnull130     function Count: integer;
131     property Items[Index: integer]: TBuildMatrixOption read GetItems; default;
IndexOfnull132     function IndexOf(Option: TBuildMatrixOption): integer;
Addnull133     function Add(Typ: TBuildMatrixOptionType = bmotCustom; Targets: string = '*'): TBuildMatrixOption;
134     procedure Delete(Index: integer);
135     procedure DisableModes(const IsModeEvent: TStrToBoolEvent);
136     procedure RenameMode(const OldMode, NewMode: string);
137 
138     // equals, modified
139     property ChangeStep: int64 read FChangeStep;
140     procedure IncreaseChangeStep;
Equalsnull141     function Equals(Obj: TObject): boolean; override;
142     property OnChanged: TNotifyEvent read FOnChanged write FOnChangesd;
143     property Modified: boolean read GetModified write SetModified;
144 
145     // load, save
146     procedure LoadFromConfig(Cfg: TConfigStorage);
147     procedure SaveToConfig(Cfg: TConfigStorage; const SaveMode: TStrToBoolEvent);
148     procedure LoadFromXMLConfig(Cfg: TXMLConfig; const aPath: string);
149     procedure SaveToXMLConfig(Cfg: TXMLConfig; const aPath: string;
150       const SaveMode: TStrToBoolEvent);
SaveAtOldXMLConfignull151     function SaveAtOldXMLConfig(Cfg: TXMLConfig; const Path, ModeIdent: string): integer;
152     procedure SaveSessionEnabled(Cfg: TXMLConfig; const Path, ModeIdent: string; var Cnt: integer);
153 
154     // queries
155     procedure AppendCustomOptions(Target, ActiveMode: string; var Options: string);
156     procedure GetOutputDirectory(Target, ActiveMode: string; var OutDir: string);
FindOptionnull157     function FindOption(const ID: string): TBuildMatrixOption;
FindMacronull158     function FindMacro(const MacroName, MacroValue: string): TBuildMatrixOption;
159     procedure EnableModeIfOptionFound(ModeID, OptionID: String);
160   end;
161 
162   EMMMacroSyntaxException = class(Exception)
163   end;
164 
165 
BuildMatrixTargetFitsnull166 function BuildMatrixTargetFits(Target, Targets: string): boolean;
BuildMatrixTargetFitsPatternnull167 function BuildMatrixTargetFitsPattern(Target, Pattern: PChar): boolean;
CheckBuildMatrixTargetsSyntaxnull168 function CheckBuildMatrixTargetsSyntax(const Targets: String): String;
BuildMatrixModeFitsnull169 function BuildMatrixModeFits(Mode, ModesSeparatedByLineBreaks: string): boolean;
Str2BuildMatrixOptionTypenull170 function Str2BuildMatrixOptionType(const s: string): TBuildMatrixOptionType;
CreateBuildMatrixOptionGUIDnull171 function CreateBuildMatrixOptionGUID: string;
172 
SplitMatrixMacronull173 function SplitMatrixMacro(MacroAssignment: string;
174   out MacroName, MacroValue: string; ExceptionOnError: boolean): boolean;
175 procedure ApplyBuildMatrixMacros(Options: TBuildMatrixOptions; Target, ActiveMode: string;
176   CfgVars: TCTCfgScriptVariables);
177 
178 implementation
179 
BuildMatrixTargetFitsnull180 function BuildMatrixTargetFits(Target, Targets: string): boolean;
181 { case insensitive
182   * = all
183   a = fits a and A
184   a* = fits all starting with a
185   a? = fits all two letter names starting with a
186 
187   Comma and minus:
188     Fits if there is at least one positive match and no negative match
189   a,b = fits a or b
190   -a = if target is a, stop immediately with 'false'
191   -ab,a* = fits all beginning with a except for ab
192   a*,-ab = fits all beginning with a, the -ab is ignored
193 }
194 var
195   p: PChar;
196   Negated: Boolean;
197 begin
198   Result:=false;
199   if (Targets='') or (Target='') then exit;
200   p:=PChar(Targets);
201   repeat
202     if p^='-' then begin
203       Negated:=true;
204       inc(p);
205     end else
206       Negated:=false;
207     if BuildMatrixTargetFitsPattern(PChar(Target),p) then begin
208       if Negated then begin
209         exit(false);
210       end else begin
211         Result:=true;
212       end;
213     end;
214     while not (p^ in [',',#0]) do
215       inc(p);
216     while p^=',' do
217       inc(p);
218   until p^=#0;
219 end;
220 
BuildMatrixTargetFitsPatternnull221 function BuildMatrixTargetFitsPattern(Target, Pattern: PChar): boolean;
222 // Pattern ends at #0 or comma
223 // ? means one arbitrary character
224 // * means any arbitrary characters, even none
225 begin
226   Result:=false;
227   if (Target=nil) or (Target^=#0) or (Pattern=nil) or (Pattern^ in [#0,',']) then
228     exit;
229   repeat
230     case Pattern^ of
231     #0,',':
232       begin
233         // end of pattern reached
234         Result:=Target^=#0;
235         exit;
236       end;
237     '?':
238       begin
239         // one arbitrary character
240         if Target^=#0 then
241           exit;
242         inc(Pattern);
243         inc(Target);
244       end;
245     '*':
246       begin
247         repeat
248           inc(Pattern);
249         until Pattern^<>'*';
250         if Pattern^ in [#0,','] then
251           exit(true);
252         // behind the * comes a none * => check recursively all combinations
253         while Target^<>#0 do begin
254           if BuildMatrixTargetFitsPattern(Target,Pattern) then
255             exit(true);
256           inc(Target);
257         end;
258         exit;
259       end;
260     'a'..'z','A'..'Z':
261       begin
262         if UpChars[Pattern^]<>UpChars[Target^] then
263           exit;
264         inc(Pattern);
265         inc(Target)
266       end;
267     else
268       if Pattern^<>Target^ then
269         exit;
270       inc(Pattern);
271       inc(Target);
272     end;
273   until false;
274 end;
275 
CheckBuildMatrixTargetsSyntaxnull276 function CheckBuildMatrixTargetsSyntax(const Targets: String): String;
277 var
278   p: PChar;
279 
280   procedure WarnInvalidChar;
281   begin
282     Result:=Format(lisMMInvalidCharacterAt, [dbgstr(p^), IntToStr(p-PChar(
283       Targets)+1)]);
284   end;
285 
286 begin
287   Result:='';
288   if Targets='' then exit;
289   p:=PChar(Targets);
290   repeat
291     case p^ of
292     #0:
293       if p-PChar(Targets)=length(Targets) then
294         break
295       else begin
296         WarnInvalidChar;
297         exit;
298       end;
299     #1..#32,#127:
300       begin
301         WarnInvalidChar;
302         exit;
303       end;
304     end;
305     inc(p);
306   until false;
307 end;
308 
BuildMatrixModeFitsnull309 function BuildMatrixModeFits(Mode, ModesSeparatedByLineBreaks: string): boolean;
310 var
311   p: PChar;
312   m: PChar;
313 begin
314   Result:=false;
315   if Mode='' then exit;
316   if ModesSeparatedByLineBreaks='' then exit;
317   p:=PChar(ModesSeparatedByLineBreaks);
318   while p^<>#0 do begin
319     while p^ in [#1..#31] do inc(p);
320     m:=PChar(Mode);
321     while (UpChars[p^]=UpChars[m^]) and (p^>=' ') do begin
322       inc(p);
323       inc(m);
324     end;
325     if (m^=#0) and (p^ in [#10,#13,#0]) then
326       exit(true);
327     while p^>=' ' do inc(p);
328   end;
329 end;
330 
Str2BuildMatrixOptionTypenull331 function Str2BuildMatrixOptionType(const s: string): TBuildMatrixOptionType;
332 begin
333   for Result:=low(TBuildMatrixOptionType) to high(TBuildMatrixOptionType) do
334     if SysUtils.CompareText(BuildMatrixOptionTypeNames[Result],s)=0 then exit;
335   Result:=bmotCustom;
336 end;
337 
CreateBuildMatrixOptionGUIDnull338 function CreateBuildMatrixOptionGUID: string;
339 var
340   i: Integer;
341 begin
342   SetLength(Result,12);
343   for i:=1 to length(Result) do
344     Result[i]:=chr(ord('0')+random(10));
345 end;
346 
SplitMatrixMacronull347 function SplitMatrixMacro(MacroAssignment: string; out MacroName,
348   MacroValue: string; ExceptionOnError: boolean): boolean;
349 
350   procedure E(Msg: string);
351   begin
352     raise EMMMacroSyntaxException.Create(Msg);
353   end;
354 
355 var
356   p: PChar;
357   StartP: PChar;
358 begin
359   Result:=false;
360   MacroName:='';
361   MacroValue:='';
362   if MacroAssignment='' then begin
363     if ExceptionOnError then
364       E(lisMMMissingMacroName);
365     exit;
366   end;
367   p:=PChar(MacroAssignment);
368   if not IsIdentStartChar[p^] then begin
369     if ExceptionOnError then
370       E(Format(lisMMExpectedMacroNameButFound, [dbgstr(p^)]));
371     exit;
372   end;
373   StartP:=p;
374   repeat
375     inc(p);
376   until not IsIdentChar[p^];
377   MacroName:=copy(MacroAssignment,1,p-StartP);
378   if (p^<>':') or (p[1]<>'=') then begin
379     if ExceptionOnError then
380       E(Format(lisMMExpectedAfterMacroNameButFound, [dbgstr(p^)]));
381     exit;
382   end;
383   inc(p,2);
384   StartP:=p;
385   repeat
386     if (p^=#0) and (p-PChar(MacroAssignment)=length(MacroAssignment)) then break;
387     if p^ in [#0..#31,#127] then begin
388       if ExceptionOnError then
389         E(Format(lisMMInvalidCharacterInMacroValue, [dbgstr(p^)]));
390       exit;
391     end;
392     inc(p);
393   until false;
394   MacroValue:=copy(MacroAssignment,StartP-PChar(MacroAssignment)+1,p-StartP);
395   Result:=true;
396 end;
397 
398 procedure ApplyBuildMatrixMacros(Options: TBuildMatrixOptions;
399   Target, ActiveMode: string; CfgVars: TCTCfgScriptVariables);
400 var
401   i: Integer;
402   Option: TBuildMatrixOption;
403 begin
404   if (Options=nil) or (CfgVars=nil) then exit;
405   for i:=0 to Options.Count-1 do begin
406     Option:=Options[i];
407     if Option.Typ<>bmotIDEMacro then continue;
408     if not Option.FitsMode(ActiveMode) then continue;
409     if not Option.FitsTarget(Target) then continue;
410     //debugln(['ApplyBuildMatrixMacros Option.MacroName="',Option.MacroName,'" Value="',Option.Value,'"']);
411     CfgVars.Values[Option.MacroName]:=Option.Value;
412   end;
413 end;
414 
415 { TBuildMatrixOptions }
416 
GetItemsnull417 function TBuildMatrixOptions.GetItems(Index: integer): TBuildMatrixOption;
418 begin
419   Result:=TBuildMatrixOption(fItems[Index]);
420 end;
421 
GetModifiednull422 function TBuildMatrixOptions.GetModified: boolean;
423 begin
424   Result:=fSavedChangeStep<>FChangeStep;
425 end;
426 
427 procedure TBuildMatrixOptions.SetModified(AValue: boolean);
428 begin
429   if AValue then
430     IncreaseChangeStep
431   else
432     fSavedChangeStep:=FChangeStep;
433 end;
434 
435 procedure TBuildMatrixOptions.Assign(Source: TPersistent);
436 var
437   aSource: TBuildMatrixOptions;
438   i: Integer;
439   Item: TBuildMatrixOption;
440 begin
441   if Source is TBuildMatrixOptions then
442   begin
443     aSource:=TBuildMatrixOptions(Source);
444     Clear;
445     for i:=0 to aSource.Count-1 do begin
446       Item:=TBuildMatrixOption.Create(Self);
447       Item.Assign(aSource[i]);
448     end;
449   end else
450     inherited Assign(Source);
451 end;
452 
453 constructor TBuildMatrixOptions.Create;
454 begin
455   FChangeStep:=CTInvalidChangeStamp64;
456   fItems:=TObjectList.create(true);
457 end;
458 
459 destructor TBuildMatrixOptions.Destroy;
460 begin
461   Clear;
462   FreeAndNil(fItems);
463   inherited Destroy;
464 end;
465 
466 procedure TBuildMatrixOptions.Clear;
467 begin
468   if fItems.Count=0 then exit;
469   fClearing:=true;
470   fItems.Clear;
471   fClearing:=false;
472   IncreaseChangeStep;
473 end;
474 
475 procedure TBuildMatrixOptions.DisableModes(const IsModeEvent: TStrToBoolEvent);
476 var
477   i: Integer;
478 begin
479   for i:=0 to Count-1 do
480     Items[i].DisableModes(IsModeEvent);
481 end;
482 
483 procedure TBuildMatrixOptions.RenameMode(const OldMode, NewMode: string);
484 var
485   i: Integer;
486 begin
487   for i:=0 to Count-1 do
488     Items[i].RenameMode(OldMode,NewMode);
489 end;
490 
TBuildMatrixOptions.Countnull491 function TBuildMatrixOptions.Count: integer;
492 begin
493   Result:=fItems.Count;
494 end;
495 
TBuildMatrixOptions.IndexOfnull496 function TBuildMatrixOptions.IndexOf(Option: TBuildMatrixOption): integer;
497 begin
498   Result:=fItems.IndexOf(Option);
499 end;
500 
Addnull501 function TBuildMatrixOptions.Add(Typ: TBuildMatrixOptionType; Targets: string
502   ): TBuildMatrixOption;
503 begin
504   Result:=TBuildMatrixOption.Create(Self);
505   Result.Targets:=Targets;
506   Result.Typ:=Typ;
507 end;
508 
509 procedure TBuildMatrixOptions.Delete(Index: integer);
510 begin
511   Items[Index].Free;
512 end;
513 
514 procedure TBuildMatrixOptions.IncreaseChangeStep;
515 begin
516   CTIncreaseChangeStamp64(FChangeStep);
517   if Assigned(OnChanged) then
518     OnChanged(Self);
519 end;
520 
Equalsnull521 function TBuildMatrixOptions.Equals(Obj: TObject): boolean;
522 var
523   Src: TBuildMatrixOptions;
524   i: Integer;
525 begin
526   Result:=false;
527   if Self=Obj then exit;
528   if not (Obj is TBuildMatrixOptions) then exit;
529   Src:=TBuildMatrixOptions(Obj);
530   if Src.Count<>Count then exit;
531   for i:=0 to Count-1 do
532     if not Src[i].Equals(Items[i]) then exit;
533   Result:=true;
534 end;
535 
536 procedure TBuildMatrixOptions.LoadFromConfig(Cfg: TConfigStorage);
537 var
538   Cnt: Integer;
539   i: Integer;
540   Option: TBuildMatrixOption;
541 begin
542   Clear;
543   Cnt:=Cfg.GetValue('Count',0);
544   for i:=1 to Cnt do begin
545     Option:=TBuildMatrixOption.Create(Self);
546     Cfg.AppendBasePath('Item'+IntToStr(i));
547     Option.LoadFromConfig(Cfg);
548     Cfg.UndoAppendBasePath;
549   end;
550 end;
551 
552 procedure TBuildMatrixOptions.SaveToConfig(Cfg: TConfigStorage;
553   const SaveMode: TStrToBoolEvent);
554 var
555   i: Integer;
556 begin
557   Cfg.SetDeleteValue('Count',Count,0);
558   for i:=0 to Count-1 do begin
559     Cfg.AppendBasePath('Item'+IntToStr(i+1));
560     Items[i].SaveToConfig(Cfg,SaveMode);
561     Cfg.UndoAppendBasePath;
562   end;
563 end;
564 
565 procedure TBuildMatrixOptions.LoadFromXMLConfig(Cfg: TXMLConfig;
566   const aPath: string);
567 var
568   Cnt: Integer;
569   i: Integer;
570   Option: TBuildMatrixOption;
571 begin
572   Clear;
573   Cnt:=Cfg.GetValue(aPath+'Count',0);
574   //debugln(['TBuildMatrixOptions.LoadFromXMLConfig Cnt=',Cnt]);
575   for i:=1 to Cnt do begin
576     Option:=TBuildMatrixOption.Create(Self);
577     Option.LoadFromXMLConfig(Cfg,aPath+'Item'+IntToStr(i)+'/');
578   end;
579   //debugln(['TBuildMatrixOptions.LoadFromXMLConfig Count=',Count]);
580 end;
581 
582 procedure TBuildMatrixOptions.SaveToXMLConfig(Cfg: TXMLConfig;
583   const aPath: string; const SaveMode: TStrToBoolEvent);
584 var
585   i: Integer;
586 begin
587   //debugln(['TBuildMatrixOptions.SaveToXMLConfig ',aPath]);
588   Cfg.SetDeleteValue(aPath+'Count',Count,0);
589   for i:=0 to Count-1 do
590     Items[i].SaveToXMLConfig(Cfg,aPath+'Item'+IntToStr(i+1)+'/',SaveMode);
591 end;
592 
SaveAtOldXMLConfignull593 function TBuildMatrixOptions.SaveAtOldXMLConfig(Cfg: TXMLConfig;
594   const Path, ModeIdent: string): integer;
595 var
596   i: Integer;
597   MatrixOption: TBuildMatrixOption;
598   SubPath: String;
599 begin
600   Result:=0;
601   for i:=0 to Count-1 do
602   begin
603     MatrixOption:=Items[i];
604     if (MatrixOption.Typ=bmotIDEMacro)
605     and MatrixOption.FitsTarget(BuildMatrixProjectName)
606     and MatrixOption.FitsMode(ModeIdent) then
607     begin
608       inc(Result);
609       SubPath:=Path+'Macro'+IntToStr(i+1)+'/';
610       Cfg.SetDeleteValue(SubPath+'Name',MatrixOption.MacroName,'');
611       Cfg.SetDeleteValue(SubPath+'Value',MatrixOption.Value,'');
612     end;
613   end;
614 end;
615 
616 procedure TBuildMatrixOptions.SaveSessionEnabled(Cfg: TXMLConfig;
617   const Path, ModeIdent: string; var Cnt: integer);
618 var
619   MatrixOption: TBuildMatrixOption;
620   SubPath: String;
621   i: Integer;
622 begin
623   for i:=0 to Count-1 do begin
624     MatrixOption:=Items[i];
625     //debugln(['SaveSessionEnabled ',MatrixOption.AsString]);
626     if not MatrixOption.FitsMode(ModeIdent) then continue;
627     inc(Cnt);
628     SubPath:=Path+'Item'+IntToStr(Cnt)+'/';
629     //debugln(['SaveSessionEnabled ModeID="',CurMode.Identifier,'" OptionID="',MatrixOption.ID,'" ',MatrixOption.AsString]);
630     Cfg.SetDeleteValue(SubPath+'Mode',ModeIdent,'');
631     Cfg.SetDeleteValue(SubPath+'Option',MatrixOption.ID,'');
632   end;
633 end;
634 
635 procedure TBuildMatrixOptions.AppendCustomOptions(Target, ActiveMode: string;
636   var Options: string);
637 var
638   i: Integer;
639   Option: TBuildMatrixOption;
640   Value: String;
641 begin
642   for i:=0 to Count-1 do begin
643     Option:=Items[i];
644     if Option.Typ<>bmotCustom then continue;
645     Value:=Trim(Option.Value);
646     if Value='' then continue;
647     if not Option.FitsTarget(Target) then continue;
648     if not Option.FitsMode(ActiveMode) then continue;
649     if Options<>'' then Options+=' ';
650     Options+=Value;
651   end;
652 end;
653 
654 procedure TBuildMatrixOptions.GetOutputDirectory(Target, ActiveMode: string;
655   var OutDir: string);
656 var
657   i: Integer;
658   Option: TBuildMatrixOption;
659 begin
660   for i:=0 to Count-1 do begin
661     Option:=Items[i];
662     if Option.Typ<>bmotOutDir then continue;
663     if not Option.FitsTarget(Target) then continue;
664     if not Option.FitsMode(ActiveMode) then continue;
665     OutDir:=Option.Value;
666   end;
667 end;
668 
TBuildMatrixOptions.FindOptionnull669 function TBuildMatrixOptions.FindOption(const ID: string): TBuildMatrixOption;
670 var
671   i: Integer;
672 begin
673   for i:=0 to Count-1 do begin
674     Result:=Items[i];
675     if Result.ID=ID then exit;
676   end;
677   Result:=nil;
678 end;
679 
TBuildMatrixOptions.FindMacronull680 function TBuildMatrixOptions.FindMacro(const MacroName, MacroValue: string): TBuildMatrixOption;
681 var
682   i: Integer;
683 begin
684   i:=Count-1;
685   while i>=0 do
686   begin
687     Result:=Items[i];
688     if (Result.Typ=bmotIDEMacro)
689     and (Result.Targets='*')
690     and (Result.MacroName=MacroName)
691     and (Result.Value=MacroValue)
692     then
693       exit;
694     dec(i);
695   end;
696   Result:=nil;
697 end;
698 
699 procedure TBuildMatrixOptions.EnableModeIfOptionFound(ModeID, OptionID: String);
700 var
701   Opt: TBuildMatrixOption;
702 begin
703   Opt:=FindOption(OptionID);
704   if Assigned(Opt) then
705     Opt.EnableMode(ModeID);
706 end;
707 
708 { TBuildMatrixOption }
709 
710 procedure TBuildMatrixOption.SetMacroName(AValue: string);
711 begin
712   if FMacroName=AValue then Exit;
713   FMacroName:=AValue;
714   List.IncreaseChangeStep;
715 end;
716 
717 procedure TBuildMatrixOption.SetModes(AValue: string);
718 begin
719   if FModes=AValue then exit;
720   FModes:=AValue;
721   List.IncreaseChangeStep;
722 end;
723 
724 procedure TBuildMatrixOption.SetTargets(AValue: string);
725 begin
726   if FTargets=AValue then Exit;
727   FTargets:=AValue;
728   List.IncreaseChangeStep;
729 end;
730 
731 procedure TBuildMatrixOption.SetTyp(AValue: TBuildMatrixOptionType);
732 begin
733   if FTyp=AValue then Exit;
734   FTyp:=AValue;
735   List.IncreaseChangeStep;
736 end;
737 
738 procedure TBuildMatrixOption.SetValue(AValue: string);
739 begin
740   if FValue=AValue then Exit;
741   FValue:=AValue;
742   List.IncreaseChangeStep;
743 end;
744 
745 procedure TBuildMatrixOption.Assign(Source: TPersistent);
746 var
747   aSource: TBuildMatrixOption;
748 begin
749   if Source is TBuildMatrixOption then
750   begin
751     aSource:=TBuildMatrixOption(Source);
752     ID:=aSource.ID;
753     Targets:=aSource.Targets;
754     Modes:=aSource.Modes;
755     Typ:=aSource.Typ;
756     MacroName:=aSource.MacroName;
757     Value:=aSource.Value;
758   end else
759     inherited Assign(Source);
760 end;
761 
762 constructor TBuildMatrixOption.Create(aList: TBuildMatrixOptions);
763 begin
764   FID:=CreateBuildMatrixOptionGUID;
765   FList:=aList;
766   if List<>nil then
767     List.fItems.Add(Self);
768 end;
769 
770 destructor TBuildMatrixOption.Destroy;
771 begin
772   List.fItems.Remove(Self);
773   FList:=nil;
774   inherited Destroy;
775 end;
776 
FitsTargetnull777 function TBuildMatrixOption.FitsTarget(const Target: string): boolean;
778 begin
779   Result:=BuildMatrixTargetFits(Target,Targets);
780 end;
781 
TBuildMatrixOption.FitsModenull782 function TBuildMatrixOption.FitsMode(const Mode: string): boolean;
783 begin
784   Result:=BuildMatrixModeFits(Mode,Modes);
785 end;
786 
TBuildMatrixOption.Equalsnull787 function TBuildMatrixOption.Equals(Obj: TObject): boolean;
788 var
789   Src: TBuildMatrixOption;
790 begin
791   Result:=false;
792   if Obj=Self then exit;
793   if not (Obj is TBuildMatrixOption) then exit;
794   Src:=TBuildMatrixOption(Obj);
795   if Src.Targets<>Targets then exit;
796   if Src.Modes<>Modes then exit;
797   if Src.Typ<>Typ then exit;
798   if Src.MacroName<>MacroName then exit;
799   if Src.Value<>Value then exit;
800   Result:=true;
801 end;
802 
GetModesSeparatedByCommanull803 function TBuildMatrixOption.GetModesSeparatedByComma(
804   const SaveModes: TStrToBoolEvent): string;
805 var
806   p, StartP: PChar;
807   l: SizeInt;
808   CurMode: string;
809   i: Integer;
810 begin
811   Result:='';
812   if Modes='' then exit;
813   p:=PChar(Modes);
814   while p^<>#0 do begin
815     StartP:=p;
816     while not (p^ in [#0,#10,#13]) do inc(p);
817     l:=p-StartP;
818     while p^ in [#10,#13] do inc(p);
819     if l=0 then continue; // skip empty strings
820     SetLength(CurMode,l);
821     System.Move(StartP^,CurMode[1],l);
822     if Assigned(SaveModes) and not SaveModes(CurMode) then continue;
823     // convert a single comma to double comma
824     for i:=length(CurMode) downto 1 do
825       if CurMode[i]=',' then
826         System.Insert(',',CurMode,i);
827     if Result<>'' then
828       Result+=',';
829     Result+=CurMode;
830   end;
831   //debugln(['TBuildMatrixOption.GetModesSeparatedByComma ',dbgstr(Modes),' -> ',dbgstr(Result)]);
832 end;
833 
834 procedure TBuildMatrixOption.SetModesFromCommaSeparatedList(aList: string);
835 var
836   p: Integer;
837 begin
838   //debugln(['TBuildMatrixOption.SetModesFromCommaSeparatedList START aList=',aList]);
839   p:=1;
840   while p<=length(aList) do begin
841     if aList[p]=',' then begin
842       if (p<length(aList)) and (aList[p+1]=',') then begin
843         // double comma is normal character = single comma
844         system.Delete(aList,p,1);
845         inc(p);
846       end else begin
847         // single comma is separator
848         ReplaceSubstring(aList,p,1,LineEnding);
849         inc(p,length(LineEnding));
850       end;
851     end else begin
852       inc(p);
853     end;
854   end;
855   Modes:=aList;
856   //debugln(['TBuildMatrixOption.SetModesFromCommaSeparatedList END Modes=',dbgstr(Modes)]);
857 end;
858 
859 procedure TBuildMatrixOption.DisableModes(const DisableModeEvent: TStrToBoolEvent);
860 var
861   CurModes: String;
862   p: PChar;
863   StartP: PChar;
864   CurMode: String;
865   StartPos: integer;
866 begin
867   CurModes:=Modes;
868   p:=PChar(CurModes);
869   while p^<>#0 do begin
870     StartP:=p;
871     while not (p^ in [#0,#10,#13]) do inc(p);
872     StartPos:=StartP-PChar(CurModes)+1;
873     CurMode:=copy(CurModes,StartPos,p-StartP);
874     while p^ in [#10,#13] do inc(p);
875     if DisableModeEvent(CurMode) then begin
876       System.Delete(CurModes,StartPos,p-StartP);
877       p:=Pointer(CurModes)+StartPos-1;
878     end;
879   end;
880   Modes:=CurModes;
881 end;
882 
883 procedure TBuildMatrixOption.EnableMode(aMode: string);
884 begin
885   if FitsMode(aMode) then exit;
886   if Modes<>'' then
887     aMode:=LineEnding+aMode;
888   Modes:=Modes+aMode;
889 end;
890 
891 procedure TBuildMatrixOption.RenameMode(const OldMode, NewMode: string);
892 var
893   CurModes: String;
894   p: PChar;
895   StartP: PChar;
896   StartPos: SizeInt;
897   CurMode: String;
898 begin
899   CurModes:=Modes;
900   p:=PChar(CurModes);
901   while p^<>#0 do begin
902     StartP:=p;
903     while not (p^ in [#0,#10,#13]) do inc(p);
904     StartPos:=StartP-PChar(CurModes)+1;
905     CurMode:=copy(CurModes,StartPos,p-StartP);
906     if CompareText(CurMode,OldMode)=0 then begin
907       ReplaceSubstring(CurModes,StartPos,p-StartP,NewMode);
908       p:=Pointer(CurModes)+StartPos-1+length(NewMode);
909     end;
910     while p^ in [#10,#13] do inc(p);
911   end;
912   Modes:=CurModes;
913 end;
914 
915 procedure TBuildMatrixOption.LoadFromConfig(Cfg: TConfigStorage);
916 begin
917   ID:=Cfg.GetValue('ID','');
918   if ID='' then ID:=CreateBuildMatrixOptionGUID;
919   Targets:=Cfg.GetValue('Targets','*');
920   SetModesFromCommaSeparatedList(Cfg.GetValue('Modes',''));
921   Typ:=Str2BuildMatrixOptionType(Cfg.GetValue('Type',''));
922   MacroName:=Cfg.GetValue('MacroName','');
923   Value:=Cfg.GetValue('Value','');
924 end;
925 
926 procedure TBuildMatrixOption.SaveToConfig(Cfg: TConfigStorage;
927   const SaveModes: TStrToBoolEvent);
928 begin
929   Cfg.SetDeleteValue('ID',ID,'');
930   Cfg.SetDeleteValue('Targets',Targets,'*');
931   Cfg.SetDeleteValue('Modes',GetModesSeparatedByComma(SaveModes),'');
932   Cfg.SetDeleteValue('Type',BuildMatrixOptionTypeNames[Typ],BuildMatrixOptionTypeNames[bmotCustom]);
933   Cfg.SetDeleteValue('MacroName',MacroName,'');
934   Cfg.SetDeleteValue('Value',Value,'');
935 end;
936 
937 procedure TBuildMatrixOption.LoadFromXMLConfig(Cfg: TXMLConfig;
938   const aPath: string);
939 begin
940   ID:=Cfg.GetValue(aPath+'ID','');
941   if ID='' then ID:=CreateBuildMatrixOptionGUID;
942   Targets:=Cfg.GetValue(aPath+'Targets','*');
943   SetModesFromCommaSeparatedList(Cfg.GetValue(aPath+'Modes',''));
944   Typ:=Str2BuildMatrixOptionType(Cfg.GetValue(aPath+'Type',''));
945   MacroName:=Cfg.GetValue(aPath+'MacroName','');
946   Value:=Cfg.GetValue(aPath+'Value','');
947 end;
948 
949 procedure TBuildMatrixOption.SaveToXMLConfig(Cfg: TXMLConfig;
950   const aPath: string; const SaveModes: TStrToBoolEvent);
951 begin
952   Cfg.SetDeleteValue(aPath+'ID',ID,'');
953   Cfg.SetDeleteValue(aPath+'Targets',Targets,'*');
954   Cfg.SetDeleteValue(aPath+'Modes',GetModesSeparatedByComma(SaveModes),'');
955   Cfg.SetDeleteValue(aPath+'Type',BuildMatrixOptionTypeNames[Typ],BuildMatrixOptionTypeNames[bmotCustom]);
956   Cfg.SetDeleteValue(aPath+'MacroName',MacroName,'');
957   Cfg.SetDeleteValue(aPath+'Value',Value,'');
958 end;
959 
AsStringnull960 function TBuildMatrixOption.AsString: string;
961 begin
962   Result:='ID="'+ID+'" '+BuildMatrixOptionTypeNames[Typ]
963     +' Value="'+Value+'"'
964     +' Modes="'+dbgstr(Modes)+'"';
965 end;
966 
967 end.
968 
969