blob: 64899bfff5052f7a1edd47489420c4cac3eeefc7 [file] [log] [blame]
# Copyright 2014 Google Inc. 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.
pex_file_types = FileType([".py"])
egg_file_types = FileType([".egg", ".whl"])
pex_test_file_types = FileType(["_unittest.py", "_test.py"])
def collect_transitive_sources(ctx):
source_files = set(order="compile")
for dep in ctx.attr.deps:
source_files += dep.transitive_py_files
source_files += pex_file_types.filter(ctx.files.srcs)
return source_files
def collect_transitive_eggs(ctx):
transitive_eggs = set(order="compile")
for dep in ctx.attr.deps:
transitive_eggs += dep.transitive_egg_files
transitive_eggs += egg_file_types.filter(ctx.files.eggs)
return transitive_eggs
def collect_transitive_reqs(ctx):
transitive_reqs = set(order="compile")
for dep in ctx.attr.deps:
transitive_reqs += dep.transitive_reqs
transitive_reqs += ctx.attr.reqs
return transitive_reqs
def pex_library_impl(ctx):
transitive_sources = collect_transitive_sources(ctx)
transitive_eggs = collect_transitive_eggs(ctx)
transitive_reqs = collect_transitive_reqs(ctx)
return struct(
files = set(),
transitive_py_files = transitive_sources,
transitive_reqs = transitive_reqs,
transitive_egg_files = transitive_eggs)
def get_package_from_path(pkg_path):
return pkg_path.replace('/', '.')[:-3]
# Converts map to text format. Each file on separate line.
def textify_pex_input(input_map):
kv_pairs = ['\t%s:%s' % (pkg, input_map[pkg]) for pkg in input_map.keys()]
return '\n'.join(kv_pairs)
def write_pex_manifest_text(modules, prebuilt_libs, resources, requirements):
return ('modules:\n%s\nrequirements:\n%s\nresources:\n%s\nnativeLibraries:\nprebuiltLibraries:\n%s' % (
textify_pex_input(modules),
textify_pex_input(dict(zip(requirements,requirements))),
textify_pex_input(resources),
textify_pex_input(prebuilt_libs)))
def get_module_path(ctx, pkg_path):
genfiles_dir = ctx.configuration.genfiles_dir.path
if pkg_path.startswith(genfiles_dir):
module_path = pkg_path[len(genfiles_dir):]
if module_path.startswith('/'):
return module_path[1:]
return module_path
return pkg_path
def make_manifest(ctx, output):
transitive_sources = collect_transitive_sources(ctx)
transitive_reqs = collect_transitive_reqs(ctx)
transitive_eggs = collect_transitive_eggs(ctx)
transitive_resources = ctx.files.resources
pex_modules = {}
pex_prebuilt_libs = {}
pex_resources = {}
pex_requirements = []
for f in transitive_sources:
pex_modules[get_module_path(ctx, f.path)] = f.path
for f in transitive_eggs:
pex_prebuilt_libs[get_module_path(ctx, f.path)] = f.path
for f in transitive_resources:
pex_resources[get_module_path(ctx, f.path)] = f.path
manifest_text = write_pex_manifest_text(pex_modules, pex_prebuilt_libs, pex_resources, transitive_reqs)
ctx.action(
inputs = list(transitive_sources) + list(transitive_eggs) + list(transitive_resources),
outputs = [ output ],
command = ('touch %s && echo "%s" > %s' % (output.path, manifest_text, output.path)))
def common_pex_arguments(entry_point, deploy_pex_path, manifest_file_path):
arguments = ['--disable-cache']
arguments += ['--entry-point', entry_point]
# Our internal build environment requires extra args injected and this is a brutal hack. Ideally
# bazel would provide a mechanism to swap in env-specific global params here
#EXTRA_PEX_ARGS#
arguments += [deploy_pex_path]
arguments += [manifest_file_path]
return arguments
def pex_binary_impl(ctx):
if not ctx.file.main:
main_file = pex_file_types.filter(ctx.files.srcs)[0]
else:
main_file = ctx.file.main
# Package name is same as folder name followed by filename (without .py extension)
main_pkg = get_package_from_path(main_file.path)
deploy_pex = ctx.new_file(
ctx.configuration.bin_dir, ctx.outputs.executable, '.pex')
manifest_file = ctx.new_file(
ctx.configuration.bin_dir, deploy_pex, '.manifest')
make_manifest(ctx, manifest_file)
transitive_sources = collect_transitive_sources(ctx)
transitive_eggs = collect_transitive_eggs(ctx)
transitive_resources = ctx.files.resources
pexbuilder = ctx.executable._pexbuilder
# form the arguments to pex builder
arguments = [] if ctx.attr.zip_safe else ["--not-zip-safe"]
arguments += common_pex_arguments(main_pkg, deploy_pex.path, manifest_file.path)
# form the inputs to pex builder
inputs = [main_file, manifest_file]
inputs += list(transitive_sources)
inputs += list(transitive_eggs)
inputs += list(transitive_resources)
ctx.action(
mnemonic = "PexPython",
inputs = inputs,
outputs = [deploy_pex],
executable = pexbuilder,
arguments = arguments)
executable = ctx.outputs.executable
ctx.action(
inputs = [deploy_pex],
outputs = [executable],
command = "cp %s %s" % (deploy_pex.path, executable.path))
return struct(files = set([executable]))
def pex_test_impl(ctx):
deploy_pex = ctx.new_file(
ctx.configuration.bin_dir, ctx.outputs.executable, '.pex')
manifest_file = ctx.new_file(
ctx.configuration.bin_dir, deploy_pex, '.manifest')
make_manifest(ctx, manifest_file)
# Get pex test files
transitive_sources = collect_transitive_sources(ctx)
transitive_eggs = collect_transitive_eggs(ctx)
transitive_resources = ctx.files.resources
pexbuilder = ctx.executable._pexbuilder
pex_test_files = pex_test_file_types.filter(pex_file_types.filter(transitive_sources))
test_run_args = ' '.join([f.path for f in pex_test_files])
arguments = common_pex_arguments('pytest', deploy_pex.path, manifest_file.path)
ctx.action(
inputs = [manifest_file] + list(transitive_sources) + list(transitive_eggs) + list(transitive_resources),
executable = pexbuilder,
outputs = [ deploy_pex ],
mnemonic = "PexPython",
arguments = arguments)
executable = ctx.outputs.executable
ctx.action(
inputs = [ deploy_pex ],
outputs = [ executable ],
command = "echo PYTHONDONTWRITEBYTECODE=1 \"`pwd`/%s `pwd`/%s\" > %s" % (deploy_pex.path, test_run_args, executable.path))
return struct(files = set([executable]))
pex_srcs_attr = attr.label_list(
flags=["DIRECT_COMPILE_TIME_INPUT"],
allow_files = pex_file_types
)
pex_deps_attr = attr.label_list(
providers = ["transitive_py_files", "transitive_egg_files", "transitive_reqs"],
allow_files = False)
eggs_attr = attr.label_list(
flags=["DIRECT_COMPILE_TIME_INPUT"],
allow_files = egg_file_types)
resource_attr = attr.label_list(
allow_files = True)
pex_attrs = {
"srcs": pex_srcs_attr,
"deps": pex_deps_attr,
"eggs": eggs_attr,
"reqs": attr.string_list(),
"resources": resource_attr,
"main": attr.label(allow_files=True, single_file=True)
}
pex_bin_attrs = pex_attrs + {
"zip_safe": attr.bool(
default = True,
mandatory = False
)
}
pex_library = rule(
pex_library_impl,
attrs = pex_attrs
)
pex_binary_outputs = {
"deploy_pex": "%{name}.pex"
}
pex_binary = rule(
pex_binary_impl,
executable = True,
attrs = pex_bin_attrs + {
"_pexbuilder": attr.label(
default = Label("//third_party/pex:_pex"),
allow_files = False,
executable = True,
),
},
)
pex_test = rule(
pex_test_impl,
executable = True,
attrs = pex_attrs + {
"_pexbuilder": attr.label(
default = Label("//third_party/pex:_pex"),
allow_files = False,
executable = True,
),
},
test = True,
)