Development app builds (#5255)

* Workflow changes

* Propagate app_identifier via State

* Remove old updater stuff

* Remove patch file

* Remove unused binding

* Fix application loading

* Don't sign windows binaries by default

* Remove the damn emojis

* Figure out where windows artifacts are

* Fix windows artifacts
This commit is contained in:
François-Xavier Talbot
2026-02-02 08:10:23 -05:00
committed by GitHub
parent 3552c8280b
commit e3395a7366
10 changed files with 118 additions and 56 deletions

View File

@@ -19,7 +19,7 @@ on:
sign-windows-binaries:
description: Sign Windows binaries
type: boolean
default: true
default: false
required: false
jobs:
@@ -40,33 +40,48 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: 📥 Check out code
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 🧰 Setup Rust toolchain
- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ''
target: ${{ startsWith(matrix.platform, 'macos') && 'x86_64-apple-darwin' || '' }}
- name: 🧰 Install pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: 🧰 Setup Node.js
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm
- name: 🧰 Install Linux build dependencies
- name: Generate tauri-dev.conf.json
shell: bash
run: |
GIT_HASH=$(git rev-parse --short HEAD)
cat > apps/app/tauri-dev.conf.json <<EOF
{
"productName": "Modrinth App (dev-${GIT_HASH})",
"mainBinaryName": "Modrinth App (dev-${GIT_HASH})",
"identifier": "ModrinthApp-dev-${GIT_HASH}",
"bundle": {
"fileAssociations": []
}
}
EOF
- name: Install Linux build dependencies
if: startsWith(matrix.platform, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -yq libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev
- name: 🧰 Setup Dasel
- name: Setup Dasel
uses: jaxxstorm/action-install-gh-release@v2.1.0
with:
repo: TomWright/dasel
@@ -75,7 +90,7 @@ jobs:
rename-to: ${{ startsWith(matrix.platform, 'windows') && 'dasel.exe' || 'dasel' }}
chmod: 0755
- name: ⚙️ Set application version and environment
- name: Set application version and environment
shell: bash
run: |
APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')"
@@ -86,13 +101,13 @@ jobs:
cp packages/app-lib/.env.prod packages/app-lib/.env
- name: 💨 Setup Turbo cache
- name: Setup Turbo cache
uses: rharkor/caching-for-turbo@v1.8
- name: 🧰 Install dependencies
- name: Install dependencies
run: pnpm install
- name: ✍️ Set up Windows code signing
- name: Set up Windows code signing
if: startsWith(matrix.platform, 'windows')
shell: bash
run: |
@@ -102,8 +117,8 @@ jobs:
dasel delete -f apps/app/tauri-release.conf.json 'bundle.windows.signCommand'
fi
- name: 🔨 Build macOS app
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json
- name: Build macOS app
run: ${{ github.ref == 'refs/heads/main' && 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-dev.conf.json' }}
if: startsWith(matrix.platform, 'macos')
env:
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
@@ -116,19 +131,19 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: 🔨 Build Linux app
run: pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json
- name: Build Linux app
run: ${{ github.ref == 'refs/heads/main' && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json' }}
if: startsWith(matrix.platform, 'ubuntu')
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: 🔨 Build Windows app
- name: Build Windows app
run: |
[System.Convert]::FromBase64String("$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64") | Set-Content -Path signer-client-cert.p12 -AsByteStream
$env:DIGICERT_ONE_SIGNER_CREDENTIALS = "$env:DIGICERT_ONE_SIGNER_API_KEY|$PWD\signer-client-cert.p12|$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD"
$env:JAVA_HOME = "$env:JAVA_HOME_17_X64"
pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis,updater'
${{ github.ref == 'refs/heads/main' && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles "nsis,updater"' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json --verbose --bundles "nsis,updater"' }}
Remove-Item -Path signer-client-cert.p12 -ErrorAction SilentlyContinue
if: startsWith(matrix.platform, 'windows')
env:
@@ -138,7 +153,7 @@ jobs:
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64 }}
DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD: ${{ secrets.DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD }}
- name: 📤 Upload app bundles
- name: Upload app bundles
uses: actions/upload-artifact@v4
with:
name: App bundle (${{ matrix.artifact-target-name }})
@@ -146,7 +161,13 @@ jobs:
target/release/bundle/appimage/Modrinth App_*.AppImage*
target/release/bundle/deb/Modrinth App_*.deb*
target/release/bundle/rpm/Modrinth App-*.rpm*
target/release/bundle/appimage/Modrinth App (dev-*)_*.AppImage*
target/release/bundle/deb/Modrinth App (dev-*)_*.deb*
target/release/bundle/rpm/Modrinth App (dev-*)-*.rpm*
target/universal-apple-darwin/release/bundle/macos/Modrinth App.app.tar.gz*
target/universal-apple-darwin/release/bundle/macos/Modrinth App (dev-*)*.app.tar.gz
target/universal-apple-darwin/release/bundle/dmg/Modrinth App (dev-*)*.dmg
target/universal-apple-darwin/release/bundle/dmg/Modrinth App_*.dmg*
target/release/bundle/nsis/Modrinth App_*-setup.exe*
target/release/bundle/nsis/Modrinth App (dev-*)_*-setup.exe*
target/release/bundle/nsis/Modrinth App_*-setup.nsis.zip*

View File

@@ -38,10 +38,10 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
async fn main() -> theseus::Result<()> {
println!("Starting.");
let _log_guard = theseus::start_logger();
let _log_guard = theseus::start_logger("ModrinthApp");
// Initialize state
State::init().await?;
State::init("ModrinthApp".to_owned()).await?;
let worlds = get_recent_worlds(4, EnumSet::all()).await?;
for world in worlds {

View File

@@ -29,6 +29,9 @@ pub async fn settings_set(settings: Settings) -> Result<()> {
#[tauri::command]
pub async fn cancel_directory_change() -> Result<()> {
settings::cancel_directory_change().await?;
let state = State::get().await?;
let identifier = &state.app_identifier;
settings::cancel_directory_change(identifier).await?;
Ok(())
}

View File

@@ -107,10 +107,12 @@ pub async fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
#[tauri::command]
pub async fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
// failure to get folder just opens filesystem
// (ie: if in debug mode only and launcher_logs never created)
open_path(app, path).await;
if let Some(d) = DirectoryInfo::global_handle_if_ready() {
let path = d.launcher_logs_dir().unwrap_or_default();
// failure to get folder just opens filesystem
// (ie: if in debug mode only and launcher_logs never created)
open_path(app, path).await;
}
}
// Get opening command

View File

@@ -28,7 +28,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
theseus::EventState::init(app.clone()).await?;
tracing::info!("Initializing app state...");
State::init().await?;
State::init(app.config().identifier.clone()).await?;
let state = State::get().await?;
app.asset_protocol_scope()
@@ -110,7 +110,10 @@ fn main() {
RUST_LOG="theseus=trace" {run command}
*/
let _log_guard = theseus::start_logger();
let tauri_context = tauri::generate_context!();
let _log_guard = theseus::start_logger(&tauri_context.config().identifier);
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
@@ -243,7 +246,7 @@ fn main() {
]);
tracing::info!("Initializing app...");
let app = builder.build(tauri::generate_context!());
let app = builder.build(tauri_context);
match app {
Ok(app) => {

View File

@@ -23,10 +23,12 @@ pub async fn set(settings: Settings) -> crate::Result<()> {
}
#[tracing::instrument]
pub async fn cancel_directory_change() -> crate::Result<()> {
pub async fn cancel_directory_change(
app_identifier: &str,
) -> crate::Result<()> {
// This is called to handle state initialization errors due to folder migrations
// failing, so fetching a DB connection pool from `State::get` is not reliable here
let pool = crate::state::db::connect().await?;
let pool = crate::state::db::connect(app_identifier).await?;
let mut settings = Settings::get(&pool).await?;
if let Some(prev_custom_dir) = settings.prev_custom_dir {

View File

@@ -18,7 +18,7 @@
// Handling for the live development logging
// This will log to the console, and will not log to a file
#[cfg(debug_assertions)]
pub fn start_logger() -> Option<()> {
pub fn start_logger(_app_identifier: &str) -> Option<()> {
use tracing_subscriber::prelude::*;
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
@@ -36,7 +36,7 @@ pub fn start_logger() -> Option<()> {
// Handling for the live production logging
// This will log to a file in the logs directory, and will not show any logs in the console
#[cfg(not(debug_assertions))]
pub fn start_logger() -> Option<()> {
pub fn start_logger(app_identifier: &str) -> Option<()> {
use crate::prelude::DirectoryInfo;
use chrono::Local;
use std::fs::OpenOptions;
@@ -44,7 +44,9 @@ pub fn start_logger() -> Option<()> {
use tracing_subscriber::prelude::*;
// Initialize and get logs directory path
let logs_dir = if let Some(d) = DirectoryInfo::launcher_logs_dir() {
let logs_dir = if let Some(d) =
DirectoryInfo::launcher_logs_dir_path(app_identifier)
{
d
} else {
eprintln!("Could not start logger");

View File

@@ -6,12 +6,13 @@ use sqlx::{Pool, Sqlite};
use std::str::FromStr;
use std::time::Duration;
pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
let settings_dir = DirectoryInfo::get_initial_settings_dir().ok_or(
crate::ErrorKind::FSError(
pub(crate) async fn connect(
app_identifier: &str,
) -> crate::Result<Pool<Sqlite>> {
let settings_dir = DirectoryInfo::initial_settings_dir_path(app_identifier)
.ok_or(crate::ErrorKind::FSError(
"Could not find valid config dir".to_string(),
),
)?;
))?;
if !settings_dir.exists() {
crate::util::io::create_dir_all(&settings_dir).await?;

View File

@@ -1,6 +1,7 @@
//! Theseus directory information
use crate::LoadingBarType;
use crate::event::emit::{emit_loading, init_loading};
use crate::state::LAUNCHER_STATE;
use crate::state::{JavaVersion, Profile, Settings};
use crate::util::fetch::IoSemaphore;
use dashmap::DashSet;
@@ -17,24 +18,35 @@ pub const METADATA_FOLDER_NAME: &str = "meta";
pub struct DirectoryInfo {
pub settings_dir: PathBuf, // Base settings directory- app database
pub config_dir: PathBuf, // Base config directory- instances, minecraft downloads, etc. Changeable as a setting.
pub app_identifier: String,
}
impl DirectoryInfo {
pub fn global_handle_if_ready() -> Option<&'static Self> {
LAUNCHER_STATE.get().map(|x| &x.directories)
}
pub fn get_initial_settings_dir(&self) -> Option<PathBuf> {
Self::initial_settings_dir_path(&self.app_identifier)
}
// Get the settings directory
// init() is not needed for this function
pub fn get_initial_settings_dir() -> Option<PathBuf> {
pub fn initial_settings_dir_path(app_identifier: &str) -> Option<PathBuf> {
Self::env_path("THESEUS_CONFIG_DIR")
.or_else(|| Some(dirs::data_dir()?.join("ModrinthApp")))
.or_else(|| Some(dirs::data_dir()?.join(app_identifier)))
}
/// Get all paths needed for Theseus to operate properly
#[tracing::instrument]
pub async fn init(config_dir: Option<String>) -> crate::Result<Self> {
let settings_dir = Self::get_initial_settings_dir().ok_or(
crate::ErrorKind::FSError(
pub async fn init(
config_dir: Option<String>,
app_identifier: &str,
) -> crate::Result<Self> {
let settings_dir = Self::initial_settings_dir_path(app_identifier)
.ok_or(crate::ErrorKind::FSError(
"Could not find valid settings dir".to_string(),
),
)?;
))?;
fs::create_dir_all(&settings_dir).await.map_err(|err| {
crate::ErrorKind::FSError(format!(
@@ -48,6 +60,7 @@ impl DirectoryInfo {
Ok(Self {
settings_dir,
config_dir,
app_identifier: app_identifier.to_owned(),
})
}
@@ -154,8 +167,14 @@ impl DirectoryInfo {
}
#[inline]
pub fn launcher_logs_dir() -> Option<PathBuf> {
Self::get_initial_settings_dir()
pub fn launcher_logs_dir(&self) -> Option<PathBuf> {
self.get_initial_settings_dir()
.map(|d| d.join(LAUNCHER_LOGS_FOLDER_NAME))
}
#[inline]
pub fn launcher_logs_dir_path(app_identifier: &str) -> Option<PathBuf> {
Self::initial_settings_dir_path(app_identifier)
.map(|d| d.join(LAUNCHER_LOGS_FOLDER_NAME))
}
@@ -176,15 +195,15 @@ impl DirectoryInfo {
settings: &mut Settings,
exec: E,
io_semaphore: &IoSemaphore,
app_identifier: &str,
) -> crate::Result<()>
where
E: sqlx::Executor<'a, Database = sqlx::Sqlite> + Copy,
{
let app_dir = DirectoryInfo::get_initial_settings_dir().ok_or(
crate::ErrorKind::FSError(
let app_dir = DirectoryInfo::initial_settings_dir_path(app_identifier)
.ok_or(crate::ErrorKind::FSError(
"Could not find valid config dir".to_string(),
),
)?;
))?;
if let Some(ref prev_custom_dir) = settings.prev_custom_dir {
let prev_dir = PathBuf::from(prev_custom_dir);

View File

@@ -71,6 +71,9 @@ pub struct State {
/// Process manager
pub process_manager: ProcessManager,
/// App identifier string (like com.modrinth.ModrinthApp)
pub app_identifier: String,
/// Friends socket
pub friends_socket: FriendsSocket,
@@ -80,9 +83,9 @@ pub struct State {
}
impl State {
pub async fn init() -> crate::Result<()> {
pub async fn init(app_identifier: String) -> crate::Result<()> {
let state = LAUNCHER_STATE
.get_or_try_init(Self::initialize_state)
.get_or_try_init(move || Self::initialize_state(app_identifier))
.await?;
tokio::task::spawn(async move {
@@ -132,9 +135,11 @@ impl State {
}
#[tracing::instrument]
async fn initialize_state() -> crate::Result<Arc<Self>> {
async fn initialize_state(
app_identifier: String,
) -> crate::Result<Arc<Self>> {
tracing::info!("Connecting to app database");
let pool = db::connect().await?;
let pool = db::connect(&app_identifier).await?;
legacy_converter::migrate_legacy_data(&pool).await?;
@@ -153,9 +158,12 @@ impl State {
&mut settings,
&pool,
&io_semaphore,
&app_identifier,
)
.await?;
let directories = DirectoryInfo::init(settings.custom_dir).await?;
let directories =
DirectoryInfo::init(settings.custom_dir, &app_identifier).await?;
let discord_rpc = DiscordGuard::init()?;
@@ -177,6 +185,7 @@ impl State {
friends_socket,
pool,
file_watcher,
app_identifier,
}))
}
}