From cd013492b552c8ed0ae74eaae49868389d197082 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Fri, 19 Jun 2026 17:29:25 +0200 Subject: [PATCH 01/21] libobs-metal: Add opt-outs for swift-format lints Suppresses some warnings emitted by swift-format's linting report: * "AlwaysUseLowerCamelCase" does not apply to functions exposed via C ABI for interoperability with libobs * "AvoidRetroactiveConformances" has to be violated in the select instances in code (the decoration itself is used to silence a compiler warning). --- libobs-metal/MTLOrigin+Extensions.swift | 2 ++ libobs-metal/MTLRegion+Extensions.swift | 2 ++ libobs-metal/MTLSize+Extensions.swift | 2 ++ libobs-metal/MTLViewport+Extensions.swift | 2 ++ libobs-metal/MetalDevice.swift | 4 ++-- libobs-metal/libobs+SignalHandlers.swift | 2 ++ libobs-metal/metal-indexbuffer.swift | 2 ++ libobs-metal/metal-samplerstate.swift | 2 ++ libobs-metal/metal-shader.swift | 2 ++ libobs-metal/metal-stagesurf.swift | 2 ++ libobs-metal/metal-subsystem.swift | 2 ++ libobs-metal/metal-swapchain.swift | 2 ++ libobs-metal/metal-texture2d.swift | 2 ++ libobs-metal/metal-texture3d.swift | 2 ++ libobs-metal/metal-unimplemented.swift | 2 ++ libobs-metal/metal-vertexbuffer.swift | 2 ++ libobs-metal/metal-zstencilbuffer.swift | 2 ++ 17 files changed, 34 insertions(+), 2 deletions(-) diff --git a/libobs-metal/MTLOrigin+Extensions.swift b/libobs-metal/MTLOrigin+Extensions.swift index 2d160efa5ddad2..dabe0c86d57bf4 100644 --- a/libobs-metal/MTLOrigin+Extensions.swift +++ b/libobs-metal/MTLOrigin+Extensions.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AvoidRetroactiveConformances + import Foundation import Metal diff --git a/libobs-metal/MTLRegion+Extensions.swift b/libobs-metal/MTLRegion+Extensions.swift index afa36322dc53ee..b4416b572edb1d 100644 --- a/libobs-metal/MTLRegion+Extensions.swift +++ b/libobs-metal/MTLRegion+Extensions.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AvoidRetroactiveConformances + import Foundation import Metal diff --git a/libobs-metal/MTLSize+Extensions.swift b/libobs-metal/MTLSize+Extensions.swift index 001097979912a5..216d59807acb31 100644 --- a/libobs-metal/MTLSize+Extensions.swift +++ b/libobs-metal/MTLSize+Extensions.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AvoidRetroactiveConformances + import Foundation import Metal diff --git a/libobs-metal/MTLViewport+Extensions.swift b/libobs-metal/MTLViewport+Extensions.swift index 97c9a9cc362787..4c97f01910ec3c 100644 --- a/libobs-metal/MTLViewport+Extensions.swift +++ b/libobs-metal/MTLViewport+Extensions.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AvoidRetroactiveConformances + import Foundation import Metal diff --git a/libobs-metal/MetalDevice.swift b/libobs-metal/MetalDevice.swift index d3fa017fca636d..44e2c13a651d17 100644 --- a/libobs-metal/MetalDevice.swift +++ b/libobs-metal/MetalDevice.swift @@ -580,8 +580,8 @@ class MetalDevice { commandBuffer.commit() - renderState.inFlightRenderTargets.forEach { - $0.hasPendingWrites = false + for renderTarget in renderState.inFlightRenderTargets { + renderTarget.hasPendingWrites = false } renderState.inFlightRenderTargets.removeAll(keepingCapacity: true) diff --git a/libobs-metal/libobs+SignalHandlers.swift b/libobs-metal/libobs+SignalHandlers.swift index fa31d131de1e0d..21d12f92bcd136 100644 --- a/libobs-metal/libobs+SignalHandlers.swift +++ b/libobs-metal/libobs+SignalHandlers.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation enum MetalSignalType: String { diff --git a/libobs-metal/metal-indexbuffer.swift b/libobs-metal/metal-indexbuffer.swift index ea25b6b8ba0196..3c521d53578b5a 100644 --- a/libobs-metal/metal-indexbuffer.swift +++ b/libobs-metal/metal-indexbuffer.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-samplerstate.swift b/libobs-metal/metal-samplerstate.swift index 7f14c9ed8fb2a5..826a79be9b40b8 100644 --- a/libobs-metal/metal-samplerstate.swift +++ b/libobs-metal/metal-samplerstate.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-shader.swift b/libobs-metal/metal-shader.swift index acf11b89ec0e27..189fc5fbab4058 100644 --- a/libobs-metal/metal-shader.swift +++ b/libobs-metal/metal-shader.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-stagesurf.swift b/libobs-metal/metal-stagesurf.swift index 372b0793633d41..80630c877a1635 100644 --- a/libobs-metal/metal-stagesurf.swift +++ b/libobs-metal/metal-stagesurf.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-subsystem.swift b/libobs-metal/metal-subsystem.swift index 9426277c2e75f6..d005029daec637 100644 --- a/libobs-metal/metal-subsystem.swift +++ b/libobs-metal/metal-subsystem.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal import simd diff --git a/libobs-metal/metal-swapchain.swift b/libobs-metal/metal-swapchain.swift index 94cb5e5ef5f383..6d57979fbb2d02 100644 --- a/libobs-metal/metal-swapchain.swift +++ b/libobs-metal/metal-swapchain.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import AppKit import Foundation diff --git a/libobs-metal/metal-texture2d.swift b/libobs-metal/metal-texture2d.swift index 084676b6df179c..d83ddd0d9c3f33 100644 --- a/libobs-metal/metal-texture2d.swift +++ b/libobs-metal/metal-texture2d.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-texture3d.swift b/libobs-metal/metal-texture3d.swift index 0e2bebac83edd7..807c028da49353 100644 --- a/libobs-metal/metal-texture3d.swift +++ b/libobs-metal/metal-texture3d.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal diff --git a/libobs-metal/metal-unimplemented.swift b/libobs-metal/metal-unimplemented.swift index 35154fc857118b..76cc7dcf3b262c 100644 --- a/libobs-metal/metal-unimplemented.swift +++ b/libobs-metal/metal-unimplemented.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + @_cdecl("device_load_default_samplerstate") public func device_load_default_samplerstate(device: UnsafeRawPointer, b_3d: Bool, unit: Int) { return diff --git a/libobs-metal/metal-vertexbuffer.swift b/libobs-metal/metal-vertexbuffer.swift index 7357f96e889a1f..70e5cc0b5d77b2 100644 --- a/libobs-metal/metal-vertexbuffer.swift +++ b/libobs-metal/metal-vertexbuffer.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + /// Creates a new ``MetalVertexBuffer`` instance with the given vertex buffer data and usage flags /// - Parameters: /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs` diff --git a/libobs-metal/metal-zstencilbuffer.swift b/libobs-metal/metal-zstencilbuffer.swift index cb5c97db661b20..5a54805993f0b5 100644 --- a/libobs-metal/metal-zstencilbuffer.swift +++ b/libobs-metal/metal-zstencilbuffer.swift @@ -15,6 +15,8 @@ along with this program. If not, see . ******************************************************************************/ +// swift-format-ignore-file: AlwaysUseLowerCamelCase + import Foundation import Metal From 6953601cc967c725dd92a931838a41a6a9b9cb80 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Tue, 23 Jun 2026 02:47:36 +0200 Subject: [PATCH 02/21] libobs-metal: Fix swift-format issues --- libobs-metal/OBSShader.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libobs-metal/OBSShader.swift b/libobs-metal/OBSShader.swift index a53bb0cec9b5cb..eba7db6246dcd8 100644 --- a/libobs-metal/OBSShader.swift +++ b/libobs-metal/OBSShader.swift @@ -356,11 +356,11 @@ class OBSShader { let sampler: UnsafeMutablePointer? = parser.samplers.array.advanced(by: i) if let sampler { - var sampler_info = gs_sampler_info() - shader_sampler_convert(sampler, &sampler_info) + var samplerInfo = gs_sampler_info() + shader_sampler_convert(sampler, &samplerInfo) let borderColor: MTLSamplerBorderColor = - switch sampler_info.border_color { + switch samplerInfo.border_color { case 0x00_00_00_FF: .opaqueBlack case 0xFF_FF_FF_FF: @@ -372,14 +372,14 @@ class OBSShader { let descriptor = MTLSamplerDescriptor() descriptor.borderColor = borderColor - descriptor.maxAnisotropy = Int(sampler_info.max_anisotropy) + descriptor.maxAnisotropy = Int(samplerInfo.max_anisotropy) guard - let sAddressMode = sampler_info.address_u.mtlMode, - let tAddressMode = sampler_info.address_v.mtlMode, - let rAddressMode = sampler_info.address_w.mtlMode, - let minMagFilter = sampler_info.filter.minMagFilter, - let mipFilter = sampler_info.filter.mipFilter + let sAddressMode = samplerInfo.address_u.mtlMode, + let tAddressMode = samplerInfo.address_v.mtlMode, + let rAddressMode = samplerInfo.address_w.mtlMode, + let minMagFilter = samplerInfo.filter.minMagFilter, + let mipFilter = samplerInfo.filter.mipFilter else { samplers.append(descriptor) continue From d3595cdd5648ae4629d5a9a6e9555bc017506816 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Thu, 23 Jul 2026 23:28:25 +0200 Subject: [PATCH 03/21] plugins: Fix swift-format issues in virtualcam --- .../camera-extension/OBSCameraDeviceSource.swift | 12 ++++++------ .../src/camera-extension/OBSCameraStreamSink.swift | 2 +- .../camera-extension/OBSCameraStreamSource.swift | 2 +- .../mac-virtualcam/src/camera-extension/main.swift | 13 ++++++------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/plugins/mac-virtualcam/src/camera-extension/OBSCameraDeviceSource.swift b/plugins/mac-virtualcam/src/camera-extension/OBSCameraDeviceSource.swift index 500c31dc4545c6..4b2f3301ea6637 100644 --- a/plugins/mac-virtualcam/src/camera-extension/OBSCameraDeviceSource.swift +++ b/plugins/mac-virtualcam/src/camera-extension/OBSCameraDeviceSource.swift @@ -12,7 +12,7 @@ import Foundation import IOKit.audio import os.log -let OBSCameraFrameRate: Int = 60 +let obsCameraFrameRate: Int = 60 class OBSCameraDeviceSource: NSObject, CMIOExtensionDeviceSource { private(set) var device: CMIOExtensionDevice! @@ -71,8 +71,8 @@ class OBSCameraDeviceSource: NSObject, CMIOExtensionDeviceSource { let videoStreamFormat = CMIOExtensionStreamFormat.init( formatDescription: _videoDescription, - maxFrameDuration: CMTime(value: 1, timescale: Int32(OBSCameraFrameRate)), - minFrameDuration: CMTime(value: 1, timescale: Int32(OBSCameraFrameRate)), + maxFrameDuration: CMTime(value: 1, timescale: Int32(obsCameraFrameRate)), + minFrameDuration: CMTime(value: 1, timescale: Int32(obsCameraFrameRate)), validFrameDurations: nil ) _bufferAuxAttributes = [kCVPixelBufferPoolAllocationThresholdKey: 5] @@ -132,7 +132,7 @@ class OBSCameraDeviceSource: NSObject, CMIOExtensionDeviceSource { } func startStreaming() { - guard let _ = _bufferPool else { + guard _bufferPool != nil else { return } @@ -142,7 +142,7 @@ class OBSCameraDeviceSource: NSObject, CMIOExtensionDeviceSource { _placeholderTimer!.schedule( deadline: .now(), - repeating: 1.0 / Double(OBSCameraFrameRate), + repeating: 1.0 / Double(obsCameraFrameRate), leeway: .seconds(0) ) @@ -279,7 +279,7 @@ class OBSCameraDeviceSource: NSObject, CMIOExtensionDeviceSource { _consumeBufferTimer!.schedule( deadline: .now(), - repeating: 1.0 / (Double(OBSCameraFrameRate) * 3.0), + repeating: 1.0 / (Double(obsCameraFrameRate) * 3.0), leeway: .seconds(0) ) diff --git a/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSink.swift b/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSink.swift index ff24798489e792..cb545d35e7f549 100644 --- a/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSink.swift +++ b/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSink.swift @@ -67,7 +67,7 @@ class OBSCameraStreamSink: NSObject, CMIOExtensionStreamSource { streamProperties.activeFormatIndex = 0 } if properties.contains(.streamFrameDuration) { - let frameDuration = CMTime(value: 1, timescale: Int32(OBSCameraFrameRate)) + let frameDuration = CMTime(value: 1, timescale: Int32(obsCameraFrameRate)) streamProperties.frameDuration = frameDuration } if properties.contains(.streamSinkBufferQueueSize) { diff --git a/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSource.swift b/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSource.swift index 55d8d379052f20..c0300adf0b0862 100644 --- a/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSource.swift +++ b/plugins/mac-virtualcam/src/camera-extension/OBSCameraStreamSource.swift @@ -58,7 +58,7 @@ class OBSCameraStreamSource: NSObject, CMIOExtensionStreamSource { streamProperties.activeFormatIndex = 0 } if properties.contains(.streamFrameDuration) { - let frameDuration = CMTime(value: 1, timescale: Int32(OBSCameraFrameRate)) + let frameDuration = CMTime(value: 1, timescale: Int32(obsCameraFrameRate)) streamProperties.frameDuration = frameDuration } diff --git a/plugins/mac-virtualcam/src/camera-extension/main.swift b/plugins/mac-virtualcam/src/camera-extension/main.swift index 589992db4bcbe3..cb07ec0cda86b3 100644 --- a/plugins/mac-virtualcam/src/camera-extension/main.swift +++ b/plugins/mac-virtualcam/src/camera-extension/main.swift @@ -10,18 +10,17 @@ import CoreMediaIO import Foundation import os.log -let OBSCameraDeviceUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraDeviceUUID") as? String -let OBSCameraSourceUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraSourceUUID") as? String -let OBSCameraSinkUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraSinkUUID") as? String +let obsCameraDeviceUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraDeviceUUID") as? String +let obsCameraSourceUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraSourceUUID") as? String +let obsCameraSinkUUID = Bundle.main.object(forInfoDictionaryKey: "OBSCameraSinkUUID") as? String -guard let OBSCameraDeviceUUID = OBSCameraDeviceUUID, let OBSCameraSourceUUID = OBSCameraSourceUUID, - let OBSCameraSinkUUID = OBSCameraSinkUUID +guard let obsCameraDeviceUUID, let obsCameraSourceUUID, let obsCameraSinkUUID else { fatalError("Unable to retrieve Camera Extension UUIDs from Info.plist.") } -guard let deviceUUID = UUID(uuidString: OBSCameraDeviceUUID), let sourceUUID = UUID(uuidString: OBSCameraSourceUUID), - let sinkUUID = UUID(uuidString: OBSCameraSinkUUID) +guard let deviceUUID = UUID(uuidString: obsCameraDeviceUUID), let sourceUUID = UUID(uuidString: obsCameraSourceUUID), + let sinkUUID = UUID(uuidString: obsCameraSinkUUID) else { fatalError("Unable to generate Camera Extension UUIDs from Info.plist values.") } From 0cc39e7a4c50c0b067019712449c67e940119499 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Sat, 1 Aug 2026 19:03:50 +0200 Subject: [PATCH 04/21] cmake: Update formatting for gersemi 0.28.0 Version 0.28.0 of gersemi has support for "REGEX REPLACE" string operations and thus reverts the worse formatting introduced by a prior version. --- cmake/common/versionconfig.cmake | 3 +-- cmake/finders/FindAsio.cmake | 3 +-- cmake/finders/FindMbedTLS.cmake | 6 ++---- cmake/finders/FindPulseAudio.cmake | 3 +-- cmake/finders/FindUthash.cmake | 3 +-- cmake/windows/buildspec.cmake | 3 +-- 6 files changed, 7 insertions(+), 14 deletions(-) diff --git a/cmake/common/versionconfig.cmake b/cmake/common/versionconfig.cmake index 7a1e753125eeec..821324da8c2c97 100644 --- a/cmake/common/versionconfig.cmake +++ b/cmake/common/versionconfig.cmake @@ -26,8 +26,7 @@ if(NOT DEFINED OBS_VERSION_OVERRIDE AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git elseif(DEFINED OBS_VERSION_OVERRIDE) if(OBS_VERSION_OVERRIDE MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+).*") string( - REGEX REPLACE - "([0-9]+)\\.([0-9]+)\\.([0-9]+).*" + REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+).*" "\\1;\\2;\\3" _obs_version_canonical ${OBS_VERSION_OVERRIDE} diff --git a/cmake/finders/FindAsio.cmake b/cmake/finders/FindAsio.cmake index 3ea564f90ed472..d5eef45daaac85 100644 --- a/cmake/finders/FindAsio.cmake +++ b/cmake/finders/FindAsio.cmake @@ -56,8 +56,7 @@ elseif(EXISTS "${Asio_INCLUDE_DIR}/asio/version.hpp") ) string( - REGEX REPLACE - "#define[ \t]+ASIO_VERSION[ \t]+[0-9]+[ \t]+\\/\\/[ \t]([0-9]+\\.[0-9]+\\.[0-9]+)" + REGEX REPLACE "#define[ \t]+ASIO_VERSION[ \t]+[0-9]+[ \t]+\\/\\/[ \t]([0-9]+\\.[0-9]+\\.[0-9]+)" "\\1" Asio_VERSION "${_version_string}" diff --git a/cmake/finders/FindMbedTLS.cmake b/cmake/finders/FindMbedTLS.cmake index f6fa184e9376c4..035d4c6d529e7e 100644 --- a/cmake/finders/FindMbedTLS.cmake +++ b/cmake/finders/FindMbedTLS.cmake @@ -114,8 +114,7 @@ elseif(EXISTS "${MbedTLS_INCLUDE_DIR}/mbedtls/build_info.h") REGEX "#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+.+" ) string( - REGEX REPLACE - ".*#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"(.+)\".*" + REGEX REPLACE ".*#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"(.+)\".*" "\\1" MbedTLS_VERSION "${_VERSION_STRING}" @@ -127,8 +126,7 @@ elseif(EXISTS "${MbedTLS_INCLUDE_DIR}/mbedtls/version.h") REGEX "#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+.+" ) string( - REGEX REPLACE - ".*#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"(.+)\".*" + REGEX REPLACE ".*#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"(.+)\".*" "\\1" MbedTLS_VERSION "${_VERSION_STRING}" diff --git a/cmake/finders/FindPulseAudio.cmake b/cmake/finders/FindPulseAudio.cmake index 798b4d3a1c48b5..6494f499c4d71b 100644 --- a/cmake/finders/FindPulseAudio.cmake +++ b/cmake/finders/FindPulseAudio.cmake @@ -68,8 +68,7 @@ elseif(EXISTS "${PulseAudio_INCLUDE_DIR}/version.h") REGEX "^.*pa_get_headers_version\\(\\)[\t ]+\\(\".*\"\\)[ \t]*$" ) string( - REGEX REPLACE - ".*pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]*)\"\\).*" + REGEX REPLACE ".*pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]*)\"\\).*" "\\1" PulseAudio_VERSION "${_VERSION_STRING}" diff --git a/cmake/finders/FindUthash.cmake b/cmake/finders/FindUthash.cmake index 3eb7e8a2ef5473..49b83982b08317 100644 --- a/cmake/finders/FindUthash.cmake +++ b/cmake/finders/FindUthash.cmake @@ -43,8 +43,7 @@ if(EXISTS "${Uthash_INCLUDE_DIR}/uthash.h") ) string( - REGEX REPLACE - "#define[ \t]+UTHASH_VERSION[ \t]+([0-9]+\\.[0-9]+\\.[0-9]+)" + REGEX REPLACE "#define[ \t]+UTHASH_VERSION[ \t]+([0-9]+\\.[0-9]+\\.[0-9]+)" "\\1" Uthash_VERSION "${_version_string}" diff --git a/cmake/windows/buildspec.cmake b/cmake/windows/buildspec.cmake index f2a58493c78b18..807440742f8050 100644 --- a/cmake/windows/buildspec.cmake +++ b/cmake/windows/buildspec.cmake @@ -32,8 +32,7 @@ function(_handle_qt_cross_compile architecture) if(config_has_buildabi) string( - REGEX REPLACE - "host_build {\n[ \t]+QT_ARCH = (x86_64|arm64)\n.+[ \t]+QT_TARGET_ARCH = (x86_64|arm64)\n.+}.+" + REGEX REPLACE "host_build {\n[ \t]+QT_ARCH = (x86_64|arm64)\n.+[ \t]+QT_TARGET_ARCH = (x86_64|arm64)\n.+}.+" "\\1;\\2" host_build_tuple "${qt_arch_config}" From cce7d6ab36447701212fedf5feb9704d3c955292 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Tue, 23 Jun 2026 01:41:25 +0200 Subject: [PATCH 05/21] build-aux: Update formatter script The updated script works as a formatter and linter launcher and is now available for Zsh, Bash, and Powershell. Symbolic links serve as easy entry points for the linter and use the same universal launcher script (which is compatible with Csh and Powershell) and identifies the desired linter by the used file name. Because Powershell only executes script files that carry the ".ps1" suffix, separate variants for Windows need to be provided, but still all point to the same universal launcher under the hood. New features: * Bash version for linting and formatting * Powershell version for linting and formatting * Zizmor linting for GitHub Actions workflows * Xmllint linting * Linting only checks files but does not format them * Linting mode has special "GitHub" syntax to annotate failed files --- build-aux/.run-format-launcher | 38 +++ build-aux/.run-format.bash | 333 +++++++++++++++++++++++ build-aux/.run-format.ps1 | 450 +++++++++++++++++++++++++++++++ build-aux/.run-format.zsh | 475 +++++++++++++++++---------------- build-aux/run-clang-format | 2 +- build-aux/run-clang-format.ps1 | 1 + build-aux/run-gersemi | 2 +- build-aux/run-gersemi.ps1 | 1 + build-aux/run-swift-format | 2 +- build-aux/run-xmllint | 1 + build-aux/run-zizmor | 1 + build-aux/run-zizmor.ps1 | 1 + 12 files changed, 1072 insertions(+), 235 deletions(-) create mode 100755 build-aux/.run-format-launcher create mode 100755 build-aux/.run-format.bash create mode 100644 build-aux/.run-format.ps1 create mode 120000 build-aux/run-clang-format.ps1 create mode 120000 build-aux/run-gersemi.ps1 create mode 120000 build-aux/run-xmllint create mode 120000 build-aux/run-zizmor create mode 120000 build-aux/run-zizmor.ps1 diff --git a/build-aux/.run-format-launcher b/build-aux/.run-format-launcher new file mode 100755 index 00000000000000..fccf6e8e75bad8 --- /dev/null +++ b/build-aux/.run-format-launcher @@ -0,0 +1,38 @@ +#!/usr/bin/env sh + +# Combined POSIX Csh/Powershell entry point script +# Based on StackOverflow answer by Jeff Hykin +# https://stackoverflow.com/a/67292076 +# +# In Csh the first line will echo "--%" to /dev/null, followed by the colon built-in +# (using the semi-colon to create a command list). This built-in does nothing apart +# from variable expansions, and is given a multi-line string. +# +# In Powershell the line uses the '--%' token to stop parsing, which will also send the +# 'echo'. output to Powershell's null output, and will then encounter the declaration +# of a multiline comment, which extends until after the "exit" command. +# +# Thus Powershell will execute the code below "exit" in earnest, whereas Csh will ignore +# anything past that "exit". +# + +echo --% >/dev/null;: ' | out-null +<#' +host_os="$(uname)" +script_location="$(dirname "${0}")" +linter_name="$(basename "${0}")" +linter_name="${linter_name#*-}" + +if [ "${host_os}" = 'Darwin' ]; then + exec zsh "${script_location}/.run-format.zsh" --linter "${linter_name}" "${@}" +elif command -v zsh > /dev/null; then + exec zsh "${script_location}/.run-format.zsh" --linter "${linter_name}" "${@}" +else + exec bash "${script_location}/.run-format.bash" --linter "${linter_name}" "${@}" +fi +exit #> + +$ScriptLocation = $PSScriptRoot +$LinterName = ($MyInvocation.MyCommand.Name) -replace 'run-(.+).ps1','$1' + +. ${ScriptLocation}/.run-format.ps1 -Linter "${LinterName}" @Args diff --git a/build-aux/.run-format.bash b/build-aux/.run-format.bash new file mode 100755 index 00000000000000..80046e1e5093bf --- /dev/null +++ b/build-aux/.run-format.bash @@ -0,0 +1,333 @@ +#!/usr/bin/env bash +set -o errexit +set -o nounset +set -o pipefail + +## Enable for script debugging +# set -x + +shopt -s extglob +shopt -s globstar + +check_version() { + local version=() + local checked_version=() + + { IFS='.' read -r -a version; } <<< "${1}" + { IFS='.' read -r -a checked_version; } <<< "${2}" + + if (( version[0] >= checked_version[0] + && version[1] >= checked_version[1] + && version[2] >= checked_version[2] )); then + return 0 + else + return 1 + fi +} + +check_linter() { + local -i found=0 + local linter="${1}" + local min_version='' + local version_number='' + + case "${linter}" in + clang-format) + if command -v clang-format-22 > /dev/null; then + linter='clang-format-22' + found=1 + elif command -v clang-format > /dev/null; then + linter='clang-format' + found=1 + fi + + if (( found )); then + min_version='22.1.3' + + local -a clang_format_version + read -r -a clang_format_version <<< "$("${linter}" --version 2>/dev/null || true)" + local -i last_index="$(( ${#clang_format_version[@]} - 1 ))" + version_number="${clang_format_version[${last_index}]}" + fi + ;; + swift-format) + if command -v swift-format > /dev/null; then + linter='swift-format' + found=1 + fi + + if (( found )); then + min_version='602.0.0' + version_number="$(swift-format --version 2>/dev/null || true)" + fi + ;; + gersemi) + if command -v gersemi > /dev/null; then + linter='gersemi' + found=1 + fi + + if (( found )); then + min_version='0.27.0' + local -a gersemi_version + read -r -a gersemi_version <<< "$(gersemi --version 2>/dev/null || true)" + version_number="${gersemi_version[1]}" + fi + ;; + zizmor) + if command -v zizmor > /dev/null; then + linter='zizmor' + found=1 + fi + + if (( found )); then + min_version='1.25.0' + local -a zizmor_version + read -r -a zizmor_version <<< "$(zizmor --version 2>/dev/null || true)" + version_number="${zizmor_version[1]}" + fi + ;; + xmllint) + if command -v xmllint > /dev/null; then + linter='xmllint' + found=1 + fi + + if (( found )); then + min_version='20900.0.0' + local -a xmllint_version + read -r -a xmllint_version <<< "$(xmllint --version 2>&1 || true)" + version_number="${xmllint_version[4]}.0.0" + fi + ;; + *) + echo " ${_red}✖${_reset} Unsupported linter specified." + return 1 + ;; + esac + + if (( ! found )); then + echo " ${_red}✖${_reset} Unable to find '${linter}' on system." + return 1 + fi + + if ! check_version "${version_number}" "${min_version}"; then + echo " ${_red}✖${_reset} ${linter} ${version_number} found (Required: ${min_version})." + return 1 + fi +} + +generate_file_list() { + local linter="${1}" + + if (( ! ${#source_files[@]} )); then + case "${linter}" in + clang-format) + source_files=(@(libobs|libobs-*|frontend|plugins|deps|shared|test)/**/*.@(c|cpp|h|hpp|m|mm)) + read -r -a source_files <<< "${source_files[@]//*\/@(decklink\/*\/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)\/*/}" + ;; + swift-format) + source_files=(@(libobs|libobs-*|frontend|plugins)/**/*.swift) + ;; + gersemi) + source_files=(CMakeLists.txt @(libobs|libobs-*|frontend|plugins|deps|shared|cmake|test)/**/@(CMakeLists.txt|*.cmake)) + read -r -a source_files <<< "${source_files[@]//*\/@(jansson|decklink\/*\/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)\/*/}" + ;; + zizmor) + source_files=(.github/@(workflows|actions)/**/*.@(yaml|yml)) + ;; + xmllint) + source_files=(frontend/forms/**/*.ui) + ;; + *) ;; + esac + else + source_files=("${source_files[@]//${project_root}\/}") + fi +} + +invoke_formatter() { + local formatter="${1}" + shift + local -a source_files + read -r -a source_files <<< "${@}" + local -a format_arguments + + generate_file_list "${formatter}" + + case "${formatter}" in + clang-format) + format_arguments=(--style=file --fallback-style=none -i) + if (( verbose_output )); then + format_arguments+=('--verbose') + fi + ;; + swift-format) + format_arguments=(format --parallel --color-diagnostics -i) + ;; + gersemi) + format_arguments=(--no-cache -i) + ;; + *) + return 1 + ;; + esac + + "${formatter}" "${format_arguments[@]}" "${source_files[@]}" +} + +invoke_linter() { + local linter="${1}" + shift + local -a source_files + read -r -a source_files <<< "${@}" + + local regexp + local -a indices + local -a lint_arguments + + generate_file_list "${linter}" + + case "${linter}" in + clang-format) + regexp='^([^:]+):([0-9]+):[0-9]+:[[:space:]](.+):[[:space:]](.+)\[-W(.+)\]$' + indices=(1 2 3 5 4) + lint_arguments=(--style=file --fallback-style=none -Werror --dry-run) + if (( verbose_output )); then + lint_arguments+=(--verbose) + fi + ;; + swift-format) + regexp='^([^:]+):([0-9]+):[0-9]+:[[:space:]](.+):[[:space:]]\[(.+)\][[:space:]](.+)$' + indices=(1 2 3 4 5) + lint_arguments=(lint) + ;; + gersemi) + regexp="^${project_root}/([^[:space:]]+)[[:space:]](.+)" + indices=(1 'Entire File' 'error' 'gersemi' 2) + lint_arguments=(--check --no-cache --warnings-as-errors) + ;; + zizmor) + regexp='^::(.+)[[:space:]]file=(.+),line=([0-9]+),title=(.+)::.+:[0-9]+:[[:space:]](.+)$' + indices=(2 3 1 4 5) + lint_arguments=(--offline --persona=auditor --format=github --no-progress --quiet) + ;; + xmllint) + regexp='^([^:]+):([0-9]+):[[:space:]]+.+:[[:space:]](.+):[[:space:]](.+)$' + indices=(1 2 error 3 4) + lint_arguments=(--schema "${project_root}/frontend/forms/XML-Schema-Qt5.15.xsd" --noout) + ;; + *) + return 1 + ;; + esac + + local -i num_failures=0 + + if (( ${#source_files[@]} )); then + local line + local file_path + local file_name + local line_number + local error_level + local error_title + local error_message + local -a BASH_REMATCH + while read -r line; do + local ordered_output='' + if [[ "${linter}" == 'zizmor' ]] && (( github_style )); then + echo "${line}" + num_failures+=1 + continue + fi + + if [[ "${line}" =~ ${regexp} ]]; then + for index in "${indices[@]}"; do + if [[ "${index}" = [[:digit:]] ]]; then + ordered_output+="${BASH_REMATCH[${index}]}|" + else + ordered_output+="${index}|" + fi + done + + { IFS="|" read -r file_path line_number error_level error_title error_message; } <<< "${ordered_output}" + + if (( github_style )); then + file_name="$(basename "${file_path}")" + echo "::${error_level} file=${file_path},line=${line_number},title=${error_title}::${file_name}:${line_number}: ${error_message}" + else + echo " ${_red}✖${_reset} ${file_path}:${line_number} - ${error_title}: ${error_message}" + fi + + num_failures+=1 + else + echo "${line}" + fi + done < <("${linter}" "${lint_arguments[@]}" "${source_files[@]}" 2>&1 || true) + fi + + if (( num_failures )); then + return 1 + fi +} + +main() { + local _red='' + local _reset='' + + if [[ -z "${CI:-}" ]]; then + _red="$(tput setaf 1)" + _reset="$(tput sgr0)" + fi + + if (( BASH_VERSINFO[0] < 4 )); then + echo " ${_red}✖${_reset} ${0} requires Bash 4.0 or later (detected version: ${BASH_VERSION})." + exit 1 + fi + + if [[ -z "${SCRIPT_HOME:-}" ]]; then + local script_realpath + local script_dirname + + script_realpath="$(realpath "${0}")" + script_dirname="$(dirname "${script_realpath}")" + + typeset -g SCRIPT_HOME="${script_dirname}" + fi + + local project_root + project_root="$(dirname "${SCRIPT_HOME}")" + + local -i verbose_output=0 + local -i lint_only=0 + local -i github_style=0 + + local -a args + while (( ${#} )); do + case "${1}" in + -c|--check) lint_only=1; shift ;; + -v|--verbose) verbose_output=1; shift ;; + -gh|--github) github_style=1; shift ;; + -l|--linter) + if [[ -z "${LINTER_NAME:-}" && -n "${2}" ]]; then + typeset -g LINTER_NAME="${2}" + shift 2 + fi + ;; + *) + args+=("${1}") + shift + ;; + esac + done + + check_linter "${LINTER_NAME}" + + if (( lint_only )); then + invoke_linter "${LINTER_NAME}" "${args[@]}" + else + invoke_formatter "${LINTER_NAME}" "${args[@]}" + fi +} + +main "${@}" diff --git a/build-aux/.run-format.ps1 b/build-aux/.run-format.ps1 new file mode 100644 index 00000000000000..4272e93522bd68 --- /dev/null +++ b/build-aux/.run-format.ps1 @@ -0,0 +1,450 @@ +#Requires -Version 7.3 + +[CmdletBinding(PositionalBinding=$false)] +param( + [ValidateSet('clang-format', 'swift-format', 'gersemi', 'zizmor', 'xmllint')] + [string] $Linter, + [switch] $Check, + [switch] $GitHubStyle, + [Parameter(ValueFromRemainingArguments)] + [string[]] $SourceFiles +) + +begin { + $_EAP = $ErrorActionPreference + $_DP = $DebugPreference + $_VP = $VerbosePreference + $_IP = $InformationPreference + + $ErrorActionPreference = 'Stop' + + if ( $DebugPreference -eq 'Continue' ) { + $VerbosePreference = 'Continue' + $InformationPreference = 'Continue' + } + + $Sep = [System.IO.Path]::DirectorySeparatorChar + $Sep = "${Sep}${Sep}" + + function Invoke-External { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true, Position=0)] + [string] $Command, + [Parameter(ValueFromRemainingArguments, Position=1)] + [string[]] $Arguments + ) + + begin { + $_EAP = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + } + + process { + Write-Debug "Invoke-External: ${Command} ${Arguments} 2>&1" + + & $Command @Arguments 2>&1 + $Result = $LASTEXITCODE + } + + end { + $ErrorActionPreference = $_EAP + + if ( $Result -ne 0 ) { + throw "${Command} ${Arguments} exited with non-zero code ${Result}." + } + } + } + + function Test-CommandExists { + [CmdletBinding()] + Param( + [Parameter(Mandatory)] + [String] $Command + ) + + process { + try { + Get-Command $Command -ErrorAction 'Stop' + $true + } catch { + $false + } + } + } + + function Check-Linter { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateSet('clang-format', 'swift-format', 'gersemi', 'zizmor', 'xmllint')] + [string] $Linter + ) + + begin { + $Found = $false + + $LinterCommand = $null + $MinimumVersion = $null + $VersionNumber = $null + } + + process { + switch ($Linter) { + clang-format { + if ((Test-CommandExists 'clang-format-22')) { + $LinterCommand = Get-Command 'clang-format-22' + } elseif ((Test-CommandExists 'clang-format')) { + $LinterCommand = Get-Command 'clang-format' + } else { + break + } + + $MinimumVersion = New-Object -TypeName System.Version -ArgumentList '22.1.3' + + $ClangFormatVersion = (($( Invoke-External $LinterCommand --version ) -split ' ')[2]) + $VersionNumber = New-Object -TypeName System.Version -ArgumentList $ClangFormatVersion + + $Found = $true + break + } + gersemi { + if ((Test-CommandExists 'gersemi')) { + $LinterCommand = Get-Command 'gersemi' + } else { + break + } + + $MinimumVersion = New-Object -TypeName System.Version -ArgumentList '0.27.0' + + $GersemiVersion = (($( Invoke-External $LinterCommand --version ) -split ' ')[1]) + $VersionNumber = New-Object -TypeName System.Version -ArgumentList $GersemiVersion + + $Found = $true + break + } + zizmor { + if ((Test-CommandExists 'zizmor')) { + $LinterCommand = Get-Command 'zizmor' + } else { + break + } + + $MinimumVersion = New-Object -TypeName System.Version -ArgumentList '1.25.0' + + $ZizmorVersion = (($( Invoke-External $LinterCommand --version ) -split ' ')[1]) + $VersionNumber = New-Object -TypeName System.Version -ArgumentList $ZizmorVersion + + $Found = $true + break + } + default { + throw "Unsupported linter '${Linter}' specified." + } + } + } + + end { + if ($Found -eq $false) { + throw "Unable to find '${Linter}' on system." + } + + if (!($VersionNumber -ge $MinimumVersion)) { + throw "${Linter} ${VersionNumber} found (Required: ${MinimumVersion})." + } + + $LinterCommand + } + } + + function Generate-File-List { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateSet('clang-format', 'swift-format', 'gersemi', 'zizmor', 'xmllint')] + [string] $Linter + ) + + begin { + $Files = $null + $ProjectRootPattern = "^$([regex]::Escape($ProjectRoot))${Sep}" + } + + process { + + switch ($Linter) { + clang-format { + $Directories = Get-ChildItem -Path $ProjectRoot -Attribute Directory | Where-Object { + $_.Name -match '^(libobs*|frontend|plugins|deps|shared|test)' + } + + $Pattern = ".*${Sep}(decklink${Sep}.+${Sep}decklink-sdk|obs-websocket|obs-browser|libdshowcapture)" + $Files = $Directories | ForEach-Object { + Get-ChildItem -Path $_ -Recurse -File -Include '*.c','*.h','*.m','*.hpp','*.cpp','*.mm' + } | Where-Object { + ! ($_.Directory.FullName -match $Pattern) + } | ForEach-Object { + $Sep = [System.IO.Path]::DirectorySeparatorChar + ($_.FullName) -replace $ProjectRootPattern,".${Sep}" + } + break + } + gersemi { + $Directories = Get-ChildItem -Path $ProjectRoot -Attribute Directory | Where-Object { + $_.Name -match '^(libobs*|frontend|plugins|deps|shared|cmake|test)' + } + + $Pattern = ".*${Sep}(jansson|decklink${Sep}.+${Sep}decklink-sdk|libdshowcapture|obs-websocket|obs-browser)" + $Files = $Directories | ForEach-Object { + Get-ChildItem -Path $_ -Recurse -File -Include '*.cmake','CmakeLists.txt' + } | Where-Object { + ! ($_.Directory.FullName -match $Pattern) + } | ForEach-Object { + $Sep = [System.IO.Path]::DirectorySeparatorChar + ($_.FullName) -replace $ProjectRootPattern,".${Sep}" + } + $Files += ".${Sep}CMakeLists.txt" + break + } + zizmor { + $Directories = Get-ChildItem -Path $ProjectRoot -Attribute Directory | Where-Object { + $_.Name -match '^.github/(workflows|actions)' + } + $Files = $Directories | ForEach-Object { + Get-ChildItem -Path $_ -Recurse -File -Include '*.yaml','*.yml' + } | ForEach-Object { + $Sep = [System.IO.Path]::DirectorySeparatorChar + ($_.FullName) -replace $ProjectRootPattern,".${Sep}" + } + break + } + default { + break + } + } + } + + end { + $Files + } + } + + function Invoke-Formatter { + [CmdletBinding(PositionalBinding=$false)] + param( + [Parameter(Mandatory)] + [ValidateSet('clang-format', 'swift-format', 'gersemi', 'zizmor', 'xmllint')] + [string] $Formatter, + [Parameter(Mandatory)] + [object] $FormatterCommand, + [Parameter(ValueFromRemainingArguments)] + [string[]] $SourceFiles + ) + + begin { + $FormatterArguments = $null + $TempFile = New-TemporaryFile + } + + process { + switch ($Formatter) { + clang-format { + $FormatterArguments = @( + '--style=file' + '--fallback-style=none' + $( if($VerbosePreference -eq 'Continue') {'--verbose'} ) + '-i' + ) + break + } + gersemi { + $FormatterArguments = @( + '--no-cache' + '-i' + ) + break + } + default { + throw "Unsupported formatter '${Formatter}' specified" + } + } + + # Special handling for clang-format: Due to the amount of eligible source files in the project, + # a command line listing all files to format will exceed Windows's limit of 8191 characters. + # clang and clang-format support a special mode to provide the list of files in a separate file, + # which is used here. + if ($Formatter -eq 'clang-format') { + $SourceFiles | Out-File -FilePath $TempFile + $FilesToFormat = @( "@$( $TempFile.FullName )" ) + } else { + $FilesToFormat = $SourceFiles + } + + try { + Invoke-External $FormatterCommand.Source @FormatterArguments @FilesToFormat + } catch {} + } + + end { + Remove-Item $TempFile + } + } + + function Invoke-Linter { + [CmdletBinding(PositionalBinding=$false)] + param( + [Parameter(Mandatory)] + [ValidateSet('clang-format', 'swift-format', 'gersemi', 'zizmor', 'xmllint')] + [string] $Linter, + [Parameter(Mandatory)] + [object] $LinterCommand, + [Parameter(ValueFromRemainingArguments)] + [string[]] $SourceFiles + ) + + begin { + $LinterArguments = $null + $RegexpPattern = $null + $Indices = $null + + $NumFailures = 0 + + $TempFile = New-TemporaryFile + } + + process { + switch ( $Linter ) { + clang-format { + $RegexpPattern = '^([^:]+):([0-9]+):[0-9]+:\s(.+):\s(.+)\[-W(.+)\]$' + $Indices = @(1,2,3,5,4) + $LinterArguments = @( + '--style=file' + '--fallback-style=none' + '-Werror' + '--dry-run' + $( if($VerbosePreference -eq 'Continue') {'--verbose'} ) + ) + break + } + gersemi { + $RegexpPattern = "^$([regex]::Escape($ProjectRoot))${Sep}([^\s]+)\s(.+)" + $Indices = @(1,'Entire File','error','gersemi',2) + $LinterArguments = @( + '--check' + '--no-cache' + '--warnings-as-errors' + ) + break + } + zizmor { + $RegexpPattern = '^::(.+)\sfile=(.+),line=([0-9]+),title=(.+)::.+:[0-9]+:\s(.+)$' + $Indices = @(2,3,1,4,5) + $LinterArguments = @( + '--offline' + '--persona=auditor' + '--format=github' + '--no-progress' + '--quiet' + ) + break + } + default { + throw "Unsupported linter '${Linter}' specified" + } + } + + # Special handling for clang-format: Due to the amount of eligible source files in the project, + # a command line listing all files to format will exceed Windows's limit of 8191 characters. + # clang and clang-format support a special mode to provide the list of files in a separate file, + # which is used here. + if ($Linter -eq 'clang-format') { + $SourceFiles | Out-File -FilePath $TempFile + $FilesToFormat = @( "@$($TempFile.FullName)" ) + } else { + $FilesToFormat = $SourceFiles + } + + try { + Invoke-External $LinterCommand.Source @LinterArguments @FilesToFormat | ForEach-Object { + if (($Linter -eq 'zizmor') -and ($script:GitHubStyle)) { + Write-Host $_ + $NumFailures += 1 + continue + } + + $Matched = $_ -match $RegexpPattern + + if ($Matched -eq $true) { + $FilePath, $LineNumber, $ErrorLevel, $ErrorTitle, $ErrorMessage = $Indices | ForEach-Object { + $Matches[$_] ?? $_ + } + + $FilePath = ($FilePath -replace '\\','/') -replace '\./','/' + if ($script:GitHubStyle) { + $FileName = ($FilePath | Get-Item).Name + Write-Host "::${ErrorLevel} file=${FilePath},line=${LineNumber},title=${ErrorTitle}::${FileName}:${LineNumber}: ${ErrorMessage}" + } else { + Write-Host -NoNewLine -ForegroundColor Red " ✖ " + Write-Host "${FilePath}:${LineNumber} - ${ErrorTitle}: ${ErrorMessage}" + } + $NumFailures += 1 + } else { + Write-Host $_ + } + } + } catch {} + } + + end { + Remove-Item $TempFile + + $NumFailures + } + } +} + +process { + $ScriptHome = $PSScriptRoot + $ProjectRoot = ($PSScriptRoot | Get-Item).Parent + + $LinterCommand = $null + + if (($null -eq $Linter) -and ($null -ne $env:LINTER_COMMAND)) { + $Linter = $env:LINTER_COMMAND + } + + $LinterCommand = Check-Linter -Linter $Linter + + if ($Linter -eq $null) { + throw 'No linter detected or provided via ''LINTER_COMMAND'' environment variable.' + } + + if ($SourceFiles -eq $null ) { + $SourceFiles = Generate-File-List -Linter $Linter + } else { + $SourceFiles = Get-ChildItem -Path ${SourceFiles} | ForEach-Object { + ($_.FullName) -replace "^$([regex]::Escape($ProjectRoot))${Sep}",'' + } + } + + $NumFailures = 0 + + if ($script:Check) { + $NumFailures = Invoke-Linter -LinterCommand $LinterCommand -Linter $Linter @SourceFiles + } else { + Invoke-Formatter -FormatterCommand $LinterCommand -Formatter $Linter @SourceFiles + } +} + +end { + $ErrorActionPreference = $_EAP + $DebugPreference = $_DP + $VerbosePreference = $_VP + $InformationPreference = $_IP + + if ($NumFailures -gt 0) { + exit 1 + } else { + exit 0 + } +} diff --git a/build-aux/.run-format.zsh b/build-aux/.run-format.zsh index 9146c087f37285..72e8c756fc06ec 100755 --- a/build-aux/.run-format.zsh +++ b/build-aux/.run-format.zsh @@ -1,295 +1,306 @@ #!/usr/bin/env zsh builtin emulate -L zsh -setopt EXTENDED_GLOB -setopt PUSHD_SILENT setopt ERR_EXIT setopt ERR_RETURN -setopt NO_UNSET -setopt PIPE_FAIL +setopt EXTENDED_GLOB +setopt FUNCTION_ARGZERO setopt NO_AUTO_PUSHD setopt NO_PUSHD_IGNORE_DUPS -setopt FUNCTION_ARGZERO +setopt NO_UNSET +setopt PIPE_FAIL +setopt PUSHD_SILENT +setopt WARN_CREATE_GLOBAL +setopt WARN_NESTED_VAR ## Enable for script debugging -# setopt WARN_CREATE_GLOBAL -# setopt WARN_NESTED_VAR # setopt XTRACE -autoload -Uz is-at-least && if ! is-at-least 5.2; then - print -u2 -PR "%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade zsh to fix this issue." - exit 1 -fi - -invoke_formatter() { - if (( # < 1 )) { - log_error "Usage invoke_formatter [formatter_name]" - exit 2 - } - - local formatter="${1}" - shift - local -a source_files=(${@}) +check_linter() { + local -i found=0 + local linter=${1} + local min_version + local version_number - case ${formatter} { - clang) + case ${linter} { + clang-format) if (( ${+commands[clang-format-22]} )) { - local formatter=clang-format-22 + linter='clang-format-22' + found=1 } elif (( ${+commands[clang-format]} )) { - local formatter=clang-format - } else { - log_error "No viable clang-format version found (required 22.1.3)" - exit 2 + linter='clang-format' + found=1 } - local -a formatter_version=($(${formatter} --version)) - - if ! is-at-least 22.1.3 ${formatter_version[-1]}; then - log_error "clang-format is not version 22.1.3 or above (found ${formatter_version[-1]}." - exit 2 - fi - - if ! is-at-least ${formatter_version[-1]} 22.1.3; then - log_warning "clang-format is more recent than version 22.1.3 (found ${formatter_version[-1]})." - fi - - if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins|deps|shared|test)/**/*.(c|cpp|h|hpp|m|mm)(.N)) + if (( found )) { + min_version='22.1.3' - source_files=(${source_files:#*/(obs-websocket/deps|decklink/*/decklink-sdk|mac-syphon/syphon-framework|libdshowcapture)/*}) - - local -a format_args=(-style=file -fallback-style=none) - if (( _loglevel > 2 )) format_args+=(--verbose) - - check_files() { - local -i num_failures=0 - local -a source_files=($@) - local file - local -a format_args=(-style=file -fallback-style=none) - if (( _loglevel > 2 )) format_args+=(--verbose) - - local -a command=(${formatter} ${format_args}) - - for file (${source_files}) { - if ! ${command} "${file}" | diff -q "${file}" - &> /dev/null; then - log_error "${file} requires formatting changes." - if (( fail_on_error == 2 )) return 2; - num_failures=$(( num_failures + 1 )) - fi - } - if (( num_failures && fail_on_error == 1 )) return 2 + local -a clang_format_version + read -r -A clang_format_version <<< "$(${linter} --version 2>/dev/null || true)" + version_number=${clang_format_version[-1]} + } + ;; + swift-format) + if (( ${+commands[swift-format]} )) { + linter='swift-format' + found=1 } - format_files() { - local -a source_files=($@) - - if (( ${#source_files} )) { - local -a format_args=(-style=file -fallback-style=none -i) - if (( _loglevel > 2 )) format_args+=(--verbose) - - "${formatter}" ${format_args} ${source_files} - } + if (( found )) { + min_version='602.0.0' + version_number="$(swift-format --version 2>/dev/null || true)" } ;; gersemi) - - if (( ${+commands[gersemi-0.25]} )) { - local formatter=gersemi-0.25 - } elif (( ${+commands[gersemi]} )) { - local formatter=gersemi - } else { - log_error "No viable gersemi version found (required 0.25.0)" - exit 2 + if (( ${+commands[gersemi]} )) { + linter='gersemi' + found=1 } - local gersemi_version=($(${formatter} --version)) - - if ! is-at-least 0.25.0 ${gersemi_version[2]}; then - log_error "gersemi is not version 0.25.0 or above (found ${gersemi_version[2]}." - exit 2 - fi - - if (( ! #source_files )) source_files=(CMakeLists.txt (libobs|libobs-*|frontend|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N)) - - source_files=(${source_files:#*/(jansson|decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*}) - source_files=(${source_files:#(cmake/Modules/*|*/legacy.cmake)}) - - check_files() { - local -i num_failures=0 - local -a source_files=($@) - local file - local -a command=(${formatter} -c --no-cache ${source_files}) - local -i in_error=0 - - if (( ${#source_files} )) { - while read -r line; do - local -a line_tokens=(${(z)line}) - if (( #line_tokens )) { - file=${line_tokens[1]} - - if [[ -r ${file} ]] { - in_error=0 - file=${file//*${project_root}\//} - - log_error "${file} requires formatting changes." - } else { - if (( in_error )) { - log_output "${line}" - } else { - log_error "${line}" - } - in_error=1 - } - } else { - log_output "${line}" - } - - if (( fail_on_error == 2 )) return 2 - num_failures=$(( num_failures + 1 )) - done < <(${command} 2>&1) - - if (( num_failures && fail_on_error == 1 )) return 2 - } + if (( found )) { + min_version='0.27.0' + local -a gersemi_version + read -r -A gersemi_version <<< "$(gersemi --version 2>/dev/null || true)" + version_number=${gersemi_version[2]} + } + ;; + zizmor) + if (( ${+commands[zizmor]} )) { + linter='zizmor' + found=1 } - format_files() { - local -a source_files=($@) - - if (( ${#source_files} )) { - "${formatter}" -i ${source_files} - } + if (( found )) { + min_version='1.25.0' + local -a zizmor_version + read -r -A zizmor_version <<< "$(zizmor --version 2>/dev/null || true)" + version_number=${zizmor_version[2]} } ;; - swift) - local formatter=swift-format - if (( ${+commands[swift-format]} )) { - local swift_format_version=$(swift-format --version) + xmllint) + if (( ${+commands[xmllint]} )) { + linter='xmllint' + found=1 + } - if ! is-at-least 508.0.0 ${swift_format_version}; then - log_error "swift-format is not version 508.0.0 or above (found ${swift_format_version})." - exit 2 - fi - } else { - log_error "No viable swift-format version found (required 508.0.0)" - exit 2 + if (( found )) { + min_version='20900.0.0' + local -a xmllint_version + read -r -A xmllint_version <<< "$(xmllint --version 2>&1 || true)" + version_number="${xmllint_version[5]}.0.0" } + ;; + *) + print -u2 -PR "%F{1} ✖ %f Unsupported linter specified." + return 1 + ;; + } - if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins)/**/*.swift(.N)) + if (( ! found )) { + print -u2 -PR "%F{1} ✖ %f Unable to find %B'${linter}'%b on system." + return 1 + } - check_files() { - local -i num_failures=0 - local -a source_files=($@) - local file - local -a format_args=() + if ! is-at-least ${min_version} ${version_number}; then + print -u2 -PR "%F{1} ✖ %f ${linter} ${version_number} found (Required: %B${min_version}%b)." + return 1 + fi +} - local -a command=(${formatter} ${format_args}) +generate_file_list() { + local linter=${1} + local -a found_files - for file (${source_files}) { - if ! "${command}" "${file}" | diff -q "${file}" - &> /dev/null; then - log_error "${file} requires formatting changes." - if (( fail_on_error == 2 )) return 2; - num_failures=$(( num_failures + 1 )) - fi - } - if (( num_failures && fail_on_error == 1 )) return 2 - } + if (( ! #source_files )) { + case ${linter} { + clang-format) + found_files=((libobs|libobs-*|frontend|plugins|deps|shared|test)/**/*.(c|cpp|h|hpp|m|mm)(.N)) + found_files=(${found_files:#*/(decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*}) + ;; + swift-format) + found_files=((libobs|libobs-*|frontend|plugins)/**/*.swift(.N)) + ;; + gersemi) + found_files=(CMakeLists.txt (libobs|libobs-*|frontend|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N)) + found_files=(${found_files:#*/(jansson|decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*}) + ;; + zizmor) + found_files=(.github/(workflows|actions)/**/*.(yaml|yml)) + ;; + xmllint) + found_files=(frontend/forms/**/*ui) + ;; + *) return ;; + } - format_files() { - local -a source_files=($@) + typeset -ga source_files=(${found_files}) + } else { + typeset -ga source_files=(${source_files//${project_root}\/}) + } +} - if (( ${#source_files} )) { - local -a format_args=(-i) +invoke_formatter() { + local formatter=${1} + shift + local -a source_files + read -r -A source_files <<< "${@}" + local -a format_arguments - "${formatter}" ${format_args} ${source_files} - } + generate_file_list ${formatter} + + case ${formatter} { + clang-format) + format_arguments=(--style=file --fallback-style=none -i) + if (( verbose_output )) { + format_arguments+=(--verbose) } ;; - *) log_error "Invalid formatter specified: ${1}. Valid options are clang-format, gersemi, and swift-format."; exit 2 ;; + swift-format) + format_arguments=(format --parallel --color-diagnostics -i) + ;; + gersemi) + format_arguments=(--no-cache -i) + ;; + *) + return 1 } - local file - local -i num_failures=0 - if (( check_only )) { - if (( ${+functions[check_files]} )) { - check_files ${source_files} - } else { - log_error "No format check function defined for formatter '${formatter}'" - exit 2 - } - } else { - if (( ${+functions[format_files]} )) { - format_files ${source_files} - } else { - log_error "No format function defined for formatter '${formatter}'" - exit 2 - } - } + ${formatter} ${format_arguments} ${source_files} } -run_format() { - if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} - if (( ! ${+FORMATTER_NAME} )) typeset -g FORMATTER_NAME=${${(s:-:)ZSH_ARGZERO:t:r}[2]} - local project_root=${SCRIPT_HOME:A:h} +invoke_linter() { + local linter=${1} + shift + local -a source_files + read -r -A source_files <<< "${@}" + + local regexp + local glob_expression + local -a indices + local -a lint_arguments + + generate_file_list ${linter} + + case ${linter} { + clang-format) + regexp='^([^:]+):([0-9]+):[0-9]+:[[:space:]](.+):[[:space:]](.+)\[-W(.+)\]$' + indices=(1 2 3 5 4) + lint_arguments=(--style=file --fallback-style=none -Werror --dry-run) + if (( verbose_output )) { + lint_arguments+=(--verbose) + } + ;; + swift-format) + regexp='^([^:]+):([0-9]+):[0-9]+:[[:space:]](.+):[[:space:]]\[(.+)\][[:space:]](.+)$' + indices=(1 2 3 4 5) + lint_arguments=(lint) + ;; + gersemi) + regexp="^${project_root}/([^[:space:]]+)[[:space:]](.+)" + indices=(1 'Entire File' 'error' 'gersemi' 2) + lint_arguments=(--check --no-cache --warnings-as-errors) + ;; + zizmor) + regexp='^::(.+)[[:space:]]file=(.+),line=([0-9]+),title=(.+)::.+:[0-9]+:[[:space:]](.+)$' + indices=(2 3 1 4 5) + lint_arguments=(--offline --persona=auditor --format=github --no-progress --quiet) + ;; + xmllint) + regexp='^([^:]+):([0-9]+):[[:space:]]+.+:[[:space:]](.+):[[:space:]](.+)$' + indices=(1 2 error 3 4) + lint_arguments=(--schema ${project_root}/frontend/forms/XML-Schema-Qt5.15.xsd --noout) + ;; + *) + return 1 + ;; + } - typeset -g host_os=${${(L)$(uname -s)}//darwin/macos} - local -i fail_on_error=0 - local -i check_only=0 - local -i verbosity=1 - local -r _version='1.0.0' + local -i num_failures=0 - fpath=("${SCRIPT_HOME}/.functions" ${fpath}) - autoload -Uz set_loglevel log_info log_error log_output log_status log_warning + if (( #source_files )) { + local file_path + local line_number + local error_level + local error_title + local error_message + + local line + local ordered_output + local -a match + local mbegin + local mend + local MATCH + local MBEGIN + local MEND + + while read -r line; do + if [[ ${linter} == 'zizmor' ]] && (( github_style )) { + print -- ${line} + num_failures+=1 + continue + } - local -r _usage=" -Usage: %B${functrace[1]%:*}%b