1#!/usr/bin/env python
2# encoding: utf-8
3# Thomas Nagy 2008-2018 (ita)
4
5"""
6MacOSX related tools
7"""
8
9import os, shutil, platform
10from waflib import Task, Utils
11from waflib.TaskGen import taskgen_method, feature, after_method, before_method
12
13app_info = '''
14<?xml version="1.0" encoding="UTF-8"?>
15<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
16<plist version="0.9">
17<dict>
18	<key>CFBundlePackageType</key>
19	<string>APPL</string>
20	<key>CFBundleGetInfoString</key>
21	<string>Created by Waf</string>
22	<key>CFBundleSignature</key>
23	<string>????</string>
24	<key>NOTE</key>
25	<string>THIS IS A GENERATED FILE, DO NOT MODIFY</string>
26	<key>CFBundleExecutable</key>
27	<string>{app_name}</string>
28</dict>
29</plist>
30'''
31"""
32plist template
33"""
34
35@feature('c', 'cxx')
36def set_macosx_deployment_target(self):
37	"""
38	see WAF issue 285 and also and also http://trac.macports.org/ticket/17059
39	"""
40	if self.env.MACOSX_DEPLOYMENT_TARGET:
41		os.environ['MACOSX_DEPLOYMENT_TARGET'] = self.env.MACOSX_DEPLOYMENT_TARGET
42	elif 'MACOSX_DEPLOYMENT_TARGET' not in os.environ:
43		if Utils.unversioned_sys_platform() == 'darwin':
44			os.environ['MACOSX_DEPLOYMENT_TARGET'] = '.'.join(platform.mac_ver()[0].split('.')[:2])
45
46@taskgen_method
47def create_bundle_dirs(self, name, out):
48	"""
49	Creates bundle folders, used by :py:func:`create_task_macplist` and :py:func:`create_task_macapp`
50	"""
51	dir = out.parent.find_or_declare(name)
52	dir.mkdir()
53	macos = dir.find_or_declare(['Contents', 'MacOS'])
54	macos.mkdir()
55	return dir
56
57def bundle_name_for_output(out):
58	name = out.name
59	k = name.rfind('.')
60	if k >= 0:
61		name = name[:k] + '.app'
62	else:
63		name = name + '.app'
64	return name
65
66@feature('cprogram', 'cxxprogram')
67@after_method('apply_link')
68def create_task_macapp(self):
69	"""
70	To compile an executable into a Mac application (a .app), set its *mac_app* attribute::
71
72		def build(bld):
73			bld.shlib(source='a.c', target='foo', mac_app=True)
74
75	To force *all* executables to be transformed into Mac applications::
76
77		def build(bld):
78			bld.env.MACAPP = True
79			bld.shlib(source='a.c', target='foo')
80	"""
81	if self.env.MACAPP or getattr(self, 'mac_app', False):
82		out = self.link_task.outputs[0]
83
84		name = bundle_name_for_output(out)
85		dir = self.create_bundle_dirs(name, out)
86
87		n1 = dir.find_or_declare(['Contents', 'MacOS', out.name])
88
89		self.apptask = self.create_task('macapp', self.link_task.outputs, n1)
90		inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Contents/MacOS/' % name
91		self.add_install_files(install_to=inst_to, install_from=n1, chmod=Utils.O755)
92
93		if getattr(self, 'mac_files', None):
94			# this only accepts files; they will be installed as seen from mac_files_root
95			mac_files_root = getattr(self, 'mac_files_root', None)
96			if isinstance(mac_files_root, str):
97				mac_files_root = self.path.find_node(mac_files_root)
98				if not mac_files_root:
99					self.bld.fatal('Invalid mac_files_root %r' % self.mac_files_root)
100			res_dir = n1.parent.parent.make_node('Resources')
101			inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Resources' % name
102			for node in self.to_nodes(self.mac_files):
103				relpath = node.path_from(mac_files_root or node.parent)
104				self.create_task('macapp', node, res_dir.make_node(relpath))
105				self.add_install_as(install_to=os.path.join(inst_to, relpath), install_from=node)
106
107		if getattr(self.bld, 'is_install', None):
108			# disable regular binary installation
109			self.install_task.hasrun = Task.SKIP_ME
110
111@feature('cprogram', 'cxxprogram')
112@after_method('apply_link')
113def create_task_macplist(self):
114	"""
115	Creates a :py:class:`waflib.Tools.c_osx.macplist` instance.
116	"""
117	if  self.env.MACAPP or getattr(self, 'mac_app', False):
118		out = self.link_task.outputs[0]
119
120		name = bundle_name_for_output(out)
121
122		dir = self.create_bundle_dirs(name, out)
123		n1 = dir.find_or_declare(['Contents', 'Info.plist'])
124		self.plisttask = plisttask = self.create_task('macplist', [], n1)
125		plisttask.context = {
126			'app_name': self.link_task.outputs[0].name,
127			'env': self.env
128		}
129
130		plist_ctx = getattr(self, 'plist_context', None)
131		if (plist_ctx):
132			plisttask.context.update(plist_ctx)
133
134		if getattr(self, 'mac_plist', False):
135			node = self.path.find_resource(self.mac_plist)
136			if node:
137				plisttask.inputs.append(node)
138			else:
139				plisttask.code = self.mac_plist
140		else:
141			plisttask.code = app_info
142
143		inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Contents/' % name
144		self.add_install_files(install_to=inst_to, install_from=n1)
145
146@feature('cshlib', 'cxxshlib')
147@before_method('apply_link', 'propagate_uselib_vars')
148def apply_bundle(self):
149	"""
150	To make a bundled shared library (a ``.bundle``), set the *mac_bundle* attribute::
151
152		def build(bld):
153			bld.shlib(source='a.c', target='foo', mac_bundle = True)
154
155	To force *all* executables to be transformed into bundles::
156
157		def build(bld):
158			bld.env.MACBUNDLE = True
159			bld.shlib(source='a.c', target='foo')
160	"""
161	if self.env.MACBUNDLE or getattr(self, 'mac_bundle', False):
162		self.env.LINKFLAGS_cshlib = self.env.LINKFLAGS_cxxshlib = [] # disable the '-dynamiclib' flag
163		self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.macbundle_PATTERN
164		use = self.use = self.to_list(getattr(self, 'use', []))
165		if not 'MACBUNDLE' in use:
166			use.append('MACBUNDLE')
167
168app_dirs = ['Contents', 'Contents/MacOS', 'Contents/Resources']
169
170class macapp(Task.Task):
171	"""
172	Creates mac applications
173	"""
174	color = 'PINK'
175	def run(self):
176		self.outputs[0].parent.mkdir()
177		shutil.copy2(self.inputs[0].srcpath(), self.outputs[0].abspath())
178
179class macplist(Task.Task):
180	"""
181	Creates plist files
182	"""
183	color = 'PINK'
184	ext_in = ['.bin']
185	def run(self):
186		if getattr(self, 'code', None):
187			txt = self.code
188		else:
189			txt = self.inputs[0].read()
190		context = getattr(self, 'context', {})
191		txt = txt.format(**context)
192		self.outputs[0].write(txt)
193
194