Created
March 25, 2023 22:30
-
-
Save DelphiWorlds/9043739d2e4629dfa3df8a17723a03a3 to your computer and use it in GitHub Desktop.
Source used in Codex for building a jar from Java source
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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