mirror of
https://github.com/google/pebble.git
synced 2025-03-15 16:51:21 +00:00
375 lines
16 KiB
Python
375 lines
16 KiB
Python
|
# 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)
|