Skip to content

Commit 178770f

Browse files
committed
Fixed Android build with gestalt-di.
1 parent e9656c6 commit 178770f

File tree

2 files changed

+97
-121
lines changed

2 files changed

+97
-121
lines changed

build.gradle

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ dependencies {
7171
}
7272
// Android-compatible JOML variant
7373
implementation "org.joml:joml-jdk3:1.9.25"
74-
implementation(group: 'org.terasology.gestalt', name: 'gestalt-android', version: '7.2.0-SNAPSHOT')
74+
implementation(group: 'org.terasology.gestalt', name: 'gestalt-android', version: gestaltVersion)
7575
// TODO: Needed for gestalt because of an internal dependency but since that dependency is never
7676
// exposed in a public API, I have no idea why it's needed for compilation.
7777
implementation "com.github.zafarkhaja:java-semver:0.10.0"
@@ -89,8 +89,14 @@ dependencies {
8989
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86"
9090
natives "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-x86_64"
9191

92+
// Add reflections purely for NUI. NUI uses the ReflectionUtils class from it.
93+
implementation "org.terasology:reflections:0.9.12-MB"
94+
9295
// Android-compatible logging
9396
implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.25'
97+
98+
// Backport some Java 8 APIs like java.time for gestalt
99+
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
94100
}
95101

96102
ext {
@@ -127,6 +133,9 @@ android {
127133

128134
// Make it clear we're compiling for Java 8
129135
compileOptions {
136+
// Backport some Java 8 APIs like java.time for gestalt
137+
coreLibraryDesugaringEnabled true
138+
130139
sourceCompatibility JavaVersion.VERSION_1_8
131140
targetCompatibility JavaVersion.VERSION_1_8
132141
}
@@ -153,6 +162,15 @@ android {
153162
abortOnError false
154163
}
155164

165+
packagingOptions {
166+
merge 'META-INF/annotations/*'
167+
merge 'META-INF/subtypes/*'
168+
merge 'META-INF/services/*'
169+
170+
// The contents of the "gestalt-indexes-present" file is never read, so just pick any one of them
171+
pickFirst 'META-INF/gestalt-indexes-present'
172+
}
173+
156174
defaultConfig {
157175
targetSdkVersion(project.ext.targetSdk)
158176
minSdkVersion(project.ext.minSdk)
@@ -225,9 +243,9 @@ def deleteDir(File dir) {
225243
dir.delete()
226244
}
227245

228-
task modulesReflections
246+
task modulesJar
229247
rootProject.destinationSolModules().each { module ->
230-
modulesReflections.dependsOn ":modules:$module.name" + ":cacheReflections"
248+
modulesJar.dependsOn ":modules:$module.name" + ":jar"
231249
}
232250

233251
task exportModules() {
@@ -243,8 +261,7 @@ task exportModules() {
243261

244262
outputs.dir("$projectDir/assets/modules")
245263

246-
dependsOn ":engine:cacheReflections"
247-
dependsOn modulesReflections
264+
dependsOn modulesJar
248265

249266
doLast {
250267
dexModules()
@@ -277,7 +294,6 @@ task exportModules() {
277294
Files.createSymbolicLink(Paths.get("$projectDir/assets/modules/${module.name}/module.json"), Paths.get("$rootDir/modules/${module.name}/module.json"))
278295

279296
file("$projectDir/assets/modules/${module.name}/build/classes").mkdirs()
280-
Files.createSymbolicLink(Paths.get("$projectDir/assets/modules/${module.name}/build/classes/reflections.cache"), Paths.get("$rootDir/modules/${module.name}/build/classes/reflections.cache"))
281297
Files.createSymbolicLink(Paths.get("$projectDir/assets/modules/${module.name}/build/dexes"), Paths.get("$rootDir/modules/${module.name}/build/dexes"))
282298
}
283299
} else {
@@ -297,8 +313,7 @@ task exportModules() {
297313
include "${module.name}/assets/**"
298314
include "${module.name}/overrides/**"
299315
include "${module.name}/deltas/**"
300-
include "${module.name}/build/dexes/*.dex"
301-
include "${module.name}/build/classes/reflections.cache"
316+
include "${module.name}/build/dexes/*.jar"
302317
}
303318
}
304319
}
@@ -326,9 +341,9 @@ def dexModules() {
326341
}
327342
def dex = "${buildToolsVersions[0]}/$dexCommand"
328343
for (module in rootProject.destinationSolModules()) {
329-
def moduleClassesDir = "${rootProject.projectDir}/modules/${module.name}/build/classes/"
344+
def moduleClassesDir = file("${rootProject.projectDir}/modules/${module.name}/build/classes/")
330345
def moduleClassesFiles = fileTree(moduleClassesDir).filter { it.isFile() && it.name.endsWith('.class')}.files
331-
def classesRootPath = new File(moduleClassesDir).toPath()
346+
def classesRootPath = moduleClassesDir.toPath()
332347
def moduleClasses = []
333348
for (file in moduleClassesFiles) {
334349
moduleClasses.add(classesRootPath.relativize(file.toPath()))
@@ -339,12 +354,30 @@ def dexModules() {
339354
}
340355
def moduleDexesDir = "${rootProject.projectDir}/modules/${module.name}/build/dexes/"
341356
mkdir(moduleDexesDir)
357+
def jarOutputPath = "$moduleDexesDir/${module.name}.jar"
342358
exec {
343359
workingDir moduleClassesDir
344360
commandLine (["$dex"] + moduleClasses + ['--classpath', "$rootDir/engine/build/classes",
345361
'--lib', "$path/platforms/android-$compileSdk/android.jar",
346362
'--min-api', "$minSdk",
347-
'--output', "$moduleDexesDir"])
363+
'--output', "$jarOutputPath"])
364+
}
365+
FileSystem jarFileSystem
366+
try {
367+
jarFileSystem = FileSystems.newFileSystem(URI.create("jar:" + new File(jarOutputPath).toURI()), new HashMap<String, Object>())
368+
fileTree(moduleClassesDir).filter {
369+
it.isFile() && !it.name.endsWith(".class") && !it.path.startsWith("assets/")
370+
}.each {
371+
def destinationPath = jarFileSystem.getPath("/${moduleClassesDir.relativePath(it)}")
372+
Files.createDirectories(destinationPath.parent)
373+
Files.copy(it.toPath(), destinationPath)
374+
}
375+
} catch (Exception e) {
376+
e.printStackTrace()
377+
} finally {
378+
if (jarFileSystem != null) {
379+
jarFileSystem.close()
380+
}
348381
}
349382
}
350383
}

src/com/miloshpetrov/sol2/android/SolAndroid.java

Lines changed: 53 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -6,147 +6,90 @@
66

77
import com.badlogic.gdx.backends.android.AndroidApplication;
88
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
9-
import com.google.common.base.Charsets;
10-
import com.google.common.io.ByteStreams;
11-
import org.destinationsol.Const;
9+
import com.google.common.reflect.Reflection;
1210
import org.destinationsol.SolApplication;
13-
import org.destinationsol.modules.ModuleManager;
11+
import org.destinationsol.modules.FacadeModuleConfig;
12+
import org.terasology.context.Lifetime;
13+
import org.terasology.gestalt.android.AndroidAssetsFileSource;
1414
import org.terasology.gestalt.android.AndroidModuleClassLoader;
15+
import org.terasology.gestalt.android.AndroidModulePathScanner;
16+
import org.terasology.gestalt.di.ServiceRegistry;
17+
import org.terasology.gestalt.di.index.UrlClassIndex;
18+
import org.terasology.gestalt.module.Module;
19+
import org.terasology.gestalt.module.ModuleEnvironment;
20+
import org.terasology.gestalt.module.ModuleMetadataJsonAdapter;
21+
import org.terasology.gestalt.module.ModulePathScanner;
1522

1623
import java.io.File;
17-
import java.io.FileInputStream;
18-
import java.io.FileOutputStream;
19-
import java.io.IOException;
2024
import java.io.InputStream;
2125
import java.io.InputStreamReader;
22-
import java.io.OutputStreamWriter;
26+
import java.util.Collections;
2327

2428
public class SolAndroid extends AndroidApplication {
2529
@Override
2630
public void onCreate(Bundle savedInstanceState) {
2731
super.onCreate(savedInstanceState);
2832
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
2933

30-
File filesDir = getFilesDir();
31-
copyModulesToDataDir(filesDir);
32-
33-
// Android does not allow changing the system security policy.
34-
// Modules should still be restricted via classpath filtering though.
35-
ModuleManager manager = new ModuleManager(new File(filesDir, "modules"), false,
36-
(module, parent, permissionProvider) -> AndroidModuleClassLoader.create(module, parent, permissionProvider, getCodeCacheDir()));
37-
38-
try {
39-
manager.init();
40-
} catch (Exception e) {
41-
Log.e("DESTINATION_SOL_INIT", "Failed to initialise ModuleManager.");
42-
}
43-
4434
try {
45-
initialize(new SolApplication(manager, 60.0f), config);
35+
initialize(new SolApplication(60.0f, new AndroidServices(getAssets(), getCodeCacheDir())), config);
4636
} catch (Exception e) {
4737
Log.e("DESTINATION_SOL", "FATAL ERROR: Forced abort!", e);
4838
}
4939
}
5040

51-
private void clearCachedModules(File modulesDir) {
52-
File[] files = modulesDir.listFiles();
53-
if (files != null) {
54-
for (File file : modulesDir.listFiles()) {
55-
clearCachedModules(file);
56-
}
41+
private static class AndroidServices extends ServiceRegistry {
42+
public AndroidServices(AssetManager assets, File codeCacheDir) {
43+
this.with(FacadeModuleConfig.class).lifetime(Lifetime.Singleton).use(() -> new AndroidModuleConfig(assets, codeCacheDir));
44+
this.with(ModulePathScanner.class).lifetime(Lifetime.Singleton).use(() -> new AndroidModulePathScanner(assets, codeCacheDir));
5745
}
58-
59-
modulesDir.delete();
6046
}
6147

62-
private void copyModulesToDataDir(File dataDir) {
63-
File assetVersionFile = new File(dataDir, "version.txt");
64-
String versionString = "";
65-
if (assetVersionFile.exists()) {
66-
try (FileInputStream inputStream = new FileInputStream(assetVersionFile)) {
67-
try (InputStreamReader streamReader = new InputStreamReader(inputStream, Charsets.UTF_8)) {
68-
StringBuilder builder = new StringBuilder();
69-
char[] buffer = new char[32];
70-
while (streamReader.read(buffer) != -1) {
71-
builder.append(buffer);
72-
}
73-
versionString = builder.toString();
74-
} catch (Exception e) {
75-
e.printStackTrace();
76-
}
77-
} catch (Exception e) {
78-
e.printStackTrace();
79-
}
80-
}
81-
82-
boolean refreshCache = (!versionString.equals(Const.VERSION) || com.miloshpetrov.sol2.android.BuildConfig.DEBUG);
48+
private static class AndroidModuleConfig implements FacadeModuleConfig {
49+
private final AssetManager assets;
50+
private final File codeCacheDir;
8351

84-
if (refreshCache) {
85-
try (FileOutputStream stream = new FileOutputStream(assetVersionFile)) {
86-
try (OutputStreamWriter writer = new OutputStreamWriter(stream)) {
87-
writer.write(Const.VERSION);
88-
} catch (Exception e) {
89-
e.printStackTrace();
90-
}
91-
} catch (Exception e) {
92-
e.printStackTrace();
93-
}
94-
95-
clearCachedModules(new File(dataDir, "modules"));
52+
public AndroidModuleConfig(AssetManager assets, File codeCacheDir) {
53+
this.assets = assets;
54+
this.codeCacheDir = codeCacheDir;
9655
}
9756

98-
copyModules(dataDir, "modules", refreshCache);
99-
100-
File musicFolder = new File(dataDir, "music");
101-
File soundFolder = new File(dataDir, "sound");
102-
if (!musicFolder.exists()) {
103-
musicFolder.mkdir();
57+
@Override
58+
public File getModulesPath() {
59+
return new File("modules");
10460
}
10561

106-
if (!soundFolder.exists()) {
107-
soundFolder.mkdir();
62+
// Android does not allow changing the system security policy.
63+
// Modules should still be restricted via classpath filtering though.
64+
@Override
65+
public boolean useSecurityManager() {
66+
return false;
10867
}
109-
}
11068

111-
private void copyModules(File dataDir, String rootDir, boolean replaceFiles) {
112-
AssetManager assets = getAssets();
113-
try {
114-
String[] filesToCopy = assets.list(rootDir);
115-
for (String fileToCopy : filesToCopy) {
116-
String filePath = rootDir + "/" + fileToCopy;
117-
File file = new File(dataDir + "/" + rootDir, fileToCopy);
118-
file.mkdirs();
119-
if (assets.list(filePath).length > 0) {
120-
// File is a directory
121-
if (!file.exists()) {
122-
file.mkdirs();
123-
}
124-
copyModules(dataDir, filePath, replaceFiles);
125-
} else {
126-
if (file.exists() && replaceFiles) {
127-
// Replace old copies with newer ones
128-
file.delete();
129-
}
69+
@Override
70+
public Module createEngineModule() {
71+
try {
72+
InputStream engineModuleMetadataStream = assets.open("engine/module.json");
73+
return new Module(new ModuleMetadataJsonAdapter().read(new InputStreamReader(engineModuleMetadataStream)),
74+
new AndroidAssetsFileSource(assets, "engine"),
75+
Collections.emptyList(), UrlClassIndex.byClassLoaderPrefix("org.destinationsol"), x -> {
76+
String classPackageName = Reflection.getPackageName(x);
77+
return "org.destinationsol".equals(classPackageName) || classPackageName.startsWith("org.destinationsol.");
78+
});
79+
} catch (Exception e) {
80+
Log.e("DestinationSol", "Error loading engine module!");
81+
return null;
82+
}
83+
}
13084

131-
file.createNewFile();
85+
@Override
86+
public ModuleEnvironment.ClassLoaderSupplier getClassLoaderSupplier() {
87+
return (module, parent, permissionProvider) -> AndroidModuleClassLoader.create(module, parent, permissionProvider, codeCacheDir);
88+
}
13289

133-
byte[] buffer = new byte[512];
134-
try (InputStream inputStream = assets.open(filePath)) {
135-
try (FileOutputStream outputStream = new FileOutputStream(file)) {
136-
ByteStreams.copy(inputStream, outputStream);
137-
} catch (Exception e) {
138-
Log.e("DESTINATION_SOL", "", e);
139-
e.printStackTrace();
140-
}
141-
} catch (IOException e) {
142-
Log.e("DESTINATION_SOL", "", e);
143-
e.printStackTrace();
144-
}
145-
}
146-
}
147-
} catch (IOException e) {
148-
Log.e("DESTINATION_SOL", "", e);
149-
e.printStackTrace();
90+
@Override
91+
public Class<?>[] getAPIClasses() {
92+
return new Class<?>[0];
15093
}
15194
}
15295
}

0 commit comments

Comments
 (0)