Skip to content

Commit

Permalink
Add cmd_bash, cmd_ps, cmd_bat attributes to genrule
Browse files Browse the repository at this point in the history
Based on #9022

Users now can use Batch or Powershell command in genrule on Windows.
The attributes have the following priority:
`cmd_ps` > `cmd_bat` > `cmd_bash` > `cmd`, but `cmd_ps` and `cmd_bat` will only be taken into consideration on Windows.

Fixes #7503

Closes #9024.

PiperOrigin-RevId: 261093981
  • Loading branch information
meteorcloudy authored and copybara-github committed Aug 1, 2019
1 parent c35878a commit d3bacfb
Show file tree
Hide file tree
Showing 16 changed files with 714 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,14 @@ public static BashCommandConstructor buildBashCommandConstructor(
Map<String, String> executionInfo, PathFragment shExecutable, String scriptPostFix) {
return new BashCommandConstructor(shellPath(executionInfo, shExecutable), scriptPostFix);
}

public static WindowsBatchCommandConstructor buildWindowsBatchCommandConstructor(
String scriptPostFix) {
return new WindowsBatchCommandConstructor(scriptPostFix);
}

public static WindowsPowershellCommandConstructor buildWindowsPowershellCommandConstructor(
String scriptPostFix) {
return new WindowsPowershellCommandConstructor(scriptPostFix);
}
}
13 changes: 10 additions & 3 deletions src/main/java/com/google/devtools/build/lib/analysis/Expander.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public final class Expander {
*/
private Expander withLocations(boolean execPaths, boolean allowData) {
TemplateContext newTemplateContext =
new LocationTemplateContext(templateContext, ruleContext, labelMap, execPaths, allowData);
new LocationTemplateContext(
templateContext, ruleContext, labelMap, execPaths, allowData, false);
return new Expander(ruleContext, newTemplateContext, labelMap);
}

Expand All @@ -79,12 +80,18 @@ public Expander withDataExecLocations() {
* Returns a new instance that also expands locations, passing the given location map, as well as
* {@code execPaths} to the underlying {@link LocationTemplateContext}.
*/
public Expander withExecLocations(ImmutableMap<Label, ImmutableCollection<Artifact>> locations) {
public Expander withExecLocations(
ImmutableMap<Label, ImmutableCollection<Artifact>> locations, boolean windowsPath) {
TemplateContext newTemplateContext =
new LocationTemplateContext(templateContext, ruleContext, locations, true, false);
new LocationTemplateContext(
templateContext, ruleContext, locations, true, false, windowsPath);
return new Expander(ruleContext, newTemplateContext);
}

public Expander withExecLocations(ImmutableMap<Label, ImmutableCollection<Artifact>> locations) {
return withExecLocations(locations, false);
}

/**
* Expands the given value string, tokenizes it, and then adds it to the given list. The attribute
* name is only used for error reporting.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,41 +49,58 @@ final class LocationTemplateContext implements TemplateContext {
private final TemplateContext delegate;
private final ImmutableMap<String, LocationFunction> functions;
private final ImmutableMap<RepositoryName, RepositoryName> repositoryMapping;
private final boolean windowsPath;

private LocationTemplateContext(
TemplateContext delegate,
Label root,
Supplier<Map<Label, Collection<Artifact>>> locationMap,
boolean execPaths,
ImmutableMap<RepositoryName, RepositoryName> repositoryMapping) {
ImmutableMap<RepositoryName, RepositoryName> repositoryMapping,
boolean windowsPath) {
this.delegate = delegate;
this.functions = LocationExpander.allLocationFunctions(root, locationMap, execPaths);
this.repositoryMapping = repositoryMapping;
this.windowsPath = windowsPath;
}

public LocationTemplateContext(
TemplateContext delegate,
RuleContext ruleContext,
@Nullable ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap,
boolean execPaths,
boolean allowData) {
boolean allowData,
boolean windowsPath) {
this(
delegate,
ruleContext.getLabel(),
// Use a memoizing supplier to avoid eagerly building the location map.
Suppliers.memoize(
() -> LocationExpander.buildLocationMap(ruleContext, labelMap, allowData)),
execPaths,
ruleContext.getRule().getPackage().getRepositoryMapping());
ruleContext.getRule().getPackage().getRepositoryMapping(),
windowsPath);
}

@Override
public String lookupVariable(String name) throws ExpansionException {
return delegate.lookupVariable(name);
String val = delegate.lookupVariable(name);
if (windowsPath) {
val = val.replace('/', '\\');
}
return val;
}

@Override
public String lookupFunction(String name, String param) throws ExpansionException {
String val = lookupFunctionImpl(name, param);
if (windowsPath) {
val = val.replace('/', '\\');
}
return val;
}

private String lookupFunctionImpl(String name, String param) throws ExpansionException {
try {
LocationFunction f = functions.get(name);
if (f != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2019 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.

package com.google.devtools.build.lib.analysis;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;

/** The class for constructing command line for Batch on Windows. */
public final class WindowsBatchCommandConstructor implements CommandConstructor {
private final String scriptNameSuffix;

WindowsBatchCommandConstructor(String scriptNameSuffix) {
this.scriptNameSuffix = scriptNameSuffix;
}

@Override
public ImmutableList<String> asExecArgv(String command) {
// `cmd.exe` exists at C:\Windows\System32, which is in the default PATH on Windows.
return ImmutableList.of(
"cmd.exe", "/S", // strip first and last quotes and execute everything else as is.
"/E:ON", // enable extended command set.
"/V:ON", // enable delayed variable expansion
"/D", // ignore AutoRun registry entries.
"/c", // execute command. This must be the last option before the command itself.
command);
}

@Override
public ImmutableList<String> asExecArgv(Artifact scriptFileArtifact) {
return this.asExecArgv(scriptFileArtifact.getExecPathString().replace('/', '\\'));
}

@Override
public Artifact commandAsScript(RuleContext ruleContext, String command) {
String scriptFileName = ruleContext.getTarget().getName() + this.scriptNameSuffix;
String scriptFileContents = "@echo off\n" + command;
return FileWriteAction.createFile(
ruleContext, scriptFileName, scriptFileContents, /*executable=*/ true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2019 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.

package com.google.devtools.build.lib.analysis;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;

/** The class for constructing command line for Powershell on Windows. */
public final class WindowsPowershellCommandConstructor implements CommandConstructor {
private static final String POWERSHELL_SETUP_COMMANDS =
// 1. Use Set-ExecutionPolicy to allow users to run scripts unsigned.
"Set-ExecutionPolicy -Scope CurrentUser RemoteSigned; "
// 2. Set $errorActionPreference to Stop so that we exit immediately if a CmdLet
// fails,
// but when an external command fails, it will still continue.
+ "$errorActionPreference='Stop'; "
// 3. Change the default encoding to utf-8, by default it was utf-16.
// https://stackoverflow.com/questions/40098771
+ "$PSDefaultParameterValues['*:Encoding'] = 'utf8'; ";
private final String scriptNameSuffix;

WindowsPowershellCommandConstructor(String scriptNameSuffix) {
this.scriptNameSuffix = scriptNameSuffix;
}

@Override
public ImmutableList<String> asExecArgv(String command) {
// `powershell.exe` exists at C:\Windows\System32\WindowsPowerShell\v1.0,
// which is in the default PATH on Windows.
// We currently don't support Powershell on Linux/macOS, although Powershell is available on
// those platforms.
return ImmutableList.of("powershell.exe", "/c", POWERSHELL_SETUP_COMMANDS + command);
}

@Override
public ImmutableList<String> asExecArgv(Artifact scriptFileArtifact) {
// Powershell doesn't search for the current directory by default, we need to always add
// ".\" prefix to a relative path.
return this.asExecArgv(".\\" + scriptFileArtifact.getExecPathString().replace('/', '\\'));
}

@Override
public Artifact commandAsScript(RuleContext ruleContext, String command) {
String scriptFileName = ruleContext.getTarget().getName() + scriptNameSuffix;
return FileWriteAction.createFile(ruleContext, scriptFileName, command, /*executable=*/ true);
}
}
Loading

0 comments on commit d3bacfb

Please sign in to comment.