ifener / javachromiumembedded

Automatically exported from code.google.com/p/javachromiumembedded
0 stars 0 forks source link

Native libraries should be loaded manually from a JAR #133

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Loading the native libraries with the System.loadLibrary()-method makes the 
deploy of an appliction that uses JCEF very complex if you want to deploy it to 
hundreds of client machines, because you have to ensure that the library path 
on every machine is set correctly.

It would be much nicer if the libraries are extracted from a JAR and stored to 
a temporary directory. Then they can be loaded with the System.load()-method.

For Windows the temporary library files have to be deleted manually because the 
deleteOnExit-switch does not work for loaded library files. This may be done 
when the application starts the next time.

Original issue reported on code.google.com by th...@literarisch-wertvoll.de on 5 Nov 2014 at 3:12

GoogleCodeExporter commented 9 years ago
Hi th...,
that's a thing which is on my list for longer time, too. Maybe I'll create a 
patch within the next weeks/month.
One thing you've to mention is the required application layout of CEF 
(https://code.google.com/p/chromiumembedded/wiki/GeneralUsage#Application_Layout
). Especially for Mac it isn't that easy going.

Regards, Kai

Original comment by k...@censhare.de on 6 Nov 2014 at 6:45

GoogleCodeExporter commented 9 years ago
Hi Kai,

I'm glad to hear this already is on your list. My customer is currently 
thinking about replacing his Swing-GUIs with HTML-GUIs that need to run in the 
old application Frame. Since the Java FX Web View has an awful JavaScript 
Performance the JCEF seems to be the only way to realize this.

Regards,  Thies

Original comment by th...@literarisch-wertvoll.de on 7 Nov 2014 at 9:11

GoogleCodeExporter commented 9 years ago
I had to do this for our app embedding JCEF. This is something other libraries 
such as SWT and JOGL do as well. In our app, we extract to a temp folder, whose 
location is persisted by the application. We have a cb-win64.jar, cb-win32.jar, 
cb-macosx64.jar with all the appropriate resources.

Here's that code below. You'll probably come up with something optimized for 
your build scripts. A few interesting notes:
1. Notice the Mac is a bit special. There's also a list of resources that need 
the executable bit to be set.
2. The temp folder must be a location with full privileges on Windows. I'm 
saying this because we're using Javawebstart and some of its api's were 
pointing to a Windows folder with lower permissions (LocalLow folder, can't 
link to dll's outside of this folder, so could not link to jawt.dll).

The app calls extractToLibraryPath on startup. Then it can assumes the jcef/cef 
libraries are available to be loaded.
I also used System.loadLibrary with absolute paths instead of relative paths as 
JCEF currently does today. This way, we don't need to rely on external 
properties, we just need to know where we extracted the libs.

If this doesn't help, please ignore this comment... It's working well for us.

AutoExtract.java

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.cef.OS;

/**
 * Automatically extract the native files in the appropriate structure.
 * This assumes the native files are properly packaged into the correct platform jar.
 */
public class AutoExtract {

    static String[] resourcesWin64 = new String[] { "version.txt", "cef.pak",
            "d3dcompiler_43.dll", "d3dcompiler_46.dll",
            "devtools_resources.pak", "ffmpegsumo.dll", "icudtl.dat",
            "jcef.dll", "jcef_helper.exe", "libcef.dll", "libEGL.dll",
            "libGLESv2.dll",
            "locales/en-US.pak"
    };

    static String[] resourcesWin32 = resourcesWin64;

    static String[] executablesMacOS64 = new String[] {
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/ffmpegsumo.so",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_inspector",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/MacOS/crash_report_sender",
        "jcef_app.app/Contents/Frameworks/jcef Helper EH.app/Contents/MacOS/jcef Helper EH",
        "jcef_app.app/Contents/Frameworks/jcef Helper NP.app/Contents/MacOS/jcef Helper NP",
        "jcef_app.app/Contents/Frameworks/jcef Helper.app/Contents/MacOS/jcef Helper",
        "jcef_app.app/Contents/Frameworks/libplugin_carbon_interpose.dylib",
        "jcef_app.app/Contents/Java/libjcef.dylib"
    };
    static String[] resourcesMacOS64 = new String[] {
        "version.txt",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Libraries/ffmpegsumo.so",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/am.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ar.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/bg.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/bn.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ca.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/cef.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_inspector",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/Info.plist",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/MacOS/crash_report_sender",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/PkgInfo",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/Resources/Breakpad.nib",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/Resources/crash_report_sender.icns",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/crash_report_sender.app/Contents/Resources/English.lproj/Localizable.strings",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/cs.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/da.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/de.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/devtools_resources.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/el.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/en.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/en_GB.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/es.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/es_419.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/et.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/fa.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/fi.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/fil.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/fr.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/gu.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/he.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/hi.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/hr.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/hu.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/id.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/Info.plist",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/it.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ja.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/kn.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ko.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/lt.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/lv.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ml.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/mr.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ms.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/nb.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/nl.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/pl.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/pt_BR.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/pt_PT.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ro.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ru.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/sk.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/sl.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/sr.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/sv.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/sw.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/ta.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/te.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/th.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/tr.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/uk.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/vi.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/zh_CN.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources/zh_TW.lproj/locale.pak",
        "jcef_app.app/Contents/Frameworks/jcef Helper EH.app/Contents/Info.plist",
        "jcef_app.app/Contents/Frameworks/jcef Helper EH.app/Contents/MacOS/jcef Helper EH",
        "jcef_app.app/Contents/Frameworks/jcef Helper EH.app/Contents/PkgInfo",
        "jcef_app.app/Contents/Frameworks/jcef Helper NP.app/Contents/Info.plist",
        "jcef_app.app/Contents/Frameworks/jcef Helper NP.app/Contents/MacOS/jcef Helper NP",
        "jcef_app.app/Contents/Frameworks/jcef Helper NP.app/Contents/PkgInfo",
        "jcef_app.app/Contents/Frameworks/jcef Helper.app/Contents/Info.plist",
        "jcef_app.app/Contents/Frameworks/jcef Helper.app/Contents/MacOS/jcef Helper",
        "jcef_app.app/Contents/Frameworks/jcef Helper.app/Contents/PkgInfo",
        "jcef_app.app/Contents/Frameworks/libplugin_carbon_interpose.dylib",
        "jcef_app.app/Contents/Info.plist",
        "jcef_app.app/Contents/Java/libjcef.dylib",
        "jcef_app.app/Contents/PkgInfo"
    };

    static final String archWin64 = "win64";
    static final String archWin32 = "win32";
    static final String archMacOSX64 = "macosx64";

    Path rootDir;

    public AutoExtract() {
    }

    public static class LibraryPathData {
        public String libraryPath;
        public String arch;
        public String libraryPathWithArch;
        public boolean success;
        public boolean reusedCache;
        public Exception error;
    }

    /**
     * Create a native library cache under the given root folder if none is found under
     * the current architecture.
     * <p>
     * Returns information about the cache location, architecture, success and if an existing cache
     * was reused or a new one created.
     */
    public LibraryPathData extractToLibraryPath(String libraryPath) {
        if (libraryPath == null)
            throw new IllegalArgumentException("Must provide native library path");
        LibraryPathData result = new LibraryPathData();
        try {
            String arch = getArchFolderName();
            rootDir = new File(libraryPath).toPath();
            Path archPath = new File(rootDir.toAbsolutePath().toFile(), arch).toPath();
            boolean exists = Files.exists(archPath);
            result.reusedCache = !exists;
            result.libraryPath = libraryPath;
            result.arch = arch;
            result.libraryPathWithArch = archPath.toAbsolutePath().toString();
            if (!exists) {
                Path rootFolder = Files.createDirectory(archPath);
                String rootFolderString = rootFolder.toAbsolutePath().toString();
                extract(arch, rootFolderString);
            }
            result.success = true;
            return result;
        } catch (IOException e) {
            result.success = false;
            result.error = e;
            e.printStackTrace();
        }
        return result;
    }
    /*
     * processes the existing version.txt file in the specified library path
     */
    public static String getExistingVersion(String libraryPath) {
        String arch = getArchFolderName();

        Path nativeVersionPath = new File(libraryPath, arch+ "//version.txt").toPath();

        if(!Files.exists(nativeVersionPath, LinkOption.NOFOLLOW_LINKS)) {
            return null;
        }

        byte[] encoded = null;
        try {
            encoded = Files.readAllBytes(Paths.get(nativeVersionPath.toUri()));
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        String version = null;
        if(encoded != null) {
            version = new String(encoded, Charset.defaultCharset());
        }

        return version;
    }
    /*
     * Retrieves the version from "version.txt" in the jar
     * @return
     */
    public static String getPackagedNativeVersion() {
        ClassLoader loader = AutoExtract.class.getClassLoader();
        InputStream is = loader.getResourceAsStream("version.txt");
        if(is != null) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            StringBuilder out = new StringBuilder();
            String line;
            try {
                while ((line = reader.readLine()) != null) {
                    out.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            return out.toString();
        }

        System.out.println("Could not find a packaged version file");
        return null;
    }
    /**
     * Return the name of the arch specific subfolder we extract the native libraries into
     */
    public static String getArchFolderName() {
        String arch = null;
        if (isWindows())
            arch = isWindows32Bit() ? archWin32 : archWin64;
        if (isMac())
            arch = archMacOSX64;
        if (arch == null)
            throw new RuntimeException("Unsupported platform: "+System.getProperty("os.name"));
        return arch;
    }

    /**
     * Extract native files to a temp folder.
     * Returns the root of that folder.
     * 
     * The folder and all its contents get deleted when program exits.
     */
    public String extractToTempDirectory() {
        try {
            String arch = null;
            if (isWindows())
                arch = isWindows32Bit() ? archWin32 : archWin64;
            if (isMac())
                arch = archMacOSX64;
            if (arch == null)
                throw new RuntimeException("Unsupported platform: "+System.getProperty("os.name"));

            rootDir = Files.createTempDirectory("cef"+Long.toString(System.nanoTime()));
            Path rootFolder = Files.createDirectory(new File(rootDir.toAbsolutePath().toFile(), arch).toPath());
            String rootFolderString = rootFolder.toAbsolutePath().toString();
            extract(arch, rootFolderString);
            return rootFolderString;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().startsWith("windows");
    }

    public static boolean isWindows32Bit() {
        return "x86".equals(System.getProperty("os.arch"));
    }

    public static boolean isMac() {
        return System.getProperty("os.name").toLowerCase().startsWith("mac");
    }

    /**
     * Extract the given resources under the given install path from the given
     * jar name. Resources are String using / as a folder separator if they are
     * under a subfolder. Subfolder gets created as needed.
     */
    void extract(String arch, String installPath) throws IOException {

        String[] resources = null;
        File rootFolder = new File(installPath);

        if (archWin64.equals(arch)) {
            resources = resourcesWin64;
        }
        if (archWin32.equals(arch)) {
            resources = resourcesWin32;
        }
        if (archMacOSX64.equals(arch)) {
            resources = resourcesMacOS64;
        }

        ClassLoader loader = AutoExtract.class.getClassLoader();
        List<String> executables = OS.isMacintosh() ? Arrays.asList(executablesMacOS64) : new ArrayList<String>();
        for (String resource : resources) {
            File target = new File(rootFolder, resource);
            Path path = target.getAbsoluteFile().toPath();
            System.out.println("Extracting "+path);
            Path parent = path.getParent();
            Files.createDirectories(parent);
            InputStream in = loader.getResourceAsStream(resource);
            Files.copy(in, path);
            if (executables.contains(resource)) {
                target.setExecutable(true);
            }
        }
    }

    public void deleteTempDirectoryOnShutdown() {
        try {
            Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file,
                        BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
                    if (e == null) {
                        Files.delete(dir);
                        return FileVisitResult.CONTINUE;
                    }
                    throw e;
                }
            } );
        } catch (IOException e) {
            throw new RuntimeException("Failed to delete "+rootDir, e);
        }
    }

    /**
     * Utility to generate the list of mac resources to extract from the jar and mark as executable.
     */
    public static void main(String[] args) {
        String jcefAppRootFolder = "/Users/admin/Documents/chrix/data/git/embeddedBrowserR60/cef/native/macosx64/jcef_app.app";
        String rootFilter = "/Users/admin/Documents/chrix/data/git/embeddedBrowserR60/cef/native/macosx64/";
        List<String> files = new ArrayList<String>();
        List<String> executableFiles = new ArrayList<String>();
        scanFiles(jcefAppRootFolder, files, executableFiles);
        System.out.println("-- Files --");
        for (String entry : files) {
            System.out.println("\""+entry.substring(rootFilter.length())+"\",");
        }
        System.out.println("-- Executables --");
        for (String entry : executableFiles) {
            System.out.println("\""+entry.substring(rootFilter.length())+"\",");
        }

    }

    /**
     * Given the root directory, scan all children recursively. Return all files found. Files requiring the executable bit are also collected in an extra list.
     */
    static void scanFiles(String directory, List<String> files, List<String> executableFiles) {
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(Paths.get(directory))) {
            for (Path path : ds) {
                if (Files.isDirectory(path)) {
                    scanFiles(path.toString(), files, executableFiles);
                }
                if (Files.isRegularFile(path)) {
                    files.add(path.toString());
                    if (Files.isExecutable(path)) {
                        executableFiles.add(path.toString());
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Original comment by christop...@gmail.com on 10 Nov 2014 at 8:51