diff --git a/config/pkg.target.json b/config/pkg.target.json index b5e4a7c8..8f47502b 100644 --- a/config/pkg.target.json +++ b/config/pkg.target.json @@ -82,7 +82,10 @@ }, "zig": { "type": "target", - "artifact": "zig" + "artifact": "zig", + "static-bins": [ + "{pkg_root_path}/zig/zig" + ] }, "nasm": { "type": "target", diff --git a/src/Package/Artifact/zig.php b/src/Package/Artifact/zig.php new file mode 100644 index 00000000..2ac7b454 --- /dev/null +++ b/src/Package/Artifact/zig.php @@ -0,0 +1,98 @@ +executeCurl('https://ziglang.org/download/index.json', retries: $downloader->getRetry()); + $index_json = json_decode($index_json ?: '', true); + $latest_version = null; + foreach ($index_json as $version => $data) { + $latest_version = $version; + break; + } + + if (!$latest_version) { + throw new DownloaderException('Could not determine latest Zig version'); + } + $zig_arch = SystemTarget::getTargetArch(); + $zig_os = match (SystemTarget::getTargetOS()) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'Linux' => 'linux', + default => throw new DownloaderException('Unsupported OS for Zig: ' . SystemTarget::getTargetOS()), + }; + $platform_key = "{$zig_arch}-{$zig_os}"; + if (!isset($index_json[$latest_version][$platform_key])) { + throw new DownloaderException("No download available for {$platform_key} in Zig version {$latest_version}"); + } + $download_info = $index_json[$latest_version][$platform_key]; + $url = $download_info['tarball']; + $sha256 = $download_info['shasum']; + $filename = basename($url); + $path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename; + default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry()); + // verify hash + $file_hash = hash_file('sha256', $path); + if ($file_hash !== $sha256) { + throw new DownloaderException("Hash mismatch for downloaded Zig binary. Expected {$sha256}, got {$file_hash}"); + } + return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/zig', verified: true, version: $latest_version); + } + + #[AfterBinaryExtract('zig', [ + 'linux-x86_64', + 'linux-aarch64', + 'macos-x86_64', + 'macos-aarch64', + ])] + public function postExtractZig(string $target_path): void + { + $files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy']; + $all_exist = true; + foreach ($files as $file) { + if (!file_exists("{$target_path}/{$file}")) { + $all_exist = false; + break; + } + } + if ($all_exist) { + return; + } + + $script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh'; + $script_content = file_get_contents($script_path); + + file_put_contents("{$target_path}/zig-cc", $script_content); + chmod("{$target_path}/zig-cc", 0755); + + $script_content = str_replace('zig cc', 'zig c++', $script_content); + file_put_contents("{$target_path}/zig-c++", $script_content); + file_put_contents("{$target_path}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@"); + file_put_contents("{$target_path}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@"); + file_put_contents("{$target_path}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@"); + file_put_contents("{$target_path}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@"); + chmod("{$target_path}/zig-c++", 0755); + chmod("{$target_path}/zig-ar", 0755); + chmod("{$target_path}/zig-ld.lld", 0755); + chmod("{$target_path}/zig-ranlib", 0755); + chmod("{$target_path}/zig-objcopy", 0755); + } +} diff --git a/src/StaticPHP/Doctor/Item/ZigCheck.php b/src/StaticPHP/Doctor/Item/ZigCheck.php new file mode 100644 index 00000000..c8d00574 --- /dev/null +++ b/src/StaticPHP/Doctor/Item/ZigCheck.php @@ -0,0 +1,53 @@ +addInstallPackage($package); + $installed = $installer->isPackageInstalled($package); + if ($installed) { + return CheckResult::ok(); + } + return CheckResult::fail('zig is not installed', 'install-zig'); + } + + #[FixItem('install-zig')] + public function installZig(): bool + { + $arch = arch2gnu(php_uname('m')); + $os = match (PHP_OS_FAMILY) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + $installer = new PackageInstaller(); + $installer->addInstallPackage('zig'); + $installer->run(false); + return $installer->isPackageInstalled('zig'); + } +} diff --git a/src/globals/scripts/zig-cc.sh b/src/globals/scripts/zig-cc.sh new file mode 100755 index 00000000..5c9017c7 --- /dev/null +++ b/src/globals/scripts/zig-cc.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +if [ "$BUILD_ROOT_PATH" = "" ]; then + echo "The script must be run in the SPC build environment." + exit 1 +fi + +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +BUILDROOT_ABS=$BUILD_ROOT_PATH +PARSED_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + -isystem) + shift + ARG="$1" + shift + ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)" + [[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem" "$ARG") + ;; + -isystem*) + ARG="${1#-isystem}" + shift + ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)" + [[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem$ARG") + ;; + -march=*|-mcpu=*) + OPT_NAME="${1%%=*}" + OPT_VALUE="${1#*=}" + # Skip armv8- flags entirely as Zig doesn't support them + if [[ "$OPT_VALUE" == armv8-* ]]; then + shift + continue + fi + # replace -march=x86-64 with -march=x86_64 + OPT_VALUE="${OPT_VALUE//-/_}" + PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}") + shift + ;; + *) + PARSED_ARGS+=("$1") + shift + ;; + esac +done + +[[ -n "$SPC_TARGET" ]] && TARGET="-target $SPC_TARGET" || TARGET="" + +if [[ "$SPC_TARGET" =~ \.[0-9]+\.[0-9]+ ]]; then + output=$(zig cc $TARGET $SPC_COMPILER_EXTRA "${PARSED_ARGS[@]}" 2>&1) + status=$? + + if [[ $status -eq 0 ]]; then + echo "$output" + exit 0 + fi + + if echo "$output" | grep -qE "version '.*' in target triple"; then + filtered_output=$(echo "$output" | grep -vE "version '.*' in target triple") + echo "$filtered_output" + exit 0 + fi +fi + +exec zig cc $TARGET $SPC_COMPILER_EXTRA "${PARSED_ARGS[@]}"