diff --git a/src/python/pants/backend/go/target_types.py b/src/python/pants/backend/go/target_types.py index 7ad98051950..5d98cdf9020 100644 --- a/src/python/pants/backend/go/target_types.py +++ b/src/python/pants/backend/go/target_types.py @@ -132,6 +132,32 @@ class GoTestAddressSanitizerEnabledField(GoRaceDetectorEnabledField): ) +class GoAssemblerFlagsField(StringSequenceField): + alias = "assembler_flags" + help = softwrap( + """ + Extra flags to pass to the Go assembler (i.e., `go tool asm`) when assembling Go-format assembly code. + + Note: These flags will not be added to gcc/clang-format assembly that is assembled in packages using Cgo. + + This field can be specified on several different target types: + + - On `go_mod` targets, the assembler flags are used when building any package involving the module + including both first-party (i.e., `go_package` targets) and third-party dependencies. + + - On `go_binary` targets, the assembler flags are used when building any packages comprising that binary + including third-party dependencies. These assembler flags will be added after any assembler flags + added by any `assembler_flags` field set on the applicable `go_mod` target. + + - On `go_package` targets, the assembler flags are used only for building that specific package and not + for any other package. These assembler flags will be added after any assembler flags added by any + `assembler_flags` field set on the applicable `go_mod` target or applicable `go_binary` target. + + Run `go doc cmd/asm` to see the flags supported by `go tool asm`. + """ + ) + + class GoCompilerFlagsField(StringSequenceField): alias = "compiler_flags" help = softwrap( @@ -290,6 +316,7 @@ class GoModTarget(TargetGenerator): GoRaceDetectorEnabledField, GoMemorySanitizerEnabledField, GoAddressSanitizerEnabledField, + GoAssemblerFlagsField, GoCompilerFlagsField, GoLinkerFlagsField, ) @@ -372,6 +399,7 @@ class GoPackageTarget(Target): GoTestRaceDetectorEnabledField, GoTestMemorySanitizerEnabledField, GoTestAddressSanitizerEnabledField, + GoAssemblerFlagsField, GoCompilerFlagsField, SkipGoTestsField, ) @@ -420,6 +448,7 @@ class GoBinaryTarget(Target): GoRaceDetectorEnabledField, GoMemorySanitizerEnabledField, GoAddressSanitizerEnabledField, + GoAssemblerFlagsField, GoCompilerFlagsField, GoLinkerFlagsField, RestartableField, diff --git a/src/python/pants/backend/go/util_rules/assembly.py b/src/python/pants/backend/go/util_rules/assembly.py index 8d16e6e1548..ff662f7bad9 100644 --- a/src/python/pants/backend/go/util_rules/assembly.py +++ b/src/python/pants/backend/go/util_rules/assembly.py @@ -50,6 +50,7 @@ class AssembleGoAssemblyFilesRequest: dir_path: str asm_header_path: str | None import_path: str + extra_assembler_flags: tuple[str, ...] @dataclass(frozen=True) @@ -160,6 +161,7 @@ def obj_output_path(s_file: str) -> str: os.path.join(goroot.path, "pkg", "include"), *maybe_asm_header_path_args, *maybe_package_import_path_args, + *request.extra_assembler_flags, "-o", obj_output_path(s_file), str(os.path.normpath(PurePath(".", request.dir_path, s_file))), diff --git a/src/python/pants/backend/go/util_rules/build_opts.py b/src/python/pants/backend/go/util_rules/build_opts.py index 173c219c05d..cdd0487cf1e 100644 --- a/src/python/pants/backend/go/util_rules/build_opts.py +++ b/src/python/pants/backend/go/util_rules/build_opts.py @@ -10,6 +10,7 @@ from pants.backend.go.subsystems.gotest import GoTestSubsystem from pants.backend.go.target_types import ( GoAddressSanitizerEnabledField, + GoAssemblerFlagsField, GoCgoEnabledField, GoCompilerFlagsField, GoLinkerFlagsField, @@ -62,6 +63,11 @@ class GoBuildOptions: # Note: These flags come from `go_mod` and `go_binary` targets. linker_flags: tuple[str, ...] = () + # Extra flags to pass to the Go assembler (i.e., `go tool asm`). + # Note: These flags come from `go_mod` and `go_binary` targets. Package-specific assembler flags + # may still come from `go_package` targets. + assembler_flags: tuple[str, ...] = () + def __post_init__(self): assert not (self.with_race_detector and self.with_msan) assert not (self.with_race_detector and self.with_asan) @@ -94,6 +100,7 @@ class GoBuildOptionsFieldSet(FieldSet): asan: GoAddressSanitizerEnabledField compiler_flags: GoCompilerFlagsField linker_flags: GoLinkerFlagsField + assembler_flags: GoAssemblerFlagsField @dataclass(frozen=True) @@ -350,6 +357,17 @@ async def go_extract_build_options_from_target( if target_fields is not None: linker_flags.extend(target_fields.linker_flags.value or []) + # Extract any extra assembler flags specified on `go_mod` or `go_binary` targets. + # Note: An `assembler_flags` field specified on a `go_package` target is extracted elsewhere. + assembler_flags: list[str] = [] + if go_mod_target_fields is not None: + # To avoid duplication of options, only add the module-specific assembler flags if the target for this request + # is not a `go_mod` target (i.e., because it does not conform to the field set or has a different address). + if target_fields is None or go_mod_target_fields.address != target_fields.address: + assembler_flags.extend(go_mod_target_fields.assembler_flags.value or []) + if target_fields is not None: + assembler_flags.extend(target_fields.assembler_flags.value or []) + return GoBuildOptions( cgo_enabled=cgo_enabled, with_race_detector=with_race_detector, @@ -357,6 +375,7 @@ async def go_extract_build_options_from_target( with_asan=with_asan, compiler_flags=tuple(compiler_flags), linker_flags=tuple(linker_flags), + assembler_flags=tuple(assembler_flags), ) diff --git a/src/python/pants/backend/go/util_rules/build_opts_test.py b/src/python/pants/backend/go/util_rules/build_opts_test.py index 1c615c399ac..7cfc022c0d3 100644 --- a/src/python/pants/backend/go/util_rules/build_opts_test.py +++ b/src/python/pants/backend/go/util_rules/build_opts_test.py @@ -442,3 +442,77 @@ def assert_flags(address: Address, expected_value: Iterable[str]) -> None: assert_flags(Address("mod_with_field", target_name="mod"), ["-foo"]) assert_flags(Address("mod_with_field", target_name="bin_without_field"), ["-foo"]) assert_flags(Address("mod_with_field", target_name="bin_with_field"), ["-foo", "-bar"]) + + +def test_assembler_flags_fields(rule_runner: RuleRunner) -> None: + rule_runner.write_files( + { + "mod_with_field/BUILD": dedent( + """\ + go_mod( + name="mod", + assembler_flags=["-foo"], + ) + + go_package(name="pkg") + + go_binary( + name="bin_without_field", + ) + + go_binary( + name="bin_with_field", + assembler_flags=["-bar"], + ) + """ + ), + "mod_with_field/go.mod": "module example.pantsbuild.org/mod_with_field\n", + "mod_with_field/main.go": dedent( + """\ + package main + func main() {} + """ + ), + "mod_with_field/pkg_with_field/BUILD": dedent( + """ + go_package( + assembler_flags=["-xyzzy"], + ) + """ + ), + "mod_with_field/pkg_with_field/foo.go": dedent( + """\ + package pkg_with_field + """ + ), + } + ) + + def assert_flags(address: Address, expected_value: Iterable[str]) -> None: + opts = rule_runner.request( + GoBuildOptions, + ( + GoBuildOptionsFromTargetRequest( + address=address, + ), + ), + ) + assert opts.assembler_flags == tuple( + expected_value + ), f"{address}: expected `assembler_flags` to be {expected_value}" + + assert_flags(Address("mod_with_field", target_name="mod"), ["-foo"]) + assert_flags(Address("mod_with_field", target_name="bin_without_field"), ["-foo"]) + assert_flags(Address("mod_with_field", target_name="bin_with_field"), ["-foo", "-bar"]) + assert_flags(Address("mod_with_field", target_name="pkg"), ["-foo"]) + assert_flags(Address("mod_with_field/pkg_with_field"), ["-foo"]) + + build_request = rule_runner.request( + BuildGoPackageRequest, + [ + BuildGoPackageTargetRequest( + Address("mod_with_field/pkg_with_field"), build_opts=GoBuildOptions() + ) + ], + ) + assert build_request.pkg_specific_assembler_flags == ("-xyzzy",) diff --git a/src/python/pants/backend/go/util_rules/build_pkg.py b/src/python/pants/backend/go/util_rules/build_pkg.py index 44e7cdd5b36..2c28ee0ccb7 100644 --- a/src/python/pants/backend/go/util_rules/build_pkg.py +++ b/src/python/pants/backend/go/util_rules/build_pkg.py @@ -73,6 +73,7 @@ def __init__( fortran_files: tuple[str, ...] = (), prebuilt_object_files: tuple[str, ...] = (), pkg_specific_compiler_flags: tuple[str, ...] = (), + pkg_specific_assembler_flags: tuple[str, ...] = (), ) -> None: """Build a package and its dependencies as `__pkg__.a` files. @@ -105,6 +106,7 @@ def __init__( self.fortran_files = fortran_files self.prebuilt_object_files = prebuilt_object_files self.pkg_specific_compiler_flags = pkg_specific_compiler_flags + self.pkg_specific_assembler_flags = pkg_specific_assembler_flags self._hashcode = hash( ( self.import_path, @@ -127,6 +129,7 @@ def __init__( self.fortran_files, self.prebuilt_object_files, self.pkg_specific_compiler_flags, + self.pkg_specific_assembler_flags, ) ) @@ -154,7 +157,8 @@ def __repr__(self) -> str: f"objc_files={self.objc_files}, " f"fortran_files={self.fortran_files}, " f"prebuilt_object_files={self.prebuilt_object_files}, " - f"pkg_specific_compiler_flags={self.pkg_specific_compiler_flags}" + f"pkg_specific_compiler_flags={self.pkg_specific_compiler_flags}, " + f"pkg_specific_assembler_flags={self.pkg_specific_assembler_flags}" ")" ) @@ -185,6 +189,7 @@ def __eq__(self, other): and self.fortran_files == other.fortran_files and self.prebuilt_object_files == other.prebuilt_object_files and self.pkg_specific_compiler_flags == other.pkg_specific_compiler_flags + and self.pkg_specific_assembler_flags == other.pkg_specific_assembler_flags # TODO: Use a recursive memoized __eq__ if this ever shows up in profiles. and self.direct_dependencies == other.direct_dependencies ) @@ -628,6 +633,9 @@ async def build_go_package( dir_path=request.dir_path, asm_header_path=asm_header_path, import_path=request.import_path, + extra_assembler_flags=tuple( + *request.build_opts.assembler_flags, *request.pkg_specific_assembler_flags + ), ), ) assembly_result = assembly_fallible_result.result diff --git a/src/python/pants/backend/go/util_rules/build_pkg_target.py b/src/python/pants/backend/go/util_rules/build_pkg_target.py index e0169c8d3bf..36ac21dbd1e 100644 --- a/src/python/pants/backend/go/util_rules/build_pkg_target.py +++ b/src/python/pants/backend/go/util_rules/build_pkg_target.py @@ -10,6 +10,7 @@ from pants.backend.go.dependency_inference import GoModuleImportPathsMapping from pants.backend.go.target_type_rules import GoImportPathMappingRequest from pants.backend.go.target_types import ( + GoAssemblerFlagsField, GoCompilerFlagsField, GoImportPathField, GoPackageSourcesField, @@ -287,6 +288,12 @@ async def setup_build_go_package_target_request( if compiler_flags_field and compiler_flags_field.value: pkg_specific_compiler_flags = compiler_flags_field.value + pkg_specific_assembler_flags: tuple[str, ...] = () + if target.has_field(GoAssemblerFlagsField): + assembler_flags_field = target.get(GoAssemblerFlagsField) + if assembler_flags_field and assembler_flags_field.value: + pkg_specific_assembler_flags = assembler_flags_field.value + all_direct_dependencies = await Get(Targets, DependenciesRequest(target[Dependencies])) first_party_dep_import_path_targets = [] @@ -398,6 +405,7 @@ async def setup_build_go_package_target_request( embed_config=embed_config, with_coverage=with_coverage, pkg_specific_compiler_flags=tuple(pkg_specific_compiler_flags), + pkg_specific_assembler_flags=tuple(pkg_specific_assembler_flags), ) return FallibleBuildGoPackageRequest(result, import_path)