Building a universal Appbunder build for java on MacOS M1 machine
Asked Answered
C

1

12

I have been building MacOs Java application on Intel MacMini for number of years, essentially

Now have it building on MacOS M1 and it builds okay. When I run on Mac M1 it works but when I try to run on Intel Macmini it fails with Unable to load JRE Environment

This kind of makes sense because when I build it I am just providing a single jre based on the M1 version of Adoptium Terium 17. But I dont understand how I provide two JRE's if this is what I need to do

Below I show the main build script

#!/bin/bash
#set -x
cd $HOME/code/jthink/songkong/src/main/scripts
hiutil -C  -fapplehelpbook/SongKongHelp/SongKongHelp.helpindex applehelpbook/SongKongHelp/
cd $HOME/code/jthink/songkong
sudo rm -fr /Applications/SongKong.app
mvn -f pommacos.xml clean
mvn -f pommacos.xml -DskipTests=true install
rm -fr target/songkong-8.2
unzip target/songkong-8.2-distribution.zip -d target
ant
while read line; do
  echo "$line"
  if [[ "$line" = "<string>1.0</string>" ]]; then
    cat mergefile.txt    # or echo or printf your extra lines
  fi
done < /Applications/SongKong.app/Contents/Info.plist > Info.new
while read line; do
  echo "$line"
  if [[ "$line" = "<false/></dict>" ]]; then
    cat mergefile2.txt
  fi
done < Info.new > Info.new2
rm /Applications/SongKong.app/Contents/Info.plist
rm Info.new
mv Info.new2 /Applications/SongKong.app/Contents/Info.plist
sudo cp -r target/songkong-8.2/applehelpbook/SongKongHelp /Applications/SongKong.app/Contents/Resources
export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate"
/usr/bin/codesign --timestamp --options runtime \
--entitlements $HOME/code/jthink/songkong/songkong.entitlements \
--sign "Developer ID Application: P Taylor" \
--force --deep --verbose /Applications/SongKong.app
/usr/bin/codesign -vvv --deep --strict /Applications/SongKong.app
spctl -a -t exec -vv /Applications/SongKong.app
cd $HOME/code/jthink/SongKong
/usr/local/bin/dmgcanvas $HOME/code/jthink/SongKong/dmgCanvas_songkong.dmgCanvas $HOME/songkong-osx.dmg -v SongKong -identity "Developer ID Application: P Taylor" -notarizationAppleID [email protected] -notarizationPassword xxxxxxxxxxxxxxxxxxxxxx -notarizationPrimaryBundleID songkong

and the ant file used by the appbundler task

    <?xml version="1.0" encoding="iso-8859-1"?>
<project name="songkong">
    <property environment="env"/>
    <property name="songkongsrc"        value="/Users/paultaylor/code/jthink/SongKong/target/songkong-8.2"/>
    <property name="resources"      value="/Users/paultaylor/code/jthink/SongKong/src/main/resources"/>
    <property name="songkonglib"        value="${songkongsrc}/lib"/>
    <property name="songkongtargetroot"     value="/Applications"/>
    <property name="songkongtarget"     value="${songkongtargetroot}/SongKong.app"/>
    <property name="songkongcontentstarget"     value="${songkongtarget}/Contents"/>

    <!-- Obtain path to the selected JRE -->
    <exec executable="/usr/libexec/java_home"
          failonerror="true"
          outputproperty="runtime">
        <arg value="-v"/>
        <arg value="17"/>
    </exec>
    <taskdef name="bundleapp" classname="com.oracle.appbundler.AppBundlerTask"/>
    <bundleapp outputdirectory="${songkongtargetroot}"
               name="SongKong"
               displayname="SongKong"
               executableName="SongKong"
               identifier="com.jthink.songkong"
               shortversion="8.2"
               mainclassname="com.jthink.songkong.cmdline.SongKong"
               signature="sgkg"
               applicationCategory="public.app-category.music"
               icon="${songkongsrc}/songkong.icns"
    >
         <classpath dir="${songkonglib}">
              <include name="**/*.jar"/>
         </classpath>
         <classpath dir="${songkongsrc}">
              <include name="songkong.properties"/>
         </classpath>
         <librarypath dir="${songkongsrc}">
              <include name="fpcalc_osx"/>
              <include name="libAppleScriptEngine.dylib"/>
         </librarypath>
        <option value="-Dcom.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog"/>
        <option value="-Dorg.jboss.logging.provider=jdk"/>
        <option value="-Djava.util.logging.config.class=com.jthink.songkong.logging.StandardLogging"/>
        <option value="-Dapple.laf.useScreenMenuBar=true"/>
        <option value="-Dapple.awt.brushMetalLook=true"/>
        <option value="-Dsun.java2d.metal=true"/>
        <option value="-XX:+HeapDumpOnOutOfMemoryError"/>
        <plistentry key="NSRequiresAquaSystemAppearance" type="boolean" value="false"/>
        <plistentry key="AppleWindowTabbingMode" value="manual"/>
        <bundledocument extensions="mp3,wma,ogg,flac,wav,aif,aiff,aifc"
                        name="Music Files"
                        role="editor"
                        isPackage="false">
        </bundledocument>
        <jlink runtime="${runtime}">
            <jmod name="java.desktop"/>
            <jmod name="java.datatransfer"/>
            <jmod name="java.logging"/>
            <jmod name="java.management"/>
            <jmod name="java.naming"/>
            <jmod name="java.net.http"/>
            <jmod name="java.prefs"/>
            <jmod name="java.scripting"/>
            <jmod name="java.sql"/>
            <jmod name="jdk.management"/>
            <jmod name="jdk.unsupported"/>
            <jmod name="jdk.jcmd"/>
            <jmod name="jdk.crypto.ec"/>
            <jmod name="jdk.dynalink"/>
            <argument value="--compress=2"/>
            <argument value="--release-info=${runtime}/release"/>
        </jlink>
    </bundleapp>
    <copy file="${resources}/create_itunes_model.scpt"      tofile="${songkongcontentstarget}/Java/create_itunes_model.scpt"/>
    <copy file="${resources}/create_music_model.scpt"      tofile="${songkongcontentstarget}/Java/create_music_model.scpt"/>
    <copy file="${resources}/get_playlist_folder.scpt"      tofile="${songkongcontentstarget}/Java/get_playlist_folder.scpt"/>
    <copy file="${resources}/get_music_playlist_folder.scpt"      tofile="${songkongcontentstarget}/Java/get_music_playlist_folder.scpt"/>
    <copy file="${songkongsrc}/songkong.properties" tofile="${songkongcontentstarget}/songkong.properties"/>
    <copy file="${songkongsrc}/songkong1.properties" tofile="${songkongcontentstarget}/songkong1.properties"/>
    <copy file="${songkongsrc}/songkong2.properties" tofile="${songkongcontentstarget}/songkong2.properties"/>
    <copy file="${songkongsrc}/songkong2.properties" tofile="${songkongcontentstarget}/songkong3.properties"/>
    <copy file="${songkongsrc}/songkong2.properties" tofile="${songkongcontentstarget}/songkong4.properties"/>
    <copy file="${songkongsrc}/songkong5.properties" tofile="${songkongcontentstarget}/songkong5.properties"/>
    <copy file="${songkongsrc}/renamemask.properties" tofile="${songkongcontentstarget}/renamemask.properties"/>
    <copy file="${songkongsrc}/general.properties" tofile="${songkongcontentstarget}/general.properties"/>
    <copy file="${songkongsrc}/license.properties" tofile="${songkongcontentstarget}/license.properties"/>
    <copy file="${songkongsrc}/genrelist.txt" tofile="${songkongcontentstarget}/genrelist.txt"/>
    <copy file="${songkongsrc}/classical_composers.txt" tofile="${songkongcontentstarget}/classical_composers.txt"/>
    <copy file="${songkongsrc}/classical_conductors.txt" tofile="${songkongcontentstarget}/classical_conductors.txt"/>
    <copy file="${songkongsrc}/classical_people.txt" tofile="${songkongcontentstarget}/classical_people.txt"/>
    <copy file="${songkongsrc}/not_classical_release.txt" tofile="${songkongcontentstarget}/not_classical_release.txt"/>
    <copy file="${songkongsrc}/license.txt" tofile="${songkongcontentstarget}/license.txt"/>
    <copy file="${songkongsrc}/index.html" tofile="${songkongcontentstarget}/index.html"/>
    <copy file="${songkongsrc}/pdfOSX/help.pdf"        tofile="${songkongcontentstarget}/help.pdf"/>
    <copy file="${songkongsrc}/osx/bin/songkong.sh"    tofile="${songkongcontentstarget}/bin/songkong.sh"/>
    <copy file="${songkongsrc}/osx/bin/songkongremote.sh"    tofile="${songkongcontentstarget}/bin/songkongremote.sh"/>
    <copy todir="${songkongcontentstarget}/style"><fileset dir="${songkongsrc}/style"/></copy>
    <copy todir="${songkongcontentstarget}/lang"><fileset dir="${songkongsrc}/lang"/></copy>
    <chmod file="${songkongcontentstarget}/bin/songkong.sh" perm="777"/>
    <chmod file="${songkongcontentstarget}/bin/songkongremote.sh" perm="777"/>
</project>
Califate answered 29/3, 2022 at 15:34 Comment(0)
P
5

Edit 1:.

So when you say, you want to create a universal app, as I understand:

  1. you want to have a single jar that will run on both platforms
  2. you want to bundle both jre's in same build as you want to run on two separate platforms.
  3. You will have both jre's present on your host system from which you are bundling app.
  4. You want single app with both jre's included for both mac platforms.

If you want to achieve 1-4, there is a hard road ahead:

You can also customize an appbundler by forking this repository

You need to focus on this class first: https://github.com/TheInfiniteKind/appbundler/blob/master/appbundler/src/com/oracle/appbundler/AppBundlerTask.java

How it works: Challenge we need to solve is Appbundler takes only 1 runtime at any given time.

https://github.com/TheInfiniteKind/appbundler/blob/master/appbundler/src/com/oracle/appbundler/AppBundlerTask.java#L235

Later it copies the runtime contents to a directory called PlugIns

https://github.com/TheInfiniteKind/appbundler/blob/master/appbundler/src/com/oracle/appbundler/AppBundlerTask.java#L467

You can extend AppBundlerTask and override public void execute() method.

Here you can provide List<Runtime> instead of single Runtime.

now, this execute method creates a Info.plist file, you can create two instead with different runtime elements. https://mcmap.net/q/405025/-configuration-dependent-value-in-info-plist-file

I am highly in doubt if this will work for following three issues in my mind: a. How Plugins directory is loaded in the app and how its used is not known to me, so two runtimes in same dir, no idea if its okay. b. If at all we can have two plist file in same app how to toggle them with device environment variable from app side is not known to me. This env variable will decide what plist file to select. c. app size post bundling two jre's in single app.

Also note, accordingly you will have to update your ant task to provide two directories for 2 jre's.


If only 1,2,3 are correct, you can bundle two jre's in two different applications:

I think answer still remains the same as the exec tag itself is just setting your java runtime. However you can try following way as well.

Just add condition for this exec

<!-- Obtain path to the selected JRE -->
<exec executable="/usr/libexec/java_home"
      failonerror="true"
      outputproperty="runtime">
    <arg value="-v"/>
    <arg value="17"/>
</exec>

kindly note that: this exec tag is doing same thing that <runtime dir="${env.JAVA_HOME}" /> is doing in provided article.

https://docs.oracle.com/javase/7/docs/technotes/guides/jweb/packagingAppsForMac.html

eg:

<?xml version="1.0" encoding="UTF-8"?>
<target name="set-runtime">
   <echo level="info">setting-runtime</echo>
   <if>
      <equals arg1="${os.family}" arg2="mac" />
      <then>
         <echo message="setting to mac desktop jre" />
         <!-- Obtain path to the selected JRE -->
         <exec executable="/usr/libexec/java_home" failonerror="true" outputproperty="runtime">
            <arg value="-v" />
            <arg value="17" />
         </exec>
      </then>
      <else>
         <echo message="setting to mac mini jre" />
         <!-- Obtain path to the selected JRE -->
         <exec executable="path to java home for mac mini on my system" failonerror="true" outputproperty="runtime">
            <arg value="-v" />
            <arg value="17" />
         </exec>
      </else>
   </if>
</target>

From what I am guessing you are loading the jre in exec tag of ant xml.

You can use Ant conditionals if you want to negotiate based on some criteria eg: device, os, os version and so on.

<condition property="isMacOsButNotMacOsX">
  <and>
    <os family="mac"/>
    <not>
      <os family="unix"/>
    </not>
  </and>
</condition>

also ref: https://mcmap.net/q/933566/-execute-ant-task-if-two-conditions-are-met

Additionally, if you want to do anything special eg: package jar for two different platforms altogether with different jdk versions you can: https://www.zghurskyi.com/maven-package-to-multiple-java-versions/

Parnassus answered 4/4, 2022 at 17:9 Comment(4)
No its defined in the appbundler task, i think this questions is basically about if/how can use appbundler to create universal binary.Califate
@PaulTaylor its a difficult task as I see, edited in same answer to give you more insights on which you can try things out.Parnassus
Thanks. re 1 you want to have a single jar that will run on both platforms, No I just want a single .app bundleCalifate
understood, so you will have to have two jres and stuff mentioned for pt 1-4 in editParnassus

© 2022 - 2024 — McMap. All rights reserved.