Skip to content

Commit 7c24576

Browse files
committed
Compile cmd/pack from source
1 parent aca78c7 commit 7c24576

File tree

11 files changed

+61
-47
lines changed

11 files changed

+61
-47
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/bazelbuild/rules_go
22

3-
toolchain go1.23.6
3+
toolchain go1.25.0
44

55
go 1.22.0
66

go/private/BUILD.sdk.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,21 @@ go_tool_binary(
8080
exec_compatible_with = {exec_compatible_with},
8181
ldflags = "-X main.rulesGoStdlibPrefix={}".format(RULES_GO_STDLIB_PREFIX),
8282
sdk = ":go_sdk",
83+
# The .exe suffix is required on Windows and harmless on other platforms.
84+
# Output attributes are not configurable, so we use it everywhere.
85+
out_pack = "pack.exe",
8386
)
8487

8588
non_go_reset_target(
8689
name = "builder_reset",
8790
dep = ":builder",
8891
)
8992

93+
non_go_reset_target(
94+
name = "pack_reset",
95+
dep = ":pack.exe",
96+
)
97+
9098
# TODO(jayconrod): Gazelle depends on this file directly. This dependency
9199
# should be broken, and this rule should be folded into go_sdk.
92100
package_list(
@@ -98,6 +106,7 @@ package_list(
98106

99107
declare_go_toolchains(
100108
builder = ":builder_reset",
109+
pack = ":pack_reset",
101110
host_goos = "{goos}",
102111
sdk = ":go_sdk",
103112
)

go/private/actions/compilepkg.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def emit_compilepkg(
9494
archives = archives + [go.coverdata]
9595

9696
sdk = go.sdk
97-
inputs_direct = (sources + embedsrcs + [sdk.package_list] +
97+
inputs_direct = (sources + embedsrcs + [sdk.package_list, go.toolchain._pack] +
9898
[archive.data.export_file for archive in archives])
9999
inputs_transitive = [sdk.headers, sdk.tools, go.stdlib.libs, headers]
100100
outputs = [out_lib, out_export]
@@ -103,6 +103,7 @@ def emit_compilepkg(
103103
shared_args.add_all(sources, before_each = "-src")
104104

105105
compile_args = go.tool_args(go)
106+
compile_args.add("-pack", go.toolchain._pack)
106107
compile_args.add_all(embedsrcs, before_each = "-embedsrc", expand_directories = False)
107108
compile_args.add_all(
108109
sources + [out_lib] + embedsrcs,
@@ -200,7 +201,6 @@ def emit_compilepkg(
200201
go.actions.run(
201202
inputs = depset(inputs_direct, transitive = inputs_transitive),
202203
outputs = outputs,
203-
tools = [go.toolchain.sdk.go],
204204
mnemonic = "GoCompilePkgExternal" if is_external_pkg else "GoCompilePkg",
205205
executable = go.toolchain._builder,
206206
arguments = ["compilepkg", shared_args, compile_args],

go/private/common.bzl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,11 @@ COVERAGE_OPTIONS_DENYLIST = {
211211
"-fcoverage-mapping": None,
212212
}
213213

214-
_RULES_GO_RAW_REPO_NAME = str(Label("//:unused"))[:-len("//:unused")]
215-
216-
# When rules_go is the main repository and Bazel < 6 is used, the repo name does
217-
# not start with a "@", so we need to add it.
218-
RULES_GO_REPO_NAME = _RULES_GO_RAW_REPO_NAME if _RULES_GO_RAW_REPO_NAME.startswith("@") else "@" + _RULES_GO_RAW_REPO_NAME
214+
RULES_GO_REPO_NAME = str(Label("//:unused"))[:-len("//:unused")]
219215
RULES_GO_STDLIB_PREFIX = RULES_GO_REPO_NAME + "//stdlib:"
220216

221217
# TODO: Remove the "and" once the rules_go repo itself uses Bzlmod.
222-
RULES_GO_IS_BZLMOD_REPO = _RULES_GO_RAW_REPO_NAME.lstrip("@") != "io_bazel_rules_go" and _RULES_GO_RAW_REPO_NAME.lstrip("@")
218+
RULES_GO_IS_BZLMOD_REPO = RULES_GO_REPO_NAME.lstrip("@") != "io_bazel_rules_go" and RULES_GO_REPO_NAME.lstrip("@")
223219

224220
# Marks an action as supporting path mapping (--experimental_output_paths=strip).
225221
# See https://www.youtube.com/watch?v=Et1rjb7ixUU for more details.

go/private/go_toolchain.bzl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def _go_toolchain_impl(ctx):
5050

5151
# Internal fields -- may be read by emit functions.
5252
_builder = ctx.executable.builder,
53+
_pack = ctx.executable.pack,
5354
),
5455
]
5556

@@ -63,6 +64,12 @@ go_toolchain = rule(
6364
executable = True,
6465
doc = "Tool used to execute most Go actions",
6566
),
67+
"pack": attr.label(
68+
mandatory = True,
69+
cfg = "exec",
70+
executable = True,
71+
doc = "Tool used to pack object files into archives",
72+
),
6673
"goos": attr.string(
6774
mandatory = True,
6875
doc = "Default target OS",
@@ -89,7 +96,7 @@ go_toolchain = rule(
8996
provides = [platform_common.ToolchainInfo],
9097
)
9198

92-
def declare_go_toolchains(host_goos, sdk, builder):
99+
def declare_go_toolchains(host_goos, sdk, builder, pack):
93100
"""Declares go_toolchain targets for each platform."""
94101
for p in PLATFORMS:
95102
if p.cgo:
@@ -111,6 +118,7 @@ def declare_go_toolchains(host_goos, sdk, builder):
111118
goarch = p.goarch,
112119
sdk = sdk,
113120
builder = builder,
121+
pack = pack,
114122
link_flags = link_flags,
115123
cgo_link_flags = cgo_link_flags,
116124
tags = ["manual"],

go/private/rules/binary.bzl

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,10 @@ set GOTOOLCHAIN=local
497497
set GO111MODULE=off
498498
set GOTELEMETRY=off
499499
set GOENV=off
500-
{go} build -o {out} -trimpath -ldflags \"-buildid='' {ldflags}\" {srcs}
500+
{go} build -o {out_pack} -trimpath -ldflags \"-buildid='' {ldflags}\" cmd/pack
501+
if %ERRORLEVEL% EQU 0 (
502+
{go} build -o {out} -trimpath -ldflags \"-buildid='' {ldflags}\" {srcs}
503+
)
501504
set GO_EXIT_CODE=%ERRORLEVEL%
502505
RMDIR /S /Q "{gotmp}"
503506
MKDIR "{gotmp}"
@@ -506,6 +509,7 @@ exit /b %GO_EXIT_CODE%
506509
gotmp = gotmp.path.replace("/", "\\"),
507510
go = sdk.go.path.replace("/", "\\"),
508511
out = out.path,
512+
out_pack = ctx.outputs.out_pack.path,
509513
srcs = " ".join([f.path for f in ctx.files.srcs]),
510514
ldflags = ctx.attr.ldflags,
511515
)
@@ -521,32 +525,35 @@ exit /b %GO_EXIT_CODE%
521525
transitive = [sdk.headers, sdk.srcs, sdk.tools],
522526
),
523527
toolchain = None,
524-
outputs = [out, gotmp],
528+
outputs = [out, ctx.outputs.out_pack, gotmp],
525529
mnemonic = "GoToolchainBinaryBuild",
526530
)
527531
else:
528-
# -a flag instructs the compiler to not read from GOCACHE and force a rebuild.
529-
# This provides extra safety in cases of unsandboxed execution.
532+
# We do not use -a here as the cache drastically reduces the time spent
533+
# on the second go build (roughly 50% faster).
530534
args = ctx.actions.args()
531-
args.add("build")
532-
args.add("-a")
533-
args.add("-o", out)
534-
args.add("-trimpath")
535-
args.add("-ldflags", ctx.attr.ldflags, format = '-buildid="" %s')
535+
args.add(ctx.outputs.out_pack)
536+
args.add(out)
536537
args.add_all(ctx.files.srcs)
537538

538539
ctx.actions.run_shell(
539540
command = """
540-
trap "HOME={HOME} GOROOT={GOROOT} {go} clean -cache" EXIT;
541-
HOME={HOME} {go} "$@" """.format(
541+
set -eu
542+
export HOME={HOME}
543+
trap "{go} clean -cache" EXIT;
544+
{go} build -trimpath -ldflags='-buildid="" {ldflags}' -o "$1" cmd/pack
545+
shift
546+
{go} build -trimpath -ldflags='-buildid="" {ldflags}' -o "$@"
547+
""".format(
542548
go = sdk.go.path,
543-
GOROOT = sdk.root_file.dirname,
549+
ldflags = ctx.attr.ldflags,
544550
# The value of GOCACHE/GOPATH are determined from HOME.
545551
# We place them in the execroot to avoid dependency on `mktemp` and because we don't know
546552
# a safe scratch space on all systems. Note that HOME must be an absolute path, otherwise the
547553
# Go toolchain will write some outputs to the wrong place and the result will be uncacheable.
548-
# We use a hardcoded UUID to prevent collisions with anything else under unsandboxed strategy.
549-
HOME = "$(pwd)/_go_tool_binary-fake-home-85e96dea-541b-4188-8d13-5c2c42bdbd06",
554+
# We include the output path of this action to prevent collisions with anything else,
555+
# including differently configured versions of the same target, under an unsandboxed strategy.
556+
HOME = "$(pwd)/_go_tool_binary-fake-home-" + out.path.replace("/", "_"),
550557
),
551558
arguments = [args],
552559
tools = [sdk.go],
@@ -562,7 +569,7 @@ HOME={HOME} {go} "$@" """.format(
562569
transitive = [sdk.headers, sdk.srcs, sdk.libs, sdk.tools],
563570
),
564571
toolchain = None,
565-
outputs = [out],
572+
outputs = [out, ctx.outputs.out_pack],
566573
mnemonic = "GoToolchainBinaryBuild",
567574
)
568575

@@ -586,6 +593,7 @@ go_tool_binary = rule(
586593
"ldflags": attr.string(
587594
doc = "Raw value to pass to go build via -ldflags without tokenization",
588595
),
596+
"out_pack": attr.output(),
589597
},
590598
executable = True,
591599
doc = """Used instead of go_binary for executables used in the toolchain.
@@ -594,6 +602,11 @@ go_tool_binary depends on tools and libraries that are part of the Go SDK.
594602
It does not depend on other toolchains. It can only compile binaries that
595603
just have a main package and only depend on the standard library and don't
596604
require build constraints.
605+
606+
It is currently only used to build the `builder` tool maintained as part of
607+
rules_go as well as the `pack` tool provided by the Go SDK in source form
608+
only as of Go 1.25. Combining both builds into a single action drastically
609+
reduces the overall build time due to Go's own caching mechanism.
597610
""",
598611
)
599612

go/private/rules/transition.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ non_go_reset_target = rule(
344344
"dep": attr.label(
345345
mandatory = True,
346346
cfg = non_go_tool_transition,
347+
allow_files = True,
347348
),
348349
"_allowlist_function_transition": attr.label(
349350
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",

go/tools/builders/compilepkg.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func compilePkg(args []string) error {
3737

3838
fs := flag.NewFlagSet("GoCompilePkg", flag.ExitOnError)
3939
goenv := envFlags(fs)
40+
var pack string
4041
var unfilteredSrcs, coverSrcs, embedSrcs, embedLookupDirs, embedRoots, recompileInternalDeps multiFlag
4142
var deps archiveMultiFlag
4243
var importPath, packagePath, packageListPath, coverMode string
@@ -45,6 +46,7 @@ func compilePkg(args []string) error {
4546
var gcFlags, asmFlags, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags quoteMultiFlag
4647
var coverFormat string
4748
var pgoprofile string
49+
fs.StringVar(&pack, "pack", "", "Path of the pack tool.")
4850
fs.Var(&unfilteredSrcs, "src", ".go, .c, .cc, .m, .mm, .s, or .S file to be filtered and compiled")
4951
fs.Var(&coverSrcs, "cover", ".go file that should be instrumented for coverage (must also be a -src)")
5052
fs.Var(&embedSrcs, "embedsrc", "file that may be compiled into the package with a //go:embed directive")
@@ -106,6 +108,7 @@ func compilePkg(args []string) error {
106108

107109
return compileArchive(
108110
goenv,
111+
pack,
109112
importPath,
110113
packagePath,
111114
srcs,
@@ -137,6 +140,7 @@ func compilePkg(args []string) error {
137140

138141
func compileArchive(
139142
goenv *env,
143+
pack string,
140144
importPath string,
141145
packagePath string,
142146
srcs archiveSrcs,
@@ -456,7 +460,7 @@ func compileArchive(
456460
// Pack .o and .syso files into the archive. These may come from cgo generated code,
457461
// cgo dependencies (cdeps), windows resource file generation, or assembly.
458462
if len(objFiles) > 0 {
459-
if err := appendToArchive(goenv, outLinkObj, objFiles); err != nil {
463+
if err := appendToArchive(goenv, pack, outLinkObj, objFiles); err != nil {
460464
return err
461465
}
462466
}
@@ -531,9 +535,9 @@ func compileGo(goenv *env, srcs []string, packagePath, importcfgPath, embedcfgPa
531535
return goenv.runCommand(args)
532536
}
533537

534-
func appendToArchive(goenv *env, outPath string, objFiles []string) error {
538+
func appendToArchive(goenv *env, pack, outPath string, objFiles []string) error {
535539
// Use abs to work around long path issues on Windows.
536-
args := goenv.goCmd("tool", "pack", "r", abs(outPath))
540+
args := []string{pack, "r", abs(outPath)}
537541
args = append(args, objFiles...)
538542
return goenv.runCommand(args)
539543
}

go/tools/builders/stdlib.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"flag"
1919
"fmt"
2020
"go/build"
21-
"runtime"
2221
"os"
2322
"path/filepath"
2423
"regexp"
@@ -171,21 +170,5 @@ You may need to use the flags --cpu=x64_windows --compiler=mingw-gcc.`)
171170
if err := goenv.runCommand(installArgs); err != nil {
172171
return err
173172
}
174-
175-
// Install the "cmd/pack" command from source if it's missing as it's no longer shipped with Go distributions
176-
// after go1.25 https://github.com/golang/go/issues/74080
177-
if _, err := os.Stat(filepath.Join(output, "pkg/tool", runtime.GOOS + "_" + runtime.GOARCH, "pack")); err != nil && os.IsNotExist(err) {
178-
originalOS, originalARCH := os.Getenv("GOOS"), os.Getenv("GOARCH")
179-
os.Setenv("GOOS", runtime.GOOS)
180-
os.Setenv("GOARCH", runtime.GOARCH)
181-
defer func() {
182-
os.Setenv("GOOS", originalOS)
183-
os.Setenv("GOARCH", originalARCH)
184-
}()
185-
toolArgs := goenv.goCmd("install", "-toolexec", abs(os.Args[0])+" filterbuildid", "cmd/pack")
186-
if err := goenv.runCommand(toolArgs); err != nil {
187-
return err
188-
}
189-
}
190173
return nil
191174
}

tests/bcr/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ go_test(
8181
sdk_transition_test(
8282
name = "wrap_sdk_test",
8383
binary = ":wrap_test",
84-
sdk_version = "1.23.6",
84+
sdk_version = "1.25.0",
8585
)
8686

8787
go_library(

0 commit comments

Comments
 (0)