├── embdeploy.res ├── LICENSE ├── README.md ├── .gitignore ├── embdeploy.dpr ├── DeployChannels.pas ├── deployer.pas └── embdeploy.dproj /embdeploy.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vldgeorgiev/EmbDeploy/HEAD/embdeploy.res -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Vladimir Georgiev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EmbDeploy 2 | ========= 3 | 4 | http://vldgeorgiev.wordpress.com/2014/04/29/automated-deployment-of-a-delphi-osx-project-from-the-command-line/ 5 | 6 | (c) Vladimir Georgiev, 2013 7 | 8 | Automated deployer for Embarcadero RAD Studio projects. 9 | Uses the paclient.exe tool from Embarcadero to automate deploying projects to remote hosts from the command line. By default it is possible to deploy a project to OSX or another host only from the Delphi IDE, and not from a command line or script. This makes it harder to write automated build scripts. 10 | The "embdeploy" tool simplifies this by parsing the project and issuing commands to paclient.exe to deploy the project files. 11 | 12 | Embdeploy also has to option to execute custom commands on the remote host, e.g. "copy", "rm", "chmod", "codesign", etc. The command has to be enclosed in double quotes and can contain additional single quotes inside. The inside quotes might have to be escaped, depending on how you call Embdeploy. 13 | The command is executed from the folder above the remote project root. E.g. if the project is for OSX and called "myproject" it will be deployed to "/myproject.app" and the command executed from the . 14 | There is a parameter $PROOT that can be used inside commands and is replaced with the name of the project folder, which is "myproject.app" in the case above. 15 | 16 | There is also an option to produce a ZIP archive of the deployment files. It is useful for creating a ZIP of an OSX project ".app" bundle without deploying it to OSX first. This makes it easier to use an automated build script and upload the files from Windows only without switching to OSX. It can only be used on non-sandboxed applications, because the codesigning is done on OSX only. 17 | 18 | Vladimir Georgiev, 2013 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | 60 | # Delphi history and backups 61 | __history/ 62 | __recovery/ 63 | *.~* 64 | 65 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 66 | *.stat -------------------------------------------------------------------------------- /embdeploy.dpr: -------------------------------------------------------------------------------- 1 | { 2 | Automated deployer for Embarcadero RAD Studio projects 3 | Created by Vladimir Georgiev, 2013 4 | 5 | MIT License (MIT) 6 | } 7 | 8 | program embdeploy; 9 | 10 | {$APPTYPE CONSOLE} 11 | 12 | {$R *.res} 13 | 14 | uses System.SysUtils, 15 | System.IOUtils, 16 | Deployer in 'Deployer.pas', 17 | DeployChannels in 'DeployChannels.pas'; 18 | 19 | const 20 | VERSION = '1.4'; 21 | 22 | var 23 | Deployer: TDeployer; 24 | Project, Param, DelphiVer: String; 25 | logExceptions: boolean; 26 | 27 | // Display the parameters usage information 28 | procedure ShowUsage; 29 | procedure ShowParam(aParam, aText: String); 30 | begin 31 | Writeln(Format(' %-16s %s', [aParam, aText])); 32 | end; 33 | begin 34 | WriteLn(''); 35 | Writeln('Usage: embdeploy [-delphiver "ver"] -deploy|(-cmd "command")|(-bundle "zip") [-platform|-profile|-config|-proot "name"] [-ignore] ProjectName'); 36 | WriteLn(''); 37 | ShowParam('ProjectName', 'Name (relative or absolute) of the project file (.dproj)'); 38 | ShowParam('-delphiver "ver"', 'Delphi version to use the paclient from. It is the number from the HKCU/Software/Emb...'); 39 | ShowParam('-deploy', 'Deploy the project to the remote profile'); 40 | ShowParam('-platform "name"', 'Platform to deploy (Win32, OSX32, iOSDevice, etc). If not specified the default one from ' + 41 | 'the project is used'); 42 | ShowParam('-profile "name"', 'Name of the remote profile to use. If not specified the default one for the platform is used'); 43 | ShowParam('-config "name"', 'Release or Debug configuration. If not specified the default one from the project file is used'); 44 | ShowParam('-proot "name"', 'Remote project root folder. If not specified a default one is generated from the project name'); 45 | ShowParam('-cmd "command"', 'Execute an arbitrary command line on the remote server. The command is anything that ' + 46 | 'can be executed from a terminal or command line prompt. It is executed from ' + 47 | 'above the remote project folder. The command can contain the $PROOT parameter, which is ' + 48 | 'replaced with the project root folder, e.g. $PROOT/Contents/... becomes myproject.app/Contents/...'); 49 | ShowParam('-ignore', 'Ignore errors reported by paclient.exe and continue deploying'); 50 | ShowParam('-bundle "zipname"', 'Produce a ZIP archive of the files to be deployed. Useful for making a ZIP of an OSX project APP bundle'); 51 | ShowParam('-verbose', 'Produces detailed debugging messages'); 52 | ShowParam('-registerPAClient','Uses the PAClient to deploy the project'); 53 | ShowParam('-registerFolder "folder"', 'OSX only: Creates the APP folder structure on Windows.'+ 54 | ' Useful for building OSX without the need to use the paclient on OSX'); 55 | ShowParam('-binaryFolder "folder"','The folder for the binary files. If not provided, the default location is assumed'); 56 | ShowParam('-logExceptions','Logs any exceptions and quits instead of raising them'); 57 | ShowParam('-verInfoKeys','Specify a file with custom verInfoKeys. Useful to make a custom Info.plist file'); 58 | end; 59 | 60 | // Check if the valid combination of parameters is passed 61 | function ValidateParams: Boolean; 62 | var 63 | tmpMessage: string; 64 | begin 65 | Project := ParamStr(ParamCount); 66 | if not FileExists(Project) then 67 | begin 68 | tmpMessage:='Project "' + Project +'" not found'; 69 | if logExceptions then 70 | begin 71 | Writeln(tmpMessage); 72 | Halt(1); 73 | end 74 | else 75 | raise Exception.Create(tmpMessage); 76 | end; 77 | 78 | Result := FindCmdLineSwitch('deploy') or FindCmdLineSwitch('cmd') or FindCmdLineSwitch('bundle'); 79 | end; 80 | 81 | // Main application body 82 | begin 83 | try 84 | ExitCode := 1; // Default to error and change to success later 85 | 86 | Writeln('Automated deployer for Embarcadero RAD Studio projects - Version ' + VERSION); 87 | Writeln('Written by Vladimir Georgiev, 2013'); 88 | 89 | if FindCmdLineSwitch('?') or (ParamCount=0) then 90 | begin 91 | ShowUsage; 92 | Exit; 93 | end; 94 | 95 | logExceptions:=FindCmdLineSwitch('logExceptions'); 96 | ValidateParams; 97 | 98 | if FindCmdLineSwitch('delphiver', Param) then 99 | DelphiVer := Param; 100 | Deployer := TDeployer.Create(DelphiVer); 101 | try 102 | Deployer.LogExceptions:=logExceptions; 103 | 104 | if FindCmdLineSwitch('platform', Param) then 105 | Deployer.Platform := Param; 106 | if FindCmdLineSwitch('profile', Param) then 107 | Deployer.RemoteProfile := Param; 108 | if FindCmdLineSwitch('config', Param) then 109 | Deployer.Config := Param; 110 | if FindCmdLineSwitch('proot', Param) then 111 | Deployer.ProjectRoot := Param; 112 | 113 | Deployer.IgnoreErrors := FindCmdLineSwitch('ignore'); 114 | 115 | Deployer.Verbose:=FindCmdLineSwitch('verbose'); 116 | 117 | Deployer.BinaryFolder:=''; 118 | if FindCmdLineSwitch('binaryFolder', Param) then 119 | Deployer.BinaryFolder:=Param; 120 | 121 | if FindCmdLineSwitch('registerPAClient') then 122 | Deployer.RegisterPACLient; 123 | if FindCmdLineSwitch('registerFolder', Param) then 124 | Deployer.RegisterFolder(Param, TPath.GetFileNameWithoutExtension(Project)); 125 | 126 | if FindCmdLineSwitch('verInfoKeys', Param) then 127 | begin 128 | if TFile.Exists(Param) then 129 | Deployer.CustomVerInfoKeys := Tfile.ReadAllText(Param) 130 | else 131 | Writeln('Ignoring verInfoKeys param because the file was not found'); 132 | end; 133 | 134 | // Deploy the project 135 | if FindCmdLineSwitch('deploy') then 136 | begin 137 | Deployer.DeployProject(Project); 138 | Writeln('Deployment complete'); 139 | end; 140 | 141 | // Execute a custom remote command 142 | if FindCmdLineSwitch('cmd', Param) then 143 | begin 144 | Deployer.ExecuteCommand(Project, Param); 145 | Writeln('Command executed'); 146 | end; 147 | 148 | // Make a ZIP bundle of the project deployment files 149 | if FindCmdLineSwitch('bundle', Param) then 150 | begin 151 | Deployer.BundleProject(Project, Param); 152 | Writeln('ZIP bundle complete'); 153 | end; 154 | 155 | ExitCode := 0; // Success 156 | finally 157 | Deployer.Free; 158 | end; 159 | except 160 | on E: Exception do 161 | begin 162 | Writeln('Error deploying project:'); 163 | Writeln(E.Message); 164 | end; 165 | end; 166 | end. 167 | -------------------------------------------------------------------------------- /DeployChannels.pas: -------------------------------------------------------------------------------- 1 | unit DeployChannels; 2 | 3 | interface 4 | 5 | type 6 | IDeployChannelBasic = interface 7 | ['{E9BA7BAA-8CAF-49F4-AEBD-C622C455458C}'] 8 | procedure SetVerbose(const newVerbose: Boolean); 9 | function GetVerbose: boolean; 10 | procedure SetFileListName (const newFileList: string); 11 | function GetFileListName: string; 12 | procedure SetProjectRoot (const newProjectRoot: string); 13 | function GetProjectRoot: string; 14 | procedure SetChannelName (const newChannelName: string); 15 | function GetChannelName: string; 16 | procedure SetLogExceptions(const newLog: Boolean); 17 | function GetLogExceptions: boolean; 18 | 19 | property Verbose: boolean read GetVerbose write SetVerbose; 20 | property FileListName: string read GetFileListName write SetFileListName; 21 | property ProjectRoot: string read GetProjectRoot write SetProjectRoot; 22 | property ChannelName: string read GetChannelName write SetChannelName; 23 | property LogExceptions: boolean read GetLogExceptions write SetLogExceptions; 24 | end; 25 | 26 | IDeployChannel = interface (IDeployChannelBasic) 27 | ['{0AC82C72-6867-45DA-A37F-901C471D1A47}'] 28 | procedure SetupChannel; 29 | function CleanChannel: boolean; 30 | function DeployFile(const localName: string; const remoteDir: string; 31 | const operation: Integer; const remoteName: string):boolean; 32 | procedure CloseChannel; 33 | end; 34 | 35 | TDeployBaseChannel = class (TInterfacedObject, IDeployChannelBasic) 36 | private 37 | fVerbose: Boolean; 38 | fFileListName, 39 | fProjectRoot: string; 40 | fChannelName: string; 41 | fLogExceptions: Boolean; 42 | public 43 | procedure SetVerbose(const newVerbose: Boolean); 44 | function GetVerbose: boolean; 45 | procedure SetFileListName (const newFileList: string); 46 | function GetFileListName: string; 47 | procedure SetProjectRoot (const newProjectRoot: string); 48 | function GetProjectRoot: string; 49 | procedure SetChannelName (const newChannelName: string); 50 | function GetChannelName: string; 51 | procedure SetLogExceptions(const newLog: Boolean); 52 | function GetLogExceptions: boolean; 53 | end; 54 | 55 | TPAClientChannel = class(TDeployBaseChannel, IDeployChannel) 56 | private 57 | fRemoteProfile, 58 | fPAClientPath, 59 | fDelphiVersion, 60 | fPlatfrom: string; 61 | function CallPAClient(const aCommand: string): Boolean; 62 | public 63 | constructor Create (const newRemoteProfile: string; const newPAClientPAth: string; 64 | const newDelphiVersion: string; const newPlatform: string); 65 | procedure SetupChannel; 66 | function CleanChannel:boolean; 67 | function DeployFile(const localName: string; const remoteDir: string; 68 | const operation: Integer; const remoteName: string):boolean; 69 | procedure CloseChannel; 70 | end; 71 | 72 | TFolderChannel = class(TDeployBaseChannel, IDeployChannel) 73 | private 74 | fFolder: string; 75 | fProjectName: string; 76 | fBaseProjectName: string; 77 | public 78 | constructor Create (const newFolder: string; const ProjectName: string; 79 | const BaseProjectName: string); 80 | procedure SetupChannel; 81 | function CleanChannel:boolean; 82 | function DeployFile(const localName: string; const remoteDir: string; 83 | const operation: Integer; const remoteName: string):boolean; 84 | procedure CloseChannel; 85 | end; 86 | 87 | implementation 88 | 89 | uses 90 | System.SysUtils, System.Win.Registry, Winapi.Windows, System.Classes, System.IOUtils; 91 | 92 | const 93 | // Paclient commands and the parameters to be substituted 94 | PACLIENT_CLEAN = '--Clean="%s,%s"'; //0 - project root name, 1 - path to a temp file with containing a list of files 95 | PACLIENT_PUT = '--put="%s,%s,%d,%s"'; //0 - local name, 1 - remote path, 2 - operation, 3 - remote name 96 | 97 | { TPAClientChannel } 98 | 99 | // Execute the PaClient.exe app and pass it the command and profile 100 | // Filter some of the paclient output by capturing the out and err pipes 101 | function TPAClientChannel.CallPAClient(const aCommand: string): Boolean; 102 | var 103 | Security : TSecurityAttributes; 104 | PipeRead, PipeWrite: THandle; 105 | BytesInPipe : Cardinal; 106 | Buffer : array[0..2000] of AnsiChar; 107 | Output : TStringList; 108 | StartInfo : TStartupInfo; 109 | ProcInfo : TProcessInformation; 110 | I : Integer; 111 | ExCode : Cardinal; 112 | fullProcessPath : string; 113 | begin 114 | Result := false; 115 | 116 | // Create a pipe to capture the output 117 | Security.nLength := SizeOf(TSecurityAttributes); 118 | Security.bInheritHandle := true; 119 | Security.lpSecurityDescriptor := nil; 120 | if not CreatePipe(PipeRead, PipeWrite, @Security, 0) then 121 | if fLogExceptions then 122 | begin 123 | Writeln('OS Raised an exception.'); 124 | Halt(1); 125 | end 126 | else 127 | RaiseLastOSError; 128 | 129 | try 130 | ZeroMemory(@StartInfo, SizeOf(StartInfo)); 131 | ZeroMemory(@ProcInfo, SizeOf(ProcInfo)); 132 | StartInfo.cb := SizeOf(StartInfo); 133 | StartInfo.hStdOutput := PipeWrite; 134 | StartInfo.hStdError := PipeWrite; 135 | StartInfo.dwFlags := STARTF_USESTDHANDLES; // use output redirect pipe 136 | 137 | fullProcessPath:='"'+fPaclientPath+'"' + ' ' + aCommand + ' "' + fRemoteProfile+'"'; 138 | if fVerbose then 139 | Writeln('Full Command Line: '+fullProcessPath); 140 | 141 | if CreateProcess(nil, PChar(fullProcessPath), nil, nil, true, 142 | CREATE_NO_WINDOW, nil, nil, StartInfo, ProcInfo) then 143 | try 144 | WaitForSingleObject(ProcInfo.hProcess, Infinite); 145 | // The process has finished, so close the write pipe and read the output 146 | CloseHandle(PipeWrite); 147 | ZeroMemory(@Buffer, Length(Buffer)); 148 | ReadFile(PipeRead, Buffer[0], Length(Buffer), BytesInPipe, nil); 149 | // Parse the output, delete the first 4 lines, that are not very useful, and display the rest indented 150 | Output := TStringList.Create; 151 | try 152 | Output.Text:=String(Buffer); 153 | Output.Delete(0); Output.Delete(0); Output.Delete(0); Output.Delete(0); 154 | for I := 0 to Output.Count - 1 do 155 | WriteLn(' ' + Output[I]); 156 | finally 157 | Output.Free; 158 | end; 159 | 160 | // Check the exit code of paclient.exe / 0 is success 161 | Result := GetExitCodeProcess(ProcInfo.hProcess, ExCode) and (ExCode = 0); 162 | finally 163 | CloseHandle(ProcInfo.hProcess); 164 | CloseHandle(ProcInfo.hThread); 165 | end; 166 | finally 167 | CloseHandle(PipeRead); 168 | {$IFNDEF DEBUG} // The PipeWrite handle is closed twice, 169 | //which is acceptable in Release, 170 | //but raises exceptions when running with the debugger 171 | CloseHandle(PipeWrite); 172 | {$ENDIF} 173 | end; 174 | end; 175 | 176 | function TPAClientChannel.CleanChannel: boolean; 177 | begin 178 | result:=CallPaclient(Format(PACLIENT_CLEAN, [fProjectRoot, fFileListName])); 179 | end; 180 | 181 | procedure TPAClientChannel.CloseChannel; 182 | begin 183 | 184 | end; 185 | 186 | constructor TPAClientChannel.Create(const newRemoteProfile, newPAClientPAth, 187 | newDelphiVersion, newPlatform: string); 188 | begin 189 | inherited Create; 190 | fRemoteProfile:=newRemoteProfile; 191 | fPAClientPath:=newPAClientPAth; 192 | fDelphiVersion:=newDelphiVersion; 193 | fPlatfrom:=newPlatform; 194 | end; 195 | 196 | function TPAClientChannel.DeployFile(const localName, remoteDir: string; 197 | const operation: Integer; const remoteName: string): boolean; 198 | begin 199 | result:=CallPaclient(Format(PACLIENT_PUT, [localName, remoteDir, 200 | operation, remoteName])); 201 | end; 202 | 203 | 204 | // Check if there is a remote profile and try to find one. Must be after the project is parsed 205 | procedure TPAClientChannel.SetupChannel; 206 | var 207 | Reg: TRegistry; 208 | regKey: string; 209 | begin 210 | if fRemoteProfile.IsEmpty then 211 | begin 212 | Reg:=TRegistry.Create; 213 | try 214 | Reg.RootKey := HKEY_CURRENT_USER; 215 | regKey:='Software\Embarcadero\BDS\' + fDelphiVersion + '\RemoteProfiles'; 216 | if fVerbose then 217 | Writeln('Looking at Registry Key: '+regKey); 218 | if Reg.OpenKey(regKey, false) then 219 | if Reg.ValueExists('Default_'+fPlatfrom) then 220 | fRemoteProfile := Reg.ReadString('Default_' + fPlatfrom); 221 | if fRemoteProfile.IsEmpty then 222 | if fLogExceptions then 223 | begin 224 | Writeln('Default remote profile not found. Please specify a profile'); 225 | Halt(1); 226 | end 227 | else 228 | raise Exception.Create('Default remote profile not found. Please specify a profile'); 229 | finally 230 | Reg.Free; 231 | end; 232 | end; 233 | 234 | Writeln('Using default profile: ' + fRemoteProfile); 235 | end; 236 | 237 | { TFolderChannel } 238 | 239 | function TFolderChannel.CleanChannel:boolean; 240 | begin 241 | Result:=true; 242 | end; 243 | 244 | procedure TFolderChannel.CloseChannel; 245 | begin 246 | 247 | end; 248 | 249 | constructor TFolderChannel.Create(const newFolder: string; const ProjectName: string; 250 | const BaseProjectName: string); 251 | begin 252 | inherited Create; 253 | fFolder:=newFolder; 254 | fProjectName:=ProjectName; 255 | fBaseProjectName:=BaseProjectName; 256 | end; 257 | 258 | function TFolderChannel.DeployFile(const localName, remoteDir: string; 259 | const operation: Integer; const remoteName: string): boolean; 260 | var 261 | Source, 262 | Target: TFileStream; 263 | targetDir, 264 | targetPath: string; 265 | fileAttributes: TFileAttributes; 266 | begin 267 | result:=True; 268 | Source:= TFileStream.Create(localName, fmOpenRead); 269 | try 270 | if not DirectoryExists(remoteDir) then 271 | begin 272 | targetDir:=TPath.Combine(fFolder,remoteDir); 273 | TDirectory.CreateDirectory(targetDir); 274 | fileAttributes:=TDirectory.GetAttributes(targetDir); 275 | Exclude(fileAttributes, TFileAttribute.faReadOnly); 276 | TDirectory.SetAttributes(targetDir,fileAttributes); 277 | end; 278 | 279 | targetPath:=TPath.Combine( 280 | TPath.Combine(fFolder, remoteDir), remoteName); 281 | Target := TFileStream.Create(targetPath, fmOpenWrite or fmCreate ); 282 | try 283 | Target.CopyFrom(Source, Source.Size ) ; 284 | finally 285 | Target.Free; 286 | end; 287 | 288 | fileAttributes:=TFile.GetAttributes(targetPath); 289 | Exclude(fileAttributes, TFileAttribute.faReadOnly); 290 | TFile.SetAttributes(targetPath, fileAttributes); 291 | 292 | if remoteName=fBaseProjectName then 293 | begin 294 | Include(fileAttributes, TFileAttribute.faArchive); 295 | TFile.SetAttributes(targetPath, fileAttributes); 296 | end; 297 | 298 | finally 299 | Source.Free; 300 | end; 301 | 302 | end; 303 | 304 | procedure TFolderChannel.SetupChannel; 305 | procedure DeleteDirectory(const Name: string); 306 | var 307 | F: TSearchRec; 308 | str: string; 309 | begin 310 | if FindFirst(Name + '\*', faAnyFile, F) = 0 then begin 311 | try 312 | repeat 313 | if (F.Attr and faDirectory <> 0) then 314 | begin 315 | if (F.Name <> '.') and (F.Name <> '..') then 316 | begin 317 | DeleteDirectory(Name + '\' + F.Name); 318 | end; 319 | end 320 | else 321 | begin 322 | str:=Name + '\' + F.Name; 323 | DeleteFile(PWideChar(str)); 324 | end; 325 | until FindNext(F) <> 0; 326 | finally 327 | FindClose(F.FindHandle); 328 | end; 329 | RemoveDir(Name); 330 | end; 331 | end; 332 | 333 | begin 334 | DeleteDirectory(TPath.Combine(fFolder, fProjectName)); 335 | end; 336 | 337 | { TDeployBaseChannel } 338 | 339 | function TDeployBaseChannel.GetChannelName: string; 340 | begin 341 | result:=fChannelName; 342 | end; 343 | 344 | function TDeployBaseChannel.GetFileListName: string; 345 | begin 346 | result:=fFileListName; 347 | end; 348 | 349 | function TDeployBaseChannel.GetLogExceptions: boolean; 350 | begin 351 | result:=fLogExceptions; 352 | end; 353 | 354 | function TDeployBaseChannel.GetProjectRoot: string; 355 | begin 356 | result:=fProjectRoot; 357 | end; 358 | 359 | function TDeployBaseChannel.GetVerbose: boolean; 360 | begin 361 | result:=fVerbose; 362 | end; 363 | 364 | procedure TDeployBaseChannel.SetChannelName(const newChannelName: string); 365 | begin 366 | fChannelName:=newChannelName; 367 | end; 368 | 369 | procedure TDeployBaseChannel.SetFileListName(const newFileList: string); 370 | begin 371 | fFileListName:=newFileList; 372 | end; 373 | 374 | procedure TDeployBaseChannel.SetLogExceptions(const newLog: Boolean); 375 | begin 376 | fLogExceptions:=newLog; 377 | end; 378 | 379 | procedure TDeployBaseChannel.SetProjectRoot(const newProjectRoot: string); 380 | begin 381 | fProjectRoot:=newProjectRoot; 382 | end; 383 | 384 | procedure TDeployBaseChannel.SetVerbose(const newVerbose: Boolean); 385 | begin 386 | fVerbose:=newVerbose; 387 | end; 388 | 389 | end. 390 | -------------------------------------------------------------------------------- /deployer.pas: -------------------------------------------------------------------------------- 1 | unit Deployer; 2 | 3 | interface 4 | 5 | uses 6 | Winapi.ActiveX, Winapi.MsXML, 7 | System.Zip, System.IOUtils, 8 | System.Sysutils, System.Classes, System.Win.Registry, Winapi.Windows, 9 | DeployChannels, System.Generics.Collections, Vcl.Dialogs; 10 | 11 | type 12 | TDeployClass = record 13 | Required : Boolean; 14 | Name : String; 15 | RemoteDir : String; 16 | Operation : Integer; 17 | Extensions: String; // What is it used for? 18 | end; 19 | 20 | TDeployFile = record 21 | Enabled : Boolean; 22 | ClassName : String; 23 | LocalName : String; 24 | RemoteName : String; 25 | RemoteDir : String; 26 | Operation : Integer; 27 | Configuration: String; // Release/Debug/Base/Others... 28 | end; 29 | 30 | TEntitlementsRecord= record 31 | ReadOnlyMusic: Boolean; // 32 | ///// The IDE doesn't create key for this entry 33 | ///// 34 | ReadWriteMusic: Boolean; 35 | //// 36 | 37 | ReadOnlyPictures: Boolean; // 38 | ////// There is a bug in the creation of the file by the IDE 39 | ReadWritePictures: Boolean; // 40 | ///// The key com.apple.security.assets.pictures.read-only is 41 | ///// generated twice. 42 | ///// The com.apple.security.assets.pictures.read-write is missing 43 | 44 | IncomingNetwork: Boolean; // 45 | OutgoingNetwork: Boolean; // 46 | Location: Boolean; //true 47 | USBDevice: Boolean; // 48 | ReadWriteCalendars: Boolean; // 49 | ReadWriteFileDialog: Boolean; // 50 | ReadOnlyFileDialog: Boolean; // 51 | ReadWriteDownloads: Boolean; // 52 | ReadWriteMovies: Boolean; // 53 | ReadOnlyMovies: Boolean; // 54 | RecordingMicrophone: Boolean; // 55 | 56 | Printing: Boolean; // 57 | CaptureCamera: Boolean; // 58 | ReadWriteAddressBook: Boolean; // 59 | ChildProcessInheritance: Boolean; // 60 | end; 61 | 62 | TDeployer = class 63 | private 64 | fProjectParsed:Boolean; 65 | fDelphiPath : String; 66 | fDelphiVersion: String; // The version number - 9.0, 10.0, 11.0, etc 67 | fConfig : String; 68 | fProjectName : String; 69 | fPlatform : String; 70 | fProjectRoot : String; 71 | fRemoteProfile: String; 72 | fDeployClasses: array of TDeployClass; 73 | fDeployFiles : array of TDeployFile; 74 | fIgnoreErrors : Boolean; 75 | fPaclientPath : String; 76 | fEntitlements : TEntitlementsRecord; 77 | fVerInfoKeys : TStringList; 78 | fCustomVerInfoKeys:String; 79 | fVerbose : Boolean; 80 | fDeployChannels: TList; 81 | fBinaryFolder : string; 82 | fLogExceptions: Boolean; 83 | fDebugExcludeFiles: TList; //Keeps a list of extensions to be excluded depending on 84 | //the configuration. Eg. .rsm file is deployed in Debug 85 | //but not in Release configuration. This allows for use of 86 | //Debug builds to Release deployments 87 | fReleaseExcludeFiles: TList; 88 | procedure GetEmbarcaderoPaths; 89 | procedure ParseProject(const aProjectPath: String); 90 | procedure CreateDeploymentFile(const fullPath: string); 91 | procedure CreateEntitlementsFile(const fullPath: string); 92 | procedure CreateInfoPlistFile(const fullPath: string); 93 | procedure SetupChannels; 94 | function CreateVerInfoKeys():TStringList; 95 | public 96 | constructor Create(const aDelphiVersion: String); 97 | destructor Destroy; override; 98 | procedure BundleProject(const aProjectPath, aZIPName: String); 99 | procedure DeployProject(const aProjectPath: String); 100 | procedure ExecuteCommand(const aProjectPath, aCommand: String); 101 | procedure RegisterPACLient; 102 | procedure RegisterFolder(const regPath: string; const project: string); 103 | 104 | property Config : String read fConfig write fConfig; 105 | property IgnoreErrors : Boolean read fIgnoreErrors write fIgnoreErrors; 106 | property Platform : String read fPlatform write fPlatform; 107 | property ProjectRoot : String read fProjectRoot write fProjectRoot; 108 | property RemoteProfile: String read fRemoteProfile write fRemoteProfile; 109 | property Verbose : boolean read fVerbose write fVerbose; 110 | property BinaryFolder : string read fBinaryFolder write fBinaryFolder; 111 | property LogExceptions: Boolean read fLogExceptions write fLogExceptions; 112 | property CustomVerInfoKeys:String read fCustomVerInfoKeys write fCustomVerInfoKeys; 113 | end; 114 | 115 | 116 | implementation 117 | 118 | uses 119 | System.StrUtils; 120 | 121 | // Try to find the last version of Delphi installed to get the Redist folder and Paclient path 122 | procedure TDeployer.GetEmbarcaderoPaths; 123 | var 124 | Reg: TRegistry; 125 | Versions: TStringList; 126 | begin 127 | Reg := TRegistry.Create; 128 | try 129 | Reg.RootKey := HKEY_CURRENT_USER; 130 | 131 | // If no Delphi version is supplied try to get the latest one from the registry 132 | if fDelphiVersion.IsEmpty then 133 | begin 134 | Versions := TStringList.Create; 135 | try 136 | if Reg.OpenKey('SOFTWARE\Embarcadero\BDS', false) then 137 | begin 138 | Reg.GetKeyNames(Versions); 139 | Reg.CloseKey; 140 | if (Versions.Count > 0) then 141 | fDelphiVersion := Versions[Versions.Count - 1]; 142 | end; 143 | finally 144 | Versions.Free; 145 | end; 146 | end; 147 | 148 | if Reg.OpenKey('SOFTWARE\Embarcadero\BDS\' + fDelphiVersion, false) then 149 | if Reg.ValueExists('RootDir') then 150 | fDelphiPath := Reg.ReadString('RootDir'); 151 | if fDelphiPath.IsEmpty then 152 | if fLogExceptions then 153 | begin 154 | Writeln('The Delphi install path could not be found.'); 155 | Halt(1); 156 | end 157 | else 158 | raise Exception.Create('The Delphi install path could not be found.'); 159 | 160 | fPaclientPath := IncludeTrailingPathDelimiter(fDelphiPath) + 'bin\paclient.exe' 161 | finally 162 | Reg.Free; 163 | end; 164 | end; 165 | 166 | function TDeployer.CreateVerInfoKeys():TStringList; 167 | begin 168 | Result := TStringList.Create; 169 | Result.Delimiter := ';'; 170 | Result.StrictDelimiter := true; 171 | end; 172 | 173 | constructor TDeployer.Create(const aDelphiVersion: String); 174 | begin 175 | inherited Create; 176 | fDeployChannels:=TList.Create; 177 | fDebugExcludeFiles:=TList.Create; 178 | fReleaseExcludeFiles:=TList.Create; 179 | fVerInfoKeys := CreateVerInfoKeys; 180 | fReleaseExcludeFiles.Add('.rsm'); 181 | fDelphiVersion := aDelphiVersion; 182 | GetEmbarcaderoPaths; 183 | end; 184 | 185 | // Creates files required for the deployment package 186 | // Currently it creates the .entitlements and .info.plist files for OSX32 187 | procedure TDeployer.CreateDeploymentFile(const fullPath: string); 188 | var 189 | ext: string; 190 | begin 191 | ext:=ExtractFileExt(fullPath); 192 | if (ext='.entitlements') and (fPlatform='OSX32') then 193 | CreateEntitlementsFile(fullPath); 194 | if (ext='.plist') and (fPlatform='OSX32') then 195 | CreateInfoPlistFile(fullPath); 196 | end; 197 | 198 | procedure TDeployer.CreateEntitlementsFile(const fullPath: string); 199 | 200 | function BooleanToString(const value: boolean): string; 201 | begin 202 | if value then 203 | result:='true' 204 | else 205 | result:='false'; 206 | end; 207 | 208 | var 209 | xmlFile: TStringList; 210 | begin 211 | xmlFile:=TStringList.Create; 212 | try 213 | xmlFile.Add(''); 214 | xmlFile.Add(''); 215 | xmlFile.Add(''); 216 | xmlFile.Add(''); 217 | 218 | xmlFile.Add(' com.apple.security.assets.music.read-only'); 219 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadOnlyMusic)+'/>'); 220 | xmlFile.Add(' com.apple.security.assets.music.read-write'); 221 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteMusic)+'/>'); 222 | xmlFile.Add(' com.apple.security.assets.pictures.read-only'); 223 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadOnlyPictures)+'/>'); 224 | xmlFile.Add(' com.apple.security.assets.pictures.read-write'); 225 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWritePictures)+'/>'); 226 | xmlFile.Add(' com.apple.security.network.client'); 227 | xmlFile.Add(' <'+BooleanToString(fEntitlements.IncomingNetwork)+'/>'); 228 | xmlFile.Add(' com.apple.security.network.server'); 229 | xmlFile.Add(' <'+BooleanToString(fEntitlements.OutgoingNetwork)+'/>'); 230 | xmlFile.Add(' com.apple.security.personal-information.location'); 231 | xmlFile.Add(' <'+BooleanToString(fEntitlements.Location)+'/>'); 232 | xmlFile.Add(' com.apple.security.device.usb'); 233 | xmlFile.Add(' <'+BooleanToString(fEntitlements.USBDevice)+'/>'); 234 | xmlFile.Add(' com.apple.security.personal-information.calendars'); 235 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteCalendars)+'/>'); 236 | xmlFile.Add(' com.apple.security.files.user-selected.read-write'); 237 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteFileDialog)+'/>'); 238 | xmlFile.Add(' com.apple.security.files.user-selected.read-only'); 239 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadOnlyFileDialog)+'/>'); 240 | xmlFile.Add(' com.apple.security.files.downloads.read-write'); 241 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteDownloads)+'/>'); 242 | xmlFile.Add(' com.apple.security.assets.movies.read-write'); 243 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteMovies)+'/>'); 244 | xmlFile.Add(' com.apple.security.assets.movies.read-only'); 245 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadOnlyMovies)+'/>'); 246 | xmlFile.Add(' com.apple.security.device.microphone'); 247 | xmlFile.Add(' <'+BooleanToString(fEntitlements.RecordingMicrophone)+'/>'); 248 | xmlFile.Add(' com.apple.security.print'); 249 | xmlFile.Add(' <'+BooleanToString(fEntitlements.Printing)+'/>'); 250 | xmlFile.Add(' com.apple.security.device.camera'); 251 | xmlFile.Add(' <'+BooleanToString(fEntitlements.CaptureCamera)+'/>'); 252 | xmlFile.Add(' com.apple.security.personal-information.addressbook'); 253 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ReadWriteAddressBook)+'/>'); 254 | xmlFile.Add(' com.apple.security.inherit'); 255 | xmlFile.Add(' <'+BooleanToString(fEntitlements.ChildProcessInheritance)+'/>'); 256 | 257 | XmlFile.Add(''); 258 | xmlFile.Add(''); 259 | 260 | xmlFile.SaveToFile(fullPath); 261 | finally 262 | xmlFile.Free; 263 | end; 264 | end; 265 | 266 | procedure TDeployer.CreateInfoPlistFile(const fullPath: string); 267 | var 268 | xmlFile:TStringList; 269 | i: Integer; 270 | begin 271 | xmlFile:=TStringList.Create; 272 | try 273 | xmlFile.Add(''); 274 | xmlFile.Add(''); 275 | xmlFile.Add(''); 276 | xmlFile.Add(''); 277 | 278 | for i := 0 to fVerInfoKeys.Count-1 do 279 | begin 280 | xmlFile.Add(' '+fVerInfoKeys[i].Split(['='])[0]+''); 281 | xmlFile.Add(' '+fVerInfoKeys[i].Split(['='])[1]+''); 282 | end; 283 | 284 | xmlFile.Add(' CFBundleIconFile'); 285 | xmlFile.Add(' '+fProjectName+'.icns'); 286 | 287 | XmlFile.Add(''); 288 | xmlFile.Add(''); 289 | 290 | xmlFile.SaveToFile(fullPath); 291 | finally 292 | xmlFile.Free; 293 | end; 294 | end; 295 | 296 | // Produce a ZIP archive of the files to be deployed (useful to produce archives of the OSX .APP bundles on Win) 297 | procedure TDeployer.BundleProject(const aProjectPath, aZIPName: String); 298 | var 299 | Zip: TZipFile; 300 | I: Integer; 301 | addedFiles:TList; 302 | begin 303 | ParseProject(aProjectPath); 304 | 305 | WriteLn(Format('Archiving %d files from project %s, config %s', [length(fDeployFiles), aProjectPath, fConfig])); 306 | 307 | // Create the folders in the zip name, if any 308 | ForceDirectories(ExtractFilePath(ExpandFileName(aZIPName))); 309 | 310 | Zip := TZipFile.Create; 311 | try 312 | if FileExists(aZIPName) then 313 | TFile.Delete(aZIPName); 314 | Zip.Open(aZIPName, zmWrite); 315 | 316 | // Add each file to the archive with its path. The \ is replaced with / to make the archive work in OSX 317 | addedFiles := TList.Create; 318 | try 319 | for I := 0 to Length(fDeployFiles) - 1 do 320 | begin 321 | if not addedFiles.Contains(fDeployFiles[I].LocalName) then 322 | begin 323 | Zip.Add(fDeployFiles[I].LocalName, fDeployFiles[I].RemoteDir.Replace('\', '/') + fDeployFiles[I].RemoteName.Replace('\', '/')); 324 | addedFiles.Add(fDeployFiles[I].LocalName); 325 | end; 326 | end; 327 | finally 328 | addedFiles.Free; 329 | end; 330 | finally 331 | Zip.Free; 332 | end; 333 | end; 334 | 335 | // Deploy the project to the remote server 336 | procedure TDeployer.DeployProject(const aProjectPath: String); 337 | var 338 | S, TempFile: String; 339 | I: Integer; 340 | tmpChannel: IDeployChannel; 341 | begin 342 | ParseProject(aProjectPath); 343 | 344 | SetupChannels; 345 | 346 | WriteLn(Format('Deploying %d files from project %s, config %s', [length(fDeployFiles), aProjectPath, fConfig])); 347 | 348 | // Build a temp file list to clean the remote project folder 349 | TempFile := TPath.GetTempFileName; 350 | for I := 0 to Length(fDeployFiles) - 1 do 351 | S := S + fDeployFiles[I].RemoteDir + fDeployFiles[I].RemoteName + sLineBreak; 352 | TFile.WriteAllText(TempFile, S); 353 | 354 | if fVerbose then 355 | Writeln('TempFile: '+TempFile); 356 | 357 | for tmpChannel in fDeployChannels do 358 | tmpChannel.FileListName:=TempFile; 359 | 360 | //Clean up deploy channels 361 | // Execute the clean command and delete the temp file 362 | Writeln('Cleaning remote project folder'); 363 | 364 | for tmpChannel in fDeployChannels do 365 | begin 366 | if (not tmpChannel.CleanChannel) and (not fIgnoreErrors) then 367 | if fLogExceptions then 368 | begin 369 | Writeln('Error in '+tmpChannel.ChannelName+'. Deployment stopped.'); 370 | Halt(1); 371 | end 372 | else 373 | raise Exception.Create('Error in '+tmpChannel.ChannelName+'. Deployment stopped.'); 374 | end; 375 | TFile.Delete(TempFile); 376 | 377 | 378 | // Deploy the files 379 | for I := 0 to Length(fDeployFiles) - 1 do 380 | begin 381 | Writeln('Deploying file: ' + fDeployFiles[I].LocalName); 382 | 383 | if not TFile.Exists(fDeployFiles[I].LocalName) then 384 | CreateDeploymentFile(fDeployFiles[I].LocalName); 385 | 386 | for tmpChannel in fDeployChannels do 387 | begin 388 | if (not tmpChannel.DeployFile(fDeployFiles[I].LocalName, fDeployFiles[I].RemoteDir, 389 | fDeployFiles[I].Operation, fDeployFiles[I].RemoteName)) and (not fIgnoreErrors) then 390 | if fLogExceptions then 391 | begin 392 | Writeln('Error in '+tmpChannel.ChannelName+'. Deployment stopped.'); 393 | Halt(1); 394 | end 395 | else 396 | raise Exception.Create('Error in '+tmpChannel.ChannelName+'. Deployment stopped.'); 397 | end; 398 | end; 399 | 400 | for tmpChannel in fDeployChannels do 401 | tmpChannel.CloseChannel; 402 | end; 403 | 404 | destructor TDeployer.Destroy; 405 | begin 406 | fDeployChannels.Free; 407 | fDebugExcludeFiles.Free; 408 | fReleaseExcludeFiles.Free; 409 | fVerInfoKeys.Free; 410 | inherited; 411 | end; 412 | 413 | // Parse the project file to find the list of files to be deployed 414 | procedure TDeployer.ParseProject(const aProjectPath: String); 415 | var 416 | XmlDoc: IXMLDOMDocument; 417 | Node : IXMLDOMNode; 418 | Nodes : IXMLDOMNodeList; 419 | I, J, Count : Integer; 420 | entitlementPath: string; 421 | tmpDeployFile: TDeployFile; 422 | currExcludeList: TList; 423 | tmpstr: string; 424 | verInfoKeys:TStringList; 425 | begin 426 | //Avoiding rework and some bugs with bundle option 427 | if fProjectParsed then 428 | Exit; 429 | 430 | CoInitialize(nil); 431 | XmlDoc := CoDOMDocument.Create; 432 | 433 | try 434 | // Set the project name the same as the file 435 | fProjectName := ChangeFileExt(ExtractFileName(aProjectPath), ''); 436 | 437 | // Load the project file 438 | if not XmlDoc.load(aProjectPath) then 439 | if fLogExceptions then 440 | begin 441 | Writeln('Project file could not be loaded'); 442 | Halt(1); 443 | end 444 | else 445 | raise Exception.Create('Project file could not be loaded'); 446 | 447 | // Read the default platform if not set with a parameter (OSX32, Win32/64, etc) 448 | if fPlatform.IsEmpty then 449 | begin 450 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/Platform/node()'); 451 | if not Assigned(Node) then 452 | if fLogExceptions then 453 | begin 454 | Writeln('Default platform not found in the project. Please specify a platform.'); 455 | Halt(1); 456 | end 457 | else 458 | raise Exception.Create('Default platform not found in the project. Please specify a platform.'); 459 | fPlatform := Node.nodeValue; 460 | end; 461 | 462 | // Read the default config if not set with a parameter (Release or Debug or another) 463 | if fConfig.IsEmpty then 464 | begin 465 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/Config/node()'); 466 | if not Assigned(Node) then 467 | if fLogExceptions then 468 | begin 469 | Writeln('Default build config(Release/Debug/...) not found in the project. Please specify a config.'); 470 | Halt(1); 471 | end 472 | else 473 | raise Exception.Create('Default build config(Release/Debug/...) not found in the project. Please specify a config.'); 474 | fConfig := Node.nodeValue; 475 | end; 476 | 477 | // Read the ProjectRoot for building the deploy file names 478 | if fProjectRoot.IsEmpty then 479 | begin 480 | Node := XmlDoc.selectSingleNode('//Deployment/ProjectRoot[@Platform="' + fPlatform + '"]'); 481 | if not Assigned(Node) then 482 | if fLogExceptions then 483 | begin 484 | Writeln('ProjectRoot not found in the project.'); 485 | Halt(1); 486 | end 487 | else 488 | raise Exception.Create('ProjectRoot not found in the project.'); 489 | fProjectRoot := Node.attributes.getNamedItem('Name').nodeValue; 490 | fProjectRoot := fProjectRoot.Replace('$(PROJECTNAME)', fProjectName); 491 | end; 492 | 493 | //Get the .entitlements details for OSX 494 | if fPlatform='OSX32' then 495 | begin 496 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadOnlyMusic/node()'); 497 | if Assigned(Node) then 498 | fEntitlements.ReadOnlyMusic := (UpperCase(Node.nodeValue) = 'TRUE') 499 | else 500 | fEntitlements.ReadOnlyMusic:=False; 501 | 502 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteMusic/node()'); 503 | if Assigned(Node) then 504 | fEntitlements.ReadWriteMusic := (UpperCase(Node.nodeValue) = 'TRUE') 505 | else 506 | fEntitlements.ReadWriteMusic:=False; 507 | 508 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadOnlyPictures/node()'); 509 | if Assigned(Node) then 510 | fEntitlements.ReadOnlyPictures := (UpperCase(Node.nodeValue) = 'TRUE') 511 | else 512 | fEntitlements.ReadOnlyPictures:=False; 513 | 514 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWritePictures/node()'); 515 | if Assigned(Node) then 516 | fEntitlements.ReadWritePictures := (UpperCase(Node.nodeValue) = 'TRUE') 517 | else 518 | fEntitlements.ReadWritePictures:=False; 519 | 520 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_IncomingNetwork/node()'); 521 | if Assigned(Node) then 522 | fEntitlements.IncomingNetwork := (UpperCase(Node.nodeValue) = 'TRUE') 523 | else 524 | fEntitlements.IncomingNetwork:=False; 525 | 526 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_OutgoingNetwork/node()'); 527 | if Assigned(Node) then 528 | fEntitlements.OutgoingNetwork := (UpperCase(Node.nodeValue) = 'TRUE') 529 | else 530 | fEntitlements.OutgoingNetwork:=False; 531 | 532 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_Location/node()'); 533 | if Assigned(Node) then 534 | fEntitlements.Location := (UpperCase(Node.nodeValue) = 'TRUE') 535 | else 536 | fEntitlements.Location:=False; 537 | 538 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_USBDevices/node()'); 539 | if Assigned(Node) then 540 | fEntitlements.USBDevice := (UpperCase(Node.nodeValue) = 'TRUE') 541 | else 542 | fEntitlements.USBDevice:=False; 543 | 544 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteCalendars/node()'); 545 | if Assigned(Node) then 546 | fEntitlements.ReadWriteCalendars := (UpperCase(Node.nodeValue) = 'TRUE') 547 | else 548 | fEntitlements.ReadWriteCalendars:=False; 549 | 550 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteFileDialog/node()'); 551 | if Assigned(Node) then 552 | fEntitlements.ReadWriteFileDialog := (UpperCase(Node.nodeValue) = 'TRUE') 553 | else 554 | fEntitlements.ReadWriteFileDialog:=False; 555 | 556 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadOnlyFileDialog/node()'); 557 | if Assigned(Node) then 558 | fEntitlements.ReadOnlyFileDialog := (UpperCase(Node.nodeValue) = 'TRUE') 559 | else 560 | fEntitlements.ReadOnlyFileDialog:=False; 561 | 562 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteDownloads/node()'); 563 | if Assigned(Node) then 564 | fEntitlements.ReadWriteDownloads := (UpperCase(Node.nodeValue) = 'TRUE') 565 | else 566 | fEntitlements.ReadWriteDownloads:=False; 567 | 568 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteMovies/node()'); 569 | if Assigned(Node) then 570 | fEntitlements.ReadWriteMovies := (UpperCase(Node.nodeValue) = 'TRUE') 571 | else 572 | fEntitlements.ReadWriteMovies:=False; 573 | 574 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadOnlyMovies/node()'); 575 | if Assigned(Node) then 576 | fEntitlements.ReadOnlyMovies := (UpperCase(Node.nodeValue) = 'TRUE') 577 | else 578 | fEntitlements.ReadOnlyMovies:=False; 579 | 580 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_RecordingMicrophone/node()'); 581 | if Assigned(Node) then 582 | fEntitlements.RecordingMicrophone := (UpperCase(Node.nodeValue) = 'TRUE') 583 | else 584 | fEntitlements.RecordingMicrophone:=False; 585 | 586 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_Printing/node()'); 587 | if Assigned(Node) then 588 | fEntitlements.Printing := (UpperCase(Node.nodeValue) = 'TRUE') 589 | else 590 | fEntitlements.Printing:=False; 591 | 592 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_CaptureCamera/node()'); 593 | if Assigned(Node) then 594 | fEntitlements.CaptureCamera := (UpperCase(Node.nodeValue) = 'TRUE') 595 | else 596 | fEntitlements.CaptureCamera:=False; 597 | 598 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ReadWriteAddressBook/node()'); 599 | if Assigned(Node) then 600 | fEntitlements.ReadWriteAddressBook := (UpperCase(Node.nodeValue) = 'TRUE') 601 | else 602 | fEntitlements.ReadWriteAddressBook:=False; 603 | 604 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup/EL_ChildProcessInheritance/node()'); 605 | if Assigned(Node) then 606 | fEntitlements.ChildProcessInheritance := (UpperCase(Node.nodeValue) = 'TRUE') 607 | else 608 | fEntitlements.ChildProcessInheritance:=False; 609 | 610 | //Get the .plist details for OSX 611 | if fCustomVerInfoKeys.Trim = '' then 612 | begin 613 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup[@Condition="''$(Base_OSX32)''!=''''"]/VerInfo_Keys/node()'); 614 | if Assigned(Node) then 615 | begin 616 | tmpstr := Node.nodeValue; 617 | tmpstr:=ReplaceStr(tmpstr,'$(MSBuildProjectName)',fProjectName); 618 | 619 | fVerInfoKeys.DelimitedText := tmpstr; 620 | end; 621 | 622 | if fConfig = 'Release' then 623 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup[@Condition="''$(Cfg_1_OSX32)''!=''''"]/VerInfo_Keys/node()') 624 | else 625 | Node := XmlDoc.selectSingleNode('/Project/PropertyGroup[@Condition="''$(Cfg_2_OSX32)''!=''''"]/VerInfo_Keys/node()'); 626 | 627 | if Assigned(Node) then 628 | begin 629 | tmpstr := Node.nodeValue; 630 | tmpstr:=ReplaceStr(tmpstr,'$(MSBuildProjectName)',fProjectName); 631 | 632 | if fVerInfoKeys.Count > 0 then 633 | begin 634 | verInfoKeys := CreateVerInfoKeys(); 635 | try 636 | verInfoKeys.DelimitedText := tmpstr; 637 | for I := 0 to verInfoKeys.Count-1 do 638 | begin 639 | fVerInfoKeys.Values[verInfoKeys.Names[i]] := verInfoKeys.ValueFromIndex[i]; 640 | end; 641 | finally 642 | verInfoKeys.Free; 643 | end; 644 | end 645 | else 646 | fVerInfoKeys.DelimitedText := tmpstr; 647 | end; 648 | end 649 | else 650 | fVerInfoKeys.DelimitedText := fCustomVerInfoKeys; 651 | end; 652 | 653 | // Get all the Platform subnodes of the DeployClass nodes for the specified platform 654 | Nodes := XmlDoc.selectNodes('//DeployClass/Platform[@Name="' + fPlatform + '"]'); 655 | SetLength(fDeployClasses, Nodes.length); 656 | Count := 0; 657 | for I := 0 to Nodes.length - 1 do 658 | begin 659 | // Get the Name and Required attributes of the DeployClass node (the parent) 660 | if Nodes.item[I].parentNode.attributes.getNamedItem('Name') <> nil then 661 | fDeployClasses[Count].Name := Nodes.item[I].parentNode.attributes.getNamedItem('Name').nodeValue; 662 | if Nodes.item[I].parentNode.attributes.getNamedItem('Required') <> nil then 663 | fDeployClasses[Count].Required := StrToBool(Nodes.item[I].parentNode.attributes.getNamedItem('Required').nodeValue); 664 | 665 | // Get the RemoteDir, Operation, Extensions subnode values 666 | if Nodes.item[I].selectSingleNode('RemoteDir/node()') <> nil then 667 | fDeployClasses[Count].RemoteDir := Nodes.item[I].selectSingleNode('RemoteDir/node()').nodeValue; 668 | if Nodes.item[I].selectSingleNode('Operation/node()') <> nil then 669 | fDeployClasses[Count].Operation := Nodes.item[I].selectSingleNode('Operation/node()').nodeValue; 670 | if Nodes.item[I].selectSingleNode('Extensions/node()') <> nil then 671 | fDeployClasses[Count].Extensions := Nodes.item[I].selectSingleNode('Extensions/node()').nodeValue; 672 | 673 | // VG 300715: The XE8 sets the ProjectOSXEntitlements RemoteFolder incorrectly to "../" instead of "Contents" 674 | // Hardcode a quick fix for it and watch for other such problems 675 | // 676 | //Not sure how XE10, 10.1 (Seatlle) deal with this but in D10.1 Berlin the folder is set to "..\" and not to "../" 677 | {$IFDEF VER310} 678 | entitlementPath:='..\'; 679 | {$ELSE} 680 | entitlementPath:='../'; 681 | {$ENDIF} 682 | if fDeployClasses[Count].Name.Equals('ProjectOSXEntitlements') and fDeployClasses[Count].RemoteDir.Equals(entitlementPath) then 683 | fDeployClasses[Count].RemoteDir := 'Contents'; 684 | 685 | Inc(Count); 686 | end; 687 | SetLength(fDeployClasses, Count); 688 | 689 | // Get all the Platform subnodes of the DeployFile nodes for the specified platform 690 | Nodes := XmlDoc.selectNodes('//DeployFile/Platform[@Name="' + fPlatform + '"]'); 691 | SetLength(fDeployFiles, Nodes.length); 692 | Count := 0; 693 | for I := 0 to Nodes.length - 1 do 694 | begin 695 | // Get the LocalName, Configuration, Class attributes of the DeployFiles node (the parent) 696 | if Nodes.item[I].parentNode.attributes.getNamedItem('LocalName') <> nil then 697 | fDeployFiles[Count].LocalName := Nodes.item[I].parentNode.attributes.getNamedItem('LocalName').nodeValue; 698 | if Nodes.item[I].parentNode.attributes.getNamedItem('Configuration') <> nil then 699 | fDeployFiles[Count].Configuration := Nodes.item[I].parentNode.attributes.getNamedItem('Configuration').nodeValue; 700 | if Nodes.item[I].parentNode.attributes.getNamedItem('Class') <> nil then 701 | fDeployFiles[Count].ClassName := Nodes.item[I].parentNode.attributes.getNamedItem('Class').nodeValue; 702 | 703 | // Get the Enabled, RemoteDir, RemoteName subnode values 704 | if Nodes.item[I].selectSingleNode('Enabled/node()') <> nil then 705 | fDeployFiles[Count].Enabled := StrToBool(Nodes.item[I].selectSingleNode('Enabled/node()').nodeValue) 706 | else 707 | fDeployFiles[Count].Enabled := true; 708 | if Nodes.item[I].selectSingleNode('RemoteDir/node()') <> nil then 709 | fDeployFiles[Count].RemoteDir := Nodes.item[I].selectSingleNode('RemoteDir/node()').nodeValue; 710 | if Nodes.item[I].selectSingleNode('RemoteName/node()') <> nil then 711 | fDeployFiles[Count].RemoteName := Nodes.item[I].selectSingleNode('RemoteName/node()').nodeValue; 712 | 713 | Inc(Count); 714 | end; 715 | SetLength(fDeployFiles, Count); 716 | 717 | // Disable files that are not for the specified Configuration or are duplicated (e.g. both Base and Release configs) 718 | for I := 0 to Length(fDeployFiles) - 1 do 719 | begin 720 | // Check the Base configurations if there is a file with a Release/Debug config and use it, otherwise use the Base 721 | if (fDeployFiles[I].Configuration = 'Base') or fDeployFiles[I].Configuration.IsEmpty then 722 | begin 723 | for J := 0 to Length(fDeployFiles) - 1 do 724 | if (fDeployFiles[I].LocalName = fDeployFiles[J].LocalName) and (fDeployFiles[J].Configuration = fConfig) then 725 | begin 726 | // There is a more detailed config found, so disable the Base 727 | fDeployFiles[I].Enabled := false; 728 | Break; 729 | end; 730 | end else 731 | // Check if the file is for a different non-Base config and disable it 732 | // Second check added because Delphi seems to save incorrect info for some files in the Dproj 733 | // E.g. the ICNS file with class ProjectOSXResource appears only under a Release config, but not under a 734 | // Sandbox config. If Sandbox is selected from the IDE and deployed, the Dproj changes and the ICNS is included 735 | // in Sandbox, but sometimes excluded from Release. No pattern found 736 | // The only solution is not to disable 'standard' Class files, e.g. non user added 737 | if (fDeployFiles[I].Configuration <> fConfig) and (fDeployFiles[I].ClassName = 'File') then 738 | fDeployFiles[I].Enabled := false; 739 | end; 740 | 741 | // Shrink the array to skip the disabled items 742 | Count := 0; 743 | for I := 0 to Length(fDeployFiles) - 1 do 744 | if fDeployFiles[I].Enabled then 745 | begin 746 | fDeployFiles[Count] := fDeployFiles[I]; 747 | Inc(Count); 748 | end; 749 | SetLength(fDeployFiles, Count); 750 | 751 | // Match the above DeployFile and DeployClass entries and build the complete Remote/Local names, dirs, etc 752 | for I := 0 to Length(fDeployFiles) - 1 do 753 | begin 754 | // Find if there is the same DeployClass and copy the attributes from it if they don't exist already 755 | for J := 0 to Length(fDeployClasses) - 1 do 756 | begin 757 | if fDeployFiles[I].ClassName = fDeployClasses[J].Name then 758 | begin 759 | if fDeployFiles[I].RemoteDir.IsEmpty then 760 | fDeployFiles[I].RemoteDir := fDeployClasses[J].RemoteDir; 761 | fDeployFiles[I].Operation := fDeployClasses[J].Operation; 762 | Break; 763 | end; 764 | end; 765 | 766 | // Replace the BDS paths for the Redist files from Embarcadero 767 | fDeployFiles[I].LocalName := fDeployFiles[I].LocalName.Replace('$(BDS)\', fDelphiPath); 768 | 769 | // Finalize the Remote names and dirs 770 | fDeployFiles[I].RemoteDir := IncludeTrailingPathDelimiter(IncludeTrailingPathDelimiter(fProjectRoot) + fDeployFiles[I].RemoteDir); 771 | 772 | if fDeployFiles[I].RemoteName.IsEmpty then 773 | fDeployFiles[I].RemoteName := ExtractFileName(fDeployFiles[I].LocalName); 774 | end; 775 | 776 | if fVerbose then 777 | begin 778 | Writeln('--------------------'); 779 | Writeln('Files to be deployed'); 780 | Writeln('--------------------'); 781 | Writeln('Class Name - Local Name - Remote Name - Remote Dir'); 782 | Writeln('--------------------------------------------------'); 783 | for i := 0 to Length(fDeployFiles)-1 do 784 | Writeln(Format('%10s - %10s - %10s - %10s', 785 | [fDeployFiles[i].ClassName, fDeployFiles[i].LocalName, 786 | fDeployFiles[i].RemoteName, fdeployFiles[i].RemoteDir])); 787 | end; 788 | 789 | 790 | //Change the folder to the binaryFolder is supplied in the command line 791 | if trim(fBinaryFolder)<>'' then 792 | begin 793 | for i := 0 to Length(fDeployFiles)-1 do 794 | if (Trim(fDeployFiles[i].ClassName)<>'DependencyModule') 795 | and (Trim(fDeployFiles[i].ClassName)<>'ProjectOSXResource') then 796 | fDeployFiles[i].LocalName:=TPath.Combine( 797 | IncludeTrailingPathDelimiter(fBinaryFolder), 798 | ExtractFileName(fDeployFiles[i].LocalName)); 799 | 800 | if fVerbose then 801 | begin 802 | Writeln('--------------------'); 803 | Writeln('Files to be deployed (after use of BinaryFolder:'+fBinaryFolder+')'); 804 | Writeln('--------------------'); 805 | Writeln('Class Name - Local Name - Remote Name - Remote Dir'); 806 | Writeln('--------------------------------------------------'); 807 | for i := 0 to Length(fDeployFiles)-1 do 808 | Writeln(Format('%10s - %10s - %10s - %10s', 809 | [fDeployFiles[i].ClassName, fDeployFiles[i].LocalName, 810 | fDeployFiles[i].RemoteName, fdeployFiles[i].RemoteDir])); 811 | Writeln('--------------------------------------------------'); 812 | end; 813 | 814 | end; 815 | 816 | Writeln('Checking files against configuration ('+fConfig+')'); 817 | if fConfig='Debug' then 818 | currExcludeList:=fDebugExcludeFiles; 819 | if fConfig='Release' then 820 | currExcludeList:=fReleaseExcludeFiles; 821 | i:=0; 822 | for tmpDeployFile in fDeployFiles do 823 | begin 824 | tmpstr:=ExtractFileExt(tmpDeployFile.LocalName); 825 | if currExcludeList.Contains(ExtractFileExt(tmpDeployFile.LocalName)) then 826 | begin 827 | Writeln('Ignored: '+tmpDeployFile.LocalName); 828 | fDeployFiles[i]:=fDeployFiles[High(fDeployFiles)]; 829 | if i 2 | 3 | {08741C85-7A9F-4CB4-ACED-76127D3C0E23} 4 | 18.1 5 | None 6 | embdeploy.dpr 7 | True 8 | Release 9 | Win32 10 | 1 11 | Console 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Base 34 | true 35 | 36 | 37 | true 38 | Base 39 | true 40 | 41 | 42 | true 43 | Base 44 | true 45 | 46 | 47 | true 48 | Base 49 | true 50 | 51 | 52 | true 53 | Base 54 | true 55 | 56 | 57 | true 58 | Cfg_1 59 | true 60 | true 61 | 62 | 63 | true 64 | Base 65 | true 66 | 67 | 68 | true 69 | Cfg_2 70 | true 71 | true 72 | 73 | 74 | $(BDS)\bin\delphi_PROJECTICNS.icns 75 | $(BDS)\bin\delphi_PROJECTICON.ico 76 | 2 77 | embdeploy 78 | 80 | true 81 | None 82 | true 83 | 1026 84 | CompanyName=Vladimir Georgiev;FileDescription=Automated deployer for Embarcadero RAD Studio projects;FileVersion=1.2.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=EmbDeploy;ProductVersion=1.0.0.0;Comments= 85 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 86 | .\$(Platform) 87 | .\$(Platform) 88 | false 89 | false 90 | false 91 | false 92 | false 93 | 94 | 95 | true 96 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png 97 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png 98 | false 99 | $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png 100 | true 101 | true 102 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png 103 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png 104 | true 105 | true 106 | $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png 107 | true 108 | android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar 109 | Debug 110 | true 111 | true 112 | true 113 | true 114 | $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png 115 | $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png 116 | $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png 117 | package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= 118 | 119 | 120 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 121 | iPhoneAndiPad 122 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 123 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 124 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 125 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 126 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 127 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 128 | $(MSBuildProjectName) 129 | Debug 130 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 131 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 132 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 133 | CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= 134 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 135 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 136 | 137 | 138 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 139 | iPhoneAndiPad 140 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 141 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 142 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 143 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 144 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 145 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 146 | $(MSBuildProjectName) 147 | Debug 148 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 149 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 150 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 151 | CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= 152 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 153 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 154 | 155 | 156 | iPhoneAndiPad 157 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png 158 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png 159 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png 160 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png 161 | $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png 162 | $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png 163 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png 164 | $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png 165 | $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png 166 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png 167 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png 168 | CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= 169 | $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png 170 | 171 | 172 | FlexCel_Core;DBXSqliteDriver;fmx;IndySystem;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;DbxCommonDriver;dbxcds;IcsFmxDXE4Run;DBXOracleDriver;CustomIPTransport;dsnap;IndyIPServer;fmxase;IndyCore;IndyIPCommon;CloudService;FmxTeeUI;FMX_FlexCel_Core;inetdbxpress;bindcompfmx;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;FlexCel_Render;xmlrtl;ibxpress;IndyProtocols;DBXMySQLDriver;bindengine;soaprtl;bindcompdbx;FMXTee;FlexCel_Pdf;FMX_FlexCel_Components;IcsCommonDXE4Run;DBXInformixDriver;DBXFirebirdDriver;inet;fmxobj;DBXSybaseASADriver;fmxdae;FlexCel_XlsAdapter;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 173 | true 174 | 175 | 176 | .\ 177 | false 178 | 179 | FlexCel_Core;DBXSqliteDriver;frxDB18;FMXfrx18;fmx;IndySystem;TeeDB;frx18;inetdbbde;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;unidacvcl180;DataSnapProviderClient;VCL_FlexCel_Components_DESIGN;DBXSybaseASEDriver;IcsVclDXE4Run;DbxCommonDriver;VCL_FlexCel_Components;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;IcsFmxDXE4Run;dacvcl180;DBXDb2Driver;OmniThreadLibraryRuntimeXE3;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;FMXfs18;IndyIPCommon;CloudService;DBXMSSQLDriver;FmxTeeUI;CodeSiteExpressPkg;FMX_FlexCel_Core;TMSFMXPackPkgDXE4;inetdbxpress;webdsnap;FMX_FlexCel_Components_DESIGN;FMXfrxe18;unidac180;adortl;madBasic_;FMXfrxTee18;bindcompfmx;tmsdXE4;FMXfrxDB18;vcldbx;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;Tee;DBXOdbcDriver;FlexCel_Render;madDisAsm_;xmlrtl;svnui;ibxpress;IndyProtocols;DBXMySQLDriver;frxe18;vclactnband;bindengine;soaprtl;bindcompdbx;FMXTee;TeeUI;bindcompvcl;VCL_FlexCel_Core;FlexCel_Pdf;vclie;FMX_FlexCel_Components;IcsCommonDXE4Run;vcltouch;madExcept_;tmsexdXE4;crcontrols180;FMXfsDB18;VclSmp;DBXInformixDriver;Intraweb;DataSnapConnectors;dsnapcon;DBXFirebirdDriver;tmsxlsdXE4;inet;fmxobj;dac180;vclx;svn;DBXSybaseASADriver;FMXfsTee18;fmxdae;FlexCel_XlsAdapter;bdertl;VirtualTreesR;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 180 | 1033 181 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 182 | true 183 | 184 | 185 | false 186 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 187 | 1033 188 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 189 | 190 | 191 | DEBUG;$(DCC_Define) 192 | true 193 | false 194 | true 195 | true 196 | true 197 | 198 | 199 | C:\Sistemas\PromedicoMac\trunk\ 200 | CompanyName=Vladimir Georgiev;FileDescription=Automated deployer for Embarcadero RAD Studio projects;FileVersion=1.2.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=EmbDeploy;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=08/08/2017 09:38:49 201 | 3 202 | -delphiver "18.0" -registerFolder "C:\Sistemas\PromedicoMac\trunk\OSX32_Build" -bundle PromedicoMac.zip -Config Release -deploy "C:\Sistemas\PromedicoMac\trunk\PromedicoMac.dproj" 203 | 1033 204 | false 205 | 206 | 207 | false 208 | RELEASE;$(DCC_Define) 209 | 0 210 | 0 211 | 212 | 213 | .\ 214 | -deploy C:\Sistemas\PromedicoMac\trunk\PromedicoMac.deployproj 215 | 3 216 | true 217 | 2 218 | CompanyName=Vladimir Georgiev;FileDescription=Automated deployer for Embarcadero RAD Studio projects;FileVersion=1.3.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=EmbDeploy;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=08/08/2017 09:42:35 219 | 3 220 | 1033 221 | 222 | 223 | 224 | MainSource 225 | 226 | 227 | 228 | 229 | Cfg_2 230 | Base 231 | 232 | 233 | Base 234 | 235 | 236 | Cfg_1 237 | Base 238 | 239 | 240 | 241 | Delphi.Personality.12 242 | 243 | 244 | 245 | 246 | embdeploy.dpr 247 | 248 | 249 | False 250 | False 251 | 1 252 | 0 253 | 0 254 | 0 255 | False 256 | False 257 | False 258 | False 259 | False 260 | 1026 261 | 1251 262 | 263 | 264 | 265 | 266 | 1.0.0.0 267 | 268 | 269 | 270 | 271 | 272 | 1.0.0.0 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | LiveBinding Expression Components FireDac 291 | LiveBindings Expression Components DbExpress 292 | Microsoft Office 2000 Sample Automation Server Wrapper Components 293 | Microsoft Office XP Sample Automation Server Wrapper Components 294 | 295 | 296 | 297 | 298 | 299 | true 300 | 301 | 302 | 303 | 304 | true 305 | 306 | 307 | 308 | 309 | true 310 | 311 | 312 | 313 | 314 | true 315 | 316 | 317 | 318 | 319 | true 320 | 321 | 322 | 323 | 324 | embdeploy.exe 325 | true 326 | 327 | 328 | 329 | 330 | 0 331 | .dll;.bpl 332 | 333 | 334 | 1 335 | .dylib 336 | 337 | 338 | 339 | 340 | Contents\Resources 341 | 1 342 | 343 | 344 | 345 | 346 | classes 347 | 1 348 | 349 | 350 | 351 | 352 | res\drawable-xxhdpi 353 | 1 354 | 355 | 356 | 357 | 358 | Contents\MacOS 359 | 0 360 | 361 | 362 | 1 363 | 364 | 365 | 366 | 367 | library\lib\mips 368 | 1 369 | 370 | 371 | 372 | 373 | 1 374 | 375 | 376 | 1 377 | 378 | 379 | 1 380 | 381 | 382 | 383 | 384 | 0 385 | 386 | 387 | 1 388 | 389 | 390 | 1 391 | 392 | 393 | 1 394 | 395 | 396 | library\lib\armeabi-v7a 397 | 1 398 | 399 | 400 | 1 401 | 402 | 403 | 404 | 405 | 0 406 | 407 | 408 | 1 409 | .framework 410 | 411 | 412 | 413 | 414 | 1 415 | 416 | 417 | 1 418 | 419 | 420 | 1 421 | 422 | 423 | 424 | 425 | library\lib\x86 426 | 1 427 | 428 | 429 | 430 | 431 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 432 | 1 433 | 434 | 435 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 436 | 1 437 | 438 | 439 | 440 | 441 | 1 442 | 443 | 444 | 1 445 | 446 | 447 | 1 448 | 449 | 450 | 451 | 452 | 1 453 | 454 | 455 | 1 456 | 457 | 458 | 1 459 | 460 | 461 | 462 | 463 | 464 | library\lib\armeabi 465 | 1 466 | 467 | 468 | 469 | 470 | 0 471 | 472 | 473 | 1 474 | 475 | 476 | 1 477 | 478 | 479 | 480 | 481 | 1 482 | 483 | 484 | 1 485 | 486 | 487 | 1 488 | 489 | 490 | 491 | 492 | res\drawable-normal 493 | 1 494 | 495 | 496 | 497 | 498 | res\drawable-xhdpi 499 | 1 500 | 501 | 502 | 503 | 504 | res\drawable-large 505 | 1 506 | 507 | 508 | 509 | 510 | 1 511 | 512 | 513 | 1 514 | 515 | 516 | 1 517 | 518 | 519 | 520 | 521 | 522 | library\lib\armeabi-v7a 523 | 1 524 | 525 | 526 | 527 | 528 | res\drawable-hdpi 529 | 1 530 | 531 | 532 | 533 | 534 | 535 | 536 | 1 537 | 538 | 539 | 1 540 | 541 | 542 | 1 543 | 544 | 545 | 546 | 547 | res\values 548 | 1 549 | 550 | 551 | 552 | 553 | res\drawable-small 554 | 1 555 | 556 | 557 | 558 | 559 | res\drawable 560 | 1 561 | 562 | 563 | 564 | 565 | 1 566 | 567 | 568 | 1 569 | 570 | 571 | 1 572 | 573 | 574 | 575 | 576 | 1 577 | 578 | 579 | 580 | 581 | res\drawable 582 | 1 583 | 584 | 585 | 586 | 587 | 0 588 | 589 | 590 | 0 591 | 592 | 593 | 0 594 | 595 | 596 | 0 597 | 598 | 599 | 0 600 | 601 | 602 | 0 603 | 604 | 605 | 606 | 607 | library\lib\armeabi-v7a 608 | 1 609 | 610 | 611 | 612 | 613 | 0 614 | .bpl 615 | 616 | 617 | 1 618 | .dylib 619 | 620 | 621 | 1 622 | .dylib 623 | 624 | 625 | 1 626 | .dylib 627 | 628 | 629 | 1 630 | .dylib 631 | 632 | 633 | 634 | 635 | res\drawable-mdpi 636 | 1 637 | 638 | 639 | 640 | 641 | res\drawable-xlarge 642 | 1 643 | 644 | 645 | 646 | 647 | res\drawable-ldpi 648 | 1 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | False 663 | False 664 | False 665 | False 666 | False 667 | True 668 | False 669 | 670 | 671 | 12 672 | 673 | 674 | 675 | 676 | 677 | 678 | False 679 | 680 | False 681 | copy /Y $(OUTPUTPATH) $(WINDIR) 682 | 683 | False 684 | 685 | 686 | 687 | False 688 | 689 | False 690 | copy /Y $(OUTPUTPATH) $(WINDIR) 691 | 692 | False 693 | 694 | 695 | 696 | False 697 | 698 | False 699 | copy /Y $(OUTPUTPATH) $(WINDIR) 700 | 701 | False 702 | 703 | 704 | 705 | False 706 | 707 | False 708 | copy /Y $(OUTPUTPATH) $(WINDIR) 709 | 710 | False 711 | 712 | 713 | 714 | False 715 | 716 | False 717 | copy /Y $(OUTPUTPATH) $(WINDIR) 718 | 719 | False 720 | 721 | 722 | 723 | False 724 | 725 | False 726 | 727 | True 728 | 729 | 730 | 731 | False 732 | 733 | False 734 | copy /Y $(OUTPUTPATH) $(WINDIR) 735 | 736 | False 737 | 738 | 739 | 740 | False 741 | 742 | False 743 | copy /Y $(OUTPUTPATH) $(WINDIR) 744 | 745 | False 746 | 747 | 748 | 749 | False 750 | 751 | False 752 | copy /Y $(OUTPUTPATH) $(WINDIR) 753 | 754 | False 755 | 756 | 757 | 758 | False 759 | 760 | False 761 | copy /Y $(OUTPUTPATH) $(WINDIR) 762 | 763 | False 764 | 765 | 766 | 767 | False 768 | 769 | False 770 | copy /Y $(OUTPUTPATH) $(WINDIR) 771 | 772 | False 773 | 774 | 775 | 776 | False 777 | 778 | False 779 | copy /Y $(OUTPUTPATH) $(WINDIR) 780 | 781 | False 782 | 783 | 784 | 785 | False 786 | 787 | False 788 | 789 | True 790 | 791 | 792 | 793 | False 794 | 795 | False 796 | copy /Y $(OUTPUTPATH) $(WINDIR) 797 | 798 | False 799 | 800 | 801 | 802 | 815 | --------------------------------------------------------------------------------