| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| load( |
| ":providers.bzl", |
| "FuchsiaComponentInfo", |
| "FuchsiaDriverToolInfo", |
| "FuchsiaPackageInfo", |
| "FuchsiaPackageResourcesInfo", |
| ) |
| load(":fuchsia_component.bzl", "fuchsia_component_for_unit_test") |
| load(":fuchsia_debug_symbols.bzl", "collect_debug_symbols", "get_build_id_dirs", "strip_resources") |
| load(":fuchsia_transition.bzl", "fuchsia_transition") |
| load(":package_publishing.bzl", "package_repo_path_from_label", "publish_package") |
| load(":utils.bzl", "label_name", "make_resource_struct", "rule_variants", "stub_executable") |
| load("//fuchsia/private/workflows:fuchsia_package_tasks.bzl", "fuchsia_package_tasks") |
| |
| def fuchsia_package( |
| *, |
| name, |
| package_name = None, |
| archive_name = None, |
| components = [], |
| resources = [], |
| tools = [], |
| **kwargs): |
| """Builds a fuchsia package. |
| |
| This rule produces a fuchsia package which can be published to a package |
| server and loaded on a device. |
| |
| The rule will return both package manifest json file which can be used later |
| in the build system and an archive (.far) of the package which can be shared. |
| |
| This macro will expand out into several fuchsia tasks that can be run by a |
| bazel invocation. Given a package definition, the following targets will be |
| created. |
| |
| ``` |
| fuchsia_package( |
| name = "pkg", |
| components = [":my_component"], |
| tools = [":my_tool"] |
| ) |
| ``` |
| - pkg.help: Calling run on this target will show the valid macro-expanded targets |
| - pkg.publish: Calling run on this target will publish the package |
| - pkg.my_component: Calling run on this target will call `ffx component run` |
| with the component url if it is fuchsia_component instance and will |
| call `ffx driver register` if it is a fuchsia_driver_component. |
| - pkg.my_tool: Calling run on this target will call `ffx driver run-tool` if |
| the tool is a fuchsia_driver_tool |
| |
| Args: |
| name: The target name. |
| components: A list of components to add to this package. The dependencies |
| of these targets will have their debug symbols stripped and added to |
| the build-id directory. |
| resources: A list of additional resources to add to this package. These |
| resources will not have debug symbols stripped. |
| tools: Additional tools that should be added to this package. |
| package_name: An optional name to use for this package, defaults to name. |
| archive_name: An option name for the far file. |
| **kwargs: extra attributes to pass along to the build rule. |
| """ |
| _build_fuchsia_package( |
| name = "%s_fuchsia_package" % name, |
| components = components, |
| resources = resources, |
| tools = tools, |
| package_name = package_name or name, |
| archive_name = archive_name, |
| **kwargs |
| ) |
| |
| fuchsia_package_tasks( |
| name = name, |
| package = "%s_fuchsia_package" % name, |
| components = {component: component for component in components}, |
| tools = {tool: tool for tool in tools}, |
| **kwargs |
| ) |
| |
| def _fuchsia_test_package( |
| *, |
| name, |
| package_name = None, |
| archive_name = None, |
| resources = [], |
| _test_component_mapping, |
| _components = [], |
| **kwargs): |
| """Defines test variants of fuchsia_package. |
| |
| See fuchsia_package for argument descriptions.""" |
| |
| _build_fuchsia_package_test( |
| name = "%s_fuchsia_package" % name, |
| test_components = _test_component_mapping.values(), |
| components = _components, |
| resources = resources, |
| package_name = package_name or name, |
| archive_name = archive_name, |
| **kwargs |
| ) |
| |
| fuchsia_package_tasks( |
| name = name, |
| package = "%s_fuchsia_package" % name, |
| components = _test_component_mapping, |
| is_test = True, |
| **kwargs |
| ) |
| |
| def fuchsia_test_package( |
| *, |
| name, |
| test_components = [], |
| components = [], |
| **kwargs): |
| """A test variant of fuchsia_package. |
| |
| See _fuchsia_test_package for additional arguments.""" |
| _fuchsia_test_package( |
| name = name, |
| _test_component_mapping = {component: component for component in test_components}, |
| _components = components, |
| **kwargs |
| ) |
| |
| def fuchsia_unittest_package( |
| *, |
| name, |
| package_name = None, |
| archive_name = None, |
| resources = [], |
| unit_tests, |
| **kwargs): |
| """A variant of fuchsia_test_package containing unit tests. |
| |
| See _fuchsia_test_package for additional arguments.""" |
| |
| test_component_mapping = {} |
| for unit_test in unit_tests: |
| test_component_mapping[unit_test] = "%s_unit_test" % label_name(unit_test) |
| fuchsia_component_for_unit_test( |
| name = test_component_mapping[unit_test], |
| unit_test = unit_test, |
| **kwargs |
| ) |
| |
| _fuchsia_test_package( |
| name = name, |
| package_name = package_name, |
| archive_name = archive_name, |
| resources = resources, |
| _test_component_mapping = test_component_mapping, |
| **kwargs |
| ) |
| pass |
| |
| def _build_fuchsia_package_impl(ctx): |
| sdk = ctx.toolchains["@rules_fuchsia//fuchsia:toolchain"] |
| archive_name = ctx.attr.archive_name or ctx.attr.package_name |
| |
| if not archive_name.endswith(".far"): |
| archive_name += ".far" |
| |
| # where we will collect all of the temporary files |
| pkg_dir = ctx.label.name + "_pkg/" |
| |
| # Declare all of the output files |
| manifest = ctx.actions.declare_file(pkg_dir + "manifest") |
| meta_package = ctx.actions.declare_file(pkg_dir + "meta/package") |
| meta_far = ctx.actions.declare_file(pkg_dir + "meta.far") |
| output_package_manifest = ctx.actions.declare_file(ctx.label.name + "_package_manifest.json") |
| far_file = ctx.actions.declare_file(archive_name) |
| |
| # The Fuchsia target API level of this package |
| api_level = sdk.default_api_level |
| |
| api_level_input = ["-api-level", str(api_level)] |
| |
| # All of the resources that will go into the package |
| package_resources = [ |
| # Initially include the meta package |
| make_resource_struct( |
| src = meta_package, |
| dest = "meta/package", |
| ), |
| ] |
| |
| # Resources that we will pass through the debug symbol stripping process |
| resources_to_strip = [] |
| components = [] |
| drivers = [] |
| |
| # Verify correctness of test vs non-test components. |
| for test_component in ctx.attr.test_components: |
| if not test_component[FuchsiaComponentInfo].is_test: |
| fail("Please use `components` for non-test components.") |
| for component in ctx.attr.components: |
| if component[FuchsiaComponentInfo].is_test: |
| fail("Please use `test_components` for test components.") |
| |
| # Collect all the resources from the deps |
| for dep in ctx.attr.test_components + ctx.attr.components + ctx.attr.resources + ctx.attr.tools: |
| if FuchsiaComponentInfo in dep: |
| component_info = dep[FuchsiaComponentInfo] |
| component_manifest = component_info.manifest |
| component_dest = "meta/%s" % (component_manifest.basename) |
| components.append(component_dest) |
| |
| if component_info.is_driver: |
| drivers.append(component_dest) |
| |
| package_resources.append( |
| # add the component manifest |
| make_resource_struct( |
| src = component_manifest, |
| dest = component_dest, |
| ), |
| ) |
| resources_to_strip.extend([r for r in component_info.resources]) |
| elif FuchsiaDriverToolInfo in dep: |
| resources_to_strip.extend(dep[FuchsiaDriverToolInfo].resources) |
| elif FuchsiaPackageResourcesInfo in dep: |
| # Don't strip debug symbols from resources. |
| package_resources.extend(dep[FuchsiaPackageResourcesInfo].resources) |
| else: |
| fail("Unknown dependency type being added to package: %s" % dep.label) |
| |
| # Grab all of our stripped resources |
| stripped_resources, _debug_info = strip_resources(ctx, resources_to_strip) |
| package_resources.extend(stripped_resources) |
| |
| # Write our package_manifest file |
| ctx.actions.write( |
| output = manifest, |
| content = "\n".join(["%s=%s" % (r.dest, r.src.path) for r in package_resources]), |
| ) |
| |
| # Create the meta/package file |
| output_dir = manifest.dirname |
| ctx.actions.run( |
| executable = sdk.pm, |
| arguments = [ |
| "-o", # output directory |
| output_dir, |
| "-n", # name of the package |
| ctx.attr.package_name, |
| "init", |
| ], |
| outputs = [ |
| meta_package, |
| ], |
| mnemonic = "FuchsiaPmInit", |
| ) |
| |
| # The only input to the build step is the manifest but we need to |
| # include all of the resources as inputs so that if they change the |
| # package will get rebuilt. |
| build_inputs = [r.src for r in package_resources] + [ |
| manifest, |
| meta_package, |
| ] |
| |
| repo_name_args = ["-r", ctx.attr.package_repository_name] if (ctx.attr.package_repository_name != None) else [] |
| |
| # Build the package |
| ctx.actions.run( |
| executable = sdk.pm, |
| arguments = [ |
| "-o", |
| output_dir, |
| "-m", |
| manifest.path, |
| "-n", |
| ctx.attr.package_name, |
| ] + repo_name_args + api_level_input + [ |
| "build", |
| "--output-package-manifest", |
| output_package_manifest.path, |
| ], |
| inputs = build_inputs, |
| outputs = [ |
| output_package_manifest, |
| meta_far, |
| ], |
| mnemonic = "FuchsiaPmBuild", |
| progress_message = "Building package for %s" % ctx.label, |
| ) |
| |
| # Create the far file. |
| ctx.actions.run( |
| executable = sdk.pm, |
| arguments = [ |
| "-o", |
| output_dir, |
| "-m", |
| manifest.path, |
| "-n", |
| ctx.attr.package_name, |
| "archive", |
| "-output", |
| # pm automatically adds .far so we have to remove it here to make |
| # bazel happy since we need to declare the output with the extension |
| far_file.path[:-4], |
| ], |
| inputs = [meta_far, output_package_manifest] + build_inputs, |
| outputs = [ |
| far_file, |
| ], |
| mnemonic = "FuchsiaPmArchive", |
| progress_message = "Archiving package for %{label}", |
| ) |
| |
| output_files = [ |
| far_file, |
| output_package_manifest, |
| manifest, |
| meta_far, |
| ] |
| |
| # Attempt to publish if told to do so |
| repo_path = package_repo_path_from_label(ctx.attr._package_repo_path) |
| if repo_path: |
| # TODO: collect all dependent packages |
| stamp_file = publish_package(ctx, sdk.pm, repo_path, [output_package_manifest]) |
| output_files.append(stamp_file) |
| |
| # Sanity check that we are not trying to put 2 different resources at the same mountpoint |
| collected_blobs = {} |
| for resource in package_resources: |
| if resource.dest in collected_blobs and resource.src.path != collected_blobs[resource.dest]: |
| fail("Trying to add multiple resources with the same filename and different content") |
| else: |
| collected_blobs[resource.dest] = resource.src.path |
| |
| return [ |
| DefaultInfo(files = depset(output_files), executable = stub_executable(ctx)), |
| FuchsiaPackageInfo( |
| far_file = far_file, |
| package_manifest = output_package_manifest, |
| files = [output_package_manifest, meta_far] + build_inputs, |
| package_name = ctx.attr.package_name, |
| components = components, |
| drivers = drivers, |
| meta_far = meta_far, |
| package_resources = package_resources, |
| |
| # TODO: Remove this field, change usages to FuchsiaDebugSymbolInfo. |
| build_id_dir = get_build_id_dirs(_debug_info)[0], |
| ), |
| collect_debug_symbols( |
| _debug_info, |
| ctx.attr.test_components, |
| ctx.attr.components, |
| ctx.attr.resources, |
| ctx.attr.tools, |
| ctx.attr._fuchsia_sdk_debug_symbols, |
| ), |
| ] |
| |
| _build_fuchsia_package, _build_fuchsia_package_test = rule_variants( |
| variants = (None, "test"), |
| doc = "Builds a fuchsia package.", |
| implementation = _build_fuchsia_package_impl, |
| cfg = fuchsia_transition, |
| toolchains = ["@rules_fuchsia//fuchsia:toolchain", "@bazel_tools//tools/cpp:toolchain_type"], |
| attrs = { |
| "package_name": attr.string( |
| doc = "The name of the package", |
| mandatory = True, |
| ), |
| "archive_name": attr.string( |
| doc = "What to name the archive. The .far file will be appended if not in this name. Defaults to package_name", |
| ), |
| # TODO(https://fxbug.dev/114334): Improve doc for this field when we |
| # have more clarity from the bug. |
| "package_repository_name": attr.string( |
| doc = "Repository name of this package, defaults to None", |
| ), |
| "components": attr.label_list( |
| doc = "The list of components included in this package", |
| providers = [FuchsiaComponentInfo], |
| ), |
| "test_components": attr.label_list( |
| doc = "The list of test components included in this package", |
| providers = [FuchsiaComponentInfo], |
| ), |
| "resources": attr.label_list( |
| doc = "The list of resources included in this package", |
| providers = [FuchsiaPackageResourcesInfo], |
| ), |
| "tools": attr.label_list( |
| doc = "The list of tools included in this package", |
| providers = [FuchsiaDriverToolInfo], |
| ), |
| "_fuchsia_sdk_debug_symbols": attr.label( |
| doc = "Include debug symbols from @fuchsia_sdk.", |
| default = "@fuchsia_sdk//:debug_symbols", |
| ), |
| "_package_repo_path": attr.label( |
| doc = "The command line flag used to publish packages.", |
| default = "//fuchsia:package_repo", |
| ), |
| "_elf_strip_tool": attr.label( |
| default = "//fuchsia/tools:elf_strip", |
| executable = True, |
| cfg = "exec", |
| ), |
| "_generate_symbols_dir_tool": attr.label( |
| default = "//fuchsia/tools:generate_symbols_dir", |
| executable = True, |
| cfg = "exec", |
| ), |
| "_cc_toolchain": attr.label( |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| "_allowlist_function_transition": attr.label( |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", |
| ), |
| }, |
| ) |