blob: cb80376cc009988de850154faf79a3960b409110 [file]
// Copyright 2024 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.packages;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests the execution of symbolic macro implementations. */
@RunWith(JUnit4.class)
public final class SymbolicMacroTest extends BuildViewTestCase {
@Before
public void setUp() throws Exception {
setBuildLanguageOptions("--experimental_enable_first_class_macros");
}
/**
* Returns a package by the given name (no leading "//"), or null upon {@link
* NoSuchPackageException}.
*/
@CanIgnoreReturnValue
@Nullable
private Package getPackage(String pkgName) throws InterruptedException {
try {
return getPackageManager().getPackage(reporter, PackageIdentifier.createInMainRepo(pkgName));
} catch (NoSuchPackageException unused) {
return null;
}
}
private void assertPackageNotInError(@Nullable Package pkg) {
assertThat(pkg).isNotNull();
assertThat(pkg.containsErrors()).isFalse();
}
@Test
public void implementationIsInvokedWithNameParam() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _impl(name):",
" print('my_macro called with name = %s' % name)",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"my_macro(name='abc')");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertContainsEvent("called with name = abc");
}
@Test
public void implementationFailsDueToBadSignature() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _impl():",
" pass",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"my_macro(name='abc')");
reporter.removeHandler(failFastHandler);
Package pkg = getPackage("pkg");
assertThat(pkg).isNotNull();
assertThat(pkg.containsErrors()).isTrue();
assertContainsEvent("_impl() got unexpected keyword argument: name");
}
@Test
public void macroCanDeclareTargets() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _impl(name):",
" native.cc_library(name = name + '$lib')",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"my_macro(name='abc')");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertThat(pkg.getTargets()).containsKey("abc$lib");
}
@Test
public void macroCanDeclareSubmacros() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _inner_impl(name):",
" native.cc_library(name = name + '$lib')",
"inner_macro = macro(implementation=_inner_impl)",
"def _impl(name):",
" inner_macro(name = name + '$inner')",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"my_macro(name='abc')");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertThat(pkg.getTargets()).containsKey("abc$inner$lib");
}
// TODO: #19922 - Invert this, symbolic macros shouldn't be able to call glob().
@Test
public void macroCanCallGlob() throws Exception {
scratch.file("pkg/foo.txt");
scratch.file(
"pkg/foo.bzl", //
"def _impl(name):",
" print('Glob result: %s' % native.glob(['foo*']))",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"my_macro(name='abc')");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertContainsEvent("Glob result: [\"foo.bzl\", \"foo.txt\"]");
}
// TODO: #19922 - Invert this, symbolic macros shouldn't be able to call existing_rules().
@Test
public void macroCanCallExistingRules() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _impl(name):",
" native.cc_binary(name = name + '$lib')",
" print('existing_rules() keys: %s' % native.existing_rules().keys())",
"my_macro = macro(implementation=_impl)");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro')",
"cc_library(name = 'outer_target')",
"my_macro(name='abc')");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertContainsEvent("existing_rules() keys: [\"outer_target\", \"abc$lib\"]");
}
// TODO: #19922 - This behavior is necessary to preserve compatibility with use cases for
// native.existing_rules(), but it's a blocker for making symbolic macro evaluation lazy.
@Test
public void macroDeclaredTargetsAreVisibleToExistingRules() throws Exception {
scratch.file(
"pkg/foo.bzl", //
"def _impl(name):",
" native.cc_binary(name = name + '$lib')",
"my_macro = macro(implementation=_impl)",
"def query():",
" print('existing_rules() keys: %s' % native.existing_rules().keys())");
scratch.file(
"pkg/BUILD", //
"load(':foo.bzl', 'my_macro', 'query')",
"cc_library(name = 'outer_target')",
"my_macro(name='abc')",
"query()");
Package pkg = getPackage("pkg");
assertPackageNotInError(pkg);
assertContainsEvent("existing_rules() keys: [\"outer_target\", \"abc$lib\"]");
}
// TODO: #19922 - Add more test cases for interaction between macros and environment_group,
// package_group, implicit/explicit input files, and the package() function. But all of these
// behaviors are about to change (from allowed to prohibited).
}