Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
5bd6ae7df0
WIP 2023-05-25 22:51:04 +02:00
8 changed files with 300 additions and 4 deletions

108
.ci/image/Dockerfile Normal file
View file

@ -0,0 +1,108 @@
FROM rust:slim-bullseye
RUN set -eux; \
apt-get update; \
apt-get install --no-install-recommends -y \
build-essential \
curl \
gpg \
jq \
libatk1.0-dev \
libclang-13-dev \
libglib2.0-dev \
libgtk-3-dev \
libpango1.0-dev \
libssl-dev \
libzstd-dev \
pkg-config; \
apt-get remove -y --auto-remove; \
rm -rf /var/lib/apt/lists/*; \
rustup default nightly
# https://jake-shadle.github.io/xwin/
ENV KEYRINGS /usr/local/share/keyrings
ARG XWIN_VERSION=0.2.11
ARG XWIN_PREFIX="xwin-$XWIN_VERSION-x86_64-unknown-linux-musl"
ARG LLVM_VERSION=16
ADD https://apt.llvm.org/llvm-snapshot.gpg.key /root/llvm-snapshot.gpg.key
ADD https://dl.winehq.org/wine-builds/winehq.key /root/winehq.key
ADD https://github.com/Jake-Shadle/xwin/releases/download/$XWIN_VERSION/$XWIN_PREFIX.tar.gz /root/$XWIN_PREFIX.tar.gz
RUN set -eux; \
mkdir -p $KEYRINGS; \
# clang/lld/llvm
gpg --dearmor > $KEYRINGS/llvm.gpg < /root/llvm-snapshot.gpg.key; \
# wine
gpg --dearmor > $KEYRINGS/winehq.gpg < /root/winehq.key; \
echo "deb [signed-by=$KEYRINGS/llvm.gpg] http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-${LLVM_VERSION} main" > /etc/apt/sources.list.d/llvm.list; \
echo "deb [signed-by=$KEYRINGS/winehq.gpg] https://dl.winehq.org/wine-builds/debian/ bullseye main" > /etc/apt/sources.list.d/winehq.list; \
dpkg --add-architecture i386; \
apt-get update; \
apt-get install --no-install-recommends -y \
libclang-${LLVM_VERSION}-dev \
gcc-mingw-w64-x86-64 \
clang-${LLVM_VERSION} \
llvm-${LLVM_VERSION} \
lld-${LLVM_VERSION} \
winehq-staging \
tar; \
# ensure that clang/clang++ are callable directly
ln -s clang-${LLVM_VERSION} /usr/bin/clang && ln -s clang /usr/bin/clang++ && ln -s lld-${LLVM_VERSION} /usr/bin/ld.lld; \
# We also need to setup symlinks ourselves for the MSVC shims because they aren't in the debian packages
ln -s clang-${LLVM_VERSION} /usr/bin/clang-cl && ln -s llvm-ar-${LLVM_VERSION} /usr/bin/llvm-lib && ln -s lld-link-${LLVM_VERSION} /usr/bin/lld-link; \
# Verify the symlinks are correct
clang++ -v; \
ld.lld -v; \
# Doesn't have an actual -v/--version flag, but it still exits with 0
llvm-lib -v; \
clang-cl -v; \
lld-link --version; \
# Use clang instead of gcc when compiling and linking binaries targeting the host (eg proc macros, build files)
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100; \
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100; \
update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld 100; \
rustup target add x86_64-pc-windows-msvc; \
rustup component add rust-src; \
# Install xwin to cargo/bin via github release. Note you could also just use `cargo install xwin`.
tar -xzv -f /root/$XWIN_PREFIX.tar.gz -C /usr/local/cargo/bin --strip-components=1 $XWIN_PREFIX/xwin; \
# Splat the CRT and SDK files to /xwin/crt and /xwin/sdk respectively
xwin --accept-license splat --include-debug-libs --output /xwin; \
# Remove unneeded files to reduce image size
apt-get remove -y --auto-remove; \
rm -rf \
.xwin-cache \
/usr/local/cargo/bin/xwin \
/root/$XWIN_PREFIX.tar.gz \
/var/lib/apt/lists/* \
/root/*.key;
# Note that we're using the full target triple for each variable instead of the
# simple CC/CXX/AR shorthands to avoid issues when compiling any C/C++ code for
# build dependencies that need to compile and execute in the host environment
ENV CC_x86_64_pc_windows_msvc="clang-cl" \
CXX_x86_64_pc_windows_msvc="clang-cl" \
AR_x86_64_pc_windows_msvc="llvm-lib" \
# wine can be quite spammy with log messages and they're generally uninteresting
WINEDEBUG="-all" \
# Use wine to run test executables
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUNNER="wine" \
# Note that we only disable unused-command-line-argument here since clang-cl
# doesn't implement all of the options supported by cl, but the ones it doesn't
# are _generally_ not interesting.
CL_FLAGS="-Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc/xwin/crt/include /imsvc/xwin/sdk/include/ucrt /imsvc/xwin/sdk/include/um /imsvc/xwin/sdk/include/shared" \
# Let cargo know what linker to invoke if you haven't already specified it
# in a .cargo/config.toml file
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link" \
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64"
# These are separate since docker/podman won't transform environment variables defined in the same ENV block
ENV CFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS" \
CXXFLAGS_x86_64_pc_windows_msvc="$CL_FLAGS"
# Run wineboot just to setup the default WINEPREFIX so we don't do it every
# container run
RUN wine wineboot --init
WORKDIR /src/dt_p2p

View file

@ -7,3 +7,13 @@ members = [
# [profile.dev.package.backtrace]
# opt-level = 3
[profile.release]
strip = "debuginfo"
# The MSVC toolchain cannot handle LTO properly. Some symbol related to
# panic unwind would always be missing.
# So we use a separate profile for when we can compile with LTO.
[profile.release-lto]
inherits = "release"
lto = true

35
Dockerfile Normal file
View file

@ -0,0 +1,35 @@
FROM dtmt-ci-base-msvc
# Create dummy crates and copy their Cargo.toml, so that dependencies can be cached
RUN set -e; \
cargo new --bin crates/dtmt; \
cargo new --bin crates/dtmm; \
cargo new --lib lib/dtmt-shared; \
cargo new --lib lib/nexusmods; \
cargo new --lib lib/sdk; \
cargo new --lib lib/serde_sjson; \
cargo new --lib lib/steamlocate-rs
COPY Cargo.toml Cargo.lock /src/dtmt/
COPY crates/dtmt/Cargo.toml /src/dtmt/crates/dtmt/
COPY crates/dtmm/Cargo.toml /src/dtmt/crates/dtmm/
COPY lib/dtmt-shared/Cargo.toml /src/dtmt/lib/dtmt-shared/
COPY lib/nexusmods/Cargo.toml /src/dtmt/lib/nexusmods/
COPY lib/sdk/Cargo.toml /src/dtmt/lib/sdk/
COPY lib/serde_sjson/Cargo.toml /src/dtmt/lib/serde_sjson/
COPY lib/steamlocate-rs/Cargo.toml /src/dtmt/lib/steamlocate-rs/
# Crates with build scripts cannot be split that way, but they shouldn't change too often
COPY lib/luajit2-sys /src/dtmt/lib/luajit2-sys
COPY lib/oodle /src/dtmt/lib/oodle
# color-eyre needs to be copied, too, then, as it's used by `oodle`
COPY lib/color-eyre /src/dtmt/lib/color-eyre
COPY --from=dtmt-ci-base-msvc /src/*.lib /src/dtmt/lib/oodle/
RUN cargo build --release --target x86_64-pc-windows-msvc --locked -Zbuild-std
RUN rm -r crates lib
COPY . /src/dtmt
COPY --from=dtmt-ci-base-msvc /src/*.lib /src/dtmt/lib/oodle/
RUN cargo build --release --target x86_64-pc-windows-msvc --frozen -Zbuild-std

12
Justfile Normal file
View file

@ -0,0 +1,12 @@
image_name := 'dt_p2p-ci-base'
ci-build:
docker run --rm -ti --user $(id -u) -v ./:/src/dt_p2p {{ image_name }} cargo --color always build --target x86_64-pc-windows --locked -Zbuild-std
build-image:
docker build -f .ci/Dockerfile .
ci-image:
docker build -t {{ image_name }} .ci/image
docker tag {{ image_name }} registry.sclu1034.dev/{{ image_name }}
docker push registry.sclu1034.dev/{{ image_name }}

View file

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2.144"
[lib]
crate-type = ["cdylib", "lib"]

View file

@ -10,6 +10,7 @@ fn main() {
let bindings = bindgen::Builder::default()
.header(HEADER_NAME)
.rustified_enum("PluginApiID")
.clang_arg("-Istingray_sdk/")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
@ -19,4 +20,10 @@ fn main() {
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
if cfg!(debug_assertions) {
bindings
.write_to_file(out_path.join("../../../bindings.rs"))
.expect("Couldn't write bindings to debug path");
}
}

View file

@ -1,6 +1,53 @@
mod stingray_sdk;
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::c_char;
use std::ffi::CString;
use stingray_sdk::GetApiFunction;
use stingray_sdk::PluginApi;
use stingray_sdk::PluginApiID;
use crate::stingray_sdk::LoggingApi;
const PLUGIN_NAME: &str = "dt_p2p";
#[no_mangle]
pub extern "C" fn get_name() -> *const c_char {
let s = CString::new(PLUGIN_NAME).expect("Failed to create CString from plugin name");
s.as_ptr()
}
#[no_mangle]
pub extern "C" fn setup_game(get_engine_api: GetApiFunction) {
println!("setup_game");
let log = LoggingApi::get(get_engine_api);
log.info(
PLUGIN_NAME,
format!("Hello, world! This is {}!", PLUGIN_NAME),
);
}
#[no_mangle]
pub extern "C" fn shutdown_game() {
println!("shutdown_game");
// log.info(PLUGIN_NAME, format!("Goodbye, world!", PLUGIN_NAME));
}
#[no_mangle]
pub extern "C" fn get_plugin_api(id: PluginApiID) -> *mut PluginApi {
if id == PluginApiID::PLUGIN_API_ID {
let api = PluginApi {
get_name: Some(get_name),
setup_game: Some(setup_game),
shutdown_game: Some(shutdown_game),
..Default::default()
};
Box::into_raw(Box::new(api))
} else {
std::ptr::null_mut()
}
}

View file

@ -4,4 +4,80 @@
#![allow(clippy::type_complexity)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::raw::c_void;
pub use bindings::GetApiFunction;
pub use bindings::PluginApi;
pub use bindings::PluginApiID;
impl std::default::Default for PluginApi {
fn default() -> Self {
Self {
version: 65,
flags: 3,
fn_0: std::ptr::null_mut(),
fn_1: std::ptr::null_mut(),
fn_2: std::ptr::null_mut(),
fn_3: std::ptr::null_mut(),
fn_4: std::ptr::null_mut(),
fn_5: std::ptr::null_mut(),
setup_game: None,
update_game: None,
shutdown_game: None,
fn_9: std::ptr::null_mut(),
fn_10: std::ptr::null_mut(),
fn_11: std::ptr::null_mut(),
get_name: None,
}
}
}
fn get_engine_api(f: GetApiFunction, id: PluginApiID) -> *mut c_void {
let f = if cfg!(debug_assertions) {
f.expect("'GetApiFunction' is always passed by the engine")
} else {
// `Option::unwrap` still generates several instructions in
// optimized code.
unsafe { f.unwrap_unchecked() }
};
unsafe { f(id as u32) }
}
pub struct LoggingApi {
info: unsafe extern "C" fn(*const c_char, *const c_char),
warning: unsafe extern "C" fn(*const c_char, *const c_char),
error: unsafe extern "C" fn(*const c_char, *const c_char),
}
impl LoggingApi {
pub fn get(f: GetApiFunction) -> Self {
let api = unsafe {
let api = get_engine_api(f, PluginApiID::LOGGING_API_ID);
api as *mut bindings::LoggingApi
};
unsafe {
Self {
info: (*api).info.unwrap_unchecked(),
warning: (*api).warning.unwrap_unchecked(),
error: (*api).error.unwrap_unchecked(),
}
}
}
pub fn info(&self, system: impl Into<Vec<u8>>, message: impl Into<Vec<u8>>) {
let f = self.info;
let system = CString::new(system).expect("Invalid CString");
let message = CString::new(message).expect("Invalid CString");
unsafe {
f(system.as_ptr(), message.as_ptr());
}
}
}