Search code examples
inno-setup

Recurse sub directories without creating those same sub directories in Inno Setup


I often use the recursesubdirs flag to traverse through a number of sub directories and extract a specific file or type of file without having to explicitly refer to each file individually. Example:

Source: C:\kh25\dependencies\*.dll; DestDir: {app}; Flags: recursesubdirs

This creates the exact same directory structure in my destination {app} path as the source from where the DLL was initially retrieved from. For example if I retrieved a DLL from C:\kh25\dependencies\test above then it would place that DLL in the {app}\test path.

This behaviour can be modified by doing this:

Source: C:\kh25\dependencies\test\*.dll; DestDir: {app}; Flags: recursesubdirs

But obviously this means I would have to reference each sub directory in dependencies individually.

So the $64,000 question is how can I prevent the same directory from being recreated in the destination without having to explicitly reference the source directory?


Solution

  • Use Inno Setup preprocessor to generate the entries for the [Files] section.

    One possible (and relatively nice and simple) solution is using a recursive macro like:

    #pragma parseroption -p-
    
    #define FileEntry(Source) \
        "Source: " + Source + "; DestDir: {app}\n"
    
    #define ProcessFile(Source, FindResult, FindHandle) \
        FindResult \
            ? \
                Local[0] = FindGetFileName(FindHandle), \
                Local[1] = Source + "\\" + Local[0], \
                (Local[0] != "." && Local[0] != ".." \
                    ? (DirExists(Local[1]) ? \
                        ProcessFolder(Local[1]) : FileEntry(Local[1])) \
                    : "") + \
                ProcessFile(Source, FindNext(FindHandle), FindHandle) \
            : \
                ""
    
    #define ProcessFolder(Source) \
        Local[0] = FindFirst(Source + "\\*", faAnyFile), \
        ProcessFile(Source, Local[0], Local[0])
    
    #pragma parseroption -p+
    
    #emit ProcessFolder("C:\kh25\dependencies")
    

    Though this solution has its limit and may crash the preprocessor with large number of files or deep directory structure (works ok for thousands of files).

    Inspired by the answer by @Zlatko Karakaš to Use Inno Setup PreProcessor to get the files and size of the source path and its subdirs.


    More reliable (and ugly and complex) solution is using a user defined procedures. It's complicated because the preprocessor lacks a support for parameters of user-defined procedures.

    [Files]
    
    #define FindHandle
    #define FindResult 
    #dim InnerMask[65536]
    #define InnerMask[0] ""
    
    #sub ProcessFoundFile
        #define InnerFileName FindGetFileName(FindHandle)
        #define fileName InnerMask[InnerMaskWorkPosition] + InnerFileName
        #if InnerFileName!="." && InnerFileName!=".."
            #if DirExists(FileName)
                #define Public InnerMask[InnerMaskPosition] FileName+"\"
                #define Public InnerMaskPosition InnerMaskPosition + 1
            #else
                Source: {#FileName}; DestDir: {app}
            #endif
        #endif 
    #endsub
    
    #sub ProcessInnerMaskPosition 
        #for { \
            FindHandle = FindResult = \
                FindFirst(InnerMask[InnerMaskWorkPosition]+"*", faAnyFile); \
            FindResult; FindResult = FindNext(FindHandle)} ProcessFoundFile
        #if FindHandle
            #expr FindClose(FindHandle)
        #endif
    #endsub
    
    #sub CollectFiles
        #define Public InnerMaskPosition 1
        #define Public InnerMaskWorkPosition 0
        #for { \
            InnerMaskWorkPosition = 0; InnerMaskWorkPosition < InnerMaskPosition; \
                InnerMaskWorkPosition++} \
                ProcessInnerMaskPosition
        #undef Public InnerMaskPosition
        #undef Public InnerMaskWorkPosition
    #endsub
    
    #expr InnerMask[0]="C:\kh25\dependencies\"
    #expr CollectFiles
    

    The recursive scan function taken from the answer by @René Martin to Use Inno Setup PreProcessor to get the files and size of the source path and its subdirs.


    Add a SaveToFile call at the very end of your Inno Setup script too see, what the preprocessor generates:

    #expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
    

    See Inno Setup: How do I see the output (translation) of the Inno Setup Preprocessor?


    My answer to this question explains the differences between the two approaches taken above:
    Can Inno Setup Preprocessor be used to build a duplicated set of custom messages?