Browse Source

UseJava: Add RESOURCES with NAMESPACE to add_jar()

add_jar() currently requires (undocumented) that resources be supplied
as relative paths.  The resources *may* then end up in a path which does
not reflect the original path particularly when performing out-of-source
builds.  This change adds a RESOURCE (and NAMESPACE) parameter and a
function to add the names resources into the named namespace within the
jar- and thus address both of these problems.

Fixes: #22101
Smit tay 4 years ago
parent
commit
3e03f359a7

+ 5 - 0
Help/release/dev/UseJava-RESOURCES-NAMESPACE.rst

@@ -0,0 +1,5 @@
+UseJava-RESOURCES-NAMESPACE
+---------------------------
+
+* The :module:`UseJava` module command ``add_jar`` gained option RESOURCES
+  allow explicit naming of resouces with non-optional namespace.

+ 96 - 7
Modules/UseJava.cmake

@@ -18,6 +18,7 @@ Creating And Installing JARs
 
   add_jar(<target_name>
           [SOURCES] <source1> [<source2>...] [<resource1>...]
+          [RESOURCES NAMESPACE <ns1> <resource1>... [NAMESPACE <nsX> <resourceX>...]... ]
           [INCLUDE_JARS <jar1> [<jar2>...]]
           [ENTRY_POINT <entry>]
           [VERSION <version>]
@@ -31,7 +32,31 @@ This command creates a ``<target_name>.jar``.  It compiles the given
 ``<source>`` files and adds the given ``<resource>`` files to
 the jar file.  Source files can be java files or listing files
 (prefixed by ``@``).  If only resource files are given then just a jar file
-is created.  The list of ``INCLUDE_JARS`` are added to the classpath when
+is created.
+
+The ``RESOURCES`` parameter adds the named ``<resource>`` files to the jar
+by stripping the source file path and placing the file beneath ``<ns>``
+within the jar. For example::
+
+  RESOURCES NAMESPACE "/com/my/namespace" "a/path/to/resource.txt"
+
+results in a resource accessible via ``/com/my/namespace/resource.txt``
+within the jar.
+
+Resources may be added without adjusting the namespace by adding them to
+the list of ``SOURCES`` (original behavior), in this case, resource
+paths must be relative to ``CMAKE_CURRENT_SOURCE_DIR``.  Adding resources
+without using the ``RESOURCES`` parameter in out of source builds will
+almost certainly result in confusion.
+
+.. note:: SOURCES
+  Adding resources via the ``SOURCES`` parameter relies upon a hard-coded
+  list of file extensions which are tested to determine compilability
+  (e.g. File.java). ``SOURCES`` files which match the extensions are compiled.
+  Files which do not match are treated as resources. To include uncompiled
+  resources matching those file extensions use the ``RESOURCES`` parameter.
+
+The list of ``INCLUDE_JARS`` are added to the classpath when
 compiling the java sources and also to the dependencies of the target.
 ``INCLUDE_JARS`` also accepts other target names created by ``add_jar()``.
 For backwards compatibility, jar files listed as sources are ignored (as
@@ -449,6 +474,58 @@ function(__java_export_jar VAR TARGET PATH)
     set(${VAR} "${${VAR}}" PARENT_SCOPE)
 endfunction()
 
+function(__java_copy_resource_namespaces VAR DEST JAVA_RESOURCE_FILES JAVA_RESOURCE_FILES_RELATIVE)
+
+    set(_ns_ID "")
+    set(_ns_VAL "")
+
+    foreach(_item IN LISTS VAR)
+        if(NOT _ns_ID)
+            if(NOT _item STREQUAL "NAMESPACE")
+                message(FATAL_ERROR "UseJava: Expecting \"NAMESPACE\", got\t\"${_item}\"")
+                return()
+            endif()
+        endif()
+
+        if(_item STREQUAL "NAMESPACE")
+            set(_ns_VAL "")               # Prepare for next namespace
+            set(_ns_ID "${_item}")
+            continue()
+        endif()
+
+        if( NOT _ns_VAL)
+            # we're expecting the next token to be a namespace value
+            # whatever it is, we're treating it like a namespace
+            set(_ns_VAL "${_item}")
+            continue()
+        endif()
+
+        if(_ns_ID AND _ns_VAL)
+            # We're expecting a file name, check to see if we got one
+            cmake_path(ABSOLUTE_PATH _item OUTPUT_VARIABLE _test_file_name)
+            if (NOT EXISTS "${_test_file_name}")
+                message(FATAL_ERROR "UseJava: File does not exist:\t${_item}")
+                return()
+            endif()
+        endif()
+
+        cmake_path(ABSOLUTE_PATH _item OUTPUT_VARIABLE _abs_file_name)
+        cmake_path(GET _item FILENAME _resource_file_name)
+        set(_dest_resource_file_name "${_ns_VAL}/${_resource_file_name}" )
+
+        __java_copy_file( ${_abs_file_name}
+                          ${DEST}/${_dest_resource_file_name}
+                          "Copying ${_item} to the build directory")
+
+        list(APPEND RESOURCE_FILES_LIST           ${DEST}/${_dest_resource_file_name})
+        list(APPEND RELATIVE_RESOURCE_FILES_LIST  ${_dest_resource_file_name})
+
+    endforeach()
+
+    set(${JAVA_RESOURCE_FILES} "${RESOURCE_FILES_LIST}" PARENT_SCOPE)
+    set(${JAVA_RESOURCE_FILES_RELATIVE} "${RELATIVE_RESOURCE_FILES_LIST}" PARENT_SCOPE)
+endfunction()
+
 # define helper scripts
 set(_JAVA_EXPORT_TARGETS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJava/javaTargets.cmake.in)
 set(_JAVA_SYMLINK_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJava/Symlinks.cmake)
@@ -461,12 +538,14 @@ endif()
 
 function(add_jar _TARGET_NAME)
 
-    cmake_parse_arguments(_add_jar
-      ""
-      "VERSION;OUTPUT_DIR;OUTPUT_NAME;ENTRY_POINT;MANIFEST"
-      "SOURCES;INCLUDE_JARS;GENERATE_NATIVE_HEADERS"
-      ${ARGN}
-    )
+    set(options)  # currently there are no zero value args (aka: options)
+    set(oneValueArgs "ENTRY_POINT;MANIFEST;OUTPUT_DIR;;OUTPUT_NAME;VERSION" )
+    set(multiValueArgs "GENERATE_NATIVE_HEADERS;INCLUDE_JARS;RESOURCES;SOURCES" )
+
+    cmake_parse_arguments(PARSE_ARGV 1 _add_jar
+                    "${options}"
+                    "${oneValueArgs}"
+                    "${multiValueArgs}" )
 
     # In CMake < 2.8.12, add_jar used variables which were set prior to calling
     # add_jar for customizing the behavior of add_jar. In order to be backwards
@@ -490,6 +569,9 @@ function(add_jar _TARGET_NAME)
         set(_add_jar_ENTRY_POINT "${CMAKE_JAVA_JAR_ENTRY_POINT}")
     endif()
 
+    # This *should* still work if <resources1>... are included without a
+    # named RESOURCES argument.  In that case, the old behavior of potentially
+    # misplacing the within the Jar will behave as previously (incorrectly)
     set(_JAVA_SOURCE_FILES ${_add_jar_SOURCES} ${_add_jar_UNPARSED_ARGUMENTS})
 
     if (NOT DEFINED _add_jar_OUTPUT_DIR)
@@ -639,6 +721,13 @@ function(add_jar _TARGET_NAME)
         endif ()
     endforeach()
 
+    if(_add_jar_RESOURCES)         # Process RESOURCES if it exists
+        __java_copy_resource_namespaces("${_add_jar_RESOURCES}"
+                                        ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
+                                        _JAVA_RESOURCE_FILES
+                                        _JAVA_RESOURCE_FILES_RELATIVE)
+    endif()
+
     foreach(_JAVA_INCLUDE_JAR IN LISTS _add_jar_INCLUDE_JARS)
         if (TARGET ${_JAVA_INCLUDE_JAR})
             get_target_property(_JAVA_JAR_PATH ${_JAVA_INCLUDE_JAR} JAR_FILE)

+ 7 - 0
Tests/Java/CMakeLists.txt

@@ -17,9 +17,16 @@ add_jar(hello2 @${CMAKE_CURRENT_BINARY_DIR}/java_fileslist)
 # use listing file to specify sources and specify output directory (issue #17316)
 add_jar(hello3 @${CMAKE_CURRENT_BINARY_DIR}/java_fileslist OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/hello3")
 
+add_jar(ResourceNS
+        SOURCES ResourceNS.java
+        RESOURCES NAMESPACE ns/ns1 HelloWorld.txt
+                  NAMESPACE ns/ns2 HelloWorld.txt) # add a second namespace
+
 add_test (NAME Java.Jar
           COMMAND "${Java_JAVA_EXECUTABLE}" -classpath hello.jar HelloWorld)
 add_test (NAME Java.JarSourceList
           COMMAND "${Java_JAVA_EXECUTABLE}" -classpath hello2.jar HelloWorld)
 add_test (NAME Java.JarSourceListAndOutput
           COMMAND "${Java_JAVA_EXECUTABLE}" -classpath "${CMAKE_CURRENT_BINARY_DIR}/hello3/hello3.jar" HelloWorld)
+add_test (NAME Java.JarResourceNS
+          COMMAND "${Java_JAVA_EXECUTABLE}" -classpath ResourceNS.jar ResourceNS)

+ 1 - 0
Tests/Java/HelloWorld.txt

@@ -0,0 +1 @@
+Hello World !

+ 48 - 0
Tests/Java/ResourceNS.java

@@ -0,0 +1,48 @@
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+class ResourceNS
+{
+    public static void main(String args[])
+    {
+        ResourceNS res = new ResourceNS();
+        res.displayResourceText();
+    }
+
+    public void displayResourceText()
+    {
+        /*
+         * Since Java SE 9, invoking getResourceXXX on a class in a named
+         * module will only locate the resource in that module, it will
+         * not search the class path as it did in previous release. So when
+         * you use Class.getClassLoader().getResource() it will attempt to
+         * locate the resource in the module containing the ClassLoader,
+         * possibly something like:
+         *      jdk.internal.loader.ClassLoaders.AppClassLoader
+         * which is probably not the module that your resource is in, so it
+         * returns null.
+         *
+         * You have to make java 9+ search for the file in your module.
+         * Do that by changing Class to any class defined in your module in
+         * order to make java use the proper class loader.
+        */
+
+        // Namespaces are relative, use leading '/' for full namespace
+        InputStream is =
+            ResourceNS.class.getResourceAsStream("/ns/ns1/HelloWorld.txt");
+        // C++:    cout << is.readline();    // oh, well !
+        InputStreamReader isr = new InputStreamReader(is);
+        BufferedReader reader = new BufferedReader(isr);
+        String out = "";
+        try{
+            out = reader.readLine();
+        } catch(IOException e) {
+            e.printStackTrace();
+            System.out.println(e);
+        }
+
+        System.out.println(out);
+    }
+}