📜 ⬆️ ⬇️

Java App Bundlers Review

So, last time I wrote about a tool for building JavaFXPackager applications. There were 2 ways to build the application, but none of them could be conveniently called just from the code. But we are Java programmers. And for such pipe programmers from version 8u20, a special API in JavaFXPackager was created in the JDK, which allows you to just take and assemble a bundle from your binaries. One problem is that this API is undocumented. But it does not matter, we will understand.

Information sources


The primary source would naturally be JavaDoc, which, however, is also not publicly available. Therefore, we will build it ourselves from the source . Without forcing readers to do it, I’ll just post it: ginz.bitbucket.org/fxpackager-javadoc . Of course, it will not be out of place to have the source code, as the API is badly documented.

Introduction


What class makes the first to attract attention? Apparently, the Bundler interface with this specification:

// action methods File execute(Map<String,? super Object> params, File outputParentDir); boolean validate(Map<String,? super Object> params); // information methods Collection<BundlerParamInfo<?>> getBundleParameters(); String getBundleType(); String getDescription(); String getID(); String getName(); 

')
Methods are clearly divided into 2 types: methods that produce actions with already given parameters and methods that give information about this bundler.

The execute takes a Map of parameters and an output directory as parameters and creates a bundle in this directory in some expected format. But we can expect the correct execution only if validate passes without exceptions, and they can throw 2 types of them: UnsupportedPlatformException and ConfigException, the telling names of which suggest that if you throw out the first one, you simply use a bandler that is not supported by your platform. The second is discarded if you have something wrong with the parameters passed.

And what generally can be passed as parameters to these methods? Most of the keys in these methods are in all static instances of the BundlerParamInfo class, most of which (common cross-platform parameters) are in the StandardBundlerParam class, and more specific (platform-dependent), as a rule, are in the Bundler classes themselves: LinuxAppBundler , WinExeBundler, and so on. Although it is not entirely clear how this class is generally related to what we put in params, which we pass execute. In fact, it turns out that the id obtained by getID () is the key in this Map, and the value must be of the type that has been parameterized by the BundlerParamInfo.

And how to get copies of bandlers?


The easiest way to get all possible “pre-installed” Bundlers through the static createBundlersInstance method of the Bundlers interface (Warning, HOT: Java 8):

 public static List<Bundler> getSuitableBundlers() { return Bundlers.createBundlersInstance() .getBundlers() .stream() .filter(bundler -> { try { bundler.validate(Collections.emptyMap()); } catch (UnsupportedPlatformException ex) { return false; } catch (ConfigException ignored) { } return true; }).collect(Collectors.toList()); } 


Thus, we not only received all the “pre-installed” bundlers, but also filtered them according to the platform principle: as a rule, it is impossible to collect packages for one platform to another.

And what, no examples ?!


And, exactly, you can try to stop theorizing and draw some example.

Since it seems to me that static typing is always better, we can wrap the creation of an associative array of parameters into something like BunderParamsBuilder:

 public class BundlerParamsBuilder { private Map<String, Object> params = new HashMap<>(); public <T> BundlerParamsBuilder setParam(BundlerParamInfo<T> param, T value) { params.put(param.getID(), value); return this; } public BundlerParamsBuilder unsafeSetParam(String key, Object value) { params.put(key, value); return this; } public Map<String, Object> build() { return new HashMap<>(params); } } 

Thus, the project build will be approximately as follows (it implies the existence of starting /tmp/helloWorld.jar):

 List<Bundler> bundlers = getSuitableBundlers(); Path directoryWithBundles = Files.createTempDirectory("bundles"); Path jar = Paths.get("/tmp/helloWorld.jar"); RelativeFileSet mainJar = new RelativeFileSet(jar.getParent().toFile(), new HashSet<File>( Arrays.asList(jar.toFile()) )); Map<String, Object> params = new BundlerParamsBuilder() .setParam(StandardBundlerParam.APP_NAME, "HelloWorld") .setParam(StandardBundlerParam.APP_RESOURCES, mainJar) .build(); bundlers.forEach(bundler -> bundler.execute(params, directoryWithBundles.toFile())); System.out.println("Bundles are created in " + directoryWithBundles); System.out.println("Parameters after bundling: " + params); 

Exhaust after launch will be about the following:
 The bundles are created in / tmp / bundles5791581710818077755
 Parameters after bundling: {appVersion = 1.0, copyright = Copyright (C) 2015, stopOnUninstall = true, .mac-jdk.runtime.rules = [Lcom.oracle.tools.packager.JreUtils $ Rule; @ 4c3e4790, mac.app. bundler = Mac Application Image, linux.deb. , linux.deb.licenseText = Unknown, linux.deb.maintainer = Unknown <Unknown>, jvmProperties = {}, mac.signing-key-user-name =, licenseFile = [], identifier = HelloWorld, linux.rpm.imageDir = / tmp / fxbundler6592356981290936843 / images / linux-rpm.image, runtime = RelativeFileSet {basedir: /home/dginzburg/soft/jdk1.8.0_25/jre, files: [<very big list of files>]}, shortcutHint = false , mainJar = RelativeFileSet {basedir: / tmp, files: [helloWorld.jar]}, jvmOptions = [], name.fs = HelloWorld, fxPackaging = false, name = HelloWorld, appResources = RelativeFileSet {basedir: / tmp, files: [ helloWorld.jar]}, mac.category = Unknown, linux.deb.imageDir = / tmp  /fxbundler6592356981290936843/images/linux-deb.image/helloworld-1.0, .mac.default.icns = GenericAppHiDPI.icns, runAtStartup = false, linux.app.bundler = Linux Application Image, mac.signing-key-developer-id- app = null, linux.launcher.url = jar: file: /home/dginzburg/soft/jdk1.8.0_25/lib/ant-javafx.jar! / com / oracle / tools / packager / linux / JavaAppLauncher, description = HelloWorld , configRoot = / tmp / fxbundler6592356981290936843 / macosx, preferencesID = Hello, if you want to have a ceo, why = = helloworld-1.0, mac.CFBundleIdentifier = HelloWorld, serviceHint = false, vendor = Unknown, email = Unknown, applicationCategory = Unknown, mac.app.imageRoot = / tmp / fxbundler6592356981290936843 / images / dmg.image, userJvmOptions =} =, linux.deb.configDir = / tmp / fxbundler6592356981290936843 / images / linux-deb.image / helloworld-1.0 / DEBIAN, verbose = false, imagesRoot = / tmp / fxbundler6592  356981290936843 / images, mac.daemon.image = / tmp / fxbundler6592356981290936843 / images / pkg.daemon, applicationClass = HelloWorld, .linux.runtime.rules = [Lcom.oracle.tools.packager.JreUtils $ Rule; 38ccce, 38.cref, $ 38.3; true}


Oh, where did we get all this here? We kind of put in params only 2 parameters. To understand this, you need to look at the BunderParamInfo.fetchFrom method, which is used to get the parameter value from params:

 //... if (getDefaultValueFunction() != null) { T result = getDefaultValueFunction().apply(params); if (result != null) { params.put(getID(), result); } return result; } //... 

Yeah, if the parameter was not found in params and it can be obtained through other parameters (the defaultValueFunction function is responsible for this), then the value obtained in this way is pushed in params. For example, we did not specify the MAIN_JAR parameter, but MAIN_JAR is required in order to create a launch file. Let's take a look at how MAIN_JAR is defined: as defaultValueFunction we see:

 params -> { extractMainClassInfoFromAppResources(params); return (RelativeFileSet) params.get("mainJar"); } 

This is the answer where the mainJar value comes from.

Full list of parameters


In order not to look for the parameters each time, I made a label , into which I output information on all the found static instances of BundlerParamInfo.

Conclusion


The very minimum and the most obvious features are described, so feel free to read JavaDoc and even code.
The article will be improved and corrected at the request of commentators.

Source: https://habr.com/ru/post/257363/


All Articles