audiveris icon indicating copy to clipboard operation
audiveris copied to clipboard

macOS Build

Open fbmrqs opened this issue 9 months ago • 25 comments

Hi,

I have produced a macOS build, and it works perfectly. Is there any reason why you don't feature a macOS release?

See below:

cd ~/Downloads
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # only if brew is not installed
brew install git gradle tesseract freetype openjdk@21
export JAVA_HOME=/usr/local/opt/openjdk@21
git clone https://github.com/Audiveris/audiveris.git
cd audiveris
git checkout development
git pull --all
./gradlew build -Dorg.gradle.java.home=$JAVA_HOME
./gradlew run -Dorg.gradle.java.home=$JAVA_HOME          

If you don't have a Mac at your disposal, I'd happily provide the binaries to you. It'd help a lot of people.

Thank you very much!

fbmrqs avatar Mar 06 '25 14:03 fbmrqs

Is there any reason why you don't feature a macOS release?

The reason is simple: I have no access to a Mac. But if you can build an installer for macOS, you are more than welcome!

Obviously, you are able to clone, build and run from sources (as we can do for most OSes), but building an installer is something more complex (we have one for Windows, we are starting with Linux/flatpak, but have yet nothing for macOS).

Sure, many people would love to install a binary rather than having to deal with a development environment. Could you elaborate on your solution?

hbitteur avatar Mar 06 '25 17:03 hbitteur

Hi,

Great! I wrote a script to build the DMG installer, which worked perfectly. I have an Intel iMac, so I sent the installer to a friend with an Apple Silicon Mac, which also worked as intended.

See the attached screenshot of the working installation:

Image

The build script browses to the download folder, deletes the audiveris folder if found, downloads the repository, creates the app icon using the image from the source code, builds the JRE to bundle, includes dependencies and libraries (for a one-click installation), and creates the macOS DMG package. Unfortunately, I don't have an Apple Developer account, so I didn't sign the installer and Mac users will have to go to the privacy settings to run this app. Would you like me to send you the DMG somehow? Shall I make a pull request with the build script? See below:

#!/bin/zsh
set -e

# Ensure dependencies are installed
echo "Installing required dependencies..."
if ! command -v brew &>/dev/null; then
  echo "Installing Homebrew..."
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi

echo "Installing required packages..."
brew install git gradle tesseract freetype openjdk@21 imagemagick

# Set up environment
export JAVA_HOME=/usr/local/opt/openjdk@21
cd ~/Downloads
rm -rf audiveris

# Clone and build Audiveris
if [ ! -d "audiveris" ]; then
  echo "Cloning Audiveris repository..."
  git clone https://github.com/Audiveris/audiveris.git
fi

cd audiveris
git checkout development
git pull --all

echo "Building Audiveris..."
./gradlew build -Dorg.gradle.java.home=$JAVA_HOME

# Create package directories
echo "Setting up packaging directories..."
mkdir -p macos/res
mkdir -p macos/iconset

# Create icns file from source PNG
echo "Creating .icns file from source icon..."
SOURCE_ICON="$HOME/Downloads/audiveris/app/src/main/java/org/audiveris/omr/ui/resources/icon-256.png"

if [ -f "$SOURCE_ICON" ]; then
  echo "Converting PNG icon to ICNS format..."
  
  # Create iconset directory
  mkdir -p macos/icon.iconset

  # Generate different icon sizes
  magick "$SOURCE_ICON" -resize 16x16 macos/icon.iconset/icon_16x16.png
  magick "$SOURCE_ICON" -resize 32x32 macos/icon.iconset/icon_32x32.png
  magick "$SOURCE_ICON" -resize 64x64 macos/icon.iconset/icon_64x64.png
  magick "$SOURCE_ICON" -resize 128x128 macos/icon.iconset/icon_128x128.png
  magick "$SOURCE_ICON" -resize 256x256 macos/icon.iconset/icon_256x256.png
  magick "$SOURCE_ICON" -resize 512x512 macos/icon.iconset/icon_512x512.png
  
  # For retina displays (@2x)
  magick "$SOURCE_ICON" -resize 32x32 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 64x64 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 128x128 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 256x256 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 512x512 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 1024x1024 macos/icon.iconset/[email protected]

  # Convert iconset to icns
  iconutil -c icns -o macos/res/Audiveris.icns macos/icon.iconset
  
  # Clean up temporary iconset
  rm -rf macos/iconset
  
  echo "Icon successfully created at macos/res/Audiveris.icns"
else
  echo "Warning: Source icon not found at $SOURCE_ICON"
  echo "Creating placeholder icon file (replace with actual icon)..."
  touch macos/res/Audiveris.icns
fi

# Build JRE to bundle
echo "Creating custom JRE..."
./gradlew app:installDist -Dorg.gradle.java.home=$JAVA_HOME
jlink --add-modules java.base,java.datatransfer,java.desktop,java.xml,java.naming,jdk.zipfs --output macos/res/customjre

# Function to recursively find and process all dependencies
process_dependencies() {
  local processed_libs=()
  local lib_queue=("macos/res/customjre/lib/libfontmanager.dylib")
  local visited=()
  
  echo "Analyzing dependencies recursively..."
  
  while [ ${#lib_queue[@]} -gt 0 ]; do
    local current=${lib_queue[1]}
    lib_queue=(${lib_queue[@]:1})
    
    # Skip if already processed
    if [[ " ${visited[@]} " =~ " ${current} " ]]; then
      continue
    fi
    
    visited+=("$current")
    
    echo "Processing: $current"
    
    # Get direct dependencies
    local deps=($(otool -L "$current" | grep -v ":" | grep -v "/System/" | grep -v "/usr/lib/" | awk '{print $1}'))
    
    for dep in "${deps[@]}"; do
      # Skip system libraries
      if [[ "$dep" == /System/* ]] || [[ "$dep" == /usr/lib/* ]]; then
        continue
      fi
      
      # If this is a brew-installed library
      if [[ "$dep" == /usr/local/opt/* ]] && [[ -f "$dep" ]]; then
        local basename=$(basename "$dep")
        
        # Copy library if not already done
        if [ ! -f "macos/res/customjre/lib/$basename" ]; then
          echo "Copying $dep to bundle"
          cp "$dep" "macos/res/customjre/lib/$basename"
          processed_libs+=("$basename")
          lib_queue+=("macos/res/customjre/lib/$basename")
        fi
        
        # Update reference in current library
        echo "Updating reference to $basename in $(basename $current)"
        install_name_tool -change "$dep" "@loader_path/$basename" "$current"
      fi
    done
  done
  
  # Set library IDs for all processed libraries
  echo "Setting library IDs..."
  for lib in "${processed_libs[@]}"; do
    echo "Setting ID for $lib"
    install_name_tool -id "@loader_path/$lib" "macos/res/customjre/lib/$lib"
  done
  
  return 0
}

# Copy key libraries and process dependencies
echo "Copying initial libraries..."
cp /usr/local/opt/tesseract/lib/libtesseract.5.dylib macos/res/customjre/lib/
cp /usr/local/opt/leptonica/lib/libleptonica.6.dylib macos/res/customjre/lib/

# Process all dependencies recursively
process_dependencies

# Get current version and ensure it's properly formatted
version=$(./gradlew -q printVersion 2>/dev/null || echo "5.2.0")
clean_version=$(echo "$version" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)*' | head -1)

# If no valid version was found, use a default
if [ -z "$clean_version" ]; then
    clean_version="5.2"
    echo "Warning: Could not extract a valid version number, using default: $clean_version"
else
    echo "Using version: $clean_version"
fi

# Generate installer
echo "Creating macOS package..."
jpackage --type dmg \
  --input app/build/install/app/lib \
  --main-jar audiveris.jar \
  --name Audiveris \
  --app-version "$clean_version" \
  --runtime-image macos/res/customjre \
  --icon macos/res/Audiveris.icns \
  --main-class Audiveris \
  --dest ~/Downloads \
  --java-options '--add-exports java.desktop/com.apple.eawt=ALL-UNNAMED' \
  --mac-package-identifier org.audiveris.app \
  --mac-package-name "Audiveris" \
  --vendor "Audiveris" \
  --description "Optical Music Recognition"

echo "Package created at ~/Downloads/Audiveris-$clean_version.dmg"

# Check if architecture is universal
echo "Checking architecture compatibility:"
lipo -info ~/Downloads/Audiveris-$clean_version.dmg/Audiveris.app/Contents/MacOS/Audiveris 2>/dev/null || echo "Not a universal binary - will only work on $(uname -m) machines"

echo "Done!"

Many thanks, and keep up the great work.

fbmrqs avatar Mar 09 '25 20:03 fbmrqs

Thank you Fabio for this work. I'm impressed!

I'd suggest we create a gradle subproject for this. Today, we have 4 subprojects defined in settings.gradle file:

  • app
  • schemas
  • windows-installer
  • flatpak

We could thus add "macos-installer" (or any better name you can find) to host your script and any related material. And then we'll integrate your contribution via a PR.

LIke I said, I cannot run or test the script on my side. I simply made a kind of re-reading, trying to understand each of the script steps. Here below are a few questions or remarks, in no particular order. Perhaps I did not understand everything. I apologize for this.

We should avoid hard-coded constants, or at least define all of them in a single location at the beginning of the script, so that we can visually check them for any needed modification. Typically the Java version (here: 21) is one of these constants.

Line 12: brew install git gradle tesseract freetype openjdk@21 imagemagick I'm not sure we need to install tesseract, because it is used as a linked library together with leptonica (see the app/build.gradle file) On the other hand, in lines 142-143, you make a copy of libtesseract.5.dylib and libleptonica.6.dylib, so perhaps I'm wrong.

Creating the iconset directory. I suspect a confusion between macos/iconset and macos/icon.iconset directories: Line 35: mkdir -p macos/iconset Line 45: mkdir -p macos/icon.iconset (only if the icon-256.png source is found) Line 67: rm -rf macos/iconset (only if the icon-256.png source was found)

Get current version (starting line 148). What are these 5.2.0 and 5.2 values? We are working on 5.4 right now. To my knowledge, there is yet no printVersion task in the current app/build.gradle file. But it's a very good idea for a bridge between gradle and script. Doing so, we could avoid most hard-coded constants.

That's all I was able to notice.

I think a README.md could document how to use the installer, including your remark about the lack of signature and thus the need to go to the privacy settings.

Thanks for this good work!

hbitteur avatar Mar 10 '25 10:03 hbitteur

We could be even more ambitious!

Perhaps we could:

  1. use the same approach (based on jlink and jpackage Java tools) for various OSes
  2. drive them directly from gradle

I need a few days to investigate, but the more I think about it, the more I like your approach.

hbitteur avatar Mar 10 '25 14:03 hbitteur

Good news: In half a day, I have been able to build a Windows installer for Audiveris, based on your approach. I used a gradle jpackage plugin (id 'org.panteleyev.jpackageplugin' version '1.6.1') and just added these lines in the existing app/build.gradle file:

//-----------
// Packaging
//-----------

task copyDependencies (type: Copy) {
    from(configurations.runtimeClasspath)
    into("$buildDir/jars")
}

task copyJar (type: Copy) {
    from(tasks.jar)
    into("$buildDir/jars")
}

tasks.jpackage {
    dependsOn("build", "copyDependencies", "copyJar")

    input  = "$buildDir/jars"
    destination = "$buildDir/dist"

    appName = "Audiveris"
    vendor = "audiveris.org"
    appDescription = "Optical Music Recognition"

    appVersion = "${project.version}"

    mainJar = "${programId}.jar"
    mainClass = "$project.ext.mainClass"

    javaOptions =  ["--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED", 
                    "-Dfile.encoding=UTF-8",
                    "-Xms512m", 
                    "-Xmx1024m"]

    fileAssociations = ["dev/associations.properties"]

    windows {
        type = "msi"
        icon = "res/icon-256.ico"
        winDirChooser = true    // Let the user choose the installation directory
        winShortcutPrompt = true    // User decides on menu and shortcut creation
        // winConsole = true    // Create a console launcher
    }
}

Here is the referenced associations.properties file:

mime-type = application/audiveris
extension = omr
description = OMR book file

Then, to build the installer:

 ./gradlew -q jpackage

Done!!!

So, I'm very confident in our ability to merge our two installers (and probably do the same for Linux). More on this later.

hbitteur avatar Mar 10 '25 19:03 hbitteur

Hi,

My pleasure. You created a fantastic app, so it's only fair that its support is extended to macOS so that more people can use it.

I'll try to reply to your remarks one by one.

We should avoid hard-coded constants, or at least define all of them in a single location at the beginning of the script, so that we can visually check them for any needed modification.

It makes sense. Adjusted!

I'm not sure we need to install tesseract, because it is used as a linked library together with leptonica (see the app/build.gradle file)

I believe I was being overcautious when copying tesseract from the brew installation. I just wanted to make sure the installer would work on any Mac. I'll try getting an Apple Silicon Mac and run some tests before confirming this. I have added a TODO mark to the script.

Creating the iconset directory. I suspect a confusion between macos/iconset and macos/icon.iconset directories:

My confusion indeed. Adjusted!

Get current version (starting line 148). What are these 5.2.0 and 5.2 values? We are working on 5.4 right now.

The script is getting the correct version (5.4), so that's all good. I must've misread it and typed 5.2 as the failover instead. This is because macOS requires developers to set up a version when bundling an installer. I'll change from 5.2.0 and 5.2 to 1.0.0 and 1.0 respectively. I was just overly cautious, but I don't think that this failover will ever be triggered. Let me know if you believe these new values (1.0.0 and 1.0) need to be set as constants.

So, I'm very confident in our ability to merge our two installers (and probably do the same for Linux).

This is fantastic news! I'm so glad to hear.

See below the currently amended script:

#!/bin/zsh
set -e

JAVA_VERSION=21

# Ensure dependencies are installed
echo "Installing required dependencies..."
if ! command -v brew &>/dev/null; then
  echo "Installing Homebrew..."
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi

echo "Installing required packages..." # TODO: check if tesseract and freetype are necessary
brew install git gradle tesseract freetype openjdk@${JAVA_VERSION} imagemagick

# Set up environment
export JAVA_HOME=/usr/local/opt/openjdk@21
cd ~/Downloads
rm -rf audiveris

# Clone and build Audiveris
if [ ! -d "audiveris" ]; then
  echo "Cloning Audiveris repository..."
  git clone https://github.com/Audiveris/audiveris.git
fi

cd audiveris
git checkout development
git pull --all

echo "Building Audiveris..."
./gradlew build -Dorg.gradle.java.home=$JAVA_HOME

# Create package directories
echo "Setting up packaging directories..."
mkdir -p macos/res

# Create icns file from source PNG
echo "Creating .icns file from source icon..."
SOURCE_ICON="$HOME/Downloads/audiveris/app/src/main/java/org/audiveris/omr/ui/resources/icon-256.png"

if [ -f "$SOURCE_ICON" ]; then
  echo "Converting PNG icon to ICNS format..."
  
  # Create iconset directory
  mkdir -p macos/icon.iconset

  # Generate different icon sizes
  magick "$SOURCE_ICON" -resize 16x16 macos/icon.iconset/icon_16x16.png
  magick "$SOURCE_ICON" -resize 32x32 macos/icon.iconset/icon_32x32.png
  magick "$SOURCE_ICON" -resize 64x64 macos/icon.iconset/icon_64x64.png
  magick "$SOURCE_ICON" -resize 128x128 macos/icon.iconset/icon_128x128.png
  magick "$SOURCE_ICON" -resize 256x256 macos/icon.iconset/icon_256x256.png
  magick "$SOURCE_ICON" -resize 512x512 macos/icon.iconset/icon_512x512.png
  
  # For retina displays (@2x)
  magick "$SOURCE_ICON" -resize 32x32 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 64x64 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 128x128 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 256x256 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 512x512 macos/icon.iconset/[email protected]
  magick "$SOURCE_ICON" -resize 1024x1024 macos/icon.iconset/[email protected]

  # Convert iconset to icns
  iconutil -c icns -o macos/res/Audiveris.icns macos/icon.iconset
  
  # Clean up temporary iconset
  rm -rf macos/icon.iconset
  
  echo "Icon successfully created at macos/res/Audiveris.icns"
else
  echo "Warning: Source icon not found at $SOURCE_ICON"
  echo "Creating placeholder icon file (replace with actual icon)..."
  touch macos/res/Audiveris.icns
fi

# Build JRE to bundle
echo "Creating custom JRE..."
./gradlew app:installDist -Dorg.gradle.java.home=$JAVA_HOME
jlink --add-modules java.base,java.datatransfer,java.desktop,java.xml,java.naming,jdk.zipfs --output macos/res/customjre

# Function to recursively find and process all dependencies
process_dependencies() {
  local processed_libs=()
  local lib_queue=("macos/res/customjre/lib/libfontmanager.dylib")
  local visited=()
  
  echo "Analyzing dependencies recursively..."
  
  while [ ${#lib_queue[@]} -gt 0 ]; do
    local current=${lib_queue[1]}
    lib_queue=(${lib_queue[@]:1})
    
    # Skip if already processed
    if [[ " ${visited[@]} " =~ " ${current} " ]]; then
      continue
    fi
    
    visited+=("$current")
    
    echo "Processing: $current"
    
    # Get direct dependencies
    local deps=($(otool -L "$current" | grep -v ":" | grep -v "/System/" | grep -v "/usr/lib/" | awk '{print $1}'))
    
    for dep in "${deps[@]}"; do
      # Skip system libraries
      if [[ "$dep" == /System/* ]] || [[ "$dep" == /usr/lib/* ]]; then
        continue
      fi
      
      # If this is a brew-installed library
      if [[ "$dep" == /usr/local/opt/* ]] && [[ -f "$dep" ]]; then
        local basename=$(basename "$dep")
        
        # Copy library if not already done
        if [ ! -f "macos/res/customjre/lib/$basename" ]; then
          echo "Copying $dep to bundle"
          cp "$dep" "macos/res/customjre/lib/$basename"
          processed_libs+=("$basename")
          lib_queue+=("macos/res/customjre/lib/$basename")
        fi
        
        # Update reference in current library
        echo "Updating reference to $basename in $(basename $current)"
        install_name_tool -change "$dep" "@loader_path/$basename" "$current"
      fi
    done
  done
  
  # Set library IDs for all processed libraries
  echo "Setting library IDs..."
  for lib in "${processed_libs[@]}"; do
    echo "Setting ID for $lib"
    install_name_tool -id "@loader_path/$lib" "macos/res/customjre/lib/$lib"
  done
  
  return 0
}

# Copy key libraries and process dependencies
echo "Copying initial libraries..."
cp /usr/local/opt/tesseract/lib/libtesseract.5.dylib macos/res/customjre/lib/
cp /usr/local/opt/leptonica/lib/libleptonica.6.dylib macos/res/customjre/lib/

# Process all dependencies recursively
process_dependencies

# Get current version and ensure it's properly formatted
version=$(./gradlew -q printVersion 2>/dev/null || echo "1.0.0")
clean_version=$(echo "$version" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)*' | head -1)

# If no valid version was found, use a default
if [ -z "$clean_version" ]; then
    clean_version="1.0"
    echo "Warning: Could not extract a valid version number, using default: $clean_version"
else
    echo "Using version: $clean_version"
fi

# Generate installer
echo "Creating macOS package..."
jpackage --type dmg \
  --input app/build/install/app/lib \
  --main-jar audiveris.jar \
  --name Audiveris \
  --app-version "$clean_version" \
  --runtime-image macos/res/customjre \
  --icon macos/res/Audiveris.icns \
  --main-class Audiveris \
  --dest ~/Downloads \
  --java-options '--add-exports java.desktop/com.apple.eawt=ALL-UNNAMED' \
  --mac-package-identifier org.audiveris.app \
  --mac-package-name "Audiveris" \
  --vendor "Audiveris" \
  --description "Optical Music Recognition"

echo "Package created at ~/Downloads/Audiveris-$clean_version.dmg"

# Check if architecture is universal
echo "Checking architecture compatibility:"
lipo -info ~/Downloads/Audiveris-$clean_version.dmg/Audiveris.app/Contents/MacOS/Audiveris 2>/dev/null || echo "Not a universal binary - will only work on $(uname -m) machines"

echo "Done!"

And the README.md:

# How to Use the Audiveris macOS Installer

This guide explains how to install and run the Audiveris application on macOS using the provided DMG installer. Since the installer is not signed with an Apple Developer certificate, you'll need to adjust your macOS privacy settings to allow it to run.

## Installation Steps

1. **Obtain the DMG File**  
   - Download or receive the `Audiveris-<version>.dmg` file (e.g., `Audiveris-1.0.dmg`) from the source (e.g., a contributor or repository release).

2. **Open the DMG**  
   - Double-click the `Audiveris-<version>.dmg` file in your `Downloads` folder (or wherever it’s saved). This mounts the installer as a virtual disk on your desktop or in Finder.

3. **Install the Application**  
   - Inside the mounted DMG, you’ll see `Audiveris.app`. Drag this file to your **Applications** folder to install it.
   - Once copied, you can eject the DMG by clicking the eject icon next to it in Finder or dragging it to the trash.

## Running Audiveris

Since the app is not signed, macOS will block it by default. Follow these steps to allow it to run:

1. **Attempt to Open the App**  
   - Go to your **Applications** folder and double-click `Audiveris.app`.  
   - You’ll likely see a warning: *"“Audiveris” cannot be opened because it is from an unidentified developer."*

2. **Adjust Privacy Settings**  
   - Open **System Preferences** (or **System Settings** on macOS Ventura and later):  
     - Click the Apple menu () > **System Preferences** > **Security & Privacy** > **General** tab.  
   - At the bottom, you’ll see a message: *“Audiveris” was blocked from use because it is not from an identified developer.*  
   - Click **"Open Anyway"** to allow the app to run.  

3. **Launch the App**  
   - Double-click `Audiveris.app` again. You may see one final prompt asking for confirmation—click **"Open"**.  
   - The app should now launch successfully.

## Notes

- **Unsigned App**: The lack of a signature is due to the installer not being created with an Apple Developer account. This is a one-time adjustment; once approved, macOS will remember your choice.
- **Troubleshooting**: If the app still won’t open, ensure you’ve completed the privacy settings step. For persistent issues, contact the provider or check the Audiveris documentation.

Let me know if you have any other comments. Many thanks!

fbmrqs avatar Mar 10 '25 20:03 fbmrqs

This comes at a fantastic time for me - I've been having a very hard time getting Audiveris to work correctly on my CLI on Mac (I'm only about a year into programming, so I'm still getting a grasp on plenty of new-to-me things). Should I expect this Mac installer to be available soon? Am I able to execute what you have provided in order to run the installer before it is published? I apologize if these are silly questions, but I'll really appreciate any guidance you can offer.

trevorjmccoy avatar Mar 11 '25 17:03 trevorjmccoy

Hi Trevor, you came at the right time! You can try copying the amended script from my last message and then executing it on your computer at your own risk; it should produce the installer in your Downloads folder. Please follow the instructions in my message to install it. If you don't know how to run a bash script, I would not recommend you to proceed.

fbmrqs avatar Mar 11 '25 17:03 fbmrqs

Hi Fabio, It worked like a charm, thank you so much!

trevorjmccoy avatar Mar 11 '25 20:03 trevorjmccoy

Hello Fabio,

I have just created a new branch, name "packaging" off of the "development" branch. Please pull and checkout. Using this branch, we can work together without interfering with the standard development branch.

It contains a new subproject, named "packaging" as well :-) I populated the packaging subproject with a first version, for which only Windows is really up and running. We have just placeholders for the mac and linux blocks right now, but it's a starting point.

I will need your help on at least 2 points:

  • How do you know which java modules we have to insert? (right now, I suppose I copy the whole jvm modules)
  • Icons conversion and other works you made from your shell script, to be integrated as specific tasks in the packaging/build.gradle script.

That's all for now.

hbitteur avatar Mar 12 '25 19:03 hbitteur

Hello Fabio,

I just pushed to the "packaging" branch the commit fc18cfdf5a51af772e297c0d42b9895c46569f01 which is OK for the .exe and .msi Windows installers. To run it:

# For a .exe
./gradlew -q -PinstallerType=exe :packaging:jpackage
# For a .msi
./gradlew -q -PinstallerType=msi :packaging:jpackage

Before getting to your macos version, I will try to complete the linux version. The reason is I can run & test on Linux in VirtualBox.

Meanwhile, you can have a look at the packaging/build.gradle file. You will notice I don't need special code to retrieve the dependencies.

/Hervé

hbitteur avatar Mar 16 '25 19:03 hbitteur

This morning, I switched to (VirtualBox based) Ubuntu. The jpackage task first complained about missing fakeroot software, which I then installed. And bingo! I was able to produce a .deb installer.

Once locally installed, I could launch the application from /opt/audiveris/bin/Audiveris

The application runs OK, except that it cannot access the Internet (just to check version, or to download additional languages). The error is:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

I first suspected a missing module in the (custom) JRE. Just in case, I added the "java.net.http" module to the list provided by @fbmrqs (which was: java.base,java.datatransfer,java.desktop,java.xml,java.naming,jdk.zipfs), but to no avail.

Any idea about what is happening?

hbitteur avatar Mar 17 '25 10:03 hbitteur

Answering my own question: I added the module "jdk.crypto.ec" as suggested on the Internet, and it now works. Internet is great! :-)

Now, why did it run OK on Windows and not on Linux? Perhaps because the underlying Java environments (both 21) are not identical. Plus, this module is said to "migrate" to the "java.base" module. Perhaps, it has already migrated for Windows...

hbitteur avatar Mar 17 '25 11:03 hbitteur

And the README.md:

# How to Use the Audiveris macOS Installer

This guide explains how to install and run the Audiveris application on macOS using the provided DMG installer. Since the installer is not signed with an Apple Developer certificate, you'll need to adjust your macOS privacy settings to allow it to run.

## Installation Steps

1. **Obtain the DMG File**  
   - Download or receive the `Audiveris-<version>.dmg` file (e.g., `Audiveris-1.0.dmg`) from the source (e.g., a contributor or repository release).

2. **Open the DMG**  
   - Double-click the `Audiveris-<version>.dmg` file in your `Downloads` folder (or wherever it’s saved). This mounts the installer as a virtual disk on your desktop or in Finder.

3. **Install the Application**  
   - Inside the mounted DMG, you’ll see `Audiveris.app`. Drag this file to your **Applications** folder to install it.
   - Once copied, you can eject the DMG by clicking the eject icon next to it in Finder or dragging it to the trash.

## Running Audiveris

Since the app is not signed, macOS will block it by default. Follow these steps to allow it to run:

1. **Attempt to Open the App**  
   - Go to your **Applications** folder and double-click `Audiveris.app`.  
   - You’ll likely see a warning: *"“Audiveris” cannot be opened because it is from an unidentified developer."*

2. **Adjust Privacy Settings**  
   - Open **System Preferences** (or **System Settings** on macOS Ventura and later):  
     - Click the Apple menu () > **System Preferences** > **Security & Privacy** > **General** tab.  
   - At the bottom, you’ll see a message: *“Audiveris” was blocked from use because it is not from an identified developer.*  
   - Click **"Open Anyway"** to allow the app to run.  

3. **Launch the App**  
   - Double-click `Audiveris.app` again. You may see one final prompt asking for confirmation—click **"Open"**.  
   - The app should now launch successfully.

## Notes

- **Unsigned App**: The lack of a signature is due to the installer not being created with an Apple Developer account. This is a one-time adjustment; once approved, macOS will remember your choice.
- **Troubleshooting**: If the app still won’t open, ensure you’ve completed the privacy settings step. For persistent issues, contact the provider or check the Audiveris documentation.

Let me know if you have any other comments. Many thanks!

I suggest to copy this verbatim into the Audiveris handbook (which is meant for the end user) in its installation chapter, because it describes how to use the .dmg file.

The purpose of a README.md file, located in the packaging folder, would be meant for the developer: how to build the installer(s) like the .dmg file, which tools are needed, special items depending on the hosting OS, etc. We will write this file incrementally, as we progress in the packaging development.

hbitteur avatar Mar 17 '25 19:03 hbitteur

@fbmrqs Hello Fabio, I just pushed the commit b5d8cad2481be412315772d9ca52ab675c7129f8 on the "packaging" branch. Could you pull it from your side and check if it works as expected on macOS?

Use the command:

./gradlew -qi jpackage

I wrote the "mac" block within the jpackage task, rather blindly, by "converting" your zsh script to gradle. The task makes calls to magick and to iconutil to convert the icons.

Notice I did not convert the last command of your script:

lipo -info ~/Downloads/Audiveris-$clean_version.dmg/Audiveris.app/Contents/MacOS/Audiveris 2>/dev/null || echo "Not a universal binary - will only work on $(uname -m) machines"

It seems to be only for user information purpose, but more importantly, I did not understand the argument which follows the "-info" argument:

~/Downloads/Audiveris-$clean_version.dmg/Audiveris.app/Contents/MacOS/Audiveris

For me, "~/Downloads/Audiveris-$clean_version.dmg" is the name of the created installer file, but it is followed by the text "/Audiveris.app/Contents/MacOS/Audiveris", as if it was a folder rather than a plain file.

Could you shed some light on this "lipo" command?

Thanks, /Hervé

P.S. Meanwhile, I will start to work on Github actions to make them run these packaging tasks.

hbitteur avatar Mar 20 '25 15:03 hbitteur

News from the front:

I'm facing problems while building the installer for macOS (via a github action). For details, see packaging/build.gradle and .github/workflows/build-installers.yml files.

The various "magick" commands populate the build/iconset folder. Its final content is:

total 608
drwxr-xr-x  14 runner  staff     448 Mar 21 16:52 .
drwxr-xr-x   5 runner  staff     160 Mar 21 16:52 ..
-rw-r--r--   1 runner  staff    6118 Mar 21 16:52 icon_128x128.png
-rw-r--r--   1 runner  staff   12664 Mar 21 16:52 [email protected]
-rw-r--r--   1 runner  staff     951 Mar 21 16:52 icon_16x16.png
-rw-r--r--   1 runner  staff    1677 Mar 21 16:52 [email protected]
-rw-r--r--   1 runner  staff   12664 Mar 21 16:52 icon_256x256.png
-rw-r--r--   1 runner  staff   38058 Mar 21 16:52 [email protected]
-rw-r--r--   1 runner  staff    1677 Mar 21 16:52 icon_32x32.png
-rw-r--r--   1 runner  staff    3082 Mar 21 16:52 [email protected]
-rw-r--r--   1 runner  staff   38058 Mar 21 16:52 icon_512x512.png
-rw-r--r--   1 runner  staff  158218 Mar 21 16:52 [email protected]
-rw-r--r--   1 runner  staff    3082 Mar 21 16:52 icon_64x64.png
-rw-r--r--   1 runner  staff    6118 Mar 21 16:52 [email protected]

Then iconutil is called, but fails:

Starting process 'command 'iconutil''.
Working directory: /Users/runner/work/audiveris/audiveris/packaging
Command: iconutil --convert icns --output build/Audiveris.icns build/iconset
Successfully started process 'command 'iconutil''
build/iconset:Invalid Iconset.
build/iconset:Invalid Iconset.
FAILURE: Build failed with an exception.

Gradle can only tell me that iconutil has failed. But iconutil is not very talkative...

Imagemagick version is 7.1.1:

Version: ImageMagick 7.1.1-45 Q16-HDRI aarch64 22722 https://imagemagick.org/
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP 
Delegates (built-in): bzlib fontconfig freetype gslib heic jng jp2 jpeg jxl lcms lqr ltdl lzma openexr png ps raw tiff webp xml zlib zstd
Compiler: clang (16.0.0)

For iconutil, I can't tell (there is no "--version" or similar option)

@fbmrqs Could you check if the build/iconset folder content, as listed above, looks similar to yours, in terms of file names and sizes? Thanks in advance for any hint.

hbitteur avatar Mar 21 '25 17:03 hbitteur

@fbmrqs Fabio, I need your help.

I have just published an Audiveris prerelease Draft 5.5.0 Installers. It is entirely built and published via a dedicated github workflow. This is a major step forward, and you deserve credit for your seminal work.

To make it work on macOS, I had to bypass the creation of the .icns from the iconset folder. I could not make iconutil work. Perhaps I'm not calling it correctly, or the iconset folder does not contain the expected images. I don't know. What puzzles me, is the fact that iconutil (according to the "man iconutil" command) expects a file, not a folder. Perhaps there is some specific macOS behavior behind the scene? I can't test this.

For the time being, could you download the .dmg installer file available in the draft release, and verify if you can install it. And also tell me how it looks without the Audiveris icon. The purpose is to make sure that any macOS user can already use it as it is.

Thanks in advance /Hervé

hbitteur avatar Mar 24 '25 18:03 hbitteur

So, I'm guessing this macOS build has only been tested against Intel based Macs. I've got an Apple Silicon Mac (running Sequoia), and I've installed the dmg file. I've included a picture of the icon in the Launchpad below. However, the issue is whenever I try to run it, it does a single bounce on the dock, but doesn't open. Perhaps this requires some further study though my knowledge of coding is very limited.

Image

Paladinleeds avatar Mar 26 '25 10:03 Paladinleeds

@Paladinleeds Thank you for trying out the .dmg file. I have no access to a Mac, therefore we can only wait for someone more knowledgeable of the Mac environment.

@fbmrqs This question is for you ... :-)

hbitteur avatar Mar 26 '25 11:03 hbitteur

Still no sign of life from @fbmrqs ... :-( Fabio, are you still there?

hbitteur avatar Apr 01 '25 15:04 hbitteur

when we try and install we get the warning about it not being signed or similar. is there a way around this on a Mac?

david3a avatar Apr 14 '25 18:04 david3a

@david3a Not to my knowledge. See this handbook section .

hbitteur avatar Apr 14 '25 18:04 hbitteur

@david3a I don't know how to avoid the warning. I can only tell you that you can go past the warning, as explained in the handbook.

Now, if someone can tell me how to "sign" or "get known" by Apple, I'm interested of course (despite the fact that I have no access to a Mac).

hbitteur avatar Apr 14 '25 18:04 hbitteur

@hbitteur, I think you need an active Apple developer account, and that means money. We had this, but it expires every year. we stick to web apps now for mac's

david3a avatar Apr 15 '25 06:04 david3a

I think you need an active Apple developer account, and that means money. We had this, but it expires every year. we stick to web apps now for mac's

I don't really want to add another penny to Apple's billions. So I think we should stick with the current approach.

Despite the warning, any Mac user seems to be able to use the latest installer version. And the same goes for the other operating systems.

So I'm going to upgrade the current pre-release to a full release.

hbitteur avatar Apr 15 '25 08:04 hbitteur