MAKEMSI quickly and reliably creates MSI files in a non-programmatic way
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
[Bottom][Contents][Prev]: Sample Validation Suite[Next]: Making Merge Modules
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
\->MAKEMSI Installs...->Available Frameworks->Python To MSI

Python To MSI

This framework compiles your Python scripts (using "PY2EXE") and creates an MSI to install them.

The "py2exe"product is a Python distutils extension which converts python scripts into executable windows programs, able to run without requiring a python installation! You will probably need to use other commands such as "path" and "shortcut".

This framework is not yet complete but it could be used as is (some limitations), but if interest is shown I'll speed up its development... I have got this to work for simple ".py" files, I'm only starting to look at python so perhaps I need to add support for particular things, let me know (it would help my testing/development to have working python code).

I have not attempted to merge this into python "distutils", when I learn more I may decide its worth it.

Your Main Options

The python support can be used in these ways:

  1. You include MAKEMSI support as for any other MAKEMSI project and the relevant python related commands will be available. A possible disadvantage with this is that if the python compile step fails you have had to wait for the initial MAKEMSI steps to complete!

  2. You include "PY2MSI.MMH" directly (before MAKEMSI support) and have a version file containing product and version information like any other MAKEMSI project.

  3. You include "PY2MSI.MMH" directly (before MAKEMSI support) but don't have a ".ver" file. You supply or allow to default information such as:

Example - HELLO.PY to MSI Script - #1

This script is based on the "simple" sample that "PY2EXE" installs at "Lib\site-packages\py2exe\samples\simple", copy "hello.py" and "test_wx.py" from these directories.

;----------------------------------------------------------------------------
;
;    MODULE NAME:   HELLO.PY.MM
;
;        $Author:   USER "Dennis"  $
;      $Revision:   1.50  $
;          $Date:   16 Sep 2005 18:09:04  $
;       $Logfile:   C:/DBAREIS/Projects.PVCS/Win32/MakeMsi/MmVersion.mmh.pvcs  $
;
; DESCRIPTION
; ~~~~~~~~~~~
; Demonstrates the use of "PY2MSI.MMH" framework.
; This shows how to integrate a Python build with a "traditional" MAKEMSI
; script.
;----------------------------------------------------------------------------


;----------------------------------------------------------------------------
;--- Include MAKEMSI support (with my customisations and MSI branding) ------
;----------------------------------------------------------------------------
#include "ME.MMH"


;----------------------------------------------------------------------------
;--- Build the "dist" contents (compiled scripts) ---------------------------
;----------------------------------------------------------------------------
<$PyCompile CopyRight="(C)opyright Dennis Bareis 2005. All Rights Reserved">
    ;--- File 1 -------------------------------------------------------------
    <$PyScript "hello.py"   Type="console">     ;;File version = MSI version
    <$PyScript "test_wx.py" Type="windows">
<$/PyCompile>


;----------------------------------------------------------------------------
;--- Define some files we don't wish to include in the MSI ------------------
;----------------------------------------------------------------------------
<$FilesExclude EXLIST="PY2EXE_DIST" "<$PY2MSI_OPTION_dist_dir>\MSVCR71.dll">
<$FilesExclude EXLIST="PY2EXE_DIST" "<$PY2MSI_OPTION_dist_dir>\w9xpopen.exe">


;----------------------------------------------------------------------------
;--- Define where we wish to install these files at installation time -------
;----------------------------------------------------------------------------
<$DirectoryTree Key="INSTALLDIR" Dir="<$PY2MSI_INSTALL_DIR>" CHANGE="\" PrimaryFolder="Y">


;----------------------------------------------------------------------------
;--- Add the files in the dist directory (ignore those we defined above) ----
;----------------------------------------------------------------------------
<$Files "<$PY2MSI_OPTION_dist_dir>\*.*" DestDir="INSTALLDIR" EXLIST="PY2EXE_DIST">


;----------------------------------------------------------------------------
;--- Add a shortcut to one of these files -----------------------------------
;----------------------------------------------------------------------------
;--- need to ignore warnings for now...
<$Component "ShortCut" Create="Y" Directory_="INSTALLDIR" LM="Y">
   <$DirectoryTree Key="SCDIR" Dir="<$PY2MSI_SHORCUT_DIR>" MAKE="Y" REMOVE="Y">
   #(
       <$Shortcut
                   Dir="SCDIR"
               ;Feature="."                             ;;Advertise current
               Target="[INSTALLDIR]test_wx.exe"
                 Title="Py Test"
           Description="blah blah."
                  ;Icon="@Help"
               WorkDir="INSTALLDIR"
       >
   #)
<$/Component>

Example - HELLO.PY to MSI Script - #2

This script is based on the "simple" sample that "PY2EXE" installs at "Lib\site-packages\py2exe\samples\simple", copy "hello.py" and "test_wx.py" from these directories.

;----------------------------------------------------------------------------
;
;    MODULE NAME:   HELLO.PY.MM
;
;        $Author:   USER "Dennis"  $
;      $Revision:   1.50  $
;          $Date:   16 Sep 2005 18:09:04  $
;       $Logfile:   C:/DBAREIS/Projects.PVCS/Win32/MakeMsi/MmVersion.mmh.pvcs  $
;
; DESCRIPTION
; ~~~~~~~~~~~
; Demonstrates the use of "PY2MSI.MMH" framework.
;----------------------------------------------------------------------------


;--- Include the framework --------------------------------------------------
#include "PY2MSI.MMH"


;--- Build the "dist" contents ----------------------------------------------
<$PyCompile Company="Dennis Bareis" CopyRight="(C)opyright Dennis Bareis 2005. All Rights Reserved">
    ;--- File 1 -------------------------------------------------------------
    <$PyScript "hello.py"   Version="1.2.3" Type="console">
    <$PyScript "test_wx.py" Version="5.6.7" Type="windows">
<$/PyCompile>


;--- Following needs major improving... -------------------------------------
#define  VER_FILE_DONT_USE_IT
#define  VER_FILE_DONT_READ_PRODUCT_INFO
#define  VER_BUTTON_FOR_CHANGE_HISTORY
#define? ProdInfo.ProductName          <$PY2MSI_MSI_PRODUCT_NAME>
#define? ProductVersion                <$PY2MSI_MSI_PRODUCT_VERSION>
#define? ProdInfo.MsiName              <$PY2MSI_MSI_PRODUCT_NAME>
#define? ProdInfo.Installed
#define? ProdInfo.Note
#define? ProdInfo.Description
#define? ProdInfo.UpgradeCodes
#define? ProdInfo.Licence

#define VER_NumberOfChangeDetails 0
#define ProductChange
#include "me.mmh"

<$DirectoryTree Key="INSTALLDIR" Dir="[ProgramFilesFolder]\<$PY2MSI_MSI_PRODUCT_NAME>" CHANGE="\" PrimaryFolder="Y">

;--- Add the files (components generated) -----------------------------------
<$FilesExclude EXLIST="PY2EXE_DIST" "<$PY2MSI_OPTION_dist_dir>\MSVCR71.dll">
<$FilesExclude EXLIST="PY2EXE_DIST" "<$PY2MSI_OPTION_dist_dir>\w9xpopen.exe">
<$Files        EXLIST="PY2EXE_DIST" "<$PY2MSI_OPTION_dist_dir>\*.*" DestDir="INSTALLDIR">

Example - PY2MSI.MMH (Python Framework Header)

;----------------------------------------------------------------------------
;
;    MODULE NAME:   PY2MSI.MMH
;
;        $Author:   USER "Dennis"  $
;      $Revision:   1.1  $
;          $Date:   26 Feb 2007 17:22:08  $
;       $Logfile:   C:/DBAREIS/Projects.PVCS/Win32/MakeMsi/PY2MSI.MMH.pvcs  $
;      COPYRIGHT:   (C)opyright Dennis Bareis, Australia, 2003
;                   All rights reserved.
;
; DESCRIPTION
; ~~~~~~~~~~~
; This header:
;   1. allows a user to define how the compile of Python scripts
;      should be performed.
;   2. Compiles the ".py" files specified into ".EXE" (using "PY2EXE").
;   3. Builds the MSI.
;   4. Allows you to define extra files (not yet)
;----------------------------------------------------------------------------

;----------------------------------------------------------------------------
;--- Only load support once -------------------------------------------------
;----------------------------------------------------------------------------
#ifdef  PY2MSI_LOADED
    ;--- Already loaded -----------------------------------------------------
    #eof 1
#endif
#define PY2MSI_LOADED


;----------------------------------------------------------------------------
;--- Local Namespace, START -------------------------------------------------
;----------------------------------------------------------------------------
#NextId PUSH
#NextId


;----------------------------------------------------------------------------
;--- What mode are we funtioning in? ----------------------------------------
;----------------------------------------------------------------------------
#ifdef  MAKEMSI_VERSION
    ;--- MAKEMSI.MMH already included ---------------------------------------
    #define @@PY2MSI_MAKEMSI_PREVIOUSLY_LOADED Y
#elseif
    ;--- We well need to do so... -------------------------------------------
    #define @@PY2MSI_MAKEMSI_PREVIOUSLY_LOADED N
#endif


;----------------------------------------------------------------------------
;--- Some Defaults ----------------------------------------------------------
;----------------------------------------------------------------------------
#define? PY2MSI_OUTPUT_DIR                  out
#define? PY2MSI_OPTION_dist_dir             <$PY2MSI_OUTPUT_DIR>\distribute
#define? PY2MSI_OPTION_build_dir            build                   ;;want: <$PY2MSI_OUTPUT_DIR>\build
#define? PY2MSI_COMPILE_PY_NAME             <$PY2MSI_OUTPUT_DIR>\<??InputFile $$FilePart:N>.py
#define? PY2MSI_INSTALL_DIR                 [ProgramFilesFolder]\<$PY2MSI_MSI_PRODUCT_NAME>
#define? PY2MSI_SHORTCUT_DIR                [ProgramMenuFolder]\<$PY2MSI_MSI_PRODUCT_NAME>
#define? PY2MSI_DEFAULT_COMPILE_COPYRIGHT                           ;;Override this in your own header!
#define? PY2MSI_DEFAULT_COMPILE_TYPE        console
#define? PY2MSI_OPTION_compressed           1
#define? PY2MSI_OPTION_optimize             2
#define? PY2MSI_OPTION_ascii                1
#define? PY2MSI_OPTION_bundle_files         1


;----------------------------------------------------------------------------
;--- Work out some basic MSI product information ----------------------------
;----------------------------------------------------------------------------
#ifndef PY2MSI_MSI_PRODUCT_NAME
    ;--- User didn't supply, we will use the name of the ".MM" --------------
    #if ['<$@@PY2MSI_MAKEMSI_PREVIOUSLY_LOADED>' = 'N']
        ;--- Use the name of the ".MM" (best we can do) ---------------------
        #define PY2MSI_MSI_PRODUCT_NAME <?InputFile $$FilePart:BaseName>
    #elseif
        ;--- Get information from the ".ver" file ---------------------------
        #define PY2MSI_MSI_PRODUCT_NAME <$ProdInfo.ProductName>
    #endif
#endif
#ifndef PY2MSI_MSI_PRODUCT_VERSION
    #if ['<$@@PY2MSI_MAKEMSI_PREVIOUSLY_LOADED>' = 'N']
        ;--- Use the date/time as the version (YY.MM.DD.HHMM) ---------------
        #DefineRexx ''
            @@TS  = TimeStamp();
            @@Ver = substr(@@TS, 3,2) || "." || substr(@@TS, 5,2) || "." || substr(@@TS, 7,2) || "." || substr(@@TS, 9,4);
            call MacroSet 'PY2MSI_MSI_PRODUCT_VERSION', @@Ver;
        #DefineRexx
    #elseif
        ;--- Get information from the ".ver" file ---------------------------
        #define PY2MSI_MSI_PRODUCT_VERSION  <$ProductVersion>
    #endif
#endif
#ifndef PY2MSI_MSI_COMPANY
    #if ['<$@@PY2MSI_MAKEMSI_PREVIOUSLY_LOADED>' = 'N']
        ;--- User should probably override ----------------------------------
        #define PY2MSI_MSI_COMPANY <??*USERNAME>          ;;Use "USERNAME" environment variable
    #elseif
        ;--- Get information from the ".ver" file ---------------------------
        #define PY2MSI_MSI_COMPANY <$COMPANY_CONTACT_NAME>
    #endif
#endif


;----------------------------------------------------------------------------
;--- START Compile definition -----------------------------------------------
;----------------------------------------------------------------------------
#(  ''
    #define PyCompile

    ;--- Validate passed parameters -----------------------------------------
    {$!Keywords}
    {$!:VERSION,COMPANY,COPYRIGHT,PRODUCTNAME}

    ;--- Dump main product details ------------------------------------------
    #info ^PYTHON DETAILS^
    #info ^~~~~~~~~~~~~~~^
    #info ^Product: <$PY2MSI_MSI_PRODUCT_NAME>^
    #info ^Version: <$PY2MSI_MSI_PRODUCT_VERSION>^
    #info ^Company: <$PY2MSI_MSI_COMPANY>^

    ;--- Start "setup" file -------------------------------------------------
    #output "<$PY2MSI_COMPILE_PY_NAME>" ASIS
    #( '<?NewLine>'
        from distutils.core import setup
        import py2exe

        ;--- Define EXE property --------------------------------------------
        <?NewLine><?NewLine>
        <?_>### EXE properties #####################################################
        <?_>class PyExe:
        <?_>    def __init__(self, **kw):
        <?_>
        <?_>        ### VersionInfo resources ###################################
        <?_>        self.name         = r"{$ProductName=^<$PY2MSI_MSI_PRODUCT_NAME>^}"         #File's Product Name
        <?_>        self.version      = r"{$Version=^<$PY2MSI_MSI_PRODUCT_VERSION>^}"          #File's Product+File version
        <?_>        self.company_name = r"{$Company=^<$PY2MSI_MSI_COMPANY>^}"                  #File's Company
        <?_>        self.copyright    = r"{$Copyright=^<$PY2MSI_DEFAULT_COMPILE_COPYRIGHT>^}"  #File's Copyright
        <?_>
        <?_>        ### Override the above defaults #############################
        <?_>        self.__dict__.update(kw)
        <?NewLine><?NewLine>
    #)
#)


;----------------------------------------------------------------------------
;--- END Compile definition -------------------------------------------------
;----------------------------------------------------------------------------
#( ''
    #define /PyCompile

    #if @@ExeCnt = 0
        #error ^You must use the "COMPILE" command at least once!^
    #endif

    ;--- Output the Setup command -------------------------------------------
    #(  '<?NewLine>'
        <?NewLine><?NewLine>
        <?_>### Compile #####################################################
        <?_>setup(

        <?_>    options = {"py2exe":
        <?_>                        {
        <?_>                           "compressed":   <$PY2MSI_OPTION_Compressed>,
        <?_>                           "optimize":     <$PY2MSI_OPTION_optimize>,
        <?_>                           "ascii":        <$PY2MSI_OPTION_ascii>,
        <?_>                           "bundle_files": <$PY2MSI_OPTION_bundle_files>,
        <?_>                           "dist_dir":     r"<$PY2MSI_OPTION_dist_dir>"
        <?_>                        }
        <?_>              }
        <?_>    ,

        <?NewLine><?NewLine>
        #{ for @@i = 1 to @@TypeCnt
           <?_>    <??@@Type.@@i> = [<?=value('@@' || @@Type.@@i)>],
        #}
        <?_>    #zipfile=None,
        <?_>)
    #)

    ;--- Complete the file --------------------------------------------------
    #output

    ;--- Start the compile --------------------------------------------------
    #info ^Compiling the Python scripts...^
    #DefineRexx ''
        ;--- Py2EXE bug means that build dir needs deleting if redirecting output ---
        call AddressCmd 'rd /s /q "<$PY2MSI_OPTION_build_dir>" >nul 2>&1';

        ;--- Start with clean dist directory --------------------------------
        call AddressCmd 'rd /s /q "<$PY2MSI_OPTION_dist_dir>" >nul 2>&1';

        ;--- Actual compile step --------------------------------------------
        @@OutputFile = '<$PY2MSI_OUTPUT_DIR>\<??InputFile $$FilePart:N>(compile).TXT';
        @@Cmd = '"<$PY2MSI_COMPILE_PY_NAME>" py2exe';
        @@Cmd = @@Cmd || ' > "' || @@OutputFile || '" 2>&1';
        @@Rc = AddressCmd('cmd.exe /c ' || @@Cmd);
    #DefineRexx
    #if @@Rc <> 0
        ;--- Display an error message ---------------------------------------
        #DefineRexx ''
            @@Output = charin(@@OutputFile, 1, 99999);
            call FileClose @@OutputFile;
            call say copies('*+', 39);
            call say @@Output;
            call say copies('*+', 39);
            if  pos('running py2exe', @@Output) = 0 then;
                @@Reason = "You don't seem to have python and/or py2exe installed!";
            else;
                @@Reason = "You do seem to have python and py2exe installed...";
        #DefineRexx
        #error ^Compile failed, expected RC=0, got RC=<??@@Rc>, from:{NL}{NL}<??@@Cmd>{NL}{NL}<??@@Reason>^
    #endif
    #info ^Successfully compiled the python files^
#)


;----------------------------------------------------------------------------
;--- Definition of a single EXE ---------------------------------------------
;----------------------------------------------------------------------------
#RexxVar @@TypeCnt = '0'
#RexxVar @@ExeCnt  = '0'
#( ''
    #define PyScript

    #evaluate ^^ ^<$@@Rexx4PyScript {$?}>^
    <?NewLine><?NewLine>
    <??@@C>
    <?NewLine><?NewLine>
#)
#DefineRexx '@@Rexx4PyScript'
    ;--- Name of this EXE? --------------------------------------------------
    @@PyName = '{$#1}'

    ;--- Get File property Parameters ---------------------------------------
    @@Version     = "{$Version=^^}";
    @@Company     = "{$Company=^^}";
    @@CopyRight   = "{$Copyright=^^}";
    @@Description = "{$Description=^^}";
    @@ProductName = "{$ProductName=^^}"
    {$Extra=^^ $$RxVar:@@Extra};               ;;Playsafe (allows user to specify "unknown" parameters
    if  @@Description = '' then
        @@Description = 'Compiled Python Script: ' || @@PyName
    if  @@ProductName = '' then
        @@ProductName = @@PyName

    ;--- Alias fo this EXE? -------------------------------------------------
    @@Type   = '{$Type=^<$PY2MSI_DEFAULT_COMPILE_TYPE>^}';
    @@ExeCnt = @@ExeCnt + 1
    @@Alias  = 'EXE_' || @@ExeCnt || '_' || @@Type;

    ;--- Keep Track of Types ------------------------------------------------
    @@Fnd = 'N';
    do  @@I =  1 TO @@TypeCnt
        if @@Type = @@Type.@@I then
        do
            ;--- Found it ---------------------------------------------------
            @@Fnd = 'Y';
            leave;
        end;
    end;
    if  @@Fnd = 'N' then
    do
        ;--- Initialize for this type ---------------------------------------
        @@TypeCnt        = @@TypeCnt + 1;
        @@Type.@@TypeCnt = @@Type;              ;;Remember about this type
        @@{$Type} = ''
    end;
    @@{$Type} = strip(@@{$Type} || ' ' || @@Alias);

    ;--- Output the correct lines to define this EXE file -------------------
    @@C = @@Alias || ' = PyExe(<?NewLine>';
    @@C =     @@C || '    script       = r"' || @@PyName || '",  #EXE Name to generate<?NewLine>'
    if  @@Description <> '' then
        @@C = @@C || '    description  = r"' || @@Description || '",    #Description of this EXE<?NewLine>'
    if  @@Version <> '' then
        @@C = @@C || '    version      = r"' || @@Version || '",    #Product+File Version details<?NewLine>'
    if  @@Company <> '' then
        @@C = @@C || '    company_name = r"' || @@Company || '",    #Company Details<?NewLine>'
    if  @@CopyRight <> '' then
        @@C = @@C || '    copyright    = r"' || @@CopyRight || '",  #Copyright Text<?NewLine>'
    if  @@ProductName <> '' then
        @@C = @@C || '    name         = r"' || @@ProductName || '",  #Product Name<?NewLine>'
    if  @@Extra <> '' then
        @@C = @@C || '    ' || @@Extra || '<?NewLine>'
    @@C = @@C || ')'
#DefineRexx


;----------------------------------------------------------------------------
;--- Extra files ------------------------------------------------------------
;----------------------------------------------------------------------------
#( ''
    #define PyFile

    #info ^Extra distribution files not yet supported^


;**    [CommentBlockStart     (September 25, 2005 10:45:58 AM EST, Dennis)
;**+----------------------------------------------------------------------
;**|        # file setup.py
;**|     [...]
;**|     import glob
;**|     [...]
;**|     data_files=[("images",
;**|                 ["images/BeefBuilder.ico",
;**|                  "images/add-arrow.bmp",
;**|                  "images/add-node.bmp",
;**|                  "images/subtract.bmp",
;**|                  "images/struct-a-file.gif",
;**|                  "images/export.bmp",
;**|                  "images/new.bmp",
;**|                  "images/open.bmp",
;**|                  "images/save.bmp"]),
;**|                 ("pool", glob.glob("pool/*.py*"))]
;**|     [...]
;**+----------------------------------------------------------------------
;**    CommentBlockEnd]       (September 25, 2005 10:45:58 AM EST, Dennis)




#)



;----------------------------------------------------------------------------
;--- Local Namespace, END ---------------------------------------------------
;----------------------------------------------------------------------------
#NextId POP


Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.Please email me any feedback, additional information or corrections.
See this page online (look for updates)

[Top][Contents][Prev]: Sample Validation Suite[Next]: Making Merge Modules


MAKEMSI© is (C)opyright Dennis Bareis 2003-2008 (All rights reserved).
Saturday May 28 2022 at 3:11pm
Visit MAKEMSI's Home Page
Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.