File: sourcecloser.lpr

package info (click to toggle)
lazarus 2.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 214,460 kB
  • sloc: pascal: 1,862,622; xml: 265,709; cpp: 56,595; sh: 3,008; java: 609; makefile: 535; perl: 297; sql: 222; ansic: 137
file content (624 lines) | stat: -rw-r--r-- 20,276 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
{ Command line utility to create closed source Lazarus packages.
  Run with -h to see help

  Copyright (C) 2013 Mattias Gaertner mattias@freepascal.org

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version with the following modification:

  As a special exception, the copyright holders of this library give you
  permission to link this library with independent modules to produce an
  executable, regardless of the license terms of these independent modules,and
  to copy and distribute the resulting executable under terms of your choice,
  provided that you also meet, for each linked independent module, the terms
  and conditions of the license of that module. An independent module is a
  module which is not derived from or based on this library. If you modify
  this library, you may extend this exception to your version of the library,
  but you are not obligated to do so. If you do not wish to do so, delete this
  exception statement from your version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
program sourcecloser;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, CustApp,
  // LazUtils
  AvgLvlTree, LazLogger, LazFileUtils, Laz2_XMLCfg, LazUTF8,
  // CodeTools
  FileProcs, BasicCodeTools, CodeToolManager, CodeCache, SourceChanger,
  CodeTree, DefineTemplates;

type

  { TTarget }

  TTarget = class
  public
    Names, Values: array of string;
    procedure Add(Name, Value: string);
    function AsString: string;
  end;

  { TSourceCloser }

  TSourceCloser = class(TCustomApplication)
  private
    FClosedSrcError: TStringList;
    FCompilerOptions: string;
    FDefines: TStringToStringTree;
    FDisableCompile: boolean;
    FIncludePath: string;
    FLPKFilenames: TStrings;
    FRemovePrivateSections: boolean;
    FUndefines: TStringToStringTree;
    FUnitFilenames: TStrings;
    FVerbosity: integer;
    fDefinesApplied: boolean;
    procedure DeleteNode(Tool: TCodeTool; Node: TCodeTreeNode;
      const StartPos, EndPos: Integer;
      Changer: TSourceChangeCache; AddEmptyLine: boolean);
  protected
    procedure DoRun; override;
    procedure ApplyDefines;
    procedure ConvertLPK(LPKFilename: string);
    procedure ConvertUnit(UnitFilename: string);
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure WriteHelp; virtual;
    property Verbosity: integer read FVerbosity write FVerbosity;
    property Defines: TStringToStringTree read FDefines;
    property Undefines: TStringToStringTree read FUndefines;
    property IncludePath: string read FIncludePath write FIncludePath;
    property LPKFilenames: TStrings read FLPKFilenames;
    property UnitFilenames: TStrings read FUnitFilenames;
    property CompilerOptions: string read FCompilerOptions write FCompilerOptions;
    property RemovePrivateSections: boolean read FRemovePrivateSections write FRemovePrivateSections;
    property ClosedSrcError: TStringList read FClosedSrcError write FClosedSrcError;
    property DisableCompile: boolean read FDisableCompile write FDisableCompile;
  end;

function IndexOfFilename(List: TStrings; Filename: string): integer;
begin
  Result:=List.Count-1;
  while (Result>=0) and (CompareFilenames(List[Result],Filename)<>0) do
    dec(Result);
end;

{ TTarget }

procedure TTarget.Add(Name, Value: string);
var
  Cnt: Integer;
begin
  Cnt:=length(Names);
  SetLength(Names,Cnt+1);
  Names[Cnt]:=Name;
  SetLength(Values,Cnt+1);
  Values[Cnt]:=Value;
end;

function TTarget.AsString: string;
var
  i: Integer;
begin
  Result:='';
  for i:=0 to length(Names)-1 do begin
    if Result<>'' then Result+=',';
    Result+=Names[i]+'='+Values[i];
  end;
end;

{ TSourceCloser }

procedure TSourceCloser.DeleteNode(Tool: TCodeTool; Node: TCodeTreeNode;
  const StartPos, EndPos: Integer; Changer: TSourceChangeCache;
  AddEmptyLine: boolean);

  procedure E(Msg: string);
  begin
    writeln('ERROR: '+Msg);
    Halt(1);
  end;

var
  EndCodePos: TCodePosition;
  StartCodePos: TCodePosition;
  s: String;
begin
  //debugln(['TSourceCloser.DeleteNode ',Node.DescAsString,' "',dbgstr(Tool.Src,StartPos,EndPos-StartPos),'"']);
  if not Tool.CleanPosToCodePos(StartPos, StartCodePos) then
    E('unable to delete '+Node.DescAsString+' of "'+Tool.MainFilename+'" (invalid startpos '
      +Tool.CleanPosToStr(StartPos, true)+')');
  if not Tool.CleanPosToCodePos(EndPos, EndCodePos) then
    E('unable to delete '+Node.DescAsString+' of "'+Tool.MainFilename+'" (invalid endpos '+
      Tool.CleanPosToStr(EndPos, true)+')');
  if StartCodePos.Code<>EndCodePos.Code then
    E('unable to delete '+Node.DescAsString+' of "'+Tool.MainFilename+'" from '+
      Tool.CleanPosToStr(StartPos, true)+' to '+Tool.CleanPosToStr(EndPos, true)
      );
  s:='';
  if AddEmptyLine then
    s:=LineEnding;
  if not Changer.ReplaceEx(gtNone, gtNone, 0, 0, StartCodePos.Code,
    StartCodePos.P, EndCodePos.P, s) then
    E('unable to delete '+Node.DescAsString+' of "'+Tool.MainFilename+'"');
end;

procedure TSourceCloser.DoRun;
const
  ShortOpts = 'hvqc:pd:u:i:ke:';
  LongOpts = 'help verbose quiet compileroptions: disablecompile define: undefine: includepath: keepprivate error:';
var
  ErrMsgIsDefault: Boolean;

  procedure E(Msg: string; WithHelp: boolean = false);
  begin
    DebugLn(['ERROR: ',Msg]);
    if WithHelp then
      WriteHelp;
    Terminate;
    Halt(1);
  end;

  procedure ParseValueParam(ShortOpt: char; Value: string);
  begin
    case ShortOpt of
    'c':
      begin
        FCompilerOptions+=Value;
      end;
    'e':
      begin
        if ErrMsgIsDefault then begin
          ErrMsgIsDefault:=false;
          FClosedSrcError.Clear;
        end;
        Value:=UTF8Trim(Value,[]);
        if Value='' then exit;
        FClosedSrcError.Add(Value);
      end;
    'i':
      begin
        Value:=UTF8Trim(Value,[]);
        if Value='' then exit;
        if IncludePath<>'' then
          Value:=';'+Value;
        fIncludePath+=Value;
      end;
    'd':
      begin
        if not IsValidIdent(Value) then
          E('invalid define:'+Value);
        Defines[Value]:='';
      end;
    'u':
      begin
        if not IsValidIdent(Value) then
          E('invalid define:'+Value);
        Defines[Value]:='';
      end;
    else
      E('invalid option "'+ShortOpt+'"');
    end;
  end;

  procedure AddFile(aFilename: string);
  var
    Ext: String;
  begin
    debugln(['AddFile ',aFilename]);
    Ext:=lowercase(ExtractFileExt(aFilename));
    if Ext='.lpk' then begin
      if IndexOfFilename(LPKFilenames,aFilename)>=0 then
        E('duplicate lpk:'+aFilename); // duplicate lpk, compilation order is unclear => error
      LPKFilenames.Add(aFilename);
    end
    else if FilenameIsPascalUnit(aFilename) then begin
      if IndexOfFilename(UnitFilenames,aFilename)>=0 then
        exit; // duplicate unit is ok => ignore
      UnitFilenames.Add(aFilename);
    end else
      E('only lpk and units are supported, invalid file:'+aFilename);
  end;

  procedure AddFiles(Pattern: string);
  var
    Info: TSearchRec;
  begin
    if FindFirstUTF8(Pattern,faAnyFile,Info)=0 then begin
      repeat
        if (Info.Name='.') or (Info.Name='..') then continue;
        if (faDirectory and Info.Attr)>0 then continue;
        AddFile(ExtractFilePath(Pattern)+Info.Name);
      until FindNextUTF8(Info)<>0;
    end;
    FindCloseUTF8(Info);
  end;

var
  ErrorMsg: String;
  i: Integer;
  Param: String;
  S2SItem: PStringToStringItem;
  Filename: String;
  Option: string;
  p: SizeInt;
begin
  // quick check parameters
  ErrorMsg:=CheckOptions(ShortOpts,LongOpts);
  if ErrorMsg<>'' then
    E(ErrorMsg);

  // parse parameters
  if HasOption('h','help') then begin
    WriteHelp;
    Terminate;
    Exit;
  end;

  ErrMsgIsDefault:=true;
  i:=1;
  while i<=System.ParamCount do begin
    Param:=ParamStrUTF8(i);
    inc(i);
    if Param='' then continue;
    if (Param='-q') or (Param='--quiet') then
      dec(fVerbosity)
    else if (Param='-v') or (Param='--verbose') then
      inc(fVerbosity)
    else if (Param='-k') or (Param='--keepprivate') then
      RemovePrivateSections:=false
    else if (Param='-p') or (Param='--disablecompile') then
      DisableCompile:=true
    else if Param[1]<>'-' then begin
      Filename:=TrimAndExpandFilename(Param);
      if (Pos('*',ExtractFileName(Filename))>0) or (Pos('?',ExtractFileName(Filename))>0)
      then begin
        AddFiles(Filename);
      end else begin
        if not FileExistsUTF8(Filename) then
          E('file not found: '+Param);
        if DirPathExists(Filename) then
          E('file is a directory: '+Param);
        AddFile(Filename);
      end;
    end else if (length(Param)=2) and (Pos(Param[2]+':',ShortOpts)>0) then begin
      // e.g. -t <target>
      Option:=Param[2];
      if i>System.ParamCount then
        E('missing value for option: '+Param);
      Param:=ParamStrUTF8(i);
      inc(i);
      ParseValueParam(Option[1],Param);
    end else if (copy(Param,1,2)='--') then begin
      p:=Pos('=',Param);
      if p<1 then
        E('invalid long option syntax: '+Param);
      Option:=copy(Param,3,p-3);
      delete(Param,1,p);
      if Option='compileroptions' then Option:='c'
      else if Option='define' then Option:='d'
      else if Option='undefine' then Option:='u'
      else if Option='includepath' then Option:='i'
      else if Option='error' then Option:='e'
      else
        E('invalid long option');
      ParseValueParam(Option[1],Param);
    end else
      E('invalid option: '+Param);
  end;

  if Verbosity>0 then begin
    debugln(['Options:']);
    debugln(['Verbosity=',Verbosity]);
    debugln(['IncludePath=',IncludePath]);
    for S2SItem in Defines do
      debugln('Define:',S2SItem^.Name);
    for S2SItem in Undefines do
      debugln('Undefine:',S2SItem^.Name);
    for i:=0 to LPKFilenames.Count-1 do
      debugln(['LPK[',i+1,']:',LPKFilenames[i]]);
    for i:=0 to UnitFilenames.Count-1 do
      debugln(['Unit[',i+1,']:',UnitFilenames[i]]);
  end;
  if (LPKFilenames.Count=0) and (UnitFilenames.Count=0) then
    E('you must pass at least one lpk or pas file',true);

  for i:=0 to LPKFilenames.Count-1 do
    ConvertLPK(LPKFilenames[i]);
  for i:=0 to UnitFilenames.Count-1 do
    ConvertUnit(UnitFilenames[i]);

  // stop program loop
  Terminate;
end;

procedure TSourceCloser.ApplyDefines;
var
  IncPathTemplate: TDefineTemplate;
  S2SItem: PStringToStringItem;
  MacroName: String;
  DefTemplate: TDefineTemplate;
begin
  if fDefinesApplied then exit;
  fDefinesApplied:=true;

  CodeToolBoss.SimpleInit('codetools.cache');

  if IncludePath<>'' then begin
    IncPathTemplate:=TDefineTemplate.Create('IncPath',
      'extending include search path',
      IncludePathMacroName,  // variable name: #IncPath
      '$('+IncludePathMacroName+');'+IncludePath
      ,da_DefineRecurse
      );
    CodeToolBoss.DefineTree.Add(IncPathTemplate);
  end;
  for S2SItem in Defines do begin
    MacroName:=S2SItem^.Name;
    DefTemplate:=TDefineTemplate.Create('Define '+MacroName,
      'Define '+MacroName,
      MacroName,S2SItem^.Value,da_DefineRecurse);
    CodeToolBoss.DefineTree.Add(DefTemplate);
  end;
  for S2SItem in Undefines do begin
    MacroName:=S2SItem^.Name;
    DefTemplate:=TDefineTemplate.Create('Undefine '+MacroName,
      'Undefine '+MacroName,
      MacroName,'',da_UndefineRecurse);
    CodeToolBoss.DefineTree.Add(DefTemplate);
  end;
end;

procedure TSourceCloser.ConvertLPK(LPKFilename: string);
// set lpk to compile only manually
// add -Ur to compiler options
const
  OtherPath='Package/CompilerOptions/Other/';
  CustomOptionsPath=OtherPath+'CustomOptions/Value';
var
  xml: TXMLConfig;
  CustomOptions: String;
  NewOptions: String;
begin
  debugln(['Converting lpk: ',LPKFilename]);
  xml:=TXMLConfig.Create(LPKFilename);
  try
    // set lpk to compile only manually
    xml.SetValue('Package/AutoUpdate/Value','Manually');

    // add -Ur to compiler options
    CustomOptions:=xml.GetValue(CustomOptionsPath,'');
    NewOptions:=CustomOptions;
    if Pos('-Ur',NewOptions)<1 then begin
      if NewOptions<>'' then NewOptions+=' ';
      NewOptions+='-Ur';
    end;
    if FCompilerOptions<>'' then begin
      if NewOptions<>'' then NewOptions+=' ';
      NewOptions+=FCompilerOptions;
    end;
    xml.SetValue(CustomOptionsPath,NewOptions);

    // disable compile commands
    if DisableCompile then begin
      xml.SetValue(OtherPath+'CompilerPath/Value','');
      xml.SetDeleteValue(OtherPath+'ExecuteBefore/Command/Value','','');
      xml.SetDeleteValue(OtherPath+'ExecuteAfter/Command/Value','','');
      xml.SetDeleteValue(OtherPath+'CreateMakefileOnBuild/Value','','');
    end;

    // write
    xml.Flush;
  finally
    xml.Free;
  end;
end;

procedure TSourceCloser.ConvertUnit(UnitFilename: string);

  procedure E(Msg: string);
  begin
    writeln('ERROR: '+Msg);
    Halt(1);
  end;

var
  Code: TCodeBuffer;
  Changer: TSourceChangeCache;
  Tool: TCodeTool;
  Node: TCodeTreeNode;
  StartPos: Integer;
  EndPos: Integer;
  CodeList: TFPList;
  i: Integer;
  FromPos: Integer;
  ToPos: Integer;
  AddEmptyLine: Boolean;
  s: String;
begin
  debugln(['Converting unit: ',UnitFilename]);
  ApplyDefines;

  // load file
  Code:=CodeToolBoss.LoadFile(UnitFilename,true,false);
  if Code=nil then
    E('unable to read "'+UnitFilename+'"');
  // parse whole unit
  if (not CodeToolBoss.Explore(Code,Tool,false)) or (CodeToolBoss.ErrorMessage<>'') then
    E('parse error');
  if Tool.GetSourceType<>ctnUnit then
    E('not a unit, skipping "'+Code.Filename+'"');

  // init SourceChangeCache
  Changer:=CodeToolBoss.SourceChangeCache;
  Changer.MainScanner:=Tool.Scanner;

  // add errors, so that user cannot accidentally compile the unit
  s:='';
  for i:=0 to FClosedSrcError.Count-1 do begin
    s:=s+'{$Error '+FClosedSrcError[i]+'}'+LineEnding;
  end;
  if s<>'' then
    Changer.ReplaceEx(gtNone,gtNone,0,0,Code,1,1,s);

  if RemovePrivateSections then begin
    // delete private sections in the interface
    Node:=Tool.FindInterfaceNode;
    while Node<>nil do begin
      if Node.Desc=ctnClassPrivate then begin
        FromPos:=Tool.FindLineEndOrCodeInFrontOfPosition(Node.StartPos);
        ToPos:=Tool.FindLineEndOrCodeInFrontOfPosition(Node.EndPos);
        DeleteNode(Tool,Node,FromPos,ToPos,Changer,false);
        Node:=Node.NextSkipChilds;
      end else
        Node:=Node.Next;
      if Node=nil then break;
      if Node.Desc in [ctnImplementation,ctnInitialization,ctnFinalization] then
        break;
    end;
  end;

  // delete implementation, initialization and finalization section
  Node:=Tool.Tree.Root;
  while (Node<>nil) do begin
    if Node.Desc=ctnImplementation then begin
      // delete implementation section including the 'implementation' keyword
      StartPos:=Node.StartPos;
      EndPos:=Node.NextBrother.StartPos;
      DeleteNode(Tool, Node, StartPos, EndPos, Changer,false);
    end else if Node.Desc in [ctnInitialization,ctnFinalization] then begin
      // delete the content of the finalization and initialization section
      Tool.MoveCursorToNodeStart(Node);
      Tool.ReadNextAtom; // read 'initialization'
      StartPos:=Tool.CurPos.EndPos;
      EndPos:=Node.NextBrother.StartPos;
      AddEmptyLine:=true;
      if Trim(copy(Tool.Src,StartPos,EndPos-StartPos))='' then begin
        // empty initialization => delete keyword as well
        StartPos:=Node.StartPos;
        AddEmptyLine:=false;
      end else begin
        // initialization section is not empty
        // => keep the keyword, because it is needed by tools like 'Unused units'
      end;
      DeleteNode(Tool, Node, StartPos, EndPos, Changer, AddEmptyLine);
    end;
    Node:=Node.NextBrother;
  end;

  // apply changes and write changes to disk
  CodeList:=TFPList.Create;
  try
    for i:=0 to Changer.BuffersToModifyCount-1 do
      CodeList.Add(Changer.BuffersToModify[i]);
    if not Changer.Apply then
      E('unable to modify "'+UnitFilename+'"');
    for i:=0 to CodeList.Count-1 do begin
      Code:=TCodeBuffer(CodeList[i]);
      if not Code.Save then
        E('unable to write "'+Code.Filename+'"');
    end;
  finally
    CodeList.Free;
  end;
end;

constructor TSourceCloser.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  StopOnException:=True;
  fDefines:=TStringToStringTree.Create(false);
  FUndefines:=TStringToStringTree.Create(false);
  FLPKFilenames:=TStringList.Create;
  FUnitFilenames:=TStringList.Create;
  FRemovePrivateSections:=true;
  FClosedSrcError:=TStringList.Create;
  FClosedSrcError.Add('This is a closed source unit. You cannot compile it, it was already compiled.');
  FClosedSrcError.Add('Probably the IDE has cleaned up and you have to unpack the zip again.');
end;

destructor TSourceCloser.Destroy;
begin
  FreeAndNil(FClosedSrcError);
  FreeAndNil(FLPKFilenames);
  FreeAndNil(FUnitFilenames);
  FreeAndNil(FDefines);
  FreeAndNil(FUndefines);
  inherited Destroy;
end;

procedure TSourceCloser.WriteHelp;
var
  i: Integer;
begin
  writeln('Usage:');
  writeln('  ',ExeName,' -h');
  writeln('  ',ExeName,' [options] [unit1.pas unit2.pp ...] [pkg1.lpk pk2.lpk ..]');
  writeln;
  writeln('Description:');
  writeln('  This tool helps creating closed source Lazarus packages.');
  writeln;
  writeln('  If the file names contain * or ? it will be used as mask.');
  writeln('  If you pass a lpk file, this tool will set "compile manually"');
  writeln('  and appends "-Ur" to the compiler options.');
  writeln('  You can pass multiple lpk files and they will be edited in this order.');
  writeln('  If you pass a .pas or .pp file it will be treated as a pascal unit and');
  writeln('  will remove the implementation, initialization, finalization sections.');
  writeln;
  writeln('Options:');
  writeln('  -h, --help    : This help messages.');
  writeln('  -v, --verbose : be more verbose.');
  writeln('  -q, --quiet   : be more quiet.');
  writeln('Package/lpk options:');
  writeln('  --compileroptions=<compiler options>');
  writeln('          Add custom compiler options to lpk.');
  writeln('  -p, --disablecompile');
  writeln('          Remove all compile commands from lpk.');
  writeln('Unit options:');
  writeln('  -d <MacroName>, --define=<MacroName> :');
  writeln('          Define Free Pascal macro. Can be passed multiple times.');
  writeln('  -u <MacroName>, --undefine=<MacroName> :');
  writeln('          Undefine Free Pascal macro. Can be passed multiple times.');
  writeln('  -i <path>, --includepath=<path> :');
  writeln('         Append <path> to include search path. Can be passed multiple times.');
  writeln('  -k, --keepprivate');
  writeln('         Keep private sections in interface.');
  writeln('  -e <errormessage>, --error=<error message>');
  writeln('         Change the message of the error directive added to the units.');
  writeln('         You can add this multiple times to add multiple directives.');
  writeln('         The default error message is:');
  for i:=0 to FClosedSrcError.Count-1 do
    writeln('   ',FClosedSrcError[i]);
  writeln;
  writeln('Environment variables:');
  writeln('  PP            path to compiler,');
  writeln('                e.g. C:\lazarus\fpc\2.6.2\bin\i386-win32\fpc.exe');
  writeln('                The compiler is queried for the current defines for the');
  writeln('                target platform.');
  writeln('  FPCTARGET     target os, e.g. win32');
  writeln('  FPCTARGETCPU  target cpu, e.g. i386');
end;

var
  Application: TSourceCloser;
begin
  Application:=TSourceCloser.Create(nil);
  Application.Title:='SourceCloser';
  Application.Run;
  Application.Free;
end.