Skip to content

Commit 919c061

Browse files
authored
Merge pull request #502 from danthe1st/main
native-image compilation, disable member cache
2 parents 173044b + 1d42fe3 commit 919c061

29 files changed

+604
-4839
lines changed

.github/workflows/build.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ jobs:
2727
if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }}
2828
steps:
2929
- uses: actions/checkout@v4
30-
- name: Set up JDK 17
31-
uses: actions/setup-java@v4
30+
- name: Set up JDK 21
31+
uses: graalvm/setup-graalvm@v1
3232
with:
33-
java-version: '17'
34-
distribution: 'temurin'
35-
- name: Build JAR
36-
run: ./gradlew shadowJar
33+
java-version: '21'
34+
distribution: 'graalvm-community'
35+
- name: Build native-image
36+
run: ./gradlew nativeCompile -Pprod
3737
- name: Build Docker image
3838
run: docker build -t javabot .
3939
- name: Tag docker image

Dockerfile

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
FROM eclipse-temurin:21-jre
2-
RUN mkdir /work
3-
COPY build/libs/JavaBot-1.0.0-SNAPSHOT-all.jar /work/bot.jar
1+
FROM alpine:latest
2+
RUN apk add --no-cache libsm libxrender libxext libxtst libxi gcompat ttf-dejavu
3+
4+
COPY build/native/nativeCompile /work
45
WORKDIR /work
6+
57
RUN chown 1000:1000 /work
8+
USER 1000
9+
ENV HOME=/work
10+
11+
# https://github.com/openjdk/jdk/pull/20169
12+
# need to create fake JAVA_HOME
13+
RUN mkdir -p /tmp/JAVA_HOME/conf/fonts
14+
RUN mkdir /tmp/JAVA_HOME/lib
15+
16+
617
VOLUME "/work/config"
718
VOLUME "/work/logs"
819
VOLUME "/work/db"
920
VOLUME "/work/purgeArchives"
10-
USER 1000
11-
ENTRYPOINT [ "java", "-jar", "bot.jar" ]
21+
ENTRYPOINT [ "./javabot", "-Djava.home=/tmp/JAVA_HOME" ]

build.gradle.kts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.*
44
plugins {
55
java
66
id("com.github.johnrengelman.shadow") version "7.1.2"
7-
id("org.springframework.boot") version "3.2.0"
8-
id("io.spring.dependency-management") version "1.0.15.RELEASE"
7+
id("org.springframework.boot") version "3.3.5"
8+
id("io.spring.dependency-management") version "1.1.6"
9+
id("org.graalvm.buildtools.native") version "0.10.3"
910
checkstyle
1011
}
1112

@@ -64,6 +65,9 @@ dependencies {
6465
// Spring
6566
implementation("org.springframework.boot:spring-boot-starter-web")
6667
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
68+
69+
//required for registering native hints
70+
implementation("org.jetbrains.kotlin:kotlin-reflect")
6771
}
6872

6973
configurations {
@@ -101,4 +105,27 @@ tasks.withType<ShadowJar> {
101105
checkstyle {
102106
toolVersion = "9.1"
103107
configDirectory.set(File("checkstyle"))
104-
}
108+
}
109+
110+
tasks.withType<Checkstyle>() {
111+
exclude("**/generated/**")
112+
}
113+
114+
tasks.checkstyleAot {
115+
isEnabled = false
116+
}
117+
tasks.processTestAot {
118+
isEnabled = false
119+
}
120+
121+
graalvmNative {
122+
binaries {
123+
named("main") {
124+
if (hasProperty("prod")) {
125+
buildArgs.add("-O3")
126+
} else {
127+
quickBuild.set(true)
128+
}
129+
}
130+
}
131+
}

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
rootProject.name = "JavaBot"
1+
rootProject.name = "javabot"
22

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package net.discordjug.javabot;
2+
3+
import java.nio.channels.Channel;
4+
5+
import club.minnced.discord.webhook.send.WebhookEmbed;
6+
import net.discordjug.javabot.data.config.BotConfig;
7+
import net.discordjug.javabot.data.config.GuildConfig;
8+
import net.discordjug.javabot.data.config.GuildConfigItem;
9+
import net.discordjug.javabot.data.config.SystemsConfig;
10+
import net.discordjug.javabot.data.config.SystemsConfig.ApiConfig;
11+
import net.discordjug.javabot.data.config.guild.HelpConfig;
12+
import net.discordjug.javabot.data.config.guild.MessageCacheConfig;
13+
import net.discordjug.javabot.data.config.guild.MetricsConfig;
14+
import net.discordjug.javabot.data.config.guild.ModerationConfig;
15+
import net.discordjug.javabot.data.config.guild.QOTWConfig;
16+
import net.discordjug.javabot.data.config.guild.ServerLockConfig;
17+
import net.discordjug.javabot.data.config.guild.StarboardConfig;
18+
import net.dv8tion.jda.api.entities.Guild;
19+
import net.dv8tion.jda.api.entities.Member;
20+
import net.dv8tion.jda.api.entities.Role;
21+
import net.dv8tion.jda.api.entities.ScheduledEvent;
22+
import net.dv8tion.jda.api.entities.ThreadMember;
23+
import net.dv8tion.jda.api.entities.User;
24+
import net.dv8tion.jda.api.entities.channel.forums.ForumTag;
25+
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
26+
import net.dv8tion.jda.api.entities.sticker.GuildSticker;
27+
import net.dv8tion.jda.api.hooks.ListenerAdapter;
28+
import net.dv8tion.jda.api.managers.AudioManager;
29+
import net.dv8tion.jda.internal.entities.MemberPresenceImpl;
30+
import net.dv8tion.jda.internal.requests.restaction.PermOverrideData;
31+
import org.h2.server.TcpServer;
32+
import org.springframework.aot.hint.MemberCategory;
33+
import org.springframework.aot.hint.RuntimeHints;
34+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
35+
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
36+
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.context.annotation.ImportRuntimeHints;
38+
import org.springframework.core.io.ClassPathResource;
39+
40+
/**
41+
* Configure classes and resources to be accessible from native-image.
42+
*/
43+
@Configuration
44+
@ImportRuntimeHints(RuntimeHintsConfiguration.class)
45+
@RegisterReflectionForBinding({
46+
//register config classes for reflection
47+
BotConfig.class, GuildConfig.class, GuildConfigItem.class, SystemsConfig.class, ApiConfig.class,
48+
HelpConfig.class, MessageCacheConfig.class, MetricsConfig.class, ModerationConfig.class, QOTWConfig.class, ServerLockConfig.class, StarboardConfig.class,
49+
50+
//ensure JDA can create necessary caches
51+
User[].class, Guild[].class, Member[].class, Role[].class, Channel[].class, AudioManager[].class, ScheduledEvent[].class, ThreadMember[].class, ForumTag[].class, RichCustomEmoji[].class, GuildSticker[].class, MemberPresenceImpl[].class,
52+
//needs to be serialized for channel managers etc
53+
PermOverrideData.class,
54+
//ensure that webhook embed authors can be serialized
55+
WebhookEmbed.EmbedAuthor.class
56+
})
57+
public class RuntimeHintsConfiguration implements RuntimeHintsRegistrar {
58+
59+
@Override
60+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
61+
62+
//ensure resources are available in native-image
63+
hints.resources().registerPattern("assets/**");
64+
hints.resources().registerPattern("database/**");
65+
hints.resources().registerPattern("help_guidelines/**");
66+
hints.resources().registerPattern("help_overview/**");
67+
hints.resources().registerResource(new ClassPathResource("quartz.properties"));
68+
69+
//allow H2 to create the TCP server (necessary for starting the DB)
70+
hints.reflection().registerType(TcpServer.class, MemberCategory.INVOKE_PUBLIC_METHODS);
71+
72+
// JDA needs to be able to access listener methods
73+
hints.reflection().registerType(ListenerAdapter.class, MemberCategory.INVOKE_PUBLIC_METHODS);
74+
75+
// caffeine
76+
hints.reflection().registerTypeIfPresent(getClass().getClassLoader(), "com.github.benmanes.caffeine.cache.SSW", MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
77+
}
78+
}

src/main/java/net/discordjug/javabot/SpringConfig.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import org.springframework.context.ApplicationContext;
1111
import org.springframework.context.annotation.Bean;
1212
import org.springframework.context.annotation.Configuration;
13-
1413
import xyz.dynxsty.dih4jda.DIH4JDA;
1514
import xyz.dynxsty.dih4jda.DIH4JDABuilder;
1615
import xyz.dynxsty.dih4jda.exceptions.DIH4JDAException;
@@ -71,8 +70,8 @@ JDA jda(BotConfig botConfig, ApplicationContext ctx) {
7170
return JDABuilder.createDefault(botConfig.getSystems().getJdaBotToken())
7271
.setStatus(OnlineStatus.DO_NOT_DISTURB)
7372
.setChunkingFilter(ChunkingFilter.ALL)
74-
.setMemberCachePolicy(MemberCachePolicy.ALL)
75-
.enableCache(CacheFlag.ACTIVITY)
73+
.setMemberCachePolicy(MemberCachePolicy.VOICE)
74+
.enableCache(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE)
7675
.enableIntents(GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_PRESENCES, GatewayIntent.MESSAGE_CONTENT)
7776
.addEventListeners(listeners.toArray())
7877
.build();

src/main/java/net/discordjug/javabot/api/TomcatConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.springframework.context.annotation.Bean;
88
import org.springframework.context.annotation.Configuration;
99

10+
import java.net.InetAddress;
11+
1012
import net.discordjug.javabot.data.config.SystemsConfig;
1113

1214

@@ -18,6 +20,7 @@
1820
public class TomcatConfig {
1921

2022
private final int ajpPort;
23+
private final InetAddress ajpAddress;
2124
private final boolean tomcatAjpEnabled;
2225
private final SystemsConfig systemsConfig;
2326

@@ -26,11 +29,13 @@ public class TomcatConfig {
2629
* @param ajpPort The port to run AJP under
2730
* @param tomcatAjpEnabled <code>true</code> if AJP is enabled, else <code>false</code>
2831
* @param systemsConfig an object representing the configuration of various systems
32+
* @param ajpAddress the listen address for AJP
2933
*/
30-
public TomcatConfig(@Value("${tomcat.ajp.port}") int ajpPort, @Value("${tomcat.ajp.enabled}") boolean tomcatAjpEnabled, SystemsConfig systemsConfig) {
34+
public TomcatConfig(@Value("${tomcat.ajp.port}") int ajpPort, @Value("${tomcat.ajp.enabled}") boolean tomcatAjpEnabled, @Value("${tomcat.ajp.address}") InetAddress ajpAddress, SystemsConfig systemsConfig) {
3135
this.ajpPort = ajpPort;
3236
this.tomcatAjpEnabled = tomcatAjpEnabled;
3337
this.systemsConfig = systemsConfig;
38+
this.ajpAddress = ajpAddress;
3439
}
3540

3641
/**
@@ -46,6 +51,7 @@ TomcatServletWebServerFactory servletContainer() {
4651
Connector ajpConnector = new Connector("org.apache.coyote.ajp.AjpNioProtocol");
4752
AjpNioProtocol protocol= (AjpNioProtocol) ajpConnector.getProtocolHandler();
4853
protocol.setSecret(systemsConfig.getApiConfig().getAjpSecret());
54+
protocol.setAddress(ajpAddress);
4955
ajpConnector.setPort(ajpPort);
5056
ajpConnector.setSecure(true);
5157
tomcat.addAdditionalTomcatConnectors(ajpConnector);

src/main/java/net/discordjug/javabot/api/routes/leaderboard/qotw/QOTWLeaderboardController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ public ResponseEntity<List<QOTWUserData>> getQOTWLeaderboard(
6969
if (members == null || members.isEmpty()) {
7070
List<QOTWAccount> topAccounts = pointsService.getTopAccounts(PAGE_AMOUNT, page);
7171
members = topAccounts.stream()
72-
.map(account -> new Pair<>(account, jda.retrieveUserById(account.getUserId()).complete()))
73-
.filter(pair -> guild.isMember(pair.second()))
74-
.map(pair -> createAPIAccount(pair.first(), pair.second(), topAccounts, page))
72+
.map(account -> new Pair<>(account, guild.retrieveMemberById(account.getUserId()).complete()))
73+
.filter(pair -> pair.second() != null)
74+
.map(pair -> createAPIAccount(pair.first(), pair.second().getUser(), topAccounts, page))
7575
.toList();
7676
getCache().put(new Pair<>(guild.getIdLong(), page), members);
7777
}

src/main/java/net/discordjug/javabot/listener/JobChannelCloseOldPostsListener.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import net.discordjug.javabot.data.config.guild.ModerationConfig;
1010
import net.discordjug.javabot.util.InteractionUtils;
1111
import net.dv8tion.jda.api.EmbedBuilder;
12+
import net.dv8tion.jda.api.entities.UserSnowflake;
1213
import net.dv8tion.jda.api.entities.channel.ChannelType;
1314
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
1415
import net.dv8tion.jda.api.events.channel.ChannelCreateEvent;
@@ -53,7 +54,7 @@ public void onChannelCreate(ChannelCreateEvent event) {
5354
.setTitle("Post closed")
5455
.setDescription("This post has been blocked because you have created other recent posts.\nPlease do not spam posts.")
5556
.build())
56-
.setContent(post.getOwner().getAsMention())
57+
.setContent(UserSnowflake.fromId(post.getOwnerIdLong()).getAsMention())
5758
.flatMap(msg -> post.getManager().setArchived(true).setLocked(true))
5859
.queue();
5960
return;

src/main/java/net/discordjug/javabot/systems/help/HelpForumUpdater.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ private void checkForumPost(@NotNull ThreadChannel post, HelpConfig config) {
8181
private void sendDMDormantInfoIfEnabled(ThreadChannel post, HelpConfig config) {
8282
if(Boolean.parseBoolean(preferenceService.getOrCreate(post.getOwnerIdLong(), Preference.PRIVATE_DORMANT_NOTIFICATIONS).getState())) {
8383
post
84-
.getOwner()
85-
.getUser()
86-
.openPrivateChannel()
84+
.getJDA()
85+
.openPrivateChannelById(post.getOwnerIdLong())
8786
.flatMap(c -> c.sendMessageEmbeds(createDMDormantInfo(post, config)))
8887
.queue();
8988
}

0 commit comments

Comments
 (0)