Porting PackageMaker command line build installer to pkgbuild - macos

I've been attempting to port a Mac PackageMaker command line build to pkgbuild and productbuild but I'm stuck. Unfortunately I haven't found much of anything documenting how these new programs work except for this StackOverflow post and the pkgbuild and productbuild man pages.
Here's my problem. I've created a root install directory that has the following files in it:
/some_path/Applications
/MyProgram.app
/Library
/Frameworks
/MyFramework.framework
/[library files...]
The command line call below worked great for PackageMaker. It created an installer that installed all of the files above.
$ /Developer/usr/bin/packagemaker \
--title "My Program" \
--root /some_path \
--version 1.0.0 \
--filter "\.DS_Store" \
--resources ./resources/ \
--scripts ./scripts/ \
--root-volume-only \
--domain system \
--verbose \
--no-relocate \
--target 10.5 \
--id com.my_company.pkg \
--out MyProgram.pkg
Now I'm trying to write this with pkgbuild and having a major problem. I use the following call:
$ pkgbuild \
--root /some_path \
--version 1.0.0 \
--install-location "/" \
--scripts "./scripts/" \
--identifier "com.my_company.pkg" \
MyProgram.pkg
This command builds an installer that copies the MyProgram.framework directory into /Library/Frameworks. However it does not install the MyProgram.app file into the /Applications directory. When I look at the installer logs I see this message:
Applications/MyProgram.app relocated to
/some_path/Applications/MyProgram.app
Can anyone shed some light on why this isn't adding the MyProgram.app file into the /Applications directory like PackageMaker was doing?

Unfortunately the answer to this question wasn't exactly what I was looking for. I couldn't figure out how to eliminate PackageMaker from the process. However there is a solution that includes pkgutil along with PackageMaker to create an installer with custom welcome message, license and background image entirely on the command line. The PackageMaker GUI is NOT required. The steps are as follows:
Run the packagemaker command line executable on the special directory structure. This directory structure reflects the Mac file system. Read more in the old but reliable "PackageMaker How-to" tutorial.
Run pkgutil (pkgutil --expand) to extract the package contents
Take a look at the contents and identify what you want to alter. Some options are the welcome message, license and background image.
Add commands to alter these files via the command line. Review the "Automating Apple's PackageMaker" tutorial for more information. The easiest way is just to run something like this echo '<background file="your_background.png">'.
Run pkgutil (pkgutil --flatten) to rebuild the package.

First of all, are you sure that you need an installer? You could put the framework inside the application. The Installer and pkgbuild are a bit flaky, to say the least.
Now to the problem at hand: Relocation has to do with the fact that a user could move the Application from /Applications to say /WorkApplications /PrivateApplications. In your case the Installer probably finds your Application in the build folder and installs it over this one.
I think the Installer uses the Application Bundle Identifier and Spotlight for the relocation, so for testing you could add the build folder to the Spotlight ignore list.
You can define in the Component Property List BundleIsRelocatable. If you really have to install a framework global, this is one bundle where you want to set BundleIsRelocatable to false.

The question you reference has pretty much everything you need to eliminate Package Maker entirely. One thing I added is to use sed after the productbuild --synthesize ... invocation to insert lines into the distribution file. For example, here are some Terminal commands I use once I've already built the component package:
productbuild --synthesize --package "components/SubPackage.pkg" "distribution.xml"
sed -i "" \
-e '$ i\
\ <title>Installer Title</title>' \
-e '$ i\
\ <background file="background.png" alignment="left" scaling="proportional" />' \
-e '$ i\
\ <welcome file="welcome.rtf" />' \
"distribution.xml"
productbuild --distribution "distribution.xml" --resources "resources/" --package-path "components/" "Installer.pkg"
This avoids having to use pkgutil --expand and pkgutil --flatten to modify the installer.

Create component plist file, which has value for relocatable property.
pkgbuild --analyze --root "$dst_src_root" "$installer_root/Components.plist"
Edit component plist and Set "BundleIsRelocatable" to false.
This is done one time in my project, as installer contents does not change (One application and one plugin). I am reusing same component plist everytime to create contents package.
sudo pkgbuild --root "$dst_src_root" --component-plist "$installer_root/Components.plist" --identifier "com.company.app" --version "1.0" --scripts "$dst_scpt_root" "$dst_pkg_root/InstallPackage/cisContents.pkg"
Then using productbuild, we can create final package

Related

OSX pkgbuild: Place package's plist in startup disk when installing to alternate volume

We're creating the installer for an Audio Plugin using pkgbuild. It consists of several packages (VST, AU, Content, Presets etc.).
Since the content is quite big we want to allow users to install the content package to external drives. I got that working with the appropriate settings in the distribution.xml (customLocationAllowAlternateVolumes="Yes"). However when the content is installed to such an external drive, the installer also places a .plist and a .bom file on the external drive in /Library/Receipts folder (which it creates) on that drive (see screenshot).
This folder structure is created on the external drive:
How can I achieve that these files are placed onto the startup disk no matter to which drive the content package is installed?
Here is the pkgbuild script:
#!/bin/bash
# Name of the package.
NAME="<OurProductName> Content"
IDENTIFIER="com.<OurCompanyName>.content.pkg.<OurProductName>"
# Package version number. The correct number will be set in the build process.
VERSION="1.0.1"
INSTALL_LOCATION="/Library/Application Support/<OurCompanyName>/<OurProductName>"
ROOT_LOCATION="content/"
# Remove any unwanted .DS_Store files.
find "$ROOT_LOCATION" -name '*.DS_Store' -type f -delete
chmod -R +r "$ROOT_LOCATION"
# Build package.
/usr/bin/pkgbuild \
--root "$ROOT_LOCATION" \
--install-location "$INSTALL_LOCATION" \
--identifier "$IDENTIFIER" \
--version "$VERSION" \
--scripts Scripts \
"$NAME.pkg"
This is the relevant snippet from the distribution file:
<pkg-ref id="com.<OurCompanyName>.content.pkg.<OurProductName>" version="1.0.1" auth="Admin" onConclusion="None">Some%20Product%20Name%20Content.pkg</pkg-ref>
<choice id="Content" title="<OurProductName> Content" start_visible="true" start_selected="true" start_enabled="true" customLocation="/Library/Application Support/<OurCompanyName>/<OurProductName>" customLocationAllowAlternateVolumes="Yes" description='You can change the file location for the plugin content file. Click on the folder name in "Location" to select a different one.'>
<pkg-ref id="com.<OurCompanyName>.content.pkg.<OurProductName>"/>
</choice>

xcodebuild command line hangs

The following command hangs on my osx:
xcodebuild -scheme myscheme clean archive -archivePath /tmp
This command yields two output lines, and then hangs:
User defaults from command line:
IDEArchivePathOverride = /tmp
Now, this project does NOT have a workspace generated as it was created from a cordova command line (cordova build ios). The only way around it is to open xcode and close it. this generates a workspace and then the above command succeeds.
Did anyone experience something similar and know a way out of this? Any way to generate that workspace from the command line?
I had the same problem and the only way of fixing it was to open the project from the command line, wait, and close it again after a certain time.
open "My Project.xcodeproj"
sleep 10
killall Xcode
xcodebuild -scheme "My Project" clean archive "build/MyProject"
Not nice, but works for me.
Try setting the scheme to be 'shared'.
This can be done by going to the 'Manage Schemes...' and checking the 'Shared' checkbox.
Apple documents this process here: https://developer.apple.com/library/ios/recipes/xcode_help-scheme_editor/Articles/SchemeShare.html
If you're already have, or are willing to make, Ruby available to your build system then you could use this solution.
Install the xcodeproj gem on your build system
sudo gem install xcodeproj
and then integrate the following ruby script into your project (renaming your xcodeproj path).
#!/usr/bin/env ruby
require 'xcodeproj'
xcproj = Xcodeproj::Project.open("platforms/ios/schemedemo.xcodeproj")
xcproj.recreate_user_schemes
xcproj.save
The article explains how to make it part of a cordova hook if you're doing that, I simply called ruby directly from my Jenkins build.
This works because when you recreate the proj files, you destroy the schemes, so you need to recreate them.
I believe xcodebuild hangs because some data is missing from the project. You can make a template for what this data looks like and use a hook to populate it if necessary.
cordova add platform ios
cordova build ...
open platforms/ios/Whatever.xcodeproj in xcode
create xcuserdata_template
cp -R platforms/ios/Whatever.xcodeproj/xcuserdata xcuserdata_template/
replace the unique id in that template with XXXXXXXXXX
update your hook that runs xcodebuild
Step 7 example:
XCODE_PROJ=path/to/Whatever.xcodeproj
# get the mysterious id
ID=`grep "Whatever \*\/ = {" $XCODE_PROJ/project.pbxproj | \
grep -io "[-A-Z0-9]\{24\}"`
mkdir -p $XCODE_PROJ/xcuserdata
XCUSERDATAD=$XCODE_PROJ/xcuserdata/`whoami`.xcuserdatad
if [ ! -d "$XCUSERDATAD" ]; then
cp -R path/to/xcuserdata_template/username.xcuserdatad \
$XCUSERDATAD
find $XCUSERDATAD -type f -exec sed -i '' -e "s/XXXXXXXXXX/$ID/g" {} \;
fi
xcodebuild ...

RocksDB make install

From the (slightly) outdated documentation on pyrocksdb, it says:
"If you do not want to call make install export the following enviroment variables:"
$ export CPLUS_INCLUDE_PATH=${CPLUS_INCLUDE_PATH}:`pwd`/include
$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`
$ export LIBRARY_PATH=${LIBRARY_PATH}:`pwd`
But the installation instructions for RocksDB do not seem to mention any sort of install target!
Is there an accepted procedure for installing RocksDB from source?
My thoughts are to just copy the contents of the include directory from the rocksdb directory into somewhere like /usr/local/include and copy the librocksdb.so and librocksdb.a files into /usr/local/lib. Is this an acceptable method?
Note: The method of exporting environment variables was less preferable to me, as I built rocksdb in a directory inside my home folder--I am hoping for a cleaner solution (interpret that how you want).
RocksDB recently has make install. If you use the latest version, you should be able to do make install in RocksDB.
There is no install target in the current Makefile.
This breaks the long-established conventions for writing Makefiles (or pretty-much every other build system...); it should be considered a defect.
Without spending a lot of time analysing I can't be sure, but the install target should be something like:
prefix=/usr/local
bindir=$(prefix)/bin
# Normally you'd write a macro for this; 'lib' for 32-bit, 'lib64' for 64...
libdir=$(prefix)/lib64
includedir=$(prefix)/include
# Define this to be the directory(s) the headers are installed into.
# This should not include the 'include' element:
# include/rocksdb/stuff -> rocksdb/stuff
HEADER_DIRS=...
# Define this so all paths are relative to both the $CWD/include directory...
# so include/rocksdb/foo.h -> HEADER_FILES=rocksdb/foo.h
HEADER_FILES=...
.PHONY: install
install: $(TOOLS) $(LIBRARY) $(SHARED) $(MAKEFILES)
mkdir -p $(DESTDIR)$(bindir)
mkdir -p $(DESTDIR)$(libdir)
mkdir -p $(DESTDIR)$(includedir)
for tool in $(TOOLS); do \
install -m 755 $$tool $(DESTDIR)$(bindir); \
done
# No, libraries should NOT be executable on Linux.
install -m 644 $(LIBRARY) $(DESTDIR)$(libdir)
install -m 644 $(SHARED3) $(DESTDIR)$(libdir)
ln -s $(SHARED3) $(DESTDIR)$(libdir)/$(SHARED2)
ln -s $(SHARED2) $(DESTDIR)$(libdir)/$(SHARED1)
for header_dir in $(HEADER_DIRS); do \
mkdir -p $(DESTDIR)$(includedir)/$$header_dir; \
done
for header in $(HEADER_FILES); do \
install -m 644 include/$$header $(DESTDIR)$(includedir)/$$header; \
done
This will then allow you to install the files into /usr/local, by simply doing:
make install
However, the reason it's so heavily parameterised, is so you can change the destination folder, without having to modify the Makefile. For example, to change the destination to /usr, you simply do:
make prefix=/usr install
Alternatively, if you'd like to test the installation process, without messing with your filesystem, you could do:
make DESTDIR=/tmp/rocksdb_install_test prefix=/usr install
This would put the files into /tmp/rocksdb_install_test/usr which you can then check to see if they're where you want them to be... when you're happy, you can just do rm -Rf /tmp/rocksdb_install_test to cleanup.
The variables I've used are essential for packaging with RPM or DEB.
I an use ubuntu 16.04
DEBUG_LEVEL=0 make shared_lib install-shared
In this way, the installation is already generated in the production mode.
If you want to save time, you can specify the quantities of processors used in the process by passing -j[n], in my case, -j4
DEBUG_LEVEL=0 make -j4 shared_lib install-shared
In the case of ubuntu, this is sufficient, but in the case of ubuntu for docker, you should specify where the lib was installed.
export LD_LIBRARY_PATH=/usr/local/lib
Hope this helps.
Kemper

Building distribution Installer package (.pkg) with postflight script without requiring authentication

I'm using the new domain feature of PackageMaker (introduced for Mac OS 10.5) to target the user home directory. I have created a .pmdoc file in PackageMaker.app, and everything works perfectly until I add my post-install script. Then, suddenly, my package wants root authorization when it didn't before. I've tried building from the command-line using packagemaker --doc mypackage.pmdoc --info Dist/PackageInfo supplying a tweaked PackageInfo file that explicitly specifies auth="none", but this doesn't work. When I investigate the output package by extracting it with xar -xf package.pkg, authentication seems to be specified in package.pkg/Distribution, an XML file that packagemaker generates for itself.
Due to frustration with the GUI, I've switched to using only packagemaker on the command line. However, now my packages don't display my user interface files (although they are included in the .pkg archive), and still demand root authentication. The offending line in the generated Distribution file is (notice auth="Root"):
<pkg-ref id="org.myUniqueID.pkg" installKBytes="12032" version="1.0" auth="Root">#grooveshark.pkg</pkg-ref>
This is how I run packagemaker:
packagemaker -r ./Grooveshark -f ./Dist/PackageInfo -s ./Dist/Scripts -e ./Dist/Resources -v --domain user --target 10.5 --no-relocate --discard-forks --no-recommend -o ./out.pkg
This is the layout of Dist:
Dist/Distribution # this isn't used by packagemaker, it generates its own
Dist/PackageInfo
Dist/Resources/en.lproj/background
Dist/Resources/en.lproj/License
Dist/Resources/en.lproj/ReadMe
Dist/Resources/en.lproj/Welcome.rtfd
Dist/Resources/en.lproj/Welcome.rtfd/gsDesktopPreview-mini.png
Dist/Resources/en.lproj/Welcome.rtfd/gsDesktopPreview-searchSmall.png
Dist/Resources/en.lproj/Welcome.rtfd/TXT.rtf
Dist/Scripts/jsuuid # specified as a postinstall in Dist/PackageInfo
Dist/Scripts/postflight
How can I configure my package so it will run a postinstall script without demanding root authentication? Is there some way I'm missing to specify both a PackageInfo file and a Distribution install-script XML file via the command line?
I ended up moving files int place in a distribution layout, then I used the following script to first build a traditional flat package, then expand it, copy in the settings that allow for per-user installation, then use a different process to compact it in-place, without processing, back into a PKG.
#!/usr/bin/bash
# Build Package for local install using witchcraft
PROJECT="some/filesystem/location/with/your/files"
BUILDDIR="$PROJECT/Dist/build"
PKGROOT="$PROJECT/Dist/Package_Root"
INFO="$PROJECT/Dist/PackageInfo"
DIST="$PROJECT/Dist/Distribution"
RESOURCES="$PROJECT/Dist/Resources"
SCRIPTS="$PROJECT/Dist/Scripts"
# Remove .DS_Store files
find "$PKGROOT" -name ".DS_Store" | sed 's/ /\\ /' | xargs rm
# make build dir
mkdir "$BUILDDIR"
# build flat package that needs root to install
packagemaker -r "$PKGROOT" -f "$INFO" -s "$SCRIPTS" $ARGS -o "$BUILDDIR/flat.pkg"
# Build distribution that installs into home dirs by unpacking the flat pkg
echo "Building Distribution"
echo " Copying filesystem"
cp -r "$RESOURCES" "$BUILDDIR/Resources"
cp "$DIST" "$BUILDDIR/Distribution"
echo " extracting flat package"
pkgutil --expand "$BUILDDIR/flat.pkg" "$BUILDDIR/grooveshark.pkg/"
rm "$BUILDDIR/flat.pkg"
echo " flattening distribution"
pkgutil --flatten "$BUILDDIR" "$PROJECT/$1.pkg"
echo "Finished!"

how to create drag and drop mac installer?

I have made my java application's jar file and now want to create drag and drop installer for it.Also I want to know that how to create shortcut for Applications folder for the same purpose.
how can I do this?
Seth Willits' DMG Canvas is a great and easy way to create customized DMGs (with things like an Applications folder shortcut, background image, etc). Yes, you can do this all yourself with hdiutil and whatnot, but I much prefer using a GUI.
You can Package your .jar into a .app (inside a dmg that has drag&drop) with Javapackager like this:
javapackager -deploy \
-title "YourTitle" \
-name "Name" \
-appclass your.class.start \
-native dmg \
-outdir ~/Downloads \
-outfile yourOutFileWithoutExtension \
-srcdir "SourceDir"
you could use -srcfiles Your.jar if its only a single jar.
Use either Disk Utility or the hdiutil command-line tool. It's easier to integrate hdiutil into an automatic build workflow.
You need to create a small disk image containing your application and a symbolic link to the Applications folder. Use Disk Utility or hdiutil to create the image. You can just copy the application in, and create the symbolic link using Terminal:
cd /Volumes/DiskImageName
ln -s /Applications Applications

Resources