summaryrefslogtreecommitdiff
path: root/fpcsrc/packages/fppkg/src/pkghandler.pp
blob: d407b579550a16c3dfd7c0867a81f8f49b335974 (plain)
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
{$mode objfpc}
{$h+}
unit pkghandler;

{$IFDEF OS2}
 {$DEFINE NO_UNIT_PROCESS}
{$ENDIF OS2}

{$IFDEF GO32V2}
 {$DEFINE NO_UNIT_PROCESS}
{$ENDIF GO32V2}

{$ifndef NO_UNIT_PROCESS}
  {$define HAS_UNIT_PROCESS}
{$endif NO_UNIT_PROCESS}

interface

uses
  Classes,SysUtils,
  pkgglobals,
  pkgoptions,
{$ifdef HAS_UNIT_PROCESS}
  process,
{$endif HAS_UNIT_PROCESS}
  fprepos;

type
  { TPackageHandler }

  TPackageHandler = Class(TComponent)
  private
    FPackageName : string;
  Protected
    Procedure Log(Level: TLogLevel;Msg : String);
    Procedure Log(Level: TLogLevel;Fmt : String; const Args : array of const);
    Procedure Error(Msg : String);
    Procedure Error(Fmt : String; const Args : array of const);
    Function ExecuteProcess(Const Prog,Args:String):Integer;
    Procedure SetCurrentDir(Const ADir:String);
  Public
    Constructor Create(AOwner:TComponent;const APackageName:string); virtual;
    function PackageLogPrefix:String;
    procedure ExecuteAction(const APackageName,AAction:string);
    procedure Execute; virtual; abstract;
    Property PackageName:string Read FPackageName;
  end;
  TPackageHandlerClass = class of TPackageHandler;

  EPackageHandler = Class(Exception);

// Actions/PkgHandler
procedure RegisterPkgHandler(const AAction:string;pkghandlerclass:TPackageHandlerClass);
function GetPkgHandler(const AAction:string):TPackageHandlerClass;
procedure ExecuteAction(const APackageName,AAction:string);

function PackageBuildPath(APackage:TFPPackage):String;
function PackageRemoteArchive(APackage:TFPPackage): String;
function PackageLocalArchive(APackage:TFPPackage): String;
function PackageManifestFile(APackage:TFPPackage): String;


Implementation

uses
  typinfo,
  contnrs,
  uriparser,
  pkgrepos,
  pkgmessages;

var
  PkgHandlerList  : TFPHashList;
  ExecutedActions : TFPHashList;
  CurrentDir      : string;

procedure RegisterPkgHandler(const AAction:string;pkghandlerclass:TPackageHandlerClass);
begin
  if PkgHandlerList.Find(AAction)<>nil then
    begin
      Raise EPackageHandler.CreateFmt(SErrActionAlreadyRegistered,[AAction]);
      exit;
    end;
  PkgHandlerList.Add(AAction,pkghandlerclass);
end;


function GetPkgHandler(const AAction:string):TPackageHandlerClass;
begin
  result:=TPackageHandlerClass(PkgHandlerList.Find(AAction));
  if result=nil then
    Raise EPackageHandler.CreateFmt(SErrActionNotFound,[AAction]);
end;


procedure ExecuteAction(const APackageName,AAction:string);
var
  pkghandlerclass : TPackageHandlerClass;
  FullActionName : string;
begin
  // Check if we have already executed or are executing the action
  FullActionName:=APackageName+AAction;
  if ExecutedActions.Find(FullActionName)<>nil then
    begin
      Log(llDebug,'Already executed or executing action '+FullActionName);
      exit;
    end;

  ExecutedActions.Add(FullActionName,Pointer(PtrUInt(1)));

  // Create action handler class
  pkghandlerclass:=GetPkgHandler(AAction);
  With pkghandlerclass.Create(nil,APackageName) do
    try
      Log(llDebug,SLogRunAction+' start',[AAction]);
      Execute;
      Log(llDebug,SLogRunAction+' end',[AAction]);
    finally
      Free;
    end;
end;


function PackageBuildPath(APackage:TFPPackage):String;
begin
  if APackage.Name=CurrentDirPackageName then
    begin
      // It could be that to resolve some dependencies, the current directory changes. The first time
      // PackageBuildPath is called the dependencies are not resolved yet, so store the current directory
      // for later calls.
      if CurrentDir='' then
        begin
          Result:='.';
          CurrentDir := SysUtils.GetCurrentDir;
        end
      else
        Result:=CurrentDir;
    end
  else if APackage.Name=CmdLinePackageName then
    Result:=GlobalOptions.BuildDir+ChangeFileExt(ExtractFileName(APackage.LocalFileName),'')
  else if (APackage.RecompileBroken) and (APackage.SourcePath<>'') then
    Result:=APackage.SourcePath
  else
    Result:=GlobalOptions.BuildDir+APackage.Name;
end;


function PackageRemoteArchive(APackage:TFPPackage): String;
begin
  if APackage.Name=CurrentDirPackageName then
    Error(SErrNoPackageSpecified)
  else if APackage.Name=CmdLinePackageName then
    Error(SErrPackageIsLocal);
  if APackage.DownloadURL<>'' then
    Result:=APackage.DownloadURL
  else
    Result:=GetRemoteRepositoryURL(APackage.FileName);
end;


function PackageLocalArchive(APackage:TFPPackage): String;
begin
  if APackage.Name=CurrentDirPackageName then
    Error(SErrNoPackageSpecified)
  else if APackage.Name=CmdLinePackageName then
    Result:=APackage.LocalFileName
  else
    Result:=GlobalOptions.ArchivesDir+APackage.FileName;
end;


function PackageManifestFile(APackage:TFPPackage): String;
begin
  Result:=ManifestFileName;
end;



{ TPackageHandler }

constructor TPackageHandler.Create(AOwner:TComponent;const APackageName:string);
begin
  inherited Create(AOwner);
  FPackageName:=APackageName;
end;

{$ifdef HAS_UNIT_PROCESS}
function ExecuteFPC(const Path: string; const ComLine: string): integer;
var
  P: TProcess;
  ConsoleOutput: TMemoryStream;
  BytesRead: longint;

  function ReadFromStream: longint;

  const
    READ_BYTES = 2048;

  var
    n: longint;
    BuffPos: longint;
    sLine: string;
    ch: char;
  begin
    // make sure we have room
    ConsoleOutput.SetSize(BytesRead + READ_BYTES);

    // try reading it
    n := P.Output.Read((ConsoleOutput.Memory + BytesRead)^, READ_BYTES);
    if n > 0 then
    begin
      Inc(BytesRead, n);

      sLine := '';
      BuffPos := ConsoleOutput.Position;

      //read lines from the stream
      repeat
        ConsoleOutput.Read(ch,1);

        if ch in [#10, #13] then
        begin
          log(llProgres,sLine);
          sLine := '';
          BuffPos := ConsoleOutput.Position;
        end
        else
          sLine := sLine + ch;

      until ConsoleOutput.Position >= BytesRead;

      ConsoleOutput.Position := BuffPos;
    end
    else
    begin
      // no data, wait 100 ms
      Sleep(100);
    end;

    Result := n;
  end;

begin
  result := -1;
  BytesRead := 0;
  ConsoleOutput := TMemoryStream.Create;
  try
    P := TProcess.Create(nil);
    try
      P.CommandLine := Path + ' ' + ComLine;
      P.Options := [poUsePipes];
      P.Execute;
      while P.Running do
        ReadFromStream;

      // read last part
      repeat
      until ReadFromStream = 0;
      ConsoleOutput.SetSize(BytesRead);

      result := P.ExitStatus;
    finally
      P.Free;
    end;
  finally
    ConsoleOutput.Free;
  end;
end;
{$endif HAS_UNIT_PROCESS}

Function TPackageHandler.ExecuteProcess(Const Prog,Args:String):Integer;
begin
  Log(llCommands,SLogExecute,[Prog,Args]);
  Flush(StdOut);
{$ifdef HAS_UNIT_PROCESS}
  Result:=ExecuteFPC(Prog,Args);
{$else HAS_UNIT_PROCESS}
  Result:=SysUtils.ExecuteProcess(Prog,Args);
{$endif HAS_UNIT_PROCESS}
end;


Procedure TPackageHandler.SetCurrentDir(Const ADir:String);
begin
  Log(llCommands,SLogChangeDir,[ADir]);
  if not SysUtils.SetCurrentDir(ADir) then
    Error(SErrChangeDirFailed,[ADir]);
end;


function TPackageHandler.PackageLogPrefix:String;
begin
  if PackageName<>'' then
    Result:='['+PackageName+'] '
  else
    Result:='';
end;


procedure TPackageHandler.ExecuteAction(const APackageName,AAction:string);
begin
  // Needed to override TComponent.ExecuteAction method
  pkghandler.ExecuteAction(APackageName,AAction);
end;


Procedure TPackageHandler.Log(Level:TLogLevel; Msg:String);
begin
  pkgglobals.Log(Level,PackageLogPrefix+Msg);
end;


Procedure TPackageHandler.Log(Level:TLogLevel; Fmt:String; const Args:array of const);
begin
  pkgglobals.log(Level,PackageLogPrefix+Fmt,Args);
end;


Procedure TPackageHandler.Error(Msg:String);
begin
  pkgglobals.Error(PackageLogPrefix+Msg);
end;


Procedure TPackageHandler.Error(Fmt:String; const Args:array of const);
begin
  pkgglobals.Error(PackageLogPrefix+Fmt,Args);
end;


initialization
  PkgHandlerList:=TFPHashList.Create;
  ExecutedActions:=TFPHashList.Create;
finalization
  FreeAndNil(PkgHandlerList);
  FreeAndNil(ExecutedActions);
end.