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
11 changes: 8 additions & 3 deletions .github/docker/headless/linux/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ RUN apt-get update && \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY ./HeadlessLinux64/. .
RUN chmod +x ./HeadlessLinuxServer.x86_64
ARG ARTIFACT_DIR=HeadlessLinux64
COPY ./${ARTIFACT_DIR}/. .
RUN set -eux; \
exe="$(find . -maxdepth 1 -type f \( -name 'HeadlessLinuxServer.x86_64' -o -name 'HeadlessLinuxServer.arm64' -o -name 'HeadlessLinuxServer' \) | head -n1)"; \
test -n "$exe"; \
chmod +x "$exe"; \
ln -sf "$(basename "$exe")" /app/HeadlessLinuxServer

ENTRYPOINT ["./HeadlessLinuxServer.x86_64"]
ENTRYPOINT ["./HeadlessLinuxServer"]
58 changes: 58 additions & 0 deletions .github/scripts/install_unity_package_latest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
from __future__ import annotations

import json
import sys
import urllib.request
from pathlib import Path


UNITY_REGISTRY_URL = "https://packages.unity.com"


def fetch_latest_version(package_name: str) -> str:
url = f"{UNITY_REGISTRY_URL}/{package_name}"
with urllib.request.urlopen(url, timeout=30) as response:
package_info = json.load(response)

latest = package_info.get("dist-tags", {}).get("latest")
if not latest:
raise RuntimeError(f"Package {package_name} did not expose dist-tags.latest at {url}")

return latest


def update_manifest_dependency(manifest_path: Path, package_name: str, version: str) -> None:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
dependencies = manifest.setdefault("dependencies", {})
previous_version = dependencies.get(package_name)
dependencies[package_name] = version
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")

if previous_version is None:
print(f"Added {package_name}@{version} to {manifest_path}")
else:
print(f"Updated {package_name}: {previous_version} -> {version} in {manifest_path}")


def main() -> int:
if len(sys.argv) != 3:
print("Usage: install_unity_package_latest.py <project_root> <package_name>", file=sys.stderr)
return 1

project_root = Path(sys.argv[1]).resolve()
package_name = sys.argv[2].strip()
manifest_path = project_root / "Packages" / "manifest.json"

if not manifest_path.exists():
print(f"Manifest not found: {manifest_path}", file=sys.stderr)
return 1

latest_version = fetch_latest_version(package_name)
print(f"Resolved latest {package_name} version: {latest_version}")
update_manifest_dependency(manifest_path, package_name, latest_version)
return 0


if __name__ == "__main__":
raise SystemExit(main())
103 changes: 88 additions & 15 deletions .github/workflows/build-docker-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
run: 'echo "Image pushed with digest: ${{ steps.build.outputs.digest }}"'

headless-build:
name: Build headless for ${{ matrix.targetPlatform }}
name: Build headless for ${{ matrix.targetPlatform }}${{ matrix.linuxArchitecture && format(' - {0}', matrix.linuxArchitecture) || '' }}
timeout-minutes: 100
runs-on: ${{ matrix.buildPlatform }}
permissions:
Expand All @@ -103,14 +103,28 @@ jobs:
- targetPlatform: StandaloneLinux64
buildPlatform: ubuntu-latest
buildName: HeadlessLinuxServer
buildOutput: LinuxServer
artifactName: LinuxHeadless
buildOutput: LinuxServerAmd64
artifactName: LinuxHeadlessAmd64
headlessFolderName: HeadlessLinux64
linuxArchitecture: X64
customParameters: -standaloneBuildSubtarget Server -linuxArchitecture X64
buildMethod: BasisHeadlessBuild.BuildLinuxServer
- targetPlatform: StandaloneLinux64
buildPlatform: ubuntu-latest
buildName: HeadlessLinuxServer
buildOutput: LinuxServerArm64
artifactName: LinuxHeadlessArm64
headlessFolderName: HeadlessLinuxArm64
linuxArchitecture: ARM64
customParameters: -standaloneBuildSubtarget Server -linuxArchitecture ARM64
buildMethod: BasisHeadlessBuild.BuildLinuxServer
- targetPlatform: StandaloneWindows64
buildPlatform: ubuntu-latest
buildName: HeadlessWindowsServer
buildOutput: WindowsServer
artifactName: WindowsHeadless
headlessFolderName: HeadlessWindows64
customParameters: -standaloneBuildSubtarget Server
buildMethod: BasisHeadlessBuild.BuildWindowsServer
needs: [check-secret]
if: needs.check-secret.outputs.secret-is-set == 'true'
Expand Down Expand Up @@ -144,6 +158,11 @@ jobs:
shell: bash
run: |
python3 .github/scripts/sanitize_headless_ci.py "${projectPath}"
- name: "Install Linux Arm64 Unity SDK package"
if: matrix.targetPlatform == 'StandaloneLinux64' && matrix.linuxArchitecture == 'ARM64'
shell: bash
run: |
python3 .github/scripts/install_unity_package_latest.py "${projectPath}" "com.unity.sdk.linux-arm64"
- name: "Disable OpenVR editor setup for Windows headless builds"
if: runner.os == 'Windows'
shell: pwsh
Expand Down Expand Up @@ -178,7 +197,7 @@ jobs:
with:
buildName: ${{ matrix.buildName }}
buildMethod: ${{ matrix.buildMethod }}
customParameters: -standaloneBuildSubtarget Server
customParameters: ${{ matrix.customParameters }}
projectPath: ${{ env.projectPath }}
targetPlatform: ${{ matrix.targetPlatform }}
versioning: None
Expand All @@ -194,15 +213,15 @@ jobs:
if: matrix.targetPlatform == 'StandaloneLinux64'
shell: bash
run: |
sudo mv "build/${{ matrix.buildOutput }}/StandaloneLinux64" "build/${{ matrix.buildOutput }}/HeadlessLinux64"
sudo chown -R "$(id -u):$(id -g)" "build/${{ matrix.buildOutput }}/HeadlessLinux64"
sudo mv "build/${{ matrix.buildOutput }}/StandaloneLinux64" "build/${{ matrix.buildOutput }}/${{ matrix.headlessFolderName }}"
sudo chown -R "$(id -u):$(id -g)" "build/${{ matrix.buildOutput }}/${{ matrix.headlessFolderName }}"
- name: "Write headless config.xml into artifact"
shell: bash
run: |
if [ "${{ matrix.targetPlatform }}" = "StandaloneWindows64" ]; then
data_dir="build/${{ matrix.buildOutput }}/HeadlessWindows64/HeadlessWindowsServer_Data"
data_dir="build/${{ matrix.buildOutput }}/${{ matrix.headlessFolderName }}/HeadlessWindowsServer_Data"
else
data_dir="build/${{ matrix.buildOutput }}/HeadlessLinux64/HeadlessLinuxServer_Data"
data_dir="build/${{ matrix.buildOutput }}/${{ matrix.headlessFolderName }}/HeadlessLinuxServer_Data"
fi

mkdir -p "$data_dir"
Expand Down Expand Up @@ -239,20 +258,34 @@ jobs:
path: build/${{ matrix.buildOutput }}

headless-docker-linux:
name: Build & push headless Linux image
name: Build & push headless Linux image (${{ matrix.archSuffix }})
runs-on: ubuntu-latest
needs: [headless-build]
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- artifactName: LinuxHeadlessAmd64
dockerPlatform: linux/amd64
archSuffix: amd64
artifactDir: HeadlessLinux64
- artifactName: LinuxHeadlessArm64
dockerPlatform: linux/arm64
archSuffix: arm64
artifactDir: HeadlessLinuxArm64
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download Linux headless artifact
uses: actions/download-artifact@v4
with:
name: LinuxHeadless
name: ${{ matrix.artifactName }}
path: headless
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
Expand All @@ -268,25 +301,65 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.HEADLESS_IMAGE_NAME }}
tags: |
type=raw,value=nightly-linux,enable=${{ github.ref == 'refs/heads/developer' }}
type=raw,value=latest-linux,enable=${{ github.ref == 'refs/heads/long-term-support' }}
type=sha,prefix={{branch}}-,suffix=-linux,format=short
type=ref,event=branch,suffix=-linux
type=raw,value=nightly-linux-${{ matrix.archSuffix }},enable=${{ github.ref == 'refs/heads/developer' }}
type=raw,value=latest-linux-${{ matrix.archSuffix }},enable=${{ github.ref == 'refs/heads/long-term-support' }}
type=sha,prefix={{branch}}-,suffix=-linux-${{ matrix.archSuffix }},format=short
type=ref,event=branch,suffix=-linux-${{ matrix.archSuffix }}
- name: Build and push headless Linux image
uses: docker/build-push-action@v5
id: build
with:
context: ./headless
file: ./.github/docker/headless/linux/Dockerfile
platforms: linux/amd64
platforms: ${{ matrix.dockerPlatform }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
ARTIFACT_DIR=${{ matrix.artifactDir }}
- name: Image digest
run: 'echo "Headless Linux image pushed with digest: ${{ steps.build.outputs.digest }}"'

headless-docker-linux-manifest:
name: Publish headless Linux manifest
runs-on: ubuntu-latest
needs: [headless-docker-linux]
permissions:
contents: read
packages: write
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.HEADLESS_IMAGE_NAME }}
tags: |
type=raw,value=nightly-linux,enable=${{ github.ref == 'refs/heads/developer' }}
type=raw,value=latest-linux,enable=${{ github.ref == 'refs/heads/long-term-support' }}
type=sha,prefix={{branch}}-,suffix=-linux,format=short
type=ref,event=branch,suffix=-linux
- name: Create Linux multi-arch manifest
shell: bash
run: |
while IFS= read -r tag; do
[ -n "$tag" ] || continue
docker buildx imagetools create \
--tag "$tag" \
"${tag}-amd64" \
"${tag}-arm64"
done <<< "${{ steps.meta.outputs.tags }}"

headless-docker-windows:
name: Build & push headless Windows image
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public static async Task<BeeResult<BeeDownloadResult>> DownloadBEEEx(string url,

if (platformSectionData == null || platformSectionData.Length == 0)
{
return BeeResult<BeeDownloadResult>.Fail($"DownloadBEEEx: No platform-matching section found in connector. Platform Request was {Application.platform} using platform keys -> {BasisBundleConnector.DebugOfPlatforms()}");
return BeeResult<BeeDownloadResult>.Fail($"DownloadBEEEx: No platform-matching section found in connector. Platform Request was {Application.platform}. {BasisBundleConnector.DebugOfPlatforms(connector)}");
}

// 5) Write local .bee (Int32 header + connector + section)
Expand Down
26 changes: 24 additions & 2 deletions Basis/Packages/com.basis.sdk/Scripts/BasisBundleConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,31 @@ public static bool IsPlatform(BasisBundleGenerated platformBundle)
{ Enum.GetName(typeof(BuildTarget), BuildTarget.StandaloneLinux64), new HashSet<RuntimePlatform> { RuntimePlatform.LinuxEditor, RuntimePlatform.LinuxPlayer, RuntimePlatform.LinuxServer } },
{ Enum.GetName(typeof(BuildTarget), BuildTarget.iOS), new HashSet<RuntimePlatform> { RuntimePlatform.IPhonePlayer } }
};
public static string DebugOfPlatforms()
public static string DebugOfPlatforms(BasisBundleConnector connector = null)
{
return string.Join("\n", platformMappings.Select(kvp => $" {kvp.Key} => [{string.Join(", ", kvp.Value)}]"));
string bundlePlatforms = " <unknown>";

if (connector != null)
{
if (connector.BasisBundleGenerated == null || connector.BasisBundleGenerated.Length == 0)
{
bundlePlatforms = " <none>";
}
else
{
var platforms = connector.BasisBundleGenerated
.Where(bundle => bundle != null)
.Select(bundle => string.IsNullOrWhiteSpace(bundle.Platform) ? "<empty>" : bundle.Platform)
.ToArray();

bundlePlatforms = platforms.Length == 0
? " <none>"
: string.Join("\n", platforms.Select(platform => $" {platform}"));
}
}

string knownPlatforms = string.Join("\n", platformMappings.Select(kvp => $" {kvp.Key} => [{string.Join(", ", kvp.Value)}]"));
return $"Bundle Generated Platforms:\n{bundlePlatforms}\nKnown Platform Mappings:\n{knownPlatforms}";
}
public enum BuildTarget
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ private static void BuildServer(BuildTarget target)
string buildName = GetArgument("customBuildName") ?? Path.GetFileNameWithoutExtension(buildPath);
string projectPath = GetArgument("projectPath") ?? Directory.GetCurrentDirectory();
string standaloneSubtargetArg = GetArgument("standaloneBuildSubtarget") ?? "Server";
string linuxArchitectureArg = GetArgument("linuxArchitecture");

Debug.Log($"[BasisHeadlessBuild] Starting {target} build");
Debug.Log($"[BasisHeadlessBuild] projectPath={projectPath}");
Expand All @@ -38,6 +39,7 @@ private static void BuildServer(BuildTarget target)
Debug.Log($"[BasisHeadlessBuild] activeBuildTarget(before)={EditorUserBuildSettings.activeBuildTarget}");
Debug.Log($"[BasisHeadlessBuild] activeBuildTargetGroup(before)={BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)}");
Debug.Log($"[BasisHeadlessBuild] standaloneBuildSubtarget(arg)={standaloneSubtargetArg}");
Debug.Log($"[BasisHeadlessBuild] linuxArchitecture(arg)={linuxArchitectureArg ?? "<default>"}");

BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(target);
if (!BuildPipeline.IsBuildTargetSupported(targetGroup, target))
Expand All @@ -53,6 +55,12 @@ private static void BuildServer(BuildTarget target)

StandaloneBuildSubtarget standaloneSubtarget = ParseStandaloneSubtarget(standaloneSubtargetArg);
EditorUserBuildSettings.standaloneBuildSubtarget = standaloneSubtarget;
if (target == BuildTarget.StandaloneLinux64)
{
int linuxArchitecture = ParseLinuxArchitecture(linuxArchitectureArg);
PlayerSettings.SetArchitecture(targetGroup, linuxArchitecture);
Debug.Log($"[BasisHeadlessBuild] Linux architecture(set)={linuxArchitecture}");
}
Debug.Log($"[BasisHeadlessBuild] activeBuildTarget(after)={EditorUserBuildSettings.activeBuildTarget}");
Debug.Log($"[BasisHeadlessBuild] standaloneBuildSubtarget(set)={EditorUserBuildSettings.standaloneBuildSubtarget}");

Expand Down Expand Up @@ -166,6 +174,25 @@ private static StandaloneBuildSubtarget ParseStandaloneSubtarget(string value)
return StandaloneBuildSubtarget.Server;
}

private static int ParseLinuxArchitecture(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return 0;
}

switch (value.Trim().ToUpperInvariant())
{
case "ARM64":
return 1;
case "UNIVERSAL":
return 2;
case "X64":
default:
return 0;
}
}

private static string RequireArgument(string name)
{
string value = GetArgument(name);
Expand Down
Loading