pebble_sdk_common.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os
  15. import time
  16. import types
  17. from waflib import Logs
  18. from waflib.Configure import conf
  19. from waflib.Task import Task
  20. from waflib.TaskGen import after_method, before_method, feature
  21. from waflib.Tools import c, c_preproc
  22. import ldscript, process_bundle, process_headers, process_js, report_memory_usage, xcode_pebble
  23. from pebble_sdk_platform import maybe_import_internal
  24. from sdk_helpers import (append_to_attr, find_sdk_component, get_node_from_abspath,
  25. wrap_task_name_with_platform)
  26. # Override the default waf task __str__ method to include display of the HW platform being targeted
  27. Task.__str__ = wrap_task_name_with_platform
  28. def options(opt):
  29. """
  30. Specify the options available when invoking waf; uses OptParse. This method is called from
  31. app and lib waftools by `opt.load('pebble_sdk_common')`
  32. :param opt: the OptionContext object
  33. :return: N/A
  34. """
  35. opt.load('gcc')
  36. opt.add_option('-d', '--debug', action='store_true', default=False, dest='debug',
  37. help='Build in debug mode')
  38. opt.add_option('--no-groups', action='store_true', default=False, dest='no_groups')
  39. opt.add_option('--sandboxed-build', action='store_true', default=False, dest='sandbox')
  40. def configure(conf):
  41. """
  42. Configure the tools for the build by locating SDK prerequisites on the filesystem
  43. :param conf: the ConfigureContext
  44. :return: N/A
  45. """
  46. if not conf.options.debug:
  47. conf.env.append_value('DEFINES', 'RELEASE')
  48. else:
  49. Logs.pprint("CYAN", "Debug enabled")
  50. if conf.options.no_groups:
  51. conf.env.USE_GROUPS = False
  52. else:
  53. conf.env.USE_GROUPS = True
  54. conf.env.SANDBOX = conf.options.sandbox
  55. conf.env.VERBOSE = conf.options.verbose
  56. conf.env.TIMESTAMP = int(time.time())
  57. # If waf is in ~/pebble-dev/PebbleSDK-X.XX/waf
  58. # Then this file is in ~/pebble-dev/PebbleSDK-X.XX/.waflib-xxxx/waflib/extras/
  59. # => we need to go up 3 directories to find the folder containing waf
  60. pebble_sdk = conf.root.find_dir(os.path.dirname(__file__)).parent.parent.parent
  61. if pebble_sdk is None:
  62. conf.fatal("Unable to find Pebble SDK!\n"
  63. "Please make sure you are running waf directly from your SDK.")
  64. conf.env.PEBBLE_SDK_ROOT = pebble_sdk.abspath()
  65. # Set location of Pebble SDK common folder
  66. pebble_sdk_common = pebble_sdk.find_node('common')
  67. conf.env.PEBBLE_SDK_COMMON = pebble_sdk_common.abspath()
  68. if 'NODE_PATH' in os.environ:
  69. conf.env.NODE_PATH = conf.root.find_node(os.environ['NODE_PATH']).abspath()
  70. webpack_path = conf.root.find_node(conf.env.NODE_PATH).find_node('.bin').abspath()
  71. try:
  72. conf.find_program('webpack', path_list=[webpack_path])
  73. except conf.errors.ConfigurationError:
  74. pass # Error will be caught after checking for enableMultiJS setting
  75. else:
  76. Logs.pprint('YELLOW', "WARNING: Unable to find $NODE_PATH variable required for SDK "
  77. "build. Please verify this build was initiated with a recent "
  78. "pebble-tool.")
  79. maybe_import_internal(conf.env)
  80. def build(bld):
  81. """
  82. This method is invoked from the app or lib waftool with the `bld.load('pebble_sdk_common')`
  83. call and sets up additional task generators for the SDK.
  84. :param bld: the BuildContext object
  85. :return: N/A
  86. """
  87. # cached_env is set to a shallow copy of the current ConfigSet for this BuildContext
  88. bld.env = bld.all_envs['']
  89. bld.load('file_name_c_define')
  90. # Process message keys
  91. bld(features='message_keys')
  92. cached_env = bld.env
  93. for platform in bld.env.TARGET_PLATFORMS:
  94. # bld.env is set to a shallow copy of the ConfigSet labeled <platform>
  95. bld.env = bld.all_envs[platform]
  96. # Create a build group (set of TaskGens) for <platform>
  97. if bld.env.USE_GROUPS:
  98. bld.add_group(bld.env.PLATFORM_NAME)
  99. # Generate a linker script specific to the current platform
  100. build_node = bld.path.get_bld().find_or_declare(bld.env.BUILD_DIR)
  101. bld(features='subst',
  102. source=find_sdk_component(bld, bld.env, 'pebble_app.ld.template'),
  103. target=build_node.make_node('pebble_app.ld.auto'),
  104. **bld.env.PLATFORM)
  105. # Locate Rocky JS tooling script
  106. js_tooling_script = find_sdk_component(bld, bld.env, 'tools/generate_snapshot.js')
  107. bld.env.JS_TOOLING_SCRIPT = js_tooling_script if js_tooling_script else None
  108. # bld.env is set back to a shallow copy of the original ConfigSet that was set when this
  109. # `build` method was invoked
  110. bld.env = cached_env
  111. # Create a build group for bundling (should run after the build groups for each platform)
  112. if bld.env.USE_GROUPS:
  113. bld.add_group('bundle')
  114. def _wrap_c_preproc_scan(task):
  115. """
  116. This function is a scanner function that wraps c_preproc.scan to fix up pebble.h dependencies.
  117. pebble.h is outside out the bld/src trees so therefore it's not considered a valid dependency
  118. and isn't scanned for further dependencies. Normally this would be fine but pebble.h includes
  119. an auto-generated resource id header which is really a dependency. We detect this include and
  120. add the resource id header file to the nodes being scanned by c_preproc.
  121. :param task: the task instance
  122. :return: N/A
  123. """
  124. (nodes, names) = c_preproc.scan(task)
  125. if 'pebble.h' in names:
  126. nodes.append(get_node_from_abspath(task.generator.bld, task.env.RESOURCE_ID_HEADER))
  127. nodes.append(get_node_from_abspath(task.generator.bld, task.env.MESSAGE_KEYS_HEADER))
  128. return nodes, names
  129. @feature('c')
  130. @before_method('process_source')
  131. def setup_pebble_c(task_gen):
  132. """
  133. This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
  134. ensures that the SDK `include` path for the current platform, as well as the project root
  135. directory and the project src directory are included as header search paths (includes) for the
  136. build.
  137. :param task_gen: the task generator instance
  138. :return: N/A
  139. """
  140. platform = task_gen.env.PLATFORM_NAME
  141. append_to_attr(task_gen, 'includes',
  142. [find_sdk_component(task_gen.bld, task_gen.env, 'include'),
  143. '.', 'include', 'src'])
  144. append_to_attr(task_gen, 'includes', platform)
  145. for lib in task_gen.bld.env.LIB_JSON:
  146. if 'pebble' in lib:
  147. lib_include_node = task_gen.bld.path.find_node(lib['path']).find_node('include')
  148. append_to_attr(task_gen, 'includes',
  149. [lib_include_node,
  150. lib_include_node.find_node(str(lib['name'])).find_node(platform)])
  151. @feature('c')
  152. @after_method('process_source')
  153. def fix_pebble_h_dependencies(task_gen):
  154. """
  155. This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
  156. ensures that the _wrap_c_preproc_scan method is run for all c tasks.
  157. :param task_gen: the task generator instance
  158. :return: N/A
  159. """
  160. for task in task_gen.tasks:
  161. if type(task) == c.c:
  162. # Swap out the bound member function for our own
  163. task.scan = types.MethodType(_wrap_c_preproc_scan, task, c.c)
  164. @feature('pebble_cprogram')
  165. @before_method('process_source')
  166. def setup_pebble_cprogram(task_gen):
  167. """
  168. This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
  169. adds the appinfo.auto.c file to the source file list, adds the SDK pebble library to the lib
  170. path for the build, sets the linkflags for the build, and specifies the linker script to
  171. use during the linking step.
  172. :param task_gen: the task generator instance
  173. :return: None
  174. """
  175. build_node = task_gen.path.get_bld().make_node(task_gen.env.BUILD_DIR)
  176. platform = task_gen.env.PLATFORM_NAME
  177. if not hasattr(task_gen, 'bin_type') or getattr(task_gen, 'bin_type') != 'lib':
  178. append_to_attr(task_gen, 'source', build_node.make_node('appinfo.auto.c'))
  179. append_to_attr(task_gen, 'source', build_node.make_node('src/resource_ids.auto.c'))
  180. if task_gen.env.MESSAGE_KEYS:
  181. append_to_attr(task_gen,
  182. 'source',
  183. get_node_from_abspath(task_gen.bld,
  184. task_gen.env.MESSAGE_KEYS_DEFINITION))
  185. append_to_attr(task_gen, 'stlibpath',
  186. find_sdk_component(task_gen.bld, task_gen.env, 'lib').abspath())
  187. append_to_attr(task_gen, 'stlib', 'pebble')
  188. for lib in task_gen.bld.env.LIB_JSON:
  189. # Skip binary check for non-Pebble libs
  190. if not 'pebble' in lib:
  191. continue
  192. binaries_path = task_gen.bld.path.find_node(lib['path']).find_node('binaries')
  193. if binaries_path:
  194. # Check for existence of platform folders inside binaries folder
  195. platform_binary_path = binaries_path.find_node(platform)
  196. if not platform_binary_path:
  197. task_gen.bld.fatal("Library {} is missing the {} platform folder in {}".
  198. format(lib['name'], platform, binaries_path))
  199. # Check for existence of binary for each platform
  200. if lib['name'].startswith('@'):
  201. scoped_name = lib['name'].rsplit('/', 1)
  202. lib_binary = (platform_binary_path.find_node(str(scoped_name[0])).
  203. find_node("lib{}.a".format(scoped_name[1])))
  204. else:
  205. lib_binary = platform_binary_path.find_node("lib{}.a".format(lib['name']))
  206. if not lib_binary:
  207. task_gen.bld.fatal("Library {} is missing a binary for the {} platform".
  208. format(lib['name'], platform))
  209. # Link library binary (supports scoped names)
  210. if lib['name'].startswith('@'):
  211. append_to_attr(task_gen, 'stlibpath',
  212. platform_binary_path.find_node(str(scoped_name[0])).abspath())
  213. append_to_attr(task_gen, 'stlib', scoped_name[1])
  214. else:
  215. append_to_attr(task_gen, 'stlibpath', platform_binary_path.abspath())
  216. append_to_attr(task_gen, 'stlib', lib['name'])
  217. append_to_attr(task_gen, 'linkflags',
  218. ['-Wl,--build-id=sha1',
  219. '-Wl,-Map,pebble-{}.map,--emit-relocs'.format(getattr(task_gen,
  220. 'bin_type',
  221. 'app'))])
  222. if not hasattr(task_gen, 'ldscript'):
  223. task_gen.ldscript = (
  224. build_node.find_or_declare('pebble_app.ld.auto').path_from(task_gen.path))
  225. def _get_entry_point(ctx, js_type, waf_js_entry_point):
  226. """
  227. Returns the appropriate JS entry point, extracted from a project's package.json file,
  228. wscript or common SDK default
  229. :param ctx: the BuildContext
  230. :param js_type: type of JS build, pkjs or rockyjs
  231. :param waf_js_entry_point: the JS entry point specified by waftools
  232. :return: the JS entry point for the bundled JS file
  233. """
  234. fallback_entry_point = waf_js_entry_point
  235. if not fallback_entry_point:
  236. if js_type == 'pkjs':
  237. if ctx.path.find_node('src/pkjs/index.js'):
  238. fallback_entry_point = 'src/pkjs/index.js'
  239. else:
  240. fallback_entry_point = 'src/js/app.js'
  241. if js_type == 'rockyjs':
  242. fallback_entry_point = 'src/rocky/index.js'
  243. project_info = ctx.env.PROJECT_INFO
  244. if not project_info.get('main'):
  245. return fallback_entry_point
  246. if project_info['main'].get(js_type):
  247. return str(project_info['main'][js_type])
  248. return fallback_entry_point
  249. @conf
  250. def pbl_bundle(self, *k, **kw):
  251. """
  252. This method is bound to the build context and is called by specifying `bld.pbl_bundle`. We
  253. set the custome features `js` and `bundle` to run when this method is invoked.
  254. :param self: the BuildContext object
  255. :param k: none expected
  256. :param kw:
  257. binaries - a list containing dictionaries specifying the HW platform targeted by the
  258. binary built, the app binary, and an optional worker binary
  259. js - the source JS files to be bundled
  260. js_entry_file - an optional parameter to specify the entry JS file when
  261. enableMultiJS is set to 'true'
  262. :return: a task generator instance with keyword arguments specified
  263. """
  264. if kw.get('bin_type', 'app') == 'lib':
  265. kw['features'] = 'headers js package'
  266. else:
  267. if self.env.BUILD_TYPE == 'rocky':
  268. kw['js_entry_file'] = _get_entry_point(self, 'pkjs', kw.get('js_entry_file'))
  269. kw['features'] = 'js bundle'
  270. return self(*k, **kw)
  271. @conf
  272. def pbl_build(self, *k, **kw):
  273. """
  274. This method is bound to the build context and is called by specifying `bld.pbl_build()`. We
  275. set the custom features `c`, `cprogram` and `pebble_cprogram` to run when this method is
  276. invoked. This method is intended to someday replace `pbl_program` and `pbl_worker` so that
  277. all apps, workers, and libs will run through this method.
  278. :param self: the BuildContext object
  279. :param k: none expected
  280. :param kw:
  281. source - the source C files to be built and linked
  282. target - the destination binary file for the compiled source
  283. :return: a task generator instance with keyword arguments specified
  284. """
  285. valid_bin_types = ('app', 'worker', 'lib', 'rocky')
  286. bin_type = kw.get('bin_type', None)
  287. if bin_type not in valid_bin_types:
  288. self.fatal("The pbl_build method requires that a valid bin_type attribute be specified. "
  289. "Valid options are {}".format(valid_bin_types))
  290. if bin_type == 'rocky':
  291. kw['features'] = 'c cprogram pebble_cprogram memory_usage'
  292. elif bin_type in ('app', 'worker'):
  293. kw['features'] = 'c cprogram pebble_cprogram memory_usage'
  294. kw[bin_type] = kw['target']
  295. elif bin_type == 'lib':
  296. kw['features'] = 'c cstlib memory_usage'
  297. path, name = kw['target'].rsplit('/', 1)
  298. kw['lib'] = self.path.find_or_declare(path).make_node("lib{}.a".format(name))
  299. # Pass values needed for memory usage report
  300. if bin_type != 'worker':
  301. kw['resources'] = (
  302. self.env.PROJECT_RESBALL if bin_type == 'lib' else
  303. self.path.find_or_declare(self.env.BUILD_DIR).make_node('app_resources.pbpack'))
  304. return self(*k, **kw)
  305. @conf
  306. def pbl_js_build(self, *k, **kw):
  307. """
  308. This method is bound to the build context and is called by specifying `bld.pbl_cross_compile()`.
  309. When this method is invoked, we set the custom feature `rockyjs` to run, which handles
  310. processing of JS files in preparation for Rocky.js bytecode compilation (this actually
  311. happens during resource generation)
  312. :param self: the BuildContext object
  313. :param k: none expected
  314. :param kw:
  315. source - the source JS files that will eventually be compiled into bytecode
  316. target - the destination JS file that will be specified as the source file for the
  317. bytecode compilation process
  318. :return: a task generator instance with keyword arguments specified
  319. """
  320. kw['js_entry_file'] = _get_entry_point(self, 'rockyjs', kw.get('js_entry_file'))
  321. kw['features'] = 'rockyjs'
  322. return self(*k, **kw)