diff options
Diffstat (limited to 'src')
5 files changed, 154 insertions, 65 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index fb1870b6e8..f17579cdfa 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -734,6 +734,7 @@ java_library( "bazel/rules/java/java_stub_template.txt", "bazel/rules/java/java_stub_template_windows.txt", "bazel/rules/python/python_stub_template.txt", + "bazel/rules/sh/sh_stub_template_windows.txt", ], deps = [ ":bazel", diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java index 106a066666..a96156aa42 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java @@ -1129,6 +1129,17 @@ public final class BuildConfiguration implements BuildEvent { ) public TriState buildPythonZip; + @Option( + name = "windows_exe_launcher", + defaultValue = "true", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Build a Windows exe launcher for sh_binary rule, " + + "it has no effect on other platforms than Windows" + ) + public boolean windowsExeLauncher; + @Override public FragmentOptions getHost(boolean fallback) { Options host = (Options) getDefault(); @@ -1139,6 +1150,7 @@ public final class BuildConfiguration implements BuildEvent { host.useDynamicConfigurations = useDynamicConfigurations; host.enableRunfiles = enableRunfiles; host.buildPythonZip = buildPythonZip; + host.windowsExeLauncher = windowsExeLauncher; host.commandLineBuildVariables = commandLineBuildVariables; host.enforceConstraints = enforceConstraints; host.separateGenfilesDirectory = separateGenfilesDirectory; @@ -2711,6 +2723,10 @@ public final class BuildConfiguration implements BuildEvent { } } + public boolean enableWindowsExeLauncher() { + return options.windowsExeLauncher; + } + /** * Collects executables defined by fragments. */ diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java index 0b3d37dfd3..f264df8d49 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java @@ -16,7 +16,6 @@ package com.google.devtools.build.lib.bazel.rules.sh; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteSource; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.ConfiguredTarget; @@ -29,6 +28,9 @@ import com.google.devtools.build.lib.analysis.RunfilesSupport; import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction; import com.google.devtools.build.lib.analysis.actions.ExecutableSymlinkAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; import com.google.devtools.build.lib.bazel.rules.BazelConfiguration; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; @@ -43,6 +45,8 @@ import java.nio.ByteOrder; * Implementation for the sh_binary rule. */ public class ShBinary implements RuleConfiguredTargetFactory { + private static final Template STUB_SCRIPT_WINDOWS = + Template.forResource(ShBinary.class, "sh_stub_template_windows.txt"); @Override public ConfiguredTarget create(RuleContext ruleContext) throws RuleErrorException { @@ -114,21 +118,8 @@ public class ShBinary implements RuleConfiguredTargetFactory { || artifact.getExtension().equals("bat"); } - private static Artifact launcherForWindows( - RuleContext ruleContext, Artifact primaryOutput, Artifact mainFile) + private static Artifact createWindowsExeLauncher(RuleContext ruleContext, Artifact mainFile) throws RuleErrorException { - if (isWindowsExecutable(mainFile)) { - // If the extensions don't match, we should always respect mainFile's extension. - if (mainFile.getExtension().equals(primaryOutput.getExtension())) { - return primaryOutput; - } else { - ruleContext.ruleError( - "Source file is a Windows executable file," - + " target name extension should match source file extension"); - throw new RuleErrorException(); - } - } - // The launcher file consists of a base launcher binary and the launch information appended to // the binary. The length of launch info is a signed 64-bit integer written at the end of // the binary in little endian. @@ -170,7 +161,7 @@ public class ShBinary implements RuleConfiguredTargetFactory { launchInfoFile, ByteSource.wrap(launchInfo.toByteArray()), /*makeExecutable=*/ false)); - String path = ruleContext.getConfiguration().getActionEnvironment().getFixedEnv().get("PATH"); + ruleContext.registerAction( new SpawnAction.Builder() .addInput(launcher) @@ -184,10 +175,47 @@ public class ShBinary implements RuleConfiguredTargetFactory { + " " + bashLauncher.getExecPathString().replace('/', '\\') + " > nul\"") - .setEnvironment(ImmutableMap.of("PATH", path)) + .useDefaultShellEnvironment() .setMnemonic("BuildBashLauncher") .build(ruleContext)); return bashLauncher; } + + private static Artifact launcherForWindows( + RuleContext ruleContext, Artifact primaryOutput, Artifact mainFile) + throws RuleErrorException { + if (isWindowsExecutable(mainFile)) { + if (mainFile.getExtension().equals(primaryOutput.getExtension())) { + return primaryOutput; + } else { + // If the extensions don't match, we should always respect mainFile's extension. + ruleContext.ruleError( + "Source file is a Windows executable file," + + " target name extension should match source file extension"); + throw new RuleErrorException(); + } + } + + if (ruleContext.getConfiguration().enableWindowsExeLauncher()) { + return createWindowsExeLauncher(ruleContext, mainFile); + } + + Artifact wrapper = + ruleContext.getImplicitOutputArtifact(ruleContext.getTarget().getName() + ".cmd"); + ruleContext.registerAction( + new TemplateExpansionAction( + ruleContext.getActionOwner(), + wrapper, + STUB_SCRIPT_WINDOWS, + ImmutableList.of( + Substitution.of( + "%bash_exe_path%", + ruleContext + .getFragment(BazelConfiguration.class) + .getShellExecutable() + .getPathString())), + true)); + return wrapper; + } } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_stub_template_windows.txt b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_stub_template_windows.txt new file mode 100644 index 0000000000..175985fac6 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_stub_template_windows.txt @@ -0,0 +1,35 @@ +@rem Copyright 2016 The Bazel Authors. All rights reserved. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem This script was generated from sh_stub_template_windows.txt. Please +@rem don't edit it directly. +@rem See the header comments in the accompanying shell script (same path as that +@rem of this file, minus the ".cmd" extension) for available command line flags. + +@echo off +SETLOCAL ENABLEEXTENSIONS + +set bash_path=%bash_exe_path% + +rem launcher=${$0%.cmd} +set launcher=%~dp0%~n0 + +set sh_path=%launcher:\=/% + +set RUNFILES_MANIFEST_ONLY=1 +set RUNFILES_MANIFEST_FILE=%sh_path%.runfiles/MANIFEST +set all_args=%* +rem replaces $ with \$ in $*, then puts it on the command line +rem Cribbed from here: http://ss64.com/nt/syntax-replace.html +call %bash_path% -c "'%sh_path%' %all_args:$=\$%" diff --git a/src/test/py/bazel/launcher_test.py b/src/test/py/bazel/launcher_test.py index 1baf5d29c6..3487545820 100644 --- a/src/test/py/bazel/launcher_test.py +++ b/src/test/py/bazel/launcher_test.py @@ -82,59 +82,20 @@ class LauncherTest(test_base.TestBase): self.assertEqual(stdout[2], 'runfiles_manifest_only=') self.assertRegexpMatches(stdout[3], r'^runfiles_manifest_file.*MANIFEST$') - def testShBinaryLauncher(self): - self.ScratchFile('WORKSPACE') - self.ScratchFile( - 'foo/BUILD', - [ - # On Linux/MacOS, all sh_binary rules generate an output file with - # the same name as the rule, and this is a symlink to the file in - # `srcs`. (Bazel allows only one file in `sh_binary.srcs`.) - # On Windows, if the srcs's extension is one of ".exe", ".cmd", or - # ".bat", then Bazel requires the rule's name has the same - # extension, and the output file will be a copy of the source file. - 'sh_binary(', - ' name = "bin1.sh",', - ' srcs = ["foo.sh"],', - ' data = ["//bar:bar.txt"],', - ')', - 'sh_binary(', - ' name = "bin2.cmd",', # name's extension matches that of srcs[0] - ' srcs = ["foo.cmd"],', - ' data = ["//bar:bar.txt"],', - ')', - 'sh_binary(', - ' name = "bin3.bat",', # name's extension doesn't match srcs[0]'s - ' srcs = ["foo.cmd"],', - ' data = ["//bar:bar.txt"],', - ')', - ]) - foo_sh = self.ScratchFile('foo/foo.sh', [ - '#!/bin/bash', - 'echo hello shell', - 'echo runfiles_manifest_only=${RUNFILES_MANIFEST_ONLY:-}', - 'echo runfiles_manifest_file=${RUNFILES_MANIFEST_FILE:-}', - ]) - foo_cmd = self.ScratchFile('foo/foo.cmd', ['@echo hello batch']) - self.ScratchFile('bar/BUILD', ['exports_files(["bar.txt"])']) - self.ScratchFile('bar/bar.txt', ['hello']) - os.chmod(foo_sh, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - os.chmod(foo_cmd, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - - exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin']) + def _buildShBinaryTargets(self, bazel_bin, launcher_flag, bin1_suffix): + exit_code, _, stderr = self.RunBazel(['build', '//foo:bin1.sh'] + + launcher_flag) self.AssertExitCode(exit_code, 0, stderr) - bazel_bin = stdout[0] - exit_code, _, stderr = self.RunBazel(['build', '//foo:bin1.sh']) - self.AssertExitCode(exit_code, 0, stderr) - - bin1 = os.path.join(bazel_bin, 'foo', 'bin1.sh.exe' + bin1 = os.path.join(bazel_bin, 'foo', 'bin1.sh.%s' % bin1_suffix if self.IsWindows() else 'bin1.sh') + self.assertTrue(os.path.exists(bin1)) self.assertTrue( os.path.isdir(os.path.join(bazel_bin, 'foo/bin1.sh.runfiles'))) - exit_code, _, stderr = self.RunBazel(['build', '//foo:bin2.cmd']) + exit_code, _, stderr = self.RunBazel(['build', '//foo:bin2.cmd'] + + launcher_flag) self.AssertExitCode(exit_code, 0, stderr) bin2 = os.path.join(bazel_bin, 'foo/bin2.cmd') @@ -142,7 +103,8 @@ class LauncherTest(test_base.TestBase): self.assertTrue( os.path.isdir(os.path.join(bazel_bin, 'foo/bin2.cmd.runfiles'))) - exit_code, _, stderr = self.RunBazel(['build', '//foo:bin3.bat']) + exit_code, _, stderr = self.RunBazel(['build', '//foo:bin3.bat'] + + launcher_flag) if self.IsWindows(): self.AssertExitCode(exit_code, 1, stderr) self.assertIn('target name extension should match source file extension.', @@ -202,6 +164,52 @@ class LauncherTest(test_base.TestBase): self.AssertExitCode(exit_code, 0, stderr) self.assertEqual(stdout[0], 'hello batch') + def testShBinaryLauncher(self): + self.ScratchFile('WORKSPACE') + self.ScratchFile( + 'foo/BUILD', + [ + # On Linux/MacOS, all sh_binary rules generate an output file with + # the same name as the rule, and this is a symlink to the file in + # `srcs`. (Bazel allows only one file in `sh_binary.srcs`.) + # On Windows, if the srcs's extension is one of ".exe", ".cmd", or + # ".bat", then Bazel requires the rule's name has the same + # extension, and the output file will be a copy of the source file. + 'sh_binary(', + ' name = "bin1.sh",', + ' srcs = ["foo.sh"],', + ' data = ["//bar:bar.txt"],', + ')', + 'sh_binary(', + ' name = "bin2.cmd",', # name's extension matches that of srcs[0] + ' srcs = ["foo.cmd"],', + ' data = ["//bar:bar.txt"],', + ')', + 'sh_binary(', + ' name = "bin3.bat",', # name's extension doesn't match srcs[0]'s + ' srcs = ["foo.cmd"],', + ' data = ["//bar:bar.txt"],', + ')', + ]) + foo_sh = self.ScratchFile('foo/foo.sh', [ + '#!/bin/bash', + 'echo hello shell', + 'echo runfiles_manifest_only=${RUNFILES_MANIFEST_ONLY:-}', + 'echo runfiles_manifest_file=${RUNFILES_MANIFEST_FILE:-}', + ]) + foo_cmd = self.ScratchFile('foo/foo.cmd', ['@echo hello batch']) + self.ScratchFile('bar/BUILD', ['exports_files(["bar.txt"])']) + self.ScratchFile('bar/bar.txt', ['hello']) + os.chmod(foo_sh, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + os.chmod(foo_cmd, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + + exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin']) + self.AssertExitCode(exit_code, 0, stderr) + bazel_bin = stdout[0] + + self._buildShBinaryTargets(bazel_bin, ['--windows_exe_launcher=0'], 'cmd') + self._buildShBinaryTargets(bazel_bin, [], 'exe') + def testShBinaryArgumentPassing(self): self.ScratchFile('WORKSPACE') self.ScratchFile('foo/BUILD', [ @@ -227,7 +235,8 @@ class LauncherTest(test_base.TestBase): self.AssertExitCode(exit_code, 0, stderr) bazel_bin = stdout[0] - exit_code, _, stderr = self.RunBazel(['build', '//foo:bin']) + exit_code, _, stderr = self.RunBazel( + ['build', '--windows_exe_launcher', '//foo:bin']) self.AssertExitCode(exit_code, 0, stderr) bin1 = os.path.join(bazel_bin, 'foo', 'bin.exe' |