Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DelphiWorlds/9043739d2e4629dfa3df8a17723a03a3 to your computer and use it in GitHub Desktop.
Save DelphiWorlds/9043739d2e4629dfa3df8a17723a03a3 to your computer and use it in GitHub Desktop.
Source used in Codex for building a jar from Java source
unit Codex.Wizard.Android.CreateJarProcess;
interface
uses
System.Classes,
DW.RunProcess.Win;
type
TJarProcessStep = (None, BuildJava, BuildJar, DexJar);
TCreateJarProcess = class(TRunProcess)
private
FAPILevelPath: string;
FCurrentDir: string;
FDebugConfig: Boolean;
FDexPath: string;
FIncludedJars: TStrings;
FIsError: Boolean;
FJarFilename: string;
FJavaFiles: TStrings;
FJavaSourceFiles: TStrings;
FJavaSourcesFileName: string;
FJDKPath: string;
FShouldDex: Boolean;
FShouldRetainWorkingFiles: Boolean;
FSourceVersion: string;
FStep: TJarProcessStep;
FTargetVersion: string;
FWorkingDir: string;
FWorkingPath: string;
function BuildJava: Boolean;
procedure BuildJar;
function CheckRequirements: Boolean;
procedure Cleanup;
procedure DexJar;
function GetJavaFiles: Boolean;
protected
procedure DoTerminated(const AExitCode: Cardinal); override;
public
constructor Create;
destructor Destroy; override;
function Run: Boolean; override;
property APILevelPath: string read FAPILevelPath write FAPILevelPath;
property DebugConfig: Boolean read FDebugConfig write FDebugConfig;
property DexPath: string read FDexPath write FDexPath;
property IncludedJars: TStrings read FIncludedJars;
property JarFilename: string read FJarFilename write FJarFilename;
property JavaSourceFiles: TStrings read FJavaSourceFiles;
property JDKPath: string read FJDKPath write FJDKPath;
property ShouldDex: Boolean read FShouldDex write FShouldDex;
property ShouldRetainWorkingFiles: Boolean read FShouldRetainWorkingFiles write FShouldRetainWorkingFiles;
property SourceVersion: string read FSourceVersion write FSourceVersion;
property TargetVersion: string read FTargetVersion write FTargetVersion;
property OnProcessOutput;
end;
implementation
uses
System.IOUtils, System.SysUtils, System.Math,
DW.IOUtils.Helpers;
// %JDK_PATH%\javac -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar;%BDS_ANDROID%\fmx.jar;%BDS_ANDROID%\android-support-v4.jar -d %OUTPUT_DIR%\classes ^
// %JDK_PATH%\jar cf %OUTPUT_FILE% -C %OUTPUT_DIR%\classes com
const
// Variables, in this order: FJDKPath, FAndroidPlatformPath, Included Jars, FWorkingOutputPath, FTargetVersion, FSourceVersion, FJavaSourcesFileName
cJavaCompilerCommand = '%s\javac'; // -nowarn
cJavaCompilerParams = '-Xlint:deprecation -classpath "%s\android.jar;%s" -d "%s\classes" -target %s -source %s @"%s"'; // -verbose
cJavaCompileTimeout = 30000;
// Variables, in this order: FJDKPath, FJarFilename, LWorkingOutputPath
cJarBuildCommand = '%s\jar';
// Jar file output, classes dir options
cJarBuildParams = 'cvf %s %s';
cJarClassDirOptionParam = '-C %s\classes %s';
cJarBuildTimeout = 15000;
cDexCommand = '%s\dx.bat';
// e.g. --dex --output=bin\release\fmx.dex.jar --positions=lines bin\release\fmx.jar
cDexParams = '--dex --output="%s" --positions=lines "%s"';
type
TStringsHelper = class helper for TStrings
public
function CombineText(const ASeparator: string; const AQuoted: Boolean): string;
end;
{ TStringsHelper }
function TStringsHelper.CombineText(const ASeparator: string; const AQuoted: Boolean): string;
var
I: Integer;
begin
Result := '';
for I := 0 to Count - 1 do
begin
if I > 0 then
Result := Result + ASeparator;
if AQuoted then
Result := Result + '"' + Strings[I] + '"'
else
Result := Result + Strings[I];
end;
end;
{ TCreateJarProcess }
constructor TCreateJarProcess.Create;
begin
inherited;
FSourceVersion := '1.7';
FTargetVersion := '1.7';
FIncludedJars := TStringList.Create;
FJavaSourceFiles := TStringList.Create;
FJavaFiles := TStringList.Create;
end;
destructor TCreateJarProcess.Destroy;
begin
FIncludedJars.Free;
FJavaSourceFiles.Free;
FJavaFiles.Free;
inherited;
end;
function TCreateJarProcess.GetJavaFiles: Boolean;
var
I: Integer;
LFile, LFolder, LFolderFolder, LCommonFolder: string;
begin
FWorkingDir := '';
FJavaFiles.Clear;
for I := 0 to FJavaSourceFiles.Count - 1 do
begin
if FJavaSourceFiles[I].EndsWith('*.java') then
begin
for LFile in TDirectory.GetFiles(TPath.GetDirectoryName(FJavaSourceFiles[I]), '*.java', TSearchOption.soAllDirectories) do
FJavaFiles.Add(LFile);
end
else if TFile.Exists(FJavaSourceFiles[I]) then
FJavaFiles.Add(JavaSourceFiles[I]);
end;
if FJavaFiles.Count > 0 then
LCommonFolder := TPath.GetDirectoryName(FJavaFiles[0]);
if FJavaFiles.Count > 1 then
begin
for I := 1 to FJavaFiles.Count - 1 do
begin
LFolder := TPath.GetDirectoryName(FJavaFiles[I]);
while not LFolder.IsEmpty and not LCommonFolder.StartsWith(LFolder) do
begin
LFolderFolder := TPath.GetDirectoryName(LFolder);
if not LFolder.Equals(LFolderFolder) then
LFolder := LFolderFolder
else
LFolder := '';
end;
if not LFolder.IsEmpty then
LCommonFolder := LFolder;
end;
end;
for I := FJavaFiles.Count - 1 downto 0 do
begin
if FJavaFiles[I].StartsWith(LCommonFolder) then
FJavaFiles[I] := FJavaFiles[I].Substring(Length(LCommonFolder) + 1)
else
FJavaFiles.Delete(I);
end;
Result := FJavaFiles.Count > 0;
if Result then
FWorkingDir := LCommonFolder;
end;
function TCreateJarProcess.CheckRequirements: Boolean;
begin
if GetJavaFiles then
begin
FWorkingPath := MakeWorkingPath;
FJavaSourcesFileName := TPath.Combine(FWorkingPath, 'JavaSources.txt');
FJavaFiles.SaveToFile(FJavaSourcesFileName);
Result := True;
end
else
begin
DoOutput('No java files have been specified, or they do not share a common folder');
Result := False;
end;
end;
function TCreateJarProcess.Run: Boolean;
begin
Result := False;
FIsError := False;
FStep := TJarProcessStep.None;
if CheckRequirements then
Result := BuildJava;
end;
function TCreateJarProcess.BuildJava: Boolean;
var
LCmd, LParams, LClassesPath: string;
begin
Result := False;
FCurrentDir := TDirectory.GetCurrentDirectory;
TDirectory.SetCurrentDirectory(FWorkingDir);
FStep := TJarProcessStep.BuildJava;
LClassesPath := TPath.Combine(FWorkingPath, 'classes');
ForceDirectories(LClassesPath);
if TDirectory.Exists(LClassesPath) then
begin
LCmd := Format(cJavaCompilerCommand, [FJDKPath]);
LParams := Format(cJavaCompilerParams, [FAPILevelPath, FIncludedJars.CombineText(';', False), FWorkingPath, FTargetVersion, FSourceVersion,
FJavaSourcesFileName]);
Process.CommandLine := LCmd + ' ' + LParams;
DoOutput('Compiling Java sources..');
DoOutput(Format('Executing: %s', [Process.CommandLine]));
Result := InternalRun;
end
else
DoOutput('Unable to create folder: ' + LClassesPath);
end;
procedure TCreateJarProcess.BuildJar;
var
LCmd, LParams, LClassesOptions: string;
LSubfolders: TArray<string>;
I: Integer;
begin
FStep := TJarProcessStep.BuildJar;
LCmd := Format(cJarBuildCommand, [FJDKPath]);
LSubfolders := TDirectory.GetDirectories(TPath.Combine(FWorkingPath, 'classes'), '*.*', TSearchOption.soTopDirectoryOnly);
if Length(LSubfolders) > 0 then
begin
for I := 0 to Length(LSubfolders) - 1 do
LSubfolders[I] := Format(cJarClassDirOptionParam, [FWorkingPath, TPath.GetFileName(LSubFolders[I])]);
LClassesOptions := string.Join(' ', LSubfolders);
LParams := Format(cJarBuildParams, [FJarFilename, LClassesOptions]);
Process.CommandLine := LCmd + ' ' + LParams;
DoOutput(Format('Building %s..', [TPath.GetFileName(FJarFilename)]));
DoOutput(Format('Command line: %s', [Process.CommandLine]));
InternalRun;
end
else
DoOutput('Unable to determine any subfolders under classes. Leaving working path intact: ' + FWorkingPath);
end;
procedure TCreateJarProcess.DexJar;
var
LCmd, LParams: string;
begin
FStep := TJarProcessStep.DexJar;
LCmd := Format(cDexCommand, [FDexPath]);
LParams := Format(cDexParams, [TPath.ChangeExtension(FJarFilename, '.dex.jar'), FJarFilename]);
DoOutput(Format('Dexing %s..', [TPath.GetFileName(FJarFilename)]));
Process.CommandLine := LCmd + ' ' + LParams;
InternalRun;
end;
procedure TCreateJarProcess.DoTerminated(const AExitCode: Cardinal);
begin
FIsError := FIsError or (AExitCode <> 0);
case FStep of
TJarProcessStep.BuildJava:
begin
TDirectory.SetCurrentDirectory(FCurrentDir);
if FIsError then
begin
DoOutput(Format('Compile failed with exit code: %d', [AExitCode]));
Cleanup;
end
else
BuildJar;
end;
TJarProcessStep.BuildJar:
begin
if not FIsError then
begin
DoOutput(Format('Successfully built: %s', [FJarFilename]));
if FShouldDex then
DexJar
else
Cleanup;
end
else
begin
DoOutput(Format('Build failed with exit code: %d', [AExitCode]));
Cleanup;
end;
inherited;
end;
TJarProcessStep.DexJar:
begin
if not FIsError then
DoOutput(Format('Successfully dexed: %s', [FJarFilename]))
else
DoOutput(Format('Dex-ing failed with exit code: %d', [AExitCode]));
Cleanup;
inherited;
end;
end;
end;
procedure TCreateJarProcess.Cleanup;
var
LWorkingPathMessage: string;
begin
LWorkingPathMessage := '';
if not FShouldRetainWorkingFiles then
TDirectory.Delete(FWorkingPath, True)
else
LWorkingPathMessage :=' Left working path intact: ' + FWorkingPath;
if FIsError then
DoOutput('***** Process failed *****' + LWorkingPathMessage)
else
DoOutput('* Process completed! *' + LWorkingPathMessage);
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment