pebble/sdk/waftools/pebble_sdk_common.py

375 lines
16 KiB
Python
Raw Permalink Normal View History

# 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)