-
Notifications
You must be signed in to change notification settings - Fork 522
/
module_mappings.bzl
143 lines (128 loc) · 5.74 KB
/
module_mappings.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# 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.
# Definitions for handling path re-mapping, to support short module names.
# See pathMapping doc: https://github.com/Microsoft/TypeScript/issues/5039
#
# This reads the module_root and module_name attributes from typescript rules in
# the transitive closure, rolling these up to provide a mapping to the
# TypeScript compiler and to editors.
#
"""Helper function and aspect to get module mappings from deps
"""
def _get_deps(attrs, names):
return [d for n in names if hasattr(attrs, n)
for d in getattr(attrs, n)]
# Traverse 'srcs' in addition so that we can go across a genrule
_MODULE_MAPPINGS_DEPS_NAMES = (["deps", "srcs"]
)
_DEBUG = False
def _debug(msg, values=()):
if _DEBUG:
print(msg % values)
def get_module_mappings(label, attrs, srcs = [], workspace_name = None, mappings_attr = "es6_module_mappings"):
"""Returns the module_mappings from the given attrs.
Collects a {module_name - module_root} hash from all transitive dependencies,
checking for collisions. If a module has a non-empty `module_root` attribute,
all sources underneath it are treated as if they were rooted at a folder
`module_name`.
Args:
label: label
attrs: attributes
srcs: sources (defaults to [])
workspace_name: workspace name (defaults to None)
mappings_attr: mappings attribute to look for (defaults to "es6_module_mappings")
Returns:
The module mappings
"""
mappings = dict()
all_deps = _get_deps(attrs, names = _MODULE_MAPPINGS_DEPS_NAMES)
for dep in all_deps:
if not hasattr(dep, mappings_attr):
continue
for k, v in getattr(dep, mappings_attr).items():
if k in mappings and mappings[k] != v:
fail(("duplicate module mapping at %s: %s maps to both %s and %s" %
(label, k, mappings[k], v)), "deps")
mappings[k] = v
if ((hasattr(attrs, "module_name") and attrs.module_name) or
(hasattr(attrs, "module_root") and attrs.module_root)):
mn = attrs.module_name
if not mn:
mn = label.name
mr = label.package
if workspace_name:
mr = "%s/%s" % (workspace_name, mr)
elif label.workspace_root:
mr = "%s/%s" % (label.workspace_root, mr)
if attrs.module_root and attrs.module_root != ".":
if attrs.module_root.endswith(".ts"):
if workspace_name:
# workspace_name is set only when doing module mapping for runtime.
# .d.ts module_root means we should be able to load in two ways:
# module_name -> module_path/module_root.js
# module_name/foo -> module_path/foo
# So we add two mappings. The one with the trailing slash is longer,
# so the loader should prefer it for any deep imports. The mapping
# without the trailing slash will be used only when importing from the
# bare module_name.
mappings[mn + "/"] = mr + "/"
mr = "%s/%s" % (mr, attrs.module_root.replace(".d.ts", ".js"))
else:
# This is the type-checking module mapping. Strip the trailing .d.ts
# as it doesn't belong in TypeScript's path mapping.
mr = "%s/%s" % (mr, attrs.module_root.replace(".d.ts", ""))
# Validate that sources are underneath the module root.
# module_roots ending in .ts are a special case, they are used to
# restrict what's exported from a build rule, e.g. only exports from a
# specific index.d.ts file. For those, not every source must be under the
# given module root.
else:
mr = "%s/%s" % (mr, attrs.module_root)
for s in srcs:
if not s.short_path.startswith(mr):
fail(("all sources must be under module root: %s, but found: %s" %
(mr, s.short_path)))
if mn in mappings and mappings[mn] != mr:
fail(("duplicate module mapping at %s: %s maps to both %s and %s" %
(label, mn, mappings[mn], mr)), "deps")
mappings[mn] = mr
_debug("Mappings at %s: %s", (label, mappings))
return mappings
def _module_mappings_aspect_impl(target, ctx):
mappings = get_module_mappings(target.label, ctx.rule.attr)
return struct(es6_module_mappings = mappings)
module_mappings_aspect = aspect(
_module_mappings_aspect_impl,
attr_aspects = _MODULE_MAPPINGS_DEPS_NAMES,
)
# When building a mapping for use at runtime, we need paths to be relative to
# the runfiles directory. This requires the workspace_name to be prefixed on
# each module root.
def _module_mappings_runtime_aspect_impl(target, ctx):
if target.label.workspace_root:
# We need the workspace_name for the target being visited.
# Skylark doesn't have this - instead they have a workspace_root
# which looks like "external/repo_name" - so grab the second path segment.
# TODO(alexeagle): investigate a better way to get the workspace name
workspace_name = target.label.workspace_root.split("/")[1]
else:
workspace_name = ctx.workspace_name
mappings = get_module_mappings(target.label, ctx.rule.attr,
workspace_name = workspace_name,
mappings_attr = "runfiles_module_mappings")
return struct(runfiles_module_mappings = mappings)
module_mappings_runtime_aspect = aspect(
_module_mappings_runtime_aspect_impl,
attr_aspects = _MODULE_MAPPINGS_DEPS_NAMES,
)