blob: 89b40ae642a80c626c944131732bd59e249d4de8 [file] [log] [blame]
// Copyright 2015 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.bazel.rules.android;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.AndroidNdkCrosstools;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.AndroidNdkCrosstools.NdkCrosstoolsException;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.ApiLevel;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.NdkMajorRevision;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.NdkPaths;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.NdkRelease;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.StlImpl;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.StlImpls;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.StlImpls.GnuLibStdCppStlImpl;
import com.google.devtools.build.lib.bazel.rules.android.ndkcrosstools.StlImpls.LibCppStlImpl;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.lib.rules.repository.WorkspaceAttributeMapper;
import com.google.devtools.build.lib.skyframe.DirectoryListingValue;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.ResourceFileLoader;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CompilationMode;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CompilationModeFlags;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CrosstoolRelease;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.ToolPath;
import com.google.devtools.build.skyframe.SkyFunction.Environment;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Implementation of the {@code android_ndk_repository} rule. */
public class AndroidNdkRepositoryFunction extends AndroidRepositoryFunction {
private static final String TOOLCHAIN_NAME_PREFIX = "toolchain-";
private static final String PATH_ENV_VAR = "ANDROID_NDK_HOME";
private static final PathFragment PLATFORMS_DIR = PathFragment.create("platforms");
private static final ImmutableList<String> PATH_ENV_VAR_AS_LIST = ImmutableList.of(PATH_ENV_VAR);
private static String getDefaultCrosstool(Integer majorRevision) {
// From NDK 17, libc++ replaces gnu-libstdc++ as the default STL.
return majorRevision <= 16 ? GnuLibStdCppStlImpl.NAME : LibCppStlImpl.NAME;
}
private static PathFragment getAndroidNdkHomeEnvironmentVar(
Path workspace, Map<String, String> env) {
return workspace.getRelative(PathFragment.create(env.get(PATH_ENV_VAR))).asFragment();
}
private static String createBuildFile(
String ruleName, String defaultCrosstool, List<CrosstoolStlPair> crosstools) {
String buildFileTemplate = getTemplate("android_ndk_build_file_template.txt");
String ccToolchainSuiteTemplate = getTemplate("android_ndk_cc_toolchain_suite_template.txt");
String ccToolchainTemplate = getTemplate("android_ndk_cc_toolchain_template.txt");
String stlFilegroupTemplate = getTemplate("android_ndk_stl_filegroup_template.txt");
String miscLibrariesTemplate = getTemplate("android_ndk_misc_libraries_template.txt");
StringBuilder ccToolchainSuites = new StringBuilder();
StringBuilder ccToolchainRules = new StringBuilder();
StringBuilder stlFilegroups = new StringBuilder();
for (CrosstoolStlPair crosstoolStlPair : crosstools) {
// Create the cc_toolchain_suite rule
CrosstoolRelease crosstool = crosstoolStlPair.crosstoolRelease;
StringBuilder toolchainMap = new StringBuilder();
for (CToolchain toolchain : crosstool.getToolchainList()) {
toolchainMap.append(
String.format(
" '%s': ':%s',\n '%s|%s': ':%s',\n",
toolchain.getTargetCpu(),
toolchain.getToolchainIdentifier(),
toolchain.getTargetCpu(),
toolchain.getCompiler(),
toolchain.getToolchainIdentifier()));
}
String toolchainName = createToolchainName(crosstoolStlPair.stlImpl.getName());
ccToolchainSuites.append(
ccToolchainSuiteTemplate
.replace("%toolchainName%", toolchainName)
.replace("%toolchainMap%", toolchainMap.toString().trim())
.replace("%crosstoolReleaseProto%", crosstool.toString()));
// Create the cc_toolchain rules
for (CToolchain toolchain : crosstool.getToolchainList()) {
ccToolchainRules.append(
createCcToolchainRule(
ccToolchainTemplate, crosstoolStlPair.stlImpl.getName(), toolchain));
}
// Create the STL file group rules
for (Map.Entry<String, String> entry :
crosstoolStlPair.stlImpl.getFilegroupNamesAndFilegroupFileGlobPatterns().entrySet()) {
stlFilegroups.append(
stlFilegroupTemplate
.replace("%name%", entry.getKey())
.replace("%fileGlobPattern%", entry.getValue()));
}
}
return buildFileTemplate
.replace("%ruleName%", ruleName)
.replace("%defaultCrosstool%", "//:toolchain-" + defaultCrosstool)
.replace("%ccToolchainSuites%", ccToolchainSuites)
.replace("%ccToolchainRules%", ccToolchainRules)
.replace("%stlFilegroups%", stlFilegroups)
.replace("%miscLibraries%", miscLibrariesTemplate);
}
static String createToolchainName(String stlName) {
return TOOLCHAIN_NAME_PREFIX + stlName;
}
private static String createCcToolchainRule(
String ccToolchainTemplate, String version, CToolchain toolchain) {
// TODO(bazel-team): It's unfortunate to have to extract data from a CToolchain proto like this.
// It would be better to have a higher-level construction (like an AndroidToolchain class)
// from which the CToolchain proto and rule information needed here can be created.
// Alternatively it would be nicer to just be able to glob the entire NDK and add that one glob
// to each cc_toolchain rule, and then the complexities in the method and the templates can
// go away, but globbing the entire NDK takes ~60 seconds, mostly because of MD5ing all the
// binary files in the NDK (eg the .so / .a / .o files).
// This also includes the files captured with cxx_builtin_include_directory.
// Use gcc specifically because clang toolchains will have both gcc and llvm toolchain paths,
// but the gcc tool will actually be clang.
ToolPath gcc = null;
for (ToolPath toolPath : toolchain.getToolPathList()) {
if ("gcc".equals(toolPath.getName())) {
gcc = toolPath;
}
}
checkNotNull(gcc, "gcc not found in crosstool toolpaths");
String toolchainDirectory = NdkPaths.getToolchainDirectoryFromToolPath(gcc.getPath());
// Create file glob patterns for the various files that the toolchain references.
String androidPlatformIncludes =
NdkPaths.stripRepositoryPrefix(toolchain.getBuiltinSysroot()) + "/**/*";
List<String> toolchainFileGlobPatterns = new ArrayList<>();
toolchainFileGlobPatterns.add(androidPlatformIncludes);
for (String cxxFlag : toolchain.getUnfilteredCxxFlagList()) {
if (!cxxFlag.startsWith("-")) { // Skip flag names
toolchainFileGlobPatterns.add(NdkPaths.stripRepositoryPrefix(cxxFlag) + "/**/*");
}
}
// For NDK 15 and up. Unfortunately, the toolchain does not encode the NDK revision number.
toolchainFileGlobPatterns.add("ndk/sysroot/**/*");
// If this is a clang toolchain, also add the corresponding gcc toolchain to the globs.
int gccToolchainIndex = toolchain.getCompilerFlagList().indexOf("-gcc-toolchain");
if (gccToolchainIndex > -1) {
String gccToolchain = toolchain.getCompilerFlagList().get(gccToolchainIndex + 1);
toolchainFileGlobPatterns.add(NdkPaths.stripRepositoryPrefix(gccToolchain) + "/**/*");
}
StringBuilder toolchainFileGlobs = new StringBuilder();
for (String toolchainFileGlobPattern : toolchainFileGlobPatterns) {
toolchainFileGlobs.append(String.format(" \"%s\",\n", toolchainFileGlobPattern));
}
return ccToolchainTemplate
.replace("%toolchainName%", toolchain.getToolchainIdentifier())
.replace("%cpu%", toolchain.getTargetCpu())
.replace("%platform_cpu%", getPlatformCpuLabel(toolchain.getTargetCpu()))
.replace("%compiler%", toolchain.getCompiler())
.replace("%version%", version)
.replace("%dynamicRuntimeLibs%", toolchain.getDynamicRuntimesFilegroup())
.replace("%staticRuntimeLibs%", toolchain.getStaticRuntimesFilegroup())
.replace("%toolchainDirectory%", toolchainDirectory)
.replace("%toolchainFileGlobs%", toolchainFileGlobs.toString().trim());
}
private static String getPlatformCpuLabel(String targetCpu) {
// Create a mapping of CcToolchain CPU values to platform arch constraint values
// in @bazel_tools//platforms
switch (targetCpu) {
case "x86":
return "x86_32";
case "armeabi-v7a":
return "arm";
case "arm64-v8a":
return "aarch64";
default:
return "x86_64";
}
}
private static String getTemplate(String templateFile) {
try {
return ResourceFileLoader.loadResource(AndroidNdkRepositoryFunction.class, templateFile);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public boolean isLocal(Rule rule) {
return true;
}
@Override
public boolean verifyMarkerData(Rule rule, Map<String, String> markerData, Environment env)
throws InterruptedException {
WorkspaceAttributeMapper attributes = WorkspaceAttributeMapper.of(rule);
if (attributes.isAttributeValueExplicitlySpecified("path")) {
return true;
}
return super.verifyEnvironMarkerData(markerData, env, PATH_ENV_VAR_AS_LIST);
}
@Override
public RepositoryDirectoryValue.Builder fetch(
Rule rule,
Path outputDirectory,
BlazeDirectories directories,
Environment env,
Map<String, String> markerData,
SkyKey key)
throws InterruptedException, RepositoryFunctionException {
Map<String, String> environ =
declareEnvironmentDependencies(markerData, env, PATH_ENV_VAR_AS_LIST);
if (environ == null) {
return null;
}
prepareLocalRepositorySymlinkTree(rule, outputDirectory);
WorkspaceAttributeMapper attributes = WorkspaceAttributeMapper.of(rule);
PathFragment pathFragment;
if (attributes.isAttributeValueExplicitlySpecified("path")) {
pathFragment = getTargetPath(rule, directories.getWorkspace());
} else if (environ.get(PATH_ENV_VAR) != null) {
pathFragment = getAndroidNdkHomeEnvironmentVar(directories.getWorkspace(), environ);
} else {
throw new RepositoryFunctionException(
new EvalException(
rule.getLocation(),
"Either the path attribute of android_ndk_repository or the ANDROID_NDK_HOME "
+ " environment variable must be set."),
Transience.PERSISTENT);
}
Path ndkSymlinkTreeDirectory = outputDirectory.getRelative("ndk");
try {
ndkSymlinkTreeDirectory.createDirectory();
} catch (IOException e) {
throw new RepositoryFunctionException(e, Transience.TRANSIENT);
}
Path ndkHome = directories.getOutputBase().getFileSystem().getPath(pathFragment);
if (!symlinkLocalRepositoryContents(ndkSymlinkTreeDirectory, ndkHome)) {
return null;
}
String ruleName = rule.getName();
// We need to fetch the NDK release info from the actual home to avoid cycle in the
// dependency graph (the path relative to the repository root depends on the
// repository being fetched).
NdkRelease ndkRelease = getNdkRelease(ndkHome, env);
if (env.valuesMissing()) {
return null;
}
String apiLevelString;
if (attributes.isAttributeValueExplicitlySpecified("api_level")) {
try {
apiLevelString = attributes.get("api_level", Type.INTEGER).toString();
} catch (EvalException e) {
throw new RepositoryFunctionException(e, Transience.PERSISTENT);
}
} else {
DirectoryListingValue platformsDirectoryValue =
getDirectoryListing(ndkHome, PLATFORMS_DIR, env);
if (platformsDirectoryValue == null) {
return null;
}
ImmutableSortedSet<Integer> apiLevels = getApiLevels(platformsDirectoryValue.getDirents());
if (apiLevels.isEmpty()) {
// Every Android NDK to date ships with multiple api levels, so the only reason that this
// would be empty is if the user is not pointing to a standard NDK or has tinkered with it
// themselves.
throwInvalidPathException(
ndkHome,
new EvalException(
rule.getLocation(),
"android_ndk_repository requires that at least one Android platform is present in "
+ "the Android NDK platforms directory."));
}
apiLevelString = apiLevels.first().toString();
}
// NDK minor revisions should be backwards compatible within a major revision, the crosstools
// we generate don't care about the minor revision.
NdkMajorRevision ndkMajorRevision;
if (!ndkRelease.isValid) {
String warningMessage =
String.format(
"The revision of the Android NDK referenced by android_ndk_repository rule '%s' "
+ "could not be determined (the revision string found is '%s'). "
+ "Bazel will attempt to treat the NDK as if it was r%s. This may cause "
+ "compilation and linkage problems. Please download a supported NDK version.\n",
ruleName,
ndkRelease.rawRelease,
AndroidNdkCrosstools.LATEST_KNOWN_REVISION.getKey());
env.getListener().handle(Event.warn(warningMessage));
ndkMajorRevision = AndroidNdkCrosstools.LATEST_KNOWN_REVISION.getValue();
} else if (!AndroidNdkCrosstools.isKnownNDKRevision(ndkRelease)) {
String warningMessage =
String.format(
"The major revision of the Android NDK referenced by android_ndk_repository rule "
+ "'%s' is %s. The major revisions supported by Bazel are %s. Bazel will attempt "
+ "to treat the NDK as if it was r%s. This may cause compilation and linkage "
+ "problems. Please download a supported NDK version.\n",
ruleName,
ndkRelease.majorRevision,
AndroidNdkCrosstools.KNOWN_NDK_MAJOR_REVISIONS.keySet(),
AndroidNdkCrosstools.LATEST_KNOWN_REVISION.getKey());
env.getListener().handle(Event.warn(warningMessage));
ndkMajorRevision = AndroidNdkCrosstools.LATEST_KNOWN_REVISION.getValue();
} else {
ndkMajorRevision =
AndroidNdkCrosstools.KNOWN_NDK_MAJOR_REVISIONS.get(ndkRelease.majorRevision);
}
ApiLevel apiLevel = ndkMajorRevision.apiLevel(env.getListener(), ruleName, apiLevelString);
ImmutableList.Builder<CrosstoolStlPair> crosstoolsAndStls = ImmutableList.builder();
try {
String hostPlatform = AndroidNdkCrosstools.getHostPlatform(ndkRelease);
NdkPaths ndkPaths = new NdkPaths(ruleName, hostPlatform, apiLevel, ndkRelease.majorRevision);
for (StlImpl stlImpl : StlImpls.get(ndkPaths, ndkRelease.majorRevision)) {
CrosstoolRelease crosstoolRelease =
ndkMajorRevision.crosstoolRelease(ndkPaths, stlImpl, hostPlatform);
crosstoolsAndStls.add(new CrosstoolStlPair(crosstoolRelease, stlImpl));
}
} catch (NdkCrosstoolsException e) {
throw new RepositoryFunctionException(new IOException(e), Transience.PERSISTENT);
}
String defaultCrosstool = getDefaultCrosstool(ndkRelease.majorRevision);
ImmutableList<CrosstoolStlPair> crosstoolStlPairs = crosstoolsAndStls.build();
String buildFile = createBuildFile(ruleName, defaultCrosstool, crosstoolStlPairs);
writeBuildFile(outputDirectory, buildFile);
ImmutableList.Builder<String> bigConditional = ImmutableList.builder();
for (CrosstoolStlPair pair : crosstoolStlPairs) {
for (CToolchain toolchain : pair.crosstoolRelease.getToolchainList()) {
bigConditional.addAll(generateBzlConfigFor(pair.stlImpl.getName(), toolchain));
}
}
writeFile(
outputDirectory,
"cc_toolchain_config.bzl",
getTemplate("android_ndk_cc_toolchain_config_template.txt")
.replaceAll(
"%big_conditional_populating_variables%",
Joiner.on("\n" + " ").join(bigConditional.build())));
return RepositoryDirectoryValue.builder().setPath(outputDirectory);
}
private ImmutableList<String> generateBzlConfigFor(String version, CToolchain toolchain) {
ImmutableList.Builder<String> bigConditional = ImmutableList.builder();
String cpu = toolchain.getTargetCpu();
String compiler = toolchain.getCompiler();
Preconditions.checkArgument(
toolchain.getLinkingModeFlagsCount() == 0, "linking_mode_flags not supported.");
Preconditions.checkArgument(
toolchain.getActionConfigCount() == 0, "action_configs not supported.");
Preconditions.checkArgument(toolchain.getFeatureCount() == 0, "features not supported.");
Preconditions.checkArgument(toolchain.getArFlagCount() == 0, "ar_flags not supported.");
Preconditions.checkArgument(
toolchain.getArtifactNamePatternCount() == 0, "artifact_name_patterns not supported.");
Preconditions.checkArgument(toolchain.getCxxFlagCount() == 0, "cxx_flags not supported.");
Preconditions.checkArgument(
toolchain.getDynamicLibraryLinkerFlagCount() == 0,
"dynamic_library_linker_flags not supported.");
Preconditions.checkArgument(
toolchain.getLdEmbedFlagCount() == 0, "ld_embed_flags not supported.");
Preconditions.checkArgument(
toolchain.getObjcopyEmbedFlagCount() == 0, "objcopy_embed_flags not supported.");
Preconditions.checkArgument(
toolchain.getMakeVariableCount() == 0, "make_variables not supported.");
Preconditions.checkArgument(
toolchain.getTestOnlyLinkerFlagCount() == 0, "test_only_linker_flags not supported.");
CompilationModeFlags fastbuild = null;
CompilationModeFlags dbg = null;
CompilationModeFlags opt = null;
for (CompilationModeFlags flags : toolchain.getCompilationModeFlagsList()) {
Preconditions.checkArgument(
flags.getCxxFlagCount() == 0, "compilation_mode_flags.cxx_flags not supported.");
Preconditions.checkArgument(
flags.getLinkerFlagCount() == 0, "compilation_mode_flags.linker_flags not supported.");
if (flags.getMode().equals(CompilationMode.FASTBUILD)) {
fastbuild = flags;
} else if (flags.getMode().equals(CompilationMode.DBG)) {
dbg = flags;
} else if (flags.getMode().equals(CompilationMode.OPT)) {
opt = flags;
}
}
bigConditional.add(
String.format(
"if cpu == '%s' and compiler == '%s' and version == '%s':", cpu, compiler, version),
String.format(
" default_compile_flags = [%s]",
toSequenceOfStarlarkStrings(toolchain.getCompilerFlagList())),
String.format(
" unfiltered_compile_flags = [%s]",
toSequenceOfStarlarkStrings(toolchain.getUnfilteredCxxFlagList())),
String.format(
" default_link_flags = [%s]",
toSequenceOfStarlarkStrings(toolchain.getLinkerFlagList())),
String.format(
" default_fastbuild_flags = [%s]",
toSequenceOfStarlarkStrings(
fastbuild != null ? fastbuild.getCompilerFlagList() : ImmutableList.of())),
String.format(
" default_dbg_flags = [%s]",
toSequenceOfStarlarkStrings(
dbg != null ? dbg.getCompilerFlagList() : ImmutableList.of())),
String.format(
" default_opt_flags = [%s]",
toSequenceOfStarlarkStrings(
opt != null ? opt.getCompilerFlagList() : ImmutableList.of())),
String.format(
" cxx_builtin_include_directories = [%s]",
toSequenceOfStarlarkStrings(toolchain.getCxxBuiltinIncludeDirectoryList())),
String.format(" target_cpu = '%s'", toolchain.getTargetCpu()),
String.format(" toolchain_identifier = '%s'", toolchain.getToolchainIdentifier()),
String.format(" host_system_name = '%s'", toolchain.getHostSystemName()),
String.format(" target_system_name = '%s'", toolchain.getTargetSystemName()),
String.format(" target_libc = '%s'", toolchain.getTargetLibc()),
String.format(" target_compiler = '%s'", toolchain.getCompiler()),
String.format(" abi_version = '%s'", toolchain.getAbiVersion()),
String.format(" abi_libc_version = '%s'", toolchain.getAbiLibcVersion()),
String.format(" builtin_sysroot = '%s'", toolchain.getBuiltinSysroot()));
bigConditional.addAll(
toolchain.getToolPathList().stream()
.map(
tp ->
String.format(
" %s_path = '%s'",
tp.getName().toLowerCase().replaceAll("-", "_"), tp.getPath()))
.collect(ImmutableList.toImmutableList()));
return bigConditional.add("").build();
}
private String toSequenceOfStarlarkStrings(Iterable<String> flags) {
return "'" + Joiner.on("', '").join(flags) + "'";
}
@Override
public Class<? extends RuleDefinition> getRuleDefinition() {
return AndroidNdkRepositoryRule.class;
}
private NdkRelease getNdkRelease(Path directory, Environment env)
throws RepositoryFunctionException, InterruptedException {
// For NDK r11+
Path releaseFilePath = directory.getRelative("source.properties");
if (!releaseFilePath.exists()) {
// For NDK r10e
releaseFilePath = directory.getRelative("RELEASE.TXT");
}
SkyKey releaseFileKey =
FileValue.key(RootedPath.toRootedPath(Root.fromPath(directory), releaseFilePath));
String releaseFileContent = "";
try {
env.getValueOrThrow(releaseFileKey, IOException.class);
releaseFileContent =
new String(FileSystemUtils.readContent(releaseFilePath), StandardCharsets.UTF_8);
} catch (IOException e) {
throwInvalidPathException(
directory,
new IOException(
"Could not read "
+ releaseFilePath.getBaseName()
+ " in Android NDK: "
+ e.getMessage()));
}
return NdkRelease.create(releaseFileContent.trim());
}
@Override
protected void throwInvalidPathException(Path path, Exception e)
throws RepositoryFunctionException {
throw new RepositoryFunctionException(
new IOException(
String.format(
"%s Unable to read the Android NDK at %s, the path may be invalid. Is "
+ "the path in android_ndk_repository() or %s set correctly? If the path is "
+ "correct, the contents in the Android NDK directory may have been modified.",
e.getMessage(), path, PATH_ENV_VAR),
e),
Transience.PERSISTENT);
}
private static final class CrosstoolStlPair {
private final CrosstoolRelease crosstoolRelease;
private final StlImpl stlImpl;
private CrosstoolStlPair(CrosstoolRelease crosstoolRelease, StlImpl stlImpl) {
this.crosstoolRelease = crosstoolRelease;
this.stlImpl = stlImpl;
}
}
}