OSGi, VTK and MacOSX
ls://dev.artenum.com/blog/ben/posts/osgi_vtk_and_macosx
1 Objectives In order to develop modular and dynamic Java applications for some of our projects at Artenum, we have decided to use the [http://www.osgi.org|OSGi] framework. Internal note: https://code.artenum.com/internal/wiki/-/wikis/dev/How-to-compile-VTK/ {quote:http://en.wikipedia.org/wiki/OSGi} The OSGi framework is a module system and service platform for the Java programming language that implements a complete and dynamic component model. {quote} OSGi is already successfully used by [http://www.eclipse.org|Eclipse] or [https://glassfish.dev.java.net/|Glassfish v3] for instance. The main advantage of OSGi is that it provides an easy way to build component-based applications that can be dynamically loaded and updated at runtime. While creating an OSGi bundle is rather simple, when dealing with native libraries, things tend to become much more complicated, especially because there is very little documentation available on the subject. The main objective of this (very long, sorry about that) article is to explain the specificities of MacOSX and the different issues Jérémie and myself faced while creating an OSGi bundle for [http://www.vtk.org|VTK], the open source Visualization Toolkit, written in C++. This article is divided in four parts: - __1) Introduction regarding JNI, dynamic linking on MacOSX and native libraries in OSGi__, explains some particularities of OSX and OSGi regarding the use of native code in Java -- a) JNI -- b) Dynamic linking -- c) OSGi and native libraries - __2) Compilation of VTK with Java wrapping__, details how to take into account those OSX-specific aspects to compile VTK -- a) Prerequisites -- b) OSX special actions -- c) Compiling VTK -- d) Removing the version numbers -- e) Downloading the result - __3) Creating a simple Java application using VTK__, demonstrates the basic usage of VTK from a Java application - __4) Embedding VTK in an OSGi bundle__, explains how to package VTK as an OSGi bundle I hope this will help whoever wants to try integrating VTK in a generic OSGi application or in an Eclipse RCP application. 1 1) Introduction regarding JNI, dynamic linking on MacOSX and native libraries in OSGi 1.1 a) JNI When you want to use a native library such as VTK in a Java application, one of the possibilities is to use JNI, the [http://en.wikipedia.org/wiki/Java_Native_Interface|Java Native Interface], to allow the communication between the Java code running in the JVM and the native library. The details on how to create the native dynamic libraries and how to access them from Java code are out of the scope of this article, but you can find a very simple example about how to use JNI [http://blogs.sun.com/moonocean/entry/a_simple_example_of_jni|here]. What is important to know here is that to use native code in a Java application, one needs: 1. The dynamic libraries compiled from the native code (.dylib or .jnilib files on OSX, .so files on Linux, .dll files on Windows) 1. Java classes declaring the native functions to be used 1. A way to indicate the JVM where dynamic libraries are located, as it will load them dynamically at runtime (hence the name) Hopefully, VTK provides CMake scripts to perform the Java wrapping of the library, i.e. generating the dynamic libraries and the Java classes to access them (items 1 and 2). For item 3, on OSX, there are two ways to indicate the location of the dynamic libraries to the JVM when executing the application: - The first one is to use the system environment variable __DYLD_LIBRARY_PATH__ (on Linux, it's LD_LIBRARY_PATH, on Windows PATH): {code} > export DYLD_LIBRARY_PATH=path/to/my/libs > java -jar myApplication.jar {code} - The second method is to use the __java.library.path__ Java property to indicate the libraries path. {code} > java -Djava.library.path=path/to/my/libs -jar myApplication.jar {code} 1.1 b) Dynamic linking This is where we have to go a little deeper on how MacOSX actually deals with dynamic linking and how does the dynamic linker know where to find the libraries. As very well explained in this article [http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html], MacOSX uses the "install name" to find libraries: {quote:http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html} An install name is just a pathname embedded within a dynamic library which tells the linker where that library can be found at runtime. For example, __libfoo.dylib__ might have an install name of __/usr/lib/libfoo.dylib__. This install name gets copied into the application at link time. When the dynamic linker goes looking for __libfoo.dylib__ at runtime, it will fetch the install name out of the application and know to look for the library in __/usr/lib__. {quote} To see the install name of a library, you can use the __otool__ command. It will show the install name as well as the dependencies of your library. For instance, here is the result of the command on one of VTK dynamic library, generated by the default Java wrapping in VTK: {code} > otool -L libvtkCommonJava.5.6.dylib libvtkCommonJava.5.6.dylib: libvtkCommonJava.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) libvtkCommon.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) libvtksys.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0) /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0) {code} The install name of the library is the one displayed on the first line "libvtkCommonJava.5.6.dylib". This library depends on the libraries "libvtkCommon.5.6.dylib", "libvtksys.5.6.dylib", "/usr/lib/libSystem.B.dylib" and "/usr/lib/libstdc++.6.dylib". You can see that the two last dependencies are given with an absolute path, while the first ones have no path specified. This is considered as a relative path, such as "./libvtkCommonJava.5.6.dylib". And here is the problem with MacOSX: where is "."? When the __DYLD_LIBRARY_PATH__ environment variable is used, it seems that OSX considers "." as the path indicated in the environment variable, so when using this method, the java application using VTK will work properly. When using the __java.library.path__ property however, OSX considers "." as being the directory where the application is launched. This will thus only work if the dynamic libraries are at the same place as the executable. It is exactly the same as using __@executable_path__, as explained in the article [http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html], which is not handy. {quote:http://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html} Starting in 10.4, Apple provided @loader_path which does what you want here. It expands to the full path, minus the last component, of whatever is actually causing the target library to be loaded. If it's an application, then it's the same as @executable_path. If it's a framework or plugin \[or a dynamic library\], though, then it's relative to that framework or plugin, which is much more useful. {quote} This is why when compiling VTK with Java wrapping, one needs to modify the library path to be, for instance: {code} libvtkCommonJava.dylib: @loader_path/libvtkCommonJava.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtkCommon.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtksys.5.6.dylib (compatibility version 5.6.0, current version 5.6.1) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0) /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0) {code} MacOSX has a tool, called __install_name_tool__ that can modify the install name (with -id option) or the dependencies names (with -change option). We will see how to use it for VTK in part 2. 1.1 c) OSGi and native libraries The OSGi framework dynamically loads and updates Java components that are called ~~bundles~~. In its most common form, a bundle is simply a JAR file whose manifest has OSGi-specific entries*. This means that to distribute the native libraries inside the bundle and to benefit from OSGi dynamism, one needs to add the libraries in the jar file. The JVM cannot however link Java code to library files contained in a JAR file. This is why, at runtime and on request, the OSGi framework is going to unpack the required native libraries in a temporary folder. Unfortunately, OSGi can only extract .jnilib files from the bundle and ignores .dylib files. This is very annoying, as by default, VTK Java wrapping creates .dylib library files. We will see in the next part of this article how to solve this problem and obtain, for example: {code} libvtkCommonJava.jnilib: @loader_path/libvtkCommonJava.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtkCommon.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtksys.5.6.jnilib (compatibility version 5.6.0, current version 5.6.1) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0) /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0) {code} Moreover, the OSGi framework extracts those libraries one by one, when asked by a ~~System.loadLibrary()~~ call. This means that if we load the previously mentioned ~~vtkCommonJava~~ library without having explicitly loaded ~~vtkCommon~~ and ~~vtksys~~, they will __NOT__ be present in the temporary folder and OSGi will throw an ~~UnsatisfiedLinkError~~. This is not very handy (to say the least), as we need to manually call the ~~System.loadLibrary()~~ for all shared library in the order of their dependencies. The __otool__ command is very useful at this point. There is a last problem. As shown earlier, the dependencies created by the default VTK Java wrapping explicitly specify the version number of the libraries. And as just stated in the previous paragraph, OSGi will __NOT__ extract ~~libvtkCommon.5.6.jnilib~~ for instance unless we explicitly call ~~System.loadLibrary(vtkCommon.5.6)~~. Thus calling ~~System.loadLibrary(vtkCommonJava)~~ will throw an ~~UnsatisfiedLinkError~~. In order to fix this last linking problem, we should modify the dependencies to not specify the version number required, such as: {code} libvtkCommonJava.jnilib: @loader_path/libvtkCommonJava.jnilib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtkCommon.jnilib (compatibility version 5.6.0, current version 5.6.1) @loader_path/libvtksys.jnilib (compatibility version 5.6.0, current version 5.6.1) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.0) /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0) {code} We will explain in next part how to achieve this. Of course, it will then complicate the concurrent use of several versions of VTK, but in my humble opinion, this is not something I would recommend anyway. ---- \* It is also possible in OSGi to load directly a folder (containing the Manifest with OSGi-specific attributes), without requiring to compress the bundle in a jar file. This saves some time when using native libraries as it is not necessary to unpack the native libs at runtime, but it uses more disk space, as jar file are compressed, and is less practical for distribution. ---- 1 2) Compiling VTK for OSX with Java wrapping 1.1 a) Prerequisites Now that we have seen the constraints on the Java wrapping imposed by MacOSX, it is time to see how to adapt CMake scripts to generate .jnilib libraries with @loader_path attributes. First, you need to download [http://cmake.org/|CMake], the cross-platform build tool and VTK latest [http://vtk.org/VTK/resources/software.html#latest|source code]. When you first launch CMake, it will ask you the path of the source code and the path of the build directory in which it is going to generate all CMake scripts necessary to compile VTK. Then, when you click on the "configure" button, it will ask you to choose the generator for the project. As illustrated on figure below, choose Unix Makefiles with default native compilers. {image:../../download/generator_png} You will then have to choose the settings of installation, as shown on figure below: {image:../../download/configuration_png} Click on the "configure" button and then "generate". 1.1 b) OSX Special actions Depending on the version of VTK, special actions are required to make vrk compatible with OSGi (as explained in the first section). - In the latest versions of VTK (> 6.3.0), there is a ~~VTK_JAVA_INSTALL~~ option. If we select this option, the dynamic librairies are copied to the destination folder and jars are created there. The folders lib, bin and include are not created. This option also changes the extension of the dynamic libraries from .dylib to .jnilib. - On previous versions of VTK (< 6.3.0), you need to perform the following actions : __Changing .dylib extensions to .jnilib__ With the Terminal application, go to the root folder of the build directory. We are now going to you __sed__ command to replace all .dylib extensions by .jnilib extensions. Use the [../../download/changeLibExtensions_sh?action=download&nodecorator] script: __Nota Bene:__ ~~there are no double backslashes in the sed commands, its the wiki system I use that does not want to write a single backslash, so DO NOT copy/paste the following lines or replace the double backslashes by single ones. The double slash // is correct however. The best solution is to download the sh file directly~~ {code} > grep -r -H ".dylib" .|awk -F':' '{print $1}'|sort|uniq|grep -v "^Binary"|grep -v "CMakeCache.txt">dylibFiles.txt > cat dylibFiles.txt | xargs sed -i "" 's/\\.dylib/\\.jnilib/g' {code} __Inserting the @loader_path attribute in dynamic library dependencies__ We know have to insert the @loader_path attribute in the installation script. Use the [../../download/addLoaderPath_sh?action=download&nodecorator] script: __Nota Bene:__ ~~there are no double backslashes in the sed commands, its the wiki system I use that does not want to write a single backslash, so DO NOT copy/paste the following lines or replace the double backslashes by single ones. The double slash // is correct however. The best solution is to download the sh file directly~~ {code} > grep -r -H '\\-change' .|awk -F':' '{print $1}'|sort|uniq|grep -v "^Binary"|grep -v "$~"|grep -v "addLoaderPath.sh">changeLib.txt > cat changeLib.txt | xargs sed -i "" 's/id "/id "@loader_path\\//' > cat changeLib.txt | xargs sed -i "" 's/-change "\\(.*\\)" "\\(.*\\)"/-change "\\1" "@loader_path\\/\\2"/' {code} 1.1 c) Compiling VTK We just have now to compile and install VTK (depending on your machine this may take one to two hours, so be patient :-) ) {code} > make -j 2 > make install {code} The "-j 2" option is set to use two processors on my machine. 1.1 d) Removing the version numbers To remove the version numbers, I have created a small script called [../../download/copyNoVersion_sh?action=download&nodecorator]. To use it, first create a new empty directory and execute it with the command: {code} ./copyNoVersion $JNILIB_FOLDER {code} where $JNIFOLDER is the folder containing the jnilib files you just created. The script copies the required jnilib files (not the symbolic links), changes their name to remove the version number and then updates the dependencies with the __otool__ command to remove the version numbers as well. 1.1 e) Download the result If you don't want to go through all these steps and just would like the result directly, you can download it for VTK 5.6.1 here: [http://maven.artenum.com/content/groups/public/vtk/vtk-native/5.6.1/vtk-native-5.6.1-osx64b.zip]. The CMakeCache.txt file I used to compile and wrap this version of VTK is here: [../../download/CMakeCache_txt?action=download&nodecorator] 1 3) Creating a simple Java application using VTK Now that we have a compiled version of VTK, lets create a concrete example showing how to use VTK from a pure Java application (no OSGi yet). This sample application will display a 3D cone in a JFrame. {image:../../download/screenshot_cone_png} The source code (together with the compiled VTK lib) can be download here : [../../download/test-vtk_zip?action=download&nodecorator]. In this project, there are two classes: - __org.test.vtk.TestVTK__ is a JFrame that contains one method ~~start()~~ to initialize a vtkPanel containing the 3D cone. - __org.test.vtk.Start__ is the class that contains the ~~main(String\[\] args)~~ method that will be executed on startup. I have used [http://maven.apache.org|Maven] for the project compilation and packaging, so if you want to generate the executable jar file, you will need Maven installed on your machine. Then, in the Terminal application, go to the root folder of the project and type: {code} mvn clean package {code} This should create a "target" folder containing two jar files. What we are interested in is the __test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar__ file that contains both the project classes and the VTK java classes. Now, lets execute the code. As explained in part 1, on OSX, there are two ways to execute a Java application using native libraries: - The first one is to use the system environment variable __DYLD_LIBRARY_PATH__. This is what is done in the __runSysEnv.sh__ script placed in the project root folder. {code} export DYLD_LIBRARY_PATH=./dependencies/thirdparty/vtk/vtk-5.6.1/osx64b/ java -jar target/test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar {code} - The second method uses the __java.library.path__ Java property to indicate the JVM where to find the libraries. This is what is done in the __runJavaProp.sh__ script placed in the project root folder. {code} java -Djava.library.path=./dependencies/thirdparty/vtk/vtk-5.6.1/osx64b/ -jar target/test-vtk-2.0-SNAPSHOT-jar-with-dependencies.jar {code} If you execute either of the provided scripts, you will see a beautiful 3D cone. If we would have used the default VTK Java wrapping, only the first one would have worked, while the second one would have thrown an ~~UnsatisfiedLinkError~~. 1 4) Embedding VTK in an OSGi bundle Now that we have seen how to create a Java project using VTK on OSX, we can try to create a VTK OSGi bundle. In this project, we have created two classes: - __org.test.vtk.osgi.TestVTK__ is the same JFrame that contains the method ~~start()~~ to initialize a vtkPanel containing the 3D cone. It also contains the ~~stop()~~method called when the bundle is stopped. - __org.test.vtk.osgi.Activator__ is the ~~BundleActivator~~ class that will be called by OSGi when the bundle is started or stopped. It statically loads the VTK libraries one by one, in the correct order of their dependencies, as shown below: {code} static { System.loadLibrary("vtkproj4"); System.loadLibrary("vtkalglib"); System.loadLibrary("vtksys"); System.loadLibrary("vtkCommon"); System.loadLibrary("vtkFiltering"); System.loadLibrary("vtkexpat"); System.loadLibrary("vtkjpeg"); System.loadLibrary("vtkzlib"); System.loadLibrary("vtklibxml2"); System.loadLibrary("vtktiff"); System.loadLibrary("vtkpng"); System.loadLibrary("vtksqlite"); System.loadLibrary("vtkmetaio"); System.loadLibrary("vtkNetCDF"); System.loadLibrary("vtkDICOMParser"); System.loadLibrary("vtkNetCDF_cxx"); System.loadLibrary("vtkIO"); System.loadLibrary("vtkImaging"); System.loadLibrary("vtkverdict"); System.loadLibrary("vtkGraphics"); System.loadLibrary("vtkfreetype"); System.loadLibrary("vtkftgl"); System.loadLibrary("vtkGenericFiltering"); System.loadLibrary("vtkRendering"); System.loadLibrary("vtkexoIIc"); System.loadLibrary("Cosmo"); System.loadLibrary("VPIC"); System.loadLibrary("vtkParallel"); System.loadLibrary("vtkHybrid"); System.loadLibrary("vtkWidgets"); System.loadLibrary("vtkVolumeRendering"); } {code} We can execute the bundle in an OSGi service platform implementation (we chose [http://www.eclipse.org/equinox|Equinox], but it also works with [http://felix.apache.org|Felix]). {code} # First we delete the cache folder contraining previously loaded bundles rm -rf configuration/org.eclipse.osgi configuration *.log \# Then we execute Equinox with the argument -Xcheck:jni to see JNI-related errors java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/target/test-osgi-vtk-2.0-SNAPSHOT.jar osgi> start 1 {code} But here, we end up with an error: {code} WARNING in native method: JNI call made with exception pending at vtk.vtkPanel.RenderCreate(Native Method) at vtk.vtkPanel.Render(vtkPanel.java:166) - locked <1060ffa88> (a vtk.vtkPanel) at vtk.vtkPanel.paint(vtkPanel.java:189) at sun.awt.RepaintArea.paintComponent(RepaintArea.java:276) at sun.awt.RepaintArea.paint(RepaintArea.java:241) at apple.awt.ComponentModel.handleEvent(ComponentModel.java:263) at java.awt.Component.dispatchEventImpl(Component.java:4790) at java.awt.Component.dispatchEvent(Component.java:4544) at java.awt.EventQueue.dispatchEvent(EventQueue.java:635) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188) at java.awt.EventDispatchThread.run(EventDispatchThread.java:122) FATAL ERROR in native method: Bad global or local ref passed to JNI at vtk.vtkPanel.RenderCreate(Native Method) at vtk.vtkPanel.Render(vtkPanel.java:166) - locked <1060ffa88> (a vtk.vtkPanel) at vtk.vtkPanel.paint(vtkPanel.java:189) at sun.awt.RepaintArea.paintComponent(RepaintArea.java:276) at sun.awt.RepaintArea.paint(RepaintArea.java:241) at apple.awt.ComponentModel.handleEvent(ComponentModel.java:263) at java.awt.Component.dispatchEventImpl(Component.java:4790) at java.awt.Component.dispatchEvent(Component.java:4544) at java.awt.EventQueue.dispatchEvent(EventQueue.java:635) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188) at java.awt.EventDispatchThread.run(EventDispatchThread.java:122) Invalid memory access of location 0x0 rip=0x1010af1f4 ./ben.sh: line 11: 23453 Abort trap java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console {code} The problem comes from the way OSGi bundle classloader manages package visibility (thank you Richard for your help on this issue: http://www.mail-archive.com/users@felix.apache.org/msg08864.html). In standard Java applications, the Classloader makes all packages accessible by anyone. In a modular frameworks like OSGi, the idea is that each bundle has to __declare__ what it needs (Import-Package) and what it provides (Export-Package). The framework then enforces these visibility constraints in order to build a loosely coupled module system. In OSGi, when a package is not explicitly exported, it is not visible to others. And when it is not explicitly imported by a bundle, this one cannot access it. By default in OSGi, only java.* packages are available to anyone. And this is what causes our error above: the RenderCreate native method requires apple.awt.ComponentModel to work properly. But the apple.awt package was not declared in the bundle Import-Package tag, so when the native code is called, it does not find it. There are two ways to solve the problem: - In Felix/Equinox configuration settings, add the "org.osgi.framework.bootdelegation=apple.*" property. This tells OSGi that all apple.* packages are visible to all bundles. This works, but you somewhat lose the modularity of OSGi, as everyone can access those packages. - Explicitly import the package in the bundle manifest Import-Package entry. For OSGi to resolve this new dependency of your bundle, you can either: -- add org.osgi.framework.system.packages.extra=apple.awt in Felix/Equinox configuration settings (your configuration is however now platform-dependent) -- create a system bundle fragment that exports apple.awt (and contains no other files than the manifest) I chose in my example application to implement the latter: a system bundle fragment, called apple.awt.1.0.0.jar is available at the root project. When running my OSGi framework, I just need to load the fragment bundle: {code} # First we delete the cache folder contraining previously loaded bundles rm -rf configuration/org.eclipse.osgi configuration *.log \# Then we execute Equinox with the argument -Xcheck:jni to see JNI-related errors java -Xcheck:jni -jar org.eclipse.osgi_3.6.0.v20100517.jar -console osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/apple.awt.1.0.0.jar osgi> install file:/Users/ben/Documents/workspace/test-osgi-vtk/target/test-osgi-vtk-2.0-SNAPSHOT.jar osgi> start 2 {code} Note that we don't start the system fragment. It is not possible to start a fragment because it has no activator. The source code of this sample project (together with the compiled VTK lib) can be download here : [../../download/test-osgi-vtk_zip?action=download&nodecorator]. 1 Conclusion and suggestions As we have seen, we have successfully created a VTK OSGi bundle for OSX. We have first reviewed how MacOSX linking works and what are the OSGi-specific constraints when dealing with native code. We also saw how to solve these linking problems via a few "simple" shell scripts. Then, we showed how to use VTK from a basic Java application, before embedding VTK in an OSGi bundle. We are going to suggest VTK development team to integrate at least the jnilib extensions and the @loader_path to their CMake scripts to prevent the use of our pre-compilation scripts. We are also going to suggest them not to version the jnilib files. The next step of this work will be to create a cross-platform version of our VTK bundle. Here, we have developed a single bundle for OSX, but we would like to use VTK on the major platforms available (Windows, Linux, OSX) for 32 and 64 bits architectures. We will thus create a common VTK bundle and platform-dependent fragments that will be deployed depending on the target environment. ---- {resource:../../comments/OSGi_MacOSX_and_VTK}
Last edited by Arnaud Trouche at Jan 18, 2024 10:35 AM -
Edit content
-
View history
-
View source
Decorated version
-
Feeds
-
LibreSource