123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- # Copyright 2024 Google LLC
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import os
- import time
- import types
- from waflib import Logs
- from waflib.Configure import conf
- from waflib.Task import Task
- from waflib.TaskGen import after_method, before_method, feature
- from waflib.Tools import c, c_preproc
- import ldscript, process_bundle, process_headers, process_js, report_memory_usage, xcode_pebble
- from pebble_sdk_platform import maybe_import_internal
- from sdk_helpers import (append_to_attr, find_sdk_component, get_node_from_abspath,
- wrap_task_name_with_platform)
- # Override the default waf task __str__ method to include display of the HW platform being targeted
- Task.__str__ = wrap_task_name_with_platform
- def options(opt):
- """
- Specify the options available when invoking waf; uses OptParse. This method is called from
- app and lib waftools by `opt.load('pebble_sdk_common')`
- :param opt: the OptionContext object
- :return: N/A
- """
- opt.load('gcc')
- opt.add_option('-d', '--debug', action='store_true', default=False, dest='debug',
- help='Build in debug mode')
- opt.add_option('--no-groups', action='store_true', default=False, dest='no_groups')
- opt.add_option('--sandboxed-build', action='store_true', default=False, dest='sandbox')
- def configure(conf):
- """
- Configure the tools for the build by locating SDK prerequisites on the filesystem
- :param conf: the ConfigureContext
- :return: N/A
- """
- if not conf.options.debug:
- conf.env.append_value('DEFINES', 'RELEASE')
- else:
- Logs.pprint("CYAN", "Debug enabled")
- if conf.options.no_groups:
- conf.env.USE_GROUPS = False
- else:
- conf.env.USE_GROUPS = True
- conf.env.SANDBOX = conf.options.sandbox
- conf.env.VERBOSE = conf.options.verbose
- conf.env.TIMESTAMP = int(time.time())
- # If waf is in ~/pebble-dev/PebbleSDK-X.XX/waf
- # Then this file is in ~/pebble-dev/PebbleSDK-X.XX/.waflib-xxxx/waflib/extras/
- # => we need to go up 3 directories to find the folder containing waf
- pebble_sdk = conf.root.find_dir(os.path.dirname(__file__)).parent.parent.parent
- if pebble_sdk is None:
- conf.fatal("Unable to find Pebble SDK!\n"
- "Please make sure you are running waf directly from your SDK.")
- conf.env.PEBBLE_SDK_ROOT = pebble_sdk.abspath()
- # Set location of Pebble SDK common folder
- pebble_sdk_common = pebble_sdk.find_node('common')
- conf.env.PEBBLE_SDK_COMMON = pebble_sdk_common.abspath()
- if 'NODE_PATH' in os.environ:
- conf.env.NODE_PATH = conf.root.find_node(os.environ['NODE_PATH']).abspath()
- webpack_path = conf.root.find_node(conf.env.NODE_PATH).find_node('.bin').abspath()
- try:
- conf.find_program('webpack', path_list=[webpack_path])
- except conf.errors.ConfigurationError:
- pass # Error will be caught after checking for enableMultiJS setting
- else:
- Logs.pprint('YELLOW', "WARNING: Unable to find $NODE_PATH variable required for SDK "
- "build. Please verify this build was initiated with a recent "
- "pebble-tool.")
- maybe_import_internal(conf.env)
- def build(bld):
- """
- This method is invoked from the app or lib waftool with the `bld.load('pebble_sdk_common')`
- call and sets up additional task generators for the SDK.
- :param bld: the BuildContext object
- :return: N/A
- """
- # cached_env is set to a shallow copy of the current ConfigSet for this BuildContext
- bld.env = bld.all_envs['']
- bld.load('file_name_c_define')
- # Process message keys
- bld(features='message_keys')
- cached_env = bld.env
- for platform in bld.env.TARGET_PLATFORMS:
- # bld.env is set to a shallow copy of the ConfigSet labeled <platform>
- bld.env = bld.all_envs[platform]
- # Create a build group (set of TaskGens) for <platform>
- if bld.env.USE_GROUPS:
- bld.add_group(bld.env.PLATFORM_NAME)
- # Generate a linker script specific to the current platform
- build_node = bld.path.get_bld().find_or_declare(bld.env.BUILD_DIR)
- bld(features='subst',
- source=find_sdk_component(bld, bld.env, 'pebble_app.ld.template'),
- target=build_node.make_node('pebble_app.ld.auto'),
- **bld.env.PLATFORM)
- # Locate Rocky JS tooling script
- js_tooling_script = find_sdk_component(bld, bld.env, 'tools/generate_snapshot.js')
- bld.env.JS_TOOLING_SCRIPT = js_tooling_script if js_tooling_script else None
- # bld.env is set back to a shallow copy of the original ConfigSet that was set when this
- # `build` method was invoked
- bld.env = cached_env
- # Create a build group for bundling (should run after the build groups for each platform)
- if bld.env.USE_GROUPS:
- bld.add_group('bundle')
- def _wrap_c_preproc_scan(task):
- """
- This function is a scanner function that wraps c_preproc.scan to fix up pebble.h dependencies.
- pebble.h is outside out the bld/src trees so therefore it's not considered a valid dependency
- and isn't scanned for further dependencies. Normally this would be fine but pebble.h includes
- an auto-generated resource id header which is really a dependency. We detect this include and
- add the resource id header file to the nodes being scanned by c_preproc.
- :param task: the task instance
- :return: N/A
- """
- (nodes, names) = c_preproc.scan(task)
- if 'pebble.h' in names:
- nodes.append(get_node_from_abspath(task.generator.bld, task.env.RESOURCE_ID_HEADER))
- nodes.append(get_node_from_abspath(task.generator.bld, task.env.MESSAGE_KEYS_HEADER))
- return nodes, names
- @feature('c')
- @before_method('process_source')
- def setup_pebble_c(task_gen):
- """
- This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
- ensures that the SDK `include` path for the current platform, as well as the project root
- directory and the project src directory are included as header search paths (includes) for the
- build.
- :param task_gen: the task generator instance
- :return: N/A
- """
- platform = task_gen.env.PLATFORM_NAME
- append_to_attr(task_gen, 'includes',
- [find_sdk_component(task_gen.bld, task_gen.env, 'include'),
- '.', 'include', 'src'])
- append_to_attr(task_gen, 'includes', platform)
- for lib in task_gen.bld.env.LIB_JSON:
- if 'pebble' in lib:
- lib_include_node = task_gen.bld.path.find_node(lib['path']).find_node('include')
- append_to_attr(task_gen, 'includes',
- [lib_include_node,
- lib_include_node.find_node(str(lib['name'])).find_node(platform)])
- @feature('c')
- @after_method('process_source')
- def fix_pebble_h_dependencies(task_gen):
- """
- This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
- ensures that the _wrap_c_preproc_scan method is run for all c tasks.
- :param task_gen: the task generator instance
- :return: N/A
- """
- for task in task_gen.tasks:
- if type(task) == c.c:
- # Swap out the bound member function for our own
- task.scan = types.MethodType(_wrap_c_preproc_scan, task, c.c)
- @feature('pebble_cprogram')
- @before_method('process_source')
- def setup_pebble_cprogram(task_gen):
- """
- This method is called before all of the c aliases (objects, shlib, stlib, program, etc) and
- adds the appinfo.auto.c file to the source file list, adds the SDK pebble library to the lib
- path for the build, sets the linkflags for the build, and specifies the linker script to
- use during the linking step.
- :param task_gen: the task generator instance
- :return: None
- """
- build_node = task_gen.path.get_bld().make_node(task_gen.env.BUILD_DIR)
- platform = task_gen.env.PLATFORM_NAME
- if not hasattr(task_gen, 'bin_type') or getattr(task_gen, 'bin_type') != 'lib':
- append_to_attr(task_gen, 'source', build_node.make_node('appinfo.auto.c'))
- append_to_attr(task_gen, 'source', build_node.make_node('src/resource_ids.auto.c'))
- if task_gen.env.MESSAGE_KEYS:
- append_to_attr(task_gen,
- 'source',
- get_node_from_abspath(task_gen.bld,
- task_gen.env.MESSAGE_KEYS_DEFINITION))
- append_to_attr(task_gen, 'stlibpath',
- find_sdk_component(task_gen.bld, task_gen.env, 'lib').abspath())
- append_to_attr(task_gen, 'stlib', 'pebble')
- for lib in task_gen.bld.env.LIB_JSON:
- # Skip binary check for non-Pebble libs
- if not 'pebble' in lib:
- continue
- binaries_path = task_gen.bld.path.find_node(lib['path']).find_node('binaries')
- if binaries_path:
- # Check for existence of platform folders inside binaries folder
- platform_binary_path = binaries_path.find_node(platform)
- if not platform_binary_path:
- task_gen.bld.fatal("Library {} is missing the {} platform folder in {}".
- format(lib['name'], platform, binaries_path))
- # Check for existence of binary for each platform
- if lib['name'].startswith('@'):
- scoped_name = lib['name'].rsplit('/', 1)
- lib_binary = (platform_binary_path.find_node(str(scoped_name[0])).
- find_node("lib{}.a".format(scoped_name[1])))
- else:
- lib_binary = platform_binary_path.find_node("lib{}.a".format(lib['name']))
- if not lib_binary:
- task_gen.bld.fatal("Library {} is missing a binary for the {} platform".
- format(lib['name'], platform))
- # Link library binary (supports scoped names)
- if lib['name'].startswith('@'):
- append_to_attr(task_gen, 'stlibpath',
- platform_binary_path.find_node(str(scoped_name[0])).abspath())
- append_to_attr(task_gen, 'stlib', scoped_name[1])
- else:
- append_to_attr(task_gen, 'stlibpath', platform_binary_path.abspath())
- append_to_attr(task_gen, 'stlib', lib['name'])
- append_to_attr(task_gen, 'linkflags',
- ['-Wl,--build-id=sha1',
- '-Wl,-Map,pebble-{}.map,--emit-relocs'.format(getattr(task_gen,
- 'bin_type',
- 'app'))])
- if not hasattr(task_gen, 'ldscript'):
- task_gen.ldscript = (
- build_node.find_or_declare('pebble_app.ld.auto').path_from(task_gen.path))
- def _get_entry_point(ctx, js_type, waf_js_entry_point):
- """
- Returns the appropriate JS entry point, extracted from a project's package.json file,
- wscript or common SDK default
- :param ctx: the BuildContext
- :param js_type: type of JS build, pkjs or rockyjs
- :param waf_js_entry_point: the JS entry point specified by waftools
- :return: the JS entry point for the bundled JS file
- """
- fallback_entry_point = waf_js_entry_point
- if not fallback_entry_point:
- if js_type == 'pkjs':
- if ctx.path.find_node('src/pkjs/index.js'):
- fallback_entry_point = 'src/pkjs/index.js'
- else:
- fallback_entry_point = 'src/js/app.js'
- if js_type == 'rockyjs':
- fallback_entry_point = 'src/rocky/index.js'
- project_info = ctx.env.PROJECT_INFO
- if not project_info.get('main'):
- return fallback_entry_point
- if project_info['main'].get(js_type):
- return str(project_info['main'][js_type])
- return fallback_entry_point
- @conf
- def pbl_bundle(self, *k, **kw):
- """
- This method is bound to the build context and is called by specifying `bld.pbl_bundle`. We
- set the custome features `js` and `bundle` to run when this method is invoked.
- :param self: the BuildContext object
- :param k: none expected
- :param kw:
- binaries - a list containing dictionaries specifying the HW platform targeted by the
- binary built, the app binary, and an optional worker binary
- js - the source JS files to be bundled
- js_entry_file - an optional parameter to specify the entry JS file when
- enableMultiJS is set to 'true'
- :return: a task generator instance with keyword arguments specified
- """
- if kw.get('bin_type', 'app') == 'lib':
- kw['features'] = 'headers js package'
- else:
- if self.env.BUILD_TYPE == 'rocky':
- kw['js_entry_file'] = _get_entry_point(self, 'pkjs', kw.get('js_entry_file'))
- kw['features'] = 'js bundle'
- return self(*k, **kw)
- @conf
- def pbl_build(self, *k, **kw):
- """
- This method is bound to the build context and is called by specifying `bld.pbl_build()`. We
- set the custom features `c`, `cprogram` and `pebble_cprogram` to run when this method is
- invoked. This method is intended to someday replace `pbl_program` and `pbl_worker` so that
- all apps, workers, and libs will run through this method.
- :param self: the BuildContext object
- :param k: none expected
- :param kw:
- source - the source C files to be built and linked
- target - the destination binary file for the compiled source
- :return: a task generator instance with keyword arguments specified
- """
- valid_bin_types = ('app', 'worker', 'lib', 'rocky')
- bin_type = kw.get('bin_type', None)
- if bin_type not in valid_bin_types:
- self.fatal("The pbl_build method requires that a valid bin_type attribute be specified. "
- "Valid options are {}".format(valid_bin_types))
- if bin_type == 'rocky':
- kw['features'] = 'c cprogram pebble_cprogram memory_usage'
- elif bin_type in ('app', 'worker'):
- kw['features'] = 'c cprogram pebble_cprogram memory_usage'
- kw[bin_type] = kw['target']
- elif bin_type == 'lib':
- kw['features'] = 'c cstlib memory_usage'
- path, name = kw['target'].rsplit('/', 1)
- kw['lib'] = self.path.find_or_declare(path).make_node("lib{}.a".format(name))
- # Pass values needed for memory usage report
- if bin_type != 'worker':
- kw['resources'] = (
- self.env.PROJECT_RESBALL if bin_type == 'lib' else
- self.path.find_or_declare(self.env.BUILD_DIR).make_node('app_resources.pbpack'))
- return self(*k, **kw)
- @conf
- def pbl_js_build(self, *k, **kw):
- """
- This method is bound to the build context and is called by specifying `bld.pbl_cross_compile()`.
- When this method is invoked, we set the custom feature `rockyjs` to run, which handles
- processing of JS files in preparation for Rocky.js bytecode compilation (this actually
- happens during resource generation)
- :param self: the BuildContext object
- :param k: none expected
- :param kw:
- source - the source JS files that will eventually be compiled into bytecode
- target - the destination JS file that will be specified as the source file for the
- bytecode compilation process
- :return: a task generator instance with keyword arguments specified
- """
- kw['js_entry_file'] = _get_entry_point(self, 'rockyjs', kw.get('js_entry_file'))
- kw['features'] = 'rockyjs'
- return self(*k, **kw)
|