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, False, verbose)
34 if os.path.exists(path):
35 if verify(path, sha_path, False):
37 print("using already-download file " + path)
41 print("ignoring already-download file " + path + " due to failed verification")
43 download(temp_path, url, True, verbose)
44 if not verify(temp_path, sha_path, verbose):
45 raise RuntimeError("failed verification")
47 print("moving {} to {}".format(temp_path, path))
48 shutil.move(temp_path, path)
50 delete_if_present(sha_path, verbose)
51 delete_if_present(temp_path, verbose)
54 def delete_if_present(path, verbose):
55 if os.path.isfile(path):
57 print("removing " + path)
61 def download(path, url, probably_big, verbose):
62 if probably_big or verbose:
63 print("downloading {}".format(url))
64 # see http://serverfault.com/questions/301128/how-to-download
65 if sys.platform == 'win32':
66 run(["PowerShell.exe", "/nologo", "-Command",
67 "(New-Object System.Net.WebClient)"
68 ".DownloadFile('{}', '{}')".format(url, path)],
71 if probably_big or verbose:
75 run(["curl", option, "--retry", "3", "-Sf", "-o", path, url], verbose=verbose)
78 def verify(path, sha_path, verbose):
80 print("verifying " + path)
81 with open(path, "rb") as f:
82 found = hashlib.sha256(f.read()).hexdigest()
83 with open(sha_path, "r") as f:
84 expected = f.readline().split()[0]
85 verified = found == expected
87 print("invalid checksum:\n"
89 " expected: {}".format(found, expected))
93 def unpack(tarball, dst, verbose=False, match=None):
94 print("extracting " + tarball)
95 fname = os.path.basename(tarball).replace(".tar.gz", "")
96 with contextlib.closing(tarfile.open(tarball)) as tar:
97 for p in tar.getnames():
100 name = p.replace(fname + "/", "", 1)
101 if match is not None and not name.startswith(match):
103 name = name[len(match) + 1:]
105 fp = os.path.join(dst, name)
107 print(" extracting " + p)
109 tp = os.path.join(dst, p)
110 if os.path.isdir(tp) and os.path.exists(fp):
113 shutil.rmtree(os.path.join(dst, fname))
115 def run(args, verbose=False):
117 print("running: " + ' '.join(args))
119 # Use Popen here instead of call() as it apparently allows powershell on
120 # Windows to not lock up waiting for input presumably.
121 ret = subprocess.Popen(args)
124 err = "failed to run: " + ' '.join(args)
126 raise RuntimeError(err)
129 def stage0_data(rust_root):
130 nightlies = os.path.join(rust_root, "src/stage0.txt")
132 with open(nightlies, 'r') as nightlies:
133 for line in nightlies:
134 line = line.rstrip() # Strip newline character, '\n'
135 if line.startswith("#") or line == '':
137 a, b = line.split(": ", 1)
141 def format_build_time(duration):
142 return str(datetime.timedelta(seconds=int(duration)))
145 class RustBuild(object):
146 def download_stage0(self):
147 cache_dst = os.path.join(self.build_dir, "cache")
148 rustc_cache = os.path.join(cache_dst, self.stage0_rustc_date())
149 cargo_cache = os.path.join(cache_dst, self.stage0_cargo_rev())
150 if not os.path.exists(rustc_cache):
151 os.makedirs(rustc_cache)
152 if not os.path.exists(cargo_cache):
153 os.makedirs(cargo_cache)
155 if self.rustc().startswith(self.bin_root()) and \
156 (not os.path.exists(self.rustc()) or self.rustc_out_of_date()):
157 self.print_what_it_means_to_bootstrap()
158 if os.path.exists(self.bin_root()):
159 shutil.rmtree(self.bin_root())
160 channel = self.stage0_rustc_channel()
161 filename = "rust-std-{}-{}.tar.gz".format(channel, self.build)
162 url = "https://static.rust-lang.org/dist/" + self.stage0_rustc_date()
163 tarball = os.path.join(rustc_cache, filename)
164 if not os.path.exists(tarball):
165 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
166 unpack(tarball, self.bin_root(),
167 match="rust-std-" + self.build,
168 verbose=self.verbose)
170 filename = "rustc-{}-{}.tar.gz".format(channel, self.build)
171 url = "https://static.rust-lang.org/dist/" + self.stage0_rustc_date()
172 tarball = os.path.join(rustc_cache, filename)
173 if not os.path.exists(tarball):
174 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
175 unpack(tarball, self.bin_root(), match="rustc", verbose=self.verbose)
176 with open(self.rustc_stamp(), 'w') as f:
177 f.write(self.stage0_rustc_date())
179 if self.cargo().startswith(self.bin_root()) and \
180 (not os.path.exists(self.cargo()) or self.cargo_out_of_date()):
181 self.print_what_it_means_to_bootstrap()
182 filename = "cargo-nightly-{}.tar.gz".format(self.build)
183 url = "https://s3.amazonaws.com/rust-lang-ci/cargo-builds/" + self.stage0_cargo_rev()
184 tarball = os.path.join(cargo_cache, filename)
185 if not os.path.exists(tarball):
186 get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
187 unpack(tarball, self.bin_root(), match="cargo", verbose=self.verbose)
188 with open(self.cargo_stamp(), 'w') as f:
189 f.write(self.stage0_cargo_rev())
191 def stage0_cargo_rev(self):
192 return self._cargo_rev
194 def stage0_rustc_date(self):
195 return self._rustc_date
197 def stage0_rustc_channel(self):
198 return self._rustc_channel
200 def rustc_stamp(self):
201 return os.path.join(self.bin_root(), '.rustc-stamp')
203 def cargo_stamp(self):
204 return os.path.join(self.bin_root(), '.cargo-stamp')
206 def rustc_out_of_date(self):
207 if not os.path.exists(self.rustc_stamp()) or self.clean:
209 with open(self.rustc_stamp(), 'r') as f:
210 return self.stage0_rustc_date() != f.read()
212 def cargo_out_of_date(self):
213 if not os.path.exists(self.cargo_stamp()) or self.clean:
215 with open(self.cargo_stamp(), 'r') as f:
216 return self.stage0_cargo_rev() != f.read()
219 return os.path.join(self.build_dir, self.build, "stage0")
221 def get_toml(self, key):
222 for line in self.config_toml.splitlines():
223 if line.startswith(key + ' ='):
224 return self.get_string(line)
227 def get_mk(self, key):
228 for line in iter(self.config_mk.splitlines()):
229 if line.startswith(key):
230 return line[line.find(':=') + 2:].strip()
234 config = self.get_toml('cargo')
237 config = self.get_mk('CFG_LOCAL_RUST_ROOT')
239 return config + '/bin/cargo' + self.exe_suffix()
240 return os.path.join(self.bin_root(), "bin/cargo" + self.exe_suffix())
243 config = self.get_toml('rustc')
246 config = self.get_mk('CFG_LOCAL_RUST_ROOT')
248 return config + '/bin/rustc' + self.exe_suffix()
249 return os.path.join(self.bin_root(), "bin/rustc" + self.exe_suffix())
251 def get_string(self, line):
252 start = line.find('"')
253 end = start + 1 + line[start + 1:].find('"')
254 return line[start + 1:end]
256 def exe_suffix(self):
257 if sys.platform == 'win32':
262 def print_what_it_means_to_bootstrap(self):
263 if hasattr(self, 'printed'):
266 if os.path.exists(self.bootstrap_binary()):
268 if not '--help' in sys.argv or len(sys.argv) == 1:
271 print('info: the build system for Rust is written in Rust, so this')
272 print(' script is now going to download a stage0 rust compiler')
273 print(' and then compile the build system itself')
275 print('info: in the meantime you can read more about rustbuild at')
276 print(' src/bootstrap/README.md before the download finishes')
278 def bootstrap_binary(self):
279 return os.path.join(self.build_dir, "bootstrap/debug/bootstrap")
281 def build_bootstrap(self):
282 self.print_what_it_means_to_bootstrap()
283 build_dir = os.path.join(self.build_dir, "bootstrap")
284 if self.clean and os.path.exists(build_dir):
285 shutil.rmtree(build_dir)
286 env = os.environ.copy()
287 env["CARGO_TARGET_DIR"] = build_dir
288 env["RUSTC"] = self.rustc()
289 env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib")
290 env["DYLD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib")
291 env["PATH"] = os.path.join(self.bin_root(), "bin") + \
292 os.pathsep + env["PATH"]
293 if not os.path.isfile(self.cargo()):
294 raise Exception("no cargo executable found at `%s`" % self.cargo())
295 args = [self.cargo(), "build", "--manifest-path",
296 os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")]
297 if self.use_vendored_sources:
298 args.append("--frozen")
301 def run(self, args, env):
302 proc = subprocess.Popen(args, env=env)
307 def build_triple(self):
308 default_encoding = sys.getdefaultencoding()
309 config = self.get_toml('build')
312 config = self.get_mk('CFG_BUILD')
316 ostype = subprocess.check_output(['uname', '-s']).strip().decode(default_encoding)
317 cputype = subprocess.check_output(['uname', '-m']).strip().decode(default_encoding)
318 except (subprocess.CalledProcessError, WindowsError):
319 if sys.platform == 'win32':
320 return 'x86_64-pc-windows-msvc'
321 err = "uname not found"
326 # Darwin's `uname -s` lies and always returns i386. We have to use
328 if ostype == 'Darwin' and cputype == 'i686':
329 args = ['sysctl', 'hw.optional.x86_64']
330 sysctl = subprocess.check_output(args).decode(default_encoding)
334 # The goal here is to come up with the same triple as LLVM would,
335 # at least for the subset of platforms we're willing to target.
336 if ostype == 'Linux':
337 ostype = 'unknown-linux-gnu'
338 elif ostype == 'FreeBSD':
339 ostype = 'unknown-freebsd'
340 elif ostype == 'DragonFly':
341 ostype = 'unknown-dragonfly'
342 elif ostype == 'Bitrig':
343 ostype = 'unknown-bitrig'
344 elif ostype == 'OpenBSD':
345 ostype = 'unknown-openbsd'
346 elif ostype == 'NetBSD':
347 ostype = 'unknown-netbsd'
348 elif ostype == 'Darwin':
349 ostype = 'apple-darwin'
350 elif ostype.startswith('MINGW'):
351 # msys' `uname` does not print gcc configuration, but prints msys
352 # configuration. so we cannot believe `uname -m`:
353 # msys1 is always i686 and msys2 is always x86_64.
354 # instead, msys defines $MSYSTEM which is MINGW32 on i686 and
356 ostype = 'pc-windows-gnu'
358 if os.environ.get('MSYSTEM') == 'MINGW64':
360 elif ostype.startswith('MSYS'):
361 ostype = 'pc-windows-gnu'
362 elif ostype.startswith('CYGWIN_NT'):
364 if ostype.endswith('WOW64'):
366 ostype = 'pc-windows-gnu'
368 err = "unknown OS type: " + ostype
370 raise ValueError(err)
373 if cputype in {'i386', 'i486', 'i686', 'i786', 'x86'}:
375 elif cputype in {'xscale', 'arm'}:
377 elif cputype == 'armv7l':
380 elif cputype == 'aarch64':
382 elif cputype == 'mips':
383 if sys.byteorder == 'big':
385 elif sys.byteorder == 'little':
388 raise ValueError('unknown byteorder: ' + sys.byteorder)
389 elif cputype == 'mips64':
390 if sys.byteorder == 'big':
392 elif sys.byteorder == 'little':
395 raise ValueError('unknown byteorder: ' + sys.byteorder)
396 # only the n64 ABI is supported, indicate it
398 elif cputype in {'powerpc', 'ppc', 'ppc64'}:
400 elif cputype in {'amd64', 'x86_64', 'x86-64', 'x64'}:
403 err = "unknown cpu type: " + cputype
405 raise ValueError(err)
408 return "{}-{}".format(cputype, ostype)
411 parser = argparse.ArgumentParser(description='Build rust')
412 parser.add_argument('--config')
413 parser.add_argument('--clean', action='store_true')
414 parser.add_argument('-v', '--verbose', action='store_true')
416 args = [a for a in sys.argv if a != '-h' and a != '--help']
417 args, _ = parser.parse_known_args(args)
419 # Configure initial bootstrap
423 rb.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
424 rb.build_dir = os.path.join(os.getcwd(), "build")
425 rb.verbose = args.verbose
426 rb.clean = args.clean
429 with open(args.config or 'config.toml') as config:
430 rb.config_toml = config.read()
434 rb.config_mk = open('config.mk').read()
438 rb.use_vendored_sources = '\nvendor = true' in rb.config_toml or \
439 'CFG_ENABLE_VENDOR' in rb.config_mk
441 if 'SUDO_USER' in os.environ and not rb.use_vendored_sources:
442 if os.environ.get('USER') != os.environ['SUDO_USER']:
443 rb.use_vendored_sources = True
444 print('info: looks like you are running this command under `sudo`')
445 print(' and so in order to preserve your $HOME this will now')
446 print(' use vendored sources by default. Note that if this')
447 print(' does not work you should run a normal build first')
448 print(' before running a command like `sudo make intall`')
450 if rb.use_vendored_sources:
451 if not os.path.exists('.cargo'):
452 os.makedirs('.cargo')
453 with open('.cargo/config','w') as f:
456 replace-with = 'vendored-sources'
457 registry = 'https://example.com'
459 [source.vendored-sources]
460 directory = '{}/src/vendor'
461 """.format(rb.rust_root))
463 if os.path.exists('.cargo'):
464 shutil.rmtree('.cargo')
466 data = stage0_data(rb.rust_root)
467 rb._rustc_channel, rb._rustc_date = data['rustc'].split('-', 1)
468 rb._cargo_rev = data['cargo']
472 # Fetch/build the bootstrap
473 rb.build = rb.build_triple()
480 args = [rb.bootstrap_binary()]
481 args.extend(sys.argv[1:])
482 env = os.environ.copy()
483 env["BUILD"] = rb.build
484 env["SRC"] = rb.rust_root
485 env["BOOTSTRAP_PARENT_ID"] = str(os.getpid())
490 print("Build completed in %s" % format_build_time(end_time - start_time))
492 if __name__ == '__main__':