From de4e08e041cab76bb27df99d9ac6e37dfc44272d Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:27:01 -0400 Subject: [PATCH 1/3] Move macOS and Darwin version checks from Metal.jl --- src/ObjectiveC.jl | 3 ++ src/version.jl | 78 +++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 6 ++++ 3 files changed, 87 insertions(+) create mode 100644 src/version.jl diff --git a/src/ObjectiveC.jl b/src/ObjectiveC.jl index 7256a75..3b51050 100644 --- a/src/ObjectiveC.jl +++ b/src/ObjectiveC.jl @@ -23,6 +23,9 @@ const tracing = @load_preference("tracing", false)::Bool include("primitives.jl") include("methods.jl") +# Get macOS and Darwin version +include("version.jl") + # Calls & Properties include("abi.jl") include("syntax.jl") diff --git a/src/version.jl b/src/version.jl new file mode 100644 index 0000000..16515d7 --- /dev/null +++ b/src/version.jl @@ -0,0 +1,78 @@ +# version and support queries + +@noinline function _syscall_version(name) + size = Ref{Csize_t}() + err = @ccall sysctlbyname( + name::Cstring, C_NULL::Ptr{Cvoid}, size::Ptr{Csize_t}, + C_NULL::Ptr{Cvoid}, 0::Csize_t + )::Cint + Base.systemerror("sysctlbyname", err != 0) + + osrelease = Vector{UInt8}(undef, size[]) + err = @ccall sysctlbyname( + name::Cstring, osrelease::Ptr{Cvoid}, size::Ptr{Csize_t}, + C_NULL::Ptr{Cvoid}, 0::Csize_t + )::Cint + Base.systemerror("sysctlbyname", err != 0) + + verstr = view(String(osrelease), 1:(size[] - 1)) + return parse(VersionNumber, verstr) +end + +@static if isdefined(Base, :OncePerProcess) # VERSION >= v"1.12.0-DEV.1421" + const darwin_version = OncePerProcess{VersionNumber}() do + _syscall_version("kern.osrelease") + end + const macos_version = OncePerProcess{VersionNumber}() do + _syscall_version("kern.osproductversion") + end +else + const _darwin_version = Ref{VersionNumber}() + function darwin_version() + if !isassigned(_darwin_version) + _darwin_version[] = _syscall_version("kern.osrelease") + end + _darwin_version[] + end + + const _macos_version = Ref{VersionNumber}() + function macos_version() + if !isassigned(_macos_version) + _macos_version[] = _syscall_version("kern.osproductversion") + end + _macos_version[] + end +end + +@doc """ + ObjectiveC.darwin_version()::VersionNumber + +Returns the host Darwin kernel version. + +See also [`ObjectiveC.macos_version`](@ref). +""" darwin_version + +@doc """ + ObjectiveC.macos_version()::VersionNumber + +Returns the host macOS version. + +See also [`ObjectiveC.darwin_version`](@ref). +""" macos_version + +""" + Metal.is_macos([ver::VersionNumber]) -> Bool + +Returns whether the OS is macOS with version `ver` or newer. + +See also [`Metal.macos_version`](@ref). +""" +function is_macos(ver = nothing) + return if !Sys.isapple() + false + elseif ver === nothing + true + else + macos_version() >= ver + end +end diff --git a/test/runtests.jl b/test/runtests.jl index d280180..306d637 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,12 @@ using Test using ObjectiveC +@testset "version" begin + @test ObjectiveC.darwin_version() isa VersionNumber + @test ObjectiveC.macos_version() isa VersionNumber + @test ObjectiveC.is_macos(ObjectiveC.macos_version()) +end + @testset "@objc macro" begin # class methods @objc [NSString new]::id{Object} From 72d01dc721b145bad10b2e78a011a104cee86226 Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:27:41 -0400 Subject: [PATCH 2/3] Availability attribute to wrapper and property definitions --- Project.toml | 2 +- src/syntax.jl | 32 +++++++++++++++++++++++++++++++- test/runtests.jl | 26 ++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index dceaf65..30e4959 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ObjectiveC" uuid = "e86c9b32-1129-44ac-8ea0-90d5bb39ded9" -version = "3.3.0" +version = "3.4.0" [deps] CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82" diff --git a/src/syntax.jl b/src/syntax.jl index c022ffa..0fc0adb 100644 --- a/src/syntax.jl +++ b/src/syntax.jl @@ -250,10 +250,23 @@ function instance_message(instance, typ, msg, rettyp, argtyps, argvals) end end +# TODO: support availability macro objc(ex) objcm(__module__, ex) end +export UnavailableError +""" + UnavailableError(symbol::Symbol, minver::VersionNumber) + +Attempt to contruct an Objective-C object or property that is +not available in the current macOS version. +""" +struct UnavailableError <: Exception + symbol::Symbol + minver::VersionNumber +end +Base.showerror(io::IO, e::UnavailableError) = print(io, "UnavailableError: `", e.symbol, "` was introduced in macOS v", e.minver) # Wrapper Classes @@ -278,6 +291,7 @@ keyword arguments: * `immutable`: if `true` (default), define the instance class as an immutable. Should be disabled when you want to use finalizers. + * `availability`: A version string that represents the first macOS version where this object is available. * `comparison`: if `true` (default `false`), define `==` and `hash` methods for the wrapper class. This should not be necessary when using an immutable struct, in which case the default `==` and `hash` methods are sufficient. @@ -289,6 +303,7 @@ macro objcwrapper(ex...) # parse kwargs comparison = nothing immutable = nothing + availability = nothing for kw in kwargs if kw isa Expr && kw.head == :(=) kw, value = kw.args @@ -298,6 +313,9 @@ macro objcwrapper(ex...) elseif kw == :immutable value isa Bool || wrappererror("immutable keyword argument must be a literal boolean") immutable = value + elseif kw == :availability + Meta.isexpr(value, :macrocall) && value.args[1] == Symbol("@v_str") || wrappererror("availability keyword argument must be a `v_str` statement") + availability = macroexpand(__module__, value; recursive=false) else wrappererror("unrecognized keyword argument: $kw") end @@ -307,6 +325,7 @@ macro objcwrapper(ex...) end immutable = something(immutable, true) comparison = something(comparison, !immutable) + availability = something(availability, v"0") # parse class definition if Meta.isexpr(def, :(<:)) @@ -347,6 +366,10 @@ macro objcwrapper(ex...) # add a pseudo constructor to the abstract type that also checks for nil pointers. function $name(ptr::id) + @static if !Sys.isapple() || ObjectiveC.macos_version() < $availability + throw($UnavailableError(Symbol($name), $availability)) + end + ptr == nil && throw(UndefRefError()) $instance(ptr) end @@ -391,7 +414,7 @@ propertyerror(s::String) = error("""Objective-C property declaration: $s. """ @objcproperties ObjCType begin - @autoproperty myProperty::ObjCType [type=JuliaType] [setter=setMyProperty] + @autoproperty myProperty::ObjCType [type=JuliaType] [setter=setMyProperty] [getter=getMyProperty] [availability=v"x.y"] @getproperty myProperty function(obj) ... @@ -417,6 +440,8 @@ contains a series of property declarations: `setproperty!` definition will be generated. - `getter`: specifies the name of the Objective-C getter method. Without this, the getter method is assumed to be identical to the property + - `availability`: specifies earliest macOS version where this property became available, + similar to the Objective-C attribute of the same name - `@getproperty myProperty function(obj) ... end`: define a custom getter for the property. The function should take a single argument `obj`, which is the object that the property is being accessed on. The function should return the property value. @@ -492,6 +517,8 @@ macro objcproperties(typ, ex) # caller's module and decide on the appropriate ABI. that necessitates use of # :hygienic-scope to handle the mix of esc/hygienic code. + availability = get(kwargs, :availability, v"0") + getterproperty = if haskey(kwargs, :getter) kwargs[:getter] else @@ -499,6 +526,9 @@ macro objcproperties(typ, ex) end getproperty_ex = objcm(__module__, :([object::id{$(esc(typ))} $getterproperty]::$srcTyp)) getproperty_ex = quote + @static if !Sys.isapple() || ObjectiveC.macos_version() < $availability + throw($UnavailableError(Symbol($(esc(typ)),".",field), $availability)) + end value = $(Expr(:var"hygienic-scope", getproperty_ex, @__MODULE__, __source__)) end diff --git a/test/runtests.jl b/test/runtests.jl index 306d637..d6b062a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,32 @@ using ObjectiveC @test ObjectiveC.is_macos(ObjectiveC.macos_version()) end +# Availability +@objcwrapper availability = v"1000" TestWrapperAvail <: Object +@objcwrapper availability = v"0" TestPropAvail <: Object +@objcproperties TestPropAvail begin + @autoproperty length::Culong + @autoproperty UTF8String::Ptr{Cchar} availability = v"0" + @autoproperty UnavailableProperty::Cint availability = v"1000" +end +@testset "availability" begin + # wrapper + fakeidwrap = id{TestWrapperAvail}(1) + @test_throws UnavailableError TestWrapperAvail(fakeidwrap) + + # property + str1 = "foo" + prop = TestPropAvail(@objc [NSString stringWithUTF8String:str1::Ptr{UInt8}]::id{TestPropAvail}) + + @test :length in propertynames(prop) + @test :UTF8String in propertynames(prop) + @test :UnavailableProperty in propertynames(prop) + + @test prop.length == length(str1) + @test unsafe_string(prop.UTF8String) == str1 + @test_throws UnavailableError prop.UnavailableProperty +end + @testset "@objc macro" begin # class methods @objc [NSString new]::id{Object} From 6badbb7af21718cf91eabb4ce7549c8a3cc698fb Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:37:38 -0400 Subject: [PATCH 3/3] Test aarch64 --- .github/workflows/Test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index ce85d42..d8e8be0 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -13,7 +13,7 @@ jobs: matrix: version: ['1.10', 'pre', 'nightly'] os: ['macOS-latest'] - arch: [x64] + arch: [x64, aarch64] steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2