xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/replicate.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1// Copyright 2018 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// stdlib builds the standard library in the appropriate mode into a new goroot.
16package main
17
18import (
19	"fmt"
20	"io"
21	"os"
22	"path/filepath"
23)
24
25type replicateMode int
26
27const (
28	copyMode replicateMode = iota
29	hardlinkMode
30	softlinkMode
31)
32
33type replicateOption func(*replicateConfig)
34type replicateConfig struct {
35	removeFirst bool
36	fileMode    replicateMode
37	dirMode     replicateMode
38	paths       []string
39}
40
41func replicatePaths(paths ...string) replicateOption {
42	return func(config *replicateConfig) {
43		config.paths = append(config.paths, paths...)
44	}
45}
46
47// replicatePrepare is the common preparation steps for a replication entry
48func replicatePrepare(dst string, config *replicateConfig) error {
49	dir := filepath.Dir(dst)
50	if err := os.MkdirAll(dir, 0755); err != nil {
51		return fmt.Errorf("Failed to make %s: %v", dir, err)
52	}
53	if config.removeFirst {
54		_ = os.Remove(dst)
55	}
56	return nil
57}
58
59// replicateFile is called internally by replicate to map a single file from src into dst.
60func replicateFile(src, dst string, config *replicateConfig) error {
61	if err := replicatePrepare(dst, config); err != nil {
62		return err
63	}
64	switch config.fileMode {
65	case copyMode:
66		in, err := os.Open(src)
67		if err != nil {
68			return err
69		}
70		defer in.Close()
71		out, err := os.Create(dst)
72		if err != nil {
73			return err
74		}
75		_, err = io.Copy(out, in)
76		closeerr := out.Close()
77		if err != nil {
78			return err
79		}
80		if closeerr != nil {
81			return closeerr
82		}
83		s, err := os.Stat(src)
84		if err != nil {
85			return err
86		}
87		if err := os.Chmod(dst, s.Mode()); err != nil {
88			return err
89		}
90		return nil
91	case hardlinkMode:
92		return os.Link(src, dst)
93	case softlinkMode:
94		return os.Symlink(src, dst)
95	default:
96		return fmt.Errorf("Invalid replication mode %d", config.fileMode)
97	}
98}
99
100// replicateDir makes a tree of files visible in a new location.
101// It is allowed to take any efficient method of doing so.
102func replicateDir(src, dst string, config *replicateConfig) error {
103	if err := replicatePrepare(dst, config); err != nil {
104		return err
105	}
106	switch config.dirMode {
107	case copyMode:
108		return filepath.Walk(src, func(path string, f os.FileInfo, err error) error {
109			if f.IsDir() {
110				return nil
111			}
112			relative, err := filepath.Rel(src, path)
113			if err != nil {
114				return err
115			}
116			return replicateFile(path, filepath.Join(dst, relative), config)
117		})
118	case hardlinkMode:
119		return os.Link(src, dst)
120	case softlinkMode:
121		return os.Symlink(src, dst)
122	default:
123		return fmt.Errorf("Invalid replication mode %d", config.fileMode)
124	}
125}
126
127// replicateTree is called for each single src dst pair.
128func replicateTree(src, dst string, config *replicateConfig) error {
129	if err := os.RemoveAll(dst); err != nil {
130		return fmt.Errorf("Failed to remove file at destination %s: %v", dst, err)
131	}
132	if l, err := filepath.EvalSymlinks(src); err != nil {
133		return err
134	} else {
135		src = l
136	}
137	if s, err := os.Stat(src); err != nil {
138		return err
139	} else if s.IsDir() {
140		return replicateDir(src, dst, config)
141	}
142	return replicateFile(src, dst, config)
143}
144
145// replicate makes a tree of files visible in a new location.
146// You control how it does so using options, by default it presumes the entire tree
147// of files rooted at src must be visible at dst, and that it should do so by copying.
148// src is allowed to be a file, in which case just the one file is copied.
149func replicate(src, dst string, options ...replicateOption) error {
150	config := replicateConfig{
151		removeFirst: true,
152	}
153	for _, option := range options {
154		option(&config)
155	}
156	if len(config.paths) == 0 {
157		return replicateTree(src, dst, &config)
158	}
159	for _, base := range config.paths {
160		from := filepath.Join(src, base)
161		to := filepath.Join(dst, base)
162		if err := replicateTree(from, to, &config); err != nil {
163			return err
164		}
165	}
166	return nil
167}
168