Supporting several SU versions on Mac OSX for a plugin

Hey SU folks,

I am working on an extension which relies on some C/C++ code, compiled in .so files and I managed to make it work on several SU versions on Windows. I struggled with some versions though (those using Ruby 2.7 and 3.2), but after using the .lib files provided by thomthom here: GitHub - SketchUp/ruby-c-extension-examples: Ruby C extension examples (thanks for this!), it works just fine.

Now I’m working on the Mac versions (trying to compile .bundle files), but I am a bit puzzled by the Intel vs. M1 or x86_64 vs arm64 stories. I’ve been through many threads in the forum but couldn’t get a clear answer. The ruby-c-extension-examples offers several Ruby versions for Mac and starts offering both x86_64 and arm64 versions from Ruby 2.7. I guess that’s about when SU started dealing with both architectures.

So my questions are:

  • Is there a different OSX SU version available for each architecture?
  • If so, does it mean that I would need to compile a version of my plugin for each architecture as well, and detect the right architecture version to call when the plugin is launched?
  • If not, what are the best practices to ensure that my plugin works no matter which Mac architecture SU is installed on?

Thanks for any tips on this!

From the main page for the SketchUp C API:

Build and Release Considerations

macOS

Starting with SketchUp 2022.0, universal binaries are supported. Universal binaries on macOS run natively on both Apple silicon and Intel-based Mac computers.

SketchUp 2022 was built with target SDK 10.14. Building and releasing an application using the SketchUp C API for macOS requires including SketchUpAPI.framework. The framework is 64-bit, and can be found in the SketchUp C SDK for macOS.

Thank you @DanRathbun. Just for clarity, SU 2021, 2022 and 2023 are on Ruby 2.7. Then I guess for 2021 I should compile for x86_64, and from 2022 compile for universal, and at launch detect the SU version and relate to the adequate plugin version. Is that correct?

The logic I was using so far was solely based on Ruby versions: one .so file per ruby version. But for Mac, from 2021 I will need to factor in the SU version too. Correct? (this is what I meant by “best practices”)

RUBY_PLATFORM would also work for the detection.

Below are versions I can check…

SU Vers    ————————— RUBY_PLATFORM ——————————   SU Vers
————————— macOS —————————     ————————— Windows ————————   RUBY_VERSION
                              x64-mingw32       20.2.172      2.5.5
                              x64-mswin64_140   21.1.132      2.7.2
22.0.353   arm64-darwin20     x64-mswin64_140   22.1.164      2.7.2
23.1.341   arm64-darwin21     x64-mswin64_140   23.1.340      2.7.7
24.0.554   arm64-darwin22     x64-mswin64_140   24.0.553      3.2.2

Ok, so basically, from SU 2022, pick a .bundle version based on what RUBY_PLATFORM returns. Also I am curious to know what SU 21.1.331 would have returned for macOS in the table you provided (only x86_64-darwinXX, right?). But I guess you don’t have it installed :slight_smile: Thanks for the tip anyway.

Correct.

# Ruby WITHIN your extension submodule ...

su_ver = Sketchup.version.to_i

case Sketchup.platform
when :platform_osx  # load on Mac

  if su_ver == 20 && su_ver == 19
    # load non-ARM binary extension compiled for Ruby 2.5
    
  elsif su_ver == 21
    # load non-ARM binary extension compiled for Ruby 2.7
    
  elsif su_ver <= 23
    # load universal binary extension compiled for Ruby 2.7
    
  elsif su_ver >= 24
    # load universal binary extension compiled for Ruby 3.2

  else
    puts "SketchUp version #{su_ver} not supported by such and such extension."
    EXTENSION.uncheck # switch off this extension in Extension manager
  end

when :platform_win # load on Windows

  # similar conditional loading if statement WIN64 only

end # case

The use of an EXTENSION local constant (within your extension submodule) assumes that you assign your SketchupExtension data object instance to this constant in your extension registrar file.

Given that case when statements can take multiple values, one could also use the included code.

Some might consider it a bit clearer, and it avoids an open ended upper bound. Some people may copy extension folders from on SU version to another…

def load_bin(su_vers = Sketchup.version.to_i, platform = Sketchup.platform)
  case platform
  when :platform_osx # load on Mac
    case su_vers
    when 19, 20      # non-ARM Ruby 2.5 binary
      # load
    when 21          # non-ARM Ruby 2.7 binary
      # load
    when 22, 23      # universal Ruby 2.7 binary
      # load
    when 24          # universal Ruby 3.2 binary
      # load
    else
      puts "SketchUp version #{su_vers} not supported by such and such extension."
      EXTENSION.uncheck # switch off this extension in Extension manager
    end
  when :platform_win # load on Windows
    case su_vers
    when 19, 20      # mingw64? Ruby 2.5 binary
      # load
    when 21, 22, 23  # mswin Ruby 2.7 binary
      # load
    when 24          # mswin Ruby 3.2 binary
      # load
    else
      puts "SketchUp version #{su_vers} not supported by such and such extension."
      EXTENSION.uncheck # switch off this extension in Extension manager
    end
  end
end

Isn’t Ruby great? So many possibilities, to cater to so many tastes.

The main difference is that if SketchUp 25 were to stay on Ruby 3.2, then the >= 24 would work without an update for the extension needing to be released.

After I switched the code to a case structure, I wondered if using RbConfig::CONFIG['ruby_version'] might be better than Sketchup.version.to_i, as it’s the ‘ABI’ version, which is always of the form ‘x.y.0’.

Most pre-compiled extension gems use a folder structure based on the ABI version or x.y…