1# Copyright 2017 The TensorFlow Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Utilities for defining TensorFlow Bazel dependencies."""
16
17_SINGLE_URL_WHITELIST = depset([
18    "arm_compiler",
19])
20
21def _is_windows(ctx):
22    return ctx.os.name.lower().find("windows") != -1
23
24def _wrap_bash_cmd(ctx, cmd):
25    if _is_windows(ctx):
26        bazel_sh = _get_env_var(ctx, "BAZEL_SH")
27        if not bazel_sh:
28            fail("BAZEL_SH environment variable is not set")
29        cmd = [bazel_sh, "-l", "-c", " ".join(["\"%s\"" % s for s in cmd])]
30    return cmd
31
32def _get_env_var(ctx, name):
33    if name in ctx.os.environ:
34        return ctx.os.environ[name]
35    else:
36        return None
37
38# Checks if we should use the system lib instead of the bundled one
39def _use_system_lib(ctx, name):
40    syslibenv = _get_env_var(ctx, "TF_SYSTEM_LIBS")
41    if syslibenv:
42        for n in syslibenv.strip().split(","):
43            if n.strip() == name:
44                return True
45    return False
46
47# Executes specified command with arguments and calls 'fail' if it exited with
48# non-zero code
49def _execute_and_check_ret_code(repo_ctx, cmd_and_args):
50    result = repo_ctx.execute(cmd_and_args, timeout = 60)
51    if result.return_code != 0:
52        fail(("Non-zero return code({1}) when executing '{0}':\n" + "Stdout: {2}\n" +
53              "Stderr: {3}").format(
54            " ".join([str(x) for x in cmd_and_args]),
55            result.return_code,
56            result.stdout,
57            result.stderr,
58        ))
59
60def _repos_are_siblings():
61    return Label("@foo//bar").workspace_root.startswith("../")
62
63# Apply a patch_file to the repository root directory.
64def _apply_patch(ctx, patch_file):
65    ctx.patch(patch_file, strip = 1)
66
67def _apply_delete(ctx, paths):
68    for path in paths:
69        if path.startswith("/"):
70            fail("refusing to rm -rf path starting with '/': " + path)
71        if ".." in path:
72            fail("refusing to rm -rf path containing '..': " + path)
73    cmd = _wrap_bash_cmd(ctx, ["rm", "-rf"] + [ctx.path(path) for path in paths])
74    _execute_and_check_ret_code(ctx, cmd)
75
76def _tf_http_archive(ctx):
77    if ("mirror.tensorflow.org" not in ctx.attr.urls[0] and
78        (len(ctx.attr.urls) < 2 and
79         ctx.attr.name not in _SINGLE_URL_WHITELIST.to_list())):
80        fail("tf_http_archive(urls) must have redundant URLs. The " +
81             "mirror.tensorflow.org URL must be present and it must come first. " +
82             "Even if you don't have permission to mirror the file, please " +
83             "put the correctly formatted mirror URL there anyway, because " +
84             "someone will come along shortly thereafter and mirror the file.")
85
86    use_syslib = _use_system_lib(ctx, ctx.attr.name)
87
88    # Work around the bazel bug that redownloads the whole library.
89    # Remove this after https://github.com/bazelbuild/bazel/issues/10515 is fixed.
90    if ctx.attr.additional_build_files:
91        for internal_src in ctx.attr.additional_build_files:
92            _ = ctx.path(Label(internal_src))
93
94    # End of workaround.
95
96    if not use_syslib:
97        ctx.download_and_extract(
98            ctx.attr.urls,
99            "",
100            ctx.attr.sha256,
101            ctx.attr.type,
102            ctx.attr.strip_prefix,
103        )
104        if ctx.attr.delete:
105            _apply_delete(ctx, ctx.attr.delete)
106        if ctx.attr.patch_file != None:
107            _apply_patch(ctx, ctx.attr.patch_file)
108
109    if use_syslib and ctx.attr.system_build_file != None:
110        # Use BUILD.bazel to avoid conflict with third party projects with
111        # BUILD or build (directory) underneath.
112        ctx.template("BUILD.bazel", ctx.attr.system_build_file, {
113            "%prefix%": ".." if _repos_are_siblings() else "external",
114        }, False)
115
116    elif ctx.attr.build_file != None:
117        # Use BUILD.bazel to avoid conflict with third party projects with
118        # BUILD or build (directory) underneath.
119        ctx.template("BUILD.bazel", ctx.attr.build_file, {
120            "%prefix%": ".." if _repos_are_siblings() else "external",
121        }, False)
122
123    if use_syslib:
124        for internal_src, external_dest in ctx.attr.system_link_files.items():
125            ctx.symlink(Label(internal_src), ctx.path(external_dest))
126
127    if ctx.attr.additional_build_files:
128        for internal_src, external_dest in ctx.attr.additional_build_files.items():
129            ctx.symlink(Label(internal_src), ctx.path(external_dest))
130
131tf_http_archive = repository_rule(
132    attrs = {
133        "sha256": attr.string(mandatory = True),
134        "urls": attr.string_list(
135            mandatory = True,
136            allow_empty = False,
137        ),
138        "strip_prefix": attr.string(),
139        "type": attr.string(),
140        "delete": attr.string_list(),
141        "patch_file": attr.label(),
142        "build_file": attr.label(),
143        "system_build_file": attr.label(),
144        "system_link_files": attr.string_dict(),
145        "additional_build_files": attr.string_dict(),
146    },
147    environ = [
148        "TF_SYSTEM_LIBS",
149    ],
150    implementation = _tf_http_archive,
151)
152
153"""Downloads and creates Bazel repos for dependencies.
154
155This is a swappable replacement for both http_archive() and
156new_http_archive() that offers some additional features. It also helps
157ensure best practices are followed.
158"""
159
160def _third_party_http_archive(ctx):
161    if ("mirror.tensorflow.org" not in ctx.attr.urls[0] and
162        (len(ctx.attr.urls) < 2 and
163         ctx.attr.name not in _SINGLE_URL_WHITELIST.to_list())):
164        fail("tf_http_archive(urls) must have redundant URLs. The " +
165             "mirror.tensorflow.org URL must be present and it must come first. " +
166             "Even if you don't have permission to mirror the file, please " +
167             "put the correctly formatted mirror URL there anyway, because " +
168             "someone will come along shortly thereafter and mirror the file.")
169
170    use_syslib = _use_system_lib(ctx, ctx.attr.name)
171
172    # Use "BUILD.bazel" to avoid conflict with third party projects that contain a
173    # file or directory called "BUILD"
174    buildfile_path = ctx.path("BUILD.bazel")
175
176    if use_syslib:
177        if ctx.attr.system_build_file == None:
178            fail("Bazel was configured with TF_SYSTEM_LIBS to use a system " +
179                 "library for %s, but no system build file for %s was configured. " +
180                 "Please add a system_build_file attribute to the repository rule" +
181                 "for %s." % (ctx.attr.name, ctx.attr.name, ctx.attr.name))
182        ctx.symlink(Label(ctx.attr.system_build_file), buildfile_path)
183
184    else:
185        ctx.download_and_extract(
186            ctx.attr.urls,
187            "",
188            ctx.attr.sha256,
189            ctx.attr.type,
190            ctx.attr.strip_prefix,
191        )
192        if ctx.attr.delete:
193            _apply_delete(ctx, ctx.attr.delete)
194        if ctx.attr.patch_file != None:
195            _apply_patch(ctx, ctx.attr.patch_file)
196        ctx.symlink(Label(ctx.attr.build_file), buildfile_path)
197
198    link_dict = {}
199    if use_syslib:
200        link_dict.update(ctx.attr.system_link_files)
201
202    for internal_src, external_dest in ctx.attr.link_files.items():
203        # if syslib and link exists in both, use the system one
204        if external_dest not in link_dict.values():
205            link_dict[internal_src] = external_dest
206
207    for internal_src, external_dest in link_dict.items():
208        ctx.symlink(Label(internal_src), ctx.path(external_dest))
209
210# Downloads and creates Bazel repos for dependencies.
211#
212# This is an upgrade for tf_http_archive that works with go/tfbr-thirdparty.
213#
214# For link_files, specify each dict entry as:
215# "//path/to/source:file": "localfile"
216third_party_http_archive = repository_rule(
217    attrs = {
218        "sha256": attr.string(mandatory = True),
219        "urls": attr.string_list(
220            mandatory = True,
221            allow_empty = False,
222        ),
223        "strip_prefix": attr.string(),
224        "type": attr.string(),
225        "delete": attr.string_list(),
226        "build_file": attr.string(mandatory = True),
227        "system_build_file": attr.string(mandatory = False),
228        "patch_file": attr.label(),
229        "link_files": attr.string_dict(),
230        "system_link_files": attr.string_dict(),
231    },
232    environ = [
233        "TF_SYSTEM_LIBS",
234    ],
235    implementation = _third_party_http_archive,
236)
237