Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ dependency/license audit, secret scan, Windows installer build, workflow syntax
check, or installer release automation test fails or is skipped.
The job summary links to the run; failed step logs contain diagnostic details.
The fresh Windows installer is saved as a workflow artifact for seven days.
The installer job also runs Windows PowerShell 5.1 toolchain-manager regression tests
and silent packaged Setup tests. A local mock WinGet covers installed and missing
tools, ordinary package failures, and initialization errors `142`/`0xc0000142` without
downloading toolchains. Packaged tests verify a single PowerShell transcript for all
selected languages, that a failing WinGet is not retried for later languages, and
that bundled-only Setup does not start the toolchain manager.

## Installer releases

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ jobs:
if (-not (Get-ChildItem 'dist/RusticGameEngine-Setup-*.exe' -ErrorAction SilentlyContinue)) {
throw 'No fresh Windows installer was produced.'
}
- name: Test toolchain failures and packaged Setup
run: |
$installer = Get-ChildItem 'dist/RusticGameEngine-Setup-*.exe' | Sort-Object LastWriteTime -Descending | Select-Object -First 1
powershell -NoProfile -ExecutionPolicy Bypass -File ./tools/test-language-toolchains.ps1 -InstallerPath $installer.FullName
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: windows-installer
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/windows-installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
!/Engine/dist/
- name: Build Windows installer
run: ./tools/build-windows-installer.ps1
- name: Test toolchain failures and packaged Setup
run: |
$installer = Get-ChildItem 'dist/RusticGameEngine-Setup-*.exe' | Sort-Object LastWriteTime -Descending | Select-Object -First 1
powershell -NoProfile -ExecutionPolicy Bypass -File ./tools/test-language-toolchains.ps1 -InstallerPath $installer.FullName
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Upload installer
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
Expand Down
24 changes: 24 additions & 0 deletions Engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ removed through Windows Installed Apps. Because it is a desktop application, its
folder is not added to `PATH` by default; pass `-InstallDirectoryOnPath` to select that
installer option by default.

The builder validates the installer script with Inno Setup before compiling the
engine. This preflight omits the unbuilt binaries and disables output; it cannot
produce a distributable installer.

Setup checks and installs all selected optional toolchains in one hidden,
noninteractive PowerShell process as the original user. Already-installed toolchains
are skipped. If PowerShell cannot start, or WinGet returns an initialization failure
(`142` or `0xc0000142`), Setup reports the optional-toolchain failure once and leaves
the installed engine usable with its bundled languages. Setup always saves a log in
`%TEMP%` named `Setup Log ...txt`; the failure dialog gives its exact path. To choose
a persistent location, run `RusticGameEngine-Setup-<version>.exe /LOG="C:\path\setup.log"`.
If PowerShell started, its transcript is in the original user's
`%LOCALAPPDATA%\Rustic Game Engine\logs\toolchain-install.log`. Repair the failing
PowerShell or Microsoft App Installer installation before retrying **Language
Toolchain Manager**. The Setup log records launch errors and exit codes even when
the transcript could not be created.

On Windows, run the toolchain regression tests with
`powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\test-language-toolchains.ps1`.
Pass `-InstallerPath .\dist\RusticGameEngine-Setup-<version>.exe` to also test silent
packaged Setup with all optional languages, simulated WinGet initialization failures,
and bundled languages only. The tests use a local mock WinGet executable and never
download or install external toolchains.

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the decision-record and project-file safety
conventions.

Expand Down
12 changes: 12 additions & 0 deletions Engine/docs/GAMEPLAY_PROGRAMMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,18 @@ installs every selected item through Windows Package Manager. Lua, JavaScript, a
HTML/CSS are bundled. Additional toolchains can be installed later using **Rustic Game
Engine > Language Toolchain Manager** in the Start menu or by rerunning Setup.

Setup checks all selected toolchains in one hidden PowerShell process, skipping
already-installed tools. If PowerShell cannot start or WinGet reports initialization
error `142` or `0xc0000142`, it stops the batch and reports the optional-toolchain
failure once. Rustic remains installed with its bundled languages. These are distinct
error codes; retain the full message and the failing executable when diagnosing them.
The failure dialog shows the Setup log path (`Setup Log ...txt` in `%TEMP%` by default).
Use `/LOG="C:\path\setup.log"` to choose a persistent log location. If PowerShell started,
check the original user's `%LOCALAPPDATA%\Rustic Game Engine\logs\toolchain-install.log`
for package details and WinGet output. Otherwise, the Setup log records the PowerShell
startup failure. Repair the failing PowerShell or Microsoft App Installer installation
before retrying **Language Toolchain Manager**. Setup does not require Windows Terminal.

## HTML/CSS content

HTML/CSS/PHP UI content is confined to the project's `ui/` directory. `.html` and
Expand Down
148 changes: 58 additions & 90 deletions Engine/installer/RusticGameEngine.iss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ WizardStyle=modern
MinVersion=10.0
CloseApplications=yes
RestartApplications=no
SetupLogging=yes

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Expand Down Expand Up @@ -71,12 +72,14 @@ Source: "..\licenses\ogg-BSD-3-Clause.txt"; DestDir: "{app}\licenses"; Flags: ig
Source: "..\licenses\opus-pure-BSD-3-Clause.txt"; DestDir: "{app}\licenses"; Flags: ignoreversion
Source: "..\licenses\opus-pure-ATTRIBUTION.md"; DestDir: "{app}\licenses"; Flags: ignoreversion
Source: "..\licenses\Audio-Decoders-NOTICE.md"; DestDir: "{app}\licenses"; Flags: ignoreversion
#ifndef InstallerPreflight
Source: "..\target\distribution\rustic-luau-host.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\target\distribution\rustic-agent-backend.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\target\distribution\rustic-project-manager.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\target\distribution\rustic-editor.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\target\distribution\rustic-runtime.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\target\distribution\rustic-asset-worker.exe"; DestDir: "{app}"; Flags: ignoreversion
#endif
Source: "..\tools\manage-language-toolchains.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion

[Icons]
Expand All @@ -93,111 +96,76 @@ Filename: "{app}\rustic-agent-backend.exe"; Parameters: "--install-user-integrat
Filename: "{app}\{#AppExeName}"; Description: "Launch {#AppName}"; Flags: nowait postinstall skipifsilent

[Code]
function IsLanguageToolchainInstalled(Language: String): Boolean;
var
ResultCode: Integer;
begin
Result := ExecAsOriginalUser(
ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
'-NoProfile -ExecutionPolicy Bypass -File "' +
ExpandConstant('{app}\tools\manage-language-toolchains.ps1') +
'" -Languages "' + Language + '" -CheckOnly',
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode
) and (ResultCode = 0);
end;

function SelectedLanguageCount(): Integer;
begin
Result := 0;
if WizardIsComponentSelected('languages\python') and not IsLanguageToolchainInstalled('Python') then Result := Result + 1;
if WizardIsComponentSelected('languages\csharp') and not IsLanguageToolchainInstalled('CSharp') then Result := Result + 1;
if WizardIsComponentSelected('languages\ccpp') and not IsLanguageToolchainInstalled('CCpp') then Result := Result + 1;
if WizardIsComponentSelected('languages\java') and not IsLanguageToolchainInstalled('Java') then Result := Result + 1;
if WizardIsComponentSelected('languages\php') and not IsLanguageToolchainInstalled('Php') then Result := Result + 1;
end;

function AnyOptionalLanguageSelected(): Boolean;
procedure AddSelectedLanguage(Component, Language: String; var Languages: String);
begin
Result :=
WizardIsComponentSelected('languages\python') or
WizardIsComponentSelected('languages\csharp') or
WizardIsComponentSelected('languages\ccpp') or
WizardIsComponentSelected('languages\java') or
WizardIsComponentSelected('languages\php');
if WizardIsComponentSelected(Component) then
begin
if Languages <> '' then Languages := Languages + ',';
Languages := Languages + Language;
end;
end;

procedure InstallLanguageToolchain(Language, DisplayName: String;
var CompletedCount: Integer; TotalCount: Integer; var Failures: String);
procedure InstallSelectedLanguageToolchains();
var
Languages: String;
Failure: String;
Started: Boolean;
ResultCode: Integer;
begin
WizardForm.StatusLabel.Caption := Format(
'Installing %s (%d of %d)...', [DisplayName, CompletedCount + 1, TotalCount]);
WizardForm.ProgressGauge.Position := CompletedCount;
Languages := '';
AddSelectedLanguage('languages\python', 'Python', Languages);
AddSelectedLanguage('languages\csharp', 'CSharp', Languages);
AddSelectedLanguage('languages\ccpp', 'CCpp', Languages);
AddSelectedLanguage('languages\java', 'Java', Languages);
AddSelectedLanguage('languages\php', 'Php', Languages);
if Languages = '' then Exit;

WizardForm.StatusLabel.Caption := 'Checking and installing selected gameplay language toolchains...';
WizardForm.Refresh;

if not ExecAsOriginalUser(
ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
'-NoProfile -ExecutionPolicy Bypass -File "' +
ExpandConstant('{app}\tools\manage-language-toolchains.ps1') +
'" -Languages "' + Language + '" -LogPath "' +
ExpandConstant('{localappdata}\Rustic Game Engine\logs\toolchain-install.log') + '"',
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode
) or (ResultCode <> 0) then
begin
if Failures <> '' then Failures := Failures + ', ';
Failures := Failures + DisplayName;
// The manager checks installed packages itself. Launch it once, rather than
// starting a new PowerShell host for each check and each installation.
Log('Selected optional language toolchains: ' + Languages);
Failure := '';
ResultCode := -1;
try
Started := ExecAsOriginalUser(
ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
'-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "' +
ExpandConstant('{app}\tools\manage-language-toolchains.ps1') +
'" -Languages "' + Languages + '" -Setup',
ExpandConstant('{app}'), SW_HIDE, ewWaitUntilTerminated, ResultCode
);
if not Started then
Failure := Format('Could not start PowerShell: Windows error %d (%s).', [ResultCode, SysErrorMessage(ResultCode)])
else if ResultCode <> 0 then
Failure := Format('Language Toolchain Manager exited with code %d (0x%.8x).', [ResultCode, ResultCode]);
except
Failure := 'Could not start Language Toolchain Manager: ' + GetExceptionMessage;
end;

CompletedCount := CompletedCount + 1;
WizardForm.ProgressGauge.Position := CompletedCount;
if Failure <> '' then
begin
Log(Failure);
WizardForm.StatusLabel.Caption := 'Rustic is installed; optional toolchain installation needs attention.';
// Keep the engine usable and report one failure, including host failures
// that occur before PowerShell can create its own transcript.
SuppressibleMsgBox(
'Rustic Game Engine was installed, but optional language toolchain installation did not finish.' + #13#10 + #13#10 +
Failure + #13#10 + #13#10 +
'You can retry from Language Toolchain Manager in the Start menu.' + #13#10 +
'Setup log: ' + ExpandConstant('{log}') + #13#10 +
'Toolchain log (if PowerShell started): %LOCALAPPDATA%\Rustic Game Engine\logs\toolchain-install.log',
mbError, MB_OK, IDOK);
end;
if Failure = '' then
WizardForm.StatusLabel.Caption := 'Selected gameplay language toolchains are installed.';
WizardForm.Refresh;
end;

procedure CurStepChanged(CurStep: TSetupStep);
var
CompletedCount: Integer;
TotalCount: Integer;
Failures: String;
begin
if CurStep = ssPostInstall then
begin
TotalCount := SelectedLanguageCount();
if TotalCount > 0 then
begin
CompletedCount := 0;
Failures := '';
WizardForm.ProgressGauge.Min := 0;
WizardForm.ProgressGauge.Max := TotalCount;
WizardForm.ProgressGauge.Position := 0;

if WizardIsComponentSelected('languages\python') and not IsLanguageToolchainInstalled('Python') then
InstallLanguageToolchain('Python', 'Python 3.14', CompletedCount, TotalCount, Failures);
if WizardIsComponentSelected('languages\csharp') and not IsLanguageToolchainInstalled('CSharp') then
InstallLanguageToolchain('CSharp', '.NET SDK 10 for C#', CompletedCount, TotalCount, Failures);
if WizardIsComponentSelected('languages\ccpp') and not IsLanguageToolchainInstalled('CCpp') then
InstallLanguageToolchain('CCpp', 'LLVM for C and C++', CompletedCount, TotalCount, Failures);
if WizardIsComponentSelected('languages\java') and not IsLanguageToolchainInstalled('Java') then
InstallLanguageToolchain('Java', 'Microsoft OpenJDK 21', CompletedCount, TotalCount, Failures);
if WizardIsComponentSelected('languages\php') and not IsLanguageToolchainInstalled('Php') then
InstallLanguageToolchain('Php', 'PHP 8.4 CLI', CompletedCount, TotalCount, Failures);

WizardForm.StatusLabel.Caption := 'Finished installing gameplay language toolchains.';
WizardForm.Refresh;

if Failures <> '' then
MsgBox(Format(
'Rustic Game Engine was installed, but these optional language toolchains could not be installed: %s.' + #13#10 + #13#10 +
'You can retry from Language Toolchain Manager in the Start menu.' + #13#10 +
'Details: %s', [Failures, ExpandConstant('{localappdata}\Rustic Game Engine\logs\toolchain-install.log')]),
mbError, MB_OK);
end;
if (TotalCount = 0) and AnyOptionalLanguageSelected() then
begin
WizardForm.StatusLabel.Caption := 'Selected gameplay language toolchains are already installed.';
WizardForm.Refresh;
end;
end;
if CurStep = ssPostInstall then InstallSelectedLanguageToolchains();
end;

function NeedsAddPath(Param: string): Boolean;
Expand Down
10 changes: 10 additions & 0 deletions Engine/tools/build-windows-installer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ if (-not (Test-Path -LiteralPath $iscc -PathType Leaf)) {

Push-Location $workspaceRoot
try {
# Compile Setup's sections and Pascal code before the expensive Cargo build.
# /O- prevents producing a preflight installer; only unbuilt binaries are omitted.
Write-Host 'Validating the Windows installer script...'
& $iscc '/O-' '/DInstallerPreflight=1' 'installer\RusticGameEngine.iss'
if ($LASTEXITCODE -ne 0) { throw "Inno Setup preflight failed with exit code $LASTEXITCODE." }

Write-Host 'Testing the toolchain manager with Windows PowerShell...'
& powershell -NoProfile -ExecutionPolicy Bypass -File 'tools\test-language-toolchains.ps1'
if ($LASTEXITCODE -ne 0) { throw "Toolchain manager tests failed with exit code $LASTEXITCODE." }

Write-Host 'Building bundled Luau API host...'
& $cargo build --locked --manifest-path 'apps/luau-host/Cargo.toml' --profile distribution --target-dir 'target'
if ($LASTEXITCODE -ne 0) { throw "Luau host build failed with exit code $LASTEXITCODE." }
Expand Down
Loading
Loading