英文:
Maven jlink plugin: native libraries not found when running the resulting image
问题
以下是您要翻译的代码部分的翻译:
Context: Linux Mint 21.1, Java Adoptium 17.0.7 JDK, Maven 3.6.3.
My modular application uses LWJGL (and then its native libraries); using the Maven jlink plug-in I generate a Java image. During generation, the jlink plug-in prints the following:
[INFO] --- maven-jlink-plugin:3.1.0:jlink (default-cli) @ treni ---
INFO] -> module: org.lwjgl.stb ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar )
INFO] -> module: org.slf4j ( /home/mmg/.m2/repository/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar )
INFO] -> module: imgui.natives.linux ( /home/mmg/.m2/repository/io/github/spair/imgui-java-natives-linux/1.86.10/imgui-java-natives-linux-1.86.10.jar )
INFO] -> module: org.lwjgl ( /home/mmg/.m2/repository/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar )
INFO] -> module: imgui.binding ( /home/mmg/.m2/repository/io/github/spair/imgui-java-binding/1.86.10/imgui-java-binding-1.86.10.jar )
INFO] -> module: org.lwjgl.stb.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux.jar )
INFO] -> module: org.lwjgl.glfw ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar )
INFO] -> module: org.joml ( /home/mmg/.m2/repository/org/joml/joml/1.10.5/joml-1.10.5.jar )
INFO] -> module: org.lwjgl.opengl ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar )
INFO] -> module: org.slf4j.jul ( /home/mmg/.m2/repository/org/slf4j/slf4j-jdk14/2.0.7/slf4j-jdk14-2.0.7.jar )
INFO] -> module: org.lwjgl.opengl.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux.jar )
INFO] -> module: org.lwjgl.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux.jar )
INFO] -> module: com.vistamaresoft.treni ( /home/mmg/Documents/projects/Eclipse_workspaces/Treni/treni/target/classes )
INFO] -> module: org.lwjgl.glfw.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux.jar )
INFO] Building zip: /home/mmg/Documents/projects/Eclipse_workspaces/Treni/treni/target/treni-0.0.1.zip
which shows that the plug-in knows about all the required native library JAR's.
I expected the resulting application to be able to locate the (evidently included, see below) native libraries, but it seems it is not, at least without additional hints I have no idea how to give it (and I could not find described anywhere). In fact, when I run the generated shell script, I receive the error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Failed to locate library: liblwjgl.so
Note that the required library is contained in the lwjgl-3.3.2-natives-linux.jar
referenced by the pom.xml
dependencies via the appropriate profile (see below for the pom.xml
contents).
The shell script is the default one generated by the jlink plug-in (the same for all my jlink-ed apps, only the module/main_class changes from one to another); anyway for completeness, this is its contents:
#!/bin/sh
JLINK_VM_OPTIONS=
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.vistamaresoft.treni/com.vistamaresoft.treni.Main "$@"
The generated image DOES contain the native libraries: I tried generating an image WITHOUT the native dependencies and the result is shorter roughly of the same size of those JAR's. The difference, as expected, is in the resulting lib/modules
file. So, this file presumably does contain the needed library/ies, but the executable(s) are not able to retrieve them?
I have Googled and 'stackoverflow-ed' for a whole day and I found nothing: any suggestion, help, hint about what it is happening is welcome.
This is the (slightly shortened for brevity) pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Several descriptive tags removed for brevity -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<target.name>treni</target.name>
<!-- Versions -->
<project.version>0.0.1</project.version>
<java.version>17</java.version>
<joml.version>1.10.5</joml.version>
<joml-primitives.version>1.10.4</joml-primitives.version>
<lwjgl.version>3.3.2</lwjgl.version>
<imgui-java.version>1.86.10</imgui-java.version>
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.6.0</maven-dependency-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-jlink-plugin.version>3.1.0</maven
<details>
<summary>英文:</summary>
Context: Linux Mint 21.1, Java Adoptium 17.0.7 JDK, Maven 3.6.3.
My modular application uses LWJGL (and then its native libraries); using the Maven jlink plug-in I generate a Java image. During generation, the jlink plug-in prints the following:
[INFO] --- maven-jlink-plugin:3.1.0:jlink (default-cli) @ treni ---
INFO] -> module: org.lwjgl.stb ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2.jar )
[INFO] -> module: org.slf4j ( /home/mmg/.m2/repository/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar )
[INFO] -> module: imgui.natives.linux ( /home/mmg/.m2/repository/io/github/spair/imgui-java-natives-linux/1.86.10/imgui-java-natives-linux-1.86.10.jar )
[INFO] -> module: org.lwjgl ( /home/mmg/.m2/repository/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2.jar )
[INFO] -> module: imgui.binding ( /home/mmg/.m2/repository/io/github/spair/imgui-java-binding/1.86.10/imgui-java-binding-1.86.10.jar )
[INFO] -> module: org.lwjgl.stb.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-stb/3.3.2/lwjgl-stb-3.3.2-natives-linux.jar )
[INFO] -> module: org.lwjgl.glfw ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2.jar )
[INFO] -> module: org.joml ( /home/mmg/.m2/repository/org/joml/joml/1.10.5/joml-1.10.5.jar )
[INFO] -> module: org.lwjgl.opengl ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2.jar )
[INFO] -> module: org.slf4j.jul ( /home/mmg/.m2/repository/org/slf4j/slf4j-jdk14/2.0.7/slf4j-jdk14-2.0.7.jar )
[INFO] -> module: org.lwjgl.opengl.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-opengl/3.3.2/lwjgl-opengl-3.3.2-natives-linux.jar )
[INFO] -> module: org.lwjgl.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux.jar )
[INFO] -> module: com.vistamaresoft.treni ( /home/mmg/Documents/projects/Eclipse_workspaces/Treni/treni/target/classes )
[INFO] -> module: org.lwjgl.glfw.natives ( /home/mmg/.m2/repository/org/lwjgl/lwjgl-glfw/3.3.2/lwjgl-glfw-3.3.2-natives-linux.jar )
[INFO] Building zip: /home/mmg/Documents/projects/Eclipse_workspaces/Treni/treni/target/treni-0.0.1.zip
which shows that the plug-in knows about all the required native library JAR's.
I expected the resulting application to be able to locate the (evidently included, see below) native libraries, but it seems it is not, at leat without additional hits I have no idea how to give it (and I could not find described anywhere). In fact, when I run the generate shell script, I receive the error:
`Exception in thread "main" java.lang.UnsatisfiedLinkError: Failed to locate library: liblwjgl.so`
Note that the required library is contained in the `lwjgl-3.3.2-natives-linux.jar` referenced by the `pom.xml` dependencies via the appropriate profile (see below for the `pom.xml` contents).
The shell script is the default one generated by the jlink plug-in (the same for all my jlink-ed apps, only the module/main_class changes from one to another); anyway for completeness, this is its contents:
#!/bin/sh
JLINK_VM_OPTIONS=
DIR=dirname $0
$DIR/java $JLINK_VM_OPTIONS -m com.vistamaresoft.treni/com.vistamaresoft.treni.Main "$@"
The generated image DOES contain the native libraries: I tried generating an image WITHOUT the native dependencies and the result is shorter roughly of the same size of those JAR's. The difference, as expected, is in the resulting `lib/modules` file. So, this file presumably does contain the needed library/ies, but the executable(s) are not able to retrieve them?
I have Googled and 'stackoverflow-ed' for a whole day and I found nothing: any suggestion, help, hint about what it is happening is welcome.
This is the (slightly shortened for brevity) `pom.xml`;
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Several descriptive tags removed for brevity -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<target.name>treni</target.name>
<!-- Versions -->
<project.version>0.0.1</project.version>
<java.version>17</java.version>
<joml.version>1.10.5</joml.version>
<joml-primitives.version>1.10.4</joml-primitives.version>
<lwjgl.version>3.3.2</lwjgl.version>
<imgui-java.version>1.86.10</imgui-java.version>
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.6.0</maven-dependency-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-jlink-plugin.version>3.1.0</maven-jlink-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
</properties>
<profiles>
<profile>
<id>lwjgl-natives-linux-amd64</id>
<activation>
<os>
<family>unix</family>
<arch>amd64</arch>
</os>
</activation>
<properties>
<lwjgl.natives>natives-linux</lwjgl.natives>
<imgui.native>natives-linux</imgui.native>
<imgui.native.module>linux</imgui.native.module>
</properties>
</profile>
<!-- Windows and MacOs profiles removed for brevity -->
</profiles>
<dependencies>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-opengl</artifactId>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.joml</groupId>
<artifactId>joml</artifactId>
<version>${joml.version}</version>
</dependency>
<dependency>
<groupId>io.github.spair</groupId>
<artifactId>imgui-java-binding</artifactId>
<version>${imgui-java.version}</version>
</dependency>
<!-- Natives -->
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<version>${lwjgl.version}</version>
<classifier>${lwjgl.natives}</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-glfw</artifactId>
<version>${lwjgl.version}</version>
<classifier>${lwjgl.natives}</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-opengl</artifactId>
<version>${lwjgl.version}</version>
<classifier>${lwjgl.natives}</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
<version>${lwjgl.version}</version>
<classifier>${lwjgl.natives}</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.spair</groupId>
<artifactId>imgui-java-${imgui.native}</artifactId>
<version>${imgui-java.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration> <!--
<release>S${java.version}</release> -->
<source>${java.version}</source>
<target>${java.version}</target>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.vistamaresoft.treni.Main</mainClass>
</manifest>
</archive>
<outputDirectory>${project.build.directory}/${target.name}</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<!-- run with `mvn javadoc:javadoc` -->
<configuration>
<reportOutputDirectory>${project.basedir}/doc</reportOutputDirectory>
<show>public</show>
</configuration>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<version>1.0.0.Final</version>
<configuration>
<outputDirectory>${project.build.directory}/modules</outputDirectory>
<overwriteExistingFiles>true</overwriteExistingFiles>
<modules>
<module>
<artifact>
<groupId>io.github.spair</groupId>
<artifactId>imgui-java-binding</artifactId>
<version>${imgui-java.version}</version>
</artifact>
<moduleInfoFile>${project.basedir}/src/main/moduleInfos/imgui.binding.module-info.java</moduleInfoFile>
</module>
<module>
<artifact>
<groupId>io.github.spair</groupId>
<artifactId>imgui-java-${imgui.native}</artifactId>
<version>${imgui-java.version}</version>
</artifact>
<moduleInfoFile>${project.basedir}/src/main/moduleInfos/imgui.natives.${imgui.native.module}.module-info.java</moduleInfoFile>
</module>
</modules>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jlink-plugin</artifactId>
<version>${maven-jlink-plugin.version}</version>
<!-- run with `mvn jlink:jlink` separately from `mvn package` as combining both into `mvn package jlink:jlink` raises an error -->
<extensions>true</extensions>
<configuration>
<!-- Module paths overriding the Mavem local repo path for non-modular JAR, modularised with moditect-maven-plugin -->
<modulePaths>
<modulePath>${project.build.directory}/modules/imgui-java-binding-${imgui-java.version}.jar</modulePath>
<modulePath>${project.build.directory}/modules/imgui-java-natives-${imgui.native.module}-${imgui-java.version}.jar</modulePath>
</modulePaths>
<compress>2</compress>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<launcher>treni=com.vistamaresoft.treni/com.vistamaresoft.treni.Main</launcher>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<!-- PLUG-IN EXECUTIONS -->
<plugins>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<executions>
<execution>
<id>add-module-infos</id>
<phase>generate-resources</phase>
<goals>
<goal>add-module-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
And this is the `module-info.java` of the app (currently in a single module):
module com.vistamaresoft.treni
{
exports com.vistamaresoft.treni;
exports com.vistamaresoft.treni.engine;
exports com.vistamaresoft.treni.engine.elements;
exports com.vistamaresoft.treni.objectviewer;
exports com.vistamaresoft.treni.sim;
requires imgui.binding;
requires transitive org.joml;
requires org.lwjgl;
requires org.lwjgl.glfw;
requires org.lwjgl.opengl;
requires org.lwjgl.stb;
requires transitive org.slf4j;
requires java.prefs;
requires java.base;
requires java.logging;
}
</details>
# 答案1
**得分**: 0
我不确定这是否是最佳解决方案,因为它相当繁琐,但目前我只找到这一个,它(大部分)有效,并且可能对其他人也有用。我要感谢在[LWJGL论坛][1]中极大帮助我找到这个解决方案的SPASI。
1) **将本地库模块添加到`module-info.java`**:这确保了这些库将在jlink插件生成的可运行镜像中找到。实际上,对于每个本地库,将以下一行添加到`module-info.java`中:
```java
requires transitive org.lwjgl.natives;
- 不幸的是,无论是Eclipse还是Maven编译器,似乎都无法在本地Maven仓库
.m2
中找到这些模块。解决方法有三个部分:
2a) 复制依赖项从仓库到本地文件夹,例如target/modules
,使用Maven依赖插件的copy-dependencies
目标;这必须在Maven编译阶段之前完成(我使用generate-resources
阶段),例如:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<outputDirectory>${project.build.directory}/modules</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
2b) Maven编译器插件:将该插件指向依赖项已复制的文件夹,将以下指令添加到插件配置中:
<compilerArgs>
<arg>--module-path</arg>
<arg>${project.build.directory}/modules</arg>
</compilerArgs>
2c) Maven jlink插件:同样将该插件指向相同的文件夹,将以下指令添加到插件配置中:
<modulePaths>
<modulePath>${project.build.directory}/modules</modulePath>
</modulePaths>
现在,Maven应该能够正确编译项目并创建可运行的应用程序。
- Eclipse(以及可能其他Java IDE):这仍然没有解决Eclipse无法解析在步骤1中添加到
module-info.java
的本地库模块的问题。
这意味着不能在Eclipse中构建项目,至少对于调试来说我觉得非常有用。也许可以解决那些JAR已经模块化的库的问题(但我不知道如何),但对于那些不是模块化的JAR并且需要被模块化的库,这是无法解决的(例如io.github.spair:imgui-java-natives-<platform>
,它不是模块化的,通常用于OpenGL / Vulkan项目,其模块根本不存在于任何众所周知的位置,是在源代码之外生成的)。
由于我只通过命令行使用Maven,我通过准备(在Java源代码之外并与主项目的pom.xml
平行)两个版本的module-info.java
来解决这个问题,一个版本没有本地库模块,一个版本添加了本地库模块。
通常,项目的module-info.java
与第一个较短版本保持一致;当我需要启动Maven构建时,一个小的shell脚本将module-info
覆盖为扩展版本,运行所需的mvn
命令,最后将module-info.java
覆盖为“正常”的版本。虽然不是百分之百可靠,但还是可用的。
英文:
I am far from sure this is the best solution, as it is rather cumbersome, but it is the only one I found so far, it works (mostly) and may be useful to someone else too. I want to thank SPASI in the LWJGL forum, who greatly helped me in finding it.
- Add native libraries modules to
module-info.java
: this ensures that the libraries will be found within the runnable image generated by the jlink pug-in. In practice, add tomodule-info.java
one line like
requires transitive org.lwjgl.natives;
for each native library.
- Unfortunately, both Eclipse and the Maven compiler seem unable to locate these modules within the local Maven repo
.m2
. The solution is three-fold:
2a) Copy the dependencies from the repo to a local folder, for instance target/modules
using the copy-dependencies
goal of the Maven dependency plug-in; this must be done before the Maven compile phase (I use the generate-resources
phase), for instance:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<outputDirectory>${project.build.directory}/modules</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
2b) Maven compiler plug-in: point this plug-in to the folder where the dependencies have been copied, adding this directive to the plug-in configuration:
<compilerArgs>
<arg>--module-path</arg>
<arg>${project.build.directory}/modules</arg>
</compilerArgs>
2c) Maven jlink plug-in: point this plug-in also to the same folder, adding the following directive to the plug-in configuration:
<modulePaths>
<modulePath>${project.build.directory}/modules</modulePath>
</modulePaths>
Now, Maven should correctly compile the project and jlink a runnable application.
- Eclipse (and perhaps other Java IDE's too): this leaves unsolved the issue with Eclipse not resolving the native library modules added to the
module-info.java
in step 1.
This precludes building the project from within Eclipse, which I find very useful at least for debugging. Maybe this can be solved for the libraries whose JAR's are already modular (but I do not know how), but surely it cannot be solved for the JAR's which are NOT modular and have to be modularised (for instance with the org.moditect:moditect-maven-plugin): their modules simply do not exist in any well-known location and are generated out-of-source (this happen to me with the io.github.spair:imgui-java-natives-<platform>
which is not modular and is often used with openGL / Vulkan projects).
As I use Maven only via command line, I work-around this preparing (our of the Java source and parallel to the main project pom.xml
) 2 versions of the module-info.java
, one without and one with the added modules.
Normally the project module-info.java
is kept equal to the first, shorter, version; when I have to launch a Maven build, a small shell script overwrites module-info
with the extended version, run the required mvn
command(s) and finally overwrites module-info.java
back with the 'normal' version. Not really fool-proof, but usable.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论