1
0

2 Achegas 0ed0eeb270 ... 589f29ff6d

Autor SHA1 Mensaxe Data
  yanxuyao 589f29ff6d [*] 文档调整 hai 1 mes
  yanxuyao cf3ebe5407 [*] workspace构建 hai 1 mes

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@ xcuserdata/
 ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
 build/
 DerivedData/
+.deriveddata/
 *.moved-aside
 *.pbxuser
 !default.pbxuser

+ 6 - 6
QGVAPlayer/QGVAPlayer.xcodeproj/project.pbxproj

@@ -689,7 +689,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -745,7 +745,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				SDKROOT = iphoneos;
@@ -761,7 +761,7 @@
 				CODE_SIGN_IDENTITY = "";
 				CODE_SIGN_STYLE = Automatic;
 				DEFINES_MODULE = YES;
-				DEVELOPMENT_TEAM = 6W55574XBS;
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -772,7 +772,7 @@
 					"@executable_path/Frameworks",
 					"@loader_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayer;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
 				SKIP_INSTALL = YES;
 				TARGETED_DEVICE_FAMILY = "1,2";
@@ -785,7 +785,7 @@
 				CODE_SIGN_IDENTITY = "";
 				CODE_SIGN_STYLE = Automatic;
 				DEFINES_MODULE = YES;
-				DEVELOPMENT_TEAM = 6W55574XBS;
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -796,7 +796,7 @@
 					"@executable_path/Frameworks",
 					"@loader_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayer;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
 				SKIP_INSTALL = YES;
 				TARGETED_DEVICE_FAMILY = "1,2";

+ 9 - 9
QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj/project.pbxproj

@@ -105,7 +105,7 @@
 		63BAD43522F09D6800EAD4C4 /* QGVAPlayer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = QGVAPlayer.xcodeproj; path = ../../QGVAPlayer/QGVAPlayer.xcodeproj; sourceTree = "<group>"; };
 		BA964B5D268315E6003265F2 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
 		BA964B6126831AF1003265F2 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
-		BA964B6426831BB6003265F2 /* QGVAPlayer.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = QGVAPlayer.podspec; path = ../../QGVAPlayer.podspec; sourceTree = "<group>"; };
+		BA964B6426831BB6003265F2 /* QGVAPlayer.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = QGVAPlayer.podspec; path = ../QGVAPlayer.podspec; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -490,7 +490,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -543,7 +543,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				SDKROOT = iphoneos;
@@ -557,18 +557,18 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_STYLE = Automatic;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-				DEVELOPMENT_TEAM = 6W55574XBS;
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = "$(inherited)";
 				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../QGVAPlayer/QGVAPlayer/**";
 				INFOPLIST_FILE = QGVAPlayerDemo/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
 				LIBRARY_SEARCH_PATHS = "$(inherited)";
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayerDemo;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
@@ -579,18 +579,18 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = 6W55574XBS;
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = "$(inherited)";
 				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../QGVAPlayer/QGVAPlayer/**";
 				INFOPLIST_FILE = QGVAPlayerDemo/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
 				LIBRARY_SEARCH_PATHS = "$(inherited)";
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayerDemo;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};

+ 78 - 0
QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj/xcshareddata/xcschemes/QGVAPlayerDemo.xcscheme

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "2640"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "63BAD38222F09B1200EAD4C4"
+               BuildableName = "QGVAPlayerDemo.app"
+               BlueprintName = "QGVAPlayerDemo"
+               ReferencedContainer = "container:QGVAPlayerDemo.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "63BAD38222F09B1200EAD4C4"
+            BuildableName = "QGVAPlayerDemo.app"
+            BlueprintName = "QGVAPlayerDemo"
+            ReferencedContainer = "container:QGVAPlayerDemo.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "63BAD38222F09B1200EAD4C4"
+            BuildableName = "QGVAPlayerDemo.app"
+            BlueprintName = "QGVAPlayerDemo"
+            ReferencedContainer = "container:QGVAPlayerDemo.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 4 - 4
QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj/project.pbxproj

@@ -387,14 +387,14 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = "";
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../QGVAPlayer/QGVAPlayer/**";
 				INFOPLIST_FILE = QGVAPlayerDemoSwift/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayerDemoSwift;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/QGVAPlayerDemoSwift/QGVAPlayer-Bridging-Header.h";
@@ -410,14 +410,14 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				DEVELOPMENT_TEAM = "";
+				DEVELOPMENT_TEAM = 5H8D98R72W;
 				HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../QGVAPlayer/QGVAPlayer/**";
 				INFOPLIST_FILE = QGVAPlayerDemoSwift/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.tencent.QGVAPlayerDemoSwift;
+				PRODUCT_BUNDLE_IDENTIFIER = com.gami.vap;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/QGVAPlayerDemoSwift/QGVAPlayer-Bridging-Header.h";

+ 78 - 0
QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj/xcshareddata/xcschemes/QGVAPlayerDemoSwift.xcscheme

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "2640"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "BA964B7026832138003265F2"
+               BuildableName = "QGVAPlayerDemoSwift.app"
+               BlueprintName = "QGVAPlayerDemoSwift"
+               ReferencedContainer = "container:QGVAPlayerDemoSwift.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "BA964B7026832138003265F2"
+            BuildableName = "QGVAPlayerDemoSwift.app"
+            BlueprintName = "QGVAPlayerDemoSwift"
+            ReferencedContainer = "container:QGVAPlayerDemoSwift.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "BA964B7026832138003265F2"
+            BuildableName = "QGVAPlayerDemoSwift.app"
+            BlueprintName = "QGVAPlayerDemoSwift"
+            ReferencedContainer = "container:QGVAPlayerDemoSwift.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 13 - 0
QGVAPlayerDev.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:QGVAPlayer/QGVAPlayer.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj">
+   </FileRef>
+</Workspace>

+ 48 - 0
SDK_DEV_SETUP.md

@@ -0,0 +1,48 @@
+# QGVAPlayer SDK 开发环境(本仓库)
+
+## 1. 项目结构(开发视角)
+- `QGVAPlayer/QGVAPlayer.xcodeproj`: SDK 主工程(`QGVAPlayer` framework)
+- `QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj`: ObjC Demo(子工程依赖 SDK)
+- `QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj`: Swift Demo(子工程依赖 SDK)
+- `QGVAPlayer.podspec`: Pod 发布配置
+
+两个 Demo 都是直接依赖本地 `QGVAPlayer.xcodeproj`,适合边改 SDK 边验证。
+
+## 2. 一次性环境准备
+在仓库根目录执行:
+
+```bash
+bash scripts/dev/setup_sdk_dev.sh
+```
+
+脚本会做:
+- 检查 Xcode / `xcrun`
+- 检查并尝试安装 `MetalToolchain`
+- 准备仓库内 `./.deriveddata` 构建目录
+- 校验三个 xcodeproj 可被识别
+
+## 3. 日常开发流程(推荐)
+1. 修改 SDK 源码:`QGVAPlayer/QGVAPlayer/**/*`
+2. 编译回归 Demo:
+```bash
+bash scripts/dev/build_demo.sh all
+```
+
+可选只编译单个 Demo:
+```bash
+bash scripts/dev/build_demo.sh objc
+bash scripts/dev/build_demo.sh swift
+```
+
+3. 在 Xcode 中运行 App 验证:
+- 推荐直接打开根目录 workspace:`QGVAPlayerDev.xcworkspace`(可同时看到 SDK + ObjC Demo + Swift Demo)
+- 在 workspace 中使用共享 Scheme:`QGVAPlayerDemo` / `QGVAPlayerDemoSwift`
+- 不建议优先单独打开 `*.xcodeproj`(会看不到完整联调上下文)
+
+## 4. 备注
+- 工程内已调整:`QGVAPlayer` 与 `QGVAPlayerDemo` 的 `IPHONEOS_DEPLOYMENT_TARGET` 为 `12.0`(兼容新 Xcode)。
+- `build_demo.sh` 里已做命令行覆盖:ObjC Demo 使用 `12.0`(规避 `libarclite`),Swift Demo 使用 `14.5`(匹配 SceneDelegate API)。
+- 如果命令行提示 `metal` 不可用,先执行一次:
+```bash
+xcodebuild -downloadComponent MetalToolchain
+```

+ 51 - 0
scripts/dev/build_demo.sh

@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+DERIVED_BASE="$ROOT_DIR/.deriveddata"
+TARGET="${1:-all}"
+
+build_objc() {
+  xcodebuild \
+    -project "$ROOT_DIR/QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj" \
+    -scheme "QGVAPlayerDemo" \
+    -configuration Debug \
+    -sdk iphoneos \
+    -destination 'generic/platform=iOS' \
+    -derivedDataPath "$DERIVED_BASE/objc" \
+    IPHONEOS_DEPLOYMENT_TARGET=12.0 \
+    CODE_SIGNING_ALLOWED=NO \
+    build
+}
+
+build_swift() {
+  xcodebuild \
+    -project "$ROOT_DIR/QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj" \
+    -scheme "QGVAPlayerDemoSwift" \
+    -configuration Debug \
+    -sdk iphoneos \
+    -destination 'generic/platform=iOS' \
+    -derivedDataPath "$DERIVED_BASE/swift" \
+    IPHONEOS_DEPLOYMENT_TARGET=14.5 \
+    CODE_SIGNING_ALLOWED=NO \
+    build
+}
+
+mkdir -p "$DERIVED_BASE/objc" "$DERIVED_BASE/swift"
+
+case "$TARGET" in
+  objc)
+    build_objc
+    ;;
+  swift)
+    build_swift
+    ;;
+  all)
+    build_objc
+    build_swift
+    ;;
+  *)
+    echo "Usage: $0 [objc|swift|all]" >&2
+    exit 1
+    ;;
+esac

+ 50 - 0
scripts/dev/setup_sdk_dev.sh

@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+DERIVED_BASE="$ROOT_DIR/.deriveddata"
+
+log() {
+  printf '[setup] %s\n' "$1"
+}
+
+require_cmd() {
+  if ! command -v "$1" >/dev/null 2>&1; then
+    echo "Missing required command: $1" >&2
+    exit 1
+  fi
+}
+
+require_cmd xcodebuild
+require_cmd xcrun
+
+log "Using developer dir: $(xcode-select -p)"
+log "Xcode version: $(xcodebuild -version | tr '\n' ' ' | sed 's/  */ /g')"
+
+if xcrun --find metal >/dev/null 2>&1; then
+  METAL_BIN="$(xcrun --find metal)"
+  if "$METAL_BIN" -v >/dev/null 2>&1; then
+    log "Metal toolchain is ready: $METAL_BIN"
+  else
+    log "Metal toolchain is not ready, downloading component..."
+    xcodebuild -downloadComponent MetalToolchain
+    if ! "$METAL_BIN" -v >/dev/null 2>&1; then
+      echo "Metal toolchain is still unavailable. Run Xcode once and retry." >&2
+      exit 1
+    fi
+    log "Metal toolchain installed successfully."
+  fi
+else
+  log "metal command is missing, downloading component..."
+  xcodebuild -downloadComponent MetalToolchain
+fi
+
+mkdir -p "$DERIVED_BASE/objc" "$DERIVED_BASE/swift"
+log "DerivedData folders prepared under $DERIVED_BASE"
+
+xcodebuild -project "$ROOT_DIR/QGVAPlayer/QGVAPlayer.xcodeproj" -list >/dev/null
+xcodebuild -project "$ROOT_DIR/QGVAPlayerDemo/QGVAPlayerDemo.xcodeproj" -list >/dev/null
+xcodebuild -project "$ROOT_DIR/QGVAPlayerDemoSwift/QGVAPlayerDemoSwift.xcodeproj" -list >/dev/null
+
+log "SDK dev environment check complete."
+log "Next: run scripts/dev/build_demo.sh all"