Skip to content

Commit 471ff3e

Browse files
committed
update build paths
1 parent f58b60c commit 471ff3e

1 file changed

Lines changed: 271 additions & 15 deletions

File tree

internal/codegen/build.go

Lines changed: 271 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ type BuildOptions struct {
2929
}
3030

3131
// DefaultBuildOptions returns default build options
32+
// Environment variables are checked with fallbacks, so the code works reliably
33+
// even if env vars are not set (os.Getenv returns empty string for unset vars).
3234
func DefaultBuildOptions() *BuildOptions {
3335
linkFlags := []string{"-s"}
36+
// Check FERRET_AS first, then AS, then toolchain, then default to "as"
3437
assembler := os.Getenv("FERRET_AS")
3538
if assembler == "" {
3639
assembler = os.Getenv("AS")
3740
}
3841

42+
// Check FERRET_LD first, then LD, then toolchain, then default to "ld"
3943
linker := os.Getenv("FERRET_LD")
4044
if linker == "" {
4145
linker = os.Getenv("LD")
@@ -244,6 +248,10 @@ func ensureToolExists(kind, tool string) error {
244248
return fmt.Errorf("%s not found: %s", kind, tool)
245249
}
246250

251+
// applyLdDefaults sets up platform-specific linker defaults.
252+
// This function only runs for GNU ld linkers (checked via isLdLinker).
253+
// For other linkers (LLD, Gold, clang's linker), users should configure
254+
// linking manually via FERRET_LD_FLAGS and FERRET_LD_LIBS environment variables.
247255
func applyLdDefaults(opts *BuildOptions) error {
248256
if opts == nil || opts.LdDefaults || !isLdLinker(opts.Linker) {
249257
return nil
@@ -277,6 +285,44 @@ func applyLdDefaults(opts *BuildOptions) error {
277285
if !containsArg(opts.LinkLibs, "-lc") {
278286
opts.LinkLibs = append([]string{"-lc"}, opts.LinkLibs...)
279287
}
288+
// Link libgcc for compiler runtime functions (e.g., 128-bit float operations)
289+
// Try to find static libgcc.a first, then fall back to shared libgcc_s.so.1
290+
libgccLinked := false
291+
if tcLib := toolchainLibDir(); tcLib != "" {
292+
// Check for static libgcc.a in toolchain (though bootstrap doesn't copy it)
293+
if path := filepath.Join(tcLib, "libgcc.a"); utilsfs.IsValidFile(path) {
294+
opts.LinkLibs = append(opts.LinkLibs, "-l:libgcc.a")
295+
libgccLinked = true
296+
}
297+
}
298+
if !libgccLinked {
299+
// Try to find libgcc.a via gcc
300+
if cc := resolveCCompiler(); cc != "" {
301+
if path := gccPrintFile(cc, "libgcc.a"); path != "" {
302+
// Add the directory to library search path and link
303+
libDir := filepath.Dir(path)
304+
if !containsLibDir(opts.LinkFlags, libDir) {
305+
opts.LinkFlags = append(opts.LinkFlags, "-L", libDir)
306+
}
307+
opts.LinkLibs = append(opts.LinkLibs, "-lgcc")
308+
libgccLinked = true
309+
}
310+
}
311+
}
312+
if !libgccLinked {
313+
// Fall back to shared library (bootstrap copies libgcc_s.so.1)
314+
// Use -l: syntax (GNU ld specific) to link against exact filename
315+
// This works without needing symlinks and is safe because applyLdDefaults
316+
// only runs for GNU ld linkers
317+
if tcLib := toolchainLibDir(); tcLib != "" {
318+
if path := filepath.Join(tcLib, "libgcc_s.so.1"); utilsfs.IsValidFile(path) {
319+
opts.LinkLibs = append(opts.LinkLibs, "-l:libgcc_s.so.1")
320+
libgccLinked = true
321+
}
322+
}
323+
}
324+
// Note: We don't need a fallback to -lgcc_s since -l:libgcc_s.so.1 works
325+
// without symlinks. If libgcc_s.so.1 doesn't exist, that's a bootstrap issue.
280326
opts.LinkPost = append(opts.LinkPost, crtn)
281327
case "android":
282328
return fmt.Errorf("android builds are not supported here; use install-termux.sh")
@@ -297,6 +343,46 @@ func findLinuxCrtObjects() (string, string, string) {
297343
crti := os.Getenv("FERRET_LD_CRTI")
298344
crtn := os.Getenv("FERRET_LD_CRTN")
299345

346+
// First, check toolchain/lib directory (bundled files from bootstrap)
347+
if tcLib := toolchainLibDir(); tcLib != "" {
348+
if crt1 == "" {
349+
if path := filepath.Join(tcLib, "crt1.o"); utilsfs.IsValidFile(path) {
350+
crt1 = path
351+
}
352+
}
353+
if crti == "" {
354+
if path := filepath.Join(tcLib, "crti.o"); utilsfs.IsValidFile(path) {
355+
crti = path
356+
}
357+
}
358+
if crtn == "" {
359+
if path := filepath.Join(tcLib, "crtn.o"); utilsfs.IsValidFile(path) {
360+
crtn = path
361+
}
362+
}
363+
}
364+
365+
// If still missing, use gcc to dynamically find files
366+
cc := resolveCCompiler()
367+
if cc != "" {
368+
if crt1 == "" {
369+
if path := gccPrintFile(cc, "crt1.o"); path != "" {
370+
crt1 = path
371+
}
372+
}
373+
if crti == "" {
374+
if path := gccPrintFile(cc, "crti.o"); path != "" {
375+
crti = path
376+
}
377+
}
378+
if crtn == "" {
379+
if path := gccPrintFile(cc, "crtn.o"); path != "" {
380+
crtn = path
381+
}
382+
}
383+
}
384+
385+
// Last resort: check hardcoded paths
300386
dirs := linuxLibDirs()
301387
if crt1 == "" {
302388
crt1 = firstExisting(dirs, "crt1.o")
@@ -316,18 +402,40 @@ func findLinuxDynamicLinker() string {
316402
return override
317403
}
318404

319-
var candidates []string
320-
switch runtime.GOARCH {
321-
case "amd64":
322-
candidates = []string{"ld-linux-x86-64.so.2", "ld-musl-x86_64.so.1"}
323-
case "arm64":
324-
candidates = []string{"ld-linux-aarch64.so.1", "ld-musl-aarch64.so.1"}
325-
case "386":
326-
candidates = []string{"ld-linux.so.2", "ld-musl-i386.so.1"}
327-
default:
328-
candidates = []string{"ld-linux.so.2", "ld-musl.so.1"}
405+
candidates := linuxLoaderCandidates()
406+
407+
// First, check toolchain/lib directory (bundled files from bootstrap)
408+
if tcLib := toolchainLibDir(); tcLib != "" {
409+
for _, name := range candidates {
410+
path := filepath.Join(tcLib, name)
411+
if utilsfs.IsValidFile(path) {
412+
return path
413+
}
414+
}
415+
}
416+
417+
// Then, use gcc to dynamically find the loader
418+
cc := resolveCCompiler()
419+
if cc != "" {
420+
for _, name := range candidates {
421+
if path := gccPrintFile(cc, name); path != "" {
422+
return path
423+
}
424+
}
425+
// Also check gcc library directories
426+
if dirs := gccLibDirs(cc); len(dirs) > 0 {
427+
for _, dir := range dirs {
428+
for _, name := range candidates {
429+
path := filepath.Join(dir, name)
430+
if utilsfs.IsValidFile(path) {
431+
return path
432+
}
433+
}
434+
}
435+
}
329436
}
330437

438+
// Last resort: check hardcoded paths
331439
dirs := linuxLibDirs()
332440
for _, dir := range dirs {
333441
for _, name := range candidates {
@@ -342,19 +450,30 @@ func findLinuxDynamicLinker() string {
342450
}
343451

344452
func linuxLibDirs() []string {
345-
dirs := []string{"/lib", "/usr/lib", "/lib64", "/usr/lib64"}
453+
dirs := []string{}
454+
// Highest priority: toolchain/lib directory (bundled files from bootstrap)
346455
if tc := toolchainLibDir(); tc != "" {
347-
dirs = append([]string{tc}, dirs...)
456+
dirs = append(dirs, tc)
348457
}
458+
// Next: use gcc's library directories if available (more dynamic, works across distros)
459+
if cc := resolveCCompiler(); cc != "" {
460+
if gccDirs := gccLibDirs(cc); len(gccDirs) > 0 {
461+
dirs = append(dirs, gccDirs...)
462+
}
463+
}
464+
// Fallback: common system library directories
465+
dirs = append(dirs, "/lib", "/usr/lib", "/lib64", "/usr/lib64")
466+
// Last resort: distro-specific paths (these won't exist on all distros like Arch)
349467
switch runtime.GOARCH {
350468
case "amd64":
351-
dirs = append([]string{"/lib/x86_64-linux-gnu", "/usr/lib/x86_64-linux-gnu"}, dirs...)
469+
dirs = append(dirs, "/lib/x86_64-linux-gnu", "/usr/lib/x86_64-linux-gnu")
352470
case "arm64":
353-
dirs = append([]string{"/lib/aarch64-linux-gnu", "/usr/lib/aarch64-linux-gnu"}, dirs...)
471+
dirs = append(dirs, "/lib/aarch64-linux-gnu", "/usr/lib/aarch64-linux-gnu")
354472
case "386":
355-
dirs = append([]string{"/lib/i386-linux-gnu", "/usr/lib/i386-linux-gnu"}, dirs...)
473+
dirs = append(dirs, "/lib/i386-linux-gnu", "/usr/lib/i386-linux-gnu")
356474
}
357475

476+
// Remove duplicates while preserving order
358477
seen := make(map[string]struct{}, len(dirs))
359478
unique := make([]string, 0, len(dirs))
360479
for _, dir := range dirs {
@@ -389,6 +508,46 @@ func findWindowsCrtObjects() (string, string, string) {
389508
crtbegin := os.Getenv("FERRET_LD_CRTBEGIN")
390509
crtend := os.Getenv("FERRET_LD_CRTEND")
391510

511+
// First, check toolchain/lib directory (bundled files from bootstrap)
512+
if tcLib := toolchainLibDir(); tcLib != "" {
513+
if crt2 == "" {
514+
if path := filepath.Join(tcLib, "crt2.o"); utilsfs.IsValidFile(path) {
515+
crt2 = path
516+
}
517+
}
518+
if crtbegin == "" {
519+
if path := filepath.Join(tcLib, "crtbegin.o"); utilsfs.IsValidFile(path) {
520+
crtbegin = path
521+
}
522+
}
523+
if crtend == "" {
524+
if path := filepath.Join(tcLib, "crtend.o"); utilsfs.IsValidFile(path) {
525+
crtend = path
526+
}
527+
}
528+
}
529+
530+
// If still missing, use gcc to dynamically find files
531+
cc := resolveCCompiler()
532+
if cc != "" {
533+
if crt2 == "" {
534+
if path := gccPrintFile(cc, "crt2.o"); path != "" {
535+
crt2 = path
536+
}
537+
}
538+
if crtbegin == "" {
539+
if path := gccPrintFile(cc, "crtbegin.o"); path != "" {
540+
crtbegin = path
541+
}
542+
}
543+
if crtend == "" {
544+
if path := gccPrintFile(cc, "crtend.o"); path != "" {
545+
crtend = path
546+
}
547+
}
548+
}
549+
550+
// Last resort: check toolchain/lib directory via windowsLibDirs
392551
dirs := windowsLibDirs()
393552
if crt2 == "" {
394553
crt2 = firstExisting(dirs, "crt2.o")
@@ -422,6 +581,15 @@ func containsArg(args []string, value string) bool {
422581
return false
423582
}
424583

584+
func containsLibDir(flags []string, dir string) bool {
585+
for i, flag := range flags {
586+
if flag == "-L" && i+1 < len(flags) && flags[i+1] == dir {
587+
return true
588+
}
589+
}
590+
return false
591+
}
592+
425593
func resolveToolchainPath() string {
426594
if override := os.Getenv("FERRET_TOOLCHAIN_PATH"); override != "" {
427595
if utilsfs.IsDir(override) {
@@ -499,3 +667,91 @@ func withPathEnv(env []string, key, dir string) []string {
499667
}
500668
return append(env, key+"="+dir)
501669
}
670+
671+
func resolveCCompiler() string {
672+
if val := os.Getenv("FERRET_CC"); val != "" {
673+
if path, err := exec.LookPath(val); err == nil {
674+
return path
675+
}
676+
}
677+
if val := os.Getenv("CC"); val != "" {
678+
if path, err := exec.LookPath(val); err == nil {
679+
return path
680+
}
681+
}
682+
if runtime.GOOS == "darwin" {
683+
if path, err := exec.LookPath("clang"); err == nil {
684+
return path
685+
}
686+
}
687+
if path, err := exec.LookPath("gcc"); err == nil {
688+
return path
689+
}
690+
return ""
691+
}
692+
693+
func gccPrintFile(cc, name string) string {
694+
if cc == "" {
695+
return ""
696+
}
697+
out, err := exec.Command(cc, "-print-file-name="+name).CombinedOutput()
698+
if err != nil {
699+
return ""
700+
}
701+
path := strings.TrimSpace(string(out))
702+
if path == "" || path == name {
703+
return ""
704+
}
705+
if !utilsfs.IsValidFile(path) {
706+
return ""
707+
}
708+
return path
709+
}
710+
711+
func gccLibDirs(cc string) []string {
712+
if cc == "" {
713+
return nil
714+
}
715+
out, err := exec.Command(cc, "-print-search-dirs").CombinedOutput()
716+
if err != nil {
717+
return nil
718+
}
719+
lines := strings.Split(string(out), "\n")
720+
for _, line := range lines {
721+
line = strings.TrimSpace(line)
722+
if !strings.HasPrefix(line, "libraries:") {
723+
continue
724+
}
725+
parts := strings.SplitN(line, "=", 2)
726+
if len(parts) != 2 {
727+
continue
728+
}
729+
raw := strings.TrimSpace(parts[1])
730+
if raw == "" {
731+
continue
732+
}
733+
chunks := strings.Split(raw, string(os.PathListSeparator))
734+
dirs := make([]string, 0, len(chunks))
735+
for _, chunk := range chunks {
736+
if chunk == "" {
737+
continue
738+
}
739+
dirs = append(dirs, chunk)
740+
}
741+
return dirs
742+
}
743+
return nil
744+
}
745+
746+
func linuxLoaderCandidates() []string {
747+
switch runtime.GOARCH {
748+
case "amd64":
749+
return []string{"ld-linux-x86-64.so.2", "ld-musl-x86_64.so.1"}
750+
case "arm64":
751+
return []string{"ld-linux-aarch64.so.1", "ld-musl-aarch64.so.1"}
752+
case "386":
753+
return []string{"ld-linux.so.2", "ld-musl-i386.so.1"}
754+
default:
755+
return []string{"ld-linux.so.2", "ld-musl.so.1"}
756+
}
757+
}

0 commit comments

Comments
 (0)