blob: 3d2d110a0590bb051d59fd591fc52abff3b0f80a [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# 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.
import argparse
import json
import os
import sys
FUCHSIA_MODULE = "go.fuchsia.dev/fuchsia"
class Source(object):
def __init__(self, name, path, file):
self.name = name
self.path = path
self.file = file
def __str__(self):
return "%s[%s]" % (self.name, self.path)
def __hash__(self):
return hash((self.name, self.path))
def __eq__(self, other):
return self.name == other.name and self.path == other.path
def get_sources(dep_files, extra_sources=None):
# Aggregate source data from dependencies.
sources = set()
if extra_sources:
sources.update(extra_sources)
for dep in dep_files:
with open(dep, "r") as dep_file:
for name, path in json.load(dep_file)["sources"].items():
sources.add(Source(name, path, dep))
# Verify duplicates.
sources_by_name = {}
for src in sources:
sources_by_name.setdefault(src.name, []).append(src)
for name, srcs in sources_by_name.items():
if len(srcs) <= 1:
continue
print('Error: source "%s" has multiple paths.' % name)
for src in srcs:
print(" - %s (%s)" % (src.path, src.file))
raise Exception("Could not aggregate sources")
return {s.name: s.path for s in sources}
def main():
parser = argparse.ArgumentParser()
name_group = parser.add_mutually_exclusive_group(required=True)
name_group.add_argument("--name", help="Name of the current library")
name_group.add_argument(
"--name-file",
help="Path to a file containing the name of the current library",
)
parser.add_argument(
"--root-build-dir",
help="Path to the root build directory",
required=True,
)
parser.add_argument(
"--source-dir",
help="Path to the library's source directory",
required=True,
)
sources_group = parser.add_mutually_exclusive_group(required=True)
sources_group.add_argument(
"--sources", help="List of source files", nargs="*"
)
sources_group.add_argument(
"--allow-globbing",
action="store_true",
help="Allow globbing the entire source directory",
)
parser.add_argument(
"--output", help="Path to the file to generate", required=True
)
parser.add_argument(
"--deps", help="Dependencies of the current library", nargs="*"
)
args = parser.parse_args()
if args.name:
name = args.name
elif args.name_file:
with open(args.name_file, "r") as name_file:
name = name_file.read()
build_dir = os.path.abspath(args.root_build_dir)
# Find source_root from library source_dir, i.e. do not assume
# that the build directory is two levels down the source root
source_root = build_dir
while not os.path.exists(os.path.join(source_root, ".jiri_manifest")):
source_root = os.path.dirname(source_root)
if source_root in ("", "/"):
parser.error("Cannot find Fuchsia source directory!")
third_party_dir = os.path.join(source_root, "third_party")
# For Fuchsia sources, the declared package name must correspond to the
# source directory so that raw `go` commands (e.g., as an IDE would use) can
# resolve the source path based on the package name.
# TODO(olivernewman): Stop exempting package names that don't start with
# `FUCHSIA_MODULE`; all packages should use absolute names that start with
# the module name.
if name.startswith(FUCHSIA_MODULE) and not os.path.abspath(
args.source_dir
).startswith((build_dir, third_party_dir)):
expected_name = (
FUCHSIA_MODULE
+ "/"
+ os.path.relpath(args.source_dir, source_root).replace(
os.path.sep, "/"
)
)
if name not in (expected_name, expected_name + "/..."):
raise ValueError(
f"go_library name must correspond to the source dir: "
f"got {name!r}, expected {expected_name!r}"
)
current_sources = []
if args.sources:
for source in args.sources:
p = os.path.join(args.source_dir, source)
# Explicit sources must be files.
if not os.path.isfile(p):
raise ValueError(f"Source {p} is not a file")
current_sources.append(
Source(os.path.join(name, source), p, args.output)
)
if not name.endswith("/..."):
go_sources = {f for f in args.sources if f.endswith(".go")}
# Go sources are constrained to live top-level under `source_dir`;
# others (e.g., template files) are free to live further down.
for s in go_sources:
if os.path.dirname(s):
raise ValueError(
f'Source "{s}" for "{name}" comes from a subdirectory.'
f" Specify source_dir instead."
)
# Require all non-generated Go files to be listed as sources.
if not os.path.abspath(args.source_dir).startswith(build_dir):
# TODO: Use `glob.glob("*.go", root_dir=args.source_dir)`
# instead of os.listdir after upgrading to Python 3.10.
go_files = {
f for f in os.listdir(args.source_dir) if f.endswith(".go")
}
missing = go_files - go_sources
if missing:
raise ValueError(
f"go_library requires that all Go files in source_dir"
f" be listed as sources, but the following files are"
f" missing from sources for target {name}:"
f' {", ".join(sorted(missing))}'
)
elif args.allow_globbing:
current_sources.append(Source(name, args.source_dir, args.output))
result = get_sources(args.deps, extra_sources=current_sources)
with open(args.output, "w") as output_file:
json.dump(
{
"package": name,
"sources": result,
},
output_file,
indent=2,
sort_keys=True,
)
if __name__ == "__main__":
sys.exit(main())