MacOS X Repository

Welcome

Maybe in the future I’ll use the site to host some other stuff that I use in my job as a Mac sysadmin

Arjen

make syntax

Published: 2010-01-16 14:32:45

There is some basic stuff you need to know if you want to extend the provided luggage.makefile. I was surprised by the nice structured syntax and by how well it actually fits what you try to do when buiding a package.

Rules

Makefiles are governed by rules, basic syntax is:

target: dependency1, dependency2, etc.
	command to create target
	optional other command

If the target exists, make skips the rule. The dependencies are also rules, make checks if the dependencies are up-to-date and builds them if necessary. The commands are regular shell commands indented by a tab. make echoes every command before it is executed unless it is preceded with a @. Here’s an example from luggage.make:

payload_d:
	@sudo mkdir -p ${PAYLOAD_D}

The target is payload_d, it has no other dependencies and it is built using mkdir -p ${PAYLOAD_D} (${PAYLOAD_D} is a variable declared earlier)

Extended rule syntax

It took some time to understand this one:

unbz2-applications-%: %.tar.bz2 l_Applications
	sudo ${TAR} xjf $< -C ${WORK_D}/Applications
	@sudo chown -R root:admin ${WORK_D}/Applications/$(shell echo $< | sed s/\.tar\.bz2//g)

It matches all rules that start with unbz2-applications-, using ‘%’ as a wildcard (like * in bash). Then it substitutes the contents of ‘%’ in the first dependency (%.tar.bz2) and then uses the first dependency in the first command using $<.

So if we have a rule that states unbz2-applications-mail.app, make is going to look for a file or rule that is named mail.app.tar.bz2, then it is going to extract that using ${TAR} xjf mail.app.tar.bz2

Syntax

@ prevents the line (not the output) being echoed
- continue after an error occurs
% wildcard, matches any chars
$< matches the first dependency

permalink

the Luggage

Published: 2010-01-16 09:30:23

The Luggage is a very clean approach to creating packages. It is written by Joe Block. There are a couple of reasons why you should use this instead of a gui tool like packagemaker, iceberg or composer. [insert reasons here]

There is a good documentation page here: http://luggage.apesseekingknowledge.net/ and a git repository here: http://github.com/unixorn/luggage/

There is some stuff you need to know to get it setup properly, there are some minor bugs in luggage.make which is the driving script and there are some things you need to know to create a package that are not obvious for people not accustomed to Makefiles.

I had to read a couple of tutorials about make to understand what exactly is going on in the script, although it utilizes only a small subset of the available make commands. I put up a a brief tutorial which touches on the used commands.

Requirements

You need an OSX workstation with the Developer tools installed (the luggage needs the packagemaker commandline interface and also ‘make’)

Setup

First, download the zip:

luggage.zip (19.81 KB)

I fixed the version that is available on the downloads page which had a typo that prevented it from creating installers for GUI Applications.

Unzip the file.

There are two files that have to go in /usr/local/share/luggage (you have to create this path)

  • luggage.make
  • prototype.plist

You could put these somewhere else, and change two paths in the Makefiles you are going to create.

Run an example

Just to test that you have everything in place, you could run an example that is provided in the unixorn-luggage-8ef104f directory.

Open a terminal and cd into unixorn-luggage-8ef104f/examples/setup_kerberos

type make pkg

it will prompt you for an admin password if you are not root (luggage uses sudo for all operations)

after some terminal-twitter you will have a timestamped package with a kerberos config file in it.

Amazing isn’t it?

if you type make dmg you’ll get a package wrapped in a dmg ready to deploy on a webserver. I use this to create dmg’s that instaUp2date can download for a build.

Create a Firefox installer

Now we can get to business and create our own package Makefile. It is important to keep in mind that make expects all files to be in the same directory as the Makefile (unless stated otherwise), this was not clear for me in the luggage documentation.

Ok we create a new directory, let’s call it install_firefox.

Download the latest version of Firefox and put it into our new directory.

We’ll have to remove the quarantaine attribute to get rid of the warning:

mcalubook:~ bochoven$ cd Desktop/install_firefox/
mcalubook:install_firefox bochoven$ xattr -d com.apple.quarantine Firefox.app

Now compress the App so it becomes portable:

mcalubook:install_firefox bochoven$ tar cvjf Firefox.app.tar.bz2 Firefox.app

Now in your favorite (plain) texteditor create a new file and copy-paste the following:

include /usr/local/share/luggage/luggage.make
TITLE=install_firefox_app
REVERSE_DOMAIN=com.example.corp
PAYLOAD=unbz2-applications-Firefox.app

Save it as “Makefile” inside install_firefox make sure you uncheck “If no extension is provided, use txt”.

now we return to the terminal and type make pkg

And now we should have a nicely packaged Firefox.app

If you want to try out the package on the machine that you just used to create the package, first do a make clean, otherwise the installer is going to put the package in the fake root that the luggage created. I don’t know why this happens but the make clean fixes this.

Create a better Firefox installer

# This makefile creates a package or a dmg from any application
# that sits next to it
# make pkg
# make dmg

include /usr/local/share/luggage/luggage.make

pack-applications-%: % l_Applications
	@sudo ${CP} -R "$<" ${WORK_D}/Applications
	@sudo chown -R root:admin ${WORK_D}/Applications/"$<"
	@-sudo /usr/bin/xattr -d com.apple.quarantine ${WORK_D}/Applications/"$<"


TITLE := $(shell echo *.app | sed -e 's/\.app//' -e 's/ /_/g')
PACKAGE_NAME=${TITLE}
REVERSE_DOMAIN=com.example.corp
PAYLOAD=pack-applications-*.app

some exlanation

permalink - 0 comments

logGen

Published: 2009-03-13 13:20:53

Written by Phil Holland with changes by Dave Pugh, Chris Grieb and J. Billings

logGen is a command-line utility (for now) for detecting filesystem changes after a preference change or package installation. This is primarily useful when creating your own .pkg files so you know what you need to package. 2.2 adds the ability to exclude specific directories from the scanning process in addition to containing logic to avoid logging of resource forks. Please read the updated documentation by typing ‘perldoc /usr/local/sbin/logGen’ .

You can download the logGen installer here: logGen-2.2_.dmg (25.79 KB)

For updates and possible newer versions, please visit
http://www.lsa.umich.edu

Documentation

LOGGEN(1)             User Contributed Perl Documentation            LOGGEN(1)



NAME
       logGen - report filesystem changes

SYNOPSYS
       sudo /usr/local/sbin/logGen [--all|-a] [--fast|-f] [--root|-r <dir>]
       [--user|-u] [orig.dat]

       sudo /usr/local/sbin/logGen [--all|-a] [--fast|-f] [--root|-r <dir>]
       [--user|-u] [new.dat] [orig.dat]

       sudo /usr/local/sbin/logGen [--all|-a] [--fast|-f] [--root|-r <dir>]
       [--user|-u] [new.dat] [orig.dat] > changes.txt

DESCRIPTION
       logGen can be used to detect what files have changed as a result of a
       configuration change or installing a package.  It accomplishes this by
       utilizing a number of methods, but mostly using the modification date
       and a checksum of each file.  Lists will be generated for files that
       are added, changed, or deleted, and will include only the directory if
       everything within it has been added, changed, or deleted.  A number of
       directories are automatically ignored in the search including your home
       directory, temporary directories, network mounts, and non-root volumes.

       As with many tools, logGen cannot accurately detect changes in resource
       forks of files.

       Before performing any changes or installations you'd like to detect,
       take a baseline snapshot of the filesystem by running:

           sudo /usr/local/sbin/logGen orig.dat

       This will write out a data file (orig.dat) containing a listing of each
       file and the information logGen has recorded about each file.  This
       first pass can taken a very long time (even upwards of 30 minutes)
       depending on the speed of your machine and the number of files on your
       disk.

       Next, make the changes you'd like to detect, such as a preference
       change, installing new software, etc, and run logGen a second time.  It
       is recommended (although not required) to redirect STDOUT to a file for
       later examination.

           sudo /usr/local/sbin/logGen new.dat orig.dat > changes.txt

       This will write out a new data file (new.dat) containing a new, current
       listing of each file and the information logGen has recorded about each
       file.  Next, the data is compared between the new.dat and orig.dat
       files and the changes are summarized and printed to STDOUT, and in this
       example saved to changes.txt.  This second execution of logGen gener-
       ally takes much less time than the first.

       All of the filenames are changable.  If you omit a filename for the
       original data file (orig.dat in the above examples) it will default to
       <currentEpochTime>.log, such as "1076949440.log".

OPTIONS
       Three options are available:

       --all (or -a): Check all directories, including /tmp, /var, etc.  This
       still ignores things like /Network and /Volumes, though, to avoid
       checking lots of things you really shouldn't check.

       --fast (or -f): Skips MD5 checks of files - the only downside to this
       is that files whose timestamps have changed but whose contents remain
       identical will be reported as changed even though they didn't.

       --root <dir> (or -r <dir>): Sets the root directory of the search to
       the specified directory.  This could be useful if, for example, you're
       just looking for what preference changed in /Library/Preferences/

       --user (or -u): Includes the /Users directory in the scan.  Normally
       this directory is ignored.

       The order of the options DOES matter - They should be typed in the same
       order as they are listed here.  I felt lazy programming that day...
       Sorry.

EXAMPLE
        % sudo /usr/local/sbin/logGen orig.dat

        logGen  --  version 1.0
        Copyright 2003 - The Regents of the University of Michigan
        All Rights Reserved

        361883 new files:
        ---------------
        /
        ---------------
        0 changed files
        0 deleted files
        0 files with resource forks
        0 files with special permissions

        % sudo /usr/local/sbin/logGen new.dat orig.dat

        logGen  --  version 1.0
        Copyright 2003 - The Regents of the University of Michigan
        All Rights Reserved

        1 new files:
        ---------------
        /Library/NewDir/
        ---------------
        1 changed files:
        ---------------
        /Library/TestDir2/someFile
        ---------------
        1 deleted files:
        ---------------
        /Library/TestDir/
        ---------------
        1 files with resource forks:
        ---------------
        /Library/iconFile
        ---------------
        2 files with special permissions:
        ---------------
        /Library/aFileSetUID ( setuid )
        /Library/anotherFile ( setgid world_writable )
        ---------------

AUTHORS
       Originally written by Phil Holland at the University of Michigan.
       Numerous changes provided by Dave Pugh at the University of Michigan.
       Questions, requests, comments, and code changes should be sent to
       lsa-dev-osx@umich.edu

COPYRIGHT
       COPYRIGHT 2005-2008 THE REGENTS OF THE UNIVERSITY OF MICHIGAN ALL
       RIGHTS RESERVED

       PERMISSION IS GRANTED TO USE, COPY, CREATE DERIVATIVE WORKS AND REDIS-
       TRIBUTE THIS SOFTWARE AND SUCH DERIVATIVE WORKS FOR ANY PURPOSE, SO
       LONG AS NO FEE IS CHARGED, AND SO LONG AS THE COPYRIGHT NOTICE ABOVE,
       THIS GRANT OF PERMISSION, AND THE DISCLAIMER BELOW APPEAR IN ALL COPIES
       MADE; AND SO LONG AS THE NAME OF THE UNIVERSITY OF MICHIGAN IS NOT USED
       IN ANY ADVERTISING OR PUBLICITY PERTAINING TO THE USE OR DISTRIBUTION
       OF THIS SOFTWARE WITHOUT SPECIFIC, WRITTEN PRIOR AUTHORIZATION.

       THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION FROM THE UNI-
       VERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY PURPOSE, AND WITHOUT WAR-
       RANTY BY THE UNIVERSITY OF MICHIGAN OF ANY KIND, EITHER EXPRESS OR
       IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MER-
       CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
       UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE FOR ANY DAMAGES, INCLUDING
       SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WITH RESPECT
       TO ANY CLAIM ARISING OUT OF OR IN CONNECTION WITH THE USE OF THE SOFT-
       WARE, EVEN IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
       SUCH DAMAGES.

       If you do make any changes, it would be appreciated if you submitted
       them back to lsa-dev-osx@umich.edu



perl v5.8.6                       2008-02-12                         LOGGEN(1)

permalink

pkgGen

Published: 2009-03-12 23:16:56

pkgGen is a perl script that parses the output from logGen and copies the files to a fake root directory. You can use this tree to build a package using packagemaker, iceberg or some other packaging tool.

The script comes in an installer package that places it in /usr/local/sbin:

pkgGen.zip (7.27 KB)

Source

#!/usr/bin/perl
# pkgGen v1
# Zack Smith
# Based on a script originally created by Geoff Franks (Buy him the Beer)
# Hauptman-Woodward Medical Research Institute
# 
# This script takes two parameters. The first is the path to the 
# LogGen > log file. The second is the folder which you would like to 
# create your Root directory for the PKG installer to base from. This folder 
# MUST NOT exist, or the script will fail. 
#
# It parses the log file, regenerating the drectory structure of added files 
# and directories, preserving permissions, modification times, and resource 
# forks as tightly as possible. IMPORTANT: To preserve all permissions, run this
# with root level permissions. 
# More to come later...

use strict;

#Set up some globalish variables- charset for the encoding on the log file
#srclog for the log file, root dir for the base directory, and loglines for data
my $CHARSET = "utf-8";
my $SRCLOG;
my $ROOTDIR;
my @loglines;
#function for warn about usage and exit 
sub usage{
  warn "Usage: pkgGen <path to logGen output txt file> <path of pkg fauxroot>\n";
  exit(1);
}

#function for checking arguments
sub getarguments{
  
  #we need arguments
  if($#ARGV == -1){
    &usage;
    exit(0);
    
    #and only 2
  }elsif($#ARGV != 1){
    &usage;
    exit(0);
  }
  
  #so if we have them, set the two args
  $SRCLOG = shift @ARGV;
  $ROOTDIR= shift @ARGV;
  
  #if the log file doesn't exist, quit now.
  unless(-e $SRCLOG){
    &usage;
    exit(0);
  }
}

#first line actually run- check the arguments.
&getarguments;

#create the root directory with default perms of root:admin, 1775 (sticky bit)
mkdir($ROOTDIR) or die ("Could not create $ROOTDIR");
chown "root","admin", $ROOTDIR;
chmod oct("1775"), $ROOTDIR;

#open the log file, and read it in
open FILE , "<:encoding($CHARSET)", $SRCLOG or die ("Could not open log file $SRCLOG");
# Split the log file into an array of lines to loop through
@loglines = <FILE>;
close FILE;

#loop through the lines
for my $line (@loglines)
{
  #any matches to this unless statement are lines you do not want parsed
  # if the script dies due to meaningless filler info like the date, install 
  # mesages - anything not referring to a file, add them here to be ignored
  unless ( 
	$line =~ /(setuid|setgid|sticky_bit|world_writable) \)$/ || 
	# ABOVE: Only paths we don't need are theese.
	$line =~ /^[^\/](.*)/ ) # Any line that doesn't with a solidus we junk
  {
     # set up some variables for use within the loop- type for file type, file
     # for file path
     my $type;
     my $file;
     if ($line =~ /(.*)\/$/)
     {
       $type = "dir";
       $file = "$1";
     #  print "Dir: $file\n";
     }
      else
      { 
	     #check to see if it's a file
	     if ($line =~ /(.*)$/)
	      {
	        #set that it's a file, and save the path.
	        $type = "file";
	        $file = "$1";
	       # print "File: $file\n";
	      }
        else
        {
          #otherwise, I don't know what the line means, so quit and say what 
          #the line was
          print "Unknown line: $line\n";
          die("Exiting prematurely...");
        }
      }
      #get an array of directories leading up to the file
        my @dirs = split "/" , $file;
        my $dir;
        my $count = scalar(@dirs);
        
        #loop through all but the file/directory name itself
        for (my $i = 0; $i < $count - 1; $i++)
        {
          #grab the next directory in the array
          $dir .= shift @dirs;
          $dir .= "/";
          #grab file attributes on the real directory
          my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,
	$blksize,$blocks) = stat("/$dir");

          #translate the mode/type to just the permissions mode
          $mode = sprintf("%04o",$mode & 07777);

          #if the directory doesn't exist in the recreated structure
          unless (-d "${ROOTDIR}${dir}")
          {
            #make it, setting ownership and perms to the original's
            #print "Mode: $mode\n";
	    #print "mkdir ${ROOTDIR}${dir}"
            mkdir("$ROOTDIR/$dir");
            chown($uid,$gid,"$ROOTDIR/$dir");
            chmod oct($mode), "$ROOTDIR/$dir";

          }
        }
        
        #by the time we get here, we know the parent directories exist
        if ( $type eq "file")
        {
          #so it's time to copy the file if it's a file
          print "Copying $file\n";
#	  print "cp -vp \"$file\" \"${ROOTDIR}${file}\"\n"
          system "cp -p \"$file\" \"${ROOTDIR}${file}\"";
        }
        else
        {
            #or if it's a directory,
            if ( $type eq "dir")
            {
                #recreate it with the original perms/ownership
                 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
	$ctime,$blksize,$blocks) = stat("/$file");
                 print "Creating $file\n";
                  $mode = sprintf("%o",$mode & 07777);
#		  print "cp -Rvp \"$file\" \"${ROOTDIR}${file}\"\n"
		$file =~ s/\/$//; # Remove trailing solidus for cp sytax
		 system "cp -Rnp \"$file\" \"${ROOTDIR}${file}\"";
		# Recursively copy any new directories
                 chown($uid,$gid,"${ROOTDIR}${file}");
                 chmod oct($mode), "${ROOTDIR}${file}";

            }
            else
            {
                die( "How the heck did we get here?\n");
            }
        }
  }
}

#after it loops through the log file, that's pretty much it, so it exits.

permalink