Feedback on 3 bugs under MAC with case sensitive file system

,

Hello to all,

I frequently consult the forum to find very precious information when I program my extensions in ruby. I take this opportunity to thank the community that shares its encyclopedic knowledge. On my side, I would like to make a small contribution by reporting a recent experience. For some, what I describe will be trivial, for others, it may save time.

I recently encountered bugs in my code when deploying an extension on MAC and some of them were quite difficult to understand. I have been developing an extension under a Windows environment but I want it to work on MAC. So before its distribution I test the extension on a MAC (currently MacOS Monterey) by loading .rb files located on my computer under Windows (Windows 10) and I bring the required corrections. Tests are performed on SketchUp 2017 to 2022.

Bug 1: require " UPPERCASE_NAME "

The first bug I encountered was related to a following code line:

require 'JSON'

A ruby programmer will immediately say that ‘json’ should be written in lower case of course. That’s right and I don’t understand myself what this ‘JSON’ was doing in my code (a 3am programming, I guess). But Windows is not case sensitive and this does not cause a bug. Mac is case sensitive (in some case: see Dan’s answer below) and it causes the .rb file to stop loading. This bug is quite easy to identify when testing under Mac.

Bug 2 : call to a file with a letter in upper case when it should be in lower case.

File.read('G:/path/File_name') # the file to read is 'file_name'

The second bug is also related to case sensitivity. But it was much more difficult to identify. In my code, I was referring to a file ‘file_name’ but one of the letters was faulty typed with a capital letter in the code (for example ‘File_name’).

During the first tests on MAC no problem was encountered because even if I was using SketchUp on MacOS Monterey, the loaded files were on a Windows server. It is only when creating a .rbz file and installing the installation via this file (and thus with all the files on the MAC) that the bug was declared. Written like this it may seem simple but it may take some time to understand why switching to an .rbz triggers a bug.

Bug 3 : File instruction in a .rbe package

file_path =  __File__

The __File__instruction that allows to get the path of the loaded code file works on Windows with encrypted .rbe package but I got a bug when testing the .rbe on my MAC. The bug was known in the .rbs packages but I thought it was totally solved with .rbe packages. I couldn’t find anything on the forum about it but maybe I looked wrong.

Again it’s a pretty insidious bug since you test your extension and everything goes fine and it’s only when encrypting .rbe that everything crashes.

I hope that my experience will be useful to you.

a. Yes you should use the same case as the actual filename when it’s name is passed to the global #require method.

b. NO, Mac OS is not case sensitive by default. A user must explicitly format their Mac system and choose case sensitive file system when setting up their computer. Ruby does not work well on such case sensitive systems. This is outside the control of SketchUp.

Read the doc for the global Kernel#require method. You’ll see that it will check the global $LOADED_FEATURES array to see if any matching file paths have already been loaded. However it does not do a case insensitive match (last I knew.)

The Sketchup::require module method will downcase and “absoluteize” any valid filepaths it loads before it pushes the paths into $LOADED_FEATURES array. However, for plain rb files, it just passes the argument to Ruby’s global #require method, so using the API method will not be a workaround.

So, as Trimble does not wish to modify Ruby, then as you said the proper thing to do is using the case that the filename actually uses.

In the case of the JSON library it shouldn’t matter much if the loader file gets evaluated a second time. But SketchUp extension coders should take care to use load guards blocks to prevent duplicate UI elements (menu items, context menus, toolbar buttons, etc.,) from being created more than once.

You can test if a file exists using your supplied pathname, with the File::exist? method.

A typographic error, in your code is not a SketchUp or Ruby bug.
These kinds of errors are found when you test and debug your code.

The __FILE__ feature is an interpreter function. At one time it was bugged with the RBS scrambling but this was fixed for the 2014 release that also updated to use Ruby 2.0.0.

Note that there is an open issue with regard to __FILE__ and RBE encryption:

NOTE: It was mentioned by one of the team in the above issue that __FILE__ should return the path as it was cased when loaded. As I mentioned above, the Sketchup::require module method will downcase it’s pathname arguments, which is the cause of file pathnames being all downcase when files are encrypted. (When not encrypted, this method just passes the pathname argument unchanged to the global #require method which does not make case changes.)

To avoid case issues with comparing file pathname strings, … do not use string pathname comparison in conditional expressions for example for load guards. Instead use local module variables within your extension submodule

  if !defined?(@loaded)
     # define UI elements here (commands, menus, toolbars, etc.)
    @loaded = true
  end 

Have you tried the global __dir__ method with encryption ?

What most developers do is set local constants (within their extension submodule) to extension paths in their extension’s registrar file (which is unencrypted.)

As soon as you have called the constructor for your extension object it’s #extension_path instance method should return a valid path to the registrar. This would also likely be the same as calling the global __FILE__ function in the registrar file as it is always unencrypted.

But if it’s only the path you want, then as said, you can use __dir__ since Ruby 2.0 (SketchUp 2014+).

# encoding: UTF-8
#
# Example Extension Registrar file
module SomeAuthor
  module SomePlugin

    SUBDIR ||= File.basename(__FILE__,'.*')
    FOLDER ||= File.join(__dir__, SUBDIR)

    EXTENSION = SketchupExtension.new(
      'SomeAuthor SomePlugin',
      File.join(SUBDIR, 'main')
    )

    EXTENSION.instance_eval {
      self.description= 'This extension does nifty stuff.'
      self.version=     '1.0.0'
      self.copyright=   '©2022 Some Author'
      self.creator=     'Some Author'
    }
    Sketchup.register_extension(EXTENSION, true)

  end # extension submodule
end # top level namespace module

So later in the other files of the extension, you can get the path to your extension’s subfolder by using the FOLDER constant rather than calling the __FILE__ function or the __dir__ method.

Also you can always get the full path to the registrar file that registered your extension, from within your submodule, by calling EXTENSION.extension_path(). And then the path to the “Plugins” path where the user installed your extension via either:
File.dirname(EXTENSION.extension_path)
… or …
File.dirname(FOLDER)

3 Likes

I hope you won’t take offense at this, but what you describe are bugs in your code, not in SketchUp or in macOS . It is never right to play loose with capitalization and assume it will work everywhere.

2 Likes

Thanks Dan for these very informative comments! When I was talking about encyclopedic knowledge shared by the community on the forum, here is another proof.

The fact that MAC OS is not case sensitive by default as I mistakenly thought sheds light on everything. I had the hard disk changed on my MAC a few weeks ago with a new OS installation. I did not perform the operation myself and I did not know that an option on the case sensitive existed. I understand now why I hadn’t identified some problems in previous years on my MAC.

Now the question arises if it is relevant to test a SU extension on a MAC OS with a case sensitive file system. I assume that this configuration is rather rare.

Hi Steve, I don’t take your comment as an offense. It’s only fair to say that these are bugs in my code. I have changed my introductory text slightly to emphasize this fact.