[Pythonmac-SIG] buildpkg.py

Dinu Gherman gherman@darwin.in-berlin.de
Thu, 12 Sep 2002 08:51:48 +0200


--Apple-Mail-7--180782879
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed

Dan Grassi:

> Jack Jansen wrote:
>
>> Thanks go to Dinu Gherman for his buildpkg script (which is now 
>> included in Mac/scripts).
>
> The buildpkg.py is great and I plan to start using it immediately.

Meanwhile I've sent Jack a patch which mainly rationalizes and simpli-
fies the resource handling (I'm attaching it hear again). I also have
a nice test suite for it, but I'm not sure how this can be included
as you need some file tree for testing (well, it could be an option,
but...). Also attached...

> There are a couple of things missing from buildpkg.py.

Yep, like building multi-packages (.mpkg)... Feel free to send
patches including pointers to where some "undocumented" feature is
mentioned.

BTW, it is perfectly possible to use Python for the pre/post instal-
lation/upgrade scripts run by Installer.app. They just must not have
a .py extension! Apple seems to use Perl scripts sometimes, like in
iCal.pkg/Contents/Resources/InstallationCheck .

Regards,

Dinu

--Apple-Mail-7--180782879
Content-Disposition: attachment;
	filename=buildpkg.patch
Content-Transfer-Encoding: 7bit
Content-Type: application/octet-stream;
	x-unix-mode=0644;
	name="buildpkg.patch"

*** Mac/scripts/buildpkg.py	Fri Sep  6 23:55:13 2002
--- Mac/scripts/buildpkg-0.3.py	Mon Sep  9 18:28:53 2002
***************
*** 41,52 ****
  
  Dinu C. Gherman, 
  gherman@europemail.com
! November 2001
  
  !! USE AT YOUR OWN RISK !!
  """
  
! __version__ = 0.2
  __license__ = "FreeBSD"
  
  
--- 41,52 ----
  
  Dinu C. Gherman, 
  gherman@europemail.com
! September 2002
  
  !! USE AT YOUR OWN RISK !!
  """
  
! __version__ = 0.3
  __license__ = "FreeBSD"
  
  
***************
*** 117,135 ****
  
      This is intended to create OS X packages (with extension .pkg)
      containing archives of arbitrary files that the Installer.app 
!     will be able to handle.
  
      As of now, PackageMaker instances need to be created with the 
      title, version and description of the package to be built. 
      The package is built after calling the instance method 
!     build(root, **options). It has the same name as the constructor's 
!     title argument plus a '.pkg' extension and is located in the same 
!     parent folder that contains the root folder.
  
!     E.g. this will create a package folder /my/space/distutils.pkg/:
  
!       pm = PackageMaker("distutils", "1.0.2", "Python distutils.")
!       pm.build("/my/space/distutils")
      """
  
      packageInfoDefaults = {
--- 117,151 ----
  
      This is intended to create OS X packages (with extension .pkg)
      containing archives of arbitrary files that the Installer.app 
!     (Apple's OS X installer) will be able to handle.
  
      As of now, PackageMaker instances need to be created with the 
      title, version and description of the package to be built. 
+     
      The package is built after calling the instance method 
!     build(root, resources, **options). The generated package is 
!     a folder hierarchy with the top-level folder name equal to the 
!     constructor's title argument plus a '.pkg' extension. This final
!     package is stored in the current folder.
!     
!     The sources from the root folder will be stored in the package
!     as a compressed archive, while all files and folders from the
!     resources folder will be added to the package as they are.
  
!     Example:
!     
!     With /my/space being the current directory, the following will
!     create /my/space/distutils-1.0.2.pkg/:
  
!       PM = PackageMaker
!       pm = PM("distutils-1.0.2", "1.0.2", "Python distutils.")
!       pm.build("/my/space/sources/distutils-1.0.2")
!       
!     After a package is built you can still add further individual
!     resource files or folders to its Contents/Resources subfolder
!     by using the addResource(path) method: 
! 
!       pm.addResource("/my/space/metainfo/distutils/")
      """
  
      packageInfoDefaults = {
***************
*** 175,181 ****
          # set folder attributes
          self.sourceFolder = root
          if resources == None:
!             self.resourceFolder = root
          else:
              self.resourceFolder = resources
  
--- 191,197 ----
          # set folder attributes
          self.sourceFolder = root
          if resources == None:
!             self.resourceFolder = None
          else:
              self.resourceFolder = resources
  
***************
*** 190,196 ****
          # Check where we should leave the output. Default is current directory
          outputdir = options.get("OutputDir", os.getcwd())
          packageName = self.packageInfo["Title"]
!         self.PackageRootFolder = os.path.join(outputdir, packageName + ".pkg")
   
          # do what needs to be done
          self._makeFolders()
--- 206,212 ----
          # Check where we should leave the output. Default is current directory
          outputdir = options.get("OutputDir", os.getcwd())
          packageName = self.packageInfo["Title"]
!         self.packageRootFolder = os.path.join(outputdir, packageName + ".pkg")
   
          # do what needs to be done
          self._makeFolders()
***************
*** 201,206 ****
--- 217,235 ----
          self._addSizes()
  
  
+     def addResource(self, path):
+         "Add arbitrary file or folder to the package resource folder."
+         
+         # Folder basenames become subfolders of Contents/Resources.
+         # This method is made public for those who wknow what they do!
+    
+         prf = self.packageResourceFolder
+         if isfile(path) and not isdir(path):
+             shutil.copy(path, prf)
+         elif isdir(path):
+             os.system("cp -r %s %s" % (path, prf))
+         
+ 
      def _makeFolders(self):
          "Create package folder structure."
  
***************
*** 208,219 ****
          # packageName = "%s-%s" % (self.packageInfo["Title"], 
          #                          self.packageInfo["Version"]) # ??
  
!         contFolder = join(self.PackageRootFolder, "Contents")
          self.packageResourceFolder = join(contFolder, "Resources")
!         os.mkdir(self.PackageRootFolder)
          os.mkdir(contFolder)
          os.mkdir(self.packageResourceFolder)
  
      def _addInfo(self):
          "Write .info file containing installing options."
  
--- 237,249 ----
          # packageName = "%s-%s" % (self.packageInfo["Title"], 
          #                          self.packageInfo["Version"]) # ??
  
!         contFolder = join(self.packageRootFolder, "Contents")
          self.packageResourceFolder = join(contFolder, "Resources")
!         os.mkdir(self.packageRootFolder)
          os.mkdir(contFolder)
          os.mkdir(self.packageResourceFolder)
  
+ 
      def _addInfo(self):
          "Write .info file containing installing options."
  
***************
*** 264,321 ****
  
  
      def _addResources(self):
!         "Add Welcome/ReadMe/License files, .lproj folders and scripts."
  
!         # Currently we just copy everything that matches the allowed 
!         # filenames. So, it's left to Installer.app to deal with the 
!         # same file available in multiple formats...
  
          if not self.resourceFolder:
              return
  
!         # find candidate resource files (txt html rtf rtfd/ or lproj/)
!         allFiles = []
!         for pat in string.split("*.txt *.html *.rtf *.rtfd *.lproj", " "):
!             pattern = join(self.resourceFolder, pat)
!             allFiles = allFiles + glob.glob(pattern)
! 
!         # find pre-process and post-process scripts
!         # naming convention: packageName.{pre,post}-{upgrade,install}
!         # Alternatively the filenames can be {pre,post}-{upgrade,install}
!         # in which case we prepend the package name
!         packageName = self.packageInfo["Title"]
!         for pat in ("*upgrade", "*install"):
!             pattern = join(self.resourceFolder, packageName + pat)
!             allFiles = allFiles + glob.glob(pattern)
! 
!         # check name patterns
!         files = []
!         for f in allFiles:
!             for s in ("Welcome", "License", "ReadMe"):
!                 if string.find(basename(f), s) == 0:
!                     files.append((f, f))
!             if f[-6:] == ".lproj":
!                 files.append((f, f))
!             elif f in ["pre-upgrade", "pre-install", "post-upgrade", "post-install"]:
!                 files.append((f, self.packageInfo["Title"]+"."+f))
!             elif f[-8:] == "-upgrade":
!                 files.append((f,f))
!             elif f[-8:] == "-install":
!                 files.append((f,f))
! 
!         # copy files
!         for src, dst in files:
!             f = join(self.resourceFolder, src)
!             if isfile(f):
!                 shutil.copy(f, os.path.join(self.packageResourceFolder, dst))
!             elif isdir(f):
!                 # special case for .rtfd and .lproj folders...
!                 d = join(self.packageResourceFolder, dst)
!                 os.mkdir(d)
!                 files = GlobDirectoryWalker(f)
!                 for file in files:
!                     shutil.copy(file, d)
! 
  
      def _addSizes(self):
          "Write .sizes file with info about number and size of files."
--- 294,311 ----
  
  
      def _addResources(self):
!         "Add all files and folders inside a resources folder to the package."
  
!         # This folder normally contains Welcome/ReadMe/License files, 
!         # .lproj folders and scripts.
  
          if not self.resourceFolder:
              return
  
!         files = glob.glob("%s/*" % self.resourceFolder)
!         for f in files:
!             self.addResource(f)
!         
  
      def _addSizes(self):
          "Write .sizes file with info about number and size of files."
***************
*** 347,390 ****
  # Shortcut function interface
  
  def buildPackage(*args, **options):
!     "A Shortcut function for building a package."
      
      o = options
      title, version, desc = o["Title"], o["Version"], o["Description"]
      pm = PackageMaker(title, version, desc)
      apply(pm.build, list(args), options)
  
! 
! ######################################################################
! # Tests
! ######################################################################
! 
! def test0():
!     "Vanilla test for the distutils distribution."
! 
!     pm = PackageMaker("distutils2", "1.0.2", "Python distutils package.")
!     pm.build("/Users/dinu/Desktop/distutils2")
! 
! 
! def test1():
!     "Test for the reportlab distribution with modified options."
! 
!     pm = PackageMaker("reportlab", "1.10", 
!                       "ReportLab's Open Source PDF toolkit.")
!     pm.build(root="/Users/dinu/Desktop/reportlab", 
!              DefaultLocation="/Applications/ReportLab",
!              Relocatable="YES")
! 
! def test2():
!     "Shortcut test for the reportlab distribution with modified options."
! 
!     buildPackage(
!         "/Users/dinu/Desktop/reportlab", 
!         Title="reportlab", 
!         Version="1.10", 
!         Description="ReportLab's Open Source PDF toolkit.",
!         DefaultLocation="/Applications/ReportLab",
!         Relocatable="YES")
  
  
  ######################################################################
--- 337,350 ----
  # Shortcut function interface
  
  def buildPackage(*args, **options):
!     "A shortcut function for building a package."
      
      o = options
      title, version, desc = o["Title"], o["Version"], o["Description"]
      pm = PackageMaker(title, version, desc)
      apply(pm.build, list(args), options)
  
!     return pm
  
  
  ######################################################################
***************
*** 446,452 ****
                "Description" in ok):
          print "Missing mandatory option!"
      else:
!         apply(buildPackage, args, optsDict)
          return
  
      printUsage()
--- 406,412 ----
                "Description" in ok):
          print "Missing mandatory option!"
      else:
!         pm = apply(buildPackage, args, optsDict)
          return
  
      printUsage()

--Apple-Mail-7--180782879
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
	charset=US-ASCII;
	format=flowed



--Apple-Mail-7--180782879
Content-Disposition: attachment;
	filename=test_buildpkg.py
Content-Transfer-Encoding: quoted-printable
Content-Type: application/text;
	x-unix-mode=0755;
	x-mac-type=54455854;
	name="test_buildpkg.py"

#!/usr/bin/env=20python=0D=0A=0D=0A"test_buildpkg.py"=0D=0A=0D=0A=0D=0A=0D=
=0Aimport=20os,=20unittest,=20glob=0D=0Aimport=20buildpkg=0D=0A=0D=0A=0D=0A=
=0D=0A=0D=0Adef=20package(self,=20resFolder=3DNone,=20resFiles=3DNone):=0D=
=0A=20=20=20=20"Package=20a=20root=20directory=20with=20some=20variable=20=
resources."=0D=0A=20=20=20=20=0D=0A=20=20=20=20title,=20version,=20desc=20=
=3D=20"readline-4.3",=20"4.3",=20"GNU=20Readline=20Library"=0D=0A=20=20=20=
=20=0D=0A=20=20=20=20#=20remove=20previously=20build=20package=0D=0A=20=20=
=20=20if=20os.path.exists(title=20+=20".pkg"):=0D=0A=20=20=20=20=20=20=20=
=20os.system("rm=20-r=20%s.pkg"=20%=20title)=0D=0A=20=20=20=20=20=20=20=20=
=0D=0A=20=20=20=20#=20build=20package=0D=0A=20=20=20=20pm=20=3D=20=
buildpkg.PackageMaker(title,=20version,=20desc)=0D=0A=20=20=20=20=
pm.build(os.path.join("samples",=20title),=20resFolder)=0D=0A=20=20=20=20=
for=20path=20in=20resFiles=20or=20[]:=0D=0A=20=20=20=20=20=20=20=20=
pm.addResource(path)=0D=0A=20=20=20=20=20=20=20=20=0D=0A=20=20=20=20#=20=
check=20the=20package=20folder=20exists=0D=0A=20=20=20=20=
self.failUnless(os.path.exists(title=20+=20".pkg"))=0D=0A=0D=0A=20=20=20=20=
#=20make=20a=20list=20of=20the=20copied=20resource=20files=0D=0A=20=20=20=
=20destFolder=20=3D=20title=20+=20".pkg/Contents/Resources/"=0D=0A=20=20=20=
=20destResFiles=20=3D=20os.listdir(destFolder)=0D=0A=20=20=20=20=
destResFiles=20=3D=20map(lambda=20f:=20os.path.basename(f),=20=
destResFiles)=0D=0A=0D=0A=20=20=20=20#=20check=20the=20files=20from=20=
the=20resources=20folder=20were=20copied=0D=0A=20=20=20=20if=20=
resFolder:=0D=0A=20=20=20=20=20=20=20=20files=20=3D=20glob.glob("%s/*"=20=
%=20resFolder)=0D=0A=20=20=20=20=20=20=20=20files=20=3D=20map(lambda=20=
f:=20os.path.basename(f),=20files)=0D=0A=20=20=20=20=20=20=20=20for=20f=20=
in=20files:=0D=0A=20=20=20=20=20=20=20=20=20=20=20=20self.failUnless(f=20=
in=20destResFiles)=0D=0A=20=20=20=20=20=20=20=20=20=20=20=20=0D=0A=20=20=20=
=20#=20check=20individual=20resource=20files=20were=20copied=0D=0A=20=20=20=
=20for=20path=20in=20resFiles=20or=20[]:=0D=0A=20=20=20=20=20=20=20=20=
base=20=3D=20os.path.basename(path)=0D=0A=20=20=20=20=20=20=20=20=
self.failUnless(base=20in=20destResFiles)=0D=0A=20=20=20=20=20=20=20=20=20=
=20=20=20=0D=0A=0D=0A=0D=0A=0D=0Aclass=20=
PackageBuilderTest(unittest.TestCase):=0D=0A=20=20=20=20"Various=20tests=20=
for=20making=20OS=20X=20packages=20(.pkg)"=0D=0A=0D=0A=20=20=20=20def=20=
test0(self):=0D=0A=20=20=20=20=20=20=20=20"Vanilla=20test=20for=20the=20=
GNU=20readline=20distribution."=0D=0A=0D=0A=20=20=20=20=20=20=20=20=
package(self)=0D=0A=0D=0A=0D=0A=20=20=20=20def=20test1(self):=0D=0A=20=20=
=20=20=20=20=20=20"As=20test0,=20but=20with=20additional=20simple=20=
resources."=0D=0A=0D=0A=20=20=20=20=20=20=20=20package(self,=20=
"resources/simple")=0D=0A=0D=0A=0D=0A=20=20=20=20def=20test2(self):=0D=0A=
=20=20=20=20=20=20=20=20"As=20test0,=20but=20with=20additional=20=
localized=20resources."=0D=0A=0D=0A=20=20=20=20=20=20=20=20package(self,=20=
"resources/localized")=0D=0A=0D=0A=0D=0A=20=20=20=20def=20test3(self):=0D=
=0A=20=20=20=20=20=20=20=20"As=20test0,=20but=20with=20additional=20=
single=20resource."=0D=0A=0D=0A=20=20=20=20=20=20=20=20package(self,=20=
None,=20["resources/scripts/postflight"])=0D=0A=0D=0A=0D=0A=20=20=20=20=
def=20test4(self):=0D=0A=20=20=20=20=20=20=20=20"As=20test0,=20but=20=
with=20additional=20single=20resource=20tree."=0D=0A=0D=0A=20=20=20=20=20=
=20=20=20package(self,=20None,=20["resources/localized/German.lproj"])=0D=
=0A=0D=0A=0D=0A=0D=0A=0D=0Adef=20makeSuite():=0D=0A=20=20=20=20suite=20=3D=
=20unittest.TestSuite()=0D=0A=20=20=20=20loader=20=3D=20=
unittest.TestLoader()=0D=0A=20=20=20=20=
suite.addTest(loader.loadTestsFromTestCase(PackageBuilderTest))=0D=0A=0D=0A=
=20=20=20=20return=20suite=0D=0A=0D=0A=0D=0A=0D=0A=0D=0Aif=20__name__=20=
=3D=3D=20'__main__':=0D=0A=20=20=20=20=
unittest.TextTestRunner().run(makeSuite())=0D=0A=0D=0A=

--Apple-Mail-7--180782879--