1 import cc.squirreljme.plugin.multivm.ident.SourceTargetClassifier
2 import cc.squirreljme.plugin.multivm.BangletVariant
3 import cc.squirreljme.plugin.multivm.VMFullSuiteDepends
4 import cc.squirreljme.plugin.multivm.VMHelpers
5 import cc.squirreljme.plugin.multivm.VMRunUpToDateWhen
6 import cc.squirreljme.plugin.multivm.VMType
8 import java.nio.file.Files
9 import java.nio.file.Paths
10 import java.nio.file.StandardOpenOption
11 import java.util.regex.Pattern
12 import java.util.zip.ZipEntry
13 import java.util.zip.ZipInputStream
19 id "com.github.johnrengelman.shadow" version "7.1.2"
22 description = "Standalone SquirrelJME virtual machine on Java."
23 mainClassName = "cc.squirreljme.vm.standalone.main.Main"
26 implementation project(":emulators:springcoat-vm")
30 // Use a fixed version of the JVM
33 sourceCompatibility = JavaVersion.VERSION_1_8
34 targetCompatibility = JavaVersion.VERSION_1_8
36 // Use the default bootstrap classpath
37 options.bootstrapClasspath = null
42 compileJava.options.debug = true
43 compileJava.options.debugOptions.setDebugLevel("source,lines,vars")
46 compileTestJava.options.debug = compileJava.options.debug
47 compileTestJava.options.debugOptions = compileJava.options.debugOptions
50 // Mapper for Jar names on files to straight file names
51 static Iterable<java.nio.file.Path> mapBaseNameP(Iterable<Path> input) {
52 List<java.nio.file.Path> result = new ArrayList<>()
54 input.forEach({path -> result.add(path.getFileName())})
59 // Mapper for Jar names on files to straight file names
60 static Iterable<java.nio.file.Path> mapBaseNameF(Iterable<File> input) {
61 List<java.nio.file.Path> mapped = new ArrayList<>()
63 input.forEach({file -> mapped.add(file.toPath())})
65 return mapBaseNameP(mapped as Iterable<Path>)
68 static String flatClasspath(Project project) {
69 return VMHelpers.classpathAsString(mapBaseNameP(VMHelpers
70 .runClassPath(project, new SourceTargetClassifier(
71 SourceSet.MAIN_SOURCE_SET_NAME,
72 VMType.SPRINGCOAT, BangletVariant.NONE)) as List<Path>))
75 static java.nio.file.Path nameToDiskFile(java.nio.file.Path jarOut,
77 java.nio.file.Path diskFile = jarOut
78 for (String splice : name.split("[\\\\/]"))
79 diskFile = diskFile.resolve(splice)
84 // Prefix for included JAR resources
85 String MERGED_PREFIX = "SQUIRRELJME.SQC"
87 task collateResourceJars {
88 dependsOn new VMFullSuiteDepends(collateResourceJars,
89 new SourceTargetClassifier(SourceSet.MAIN_SOURCE_SET_NAME,
90 VMType.SPRINGCOAT, BangletVariant.NONE))
91 mustRunAfter processResources
95 description "Collates all files for standalone JARs."
97 // Inputs for this, it is just all of the input JARs and such
98 inputs.files(project.provider({ -> VMHelpers.fullSuiteLibraries(
99 rootProject.tasks.getByName("fullSpringCoat")) as Iterable<Path>}))
100 outputs.files(project.provider({ ->
101 java.nio.file.Path outBase = processResources.getOutputs().
102 files.getSingleFile().toPath().resolve(MERGED_PREFIX)
104 Set<java.nio.file.Path> result = new LinkedHashSet<>()
107 result.add(outBase.resolve("suites.list"))
110 for (File jarFile : collateResourceJars.getInputs()
112 java.nio.file.Path jarPath = jarFile.toPath()
113 java.nio.file.Path jarName = jarPath.getFileName()
114 java.nio.file.Path jarOut = outBase.resolve(jarName)
117 result.add(jarOut.resolve("META-INF")
118 .resolve("squirreljme").resolve("resources.list"))
120 // Contents of the JAR
121 try (InputStream jarIn = Files.newInputStream(jarPath,
122 StandardOpenOption.READ);
123 ZipInputStream jar = new ZipInputStream(jarIn)) {
125 ZipEntry entry = jar.getNextEntry()
129 if (entry.isDirectory())
132 // Where is this file going?
133 result.add(nameToDiskFile(jarOut, name))
141 // Up to date when dependencies are updated
142 outputs.upToDateWhen(new VMRunUpToDateWhen(new SourceTargetClassifier(
143 SourceSet.MAIN_SOURCE_SET_NAME, VMType.SPRINGCOAT,
144 BangletVariant.NONE)))
146 // Explode the JARs into the resource root
148 java.nio.file.Path outBase = processResources.getOutputs().
149 files.getSingleFile().toPath().resolve(MERGED_PREFIX)
151 // Delete old directory set first since it will have a bunch of
152 // old files in it and such...
153 if (Files.isDirectory(outBase)) {
154 Set<java.nio.file.Path> deleteFiles = new LinkedHashSet<>()
155 Set<java.nio.file.Path> deleteDirs = new LinkedHashSet<>()
156 Files.walk(outBase).forEach({
157 if (Files.isDirectory(it))
162 for (Set<java.nio.file.Path> rawByes : [deleteFiles, deleteDirs]) {
163 List<java.nio.file.Path> byes = new ArrayList<>(rawByes)
164 Collections.reverse(byes)
166 for (java.nio.file.Path bye : byes) {
167 logger.lifecycle(String.format("Cleaning %s...", bye))
170 Files.deleteIfExists(bye)
171 } catch (IOException e) {
178 // Make sure it exists
179 Files.createDirectories(outBase)
182 List<String> suiteList = new ArrayList<>()
184 // Go through all of the files
185 byte[] buf = new byte[524288];
186 for (File jarFile : collateResourceJars.getInputs().files.asList()) {
187 // Where is everything?
188 java.nio.file.Path jarPath = jarFile.toPath()
189 java.nio.file.Path jarName = jarPath.getFileName()
191 // Add to the suite list for later
192 suiteList.add(jarName.toString())
194 // Where does the exploded JAR content go?
195 java.nio.file.Path jarOut = outBase.resolve(jarName)
198 Files.createDirectories(jarOut)
200 // Read the ZIP and process
201 List<String> jarContent = new ArrayList<>()
202 try (InputStream jarIn = Files.newInputStream(jarPath,
203 StandardOpenOption.READ);
204 ZipInputStream jar = new ZipInputStream(jarIn)) {
206 ZipEntry entry = jar.getNextEntry()
210 if (entry.isDirectory())
214 String name = entry.getName()
217 // Find where it goes
218 java.nio.file.Path diskFile = nameToDiskFile(jarOut, name)
221 Files.createDirectories(diskFile.getParent())
224 logger.lifecycle(String.format("Adding %s...",
225 outBase.relativize(diskFile)))
228 try (OutputStream out = Files.newOutputStream(diskFile,
229 StandardOpenOption.WRITE,
230 StandardOpenOption.TRUNCATE_EXISTING,
231 StandardOpenOption.CREATE)) {
233 int rc = jar.read(buf)
238 out.write(buf, 0, rc)
244 // Write resource list
245 java.nio.file.Path rcListBase =
246 jarOut.resolve("META-INF").resolve("squirreljme")
247 java.nio.file.Path rcListPath =
248 rcListBase.resolve("resources.list")
249 Files.createDirectories(rcListBase)
250 Files.write(rcListPath, jarContent,
251 StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
252 StandardOpenOption.CREATE)
255 // Write the suite list
256 Files.write(outBase.resolve("suites.list"), suiteList,
257 StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
258 StandardOpenOption.CREATE)
264 dependsOn processResources, collateResourceJars
265 mustRunAfter collateResourceJars
267 // We need to set specific manifest properties
270 "X-SquirrelJME-Standalone-Main-Class":
271 "javax.microedition.midlet.__MainHandler__",
272 "X-SquirrelJME-Standalone-Parameter":
273 "cc.squirreljme.runtime.launcher.ui.MidletMain",
274 "X-SquirrelJME-Standalone-Classpath": project.provider({ ->
275 return flatClasspath(project(":modules:launcher"))
277 "X-SquirrelJME-Standalone-Library": project.provider({ ->
278 return VMHelpers.classpathAsString(
279 mapBaseNameP(VMHelpers.fullSuiteLibraries(
280 rootProject.tasks.getByName("fullSpringCoat"))
283 "X-SquirrelJME-Standalone-Internal-Jar-Root": project.provider({ ->
284 "/" + MERGED_PREFIX + "/"})
288 // Configuration for ShadowJar
290 dependsOn collateResourceJars
291 mustRunAfter collateResourceJars
293 // Always SquirrelJME
294 archiveBaseName.set("squirreljme-standalone")
296 // Set the suffix of the JAR to be the OS name and arch, since there is
297 // a dynamic library within for it
298 archiveClassifier.set(project.provider({ ->
299 String osName = System.getProperty("os.name").toLowerCase()
300 String osArch = System.getProperty("os.arch").toLowerCase()
302 // Normalize OS names
303 if (osName.contains("windows"))
305 else if (osName.contains("mac os") || osName.contains("macos"))
308 // Make sure there are no spaces or weird characters such as for
310 return (osName + "-" + osArch).replaceAll(
311 Pattern.compile("[\\s<>:\"/\\\\|?*]"), "")