Support a local Fuchsia platform tree as SDK source

if an env variable named "LOCAL_FUCHSIA_PLATFORM_BUILD" is defined and
points to Fuchsia Platform build output directory, the SDK (including
the experimental SDK, if use_experimental is true) will be directly
copied from the Fuchsia Platform build output dir.

The Fuchsia tree needs to be built with 'fx build sdk'
or 'fx build sdk sdk:ddk' if the experimental SDK is needed.

A developer using this workflow will need to issue the following command
in order to refresh the SDK:

  bazel sync --configure

For example,

```
  export LOCAL_FUCHSIA_PLATFORM_BUILD=$HOME/fuchsia/out/core.x64

  # in OOT repository
  bazel sync --configure
  bazel build --config=fuchsia_x64 src:samples_repository

  # in Fuchsia platform source:
  # change something
  fx build sdk sdk:ddk

  # in OOT repository
  bazel sync --configure
  bazel build --config=fuchsia_x64 src:samples_repository
```

Note that a few things will look different, with possible side effects:
- the SDK version (ffx sdk version) will be an empty string
- the product bundles (ie emulator images) will require an explict gs://
  URL, since SDK version is used by default and it is empty for a local
  SDK.
- anything else that depends on the SDK version may not work as
  expected.

Change-Id: I1f806c59cd23c1fe7addb9036ab0775d10b41166
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-integration/+/670846
Fuchsia-Auto-Submit: Renato Mangini Dias <mangini@google.com>
Reviewed-by: Chase Latta <chaselatta@google.com>
Commit-Queue: Renato Mangini Dias <mangini@google.com>
diff --git a/bazel_rules_fuchsia/fuchsia/private/fuchsia_sdk_repository.bzl b/bazel_rules_fuchsia/fuchsia/private/fuchsia_sdk_repository.bzl
index 606c09e..13a33ea 100644
--- a/bazel_rules_fuchsia/fuchsia/private/fuchsia_sdk_repository.bzl
+++ b/bazel_rules_fuchsia/fuchsia/private/fuchsia_sdk_repository.bzl
@@ -10,27 +10,61 @@
 # Base URL for Fuchsia IDK archives.
 _SDK_URL_TEMPLATE = "https://chrome-infra-packages.appspot.com/dl/fuchsia/sdk/{type}/{os}-amd64/+/{tag}"
 
-# Environment variable used to set a local SDK archive
-_SDK_ARCHIVE_ENV_VAR = "BAZEL_FUCHSIA_SDK_ARCHIVE"
+# Environment variable used to set a local Fuchsia Platform tree build output directory
+# If this variable is set, it should point to <FUCHSIA_DIR>/out/<BUILD_DIR> where "sdk"
+# and optionally "sdk:ddk" target(s) are built. In particular we will look for
+#     <LOCAL_FUCHSIA_PLATFORM_BUILD>/sdk/exported/core
+# and
+#     <LOCAL_FUCHSIA_PLATFORM_BUILD>/sdk/exported/ddk  (if "use_experimental" is True)
+# Those can be produced with a 'fx build sdk sdk:ddk' command in a Fuchsia Platform tree.
+
+_LOCAL_FUCHSIA_PLATFORM_BUILD = "LOCAL_FUCHSIA_PLATFORM_BUILD"
+_LOCAL_BUILD_SDK_PATH = "sdk/exported/core"
+_LOCAL_BUILD_EXPERIMENTAL_PATH = "sdk/exported/ddk"
 
 def _sdk_url(os, tag, type = "core"):
     # Return the URL of the SDK given an Operating System string and
     # a CIPD tag.
     return _SDK_URL_TEMPLATE.format(os = os, tag = tag, type = type)
 
-def _instantiate_local(ctx):
-    # Extracts the SDK from a local archive file.
-    ctx.report_progress("Extracting local SDK archive")
-    ctx.extract(archive = ctx.os.environ[_SDK_ARCHIVE_ENV_VAR])
+def _instantiate_local(ctx, manifests):
+    # Copies the SDK from a local Fuchsia platform build.
+    local_fuchsia_dir = ctx.os.environ[_LOCAL_FUCHSIA_PLATFORM_BUILD]
+    print("WARNING: using local SDK from %s" % local_fuchsia_dir)
+    ctx.report_progress("Copying local SDK from %s" % local_fuchsia_dir)
+    local_sdk = ctx.path("%s/%s" % (local_fuchsia_dir, _LOCAL_BUILD_SDK_PATH))
+    if not local_sdk.exists:
+        fail("Cannot find SDK in local Fuchsia build. Please build it with 'fx build sdk' or unset variable %s: %s" % (_LOCAL_FUCHSIA_PLATFORM_BUILD, local_sdk))
+
+    ctx.execute(["cp", "-r", "-L", "-f", "%s/." % local_sdk, "."], quiet = False)
+
+    if ctx.attr.use_experimental:
+        ctx.report_progress("Copying experimental SDK from %s" % local_fuchsia_dir)
+        local_exp = ctx.path("%s/%s" % (local_fuchsia_dir, _LOCAL_BUILD_EXPERIMENTAL_PATH))
+        if not local_exp.exists:
+            fail("Cannot find experimental SDK in local Fuchsia build. Please build it with 'fx build sdk:ddk' or unset variable %s: %s" % (_LOCAL_FUCHSIA_PLATFORM_BUILD, local_exp))
+
+        ctx.execute(["mkdir", "-p", "experimental"], quiet = False)
+        ctx.execute(["cp", "-r", "-L", "-f", "%s/." % local_exp, "experimental/."], quiet = False)
+
+    _merge_experimental_sdk(ctx, manifests)
+
+def _merge_experimental_sdk(ctx, manifests):
+    # the manifest in the experimental sdk has the same name as the one in core. To avoid overwriting
+    # the core manifest while extracting, we extract to a separate directory (experimental/), rename
+    # the manifest and then copy (hardlink to avoid unnecessary use of space) to the same place as
+    # the core sdk.
+    experimental_manifest = "meta/experimental_manifest.json"
+    ctx.execute(["mv", "experimental/meta/manifest.json", "experimental/%s" % experimental_manifest], quiet = False)
+    ctx.execute(["cp", "-r", "-f", "-l", "experimental/.", "."], quiet = False)
+    manifests.append(experimental_manifest)
 
 def _fuchsia_sdk_repository_impl(ctx):
     ctx.file("WORKSPACE.bazel", content = "")
     manifests = ["meta/manifest.json"]
     normalized_os = normalize_os(ctx)
-    if _SDK_ARCHIVE_ENV_VAR in ctx.os.environ:
-        if ctx.attr.use_experimental:
-            fail("Experimental SDK is only supported with a cipd_tag. For a local archive, please provide a single SDK.")
-        _instantiate_local(ctx)
+    if _LOCAL_FUCHSIA_PLATFORM_BUILD in ctx.os.environ:
+        _instantiate_local(ctx, manifests)
     elif ctx.attr.cipd_tag:
         sha256 = ""
         if ctx.attr.sha256:
@@ -50,22 +84,10 @@
                 sha256 = sha256,
                 output = "experimental",
             )
+            _merge_experimental_sdk(ctx, manifests)
 
-            # the manifest in the experimental sdk has the same name as the one in core. To avoid overwriting
-            # the core manifest while extracting, we extract to a separate directory (experimental/), rename
-            # the manifest and then copy (hardlink to avoid unnecessary use of space) to the same place as
-            # the core sdk.
-            experimental_manifest = "meta/experimental_manifest.json"
-            ctx.execute(["mv", "experimental/meta/manifest.json", "experimental/%s" % experimental_manifest], quiet = False)
-            ctx.execute(["cp", "-r", "-f", "-l", "experimental/.", "."], quiet = False)
-            manifests.append(experimental_manifest)
-
-    elif ctx.attr.local_archive:
-        if ctx.attr.use_experimental:
-            fail("Experimental SDK is only supported with a cipd_tag. For a local archive, please provide a single SDK.")
-        _instantiate_local(ctx)
     else:
-        fail("One of local_archive or cipd_tag must be set for fuchsia_sdk_repository")
+        fail("One of %s env variable or fuchsia_sdk_repository.cipd_tag needs to be set" % _LOCAL_FUCHSIA_PLATFORM_BUILD)
 
     ctx.report_progress("Generating Bazel rules for the SDK")
     ctx.template(
@@ -92,7 +114,8 @@
 If cipd_tag is not set, BAZEL_FUCHSIA_SDK_ARCHIVE must be set.
 """,
     implementation = _fuchsia_sdk_repository_impl,
-    environ = [_SDK_ARCHIVE_ENV_VAR],
+    environ = [_LOCAL_FUCHSIA_PLATFORM_BUILD],
+    configure = True,
     attrs = {
         "cipd_tag": attr.string(
             doc = "CIPD tag for the version to load.",