1 # Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
2 # file at the top-level directory of this distribution and at
3 # http://rust-lang.org/COPYRIGHT.
5 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 # option. This file may not be copied, modified, or distributed
9 # except according to those terms.
25 def get(url, path, verbose=False):
26 sha_url = url + ".sha256"
27 with tempfile.NamedTemporaryFile(delete=False) as temp_file:
28 temp_path = temp_file.name
29 with tempfile.NamedTemporaryFile(suffix=".sha256", delete=False) as sha_file:
30 sha_path = sha_file.name
33 download(sha_path, sha_url, verbose)
34 if os.path.exists(path):
35 if verify(path, sha_path, False):
36 print("using already-download file " + path)
39 print("ignoring already-download file " + path + " due to failed verification")
41 download(temp_path, url, verbose)
42 if not verify(temp_path, sha_path, True):
43 raise RuntimeError("failed verification")
44 print("moving {} to {}".format(temp_path, path))
45 shutil.move(temp_path, path)
47 delete_if_present(sha_path)
48 delete_if_present(temp_path)
51 def delete_if_present(path):
52 if os.path.isfile(path):
53 print("removing " + path)
57 def download(path, url, verbose):
58 print("downloading {} to {}".format(url, path))
59 # see http://serverfault.com/questions/301128/how-to-download
60 if sys.platform == 'win32':
61 run(["PowerShell.exe", "/nologo", "-Command",
62 "(New-Object System.Net.WebClient)"
63 ".DownloadFile('{}', '{}')".format(url, path)],
66 run(["curl", "-o", path, url], verbose=verbose)
69 def verify(path, sha_path, verbose):
70 print("verifying " + path)
71 with open(path, "rb") as f:
72 found = hashlib.sha256(f.read()).hexdigest()
73 with open(sha_path, "r") as f:
74 expected, _ = f.readline().split()
75 verified = found == expected
76 if not verified and verbose:
77 print("invalid checksum:\n"
79 " expected: {}".format(found, expected))
83 def unpack(tarball, dst, verbose=False, match=None):
84 print("extracting " + tarball)
85 fname = os.path.basename(tarball).replace(".tar.gz", "")
86 with contextlib.closing(tarfile.open(tarball)) as tar:
87 for p in tar.getnames():
90 name = p.replace(fname + "/", "", 1)
91 if match is not None and not name.startswith(match):
93 name = name[len(match) + 1:]
95 fp = os.path.join(dst, name)
97 print(" extracting " + p)
99 tp = os.path.join(dst, p)
100 if os.path.isdir(tp) and os.path.exists(fp):
103 shutil.rmtree(os.path.join(dst, fname))
105 def run(args, verbose=False):
107 print("running: " + ' '.join(args))
109 # Use Popen here instead of call() as it apparently allows powershell on
110 # Windows to not lock up waiting for input presumably.
111 ret = subprocess.Popen(args)
114 err = "failed to run: " + ' '.join(args)
116 raise RuntimeError(err)
119 def stage0_data(rust_root):
120 nightlies = os.path.join(rust_root, "src/stage0.txt")
122 with open(nightlies, 'r') as nightlies:
123 for line in nightlies:
124 line = line.rstrip() # Strip newline character, '\n'
125 if line.startswith("#") or line == '':
127 a, b = line.split(": ", 1)
131 def format_build_time(duration):
132 return str(datetime.timedelta(seconds=int(duration)))
135 class RustBuild(object):
136 def download_stage0(self):
137 cache_dst = os.path.join(self.build_dir, "cache")
138 rustc_cache = os.path.join(cache_dst, self.stage0_rustc_date())
139 cargo_cache = os.path.join(cache_dst, self.stage0_cargo_date())
140 if not os.path.exists(rustc_cache):
141 os.makedirs(rustc_cache)
142 if not os.path.exists(cargo_cache):
143 os.makedirs(cargo_cache)
145 if self.rustc().startswith(self.bin_root()) and \
146 (not os.path.exists(self.rustc()) or self.rustc_out_of_date()):
147 if os.path.exists(self.bin_root()):
148 shutil.rmtree(self.bin_root())
149 channel = self.stage0_rustc_channel()
150 filename = "rust-std-{}-{}.tar.gz".format(channel, self.build)
151 url = "https://static.rust-lang.org/dist/" + self.stage0_rustc_date()
152 tarball = os.path.join(rustc_cache, filename)
153 if not os.path.exists(tarball):
154 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
155 unpack(tarball, self.bin_root(),
156 match="rust-std-" + self.build,
157 verbose=self.verbose)
159 filename = "rustc-{}-{}.tar.gz".format(channel, self.build)
160 url = "https://static.rust-lang.org/dist/" + self.stage0_rustc_date()
161 tarball = os.path.join(rustc_cache, filename)
162 if not os.path.exists(tarball):
163 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
164 unpack(tarball, self.bin_root(), match="rustc", verbose=self.verbose)
165 with open(self.rustc_stamp(), 'w') as f:
166 f.write(self.stage0_rustc_date())
168 if self.cargo().startswith(self.bin_root()) and \
169 (not os.path.exists(self.cargo()) or self.cargo_out_of_date()):
170 channel = self.stage0_cargo_channel()
171 filename = "cargo-{}-{}.tar.gz".format(channel, self.build)
172 url = "https://static.rust-lang.org/cargo-dist/" + self.stage0_cargo_date()
173 tarball = os.path.join(cargo_cache, filename)
174 if not os.path.exists(tarball):
175 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
176 unpack(tarball, self.bin_root(), match="cargo", verbose=self.verbose)
177 with open(self.cargo_stamp(), 'w') as f:
178 f.write(self.stage0_cargo_date())
180 def stage0_cargo_date(self):
181 return self._cargo_date
183 def stage0_cargo_channel(self):
184 return self._cargo_channel
186 def stage0_rustc_date(self):
187 return self._rustc_date
189 def stage0_rustc_channel(self):
190 return self._rustc_channel
192 def rustc_stamp(self):
193 return os.path.join(self.bin_root(), '.rustc-stamp')
195 def cargo_stamp(self):
196 return os.path.join(self.bin_root(), '.cargo-stamp')
198 def rustc_out_of_date(self):
199 if not os.path.exists(self.rustc_stamp()) or self.clean:
201 with open(self.rustc_stamp(), 'r') as f:
202 return self.stage0_rustc_date() != f.read()
204 def cargo_out_of_date(self):
205 if not os.path.exists(self.cargo_stamp()) or self.clean:
207 with open(self.cargo_stamp(), 'r') as f:
208 return self.stage0_cargo_date() != f.read()
211 return os.path.join(self.build_dir, self.build, "stage0")
213 def get_toml(self, key):
214 for line in self.config_toml.splitlines():
215 if line.startswith(key + ' ='):
216 return self.get_string(line)
219 def get_mk(self, key):
220 for line in iter(self.config_mk.splitlines()):
221 if line.startswith(key):
222 return line[line.find(':=') + 2:].strip()
226 config = self.get_toml('cargo')
229 return os.path.join(self.bin_root(), "bin/cargo" + self.exe_suffix())
232 config = self.get_toml('rustc')
235 config = self.get_mk('CFG_LOCAL_RUST')
237 return config + '/bin/rustc' + self.exe_suffix()
238 return os.path.join(self.bin_root(), "bin/rustc" + self.exe_suffix())
240 def get_string(self, line):
241 start = line.find('"')
242 end = start + 1 + line[start + 1:].find('"')
243 return line[start + 1:end]
245 def exe_suffix(self):
246 if sys.platform == 'win32':
251 def build_bootstrap(self):
252 build_dir = os.path.join(self.build_dir, "bootstrap")
253 if self.clean and os.path.exists(build_dir):
254 shutil.rmtree(build_dir)
255 env = os.environ.copy()
256 env["CARGO_TARGET_DIR"] = build_dir
257 env["RUSTC"] = self.rustc()
258 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib")
259 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib")
260 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
261 os.pathsep + env["PATH"]
262 args = [self.cargo(), "build", "--manifest-path",
263 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
264 if self.use_vendored_sources:
265 args.append("--frozen")
268 def run(self, args, env):
269 proc = subprocess.Popen(args, env=env)
274 def build_triple(self):
275 default_encoding = sys.getdefaultencoding()
276 config = self.get_toml('build')
279 config = self.get_mk('CFG_BUILD')
283 ostype = subprocess.check_output(['uname', '-s']).strip().decode(default_encoding)
284 cputype = subprocess.check_output(['uname', '-m']).strip().decode(default_encoding)
285 except (subprocess.CalledProcessError, WindowsError):
286 if sys.platform == 'win32':
287 return 'x86_64-pc-windows-msvc'
288 err = "uname not found"
293 # Darwin's `uname -s` lies and always returns i386. We have to use
295 if ostype == 'Darwin' and cputype == 'i686':
296 args = ['sysctl', 'hw.optional.x86_64']
297 sysctl = subprocess.check_output(args).decode(default_encoding)
301 # The goal here is to come up with the same triple as LLVM would,
302 # at least for the subset of platforms we're willing to target.
303 if ostype == 'Linux':
304 ostype = 'unknown-linux-gnu'
305 elif ostype == 'FreeBSD':
306 ostype = 'unknown-freebsd'
307 elif ostype == 'DragonFly':
308 ostype = 'unknown-dragonfly'
309 elif ostype == 'Bitrig':
310 ostype = 'unknown-bitrig'
311 elif ostype == 'OpenBSD':
312 ostype = 'unknown-openbsd'
313 elif ostype == 'NetBSD':
314 ostype = 'unknown-netbsd'
315 elif ostype == 'Darwin':
316 ostype = 'apple-darwin'
317 elif ostype.startswith('MINGW'):
318 # msys' `uname` does not print gcc configuration, but prints msys
319 # configuration. so we cannot believe `uname -m`:
320 # msys1 is always i686 and msys2 is always x86_64.
321 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
323 ostype = 'pc-windows-gnu'
325 if os.environ.get('MSYSTEM') == 'MINGW64':
327 elif ostype.startswith('MSYS'):
328 ostype = 'pc-windows-gnu'
329 elif ostype.startswith('CYGWIN_NT'):
331 if ostype.endswith('WOW64'):
333 ostype = 'pc-windows-gnu'
335 err = "unknown OS type: " + ostype
337 raise ValueError(err)
340 if cputype in {'i386', 'i486', 'i686', 'i786', 'x86'}:
342 elif cputype in {'xscale', 'arm'}:
344 elif cputype == 'armv7l':
347 elif cputype == 'aarch64':
349 elif cputype in {'powerpc', 'ppc', 'ppc64'}:
351 elif cputype in {'amd64', 'x86_64', 'x86-64', 'x64'}:
354 err = "unknown cpu type: " + cputype
356 raise ValueError(err)
359 return "{}-{}".format(cputype, ostype)
362 parser = argparse.ArgumentParser(description='Build rust')
363 parser.add_argument('--config')
364 parser.add_argument('--clean', action='store_true')
365 parser.add_argument('-v', '--verbose', action='store_true')
367 args = [a for a in sys.argv if a != '-h' and a != '--help']
368 args, _ = parser.parse_known_args(args)
370 # Configure initial bootstrap
374 rb.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
375 rb.build_dir = os.path.join(os.getcwd(), "build")
376 rb.verbose = args.verbose
377 rb.clean = args.clean
380 with open(args.config or 'config.toml') as config:
381 rb.config_toml = config.read()
385 rb.config_mk = open('config.mk').read()
389 rb.use_vendored_sources = '\nvendor = true' in rb.config_toml or \
390 'CFG_ENABLE_VENDOR' in rb.config_mk
392 if rb.use_vendored_sources:
393 if not os.path.exists('.cargo'):
394 os.makedirs('.cargo')
395 f = open('.cargo/config','w')
398 replace-with = 'vendored-sources'
399 registry = 'https://example.com'
401 [source.vendored-sources]
402 directory = '{}/src/vendor'
403 """.format(rb.rust_root))
406 if os.path.exists('.cargo'):
407 shutil.rmtree('.cargo')
408 data = stage0_data(rb.rust_root)
409 rb._rustc_channel, rb._rustc_date = data['rustc'].split('-', 1)
410 rb._cargo_channel, rb._cargo_date = data['cargo'].split('-', 1)
414 # Fetch/build the bootstrap
415 rb.build = rb.build_triple()
422 args = [os.path.join(rb.build_dir, "bootstrap/debug/bootstrap")]
423 args.extend(sys.argv[1:])
424 env = os.environ.copy()
425 env["BUILD"] = rb.build
426 env["SRC"] = rb.rust_root
427 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
432 print("Build completed in %s" % format_build_time(end_time - start_time))
434 if __name__ == '__main__':