[sdk][bazel] Package up the proper version of prebuilt SDK libraries.

This change improves the packaging strategy for C++ binaries by defining explicit mappings for SDK libraries.

Test: added test for packaging non-SDK libraries, verified contents of resulting package
Bug: DX-312 #done
Change-Id: If619f9a82e40d4f5e4f99ce9d108fef849fdc444
diff --git a/sdk/bazel/base/build_defs/package.bzl b/sdk/bazel/base/build_defs/package.bzl
index 7a2fa67..9d406cd 100644
--- a/sdk/bazel/base/build_defs/package.bzl
+++ b/sdk/bazel/base/build_defs/package.bzl
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-load(":package_info.bzl", "get_aggregate_info", "PackageAggregateInfo", "PackageLocalInfo")
+load(":package_info.bzl", "get_aggregate_info", "PackageAggregateInfo", "PackageGeneratedInfo", "PackageLocalInfo")
 
 """
 Defines a Fuchsia package
@@ -18,23 +18,47 @@
         The list of targets to be built into this package
 """
 
+# The attributes along which the aspect propagates.
+# The value denotes whether the attribute represents a list of target or a
+# single target.
+_ASPECT_ATTRIBUTES = {
+    "data": True,
+    "target": False,
+    "deps": True,
+    "srcs": True,
+}
+
 def _info_impl(target, context):
     mappings = []
     if PackageLocalInfo in target:
         mappings = target[PackageLocalInfo].mappings
-    deps = context.attr.deps if hasattr(context.attr, "deps") else []
-    return [get_aggregate_info(mappings, deps)]
+    elif PackageGeneratedInfo in target:
+        mappings = target[PackageGeneratedInfo].mappings
+    deps = []
+    for attribute, is_list in _ASPECT_ATTRIBUTES.items():
+        if hasattr(context.rule.attr, attribute):
+            value = getattr(context.rule.attr, attribute)
+            if is_list:
+                deps += value
+            else:
+                deps.append(value)
+    return [
+        get_aggregate_info(mappings, deps),
+    ]
 
 # An aspect which turns PackageLocalInfo providers into a PackageAggregateInfo
 # provider to identify all elements which need to be included in the package.
 _info_aspect = aspect(
     implementation = _info_impl,
-    attr_aspects = [
-        "deps",
-    ],
+    attr_aspects = _ASPECT_ATTRIBUTES.keys(),
     provides = [
         PackageAggregateInfo,
     ],
+    # If any other aspect is applied to produce package mappings, let the result
+    # of that process be visible to the present aspect.
+    required_aspect_providers = [
+        PackageGeneratedInfo,
+    ],
 )
 
 def _fuchsia_package_impl(context):
diff --git a/sdk/bazel/base/build_defs/package_info.bzl b/sdk/bazel/base/build_defs/package_info.bzl
index ac16137..1c34d05 100644
--- a/sdk/bazel/base/build_defs/package_info.bzl
+++ b/sdk/bazel/base/build_defs/package_info.bzl
@@ -12,6 +12,15 @@
     },
 )
 
+# Identical to PackageLocalInfo, but a different type is needed when that
+# information if generated from an aspect so that it does not collide with any
+# existing PackageLocalInfo returned provider.
+PackageGeneratedInfo = provider(
+    fields = {
+        "mappings": "list of (package dest, source) pairs",
+    },
+)
+
 PackageAggregateInfo = provider(
     fields = {
         "contents": "depset of (package dest, source) pairs",
@@ -19,6 +28,10 @@
 )
 
 def get_aggregate_info(mappings, deps):
-    transitive_info = [dep[PackageAggregateInfo].contents for dep in deps]
+    transitive_info = []
+    for dep in deps:
+        if PackageAggregateInfo not in dep:
+            continue
+        transitive_info.append(dep[PackageAggregateInfo].contents)
     return PackageAggregateInfo(contents = depset(mappings,
                                                   transitive = transitive_info))
diff --git a/sdk/bazel/base/build_defs/packageable_cc_binary.bzl b/sdk/bazel/base/build_defs/packageable_cc_binary.bzl
index 15677b2..b6bdbef 100644
--- a/sdk/bazel/base/build_defs/packageable_cc_binary.bzl
+++ b/sdk/bazel/base/build_defs/packageable_cc_binary.bzl
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-load(":package_info.bzl", "PackageLocalInfo")
+load(":package_info.bzl", "get_aggregate_info", "PackageGeneratedInfo", "PackageLocalInfo")
 
 """
 Makes a cc_binary ready for inclusion in a fuchsia_package.
@@ -13,38 +13,76 @@
         The cc_binary to package up.
 """
 
-def _packageable_cc_binary(context):
-    runfiles = []
-    runfiles.append(context.attr.binary[DefaultInfo].default_runfiles.files)
-    for dep in context.attr._deps:
-        runfiles.append(dep[DefaultInfo].default_runfiles.files)
-    runfiles_depset = depset(transitive = runfiles)
+def _cc_contents_impl(target, context):
+    if context.rule.kind != "cc_binary":
+        return [
+            PackageGeneratedInfo(mappings = []),
+        ]
     mappings = {}
-    for file in runfiles_depset.to_list():
+    for file in target[DefaultInfo].files.to_list():
         if file.extension == "":
             mappings["bin/" + file.basename] = file
         elif file.extension == "so":
             mappings["lib/" + file.basename] = file
-        else:
-            print("Ignoring file: " + file.path)
     return [
-        PackageLocalInfo(mappings = mappings.items()),
+        PackageGeneratedInfo(mappings = mappings.items()),
     ]
 
-packageable_cc_binary = rule(
-    implementation = _packageable_cc_binary,
+# This aspect looks for cc_binary targets in the dependency tree of the given
+# target. For each of these targets, it then generates package content mappings.
+_cc_contents_aspect = aspect(
+    implementation = _cc_contents_impl,
+    attr_aspects = [
+        "data",
+        "deps",
+        "srcs",
+    ],
+    provides = [
+        PackageGeneratedInfo,
+    ],
+)
+
+def _packageable_cc_binary_impl(context):
+    target_files = context.attr.target[DefaultInfo].files.to_list()
+    if len(target_files) != 1:
+        fail("Packaged binary should produce a single output", attr="target")
+    output = target_files[0]
+    if output.extension != "":
+        fail("Expected executable, got: " + output.basename, attr="target")
+    return [
+        # TODO(pylaligand): remove this extra mapping once it's obsolete.
+        PackageLocalInfo(mappings = [("bin/app", output)]),
+    ]
+
+_packageable_cc_binary = rule(
+    implementation = _packageable_cc_binary_impl,
     attrs = {
-        "binary": attr.label(
+        "target": attr.label(
             doc = "The cc_binary to package",
             allow_files = False,
-        ),
-        "_deps": attr.label_list(
-            doc = "The dependencies needed in all packages",
-            default = [
-                # TODO(pylaligand): this label should be arch-independent.
-                Label("//arch/x64/sysroot:dist"),
-                Label("//pkg/fdio"),
+            aspects = [
+                _cc_contents_aspect,
             ],
-        )
+        ),
     },
 )
+
+def packageable_cc_binary(name, target):
+    packaged_name = name + "_packaged"
+
+    _packageable_cc_binary(
+        name = packaged_name,
+        target = target,
+    )
+
+    # The filegroup is needed so that the packaging can properly crawl all the
+    # dependencies and look for package contents.
+    native.filegroup(
+        name = name,
+        srcs = [
+            ":" + packaged_name,
+            # TODO(pylaligand): this label should be arch-independent.
+            Label("//arch/x64/sysroot:dist"),
+            Label("//pkg/fdio"),
+        ]
+    )
diff --git a/sdk/bazel/generate.py b/sdk/bazel/generate.py
index 9b4bc3f..824f22b 100755
--- a/sdk/bazel/generate.py
+++ b/sdk/bazel/generate.py
@@ -190,8 +190,9 @@
             destination = file.destination
             extension = os.path.splitext(destination)[1][1:]
             if extension == 'so' or extension == 'a' or extension == 'o':
-                dest = os.path.join(base, 'arch',
-                                    self.metadata.target_arch, destination)
+                relative_dest = os.path.join('arch', self.metadata.target_arch,
+                                             destination)
+                dest = os.path.join(base, relative_dest)
                 if os.path.isfile(dest):
                     raise Exception('File already exists: %s.' % dest)
                 self.make_dir(dest)
@@ -200,10 +201,11 @@
                         destination.startswith('lib')):
                     if library.prebuilt:
                         raise Exception('Multiple prebuilts for %s.' % dest)
-                    src = os.path.join('arch', self.metadata.target_arch,
-                                       destination)
-                    library.prebuilt = src
+                    library.prebuilt = relative_dest
                     library.is_static = extension == 'a'
+                if file.is_packaged:
+                    package_path = 'lib/%s' % os.path.basename(relative_dest)
+                    library.packaged_files[package_path] = relative_dest
             elif self.is_overlay:
                 # Only binaries get installed in overlay mode.
                 continue
diff --git a/sdk/bazel/template_model.py b/sdk/bazel/template_model.py
index cd67e18..bf2ff92 100644
--- a/sdk/bazel/template_model.py
+++ b/sdk/bazel/template_model.py
@@ -28,6 +28,7 @@
         self.prebuilt = ""
         self.is_static = False
         self.target_arch = target_arch
+        self.packaged_files = {}
 
 
 class FidlLibrary(object):
diff --git a/sdk/bazel/templates/cc_prebuilt_library_srcs.mako b/sdk/bazel/templates/cc_prebuilt_library_srcs.mako
index 1b6215b..8a42f5f 100644
--- a/sdk/bazel/templates/cc_prebuilt_library_srcs.mako
+++ b/sdk/bazel/templates/cc_prebuilt_library_srcs.mako
@@ -7,3 +7,12 @@
     shared_library = "${data.prebuilt}",
     % endif
 )
+
+package_files(
+    name = "${data.target_arch}_dist",
+    contents = {
+        % for path, source in sorted(data.packaged_files.iteritems()):
+        "${source}": "${path}",
+        % endfor
+    },
+)
diff --git a/sdk/bazel/templates/cc_prebuilt_library_top.mako b/sdk/bazel/templates/cc_prebuilt_library_top.mako
index c1c4b45..44ee02b 100644
--- a/sdk/bazel/templates/cc_prebuilt_library_top.mako
+++ b/sdk/bazel/templates/cc_prebuilt_library_top.mako
@@ -2,6 +2,8 @@
 
 package(default_visibility = ["//visibility:public"])
 
+load("//build_defs:package_files.bzl", "package_files")
+
 # Note: the cc_library / cc_import combo serves two purposes:
 #  - it allows the use of a select clause to target the proper architecture;
 #  - it works around an issue with cc_import which does not have an "includes"
@@ -26,6 +28,10 @@
         "${include}",
         % endfor
     ],
+    data = select({
+        "//build_defs/target_cpu:arm64": [":arm64_dist"],
+        "//build_defs/target_cpu:x64": [":x64_dist"],
+    }),
 )
 
 # Architecture-specific targets
diff --git a/sdk/bazel/tests/cc/BUILD b/sdk/bazel/tests/cc/BUILD
index f255f94..5f0487c 100644
--- a/sdk/bazel/tests/cc/BUILD
+++ b/sdk/bazel/tests/cc/BUILD
@@ -13,6 +13,29 @@
     ],
 )
 
+# Local shared library for packaging.
+cc_binary(
+    name = "libshared.so",
+    srcs = [
+        "library.cc",
+        "library.h",
+    ],
+    linkshared = True,
+)
+
+cc_library(
+    name = "shared_library",
+    hdrs = [
+        "library.h",
+    ],
+    srcs = [
+        ":libshared.so",
+    ],
+    includes = [
+        ".",
+    ],
+)
+
 # C++ program with dependency on a Fuchsia library.
 cc_binary(
     name = "pkg_dep",
@@ -20,14 +43,15 @@
         "pkg_dep.cc",
     ],
     deps = [
-        "@fuchsia_sdk//pkg/fdio",
+        ":shared_library",
+        "@fuchsia_sdk//pkg/svc",
     ],
 )
 
 # Prepare the binary for inclusion in a package.
 packageable_cc_binary(
     name = "packageable",
-    binary = ":pkg_dep",
+    target = ":pkg_dep",
 )
 
 # C++ program in a Fuchsia package.
diff --git a/sdk/bazel/tests/cc/library.cc b/sdk/bazel/tests/cc/library.cc
new file mode 100644
index 0000000..c72f367
--- /dev/null
+++ b/sdk/bazel/tests/cc/library.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 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.
+
+#include "library.h"
+
+namespace library {
+
+void do_something() {
+  // Maybe not...
+}
+
+}  // namespace library
diff --git a/sdk/bazel/tests/cc/library.h b/sdk/bazel/tests/cc/library.h
new file mode 100644
index 0000000..cea815b
--- /dev/null
+++ b/sdk/bazel/tests/cc/library.h
@@ -0,0 +1,9 @@
+// Copyright 2018 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.
+
+namespace library {
+
+void do_something();
+
+}  // namespace library
diff --git a/sdk/bazel/tests/cc/pkg_dep.cc b/sdk/bazel/tests/cc/pkg_dep.cc
index 7d8fec9..4ad11dc 100644
--- a/sdk/bazel/tests/cc/pkg_dep.cc
+++ b/sdk/bazel/tests/cc/pkg_dep.cc
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <lib/fdio/namespace.h>
+#include <lib/svc/dir.h>
+
+#include "library.h"
 
 int main(int argc, const char** argv) {
-  fdio_ns_create(NULL);
+  svc_dir_destroy(NULL);
+  library::do_something();
 }