1#!python
2'''
3Run this script in the mingw64 shell to dynamically compile a list of
4all runtime components of Csound and all their plugins and runtime
5dependencies. System files and some other files are excluded.
6The list is saved as a set of Inno Setup Compiler commands.
7'''
8import fnmatch
9import os
10import os.path
11import shutil
12import subprocess
13
14do_copy = False
15csound_directory = r'D:\msys64\home\restore\csound'
16globs = '*.exe *.dll *.so *.node *.pyd *.py *.pdb *.jar'
17ldd_globs = '*.exe *.dll *.so *.node *.pyd'
18ldd_filepath = r'D:\msys64\usr\bin\ldd'
19
20def exclude(filepath):
21    # Wrong case!
22    if filepath.endswith('CSOUND64.dll'):
23        return True
24    if fnmatch.fnmatch(filepath, '''*/python27.dll'''):
25        return True
26    if fnmatch.fnmatch(filepath, '''*/android/*'''):
27        return True
28    if fnmatch.fnmatch(filepath, '''*/frontends/*'''):
29        return True
30    if fnmatch.fnmatch(filepath, '''*/installer/*'''):
31        return True
32    if fnmatch.fnmatch(filepath, '''*find_csound_dependencies.py'''):
33        return True
34    if fnmatch.fnmatch(filepath, '''*/packages/*.exe'''):
35        return True
36    if fnmatch.fnmatch(filepath, '''*/packages/*.py'''):
37        return True
38    if fnmatch.fnmatch(filepath, '''*/tests/*'''):
39        return True
40    if fnmatch.fnmatch(filepath, '''*/pluginSdk/*'''):
41        return True
42    if fnmatch.fnmatch(filepath, '''*/CMakeFiles/*'''):
43        return True
44    if fnmatch.fnmatch(filepath, '''*/Opcodes/*'''):
45        return True
46    if fnmatch.fnmatch(filepath, '''*/Windows/*'''):
47        return True
48    if fnmatch.fnmatch(filepath, '''*/csound-msvs/*'''):
49        return True
50    if fnmatch.fnmatch(filepath, '''*/msvc/deps/*'''):
51        return True
52    if fnmatch.fnmatch(filepath, '''*/???'''):
53        return True
54    return False
55
56# Generates a line of NSIS code depending upon the type of dependency.
57# All plugin opcodes and loadable modules go in plugins64.
58# All other dependencies go in bin.
59# The logic is: if the file is a DLL or so, if it is not in in the non-opcode
60# list, put the file in plugins64; otherwise, put in bin.
61non_opcode_targets = '''
62CsoundAC.dll
63CsoundVST.dll
64_CsoundAC.pyd
65_csnd6.pyd
66_jcsound6.dll
67csnd6.dll
68csound64.dll
69luaCsnd6.dll
70luaCsoundAC.dll
71lua51.dll
72csound.node
73'''.split('\n')
74vst_targets = '''
75CsoundVST.dll
76csoundvstmain.exe
77vst4cs.dll
78'''.split('\n')
79python_targets = '''
80_csnd6.pyd
81_CsoundAC.pyd
82csnd6.py
83CsoundAC.py
84py.dll
85'''.split('\n')
86module_extensions = '.dll .so .DLL .SO'.split()
87def is_plugin(dependency):
88    path, extension = os.path.splitext(dependency)
89    if extension in module_extensions:
90        if dependency.find('csound-mingw') != -1:
91            path, filename = os.path.split(dependency)
92            if filename not in non_opcode_targets:
93                return True
94    return False
95
96def emit(dependency):
97    if is_plugin(dependency):
98        line = 'Source: "%s"; DestDir: "{#APP_PLUGINS64}"; Flags: ignoreversion; Components: core;\n' % dependency
99    else:
100        line = 'Source: "%s"; DestDir: "{#APP_BIN}"; Flags: ignoreversion; Components: core;\n' % dependency
101    # Patch the command if it is for a VST or Python feature.
102    filename = os.path.split(dependency)[1]
103    if filename in python_targets:
104        line = line.replace('Components: core;', 'Components: python;')
105    if filename in vst_targets:
106        line = line.replace('Components: core;', 'Components: csoundvst;')
107        line = '#ifdef CSOUNDVST\n' + line + '#endif\n'
108    return line
109
110os.chdir(csound_directory)
111targets = set()
112dependencies = set()
113# In order to identify what file some dependency is for.
114with open('mingw64/csound_ldd.txt', 'w') as f:
115    print 'f:', f
116    for dirpath, dirnames, files in os.walk('.'):
117        for glob in globs.split(' '):
118            matches = fnmatch.filter(files, glob)
119            for match in matches:
120                filepath = os.path.join(dirpath, match)
121                print 'filepath:', filepath
122                if (filepath.find('Setup_Csound6_') == -1) and (filepath.find('msvc') == -1):
123                    targets.add(filepath)
124    for target in sorted(targets):
125        dependencies.add(target)
126        print 'target:', target
127        for ldd_glob in ldd_globs.split():
128            if fnmatch.fnmatch(target, ldd_glob):
129                print 'match: ', target
130                popen = subprocess.Popen([ldd_filepath, target], stdout = subprocess.PIPE)
131                output = popen.communicate()[0]
132                print 'output:', output
133                f.write(target + '\n')
134                if len(output) > 1:
135                    f.write(output + '\n')
136                for line in output.split('\n'):
137                    parts = line.split()
138                    if len(parts) > 2:
139                        dependencies.add(parts[2])
140                    elif len(parts) > 0:
141                        dependencies.add(parts[0])
142print
143print 'CSOUND TARGETS AND DEPENDENCIES'
144print
145dependencies = sorted(dependencies)
146nonsystem_dependencies = set()
147for dependency in dependencies:
148    realpath = os.path.abspath(dependency)
149    print 'realpath:', realpath
150    # Fix up MSYS pathname confusion.
151    realpath = realpath.replace('\\home\\restore\\', '\\msys64\\home\\restore\\')
152    realpath = realpath.replace('D:\\c\\', 'C:\\')
153    realpath = realpath.replace('D:\\', 'D:\\msys64\\')
154    realpath = realpath.replace('D:\\msys64\\msys64\\', 'D:\\msys64\\')
155    realpath = realpath.replace('D:\\msys64\\msys64\\', 'D:\\msys64\\')
156    print 'fixed   :', realpath
157    nonsystem_dependencies.add(realpath)
158nonsystem_dependencies = sorted(nonsystem_dependencies)
159with open('installer/windows/csound_targets_and_dependencies.iss', 'w') as f:
160    for dependency in nonsystem_dependencies:
161        if not exclude(dependency):
162            print 'dependency:', dependency
163            line = emit(dependency)
164            f.write(line)
165