Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | # Create an application package. |
2 | |||
3 | # https://www.scons.org/wiki/SubstInFileBuilder |
||
4 | |||
5 | import re |
||
6 | from SCons.Script import * |
||
7 | from SCons.Builder import * |
||
8 | |||
9 | def TOOL_SUBST(env): |
||
10 | """Adds SubstInFile builder, which substitutes the keys->values of SUBST_DICT |
||
11 | from the source to the target. |
||
12 | The values of SUBST_DICT first have any construction variables expanded |
||
13 | (its keys are not expanded). |
||
14 | If a value of SUBST_DICT is a python callable function, it is called and |
||
15 | the result is expanded as the value. |
||
16 | If there's more than one source and more than one target, each target gets |
||
17 | substituted from the corresponding source. |
||
18 | """ |
||
19 | env.Append(TOOLS = 'SUBST') |
||
20 | def do_subst_in_file(targetfile, sourcefile, dict): |
||
21 | """Replace all instances of the keys of dict with their values. |
||
22 | For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'}, |
||
23 | then all instances of %VERSION% in the file will be replaced with 1.2345 etc. |
||
24 | """ |
||
25 | try: |
||
26 | f = open(sourcefile, 'rb') |
||
27 | contents = f.read() |
||
28 | f.close() |
||
29 | except: |
||
30 | raise SCons.Errors.UserError("Can't read source file %s" % (sourcefile,)) |
||
31 | for (k,v) in dict.items(): |
||
32 | contents = re.sub(k, v, contents) |
||
33 | try: |
||
34 | f = open(targetfile, 'wb') |
||
35 | f.write(contents) |
||
36 | f.close() |
||
37 | except: |
||
38 | raise SCons.Errors.UserError("Can't write target file %s" % (targetfile,)) |
||
39 | return 0 # success |
||
40 | |||
41 | def subst_in_file(target, source, env): |
||
42 | if not env.has_key('SUBST_DICT'): |
||
43 | raise SCons.Errors.UserError("SubstInFile requires SUBST_DICT to be set.") |
||
44 | d = dict(env['SUBST_DICT']) # copy it |
||
45 | for (k,v) in d.items(): |
||
46 | if callable(v): |
||
47 | d[k] = env.subst(v()) |
||
48 | elif SCons.Util.is_String(v): |
||
49 | d[k]=env.subst(v) |
||
50 | else: |
||
51 | raise SCons.Errors.UserError("SubstInFile: key %s: %r must be a string or callable" % (k, v)) |
||
52 | for (t,s) in zip(target, source): |
||
53 | return do_subst_in_file(str(t), str(s), d) |
||
54 | |||
55 | def subst_in_file_string(target, source, env): |
||
56 | """This is what gets printed on the console.""" |
||
57 | # return '\n'.join(['Substituting vars from %s into %s'%(str(s), str(t)) |
||
58 | # for (t,s) in zip(target, source)]) |
||
59 | |||
60 | def subst_emitter(target, source, env): |
||
61 | """Add dependency from substituted SUBST_DICT to target. |
||
62 | Returns original target, source tuple unchanged. |
||
63 | """ |
||
64 | d = env['SUBST_DICT'].copy() # copy it |
||
65 | for (k,v) in d.items(): |
||
66 | if callable(v): |
||
67 | d[k] = env.subst(v()) |
||
68 | elif SCons.Util.is_String(v): |
||
69 | d[k]=env.subst(v) |
||
70 | env.Depends(target, SCons.Node.Python.Value(d)) |
||
71 | return target, source |
||
72 | |||
73 | subst_action=SCons.Action.Action(subst_in_file, subst_in_file_string) |
||
74 | env['BUILDERS']['SubstInFile'] = env.Builder(action=subst_action, emitter=subst_emitter) |
||
75 | |||
76 | # https://www.scons.org/wiki/MacOSX (modified to suit) |
||
77 | |||
78 | from SCons.Defaults import SharedCheck, ProgScan |
||
79 | from SCons.Script.SConscript import SConsEnvironment |
||
80 | import sys |
||
81 | import os |
||
82 | import SCons.Node |
||
83 | import SCons.Util |
||
84 | import string |
||
85 | |||
86 | def TOOL_BUNDLE(env): |
||
87 | """defines env.LinkBundle() for linking bundles on Darwin/OSX, and |
||
88 | env.MakeBundle() for installing a bundle into its dir. |
||
89 | A bundle has this structure: (filenames are case SENSITIVE) |
||
90 | sapphire.bundle/ |
||
91 | Contents/ |
||
92 | Info.plist (an XML key->value database; defined by BUNDLE_INFO_PLIST) |
||
93 | PkgInfo (trivially short; defined by value of BUNDLE_PKGINFO) |
||
94 | MacOS/ |
||
95 | executable (the executable or shared lib, linked with Bundle()) |
||
96 | Resources/ |
||
97 | """ |
||
98 | if 'BUNDLE' in env['TOOLS']: return |
||
99 | if sys.platform == 'darwin': |
||
100 | #if tools_verbose: |
||
101 | print(" running tool: TOOL_BUNDLE") |
||
102 | env.Append(TOOLS = 'BUNDLE') |
||
103 | # This is like the regular linker, but uses different vars. |
||
104 | # XXX: NOTE: this may be out of date now, scons 0.96.91 has some bundle linker stuff built in. |
||
105 | # Check the docs before using this. |
||
106 | """LinkBundle = env.Builder(action=[SharedCheck, "$BUNDLECOM"], |
||
107 | emitter="$SHLIBEMITTER", |
||
108 | prefix = '$BUNDLEPREFIX', |
||
109 | suffix = '$BUNDLESUFFIX', |
||
110 | target_scanner = ProgScan, |
||
111 | src_suffix = '$BUNDLESUFFIX', |
||
112 | src_builder = 'SharedObject') |
||
113 | env['BUILDERS']['LinkBundle'] = LinkBundle""" |
||
114 | env['BUNDLEEMITTER'] = None |
||
115 | env['BUNDLEPREFIX'] = '' |
||
116 | env['BUNDLESUFFIX'] = '' |
||
117 | env['BUNDLEDIRSUFFIX'] = '.bundle' |
||
118 | #env['FRAMEWORKS'] = ['-framework Carbon', '-framework System'] |
||
119 | env['BUNDLE'] = '$SHLINK' |
||
120 | env['BUNDLEFLAGS'] = ' -bundle' |
||
121 | env['BUNDLECOM'] = '$BUNDLE $BUNDLEFLAGS -o ${TARGET} $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $FRAMEWORKS' |
||
122 | # This requires some other tools: |
||
123 | TOOL_WRITE_VAL(env) |
||
124 | TOOL_SUBST(env) |
||
125 | # Common type codes are BNDL for generic bundle and APPL for application. |
||
126 | def MakeBundle(env, bundledir, app, |
||
127 | key, info_plist, |
||
128 | typecode='BNDL', creator='SapP', |
||
129 | icon_file='#macosx-install/sapphire-icon.icns', |
||
130 | subst_dict=None, |
||
131 | resources=[]): |
||
132 | """Install a bundle into its dir, in the proper format""" |
||
133 | # Substitute construction vars: |
||
134 | for a in [bundledir, key, info_plist, icon_file, typecode, creator]: |
||
135 | a = env.subst(a) |
||
136 | if SCons.Util.is_List(app): |
||
137 | app = app[0] |
||
138 | if SCons.Util.is_String(app): |
||
139 | app = env.subst(app) |
||
140 | appbase = os.path.basename(app) |
||
141 | else: |
||
142 | appbase = os.path.basename(str(app)) |
||
143 | if not ('.' in bundledir): |
||
144 | bundledir += '.$BUNDLEDIRSUFFIX' |
||
145 | bundledir = env.subst(bundledir) # substitute again |
||
146 | suffix=bundledir[string.rfind(bundledir,'.'):] |
||
147 | if (suffix=='.app' and typecode != 'APPL' or |
||
148 | suffix!='.app' and typecode == 'APPL'): |
||
149 | raise Error("MakeBundle: inconsistent dir suffix %s and type code %s: app bundles should end with .app and type code APPL." % (suffix, typecode)) |
||
150 | if subst_dict is None: |
||
151 | subst_dict={'%SHORTVERSION%': '$VERSION_NUM', |
||
152 | '%LONGVERSION%': '$VERSION_NAME', |
||
153 | '%YEAR%': '$COMPILE_YEAR', |
||
154 | '%BUNDLE_EXECUTABLE%': appbase, |
||
155 | '%ICONFILE%': os.path.basename(icon_file), |
||
156 | '%CREATOR%': creator, |
||
157 | '%TYPE%': typecode, |
||
158 | '%BUNDLE_KEY%': key} |
||
159 | env.Install(bundledir+'/Contents/MacOS', app) |
||
160 | f=env.SubstInFile(bundledir+'/Contents/Info.plist', info_plist, |
||
161 | SUBST_DICT=subst_dict) |
||
162 | env.Depends(f,SCons.Node.Python.Value(key+creator+typecode+env['VERSION_NUM']+env['VERSION_NAME'])) |
||
163 | env.WriteVal(target=bundledir+'/Contents/PkgInfo', |
||
164 | source=SCons.Node.Python.Value(typecode+creator)) |
||
165 | resources.append(icon_file) |
||
166 | for r in resources: |
||
167 | if SCons.Util.is_List(r): |
||
168 | env.InstallAs(bundledir+'/Contents/Resources/'+r[1], |
||
169 | r[0]) |
||
170 | else: |
||
171 | env.Install(bundledir+'/Contents/Resources', r) |
||
172 | return [ SCons.Node.FS.default_fs.Dir(bundledir) ] |
||
173 | # This is not a regular Builder; it's a wrapper function. |
||
174 | # So just make it available as a method of Environment. |
||
175 | SConsEnvironment.MakeBundle = MakeBundle |
||
176 | def TOOL_WRITE_VAL(env): |
||
177 | #if tools_verbose: |
||
178 | print(" running tool: TOOL_WRITE_VAL") |
||
179 | env.Append(TOOLS = 'WRITE_VAL') |
||
180 | def write_val(target, source, env): |
||
181 | """Write the contents of the first source into the target. |
||
182 | source is usually a Value() node, but could be a file.""" |
||
183 | f = open(str(target[0]), 'wb') |
||
184 | f.write(source[0].get_contents()) |
||
185 | f.close() |
||
186 | env['BUILDERS']['WriteVal'] = env.Builder(action=write_val) |