diff --git a/README.md b/README.md index 56f2ac4..51c055a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ iOS Swift development IDE for Windows/Linux. Create, build, and test apps without owning a Mac. -Supports Swift 6.1 and the Swift Package Manager. +Supports Swift 6.2 and the Swift Package Manager. ### Demo @@ -30,7 +30,7 @@ Check out the [Getting Started](https://github.com/nab138/CrossCode/wiki#getting ## Features -- Generate a Darwin SDK for linux from a user provided copy of Xcode 16.3 to build the apps +- Generate a Darwin SDK for linux from a user provided copy of Xcode 26 to build the apps - Build apps using swift package manager - Log in with your Apple ID to sign apps - Install apps on device @@ -49,7 +49,7 @@ Please note that I am one person, so development may be slow. If you want to hel ## How it works -- A darwin SDK is generated from a user provided copy of Xcode 16.3 (extracted with [unxip-rs](https://github.com/nab138/unxip-rs)) and darwin tools from [darwin-tools-linux-llvm](https://github.com/xtool-org/darwin-tools-linux-llvm) +- A darwin SDK is generated from a user provided copy of Xcode 26 (extracted with [unxip-rs](https://github.com/nab138/unxip-rs)) and darwin tools from [darwin-tools-linux-llvm](https://github.com/xtool-org/darwin-tools-linux-llvm) - Swift uses the darwin SDK to build an executable which is packaged into an .app bundle. - The code to sign and install apps onto a device has been removed from CrossCode's source and moved to a standalone package, [isideload](https://github.com/nab138/isideload). It was built on a lot of other libraries, so check out its README for more info. diff --git a/bun.lock b/bun.lock index 81e610f..b6520d1 100644 --- a/bun.lock +++ b/bun.lock @@ -50,7 +50,7 @@ "@types/vscode": "^1.102.0", "@vitejs/plugin-react": "^4.2.1", "typescript": "^5.0.2", - "vite": "^7.0.0", + "vite": "^7.1.6", }, }, }, diff --git a/package.json b/package.json index f0ac257..2ffd97f 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,6 @@ "@types/vscode": "^1.102.0", "@vitejs/plugin-react": "^4.2.1", "typescript": "^5.0.2", - "vite": "^7.0.0" + "vite": "^7.1.6" } -} \ No newline at end of file +} diff --git a/src-tauri/src/builder/swift.rs b/src-tauri/src/builder/swift.rs index cd9b8d1..9f9ed7d 100644 --- a/src-tauri/src/builder/swift.rs +++ b/src-tauri/src/builder/swift.rs @@ -159,24 +159,61 @@ impl SwiftBin { } #[tauri::command] -pub fn has_darwin_sdk(toolchain_path: &str) -> bool { +pub fn has_darwin_sdk(toolchain_path: &str) -> String { let swift_bin = SwiftBin::new(toolchain_path); if swift_bin.is_err() { - return false; + return "none".to_string(); } let swift_bin = swift_bin.unwrap(); let output = swift_bin.output(&["sdk", "list"]); if output.is_err() { - return false; + return "none".to_string(); } let output = output.unwrap(); if !output.status.success() { - return false; + return "none".to_string(); + } + let output_str = String::from_utf8_lossy(&output.stdout); + if !output_str.contains("darwin") { + return "none".to_string(); + } + + let output = swift_bin.output(&[ + "sdk", + "configure", + "--show-configuration", + "darwin", + "arm64-apple-ios", + ]); + if output.is_err() { + return "none".to_string(); + } + let output = output.unwrap(); + if !output.status.success() { + return "none".to_string(); } + let output_str = String::from_utf8_lossy(&output.stdout); - output_str.contains("darwin") + let sdk_version = output_str.lines().find_map(|line| { + if line.starts_with("sdkRootPath:") { + let parts: Vec<&str> = line.split('/').collect(); + for part in parts { + if part.starts_with("iPhoneOS") && part.ends_with(".sdk") { + let version = part.trim_start_matches("iPhoneOS").trim_end_matches(".sdk"); + return Some(version.to_string()); + } + } + } + None + }); + + if let Some(version) = sdk_version { + version + } else { + "none".to_string() + } } #[tauri::command] diff --git a/src-tauri/src/lsp_utils.rs b/src-tauri/src/lsp_utils.rs index 1e497b0..b05e4bf 100644 --- a/src-tauri/src/lsp_utils.rs +++ b/src-tauri/src/lsp_utils.rs @@ -32,7 +32,7 @@ pub fn validate_project(project_path: String, toolchain_path: String) -> Project // let config_path = project_path.join(".sourcekit-lsp").join("config.json"); // if !config_path.exists() { // fs::write(config_path, "{ -// \"$schema\": \"https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.1/config.schema.json\", +// \"$schema\": \"https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.2/config.schema.json\", // \"swiftPM\": { // \"swiftSDK\": \"arm64-apple-ios\" // } diff --git a/src-tauri/templates/swiftui/Package.swift b/src-tauri/templates/swiftui/Package.swift index 6ddc581..82c8f8f 100644 --- a/src-tauri/templates/swiftui/Package.swift +++ b/src-tauri/templates/swiftui/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.1 +// swift-tools-version: 6.2 import PackageDescription diff --git a/src-tauri/templates/uikit/Package.swift b/src-tauri/templates/uikit/Package.swift index 6ddc581..82c8f8f 100644 --- a/src-tauri/templates/uikit/Package.swift +++ b/src-tauri/templates/uikit/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.1 +// swift-tools-version: 6.2 import PackageDescription diff --git a/src/components/SDKMenu.tsx b/src/components/SDKMenu.tsx index 35de414..d6cf933 100644 --- a/src/components/SDKMenu.tsx +++ b/src/components/SDKMenu.tsx @@ -7,11 +7,13 @@ import { openUrl } from "@tauri-apps/plugin-opener"; import { installSdkOperation } from "../utilities/operations"; import ErrorIcon from "@mui/icons-material/Error"; import WarningIcon from "@mui/icons-material/Warning"; +import { DARWIN_SDK_VERSION } from "../utilities/constants"; export default () => { const { selectedToolchain, hasDarwinSDK, + darwinSDKVersion, checkSDK, startOperation, isWindows, @@ -86,32 +88,46 @@ export default () => { gap: "var(--padding-md)", }} > - - {isWindowsReady ? ( - hasDarwinSDK ? ( - "Darwin SDK is installed!" +
+ + {isWindowsReady ? ( + hasDarwinSDK ? ( + "Darwin SDK is installed!" + ) : ( + <> + {selectedToolchain ? : } + {selectedToolchain + ? "Darwin SDK is not installed" + : "Select a swift toolchain first"} + + ) ) : ( - <> - {selectedToolchain ? : } - {selectedToolchain - ? "Darwin SDK is not installed" - : "Select a swift toolchain first"} - - ) - ) : ( - "Install WSL and Swift first." + "Install WSL and Swift first." + )} + + {hasDarwinSDK && ( + + {darwinSDKVersion === DARWIN_SDK_VERSION + ? `Version: ${darwinSDKVersion}` + : `Unsupported SDK version (${darwinSDKVersion}). Apps may compile, but you may not be able to use newer features (like liquid glass). Please re-install with Xcode 26.`} + )} - +
{ onClick={(e) => { e.preventDefault(); openUrl( - "https://developer.apple.com/services-account/download?path=/Developer_Tools/Xcode_16.3/Xcode_16.3.xip" + "https://developer.apple.com/services-account/download?path=/Developer_Tools/Xcode_26/Xcode_26_Universal.xip" ); }} > - Download XCode 16.3 + Download XCode 26 + +
+ + + )} ); }; diff --git a/src/pages/Onboarding.tsx b/src/pages/Onboarding.tsx index 950378c..1c298c2 100644 --- a/src/pages/Onboarding.tsx +++ b/src/pages/Onboarding.tsx @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { open } from "@tauri-apps/plugin-shell"; import "./Onboarding.css"; import { Button, Card, CardContent, Divider, Link, Typography } from "@mui/joy"; import { useIDE } from "../utilities/IDEContext"; import logo from "../assets/logo.png"; -import { useNavigate } from "react-router"; +import { useLocation, useNavigate } from "react-router"; import { openUrl } from "@tauri-apps/plugin-opener"; import SwiftMenu from "../components/SwiftMenu"; import SDKMenu from "../components/SDKMenu"; @@ -14,35 +14,16 @@ import { getVersion } from "@tauri-apps/api/app"; import { invoke } from "@tauri-apps/api/core"; import { useToast } from "react-toast-plus"; import { relaunch } from "@tauri-apps/plugin-process"; +import { SWIFT_VERSION_PREFIX } from "../utilities/constants"; export interface OnboardingProps {} export default ({}: OnboardingProps) => { - const { - selectedToolchain, - toolchains, - hasWSL, - isWindows, - openFolderDialog, - hasDarwinSDK, - } = useIDE(); - const [ready, setReady] = useState(false); + const { ready, hasWSL, isWindows, openFolderDialog } = useIDE(); const [version, setVersion] = useState(""); const navigate = useNavigate(); const { addToast } = useToast(); - useEffect(() => { - if (toolchains !== null && isWindows !== null && hasWSL !== null) { - setReady( - selectedToolchain !== null && - (isWindows ? hasWSL : true) && - hasDarwinSDK - ); - } else { - setReady(false); - } - }, [selectedToolchain, toolchains, hasWSL, isWindows, hasDarwinSDK]); - useEffect(() => { const fetchVersion = async () => { const version = await getVersion(); @@ -51,6 +32,18 @@ export default ({}: OnboardingProps) => { fetchVersion(); }, []); + const location = useLocation(); + const darwinSdkRef = useRef(null); + + useEffect(() => { + console.log(location.hash); + if (location.hash === "#install-sdk" && darwinSdkRef.current) { + darwinSdkRef.current.scrollIntoView({ + block: "start", + }); + } + }, [location.hash]); + return (
@@ -250,23 +243,23 @@ export default ({}: OnboardingProps) => { Swift - You will need a Swift 6.1 toolchain to use CrossCode. It is - recommended to install it using swiftly, but you can also install it - manually. + You will need a Swift {SWIFT_VERSION_PREFIX} toolchain to use + CrossCode. It is recommended to install it using swiftly, but you + can also install it manually. - + Darwin SDK CrossCode requires a special swift SDK to build apps for iOS. It can - be generated from a copy of Xcode 16 or later. To install it, + be generated from a copy of Xcode 26 or later. To install it, download Xcode.xip using the link below, click the "Install SDK" button, then select the downloaded file. Note that installing the - SDK will temporarily require a lot of disk space (~10GB) and may + SDK will temporarily require a lot of disk space (~11GB) and may take a while. @@ -275,6 +268,7 @@ export default ({}: OnboardingProps) => {
+
); }; diff --git a/src/utilities/IDEContext.tsx b/src/utilities/IDEContext.tsx index cef4acd..7df3d3c 100644 --- a/src/utilities/IDEContext.tsx +++ b/src/utilities/IDEContext.tsx @@ -27,14 +27,17 @@ import { StoreContext, useStore } from "./StoreContext"; import { Operation, OperationState, OperationUpdate } from "./operations"; import OperationView from "../components/OperationView"; import { UpdateContext } from "./UpdateContext"; +import { isCompatable } from "../components/SwiftMenu"; let isMainWindow = getCurrentWindow().label === "main"; export interface IDEContextType { initialized: boolean; + ready: boolean | null; isWindows: boolean; hasWSL: boolean; hasDarwinSDK: boolean; + darwinSDKVersion: string; hasLimitedRam: boolean; toolchains: ListToolchainResponse | null; selectedToolchain: Toolchain | null; @@ -98,7 +101,9 @@ export const IDEProvider: React.FC<{ null ); const [hasDarwinSDK, setHasDarwinSDK] = useState(false); + const [darwinSDKVersion, setDarwinSDKVersion] = useState("none"); const [initialized, setInitialized] = useState(false); + const [ready, setReady] = useState(null); const [devices, setDevices] = useState([]); const [consoleLines, setConsoleLines] = useState([]); const [selectedToolchain, setSelectedToolchain] = useStore( @@ -157,24 +162,28 @@ export const IDEProvider: React.FC<{ const checkSDK = useCallback(async () => { try { - let result = await invoke("has_darwin_sdk", { + let result = await invoke("has_darwin_sdk", { toolchainPath: selectedToolchain?.path || "", }); - setHasDarwinSDK(result); + setHasDarwinSDK(result != "none"); + setDarwinSDKVersion(result); } catch (e) { console.error("Failed to check for SDK:", e); setHasDarwinSDK(false); + setDarwinSDKVersion("none"); } }, [selectedToolchain]); const scanToolchains = useCallback(() => { - return invoke("get_swiftly_toolchains").then( - (response) => { - if (response) { - setToolchains(response); - } + return new Promise(async (resolve) => { + let response = await invoke( + "get_swiftly_toolchains" + ); + if (response) { + setToolchains(response); + resolve(); } - ); + }); }, []); const locateToolchain = useCallback(async () => { @@ -221,6 +230,31 @@ export const IDEProvider: React.FC<{ }, [isWindows]); useEffect(() => { + if (!initialized) return setReady(null); + if (toolchains !== null && isWindows !== null && hasWSL !== null) { + setReady( + selectedToolchain !== null && + isCompatable(selectedToolchain) && + (isWindows ? hasWSL : true) && + hasDarwinSDK + ); + } else { + setReady(false); + } + }, [ + selectedToolchain, + toolchains, + hasWSL, + isWindows, + hasDarwinSDK, + initialized, + ]); + + let startedInitializing = useRef(false); + + useEffect(() => { + if (startedInitializing.current) return; + startedInitializing.current = true; let initPromises: Promise[] = []; initPromises.push(scanToolchains()); initPromises.push( @@ -234,10 +268,11 @@ export const IDEProvider: React.FC<{ }) ); initPromises.push( - invoke("has_darwin_sdk", { + invoke("has_darwin_sdk", { toolchainPath: selectedToolchain?.path ?? "", }).then((response) => { - setHasDarwinSDK(response as boolean); + setHasDarwinSDK(response != "none"); + setDarwinSDKVersion(response); }) ); initPromises.push( @@ -473,6 +508,8 @@ export const IDEProvider: React.FC<{ selectedDevice, setSelectedDevice, mountDdi, + ready, + darwinSDKVersion, }), [ isWindows, @@ -494,6 +531,8 @@ export const IDEProvider: React.FC<{ selectedDevice, setSelectedDevice, mountDdi, + ready, + darwinSDKVersion, ] ); diff --git a/src/utilities/constants.ts b/src/utilities/constants.ts new file mode 100644 index 0000000..752e37d --- /dev/null +++ b/src/utilities/constants.ts @@ -0,0 +1,2 @@ +export const SWIFT_VERSION_PREFIX = "6.2"; +export const DARWIN_SDK_VERSION = "26.0";