pax_global_header00006660000000000000000000000064151036442450014516gustar00rootroot0000000000000052 comment=05d80e783cdd1e8236f6d9296c118f02a345404e libjjml-java-1.1.13/000077500000000000000000000000001510364424500141435ustar00rootroot00000000000000libjjml-java-1.1.13/.gitattributes000066400000000000000000000003021510364424500170310ustar00rootroot00000000000000* text=auto *.txt text *.properties text *.ini text *.bnd text *.jsh text *.java text *.c text *.cpp text *.h text *.js text *.sln text eol=crlf *.bat text eol=crlf *.png binary *.jpg binary libjjml-java-1.1.13/.github/000077500000000000000000000000001510364424500155035ustar00rootroot00000000000000libjjml-java-1.1.13/.github/workflows/000077500000000000000000000000001510364424500175405ustar00rootroot00000000000000libjjml-java-1.1.13/.github/workflows/msvc.yml000066400000000000000000000047131510364424500212400ustar00rootroot00000000000000name: All Java versions with MSVC on: workflow_dispatch: # allows manual triggering push: branches: [ "unstable" ] pull_request: branches: [ "testing", "unstable" ] jobs: build: name: Java ${{ matrix.java }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [windows-latest] build_type: [Release] c_compiler: [cl] java: ['11', '17', '21', '25' ] include: - os: windows-latest c_compiler: cl cpp_compiler: cl steps: - uses: actions/checkout@v4 name: Clone recursively with tags with: submodules: 'true' fetch-depth: '0' fetch-tags: 'true' - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings shell: bash run: | echo "build-output-dir=${{ github.workspace }}\build" >> "$GITHUB_OUTPUT" echo "${{ github.workspace }}\a2\lib\x86_64-win32-default\org.argeo.tp.ggml" >> $GITHUB_PATH - uses: actions/setup-java@v5 name: Setup Java with: distribution: 'semeru' java-version: ${{ matrix.java }} - name: Configure CMake run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -DJAVA_HOME=%JAVA_HOME% -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_SKIP_BUILD_RPATH=ON -DJJML_FORCE_BUILD_TP=ON -DA2_INSTALL_MODE=a2 -DLLAMA_CURL=OFF -DLLAMA_BUILD_COMMON=ON -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DGGML_VULKAN=OFF -S ${{ github.workspace }} - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} -j $(nproc) - name: Test run: > java -ea "-Djava.library.path=${{ github.workspace }}\a2\lib\x86_64-win32-default\org.argeo.jjml;${{ github.workspace }}\a2\lib\x86_64-win32-default\org.argeo.tp.ggml" "--module-path=${{ github.workspace }}\a2\org.argeo.jjml" --add-modules org.argeo.jjml sdk/jbin/JjmlSmokeTests.java allenai/OLMo-2-0425-1B-Instruct-GGUF libjjml-java-1.1.13/.github/workflows/ubuntu.yml000066400000000000000000000051231510364424500216060ustar00rootroot00000000000000name: Ubuntu + Vulkan backend on: workflow_dispatch: # allows manual triggering push: branches: [ "unstable" ] pull_request: branches: [ "testing", "unstable" ] jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] build_type: [Release] c_compiler: [gcc, clang] include: - os: ubuntu-24.04 c_compiler: gcc cpp_compiler: g++ - os: ubuntu-24.04-arm c_compiler: gcc cpp_compiler: g++ - os: ubuntu-24.04 c_compiler: clang cpp_compiler: clang++ - os: ubuntu-24.04-arm c_compiler: clang cpp_compiler: clang++ steps: - uses: actions/checkout@v4 name: Clone recursively with tags with: submodules: 'true' fetch-depth: '0' fetch-tags: 'true' - name: Set reusable strings # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. id: strings shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - name: Dependencies id: depends run: | sudo apt-get install -y default-jdk-headless glslc libvulkan-dev - name: Configure CMake run: > cmake -B ${{ steps.strings.outputs.build-output-dir }} -G "Unix Makefiles" -DJAVA_HOME=/usr/lib/jvm/default-java -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DJJML_FORCE_BUILD_TP=ON -DLLAMA_CURL=OFF -DLLAMA_BUILD_COMMON=ON -DLLAMA_BUILD_TOOLS=ON -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TESTS=OFF -DGGML_NATIVE=ON -DGGML_BACKEND_DL=OFF -DGGML_CPU_ALL_VARIANTS=OFF -DGGML_VULKAN=ON -S ${{ github.workspace }} - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} -j $(nproc) - name: Test run: > java -ea -Djava.library.path=${{ steps.strings.outputs.build-output-dir }}/../a2/lib/$(uname -m)-linux-gnu/org.argeo.jjml:${{ steps.strings.outputs.build-output-dir }}/../a2/lib/$(uname -m)-linux-gnu/org.argeo.tp.ggml --module-path ${{ steps.strings.outputs.build-output-dir }}/../a2/org.argeo.jjml --add-modules org.argeo.jjml sdk/jbin/JjmlSmokeTests.java allenai/OLMo-2-0425-1B-Instruct-GGUF libjjml-java-1.1.13/.gitignore000066400000000000000000000004351510364424500161350ustar00rootroot00000000000000# clean with: # git clean -dxf /output/ /build/ /a2/ /sdk.mk # CMake generated files in source CMakeCache.txt *.cmake **/CMakeFiles/ Makefile # JVM crashes core core.*.dmp jitdump.*.dmp javacore.*.txt Snap.*.trc hs_err_pid*.log install_manifest.txt # Visual Studio /out/build /.vs libjjml-java-1.1.13/.gitmodules000066400000000000000000000003261510364424500163210ustar00rootroot00000000000000[submodule "sdk/argeo-build"] path = sdk/argeo-build url = https://git.argeo.org/argeo-build [submodule "native/include/argeo/jni"] path = native/include/argeo/jni url = https://git.argeo.org/argeo-include-jni libjjml-java-1.1.13/.project000066400000000000000000000003131510364424500156070ustar00rootroot00000000000000 argeo-jjml libjjml-java-1.1.13/.settings/000077500000000000000000000000001510364424500160615ustar00rootroot00000000000000libjjml-java-1.1.13/.settings/org.eclipse.core.resources.prefs000066400000000000000000000000671510364424500242770ustar00rootroot00000000000000eclipse.preferences.version=1 encoding/=UTF-8 libjjml-java-1.1.13/CMakeLists.txt000066400000000000000000000010111510364424500166740ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.14) project("argeo-jjml" DESCRIPTION "Enterprise-grade Java bindings for the ggml ecosystem" ) set(A2_CATEGORY org.argeo.jjml) if(NOT DEFINED A2_JAVA_RELEASE) set(A2_JAVA_RELEASE 11) endif() # Can build with headless JDK set(JAVA_AWT_LIBRARY NotNeeded) set(JAVA_AWT_INCLUDE_PATH NotNeeded) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/sdk/argeo-build/cmake") find_package(ArgeoBuild) # JAVA set(BUNDLES org.argeo.jjml ) a2_build_bundles(${BUNDLES}) add_subdirectory(native) libjjml-java-1.1.13/CMakeSettings.json000066400000000000000000000017501510364424500175420ustar00rootroot00000000000000{ "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\..\\output\\argeo-jjml", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "-DJAVA_HOME=\"/usr/lib/jvm/ibm-semeru-21-openj9-amd64\" -DLLAMA_BUILD_COMMON=ON -DLLAMA_BUILD_TOOLS=ON -DLLAMA_CURL=OFF", "buildCommandArgs": "", "ctestCommandArgs": "" }, { "name": "x64-Release", "generator": "Ninja", "configurationType": "Release", "buildRoot": "${projectDir}\\..\\output\\argeo-jjml", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": " -DJAVA_HOME=\"/usr/lib/jvm/ibm-semeru-21-openj9-amd64\" -DLLAMA_BUILD_COMMON=ON -DLLAMA_BUILD_TOOLS=ON -DLLAMA_CURL=OFF", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ] } ] }libjjml-java-1.1.13/COPYING.LESSER000066400000000000000000000636421510364424500162050ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libjjml-java-1.1.13/NOTICE000066400000000000000000000126521510364424500150550ustar00rootroot00000000000000Argeo JJML - Java bindings for the ggml family of machine learning libraries, especially llama.cpp Copyright 2024-2025 Mathieu Baudier Copyright 2024-2025 Argeo GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . ## Third-party software licenses When the Program is distributed with third-party components as a Java runtime the legal details of each component can be found under the legal/ directory. ## Alternative licenses As an alternative, this Program is also provided to you under the terms and conditions of the Eclipse Public License version 2.0 or any later version. A copy of the Eclipse Public License version 2.0 is available at http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, version 2.0, or any later versions of that license, with additional EPL and JCR permissions (these additional permissions being detailed hereafter). ## Additional Permissions (when applicable) In case you decide to relicense this program to the GNU General Public License version 2.0, or any later version, the copyright holders of Argeo JJML give you the following additional permissions: # Eclipse Public License Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with any program released under the terms and conditions of the Eclipse Public License v1.0, v2.0 (even without a Secondary License enabled) or any later version of this license. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned, provided that you include the source code of that other code when and as the GNU GPL requires distribution of source code. Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. # Apache License Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with any program released under the terms and conditions of the Apache License v2.0 or any later version of this license. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned, provided that you include the source code of that other code when and as the GNU GPL requires distribution of source code. Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. # Java Content Repository API version 2.0 Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with code included in the standard release of the JCR API version 2.0 (and this version only), licensed under the Day Specification License and the Day JCR License. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned. Copies of the Day Specification License and the Day JCR License are expected to be available here: https://jackrabbit.apache.org/jcr/jcr.html Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. libjjml-java-1.1.13/README.md000066400000000000000000000301071510364424500154230ustar00rootroot00000000000000# Enterprise-grade Java bindings for the ggml ecosystem ### ## Generative AI locally, integrated into existing Java systems ## Argeo JJML provides low-level Java bindings for the [ggml](https://github.com/ggml-org/ggml) family of machine learning libraries, especially [llama.cpp](https://github.com/ggml-org/llama.cpp) which allows to run locally open-weights large language models (LLMs, aka. "generative AI"). The main goal of this lightweight component is to provide an enterprise-grade quality mechanism to integrate local LLMs into existing Java systems, with stable Java APIs, a small auditable code base, and essentially no impact on other components. While the field of LLMs is moving very fast, with new open-weight models being published on a monthly basis, there is already a lot that can be done reliably, and the ggml and llama.cpp projects have proven that they can combine a vibrant community of contributors with good software engineering. Argeo JJML provides a kind of "shock absorber" for the Java ecosystem, smoothing the unavoidable native API breakages, supporting old Java versions, and avoiding the deployment of Python-based solutions in an enterprise setting, when only inference is needed. The native interface layer is written in C++ and relies solely on the plain ggml-*.so/dll and llama.so/dll shared libraries and their headers. That is, it does not use llama.cpp's "common" layer, but rather provides a subset of its features. The Java layer does not depend on any Argeo or third-party Java libraries, and is also built with CMake. It has no other dependency than the `java.base` module of the standard Java runtime, and is therefore well-suited for creating stripped-down Java runtimes with the `jlink` utility. No tooling or application is provided, except some examples for testing and development purposes. Focus is on stability rather than supporting the latest features. Usable features such as chatbots, RAG, HTTP APIs, etc. should be implemented on top of this component, typically using third-party Java libraries and frameworks. The applications targeted by this library are mostly enterprise Java systems in regulated, sovereign or sustainable industries (where running LLMs on premise or on a private cloud is a requirement). But it can also be helpful when developing or patching ggml-based native libraries, by providing a simple way to write robust scripted tests or small prototypes using modern Java features such as `jdk.jshell`, `jdk.httpserver`, WebSocket client, etc. ## Features ## - Java 11, 17, 21 and 25 support - Persistence of context state, typically in order to "pre-compile" prompt prefixes - Parallel batches - Embeddings - Chat templates (limited to those embedded in llama.cpp) - API-less user/assistant dialog based on standard `java.util.function` interfaces - Combination and configuration of the native samplers from the Java side - API for implementing samplers in pure Java - JPMS and OSGi metadata - Android support (from SDK version 26, example project in the `unstable` branch) ## Build ## The build relies only on CMake and the [argeo-build](https://github.com/argeo/argeo-build) scripts (as a git submodule). Pinned reference versions of both [ggml](https://github.com/ggml-org/ggml) and [llama.cpp](https://github.com/ggml-org/llama.cpp) are provided as git submodules as well. *One should therefore always use `git pull --recurse-submodules` when updating.* ### Debian (reference) # Install dependencies: ``` sudo apt install default-jdk # install Java sudo apt install cmake gcc g++ # install build tools sudo apt install libllama-dev # (optional) llama.cpp dev packages, where available ``` Build: ``` git clone --recurse-submodules https://github.com/argeo/argeo-jjml cd argeo-jjml cmake -B build/default -DJAVA_HOME=/usr/lib/jvm/default-java cmake --build build/default -j $(nproc) ``` The built artifacts are located under `/../a2`. One can then run some smoke tests: ``` java -ea \ -cp "build/a2/org.argeo.jjml/*" \ -Djava.library.path=build/a2/lib/$(uname -m)-linux-gnu/org.argeo.jjml:build/a2/lib/$(uname -m)-linux-gnu/org.argeo.tp.ggml \ sdk/jbin/JjmlSmokeTests.java \ allenai/OLMo-2-0425-1B-Instruct-GGUF ``` or a basic CLI: ``` java \ -cp "build/a2/org.argeo.jjml/*" \ -Djava.library.path=build/a2/lib/$(uname -m)-linux-gnu/org.argeo.jjml:build/a2/lib/$(uname -m)-linux-gnu/org.argeo.tp.ggml \ sdk/jbin/JjmlDummyCli.java \ allenai/OLMo-2-0425-1B-Instruct-GGUF ``` If the shared libraries are found at the usual locations (`/usr`, `/usr/local`, etc., as well as Debian-specific `/usr/lib/\*/ggml` and `/usr/lib/\*/llama`) they will be used, then assuming that the related includes, cmake-* configs, etc. are available as well. Otherwise, the reference ggml and llama.cpp submodules will be built in addition to the Java bindings. If the ggml and llama.cpp libraries are rebuilt, all the `GGML_*` and `LLAMA_*` CMake options are available to their respective builds, which can therefore be customized exactly like regular llama.cpp builds. In order to force building with the reference submodules even if the libraries are locally available, use `-DJJML_FORCE_BUILD_TP=ON` when configuring CMake. Reciprocally, use `-DJJML_DO_NOT_BUILD_TP=ON` in order to make sure that the build is using the system libraries for ggml and llama.cpp (and not automatically defaulting to build the submodules). If both ggml and llama.cpp are available as system libraries, only the (tiny) JJML shared libraries are needed. In that case use only In order to add them when testing, extend the JNI path, using `-Djava.library.path=build/a2/lib/$(uname -m)-linux-gnu/org.argeo.jjml` as JNI path. When building the reference submodules, setting `-DJJML_FORCE_BUILD_LLAMA_GGML=ON` will build with the ggml version included in `native/tp/llama.cpp`. The default is to build with the separate `native/tp/ggml` reference submodule. This is useful when testing with the latest version of llama.cpp or a development branch. By default, the Java part of JJML is built for the minimal supported Java version (that is, Java 11). In order to build for a later Java version, set `A2_JAVA_RELEASE` when configuring the CMake build, e.g. `-DA2_JAVA_RELEASE=21`. While a lot of work goes into making this build straightforward and portable, there must be a clear baseline: - The reference build for Linux is on Debian Sid (amd64/x86_64 and arm64/aarch64), using the official Debian packages for ggml and llama.cpp. (JJML's lead developer is a regular contributor to this Debian packaging effort) - The reference build for Windows is with the Microsoft MSVC compiler. (see example below) - Other operating systems (notably MacOS, Android) could work but are not currently considered When reporting build issues on a given platform, please first check whether a reference build is working. ### Windows notes # An example Windows build would be (in a PowerShell terminal): ``` # install a JDK winget install Microsoft.OpenJDK.21 $env:JAVA_HOME = "C:/Program Files/Microsoft/jdk-21.0.8.9-hotspot" # use CMake from Visual Studio 2022 $env:CMAKE_HOME = "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake" & "$env:CMAKE_HOME\bin\cmake.exe" -B build/default -DJAVA_HOME=$env:JAVA_HOME & "$env:CMAKE_HOME\bin\cmake.exe" --build build/default ``` Of course, one should rather use an existing Java installation, just note that `-DJAVA_HOME` must be specified with regular slashes (`/`). Run the smoke tests: ``` $env:Path = "build/a2/lib/x86_64-win32-default/org.argeo.tp.ggml" & "$env:JAVA_HOME/bin/java" -ea ` -cp "build/a2/org.argeo.jjml/*" ` "-Djava.library.path=build/a2/lib/x86_64-win32-default/org.argeo.jjml;build/a2/lib/x86_64-win32-default/org.argeo.tp.ggml" ` sdk/jbin/JjmlSmokeTests.java ` allenai/OLMo-2-0425-1B-Instruct-GGUF ``` ### Red Hat Enterprise Linux notes # On RHEL9, build in a dedicated toolset: ``` scl enable gcc-toolset-14 -- cmake -B build/default -DJAVA_HOME=/usr/lib/jvm/java scl enable gcc-toolset-14 -- cmake --build build/default -j $(nproc) ``` For CUDA, explicitely set the CUDA compiler: ``` scl enable gcc-toolset-14 -- cmake -B build/default -DJAVA_HOME=/usr/lib/jvm/java \ -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc ``` ## Status ## Argeo JJML is currently in open beta, the last phase before a first stable release. All features of the future stable release are implemented and should not change significantly. Work has already started on commercial projects using it in various industries. Now is a good time to start using it in a given context, as we can quickly fix issues or integrate feedback, while the APIs already have taken their shape. Future features: - Shift/rewind/fork context - Speech recognition and transcription with [whisper.cpp](https://github.com/ggml-org/whisper.cpp) integration (already working in the `unstable` branch) - Image recognition and multimodal support with llama.cpp's [mtmd](https://github.com/ggml-org/llama.cpp/tree/master/tools/mtmd) (work-in-progress in the `unstable` branch) ## Contact ## Issues can be raised via [GitHub](https://github.com/argeo/argeo-jjml/issues) or Debian's [Salsa](https://salsa.debian.org/mbaudier/libjjml-java/-/issues) (especially when related to packaging or portability). All other queries, typically new features, should be directed to Mathieu Baudier via [LinkedIn](https://www.linkedin.com/in/mbaudier/). Argeo GmbH also provide support and consulting services around the integration of JJML into existing Java systems. ## Licensing ## Argeo JJML is dual-licensed: - LGPL v2.1 (or later version) - EPL v2, with GPL as a possible secondary license ``` Copyright 2024-2025 Mathieu Baudier Copyright 2024-2025 Argeo GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . ## Alternative licenses As an alternative, this Program is also provided to you under the terms and conditions of the Eclipse Public License version 2.0 or any later version. A copy of the Eclipse Public License version 2.0 is available at http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, version 2.0, or any later versions of that license, with additional EPL and JCR permissions (these additional permissions being detailed hereafter). ``` See [NOTICE](NOTICE) for more details. ``` SPDX-License-Identifier: LGPL-2.1-or-later OR EPL-2.0 OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-Apache-and-JCR-permissions ``` ## Alternatives for deploying machine learning with Java ## - [java-llama.cpp](https://github.com/kherud/java-llama.cpp) - The Java bindings referenced by the llama.cpp project. It relies on llama.cpp "common" layer and strives to provide the `llama-server` features set. It should therefore be more complete in terms of features, while slightly more heavyweight. Argeo JJML provides a different approach, not a competing one. - [Jlama](https://github.com/tjake/Jlama) - An inference engine written in Java and based on the latest advancements in Java technology (esp. the new Vector API). Supports models in *.safetensors format but (at the time of writing) not in GGUF format. - [llama3.java](https://github.com/mukel/llama3.java) - A very short plain Java implementation based on the new Vector API. Supports only Meta's llama 3.x models (in GGUF format). - [langchain4j](https://github.com/langchain4j/langchain4j) - A comprehensive LLM framework in Java with various backends, including [ollama](https://github.com/ollama/ollama) (and therefore llama.cpp). libjjml-java-1.1.13/branch.mk000066400000000000000000000000161510364424500157260ustar00rootroot00000000000000BRANCH=testinglibjjml-java-1.1.13/configure000077500000000000000000000002631510364424500160530ustar00rootroot00000000000000#!/bin/sh # Source are located where this script is SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)" # Source the configure script . $SDK_SRC_BASE/sdk/argeo-build/cmake/configure libjjml-java-1.1.13/doc/000077500000000000000000000000001510364424500147105ustar00rootroot00000000000000libjjml-java-1.1.13/doc/reference/000077500000000000000000000000001510364424500166465ustar00rootroot00000000000000libjjml-java-1.1.13/doc/reference/.gitignore000066400000000000000000000000061510364424500206320ustar00rootroot00000000000000/api/ libjjml-java-1.1.13/native/000077500000000000000000000000001510364424500154315ustar00rootroot00000000000000libjjml-java-1.1.13/native/.cproject000066400000000000000000000542471510364424500172570ustar00rootroot00000000000000 libjjml-java-1.1.13/native/.gitignore000066400000000000000000000000161510364424500174160ustar00rootroot00000000000000/build/ *.log libjjml-java-1.1.13/native/.project000066400000000000000000000014021510364424500170750ustar00rootroot00000000000000 argeo-jjml-native org.eclipse.cdt.managedbuilder.core.genmakebuilder org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder full,incremental, org.eclipse.cdt.core.cnature org.eclipse.cdt.core.ccnature org.eclipse.cdt.managedbuilder.core.managedBuildNature org.eclipse.cdt.managedbuilder.core.ScannerConfigNature libjjml-java-1.1.13/native/.settings/000077500000000000000000000000001510364424500173475ustar00rootroot00000000000000libjjml-java-1.1.13/native/.settings/.gitignore000066400000000000000000000000251510364424500213340ustar00rootroot00000000000000language.settings.xmllibjjml-java-1.1.13/native/.settings/org.eclipse.cdt.core.prefs000066400000000000000000000003421510364424500243210ustar00rootroot00000000000000doxygen/doxygen_new_line_after_brief=true doxygen/doxygen_use_brief_tag=false doxygen/doxygen_use_javadoc_tags=true doxygen/doxygen_use_pre_tag=false doxygen/doxygen_use_structural_commands=false eclipse.preferences.version=1 libjjml-java-1.1.13/native/.settings/org.eclipse.core.resources.prefs000066400000000000000000000000671510364424500255650ustar00rootroot00000000000000eclipse.preferences.version=1 encoding/=UTF-8 libjjml-java-1.1.13/native/CMakeLists.txt000066400000000000000000000067001510364424500201740ustar00rootroot00000000000000# # OPTIONS # option(JJML_FORCE_BUILD_TP "Force build of reference third-party submodules even if they are available on the system") option(JJML_DO_NOT_BUILD_TP "Fail if third-party libraries are not found, instead of building them from the reference submodules") option(JJML_JDK_TP "Use third-party environment provided by a ggml-instrumented JDK (useful for Windows)") # # THIRD PARTY DETECTION # #set(CMAKE_FIND_DEBUG_MODE ON) if(NOT JJML_FORCE_BUILD_TP) if(NOT MSVC) # Debian official until libraries are stable set(CMAKE_PREFIX_PATH /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/ggml/cmake-private /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/llama/cmake-private ) endif() if(JJML_JDK_TP) # Use environment provided by a ggml-instrumented JDK set(CMAKE_LIBRARY_PATH ${JAVA_HOME}/bin ${JAVA_HOME}/lib) include_directories(${JAVA_HOME}/include SYSTEM) endif() # look for llama.cpp via CMake configs (preferred) find_package(llama QUIET) if(llama_FOUND) message(STATUS "Found llama.cpp and ggml packages: ${llama_LIBRARY} ${GGML_LIBRARY} ${GGML_BASE_LIBRARY}") else() # look for llama.cpp via simple library search find_library(llama_LIBRARY llama) # look for ggml via CMake configs (preferred) find_package(ggml QUIET) if(ggml_FOUND) message(STATUS "Found ggml package: ${GGML_LIBRARY} ${GGML_BASE_LIBRARY}") else() # look for ggml via simple library search find_library(GGML_LIBRARY ggml) find_library(GGML_BASE_LIBRARY ggml-base) endif() endif() endif() # JJML_FORCE_BUILD_TP if(EXISTS ${GGML_LIBRARY}) # ggml as external libraries if(NOT ggml_FOUND) # not CMake configured add_library(ggml SHARED IMPORTED GLOBAL) if(MSVC) set_target_properties(ggml PROPERTIES IMPORTED_IMPLIB ${GGML_LIBRARY}) else() set_target_properties(ggml PROPERTIES IMPORTED_LOCATION ${GGML_LIBRARY}) endif() add_library(ggml-base SHARED IMPORTED GLOBAL) if(MSVC) set_target_properties(ggml-base PROPERTIES IMPORTED_IMPLIB ${GGML_BASE_LIBRARY}) else() set_target_properties(ggml-base PROPERTIES IMPORTED_LOCATION ${GGML_BASE_LIBRARY}) endif() message(STATUS "Found ggml libraries: ${GGML_LIBRARY} ${GGML_BASE_LIBRARY}") endif() if(NOT DEFINED GGML_BACKEND_DL) message(STATUS "## CHECK ###") include(CheckIncludeFileCXX) check_include_file_cxx(ggml-backend.h JJML_BACKEND_INCLUDE_FOUND) if(JJML_BACKEND_INCLUDE_FOUND) set(GGML_BACKEND_DL ON) message(STATUS "Found ggml-backend.h, enabling GGML_BACKEND_DL") endif() endif() endif() if(EXISTS "${llama_LIBRARY}") # llama.cpp as external library if(NOT llama_FOUND) # not CMake configured add_library(llama SHARED IMPORTED GLOBAL) if(MSVC) set_target_properties(llama PROPERTIES IMPORTED_IMPLIB ${llama_LIBRARY}) else() set_target_properties(llama PROPERTIES IMPORTED_LOCATION ${llama_LIBRARY}) endif() target_link_libraries(llama INTERFACE ${GGML_LIBRARY}) message(STATUS "Found LLAMA library: ${llama_LIBRARY}") endif() endif() if(NOT JJML_DO_NOT_BUILD_TP) # Build reference third-party submodules if needed add_subdirectory(tp) endif() message(STATUS "GGML_BUILD_NUMBER=${GGML_BUILD_NUMBER} LLAMA_BUILD_NUMBER=${LLAMA_BUILD_NUMBER}") # # JJML NATIVE BUILD # if(GGML_BACKEND_DL) message(STATUS "Build JNI libraries with support for dynamic ggml backends") endif() # Force ISO C++ if (MSVC) add_compile_options(/permissive-) else() add_compile_options(-pedantic-errors) endif() # JNI shared libraries add_subdirectory(org_argeo_jjml_ggml) add_subdirectory(org_argeo_jjml_llm) libjjml-java-1.1.13/native/include/000077500000000000000000000000001510364424500170545ustar00rootroot00000000000000libjjml-java-1.1.13/native/include/.gitignore000066400000000000000000000000171510364424500210420ustar00rootroot00000000000000org.argeo.jjml libjjml-java-1.1.13/native/include/argeo/000077500000000000000000000000001510364424500201515ustar00rootroot00000000000000libjjml-java-1.1.13/native/include/argeo/jni/000077500000000000000000000000001510364424500207315ustar00rootroot00000000000000libjjml-java-1.1.13/native/org_argeo_jjml_ggml/000077500000000000000000000000001510364424500214175ustar00rootroot00000000000000libjjml-java-1.1.13/native/org_argeo_jjml_ggml/CMakeLists.txt000066400000000000000000000005311510364424500241560ustar00rootroot00000000000000set(TARGET Java_org_argeo_jjml_ggml) add_library(${TARGET} SHARED jjml_ggml_backend.cpp ) if(ggml_FOUND) target_link_libraries(${TARGET} PRIVATE ggml::ggml) else() target_link_libraries(${TARGET} PRIVATE ggml) endif() if (GGML_BACKEND_DL) target_compile_definitions(${TARGET} PRIVATE GGML_BACKEND_DL) endif() a2_jni_target(${TARGET}) libjjml-java-1.1.13/native/org_argeo_jjml_ggml/jjml_ggml_backend.cpp000066400000000000000000000013721510364424500255370ustar00rootroot00000000000000#include #ifdef GGML_BACKEND_DL #include #endif #include #include "org_argeo_jjml_ggml_GgmlBackend.h" // IWYU pragma: keep JNIEXPORT jlong JNICALL Java_org_argeo_jjml_ggml_GgmlBackend_doLoadBackend( JNIEnv *env, jclass, jbyteArray path) { std::string p = argeo::jni::to_string(env, path); #ifdef GGML_BACKEND_DL ggml_backend_load(p.c_str()); #else // FIXME throw exception #endif return 0; } JNIEXPORT void JNICALL Java_org_argeo_jjml_ggml_GgmlBackend_doLoadAllBackends( JNIEnv *env, jclass, jbyteArray basePath) { std::string search_path = argeo::jni::to_string(env, basePath); #ifdef GGML_BACKEND_DL ggml_backend_load_all_from_path(search_path.c_str()); #else // FIXME throw exception #endif } libjjml-java-1.1.13/native/org_argeo_jjml_llm/000077500000000000000000000000001510364424500212555ustar00rootroot00000000000000libjjml-java-1.1.13/native/org_argeo_jjml_llm/CMakeLists.txt000066400000000000000000000015211510364424500240140ustar00rootroot00000000000000set(TARGET Java_org_argeo_jjml_llm) add_library(${TARGET} SHARED org_argeo_jjml_llm_.cpp jjml_llm.cpp jjml_llm_vocabulary.cpp jjml_llm_model.cpp jjml_llm_chat.cpp jjml_llm_context.cpp jjml_llm_sampling.cpp jjml_llm_batch_write.cpp jjml_llm_batch_read.cpp jjml_llm_embedding.cpp ) if(ggml_FOUND) target_link_libraries(${TARGET} PRIVATE ggml::ggml ggml::ggml-base llama) else() target_link_libraries(${TARGET} PRIVATE ggml ggml-base llama) endif() # When breaking changes are not functionally critical # we keep building against older, in order to extend the life # of a given JJML branch. To be trimmed when a new branch # is created. # for example: #if(LLAMA_BUILD_NUMBER LESS 6325) ## Breaks flash attention context parameters #target_compile_definitions(${TARGET} PRIVATE JJML_PRE_LLAMA_0_0_6325) #endif() a2_jni_target(${TARGET}) libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm.cpp000066400000000000000000000011041510364424500235550ustar00rootroot00000000000000#include "jjml_llm.h" #include /* * SHARED PLAIN C++ UTILITIES */ void jjml_llm_batch_add(struct llama_batch &batch, llama_token id, llama_pos pos, const std::vector &seq_ids, bool logits) { batch.token[batch.n_tokens] = id; batch.pos[batch.n_tokens] = pos; batch.n_seq_id[batch.n_tokens] = seq_ids.size(); for (size_t i = 0; i < seq_ids.size(); ++i) { batch.seq_id[batch.n_tokens][i] = seq_ids[i]; } batch.logits[batch.n_tokens] = logits; batch.n_tokens++; } void jjml_llm_batch_clear(struct llama_batch &batch) { batch.n_tokens = 0; } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm.h000066400000000000000000000007751510364424500232370ustar00rootroot00000000000000#include #include #ifndef _jjml_llama_h #define _jjml_llama_h /* * SHARED UTILITIES */ /** * @brief Adds token to a batch. * * @param batch * @param id * @param pos * @param seq_ids * @param logits */ void jjml_llm_batch_add(struct llama_batch &batch, llama_token id, llama_pos pos, const std::vector &seq_ids, bool logits); /** * @brief Clears a batch. * * @param batch the batch to clear */ void jjml_llm_batch_clear(struct llama_batch &batch); #endif libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_batch_read.cpp000066400000000000000000000225571510364424500257300ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppBatchProcessor.h" // IWYU pragma: keep #include "jjml_llm.h" #include "org_argeo_jjml_llm_.h" static std::vector jjml_get_logits(llama_context *ctx, int idx) { const auto *logits = llama_get_logits_ith(ctx, idx); const llama_vocab *vocab = llama_model_get_vocab(llama_get_model(ctx)); const int n_vocab = llama_vocab_n_tokens(vocab); std::vector cur; cur.resize(n_vocab); for (llama_token token_id = 0; token_id < n_vocab; token_id++) { cur[token_id] = llama_token_data { token_id, logits[token_id], 0.0f }; } return cur; } static llama_token jjml_check_grammar(llama_context *ctx, int idx, llama_sampler *chain, llama_sampler *grmr, llama_token id) { // check if it the sampled token fits the grammar { llama_token_data single_token_data = { id, 1.0f, 0.0f }; llama_token_data_array single_token_data_array = { &single_token_data, 1, -1, false }; llama_sampler_apply(grmr, &single_token_data_array); const bool is_valid = single_token_data_array.data[0].logit != -INFINITY; if (is_valid) { return id; } } // resampling: // if the token is not valid, sample again, but first apply the grammar sampler and then the sampling chain std::vector cur = jjml_get_logits(ctx, idx); llama_token_data_array cur_p = { cur.data(), cur.size(), -1, false, }; llama_sampler_apply(grmr, &cur_p); llama_sampler_apply(chain, &cur_p); GGML_ASSERT( cur_p.selected != -1 && "no selected token during re-sampling - check your sampling configuration"); llama_token res = cur_p.data[cur_p.selected].id; return res; } static jint jjml_llm_batch_processor_read(llama_context *ctx, llama_sampler *smpl, llama_sampler *grmr, llama_pos cur_pos, std::vector outputs, const int outputs_count, JNIEnv *env, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jobject completionHandler) { const llama_vocab *vocab = llama_model_get_vocab(llama_get_model(ctx)); const uint32_t NO_OUTPUT_ID = llama_n_batch(ctx); const int n_parallel = env->GetArrayLength(sequenceIds); assert(n_parallel > 0 && "Sequence count"); jint *arr = env->GetIntArrayElements(sequenceIds, nullptr); std::vector sequence_ids(n_parallel); for (int i = 0; i < n_parallel; i++) { sequence_ids[i] = static_cast(arr[i]); } env->ReleaseIntArrayElements(sequenceIds, arr, 0); auto *output_ids = reinterpret_cast(env->GetIntArrayElements( outputIds, nullptr)); assert(outputs_count == n_parallel && "As many buffers as sequences"); assert(outputs_count > 0); assert(lengths != nullptr); assert(env->GetArrayLength(lengths) == outputs_count); jint *seq_tokens_size = env->GetIntArrayElements(lengths, nullptr); assert(offsets != nullptr); assert(env->GetArrayLength(offsets) == outputs_count); jint *seq_offsets = env->GetIntArrayElements(offsets, nullptr); std::vector seq_tokens(outputs_count); for (int i = 0; i < outputs_count; i++) { void *output = outputs[i]; if (output != nullptr) { seq_tokens[i] = static_cast(output) + seq_offsets[i]; } else { seq_tokens[i] = nullptr; } } int max_decodes = 0; for (int i = 0; i < n_parallel; i++) { if (seq_tokens_size[i] > max_decodes) max_decodes = seq_tokens_size[i]; } PERF_BEGIN(); int next_idx = 0; llama_batch batch = llama_batch_init(n_parallel, 0, n_parallel); // FIXME deal more precisely with the upper limit int n_predict = cur_pos + max_decodes; bool all_eog = true; while (cur_pos <= n_predict) { // prepare the next batch jjml_llm_batch_clear(batch); // sample the next token for each parallel sequence / stream for (int32_t i = 0; i < n_parallel; ++i) { if (seq_tokens[i] == nullptr) // no output available continue; if (output_ids[i] == NO_OUTPUT_ID) // already finished continue; PERF_BEGIN(); llama_token new_token_id; if (grmr == nullptr) { new_token_id = llama_sampler_sample(smpl, ctx, output_ids[i]); } else { // grammar handling require lower-level methods std::vector cur = jjml_get_logits(ctx, output_ids[i]); llama_token_data_array cur_p = { cur.data(), cur.size(), -1, false, }; llama_sampler_apply(smpl, &cur_p); llama_token candidate = cur_p.data[cur_p.selected].id; new_token_id = jjml_check_grammar(ctx, output_ids[i], smpl, grmr, candidate); llama_sampler_accept(grmr, new_token_id); llama_sampler_accept(smpl, new_token_id); } // PERF_END("sampling"); bool is_eog = llama_vocab_is_eog(vocab, new_token_id); // is it an end of generation? -> mark the stream as finished if (is_eog // || next_idx == seq_tokens_size[i] // || cur_pos == n_predict // ) { if (is_eog) output_ids[i] = NO_OUTPUT_ID; jclass Integer = argeo::jni::find_jclass(env, "java/lang/Integer"); jobject completionHandlerResult = env->CallStaticObjectMethod( Integer, Integer__valueOf, next_idx); jobject completionHandlerAttachment = env->CallStaticObjectMethod(Integer, Integer__valueOf, i); // call completion handler env->CallVoidMethod(completionHandler, CompletionHandler__completed, completionHandlerResult, completionHandlerAttachment); if (!is_eog) // at least one could have continued all_eog = false; continue; } // std::cerr << cur_pos << "\t" << i << "\t" << new_token_id // << std::endl; assert(next_idx < seq_tokens_size[i] && "No overflow"); seq_tokens[i][next_idx] = new_token_id; output_ids[i] = batch.n_tokens; // push this new token for next evaluation jjml_llm_batch_add(batch, new_token_id, cur_pos, { sequence_ids[i] }, true); } next_idx++; // all streams are finished if (batch.n_tokens == 0) { break; } cur_pos += 1; // evaluate the current batch with the transformer model if (llama_decode(ctx, batch)) throw std::runtime_error("Decode failed"); } // clean up // !! dereference what Java owns before batch is freed // for (size_t i = 0; i < n_parallel; i++) { // batch.seq_id[i] = nullptr; // } llama_batch_free(batch); PERF_END(__func__); // clean up env->ReleaseIntArrayElements(offsets, reinterpret_cast(seq_offsets), 0); env->ReleaseIntArrayElements(lengths, reinterpret_cast(seq_tokens_size), 0); env->ReleaseIntArrayElements(outputIds, reinterpret_cast(output_ids), 0); // TODO assert consistency of context position with regard to the output buffers sizes return cur_pos; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppBatchProcessor_doRead( JNIEnv *env, jclass, jlong contextPointer, jlong samplerPtr, jlong grammarSamplerPtr, jint contextPosition, jobjectArray outputBuffers, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jobject completionHandler) { auto *ctx = argeo::jni::as_pointer(contextPointer); auto *smpl = argeo::jni::as_pointer(samplerPtr); auto *grmr = grammarSamplerPtr != 0 ? argeo::jni::as_pointer(grammarSamplerPtr) : nullptr; llama_pos cur_pos = static_cast(contextPosition); int outputs_count = env->GetArrayLength(outputBuffers); std::vector outputs(outputs_count); for (int i = 0; i < outputs_count; i++) { jobject buf = env->GetObjectArrayElement(outputBuffers, i); if (buf != nullptr) { outputs[i] = env->GetDirectBufferAddress(buf); } else { outputs[i] = nullptr; } } jint newPosition; try { newPosition = jjml_llm_batch_processor_read(ctx, smpl, grmr, cur_pos, outputs, outputs_count, env, offsets, lengths, sequenceIds, outputIds, completionHandler); } catch (std::exception &ex) { argeo::jni::throw_to_java(env, ex); } return newPosition; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppBatchProcessor_doReadToArrays( JNIEnv *env, jclass, jlong contextPointer, jlong samplerPtr, jlong grammarSamplerPtr, jint contextPosition, jobjectArray outputArrays, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jobject completionHandler) { auto *ctx = argeo::jni::as_pointer(contextPointer); auto *smpl = argeo::jni::as_pointer(samplerPtr); auto *grmr = grammarSamplerPtr != 0 ? argeo::jni::as_pointer(grammarSamplerPtr) : nullptr; llama_pos cur_pos = static_cast(contextPosition); int outputs_count = env->GetArrayLength(outputArrays); std::vector outputs(outputs_count); for (int i = 0; i < outputs_count; i++) { jintArray arr = (jintArray) env->GetObjectArrayElement(outputArrays, i); if (arr != nullptr) { outputs[i] = env->GetPrimitiveArrayCritical(arr, nullptr); } else { outputs[i] = nullptr; } } jint newPosition; try { newPosition = jjml_llm_batch_processor_read(ctx, smpl, grmr, cur_pos, outputs, outputs_count, env, offsets, lengths, sequenceIds, outputIds, completionHandler); } catch (std::exception &ex) { argeo::jni::throw_to_java(env, ex); } // clean up for (int i = 0; i < outputs_count; i++) { jintArray arr = (jintArray) env->GetObjectArrayElement(outputArrays, i); if (arr != nullptr) { env->ReleasePrimitiveArrayCritical(arr, outputs[i], 0); } } return newPosition; } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_batch_write.cpp000066400000000000000000000171321510364424500261400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppBatchProcessor.h" // IWYU pragma: keep #include "jjml_llm.h" /* * WRITE */ static jint jjml_llm_batch_processor_write(llama_context *ctx, llama_sampler *smpl, llama_pos cur_pos, std::vector inputs, const int inputs_count, JNIEnv *env, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jboolean lastLogits) { assert(sequenceIds != nullptr); const int n_parallel = env->GetArrayLength(sequenceIds); const llama_model *model = llama_get_model(ctx); const llama_vocab *vocab = llama_model_get_vocab(model); auto *sequence_ids = reinterpret_cast(env->GetIntArrayElements( sequenceIds, nullptr)); auto *output_ids = reinterpret_cast(env->GetIntArrayElements( outputIds, nullptr)); assert(inputs_count > 0); assert(lengths != nullptr); assert(env->GetArrayLength(lengths) == inputs_count); jint *seq_tokens_size = env->GetIntArrayElements(lengths, nullptr); assert(offsets != nullptr); assert(env->GetArrayLength(offsets) == inputs_count); jint *seq_offsets = env->GetIntArrayElements(offsets, nullptr); std::vector seq_tokens(inputs_count); for (int i = 0; i < inputs_count; i++) { void *input = inputs[i]; if (input != nullptr) { seq_tokens[i] = static_cast(input) + seq_offsets[i]; } else { seq_tokens[i] = nullptr; } } int total_tokens = 0; int max_decodes = 0; for (int i = 0; i < inputs_count; i++) { total_tokens = total_tokens + seq_tokens_size[i]; if (seq_tokens_size[i] > max_decodes) max_decodes = seq_tokens_size[i]; } PERF_BEGIN(); if (inputs_count == 1) { // common prompt to all sequences const int seq_idx = 0; int input_tokens_size = seq_tokens_size[seq_idx]; llama_batch batch = llama_batch_init( std::max((size_t) input_tokens_size, (size_t) n_parallel), 0, n_parallel); for (size_t i = 0; i < input_tokens_size; i++) { // FIXME deal with null input batch.token[batch.n_tokens] = seq_tokens[seq_idx][i]; batch.pos[batch.n_tokens] = cur_pos + i; batch.n_seq_id[batch.n_tokens] = n_parallel; for (size_t j = 0; j < n_parallel; j++) { batch.seq_id[batch.n_tokens][j] = sequence_ids[j]; } batch.logits[batch.n_tokens] = false; batch.n_tokens++; } batch.n_tokens = input_tokens_size; GGML_ASSERT(batch.n_tokens == (int ) input_tokens_size); // FIXME temporary backward compatibility std::vector seq_ids(n_parallel, 0); for (int i = 0; i < n_parallel; i++) { seq_ids[i] = sequence_ids[i]; } // deal with encoder // TODO does it make sense to do that when it is not the first batch? if (llama_model_has_encoder(model)) { if (llama_encode(ctx, batch)) throw std::runtime_error("Encode failed"); llama_token decoder_start_token_id = llama_model_decoder_start_token(model); if (decoder_start_token_id == -1) { decoder_start_token_id = llama_vocab_bos(vocab); } jjml_llm_batch_clear(batch); jjml_llm_batch_add(batch, decoder_start_token_id, cur_pos, seq_ids, false); } // llama_decode will output logits only for the last token of the prompt if (lastLogits) { batch.logits[batch.n_tokens - 1] = true; for (int i = 0; i < n_parallel; i++) { output_ids[i] = batch.n_tokens - 1; } } if (llama_decode(ctx, batch) != 0) throw std::runtime_error("Decode failed"); // sampler accept for (int i = 0; i < batch.n_tokens; i++) { llama_token token = batch.token[i]; llama_sampler_accept(smpl, token); } cur_pos = cur_pos + batch.n_tokens; llama_batch_free(batch); } else { assert(inputs_count == n_parallel); llama_batch batch = llama_batch_init(total_tokens, 0, n_parallel); try { for (size_t j = 0; j < n_parallel; j++) { for (size_t i = 0; i < seq_tokens_size[j]; i++) { // FIXME deal with null input batch.token[batch.n_tokens] = seq_tokens[j][i]; batch.pos[batch.n_tokens] = cur_pos; batch.n_seq_id[batch.n_tokens] = 1; batch.seq_id[batch.n_tokens][0] = sequence_ids[j]; batch.logits[batch.n_tokens] = false; batch.n_tokens++; cur_pos++; } if (lastLogits) { batch.logits[batch.n_tokens - 1] = true; output_ids[j] = batch.n_tokens - 1; } } // TODO deal with encoder models? if (llama_decode(ctx, batch) != 0) throw std::runtime_error("Decode failed"); } catch (...) { llama_batch_free(batch); throw std::current_exception(); } llama_batch_free(batch); } PERF_END(__func__); // clean up env->ReleaseIntArrayElements(offsets, reinterpret_cast(seq_offsets), 0); env->ReleaseIntArrayElements(lengths, reinterpret_cast(seq_tokens_size), 0); env->ReleaseIntArrayElements(sequenceIds, reinterpret_cast(sequence_ids), 0); env->ReleaseIntArrayElements(outputIds, reinterpret_cast(output_ids), 0); return cur_pos; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppBatchProcessor_doWrite( JNIEnv *env, jclass, jlong contextPointer, jlong samplerChainPointer, jint contextPosition, jobjectArray inputBuffers, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jboolean lastLogits) { auto *ctx = argeo::jni::as_pointer(contextPointer); auto *smpl = argeo::jni::as_pointer(samplerChainPointer); llama_pos cur_pos = static_cast(contextPosition); int inputs_count = env->GetArrayLength(inputBuffers); std::vector inputs(inputs_count); for (int i = 0; i < inputs_count; i++) { jobject inputBuf = env->GetObjectArrayElement(inputBuffers, i); if (inputBuf != nullptr) { inputs[i] = env->GetDirectBufferAddress(inputBuf); } else { inputs[i] = nullptr; } } jint newPosition; try { newPosition = jjml_llm_batch_processor_write(ctx, smpl, cur_pos, inputs, inputs_count, env, offsets, lengths, sequenceIds, outputIds, lastLogits); } catch (std::exception &ex) { argeo::jni::throw_to_java(env, ex); } return newPosition; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppBatchProcessor_doWriteArrays( JNIEnv *env, jclass, jlong contextPointer, jlong samplerChainPointer, jint contextPosition, jobjectArray inputArrays, jintArray offsets, jintArray lengths, jintArray sequenceIds, jintArray outputIds, jboolean lastLogits) { auto *ctx = argeo::jni::as_pointer(contextPointer); auto *smpl = argeo::jni::as_pointer(samplerChainPointer); llama_pos cur_pos = static_cast(contextPosition); int inputs_count = env->GetArrayLength(inputArrays); std::vector inputs(inputs_count); for (int i = 0; i < inputs_count; i++) { jarray arr = (jarray) env->GetObjectArrayElement(inputArrays, i); if (arr != nullptr) { inputs[i] = env->GetPrimitiveArrayCritical(arr, nullptr); } else { inputs[i] = nullptr; } } jint *sequence_ids_arr = env->GetIntArrayElements(sequenceIds, nullptr); //jint sequence_ids_arr[1]; //env->GetIntArrayRegion(sequenceIds, 0 , 1, sequence_ids_arr); jint newPosition; try { newPosition = jjml_llm_batch_processor_write(ctx, smpl, cur_pos, inputs, inputs_count, env, offsets, lengths, sequenceIds, outputIds, lastLogits); } catch (std::exception &ex) { argeo::jni::throw_to_java(env, ex); } // clean up for (int i = 0; i < inputs_count; i++) { jarray arr = (jarray) env->GetObjectArrayElement(inputArrays, i); if (arr != nullptr) { env->ReleasePrimitiveArrayCritical(arr, inputs[i], 0); } } return newPosition; } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_chat.cpp000066400000000000000000000052621510364424500245650ustar00rootroot00000000000000#include #include #include #include #include #include #include "org_argeo_jjml_llm_LLamaCppNativeChatFormatter.h" // IWYU pragma: keep /* * CHAT */ JNIEXPORT jbyteArray JNICALL Java_org_argeo_jjml_llm_LLamaCppNativeChatFormatter_doFormatChatMessages( JNIEnv *env, jclass, jobjectArray roles, jobjectArray contents, jboolean addAssistantTokens, jbyteArray chatTemplateStr) { const jsize messages_size = env->GetArrayLength(roles); assert(env->GetArrayLength(contents) == messages_size); std::vector chat_messages; try { int alloc_size = 0; // since the content can be quite big, we go through the heap for (int i = 0; i < messages_size; i++) { std::string u8_role = argeo::jni::to_string(env, roles, i); std::string u8_content = argeo::jni::to_string(env, contents, i); char *role = new char[u8_role.length() + 1]; strcpy(role, u8_role.c_str()); char *content = new char[u8_content.length() + 1]; strcpy(content, u8_content.c_str()); llama_chat_message message { role, content }; chat_messages.push_back(message); // using the same factor as in common.cpp alloc_size += (u8_role.length() + u8_content.length()) * 1.25; } std::string u8_chat_template; if (chatTemplateStr != nullptr) u8_chat_template = argeo::jni::to_string(env, chatTemplateStr); std::vector buf(alloc_size); int32_t resLength = llama_chat_apply_template( chatTemplateStr != nullptr ? u8_chat_template.c_str() : nullptr, chat_messages.data(), chat_messages.size(), addAssistantTokens, buf.data(), buf.size()); // error: chat template is not supported if (resLength < 0) { if (chatTemplateStr != nullptr) throw std::runtime_error("Custom template is not supported"); else throw std::runtime_error("Built-in template is not supported"); } // if it turns out that our buffer is too small, we resize it if ((size_t) resLength > buf.size()) { buf.resize(resLength); resLength = llama_chat_apply_template( chatTemplateStr != nullptr ? u8_chat_template.c_str() : nullptr, chat_messages.data(), chat_messages.size(), addAssistantTokens, buf.data(), buf.size()); } // we clean up, since we don't need the messages anymore for (int i = 0; i < messages_size; i++) { llama_chat_message message = chat_messages[i]; delete message.role; delete message.content; } std::string u8_res(buf.data(), resLength); jbyteArray res = env->NewByteArray(u8_res.length()); env->SetByteArrayRegion(res, 0, u8_res.length(), (jbyte*) &u8_res[0]); return res; } catch (std::exception &ex) { return argeo::jni::throw_to_java(env, ex); } } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_context.cpp000066400000000000000000000251551510364424500253350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppBackend.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_LlamaCppContext.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_.h" static struct ggml_threadpool *threadpool = NULL; /* * STATE */ JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetStateSize( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return static_cast(llama_state_get_size(ctx)); //return llama_get_state_size(ctx);// deprecated } JNIEXPORT jbyteArray JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetStateDataAsBytes( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); size_t size = llama_state_get_size(ctx); jbyteArray res = env->NewByteArray(size); void *dst = env->GetPrimitiveArrayCritical(res, NULL); size_t n_bytes = llama_state_get_data(ctx, static_cast(dst), size); env->ReleasePrimitiveArrayCritical(res, dst, 0); // TODO check n_bytes return res; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetStateData( JNIEnv *env, jobject obj, jobject buf, jint offset) { auto *ctx = argeo::jni::as_pointer(env, obj); size_t size = llama_state_get_size(ctx); void *dst = env->GetDirectBufferAddress(buf); if (dst == NULL) throw std::invalid_argument("Input is not a direct buffer"); assert(env->GetDirectBufferCapacity(buf) >= offset + size); size_t n_bytes = llama_state_get_data(ctx, static_cast(dst), size); return n_bytes; } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doSetStateDataBytes( JNIEnv *env, jobject obj, jbyteArray arr, jint offset, jint length) { auto *ctx = argeo::jni::as_pointer(env, obj); void *src = env->GetPrimitiveArrayCritical(arr, NULL); size_t n_bytes = llama_state_set_data(ctx, static_cast(src) + offset, length); env->ReleasePrimitiveArrayCritical(arr, src, 0); // TODO check n_bytes } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doSetStateData( JNIEnv *env, jobject obj, jobject buf, jint offset, jint length) { auto *ctx = argeo::jni::as_pointer(env, obj); void *src = env->GetDirectBufferAddress(buf); if (src == NULL) throw std::invalid_argument("Input is not a direct buffer"); size_t n_bytes = llama_state_set_data(ctx, static_cast(src) + offset, length); } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doSaveStateFile( JNIEnv *env, jobject obj, jbyteArray path, jobject buf, jint offset, jint length) { auto *ctx = argeo::jni::as_pointer(env, obj); std::string p = argeo::jni::to_string(env, path); void *tokens_arr = env->GetDirectBufferAddress(buf); if (tokens_arr == NULL) throw std::invalid_argument("Input is not a direct buffer"); assert(env->GetDirectBufferCapacity(buf) // >= (offset + length) * sizeof(llama_token)); auto *tokens = static_cast(tokens_arr) + offset; llama_state_save_file(ctx, p.c_str(), tokens, length); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doLoadStateFile( JNIEnv *env, jobject obj, jbyteArray path, jobject buf, jint offset) { auto *ctx = argeo::jni::as_pointer(env, obj); std::string p = argeo::jni::to_string(env, path); void *tokens_arr = env->GetDirectBufferAddress(buf); if (tokens_arr == NULL) throw std::invalid_argument("Input is not a direct buffer"); size_t capacity = env->GetDirectBufferCapacity(buf) / sizeof(llama_token) - offset; auto *tokens = static_cast(tokens_arr) + offset; size_t n_token_count; llama_state_load_file(ctx, p.c_str(), tokens, capacity, &n_token_count); return n_token_count; } /* * PARAMETERS */ /** @brief Get context parameters from Java to native.*/ static void get_context_params(JNIEnv *env, jobject params, llama_context_params *ctx_params) { jclass clss = env->FindClass(JCLASS_CONTEXT_PARAMS.c_str()); // integers ctx_params->n_ctx = env->CallIntMethod(params, env->GetMethodID(clss, "n_ctx", "()I")); ctx_params->n_batch = env->CallIntMethod(params, env->GetMethodID(clss, "n_batch", "()I")); ctx_params->n_ubatch = env->CallIntMethod(params, env->GetMethodID(clss, "n_ubatch", "()I")); ctx_params->n_seq_max = env->CallIntMethod(params, env->GetMethodID(clss, "n_seq_max", "()I")); ctx_params->n_threads = env->CallIntMethod(params, env->GetMethodID(clss, "n_threads", "()I")); ctx_params->n_threads_batch = env->CallIntMethod(params, env->GetMethodID(clss, "n_threads_batch", "()I")); // enums switch (env->CallIntMethod(params, env->GetMethodID(clss, "pooling_type", "()I"))) { case LLAMA_POOLING_TYPE_UNSPECIFIED: ctx_params->pooling_type = LLAMA_POOLING_TYPE_UNSPECIFIED; break; case LLAMA_POOLING_TYPE_NONE: ctx_params->pooling_type = LLAMA_POOLING_TYPE_NONE; break; case LLAMA_POOLING_TYPE_MEAN: ctx_params->pooling_type = LLAMA_POOLING_TYPE_MEAN; break; case LLAMA_POOLING_TYPE_CLS: ctx_params->pooling_type = LLAMA_POOLING_TYPE_CLS; break; case LLAMA_POOLING_TYPE_LAST: ctx_params->pooling_type = LLAMA_POOLING_TYPE_LAST; break; default: assert(!"Invalid pooling type value"); break; } // TODO support more types int type_k = env->CallIntMethod(params, env->GetMethodID(clss, "type_k", "()I")); switch (env->CallIntMethod(params, env->GetMethodID(clss, "type_k", "()I"))) { case GGML_TYPE_F16: ctx_params->type_k = GGML_TYPE_F16; break; case GGML_TYPE_Q4_0: ctx_params->type_k = GGML_TYPE_Q4_0; break; case GGML_TYPE_Q8_0: ctx_params->type_k = GGML_TYPE_Q8_0; break; default: assert(!"Unsupported type_k type value"); break; } switch (env->CallIntMethod(params, env->GetMethodID(clss, "type_v", "()I"))) { case GGML_TYPE_F16: ctx_params->type_v = GGML_TYPE_F16; break; case GGML_TYPE_Q4_0: ctx_params->type_v = GGML_TYPE_Q4_0; break; case GGML_TYPE_Q8_0: ctx_params->type_v = GGML_TYPE_Q8_0; break; default: assert(!"Unsupported type_k type value"); break; } // booleans ctx_params->embeddings = env->CallBooleanMethod(params, env->GetMethodID(clss, "embeddings", "()Z")); ctx_params->offload_kqv = env->CallBooleanMethod(params, env->GetMethodID(clss, "offload_kqv", "()Z")); ctx_params->kv_unified = env->CallBooleanMethod(params, env->GetMethodID(clss, "kv_unified", "()Z")); } JNIEXPORT jobject JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_newContextParams( JNIEnv *env, jclass) { llama_context_params ctx_params = llama_context_default_params(); jobject res = env->NewObject( argeo::jni::find_jclass(env, JCLASS_CONTEXT_PARAMS), // ContextParams__init, // ctx_params.n_ctx, // ctx_params.n_batch, // ctx_params.n_ubatch, // ctx_params.n_seq_max, // ctx_params.n_threads, // ctx_params.n_threads_batch, // ctx_params.rope_scaling_type, // ctx_params.pooling_type, // ctx_params.attention_type, // ctx_params.rope_freq_base, // ctx_params.rope_freq_scale, // ctx_params.yarn_ext_factor, // ctx_params.yarn_attn_factor, // ctx_params.yarn_beta_fast, // ctx_params.yarn_beta_slow, // ctx_params.yarn_orig_ctx, // ctx_params.defrag_thold, // ctx_params.type_k, // ctx_params.type_v, // ctx_params.embeddings, // ctx_params.offload_kqv, // false, // ctx_params.no_perf, // ctx_params.op_offload, // ctx_params.swa_full, // ctx_params.kv_unified // ); return res; } /* * LIFECYCLE */ JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doInit( JNIEnv *env, jclass, jobject modelObj, jobject contextParams) { try { auto *model = argeo::jni::as_pointer(env, modelObj); llama_context_params ctx_params = llama_context_default_params(); get_context_params(env, contextParams, &ctx_params); llama_context *ctx = llama_init_from_model(model, ctx_params); if (ctx == NULL) { throw std::runtime_error("Failed to create llama.cpp context"); } // Thread pool auto *reg = ggml_backend_dev_backend_reg( ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU)); auto *ggml_threadpool_new_fn = (decltype(ggml_threadpool_new)*) ggml_backend_reg_get_proc_address( reg, "ggml_threadpool_new"); auto *ggml_threadpool_free_fn = (decltype(ggml_threadpool_free)*) ggml_backend_reg_get_proc_address( reg, "ggml_threadpool_free"); unsigned int n_threads_os = std::thread::hardware_concurrency(); // struct ggml_threadpool_params tpp_batch; // ggml_threadpool_params_init(&tpp_batch, n_threads); struct ggml_threadpool_params tpp; ggml_threadpool_params_init(&tpp, n_threads_os); //set_process_priority(params.cpuparams.priority); // struct ggml_threadpool *threadpool_batch = NULL; // if (!ggml_threadpool_params_match(&tpp, &tpp_batch)) { // threadpool_batch = ggml_threadpool_new_fn(&tpp_batch); // if (!threadpool_batch) { // // FIXME throw exception // } // // // Start the non-batch threadpool in the paused state // tpp.paused = true; // } // struct ggml_threadpool *threadpool = ggml_threadpool_new_fn(&tpp); if (!threadpool) { threadpool = ggml_threadpool_new_fn(&tpp); if (!threadpool) { // FIXME throw exception } } llama_attach_threadpool(ctx, threadpool, NULL); return (jlong) ctx; } catch (const std::exception &ex) { argeo::jni::throw_to_java(env, ex); return 0; } } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doDestroy( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); llama_detach_threadpool(ctx); llama_free(ctx); } /* * ACCESSORS */ JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetPoolingType( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return llama_pooling_type(ctx); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetContextSize( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return llama_n_ctx(ctx); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetBatchSize( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return llama_n_batch(ctx); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetPhysicalBatchSize( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return llama_n_ubatch(ctx); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppContext_doGetMaxSequenceCount( JNIEnv *env, jobject obj) { auto *ctx = argeo::jni::as_pointer(env, obj); return llama_n_seq_max(ctx); } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_embedding.cpp000066400000000000000000000106061510364424500255620ustar00rootroot00000000000000#include #include #include #include "org_argeo_jjml_llm_LlamaCppEmbeddingProcessor.h" // IWYU pragma: keep #include "jjml_llm.h" #include "org_argeo_jjml_llm_.h" /* * EMBEDDING */ // from llama.cpp's common llama_embd_normalize static void embd_normalize(const float *inp, float *out, int n, int embd_norm) { double sum = 0.0; switch (embd_norm) { case -1: // no normalisation sum = 1.0; break; case 0: // max absolute for (int i = 0; i < n; i++) { if (sum < std::abs(inp[i])) sum = std::abs(inp[i]); } sum /= 32760.0; // make an int16 range break; case 2: // euclidean for (int i = 0; i < n; i++) { sum += inp[i] * inp[i]; } sum = std::sqrt(sum); break; default: // p-norm (euclidean is p-norm p=2) for (int i = 0; i < n; i++) { sum += std::pow(std::abs(inp[i]), embd_norm); } sum = std::pow(sum, 1.0 / embd_norm); break; } const float norm = sum > 0.0 ? 1.0 / sum : 0.0f; for (int i = 0; i < n; i++) { out[i] = inp[i] * norm; } } // from llama.cpp's example/embedding static void embd_batch_decode(llama_context *ctx, llama_batch &batch, float *output, int n_seq, int n_embd, int embd_norm) { const enum llama_pooling_type pooling_type = llama_pooling_type(ctx); const struct llama_model *model = llama_get_model(ctx); // clear previous kv_cache values (irrelevant for embeddings) llama_memory_t memory = llama_get_memory(ctx); llama_memory_clear(memory, true); // run model // LOG_INF("%s: n_tokens = %d, n_seq = %d\n", __func__, batch.n_tokens, n_seq); if (llama_model_has_encoder(model) && !llama_model_has_decoder(model)) { // encoder-only model if (llama_encode(ctx, batch) < 0) { // LOG_ERR("%s : failed to encode\n", __func__); } } else if (!llama_model_has_encoder(model) && llama_model_has_decoder(model)) { // decoder-only model if (llama_decode(ctx, batch) < 0) { // LOG_ERR("%s : failed to decode\n", __func__); } } for (int i = 0; i < batch.n_tokens; i++) { if (!batch.logits[i]) { continue; } const float *embd = nullptr; int embd_pos = 0; if (pooling_type == LLAMA_POOLING_TYPE_NONE) { // try to get token embeddings embd = llama_get_embeddings_ith(ctx, i); embd_pos = i; GGML_ASSERT(embd != NULL && "failed to get token embeddings"); } else { // try to get sequence embeddings - supported only when pooling_type is not NONE embd = llama_get_embeddings_seq(ctx, batch.seq_id[i][0]); embd_pos = batch.seq_id[i][0]; GGML_ASSERT(embd != NULL && "failed to get sequence embeddings"); } float *out = output + embd_pos * n_embd; embd_normalize(embd, out, n_embd, embd_norm); } } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppEmbeddingProcessor_doProcessEmbeddings( JNIEnv *env, jclass, jlong contextPointer, jobjectArray tokenLists, jfloatArray res) { auto *ctx = argeo::jni::as_pointer(contextPointer); // TODO deal with normalization int embd_normalize = -1; int n_embd = llama_model_n_embd(llama_get_model(ctx)); int n_batch = llama_n_batch(ctx); const enum llama_pooling_type pooling_type = llama_pooling_type(ctx); struct llama_batch batch = llama_batch_init(n_batch, 0, 1); int n_prompts = env->GetArrayLength(tokenLists); jfloat *emb = (jfloat*) env->GetPrimitiveArrayCritical(res, nullptr); // break into batches int e = 0; // number of embeddings already stored int s = 0; // number of prompts in current batch for (int k = 0; k < n_prompts; k++) { jintArray tokenList = (jintArray) env->GetObjectArrayElement(tokenLists, k); const uint64_t n_toks = env->GetArrayLength(tokenList); // encode if at capacity if (batch.n_tokens + n_toks > n_batch) { float *out = emb + e * n_embd; embd_batch_decode(ctx, batch, out, s, n_embd, embd_normalize); e += pooling_type == LLAMA_POOLING_TYPE_NONE ? batch.n_tokens : s; s = 0; jjml_llm_batch_clear(batch); } // add to batch // embd_batch_add_seq(batch, inp, s); size_t n_tokens = env->GetArrayLength(tokenList); int *tokens = (int*) env->GetPrimitiveArrayCritical(tokenList, nullptr); for (size_t i = 0; i < n_tokens; i++) { jjml_llm_batch_add(batch, tokens[i], i, { s }, true); } env->ReleasePrimitiveArrayCritical(tokenList, tokens, 0); s += 1; } // final batch float *out = emb + e * n_embd; embd_batch_decode(ctx, batch, out, s, n_embd, embd_normalize); env->ReleasePrimitiveArrayCritical(res, emb, 0); } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_model.cpp000066400000000000000000000162401510364424500247440ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppModel.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_LlamaCppBackend.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_.h" // CONSTANTS static const size_t META_BUFFER_SIZE = 1024; static const size_t META_BIG_BUFFER_SIZE = 20480; /* * PARAMETERS */ /** @brief Get model parameters from Java to native.*/ static void get_model_params(JNIEnv *env, jobject params, llama_model_params *mparams) { jclass clss = env->FindClass(JCLASS_MODEL_PARAMS.c_str()); mparams->n_gpu_layers = env->CallIntMethod(params, env->GetMethodID(clss, "n_gpu_layers", "()I")); mparams->vocab_only = env->CallBooleanMethod(params, env->GetMethodID(clss, "vocab_only", "()Z")); mparams->use_mmap = env->CallBooleanMethod(params, env->GetMethodID(clss, "use_mmap", "()Z")); mparams->use_mlock = env->CallBooleanMethod(params, env->GetMethodID(clss, "use_mlock", "()Z")); } JNIEXPORT jobject JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_newModelParams( JNIEnv *env, jclass) { llama_model_params mparams = llama_model_default_params(); jobject res = env->NewObject( argeo::jni::find_jclass(env, JCLASS_MODEL_PARAMS), // ModelParams__init, // mparams.n_gpu_layers, // mparams.vocab_only, // mparams.use_mmap, // mparams.use_mlock // ); //set_model_params(env, res, default_mparams); return res; } /* * LIFECYCLE */ JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doInit( JNIEnv *env, jclass, jstring localPath, jobject modelParams, jobject progressCallback) { const char *path_model = env->GetStringUTFChars(localPath, nullptr); llama_model_params mparams = llama_model_default_params(); get_model_params(env, modelParams, &mparams); // progress callback argeo::jni::java_callback progress_data; if (progressCallback != nullptr) { progress_data.callback = env->NewGlobalRef(progressCallback); progress_data.method = DoublePredicate__test; env->GetJavaVM(&progress_data.jvm); mparams.progress_callback_user_data = &progress_data; mparams.progress_callback = [](float progress, void *user_data) -> bool { return argeo::jni::exec_boolean_callback( static_cast(user_data), static_cast(progress)); }; } ggml_backend_load_all(); llama_model *model = llama_model_load_from_file(path_model, mparams); // free callback global reference if (progress_data.callback != nullptr) env->DeleteGlobalRef(progress_data.callback); env->ReleaseStringUTFChars(localPath, path_model); return (jlong) model; } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doDestroy( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); llama_model_free(model); } /* * ACCESSORS */ JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetVocabularySize( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); const llama_vocab *vocab = llama_model_get_vocab(model); return llama_vocab_n_tokens(vocab); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetContextTrainingSize( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); return llama_model_n_ctx_train(model); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetEmbeddingSize( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); return llama_model_n_embd(model); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetLayerCount( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); return llama_model_n_layer(model); } /** Gather metadata keys or values. */ static jobjectArray jjml_lama_get_meta(JNIEnv *env, llama_model *model, std::function supplier) { try { int32_t meta_count = llama_model_meta_count(model); jobjectArray res = env->NewObjectArray(meta_count, env->FindClass("[B"), nullptr); for (int32_t i = 0; i < meta_count; i++) { try { char buf[META_BUFFER_SIZE]; int32_t length = supplier(i, buf, META_BUFFER_SIZE); if (length == -1) throw std::runtime_error( "Cannot read model metadata " + std::to_string(i)); std::string u8_res; if (length > META_BUFFER_SIZE) { // chat templates can be quite big char big_buf[META_BIG_BUFFER_SIZE]; length = supplier(i, big_buf, length); u8_res = std::string(big_buf, length); } else { u8_res = std::string(buf, length); } jbyteArray str = env->NewByteArray(u8_res.length()); env->SetObjectArrayElement(res, i, str); env->SetByteArrayRegion(str, 0, u8_res.length(), (jbyte*) u8_res.c_str()); } catch (std::exception &ex) { // ignore std::cerr << "Cannot read metadata " << i << ": " << ex.what() << ". Ignoring it." << std::endl; } } return res; } catch (std::exception &ex) { return argeo::jni::throw_to_java(env, ex); } } JNIEXPORT jobjectArray JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetMetadataKeys( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); return jjml_lama_get_meta(env, model, [model](int32_t i, char *buf, size_t buf_size) { return llama_model_meta_key_by_index(model, i, buf, buf_size); }); } JNIEXPORT jobjectArray JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetMetadataValues( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); return jjml_lama_get_meta(env, model, [model](int32_t i, char *buf, size_t buf_size) { return llama_model_meta_val_str_by_index(model, i, buf, buf_size); }); } JNIEXPORT jbyteArray JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetDescription( JNIEnv *env, jobject obj) { try { auto *model = argeo::jni::as_pointer(env, obj); char buf[META_BUFFER_SIZE]; int32_t length = llama_model_desc(model, buf, META_BUFFER_SIZE); if (length == -1) throw std::runtime_error("Cannot read model description "); std::string u8_res; if (length > META_BUFFER_SIZE) { // big description char big_buf[META_BIG_BUFFER_SIZE]; length = llama_model_desc(model, big_buf, length); u8_res = std::string(big_buf, length); } else { u8_res = std::string(buf, length); } jbyteArray res = env->NewByteArray(u8_res.length()); env->SetByteArrayRegion(res, 0, u8_res.length(), (jbyte*) u8_res.c_str()); return res; } catch (std::exception &ex) { return argeo::jni::throw_to_java(env, ex); } } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetModelSize( JNIEnv *env, jobject obj) { static_assert(sizeof(jlong) >= sizeof(uint64_t)); auto *model = argeo::jni::as_pointer(env, obj); return llama_model_size(model); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppModel_doGetEndOfGenerationToken( JNIEnv *env, jobject obj) { auto *model = argeo::jni::as_pointer(env, obj); const llama_vocab *vocab = llama_model_get_vocab(model); llama_token eot = llama_vocab_eot(vocab); return eot == -1 ? llama_vocab_eos(vocab) : eot; } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_sampling.cpp000066400000000000000000000203011510364424500254470ustar00rootroot00000000000000#include #include #include "org_argeo_jjml_llm_LlamaCppNativeSampler.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_LlamaCppSamplerChain.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_LlamaCppSamplers.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_.h" /* * STANDARD SAMPLERS */ JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitGreedy( JNIEnv*, jclass) { llama_sampler *smpl = llama_sampler_init_greedy(); return reinterpret_cast(smpl); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitPenalties( JNIEnv *env, jclass, jint penalty_last_n, jfloat penalty_repeat, jfloat penalty_freq, jfloat penalty_present, jboolean penalize_nl, jboolean ignore_eos) { llama_sampler *smpl = llama_sampler_init_penalties(penalty_last_n, penalty_repeat, penalty_freq, penalty_present); return reinterpret_cast(smpl); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitTopK( JNIEnv*, jclass, jint top_k) { return reinterpret_cast(llama_sampler_init_top_k(top_k)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitTopP( JNIEnv*, jclass, jfloat top_p, jlong min_keep) { return reinterpret_cast(llama_sampler_init_top_p(top_p, min_keep)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitMinP( JNIEnv*, jclass, jfloat min_p, jlong min_keep) { return reinterpret_cast(llama_sampler_init_min_p(min_p, min_keep)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitTypicalP( JNIEnv*, jclass, jfloat typ_p, jlong min_keep) { return reinterpret_cast(llama_sampler_init_typical(typ_p, min_keep)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitTempExt( JNIEnv*, jclass, jfloat temp, jfloat dynatemp_ext, jfloat dynatemp_exponent) { return reinterpret_cast(llama_sampler_init_temp_ext(temp, dynatemp_ext, dynatemp_exponent)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitTemp( JNIEnv*, jclass, jfloat temp) { return reinterpret_cast(llama_sampler_init_temp(temp)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitDist__( JNIEnv*, jclass) { return reinterpret_cast(llama_sampler_init_dist(LLAMA_DEFAULT_SEED)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitDist__I( JNIEnv*, jclass, jint seed) { return reinterpret_cast(llama_sampler_init_dist(seed)); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitGrammar( JNIEnv *env, jclass, jobject modelObj, jbyteArray grammarStr, jbyteArray rootStr) { auto *model = argeo::jni::as_pointer(env, modelObj); const llama_vocab *vocab = llama_model_get_vocab(model); void *u8_grammar_arr = env->GetPrimitiveArrayCritical(grammarStr, 0); std::string u8_grammar(static_cast(u8_grammar_arr), env->GetArrayLength(grammarStr)); void *u8_root_arr = env->GetPrimitiveArrayCritical(rootStr, 0); std::string u8_root(static_cast(u8_root_arr), env->GetArrayLength(rootStr)); llama_sampler *smpl = llama_sampler_init_grammar(vocab, // u8_grammar.c_str(), u8_root.c_str()); // clean up env->ReleasePrimitiveArrayCritical(grammarStr, u8_grammar_arr, 0); env->ReleasePrimitiveArrayCritical(rootStr, u8_root_arr, 0); return reinterpret_cast(smpl); } /* * JAVA SAMPLER */ struct jjml_llm_sampler_java { const jobject obj; // const jmethodID applyMethod; // const jmethodID acceptMethod; // const jmethodID resetMethod; // const char *name; // JavaVM *jvm; // }; void jjml_llm_sampler_java_apply(struct llama_sampler *smpl, llama_token_data_array *cur_p) { const auto *ctx = static_cast(smpl->ctx); JNIEnv *env; argeo::jni::load_thread_jnienv(ctx->jvm, &env); jobject obj = env->NewLocalRef(ctx->obj); jobject buf = env->NewDirectByteBuffer(cur_p->data, cur_p->size * sizeof(llama_token_data)); jlong selected = env->CallLongMethod(obj, ctx->applyMethod, buf, cur_p->size, cur_p->selected, cur_p->sorted); if (selected >= 0) { cur_p->selected = static_cast(selected); } } void jjml_llm_sampler_java_accept(struct llama_sampler *smpl, llama_token token) { const auto *ctx = static_cast(smpl->ctx); JNIEnv *env; argeo::jni::load_thread_jnienv(ctx->jvm, &env); jobject obj = env->NewLocalRef(ctx->obj); env->CallVoidMethod(obj, ctx->acceptMethod, token); } void jjml_llm_sampler_java_reset(struct llama_sampler *smpl) { const auto *ctx = static_cast(smpl->ctx); JNIEnv *env; argeo::jni::load_thread_jnienv(ctx->jvm, &env); jobject obj = env->NewLocalRef(ctx->obj); env->CallVoidMethod(obj, ctx->resetMethod); } void jjml_llm_sampler_java_free(struct llama_sampler *smpl) { const auto *ctx = static_cast(smpl->ctx); JNIEnv *env; argeo::jni::load_thread_jnienv(ctx->jvm, &env); // TODO call a close method on the Java object? env->DeleteGlobalRef(ctx->obj); delete ctx; } static const char* jjml_llm_sampler_java_name( const struct llama_sampler *smpl) { //return "java"; const auto *ctx = static_cast(smpl->ctx); return ctx->name; } static struct llama_sampler_i jjml_llm_sampler_java_i = { /* .name = */jjml_llm_sampler_java_name, /* .accept = */jjml_llm_sampler_java_accept, /* .apply = */jjml_llm_sampler_java_apply, /* .reset = */jjml_llm_sampler_java_reset, /* .clone = */nullptr, /* .free = */jjml_llm_sampler_java_free, }; struct llama_sampler* jjml_llm_sampler_init_java(JNIEnv *env, jobject obj) { // jclass clss = argeo::jni::find_jclass(env, JCLASS_JAVA_SAMPLER); jjml_llm_sampler_java *ctx = new jjml_llm_sampler_java { env->NewGlobalRef( obj), // LlamaCppJavaSampler__apply, // LlamaCppJavaSampler__accept, // LlamaCppJavaSampler__reset, // "java" // }; env->GetJavaVM(&ctx->jvm); return new llama_sampler { /* .iface = */&jjml_llm_sampler_java_i, /* .ctx = */ctx, // }; } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplers_doInitJavaSampler( JNIEnv *env, jclass, jobject javaSamplerObj) { return reinterpret_cast(jjml_llm_sampler_init_java(env, javaSamplerObj)); } /* * SAMPLER CHAIN */ JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplerChain_doInit( JNIEnv*, jclass) { auto sparams = llama_sampler_chain_default_params(); llama_sampler *smpl = llama_sampler_chain_init(sparams); return reinterpret_cast(smpl); } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplerChain_doAddSampler( JNIEnv *env, jobject obj, jobject child) { auto *chain = argeo::jni::as_pointer(env, obj); auto *smpl = argeo::jni::as_pointer(env, child); llama_sampler_chain_add(chain, smpl); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplerChain_doRemoveSampler( JNIEnv *env, jobject obj, jint index) { auto *chain = argeo::jni::as_pointer(env, obj); auto *smpl = llama_sampler_chain_remove(chain, index); return reinterpret_cast(smpl); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplerChain_doGetSampler( JNIEnv *env, jobject obj, jint index) { auto *chain = argeo::jni::as_pointer(env, obj); auto *smpl = llama_sampler_chain_get(chain, index); return reinterpret_cast(smpl); } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppSamplerChain_doGetSize( JNIEnv *env, jobject obj) { auto *chain = argeo::jni::as_pointer(env, obj); return llama_sampler_chain_n(chain); } /* * GENERIC SAMPLER */ JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppNativeSampler_doReset( JNIEnv *env, jobject obj) { auto *smpl = argeo::jni::as_pointer(env, obj); llama_sampler_reset(smpl); } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppNativeSampler_doDestroy( JNIEnv *env, jobject obj) { auto *smpl = argeo::jni::as_pointer(env, obj); llama_sampler_free(smpl); } JNIEXPORT jlong JNICALL Java_org_argeo_jjml_llm_LlamaCppNativeSampler_doClone( JNIEnv *env, jobject obj) { auto *smpl = argeo::jni::as_pointer(env, obj); auto *cloned = llama_sampler_clone(smpl); return reinterpret_cast(cloned); } libjjml-java-1.1.13/native/org_argeo_jjml_llm/jjml_llm_vocabulary.cpp000066400000000000000000000167251510364424500260230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppVocabulary.h" // IWYU pragma: keep #include "org_argeo_jjml_llm_.h" /* * VOCABULARY */ // UTILITIES static std::string jjml_tokens_to_cpp_string(const llama_vocab *vocab, llama_token *tokens, int32_t n_tokens, bool remove_special, bool unparse_special) { std::string text; text.resize(std::max((int32_t) text.capacity(), n_tokens)); int32_t n_chars = llama_detokenize(vocab, tokens, n_tokens, &text[0], (int32_t) text.size(), remove_special, unparse_special); if (n_chars < 0) { text.resize(-n_chars); n_chars = llama_detokenize(vocab, tokens, n_tokens, &text[0], (int32_t) text.size(), remove_special, unparse_special); GGML_ASSERT(n_chars <= (int32_t ) text.size()); // whitespace trimming is performed after per-token detokenization } text.resize(n_chars); return text; } static std::vector jjml_cpp_string_to_tokens( const llama_vocab *vocab, char *u8_chars, int u8_size, jboolean add_special, jboolean parse_special) { // upper limit for the number of tokens int n_tokens = u8_size + 2 * add_special; std::vector tokens(n_tokens); n_tokens = llama_tokenize(vocab, u8_chars, u8_size, tokens.data(), tokens.size(), add_special, parse_special); if (n_tokens < 0) { tokens.resize(-n_tokens); int check = llama_tokenize(vocab, u8_chars, u8_size, tokens.data(), tokens.size(), add_special, parse_special); GGML_ASSERT(check == -n_tokens); } else { tokens.resize(n_tokens); } return tokens; } /* * UTF-8 from Java */ /** The input string's byte array MUST be encoded with standard UTF-8.*/ JNIEXPORT jintArray JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doTokenizeUtf8BytesAsArray( JNIEnv *env, jclass, jlong pointer, jbyteArray str, jint offset, jint length, jboolean addSpecial, jboolean parseSpecial) { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); void *u8_arr = env->GetPrimitiveArrayCritical(str, 0); char *u8_chars = static_cast(u8_arr) + offset; std::vector tokens = jjml_cpp_string_to_tokens(vocab, u8_chars, length, addSpecial, parseSpecial); // clean up env->ReleasePrimitiveArrayCritical(str, u8_arr, 0); jintArray res = env->NewIntArray(tokens.size()); env->SetIntArrayRegion(res, 0, tokens.size(), reinterpret_cast(tokens.data())); return res; } JNIEXPORT jintArray JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doTokenizeUtf8AsArray( JNIEnv *env, jclass, jlong pointer, jobject u8Buf, jint offset, jint length, jboolean addSpecial, jboolean parseSpecial) { try { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); // input void *u8_arr = env->GetDirectBufferAddress(u8Buf); if (u8_arr == NULL) throw std::invalid_argument("Input is not a direct buffer"); assert(env->GetDirectBufferCapacity(u8Buf) >= offset + length); char *u8_chars = static_cast(u8_arr) + offset; std::vector tokens = jjml_cpp_string_to_tokens(vocab, u8_chars, length, addSpecial, parseSpecial); jintArray res = env->NewIntArray(tokens.size()); env->SetIntArrayRegion(res, 0, tokens.size(), reinterpret_cast(tokens.data())); return res; } catch (const std::exception &ex) { return argeo::jni::throw_to_java(env, ex); } } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doTokenizeUtf8( JNIEnv *env, jclass, jlong pointer, jobject u8Buf, jint offset, jint length, jobject tokensBuf, jint pos, jint size, jboolean addSpecial, jboolean parseSpecial) { jint n_tokens = 0; try { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); // input void *u8_arr = env->GetDirectBufferAddress(u8Buf); if (u8_arr == NULL) throw std::invalid_argument("Input is not a direct buffer"); assert(env->GetDirectBufferCapacity(u8Buf) >= offset + length); char *u8_chars = static_cast(u8_arr) + offset; // output void *tokens_arr = env->GetDirectBufferAddress(tokensBuf); if (tokens_arr == NULL) throw std::invalid_argument("Output is not a direct buffer"); llama_token *tokens = static_cast(tokens_arr) + pos; n_tokens = llama_tokenize(vocab, u8_chars, length, tokens, size, addSpecial, parseSpecial); } catch (const std::exception &ex) { argeo::jni::throw_to_java(env, ex); } return n_tokens; } JNIEXPORT jbyteArray JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doDeTokenizeArrayAsUtf8Bytes( JNIEnv *env, jclass, jlong pointer, jintArray tokenList, jint pos, jint size, jboolean removeSpecial, jboolean unparseSpecial) { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); void *tokens_arr = env->GetPrimitiveArrayCritical(tokenList, 0); llama_token *tokens = static_cast(tokens_arr) + pos; std::string text = jjml_tokens_to_cpp_string(vocab, tokens, size, removeSpecial, unparseSpecial); // clean up env->ReleasePrimitiveArrayCritical(tokenList, tokens_arr, 0); jbyteArray res = env->NewByteArray(text.size()); env->SetByteArrayRegion(res, 0, text.size(), reinterpret_cast(text.data())); return res; } JNIEXPORT jbyteArray JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doDeTokenizeAsUtf8Bytes( JNIEnv *env, jclass, jlong pointer, jobject tokensBuf, jint pos, jint size, jboolean removeSpecial, jboolean unparseSpecial) { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); void *tokens_arr = env->GetDirectBufferAddress(tokensBuf); if (tokens_arr == NULL) throw std::invalid_argument("Output is not a direct buffer"); llama_token *tokens = static_cast(tokens_arr) + pos; std::string text = jjml_tokens_to_cpp_string(vocab, tokens, size, removeSpecial, unparseSpecial); jbyteArray res = env->NewByteArray(text.size()); env->SetByteArrayRegion(res, 0, text.size(), reinterpret_cast(text.data())); return res; } JNIEXPORT jint JNICALL Java_org_argeo_jjml_llm_LlamaCppVocabulary_doDeTokenizeAsUtf8( JNIEnv *env, jclass, jlong pointer, jobject tokensBuf, jint pos, jint size, jobject u8Buf, jint offset, jint length, jboolean removeSpecial, jboolean unparseSpecial) { jint n_chars = 0; try { auto *model = argeo::jni::as_pointer(pointer); const llama_vocab *vocab = llama_model_get_vocab(model); // input void *tokens_arr = env->GetDirectBufferAddress(tokensBuf); if (tokens_arr == NULL) throw std::invalid_argument("Output is not a direct buffer"); llama_token *tokens = static_cast(tokens_arr) + pos; // output void *u8_arr = env->GetDirectBufferAddress(u8Buf); if (u8_arr == NULL) throw std::invalid_argument("Input is not a direct buffer"); assert(env->GetDirectBufferCapacity(u8Buf) >= offset + length); char *u8_chars = static_cast(u8_arr) + offset; n_chars = llama_detokenize(vocab, tokens, size, u8_chars, length, removeSpecial, unparseSpecial); // if (n_chars < 0) // throw std::range_error( // "Output buffer capacity " + std::to_string(length) // + " is too small for " + std::to_string(-n_chars) // + " characters - " + __func__); } catch (const std::exception &ex) { argeo::jni::throw_to_java(env, ex); } return n_chars; } libjjml-java-1.1.13/native/org_argeo_jjml_llm/org_argeo_jjml_llm_.cpp000066400000000000000000000116771510364424500257600ustar00rootroot00000000000000#include "org_argeo_jjml_llm_.h" #include #include #include #include #include #include "org_argeo_jjml_llm_LlamaCppBackend.h" // IWYU pragma: keep /* * Standard Java */ // METHODS jmethodID Integer__valueOf; jmethodID DoublePredicate__test; jmethodID CompletionHandler__completed; jmethodID CompletionHandler__failed; /* * org.argeo.jjml.llama package */ jmethodID LlamaCppJavaSampler__apply; jmethodID LlamaCppJavaSampler__accept; jmethodID LlamaCppJavaSampler__reset; /* * org.argeo.jjml.llama.params package */ jmethodID ModelParams__init; jmethodID ContextParams__init; /* * LOCAL */ static bool backend_initialized = false; /** Initialization of common variables.*/ static void org_argeo_jjml_llm_(JNIEnv *env) { /* * Standard Java */ jclass Integer = argeo::jni::find_jclass(env, "java/lang/Integer"); Integer__valueOf = argeo::jni::jmethod_id_static(env, Integer, // "valueOf", "(I)Ljava/lang/Integer;"); // METHODS jclass DoublePredicate = argeo::jni::find_jclass(env, "java/util/function/DoublePredicate"); DoublePredicate__test = argeo::jni::jmethod_id(env, DoublePredicate, // "test", "(D)Z"); jclass CompletionHandler = argeo::jni::find_jclass(env, "java/nio/channels/CompletionHandler"); CompletionHandler__completed = argeo::jni::jmethod_id(env, CompletionHandler, "completed", "(Ljava/lang/Object;Ljava/lang/Object;)V"); CompletionHandler__failed = argeo::jni::jmethod_id(env, CompletionHandler, "failed", "(Ljava/lang/Throwable;Ljava/lang/Object;)V"); /* * org.argeo.jjml.llama package */ jclass LlamaCppJavaSampler = argeo::jni::find_jclass(env, JCLASS_JAVA_SAMPLER); LlamaCppJavaSampler__apply = argeo::jni::jmethod_id(env, LlamaCppJavaSampler, "apply", "(Ljava/nio/ByteBuffer;JJZ)J"); LlamaCppJavaSampler__accept = argeo::jni::jmethod_id(env, LlamaCppJavaSampler, "accept", "(I)V"); LlamaCppJavaSampler__reset = argeo::jni::jmethod_id(env, LlamaCppJavaSampler, "reset", "()V"); /* * org.argeo.jjml.llama.params package */ // We define the constructors here so that they fail right away when signatures change jclass ModelParams = argeo::jni::find_jclass(env, JCLASS_MODEL_PARAMS); ModelParams__init = argeo::jni::jmethod_id(env, ModelParams, // "", "(IZZZ)V"); jclass ContextParams = argeo::jni::find_jclass(env, JCLASS_CONTEXT_PARAMS); ContextParams__init = argeo::jni::jmethod_id(env, ContextParams, // "", "(IIIIIIIIIFFFFFFIFIIZZZZZZZ)V"); // Tip: in order to find a constructor signature, use: // javap -s '../org.argeo.jjml/bin/org/argeo/jjml/llama/params/ContextParams.class' } /* * LOCAL UTILITIES */ static void jjml_llm_init_backend() { if (!backend_initialized) { llama_backend_init(); backend_initialized = true; // disable llama logging // FIXME make it configurable llama_log_set( [](ggml_log_level /*level*/, const char* /*text*/, void* /*user_data*/) { // noop }, NULL); } } static void jjml_llm_free_backend() { if (backend_initialized) { llama_backend_free(); backend_initialized = false; } } /* * JNI */ /** Called when the library is loaded, before any other function. */ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { // load a new JNIEnv JNIEnv *env; #ifdef __ANDROID__ vm->AttachCurrentThreadAsDaemon(&env, nullptr); #else vm->AttachCurrentThreadAsDaemon((void**) &env, nullptr); #endif // cache Java references org_argeo_jjml_llm_(env); // vm->DetachCurrentThread(); // initialize llama.cpp backend jjml_llm_init_backend(); #ifdef __ANDROID__ return JNI_VERSION_1_6; #else return JNI_VERSION_10; #endif } void JNI_OnUnload(JavaVM *vm, void *reserved) { // free llama.cpp backend jjml_llm_free_backend(); } /* * BACKEND */ JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_doNumaInit( JNIEnv*, jclass, jint numaStrategy) { switch (numaStrategy) { case GGML_NUMA_STRATEGY_DISABLED: llama_numa_init(GGML_NUMA_STRATEGY_DISABLED); break; case GGML_NUMA_STRATEGY_DISTRIBUTE: llama_numa_init(GGML_NUMA_STRATEGY_DISTRIBUTE); break; case GGML_NUMA_STRATEGY_ISOLATE: llama_numa_init(GGML_NUMA_STRATEGY_ISOLATE); break; case GGML_NUMA_STRATEGY_NUMACTL: llama_numa_init(GGML_NUMA_STRATEGY_NUMACTL); break; case GGML_NUMA_STRATEGY_MIRROR: llama_numa_init(GGML_NUMA_STRATEGY_MIRROR); break; default: assert(!"Invalid NUMA strategy enum value"); break; } } JNIEXPORT void JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_doDestroy( JNIEnv*, jclass) { jjml_llm_free_backend(); } /* * COMMON NATIVE */ JNIEXPORT jboolean JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_supportsMmap( JNIEnv*, jclass) { return llama_supports_mmap(); } JNIEXPORT jboolean JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_supportsMlock( JNIEnv*, jclass) { return llama_supports_mlock(); } JNIEXPORT jboolean JNICALL Java_org_argeo_jjml_llm_LlamaCppBackend_supportsGpuOffload( JNIEnv*, jclass) { return llama_supports_gpu_offload(); } libjjml-java-1.1.13/native/org_argeo_jjml_llm/org_argeo_jjml_llm_.h000066400000000000000000000022321510364424500254100ustar00rootroot00000000000000#include #include /* * package org.argeo.jjml.llama */ #ifndef org_argeo_jjml_llama_h #define org_argeo_jjml_llama_h /** To be concatenated with Java class names.*/ const std::string JNI_PKG = "org/argeo/jjml/llm/"; const std::string JCLASS_MODEL_PARAMS = JNI_PKG + "params/ModelParams"; const std::string JCLASS_CONTEXT_PARAMS = JNI_PKG + "params/ContextParams"; const std::string JCLASS_JAVA_SAMPLER = JNI_PKG + "LlamaCppJavaSampler"; // NOTE: Only standard Java or this package's classes should be cached, // as the class loader may change in a dynamic environment (such as OSGi). // NOTE: Java methods and fields can be cached, but not classes. /* * Standard Java */ // METHODS extern jmethodID Integer__valueOf; extern jmethodID DoublePredicate__test; extern jmethodID CompletionHandler__completed; extern jmethodID CompletionHandler__failed; /* * org.argeo.jjml.llama package */ extern jmethodID LlamaCppJavaSampler__apply; extern jmethodID LlamaCppJavaSampler__accept; extern jmethodID LlamaCppJavaSampler__reset; /* * org.argeo.jjml.llama.params package */ extern jmethodID ModelParams__init; extern jmethodID ContextParams__init; #endif libjjml-java-1.1.13/native/tp/000077500000000000000000000000001510364424500160545ustar00rootroot00000000000000libjjml-java-1.1.13/native/tp/CMakeLists.txt000066400000000000000000000055631510364424500206250ustar00rootroot00000000000000# # BUILD THIRD PARTY FROM SUBMODULES # if(GGML_BACKEND_DL) set(BUILD_SHARED_LIBS ON) endif() set(TARGET_NATIVE_OUTPUT_GGML ${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/org.argeo.tp.ggml) set(TARGET_NATIVE_OUTPUT_LLAMA ${TARGET_NATIVE_OUTPUT_GGML}) # All CPU variants backends take the default output directory set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_GGML}>) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_GGML}>) if(MINGW) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_GGML}>) elseif(MSVC) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_GGML}>) else() endif() if(NOT EXISTS ${GGML_LIBRARY}) ## multiple backends libraries if(NOT DEFINED GGML_BACKEND_DL AND NOT DEFINED GGML_NATIVE AND NOT DEFINED GGML_CPU_ALL_VARIANTS) set(GGML_BACKEND_DL ON) set(GGML_CPU_ALL_VARIANTS ON) set(GGML_NATIVE OFF) message(STATUS "Enabling GGML_BACKEND_DL with all CPU variants by default") endif() # build locally if(JJML_FORCE_BUILD_LLAMA_GGML) add_subdirectory(llama.cpp/ggml) else() # default add_subdirectory(ggml) endif() message(STATUS "Build ggml locally from submodule native/tp/ggml") endif() if(NOT EXISTS "${llama_LIBRARY}") set(LLAMA_STANDALONE ON) add_subdirectory(llama.cpp) # build locally if(MINGW) set_target_properties(llama PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_NATIVE_OUTPUT_LLAMA}) if(LLAMA_BUILD_TOOLS) set_target_properties(mtmd PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_NATIVE_OUTPUT_LLAMA}) endif() elseif(MSVC) set_target_properties(llama PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_LLAMA}>) if(LLAMA_BUILD_TOOLS) set_target_properties(mtmd PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_LLAMA}>) endif() else() set_target_properties(llama PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${TARGET_NATIVE_OUTPUT_LLAMA}) if(LLAMA_BUILD_TOOLS) set_target_properties(mtmd PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_NATIVE_OUTPUT_LLAMA}) endif() endif() if(LLAMA_BUILD_TOOLS) set_target_properties(llama-cli PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<1:${TARGET_NATIVE_OUTPUT_LLAMA}>) endif() message(STATUS "Build llama.cpp locally from submodule native/tp/llama.cpp") endif() # ggml and llama.cpp versions if (Git_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-list --count HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ggml OUTPUT_VARIABLE JJML_GGML_COMMIT_COUNT OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-list --count HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/llama.cpp OUTPUT_VARIABLE JJML_LLAMA_COMMIT_COUNT OUTPUT_STRIP_TRAILING_WHITESPACE) endif() if(NOT GGML_BUILD_NUMBER) set(GGML_BUILD_NUMBER ${JJML_GGML_COMMIT_COUNT} CACHE INTERNAL GGML_BUILD_NUMBER) endif() if(NOT LLAMA_BUILD_NUMBER) set(LLAMA_BUILD_NUMBER ${JJML_LLAMA_COMMIT_COUNT} CACHE INTERNAL LLAMA_BUILD_NUMBER) endif() libjjml-java-1.1.13/org.argeo.jjml/000077500000000000000000000000001510364424500167615ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/.classpath000066400000000000000000000004461510364424500207500ustar00rootroot00000000000000 libjjml-java-1.1.13/org.argeo.jjml/.gitignore000066400000000000000000000001051510364424500207450ustar00rootroot00000000000000/bin/ /*.log /build/ /META-INF/MANIFEST.MF /META-INF/MANIFEST.src.MF libjjml-java-1.1.13/org.argeo.jjml/.project000066400000000000000000000012071510364424500204300ustar00rootroot00000000000000 org.argeo.jjml org.eclipse.jdt.core.javabuilder org.eclipse.pde.ManifestBuilder org.eclipse.pde.SchemaBuilder org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature libjjml-java-1.1.13/org.argeo.jjml/.settings/000077500000000000000000000000001510364424500206775ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/.settings/org.eclipse.core.resources.prefs000066400000000000000000000000671510364424500271150ustar00rootroot00000000000000eclipse.preferences.version=1 encoding/=UTF-8 libjjml-java-1.1.13/org.argeo.jjml/.settings/org.eclipse.jdt.core.prefs000066400000000000000000000014061510364424500256620ustar00rootroot00000000000000eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=11 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.release=enabled org.eclipse.jdt.core.compiler.source=11 libjjml-java-1.1.13/org.argeo.jjml/.settings/org.eclipse.pde.core.prefs000066400000000000000000000001311510364424500256430ustar00rootroot00000000000000eclipse.preferences.version=1 pluginProject.extensions=false resolve.requirebundle=false libjjml-java-1.1.13/org.argeo.jjml/build.properties000066400000000000000000000001141510364424500221720ustar00rootroot00000000000000source.. = src/ output.. = bin/ bin.includes = META-INF/,\ . libjjml-java-1.1.13/org.argeo.jjml/src/000077500000000000000000000000001510364424500175505ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/module-info.java000066400000000000000000000003001510364424500226220ustar00rootroot00000000000000module org.argeo.jjml { exports org.argeo.jjml.ggml; exports org.argeo.jjml.ggml.params; exports org.argeo.jjml.llm; exports org.argeo.jjml.llm.params; exports org.argeo.jjml.llm.util; } libjjml-java-1.1.13/org.argeo.jjml/src/org/000077500000000000000000000000001510364424500203375ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/000077500000000000000000000000001510364424500214345ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/000077500000000000000000000000001510364424500223705ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/000077500000000000000000000000001510364424500233165ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/GgmlBackend.java000066400000000000000000000120171510364424500263200ustar00rootroot00000000000000package org.argeo.jjml.ggml; //import static java.lang.System.Logger.Level.INFO; //import static java.lang.System.Logger.Level.WARNING; import java.io.File; import java.io.IOException; //import java.lang.System.Logger; import java.nio.charset.Charset; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** A registered GGML backend. */ public class GgmlBackend { // private final static Logger logger = System.getLogger(GgmlBackend.class.getName()); private final static String GGML_DL_PREFIX = "ggml-"; // FIXME currently unused private final static List loadedBackends = new ArrayList<>(); /** Pointer to ggml_backend_reg_t. */ private final long pointer; private final String name; private final Path path; public GgmlBackend(long pointer, String name, Path path) { this.pointer = pointer; this.name = name; this.path = path; } private static native long doLoadBackend(byte[] backendPath); private static native void doLoadAllBackends(byte[] basePath); public static void loadAllBackends() { List basePaths = new ArrayList<>(); // First try the "standard" deployment paths, so that they can be overridden // TODO make it cleaner and more configurable // TODO hardcode some paths on the native side and configure at build? // Debian String arch = System.getProperty("os.arch"); String gnuArch; if ("arm64".equals(arch) || "aarch64".equals(arch)) gnuArch = "aarch64"; else gnuArch = "x86_64"; Path path = Paths.get("/usr/lib/" + gnuArch + "-linux-gnu/ggml/backends0"); //System.out.println(path); if (Files.exists(path)) basePaths.add(path); else // Argeo basePaths.add(Paths.get("/usr/libexec/" + gnuArch + "-linux-gnu/ggml")); // Try the JNI path { String javaLibraryPath = System.getProperty("java.library.path"); if (javaLibraryPath != null && !"".equals(javaLibraryPath.trim())) { // System.out.println(javaLibraryPath); String[] paths = javaLibraryPath.split(File.pathSeparator); for (String p : paths) basePaths.add(Paths.get(p)); } } // As a last override option, environment path LD_LIBRARY_PATH { String ldLibraryPath = System.getenv("LD_LIBRARY_PATH"); if (ldLibraryPath != null && !"".equals(ldLibraryPath.trim())) { // System.out.println(ldLibraryPath); String[] paths = ldLibraryPath.split(File.pathSeparator); for (String p : paths) basePaths.add(Paths.get(p)); } } // load Path lastRelevantPath = null; basePaths: for (Path basePath : basePaths) { if (Files.exists(basePath)) { // loadBackends(basePath); try (DirectoryStream ds = Files.newDirectoryStream(basePath, System.mapLibraryName("ggml-cpu*"))) { Iterator it = ds.iterator(); // scanning some directories causes crashes on Windows, // so we skip irrelevant ones if (!it.hasNext()) continue basePaths; } catch (IOException e) { // silent continue basePaths; } lastRelevantPath = basePath; } } // // ACTUAL SEARCH // // logger.log(INFO, "Searching for ggml backends in: " + basePath); // loadBackends(basePath); if (lastRelevantPath != null) doLoadAllBackends(filePathToNative(lastRelevantPath)); else System.err.println("Could not find ggml backends in any of " + basePaths); // } public String getName() { return name; } public Path getPath() { return path; } /** Load backends available in this directory. */ public static void loadBackends(Path basePath) { // TODO recurse? could be useful for local builds backendNames: for (StandardBackend backendName : StandardBackend.values()) { // skip backends whose names are already loaded for (GgmlBackend backend : loadedBackends) { if (backendName.name().equals(backend.getName())) { // logger.log(WARNING, backendName.name() + " already loaded from " + backend.getPath()); System.err.println(backendName.name() + " already loaded from " + backend.getPath()); continue backendNames; } } String dllName; if (File.separatorChar == '\\') dllName = GGML_DL_PREFIX + backendName.name() + ".dll"; else { // FIXME deal with MacOS dllName = "lib" + GGML_DL_PREFIX + backendName.name() + ".so"; } Path backendPath = basePath.resolve(dllName); if (Files.exists(backendPath)) { long pointer = doLoadBackend(filePathToNative(backendPath)); if (pointer > 0) { // TODO log it GgmlBackend backend = new GgmlBackend(pointer, backendName.name(), backendPath); loadedBackends.add(backend); // logger.log(INFO, "Loaded backend " + backendName.name() + " from " + backend.getPath()); System.out.println("Loaded backend " + backendName.name() + " from " + backend.getPath()); } } } } /** Path as bytes, based on the OS native encoding. */ private static byte[] filePathToNative(Path path) { return path.toString().getBytes(Charset.forName(System.getProperty("sun.jnu.encoding", "UTF-8"))); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/StandardBackend.java000066400000000000000000000003411510364424500271670ustar00rootroot00000000000000package org.argeo.jjml.ggml; /** Standard GGML backends. */ public enum StandardBackend { cpu, // vulkan, // cuda, // hip, // blas, // rpc, // // unsupported: cann, // metal, // sycl, // opencl, // musa, // ; } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/package-info.java000066400000000000000000000000771510364424500265110ustar00rootroot00000000000000/** JNI integration with ggml. */ package org.argeo.jjml.ggml; libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/params/000077500000000000000000000000001510364424500246015ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/params/GgmlType.java000066400000000000000000000027651510364424500272060ustar00rootroot00000000000000package org.argeo.jjml.ggml.params; import java.util.function.IntSupplier; /** * GGML type. (see enum ggml_type, in ggml.h) */ public enum GgmlType implements IntSupplier { GGML_TYPE_F32(0), // GGML_TYPE_F16(1), // GGML_TYPE_Q4_0(2), // GGML_TYPE_Q4_1(3), // // GGML_TYPE_Q4_2(4), // support has been removed // GGML_TYPE_Q4_3(5), // support has been removed GGML_TYPE_Q5_0(6), // GGML_TYPE_Q5_1(7), // GGML_TYPE_Q8_0(8), // GGML_TYPE_Q8_1(9), // GGML_TYPE_Q2_K(10), // GGML_TYPE_Q3_K(11), // GGML_TYPE_Q4_K(12), // GGML_TYPE_Q5_K(13), // GGML_TYPE_Q6_K(14), // GGML_TYPE_Q8_K(15), // GGML_TYPE_IQ2_XXS(16), // GGML_TYPE_IQ2_XS(17), // GGML_TYPE_IQ3_XXS(18), // GGML_TYPE_IQ1_S(19), // GGML_TYPE_IQ4_NL(20), // GGML_TYPE_IQ3_S(21), // GGML_TYPE_IQ2_S(22), // GGML_TYPE_IQ4_XS(23), // GGML_TYPE_I8(24), // GGML_TYPE_I16(25), // GGML_TYPE_I32(26), // GGML_TYPE_I64(27), // GGML_TYPE_F64(28), // GGML_TYPE_IQ1_M(29), // GGML_TYPE_BF16(30), // // GGML_TYPE_Q4_0_4_4(31), // support has been removed from gguf files // GGML_TYPE_Q4_0_4_8(32), // // GGML_TYPE_Q4_0_8_8(33), // GGML_TYPE_TQ1_0(34), // GGML_TYPE_TQ2_0(35), // ; private int code; private GgmlType(int code) { this.code = code; } @Override public int getAsInt() { return code; } public static GgmlType byCode(int code) throws IllegalArgumentException { for (GgmlType type : values()) if (type.code == code) return type; throw new IllegalArgumentException("Unkown code : " + code); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/params/NumaStrategy.java000066400000000000000000000014021510364424500300640ustar00rootroot00000000000000package org.argeo.jjml.ggml.params; import java.util.function.IntSupplier; /** * GGML NUMA strategy enumeration. (see enum ggml_numa_strategy, in * ggml.h) */ public enum NumaStrategy implements IntSupplier { GGML_NUMA_STRATEGY_DISABLED(0), // GGML_NUMA_STRATEGY_DISTRIBUTE(1), // GGML_NUMA_STRATEGY_ISOLATE(2), // GGML_NUMA_STRATEGY_NUMACTL(3), // GGML_NUMA_STRATEGY_MIRROR(4), // ; private int code; private NumaStrategy(int code) { this.code = code; } @Override public int getAsInt() { return code; } public static NumaStrategy byCode(int code) throws IllegalArgumentException { for (NumaStrategy type : values()) if (type.code == code) return type; throw new IllegalArgumentException("Unkown code : " + code); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/ggml/params/package-info.java000066400000000000000000000001151510364424500277650ustar00rootroot00000000000000/** Configuration parameters of ggml. */ package org.argeo.jjml.ggml.params; libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/000077500000000000000000000000001510364424500231545ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LLamaCppNativeChatFormatter.java000066400000000000000000000032351510364424500313060ustar00rootroot00000000000000package org.argeo.jjml.llm; import static java.nio.charset.StandardCharsets.UTF_8; import java.util.List; import java.util.function.Predicate; /** * Format chat messages using llama.cpp basic capabilities (Jinja templates are not * supported). */ public class LLamaCppNativeChatFormatter { /* * NATIVE METHODS */ private static native byte[] doFormatChatMessages(byte[][] utf8Roles, byte[][] utf8Contents, boolean addAssistantTokens, byte[] ut8ChatTemplate); /* * USABLE METHODS */ /** * Format a list of chat messages either as 'user' or 'assistant' messages. * * @param messages the list of qualified chat messages * @param addAssistantTokens whether a given message should be considered 'user' * (returns true) or 'assistant' * @param chatTemplate the llama.cpp id for the chat template (e.g. * 'granite'), not a full template * @return the formatted messages as single string */ static String formatChatMessages(List messages, Predicate addAssistantTokens, String chatTemplate) { byte[][] roles = new byte[messages.size()][]; byte[][] contents = new byte[messages.size()][]; boolean currIsUserRole = false; for (int i = 0; i < messages.size(); i++) { LlamaCppChatMessage message = messages.get(i); roles[i] = message.getRole().getBytes(UTF_8); currIsUserRole = addAssistantTokens.test(message); contents[i] = message.getContent().getBytes(UTF_8); } byte[] res = doFormatChatMessages(roles, contents, currIsUserRole, chatTemplate.getBytes(UTF_8)); return new String(res, UTF_8); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppBackend.java000066400000000000000000000030541510364424500267620ustar00rootroot00000000000000package org.argeo.jjml.llm; import org.argeo.jjml.ggml.params.NumaStrategy; import org.argeo.jjml.llm.params.ContextParams; import org.argeo.jjml.llm.params.ModelParams; /** * Wrapper to the llama.cpp backend. Only static methods as this is a singleton * on the native side. *

* All other objects in this package are guaranteed to check that the backend * has been initialized, and {@link #destroy()} is called by JVM shutdown. For * simple use cases, there should therefore be no need to use this class. *

*/ public class LlamaCppBackend { static { LlamaCppNative.ensureLibrariesLoaded(); // Make sure that we will destroy the backend properly on JVM shutdown. Runtime.getRuntime().addShutdownHook(new Thread(() -> destroy(), "Destroy llama.cpp backend")); } /* * NATIVE */ static native void doNumaInit(int numaStrategyCode); static native void doDestroy(); static native ModelParams newModelParams(); static native ContextParams newContextParams(); public static native boolean supportsMmap(); public static native boolean supportsMlock(); public static native boolean supportsGpuOffload(); /* * LIFECYCLE */ /** * Initialize NUMA strategy. */ public static void numaInit(NumaStrategy numaStrategy) { if (numaStrategy != null) doNumaInit(numaStrategy.getAsInt()); } /** * Destroy the backend. Note that the JNI library won't be unloaded until the * related classloader has been garbage-collected. */ public static void destroy() { doDestroy(); } /** singleton */ private LlamaCppBackend() { } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppBatchProcessor.java000066400000000000000000000370371510364424500303640ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.io.IOException; import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.channels.CompletionHandler; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; /** * A lightweight object coordinating the processing of multiple sequences. (see * struct llama_batch, in llama.h) */ public class LlamaCppBatchProcessor { private final LlamaCppContext context; private LlamaCppSamplerChain samplerChain; private LlamaCppNativeSampler validatingSampler; /** Marker that end-of-generation has been reached for this sequence. */ private final int NO_OUTPUT_ID; /** Current position from the context perspective. */ private volatile int contextPosition = 0; // parallelism private final int parallelCount; private final /* const */ int[] sequenceIds; private final int[] outputIds; /** * Direct buffers keeping track of the tokens, typically useful when saving * session file. THes are not used by the computations as such. */ private final IntBuffer[] tokens; public LlamaCppBatchProcessor(LlamaCppContext context, LlamaCppSamplerChain samplerChain) { this(context, samplerChain, null, Collections.singleton(0)); } public LlamaCppBatchProcessor(LlamaCppContext context, LlamaCppSamplerChain samplerChain, LlamaCppNativeSampler validatingSampler, Set sequenceIds) { Objects.requireNonNull(context); Objects.requireNonNull(samplerChain); Objects.requireNonNull(sequenceIds); this.context = context; this.samplerChain = samplerChain; this.validatingSampler = validatingSampler; // there will never be an output id >= batch size this.NO_OUTPUT_ID = this.context.getBatchSize(); // parallelism if (sequenceIds.isEmpty()) throw new IllegalArgumentException("There must be at least one sequence"); this.parallelCount = sequenceIds.size(); this.sequenceIds = new int[parallelCount]; List lst = new ArrayList<>(sequenceIds); Collections.sort(lst);// ensure predictable order, as a best practice for (int i = 0; i < lst.size(); i++) this.sequenceIds[i] = lst.get(i); this.outputIds = new int[parallelCount]; Arrays.fill(outputIds, NO_OUTPUT_ID); // TODO make it optional? tokens = new IntBuffer[parallelCount]; for (int i = 0; i < parallelCount; i++) { ByteBuffer directBuf = ByteBuffer.allocateDirect(context.getContextSize() * Integer.BYTES); directBuf.order(ByteOrder.nativeOrder());// IMPORTANT! tokens[i] = directBuf.asIntBuffer(); } } /* * NATIVE METHODS */ private static native int doWrite(long contextPointer, long samplerChainPointer, int contextPosition, IntBuffer[] input, int[] offsets, int[] lengths, int[] sequenceIds, int[] outputIds, boolean lastLogit); private static native int doWriteArrays(long contextPointer, long samplerChainPointer, int contextPosition, int[][] input, int[] offsets, int[] lengths, int[] sequenceIds, int[] outputIds, boolean lastLogit); private static native int doRead(long contextPointer, long samplerChainPointer, long grammarSamplerPointer, int contextPosition, IntBuffer[] output, int[] offsets, int[] lengths, int[] sequenceIds, int[] outputIds, CompletionHandler completionHandler); private static native int doReadToArrays(long contextPointer, long samplerChainPointer, long grammarSamplerPointer, int contextPosition, int[][] output, int[] offsets, int[] lengths, int[] sequenceIds, int[] outputIds, CompletionHandler completionHandler); /* * LOW-LEVEL ACCESS */ /** * Convenience method for common input to all sequences, splitting the input in * inputs of the batch size, and calling * {@link #writeBatch(IntBuffer[], boolean)}. */ protected synchronized void writeBatch(IntBuffer buf, boolean thenLastLogits) { int tokenCount = buf.remaining(); int batchSize = getContext().getBatchSize(); int batchCount = tokenCount / batchSize; if (tokenCount % batchSize != 0) batchCount = batchCount + 1; for (int i = 0; i < batchCount; i++) { IntBuffer input = buf.slice(); boolean lastLogits; if (i == batchCount - 1) { input.limit(tokenCount % batchSize == 0 ? batchSize : tokenCount % batchSize); lastLogits = thenLastLogits; } else { input.limit(batchSize); lastLogits = false; } buf.position(buf.position() + input.limit()); writeBatch(new IntBuffer[] { input }, lastLogits); } } /** * Write tokens to the context. * * @param inputs The inputs for each sequence, or a single input common to * all sequences. The size of this array must be either one or * {@link #getParallelCount()}. * @param lastLogits Whether the last logits should be computed, thus completing * the current write cycle. * @throws IllegalArgumentException If the inputs count is different of one * (common to all sequences) or * {@link #getParallelCount()}. */ protected synchronized void writeBatch(IntBuffer[] inputs, boolean lastLogits) throws IllegalArgumentException { if (!(inputs.length == 1 || inputs.length == parallelCount)) throw new IllegalArgumentException("There must be" + (parallelCount > 1 ? " either one or " + parallelCount + " inputs" : " only one input")); int[] offsets = new int[inputs.length]; int[] lengths = new int[inputs.length]; int[][] arrays = new int[inputs.length][]; boolean allDirect = areAllBuffersDirect(inputs, offsets, lengths); if (allDirect) { contextPosition = doWrite(context.getAsLong(), samplerChain.getAsLong(), contextPosition, inputs, offsets, lengths, sequenceIds, outputIds, lastLogits); } else { buffersToArrays(inputs, offsets, lengths, arrays, true); contextPosition = doWriteArrays(context.getAsLong(), samplerChain.getAsLong(), contextPosition, arrays, offsets, lengths, sequenceIds, outputIds, lastLogits); } // cache tokens for (int i = 0; i < parallelCount; i++) { IntBuffer toCopy = inputs.length == 1 ? inputs[0] : inputs[i]; toCopy.position(inputs.length == 1 ? offsets[0] : offsets[i]); toCopy.limit(inputs.length == 1 ? offsets[0] + lengths[0] : offsets[i] + lengths[1]); tokens[i].put(toCopy); } if (lastLogits && contextPosition > 0) {// end of user input samplerChain.reset(); if (validatingSampler != null) validatingSampler.reset(); } } /** * Convenience method when there is only one sequence, calling * {@link #readBatchAsync(IntBuffer[], CompletableFuture[])}. * * @throws UnsupportedOperationException If there is more than one sequence. */ protected CompletableFuture readBatchAsync(IntBuffer output) { if (getParallelCount() != 1) throw new UnsupportedOperationException( "There are " + getParallelCount() + " sequences, while only one is allowed"); return readBatchAsync(new IntBuffer[] { output }, null); } /** * Asynchronously read generated tokens from the context. * * @param outputs Where to write the tokens. It must be of size * {@link #getParallelCount()}. * @param generationCompleted an optional list of {@link CompletableFuture} * which will be completed when the related * individual sequence is completed. if the * completion value is true it means * that generation of this sequence was completed * properly, that is an end-og-generation token was * sampled. Can be null. * @return A {@link CompletableFuture} which will complete when all sequences * have completed the reading of this batch. If the completion value is * true, it means that all sequences have completed * generation properly, that is, an end-of-generation was sampled. * @throws IllegalArgumentException If the outputs count is different from * {@link #getParallelCount()}. */ protected CompletableFuture readBatchAsync(IntBuffer[] outputs, CompletableFuture[] generationCompleted) throws IllegalArgumentException { if (outputs.length != parallelCount) throw new IllegalArgumentException("There must be " + parallelCount + " outputs"); if (generationCompleted != null && generationCompleted.length != parallelCount) throw new IllegalArgumentException("There must be " + parallelCount + " callbacks"); int[] offsets = new int[outputs.length]; int[] lengths = new int[outputs.length]; boolean allDirect = areAllBuffersDirect(outputs, offsets, lengths); int[][] arrays = allDirect ? null : new int[outputs.length][]; // this will be notified by each sequence when it is completed CompletionHandler completionHandler = new CompletionHandler() { @Override public void failed(Throwable exc, Integer sequenceIndex) { if (generationCompleted != null) generationCompleted[sequenceIndex].completeExceptionally(exc); } @Override public void completed(Integer result, Integer sequenceIndex) { IntBuffer output = outputs[sequenceIndex]; int currentOutputPosition = output.position(); int currentOutputLimit = output.limit(); if (arrays != null && !output.hasArray()) { output.put(arrays[sequenceIndex], 0, result); } else { output.position(output.position() + result); } // cache tokens for (int i = 0; i < parallelCount; i++) { IntBuffer toCopy = outputs[i]; toCopy.position(currentOutputPosition); toCopy.limit(currentOutputPosition + result); tokens[i].put(toCopy); toCopy.limit(currentOutputLimit); } if (generationCompleted != null) { int outputId = outputIds[sequenceIndex]; // notify that generation is completed for this sequence generationCompleted[sequenceIndex].complete(outputId == NO_OUTPUT_ID); } } }; // this will complete when all sequences have been completed CompletableFuture allCompleted = CompletableFuture.supplyAsync(() -> { // We synchronize in order to make sure there won't be other write or read // updating the state (contextPosition, output IDs etc.) synchronized (LlamaCppBatchProcessor.this) { if (allDirect) { contextPosition = doRead(context.getAsLong(), samplerChain.getAsLong(), validatingSampler != null ? validatingSampler.getAsLong() : 0, contextPosition, outputs, offsets, lengths, sequenceIds, outputIds, completionHandler); } else { buffersToArrays(outputs, offsets, lengths, arrays, false); contextPosition = doReadToArrays(context.getAsLong(), samplerChain.getAsLong(), validatingSampler != null ? validatingSampler.getAsLong() : 0, contextPosition, arrays, offsets, lengths, sequenceIds, outputIds, completionHandler); } // check whether generation is completed for all sequences boolean allGenerationCompleted = true; for (int i = 0; i < outputIds.length; i++) { if (NO_OUTPUT_ID != outputIds[i]) { allGenerationCompleted = false; break; } } return allGenerationCompleted; } }); return allCompleted; } /** * Common routine to fill check whether all buffers are direct. If all buffers * are direct, the arrays will be filled with proper values, otherwise they will * need to be processed again (via * {@link #buffersToArrays(IntBuffer[], int[], int[], int[][], boolean)}. */ private boolean areAllBuffersDirect(IntBuffer[] buffers, int[] offsets, int[] lengths) { boolean allDirect = true; for (int i = 0; i < buffers.length; i++) { IntBuffer buf = buffers[i]; if (buf == null) { offsets[i] = 0; lengths[i] = 0; } else if (buf.isDirect()) { offsets[i] = buf.position(); lengths[i] = buf.remaining(); } else { allDirect = false; break; } } return allDirect; } /** * Common routine to fill arrays, to be used when not all buffers are direct. */ private void buffersToArrays(IntBuffer[] buffers, int[] offsets, int[] lengths, int[][] arrays, boolean input) { for (int i = 0; i < buffers.length; i++) { IntBuffer buf = buffers[i]; if (buf == null) { offsets[i] = 0; lengths[i] = 0; arrays[i] = null; } else if (buf.hasArray()) { offsets[i] = buf.arrayOffset() + buf.position(); lengths[i] = buf.remaining(); arrays[i] = buf.array(); } else {// new array offsets[i] = 0; lengths[i] = buf.remaining(); int[] arr = new int[lengths[i]]; if (input) buf.get(arr); arrays[i] = arr; } } } /* * ACCESSORS */ /** The number of sequences being processed in parallel. */ int getParallelCount() { return parallelCount; } /** The context currently being exclusively used by this processor. */ protected LlamaCppContext getContext() { return context; } protected LlamaCppModel getModel() { return context.getModel(); } /* * OUTPUT STATUS */ protected boolean isGenerationCompleted(int sequenceIndex) { // TODO synchronize? return outputIds[sequenceIndex] == NO_OUTPUT_ID; } /* * STATE */ public synchronized void saveContextState(LlamaCppContextState savedState) { synchronized (context) { savedState.save(context, contextPosition); } } public synchronized void loadContextState(LlamaCppContextState savedState) { synchronized (context) { int savedContextPosition = savedState.load(context); contextPosition = savedContextPosition; Arrays.fill(outputIds, savedContextPosition - 1); } // FIXME load or compute last logits, // otherwise we need to write something else before it is usable } public synchronized void saveStateFile(Path path) throws IOException { if (parallelCount != 1) throw new UnsupportedOperationException("Session files are not supported for parallel batches"); synchronized (context) { Objects.requireNonNull(path); context.saveStateFile(path, tokens[0]); } } public synchronized void loadStateFile(Path path) throws IOException { if (parallelCount != 1) throw new UnsupportedOperationException("Session files are not supported for parallel batches"); synchronized (context) { Objects.requireNonNull(path); int position = context.loadStateFile(path, tokens[0]); contextPosition = position; } } /* * UTILITIES */ protected CompletableFuture[] newGenerationCompletableFutures() { @SuppressWarnings("unchecked") // required by type erasing CompletableFuture[] generationCompleted = createArr(CompletableFuture.class); for (int i = 0; i < generationCompleted.length; i++) generationCompleted[i] = new CompletableFuture(); return generationCompleted; } /** * Instantiate an array with generics. A separate method is required in order to * capture the type. */ @SuppressWarnings("unchecked") // required by type erasing private T[] createArr(Class clz) { return (T[]) Array.newInstance(clz, getParallelCount()); } /* * STATIC UTILITIES */ public static CompletionStage anyOf(List> css) { return CompletableFuture .anyOf(css.stream().map(CompletionStage::toCompletableFuture).toArray(CompletableFuture[]::new)); } public static CompletionStage allOf(List> css) { return CompletableFuture .allOf(css.stream().map(CompletionStage::toCompletableFuture).toArray(CompletableFuture[]::new)); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppChatMessage.java000066400000000000000000000007751510364424500276260ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.util.function.Supplier; /** A message qualified by a role. */ public class LlamaCppChatMessage { private final String role; private final String content; public LlamaCppChatMessage(String role, String content) { this.role = role; this.content = content; } public LlamaCppChatMessage(Supplier role, String content) { this(role.get(), content); } public String getRole() { return role; } public String getContent() { return content; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppContext.java000066400000000000000000000132061510364424500270570ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.function.LongSupplier; import org.argeo.jjml.llm.params.ContextParam; import org.argeo.jjml.llm.params.ContextParams; import org.argeo.jjml.llm.params.PoolingType; /** * Access to a llama.cpp context. (see llama_context, in llama.h) */ public class LlamaCppContext implements LongSupplier, AutoCloseable { private final static ContextParams DEFAULT_CONTEXT_PARAMS_NATIVE; static { DEFAULT_CONTEXT_PARAMS_NATIVE = LlamaCppBackend.newContextParams(); } private final long pointer; private final LlamaCppModel model; private final ContextParams initParams; // effective parameters private final PoolingType poolingType; private final int contextSize; private final int batchSize; // private final int physicalBatchSize; private final int maxSequenceCount; // private LlamaCppBatchProcessor batchProcessor; public LlamaCppContext(LlamaCppModel model) { this(model, DEFAULT_CONTEXT_PARAMS_NATIVE); } public LlamaCppContext(LlamaCppModel model, ContextParams initParams) { Objects.requireNonNull(model); Objects.requireNonNull(initParams); if (initParams.embeddings() && initParams.n_ubatch() != initParams.n_batch()) { initParams = initParams.with(ContextParam.n_batch, initParams.n_ubatch()); // logger.log(WARNING, "Embeddings requires same logical and physical batch size, forcing n_batch to " // + initParams.n_ubatch()); } this.pointer = doInit(model, initParams); this.model = model; this.initParams = initParams; // effective parameters from native side int poolingTypeCode = doGetPoolingType(); poolingType = PoolingType.byCode(poolingTypeCode); contextSize = doGetContextSize(); batchSize = doGetBatchSize(); // physicalBatchSize = doGetPhysicalBatchSize(); maxSequenceCount = doGetMaxSequenceCount(); } /* * NATIVE */ private static native long doInit(LlamaCppModel model, ContextParams params); private native void doDestroy(); private native int doGetPoolingType(); private native int doGetContextSize(); private native int doGetBatchSize(); private native int doGetPhysicalBatchSize(); private native int doGetMaxSequenceCount(); private native long doGetStateSize(); private native byte[] doGetStateDataAsBytes(); private native int doGetStateData(ByteBuffer buf, int offset); private native void doSetStateDataBytes(byte[] arr, int offset, int length); private native void doSetStateData(ByteBuffer buf, int offset, int length); private native void doSaveStateFile(byte[] path, IntBuffer buf, int offset, int length); private native int doLoadStateFile(byte[] path, IntBuffer buf, int offset); /* * STATE */ long getStateSize() { return doGetStateSize(); } void readState(ByteBuffer buf) { if (buf.isDirect()) { int offset = buf.position(); int read = doGetStateData(buf, offset); buf.position(offset + read); } else { byte[] arr = doGetStateDataAsBytes(); buf.put(arr); } } void writeState(ByteBuffer buf) { if (buf.isDirect()) { doSetStateData(buf, 0, buf.limit()); buf.position(buf.limit()); } else { byte[] arr = buf.array(); doSetStateDataBytes(arr, 0, arr.length); } } void saveStateFile(Path path, IntBuffer tokens) throws IOException { if (!tokens.isDirect()) throw new IllegalArgumentException("Tokens must be in a direct buffer"); if (Files.exists(path) && !Files.isWritable(path)) throw new IOException("Location " + path + " for session file is not writable"); doSaveStateFile(filePathToNative(path), tokens, 0, tokens.position()); } int loadStateFile(Path path, IntBuffer tokens) throws IOException { if (!Files.exists(path)) throw new FileNotFoundException("Session file " + path + " does not exist"); if (!tokens.isDirect()) throw new IllegalArgumentException("Tokens must be in a direct buffer"); int tokenCount = doLoadStateFile(filePathToNative(path), tokens, 0); tokens.position(tokenCount); return tokenCount; } /* * LIFECYCLE */ @Override public void close() throws RuntimeException { doDestroy(); } /* * PACKAGE COORDINATION */ // void setBatchProcessor(LlamaCppBatchProcessor batchProcessor) { // if (batchProcessor != null) // throw new IllegalArgumentException("A batch processor is already active for this context"); // this.batchProcessor = batchProcessor; // } /* * ACCESSORS */ @Override public long getAsLong() { return pointer; } public LlamaCppModel getModel() { return model; } public ContextParams getInitParams() { return initParams; } public PoolingType getPoolingType() { return poolingType; } public int getContextSize() { return contextSize; } public int getBatchSize() { return batchSize; } // public LlamaCppBatchProcessor getBatchProcessor() { // return batchProcessor; // } // public int getPhysicalBatchSize() { // return physicalBatchSize; // } public int getMaxSequenceCount() { return maxSequenceCount; } /* * STATIC UTILTIES */ public static ContextParams defaultContextParams() { ContextParams res = DEFAULT_CONTEXT_PARAMS_NATIVE; for (ContextParam param : ContextParam.values()) { String sysProp = System.getProperty(param.asSystemProperty()); if (sysProp != null) res = res.with(param, sysProp); } return res; } /** Path as bytes, based on the OS native encoding. */ private static byte[] filePathToNative(Path path) { return path.toString().getBytes(Charset.forName(System.getProperty("sun.jnu.encoding", "UTF-8"))); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppContextState.java000066400000000000000000000017601510364424500300620ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.nio.ByteBuffer; /** Access to a serialized context state. */ public interface LlamaCppContextState { void save(LlamaCppContext context, int contextPosition); int load(LlamaCppContext context); /** Serialized context state based on a {@link ByteBuffer}. */ static class ByteBufferSavedState implements LlamaCppContextState { private ByteBuffer savedState; private int savedContextPosition; @Override public void save(LlamaCppContext context, int contextPosition) { int stateSize = (int) context.getStateSize(); //savedState = ByteBuffer.allocate(stateSize); savedState = ByteBuffer.allocateDirect(stateSize); context.readState(savedState); // System.out.println("Saved context state (" + stateSize / (1024 * 1024) + " // MiB)"); savedContextPosition = contextPosition; } @Override public int load(LlamaCppContext context) { savedState.flip(); context.writeState(savedState); return savedContextPosition; } } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppEmbeddingProcessor.java000066400000000000000000000040071510364424500312100ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.nio.IntBuffer; import java.util.Arrays; import java.util.List; import org.argeo.jjml.llm.params.PoolingType; /** Computes embeddings. */ public class LlamaCppEmbeddingProcessor { private final LlamaCppContext context; public LlamaCppEmbeddingProcessor(LlamaCppContext context) { this.context = context; } private static native void doProcessEmbeddings(long contextPointer, int[][] tokens, float[] emb); public float[][] processEmbeddings(List prompts) { IntBuffer[] tokenLists = new IntBuffer[prompts.size()]; for (int i = 0; i < prompts.size(); i++) { String prompt = prompts.get(i); IntBuffer tokenList = context.getModel().getVocabulary().tokenize(prompt); tokenLists[i] = tokenList; } return processEmbeddings(tokenLists); } public float[][] processEmbeddings(IntBuffer[] inputs) { // logic taken from llama.cpp's examples/embedding PoolingType poolingType = context.getPoolingType(); int n_embd_count = 0; if (PoolingType.LLAMA_POOLING_TYPE_NONE.equals(poolingType)) { for (IntBuffer tokenList : inputs) { n_embd_count += tokenList.remaining(); } } else { n_embd_count = inputs.length; } int n_embd = context.getModel().getEmbeddingSize(); float[] emb = new float[n_embd_count * n_embd]; Arrays.fill(emb, 0); int[][] tokens = new int[inputs.length][]; for (int i = 0; i < inputs.length; i++) { // FIXME check buffer type tokens[i] = inputs[i].array(); } doProcessEmbeddings(context.getAsLong(), tokens, emb); // TODO optimize storage, returned values, and copy float[][] res = new float[n_embd_count][]; for (int j = 0;;) { // at least one iteration (one prompt) float[] arr = new float[n_embd]; for (int i = 0;;) { // at least one iteration (n_embd > 0) arr[i] = emb[j * n_embd + i]; i++; if (i == n_embd) break; } res[j] = arr; j++; if (j == n_embd_count) break; } return res; } /* * ACCESSORS */ protected LlamaCppContext getContext() { return context; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppInstructProcessor.java000066400000000000000000000073671510364424500311610ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; /** A processor based on chat messages. */ public class LlamaCppInstructProcessor extends LlamaCppBatchProcessor { private final LlamaCppVocabulary vocabulary; public LlamaCppInstructProcessor(LlamaCppContext context, LlamaCppSamplerChain samplerChain) { super(context, samplerChain); this.vocabulary = context.getModel().getVocabulary(); } public void write(Supplier role, String message) { Objects.requireNonNull(message); write(new LlamaCppChatMessage(role, message)); } public void write(String role, String message) { Objects.requireNonNull(message); write(new LlamaCppChatMessage(role, message)); } public void write(LlamaCppChatMessage message) { Objects.requireNonNull(message); String prompt = getModel().formatChatMessages(message); writeFormatted(prompt); } protected void writeFormatted(String prompt) { IntBuffer promptTokens = vocabulary.tokenize(prompt); assert promptTokens.position() == 0; int tokenCount = promptTokens.limit(); int[] promptArr = promptTokens.array(); int outputMax = getContext().getBatchSize(); // TODO check whether it makes sense (pattern was taken from llama.cpp code) int requiredContextSize = tokenCount + outputMax * getParallelCount(); int contextSize = getContext().getContextSize(); if (getContext().getContextSize() < requiredContextSize) throw new IllegalArgumentException( "The required KV cache size " + requiredContextSize + " is not big enough, only " + contextSize + " available. Reduce parallel or increase context size."); ByteBuffer nativeBuf = ByteBuffer.allocateDirect(requiredContextSize * Integer.BYTES); nativeBuf.order(ByteOrder.nativeOrder()); IntBuffer buf = nativeBuf.asIntBuffer(); // IntBuffer buf = IntBuffer.allocate(requiredContextSize); int batchSize = getContext().getBatchSize(); int batchCount = tokenCount / batchSize; if (tokenCount % batchSize != 0) batchCount = batchCount + 1; for (int i = 0; i < batchCount; i++) { IntBuffer input = buf.slice(); boolean lastLogits; if (i == batchCount - 1) { input.limit(tokenCount % batchSize == 0 ? batchSize : tokenCount % batchSize); lastLogits = true; } else { input.limit(batchSize); lastLogits = false; } buf.position(buf.position() + input.limit()); // copy data input.put(promptArr, i * batchSize, input.limit()); input.flip(); writeBatch(new IntBuffer[] { input }, lastLogits); } } public void readMessage(PrintStream out) throws IOException { out.flush(); // FIXME deal properly with charset, esp. on Windows // Requires Android API level 33 readMessage(new PrintWriter(out, false, StandardCharsets.UTF_8)); } public void readMessage(Writer writer) throws IOException { boolean reading = true; reads: while (reading) { ByteBuffer nativeBuf = ByteBuffer.allocateDirect(1 * Integer.BYTES); nativeBuf.order(ByteOrder.nativeOrder()); IntBuffer output = nativeBuf.asIntBuffer(); // IntBuffer output = IntBuffer.allocate(1); CompletableFuture[] generationCompleted = newGenerationCompletableFutures(); CompletableFuture allCompleted = readBatchAsync(new IntBuffer[] { output }, generationCompleted); allCompleted.join(); output.flip(); String outputStr = vocabulary.deTokenize(output); writer.write(outputStr); writer.flush(); // System.out.print(outputStr); if (isGenerationCompleted(0)) break reads; } } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppJavaSampler.java000066400000000000000000000025201510364424500276350ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * A llama.cpp sampler implemented in Java. Use * {@link LlamaCppSamplers#newJavaSampler(LlamaCppJavaSampler)} in order to get * the related {@link LlamaCppNativeSampler} to be added to a sampler chain. */ public interface LlamaCppJavaSampler { /** * Apply sampling. * * @return the selected index or a negative number if unchanged. */ long apply(ByteBuffer buf, long size, long selected, boolean sorted); /** Does nothing by default. */ default void accept(int token) { } /** Does nothing by default. */ default void reset() { } default String getName() { return getClass().getName(); } /** Trivial Java implementation of a greedy sampler. */ static class SimpleGreedy implements LlamaCppJavaSampler { @Override public long apply(ByteBuffer buf, long size, long selected, boolean sorted) { ByteBuffer b = buf.duplicate(); b.order(ByteOrder.nativeOrder()); long count = 0; long res = 0; float bestLogit = Float.NEGATIVE_INFINITY; while (count < size) { b.getInt(); // token float logit = b.getFloat(); b.getFloat(); // probability if (count == 0) { bestLogit = logit; } else if (logit > bestLogit) { res = count; bestLogit = logit; } count++; } return res; } } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppModel.java000066400000000000000000000172271510364424500265020ustar00rootroot00000000000000package org.argeo.jjml.llm; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.function.DoubleConsumer; import java.util.function.DoublePredicate; import java.util.function.LongSupplier; import org.argeo.jjml.llm.params.ModelParam; import org.argeo.jjml.llm.params.ModelParams; import org.argeo.jjml.llm.util.InstructRole; /** * Access to a llama.cpp model. (see llama_model, in llama.h) */ public class LlamaCppModel implements LongSupplier, AutoCloseable { private final static ModelParams DEFAULT_MODEL_PARAMS_NATIVE; static { DEFAULT_MODEL_PARAMS_NATIVE = LlamaCppBackend.newModelParams(); } private final long pointer; private final LlamaCppVocabulary vocabulary; private final Path localPath; private final ModelParams initParams; private boolean destroyed = false; // effective parameters private final int vocabularySize; private final int contextTrainingSize; private final int embeddingSize; private final int layerCount; private final Map metadata; private final String description; private final long modelSize; private final int endOfGenerationToken; private String chatTemplate = null; LlamaCppModel(long pointer, Path localPath, ModelParams initParams) { this.pointer = pointer; this.vocabulary = new LlamaCppVocabulary(this); this.localPath = localPath; this.initParams = initParams; // effective parameters from native side vocabularySize = doGetVocabularySize(); contextTrainingSize = doGetContextTrainingSize(); embeddingSize = doGetEmbeddingSize(); layerCount = doGetLayerCount(); byte[][] keys = doGetMetadataKeys(); byte[][] values = doGetMetadataValues(); if (keys.length != values.length) throw new IllegalStateException("Metadata keys and values don't have the same size"); LinkedHashMap map = new LinkedHashMap<>();// preserve order for (int i = 0; i < keys.length; i++) { map.put(new String(keys[i], UTF_8), new String(values[i], UTF_8)); } metadata = Collections.unmodifiableMap(map); if (metadata.containsKey("tokenizer.chat_template")) { chatTemplate = metadata.get("tokenizer.chat_template"); } description = new String(doGetDescription(), UTF_8); modelSize = doGetModelSize(); endOfGenerationToken = doGetEndOfGenerationToken(); } /* * NATIVE METHODS */ // Lifecycle private static native long doInit(String localPathStr, ModelParams params, DoublePredicate progressCallback); private native void doDestroy(); // Accessors private native int doGetVocabularySize(); private native int doGetContextTrainingSize(); private native int doGetEmbeddingSize(); private native int doGetLayerCount(); private native byte[][] doGetMetadataKeys(); private native byte[][] doGetMetadataValues(); private native byte[] doGetDescription(); private native long doGetModelSize(); private native int doGetEndOfGenerationToken(); /* * USABLE METHODS */ public String formatChatMessages(LlamaCppChatMessage... messages) { return formatChatMessages(Arrays.asList(messages)); } public String formatChatMessages(List messages) { return LLamaCppNativeChatFormatter.formatChatMessages(messages, // (message) -> message.getRole().equals(InstructRole.USER.get()), chatTemplate); } /* * LIFECYCLE */ @Override public void close() throws RuntimeException { checkDestroyed(); doDestroy(); destroyed = true; } private void checkDestroyed() { if (destroyed) throw new IllegalStateException("Model #" + pointer + " was already destroyed"); } /* * ACCESSORS */ @Override public long getAsLong() { checkDestroyed(); return pointer; } public Path getLocalPath() { return localPath; } public ModelParams getInitParams() { return initParams; } public LlamaCppVocabulary getVocabulary() { return vocabulary; } public int getVocabularySize() { return vocabularySize; } public int getContextTrainingSize() { return contextTrainingSize; } public int getEmbeddingSize() { return embeddingSize; } public int getLayerCount() { return layerCount; } public Map getMetadata() { return metadata; } public String getDescription() { return description; } public long getModelSize() { return modelSize; } public int getEndOfGenerationToken() { return endOfGenerationToken; } /* * STATIC UTILITIES */ public static LlamaCppModel load(Path localPath) throws IOException { return load(localPath, DEFAULT_MODEL_PARAMS_NATIVE); } public static ModelParams defaultModelParams() { ModelParams res = DEFAULT_MODEL_PARAMS_NATIVE; for (ModelParam param : ModelParam.values()) { String sysProp = System.getProperty(param.asSystemProperty()); if (sysProp != null) res = res.with(param, sysProp); } return res; } /** * Loads a model synchronously. For more fine-grained control (following * progress, cancelling, executor used) use * {@link #loadAsync(Path, ModelParams, DoubleConsumer, Executor)}. */ public static LlamaCppModel load(Path localPath, ModelParams initParams) throws IOException { Future future = loadAsync(localPath, initParams, null, null); try { return future.get(); } catch (InterruptedException | ExecutionException e) { throw new IOException("Cannot load model from " + localPath, e); } } /** * Loads a model asynchronously. Loading the model can be cancelled by calling * {@link Future#cancel(boolean)} with true on the returned * {@link Future}. */ public static Future loadAsync(Path localPath, ModelParams initParams, DoubleConsumer progressCallback, Executor executor) throws IOException { Objects.requireNonNull(initParams); if (!Files.exists(localPath)) throw new FileNotFoundException("Model path " + localPath + " does not exist."); FutureTask future = new FutureTask<>(() -> { checkInitParams(initParams); // long begin = System.currentTimeMillis(); long pointer = doInit(localPath.toString(), initParams, (progress) -> { if (progressCallback != null) progressCallback.accept(progress); return !Thread.interrupted(); }); // logger.log(Level.INFO, "Model initialization took " + // (System.currentTimeMillis() - begin) + " ms"); LlamaCppModel model = new LlamaCppModel(pointer, localPath, initParams); return model; }); if (executor == null) { Thread loadingThread = new Thread(future, "Load model " + localPath); // don't continue loading if the JVM is shutting down loadingThread.setDaemon(true); loadingThread.start(); } else { executor.execute(future); } return future; } private static void checkInitParams(ModelParams initParams) { // if (initParams.n_gpu_layers() != 0 && !LlamaCppBackend.supportsGpuOffload()) // logger.log(WARNING, "GPU offload is not available, but " + ModelParam.n_gpu_layers + " is set to " // + initParams.n_gpu_layers()); // if (initParams.use_mmap() && !LlamaCppBackend.supportsMmap()) // logger.log(WARNING, // "mmap is not available, but " + ModelParam.use_mmap + " is set to " + initParams.use_mmap()); // if (initParams.use_mlock() && !LlamaCppBackend.supportsMlock()) // logger.log(WARNING, // "mlock is not available, but " + ModelParam.use_mlock + " is set to " + initParams.use_mlock()); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppNative.java000066400000000000000000000120101510364424500266510ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import org.argeo.jjml.ggml.GgmlBackend; /** * Loads the shared libraries, possibly using system properties explicitly * setting them. If it is used to set them programmatically, it must be done * before accessing any other class. */ public class LlamaCppNative { /** * System property to explicitly specify the path of the GGML shared library to * use. */ public final static String SYSTEM_PROPERTY_LIBPATH_GGML = "jjml.libpath.ggml"; /** * System property to explicitly specify the path of the llama.cpp shared * library to use. */ public final static String SYSTEM_PROPERTY_LIBPATH_LLAMACPP = "jjml.libpath.llamacpp"; /** * System property to explicitly specify the path of the llama JNI shared * library to use. */ public final static String SYSTEM_PROPERTY_LIBPATH_JJML_LLM = "jjml.libpath.jjml.llm"; /** * System property to explicitly specify the path of the GGML JNI shared library * to use. */ public final static String SYSTEM_PROPERTY_LIBPATH_JJML_GGML = "jjml.libpath.jjml.ggml"; /** * Environment properties enabling to swap NVRAM to physical memory if needed. * * @see llama.cpp * build documentation */ public final static String ENV_GGML_CUDA_ENABLE_UNIFIED_MEMORY = "GGML_CUDA_ENABLE_UNIFIED_MEMORY"; private final static String JJML_GGML_LIBRARY_NAME = "Java_org_argeo_jjml_ggml"; private final static String JJML_LAMA_LIBRARY_NAME = "Java_org_argeo_jjml_llm"; private static boolean librariesLoaded = false; private static Path ggmlLibraryPath; private static Path llamaLibraryPath; private static Path jjmlLlmLibraryPath; private static Path jjmlGgmlLibraryPath; /* * STATIC UTILITIES */ public static void ensureLibrariesLoaded() { if (librariesLoaded) return; loadLibraries(); } static void loadLibraries() { checkLibrariesNotLoaded(); // Initialization fails if explicitly found libraries are not found, since it // assumed that it will be used in a development or debugging context, which can // be messy. Optional.ofNullable(System.getProperty(SYSTEM_PROPERTY_LIBPATH_GGML)).ifPresent((path) -> { ggmlLibraryPath = Paths.get(path); if (!Files.exists(ggmlLibraryPath)) throw new IllegalArgumentException( SYSTEM_PROPERTY_LIBPATH_GGML + " " + ggmlLibraryPath + " does not exist"); }); Optional.ofNullable(System.getProperty(SYSTEM_PROPERTY_LIBPATH_LLAMACPP)).ifPresent((path) -> { llamaLibraryPath = Paths.get(path); if (!Files.exists(llamaLibraryPath)) throw new IllegalArgumentException( SYSTEM_PROPERTY_LIBPATH_LLAMACPP + " " + llamaLibraryPath + " does not exist"); }); Optional.ofNullable(System.getProperty(SYSTEM_PROPERTY_LIBPATH_JJML_LLM)).ifPresent((path) -> { jjmlLlmLibraryPath = Paths.get(path); if (!Files.exists(jjmlLlmLibraryPath)) throw new IllegalArgumentException( SYSTEM_PROPERTY_LIBPATH_JJML_LLM + " " + jjmlLlmLibraryPath + " does not exist"); }); Optional.ofNullable(System.getProperty(SYSTEM_PROPERTY_LIBPATH_JJML_GGML)).ifPresent((path) -> { jjmlGgmlLibraryPath = Paths.get(path); if (!Files.exists(jjmlLlmLibraryPath)) throw new IllegalArgumentException( SYSTEM_PROPERTY_LIBPATH_JJML_GGML + " " + jjmlLlmLibraryPath + " does not exist"); }); if (ggmlLibraryPath != null) { System.load(ggmlLibraryPath.toAbsolutePath().toString()); // logger.log(Level.WARNING, "GGML library loaded from " + ggmlLibraryPath); } if (llamaLibraryPath != null) { System.load(llamaLibraryPath.toAbsolutePath().toString()); // logger.log(Level.WARNING, "llama.cpp library loaded from " + llamaLibraryPath); } // GGML // TODO move it to GGML package if (jjmlGgmlLibraryPath != null) { System.load(jjmlGgmlLibraryPath.toAbsolutePath().toString()); } else { // default behavior System.loadLibrary(JJML_GGML_LIBRARY_NAME); } GgmlBackend.loadAllBackends(); // llama.cpp if (jjmlLlmLibraryPath != null) { System.load(jjmlLlmLibraryPath.toAbsolutePath().toString()); } else { // default behavior System.loadLibrary(JJML_LAMA_LIBRARY_NAME); } // TODO register the stack where libraries were already loaded librariesLoaded = true; } public static void setJjmlLlamaLibraryPath(Path jjmlLlamaLibraryPath) { checkLibrariesNotLoaded(); LlamaCppNative.jjmlLlmLibraryPath = jjmlLlamaLibraryPath; } public static void setLlamaLibraryPath(Path llamaLibraryPath) { checkLibrariesNotLoaded(); LlamaCppNative.llamaLibraryPath = llamaLibraryPath; } public static void setGgmlLibraryPath(Path ggmlLibraryPath) { checkLibrariesNotLoaded(); LlamaCppNative.ggmlLibraryPath = ggmlLibraryPath; } /** Fails if libraries already loaded. */ private static void checkLibrariesNotLoaded() { if (librariesLoaded) throw new IllegalStateException("Shared libraries are already loaded."); } /** singleton */ private LlamaCppNative() { } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppNativeSampler.java000066400000000000000000000021441510364424500302040ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.util.function.LongSupplier; /** Access to a native llama.cpp sampler. */ public class LlamaCppNativeSampler implements LongSupplier, AutoCloseable, Cloneable { private final long pointer; private LlamaCppSamplerChain samplerChain = null; LlamaCppNativeSampler(long pointer) { this.pointer = pointer; } private native void doReset(); private native void doDestroy(); private native long doClone(); @Override public void close() throws RuntimeException { if (samplerChain == null) doDestroy(); else throw new IllegalStateException("This sampler cannot be closed as it belong to chain " + samplerChain); // TODO remove it from the chain, and then destroy it? } @Override public long getAsLong() { return pointer; } @Override protected Object clone() throws CloneNotSupportedException { return new LlamaCppNativeSampler(doClone()); } public void reset() { doReset(); } void setSamplerChain(LlamaCppSamplerChain currentChain) { this.samplerChain = currentChain; } LlamaCppSamplerChain getSamplerChain() { return samplerChain; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppSamplerChain.java000066400000000000000000000020551510364424500300010ustar00rootroot00000000000000package org.argeo.jjml.llm; /** * A native llama.cpp sampler chain. (see llama_sampler_chain_init, * in llama.h) */ public class LlamaCppSamplerChain extends LlamaCppNativeSampler { private static native long doInit(); private native void doAddSampler(LlamaCppNativeSampler sampler); private native long doRemoveSampler(int index); private native long doGetSampler(int index); private native int doGetSize(); public LlamaCppSamplerChain() { super(doInit()); } public LlamaCppSamplerChain(LlamaCppNativeSampler... samplers) { this(); for (LlamaCppNativeSampler sampler : samplers) addSampler(sampler); } public void addSampler(LlamaCppNativeSampler sampler) { // we cannot have a sampler shared between chain (or added twice), as it would // cause problems when the chain is closed. if (sampler.getSamplerChain() != null) { throw new IllegalStateException( "A sampler cannot be used by two chains or added twice, it should be removed first."); } doAddSampler(sampler); sampler.setSamplerChain(this); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppSamplers.java000066400000000000000000000122121510364424500272150ustar00rootroot00000000000000package org.argeo.jjml.llm; import static java.nio.charset.StandardCharsets.UTF_8; import org.argeo.jjml.llm.params.DefaultSamplerChainParams; /** * Access to the native standard samplers. (see * llama_sampler_init_*, in llama.h) */ public class LlamaCppSamplers { /* * NATIVE */ private static native long doInitGreedy(); private static native long doInitPenalties( // int penalty_last_n, // float penalty_repeat, // float penalty_freq, // float penalty_present, // boolean penalize_nl, // boolean ignore_eos // ); private static native long doInitTopK(int top_k); private static native long doInitTopP(float top_p, long min_keep); private static native long doInitMinP(float min_p, long min_keep); private static native long doInitTypicalP(float typ_p, long min_keep); private static native long doInitTempExt(float temp, float dynatemp_range, float dynatemp_exponent); private static native long doInitTemp(float temp); private static native long doInitDist(); private static native long doInitDist(int seed); private static native long doInitGrammar(LlamaCppModel model, byte[] grammarUtf8, byte[] rootUtf8); private static native long doInitJavaSampler(LlamaCppJavaSampler javaSampler); /* * DEFAULT CHAINS */ public static LlamaCppSamplerChain newDefaultSampler() { return newDefaultSampler(false); } public static LlamaCppSamplerChain newDefaultSampler(boolean withTemp) { return newDefaultSampler(withTemp ? new DefaultSamplerChainParams() : new DefaultSamplerChainParams(0)); } public static LlamaCppSamplerChain newDefaultSampler(DefaultSamplerChainParams params) { // see gpt_sampler_init in sampling.cpp LlamaCppSamplerChain chain = new LlamaCppSamplerChain(); chain.addSampler(LlamaCppSamplers.newSamplerPenalties(params)); if (params.temp() > 0) { chain.addSampler(LlamaCppSamplers.newSamplerTopK(params.top_k())); long min_keep = params.min_keep(); chain.addSampler(LlamaCppSamplers.newSamplerTypicalP(params.typ_p(), min_keep)); chain.addSampler(LlamaCppSamplers.newSamplerTopP(params.top_p(), min_keep)); chain.addSampler(LlamaCppSamplers.newSamplerMinP(params.min_p(), min_keep)); chain.addSampler(LlamaCppSamplers.newSamplerTempExt(params.temp(), params.dynatemp_range(), params.dynatemp_exponent())); // final sampler chain.addSampler(LlamaCppSamplers.newSamplerDist()); } else { if (params.n_probs() > 0) { chain.addSampler(LlamaCppSamplers.newSamplerTopK(params.n_probs())); } chain.addSampler(LlamaCppSamplers.newSamplerGreedy()); // chain.addSampler(LlamaCppSamplers.newJavaSampler(new LlamaCppJavaSampler.SimpleGreedy())); } return chain; } /* * FACTORY */ public static LlamaCppNativeSampler newSamplerGreedy() { return new LlamaCppNativeSampler(doInitGreedy()); } public static LlamaCppNativeSampler newSamplerPenalties(// int penalty_last_n, // last n tokens to penalize (0 = disable penalty, -1 = context size) float penalty_repeat, // 1.0 = disabled float penalty_freq, // 0.0 = disabled float penalty_present, // 0.0 = disabled boolean penalize_nl, // consider newlines as a repeatable token boolean ignore_eos // ignore the end-of-sequence token ) { return new LlamaCppNativeSampler(doInitPenalties(penalty_last_n, penalty_repeat, penalty_freq, penalty_present, penalize_nl, ignore_eos)); } public static LlamaCppNativeSampler newSamplerPenalties(DefaultSamplerChainParams params) { return newSamplerPenalties(params.penalty_last_n(), params.penalty_repeat(), params.penalty_freq(), params.penalty_freq(), params.penalize_nl(), params.ignore_eos()); } public static LlamaCppNativeSampler newSamplerTopK(int top_k) { return new LlamaCppNativeSampler(doInitTopK(top_k)); } public static LlamaCppNativeSampler newSamplerTopP(float top_p, long min_keep) { return new LlamaCppNativeSampler(doInitTopP(top_p, min_keep)); } public static LlamaCppNativeSampler newSamplerMinP(float min_p, long min_keep) { return new LlamaCppNativeSampler(doInitMinP(min_p, min_keep)); } public static LlamaCppNativeSampler newSamplerTypicalP(float typ_p, long min_keep) { return new LlamaCppNativeSampler(doInitTypicalP(typ_p, min_keep)); } public static LlamaCppNativeSampler newSamplerTempExt(float temp, float dynatemp_range, float dynatemp_exponent) { return new LlamaCppNativeSampler(doInitTempExt(temp, dynatemp_range, dynatemp_exponent)); } public static LlamaCppNativeSampler newSamplerTemp(float temp) { return new LlamaCppNativeSampler(doInitTemp(temp)); } public static LlamaCppNativeSampler newSamplerDist(int seed) { return new LlamaCppNativeSampler(doInitDist(seed)); } public static LlamaCppNativeSampler newSamplerDist() { return new LlamaCppNativeSampler(doInitDist()); } public static LlamaCppNativeSampler newSamplerGrammar(LlamaCppModel model, String grammar, String root) { return new LlamaCppNativeSampler(doInitGrammar(model, grammar.getBytes(UTF_8), root.getBytes(UTF_8))); } public static LlamaCppNativeSampler newJavaSampler(LlamaCppJavaSampler javaSampler) { return new LlamaCppNativeSampler(doInitJavaSampler(javaSampler)); } /** singleton */ private LlamaCppSamplers() { } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppTextProcessor.java000066400000000000000000000143341510364424500302620ustar00rootroot00000000000000package org.argeo.jjml.llm; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Collections; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; /** A processor based on text rather than tokens. */ public class LlamaCppTextProcessor extends LlamaCppBatchProcessor { private final LlamaCppVocabulary vocabulary; public LlamaCppTextProcessor(LlamaCppContext context, LlamaCppSamplerChain samplerChain) { this(context, samplerChain, null, Collections.singleton(0)); } public LlamaCppTextProcessor(LlamaCppContext context, LlamaCppSamplerChain samplerChain, LlamaCppNativeSampler validatingSampler, Set sequenceIds) { super(context, samplerChain, validatingSampler, sequenceIds); this.vocabulary = context.getModel().getVocabulary(); } /* * USABLE METHODS */ public String processSingleBatch(String systemPrompt) { return processBatch(systemPrompt); } public String processBatch(String prompt) { return processBatch(prompt, null, null); } public String processBatch(String prompt, String[] parameters, String postPrompt) { IntBuffer promptTokens = vocabulary.tokenize(prompt); assert promptTokens.position() == 0; int tokenCount = promptTokens.limit(); int[] promptArr = promptTokens.array(); int outputMax = getContext().getBatchSize(); // TODO check whether it makes sense (pattern was taken from llama.cpp code) int requiredContextSize = tokenCount + outputMax * getParallelCount() * 10; int contextSize = getContext().getContextSize(); if (getContext().getContextSize() < requiredContextSize) throw new IllegalArgumentException( "The required KV cache size " + requiredContextSize + " is not big enough, only " + contextSize + " available. Reduce parallel or increase context size."); boolean direct = false; // direct buffer area IntBuffer buf; if (direct) { ByteBuffer directBuf = ByteBuffer.allocateDirect(requiredContextSize * Integer.BYTES); directBuf.order(ByteOrder.nativeOrder());// IMPORTANT! buf = directBuf.asIntBuffer(); } else { buf = IntBuffer.allocate(requiredContextSize); } int batchSize = getContext().getBatchSize(); boolean tokenList = true; if (tokenList) { int batchCount = tokenCount / batchSize; if (tokenCount % batchSize != 0) batchCount = batchCount + 1; for (int i = 0; i < batchCount; i++) { IntBuffer input = buf.slice(); boolean lastLogits; if (i == batchCount - 1) { input.limit(tokenCount % batchSize == 0 ? batchSize : tokenCount % batchSize); lastLogits = parameters == null; } else { input.limit(batchSize); lastLogits = false; } buf.position(buf.position() + input.limit()); // copy data input.put(promptArr, i * batchSize, input.limit()); input.flip(); writeBatch(new IntBuffer[] { input }, lastLogits); } if (parameters != null) { if (parameters.length != getParallelCount()) throw new IllegalArgumentException("Parameters count different from sequence count"); IntBuffer[] inputs = new IntBuffer[getParallelCount()]; for (int i = 0; i < getParallelCount(); i++) { IntBuffer parametersTokens = vocabulary.tokenize(parameters[i]); if (parametersTokens.remaining() * getParallelCount() > batchSize)// TODO be more precise / robust throw new IllegalArgumentException("Parameter '" + parameters[i] + "' is too long."); inputs[i] = buf.slice(); inputs[i].limit(parametersTokens.remaining()); buf.position(buf.position() + inputs[i].limit()); // copy data inputs[i].put(parametersTokens.array(), 0, inputs[i].limit()); inputs[i].flip(); } writeBatch(inputs, postPrompt == null); } if (postPrompt != null) { IntBuffer postPromptTokens = vocabulary.tokenize(postPrompt); if (postPromptTokens.remaining() > batchSize)// TODO be more precise / robust throw new IllegalArgumentException("Post prompt '" + postPrompt + "' is too long."); IntBuffer input = buf.slice(); input.limit(postPromptTokens.remaining()); buf.position(buf.position() + input.limit()); // copy data input.put(postPromptTokens.array(), 0, input.limit()); input.flip(); writeBatch(new IntBuffer[] { input }, true); } } else { IntBuffer input = buf.slice(); vocabulary.tokenize(prompt, input, true, true); buf.position(input.position()); input.flip(); writeBatch(new IntBuffer[] { input }, true); } StringBuffer[] outputStrings = new StringBuffer[getParallelCount()]; for (int i = 0; i < outputStrings.length; i++) outputStrings[i] = new StringBuffer(); boolean reading = true; reads: while (reading) { IntBuffer[] outputs = new IntBuffer[getParallelCount()]; outputs: for (int i = 0; i < getParallelCount(); i++) { if (isGenerationCompleted(i)) { outputs[i] = null; continue outputs; } IntBuffer output = buf.slice(); output.limit(outputMax); outputs[i] = output; buf.position(buf.position() + output.limit()); } long begin = System.nanoTime(); CompletableFuture[] generationCompleted = newGenerationCompletableFutures(); CompletableFuture allCompleted = readBatchAsync(outputs, generationCompleted); allCompleted.join(); long end = System.nanoTime(); System.out.println("Read batch in " + (end - begin) / 1000000 + " ms."); int sequencesLeft = 0; for (int i = 0; i < outputs.length; i++) { IntBuffer output = outputs[i]; if (output != null) { output.flip(); String outputStr = vocabulary.deTokenize(output); outputStrings[i].append(outputStr); } if (!isGenerationCompleted(i)) { sequencesLeft++; } else { } } if (sequencesLeft == 0) break reads; System.out.println(sequencesLeft + " sequences left"); if (buf.position() + sequencesLeft * outputMax > buf.capacity()) { System.err.println("Main buffer will be full, aborting..."); break reads; } // TODO check context size and break the loop // TODO timeout? } StringJoiner res = new StringJoiner( "\n\n\n---------------------------------------------------------------\n\n\n"); for (int i = 0; i < outputStrings.length; i++) res.add(outputStrings[i]); return res.toString(); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/LlamaCppVocabulary.java000066400000000000000000000171231510364424500275440ustar00rootroot00000000000000package org.argeo.jjml.llm; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.util.List; import java.util.Objects; /** Performs de/tokenization natively. */ public class LlamaCppVocabulary { private final LlamaCppModel model; public LlamaCppVocabulary(LlamaCppModel model) { this.model = model; } // Tokenization /** * Tokenize a string encoded as a standard UTF-8 byte array. To use when it * makes more sense to convert on the Java side. * * @see #doDeTokenizeAsUtf8Array(int[], boolean, boolean) */ private static native int[] doTokenizeUtf8BytesAsArray(long pointer, byte[] str, int offset, int length, boolean addSpecial, boolean parseSpecial); private static native int[] doTokenizeUtf8AsArray(long pointer, ByteBuffer str, int offset, int length, boolean addSpecial, boolean parseSpecial); private static native int doTokenizeUtf8(long pointer, ByteBuffer str, int offset, int length, IntBuffer tokens, int pos, int size, boolean addSpecial, boolean parseSpecial); /** De-tokenize as a string encoded in standard UTF-8. */ private static native byte[] doDeTokenizeArrayAsUtf8Bytes(long pointer, int[] tokens, int pos, int size, boolean removeSpecial, boolean unparseSpecial); private static native byte[] doDeTokenizeAsUtf8Bytes(long pointer, IntBuffer tokens, int pos, int size, boolean removeSpecial, boolean unparseSpecial); private static native int doDeTokenizeAsUtf8(long pointer, IntBuffer tokens, int pos, int size, ByteBuffer str, int offset, int length, boolean removeSpecial, boolean unparseSpecial); /* * API */ public void tokenize(CharSequence str, IntBuffer tokens, boolean addSpecial, boolean parseSpecial) { CharBuffer chars = CharBuffer.wrap(str); ByteBuffer utf8 = UTF_8.encode(chars); tokenizeUtf8(utf8, tokens, addSpecial, parseSpecial); } public IntBuffer tokenize(CharSequence str, boolean addSpecial, boolean parseSpecial) { CharBuffer chars = CharBuffer.wrap(str); ByteBuffer utf8 = UTF_8.encode(chars); int[] arr = tokenizeUtf8(utf8, addSpecial, parseSpecial); return IntBuffer.wrap(arr); } public void tokenize(ByteBuffer utf8, IntBuffer tokens, boolean addSpecial, boolean parseSpecial) throws IndexOutOfBoundsException { tokenizeUtf8(utf8, tokens, addSpecial, parseSpecial); } public IntBuffer tokenize(ByteBuffer utf8, boolean addSpecial, boolean parseSpecial) { int[] arr = tokenizeUtf8(utf8, addSpecial, parseSpecial); return IntBuffer.wrap(arr); } public void deTokenize(IntBuffer in, ByteBuffer utf8, boolean removeSpecial, boolean unparseSpecial) throws IndexOutOfBoundsException { deTokenizeUtf8(in, utf8, removeSpecial, unparseSpecial); } public String deTokenize(IntBuffer in, boolean removeSpecial, boolean unparseSpecial) { byte[] bytes = deTokenizeUtf8(in, removeSpecial, unparseSpecial); return new String(bytes, UTF_8); } /* * DEFAULTS */ final public IntBuffer[] tokenizeMultiple(List prompts) { IntBuffer[] tokenLists = new IntBuffer[prompts.size()]; for (int i = 0; i < prompts.size(); i++) { CharSequence prompt = prompts.get(i); IntBuffer tokenList = tokenize(prompt); tokenLists[i] = tokenList; } return tokenLists; } final public IntBuffer tokenize(CharSequence str) { return tokenize(str, false, true); } final public void tokenize(CharSequence str, IntBuffer tokens) throws IndexOutOfBoundsException { tokenize(str, tokens, false, true); } final public String deTokenize(IntBuffer in) { return deTokenize(in, true, true); } final public void deTokenize(IntBuffer in, ByteBuffer out) throws IndexOutOfBoundsException { deTokenize(in, out, true, true); } /* * UTF-8 */ int[] tokenizeUtf8(ByteBuffer in, boolean addSpecial, boolean parseSpecial) { checkInput(in); // ensure position is 0 // ByteBuffer in = str.slice().limit(str.limit() - str.position()); synchronized (in) { int[] tokenArr; if (in.isDirect()) { tokenArr = doTokenizeUtf8AsArray(model.getAsLong(), in, in.position(), in.remaining(), addSpecial, parseSpecial); in.position(in.limit()); } else if (in.hasArray() && !in.isReadOnly()) { byte[] arr = in.array(); tokenArr = doTokenizeUtf8BytesAsArray(model.getAsLong(), arr, in.arrayOffset(), in.remaining(), addSpecial, parseSpecial); in.position(in.limit()); } else {// copy byte[] copy = new byte[in.remaining()]; in.get(copy, in.position(), copy.length); tokenArr = doTokenizeUtf8BytesAsArray(model.getAsLong(), copy, 0, copy.length, addSpecial, parseSpecial); } return tokenArr; } } void tokenizeUtf8(ByteBuffer str, IntBuffer tokens, boolean addSpecial, boolean parseSpecial) throws IndexOutOfBoundsException { checkInput(str); checkOutput(tokens); synchronized (tokens) {// we are writing into this buffer and changing its position if (str.isDirect() && tokens.isDirect()) {// optimal int count = doTokenizeUtf8(model.getAsLong(), str, str.position(), str.remaining(), tokens, tokens.position(), tokens.remaining(), addSpecial, parseSpecial); if (count < 0) throw new IndexOutOfBoundsException(-count); str.position(str.limit()); tokens.position(tokens.position() + count); } else { int[] tokenArr = tokenizeUtf8(str, addSpecial, parseSpecial); if (tokenArr.length > tokens.remaining()) throw new IndexOutOfBoundsException(tokenArr.length); tokens.put(tokenArr); } } } byte[] deTokenizeUtf8(IntBuffer in, boolean removeSpecial, boolean unparseSpecial) { byte[] outArr; if (in.isDirect()) { outArr = doDeTokenizeAsUtf8Bytes(model.getAsLong(), in, in.position(), in.remaining(), removeSpecial, unparseSpecial); in.position(in.limit()); } else if (in.hasArray() && !in.isReadOnly()) { outArr = doDeTokenizeArrayAsUtf8Bytes(model.getAsLong(), in.array(), in.arrayOffset(), in.remaining(), removeSpecial, unparseSpecial); in.position(in.limit()); } else {// copy int[] copy = new int[in.remaining()]; in.get(copy, in.position(), copy.length); outArr = doDeTokenizeArrayAsUtf8Bytes(model.getAsLong(), copy, 0, copy.length, removeSpecial, unparseSpecial); } return outArr; } void deTokenizeUtf8(IntBuffer in, ByteBuffer str, boolean removeSpecial, boolean unparseSpecial) throws IndexOutOfBoundsException { if (in.isDirect() && str.isDirect()) { int count = doDeTokenizeAsUtf8(model.getAsLong(), in, in.position(), in.remaining(), str, str.position(), str.remaining(), removeSpecial, unparseSpecial); if (count < 0) throw new IndexOutOfBoundsException(-count); str.position(str.position() + count); in.position(in.limit()); } else { byte[] bytes = deTokenizeUtf8(in, removeSpecial, unparseSpecial); if (bytes.length > (str.limit() - str.position())) throw new IndexOutOfBoundsException(bytes.length); str.put(bytes); } } /* * UTILITIES */ private void checkInput(Buffer in) { if (in instanceof IntBuffer) if (!ByteOrder.nativeOrder().equals(((IntBuffer) in).order())) throw new IllegalArgumentException("Int buffer does not use native byte order"); Objects.requireNonNull(in, "Input buffer cannot be null"); } private void checkOutput(Buffer out) { Objects.requireNonNull(out, "Output buffer cannot be null"); if (out.isReadOnly()) throw new IllegalArgumentException("Output buffer is read-only"); if (out instanceof IntBuffer) if (!ByteOrder.nativeOrder().equals(((IntBuffer) out).order())) throw new IllegalArgumentException("Int buffer does not use native byte order"); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/package-info.java000066400000000000000000000001031510364424500263350ustar00rootroot00000000000000/** JNI integration with llama.cpp. */ package org.argeo.jjml.llm; libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/000077500000000000000000000000001510364424500244375ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/AttentionType.java000066400000000000000000000012701510364424500301110ustar00rootroot00000000000000package org.argeo.jjml.llm.params; import java.util.function.IntSupplier; /** * Attention type. (see enum llama_attention_type, in llama.h) */ public enum AttentionType implements IntSupplier { LLAMA_ATTENTION_TYPE_UNSPECIFIED(-1), // LLAMA_ATTENTION_TYPE_CAUSAL(0), // LLAMA_ATTENTION_TYPE_NON_CAUSAL(1), // ; private int code; private AttentionType(int code) { this.code = code; } @Override public int getAsInt() { return code; } public static AttentionType byCode(int code) throws IllegalArgumentException { for (AttentionType type : values()) if (type.code == code) return type; throw new IllegalArgumentException("Unkown code : " + code); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/ContextParam.java000066400000000000000000000016651510364424500277170ustar00rootroot00000000000000package org.argeo.jjml.llm.params; /** Supported parameters. */ public enum ContextParam { n_ctx, // n_batch, // n_ubatch, // n_seq_max, // n_threads, // n_threads_batch, // // rope_scaling_type, // pooling_type, // // attention_type, // // rope_freq_base, // // rope_freq_scale, // // yarn_ext_factor, // // yarn_attn_factor, // // yarn_beta_fast, // // yarn_beta_slow, // // yarn_orig_ctx, // // defrag_thold, // type_k, // only Q4_0 and Q8_0 supported at this stage type_v, // only Q4_0 and Q8_0 supported at this stage embeddings, // offload_kqv, // flash_attn, // // no_perf, // // op_offload, // // swa_full, // kv_unified, // ; /** System property to set the default context size. */ final static String SYSTEM_PROPERTY_CONTEXT_PARAM_PREFIX = "jjml.llm.context."; /** As a system property used to override default value. */ public String asSystemProperty() { return SYSTEM_PROPERTY_CONTEXT_PARAM_PREFIX + name(); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/ContextParams.java000066400000000000000000000204731510364424500301000ustar00rootroot00000000000000package org.argeo.jjml.llm.params; import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.function.IntSupplier; import org.argeo.jjml.llm.LlamaCppContext; /** * Parameters to configure a new context. New instance should be created by * using the {@link #with(Map)} methods on * {@link LlamaCppContext#defaultContextParams()}, with the default values * populated by the shared library. * * Note: it provides record-style getters, in order to ease transition to Java * records in the future. */ public class ContextParams { // private final int n_ctx; // text context, 0 = from model private final int n_batch; // logical maximum batch size that can be submitted to llama_decode private final int n_ubatch; // physical maximum batch size private final int n_seq_max; // max number of sequences (i.e. distinct states for recurrent models) private final int n_threads; // number of threads to use for generation private final int n_threads_batch; // number of threads to use for batch processing private final int rope_scaling_type; // RoPE scaling type, from `enum llama_rope_scaling_type` private final int pooling_type; // whether to pool (sum) embedding results by sequence id private final int attention_type; // attention type to use for embeddings // ref: https://github.com/ggerganov/llama.cpp/pull/2054 private final float rope_freq_base; // RoPE base frequency, 0 = from model private final float rope_freq_scale; // RoPE frequency scaling factor, 0 = from model private final float yarn_ext_factor; // YaRN extrapolation mix factor, negative = from model private final float yarn_attn_factor; // YaRN magnitude scaling factor private final float yarn_beta_fast; // YaRN low correction dim private final float yarn_beta_slow; // YaRN high correction dim private final int yarn_orig_ctx; // YaRN original context size private final float defrag_thold; // defragment the KV cache if holes/size > thold, < 0 disabled (default) private final int type_k; // data type for K cache [EXPERIMENTAL] private final int type_v; // data type for V cache [EXPERIMENTAL] private final boolean embeddings; // if true, extract embeddings (together with logits) // Note: lamma.cpp common uses the inverse param 'no_kv_offload' which is then // false by default private final boolean offload_kqv; // whether to offload the KQV ops (including the KV cache) to GPU private final boolean flash_attn; // whether to use flash attention [EXPERIMENTAL] private final boolean no_perf; // whether to measure performance timings private final boolean op_offload; // offload host tensor operations to device private final boolean swa_full; // use full-size SWA cache private final boolean kv_unified; // use a unified buffer across the input sequences when computing the attention /** * Record-like full constructor. Will be called from the native side to provide * the defaults. */ ContextParams(// int n_ctx, // int n_batch, // int n_ubatch, // int n_seq_max, // int n_threads, // int n_threads_batch, // int rope_scaling_type, // int pooling_type, // int attention_type, // float rope_freq_base, // float rope_freq_scale, // float yarn_ext_factor, // float yarn_attn_factor, // float yarn_beta_fast, // float yarn_beta_slow, // int yarn_orig_ctx, // float defrag_thold, // int type_k, // int type_v, // boolean embeddings, // boolean offload_kqv, // boolean flash_attn, // boolean no_perf, // boolean op_offload, // boolean swa_full, // boolean kv_unified // ) { this.n_ctx = n_ctx; this.n_batch = n_batch; this.n_ubatch = n_ubatch; this.n_seq_max = n_seq_max; this.n_threads = n_threads; this.n_threads_batch = n_threads_batch; this.rope_scaling_type = rope_scaling_type; this.pooling_type = pooling_type; this.attention_type = attention_type; this.rope_freq_base = rope_freq_base; this.rope_freq_scale = rope_freq_scale; this.yarn_ext_factor = yarn_ext_factor; this.yarn_attn_factor = yarn_attn_factor; this.yarn_beta_fast = yarn_beta_fast; this.yarn_beta_slow = yarn_beta_slow; this.yarn_orig_ctx = yarn_orig_ctx; this.defrag_thold = defrag_thold; this.type_k = type_k; this.type_v = type_v; this.embeddings = embeddings; this.offload_kqv = offload_kqv; this.flash_attn = flash_attn; this.no_perf = no_perf; this.op_offload = op_offload; this.swa_full = swa_full; this.kv_unified = kv_unified; } public ContextParams with(ContextParam key, Object value) { Objects.requireNonNull(key); Objects.requireNonNull(value); String str; if (value instanceof IntSupplier) { // Needed by parameters defined as enums str = Integer.toString(((IntSupplier) value).getAsInt()); } else { str = value.toString(); } return with(Collections.singletonMap(key, str)); } public ContextParams with(Map p) { return new ContextParams( // parseInt(p.getOrDefault(ContextParam.n_ctx, Integer.toString(this.n_ctx))), // parseInt(p.getOrDefault(ContextParam.n_batch, Integer.toString(this.n_batch))), // parseInt(p.getOrDefault(ContextParam.n_ubatch, Integer.toString(this.n_ubatch))), // parseInt(p.getOrDefault(ContextParam.n_seq_max, Integer.toString(this.n_seq_max))), // parseInt(p.getOrDefault(ContextParam.n_threads, Integer.toString(this.n_threads))), // parseInt(p.getOrDefault(ContextParam.n_threads_batch, Integer.toString(this.n_threads_batch))), // this.rope_scaling_type, // parseInt(p.getOrDefault(ContextParam.pooling_type, Integer.toString(this.pooling_type))), // this.attention_type, // this.rope_freq_base, // this.rope_freq_scale, // this.yarn_ext_factor, // this.yarn_attn_factor, // this.yarn_beta_fast, // this.yarn_beta_slow, // this.yarn_orig_ctx, // this.defrag_thold, // parseInt(p.getOrDefault(ContextParam.type_k, Integer.toString(this.type_k))), // parseInt(p.getOrDefault(ContextParam.type_v, Integer.toString(this.type_v))), // parseBoolean(p.getOrDefault(ContextParam.embeddings, Boolean.toString(this.embeddings))), // parseBoolean(p.getOrDefault(ContextParam.offload_kqv, Boolean.toString(this.offload_kqv))), // parseBoolean(p.getOrDefault(ContextParam.flash_attn, Boolean.toString(this.flash_attn))), // this.no_perf, // this.op_offload, // this.swa_full, // parseBoolean(p.getOrDefault(ContextParam.kv_unified, Boolean.toString(this.kv_unified))) // ); } public int n_ctx() { return n_ctx; } public int n_batch() { return n_batch; } public int n_ubatch() { return n_ubatch; } public int n_seq_max() { return n_seq_max; } public int n_threads() { return n_threads; } public int n_threads_batch() { return n_threads_batch; } public int rope_scaling_type() { return rope_scaling_type; } public int pooling_type() { return pooling_type; } public int attention_type() { return attention_type; } public float rope_freq_base() { return rope_freq_base; } public float rope_freq_scale() { return rope_freq_scale; } public float yarn_ext_factor() { return yarn_ext_factor; } public float yarn_attn_factor() { return yarn_attn_factor; } public float yarn_beta_fast() { return yarn_beta_fast; } public float yarn_beta_slow() { return yarn_beta_slow; } public int yarn_orig_ctx() { return yarn_orig_ctx; } public float defrag_thold() { return defrag_thold; } public int type_k() { return type_k; } public int type_v() { return type_v; } public boolean embeddings() { return embeddings; } public boolean offload_kqv() { return offload_kqv; } public boolean flash_attn() { return flash_attn; } public boolean no_perf() { return no_perf; } public boolean op_offload() { return op_offload; } public boolean swa_full() { return swa_full; } public boolean kv_unified() { return kv_unified; } // /** Ensure that components and enum are perfectly in line. */ // private static boolean assertParamNames() { // RecordComponent[] components = ContextParams.class.getRecordComponents(); // ContextParamName[] names = ContextParamName.values(); // assert components.length == names.length; // for (int i = 0; i < components.length; i++) { // assert components[i].getName().equals(names[i].name()); // } // return true; // } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/DefaultSamplerChainParams.java000066400000000000000000000063071510364424500323270ustar00rootroot00000000000000package org.argeo.jjml.llm.params; /** Parameters for the default sampler chain. */ public class DefaultSamplerChainParams {// // TODO distinct params records per sampler // see gpt_sampler_params in common.h float temp; // temperature int n_probs; // if greater than 0, output the probabilities of top n_probs tokens long min_keep; // minimal number of tokens to keep int top_k; // <= 0 to use vocab size float top_p; // 1.0 = disabled float min_p; // 0.0 = disabled float tfs_z; // 1.0 = disabled float typ_p; // typical_p, 1.0 = disabled float dynatemp_range; // 0.0 = disabled float dynatemp_exponent; // controls how entropy maps to temperature in dynamic temperature sampler int penalty_last_n; // last n tokens to penalize (0 = disable penalty, -1 = context size) float penalty_repeat; // 1.0 = disabled float penalty_freq; // 0.0 = disabled float penalty_present; // 0.0 = disabled boolean penalize_nl; // consider newlines as a repeatable token boolean ignore_eos; // /** Record-like full constructor */ public DefaultSamplerChainParams(// float temp, // int n_probs, // long min_keep, // int top_k, // float top_p, // float min_p, // float tfs_z, // float typ_p, // float dynatemp_range, // float dynatemp_exponent, // int penalty_last_n, // float penalty_repeat, // float penalty_freq, // float penalty_present, // boolean penalize_nl, // boolean ignore_eos // ) { this.temp = temp; this.n_probs = n_probs; this.min_keep = min_keep; this.top_k = top_k; this.top_p = top_p; this.min_p = min_p; this.tfs_z = tfs_z; this.typ_p = typ_p; this.dynatemp_range = dynatemp_range; this.dynatemp_exponent = dynatemp_exponent; this.penalty_last_n = penalty_last_n; this.penalty_repeat = penalty_repeat; this.penalty_freq = penalty_freq; this.penalty_present = penalty_present; this.penalize_nl = penalize_nl; this.ignore_eos = ignore_eos; } public DefaultSamplerChainParams() { this(0.80f); } /** Default non-deterministic. */ public DefaultSamplerChainParams(float temp) { this(temp, 0); } /** Default deterministic. */ public DefaultSamplerChainParams(int n_probs) { this(0, n_probs); } /** Defaults. */ public DefaultSamplerChainParams(float temp, int n_probs) { this(temp, n_probs, 0l, 40, 0.95f, 0.05f, 1.00f, 1.00f, 0.00f, 1.00f, 64, 1.00f, 0.00f, 0.00f, false, false); } /* * GETTERS */ public float temp() { return temp; } public int n_probs() { return n_probs; } public long min_keep() { return min_keep; } public int top_k() { return top_k; } public float top_p() { return top_p; } public float min_p() { return min_p; } public float tfs_z() { return tfs_z; } public float typ_p() { return typ_p; } public float dynatemp_range() { return dynatemp_range; } public float dynatemp_exponent() { return dynatemp_exponent; } public int penalty_last_n() { return penalty_last_n; } public float penalty_repeat() { return penalty_repeat; } public float penalty_freq() { return penalty_freq; } public float penalty_present() { return penalty_present; } public boolean penalize_nl() { return penalize_nl; } public boolean ignore_eos() { return ignore_eos; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/ModelParam.java000066400000000000000000000010141510364424500273170ustar00rootroot00000000000000package org.argeo.jjml.llm.params; /** Names of the supported model parameters. */ public enum ModelParam { n_gpu_layers, // vocab_only, // use_mmap, // use_mlock, // ; /** * System property to set model parameters, such as the default number of layers * offloaded to GPU. */ final static String SYSTEM_PROPERTY_MODEL_PARAM_PREFIX = "jjml.llm.model."; /** As a system property used to override default value. */ public String asSystemProperty() { return SYSTEM_PROPERTY_MODEL_PARAM_PREFIX + name(); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/ModelParams.java000066400000000000000000000037641510364424500275200ustar00rootroot00000000000000package org.argeo.jjml.llm.params; import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; import java.util.Collections; import java.util.Map; import java.util.Objects; import org.argeo.jjml.llm.LlamaCppModel; /** * Initialization parameters of a model. New instance should be created by using * the {@link #with(Map)} methods on {@link LlamaCppModel#defaultModelParams()}, * with the default values populated by the shared library. * * Note: it provides record-style getters, in order to ease transition to Java * records in the future. * * (see llama_model_params, in llama.h) */ public class ModelParams { private final int n_gpu_layers; private final boolean vocab_only; private final boolean use_mmap; private final boolean use_mlock; /** * Record-like full constructor. Will be called by the native side to provide * the defaults. */ ModelParams( // int n_gpu_layers, // boolean vocab_only, // boolean use_mmap, // boolean use_mlock // ) { this.n_gpu_layers = n_gpu_layers; this.vocab_only = vocab_only; this.use_mmap = use_mmap; this.use_mlock = use_mlock; } public ModelParams with(ModelParam key, Object value) { Objects.requireNonNull(key); Objects.requireNonNull(value); return with(Collections.singletonMap(key, value.toString())); } public ModelParams with(Map p) { return new ModelParams( // parseInt(p.getOrDefault(ModelParam.n_gpu_layers, Integer.toString(this.n_gpu_layers))), // parseBoolean(p.getOrDefault(ModelParam.vocab_only, Boolean.toString(this.vocab_only))), // parseBoolean(p.getOrDefault(ModelParam.use_mmap, Boolean.toString(this.use_mmap))), // parseBoolean(p.getOrDefault(ModelParam.use_mlock, Boolean.toString(this.use_mlock))) // ); } public int n_gpu_layers() { return n_gpu_layers; } public boolean vocab_only() { return vocab_only; } public boolean use_mmap() { return use_mlock; } public boolean use_mlock() { return use_mlock; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/PoolingType.java000066400000000000000000000013521510364424500275540ustar00rootroot00000000000000package org.argeo.jjml.llm.params; import java.util.function.IntSupplier; /** * Pooling type. (see enum llama_pooling_type, in llama.h) */ public enum PoolingType implements IntSupplier { LLAMA_POOLING_TYPE_UNSPECIFIED(-1), // LLAMA_POOLING_TYPE_NONE(0), // LLAMA_POOLING_TYPE_MEAN(1), // LLAMA_POOLING_TYPE_CLS(2), // LLAMA_POOLING_TYPE_LAST(3), // ; private int code; private PoolingType(int code) { this.code = code; } @Override public int getAsInt() { return code; } public static PoolingType byCode(int code) throws IllegalArgumentException { for (PoolingType type : values()) if (type.code == code) return type; throw new IllegalArgumentException("Unkown pooling type code : " + code); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/RopeScalingType.java000066400000000000000000000013761510364424500303610ustar00rootroot00000000000000package org.argeo.jjml.llm.params; import java.util.function.IntSupplier; /** * Rope scaling type. (see enum llama_rope_scaling_type, in * llama.h) */ public enum RopeScalingType implements IntSupplier { LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED(-1), // LLAMA_ROPE_SCALING_TYPE_NONE(0), // LLAMA_ROPE_SCALING_TYPE_LINEAR(1), // LLAMA_ROPE_SCALING_TYPE_YARN(2), // ; private int code; private RopeScalingType(int code) { this.code = code; } @Override public int getAsInt() { return code; } public static RopeScalingType byCode(int code) throws IllegalArgumentException { for (RopeScalingType type : values()) if (type.code == code) return type; throw new IllegalArgumentException("Unkown pooling type code : " + code); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/params/package-info.java000066400000000000000000000001211510364424500276200ustar00rootroot00000000000000/** Configuration parameters of llama.cpp. */ package org.argeo.jjml.llm.params; libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/000077500000000000000000000000001510364424500241315ustar00rootroot00000000000000libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/InstructDialog.java000066400000000000000000000072611510364424500277350ustar00rootroot00000000000000package org.argeo.jjml.llm.util; import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.argeo.jjml.llm.LlamaCppContext; import org.argeo.jjml.llm.LlamaCppInstructProcessor; import org.argeo.jjml.llm.LlamaCppModel; import org.argeo.jjml.llm.LlamaCppSamplerChain; import org.argeo.jjml.llm.LlamaCppSamplers; import org.argeo.jjml.llm.params.ContextParam; import org.argeo.jjml.llm.params.ContextParams; import org.argeo.jjml.llm.params.DefaultSamplerChainParams; /** * A simple and stable API for the limited but common use case of a dialog * between a system ("user") and an LLM ("assistant") trained for instructions * ("chat"). */ public class InstructDialog implements AutoCloseable, Function, Consumer { private final LlamaCppContext context; private final LlamaCppInstructProcessor processor; /* * CONSTRUCTORS */ public InstructDialog(LlamaCppModel model, int contextSize, int parallelism, String systemPrompt) { this(model, contextSize, parallelism, systemPrompt, 0); } public InstructDialog(LlamaCppModel model, int contextSize, int parallelism, Path stateFile) throws IOException { this(model, contextSize, parallelism, stateFile, 0); } public InstructDialog(LlamaCppModel model, int contextSize, int parallelism, String systemPrompt, float temperature) { this(model, contextSize, parallelism, temperature); processor.write(getSystemRole(), systemPrompt); } public InstructDialog(LlamaCppModel model, int contextSize, int parallelism, Path stateFile, float temperature) throws IOException { this(model, contextSize, parallelism, temperature); processor.loadStateFile(stateFile); } protected InstructDialog(LlamaCppModel model, int contextSize, int parallelism, float temperature) { ContextParams contextParams = newContextParams() // .with(ContextParam.n_ctx, contextSize) // .with(ContextParam.n_threads, parallelism) // ; context = new LlamaCppContext(model, contextParams); LlamaCppSamplerChain samplerChain = newSamplerChain(context, temperature); processor = new LlamaCppInstructProcessor(context, samplerChain); } /** Writes an input message to an LLM context, and retrieve its output. */ @Override public String apply(String message) { processor.write(getInputRole(), message); StringWriter sw = new StringWriter(); try { processor.readMessage(sw); } catch (IOException e) { throw new UncheckedIOException("Cannot read from LLM context", e); } return sw.toString(); } /** * Appends an input to the context ("user message"), without triggering * generation from the model. */ @Override public void accept(String message) { processor.write(getInputRole(), message); } /** Free context resources. */ @Override public void close() throws IOException { context.close(); } /* * CONTEXT STATE */ public void saveStateFile(Path path) throws IOException { processor.saveStateFile(path); } /* * DEFAULTS TO BE OVERRIDDEN IF NEEDED */ /** * The context parameters to use when initializing. Default implementation uses * {@link LlamaCppContext#defaultContextParams()}. */ protected ContextParams newContextParams() { return LlamaCppContext.defaultContextParams(); } protected LlamaCppSamplerChain newSamplerChain(LlamaCppContext context, float temperature) { return LlamaCppSamplers.newDefaultSampler(new DefaultSamplerChainParams(temperature)); } protected Supplier getSystemRole() { return InstructRole.SYSTEM; } protected Supplier getInputRole() { return InstructRole.USER; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/InstructRole.java000066400000000000000000000006661510364424500274410ustar00rootroot00000000000000package org.argeo.jjml.llm.util; import java.util.function.Supplier; /** Commonly used instruct roles. */ public enum InstructRole implements Supplier { SYSTEM("system"), // USER("user"), // ASSISTANT("assistant"), // ; private final String role; private InstructRole(String role) { this.role = role; } @Override public String get() { return role; } @Override public String toString() { return get(); } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/SimpleModelDownload.java000066400000000000000000000121111510364424500306720ustar00rootroot00000000000000package org.argeo.jjml.llm.util; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.DoubleConsumer; /** * Downloads a GGUF model (by defaults from HuggingFace) with the same naming * conventions as llama-cli. This is meant to be used for prototyping, not as a * full-fledged models management solution. */ public class SimpleModelDownload { /** Default location for GGUF model files. */ private final static Path MODELS_BASE = File.separatorChar == '/' ? Paths.get(System.getProperty("user.home"), ".cache", "llama.cpp") : Paths.get(System.getProperty("user.home"), "AppData", "Local", "llama.cpp"); private int bufferSize = 1024 * 4096; private final Path modelsBase; public SimpleModelDownload(Path modelsBase) { this.modelsBase = modelsBase; } public SimpleModelDownload() { this(getDefaultModelsBase()); } public URL getRemoteUrl(String hfRepo, String quantization) { URI uri = getRemoteUri(hfRepo, quantization); try { return uri.toURL(); } catch (MalformedURLException e) { throw new IllegalArgumentException("Malformed download URL: " + uri, e); } } /** To be overridden in order to use something else than HuggingFace. */ public URI getRemoteUri(String hfRepo, String quantization) { return URI.create("https://huggingface.co/" + hfRepo + "/resolve/main/" + getRemoteFileName(hfRepo, quantization) + "?download=true"); } public String getRemoteFileName(String hfRepo, String quantization) { String fileName = hfRepo.split("/")[1].replace("-GGUF", "-" + quantization + ".gguf"); return fileName; } public String getLocalFileName(String hfRepo, String quantization) { String fileName = hfRepo.split("/")[1].replace("-GGUF", "-" + quantization + ".gguf"); String localFileName = hfRepo.replace("/", "_") + "_" + fileName; return localFileName; } public Path getLocalFile(String hfRepo, String quantization) { return modelsBase.resolve(getLocalFileName(hfRepo, quantization)); } public Path getOrDownloadModel(String hfRepoArg, DoubleConsumer progressCallback) throws IOException { if (hfRepoArg.contains(":")) { return getOrDownloadModel(hfRepoArg.split(":")[0], hfRepoArg.split(":")[1], progressCallback); } else { return getOrDownloadModel(hfRepoArg, "Q4_K_M", progressCallback); } } public Path getOrDownloadModel(String hfRepo, String quantization, DoubleConsumer progressCallback) throws IOException { Path localFile = getLocalFile(hfRepo, quantization); String currentEtag = null; if (Files.exists(localFile)) { try { byte[] buf = null; // (byte[]) Files.readAttributes(localFile, "user:etag").getOrDefault("etag", // null); if (buf != null) currentEtag = new String(buf, StandardCharsets.US_ASCII); } catch (Exception e) { // logger.log(DEBUG, "Cannot read attribute from " + localFile, e); } if (currentEtag == null) { return localFile; // throw new IllegalStateException("File " + localFile + " already exist, remove it first"); } } Files.createDirectories(localFile.getParent()); URL url = getRemoteUrl(hfRepo, quantization); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); if (currentEtag != null) urlConnection.setRequestProperty("If-None-Match", currentEtag); urlConnection.connect(); // TODO somehow show download progress try (InputStream in = urlConnection.getInputStream()) { int responseCode = urlConnection.getResponseCode(); double contentLength = urlConnection.getContentLengthLong(); String etag = urlConnection.getHeaderField("etag"); if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED || etag.equals(currentEtag)) { // logger.log(INFO, "Use unmodified " + hfRepo + ":" + quantization + " file with etag=" + currentEtag); return localFile; } // logger.log(INFO, "Starting download of " + url + " to " + localFile); // logger.log(DEBUG, "Effective URL " + urlConnection.getURL()); // long begin = System.currentTimeMillis(); try (OutputStream out = Files.newOutputStream(localFile, CREATE, TRUNCATE_EXISTING)) { byte[] buf = new byte[bufferSize]; double downloaded = 0; int len; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); downloaded = downloaded + len; if (progressCallback != null) progressCallback.accept(downloaded / contentLength); } } // Files.copy(in, localFile, StandardCopyOption.REPLACE_EXISTING); if (etag != null) { // Files.setAttribute(localFile, "user:etag", StandardCharsets.US_ASCII.encode(etag)); } } return localFile; } /* * STATIC */ /** The default path where GGUF files are downloaded and searched for. */ public static Path getDefaultModelsBase() { return MODELS_BASE; } } libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/SimpleProgressCallback.java000066400000000000000000000021351510364424500313700ustar00rootroot00000000000000package org.argeo.jjml.llm.util; import java.io.PrintStream; import java.util.function.DoubleConsumer; /** * Simple CLI implementation of a progress callback of a value between 0.0 and * 1.0. Typically used when loading a model. */ public class SimpleProgressCallback implements DoubleConsumer { private int lastPerctPrinted = -1; private final PrintStream out; public SimpleProgressCallback(PrintStream out) { this.out = out; } public SimpleProgressCallback() { this(System.err); } protected void printProgressBar(char[] progressBar) { if (out != null) out.print("\r" + new String(progressBar)); } @Override public void accept(double progress) { char[] progressBar = new char[10]; int perct = (int) (progress * 100); if (perct > lastPerctPrinted + 10 // || lastPerctPrinted == -1 // || progress == 1.0) { for (int i = 0; i < perct / 10; i++) progressBar[i] = '#'; for (int i = perct / 10; i < 10; i++) progressBar[i] = '-'; printProgressBar(progressBar); lastPerctPrinted = perct; if (progress == 1.0 && out != null) out.print("\n"); } } }libjjml-java-1.1.13/org.argeo.jjml/src/org/argeo/jjml/llm/util/package-info.java000066400000000000000000000001521510364424500273160ustar00rootroot00000000000000/** Non-essential utilities, typically used when using JJML directly. */ package org.argeo.jjml.llm.util; libjjml-java-1.1.13/sdk/000077500000000000000000000000001510364424500147245ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/.classpath000066400000000000000000000010571510364424500167120ustar00rootroot00000000000000 libjjml-java-1.1.13/sdk/.gitignore000066400000000000000000000000141510364424500167070ustar00rootroot00000000000000/bin/ *.log libjjml-java-1.1.13/sdk/.project000066400000000000000000000005651510364424500164010ustar00rootroot00000000000000 argeo-jjml-sdk org.eclipse.jdt.core.javabuilder org.eclipse.jdt.core.javanature libjjml-java-1.1.13/sdk/.settings/000077500000000000000000000000001510364424500166425ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/.settings/org.eclipse.jdt.core.prefs000066400000000000000000000012171510364424500236250ustar00rootroot00000000000000eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=11 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.release=disabled org.eclipse.jdt.core.compiler.source=11 libjjml-java-1.1.13/sdk/argeo-build/000077500000000000000000000000001510364424500171165ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-icon.ico000066400000000000000000000262471510364424500174560ustar00rootroot00000000000000 ,PNG  IHDR99W IDATxy|\e?LIR&&3iEB]Y\"zX@mf)Q.IzMDd"zl 6I YTM2gf|t9sy~@zO'gy3C)lHPJRҒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9*(v&TV1 "8TI]]eY:R2i|W,f  oCTZrJE&?5pyq7Φ%(e`y@C=O]w Y`7Hyw.ƼMSmK*5ȥ# Sw\)ULk(_KήgJtgrJV{f&^榓'pgrJ /SJ?|7umDz)UFC lq>(1%T7As3DgF/W-38iBɣ D\{x 7~tg|kdq[Yab=rZr!im<L؏FygރLwও} |7}dҒ LsÎp /%vn,i nML8=}pђ j2}ѯw. qnǞ"60R7qaz|qןfNe'ep\hcQM'OaIZcLzG:IXI -YM'fٗ v_D"=k m丽oػtD<oXߙi\t Qϡ]:1?d`rI K(|9,wc# ϶3T:C=L{sۻ9^N!}=n`l[7W:DWZc`ϊuLvt/-hKE{(RGTr d~%]aPV%6'No7/)sS/lWw~16@(uVD.Z& G^ɑh{kcD D'}g_ r.^p[1s</830ͱqmǞt(s3}4hJ(aX]Ϲw_;dEUM'g¤,JnpN0l|vM՞/ڮ`j<܊ܷ. t_/W0Y/3*!ll^[AgH|^ b UC'}v_^r+6֗\2y>7stъvt/?}>,C?s'>'owwS%L>a0Ƃ*y4%k9BW66oH3Zc=7I(JkYb: #21 zeSG0r ~c~C:]0pܻqӖ">!A=foɵTC˹9{!w"xůE3):މc!ڒN0^:G1h?zu/z>C:Ve'|-+#-90B^T*,R UWJBD[kW[""̬,tÇt"8IO`?`B+3y4_fV\"Kg(6C*ѽ_fQs%~ĤFd_ߔ1fLmUKn[B4_hQM+Ovhobw_&#H5=f&b¼e9ag]yIgWsj @ H0L6fMk1F]H'e/7)(V\DJ=5=e5UT,O*Ƨ~$BZt]9T/\N:-)vB7=8:w<1 ptXSr*:@tѡ_❫WtaOD;zakJDߐ0 ][Wx!+V[+"G|fEɹs'9F`ʽ_GZ%Cڥ}fE!|!E՝]/I0{e*1:qdFDc^E#t~ F{7:HQKnSvHN\(c З\PD9 E;NLTNkJ };_P+/",jۻzf?s*%7sZ wQ7w3˜/ cGZgu`! ]ɗ -a:t"1.QBWrDd_x[:UZA>xG!QJ.OI#GY:ݥsm!UJ?H߫ lJ5K(wn$/ ;BЭuOptP>UA*9>)`xdcՓ)2sZ%.Q4~/ Oɵ{EzCRf 80X:E Me6$(<Lel_Xt U8K΄=7{cuvS$si]>kŅ%2i͒ؒ?(vYbt0sɓtb5/HPcclɁ:BmJ",Qz#:阮daT-B6]bLQ 7nݘ&=+ { gljҾpt/KS\rˎnSn 2c:>l%l|tb,jprX̜V1]P@F_[}t #K.֋km UF(6t#K۔-9TfNro"H:G189?FW`7AOJVS_:HP2"'d$#7xI( ,]7>FCLΕ/ :̚X%9Dw:  Yr`GL~(TU_~9Ǝ6R$NJȒcxHM (nss5`_C0ȡZr5C?GbodTI 퀵 VV]PωJPc*$TЃկY'3tdR MD9||xilq_?<*8@̴[⋻$F1^ҙ9ȹ@t԰Cv_fP$ے\e400_ `9lKWcȒ7qCŭ9D9s&H0;&ֻ473t_ᦒ'lL-939.qnK00xSJP]QBʫ w%GRgr7j|+ [,N˦K⦒'94&N%`cb}es1zeeeɹ cyI=YvȒ*#Kħ-)y):5"|g,?YrB䆱]U{ڿ鈭Snnh yk+b֒+X$rZ0T޽`¸jyx,Eou{o]" v0Ne2Z"z D?z\䠖Aq0h35v [›9IlWmGUT܅pniFt޲a_#i+l^'3@x\_f;JG<@.`Ix7pt"sK];|8AfR[tYr2LT6%M%\c17ktEy.@j[tA  LIDAT[rL2<%q2zi?uЯJv^K!~"90kb1vGKGB@EV~t1{-' ȽPȦ#m*j)KG:zP_ !vüe9`?1A競*܍`$e,yqR?zZz+qmWt10xY#҉Y޷ —i[tTt_r @#Lk291άي_8E:˘0{L-TugKzFF3wt= njpAW즽j*1YƂeyAtGf~3!Fb|3IAL%$PN}aAbI vH.n2cgk720ᾀ`/alsy,e,C|Nul/tt Et1| l:1^`/,cxPC/Y5{<$)67u:5I7+ލwp+l*9n)"{? |PGQF7{$=MӤ "k `7ظ|ONƒFlWlHL1/% T%%]yW1%yXIGi;&ےlv!g`^&([K#Ǡ6r?l|``~M'3TwK `O5gM1~A1B?kۻzc jBx]P_v643q>3]H:Ŏ=wr],YY…6zCkZw$ʒi1vEL@? >T&TTM'>dfTA(fDa+8eitМyDK([DOyNnzU뤣ɵ]tt 1u`E{"mMGյtbuE4X5xf^Ixm#Ӓ< /浞67 ʲ]NRП@!])vK6D~ -9 p XRr+s؋Ǧ&HxU: hG/sŚUk#*wX< }vQOtRu oBvnO^سObX]ϱaPxGo\+C 1>O:߬x^T"y 6UuhgWy=y9->@k=񍆝LnxgwWI+n+8XQz%Vgr"6'neӥH/ϏwaT KObT޿@fLJG _"ߠY>?QBύwt5\pgr[͜V)1]:^yN )J9no&gfWA`[V,I\YQAyfsq{J['']xTw~Q:HPʣT<_a<9-OKG)3puޝy|Jnl:5~ &E ktygʪG^cvΊwzX:+9p?n(% /v%:5w Q #•,]Rη$ʲlr! TK)u 'LCQƷQF ߁;W"|Kn7`\ 汮\Tc+]R:{L96`_/R Gzpt1ʀp:lbjF̐ BЦ%WNEDt<0)lZ)_y.Wv9- ,9\ :u ԴPZr;2er0^yhyrX$ 2&BJ025WW?`;~ђۙYb:pn=Dn p5pUTÑ" pߣ*;j~cKKn?ğv@Sqfc/7媟qa¿iMgH#Tw|eAKX$!xD^"LnB΃ F?.3ɡ`~/E/UG^M!巷N9'p`7v'P 98ǽLΫy0%\lq -9]jI)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ)i)%RjZrJ))AIENDB`libjjml-java-1.1.13/sdk/argeo-icon.png000066400000000000000000000261371510364424500174660ustar00rootroot00000000000000PNG  IHDR99W,&IDATx |\e?23I EDpkdҊz*uk3PTFAJۤ\XDDn+EtJK *Ц3I3sLZiKN2g9|߯.vɹ28ytjlE>ESВSjE3߀\d}7?P-59gaqOW|l # ZC w\)ULg,U<9+1}*H_LN9YxNW1)UljLѧV~4fʏe?=S?+le (1%T A2֞ǵ^ZfCI1똜x,zA_}ҿӸ,09-t <LԏV|qݙn:y6ߑ@7c}Jf+-ʴ'88 +yZn@sc0NH,N8GhɅEͱ. aqv,fO`|c+͌הes+z$$LL7N<;N>##aٚ!N~%U!ǘt0৓WW6n]6d"r# G~osTv@t+g2M:I*+(fc/J(~xZ}.vt hUH6zm.2~sjxtr0WÎ* nY̝9 7yyNdg S1IztSh,ۑ)Zr>xiF,Qphsl/ZK{MLa_""ׂht0ar9ǖ܏*#MKLt"F |moytb@dm#wGK éssCyPK}bHF:)(sz9RB9B:ytt I0Ttn638̟'*ZrSԜf@'[cFC1BKntDttuKU3''OCVZrup=@XtTd'J୑` -I M:tFΣ=tY-Zr%ʶ7Ai9lF{@ zmI4a@:)J`z;ΏϫϳVXܻE:)s4Kn&Xw?`-Ȧd99 ^<@`H:lGKn"0+HG*L_Z0}J9J!($Zr4d;tuuTS{rGq`cɍ4𦇥CDKnFNoNJV| rg ׁޭw\V:IveǤsT7T@*u|) Z)4Zr(nFCtGtjŎvޏ#Jg0NM60ct U޼pBUP̭D:ivtJ~収J0vFRɷ0tNIt¤KU~8䶓wG4~ n0ðRޏVqtZ |;X@˭?J:HgP#'7}k֞~>l:60t iWM寭R_%E_Dv^GIg0\qV=-W|:hSb'TU_r[Oq% 5rz!"jniom/u]Jpt U\?W(bY\X/U'P%i,Cϗw+_ɎJ:ɪ^Z4swoʯ|Cpĭu!LV%W:A2`@/@x3`9%khGm[Ruw|}]&4:g'@ ?N0=x+Qdx1pK]=FjiUluHO:Kc=O3I{>Uҗr7%l+80O3Aݓ4xt0/޻|&Ǻn fBCW&b0y6:*" 䈈@1'{gJ]hlyxW;JU/qLqt0s[Emv]a:@5q1copyjY=/n(Qw(N7w0r/YW9|64N:DXK,]i'q%^p0s4.T?yoSGbSEՔ>]:CEH ;֬B5 WhiC KQ}]Uyr~*!L䊫W0l|vqO.k1{n48<'|ʚ=p?G^+"L|~}M3[9W0Y/3_#!ll_WA'J|Z| j<\C-:ֽE6֗\vE/ xwA&8_>ءE3lM;w+)ht0O cmB' ;L_OUErOmfuɍmH3:|ì,1T\='Lز#t0rnOsb,IuKC.joi88 ^%up=mJ,؊oKS‰ )m'3eѯ6$"-g-ĸ7>u/{3,_ %Tˉ<Çc'c% 1;OjywDԞHp31鴑2WrƗcLSWg1※3?M=kG7)u%7nMu}HRq0U9L]+s9κ0EUC|( gstt3-uK׬Na kJniͯB7=8:w<䗍]U7Hg5%/19Hb*3s{ tXSr t`oM}NM9Jl?aЗܦoK(GpNWuv!Fmnȅ䢣cH( _}D:E0Q56 C*%G|Z:C^thX*~ pYap9q'EsjT-~!l&ܣ$s`UفCM FsTؕE:B]r@8.U:5`rt uK]xKn&Æ%7W #aUO m '2s_V|),޴*0H-93_x'S97ߠo#%OUG#ugzP{|tݸg!l9|tKò^"%x55<]*رg;z bTeXsc/Lw{tVj\qht],EI Ept?} &t%iBE:_CD>Uh^JpLThtt8iǤs=(t%GD&Ϗq/IJ.}\:D5 Wɥ^|$`>z:e?A1E csP-aW2 Qy}*(T%{uyP:Uϩ!Y1FOeY).r2roUHUo\ |H(#?NQBSr&tC`ߝl`bø{I.4% TE/)QQYBSrys3㕸m Qf9GW/[A: QɑH簉՜ I:Gy!BQr-optעؤL_.N(8?a}M_QŇI$%G+9+#d™3:A:KzCW<[:N&6qQƛtp"`'}!lI5s9p:xeN:ڑ%M8UɻB:M)agq" .޽9k򜚤t0/xL:-܎a`#Lx!/9*aˤ#؄^ !b!j|ƗM*-Cb |H:GF"g{2`ҙi~E:-BZ.[f3'i#>q-'}9JI77%7n>F[ n]: ?,ع5,CMLTu!lM~a9]*B% c.UI/U1"{q4\t{F,a+7)d,„{WJP1~tQޡ`:ߋ8tHP3/PO8dj0|XSҙ^r&9otꜽ/-LMY]+.d-Z@t߉s ˚; >Ɩ\ī5Raܸ2 u$[@8I:Gyn]e$-9"#:p^aot2l![rgrt+VmNfn:y@Qb4 jj-9C"C:C͟SC1CF03 >8e4dI9Av\ҧӄgr_uB+q%l%%'=pG 9Abv0L9 S1Ntimb \$BȒ˺Mgq?pP$܅^e&K'Q0-yْc< :~HeNOΔQt#KKt|ЃL̉})(#K.yOVp~/9~閏t0-ucd1D^YM`\:G9xOb!#KɱޏںsAxtMɥS0RcJj0ʦk9?o_C0ȡZrZ0]aDxym13?Z\ܚo{N\yHtU9FBB@ =R2CK&F!'-gWU:i##xLx=<:OJaCL)^:q5ꤣ2"َR|/  ?d;>#F7ZqCm> 9DFNo;D: ^^0=忙y(wJ`)1dnlYO/BP{[s]Q0J)Z0e^FI~D5-TYxa$~Y<ϯ`Yr )9&֝'Y:G/CבV.Q&\[1 {슑%'uȟ% T{ X(-;i)9;Zn$2i̒'7\;%753yPoSq,4ǗP`懖Hb,0jiL;(H$/[3 l?8N%YrhC?|nG |N:wf7D:SɽTD獆޺uZr ~LOKn'C0!l%lM%1f+~F1?(m |L97Ǡås#qnķWrI,&oF*/=\ ptęCm:3NM6L1E|9db F7=$AiK4wSPus7TshJM|<8A!.YrJ2JS̋x:D}l[Н4iW#KǴNM684HG,~_Sf/hyjG -*#F!"λ&mњP씱`Q/ɦZcԬ7cZWa34 =u:͐I7w(d0>uو dCnp=9Vn 4øV3G$J't@H&rN[f?1]89MMĦeo þudtxG#U*6 3KΡ˫ʒ+\87Yzbܲy5j-#vNpw|@{^\" ւڕL;-eMq,޹NJUt™L2`>ZdR!7G%8Ls37%@Aph35vJ -@1*hh!mk(ӼG,Wr;[P.Nx;^_fJGP X{{94w%c9]>b`S"r^03Kş n*DUSrT4N13}aYD_o^/J'%DAo*p;Z?ΕE83{{Mc70cK.}L]/wo4y/v" >_n\R;rXKn24)Qehm5=@B:ܟhʞJB*ỊR7eƖh]!OM8 T'#ާ&;HK.Xu!cld&2'/q4!ݣ%mx`ҫ6'{_+Kp1; Jnxh/ڬ$`}ni =3%񣁎ǎ;MZnc62y((_IM^K!~ uca:wS0>,ejo2Udw{dv~> ~P:î]r% ȽPȖ#]Zf>&eJԏCd&ҷK JKW昃 r<_uu7y׃qt`<%k%C!v; ^tLp: !] \X1Q[>zL9w:aY3\//ޔX>+eJo?d&)e=P#&b|%{hCcF8 +Gfa~5+A,SA{Ӳ_[yBc]_c+kxD:D/9M4 '.2f$| Er '+\Dr7Vp*Ƿc1gk726 -!%xt(\b> ykB.Tӵv'%д^:K6ݺLA6 dO֯7~N(Jnl*IRbe`D&3.;|WūՕxXp? |P.CבFy{@Kg)C>yk]NJUYZlȍ#@ke/[Q$jMG"U¼R*M U@WbДV "1ggR-"u$[$rc|rW|(vlU(nCH[*T*؏M l 7=+.5ƭ[Mt.Ԉ͕>% ( /0}nG򰊎up}#z,B55?-QI4foMnʒcPtWR36>0u`Mω'VM)YaA=1B?Kǘ p-*&p:ܿlm.#<P=S:Ůhfc<P&6y#.\w$ʒ+4 ? )<?ёJY~h}h\rcD ^\%wt0!P:p\CZlt<obP%AP}ݽFHCuF s3C B[rGBIm^$Ŗ:x܎.0Βn$o ?yMWŌu37lW2ݶtzX .9AttE-]^:ʔ\;Lߒa4[=}!.h"ӵ-#G6.%,HLы> zjv0?ޓN#y1HUJ,k=mo>lanbE+grw1+n) b!WK0G 6l)`^-^!\?6}6Hp 9LP8Su\:_)9\n$":_i {TʞKobऱo]jۙ=%ni:&tKGC)}pt)ŇIM}IvI`SeXOL_Vדsنc j]0Ѱ;VmfBaEuã[^ur4A(A!XEd\e[a ~? øOp'0DO_*s&b_ 3Ǧs[7k9*cEv Vm3NMq'E(zL:5,]七-7tm_J %m+^QAyfĈsV{JWYQ|ٺ':JcTbNUc<ݚ?'iIG)C;D7 K RVt1t@N|y߯ 7ӞcvNN[:+pZ-YXMd 7ğR.e{|K*Kh^˷N:Nx~?^JG~ #3F:-ۚAt0@;u ȤZ:5Yv4NrU}m3j~GKg z¶/fX9O ٠/ry);U-Q&Qb^<́p@7oܴNJUo]ωF$&?CX{E=5-.l53`?<yjX$ 26B܀J02gShyׯxcKKn* f[0S~Ocq[g¿͚gpO#~Q%W{2 ~+<Dox09a!\._6 ~*~) n-)hfcly`{1:xy!&<_ѯ{@KN)e5{ZRJ)-9RVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYMKN)e5-9մRVӒSJYDaIENDB`libjjml-java-1.1.13/sdk/branches/000077500000000000000000000000001510364424500165115ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/branches/testing.bnd000066400000000000000000000000441510364424500206510ustar00rootroot00000000000000major=1 minor=1 micro=13 qualifier= libjjml-java-1.1.13/sdk/jbin/000077500000000000000000000000001510364424500156465ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/jbin/JjmlDummyCli.java000066400000000000000000000425561510364424500210650ustar00rootroot00000000000000//!/usr/bin/env -S java -cp /usr/share/java/org.argeo.jjml.jar import static java.lang.Boolean.FALSE; import static java.lang.Boolean.parseBoolean; import static java.lang.System.Logger.Level.INFO; import static java.nio.charset.StandardCharsets.UTF_8; import static org.argeo.jjml.llm.LlamaCppContext.defaultContextParams; import static org.argeo.jjml.llm.LlamaCppNative.ENV_GGML_CUDA_ENABLE_UNIFIED_MEMORY; import static org.argeo.jjml.llm.params.ModelParam.n_gpu_layers; import static org.argeo.jjml.llm.util.InstructRole.ASSISTANT; import static org.argeo.jjml.llm.util.InstructRole.SYSTEM; import static org.argeo.jjml.llm.util.InstructRole.USER; import java.io.BufferedReader; import java.io.Console; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.PrintWriter; import java.lang.System.Logger; import java.nio.IntBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.argeo.jjml.llm.LlamaCppBackend; import org.argeo.jjml.llm.LlamaCppBatchProcessor; import org.argeo.jjml.llm.LlamaCppChatMessage; import org.argeo.jjml.llm.LlamaCppContext; import org.argeo.jjml.llm.LlamaCppEmbeddingProcessor; import org.argeo.jjml.llm.LlamaCppModel; import org.argeo.jjml.llm.LlamaCppNative; import org.argeo.jjml.llm.LlamaCppSamplerChain; import org.argeo.jjml.llm.LlamaCppSamplers; import org.argeo.jjml.llm.LlamaCppVocabulary; import org.argeo.jjml.llm.params.ContextParam; import org.argeo.jjml.llm.params.ModelParam; import org.argeo.jjml.llm.params.ModelParams; import org.argeo.jjml.llm.params.PoolingType; import org.argeo.jjml.llm.util.SimpleModelDownload; import org.argeo.jjml.llm.util.SimpleProgressCallback; /** A minimal command line interface for batch processing and simple chat. */ public class JjmlDummyCli { private final static Logger logger = System.getLogger(JjmlDummyCli.class.getName()); private final static String DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant."; /** Force chat mode in (Eclipse) IDE, when no proper console is available. */ private final static boolean developing = parseBoolean(System.getProperty("JjmlDummyCli.ide", FALSE.toString())); public static void main(String... args) throws Exception { if (args.length == 0) { System.err.println("A GGUF model must be specified"); printUsage(System.err); System.exit(1); } if ("--help".equals(args[0])) { printUsage(System.out); System.exit(0); } /* * ARGUMENTS */ String arg0 = args[0]; Path modelPath = Paths.get(arg0); if (!Files.exists(modelPath)) modelPath = new SimpleModelDownload().getOrDownloadModel(arg0, new SimpleProgressCallback()); if (!Files.exists(modelPath)) throw new IllegalArgumentException("Could not find GGUF model " + modelPath); boolean embeddings = Boolean.parseBoolean(System.getProperty(ContextParam.embeddings.asSystemProperty())); int chunkSize = 0; String embeddingsFormat = "csv"; String systemPrompt = DEFAULT_SYSTEM_PROMPT; if (embeddings) { if (args.length > 1) { chunkSize = Integer.parseInt(args[1]); if (args.length > 2) { embeddingsFormat = args[2]; } } } else { if (args.length > 1) { systemPrompt = args[1]; if (systemPrompt.contains(File.separator) || systemPrompt.contains("/")) { try {// try to interpret as file systemPrompt = Files.readString(Paths.get(systemPrompt), UTF_8); } catch (IOException e) { System.err .println("Could not interpret '" + systemPrompt + "' as a file, using it as value..."); } } } } /* * AUTOCONFIG */ ModelParams modelParams = LlamaCppModel.defaultModelParams(); if ("1".equals(System.getenv(ENV_GGML_CUDA_ENABLE_UNIFIED_MEMORY)) // && System.getProperty(n_gpu_layers.asSystemProperty()) == null // && LlamaCppBackend.supportsGpuOffload() // && modelParams.n_gpu_layers() == 0 // ) { // we assume we want as many layers offloaded as possible modelParams = modelParams.with(n_gpu_layers, 99); } logger.log(INFO, "Loading model " + modelPath + " ..."); Future loaded = LlamaCppModel.loadAsync(modelPath, modelParams, new SimpleProgressCallback(), null); try (LlamaCppModel model = loaded.get(); // LlamaCppContext context = new LlamaCppContext(model, defaultContextParams()); // ) { Object processor; if (embeddings) { processor = new SimpleEmbedding(context, chunkSize); } else { processor = new SimpleChat(systemPrompt, context); } Console console = System.console(); final boolean isConsoleTerminal = console != null; // From Java 22, it will be: // boolean interactive = console.isTerminal(); final boolean interactive = developing || isConsoleTerminal; try { if (interactive) { PrintWriter out = console != null ? console.writer() : new PrintWriter(System.out, true); out.print("> "); out.flush(); try (BufferedReader reader = new BufferedReader( console != null ? console.reader() : new InputStreamReader(System.in))) { String line; while ((line = reader.readLine()) != null) { String input = handleHereDocument(line, reader); if (processor instanceof SimpleChat) { ((SimpleChat) processor).apply(input, (str) -> { out.print(str); out.flush(); }).thenAccept((v) -> { out.print("\n> "); out.flush(); }); } else if (processor instanceof SimpleEmbedding) { float[][] res = ((SimpleEmbedding) processor).apply(input); printEmbeddings(out, res, embeddingsFormat); out.print("\n> "); out.flush(); } } } } else {// batch String input; try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in, UTF_8))) { StringBuilder sb = new StringBuilder(); final int BUFFER_SIZE = 4 * 1024; char[] buf = new char[BUFFER_SIZE]; int numCharsRead; while ((numCharsRead = in.read(buf, 0, buf.length)) != -1) sb.append(buf, 0, numCharsRead); input = sb.toString(); } if (processor instanceof SimpleChat) { ((SimpleChat) processor).apply(input, (str) -> { System.out.print(str); System.out.flush(); }).toCompletableFuture().join(); } else if (processor instanceof SimpleEmbedding) { float[][] res = ((SimpleEmbedding) processor).apply(input); printEmbeddings(new PrintWriter(System.out, true, StandardCharsets.UTF_8), res, embeddingsFormat); } } } finally { // make sure that we are not reading before cleaning up backend if (processor instanceof SimpleChat) ((SimpleChat) processor).cancelCurrentRead(); } } } private static void printUsage(PrintStream out) { out.println("Usage: java " + JjmlDummyCli.class.getName() // + " []"); out.println("Usage: java -Djjml.llama.context.embeddings=true " + JjmlDummyCli.class.getName() // + " [] [ csv | pgvector ]"); out.println(); out.println("- Opens a basic interactive chat when in a terminal."); out.println("- Piping input will disable interactivity and submit the whole input as a single user prompt."); out.println("- The context does not auto-extend, that is, it will be full at some point."); out.println("- All external inputs should be encoded with UTF-8."); out.println("- If contains a file separator or /, it will be loaded as a file."); out.println("- default is '" + DEFAULT_SYSTEM_PROMPT + "'."); out.println("- If is set to \"\", message formatting with chat template is disabled."); out.println("- For embeddings, a of 0 (default) disable chunking."); out.println("- For embeddings, default output format is 'csv', while 'pgvector' generates VALUES."); out.println(); out.println("# In interactive mode, use < Suggest improvements to this Java code: < new StringJoiner(",")); else if ("pgvector".equals(format)) printEmbeddings(out, embeddings, ",\n", () -> new StringJoiner(",", "('[", "]')")); else throw new IllegalArgumentException("Unknown output format " + format); } /** Format a float array. */ private static void printEmbeddings(PrintWriter out, float[][] embeddings, String vecorSep, Supplier valueSj) { for (int i = 0; i < embeddings.length; i++) { if (i != 0) out.print(vecorSep); StringJoiner sj = valueSj.get(); for (int j = 0; j < embeddings[i].length; j++) sj.add(Float.toString(embeddings[i][j])); out.print(sj); } } } /** * A simple implementation of a chat system based on the low-level components. */ class SimpleChat extends LlamaCppBatchProcessor implements BiFunction, CompletionStage> { private final LlamaCppVocabulary vocabulary; private volatile boolean reading = false; private FutureTask currentRead = null; private final LlamaCppChatMessage systemMsg; private boolean firstMessage = true; private final boolean formatMessages; /* * In llama.cpp examples/main, a new user prompt is obtained via a substring of * the previous messages. We reproduce this behavior here, even though it is not * clear yet whether it is useful. */ // TODO: check in details and remove this as it would greatly simplify the code private final boolean usePreviousMessages; private final List messages; public SimpleChat(String systemPrompt, LlamaCppContext context) { this(systemPrompt, context, LlamaCppSamplers.newDefaultSampler(true)); } /** * Creates a simple chat processor. * * @param systemPrompt The system prompt. If null or empty, it * disables chat templating. */ public SimpleChat(String systemPrompt, LlamaCppContext context, LlamaCppSamplerChain samplerChain) { super(context, samplerChain); vocabulary = getModel().getVocabulary(); if (systemPrompt == null || "".equals(systemPrompt)) { systemMsg = null; formatMessages = false; usePreviousMessages = false; } else { systemMsg = new LlamaCppChatMessage(SYSTEM, systemPrompt); formatMessages = true; usePreviousMessages = true; } messages = usePreviousMessages ? new ArrayList<>() : null; } @Override public CompletionStage apply(String message, Consumer consumer) { if (currentRead != null && !currentRead.isDone()) { // throw new ConcurrentModificationException("Currently interacting, use // cancel."); cancelCurrentRead(); if (message.trim().equals("")) return CompletableFuture.completedStage(null); // this was just for interruption } // message = message.replace("\\\n", "\n"); String prompt; if (formatMessages) { LlamaCppChatMessage userMsg = new LlamaCppChatMessage(USER, message); if (usePreviousMessages) { String previousPrompts = messages.size() == 0 ? "" : getModel().formatChatMessages(messages); if (firstMessage) { if (systemMsg != null) messages.add(systemMsg); firstMessage = false; } messages.add(userMsg); String newPrompts = getModel().formatChatMessages(messages); assert previousPrompts.length() < newPrompts.length(); prompt = newPrompts.substring(previousPrompts.length(), newPrompts.length()); } else { List lst = new ArrayList<>(); if (firstMessage) { lst.add(systemMsg); firstMessage = false; } lst.add(userMsg); prompt = getModel().formatChatMessages(lst); } } else { prompt = message; } // tokenize IntBuffer input = vocabulary.tokenize(prompt); writeBatch(input, true); FutureTask future = new FutureTask<>(() -> { String reply = readAll(consumer); if (usePreviousMessages) { LlamaCppChatMessage assistantMsg = new LlamaCppChatMessage(ASSISTANT, reply); messages.add(assistantMsg); } return null; }); setCurrentRead(future); ForkJoinPool.commonPool().execute(future); return CompletableFuture.runAsync(() -> { try { future.get(); } catch (InterruptedException | ExecutionException e) { // TODO deal with it } }); } protected String readAll(Consumer consumer) { try { StringBuffer sb = new StringBuffer(); reading = true; running: while (reading) { IntBuffer output = IntBuffer.allocate(getContext().getBatchSize()); CompletableFuture done = SimpleChat.this.readBatchAsync(output); boolean generationCompleted = done.join(); output.flip(); String str = vocabulary.deTokenize(output); consumer.accept(str); if (usePreviousMessages) sb.append(str); output.clear(); if (generationCompleted) {// generation completed as expected break running; } if (Thread.interrupted()) {// generation was interrupted int endOfGenerationToken = getContext().getModel().getEndOfGenerationToken(); IntBuffer input = IntBuffer.allocate(1); input.put(endOfGenerationToken); input.flip(); writeBatch(input, false); if (usePreviousMessages) sb.append(vocabulary.deTokenize(input)); consumer.accept("");// flush break running; } } return sb.toString(); } finally { reading = false; } } protected boolean isReading() { return reading; } protected void cancelCurrentRead() { if (currentRead == null) return; if (!currentRead.isDone()) { currentRead.cancel(true); while (isReading()) // wait for reading to complete try { Thread.sleep(100); } catch (InterruptedException e) { return; } } } private void setCurrentRead(FutureTask currentRead) { this.currentRead = currentRead; } } /** Computes embeddings based on chunks of a given size. */ class SimpleEmbedding extends LlamaCppEmbeddingProcessor implements Function { private final LlamaCppVocabulary vocabulary; private final int chunkSize; /** * Constructor. * * @param context The context used to initialize this processor. * @param chunkSize The size of the chunks. If <=0, the strings will be * processed as a whole. */ public SimpleEmbedding(LlamaCppContext context, int chunkSize) { super(context); this.vocabulary = getContext().getModel().getVocabulary(); this.chunkSize = chunkSize; } @Override public float[][] apply(String str) { if (chunkSize <= 0 || PoolingType.LLAMA_POOLING_TYPE_NONE.equals(getContext().getPoolingType())) { return processEmbeddings(Collections.singletonList(str)); } int totalLength = str.length(); IntBuffer[] inputs = new IntBuffer[totalLength / chunkSize + (totalLength % chunkSize == 0 ? 0 : 1)]; for (int i = 0; i < inputs.length; i++) { String chunk; if (i == inputs.length - 1) { chunk = str.substring(i * chunkSize); } else { chunk = str.substring(i * chunkSize, (i + 1) * chunkSize); } inputs[i] = vocabulary.tokenize(chunk); } return processEmbeddings(inputs); } } libjjml-java-1.1.13/sdk/jbin/JjmlModelDownload.java000066400000000000000000000012541510364424500220600ustar00rootroot00000000000000//!/usr/bin/env -S java -cp /usr/share/java/org.argeo.jjml.jar import org.argeo.jjml.llm.util.SimpleModelDownload; import org.argeo.jjml.llm.util.SimpleProgressCallback; /** Downloads a model from Hugging Face. */ public class JjmlModelDownload { public static void main(String[] args) throws Exception { if (args.length == 0) { System.err.println("Download a quantized model (default is Q4_K_M)\n" + // "Usage: java " + JjmlModelDownload.class.getSimpleName() + ".java" // + " :[]\n" // + "e.g. allenai/OLMo-2-0425-1B-Instruct-GGUF:Q8_0"); } new SimpleModelDownload().getOrDownloadModel(args[0], new SimpleProgressCallback()); } } libjjml-java-1.1.13/sdk/jbin/JjmlSmokeTests.java000066400000000000000000000414541510364424500214370ustar00rootroot00000000000000 //!/usr/bin/env -S java -ea -cp /usr/share/java/org.argeo.jjml.jar import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; import static java.nio.charset.StandardCharsets.UTF_8; import static org.argeo.jjml.llm.LlamaCppContext.defaultContextParams; import static org.argeo.jjml.llm.LlamaCppModel.defaultModelParams; import static org.argeo.jjml.llm.LlamaCppSamplers.newJavaSampler; import static org.argeo.jjml.llm.params.ContextParam.embeddings; import static org.argeo.jjml.llm.params.ContextParam.kv_unified; import static org.argeo.jjml.llm.params.ContextParam.n_batch; import static org.argeo.jjml.llm.params.ContextParam.n_ctx; import static org.argeo.jjml.llm.params.ContextParam.n_threads; import static org.argeo.jjml.llm.params.ContextParam.n_ubatch; import static org.argeo.jjml.llm.util.InstructRole.ASSISTANT; import static org.argeo.jjml.llm.util.InstructRole.SYSTEM; import static org.argeo.jjml.llm.util.InstructRole.USER; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.System.Logger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import org.argeo.jjml.llm.LlamaCppBackend; import org.argeo.jjml.llm.LlamaCppContext; import org.argeo.jjml.llm.LlamaCppContextState; import org.argeo.jjml.llm.LlamaCppEmbeddingProcessor; import org.argeo.jjml.llm.LlamaCppInstructProcessor; import org.argeo.jjml.llm.LlamaCppJavaSampler; import org.argeo.jjml.llm.LlamaCppModel; import org.argeo.jjml.llm.LlamaCppNative; import org.argeo.jjml.llm.LlamaCppNativeSampler; import org.argeo.jjml.llm.LlamaCppSamplerChain; import org.argeo.jjml.llm.LlamaCppSamplers; import org.argeo.jjml.llm.LlamaCppTextProcessor; import org.argeo.jjml.llm.LlamaCppVocabulary; import org.argeo.jjml.llm.params.ContextParams; import org.argeo.jjml.llm.params.ModelParams; import org.argeo.jjml.llm.util.SimpleModelDownload; import org.argeo.jjml.llm.util.SimpleProgressCallback; /** * Minimal set of non-destructive in-memory tests, in order to check that a * given deployment and/or model are working. Java assertions must be enabled. */ class JjmlSmokeTests { private final static Logger logger = System.getLogger(JjmlSmokeTests.class.getName()); private int parallelism = Runtime.getRuntime().availableProcessors(); public void main(List args) throws Exception, AssertionError { try { if (!getClass().desiredAssertionStatus()) { logger.log(ERROR, "Assertions must be enabled. Please call Java with the -ea option."); return; } long begin = System.currentTimeMillis(); // even without a model we can check whether native libraries are loading assert ((BooleanSupplier) () -> { LlamaCppNative.ensureLibrariesLoaded(); return true; }).getAsBoolean(); logger.log(INFO, "Native libraries properly loaded."); if (args.isEmpty()) throw new IllegalArgumentException("A model must be specified"); String arg0 = args.get(0); Path modelPath = Paths.get(arg0); if (!Files.exists(modelPath)) modelPath = new SimpleModelDownload().getOrDownloadModel(arg0, new SimpleProgressCallback()); if (!Files.exists(modelPath)) throw new IllegalArgumentException("Could not find GGUF model " + modelPath); ModelParams modelParams = defaultModelParams(); logger.log(INFO, "Loading model " + modelPath + " ..."); Future loaded = LlamaCppModel.loadAsync(modelPath, modelParams, new SimpleProgressCallback(), null); try (LlamaCppModel model = loaded.get();) { logger.log(INFO, "Model " + model.getDescription()); logger.log(INFO, model.getLayerCount() + " layers"); logger.log(INFO, model.getEmbeddingSize() + " embedding size"); logger.log(INFO, model.getVocabularySize() + " vocabulary size"); logger.log(INFO, model.getContextTrainingSize() + " context training size"); StringBuilder sb = new StringBuilder(); for (String key : model.getMetadata().keySet()) sb.append(key + "=" + model.getMetadata().get(key) + "\n"); logger.log(DEBUG, "Metadata:\n" + sb); assertVocabulary(model.getVocabulary()); // TODO return if vocabulary only // if (true) // return; assertLoadUnloadDefaultContext(model); assertEmbeddings(model); assertBatch(model); assertJavaSampler(model); assertChat(model); assertSavedContextState(model); } logger.log(INFO, "Smoke tests passed in " + (System.currentTimeMillis() - begin) / 1000 + " s with model " + modelPath.getFileName()); } catch (Exception | AssertionError e) { logger.log(ERROR, "Smoke tests failed", e); throw e; } finally { LlamaCppBackend.destroy(); } } void assertVocabulary(LlamaCppVocabulary vocabulary) { int size = 256; // in direct, out direct assertVocabulary(vocabulary, // ByteBuffer.allocateDirect(size), // ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer()); // in array, out direct assertVocabulary(vocabulary, // ByteBuffer.allocate(size), // ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer()); // in string, out direct assertVocabulary(vocabulary, // null, // ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer()); // in direct, out array assertVocabulary(vocabulary, // ByteBuffer.allocateDirect(size), // IntBuffer.allocate(size / Integer.BYTES)); // in array, out array assertVocabulary(vocabulary, // ByteBuffer.allocate(size), // IntBuffer.allocate(size / Integer.BYTES)); // in string, out array assertVocabulary(vocabulary, // null, // IntBuffer.allocate(size / Integer.BYTES)); } void assertVocabulary(LlamaCppVocabulary vocabulary, ByteBuffer in, IntBuffer out) { assert testTokenizeDetokenize(vocabulary, in, out, "Hello World!"); assert testTokenizeDetokenize(vocabulary, in, out, "Même si je suis Français, je dis bonjour au monde"); assert testTokenizeDetokenize(vocabulary, in, out, "ἔορθoι χθόνιοι"); // according to olmoe-1b-7b-0924 assert testTokenizeDetokenize(vocabulary, in, out, "السلام عليكم"); // according to olmoe-1b-7b-0924 assert testTokenizeDetokenize(vocabulary, in, out, "¡Hola и أَشْكَرُ мир! 👋🏼🌍"); logger.log(INFO, "Vocabulary smoke tests variant PASSED"); } boolean testTokenizeDetokenize(LlamaCppVocabulary vocabulary, ByteBuffer in, IntBuffer buf, String msg) { if (in != null) in.clear(); buf.clear(); logger.log(DEBUG, msg); if (in == null) { IntBuffer tokens = vocabulary.tokenize(msg); buf.put(tokens); } else { in.put(msg.getBytes(UTF_8)); in.flip(); vocabulary.tokenize(msg, buf); } buf.flip(); logger.log(DEBUG, logIntegers(buf, 32, ", ")); String str; if (in == null) { str = vocabulary.deTokenize(buf); } else { in.clear(); vocabulary.deTokenize(buf, in); in.flip(); str = UTF_8.decode(in).toString(); } assert str.equals(msg); return true; } void assertLoadUnloadDefaultContext(LlamaCppModel model) { try (LlamaCppContext context = new LlamaCppContext(model);) { assert context.getContextSize() > 0; } logger.log(INFO, "Load default context smoke tests PASSED"); } void assertEmbeddings(LlamaCppModel model) { int batchSize = 512; try (LlamaCppContext context = new LlamaCppContext(model, LlamaCppContext.defaultContextParams() // .with(embeddings, true) // .with(n_ctx, 6144) // .with(n_batch, batchSize) // .with(n_ubatch, batchSize) // must be same for embeddings .with(kv_unified, true) // required for robustness );) { LlamaCppEmbeddingProcessor embeddingProcessor = new LlamaCppEmbeddingProcessor(context); List prompts = new ArrayList<>(); prompts.add("Hello world!"); prompts.add("Good night and good luck."); for (String s : prompts) logger.log(DEBUG, "=>\n" + s); float[][] embeddings = embeddingProcessor.processEmbeddings(prompts); assert embeddings.length != 0; for (float[] embedding : embeddings) { logger.log(DEBUG, "<=\n[ " + embedding[0] + ", " + embedding[1] + ", ... ]"); } } logger.log(INFO, "Embeddings smoke tests PASSED"); } void assertBatch(LlamaCppModel model) { String prompt = "Write HELLO\n"// + "HELLO\n"// + "Write WORLD\n"// + "WORLD\n"// + "Write TEST\n" // ; // !! max seq_id must be < 64 // TODO understand why Integer[] sequenceIds = { 1, 10, 63 }; try ( // LlamaCppContext context = new LlamaCppContext(model, defaultContextParams() // .with(n_ctx, 6144) // .with(n_batch, sequenceIds.length * prompt.length()) // .with(kv_unified, true) // required for robustness ); // LlamaCppSamplerChain chain = LlamaCppSamplers.newDefaultSampler(false); // LlamaCppNativeSampler validatingSampler = LlamaCppSamplers.newSamplerGrammar(model, // "root ::= [ \\t\\n]* \"TEST\"", "root");// ) { // long begin = System.currentTimeMillis(); LlamaCppTextProcessor processor = new LlamaCppTextProcessor(context, chain, validatingSampler, Set.of(sequenceIds)); System.out.println("=>\n" + prompt); String str = processor.processBatch(prompt); System.out.println("<=\n" + str); // System.out.println("\n\n## Processing took " + (System.currentTimeMillis() - // begin) + " ms"); } logger.log(INFO, "Batch smoke tests PASSED"); } void assertJavaSampler(LlamaCppModel model) { Integer[] sequenceIds = { 1 }; try ( // LlamaCppContext context = new LlamaCppContext(model, defaultContextParams() // .with(n_ctx, 6144) // .with(n_batch, sequenceIds.length * 64) // .with(kv_unified, true) // required for robustness ); // LlamaCppSamplerChain chain = new LlamaCppSamplerChain( newJavaSampler(new LlamaCppJavaSampler.SimpleGreedy())); // LlamaCppNativeSampler validatingSampler = LlamaCppSamplers.newSamplerGrammar(model, // "root ::= [ \\t\\n]* \"TEST\"", "root");// ) { // long begin = System.currentTimeMillis(); LlamaCppTextProcessor processor = new LlamaCppTextProcessor(context, chain, validatingSampler, Set.of(sequenceIds)); String prompt = "Write HELLO\n"// + "Hello\n"// + "Write World\n"// + "WORLD\n"// + "Write test\n" // ; System.out.println("=>\n" + prompt); String str = processor.processBatch(prompt); System.out.println("<=\n" + str); // System.out.println("\n\n## Processing took " + (System.currentTimeMillis() - // begin) + " ms"); } logger.log(INFO, "Java sampler smoke tests PASSED"); } void assertChat(LlamaCppModel model) throws IOException { try (// LlamaCppContext context = new LlamaCppContext(model, defaultContextParams() // .with(n_ctx, 20480) // .with(n_batch, 1024) // .with(n_threads, parallelism) // ); // LlamaCppSamplerChain chain = LlamaCppSamplers.newDefaultSampler(false); // ) { LlamaCppInstructProcessor processor = new LlamaCppInstructProcessor(context, chain); String systemMsg = "You are a helpful assistant, which answers as briefly as possible."; System.out.println(SYSTEM.name() + " :\n" + systemMsg); processor.write(SYSTEM, systemMsg); String userMsg01 = "Introduce the Java programming language in no more than two sentences."; System.out.println(USER.name() + " :\n" + userMsg01); processor.write(USER, userMsg01); System.out.println(ASSISTANT.name() + " :\n"); processor.readMessage(System.out); // make sure it can deal with a second message String userMsg02 = "Thank you!"; System.out.println(USER.name() + " :\n" + userMsg02); processor.write(USER, userMsg02); System.out.println(ASSISTANT.name() + " :\n"); processor.readMessage(System.out); } logger.log(INFO, "Chat smoke tests PASSED"); } void assertSavedContextState(LlamaCppModel model) throws IOException { ContextParams contextParams = LlamaCppContext.defaultContextParams() // .with(n_ctx, 20480) // .with(n_batch, 1024) // .with(n_threads, parallelism) // ; // final LlamaCppContextState savedState; final Path sessionFile = Files.createTempFile("jjml_session_", ".llama"); Runtime.getRuntime().addShutdownHook(new Thread((Runnable) () -> { try { Files.deleteIfExists(sessionFile); } catch (IOException e) { throw new UncheckedIOException(e); } })); try (// LlamaCppContext context = new LlamaCppContext(model, contextParams); // LlamaCppSamplerChain chain = LlamaCppSamplers.newDefaultSampler(false); // ) { LlamaCppInstructProcessor processor = new LlamaCppInstructProcessor(context, chain); long begin = System.currentTimeMillis(); String systemMsg = "You are a travel agent helping the user to chose the best holiday destination.\n" + "You answer with a city name, and one sentence explanation of your choice, nothing else."; System.out.println(SYSTEM.name() + " :\n" + systemMsg); processor.write(SYSTEM, systemMsg); String userMsg01 = "I want to spend my vacations in Europe.\n" + "I like Italy, but I am open to other destinations, as long as there is nature and culture.\n" + "I have never been to Scandinavia, but it can wait.\n" + "I would like to avoid the usual touristic destinations, so be creative!\n" + "I will travel in autumn, so it should not be too hot.\n" + "Also please consider that I speak French and German in addition to English.\n" + "And I definitely don't like holiday on the beach..."; System.out.println(USER.name() + " :\n" + userMsg01); processor.write(USER, userMsg01); savedState = new LlamaCppContextState.ByteBufferSavedState(); logger.log(INFO, "Wrote context in " + (System.currentTimeMillis() - begin) + " ms"); processor.saveContextState(savedState); long beginSaveContext = System.currentTimeMillis(); logger.log(INFO, "Saved context in " + (System.currentTimeMillis() - beginSaveContext) + " ms"); long beginSaveSessionFile = System.currentTimeMillis(); processor.saveStateFile(sessionFile); logger.log(INFO, "Saved session file to " + sessionFile + " in " + (System.currentTimeMillis() - beginSaveSessionFile) + " ms"); } String userMsg02 = "Current Date: March 13th 2020."; Consumer process = (processor) -> { System.out.println(USER.name() + " :\n" + userMsg02); processor.write(USER, userMsg02); System.out.println(ASSISTANT.name() + " :\n"); long begin = System.currentTimeMillis(); try { processor.readMessage(System.out); } catch (IOException e) { throw new UncheckedIOException(e); } logger.log(INFO, "Generation took " + +(System.currentTimeMillis() - begin) + " ms"); }; // deterministic answer try (LlamaCppContext context = new LlamaCppContext(model, contextParams); // LlamaCppSamplerChain chain = LlamaCppSamplers.newDefaultSampler(false); // ) { LlamaCppInstructProcessor processor = new LlamaCppInstructProcessor(context, chain); long beginLoad = System.currentTimeMillis(); processor.loadContextState(savedState); logger.log(INFO, "Loaded context from memory in " + (System.currentTimeMillis() - beginLoad) + " ms"); process.accept(processor); } // with temperature try (LlamaCppContext context = new LlamaCppContext(model, contextParams); // LlamaCppSamplerChain chain = LlamaCppSamplers.newDefaultSampler(true); // ) { LlamaCppInstructProcessor processor = new LlamaCppInstructProcessor(context, chain); long beginLoad = System.currentTimeMillis(); processor.loadStateFile(sessionFile); logger.log(INFO, "Loaded context from file in " + (System.currentTimeMillis() - beginLoad) + " ms"); process.accept(processor); } logger.log(INFO, "Saved context state smoke tests PASSED"); } /* * STATIC UTILITIES */ /** CLI entry point. */ public static void main(String[] args) throws Exception { if (args.length == 0) { printUsage(); System.exit(1); } new JjmlSmokeTests().main(Arrays.asList(args)); } /** Print required arguments. */ static void printUsage() { System.err.println("Usage: java " + JjmlSmokeTests.class.getName() + // ".java path/to/model.gguf | hf_repo/model[:quantization]\n" + // "e.g. java " + JjmlSmokeTests.class.getName() + ".java allenai/OLMo-2-0425-1B-Instruct-GGUF"); } /** * Writes the beginning of an integer buffer as a string. It has no side effect * on the input buffer. */ static String logIntegers(IntBuffer in, int max, String separator) { StringBuilder sb = new StringBuilder(); integers: for (int i = in.position(); i < in.limit(); i++) { if (i != in.position()) sb.append(separator); if (i == max) { sb.append("..."); break integers; } sb.append(Integer.toString(in.get(i))); } return sb.toString(); } } libjjml-java-1.1.13/sdk/logging.properties000066400000000000000000000003761510364424500204760ustar00rootroot00000000000000.level = FINER #.level = FINER handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level = FINER java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%4$s: %5$s%n libjjml-java-1.1.13/tooling.mk000066400000000000000000000273301510364424500161540ustar00rootroot00000000000000# Makefile based on default Argeo SDK conventions, # used as a high-level driver for build, packaging, etc. # Low-level build only uses CMake. -include sdk.mk include sdk/argeo-build/cmake/default.mk TARGET_NATIVE_OUTPUT_GGML=$(TARGET_NATIVE_OUTPUT)/org.argeo.tp.ggml TARGET_NATIVE_OUTPUT_JJML=$(TARGET_NATIVE_OUTPUT)/org.argeo.jjml ## # Run make clean / all / install for the default CMake build. # If system ggml and/or llama.cpp libraries are found, # they will be used to build the JNI bindings, # otherwise the missing layer will be buit from the source submodule. # Use make rebuild-force-to (see below) in order to force a local build. # rebuild-force-to: To be used for "heavy" C++ development, # that is when adding new capabilities and exploring upstream code. It allows: # - to ensure the target binaries are built from the local sources submodules # - to build the tools and examples, so that they can be browsed, debugged, # and hacked in an IDE. # Activate various features via environment variables: GGML_BLAS ?= OFF GGML_VULKAN ?= OFF GGML_CUDA ?= OFF GGML_RPC ?= OFF GGML_OPENMP ?= OFF GGML_CCACHE ?= ON LLAMA_BUILD_TOOLS ?= ON JJML_FORCE_BUILD_LLAMA_GGML ?= OFF rebuild-force-tp: clean-local echo CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) $(CMAKE) -B $(BUILD_BASE) . \ -DJJML_FORCE_BUILD_TP=ON \ -DJJML_FORCE_BUILD_LLAMA_GGML=${JJML_FORCE_BUILD_LLAMA_GGML} \ -DA2_INSTALL_MODE=a2 \ -DJAVA_HOME=$(JAVA_HOME) \ \ -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ -DCMAKE_SKIP_BUILD_RPATH=ON \ -DGGML_CCACHE=$(GGML_CCACHE) \ \ -DLLAMA_BUILD_COMMON=${LLAMA_BUILD_TOOLS} \ -DLLAMA_BUILD_TOOLS=${LLAMA_BUILD_TOOLS} \ -DLLAMA_BUILD_EXAMPLES=OFF \ -DLLAMA_BUILD_TESTS=OFF \ \ -DGGML_NATIVE=OFF \ -DGGML_CPU_ALL_VARIANTS=ON \ -DGGML_BACKEND_DL=ON \ \ -DGGML_OPENMP=$(GGML_OPENMP) \ -DGGML_BLAS=$(GGML_BLAS) \ -DGGML_BLAS_VENDOR=OpenBLAS \ -DGGML_VULKAN=$(GGML_VULKAN) \ -DGGML_CUDA=$(GGML_CUDA) \ -DGGML_CUDA_FORCE_MMQ=ON \ -DGGML_CUDA_FA_ALL_QUANTS=OFF \ -DGGML_RPC=$(GGML_RPC) \ $(CMAKE) --build $(BUILD_BASE) --config $(CMAKE_BUILD_TYPE) -j $(shell nproc) ln -f -r -s $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)*$(shlib_suffix) $(TARGET_NATIVE_OUTPUT) ln -f -r -s $(TARGET_NATIVE_OUTPUT_JJML)/$(shlib_prefix)*$(shlib_suffix) $(TARGET_NATIVE_OUTPUT) @$(RM) $(TARGET_NATIVE_OUTPUT_GGML)/vulkan-shaders-gen* # Remove locally built libraries clean-local: $(RM) -r $(BUILD_BASE) @$(RM) -r $(TARGET_NATIVE_OUTPUT_GGML) @$(RM) -v $(TARGET_NATIVE_OUTPUT)/$(shlib_prefix)ggml*$(shlib_suffix) @$(RM) -v $(TARGET_NATIVE_OUTPUT)/$(shlib_prefix)llama*$(shlib_suffix) @$(RM) -v $(TARGET_NATIVE_OUTPUT)/$(shlib_prefix)Java_org_argeo_jjml_*$(shlib_suffix) @$(RM) -v $(TARGET_NATIVE_OUTPUT)/$(shlib_prefix)Java_org_argeo_jjml_*$(shlib_suffix).* # # DOC # doc-api: $(JAVA_HOME)/bin/javadoc -Xdoclint:none \ -d doc/reference/api \ -sourcepath org.argeo.jjml/src \ -subpackages org # # BUILD ENVIRONMENT # install-deps: ifeq ($(MSYS_VERSION),0) else pacman -S --needed git make mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-cmake pacman -S --needed mingw-w64-ucrt-x86_64-ccache # Vulkan pacman -S --needed mingw-w64-ucrt-x86_64-vulkan-devel mingw-w64-ucrt-x86_64-shaderc endif # # PACKAGING # ifneq ($(git_commit_count),) PACKAGE_VERSION=$(major).$(minor).$(micro).$(git_commit_count) else PACKAGE_VERSION=$(major).$(minor).$(micro)$(qualifier) endif JMOD_JJML=org.argeo.jjml JMOD_GGML=org.argeo.tp.ggml JMOD_GGML_LLM=org.argeo.tp.ggml.llm JJML_JMODS ?= $(JMOD_JJML),$(JMOD_GGML),$(JMOD_GGML_LLM) RT_JJML ?= rt-jjml RT_JJML_DIR = $(BUILD_BASE)/$(RT_JJML)-$(JLINK_JAVA_RELEASE)-$(JLINK_JVM_VARIANT)-$(TARGET_DEB_ARCH) RT_JJML_JMODS ?= java.base,jdk.compiler,jdk.jlink,jdk.jartool,jdk.jshell JDK_JJML ?= jdk-jjml-$(JLINK_JAVA_RELEASE)-$(JLINK_JVM_VARIANT) JDK_JJML_ARTIFACT = $(JDK_JJML)-$(TARGET_NATIVE_CATEGORY_PREFIX) JDK_JJML_DIR = $(BUILD_BASE)/$(JDK_JJML_ARTIFACT) standalone-release: clean-local $(CMAKE) -B $(BUILD_BASE) . \ -DJJML_FORCE_BUILD_TP=ON \ -DA2_INSTALL_MODE=a2 \ -DJAVA_HOME="$(JAVA_HOME)" \ \ -DGGML_CCACHE=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_SKIP_BUILD_RPATH=ON \ -DLLAMA_BUILD_COMMON=ON \ -DLLAMA_BUILD_TOOLS=ON \ -DLLAMA_CURL=OFF \ -DGGML_NATIVE=OFF \ -DGGML_CPU_ALL_VARIANTS=ON \ -DGGML_BACKEND_DL=ON $(CMAKE) --build $(BUILD_BASE) --config Release -j $(shell nproc) #MSVC_BUILD_TOOLS="C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools" #MSVC_ENV="/Common7/Tools/VsDevCmd.bat" ifneq (,$(VCIDEInstallDir)) MSVC_IDE_BASE=$(shell cygpath -m '$(VCIDEInstallDir)\\..') else MSVC_IDE_BASE=$(shell cygpath -m 'C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/Common7/IDE/') endif #MSVC_CMAKE_BASE="$(MSVC_BUILD_TOOLS)/Common7/IDE/CommonExtensions/Microsoft/CMake" MSVC_CMAKE_BASE=$(MSVC_IDE_BASE)CommonExtensions/Microsoft/CMake MSVC_CMAKE="$(MSVC_CMAKE_BASE)/CMake/bin/cmake.exe" msvc-release: $(MSVC_CMAKE) \ -B "$(BUILD_BASE)" \ -DJJML_FORCE_BUILD_TP=ON \ -DA2_INSTALL_MODE=a2 \ -DJAVA_HOME="$(JAVA_HOME)" \ \ -DCMAKE_LIBRARY_ARCHITECTURE=x86_64-win32-default \ -DLLAMA_BUILD_COMMON=ON \ -DLLAMA_BUILD_TOOLS=ON \ -DLLAMA_CURL=OFF \ -DGGML_NATIVE=OFF \ -DGGML_CPU_ALL_VARIANTS=ON \ -DGGML_OPENMP=ON \ -DGGML_BACKEND_DL=ON \ -DGGML_VULKAN=OFF \ "$(SDK_SRC_BASE)" $(MSVC_CMAKE) --build $(BUILD_BASE) --config Release -j $(shell nproc) jmod-jjml: $(RM) -r $(JMODS_BASE)/$(JMOD_JJML) mkdir -p $(JMODS_BASE)/$(JMOD_JJML)/lib mkdir -p $(JMODS_BASE)/$(JMOD_JJML)/legal $(COPY) COPYING.LESSER NOTICE $(JMODS_BASE)/$(JMOD_JJML)/legal $(COPY) $(TARGET_NATIVE_OUTPUT_JJML)/$(shlib_prefix)Java_org_argeo_jjml*$(shlib_suffix) $(JMODS_BASE)/$(JMOD_JJML)/lib $(RM) $(A2_JMODS)/$(JMOD_JJML).jmod $(JLINK_HOME)/bin/jmod create \ --class-path $(A2_OUTPUT)/org.argeo.jjml/org.argeo.jjml.$(major).$(minor).jar \ --module-version $(A2_LAYER_VERSION) \ --libs $(JMODS_BASE)/$(JMOD_JJML)/lib \ --legal-notices $(JMODS_BASE)/$(JMOD_JJML)/legal \ $(A2_JMODS)/$(JMOD_JJML).jmod # list content #$(JLINK_HOME)/bin/jmod list $(A2_JMODS)/$(JMOD_JJML).jmod jmod-ggml: $(RM) -r $(JMODS_BASE)/$(JMOD_GGML) mkdir -p $(JMODS_BASE)/$(JMOD_GGML)/java mkdir -p $(JMODS_BASE)/$(JMOD_GGML)/classes mkdir -p $(JMODS_BASE)/$(JMOD_GGML)/lib mkdir -p $(JMODS_BASE)/$(JMOD_GGML)/include mkdir -p $(JMODS_BASE)/$(JMOD_GGML)/legal $(COPY) native/tp/ggml/include/*.h $(JMODS_BASE)/$(JMOD_GGML)/include $(COPY) native/tp/ggml/LICENSE native/tp/ggml/AUTHORS $(JMODS_BASE)/$(JMOD_GGML)/legal $(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml$(shlib_suffix) $(JMODS_BASE)/$(JMOD_GGML)/lib $(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml-base$(shlib_suffix) $(JMODS_BASE)/$(JMOD_GGML)/lib $(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml-cpu-*$(shlib_suffix) $(JMODS_BASE)/$(JMOD_GGML)/lib #$(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml-vulkan$(shlib_suffix) $(JMODS_BASE)/$(JMOD_GGML)/lib # MSVC linker libs -$(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml.lib $(JMODS_BASE)/$(JMOD_GGML)/lib -$(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)ggml-base.lib $(JMODS_BASE)/$(JMOD_GGML)/lib echo "module $(JMOD_GGML) {}" > $(JMODS_BASE)/$(JMOD_GGML)/java/module-info.java $(JLINK_HOME)/bin/javac --release 11 -d $(JMODS_BASE)/$(JMOD_GGML)/classes $(JMODS_BASE)/$(JMOD_GGML)/java/module-info.java $(RM) $(A2_JMODS)/$(JMOD_GGML).jmod $(JLINK_HOME)/bin/jmod create \ --class-path $(JMODS_BASE)/$(JMOD_GGML)/classes \ --libs $(JMODS_BASE)/$(JMOD_GGML)/lib \ --header-files $(JMODS_BASE)/$(JMOD_GGML)/include \ --legal-notices $(JMODS_BASE)/$(JMOD_GGML)/legal \ $(A2_JMODS)/$(JMOD_GGML).jmod # list content $(JLINK_HOME)/bin/jmod list $(A2_JMODS)/$(JMOD_GGML).jmod jmod-ggml-llm: $(RM) -r $(JMODS_BASE)/$(JMOD_GGML_LLM) mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/java mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/classes mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/bin mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/lib mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/include mkdir -p $(JMODS_BASE)/$(JMOD_GGML_LLM)/legal $(COPY) native/tp/llama.cpp/include/*.h $(JMODS_BASE)/$(JMOD_GGML_LLM)/include $(COPY) native/tp/llama.cpp/LICENSE native/tp/llama.cpp/AUTHORS $(JMODS_BASE)/$(JMOD_GGML_LLM)/legal $(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)llama$(shlib_suffix) $(JMODS_BASE)/$(JMOD_GGML_LLM)/lib # MSVC linker libs -$(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/$(shlib_prefix)llama.lib $(JMODS_BASE)/$(JMOD_GGML_LLM)/lib #$(COPY) $(BUILD_BASE)/bin/llama-cli* $(JMODS_BASE)/$(JMOD_GGML_LLM)/bin $(COPY) $(TARGET_NATIVE_OUTPUT_GGML)/llama-cli* $(JMODS_BASE)/$(JMOD_GGML_LLM)/bin # TODO add requires to ggml echo "module $(JMOD_GGML_LLM) {}" > $(JMODS_BASE)/$(JMOD_GGML_LLM)/java/module-info.java $(JLINK_HOME)/bin/javac --release 11 -d $(JMODS_BASE)/$(JMOD_GGML_LLM)/classes $(JMODS_BASE)/$(JMOD_GGML_LLM)/java/module-info.java $(RM) $(A2_JMODS)/$(JMOD_GGML_LLM).jmod $(JLINK_HOME)/bin/jmod create \ --class-path $(JMODS_BASE)/$(JMOD_GGML_LLM)/classes \ --libs $(JMODS_BASE)/$(JMOD_GGML_LLM)/lib \ --cmds $(JMODS_BASE)/$(JMOD_GGML_LLM)/bin \ --header-files $(JMODS_BASE)/$(JMOD_GGML_LLM)/include \ --legal-notices $(JMODS_BASE)/$(JMOD_GGML_LLM)/legal \ $(A2_JMODS)/$(JMOD_GGML_LLM).jmod # list content $(JLINK_HOME)/bin/jmod list $(A2_JMODS)/$(JMOD_GGML_LLM).jmod # # DISTRIBUTABLE PACKAGES # rt-jjml: standalone-release jmod-os-libc jmod-jjml jmod-ggml jmod-ggml-llm $(RM) -r $(RT_JJML_DIR) $(JLINK_HOME)/bin/jlink \ --module-path "$(JLINK_JMODS)$(file_path_sep)$(A2_JMODS)" \ --add-modules $(RT_JJML_JMODS),$(JMOD_OS_LIBS),$(JJML_JMODS) \ --output "$(RT_JJML_DIR)" mkdir -p $(RT_JJML_DIR)/jmods $(COPY) $(A2_JMODS)/$(JMOD_JJML).jmod \ $(A2_JMODS)/$(JMOD_GGML)*.jmod \ $(A2_JMODS)/$(JMOD_OS_LIBS).jmod \ $(RT_JJML_DIR)/jmods package-jmods: jmod-os-libs jmod-jjml jmod-ggml jmod-ggml-llm jdk-jjml: package-jmods $(RM) -r $(JDK_JJML_DIR) $(JLINK_HOME)/bin/jlink \ --module-path "$(JLINK_JMODS)$(file_path_sep)$(A2_JMODS)" \ --add-modules $(JLINK_MODULES),$(JMOD_OS_LIBS),$(JJML_JMODS) \ --output "$(JDK_JJML_DIR)" mkdir -p $(JDK_JJML_DIR)/src cp $(JLINK_HOME)/lib/src.zip $(JDK_JJML_DIR)/lib mkdir -p $(JDK_JJML_DIR)/src cp -r org.argeo.jjml/src $(JDK_JJML_DIR)/src/org.argeo.jjml cd $(JDK_JJML_DIR)/src \ && zip -q -ur $(JDK_JJML_DIR)/lib/src.zip * $(RM) -r $(JDK_JJML_DIR)/src mkdir -p $(JDK_JJML_DIR)/jmods $(COPY) $(A2_JMODS)/$(JMOD_JJML).jmod \ $(A2_JMODS)/$(JMOD_GGML)*.jmod \ $(A2_JMODS)/$(JMOD_OS_LIBS).jmod \ $(JDK_JJML_DIR)/jmods mkdir -p $(JDK_JJML_DIR)/lib/a2/org.argeo.jjml $(COPY) $(A2_OUTPUT)/org.argeo.jjml/*.jar $(JDK_JJML_DIR)/lib/a2/org.argeo.jjml zip-jdk-jjml: jdk-jjml # create archive cd $(BUILD_BASE) && zip -r -q \ $(JDK_JJML_ARTIFACT)-$(PACKAGE_VERSION).zip \ $(shell basename $(JDK_JJML_DIR)) #rm -rf $(JDK_JJML_DIR) ifneq (,$(shell which $(JLINK_HOME)/bin/jpackage)) msi-jdk-jjml: jdk-jjml PATH=/usr/libexec/x86_64-win32-default/wix3:$(PATH) && \ $(JLINK_HOME)/bin/jpackage \ --runtime-image $(JDK_JJML_DIR) \ --type msi \ --name $(JDK_JJML) \ --app-version $(PACKAGE_VERSION) \ --dest $(BUILD_BASE) \ --description "JDK $(JLINK_JAVA_RELEASE) with additional machine learning features" \ --vendor "Argeo GmbH" \ --license-file "$(SDK_SRC_BASE)/NOTICE" \ --win-dir-chooser \ --win-per-user-install \ --win-upgrade-uuid $(shell uuidgen --sha1 --namespace $(ARGEO_ENTERPRISE_NUMBER_UUID) --name $(JDK_JJML)) \ --install-dir "$(JDK_JJML)" \ mv $(BUILD_BASE)/$(JDK_JJML)-$(PACKAGE_VERSION).msi \ $(BUILD_BASE)/$(JDK_JJML_ARTIFACT)-$(PACKAGE_VERSION).msi endif # Note: On Windows, use dumpbin.exe in order to find depedencies of a DLL # (similar to ldd on Linux). E.g. "C:\Program Files (x86)\Microsoft Visual # Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\ # dumpbin.exe" /DEPENDENTS llama.dll pax_global_header00006660000000000000000000000064150605266670014527gustar00rootroot0000000000000052 comment=4559bd35a7f219352bd458c84fa340eca403356c libjjml-java-1.1.13/native/include/argeo/jni/000077500000000000000000000000001506052666700207425ustar00rootroot00000000000000libjjml-java-1.1.13/native/include/argeo/jni/COPYING.LESSER000066400000000000000000000636421506052666700230040ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libjjml-java-1.1.13/native/include/argeo/jni/NOTICE000066400000000000000000000125731506052666700216560ustar00rootroot00000000000000Argeo JNI - C++ headers simplifying JNI development Copyright 2024-2025 Mathieu Baudier Copyright 2024-2025 Argeo GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . ## Third-party software licenses When the Program is distributed with third-party components as a Java runtime the legal details of each component can be found under the legal/ directory. ## Alternative licenses As an alternative, this Program is also provided to you under the terms and conditions of the Eclipse Public License version 2.0 or any later version. A copy of the Eclipse Public License version 2.0 is available at http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, version 2.0, or any later versions of that license, with additional EPL and JCR permissions (these additional permissions being detailed hereafter). ## Additional Permissions (when applicable) In case you decide to relicense this program to the GNU General Public License version 2.0, or any later version, the copyright holders of Argeo JJML give you the following additional permissions: # Eclipse Public License Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with any program released under the terms and conditions of the Eclipse Public License v1.0, v2.0 (even without a Secondary License enabled) or any later version of this license. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned, provided that you include the source code of that other code when and as the GNU GPL requires distribution of source code. Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. # Apache License Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with any program released under the terms and conditions of the Apache License v2.0 or any later version of this license. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned, provided that you include the source code of that other code when and as the GNU GPL requires distribution of source code. Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. # Java Content Repository API version 2.0 Permission Linking Argeo JJML statically or dynamically with other modules is making a combined work based on Argeo JJML. Thus, the terms and conditions of the GNU General Public License cover the whole combination when this license becomes applicable. In addition, as a special exception, the copyright holders of Argeo JJML give you permission to combine Argeo JJML with code included in the standard release of the JCR API version 2.0 (and this version only), licensed under the Day Specification License and the Day JCR License. You may copy and distribute such a system following the terms of the GNU GPL for Argeo JJML and the licenses of the other code concerned. Copies of the Day Specification License and the Day JCR License are expected to be available here: https://jackrabbit.apache.org/jcr/jcr.html Note that people who make modified versions of Argeo JJML are not obligated to grant this special exception for their modified versions; it is their choice whether to do so. The GNU General Public License gives permission to release a modified version without this exception; this exception also makes it possible to release a modified version which carries forward this exception. libjjml-java-1.1.13/native/include/argeo/jni/README.md000066400000000000000000000005341506052666700222230ustar00rootroot00000000000000# C++ headers simplifying Java Native Interface development ### Mostly inline methods, in order to declutter the C++ parts of JNI bindings. It is meant to be used as a git submodule and shipped with the code using it. It must be located in `/argeo/jni`. Typically: ``` git submodule add ../argeo-include-jni ./include/argeo/jni ``` libjjml-java-1.1.13/native/include/argeo/jni/argeo_jni.h000066400000000000000000000215211506052666700230510ustar00rootroot00000000000000/* Argeo JNI - C++ headers simplifying JNI development Copyright 2024-2025 Mathieu Baudier Copyright 2024-2025 Argeo GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . ## Alternative licenses As an alternative, this Program is also provided to you under the terms and conditions of the Eclipse Public License version 2.0 or any later version. A copy of the Eclipse Public License version 2.0 is available at http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, version 2.0, or any later versions of that license, with additional EPL and JCR permissions. */ #ifndef argeo_jni_h #define argeo_jni_h #include #include #include #include /* * PRE-PROCESSING */ //#define ARGEO_PERF 1 #ifdef ARGEO_PERF #include #include #include #define PERF_DURATION(_begin_) std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - _begin_).count() #define PERF_NS(_begin_, msg) "## ARGEO_PERF ## " << std::setfill(' ') << std::setw(16) << PERF_DURATION(_begin_) << " ns " << msg << std::endl #define PERF_BEGIN() auto _begin_ = std::chrono::high_resolution_clock::now() #define PERF_END(msg) std::cout << PERF_NS(_begin_, msg) #else #define PERF_DURATION(_begin_) #define PERF_NS(_begin_, msg) #define PERF_BEGIN() #define PERF_END(msg) #endif /* * NAMESPACE INDEPENDENT */ namespace argeo::jni { /* * JAVA MACROS */ // exceptions #define RuntimeException(env) env->FindClass("java/lang/RuntimeException") #define IllegalArgumentException(env) env->FindClass("java/lang/IllegalArgumentException") #define IndexOutOfBoundsException(env) env->FindClass("java/lang/IndexOutOfBoundsException") #define IllegalStateException(env) env->FindClass("java/lang/IllegalStateException") #define LongSupplier__getAsLong(env) env->GetMethodID( \ env->FindClass("java/util/function/LongSupplier"), "getAsLong", "()J") /* * EXCEPTION HANDLING */ /** Catches a standard C++ exception and throws an unchecked Java exception. * Does nothing if a Java execution is already pending * (that is, the already thrown java exception has priority). * A best effort is made to use an appropriate standard Java exception type. * Returns a nullptr as a convenience, * so that a call to it can be directly returned as the result of a (failed) non-void C++ function. * */ inline std::nullptr_t throw_to_java(JNIEnv *env, const std::exception &ex) { if (env->ExceptionCheck()) return nullptr; #ifdef __GNUC__ if (typeid(std::invalid_argument) == typeid(ex)) { env->ThrowNew(IllegalArgumentException(env), ex.what()); } else if (typeid(std::range_error) == typeid(ex)) { env->ThrowNew(IndexOutOfBoundsException(env), ex.what()); } else if (typeid(std::runtime_error) == typeid(ex)) { env->ThrowNew(IllegalStateException(env), ex.what()); } else { env->ThrowNew(RuntimeException(env), ex.what()); } #else env->ThrowNew(RuntimeException(env), ex.what()); #endif return nullptr; } /* * SAFE FIELDS AN METHODS ACCESSORS */ // ! We assume modified UTF-8 will work like C-strings /** The name of this Java class. */ inline std::string jclass_name(JNIEnv *env, jclass clazz) { // we first need to get the class descriptor as an object ... jclass clsObj = env->GetObjectClass(clazz); // ... in order to find the proper method ... jmethodID Class__getName = env->GetMethodID(clsObj, "getName", "()Ljava/lang/String;"); // ... to apply on the class passed as argument jstring name = (jstring) env->CallObjectMethod(clazz, Class__getName); jsize length = env->GetStringLength(name); std::string str; str.resize(length); env->GetStringUTFRegion(name, 0, length, str.data()); return str; } /** This Java field. */ inline jfieldID jfield_id(JNIEnv *env, jclass clazz, std::string name, std::string sig) { jfieldID res = env->GetFieldID(clazz, name.c_str(), sig.c_str()); if (res == NULL) throw std::invalid_argument( "Invalid field '" + name + "' with signature " + sig + " of class " + jclass_name(env, clazz)); return res; } /** This Java method. */ inline jmethodID jmethod_id(JNIEnv *env, jclass clazz, std::string name, std::string sig) { jmethodID res = env->GetMethodID(clazz, name.c_str(), sig.c_str()); if (res == NULL) throw std::invalid_argument( "Invalid method '" + name + "' with signature " + sig + " of class " + jclass_name(env, clazz)); return res; } /** This Java static method. */ inline jmethodID jmethod_id_static(JNIEnv *env, jclass clazz, std::string name, std::string sig) { jmethodID res = env->GetStaticMethodID(clazz, name.c_str(), sig.c_str()); if (res == NULL) throw std::invalid_argument( "Invalid static method '" + name + "' with signature " + sig + " of class " + jclass_name(env, clazz)); return res; } /** This Java class. */ inline jclass find_jclass(JNIEnv *env, std::string name) { jclass res = env->FindClass(name.c_str()); if (res == NULL) throw std::invalid_argument("Invalid class " + name); return res; } // // CALLBACKS // /** * A structure holding the reference required for executing callback in the Java code. */ typedef struct java_callback { jobject callback = nullptr; jmethodID method = nullptr; JavaVM *jvm = nullptr; } java_callback; /** * Return the JNI environment for the current thread or attach it if it was detached. * @return true if the current thread was detached before this call. */ inline bool load_thread_jnienv(JavaVM *jvm, JNIEnv **penv) { #ifdef __ANDROID__ jint status = jvm->GetEnv((void**)penv, JNI_VERSION_1_6); #else jint status = jvm->GetEnv((void**)penv, JNI_VERSION_10); #endif bool wasDetached = true; if (JNI_EVERSION == status) { // JNI version unsupported // TODO throw exception? wasDetached = true; } else if (JNI_EDETACHED == status) { wasDetached = true; #ifdef __ANDROID__ // AttachCurrentThreadAsDaemon method signature is different on Android jvm->AttachCurrentThreadAsDaemon(penv, nullptr); #else jvm->AttachCurrentThreadAsDaemon((void**)penv, nullptr); #endif } else { //std::assert(threadEnv != nullptr,""); wasDetached = false; } return wasDetached; } /** Calls the callback's method on the provided Java object.*/ inline jboolean exec_boolean_callback(java_callback *cb, ...) { JNIEnv *threadEnv; bool wasDetached = load_thread_jnienv(cb->jvm, &threadEnv); jobject obj = threadEnv->NewLocalRef(cb->callback); // process variable arguments va_list args; jboolean result; va_start(args, cb); result = threadEnv->CallBooleanMethodV(obj, cb->method, args); va_end(args); if (wasDetached) cb->jvm->DetachCurrentThread(); return result; } // // POINTERS // /** Cast a jlong to a pointer. */ template inline T as_pointer(jlong pointer) { static_assert(std::is_pointer::value); // Check the (unlikely) case where casting pointers as jlong would fail, // since it is relied upon in order to map Java and native structures // TODO provide alternative mechanism, such as a registry? static_assert(sizeof(T) <= sizeof(jlong)); if (pointer == 0) return nullptr; return reinterpret_cast(pointer); } /** Cast a java.util.function.LongSupplier to a native pointer. * * @param env the JNI environment * @param reference the Java object implementing java.util.function.LongSupplier * @return the native pointer */ template inline T as_pointer(JNIEnv *env, jobject reference) { if (reference == nullptr) return nullptr; jlong pointer = env->CallLongMethod(reference, LongSupplier__getAsLong(env)); return as_pointer(pointer); } // // STRINGS // /** Converts an array of bytes (typically UTF-8 encoded) to a C++ string.*/ inline std::string to_string(JNIEnv *env, jbyteArray str) { jsize length = env->GetArrayLength(str); std::string res(length, 0); env->GetByteArrayRegion(str, 0, length, (jbyte*) &res[0]); return res; } /** Converts an element of an array of arrays of bytes (typically UTF-8 encoded) to a C++ string.*/ inline std::string to_string(JNIEnv *env, jobjectArray strings, size_t index) { jbyteArray str = (jbyteArray) env->GetObjectArrayElement(strings, index); return argeo::jni::to_string(env, str); } } // namespace argeo::jni #endif libjjml-java-1.1.13/native/include/argeo/jni/argeo_jni_encoding.h000066400000000000000000000103171506052666700247200ustar00rootroot00000000000000/* Argeo JNI - C++ headers simplifying JNI development Copyright 2024-2025 Mathieu Baudier Copyright 2024-2025 Argeo GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . ## Alternative licenses As an alternative, this Program is also provided to you under the terms and conditions of the Eclipse Public License version 2.0 or any later version. A copy of the Eclipse Public License version 2.0 is available at http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, version 2.0, or any later versions of that license, with additional EPL and JCR permissions. */ #ifndef argeo_jni_encoding_h #define argeo_jni_encoding_h #include #include #include #include namespace argeo::jni { // TYPES /** Utility wrapper to adapt locale-bound facets for wstring/wbuffer convert * * @see https://en.cppreference.com/w/cpp/locale/codecvt */ // We need to use this wrapper in order to have an UTF-16 converter which can be instantiated. // see https://en.cppreference.com/w/cpp/locale/codecvt // TODO Make sure that there is no better way. template struct deletable_facet: Facet { template deletable_facet(Args &&... args) : Facet(std::forward(args)...) { } ~deletable_facet() { } }; /** A converter from std::string to std::u16string. * * Usage: * * std::string text = ... * std::u16string u16text = utf16_converter.from_bytes(text); * */ typedef std::wstring_convert< argeo::jni::deletable_facet>, char16_t> utf16_convert; /** Convenience method to make casting more readable in code.*/ inline std::u16string jchars_to_utf16(const jchar *jchars, const jsize length) { // sanity check static_assert(sizeof(char16_t) == sizeof(jchar)); const char16_t *u16chars = reinterpret_cast(jchars); std::u16string u16text(u16chars, static_cast(length)); return u16text; } /** Convert a jstring to an UTF-16 string. * Note that the characters are copied, * use jchars_to_utf16 in order to work directly * on the characters returned by GetString / GetStringCritical. */ inline std::u16string jstring_to_utf16(JNIEnv *env, jstring str) { jsize length = env->GetStringLength(str); jchar *buf = new jchar[length]; env->GetStringRegion(str, 0, length, buf); std::u16string res = argeo::jni::jchars_to_utf16(buf, length); delete[] buf; return res; } /** Convert a jstring to a string, via UTF-16. * Note that the characters are copied. */ inline std::string to_string(JNIEnv *env, jstring str, utf16_convert *utf16_converter) { std::u16string u16text = jstring_to_utf16(env, str); return utf16_converter->to_bytes(u16text); } /** Convenience method to make casting more readable in code.*/ inline const jchar* utf16_to_jchars(std::u16string u16text) { static_assert(sizeof(char16_t) == sizeof(jchar)); const jchar *res = reinterpret_cast(u16text.data()); return res; } /** UTF-16 string to Java string. No conversion is needed, as this is the default format.*/ inline jstring utf16_to_jstring(JNIEnv *env, std::u16string u16text) { return env->NewString(utf16_to_jchars(u16text), static_cast(u16text.size())); } /** Converts a string to a jstring. */ inline jstring to_jstring(JNIEnv *env, std::string str, utf16_convert *utf16_converter) { std::u16string u16text = utf16_converter->from_bytes(str); return utf16_to_jstring(env, u16text); } } // namespace argeo::jni #endif pax_global_header00006660000000000000000000000064151033723140014511gustar00rootroot0000000000000052 comment=51b6d5a78bc5bd3c25a5333b54c3418db1034608 libjjml-java-1.1.13/sdk/argeo-build/000077500000000000000000000000001510337231400171115ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/.classpath000066400000000000000000000007141510337231400210760ustar00rootroot00000000000000 libjjml-java-1.1.13/sdk/argeo-build/.gitignore000066400000000000000000000000471510337231400211020ustar00rootroot00000000000000.project *.class /bin/ /sdk.mk /output libjjml-java-1.1.13/sdk/argeo-build/.project.COPYME000066400000000000000000000012041510337231400215500ustar00rootroot00000000000000 argeo-build org.eclipse.jdt.core.javabuilder org.eclipse.pde.ManifestBuilder org.eclipse.pde.SchemaBuilder org.eclipse.pde.PluginNature org.eclipse.jdt.core.javanature libjjml-java-1.1.13/sdk/argeo-build/.settings/000077500000000000000000000000001510337231400210275ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/.settings/org.eclipse.core.resources.prefs000066400000000000000000000000671510337231400272450ustar00rootroot00000000000000eclipse.preferences.version=1 encoding/=UTF-8 libjjml-java-1.1.13/sdk/argeo-build/.settings/org.eclipse.jdt.core.prefs000066400000000000000000000200521510337231400260100ustar00rootroot00000000000000eclipse.preferences.version=1 org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning org.eclipse.jdt.core.compiler.problem.deadCode=warning org.eclipse.jdt.core.compiler.problem.deprecation=warning org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled org.eclipse.jdt.core.compiler.problem.discouragedReference=warning org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=warning org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedImport=warning org.eclipse.jdt.core.compiler.problem.unusedLabel=warning org.eclipse.jdt.core.compiler.problem.unusedLocal=warning org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.release=enabled org.eclipse.jdt.core.compiler.source=17 libjjml-java-1.1.13/sdk/argeo-build/LICENSE000066400000000000000000000156071510337231400201270ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.libjjml-java-1.1.13/sdk/argeo-build/META-INF/000077500000000000000000000000001510337231400202515ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/META-INF/.gitignore000066400000000000000000000000151510337231400222350ustar00rootroot00000000000000/MANIFEST.MF libjjml-java-1.1.13/sdk/argeo-build/Makefile000066400000000000000000000010101510337231400205410ustar00rootroot00000000000000# WARNING: this Makefile is for Argeo Build to build itself # and is not meant to be included or used in regular builds include sdk.mk # Tells Make.java that we are our own Argeo Build export ARGEO_BUILD_CONFIG := . A2_CATEGORY = org.argeo.build BUNDLES = \ org.argeo.build \ DEP_CATEGORIES = \ log/syslogger/org.argeo.tp \ org.argeo.tp.build \ all: osgi # copy generated MANIFEST cp org.argeo.build/META-INF/MANIFEST.MF META-INF/MANIFEST.MF rm -rf org.argeo.build clean: rm -rf $(BUILD_BASE) include osgi.mk libjjml-java-1.1.13/sdk/argeo-build/NOTICE000066400000000000000000000010371510337231400200160ustar00rootroot00000000000000Argeo Build - Minimalistic build system written in Java Copyright 2022-2023 Mathieu Baudier Copyright 2022-2023 Argeo GmbH To the extent possible under law, the copyright owners waived all copyright and related or neighboring rights to this program. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the CC0 version 1.0 license for more details: https://creativecommons.org/publicdomain/zero/1.0/legalcodelibjjml-java-1.1.13/sdk/argeo-build/README000066400000000000000000000027271510337231400200010ustar00rootroot00000000000000Argeo Build is a minimalistic Java build system based on GNU make, which is meant to be used as a git submodule of a software layer following Argeo's conventions. It is using Java files directly as scripts, without prior compilation. It is NOT meant as a generic Java build system. ## Components Argeo Build depends on the Eclipse ECJ Java compiler, and on the BND Tools library for OSGi metadata generation (and therefore on SLF4j). - osgi.mk is included in the root Makefile of the layer being built - configure configures a build environment - Make.java compiles Java code, creates OSGi bundles based on the bnd.bnd file in each project - Repackage.java downloads and repackages as OSGi bundles Maven artifacts or Eclipse releases (cf. Argeo TP) ## Usage For example usage, look at one of the core Argeo layers (typically Argeo Commons) on http://git.argeo.org : git clone http://git.argeo.org/lgpl/argeo-commons.git --recursive export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/ ./argeo-commons/configure cd argeo-commons make clean all Argeo Build is installed as a git submodule under ./sdk/argeo-build/ In order to configure a new project, copy the file configure.template from this directory as configure at the root of the project, and make it executable: chmod +x configure git --chmod +x configure ## Licensing This code is in the public domain under the CC0 v1.0 license, so that it can be used in any licensing context by Argeo or anyone else.libjjml-java-1.1.13/sdk/argeo-build/argeo.bnd000066400000000000000000000005521510337231400206750ustar00rootroot00000000000000Bundle-Version: ${major}.${minor}.${micro}${qualifier} Export-Package: !*.internal.*, !config.*, !icons.*, !css.*, !swt.*, !rap.*, * Bundle-RequiredExecutionEnvironment=JavaSE-21 #-consumer-policy : ${range;[==,=+)} -contract: !JavaServlet,* -removeheaders = Bnd-LastModified,Build-Jdk,Built-By,Tool,Created-By Automatic-Module-Name: ${bsn} #-jpms-module-info: libjjml-java-1.1.13/sdk/argeo-build/branch.mk000066400000000000000000000002061510337231400206750ustar00rootroot00000000000000# WARNING: this Makefile is for Argeo Build to build itself # and is not meant to be included or used in regular builds BRANCH=testinglibjjml-java-1.1.13/sdk/argeo-build/build.properties000066400000000000000000000001561510337231400223300ustar00rootroot00000000000000additional.bundles = org.argeo.tp.syslogger bin.includes = META-INF/,\ src/org/ source.. = src libjjml-java-1.1.13/sdk/argeo-build/cmake/000077500000000000000000000000001510337231400201715ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/cmake/FindArgeoBuild.cmake000066400000000000000000000024141510337231400240120ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.11) # UseJava features cmake_minimum_required(VERSION 3.12) # CONFIGURE_DEPENDS in GLOB cmake_minimum_required(VERSION 3.20) # DESTINATION in JNI GENERATE_NATIVE_HEADERS if(NOT A2_CATEGORY) message(FATAL_ERROR "Variable A2_CATEGORY must be set") endif() if(NOT A2_JAVA_RELEASE) set(A2_JAVA_RELEASE 21) endif() if(NOT A2_CXX_STD) set(A2_CXX_STD cxx_std_17) endif() message(STATUS "A2_JAVA_RELEASE=${A2_JAVA_RELEASE}") if(NOT JAVA_HOME) if(NOT ANDROID) message(FATAL_ERROR "JAVA_HOME must be explicitly set") endif() endif() # Java find_package(Java ${A2_JAVA_RELEASE} REQUIRED COMPONENTS Development) include(UseJava) set(CMAKE_JAVA_COMPILE_FLAGS "--release" "${A2_JAVA_RELEASE}") if (Java_FOUND) message(STATUS "Java_JAVA_EXECUTABLE=${Java_JAVA_EXECUTABLE}") message(STATUS "Java_VERSION_MAJOR=${Java_VERSION_MAJOR}") endif() # JNI if(ANDROID) set(JAVA_AWT_LIBRARY NotNeeded) set(JAVA_JVM_LIBRARY NotNeeded) set(JAVA_INCLUDE_PATH2 NotNeeded) set(JAVA_AWT_INCLUDE_PATH NotNeeded) endif() # ANDROID find_package(JNI REQUIRED) if (JNI_FOUND) message(STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}") message(STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}") endif() # Actual logic include(argeo-build) set(ArgeoBuild_FOUND 1) message(STATUS "Argeo Build configured") libjjml-java-1.1.13/sdk/argeo-build/cmake/argeo-build-setup.cmake000066400000000000000000000110161510337231400245220ustar00rootroot00000000000000# Git find_package(Git) # # UTILITIES # ## Read simple equals-separated key/value properties ## function(a2_read_properties PATH PREFIX) # from https://stackoverflow.com/a/17168870 file(STRINGS ${PATH} ConfigContents) foreach(NameAndValue ${ConfigContents}) string(REGEX REPLACE "^[ ]+" "" NameAndValue ${NameAndValue}) string(REGEX MATCH "^[^=]+" Name ${NameAndValue}) string(REPLACE "${Name}=" "" Value ${NameAndValue}) set(${PREFIX}${Name} "${Value}" CACHE INTERNAL ${PREFIX}${Name}) endforeach() endfunction() # read_properties ## Replace .next qualifier ## function(a2_replace_next_qualifier) if(A2_qualifier STREQUAL ".next") if (Git_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-list --count ${A2_major}.${A2_minor}.${A2_micro}..HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GitRevCount OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT "${GitRevCount}" STREQUAL "") # will also be empty if not a working copy execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GitShortHash OUTPUT_STRIP_TRAILING_WHITESPACE) if(GitRevCount LESS 10) set(GitRevCountPadded "000${GitRevCount}") elseif(GitRevCount LESS 100) set(GitRevCountPadded "00${GitRevCount}") elseif(GitRevCount LESS 1000) set(GitRevCountPadded "0${GitRevCount}") else() set(GitRevCountPadded "${GitRevCount}") endif() # GitRevCountPadded set(A2_qualifier ".${GitRevCountPadded}-${GitShortHash}" CACHE INTERNAL A2_qualifier) endif() # GitRevCount endif() # Git_FOUND endif() # A2_qualifier endfunction() # a2_replace_next_qualifier # Normalize MinGW paths if(MINGW) file(REAL_PATH ".." SDK_BUILD_BASE_WIN BASE_DIRECTORY "${CMAKE_BINARY_DIR}") execute_process(COMMAND cygpath -u ${SDK_BUILD_BASE_WIN} OUTPUT_VARIABLE SDK_BUILD_BASE OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND cygpath -u ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE SDK_SRC_BASE OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND cygpath -u ${JAVA_HOME} OUTPUT_VARIABLE SDK_JAVA_HOME OUTPUT_STRIP_TRAILING_WHITESPACE) else() file(REAL_PATH ".." SDK_BUILD_BASE BASE_DIRECTORY "${CMAKE_BINARY_DIR}") set(SDK_SRC_BASE ${CMAKE_SOURCE_DIR}) set(SDK_JAVA_HOME ${JAVA_HOME}) endif() # Generate sdk.mk file for Argeo Build Make compatibility # TODO make it more robust file(WRITE ${CMAKE_SOURCE_DIR}/sdk.mk "SDK_SRC_BASE := ${SDK_SRC_BASE}\n") file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "SDK_BUILD_BASE ?= ${SDK_BUILD_BASE}\n") file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "JAVA_HOME ?= ${SDK_JAVA_HOME}\n") file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "\n") file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "-include branch.mk\n") file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "-include sdk/branches/$(BRANCH).bnd\n") if(MINGW) file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "export SDK_BUILD_BASE_WIN=${SDK_BUILD_BASE_WIN}\n") elseif(MSVC) file(APPEND ${CMAKE_SOURCE_DIR}/sdk.mk "export SDK_BUILD_BASE_WIN=${SDK_BUILD_BASE_WIN}\n") endif() # Compute version a2_read_properties(${CMAKE_SOURCE_DIR}/branch.mk "A2_") a2_read_properties(${CMAKE_SOURCE_DIR}/sdk/branches/${A2_BRANCH}.bnd "A2_") a2_replace_next_qualifier() set(A2_LAYER_VERSION "${A2_major}.${A2_minor}.${A2_micro}${A2_qualifier}") message(STATUS "Branch: ${A2_BRANCH} - Version: ${A2_LAYER_VERSION}") if("${A2_qualifier}" STREQUAL "") set(A2_RELEASING ON) message(STATUS "Releasing A2 layer") else() set(A2_RELEASING OFF) endif() if(NOT A2_OUTPUT) set(A2_OUTPUT ${SDK_BUILD_BASE}/a2) endif() message(STATUS "A2_OUTPUT=${A2_OUTPUT}") if(NOT A2_SRC_OUTPUT) set(A2_SRC_OUTPUT ${SDK_BUILD_BASE}/a2.src) endif() if(NOT A2_BASE) set(A2_BASE ${A2_OUTPUT}) endif() message(STATUS "A2_BASE=${A2_BASE}") if(NOT A2_INSTALL_MODE) set(A2_INSTALL_MODE default) endif() message(STATUS "A2_INSTALL_MODE=${A2_INSTALL_MODE}") # # OS SPECIFIC # # Use GNU conventions include(GNUInstallDirs) # supported OSes if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(A2_TARGET_OS "linux") set(A2_TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) set(A2_TARGET_CLIB "gnu") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(A2_TARGET_OS "macosx") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(A2_TARGET_OS "win32") if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") set(A2_TARGET_ARCH "x86_64") endif() # CMAKE_SYSTEM_PROCESSOR endif() # CMAKE_SYSTEM_NAME if(MINGW) set(CMAKE_SHARED_LIBRARY_PREFIX "") endif() # defaults if(NOT A2_TARGET_OS) set(A2_TARGET_OS ${CMAKE_SYSTEM_NAME}) endif() if(NOT A2_TARGET_ARCH) set(A2_TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) endif() if(NOT A2_TARGET_CLIB) set(A2_TARGET_CLIB "default") endif() libjjml-java-1.1.13/sdk/argeo-build/cmake/argeo-build.cmake000066400000000000000000000204331510337231400233670ustar00rootroot00000000000000# A2 setup include(argeo-build-setup) set(TARGET_NATIVE_CATEGORY_PREFIX "${A2_TARGET_ARCH}-${A2_TARGET_OS}-${A2_TARGET_CLIB}") message(STATUS "TARGET_NATIVE_CATEGORY_PREFIX=${TARGET_NATIVE_CATEGORY_PREFIX}") # Virtual target for all includes add_library(${A2_CATEGORY}-includes INTERFACE) message(STATUS "INCLUDES=${A2_CATEGORY}-includes") # # UTILITIES # ## Generates MANIFEST for a bundle ## function(a2_osgi_manifest BUNDLE) set(MF ${BUNDLE}/META-INF/MANIFEST.MF) file(WRITE ${MF} "") # clear file(APPEND ${MF} "Manifest-Version: 1.0\nBundle-ManifestVersion: 2\n") # standard file(APPEND ${MF} "Bundle-SymbolicName: ${BUNDLE}\n") file(APPEND ${MF} "Bundle-Version: ${A2_LAYER_VERSION}\n") file(APPEND ${MF} "Bundle-RequiredExecutionEnvironment: JavaSE-${A2_JAVA_RELEASE}\n") # exported packages, based on module-info.java if(EXISTS ${PROJECT_SOURCE_DIR}/${BUNDLE}/src/module-info.java) file (STRINGS ${PROJECT_SOURCE_DIR}/${BUNDLE}/src/module-info.java LINES REGEX "exports .*;") foreach(LINE IN LISTS LINES) string(REPLACE "exports" "" STRIPPED ${LINE}) string(STRIP ${STRIPPED} PCK) list(APPEND EXPORTED_PKGS ${PCK}) endforeach() list(LENGTH EXPORTED_PKGS EXPORTED_PKGS_N) if(${EXPORTED_PKGS_N} GREATER 0) string(REPLACE ";" ",\n " EXPORT_PACKAGE "${EXPORTED_PKGS}") file(APPEND ${MF} "Export-Package: ${EXPORT_PACKAGE}\n") endif() # export packages length else() file(APPEND ${MF} "Automatic-Module-Name: ${BUNDLE}\n") endif() # module-info.java exists # Additional hardcoded directives in bnd.bnd if(EXISTS ${PROJECT_SOURCE_DIR}/${BUNDLE}/append.MF) file(READ ${PROJECT_SOURCE_DIR}/${BUNDLE}/append.MF CONTENT) file(APPEND ${MF} "${CONTENT}") endif() # append.MF exists message(STATUS "Wrote OSGi manifest to ${MF}") endfunction() # a2_osgi_manifest ## Generates Eclipse source bundle MANIFEST ## function(a2_osgi_manifest_src BUNDLE) set(MF ${BUNDLE}/META-INF/MANIFEST.src.MF) file(WRITE ${MF} "") # clear file(APPEND ${MF} "Manifest-Version: 1.0\nBundle-ManifestVersion: 2\n") # standard file(APPEND ${MF} "Bundle-SymbolicName: ${BUNDLE}.src\n") file(APPEND ${MF} "Bundle-Version: ${A2_LAYER_VERSION}\n") file(APPEND ${MF} "Eclipse-SourceBundle: ${BUNDLE};version=${A2_LAYER_VERSION}\n") message(STATUS "Wrote Eclipse source manifest to ${MF}") endfunction() # a2_osgi_manifest_src ## Set result with the list of sources in add_jar RESOURCES format ## macro(a2_add_sources_as_resources result curdir prefix) file(GLOB_RECURSE children LIST_DIRECTORIES false RELATIVE "${curdir}" "${curdir}/*") set(packages "") foreach(child ${children}) cmake_path(GET child PARENT_PATH dir) # add '/' so that root with module-info.java is considered list(APPEND packages "/${dir}") endforeach() list(REMOVE_DUPLICATES packages) set(resources "") foreach(package ${packages}) list(APPEND resources "NAMESPACE") list(APPEND resources "${prefix}${package}") file(GLOB files LIST_DIRECTORIES false "${curdir}${package}/*") foreach(file ${files}) list(APPEND resources "${file}") endforeach() # files endforeach() # packages set(${result} ${resources}) endmacro() # a2_add_sources_as_resources # # JAVA BUILD # ## Build a bundle ## function(a2_build_bundle BUNDLE) a2_osgi_manifest(${BUNDLE}) file(GLOB_RECURSE JAVA_SRC CONFIGURE_DEPENDS "${BUNDLE}/src/*.java") # sources as resources string(TOLOWER "$ENV{SOURCE_BUNDLES}" check_source_bundles) if(NOT "${check_source_bundles}" STREQUAL "true") a2_add_sources_as_resources(SOURCES_AS_RESOURCES "${CMAKE_SOURCE_DIR}/${BUNDLE}/src" "OSGI-INF/src") else() # separate source bundles a2_add_sources_as_resources(SRC_AS_RESOURCES "${CMAKE_SOURCE_DIR}/${BUNDLE}/src" ".") a2_osgi_manifest_src(${BUNDLE}) add_jar(${BUNDLE}.src SOURCES RESOURCES ${SRC_AS_RESOURCES} MANIFEST ${BUNDLE}/META-INF/MANIFEST.src.MF OUTPUT_NAME ${BUNDLE}.${A2_major}.${A2_minor}.src OUTPUT_DIR ${A2_SRC_OUTPUT}/${A2_CATEGORY} ) endif() # check_source_bundles # required modules file (STRINGS ${BUNDLE}/src/module-info.java REQUIRES_LINES REGEX "requires transitive .*;") foreach(LINE IN LISTS REQUIRES_LINES) string(REPLACE "requires transitive" "" STRIPPED ${LINE}) string(STRIP ${STRIPPED} MODULE) list(APPEND REQUIRED_MODULES ${MODULE}) endforeach() message(STATUS "REQUIRED_MODULES=${REQUIRED_MODULES}") list(LENGTH REQUIRED_MODULES REQUIRED_MODULES_N) if(${REQUIRED_MODULES_N} GREATER 0) string(REPLACE ";" "," REQUIRED_MODULES_STR "${REQUIRED_MODULES}") list(APPEND ADD_MODULES "--add-modules") list(APPEND ADD_MODULES ${REQUIRED_MODULES_STR}) endif() # export packages length # classpath set(CLASSPATH "") cmake_path(APPEND MODULEPATH_DIRS ${A2_OUTPUT}/${A2_CATEGORY}) foreach(CATEGORY IN LISTS DEP_CATEGORIES) message(STATUS "CLASSPATH += ${A2_BASE}/${CATEGORY}/*.jar") file(GLOB JARS CONFIGURE_DEPENDS "${A2_BASE}/${CATEGORY}/*.jar") list(APPEND CLASSPATH ${JARS}) cmake_path(APPEND MODULEPATH_DIRS ${A2_BASE}/${CATEGORY}) endforeach() cmake_path(CONVERT ${MODULEPATH_DIRS} TO_NATIVE_PATH_LIST MODULEPATH) message(STATUS "MODULEPATH=${MODULEPATH}") if(${A2_INSTALL_MODE} STREQUAL "a2") set(BUNDLE_OUTPUT_NAME ${BUNDLE}.${A2_major}.${A2_minor}) set(BUNDLE_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/a2/${A2_CATEGORY}) else() # default is the Debian way set(BUNDLE_OUTPUT_NAME ${BUNDLE}-${A2_LAYER_VERSION}) set(BUNDLE_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/java) endif() set(CMAKE_JAVA_COMPILE_FLAGS --release ${A2_JAVA_RELEASE} --module-path ${MODULEPATH} ${ADD_MODULES}) add_jar(${BUNDLE} SOURCES ${JAVA_SRC} RESOURCES ${SOURCES_AS_RESOURCES} INCLUDE_JARS ${CLASSPATH} MANIFEST ${BUNDLE}/META-INF/MANIFEST.MF OUTPUT_NAME ${BUNDLE_OUTPUT_NAME} OUTPUT_DIR ${A2_OUTPUT}/${A2_CATEGORY} GENERATE_NATIVE_HEADERS ${BUNDLE}-include DESTINATION ${CMAKE_SOURCE_DIR}/native/include/${A2_CATEGORY} ) # Modules as CMake dependencies # TODO virtual dependencies for java. modules and external modules # TODO generate OSGi metadata too ? # foreach(MODULE IN LISTS REQUIRED_MODULES) # add_dependencies(${BUNDLE} ${MODULE}) # endforeach() # JNI includes add_dependencies(${A2_CATEGORY}-includes ${BUNDLE}-include) install_jar(${BUNDLE} ${BUNDLE_INSTALL_DIR}) endfunction() # a2_build_bundle ## Build a list of bundles ## macro(a2_build_bundles BUNDLES) message(STATUS "DEP_CATEGORIES=${DEP_CATEGORIES}") foreach(BUNDLE IN LISTS BUNDLES) a2_build_bundle(${BUNDLE}) endforeach() # BUNDLES endmacro() # a2_build_bundles # # NATIVE BUILD # ## Configure a JNI target according to A2 conventions ## macro(a2_jni_target TARGET) # JNI target_include_directories(${TARGET} PRIVATE ${JNI_INCLUDE_DIRS}) # local includes (possibly git submodules) target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}/native/include/) # generated include files add_dependencies(${TARGET} ${A2_CATEGORY}-includes) target_include_directories(${TARGET} PRIVATE ${CMAKE_SOURCE_DIR}/native/include/${A2_CATEGORY}) set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE ON SOVERSION ${A2_major}.${A2_minor} ) if(A2_RELEASING) set_target_properties(${TARGET} PROPERTIES VERSION ${A2_LAYER_VERSION} ) endif() target_compile_features(${TARGET} PRIVATE ${A2_CXX_STD}) # TODO simplify/factorize this if(MINGW) # bin is used as output directory in MSYS set_target_properties(${TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/${A2_CATEGORY}") set_target_properties(${TARGET} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/${A2_CATEGORY}") elseif(MSVC) # bin is used as output directory in MSVC set_target_properties(${TARGET} PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<1:${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/${A2_CATEGORY}>) set_target_properties(${TARGET} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY $<1:${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/${A2_CATEGORY}>) else() set_target_properties(${TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${A2_OUTPUT}/lib/${TARGET_NATIVE_CATEGORY_PREFIX}/${A2_CATEGORY}") endif() # MINGW if(${A2_INSTALL_MODE} STREQUAL "a2") install(TARGETS ${TARGET} LIBRARY DESTINATION lib/${CMAKE_LIBRARY_ARCHITECTURE}) else() # default is the Debian way install(TARGETS ${TARGET} LIBRARY DESTINATION lib/${CMAKE_LIBRARY_ARCHITECTURE}/jni) endif() endmacro() libjjml-java-1.1.13/sdk/argeo-build/cmake/configure000066400000000000000000000025371510337231400221040ustar00rootroot00000000000000#!/bin/sh # We build where we are SDK_BUILD_BASE=$(pwd -P)/output if [ -z "$SDK_SRC_BASE" ] then echo Script variable SDK_SRC_BASE must be set in the calling \'configure\' script, echo to the root location of the sources, typically with such a pattern: echo 'SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)"' echo "(see 'configure.template' from the argeo-build/cmake directory)" exit 1 fi SDK_MK=$SDK_SRC_BASE/sdk.mk if [ -f "$SDK_MK" ]; then echo "File $SDK_MK already exists. Remove it in order to configure a new build location:" echo "rm $SDK_MK" exit 1 else if [ -z "$JAVA_HOME" ] then JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java)))) echo "Environment variable JAVA_HOME not set, using $JAVA_HOME of $(which java)" fi # Create build directory, so that it can be used right away # and we check whether we have the rights mkdir -p $SDK_BUILD_BASE if [ ! -d "$SDK_BUILD_BASE" ]; then echo "Cannot create $SDK_BUILD_BASE, SDK configuration has failed." exit 2 fi if [ -z "$CMAKE_BUILD_TYPE" ] then CMAKE_BUILD_TYPE=Release fi (cd $SDK_SRC_BASE && \ cmake -B $SDK_BUILD_BASE/$(basename $SDK_SRC_BASE) \ -DA2_INSTALL_MODE=a2 \ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ -DJAVA_HOME=$JAVA_HOME ) echo SDK was configured with CMake. echo "JAVA_HOME : $JAVA_HOME" echo "Base for sources : $SDK_SRC_BASE" echo "Base for builds : $SDK_BUILD_BASE" fi libjjml-java-1.1.13/sdk/argeo-build/cmake/configure.template000066400000000000000000000002631510337231400237100ustar00rootroot00000000000000#!/bin/sh # Source are located where this script is SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)" # Source the configure script . $SDK_SRC_BASE/sdk/argeo-build/cmake/configure libjjml-java-1.1.13/sdk/argeo-build/cmake/default.mk000066400000000000000000000021531510337231400221470ustar00rootroot00000000000000ARGEO_BUILD_BASE := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))..) CMAKE = cmake ifeq ($(SDK_SRC_BASE),) SDK_SRC_BASE=$(abspath $(ARGEO_BUILD_BASE)/../..) SDK_BUILD_BASE=$(abspath $(SDK_SRC_BASE)/../output) A2_OUTPUT=$(abspath $(SDK_BUILD_BASE)/a2) endif BUILD_BASE=$(abspath $(SDK_BUILD_BASE)/$(notdir $(SDK_SRC_BASE))) # common Makefile path include $(dir $(lastword $(MAKEFILE_LIST)))../common.mk export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 # Required on Windows CMAKE_BUILD_TYPE ?= Release all: cmake -B $(BUILD_BASE) . \ -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ -DA2_INSTALL_MODE=a2 \ -DJAVA_HOME=$(JAVA_HOME) $(CMAKE) --build $(BUILD_BASE) --config $(CMAKE_BUILD_TYPE) -j $(shell nproc) clean: -if [ -d $(BUILD_BASE) ]; then $(CMAKE) --build $(BUILD_BASE) --target clean; fi; distclean: $(RM) -r $(BUILD_BASE) $(RM) sdk.mk install: $(CMAKE) --build $(BUILD_BASE) --target install describe: echo SDK_SRC_BASE=$(SDK_SRC_BASE) echo SDK_BUILD_BASE=$(SDK_BUILD_BASE) echo BUILD_BASE=$(BUILD_BASE) echo JAVA_HOME=$(JAVA_HOME) echo A2_OUTPUT=$(A2_OUTPUT) .PHONY: all clean distclean install describe libjjml-java-1.1.13/sdk/argeo-build/common.mk000066400000000000000000000164201510337231400207350ustar00rootroot00000000000000# # GLOBAL REFERENCES # # see https://www.iana.org/assignments/enterprise-numbers/?q=48308 ARGEO_ENTERPRISE_NUMBER_OID=1.3.6.1.4.1.48308 # see https://www.rfc-editor.org/rfc/rfc9562 # uuidgen --sha1 --namespace 6ba7b812-9dad-11d1-80b4-00c04fd430c8 --name 1.3.6.1.4.1.48308 ARGEO_ENTERPRISE_NUMBER_UUID=58873947-460c-59a6-a7b4-28a97def5f27 # # VERSIONING # build-major=2 build-minor=1 # Third party libraries required by Argeo Build ECJ_MAJOR=3 BNDLIB_BRANCH=7.0 SYSLOGGER_BRANCH=$(build-major).$(build-minor) # Layer version ifeq ($(qualifier),.next) ifneq ($(shell which git),) ifeq ($(shell git rev-parse --is-inside-work-tree),true) git_commit_count=$(shell git rev-list --count $(major).$(minor).$(micro)..HEAD) git_head_shorthash=$(shell git rev-parse --short=7 HEAD) ifneq ($(git_commit_count),) undefine qualifier qualifier=.$(shell printf "%04d" $(git_commit_count))-$(git_head_shorthash) endif endif endif endif A2_LAYER_VERSION=${major}.${minor}.${micro}$(qualifier) # The following variables are found in the sdk.mk file which is generated by the configure script: # SDK_SRC_BASE the base of the source code, typically the root of the cloned git repository # SDK_BUILD_BASE the base of the output # JAVA_HOME the base of the JDK used to build A2_OUTPUT ?= $(SDK_BUILD_BASE)/a2 JVM ?= "$(JAVA_HOME)/bin/java" JAVADOC ?= "$(JAVA_HOME)/bin/javadoc" # GNU defaults prefix ?= /usr/local datarootdir ?= $(prefix)/share exec_prefix ?= $(prefix) libdir ?= $(exec_prefix)/lib A2_INSTALL_TARGET ?= $(DESTDIR)$(datarootdir)/a2 # The following variables have default values which can be overriden # A2_BASE the space-separated directories where already built a2 categories can be found A2_BASE ?=$(call uniq, $(A2_OUTPUT) $(A2_INSTALL_TARGET) $(A2_NATIVE_INSTALL_TARGET) /usr/local/share/a2 /usr/local/lib/a2 /usr/share/a2 /usr/lib/a2) # OS-specific KNOWN_ARCHS ?= x86_64 aarch64 armv7l MSYS_VERSION := $(if $(findstring Msys, $(shell uname -o)),$(word 1, $(subst ., ,$(shell uname -r))),0) ifeq ($(MSYS_VERSION),0) TARGET_OS ?= linux TARGET_ARCH ?= $(shell uname -m) TARGET_LIBC ?= gnu shlib_prefix=lib shlib_suffix=.so file_sep=/ file_path_sep=: else TARGET_OS ?= win32 TARGET_ARCH ?= $(shell uname -m) TARGET_LIBC ?= default shlib_prefix= shlib_suffix=.dll file_sep=\\ file_path_sep=; endif ifeq ("$(TARGET_ARCH)","aarch64") TARGET_DEB_ARCH=arm64 else # we only support two architectures TARGET_DEB_ARCH=amd64 endif #TARGET_OS_CATEGORY_PREFIX=lib/$(TARGET_OS) LOCAL_NATIVE_CATEGORY_PREFIX=$(shell uname -m)-$(TARGET_OS)-$(TARGET_LIBC) TARGET_NATIVE_CATEGORY_PREFIX=$(TARGET_ARCH)-$(TARGET_OS)-$(TARGET_LIBC) A2_NATIVE_OUTPUT=$(A2_OUTPUT)/lib TARGET_NATIVE_OUTPUT=$(A2_NATIVE_OUTPUT)/$(TARGET_NATIVE_CATEGORY_PREFIX) TARGET_NATIVE_CATEGORY=$(TARGET_NATIVE_OUTPUT)/$(A2_CATEGORY) A2_NATIVE_INSTALL_TARGET ?= $(DESTDIR)$(libdir)/${TARGET_NATIVE_CATEGORY_PREFIX} PORTABLE_CATEGORIES=$(filter-out lib/% os/%, $(CATEGORIES)) NATIVE_CATEGORIES=$(filter $(TARGET_NATIVE_CATEGORY_PREFIX)/%, $(CATEGORIES)) OS_CATEGORIES=$(filter os/%, $(CATEGORIES)) #OS_CATEGORIES=$(filter-out $(foreach arch, $(KNOWN_ARCHS), $(TARGET_OS_CATEGORY_PREFIX)/$(arch)/%), $(filter $(TARGET_OS_CATEGORY_PREFIX)/%, $(CATEGORIES))) # JLINK JLINK_HOME ?= $(JAVA_HOME) JLINK_JMODS ?= $(JLINK_HOME)/jmods A2_JMODS=$(TARGET_NATIVE_OUTPUT)/jmods # Note: replacing $${MODULES// /,} is bash specific #JLINK_MODULES ?= $(shell . $(JLINK_HOME)/release && echo $${MODULES// /,}) JLINK_MODULES ?= $(subst $(space),$(comma),$(shell . $(JLINK_HOME)/release && echo $$MODULES)) JLINK_JAVA_VERSION = $(shell . $(JLINK_HOME)/release && echo $$JAVA_VERSION) ifeq ("$(shell . $(JLINK_HOME)/release && echo $$JVM_VARIANT)","Openj9") JLINK_JVM_VARIANT=openj9 else ifneq ("$(shell . $(JLINK_HOME)/release && echo $$GRAALVM_VERSION)",) JLINK_JVM_VARIANT=graalvm else JLINK_JVM_VARIANT=hotspot endif endif JLINK_JAVA_RELEASE = $(firstword $(subst .,$(space),$(JLINK_JAVA_VERSION))) JMODS_BASE=$(SDK_BUILD_BASE)/jmods define a2_jmod_bare_module $(RM) -r $(JMODS_BASE)/$(1)/java $(RM) -r $(JMODS_BASE)/$(1)/classes mkdir -p $(JMODS_BASE)/$(1)/java mkdir -p $(JMODS_BASE)/$(1)/classes echo "module $(1) {}" > $(JMODS_BASE)/$(1)/java/module-info.java $(JLINK_HOME)/bin/javac --release 11 -d $(JMODS_BASE)/$(1)/classes \ $(JMODS_BASE)/$(1)/java/module-info.java endef # Minimal required OS libs for distribution A2_OS_LIBS_CATEGORY=org.argeo.os.libs JMOD_OS_LIBS=$(A2_OS_LIBS_CATEGORY) ifeq ($(MSYS_VERSION),0) A2_OS_LIBS=\ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/ld-linux-x86-64.so.* \ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/libc.so.* \ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/libstdc++.so.* \ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/libz.so.* \ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/libm.so.* \ /usr/lib/$(TARGET_NATIVE_CATEGORY_PREFIX)/libgcc_s.so.* # TODO make it more robust A2_OS_LIBS_VERSION = $(shell gcc -dumpversion).0.0 else UCRT_BASE ?= /ucrt64 A2_OS_LIBS=\ $(UCRT_BASE)/bin/libgcc_s_seh-*.dll \ $(UCRT_BASE)/bin/libgomp-*.dll \ $(UCRT_BASE)/bin/libstdc++-*.dll \ $(UCRT_BASE)/bin/libwinpthread-*.dll A2_OS_LIBS_VERSION = $(firstword $(subst -,$(space),$(shell pacman -Q mingw-w64-ucrt-x86_64-gcc-libs | awk '{print $$2}'))) # pacman -Q mingw-w64-ucrt-x86_64-gcc-libs | awk '{print $2}' endif a2-prepare-os-libs: a2-prepare-output mkdir -p $(TARGET_NATIVE_OUTPUT)/$(A2_OS_LIBS_CATEGORY) cp $(A2_OS_LIBS) $(TARGET_NATIVE_OUTPUT)/$(A2_OS_LIBS_CATEGORY) ifeq ($(MSYS_VERSION),0) # No need to link on Linux else ln -f -r -s $(TARGET_NATIVE_OUTPUT)/$(A2_OS_LIBS_CATEGORY)/$(SHLIB_PREFIX)*$(SHLIB_SUFFIX) \ $(TARGET_NATIVE_OUTPUT) endif jmod-os-libs: a2-prepare-os-libs mkdir -p $(A2_JMODS) mkdir -p $(JMODS_BASE)/$(JMOD_OS_LIBS)/lib ifeq ($(MSYS_VERSION),0) # TODO copy only when standalone else $(COPY) $(TARGET_NATIVE_OUTPUT)/$(A2_OS_LIBS_CATEGORY)/$(SHLIB_PREFIX)*$(SHLIB_SUFFIX) \ $(JMODS_BASE)/$(JMOD_OS_LIBS)/lib endif $(call a2_jmod_bare_module,$(JMOD_OS_LIBS)) $(RM) $(A2_JMODS)/$(JMOD_OS_LIBS).jmod $(JLINK_HOME)/bin/jmod create \ --module-version $(A2_OS_LIBS_VERSION) \ --class-path $(JMODS_BASE)/$(JMOD_OS_LIBS)/classes \ --libs $(JMODS_BASE)/$(JMOD_OS_LIBS)/lib \ $(A2_JMODS)/$(JMOD_OS_LIBS).jmod ## ## SETUP ## a2-prepare-output: $(A2_NATIVE_OUTPUT)/local $(A2_NATIVE_OUTPUT)/local: mkdir -p $(A2_NATIVE_OUTPUT)/${LOCAL_NATIVE_CATEGORY_PREFIX} cd $(A2_NATIVE_OUTPUT) && ln -s ${LOCAL_NATIVE_CATEGORY_PREFIX} local mkdir -p $(A2_JMODS) ## ## UTILITIES ## # Install to a target directory without executable bit INSTALL=install -m644 -D --target-directory # Always try copy-on-write COPY=cp --reflink=auto # Recursively delete directories RMDIR=$(RM) -r # Reverse list # see https://stackoverflow.com/questions/52674/simplest-way-to-reverse-the-order-of-strings-in-a-make-variable/14260762#14260762 reverse = $(if $(wordlist 2,2,$(1)),$(call reverse,$(wordlist 2,$(words $(1)),$(1))) $(firstword $(1)),$(1)) # Remove duplicates # see https://stackoverflow.com/questions/16144115/makefile-remove-duplicate-words-without-sorting/16151140#16151140 uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1))) # Make variables used to replace spaces by a separator, typically in order to generate classpaths # for example: CLASSPATH = $(subst $(space),$(pathsep),$(strip $(JARS))) null := space := $(null) # comma:= , pathsep := : define LF $(null) endef libjjml-java-1.1.13/sdk/argeo-build/configure000077500000000000000000000036761510337231400210340ustar00rootroot00000000000000#!/bin/sh # We build where we are SDK_BUILD_BASE=$(pwd -P)/output if [ -z "$SDK_SRC_BASE" ] then echo Script variable SDK_SRC_BASE must be set in the calling \'configure\' script, echo to the root location of the sources, typically with such a pattern: echo 'SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)"' echo "(see 'configure.template' from the argeo-build directory)" echo In order to build Argeo Build itself, explicitly set SDK_SRC_BASE as an environment variable exit 1 fi SDK_MK=$SDK_SRC_BASE/sdk.mk if [ -f "$SDK_MK" ]; then echo "File $SDK_MK already exists. Remove it in order to configure a new build location:" echo "rm $SDK_MK" exit 1 else if [ -z "$JAVA_HOME" ] then JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java)))) echo "Environment variable JAVA_HOME not set, using $JAVA_HOME of $(which java)" fi # Create build directory, so that it can be used right away # and we check whether we have the rights mkdir -p $SDK_BUILD_BASE if [ ! -d "$SDK_BUILD_BASE" ]; then echo "Cannot create $SDK_BUILD_BASE, SDK configuration has failed." exit 2 fi # Generate sdk.mk cat > "$SDK_MK" <> "$SDK_MK" shift ;; --sysconfdir=*) echo "sysconfdir := ${i#*=}" >> "$SDK_MK" shift ;; --localstatedir=*) echo "localstatedir := ${i#*=}" >> "$SDK_MK" shift ;; esac done # Even when building in MSYS (that is, with proper '/' paths) # we still need at some places the Windows '\' path to output if command -v cygpath 2>&1 >/dev/null then echo "export SDK_BUILD_BASE_WIN=$(cygpath -w "$SDK_BUILD_BASE")" >> "$SDK_MK" fi echo SDK was configured. echo "JAVA_HOME : $JAVA_HOME" echo "Base for sources : $SDK_SRC_BASE" echo "Base for builds : $SDK_BUILD_BASE" fi libjjml-java-1.1.13/sdk/argeo-build/configure.template000077500000000000000000000002551510337231400226340ustar00rootroot00000000000000#!/bin/sh # Source are located where this script is SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)" # Source the configure script . $SDK_SRC_BASE/sdk/argeo-build/configure libjjml-java-1.1.13/sdk/argeo-build/ecj.args000066400000000000000000000001021510337231400205210ustar00rootroot00000000000000-source 21 -target 21 -nowarn -proceedOnError #-preserveAllLocals libjjml-java-1.1.13/sdk/argeo-build/excludes.txt000066400000000000000000000002561510337231400214710ustar00rootroot00000000000000src target .* bnd.bnd pom.xml build.properties bin generated META-INF/MANIFEST.MF configure Makefile *.mk sdk output node_modules package.json package-lock.json webpack.*.js libjjml-java-1.1.13/sdk/argeo-build/jni.mk000066400000000000000000000046621510337231400202320ustar00rootroot00000000000000ARGEO_BUILD_BASE := $(dir $(lastword $(MAKEFILE_LIST))) include $(ARGEO_BUILD_BASE)common.mk # The following variables should be declared in the including Makefile: # NATIVE_PACKAGE this native package name # A2_CATEGORY the (single) a2 category the bundles will belong to # The following variables have default values which can be overriden # DEP_NATIVE space-separated logical names of named dependencies # DEP_INCLUDES additional includes # DEP_LIBS additional native libraries DEP_NATIVE ?= DEP_INCLUDES ?= $(foreach dep, $(DEP_NATIVE), /usr/include/$(dep)) DEP_LIBS ?= $(foreach dep, $(DEP_NATIVE), -l$(dep)) TARGET_EXEC := libJava_$(NATIVE_PACKAGE).so LDFLAGS ?= -shared -fPIC -Wl,-soname,$(TARGET_EXEC).$(major).$(minor).$(micro) $(DEP_LIBS) CFLAGS ?= -O3 -fPIC SRC_DIRS := . # # Generic Argeo # BUILD_DIR := $(SDK_BUILD_BASE)/jni/$(NATIVE_PACKAGE) # Include directories INC_DIRS := $(shell find $(SRC_DIRS) -type d) "$(JAVA_HOME)/include" "$(JAVA_HOME)/include/linux" "$(JAVA_HOME)/include/win32" $(DEP_INCLUDES) all: a2-prepare-output $(TARGET_NATIVE_OUTPUT)/$(TARGET_EXEC) clean: $(RM) $(BUILD_DIR)/*.o $(RM) $(TARGET_NATIVE_OUTPUT)/$(TARGET_EXEC) install: $(TARGET_NATIVE_OUTPUT)/$(TARGET_EXEC) $(INSTALL) $(A2_NATIVE_INSTALL_TARGET) $(TARGET_NATIVE_OUTPUT)/$(TARGET_EXEC) uninstall: $(RM) $(A2_NATIVE_INSTALL_TARGET)/$(TARGET_EXEC) @if [ -d $(A2_NATIVE_INSTALL_TARGET) ]; then find $(A2_NATIVE_INSTALL_TARGET) -empty -type d -delete; fi # Sources SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') # Objects (example.cpp to ./org_example_core/example.cpp.o) OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) # Dependencies (example.cpp.o to ./org_example_core/example.cpp.d) DEPS := $(OBJS:.o=.d) # Add -I prefix to include directories INC_FLAGS := $(addprefix -I,$(INC_DIRS)) # Generate dependencies makefiles # -D__int64="long long" is required on cygwin/MSYS2 CPPFLAGS := $(INC_FLAGS) -MMD -MP -D__int64="long long" # Final build step $(TARGET_NATIVE_OUTPUT)/$(TARGET_EXEC): $(OBJS) mkdir -p $(TARGET_NATIVE_OUTPUT) $(CC) $(OBJS) -o $@ $(LDFLAGS) # Build step for C source $(BUILD_DIR)/%.c.o: %.c mkdir -p $(dir $@) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ # Build step for C++ source $(BUILD_DIR)/%.cpp.o: %.cpp mkdir -p $(dir $@) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ # Include the .d makefiles. (- pefix suppress errors if not found) -include $(DEPS) .PHONY: clean all install uninstall libjjml-java-1.1.13/sdk/argeo-build/osgi.mk000066400000000000000000000114151510337231400204050ustar00rootroot00000000000000ARGEO_BUILD_BASE := $(dir $(lastword $(MAKEFILE_LIST))) include $(ARGEO_BUILD_BASE)common.mk # The following variables should be declared in the including Makefile: # BUNDLES the space-separated list of bundles to build # A2_CATEGORY the (single) a2 category the bundles will belong to # The following environment variables can change the behaviour of the build # SOURCE_BUNDLES sources will be packaged separately in Eclipse-compatible source bundles # NO_MANIFEST_COPY generated MANIFESTs won't be copied to the source tree # The following variables have default values which can be overriden # DEP_CATEGORIES the a2 categories the compilation depends on # JAVADOC_PACKAGES the space-separated list of packages for which javadoc will be generated # NATIVE_PACKAGES the space-separated list of JNI packages (directories) DEP_CATEGORIES ?= JAVADOC_PACKAGES ?= NATIVE_PACKAGES ?= # We use the latest version of the ECJ compiler, within the A2 repository with the highest priority; # that is, an older version in /usr/local/share would have priority on a newer one in /usr/share. ECJ_JAR=$(firstword \ $(foreach base, $(A2_BASE), \ $(call reverse, $(sort $(wildcard $(base)/org.argeo.tp.build/org.eclipse.jdt.core.compiler.batch.$(ECJ_MAJOR).*.jar))) \ ) \ ) # Third-party libraries LOGGER_JAR ?= $(firstword $(foreach base, $(A2_BASE), $(wildcard $(base)/log/syslogger/org.argeo.tp/org.argeo.tp.syslogger.$(SYSLOGGER_BRANCH).jar))) BNDLIB_JAR ?= $(firstword $(foreach base, $(A2_BASE), $(wildcard $(base)/org.argeo.tp.build/biz.aQute.bndlib.$(BNDLIB_BRANCH).jar))) # Internal variables ARGEO_MAKE = $(JVM) -cp $(LOGGER_JAR):$(ECJ_JAR):$(BNDLIB_JAR) $(ARGEO_BUILD_BASE)src/org/argeo/build/Make.java JAVADOC_SRCS = $(foreach bundle, $(BUNDLES), $(bundle)/src) ifneq ($(NO_MANIFEST_COPY),true) MANIFESTS = $(foreach bundle, $(BUNDLES), $(bundle)/META-INF/MANIFEST.MF) endif BUILD_BASE = $(SDK_BUILD_BASE)/$(shell basename $(SDK_SRC_BASE)) TARGET_BUNDLES = $(abspath $(foreach bundle, $(BUNDLES),$(A2_OUTPUT)/$(shell dirname $(bundle))/$(A2_CATEGORY)/$(shell basename $(bundle)).$(major).$(minor).jar)) TODOS = $(foreach bundle, $(BUNDLES),$(BUILD_BASE)/$(bundle)/to-build) # Native JNIDIRS=$(foreach package, $(NATIVE_PACKAGES), jni/$(package)) ifneq (,$(qualifier)) # not a release QUALIFIER_ARG=--qualifier $(qualifier) endif # Needed in order to be able to expand $$ variables .SECONDEXPANSION: osgi: a2-prepare-output $(BUILD_BASE)/built $(MANIFESTS) # Actual build (compilation + bundle packaging) $(BUILD_BASE)/built : BUNDLES_TO_BUILD = $(strip $(subst $(abspath $(BUILD_BASE))/,, $(subst to-build,, $?))) $(BUILD_BASE)/built : $(TODOS) @echo "| A2 category : $(A2_CATEGORY)" @echo "| Version : $(major).$(minor).$(micro)$(qualifier)" @echo "| Bundles : $(BUNDLES_TO_BUILD)" @echo "| Dependencies : $(DEP_CATEGORIES)" @echo "| Compiler : $(notdir $(ECJ_JAR)) ($(JAVA_HOME))" @$(ARGEO_MAKE) \ all --a2-bases $(A2_BASE) --dep-categories $(DEP_CATEGORIES) \ --category $(A2_CATEGORY) --bundles $(BUNDLES_TO_BUILD) \ $(QUALIFIER_ARG) @touch $(BUILD_BASE)/built $(A2_OUTPUT)/%.$(major).$(minor).jar : $(BUILD_BASE)/$$(subst $(A2_CATEGORY)/,,$$*)/to-build $(ARGEO_MAKE) \ all --a2-bases $(A2_BASE) --dep-categories $(DEP_CATEGORIES) \ --category $(A2_CATEGORY) --bundles $(subst $(A2_CATEGORY)/,,$*) $(BUILD_BASE)/%/to-build : $$(shell find % -type f -not -path 'bin/*' -not -path '*/MANIFEST.MF' | sed 's/ /\\ /g') @rm -rf $(dir $@) @mkdir -p $(dir $@) @touch $@ # Local manifests %/META-INF/MANIFEST.MF : $(BUILD_BASE)/%/META-INF/MANIFEST.MF ifneq ($(NO_MANIFEST_COPY),true) @mkdir -p $*/META-INF @cp $< $@ endif clean-manifests : @rm -rf $(foreach bundle, $(BUNDLES), $(bundle)/META-INF/MANIFEST.MF); osgi-all: osgi jni-all osgi-clean: jni-clean rm -rf $(BUILD_BASE) osgi-install: jni-install $(ARGEO_MAKE) \ install --category $(A2_CATEGORY) --bundles $(BUNDLES) \ --target $(A2_INSTALL_TARGET) \ --os $(TARGET_OS) --target-native $(A2_NATIVE_INSTALL_TARGET) osgi-uninstall: jni-uninstall $(ARGEO_MAKE) \ uninstall --category $(A2_CATEGORY) --bundles $(BUNDLES) \ --target $(A2_INSTALL_TARGET) \ --os $(TARGET_OS) --target-native $(A2_NATIVE_INSTALL_TARGET) jni-all: $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir) all;) jni-clean: $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir) clean;) jni-install: $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir) install;) jni-uninstall: $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir) uninstall;) # Javadoc generation javadoc: $(BUILD_BASE)/built $(JAVADOC) -noindex -quiet -Xmaxwarns 1 -d $(BUILD_BASE)/api --source-path $(subst $(space),$(pathsep),$(strip $(JAVADOC_SRCS))) -subpackages $(JAVADOC_PACKAGES) .PHONY: osgi manifests javadoc osgi-all osgi-clean osgi-install osgi-uninstall jni-all jni-clean jni-install jni-uninstall libjjml-java-1.1.13/sdk/argeo-build/repackage.mk000066400000000000000000000062551510337231400213740ustar00rootroot00000000000000ARGEO_BUILD_BASE := $(dir $(lastword $(MAKEFILE_LIST))) include $(ARGEO_BUILD_BASE)common.mk # The following variables should be declared in the including Makefile: # CATEGORIES the space-separated list of categories to repackage A2_BASE ?=/usr/share/a2 /usr/local/share/a2 $(A2_OUTPUT) # Third-party libraries LOGGER_JAR ?= $(firstword $(foreach base, $(A2_BASE), $(wildcard $(base)/log/syslogger/org.argeo.tp/org.argeo.tp.syslogger.$(SYSLOGGER_BRANCH).jar))) BNDLIB_JAR ?= $(firstword $(foreach base, $(A2_BASE), $(wildcard $(base)/org.argeo.tp.build/biz.aQute.bndlib.$(BNDLIB_BRANCH).jar))) # Internal variables ARGEO_REPACKAGE = $(JVM) -cp $(LOGGER_JAR):$(BNDLIB_JAR) $(ARGEO_BUILD_BASE)src/org/argeo/build/Repackage.java TODOS_REPACKAGE = $(foreach category, $(CATEGORIES),$(BUILD_BASE)/$(category)/to-repackage) BUILD_BASE = $(SDK_BUILD_BASE)/$(shell basename $(SDK_SRC_BASE)) REPACKAGED_CATEGORIES = $(foreach category, $(CATEGORIES),$(A2_OUTPUT)/$(category)) all: a2-prepare-output $(BUILD_BASE)/repackaged install: @$(foreach category, $(CATEGORIES), $(INSTALL) $(A2_INSTALL_TARGET)/$(category) $(wildcard $(A2_OUTPUT)/$(category)/*.jar);$(LF)) @echo Installed jars from categories \'$(CATEGORIES)\' to $(A2_INSTALL_TARGET) @$(foreach category, $(CATEGORIES),\ if [ -d "$(TARGET_NATIVE_OUTPUT)/$(category)" ]; then $(INSTALL) $(A2_NATIVE_INSTALL_TARGET)/$(category) $(wildcard $(TARGET_NATIVE_OUTPUT)/$(category)/*); fi;$(LF)\ if [ -d "$(TARGET_NATIVE_OUTPUT)/$(category)" ]; then cd $(A2_NATIVE_INSTALL_TARGET) && find $(category) -type f \( -iname \*.so -o -iname \*.dll -o -iname \*.jnilib -o -iname \*.dylib \) -exec ln -fs {} \; ; fi;$(LF)\ ) @echo Installed native libraries to $(A2_NATIVE_INSTALL_TARGET) uninstall: @$(foreach category, $(CATEGORIES), $(RMDIR) $(A2_INSTALL_TARGET)/$(category);$(LF)) @echo Uninstalled jars from categories \'$(CATEGORIES)\' to $(A2_INSTALL_TARGET) @$(foreach category, $(CATEGORIES),\ if [ -d "$(A2_NATIVE_INSTALL_TARGET)/$(category)" ]; then cd $(A2_NATIVE_INSTALL_TARGET)/$(category) && find -type f \( -iname \*.so -o -iname \*.dll -o -iname \*.jnilib -o -iname \*.dylib \) -exec rm $(A2_NATIVE_INSTALL_TARGET)/{} \; ; fi;$(LF)\ if [ -d "$(A2_NATIVE_INSTALL_TARGET)/$(category)" ]; then $(RMDIR) $(A2_NATIVE_INSTALL_TARGET)/$(category); fi;$(LF)\ ) @echo Uninstalled native libraries from $(A2_NATIVE_INSTALL_TARGET) @if [ -d $(A2_INSTALL_TARGET) ]; then find $(A2_INSTALL_TARGET) -empty -type d -delete; fi @if [ -d $(A2_NATIVE_INSTALL_TARGET) ]; then find $(A2_NATIVE_INSTALL_TARGET) -empty -type d -delete; fi .SECONDEXPANSION: # We use .SECONDEXPANSION and CATEGORIES_TO_REPACKAGE instead of directly CATEGORIES # so that we don't repackage a category if it hasn't changed $(BUILD_BASE)/repackaged : CATEGORIES_TO_REPACKAGE = $(subst $(abspath $(BUILD_BASE))/,, $(subst to-repackage,, $?)) $(BUILD_BASE)/repackaged : $(TODOS_REPACKAGE) $(ARGEO_REPACKAGE) $(A2_OUTPUT) $(CATEGORIES_TO_REPACKAGE) @touch $(BUILD_BASE)/repackaged $(BUILD_BASE)/%/to-repackage : $$(shell find % -type f ) @rm -rf $(dir $@) @mkdir -p $(dir $@) @touch $@ clean: @$(foreach category, $(CATEGORIES), rm -rf $(BUILD_BASE)/$(category)) @rm -f $(BUILD_BASE)/repackaged libjjml-java-1.1.13/sdk/argeo-build/sdk/000077500000000000000000000000001510337231400176725ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/sdk/branches/000077500000000000000000000000001510337231400214575ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/sdk/branches/testing.bnd000066400000000000000000000001051510337231400236150ustar00rootroot00000000000000major=2 minor=1 micro=15 qualifier= SPDX-License-Identifier=CC0-1.0 libjjml-java-1.1.13/sdk/argeo-build/sdk/branches/unstable.bnd000066400000000000000000000001051510337231400237550ustar00rootroot00000000000000major=2 minor=3 micro=15 qualifier= SPDX-License-Identifier=CC0-1.0 libjjml-java-1.1.13/sdk/argeo-build/src/000077500000000000000000000000001510337231400177005ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/src/org/000077500000000000000000000000001510337231400204675ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/src/org/argeo/000077500000000000000000000000001510337231400215645ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/src/org/argeo/build/000077500000000000000000000000001510337231400226635ustar00rootroot00000000000000libjjml-java-1.1.13/sdk/argeo-build/src/org/argeo/build/Make.java000066400000000000000000001035201510337231400244040ustar00rootroot00000000000000package org.argeo.build; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; import static java.lang.System.Logger.Level.WARNING; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.lang.management.ManagementFactory; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.StringJoiner; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.zip.Deflater; import org.eclipse.jdt.core.compiler.CompilationProgress; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Resource; import aQute.bnd.plugin.jpms.JPMSModuleInfoPlugin; /** * Minimalistic OSGi compiler and packager, meant to be used as a single file * without being itself compiled first. It depends on the Eclipse batch compiler * (aka. ECJ) and the BND Libs library for OSGi metadata generation (which * itselfs depends on slf4j).
*
* For example, a typical system call would be:
* java -cp "/path/to/ECJ jar:/path/to/bndlib jar:/path/to/SLF4J jar" /path/to/cloned/argeo-build/src/org/argeo/build/Make.java action --option1 argument1 argument2 --option2 argument3 */ public class Make { private final static Logger logger = System.getLogger(Make.class.getName()); /** * Environment variable on whether compilation should fail on errors (if set to * true case-insensitive). Default is not to fail on error * so that a MANIFEST can in most cases be generate; one should therefore be * careful with false positives, or set this variable (typically for releases). */ private final static String ENV_FAIL_ON_ERROR = "FAIL_ON_ERROR"; /** * Environment variable on whether sources should be packaged separately (if set * to true case-insensitive) or integrated in the bundles (if other * value or not set). */ private final static String ENV_SOURCE_BUNDLES = "SOURCE_BUNDLES"; /** * Environment variable on whether legal files at the root of the sources should * be included in the generated bundles. Should be set to true * (case-insensitive) when building third-party software in order no to include * the build harness license into the generated bundles. */ private final static String ENV_NO_SDK_LEGAL = "NO_SDK_LEGAL"; /** * Environment variable to override the default location for the Argeo Build * configuration files. Typically used if Argeo Build has been compiled and * packaged separately. */ private final static String ENV_ARGEO_BUILD_CONFIG = "ARGEO_BUILD_CONFIG"; /** * Environment variable to provide the Windows path (for example * C:\Users\myuser\path\to\output) of the build output base. Overrides reading * SDK_BUILD_BASE from sdk.mk (where it will usually be exported). */ private final static String ENV_SDK_BUILD_BASE_WIN = "SDK_BUILD_BASE_WIN"; // /** Make file variable (in {@link #SDK_MK}) with a path to the sources base. */ // private final static String VAR_SDK_SRC_BASE = "SDK_SRC_BASE"; /** * Make file variable (in {@link #SDK_MK}) with a path to the build output base. */ private final static String VAR_SDK_BUILD_BASE = "SDK_BUILD_BASE"; /** * Make file variable (in {@link #BRANCH_MK}) with the branch. */ private final static String VAR_BRANCH = "BRANCH"; /** Name of the local-specific Makefile (sdk.mk). */ final static String SDK_MK = "sdk.mk"; /** Name of the branch definition Makefile (branch.mk). */ final static String BRANCH_MK = "branch.mk"; /** The execution directory (${user.dir}). */ final Path execDirectory; /** Base of the source code, typically the cloned git repository. */ final Path sdkSrcBase; /** * The base of the builder, typically a submodule pointing to the INCLUDEpublic * argeo-build directory. */ final Path argeoBuildBase; /** The base of the build for all layers. */ final Path sdkBuildBase; /** The base of the build for this layer. */ final Path buildBase; /** The base of the a2 output for all layers. */ final Path a2Output; /** The base of the a2 sources when packaged separately. */ final Path a2srcOutput; /** Whether compilation should fail on errors. */ final boolean failOnError; /** Whether sources should be packaged separately. */ final boolean sourceBundles; /** Whether common legal files should be included. */ final boolean noSdkLegal; /** Constructor initialises the base directories. */ public Make() throws IOException { failOnError = Boolean.parseBoolean(System.getenv(ENV_FAIL_ON_ERROR)); if (failOnError) logger.log(Level.INFO, "Compilation will fail on error"); sourceBundles = Boolean.parseBoolean(System.getenv(ENV_SOURCE_BUNDLES)); if (sourceBundles) logger.log(Level.INFO, "Sources will be packaged separately"); noSdkLegal = Boolean.parseBoolean(System.getenv(ENV_NO_SDK_LEGAL)); if (noSdkLegal) logger.log(Level.INFO, "SDK legal files will NOT be included"); execDirectory = Paths.get(System.getProperty("user.dir")); Path sdkMkP = findSdkMk(execDirectory); Objects.requireNonNull(sdkMkP, "No " + SDK_MK + " found under " + execDirectory); sdkSrcBase = sdkMkP.getParent(); // Map context = readMakefileVariables(sdkMkP); // sdkSrcBase = Paths.get(context.computeIfAbsent(VAR_SDK_SRC_BASE, (key) -> { // throw new IllegalStateException(key + " not found"); // })).toAbsolutePath(); Path argeoBuildBaseT = sdkSrcBase.resolve("sdk").resolve("argeo-build"); if (!Files.exists(argeoBuildBaseT)) { String fromEnv = System.getenv(ENV_ARGEO_BUILD_CONFIG); if (fromEnv != null) argeoBuildBaseT = Paths.get(fromEnv); if (fromEnv == null || !Files.exists(argeoBuildBaseT)) { throw new IllegalStateException( "Argeo Build not found. Did you initialise the git submodules or set the " + ENV_ARGEO_BUILD_CONFIG + " environment variable?"); } } argeoBuildBase = argeoBuildBaseT; String sdkBuildBaseWin = System.getenv(ENV_SDK_BUILD_BASE_WIN); if (sdkBuildBaseWin != null) { sdkBuildBase = Paths.get(sdkBuildBaseWin); } else { Map context = readMakefileVariables(sdkMkP); sdkBuildBase = Paths.get(context.computeIfAbsent(VAR_SDK_BUILD_BASE, (key) -> { throw new IllegalStateException(key + " not found"); })).toAbsolutePath(); } buildBase = sdkBuildBase.resolve(sdkSrcBase.getFileName()); a2Output = sdkBuildBase.resolve("a2"); a2srcOutput = sdkBuildBase.resolve("a2.src"); } /* * ACTIONS */ /** Compile and create the bundles in one go. */ void all(Map> options) throws IOException { compile(options); bundle(options); } /** Compile all the bundles which have been passed via the --bundle argument. */ void compile(Map> options) throws IOException { List bundles = options.get("--bundles"); Objects.requireNonNull(bundles, "--bundles argument must be set"); if (bundles.isEmpty()) return; List a2Categories = options.getOrDefault("--dep-categories", new ArrayList<>()); List a2Bases = options.getOrDefault("--a2-bases", new ArrayList<>()); a2Bases = a2Bases.stream().distinct().collect(Collectors.toList());// remove duplicates if (a2Bases.isEmpty() || !a2Bases.contains(a2Output.toString())) {// make sure a2 output is available a2Bases.add(a2Output.toString()); } List compilerArgs = new ArrayList<>(); Path ecjArgs = argeoBuildBase.resolve("ecj.args"); compilerArgs.add("@" + ecjArgs); // classpath if (!a2Categories.isEmpty()) { // We will keep only the highest major.minor // and order by bundle name, for predictability Map a2Jars = new TreeMap<>(); StringJoiner modulePath = new StringJoiner(File.pathSeparator); for (String a2Base : a2Bases) { categories: for (String a2Category : a2Categories) { Path a2Dir = Paths.get(a2Base).resolve(a2Category); if (!Files.exists(a2Dir)) continue categories; // TODO make it more robust if (a2Dir.toString().contains("org.argeo.tp.osgi.framework")) { modulePath.add(a2Dir.toString()); continue categories; } for (Path jarP : Files.newDirectoryStream(a2Dir, (p) -> p.getFileName().toString().endsWith(".jar") && !p.getFileName().toString().endsWith(".src.jar"))) { A2Jar a2Jar = new A2Jar(jarP); if (a2Jars.containsKey(a2Jar.name)) { A2Jar current = a2Jars.get(a2Jar.name); if (a2Jar.major > current.major) a2Jars.put(a2Jar.name, a2Jar); else if (a2Jar.major == current.major && a2Jar.minor > current.minor) a2Jars.put(a2Jar.name, a2Jar); // keep if minor equals } else { a2Jars.put(a2Jar.name, a2Jar); } } } } StringJoiner classPath = new StringJoiner(File.pathSeparator); for (Iterator it = a2Jars.values().iterator(); it.hasNext();) classPath.add(it.next().path.toString()); String classPathStr = classPath.toString(); if (!"".equals(classPathStr)) { compilerArgs.add("-cp"); compilerArgs.add(classPathStr); } String modulePathStr = modulePath.toString(); if (!"".equals(modulePathStr)) { compilerArgs.add("--module-path"); compilerArgs.add(modulePathStr); compilerArgs.add("--add-modules"); compilerArgs.add("org.eclipse.osgi"); } } // sources boolean atLeastOneBundleToCompile = false; bundles: for (String bundle : bundles) { StringBuilder sb = new StringBuilder(); Path bundlePath = execDirectory.resolve(bundle); if (!Files.exists(bundlePath)) { if (bundles.size() == 1) { logger.log(WARNING, "Bundle " + bundle + " not found in " + execDirectory + ", assuming this is this directory, as only one bundle was requested."); bundlePath = execDirectory; } else throw new IllegalArgumentException("Bundle " + bundle + " not found in " + execDirectory); } Path bundleSrc = bundlePath.resolve("src"); if (!Files.exists(bundleSrc)) { logger.log(WARNING, bundleSrc + " does not exist, skipping it, as this is not a Java bundle"); continue bundles; } sb.append(bundleSrc); sb.append("[-d"); compilerArgs.add(sb.toString()); sb = new StringBuilder(); sb.append(buildBase.resolve(bundle).resolve("bin")); sb.append("]"); compilerArgs.add(sb.toString()); atLeastOneBundleToCompile = true; } if (!atLeastOneBundleToCompile) return; if (logger.isLoggable(INFO)) compilerArgs.add("-time"); if (logger.isLoggable(DEBUG)) { logger.log(DEBUG, "Compiler arguments:"); for (String arg : compilerArgs) logger.log(DEBUG, arg); } boolean success = org.eclipse.jdt.core.compiler.batch.BatchCompiler.compile( compilerArgs.toArray(new String[compilerArgs.size()]), new PrintWriter(System.out), new PrintWriter(System.err), new MakeCompilationProgress()); if (!success) if (failOnError) throw new IllegalStateException("Compilation failed"); // kill the process if compilation failed else logger.log(ERROR, "!! COMPILATION FAILED !! (but packaging will continue)"); } /** Package the bundles. */ void bundle(Map> options) throws IOException { // check arguments List bundles = options.get("--bundles"); Objects.requireNonNull(bundles, "--bundles argument must be set"); if (bundles.isEmpty()) return; List categories = options.get("--category"); Objects.requireNonNull(categories, "--category argument must be set"); if (categories.size() != 1) throw new IllegalArgumentException("One and only one --category must be specified"); String category = categories.get(0); StringJoiner qualifier = new StringJoiner("-"); for (String q : options.getOrDefault("--qualifier", new ArrayList<>())) { qualifier.add(q); } final String branch; Path branchMk = sdkSrcBase.resolve(BRANCH_MK); if (Files.exists(branchMk)) { Map branchVariables = readMakefileVariables(branchMk); branch = branchVariables.get(VAR_BRANCH); } else { branch = null; } long begin = System.currentTimeMillis(); // create jars in parallel List> toDos = new ArrayList<>(); for (String bundle : bundles) { toDos.add(CompletableFuture.runAsync(() -> { try { createBundle(branch, bundle, category, qualifier.toString()); } catch (IOException e) { throw new RuntimeException("Packaging of " + bundle + " failed", e); } })); } CompletableFuture.allOf(toDos.toArray(new CompletableFuture[toDos.size()])).join(); long duration = System.currentTimeMillis() - begin; logger.log(DEBUG, "Packaging took " + duration + " ms"); } /** Install or uninstall bundles and native output. */ void install(Map> options, boolean uninstall) throws IOException { final String LIB_ = "lib/"; final String NATIVE_ = "native/"; // check arguments List bundles = multiArg(options, "--bundles", true); if (bundles.isEmpty()) return; String category = singleArg(options, "--category", true); Path targetA2 = Paths.get(singleArg(options, "--target", true)); String nativeTargetArg = singleArg(options, "--target-native", false); Path nativeTargetA2 = nativeTargetArg != null ? Paths.get(nativeTargetArg) : null; String targetOs = singleArg(options, "--os", nativeTargetArg != null); logger.log(INFO, (uninstall ? "Uninstalling bundles from " : "Installing bundles to ") + targetA2); final String branch; Path branchMk = sdkSrcBase.resolve(BRANCH_MK); if (Files.exists(branchMk)) { Map branchVariables = readMakefileVariables(branchMk); branch = branchVariables.get(VAR_BRANCH); } else { throw new IllegalArgumentException(VAR_BRANCH + " variable must be set."); } Properties properties = new Properties(); Path branchBnd = sdkSrcBase.resolve("sdk").resolve("branches").resolve(branch + ".bnd"); if (Files.exists(branchBnd)) try (InputStream in = Files.newInputStream(branchBnd)) { properties.load(in); } String major = properties.getProperty("major"); Objects.requireNonNull(major, "'major' must be set"); String minor = properties.getProperty("minor"); Objects.requireNonNull(minor, "'minor' must be set"); int count = 0; bundles: for (String bundle : bundles) { Path bundlePath = Paths.get(bundle); Path bundleParent = bundlePath.getParent(); Path a2JarDirectory = bundleParent != null ? a2Output.resolve(bundleParent).resolve(category) : a2Output.resolve(category); Path jarP = a2JarDirectory.resolve(bundlePath.getFileName() + "." + major + "." + minor + ".jar"); Path targetJarP; if (bundle.startsWith(LIB_)) {// OS-specific Objects.requireNonNull(nativeTargetA2); if (bundle.startsWith(LIB_ + NATIVE_) // portable native || bundle.startsWith(LIB_ + targetOs + "/" + NATIVE_)) {// OS-specific native targetJarP = nativeTargetA2.resolve(category).resolve(jarP.getFileName()); } else if (bundle.startsWith(LIB_ + targetOs)) {// OS-specific portable targetJarP = targetA2.resolve(category).resolve(jarP.getFileName()); } else { // ignore other OS continue bundles; } } else { targetJarP = targetA2.resolve(a2Output.relativize(jarP)); } if (uninstall) { // uninstall if (Files.exists(targetJarP)) { Files.delete(targetJarP); logger.log(DEBUG, "Removed " + targetJarP); count++; } Path targetParent = targetJarP.getParent(); if (targetParent.startsWith(targetA2)) deleteEmptyParents(targetA2, targetParent); if (nativeTargetA2 != null && targetParent.startsWith(nativeTargetA2)) deleteEmptyParents(nativeTargetA2, targetParent); } else { // install Files.createDirectories(targetJarP.getParent()); boolean update = Files.exists(targetJarP); Files.copy(jarP, targetJarP, StandardCopyOption.REPLACE_EXISTING); logger.log(DEBUG, (update ? "Updated " : "Installed ") + targetJarP); count++; } } logger.log(INFO, uninstall ? count + " bundles removed" : count + " bundles installed or updated"); } /** Extracts an argument which must be unique. */ String singleArg(Map> options, String arg, boolean mandatory) { List values = options.get(arg); if (values == null || values.size() == 0) if (mandatory) throw new IllegalArgumentException(arg + " argument must be set"); else return null; if (values.size() != 1) throw new IllegalArgumentException("One and only one " + arg + " arguments must be specified"); return values.get(0); } /** Extracts an argument which can have multiple values. */ List multiArg(Map> options, String arg, boolean mandatory) { List values = options.get(arg); if (mandatory && values == null) throw new IllegalArgumentException(arg + " argument must be set"); return values != null ? values : new ArrayList<>(); } /** Delete empty parent directory up to the base directory (included). */ void deleteEmptyParents(Path baseDir, Path targetParent) throws IOException { if (!targetParent.startsWith(baseDir)) throw new IllegalArgumentException(targetParent + " does not start with " + baseDir); if (!Files.exists(baseDir)) return; if (!Files.exists(targetParent)) { deleteEmptyParents(baseDir, targetParent.getParent()); return; } if (!Files.isDirectory(targetParent)) throw new IllegalArgumentException(targetParent + " must be a directory"); boolean isA2target = Files.isSameFile(baseDir, targetParent); if (!Files.list(targetParent).iterator().hasNext()) { Files.delete(targetParent); if (isA2target) return;// stop after deleting A2 base deleteEmptyParents(baseDir, targetParent.getParent()); } } /** Package a single bundle. */ void createBundle(String branch, String bundle, String category, String qualifier) throws IOException { final Path bundleSourceBase; if (!Files.exists(execDirectory.resolve(bundle))) { logger.log(WARNING, "Bundle " + bundle + " not found in " + execDirectory + ", assuming this is this directory."); bundleSourceBase = execDirectory; } else { bundleSourceBase = execDirectory.resolve(bundle); } Path srcP = bundleSourceBase.resolve("src"); Path compiled = buildBase.resolve(bundle); String bundleSymbolicName = bundleSourceBase.getFileName().toString(); // Metadata Properties properties = new Properties(); Path argeoBnd = argeoBuildBase.resolve("argeo.bnd"); try (InputStream in = Files.newInputStream(argeoBnd)) { properties.load(in); } if (branch != null) { Path branchBnd = sdkSrcBase.resolve("sdk").resolve("branches").resolve(branch + ".bnd"); if (Files.exists(branchBnd)) try (InputStream in = Files.newInputStream(branchBnd)) { properties.load(in); } } // override qualifier if (!"".equals(qualifier) && !".".equals(qualifier)) properties.setProperty("qualifier", qualifier); Path bndBnd = bundleSourceBase.resolve("bnd.bnd"); if (Files.exists(bndBnd)) try (InputStream in = Files.newInputStream(bndBnd)) { properties.load(in); } // Normalise if (!properties.containsKey("Bundle-SymbolicName")) properties.put("Bundle-SymbolicName", bundleSymbolicName); // Calculate MANIFEST Path binP = compiled.resolve("bin"); if (!Files.exists(binP)) Files.createDirectories(binP); Manifest manifest; Resource moduleInfoClass = null; try (Analyzer bndAnalyzer = new Analyzer()) { bndAnalyzer.setProperties(properties); Jar jar = new Jar(bundleSymbolicName, binP.toFile()); bndAnalyzer.setJar(jar); manifest = bndAnalyzer.calcManifest(); jar.setManifest(manifest); // JPMS module JPMSModuleInfoPlugin jpmsModuleInfoPlugin = new JPMSModuleInfoPlugin(); jpmsModuleInfoPlugin.mainSet(bndAnalyzer, manifest); // jpmsModuleInfoPlugin.verify(bndAnalyzer); moduleInfoClass = bndAnalyzer.getJar().getResource("module-info.class"); } catch (Exception e) { throw new RuntimeException("Bnd analysis of " + compiled + " failed", e); } String major = properties.getProperty("major"); Objects.requireNonNull(major, "'major' must be set"); String minor = properties.getProperty("minor"); Objects.requireNonNull(minor, "'minor' must be set"); // Write manifest Path manifestP = compiled.resolve("META-INF").resolve("MANIFEST.MF"); Files.createDirectories(manifestP.getParent()); try (OutputStream out = Files.newOutputStream(manifestP)) { manifest.write(out); } // Write module-info.class if (moduleInfoClass != null) { Path moduleInfoClassP = binP.resolve("module-info.class"); try { if (!Files.exists(moduleInfoClassP) && moduleInfoClass.size() > 0) { Files.createDirectories(moduleInfoClassP.getParent()); try (OutputStream out = Files.newOutputStream(moduleInfoClassP)) { moduleInfoClass.write(out); // logger.log(INFO, "Wrote " + moduleInfoClassP); } } } catch (Exception e) { throw new RuntimeException("Cannot write module-info.class"); } } // Load excludes List excludes = new ArrayList<>(); Path excludesP = argeoBuildBase.resolve("excludes.txt"); for (String line : Files.readAllLines(excludesP)) { PathMatcher pathMatcher = excludesP.getFileSystem().getPathMatcher("glob:" + line); excludes.add(pathMatcher); } Path bundleParent = Paths.get(bundle).getParent(); Path a2JarDirectory = bundleParent != null ? a2Output.resolve(bundleParent).resolve(category) : a2Output.resolve(category); Path jarP = a2JarDirectory.resolve(compiled.getFileName() + "." + major + "." + minor + ".jar"); Files.createDirectories(jarP.getParent()); try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarP), manifest)) { jarOut.setLevel(Deflater.DEFAULT_COMPRESSION); // add all classes first Files.walkFileTree(binP, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { jarOut.putNextEntry(new JarEntry(toJarEntryName(binP.relativize(file)))); Files.copy(file, jarOut); return FileVisitResult.CONTINUE; } }); // add resources Files.walkFileTree(bundleSourceBase, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { // skip output directory if it happens to be within the sources if (Files.isSameFile(sdkBuildBase, dir)) return FileVisitResult.SKIP_SUBTREE; // skip excluded patterns Path relativeP = bundleSourceBase.relativize(dir); for (PathMatcher exclude : excludes) if (exclude.matches(relativeP)) return FileVisitResult.SKIP_SUBTREE; return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativeP = bundleSourceBase.relativize(file); for (PathMatcher exclude : excludes) if (exclude.matches(relativeP)) return FileVisitResult.CONTINUE; // skip JavaScript source maps if (sourceBundles && file.getFileName().toString().endsWith(".map")) return FileVisitResult.CONTINUE; JarEntry entry = new JarEntry(toJarEntryName(relativeP)); jarOut.putNextEntry(entry); Files.copy(file, jarOut); return FileVisitResult.CONTINUE; } }); if (Files.exists(srcP)) { // Add all resources from src/ Files.walkFileTree(srcP, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { // skip directories ending with .js // TODO find something more robust? if (dir.getFileName().toString().endsWith(".js")) return FileVisitResult.SKIP_SUBTREE; return super.preVisitDirectory(dir, attrs); } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().endsWith(".java") || file.getFileName().toString().endsWith(".class")) return FileVisitResult.CONTINUE; jarOut.putNextEntry(new JarEntry(toJarEntryName(srcP.relativize(file)))); if (!Files.isDirectory(file)) Files.copy(file, jarOut); return FileVisitResult.CONTINUE; } }); // add sources // TODO add effective BND, Eclipse project file, etc., in order to be able to // repackage if (!sourceBundles) { copySourcesToJar(srcP, jarOut, "OSGI-OPT/src/"); } } // add legal notices and licenses for (Path p : listLegalFilesToInclude(bundleSourceBase).values()) { jarOut.putNextEntry(new JarEntry(p.getFileName().toString())); Files.copy(p, jarOut); } } if (sourceBundles) {// create separate sources jar Path a2srcJarDirectory = bundleParent != null ? a2srcOutput.resolve(bundleParent).resolve(category) : a2srcOutput.resolve(category); Files.createDirectories(a2srcJarDirectory); Path srcJarP = a2srcJarDirectory.resolve(compiled.getFileName() + "." + major + "." + minor + ".src.jar"); createSourceBundle(bundleSymbolicName, manifest, bundleSourceBase, srcP, srcJarP); } } /** Create a separate bundle containing the sources. */ void createSourceBundle(String bundleSymbolicName, Manifest manifest, Path bundleSourceBase, Path srcP, Path srcJarP) throws IOException { Manifest srcManifest = new Manifest(); srcManifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); srcManifest.getMainAttributes().putValue("Bundle-SymbolicName", bundleSymbolicName + ".src"); srcManifest.getMainAttributes().putValue("Bundle-Version", manifest.getMainAttributes().getValue("Bundle-Version").toString()); boolean isJsBundle = bundleSymbolicName.endsWith(".js"); if (!isJsBundle) { srcManifest.getMainAttributes().putValue("Eclipse-SourceBundle", bundleSymbolicName + ";version=\"" + manifest.getMainAttributes().getValue("Bundle-Version")); try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) { copySourcesToJar(srcP, srcJarOut, ""); // add legal notices and licenses for (Path p : listLegalFilesToInclude(bundleSourceBase).values()) { srcJarOut.putNextEntry(new JarEntry(p.getFileName().toString())); Files.copy(p, srcJarOut); } } } else {// JavaScript source maps srcManifest.getMainAttributes().putValue("Fragment-Host", bundleSymbolicName + ";bundle-version=\"" + manifest.getMainAttributes().getValue("Bundle-Version")); try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) { Files.walkFileTree(bundleSourceBase, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Path relativeP = bundleSourceBase.relativize(file); if (!file.getFileName().toString().endsWith(".map")) return FileVisitResult.CONTINUE; JarEntry entry = new JarEntry(relativeP.toString()); srcJarOut.putNextEntry(entry); Files.copy(file, srcJarOut); return FileVisitResult.CONTINUE; } }); } } } /** List the relevant legal files to include, from the SDK source base. */ Map listLegalFilesToInclude(Path bundleBase) throws IOException { Map toInclude = new HashMap<>(); if (!noSdkLegal) { try (DirectoryStream sdkSrcLegal = Files.newDirectoryStream(sdkSrcBase, (p) -> { String fileName = p.getFileName().toString(); return switch (fileName) { case "NOTICE": case "LICENSE": case "COPYING": case "COPYING.LESSER": yield true; default: yield false; }; });) { for (Path p : sdkSrcLegal) toInclude.put(p.getFileName().toString(), p); } } for (Iterator> entries = toInclude.entrySet().iterator(); entries.hasNext();) { Map.Entry entry = entries.next(); Path inBundle = bundleBase.resolve(entry.getValue().getFileName()); // remove file if it is also defined at bundle level // since it has already been copied // and has priority if (Files.exists(inBundle)) entries.remove(); } return toInclude; } /* * UTILITIES */ /** Portable conversion to a jar entry path. */ private static String toJarEntryName(Path relativePath) { StringJoiner sj = new StringJoiner("/"); for (Path p : relativePath) sj.add(p.toString()); return sj.toString(); } /** Add sources to a jar file */ void copySourcesToJar(Path srcP, JarOutputStream srcJarOut, String prefix) throws IOException { Files.walkFileTree(srcP, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { srcJarOut.putNextEntry(new JarEntry(prefix + toJarEntryName(srcP.relativize(file)))); if (!Files.isDirectory(file)) Files.copy(file, srcJarOut); return FileVisitResult.CONTINUE; } }); } /** * Recursively find the base source directory (which contains the * {@value #SDK_MK} file). */ Path findSdkMk(Path directory) { Path sdkMkP = directory.resolve(SDK_MK); if (Files.exists(sdkMkP)) { return sdkMkP.toAbsolutePath(); } if (directory.getParent() == null) return null; return findSdkMk(directory.getParent()); } /** * Reads Makefile variable assignments of the form =, :=, or ?=, ignoring white * spaces. To be used with very simple included Makefiles only. */ Map readMakefileVariables(Path path) throws IOException { Map context = new HashMap<>(); List sdkMkLines = Files.readAllLines(path); lines: for (String line : sdkMkLines) { StringTokenizer st = new StringTokenizer(line, " :=?"); if (!st.hasMoreTokens()) continue lines; String key = st.nextToken(); if (!st.hasMoreTokens()) continue lines; String value = st.nextToken(); if (st.hasMoreTokens()) // probably not a simple variable assignment continue lines; context.put(key, value); } return context; } /** Main entry point, interpreting actions and arguments. */ public static void main(String... args) { if (args.length == 0) throw new IllegalArgumentException("At least an action must be provided"); int actionIndex = 0; String action = args[actionIndex]; if (args.length > actionIndex + 1 && !args[actionIndex + 1].startsWith("-")) throw new IllegalArgumentException( "Action " + action + " must be followed by an option: " + Arrays.asList(args)); Map> options = new HashMap<>(); String currentOption = null; for (int i = actionIndex + 1; i < args.length; i++) { if (args[i].startsWith("-")) { currentOption = args[i]; if (!options.containsKey(currentOption)) options.put(currentOption, new ArrayList<>()); } else { options.get(currentOption).add(args[i]); } } try { Make argeoMake = new Make(); switch (action) { case "compile" -> argeoMake.compile(options); case "bundle" -> argeoMake.bundle(options); case "all" -> argeoMake.all(options); case "install" -> argeoMake.install(options, false); case "uninstall" -> argeoMake.install(options, true); default -> throw new IllegalArgumentException("Unkown action: " + action); } long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); logger.log(INFO, "Make.java action '" + action + "' successfully completed after " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + " s"); } catch (Exception e) { long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); logger.log(ERROR, "Make.java action '" + action + "' failed after " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + " s", e); System.exit(1); } } /** A jar file in A2 format */ static class A2Jar { final Path path; final String name; final int major; final int minor; A2Jar(Path path) { try { this.path = path; String fileName = path.getFileName().toString(); fileName = fileName.substring(0, fileName.lastIndexOf('.')); minor = Integer.parseInt(fileName.substring(fileName.lastIndexOf('.') + 1)); fileName = fileName.substring(0, fileName.lastIndexOf('.')); major = Integer.parseInt(fileName.substring(fileName.lastIndexOf('.') + 1)); name = fileName.substring(0, fileName.lastIndexOf('.')); } catch (Exception e) { throw new IllegalArgumentException("Badly formatted A2 jar " + path, e); } } } /** * An ECJ {@link CompilationProgress} printing a progress bar while compiling. */ static class MakeCompilationProgress extends CompilationProgress { private int totalWork; private long currentChunk = 0; private long chunksCount = 80; @Override public void worked(int workIncrement, int remainingWork) { if (!logger.isLoggable(Level.INFO)) // progress bar only at INFO level return; long chunk = ((totalWork - remainingWork) * chunksCount) / totalWork; if (chunk != currentChunk) { currentChunk = chunk; for (long i = 0; i < currentChunk; i++) { System.out.print("#"); } for (long i = currentChunk; i < chunksCount; i++) { System.out.print("-"); } System.out.print("\r"); } if (remainingWork == 0) System.out.print("\n"); } @Override public void setTaskName(String name) { } @Override public boolean isCanceled() { return false; } @Override public void done() { } @Override public void begin(int remainingWork) { this.totalWork = remainingWork; } } } libjjml-java-1.1.13/sdk/argeo-build/src/org/argeo/build/Repackage.java000066400000000000000000002227711510337231400254230ustar00rootroot00000000000000package org.argeo.build; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.INFO; import static java.lang.System.Logger.Level.TRACE; import static java.lang.System.Logger.Level.WARNING; import static java.nio.file.FileVisitResult.CONTINUE; import static java.nio.file.StandardOpenOption.APPEND; import static java.nio.file.StandardOpenOption.CREATE; import static java.util.jar.Attributes.Name.MANIFEST_VERSION; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_DO_NOT_MODIFY; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_M2; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_M2_MERGE; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_M2_REPO; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_NO_METADATA_GENERATION; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_SOURCES_URI; import static org.argeo.build.Repackage.ManifestHeader.ARGEO_ORIGIN_URI; import static org.argeo.build.Repackage.ManifestHeader.AUTOMATIC_MODULE_NAME; import static org.argeo.build.Repackage.ManifestHeader.BUNDLE_LICENSE; import static org.argeo.build.Repackage.ManifestHeader.BUNDLE_SYMBOLICNAME; import static org.argeo.build.Repackage.ManifestHeader.BUNDLE_VERSION; import static org.argeo.build.Repackage.ManifestHeader.ECLIPSE_SOURCE_BUNDLE; import static org.argeo.build.Repackage.ManifestHeader.EXPORT_PACKAGE; import static org.argeo.build.Repackage.ManifestHeader.IMPORT_PACKAGE; import static org.argeo.build.Repackage.ManifestHeader.REQUIRE_CAPABILITY; import static org.argeo.build.Repackage.ManifestHeader.SPDX_LICENSE_IDENTIFIER; import static org.argeo.build.Repackage.SupportedArch.aarch64; import static org.argeo.build.Repackage.SupportedArch.armv7l; import static org.argeo.build.Repackage.SupportedArch.x86_64; import static org.argeo.build.Repackage.SupportedOS.freebsd; import static org.argeo.build.Repackage.SupportedOS.linux; import static org.argeo.build.Repackage.SupportedOS.macosx; import static org.argeo.build.Repackage.SupportedOS.win32; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.System.Logger; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.StringJoiner; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.Deflater; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Jar; /** Repackages existing jar files into OSGi bundles in an A2 repository. */ public class Repackage { final static Logger logger = System.getLogger(Repackage.class.getName()); /** * Environment variable on whether sources should be packaged separately or * integrated in the bundles. */ public final static String ENV_SOURCE_BUNDLES = "SOURCE_BUNDLES"; /** Environment variable on whether operations should be parallelised. */ public final static String ENV_ARGEO_BUILD_SEQUENTIAL = "ARGEO_BUILD_SEQUENTIAL"; /** Whether repackaging should run in parallel (default) or sequentially. */ final static boolean sequential = Boolean.parseBoolean(System.getenv(ENV_ARGEO_BUILD_SEQUENTIAL)); /** Name of the file centralising information for multiple M2 artifacts. */ final static String COMMON_BND = "common.bnd"; /** Name of the file centralising information for mergin M2 artifacts. */ final static String MERGE_BND = "merge.bnd"; /** * Subdirectory of the jar file where origin informations (changes, legal * notices etc. are stored) */ final static Path ARGEO_ORIGIN = Paths.get("META-INF", "argeo", "origin"); /** File detailing modifications to the original component. */ final static Path CHANGES = ARGEO_ORIGIN.resolve("changes"); /** * Name of the file at the root of the repackaged jar, which prominently * notifies that the component has be repackaged. */ final static String README_REPACKAGED = "README.repackaged"; // cache /** Summary of all license seen during the repackaging. */ final static Map> licensesUsed = new TreeMap<>(); /** Directory where to download archives */ final Path originBase; /** Directory where to download Maven artifacts */ final Path mavenBase; /** A2 repository base for binary bundles */ final Path a2Base; /** A2 repository base for source bundles */ final Path a2SrcBase; /** A2 base for native components */ final Path a2LibBase; /** Location of the descriptors driving the packaging */ final Path descriptorsBase; /** URIs of archives to download */ final Properties uris = new Properties(); /** Mirrors for archive download. Key is URI prefix, value list of base URLs */ final Map> mirrors = new HashMap>(); /** Whether sources should be packaged separately */ final boolean separateSources; /* * A2 PROVIDER SPECIFIC */ // What should be modified or overridden in order to extend support /** Supported processor architectures (Linux kernel conventions). */ enum SupportedArch { x86_64, aarch64, armv7l } /** Supported operating systems. */ enum SupportedOS { linux, win32, macosx, freebsd } protected Path processNativeEntry(JarEntry entry, A2Origin origin, NameVersion nameVersion, Path bundleDir) throws IOException { Path target = Paths.get(entry.getName());// relative path boolean copySharedLib = false; String multiArchDir = null; arch: for (SupportedArch arch : SupportedArch.values()) { os: for (SupportedOS os : SupportedOS.values()) { String archToUse = arch.name(); String osToUse = os.name(); // TODO make compiler configurable multiArchDir = arch.name() + "-" + os.name(); if (os.equals(linux)) multiArchDir = multiArchDir + "-gnu"; else multiArchDir = multiArchDir + "-default"; if (nameVersion.getName().startsWith("org.eclipse.swt") && nameVersion.getName().contains(os.name() + "." + arch.name())) { copySharedLib = true; } else if (nameVersion.getName().equals("com.sun.jna")) { if (arch.equals(x86_64)) archToUse = "x86-64"; else if (arch.equals(armv7l)) archToUse = "arm"; if (os.equals(macosx)) osToUse = "darwin"; if (target.getParent().getFileName().toString().equals(osToUse + "-" + archToUse)) copySharedLib = true; } else if (nameVersion.getName().equals("com.jogamp")) { if (arch.equals(x86_64)) archToUse = "amd64"; else if (arch.equals(SupportedArch.armv7l)) archToUse = "armv6hf"; if (os.equals(macosx) && (arch.equals(x86_64) || arch.equals(aarch64))) archToUse = "universal"; if (os.equals(win32)) osToUse = "windows"; if (target.getParent().getFileName().toString().equals(osToUse + "-" + archToUse)) copySharedLib = true; } else if (nameVersion.getName().equals("org.jline")) { if (arch.equals(armv7l)) archToUse = "armv7"; else if (arch.equals(aarch64)) archToUse = "arm64"; if (os.equals(linux)) osToUse = "Linux"; else if (os.equals(win32)) osToUse = "Windows"; else if (os.equals(macosx)) osToUse = "Mac"; else if (os.equals(freebsd)) osToUse = "FreeBSD"; if (target.getParent().getFileName().toString().equals(archToUse) // && target.getParent().getParent().getFileName().toString().equals(osToUse)) copySharedLib = true; } if (copySharedLib) break os; } if (copySharedLib) break arch; } if (!copySharedLib) return null; Path categoryDir = bundleDir.startsWith(a2LibBase) ? bundleDir.getParent() : a2LibBase.resolve(multiArchDir).resolve(a2Base.relativize(bundleDir.getParent())); Path targetSharedLibrary = categoryDir.resolve(target.getFileName()); logger.log(TRACE, () -> "Shared library " + targetSharedLibrary); return targetSharedLibrary; } /** Whether this entry is an embedded native library. */ protected boolean isNativeLibrary(JarEntry entry) { String fileName = entry.getName(); return fileName.endsWith(".so") // || entry.getName().endsWith(".dll") // || entry.getName().endsWith(".dylib") // || entry.getName().endsWith(".jnilib") // || entry.getName().endsWith(".a"); } /** * Filters out jar entries. * * @param entry the jar entry * @param origin in order to register and explain modifications of the * third-party software * @return whether the entry should be skipped */ protected boolean preProcessJarEntry(JarEntry entry, A2Origin origin) { if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".DSA") || entry.getName().endsWith(".SF")) { origin.deleted.add("cryptographic signatures"); return true; } if (entry.getName().startsWith("META-INF/versions/")) { // skip multi-version origin.deleted.add("additional Java versions (META-INF/versions)"); return true; } if (entry.getName().startsWith("META-INF/maven/")) { origin.deleted.add("Maven information (META-INF/maven)"); return true; } // skip file system providers as they cause issues with native image if (entry.getName().startsWith("META-INF/services/java.nio.file.spi.FileSystemProvider")) { origin.deleted.add("file system providers (META-INF/services/java.nio.file.spi.FileSystemProvider)"); return true; } return false; } /* * ENTRY POINT */ /** Main entry point. */ public static void main(String[] args) { if (sequential) logger.log(INFO, "Build will be sequential"); if (args.length < 2) { System.err.println("Usage: ..."); System.exit(1); } Path a2Base = Paths.get(args[0]).toAbsolutePath().normalize(); Path descriptorsBase = Paths.get(".").toAbsolutePath().normalize(); Repackage factory = new Repackage(a2Base, descriptorsBase); List> toDos = new ArrayList<>(); for (int i = 1; i < args.length; i++) { Path categoryPath = Paths.get(args[i]); factory.cleanPreviousFailedBuild(categoryPath); if (sequential) // sequential processing happens here factory.processCategory(categoryPath); else toDos.add(CompletableFuture.runAsync(() -> factory.processCategory(categoryPath))); } if (!sequential)// parallel processing CompletableFuture.allOf(toDos.toArray(new CompletableFuture[toDos.size()])).join(); // Summary StringBuilder sb = new StringBuilder(); for (String licenseId : licensesUsed.keySet()) for (String name : licensesUsed.get(licenseId)) sb.append((licenseId.equals("") ? "Proprietary" : licenseId) + "\t\t" + name + "\n"); logger.log(INFO, "# License summary:\n" + sb); } /* * GENERIC */ /** Deletes remaining sub directories. */ void cleanPreviousFailedBuild(Path categoryPath) { Path outputCategoryPath = a2Base.resolve(categoryPath); if (!Files.exists(outputCategoryPath)) return; // clean previous failed build try { for (Path subDir : Files.newDirectoryStream(outputCategoryPath, (d) -> Files.isDirectory(d))) { if (Files.exists(subDir)) { logger.log(WARNING, "Bundle dir " + subDir + " already exists, probably from a previous failed build, deleting it..."); deleteDirectory(subDir); } } } catch (IOException e) { logger.log(ERROR, "Cannot clean previous build", e); } } /** Constructor initialising the various variables. */ Repackage(Path a2Base, Path descriptorsBase) { separateSources = Boolean.parseBoolean(System.getenv(ENV_SOURCE_BUNDLES)); if (separateSources) logger.log(INFO, "Sources will be packaged separately"); Objects.requireNonNull(a2Base); Objects.requireNonNull(descriptorsBase); this.originBase = Paths.get(System.getProperty("user.home"), ".cache", "argeo/build/origin"); this.mavenBase = Paths.get(System.getProperty("user.home"), ".m2", "repository"); // TODO define and use a build base this.a2Base = a2Base; this.a2SrcBase = separateSources ? a2Base.getParent().resolve(a2Base.getFileName() + ".src") : a2Base; this.a2LibBase = a2Base.resolve("lib"); this.descriptorsBase = descriptorsBase; if (!Files.exists(this.descriptorsBase)) throw new IllegalArgumentException(this.descriptorsBase + " does not exist"); // URIs mapping Path urisPath = this.descriptorsBase.resolve("uris.properties"); if (Files.exists(urisPath)) { try (InputStream in = Files.newInputStream(urisPath)) { uris.load(in); } catch (IOException e) { throw new IllegalStateException("Cannot load " + urisPath, e); } } // Eclipse mirrors Path eclipseMirrorsPath = this.descriptorsBase.resolve("eclipse.mirrors.txt"); List eclipseMirrors = new ArrayList<>(); if (Files.exists(eclipseMirrorsPath)) { try { eclipseMirrors = Files.readAllLines(eclipseMirrorsPath, StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalStateException("Cannot load " + eclipseMirrorsPath, e); } for (Iterator it = eclipseMirrors.iterator(); it.hasNext();) { String value = it.next(); if (value.strip().equals("")) it.remove(); } } mirrors.put("http://www.eclipse.org/downloads", eclipseMirrors); } /* * MAVEN ORIGIN */ /** Process a whole category/group id. */ void processCategory(Path categoryRelativePath) { try { Path targetCategoryBase = descriptorsBase.resolve(categoryRelativePath); try (DirectoryStream bnds = Files.newDirectoryStream(targetCategoryBase, (p) -> p.getFileName().toString().endsWith(".bnd") && !p.getFileName().toString().equals(COMMON_BND) && !p.getFileName().toString().equals(MERGE_BND))) { for (Path p : bnds) { processSingleM2ArtifactDistributionUnit(p); } } try (DirectoryStream dus = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p))) { for (Path duDir : dus) { if (duDir.getFileName().toString().endsWith("-disabled")) { // skip } else if (duDir.getFileName().toString().startsWith("eclipse-")) { processArchive(duDir, true); } else if (duDir.getFileName().toString().startsWith("archive-")) { processArchive(duDir, false); } else { processM2BasedDistributionUnit(duDir); } } } } catch (IOException e) { throw new RuntimeException("Cannot process category " + categoryRelativePath, e); } } /** Process a standalone Maven artifact. */ void processSingleM2ArtifactDistributionUnit(Path bndFile) { try { Path categoryRelativePath = descriptorsBase.relativize(bndFile.getParent()); Path targetCategoryBase = a2Base.resolve(categoryRelativePath); Properties fileProps = new Properties(); try (InputStream in = Files.newInputStream(bndFile)) { fileProps.load(in); } // use file name as symbolic name if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.get())) { String symbolicName = bndFile.getFileName().toString(); symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length()); fileProps.put(BUNDLE_SYMBOLICNAME.get(), symbolicName); } String m2Coordinates = fileProps.getProperty(ARGEO_ORIGIN_M2.get()); if (m2Coordinates == null) throw new IllegalArgumentException("No M2 coordinates available for " + bndFile); M2Artifact artifact = new M2Artifact(m2Coordinates); Path downloaded = downloadMaven(fileProps, artifact); boolean doNotModify = Boolean .parseBoolean(fileProps.getOrDefault(ARGEO_ORIGIN_DO_NOT_MODIFY.get(), "false").toString()); if (doNotModify) { processNotModified(targetCategoryBase, downloaded, fileProps, artifact); return; } // regular processing A2Origin origin = new A2Origin(); Path bundleDir = processBndJar(downloaded, targetCategoryBase, fileProps, artifact, origin); downloadAndProcessM2Sources(fileProps, artifact, bundleDir, false, false); createJar(bundleDir, origin); } catch (Exception e) { throw new RuntimeException("Cannot process " + bndFile, e); } } /** * Process multiple Maven artifacts coming from a same project and therefore * with information in common (typically the version), generating single bundles * or merging them if necessary. * * @see #COMMON_BND * @see #MERGE_BND */ void processM2BasedDistributionUnit(Path duDir) { try { Path categoryRelativePath = descriptorsBase.relativize(duDir.getParent()); Path targetCategoryBase = a2Base.resolve(categoryRelativePath); Path mergeBnd = duDir.resolve(MERGE_BND); if (Files.exists(mergeBnd)) // merge mergeM2Artifacts(mergeBnd); Path commonBnd = duDir.resolve(COMMON_BND); if (!Files.exists(commonBnd)) return; Properties commonProps = new Properties(); try (InputStream in = Files.newInputStream(commonBnd)) { commonProps.load(in); } String m2Version = commonProps.getProperty(ARGEO_ORIGIN_M2.get()); if (m2Version == null) { logger.log(WARNING, "Ignoring " + duDir + " as it is not an M2-based distribution unit"); return;// ignore, this is probably an Eclipse archive } if (!m2Version.startsWith(":")) { throw new IllegalStateException("Only the M2 version can be specified: " + m2Version); } m2Version = m2Version.substring(1); try (DirectoryStream ds = Files.newDirectoryStream(duDir, (p) -> p.getFileName().toString().endsWith(".bnd") && !p.getFileName().toString().equals(COMMON_BND) && !p.getFileName().toString().equals(MERGE_BND))) { for (Path p : ds) { Properties fileProps = new Properties(); try (InputStream in = Files.newInputStream(p)) { fileProps.load(in); } String m2Coordinates = fileProps.getProperty(ARGEO_ORIGIN_M2.get()); M2Artifact artifact = new M2Artifact(m2Coordinates); if (artifact.getVersion() == null) { artifact.setVersion(m2Version); } else { logger.log(DEBUG, p.getFileName() + " : Using version " + artifact.getVersion() + " specified in descriptor rather than " + m2Version + " specified in " + COMMON_BND); } // prepare manifest entries Properties mergedProps = new Properties(); mergedProps.putAll(commonProps); fileEntries: for (Object key : fileProps.keySet()) { if (ARGEO_ORIGIN_M2.get().equals(key)) continue fileEntries; String value = fileProps.getProperty(key.toString()); Object previousValue = mergedProps.put(key.toString(), value); if (previousValue != null) { logger.log(WARNING, commonBnd + ": " + key + " was " + previousValue + ", overridden with " + value); } } mergedProps.put(ARGEO_ORIGIN_M2.get(), artifact.toM2Coordinates()); if (!mergedProps.containsKey(BUNDLE_SYMBOLICNAME.get())) { // use file name as symbolic name String symbolicName = p.getFileName().toString(); symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length()); mergedProps.put(BUNDLE_SYMBOLICNAME.get(), symbolicName); } // download Path downloaded = downloadMaven(mergedProps, artifact); boolean doNotModify = Boolean.parseBoolean( mergedProps.getOrDefault(ARGEO_ORIGIN_DO_NOT_MODIFY.get(), "false").toString()); if (doNotModify) { processNotModified(targetCategoryBase, downloaded, mergedProps, artifact); } else { A2Origin origin = new A2Origin(); Path targetBundleDir = processBndJar(downloaded, targetCategoryBase, mergedProps, artifact, origin); downloadAndProcessM2Sources(mergedProps, artifact, targetBundleDir, false, false); createJar(targetBundleDir, origin); } } } } catch (Exception e) { throw new RuntimeException("Cannot process " + duDir, e); } } /** Merge multiple Maven artifacts. */ void mergeM2Artifacts(Path mergeBnd) throws IOException { Path duDir = mergeBnd.getParent(); String category = duDir.getParent().getFileName().toString(); Path targetCategoryBase = a2Base.resolve(category); Properties mergeProps = new Properties(); // first, load common properties Path commonBnd = duDir.resolve(COMMON_BND); if (Files.exists(commonBnd)) try (InputStream in = Files.newInputStream(commonBnd)) { mergeProps.load(in); } // then, the merge properties themselves try (InputStream in = Files.newInputStream(mergeBnd)) { mergeProps.load(in); } String m2Version = mergeProps.getProperty(ARGEO_ORIGIN_M2.get()); if (m2Version == null) { logger.log(WARNING, "Ignoring merging in " + duDir + " as it is not an M2-based distribution unit"); return;// ignore, this is probably an Eclipse archive } if (!m2Version.startsWith(":")) { throw new IllegalStateException("Only the M2 version can be specified: " + m2Version); } m2Version = m2Version.substring(1); mergeProps.put(BUNDLE_VERSION.get(), m2Version); String artifactsStr = mergeProps.getProperty(ARGEO_ORIGIN_M2_MERGE.get()); if (artifactsStr == null) throw new IllegalArgumentException(mergeBnd + ": " + ARGEO_ORIGIN_M2_MERGE + " must be set"); String bundleSymbolicName = mergeProps.getProperty(BUNDLE_SYMBOLICNAME.get()); if (bundleSymbolicName == null) throw new IllegalArgumentException("Bundle-SymbolicName must be set in " + mergeBnd); CategoryNameVersion nameVersion = new M2Artifact(category + ":" + bundleSymbolicName + ":" + m2Version); A2Origin origin = new A2Origin(); Path bundleDir = targetCategoryBase.resolve(bundleSymbolicName + "." + nameVersion.getBranch()); StringJoiner originDesc = new StringJoiner(","); String[] artifacts = artifactsStr.split(","); artifacts: for (String str : artifacts) { String m2Coordinates = str.trim(); if ("".equals(m2Coordinates)) continue artifacts; M2Artifact artifact = new M2Artifact(m2Coordinates.trim()); if (artifact.getVersion() == null) artifact.setVersion(m2Version); originDesc.add(artifact.toString()); Path downloaded = downloadMaven(mergeProps, artifact); JarEntry entry; try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(downloaded), false)) { entries: while ((entry = jarIn.getNextJarEntry()) != null) { if (entry.isDirectory()) continue entries; if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".DSA") || entry.getName().endsWith(".SF")) { origin.deleted.add("cryptographic signatures from " + artifact); continue entries; } if (entry.getName().endsWith("module-info.class")) { // skip Java 9 module info origin.deleted.add("Java module information (module-info.class) from " + artifact); continue entries; } if (entry.getName().startsWith("META-INF/versions/")) { // skip multi-version origin.deleted.add("additional Java versions (META-INF/versions) from " + artifact); continue entries; } if (entry.getName().startsWith("META-INF/maven/")) { origin.deleted.add("Maven information (META-INF/maven) from " + artifact); continue entries; } if (entry.getName().startsWith(".cache/")) { // Apache SSHD origin.deleted.add("cache directory (.cache) from " + artifact); continue entries; } if (entry.getName().equals("META-INF/DEPENDENCIES")) { origin.deleted.add("Dependencies (META-INF/DEPENDENCIES) from " + artifact); continue entries; } if (entry.getName().equals("META-INF/MANIFEST.MF")) { Path originalManifest = bundleDir.resolve(ARGEO_ORIGIN).resolve(artifact.getGroupId()) .resolve(artifact.getArtifactId()).resolve("MANIFEST.MF"); Files.createDirectories(originalManifest.getParent()); try (OutputStream out = Files.newOutputStream(originalManifest)) { Files.copy(jarIn, originalManifest); } origin.added.add( "original MANIFEST (" + bundleDir.relativize(originalManifest) + ") from " + artifact); continue entries; } if (entry.getName().endsWith("NOTICE") || entry.getName().endsWith("NOTICE.txt") || entry.getName().endsWith("NOTICE.md") || entry.getName().endsWith("LICENSE") || entry.getName().endsWith("LICENSE.md") || entry.getName().endsWith("LICENSE-notice.md") || entry.getName().endsWith("COPYING") || entry.getName().endsWith("COPYING.LESSER")) { Path artifactOriginDir = bundleDir.resolve(ARGEO_ORIGIN).resolve(artifact.getGroupId()) .resolve(artifact.getArtifactId()); Path target = artifactOriginDir.resolve(entry.getName()); Files.createDirectories(target.getParent()); Files.copy(jarIn, target); origin.moved.add(entry.getName() + " in " + artifact + " to " + bundleDir.relativize(target)); continue entries; } Path target = bundleDir.resolve(entry.getName()); Files.createDirectories(target.getParent()); if (!Files.exists(target)) { Files.copy(jarIn, target); } else { if (entry.getName().startsWith("META-INF/services/")) { try (OutputStream out = Files.newOutputStream(target, StandardOpenOption.APPEND)) { out.write("\n".getBytes()); jarIn.transferTo(out); logger.log(DEBUG, artifact.getArtifactId() + " - Appended " + entry.getName()); } origin.modified.add(entry.getName() + ", merging from " + artifact); } else if (entry.getName().startsWith("org/apache/batik/")) { logger.log(TRACE, "Skip " + entry.getName()); continue entries; } else if (entry.getName().startsWith("META-INF/NOTICE")) { logger.log(WARNING, "Skip " + entry.getName() + " from " + artifact); // TODO merge them? continue entries; } else { throw new IllegalStateException("File " + target + " from " + artifact + " already exists"); } } logger.log(TRACE, () -> "Copied " + target); } } origin.added.add("binary content of " + artifact); // process sources downloadAndProcessM2Sources(mergeProps, artifact, bundleDir, true, false); } // additional service files Path servicesDir = duDir.resolve("services"); if (Files.exists(servicesDir)) { for (Path p : Files.newDirectoryStream(servicesDir)) { Path target = bundleDir.resolve("META-INF/services/").resolve(p.getFileName()); try (InputStream in = Files.newInputStream(p); OutputStream out = Files.newOutputStream(target, StandardOpenOption.APPEND);) { out.write("\n".getBytes()); in.transferTo(out); logger.log(DEBUG, "Appended " + p); } origin.added.add(bundleDir.relativize(target).toString()); } } // BND analysis Map entries = new TreeMap<>(); try (Analyzer bndAnalyzer = new Analyzer()) { bndAnalyzer.setProperties(mergeProps); Jar jar = new Jar(bundleDir.toFile()); bndAnalyzer.setJar(jar); Manifest manifest = bndAnalyzer.calcManifest(); keys: for (Object key : manifest.getMainAttributes().keySet()) { Object value = manifest.getMainAttributes().get(key); switch (key.toString()) { case "Tool": case "Bnd-LastModified": case "Created-By": continue keys; } if (REQUIRE_CAPABILITY.get().equals(key.toString()) && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\"")) { origin.deleted.add("MANIFEST header " + key); continue keys;// hack for very old classes } entries.put(key.toString(), value.toString()); } } catch (Exception e) { throw new RuntimeException("Cannot process " + mergeBnd, e); } Manifest manifest = new Manifest(); Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF"); Files.createDirectories(manifestPath.getParent()); for (String key : entries.keySet()) { String value = entries.get(key); manifest.getMainAttributes().putValue(key, value); } manifest.getMainAttributes().putValue(ARGEO_ORIGIN_M2.get(), originDesc.toString()); processLicense(bundleDir, manifest); // write MANIFEST try (OutputStream out = Files.newOutputStream(manifestPath)) { manifest.write(out); } createJar(bundleDir, origin); } /** Generates MANIFEST using BND. */ Path processBndJar(Path downloaded, Path targetCategoryBase, Properties fileProps, M2Artifact artifact, A2Origin origin) { try { Map additionalEntries = new TreeMap<>(); boolean doNotModifyManifest = Boolean.parseBoolean( fileProps.getOrDefault(ARGEO_ORIGIN_NO_METADATA_GENERATION.get(), "false").toString()); // Note: we always force the symbolic name if (doNotModifyManifest) { for (Object key : fileProps.keySet()) { String value = fileProps.getProperty(key.toString()); additionalEntries.put(key.toString(), value); } } else { if (artifact != null) { if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.get())) { fileProps.put(BUNDLE_SYMBOLICNAME.get(), artifact.getName()); } if (!fileProps.containsKey(BUNDLE_VERSION.get())) { fileProps.put(BUNDLE_VERSION.get(), artifact.getVersion()); } } if (!fileProps.containsKey(EXPORT_PACKAGE.get()) && fileProps.containsKey(BUNDLE_VERSION.get())) { fileProps.put(EXPORT_PACKAGE.get(), "*;version=\"" + fileProps.getProperty(BUNDLE_VERSION.get()) + "\""); } // BND analysis try (Analyzer bndAnalyzer = new Analyzer()) { bndAnalyzer.setProperties(fileProps); boolean tempFile = false; File jarFile; try { jarFile = downloaded.toFile(); } catch (UnsupportedOperationException e) { // copy to temp Path tmp = Files.createTempFile(downloaded.getFileName().toString(), null); Files.copy(downloaded, tmp, StandardCopyOption.REPLACE_EXISTING); jarFile = tmp.toFile(); jarFile.deleteOnExit(); tempFile = true; } // generate metadata Jar jar = new Jar(jarFile); bndAnalyzer.setJar(jar); Manifest manifest = bndAnalyzer.calcManifest(); if (tempFile) jarFile.delete(); keys: for (Object key : manifest.getMainAttributes().keySet()) { Object value = manifest.getMainAttributes().get(key); switch (key.toString()) { case "Tool": case "Bnd-LastModified": case "Created-By": continue keys; } if (REQUIRE_CAPABILITY.get().equals(key.toString()) && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\"")) { origin.deleted.add("MANIFEST header " + key); continue keys;// !! hack for very old classes } additionalEntries.put(key.toString(), value.toString()); } } } Path targetBundleDir = processBundleJar(downloaded, targetCategoryBase, additionalEntries, origin); logger.log(DEBUG, () -> "Processed " + downloaded); return targetBundleDir; } catch (Exception e) { throw new RuntimeException("Cannot BND process " + downloaded, e); } } /** Process an artifact that should not be modified. */ void processNotModified(Path targetCategoryBase, Path downloaded, Properties fileProps, M2Artifact artifact) throws IOException { // Some proprietary or signed artifacts do not allow any modification // When releasing (with separate sources), we just copy it Path unmodifiedTarget = targetCategoryBase .resolve(fileProps.getProperty(BUNDLE_SYMBOLICNAME.get()) + "." + artifact.getBranch() + ".jar"); Files.createDirectories(unmodifiedTarget.getParent()); Files.copy(downloaded, unmodifiedTarget, StandardCopyOption.REPLACE_EXISTING); Path bundleDir = targetCategoryBase .resolve(fileProps.getProperty(BUNDLE_SYMBOLICNAME.get()) + "." + artifact.getBranch()); downloadAndProcessM2Sources(fileProps, artifact, bundleDir, false, true); Manifest manifest; try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(unmodifiedTarget))) { manifest = jarIn.getManifest(); } createSourceJar(bundleDir, manifest, fileProps); } /** Download and integrates sources for a single Maven artifact. */ void downloadAndProcessM2Sources(Properties props, M2Artifact artifact, Path targetBundleDir, boolean merging, boolean unmodified) throws IOException { try { String repoStr = props.containsKey(ARGEO_ORIGIN_M2_REPO.get()) ? props.getProperty(ARGEO_ORIGIN_M2_REPO.get()) : null; String alternateUri = props.getProperty(ARGEO_ORIGIN_SOURCES_URI.get()); M2Artifact sourcesArtifact = new M2Artifact(artifact.toM2Coordinates(), "sources"); URI sourcesUrl = alternateUri != null ? new URI(alternateUri) : M2ConventionsUtils.mavenRepoUrl(repoStr, sourcesArtifact); Path sourcesDownloaded = downloadMaven(sourcesUrl, sourcesArtifact); processM2SourceJar(sourcesDownloaded, targetBundleDir, merging ? artifact : null, unmodified); logger.log(TRACE, () -> "Processed source " + sourcesDownloaded); } catch (Exception e) { logger.log(ERROR, () -> "Cannot download source for " + artifact); } } /** Integrate sources from a downloaded jar file. */ void processM2SourceJar(Path file, Path bundleDir, M2Artifact mergingFrom, boolean unmodified) throws IOException { A2Origin origin = new A2Origin(); Path sourceDir = separateSources || unmodified ? bundleDir.getParent().resolve(bundleDir.toString() + ".src") : bundleDir.resolve("OSGI-OPT/src"); try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) { String mergingMsg = ""; if (mergingFrom != null) mergingMsg = " of " + mergingFrom; Files.createDirectories(sourceDir); JarEntry entry; entries: while ((entry = jarIn.getNextJarEntry()) != null) { String relPath = entry.getName(); if (entry.isDirectory()) continue entries; if (entry.getName().equals("META-INF/MANIFEST.MF")) {// skip META-INF entries origin.deleted.add("MANIFEST.MF from the sources" + mergingMsg); continue entries; } if (!unmodified) { if (entry.getName().startsWith("module-info.java")) {// skip Java module information origin.deleted.add("Java module information from the sources (module-info.java)" + mergingMsg); continue entries; } if (entry.getName().startsWith("/")) { // absolute paths int metaInfIndex = entry.getName().indexOf("META-INF"); if (metaInfIndex >= 0) { relPath = entry.getName().substring(metaInfIndex); origin.moved.add(" to " + relPath + " entry with absolute path " + entry.getName()); } else { logger.log(WARNING, entry.getName() + " has an absolute path"); origin.deleted.add(entry.getName() + " from the sources" + mergingMsg); } continue entries; } } Path target = sourceDir.resolve(relPath); Files.createDirectories(target.getParent()); if (!Files.exists(target)) { Files.copy(jarIn, target); logger.log(TRACE, () -> "Copied source " + target); } else { logger.log(TRACE, () -> target + " already exists, skipping..."); } } } // write the changes if (separateSources || unmodified) { origin.appendChanges(sourceDir); } else { origin.added.add("source code under OSGI-OPT/src"); origin.appendChanges(bundleDir); } } /** Download a Maven artifact. */ Path downloadMaven(Properties props, M2Artifact artifact) throws IOException { String repoStr = props.containsKey(ARGEO_ORIGIN_M2_REPO.get()) ? props.getProperty(ARGEO_ORIGIN_M2_REPO.get()) : null; String alternateUri = props.getProperty(ARGEO_ORIGIN_URI.get()); try { URI uri = alternateUri != null ? new URI(alternateUri) : M2ConventionsUtils.mavenRepoUrl(repoStr, artifact); return downloadMaven(uri, artifact); } catch (URISyntaxException e) { throw new IllegalArgumentException("Wrong aritfact URI", e); } } /** Download a Maven artifact. */ Path downloadMaven(URI uri, M2Artifact artifact) throws IOException { return download(uri, mavenBase, M2ConventionsUtils.artifactPath("", artifact)); } /* * ECLIPSE ORIGIN */ /** Process an archive in Eclipse format. */ void processArchive(Path duDir, boolean isEclipse) { try { Path categoryRelativePath = descriptorsBase.relativize(duDir.getParent()); Path targetCategoryBase = a2Base.resolve(categoryRelativePath); Files.createDirectories(targetCategoryBase); // first delete all directories from previous builds for (Path dir : Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p))) deleteDirectory(dir); Files.createDirectories(originBase); Path commonBnd = duDir.resolve(COMMON_BND); Properties commonProps = new Properties(); try (InputStream in = Files.newInputStream(commonBnd)) { commonProps.load(in); } String url = commonProps.getProperty(ARGEO_ORIGIN_URI.get()); if (url == null) { url = uris.getProperty(duDir.getFileName().toString()); if (url == null) throw new IllegalStateException("No url available for " + duDir); commonProps.put(ARGEO_ORIGIN_URI.get(), url); } Path downloaded = tryDownloadArchive(url, originBase, isEclipse); FileSystem zipFs = FileSystems.newFileSystem(downloaded, (ClassLoader) null); // filters List includeMatchers = new ArrayList<>(); Properties includes = new Properties(); try (InputStream in = Files.newInputStream(duDir.resolve("includes.properties"))) { includes.load(in); } for (Object pattern : includes.keySet()) { PathMatcher pathMatcher = zipFs.getPathMatcher("glob:/" + pattern); includeMatchers.add(pathMatcher); } List excludeMatchers = new ArrayList<>(); Path excludeFile = duDir.resolve("excludes.properties"); if (Files.exists(excludeFile)) { Properties excludes = new Properties(); try (InputStream in = Files.newInputStream(excludeFile)) { excludes.load(in); } for (Object pattern : excludes.keySet()) { PathMatcher pathMatcher = zipFs.getPathMatcher("glob:/" + pattern); excludeMatchers.add(pathMatcher); } } Path zipRoot = zipFs.getRootDirectories().iterator().next(); List sourcesBases = new ArrayList<>(); Path sourcesFile = duDir.resolve("sources.properties"); Path archiveSourcesDir = targetCategoryBase.resolve(duDir.getFileName() + "-src"); if (Files.exists(sourcesFile)) { Properties sources = new Properties(); try (InputStream in = Files.newInputStream(sourcesFile)) { sources.load(in); } for (Object base : sources.keySet()) { Path path = zipRoot.resolve(base.toString()); sourcesBases.add(path); } Files.createDirectories(archiveSourcesDir); } // keys are the bundle directories Map origins = new HashMap<>(); Files.walkFileTree(zipRoot, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { includeMatchers: for (PathMatcher includeMatcher : includeMatchers) { if (includeMatcher.matches(file)) { for (PathMatcher excludeMatcher : excludeMatchers) { if (excludeMatcher.matches(file)) { logger.log(TRACE, "Skipping excluded " + file); return FileVisitResult.CONTINUE; } } if (file.getFileName().toString().contains(".source_")) { processEclipseSourceJar(file, targetCategoryBase); logger.log(DEBUG, () -> "Processed source " + file); } else { Map map = new HashMap<>(); for (Object key : commonProps.keySet()) { if (key.equals(ManifestHeader.BUNDLE_SYMBOLICNAME.get())) { // use as prefix String bsnPrefix = commonProps.getProperty(key.toString()); String fileNameBase = file.getFileName().toString().substring(0, file.toString().lastIndexOf('.') - 1); fileNameBase = fileNameBase.replace('-', '.'); map.put(BUNDLE_SYMBOLICNAME.get(), bsnPrefix + "." + fileNameBase); } else { map.put(key.toString(), commonProps.getProperty(key.toString())); } } Properties props = new Properties(); props.putAll(map); A2Origin origin = new A2Origin(); Path bundleDir; if (isEclipse) { // bundleDir = processBundleJar(file, targetCategoryBase, map, origin); bundleDir = processBndJar(file, targetCategoryBase, props, null, origin); } else { bundleDir = processBndJar(file, targetCategoryBase, props, null, origin); } if (bundleDir == null) { logger.log(WARNING, "No bundle dir created for " + file + ", skipping..."); return FileVisitResult.CONTINUE; } origins.put(bundleDir, origin); logger.log(DEBUG, () -> "Processed " + file); } break includeMatchers; } } if (sourcesBases != null) for (Path sourcesBase : sourcesBases) { if (file.startsWith(sourcesBase)) { Path relPath = sourcesBase.relativize(file); if (relPath.getParent() != null) Files.createDirectories(archiveSourcesDir.resolve(relPath.getParent().toString())); Files.copy(file, archiveSourcesDir.resolve(relPath.toString())); } } return FileVisitResult.CONTINUE; } }); // package bundle directories and sources as jars try (DirectoryStream dirs = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p) && p.getFileName().toString().indexOf('.') >= 0 && !p.getFileName().toString().endsWith(".src"))) { for (Path bundleDir : dirs) { A2Origin origin = origins.get(bundleDir); Objects.requireNonNull(origin, "No A2 origin found for " + bundleDir); // sources if (sourcesBases != null) { Path baseSourcesDir = separateSources ? bundleDir.getParent().resolve(bundleDir.getFileName() + ".src") : bundleDir.resolve("OSGI-OPT/src"); Files.walkFileTree(bundleDir, new SimpleFileVisitor() { @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Path relPath = bundleDir.relativize(dir); Path sourcesDir = archiveSourcesDir.resolve(relPath.toString()); if (Files.exists(sourcesDir)) { Path targetSourcesDir = baseSourcesDir.resolve(relPath); Files.createDirectories(targetSourcesDir); try (DirectoryStream files = Files.newDirectoryStream(sourcesDir)) { for (Path file : files) { Path target = targetSourcesDir.resolve(file.getFileName().toString()); // do not copy if already in bundle (e.g. images, resources) Path inBundle = dir.resolve(file.getFileName().toString()); if (!Files.exists(target) && !Files.exists(inBundle)) Files.copy(file, target); } } } return super.postVisitDirectory(dir, exc); } }); } // create the bundle jar createJar(bundleDir, origin); } } if (Files.exists(archiveSourcesDir)) // clean up archive sources deleteDirectory(archiveSourcesDir); } catch (Exception e) { throw new RuntimeException("Cannot process " + duDir, e); } } /** Process sources in Eclipse format. */ void processEclipseSourceJar(Path file, Path targetBase) throws IOException { try { A2Origin origin = new A2Origin(); Path bundleDir; try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) { Manifest manifest = jarIn.getManifest(); String[] relatedBundle = manifest.getMainAttributes().getValue(ECLIPSE_SOURCE_BUNDLE.get()).split(";"); String version = relatedBundle[1].substring("version=\"".length()); version = version.substring(0, version.length() - 1); NameVersion nameVersion = new NameVersion(relatedBundle[0], version); bundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch()); Path sourceDir = separateSources ? bundleDir.getParent().resolve(bundleDir.toString() + ".src") : bundleDir.resolve("OSGI-OPT/src"); Files.createDirectories(sourceDir); JarEntry entry; entries: while ((entry = jarIn.getNextJarEntry()) != null) { if (entry.isDirectory()) continue entries; if (entry.getName().startsWith("META-INF"))// skip META-INF entries continue entries; Path target = sourceDir.resolve(entry.getName()); Files.createDirectories(target.getParent()); Files.copy(jarIn, target); logger.log(TRACE, () -> "Copied source " + target); } // write the changes if (separateSources) { origin.appendChanges(sourceDir); } else { origin.added.add("source code under OSGI-OPT/src"); origin.appendChanges(bundleDir); } } } catch (IOException e) { throw new IllegalStateException("Cannot process " + file, e); } } /* * COMMON PROCESSING */ /** Normalise a single (that is, non-merged) bundle. */ Path processBundleJar(Path file, Path targetBase, Map entries, A2Origin origin) throws IOException { // boolean embed = Boolean.parseBoolean(entries.getOrDefault(ARGEO_ORIGIN_EMBED.get(), "false").toString()); boolean doNotModify = Boolean.parseBoolean( entries.getOrDefault(ManifestHeader.ARGEO_ORIGIN_DO_NOT_MODIFY.get(), "false").toString()); boolean keepModuleInfo = Boolean.parseBoolean( entries.getOrDefault(ManifestHeader.ARGEO_ORIGIN_KEEP_MODULE_INFO.get(), "false").toString()); NameVersion nameVersion; Path bundleDir; // singleton boolean isSingleton = false; Manifest manifest; Manifest sourceManifest; try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) { sourceManifest = jarIn.getManifest(); if (sourceManifest == null) logger.log(WARNING, file + " has no manifest"); manifest = sourceManifest != null ? new Manifest(sourceManifest) : new Manifest(); String rawSourceSymbolicName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME.get()); if (rawSourceSymbolicName != null) { // make sure there is no directive String[] arr = rawSourceSymbolicName.split(";"); for (int i = 1; i < arr.length; i++) { if (arr[i].trim().equals("singleton:=true")) isSingleton = true; logger.log(DEBUG, file.getFileName() + " is a singleton"); } } // remove problematic entries in MANIFEST manifest.getEntries().clear(); String ourSymbolicName = entries.get(BUNDLE_SYMBOLICNAME.get()); // make sure there is no directive if (ourSymbolicName != null) ourSymbolicName = ourSymbolicName.split(";")[0]; String ourVersion = entries.get(BUNDLE_VERSION.get()); if (ourSymbolicName != null && ourVersion != null) { nameVersion = new NameVersion(ourSymbolicName, ourVersion); } else { nameVersion = nameVersionFromManifest(manifest); if (nameVersion == null) throw new IllegalStateException("Could not compute name/version from Manifest"); if (ourVersion != null && !nameVersion.getVersion().equals(ourVersion)) { logger.log(WARNING, "Original version is " + nameVersion.getVersion() + " while new version is " + ourVersion); entries.put(BUNDLE_VERSION.get(), ourVersion); } if (ourSymbolicName != null) { // we always force our symbolic name nameVersion.setName(ourSymbolicName); } } bundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch()); // copy original MANIFEST if (sourceManifest != null) { Path originalManifest = bundleDir.resolve(ARGEO_ORIGIN).resolve("MANIFEST.MF"); Files.createDirectories(originalManifest.getParent()); try (OutputStream out = Files.newOutputStream(originalManifest)) { sourceManifest.write(out); } origin.moved.add("original MANIFEST to " + bundleDir.relativize(originalManifest)); } // force Java 9 module name entries.put(AUTOMATIC_MODULE_NAME.get(), nameVersion.getName()); // copy entries JarEntry entry; entries: while ((entry = jarIn.getNextJarEntry()) != null) { if (entry.isDirectory()) continue entries; if (!doNotModify) { // if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".DSA") // || entry.getName().endsWith(".SF")) { // origin.deleted.add("cryptographic signatures"); // continue entries; // } if (entry.getName().endsWith("module-info.class")) { if (keepModuleInfo) { entries.remove(AUTOMATIC_MODULE_NAME.get()); } else { // skip JPMS module info origin.deleted.add("Java module information (module-info.class)"); continue entries; } } boolean skipJarEntry = preProcessJarEntry(entry, origin); if (skipJarEntry) continue entries; } if (entry.getName().startsWith("OSGI-OPT/src/")) { // skip embedded sources origin.deleted.add("embedded sources"); continue entries; } final Path target = isNativeLibrary(entry) ? processNativeEntry(entry, origin, nameVersion, bundleDir) : bundleDir.resolve(entry.getName()); if (target != null) { Files.createDirectories(target.getParent()); Files.copy(jarIn, target, StandardCopyOption.REPLACE_EXISTING); if (isNativeLibrary(entry)) { Path multiArchDirName = a2LibBase.relativize(target).getName(0); Path linkPath = a2LibBase.resolve(multiArchDirName).resolve(target.getFileName()); Files.deleteIfExists(linkPath); Files.createSymbolicLink(linkPath, target); } logger.log(TRACE, () -> "Copied " + target); } } } // copy MANIFEST Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF"); Files.createDirectories(manifestPath.getParent()); if (isSingleton && entries.containsKey(BUNDLE_SYMBOLICNAME.get())) { String sn = entries.get(BUNDLE_SYMBOLICNAME.get()); if (!sn.contains(";singleton:=true")) entries.put(BUNDLE_SYMBOLICNAME.get(), sn + ";singleton:=true"); } // Final MANIFEST decisions // We also check the original OSGi metadata and compare with our changes for (String key : entries.keySet()) { String value = entries.get(key); String previousValue = manifest.getMainAttributes().getValue(key); boolean wasDifferent = previousValue != null && !previousValue.equals(value); boolean keepPrevious = false; if (wasDifferent) { if (SPDX_LICENSE_IDENTIFIER.get().equals(key) && previousValue != null) keepPrevious = true; if (REQUIRE_CAPABILITY.get().equals(key) && previousValue != null) keepPrevious = true; else if (BUNDLE_VERSION.get().equals(key) && wasDifferent) if (previousValue.equals(value + ".0")) // typically a Maven first release keepPrevious = true; if (keepPrevious) { if (logger.isLoggable(DEBUG)) logger.log(DEBUG, file.getFileName() + ": " + key + " was NOT modified, value kept is " + previousValue + ", not overriden with " + value); value = previousValue; } } manifest.getMainAttributes().putValue(key, value); if (wasDifferent && !keepPrevious) { if (IMPORT_PACKAGE.get().equals(key) || EXPORT_PACKAGE.get().equals(key)) logger.log(TRACE, () -> file.getFileName() + ": " + key + " was modified"); else if (BUNDLE_SYMBOLICNAME.get().equals(key) || AUTOMATIC_MODULE_NAME.get().equals(key)) logger.log(DEBUG, file.getFileName() + ": " + key + " was " + previousValue + ", overridden with " + value); else logger.log(WARNING, file.getFileName() + ": " + key + " was " + previousValue + ", overridden with " + value); origin.modified.add("MANIFEST header " + key); } // !! hack to remove unresolvable if (key.equals("Provide-Capability") || key.equals(REQUIRE_CAPABILITY.get())) if (nameVersion.getName().equals("osgi.core") || nameVersion.getName().equals("osgi.cmpn")) { manifest.getMainAttributes().remove(key); origin.deleted.add("MANIFEST header " + key); } } // de-pollute MANIFEST for (Iterator> manifestEntries = manifest.getMainAttributes().entrySet() .iterator(); manifestEntries.hasNext();) { Map.Entry manifestEntry = manifestEntries.next(); String key = manifestEntry.getKey().toString(); // TODO make it more generic // if (key.equals(REQUIRE_BUNDLE.get()) && nameVersion.getName().equals("com.sun.jna.platform")) // manifestEntries.remove(); switch (key) { case "Archiver-Version": case "Build-By": case "Created-By": case "Originally-Created-By": case "Tool": case "Bnd-LastModified": manifestEntries.remove(); origin.deleted.add("MANIFEST header " + manifestEntry.getKey()); break; default: if (sourceManifest != null && !sourceManifest.getMainAttributes().containsKey(manifestEntry.getKey())) origin.added.add("MANIFEST header " + manifestEntry.getKey()); } } processLicense(bundleDir, manifest); origin.modified.add("MANIFEST (META-INF/MANIFEST.MF)"); // write the MANIFEST try (OutputStream out = Files.newOutputStream(manifestPath)) { manifest.write(out); } return bundleDir; } /** Process SPDX license identifier. */ void processLicense(Path bundleDir, Manifest manifest) { String spdxLicenceId = manifest.getMainAttributes().getValue(SPDX_LICENSE_IDENTIFIER.get()); String bundleLicense = manifest.getMainAttributes().getValue(BUNDLE_LICENSE.get()); if (spdxLicenceId == null) { logger.log(ERROR, bundleDir.getFileName() + ": " + SPDX_LICENSE_IDENTIFIER + " not available, " + BUNDLE_LICENSE + " is " + bundleLicense); } else { // only use the first licensing option int orIndex = spdxLicenceId.indexOf(" OR "); if (orIndex >= 0) spdxLicenceId = spdxLicenceId.substring(0, orIndex).trim(); String bundleDirName = bundleDir.getFileName().toString(); // force licenses of some well-known components // even if we say otherwise (typically because from an Eclipse archive) if (bundleDirName.startsWith("org.apache.")) spdxLicenceId = "Apache-2.0"; if (bundleDirName.startsWith("com.sun.jna.")) spdxLicenceId = "Apache-2.0"; if (bundleDirName.startsWith("com.ibm.icu.")) spdxLicenceId = "ICU"; if (bundleDirName.startsWith("javax.annotation.")) spdxLicenceId = "GPL-2.0-only WITH Classpath-exception-2.0"; if (bundleDirName.startsWith("javax.inject.")) spdxLicenceId = "Apache-2.0"; if (bundleDirName.startsWith("org.osgi.")) spdxLicenceId = "Apache-2.0"; manifest.getMainAttributes().putValue(SPDX_LICENSE_IDENTIFIER.get(), spdxLicenceId); synchronized (licensesUsed) { if (!licensesUsed.containsKey(spdxLicenceId)) licensesUsed.put(spdxLicenceId, new TreeSet<>()); licensesUsed.get(spdxLicenceId) .add(bundleDir.getParent().getFileName() + "/" + bundleDir.getFileName()); } } } /* * UTILITIES */ /** Recursively deletes a directory. */ static void deleteDirectory(Path path) throws IOException { if (!Files.exists(path)) return; Files.walkFileTree(path, new SimpleFileVisitor() { @Override public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException { if (e != null) throw e; Files.delete(directory); return CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return CONTINUE; } }); } /** Extract name/version from a MANIFEST. */ NameVersion nameVersionFromManifest(Manifest manifest) { Attributes attrs = manifest.getMainAttributes(); // symbolic name String symbolicName = attrs.getValue(ManifestHeader.BUNDLE_SYMBOLICNAME.get()); if (symbolicName == null) return null; // make sure there is no directive symbolicName = symbolicName.split(";")[0]; String version = attrs.getValue(ManifestHeader.BUNDLE_VERSION.get()); return new NameVersion(symbolicName, version); } /** Try to download from an URI. */ Path tryDownloadArchive(String uriStr, Path dir, boolean isEclipse) throws IOException { // find mirror List urlBases = null; String uriPrefix = null; uriPrefixes: for (String uriPref : mirrors.keySet()) { if (uriStr.startsWith(uriPref)) { if (mirrors.get(uriPref).size() > 0) { urlBases = mirrors.get(uriPref); uriPrefix = uriPref; break uriPrefixes; } } } if (urlBases == null) try { return downloadArchive(new URI(uriStr), dir, isEclipse); } catch (FileNotFoundException | URISyntaxException e) { throw new FileNotFoundException("Cannot find " + uriStr); } // try to download for (String urlBase : urlBases) { String relativePath = uriStr.substring(uriPrefix.length()); String uStr = urlBase + relativePath; try { return downloadArchive(new URI(uStr), dir, isEclipse); } catch (FileNotFoundException | URISyntaxException e) { logger.log(WARNING, "Cannot download " + uStr + ", trying another mirror"); } } throw new FileNotFoundException("Cannot find " + uriStr); } /** * Effectively download an archive. */ Path downloadArchive(URI uri, Path dir, boolean isEclipse) throws IOException { String name = null; if (isEclipse) { // use the actual file name String[] arr = uri.getPath().split("/"); name = arr[arr.length - 1]; } return download(uri, dir, name); } /** * Effectively download. Synchronised in order to avoid downloading twice in * parallel. */ synchronized Path download(URI uri, Path dir, String name) throws IOException { Path dest; if (name == null) { // We use also use parent directory in case the archive itself has a fixed name String[] segments = uri.getPath().split("/"); name = segments.length > 1 ? segments[segments.length - 2] + '-' + segments[segments.length - 1] : segments[segments.length - 1]; } dest = dir.resolve(name); if (Files.exists(dest)) { logger.log(TRACE, () -> "File " + dest + " already exists for " + uri + ", not downloading again"); return dest; } else { Files.createDirectories(dest.getParent()); } try (InputStream in = uri.toURL().openStream()) { Files.copy(in, dest); logger.log(DEBUG, () -> "Downloaded " + dest + " from " + uri); } return dest; } /** Create a JAR file from a directory. */ Path createJar(Path bundleDir, A2Origin origin) throws IOException { Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF"); Manifest manifest; try (InputStream in = Files.newInputStream(manifestPath)) { manifest = new Manifest(in); } // legal requirements origin.appendChanges(bundleDir); createReadMe(bundleDir, manifest); // create the jar Path jarPath = bundleDir.getParent().resolve(bundleDir.getFileName() + ".jar"); try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) { jarOut.setLevel(Deflater.DEFAULT_COMPRESSION); Files.walkFileTree(bundleDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().equals("MANIFEST.MF")) return super.visitFile(file, attrs); JarEntry entry = new JarEntry(toJarEntryName(bundleDir.relativize(file))); jarOut.putNextEntry(entry); Files.copy(file, jarOut); return super.visitFile(file, attrs); } }); } deleteDirectory(bundleDir); if (separateSources) createSourceJar(bundleDir, manifest, null); return jarPath; } /** Portable conversion to a jar entry path. */ private static String toJarEntryName(Path relativePath) { StringJoiner sj = new StringJoiner("/"); for (Path p : relativePath) sj.add(p.toString()); return sj.toString(); } /** Package sources separately, in the Eclipse-SourceBundle format. */ void createSourceJar(Path bundleDir, Manifest manifest, Properties props) throws IOException { boolean unmodified = props != null; Path bundleCategoryDir = bundleDir.getParent(); Path sourceDir = bundleCategoryDir.resolve(bundleDir.toString() + ".src"); if (!Files.exists(sourceDir)) { logger.log(WARNING, sourceDir + " does not exist, skipping..."); return; } Path relPath = a2Base.relativize(bundleCategoryDir); Path srcCategoryDir = a2SrcBase.resolve(relPath); Path srcJarP = srcCategoryDir.resolve(sourceDir.getFileName() + ".jar"); Files.createDirectories(srcJarP.getParent()); String bundleSymbolicName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME.get()); Objects.requireNonNull(bundleSymbolicName, BUNDLE_SYMBOLICNAME + " not available in manifest related to " + bundleDir); // in case there are additional directives bundleSymbolicName = bundleSymbolicName.split(";")[0]; Manifest srcManifest = new Manifest(); srcManifest.getMainAttributes().put(MANIFEST_VERSION, "1.0"); BUNDLE_SYMBOLICNAME.put(srcManifest, bundleSymbolicName + ".src"); BUNDLE_VERSION.put(srcManifest, BUNDLE_VERSION.get(manifest)); ECLIPSE_SOURCE_BUNDLE.put(srcManifest, bundleSymbolicName + ";version=\"" + BUNDLE_VERSION.get(manifest) + "\""); // metadata createReadMe(sourceDir, unmodified ? props : manifest); // create jar try (JarOutputStream srcJarOut = new JarOutputStream(Files.newOutputStream(srcJarP), srcManifest)) { // srcJarOut.setLevel(Deflater.BEST_COMPRESSION); Files.walkFileTree(sourceDir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().equals("MANIFEST.MF")) return super.visitFile(file, attrs); JarEntry entry = new JarEntry( sourceDir.relativize(file).toString().replace(File.separatorChar, '/')); srcJarOut.putNextEntry(entry); Files.copy(file, srcJarOut); return super.visitFile(file, attrs); } }); } deleteDirectory(sourceDir); } /** * Generate a readme clarifying and prominently notifying of the repackaging and * modifications. */ void createReadMe(Path jarDir, Object mapping) throws IOException { // write repackaged README try (BufferedWriter writer = Files.newBufferedWriter(jarDir.resolve(README_REPACKAGED))) { boolean merged = ARGEO_ORIGIN_M2_MERGE.get(mapping) != null; if (merged) writer.append("This component is a merging of third party components" + " in order to comply with A2 packaging standards.\n"); else writer.append("This component is a repackaging of a third party component" + " in order to comply with A2 packaging standards.\n"); // license String spdxLicenseId = SPDX_LICENSE_IDENTIFIER.get(mapping); if (spdxLicenseId == null) throw new IllegalStateException("An SPDX license id must have beend defined at this stage."); writer.append("\nIt is redistributed under the following license:\n\n"); writer.append("SPDX-Identifier: " + spdxLicenseId + "\n\n"); if (!spdxLicenseId.startsWith("LicenseRef")) {// standard int withIndex = spdxLicenseId.indexOf(" WITH "); if (withIndex >= 0) { String simpleId = spdxLicenseId.substring(0, withIndex).trim(); String exception = spdxLicenseId.substring(withIndex + " WITH ".length()); writer.append("which are available here: https://spdx.org/licenses/" + simpleId + "\nand here: https://spdx.org/licenses/" + exception + "\n"); } else { writer.append("which is available here: https://spdx.org/licenses/" + spdxLicenseId + "\n"); } } else { String url = BUNDLE_LICENSE.get(mapping); if (url != null) { writer.write("which is available here: " + url + "\n"); } else { logger.log(ERROR, "No licence URL for " + jarDir); } } // origin String originDesc = ARGEO_ORIGIN_URI.get(mapping); if (originDesc != null) writer.append("\nThe original component comes from " + originDesc + ".\n"); else { String m2Repo = ARGEO_ORIGIN_M2_REPO.get(mapping); originDesc = ARGEO_ORIGIN_M2.get(mapping); if (originDesc != null) writer.append("\nThe original component has M2 coordinates:\n" + originDesc.replace(',', '\n') + "\n" + (m2Repo != null ? "\nin M2 repository " + m2Repo + "\n" : "")); else logger.log(ERROR, "Cannot find origin information in " + jarDir); } String originSources = ARGEO_ORIGIN_SOURCES_URI.get(mapping); if (originSources != null) writer.append("\nThe original sources come from " + originSources + ".\n"); if (Files.exists(jarDir.resolve(CHANGES))) writer.append("\nA detailed list of changes is available under " + CHANGES + ".\n"); if (!jarDir.getFileName().toString().endsWith(".src")) {// binary archive if (separateSources) writer.append("Corresponding sources are available in the related archive named " + jarDir.toString() + ".src.jar.\n"); else writer.append("Corresponding sources are available under OSGI-OPT/src.\n"); } } } /** Standard and Argeo-specific MANIFEST headers. */ enum ManifestHeader implements Supplier { // OSGi /** OSGi bundle symbolic name. */ BUNDLE_SYMBOLICNAME("Bundle-SymbolicName"), // /** OSGi bundle version. */ BUNDLE_VERSION("Bundle-Version"), // /** OSGi bundle license. */ BUNDLE_LICENSE("Bundle-License"), // /** OSGi exported packages list. */ EXPORT_PACKAGE("Export-Package"), // /** OSGi imported packages list. */ IMPORT_PACKAGE("Import-Package"), // /** Require capability. */ REQUIRE_CAPABILITY("Require-Capability"), // // /** OSGi required bundles. */ // REQUIRE_BUNDLE("Require-Bundle"), // // /** OSGi path to embedded jar. */ // BUNDLE_CLASSPATH("Bundle-Classpath"), // // Java /** Java module name. */ AUTOMATIC_MODULE_NAME("Automatic-Module-Name"), // // Eclipse /** Eclipse source bundle. */ ECLIPSE_SOURCE_BUNDLE("Eclipse-SourceBundle"), // // SPDX /** * SPDX license identifier. * * @see https://spdx.org/licenses/ */ SPDX_LICENSE_IDENTIFIER("SPDX-License-Identifier"), // // Argeo Origin /** * Maven coordinates of the origin, possibly partial when using common.bnd or * merge.bnd. */ ARGEO_ORIGIN_M2("Argeo-Origin-M2"), // /** List of Maven coordinates to merge. */ ARGEO_ORIGIN_M2_MERGE("Argeo-Origin-M2-Merge"), // /** Maven repository, if not the default one. */ ARGEO_ORIGIN_M2_REPO("Argeo-Origin-M2-Repo"), // /** * Do not perform BND analysis of the origin component. Typically Import-Package * and Export-Package will be kept untouched. */ ARGEO_ORIGIN_NO_METADATA_GENERATION("Argeo-Origin-NoMetadataGeneration"), // /** Keep JPMS module-info */ ARGEO_ORIGIN_KEEP_MODULE_INFO("Argeo-Origin-KeepModuleInfo"), // // /** // * Embed the original jar without modifying it (may be required by some // * proprietary licenses, such as JCR Day License). // */ // ARGEO_ORIGIN_EMBED("Argeo-Origin-Embed"), // /** * Do not modify original jar (may be required by some proprietary licenses, * such as JCR Day License). */ ARGEO_ORIGIN_DO_NOT_MODIFY("Argeo-Origin-Do-Not-Modify"), // /** * Origin (non-Maven) URI of the component. It may be anything (jar, archive, * etc.). */ ARGEO_ORIGIN_URI("Argeo-Origin-URI"), // /** * Origin (non-Maven) URI of the source of the component. It may be anything * (jar, archive, code repository, etc.). */ ARGEO_ORIGIN_SOURCES_URI("Argeo-Origin-Sources-URI"), // ; private final String headerName; private ManifestHeader(String headerName) { this.headerName = headerName; } @Override public String toString() { return get(); } /** The manifest header name. */ @Override public String get() { return headerName; } /** Get the value from either a {@link Manifest} or a {@link Properties}. */ String get(Object map) { if (map instanceof Manifest manifest) return manifest.getMainAttributes().getValue(headerName); else if (map instanceof Properties props) return props.getProperty(headerName); else throw new IllegalArgumentException("Unsupported mapping " + map.getClass()); } /** Put the value into either a {@link Manifest} or a {@link Properties}. */ void put(Object map, String value) { if (map instanceof Manifest manifest) manifest.getMainAttributes().putValue(headerName, value); else if (map instanceof Properties props) props.setProperty(headerName, value); else throw new IllegalArgumentException("Unsupported mapping " + map.getClass()); } } } /** * Gathers modifications performed on the original binaries and sources, * especially in order to comply with their license requirements. */ class A2Origin { Set modified = new TreeSet<>(); Set deleted = new TreeSet<>(); Set added = new TreeSet<>(); Set moved = new TreeSet<>(); /** Append changes to the A2-ORIGIN/changes file. */ void appendChanges(Path baseDirectory) throws IOException { if (modified.isEmpty() && deleted.isEmpty() && added.isEmpty() && moved.isEmpty()) return; // no changes Path changesFile = baseDirectory.resolve(Repackage.CHANGES); Files.createDirectories(changesFile.getParent()); try (BufferedWriter writer = Files.newBufferedWriter(changesFile, APPEND, CREATE)) { for (String msg : added) writer.write("- Added " + msg + ".\n"); for (String msg : modified) writer.write("- Modified " + msg + ".\n"); for (String msg : moved) writer.write("- Moved " + msg + ".\n"); for (String msg : deleted) writer.write("- Deleted " + msg + ".\n"); } } } /** Utilities around Maven (conventions based). */ class M2ConventionsUtils { final static String MAVEN_CENTRAL_BASE_URL = "https://repo1.maven.org/maven2/"; /** The file name of this artifact when stored */ static String artifactFileName(M2Artifact artifact) { return artifact.getArtifactId() + '-' + artifact.getVersion() + (artifact.getClassifier().equals("") ? "" : '-' + artifact.getClassifier()) + '.' + artifact.getExtension(); } /** Absolute path to the file */ static String artifactPath(String artifactBasePath, M2Artifact artifact) { return artifactParentPath(artifactBasePath, artifact) + '/' + artifactFileName(artifact); } /** Absolute path to the file */ static String artifactUrl(String repoUrl, M2Artifact artifact) { if (repoUrl.endsWith("/")) return repoUrl + artifactPath("/", artifact).substring(1); else return repoUrl + artifactPath("/", artifact); } /** Absolute path to the file */ static URI mavenRepoUrl(String repoBase, M2Artifact artifact) throws URISyntaxException { String uri = artifactUrl(repoBase == null ? MAVEN_CENTRAL_BASE_URL : repoBase, artifact); return new URI(uri); } /** Absolute path to the directories where the files will be stored */ static String artifactParentPath(String artifactBasePath, M2Artifact artifact) { return artifactBasePath + (artifactBasePath.endsWith("/") || artifactBasePath.equals("") ? "" : "/") + artifactParentPath(artifact); } /** Relative path to the directories where the files will be stored */ static String artifactParentPath(M2Artifact artifact) { return artifact.getGroupId().replace('.', '/') + '/' + artifact.getArtifactId() + '/' + artifact.getVersion(); } /** Singleton */ private M2ConventionsUtils() { } } /** Simple representation of an M2 artifact. */ class M2Artifact extends CategoryNameVersion { private String classifier; M2Artifact(String m2coordinates) { this(m2coordinates, null); } M2Artifact(String m2coordinates, String classifier) { String[] parts = m2coordinates.split(":"); setCategory(parts[0]); setName(parts[1]); if (parts.length > 2) { setVersion(parts[2]); } this.classifier = classifier; } String getGroupId() { return super.getCategory(); } String getArtifactId() { return super.getName(); } String toM2Coordinates() { return getCategory() + ":" + getName() + (getVersion() != null ? ":" + getVersion() : ""); } String getClassifier() { return classifier != null ? classifier : ""; } String getExtension() { return "jar"; } } /** Combination of a category, a name and a version. */ class CategoryNameVersion extends NameVersion { private String category; CategoryNameVersion() { } CategoryNameVersion(String category, String name, String version) { super(name, version); this.category = category; } CategoryNameVersion(String category, NameVersion nameVersion) { super(nameVersion); this.category = category; } String getCategory() { return category; } void setCategory(String category) { this.category = category; } @Override public String toString() { return category + ":" + super.toString(); } } /** Combination of a name and a version. */ class NameVersion implements Comparable { private String name; private String version; NameVersion() { } /** Interprets string in OSGi-like format my.module.name;version=0.0.0 */ NameVersion(String nameVersion) { int index = nameVersion.indexOf(";version="); if (index < 0) { setName(nameVersion); setVersion(null); } else { setName(nameVersion.substring(0, index)); setVersion(nameVersion.substring(index + ";version=".length())); } } NameVersion(String name, String version) { this.name = name; this.version = version; } NameVersion(NameVersion nameVersion) { this.name = nameVersion.getName(); this.version = nameVersion.getVersion(); } String getName() { return name; } void setName(String name) { this.name = name; } String getVersion() { return version; } void setVersion(String version) { this.version = version; } String getBranch() { String[] parts = getVersion().split("\\."); if (parts.length < 2) throw new IllegalStateException("Version " + getVersion() + " cannot be interpreted as branch."); return parts[0] + "." + parts[1]; } @Override public boolean equals(Object obj) { if (obj instanceof NameVersion) { NameVersion nameVersion = (NameVersion) obj; return name.equals(nameVersion.getName()) && version.equals(nameVersion.getVersion()); } else return false; } @Override public int hashCode() { return name.hashCode(); } @Override public String toString() { return name + ":" + version; } public int compareTo(NameVersion o) { if (o.getName().equals(name)) return version.compareTo(o.getVersion()); else return name.compareTo(o.getName()); } }