+++ /dev/null
-#!/usr/bin/env python
-"""
-NOTE: This script has not been used for a very long time and very likely won't
-work. Please read the code before attempting to run it and hoping that "just
-fixing the errors" will work. -- infinity0
-
-About
-=====
-
-This python script is design to do the bare minimum to compile and link the
-Cargo binary for the purposes of bootstrapping itself on a new platform for
-which cross-compiling isn't possible. I wrote this specifically to bootstrap
-Cargo on [Bitrig](https://bitrig.org). Bitrig is a fork of OpenBSD that uses
-clang/clang++ and other BSD licensed tools instead of GNU licensed software.
-Cross compiling from another platform is extremely difficult because of the
-alternative toolchain Bitrig uses.
-
-With this script, all that should be necessary to run this is a working Rust
-toolchain, Python, and Git.
-
-This script will not set up a full cargo cache or anything. It works by
-cloning the cargo index and then starting with the cargo dependencies, it
-recursively builds the dependency tree. Once it has the dependency tree, it
-starts with the leaves of the tree, doing a breadth first traversal and for
-each dependency, it clones the repo, sets the repo's head to the correct
-revision and then executes the build command specified in the cargo config.
-
-This bootstrap script uses a temporary directory to store the built dependency
-libraries and uses that as a link path when linking dependencies and the
-cargo binary. The goal is to create a statically linked cargo binary that is
-capable of being used as a "local cargo" when running the main cargo Makefiles.
-
-Dependencies
-============
-
-* pytoml -- used for parsing toml files.
- https://github.com/avakar/pytoml
-
-* dulwich -- used for working with git repos.
- https://git.samba.org/?p=jelmer/dulwich.git;a=summary
-
-Both can be installed via the pip tool:
-
-```sh
-sudo pip install pytoml dulwich
-```
-
-Command Line Options
-====================
-
-```
---cargo-root <path> specify the path to the cargo repo root.
---target-dir <path> specify the location to store build results.
---crate-index <path> path to where crates.io index shoudl be cloned
---no-clone don't clone crates.io index, --crate-index must point to existing clone.
---no-clean don't remove the folders created during bootstrapping.
---download only download the crates needed to bootstrap cargo.
---graph output dot format graph of dependencies.
---target <triple> build target: e.g. x86_64-unknown-bitrig
---host <triple> host machine: e.g. x86_64-unknown-linux-gnu
---urls-file <file> file to write crate URLs to
---blacklist <crates> list of blacklisted crates to skip
---include-optional <crates> list of optional crates to include
---patchdir <dir> directory containing patches to apply to crates after fetching them
---save-crate if set, save .crate file when downloading
-```
-
-The `--cargo-root` option defaults to the current directory if unspecified. The
-target directory defaults to Python equivilent of `mktemp -d` if unspecified.
-The `--crate-index` option specifies where the crates.io index will be cloned. Or,
-if you already have a clone of the index, the crates index should point there
-and you should also specify `--no-clone`. The `--target` option is used to
-specify which platform you are bootstrapping for. The `--host` option defaults
-to the value of the `--target` option when not specified.
-
-Examples
-========
-
-To bootstrap Cargo on (Bitrig)[https://bitrig.org] I followed these steps:
-
-* Cloned this [bootstrap script repo](https://github.com/dhuseby/cargo-bootstra)
-to `/tmp/bootstrap`.
-* Cloned the [crates.io index](https://github.com/rust-lang/crates.io-index)
-to `/tmp/index`.
-* Created a target folder, `/tmp/out`, for the output.
-* Cloned the (Cargo)[https://github.com/rust-lang/cargo] repo to `/tmp/cargo`.
-* Copied the bootstrap.py script to the cargo repo root.
-* Ran the bootstrap.py script like so:
-```sh
-./bootstrap.py --crate-index /tmp/index --target-dir /tmp/out --no-clone --no-clean --target x86_64-unknown-bitrig
-```
-
-After the script completed, there is a Cargo executable named `cargo-0_2_0` in
-`/tmp/out`. That executable can then be used to bootstrap Cargo from source by
-specifying it as the `--local-cargo` option to Cargo's `./configure` script.
-"""
-
-import argparse
-import cStringIO
-import hashlib
-import inspect
-import json
-import os
-import re
-import shutil
-import subprocess
-import sys
-import tarfile
-import tempfile
-import urlparse
-import socket
-# In Debian crates are already downloaded when we bootstrap cargo.
-# import requests
-import pytoml as toml
-import dulwich.porcelain as git
-from glob import glob
-
-
-TARGET = None
-HOST = None
-GRAPH = None
-URLS_FILE = None
-CRATE_CACHE = None
-CRATES_INDEX = 'git://github.com/rust-lang/crates.io-index.git'
-CARGO_REPO = 'git://github.com/rust-lang/cargo.git'
-CRATE_API_DL = 'https://crates.io/api/v1/crates/%s/%s/download'
-SV_RANGE = re.compile(r'^(?P<op>(?:\<=|\>=|=|\<|\>|\^|\~))?\s*'
- r'(?P<major>(?:\*|0|[1-9][0-9]*))'
- r'(\.(?P<minor>(?:\*|0|[1-9][0-9]*)))?'
- r'(\.(?P<patch>(?:\*|0|[1-9][0-9]*)))?'
- r'(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'
- r'(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$')
-SEMVER = re.compile(r'^\s*(?P<major>(?:0|[1-9][0-9]*))'
- r'(\.(?P<minor>(?:0|[1-9][0-9]*)))?'
- r'(\.(?P<patch>(?:0|[1-9][0-9]*)))?'
- r'(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?'
- r'(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$')
-BSCRIPT = re.compile(r'^cargo:(?P<key>([^\s=]+))(=(?P<value>.+))?$')
-BNAME = re.compile('^(lib)?(?P<name>([^_]+))(_.*)?$')
-BUILT = {}
-CRATES = {}
-CVER = re.compile("-([^-]+)$")
-UNRESOLVED = []
-PFX = []
-BLACKLIST = []
-INCLUDE_OPTIONAL = []
-
-def dbgCtx(f):
- def do_dbg(self, *cargs):
- PFX.append(self.name())
- ret = f(self, *cargs)
- PFX.pop()
- return ret
- return do_dbg
-
-def dbg(s):
- print '%s: %s' % (':'.join(PFX), s)
-
-
-class PreRelease(object):
-
- def __init__(self, pr):
- self._container = []
- if pr is not None:
- self._container += str(pr).split('.')
-
- def __str__(self):
- return '.'.join(self._container)
-
- def __repr__(self):
- return self._container
-
- def __getitem__(self, key):
- return self._container[key]
-
- def __len__(self):
- return len(self._container)
-
- def __gt__(self, rhs):
- return not ((self < rhs) or (self == rhs))
-
- def __ge__(self, rhs):
- return not (self < rhs)
-
- def __le__(self, rhs):
- return not (self > rhs)
-
- def __eq__(self, rhs):
- return self._container == rhs._container
-
- def __ne__(self, rhs):
- return not (self == rhs)
-
- def __lt__(self, rhs):
- if self == rhs:
- return False
-
- # not having a pre-release is higher precedence
- if len(self) == 0:
- if len(rhs) == 0:
- return False
- else:
- # 1.0.0 > 1.0.0-alpha
- return False
- else:
- if len(rhs) is None:
- # 1.0.0-alpha < 1.0.0
- return True
-
- # if both have one, then longer pre-releases are higher precedence
- if len(self) > len(rhs):
- # 1.0.0-alpha.1 > 1.0.0-alpha
- return False
- elif len(self) < len(rhs):
- # 1.0.0-alpha < 1.0.0-alpha.1
- return True
-
- # if both have the same length pre-release, must check each piece
- # numeric sub-parts have lower precedence than non-numeric sub-parts
- # non-numeric sub-parts are compared lexically in ASCII sort order
- for l,r in zip(self, rhs):
- if l.isdigit():
- if r.isdigit():
- if int(l) < int(r):
- # 2 > 1
- return True
- elif int(l) > int(r):
- # 1 < 2
- return False
- else:
- # 1 == 1
- continue
- else:
- # 1 < 'foo'
- return True
- else:
- if r.isdigit():
- # 'foo' > 1
- return False
-
- # both are non-numeric
- if l < r:
- return True
- elif l > r:
- return False
-
- raise RuntimeError('PreRelease __lt__ failed')
-
-
-class Semver(dict):
-
- def __init__(self, sv):
- match = SEMVER.match(str(sv))
- if match is None:
- raise ValueError('%s is not a valid semver string' % sv)
-
- self._input = sv
- self.update(match.groupdict())
- self.prerelease = PreRelease(self['prerelease'])
-
- def __str__(self):
- major, minor, patch, prerelease, build = self.parts_raw()
- s = ''
- if major is None:
- s += '0'
- else:
- s += major
- s += '.'
- if minor is None:
- s += '0'
- else:
- s += minor
- s += '.'
- if patch is None:
- s += '0'
- else:
- s += patch
- if len(self.prerelease):
- s += '-' + str(self.prerelease)
- if build is not None:
- s += '+' + build
- return s
-
- def __hash__(self):
- return hash(str(self))
-
- def as_range(self):
- return SemverRange('=%s' % self)
-
- def parts(self):
- major, minor, patch, prerelease, build = self.parts_raw()
- if major is None:
- major = '0'
- if minor is None:
- minor = '0'
- if patch is None:
- patch = '0'
- return (int(major),int(minor),int(patch),prerelease,build)
-
- def parts_raw(self):
- return (self['major'],self['minor'],self['patch'],self['prerelease'],self['build'])
-
- def __lt__(self, rhs):
- lmaj,lmin,lpat,lpre,_ = self.parts()
- rmaj,rmin,rpat,rpre,_ = rhs.parts()
- if lmaj < rmaj:
- return True
- if lmaj > rmaj:
- return False
- if lmin < rmin:
- return True
- if lmin > rmin:
- return False
- if lpat < rpat:
- return True
- if lpat > rpat:
- return False
- if lpre is not None and rpre is None:
- return True
- if lpre is not None and rpre is not None:
- if self.prerelease < rhs.prerelease:
- return True
- return False
-
- def __le__(self, rhs):
- return not (self > rhs)
-
- def __gt__(self, rhs):
- return not ((self < rhs) or (self == rhs))
-
- def __ge__(self, rhs):
- return not (self < rhs)
-
- def __eq__(self, rhs):
- # build metadata is only considered for equality
- lmaj,lmin,lpat,lpre,lbld = self.parts()
- rmaj,rmin,rpat,rpre,rbld = rhs.parts()
- return lmaj == rmaj and \
- lmin == rmin and \
- lpat == rpat and \
- lpre == rpre and \
- lbld == rbld
-
- def __ne__(self, rhs):
- return not (self == rhs)
-
-
-class SemverRange(object):
-
- def __init__(self, sv):
- self._input = sv
- self._lower = None
- self._upper = None
- self._op = None
- self._semver = None
-
- sv = str(sv)
- svs = [x.strip() for x in sv.split(',')]
-
- if len(svs) > 1:
- self._op = '^'
- for sr in svs:
- rang = SemverRange(sr)
- if rang.lower() is not None:
- if self._lower is None or rang.lower() < self._lower:
- self._lower = rang.lower()
- if rang.upper() is not None:
- if self._upper is None or rang.upper() > self._upper:
- self._upper = rang.upper()
- op, semver = rang.op_semver()
- if semver is not None:
- if op == '>=':
- if self._lower is None or semver < self._lower:
- self._lower = semver
- if op == '<':
- if self._upper is None or semver > self._upper:
- self._upper = semver
- return
-
- match = SV_RANGE.match(sv)
- if match is None:
- raise ValueError('%s is not a valid semver range string' % sv)
-
- svm = match.groupdict()
- op, major, minor, patch, prerelease, build = svm['op'], svm['major'], svm['minor'], svm['patch'], svm['prerelease'], svm['build']
- prerelease = PreRelease(prerelease)
-
- # fix up the op
- if op is None:
- if major == '*' or minor == '*' or patch == '*':
- op = '*'
- else:
- # if no op was specified and there are no wildcards, then op
- # defaults to '^'
- op = '^'
- else:
- self._semver = Semver(sv[len(op):])
-
- if op not in ('<=', '>=', '<', '>', '=', '^', '~', '*'):
- raise ValueError('%s is not a valid semver operator' % op)
-
- self._op = op
-
- # lower bound
- def find_lower():
- if op in ('<=', '<', '=', '>', '>='):
- return None
-
- if op == '*':
- # wildcards specify a range
- if major == '*':
- return Semver('0.0.0')
- elif minor == '*':
- return Semver(major + '.0.0')
- elif patch == '*':
- return Semver(major + '.' + minor + '.0')
- elif op == '^':
- # caret specifies a range
- if patch is None:
- if minor is None:
- # ^0 means >=0.0.0 and <1.0.0
- return Semver(major + '.0.0')
- else:
- # ^0.0 means >=0.0.0 and <0.1.0
- return Semver(major + '.' + minor + '.0')
- else:
- # ^0.0.1 means >=0.0.1 and <0.0.2
- # ^0.1.2 means >=0.1.2 and <0.2.0
- # ^1.2.3 means >=1.2.3 and <2.0.0
- if int(major) == 0:
- if int(minor) == 0:
- # ^0.0.1
- return Semver('0.0.' + patch)
- else:
- # ^0.1.2
- return Semver('0.' + minor + '.' + patch)
- else:
- # ^1.2.3
- return Semver(major + '.' + minor + '.' + patch)
- elif op == '~':
- # tilde specifies a minimal range
- if patch is None:
- if minor is None:
- # ~0 means >=0.0.0 and <1.0.0
- return Semver(major + '.0.0')
- else:
- # ~0.0 means >=0.0.0 and <0.1.0
- return Semver(major + '.' + minor + '.0')
- else:
- # ~0.0.1 means >=0.0.1 and <0.1.0
- # ~0.1.2 means >=0.1.2 and <0.2.0
- # ~1.2.3 means >=1.2.3 and <1.3.0
- return Semver(major + '.' + minor + '.' + patch)
-
- raise RuntimeError('No lower bound')
- self._lower = find_lower()
-
- def find_upper():
- if op in ('<=', '<', '=', '>', '>='):
- return None
-
- if op == '*':
- # wildcards specify a range
- if major == '*':
- return None
- elif minor == '*':
- return Semver(str(int(major) + 1) + '.0.0')
- elif patch == '*':
- return Semver(major + '.' + str(int(minor) + 1) + '.0')
- elif op == '^':
- # caret specifies a range
- if patch is None:
- if minor is None:
- # ^0 means >=0.0.0 and <1.0.0
- return Semver(str(int(major) + 1) + '.0.0')
- else:
- # ^0.0 means >=0.0.0 and <0.1.0
- return Semver(major + '.' + str(int(minor) + 1) + '.0')
- else:
- # ^0.0.1 means >=0.0.1 and <0.0.2
- # ^0.1.2 means >=0.1.2 and <0.2.0
- # ^1.2.3 means >=1.2.3 and <2.0.0
- if int(major) == 0:
- if int(minor) == 0:
- # ^0.0.1
- return Semver('0.0.' + str(int(patch) + 1))
- else:
- # ^0.1.2
- return Semver('0.' + str(int(minor) + 1) + '.0')
- else:
- # ^1.2.3
- return Semver(str(int(major) + 1) + '.0.0')
- elif op == '~':
- # tilde specifies a minimal range
- if patch is None:
- if minor is None:
- # ~0 means >=0.0.0 and <1.0.0
- return Semver(str(int(major) + 1) + '.0.0')
- else:
- # ~0.0 means >=0.0.0 and <0.1.0
- return Semver(major + '.' + str(int(minor) + 1) + '.0')
- else:
- # ~0.0.1 means >=0.0.1 and <0.1.0
- # ~0.1.2 means >=0.1.2 and <0.2.0
- # ~1.2.3 means >=1.2.3 and <1.3.0
- return Semver(major + '.' + str(int(minor) + 1) + '.0')
-
- raise RuntimeError('No upper bound')
- self._upper = find_upper()
-
- def __repr__(self):
- return "SemverRange(%s, op=%s, semver=%s, lower=%s, upper=%s)" % (repr(self._input), self._op, self._semver, self._lower, self._upper)
-
- def __str__(self):
- return self._input
-
- def lower(self):
- return self._lower
-
- def upper(self):
- return self._upper
-
- def op_semver(self):
- return self._op, self._semver
-
- def compare(self, sv):
- if not isinstance(sv, Semver):
- sv = Semver(sv)
-
- op = self._op
- if op == '*':
- if self._semver is not None and self._semver['major'] == '*':
- return sv >= Semver('0.0.0')
- if self._lower is not None and sv < self._lower:
- return False
- if self._upper is not None and sv >= self._upper:
- return False
- return True
- elif op == '^':
- return (sv >= self._lower) and (sv < self._upper)
- elif op == '~':
- return (sv >= self._lower) and (sv < self._upper)
- elif op == '<=':
- return sv <= self._semver
- elif op == '>=':
- return sv >= self._semver
- elif op == '<':
- return sv < self._semver
- elif op == '>':
- return sv > self._semver
- elif op == '=':
- return sv == self._semver
-
- raise RuntimeError('Semver comparison failed to find a matching op')
-
-
-def test_semver():
- """
- Tests for Semver parsing. Run using py.test: py.test bootstrap.py
- """
- assert str(Semver("1")) == "1.0.0"
- assert str(Semver("1.1")) == "1.1.0"
- assert str(Semver("1.1.1")) == "1.1.1"
- assert str(Semver("1.1.1-alpha")) == "1.1.1-alpha"
- assert str(Semver("1.1.1-alpha.1")) == "1.1.1-alpha.1"
- assert str(Semver("1.1.1-alpha+beta")) == "1.1.1-alpha+beta"
- assert str(Semver("1.1.1-alpha+beta.1")) == "1.1.1-alpha+beta.1"
-
-def test_semver_eq():
- assert Semver("1") == Semver("1.0.0")
- assert Semver("1.1") == Semver("1.1.0")
- assert Semver("1.1.1") == Semver("1.1.1")
- assert Semver("1.1.1-alpha") == Semver("1.1.1-alpha")
- assert Semver("1.1.1-alpha.1") == Semver("1.1.1-alpha.1")
- assert Semver("1.1.1-alpha+beta") == Semver("1.1.1-alpha+beta")
- assert Semver("1.1.1-alpha.1+beta") == Semver("1.1.1-alpha.1+beta")
- assert Semver("1.1.1-alpha.1+beta.1") == Semver("1.1.1-alpha.1+beta.1")
-
-def test_semver_comparison():
- assert Semver("1") < Semver("2.0.0")
- assert Semver("1.1") < Semver("1.2.0")
- assert Semver("1.1.1") < Semver("1.1.2")
- assert Semver("1.1.1-alpha") < Semver("1.1.1")
- assert Semver("1.1.1-alpha") < Semver("1.1.1-beta")
- assert Semver("1.1.1-alpha") < Semver("1.1.1-beta")
- assert Semver("1.1.1-alpha") < Semver("1.1.1-alpha.1")
- assert Semver("1.1.1-alpha.1") < Semver("1.1.1-alpha.2")
- assert Semver("1.1.1-alpha+beta") < Semver("1.1.1+beta")
- assert Semver("1.1.1-alpha+beta") < Semver("1.1.1-beta+beta")
- assert Semver("1.1.1-alpha+beta") < Semver("1.1.1-beta+beta")
- assert Semver("1.1.1-alpha+beta") < Semver("1.1.1-alpha.1+beta")
- assert Semver("1.1.1-alpha.1+beta") < Semver("1.1.1-alpha.2+beta")
- assert Semver("0.5") < Semver("2.0")
- assert not (Semver("2.0") < Semver("0.5"))
- assert not (Semver("0.5") > Semver("2.0"))
- assert not (Semver("0.5") >= Semver("2.0"))
- assert Semver("2.0") >= Semver("0.5")
- assert Semver("2.0") > Semver("0.5")
- assert not (Semver("2.0") > Semver("2.0"))
- assert not (Semver("2.0") < Semver("2.0"))
-
-def test_semver_range():
- def bounds(spec, lowe, high):
- lowe = Semver(lowe) if lowe is not None else lowe
- high = Semver(high) if high is not None else high
- assert SemverRange(spec).lower() == lowe and SemverRange(spec).upper() == high
- bounds('0', '0.0.0', '1.0.0')
- bounds('0.0', '0.0.0', '0.1.0')
- bounds('0.0.0', '0.0.0', '0.0.1')
- bounds('0.0.1', '0.0.1', '0.0.2')
- bounds('0.1.1', '0.1.1', '0.2.0')
- bounds('1.1.1', '1.1.1', '2.0.0')
- bounds('^0', '0.0.0', '1.0.0')
- bounds('^0.0', '0.0.0', '0.1.0')
- bounds('^0.0.0', '0.0.0', '0.0.1')
- bounds('^0.0.1', '0.0.1', '0.0.2')
- bounds('^0.1.1', '0.1.1', '0.2.0')
- bounds('^1.1.1', '1.1.1', '2.0.0')
- bounds('~0', '0.0.0', '1.0.0')
- bounds('~0.0', '0.0.0', '0.1.0')
- bounds('~0.0.0', '0.0.0', '0.1.0')
- bounds('~0.0.1', '0.0.1', '0.1.0')
- bounds('~0.1.1', '0.1.1', '0.2.0')
- bounds('~1.1.1', '1.1.1', '1.2.0')
- bounds('*', '0.0.0', None)
- bounds('0.*', '0.0.0', '1.0.0')
- bounds('0.0.*', '0.0.0', '0.1.0')
-
-
-def test_semver_multirange():
- assert SemverRange(">= 0.5, < 2.0").compare("1.0.0")
- assert SemverRange("*").compare("0.2.7")
-
-
-class Runner(object):
-
- def __init__(self, c, e, cwd=None):
- self._cmd = c
- if not isinstance(self._cmd, list):
- self._cmd = [self._cmd]
- self._env = e
- self._stdout = []
- self._stderr = []
- self._returncode = 0
- self._cwd = cwd
-
- def __call__(self, c, e):
- cmd = self._cmd + c
- env = dict(self._env, **e)
- #dbg(' env: %s' % env)
- #dbg(' cwd: %s' % self._cwd)
- envstr = ''
- for k, v in env.iteritems():
- envstr += ' %s="%s"' % (k, v)
- if self._cwd is not None:
- dbg('cd %s && %s %s' % (self._cwd, envstr, ' '.join(cmd)))
- else:
- dbg('%s %s' % (envstr, ' '.join(cmd)))
-
- proc = subprocess.Popen(cmd, env=env,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- cwd=self._cwd)
- out, err = proc.communicate()
-
- for lo in out.split('\n'):
- if len(lo) > 0:
- self._stdout.append(lo)
- #dbg('out: %s' % lo)
-
- for le in err.split('\n'):
- if len(le) > 0:
- self._stderr.append(le)
- dbg(le)
-
- """
- while proc.poll() is None:
- lo = proc.stdout.readline().rstrip('\n')
- le = proc.stderr.readline().rstrip('\n')
- if len(lo) > 0:
- self._stdout.append(lo)
- dbg(lo)
- sys.stdout.flush()
- if len(le) > 0:
- self._stderr.append(le)
- dbg('err: %s', le)
- sys.stdout.flush()
- """
- self._returncode = proc.wait()
- #dbg(' ret: %s' % self._returncode)
- return self._stdout
-
- def output(self):
- return self._stdout
-
- def returncode(self):
- return self._returncode
-
-class RustcRunner(Runner):
-
- def __call__(self, c, e):
- super(RustcRunner, self).__call__(c, e)
- return ([], {}, {})
-
-class BuildScriptRunner(Runner):
-
- def __call__(self, c, e):
- #dbg('XXX Running build script:');
- #dbg(' env: %s' % e)
- #dbg(' '.join(self._cmd + c))
- super(BuildScriptRunner, self).__call__(c, e)
-
- # parse the output for cargo: lines
- cmd = []
- env = {}
- denv = {}
- for l in self.output():
- match = BSCRIPT.match(str(l))
- if match is None:
- continue
- pieces = match.groupdict()
- k = pieces['key']
- v = pieces['value']
-
- if k == 'rustc-link-lib':
- #dbg('YYYYYY: adding -l %s' % v)
- cmd += ['-l', v]
- elif k == 'rustc-link-search':
- #dbg("adding link search path: %s" % v)
- cmd += ['-L', v]
- elif k == 'rustc-cfg':
- cmd += ['--cfg', v]
- env['CARGO_FEATURE_%s' % v.upper().replace('-', '_')] = 1
- else:
- #dbg("env[%s] = %s" % (k, v));
- denv[k] = v
- return (cmd, env, denv)
-
-class Crate(object):
-
- def __init__(self, crate, ver, deps, cdir, build):
- self._crate = str(crate)
- self._version = Semver(ver)
- self._dep_info = deps
- self._dir = cdir
- # put the build scripts first
- self._build = [x for x in build if x.get('type') == 'build_script']
- # then add the lib/bin builds
- self._build += [x for x in build if x.get('type') != 'build_script']
- self._resolved = False
- self._deps = {}
- self._refs = []
- self._env = {}
- self._dep_env = {}
- self._extra_flags = []
-
- def name(self):
- return self._crate
-
- def dep_info(self):
- return self._dep_info
-
- def version(self):
- return self._version
-
- def dir(self):
- return self._dir
-
- def __str__(self):
- return '%s-%s' % (self.name(), self.version())
-
- def add_dep(self, crate, features):
- if str(crate) in self._deps:
- return
-
- features = [str(x) for x in features]
- self._deps[str(crate)] = { 'features': features }
- crate.add_ref(self)
-
- def add_ref(self, crate):
- if str(crate) not in self._refs:
- self._refs.append(str(crate))
-
- def resolved(self):
- return self._resolved
-
- @dbgCtx
- def resolve(self, tdir, idir, nodl, graph=None):
- if self._resolved:
- return
- if str(self) in CRATES:
- return
-
- if self._dep_info is not None:
- print ''
- dbg('Resolving dependencies for: %s' % str(self))
- for d in self._dep_info:
- kind = d.get('kind', 'normal')
- if kind not in ('normal', 'build'):
- print ''
- dbg('Skipping %s dep %s' % (kind, d['name']))
- continue
-
- optional = d.get('optional', False)
- if optional and d['name'] not in INCLUDE_OPTIONAL:
- print ''
- dbg('Skipping optional dep %s' % d['name'])
- continue
-
- svr = SemverRange(d['req'])
- print ''
- deps = []
- dbg('Looking up info for %s %s' % (d['name'], str(svr)))
- if d.get('local', None) is None:
- # go through crates first to see if the is satisfied already
- dcrate = find_crate_by_name_and_semver(d['name'], svr)
- if dcrate is not None:
- #import pdb; pdb.set_trace()
- svr = dcrate.version().as_range()
- name, ver, ideps, ftrs, cksum = crate_info_from_index(idir, d['name'], svr)
- if name in BLACKLIST:
- dbg('Found in blacklist, skipping %s' % (name))
- elif dcrate is None:
- if nodl:
- cdir = find_downloaded_crate(tdir, name, svr)
- else:
- cdir = dl_and_check_crate(tdir, name, ver, cksum)
- _, tver, tdeps, build = crate_info_from_toml(cdir)
- deps += ideps
- deps += tdeps
- else:
- dbg('Found crate already satisfying %s %s' % (d['name'], str(svr)))
- deps += dcrate.dep_info()
- else:
- cdir = d['path']
- name, ver, ideps, build = crate_info_from_toml(cdir)
- deps += ideps
-
- if name not in BLACKLIST:
- try:
- if dcrate is None:
- dcrate = Crate(name, ver, deps, cdir, build)
- if str(dcrate) in CRATES:
- dcrate = CRATES[str(dcrate)]
- UNRESOLVED.append(dcrate)
- if graph is not None:
- print >> graph, '"%s" -> "%s";' % (str(self), str(dcrate))
-
- except:
- dcrate = None
-
- # clean up the list of features that are enabled
- tftrs = d.get('features', [])
- if isinstance(tftrs, dict):
- tftrs = tftrs.keys()
- else:
- tftrs = [x for x in tftrs if len(x) > 0]
-
- # add 'default' if default_features is true
- if d.get('default_features', True):
- tftrs.append('default')
-
- features = []
- if isinstance(ftrs, dict):
- # add any available features that are activated by the
- # dependency entry in the parent's dependency record,
- # and any features they depend on recursively
- def add_features(f):
- if f in ftrs:
- for k in ftrs[f]:
- # guard against infinite recursion
- if not k in features:
- features.append(k)
- add_features(k)
- for k in tftrs:
- add_features(k)
- else:
- features += [x for x in ftrs if (len(x) > 0) and (x in tftrs)]
-
- if dcrate is not None:
- self.add_dep(dcrate, features)
-
- self._resolved = True
- CRATES[str(self)] = self
-
- @dbgCtx
- def build(self, by, out_dir, features=[]):
- extra_filename = '-' + str(self.version()).replace('.','_')
- output_name = self.name().replace('-','_')
- output = os.path.join(out_dir, 'lib%s%s.rlib' % (output_name, extra_filename))
-
- if str(self) in BUILT:
- return ({'name':self.name(), 'lib':output}, self._env, self._extra_flags)
-
- externs = []
- extra_flags = []
- for dep,info in self._deps.iteritems():
- if dep in CRATES:
- extern, env, extra_flags = CRATES[dep].build(self, out_dir, info['features'])
- externs.append(extern)
- self._dep_env[CRATES[dep].name()] = env
- self._extra_flags += extra_flags
-
- if os.path.isfile(output):
- print ''
- dbg('Skipping %s, already built (needed by: %s)' % (str(self), str(by)))
- BUILT[str(self)] = str(by)
- return ({'name':self.name(), 'lib':output}, self._env, self._extra_flags)
-
- # build the environment for subcommands
- tenv = dict(os.environ)
- env = {}
- env['PATH'] = tenv['PATH']
- env['OUT_DIR'] = out_dir
- env['TARGET'] = TARGET
- env['HOST'] = HOST
- env['NUM_JOBS'] = '1'
- env['OPT_LEVEL'] = '0'
- env['DEBUG'] = '0'
- env['PROFILE'] = 'release'
- env['CARGO_MANIFEST_DIR'] = self.dir()
- env['CARGO_PKG_VERSION_MAJOR'] = self.version()['major']
- env['CARGO_PKG_VERSION_MINOR'] = self.version()['minor']
- env['CARGO_PKG_VERSION_PATCH'] = self.version()['patch']
- pre = self.version()['prerelease']
- if pre is None:
- pre = ''
- env['CARGO_PKG_VERSION_PRE'] = pre
- env['CARGO_PKG_VERSION'] = str(self.version())
- for f in features:
- env['CARGO_FEATURE_%s' % f.upper().replace('-','_')] = '1'
- for l,e in self._dep_env.iteritems():
- for k,v in e.iteritems():
- if type(v) is not str and type(v) is not unicode:
- v = str(v)
- env['DEP_%s_%s' % (l.upper(), v.upper())] = v
-
- # create the builders, build scrips are first
- cmds = []
- for b in self._build:
- v = str(self._version).replace('.','_')
- cmd = ['rustc']
- cmd.append(os.path.join(self._dir, b['path']))
- cmd.append('--crate-name')
- if b['type'] == 'lib':
- b.setdefault('name', self.name())
- cmd.append(b['name'].replace('-','_'))
- cmd.append('--crate-type')
- cmd.append('lib')
- elif b['type'] == 'build_script':
- cmd.append('build_script_%s' % b['name'].replace('-','_'))
- cmd.append('--crate-type')
- cmd.append('bin')
- else:
- cmd.append(b['name'].replace('-','_'))
- cmd.append('--crate-type')
- cmd.append('bin')
-
- for f in features:
- cmd.append('--cfg')
- cmd.append('feature=\"%s\"' % f)
-
- cmd.append('-C')
- cmd.append('extra-filename=' + extra_filename)
-
- cmd.append('--out-dir')
- cmd.append('%s' % out_dir)
- cmd.append('--emit=dep-info,link')
- cmd.append('--target')
- cmd.append(TARGET)
- cmd.append('-L')
- cmd.append('%s' % out_dir)
- cmd.append('-L')
- cmd.append('%s/lib' % out_dir)
-
-
- # add in the flags from dependencies
- cmd += self._extra_flags
-
- for e in externs:
- cmd.append('--extern')
- cmd.append('%s=%s' % (e['name'].replace('-','_'), e['lib']))
-
- # get the pkg key name
- match = BNAME.match(b['name'])
- if match is not None:
- match = match.groupdict()['name'].replace('-','_')
-
- # queue up the runner
- cmds.append({'name':b['name'], 'env_key':match, 'cmd':RustcRunner(cmd, env)})
-
- # queue up the build script runner
- if b['type'] == 'build_script':
- bcmd = os.path.join(out_dir, 'build_script_%s-%s' % (b['name'], v))
- cmds.append({'name':b['name'], 'env_key':match, 'cmd':BuildScriptRunner(bcmd, env, self._dir)})
-
- print ''
- dbg('Building %s (needed by: %s)' % (str(self), str(by)))
-
- bcmd = []
- benv = {}
- for c in cmds:
- runner = c['cmd']
-
- (c1, e1, e2) = runner(bcmd, benv)
-
- if runner.returncode() != 0:
- raise RuntimeError('build command failed: %s' % runner.returncode())
-
- bcmd += c1
- benv = dict(benv, **e1)
-
- key = c['env_key']
- for k,v in e2.iteritems():
- self._env['DEP_%s_%s' % (key.upper(), k.upper())] = v
-
- #dbg('XXX cmd: %s' % bcmd)
- #dbg('XXX env: %s' % benv)
- #dbg('XXX denv: %s' % self._env)
- #print ''
-
- BUILT[str(self)] = str(by)
- return ({'name':self.name(), 'lib':output}, self._env, bcmd)
-
-
-def dl_crate(url, depth=0):
- if depth > 10:
- raise RuntimeError('too many redirects')
-
- r = requests.get(url)
- try:
- dbg('%sconnected to %s...%s' % ((' ' * depth), r.url, r.status_code))
-
- if URLS_FILE is not None:
- with open(URLS_FILE, "a") as f:
- f.write(r.url + "\n")
-
- return r.content
- finally:
- r.close()
-
-def dl_and_check_crate(tdir, name, ver, cksum):
- cname = '%s-%s' % (name, ver)
- cdir = os.path.join(tdir, cname)
- if cname in CRATES:
- dbg('skipping %s...already downloaded' % cname)
- return cdir
-
- def check_checksum(buf):
- if (cksum is not None):
- h = hashlib.sha256()
- h.update(buf)
- if h.hexdigest() == cksum:
- dbg('Checksum is good...%s' % cksum)
- else:
- dbg('Checksum is BAD (%s != %s)' % (h.hexdigest(), cksum))
-
- if CRATE_CACHE:
- cachename = os.path.join(CRATE_CACHE, "%s.crate" % (cname))
- if os.path.isfile(cachename):
- dbg('found crate in cache...%s.crate' % (cname))
- buf = open(cachename).read()
- check_checksum(buf)
- with tarfile.open(fileobj=cStringIO.StringIO(buf)) as tf:
- dbg('unpacking result to %s...' % cdir)
- tf.extractall(path=tdir)
- return cdir
-
- if not os.path.isdir(cdir):
- dbg('Downloading %s source to %s' % (cname, cdir))
- dl = CRATE_API_DL % (name, ver)
- buf = dl_crate(dl)
- check_checksum(buf)
-
- if CRATE_CACHE:
- dbg("saving crate to %s/%s.crate..." % (CRATE_CACHE, cname))
- with open(os.path.join(CRATE_CACHE, "%s.crate" % (cname)), "wb") as f:
- f.write(buf)
-
- fbuf = cStringIO.StringIO(buf)
- with tarfile.open(fileobj=fbuf) as tf:
- dbg('unpacking result to %s...' % cdir)
- tf.extractall(path=tdir)
-
- return cdir
-
-
-def find_downloaded_crate(tdir, name, svr):
- exists = glob("%s/%s-[0-9]*" % (tdir, name))
- if not exists:
- raise RuntimeError("crate does not exist and have --no-download: %s" % name)
-
- # First, grok the available versions.
- aver = sorted([Semver(CVER.search(x).group(1)) for x in exists])
-
- # Now filter the "suitable" versions based on our version range.
- sver = filter(svr.compare, aver)
- if not sver:
- raise RuntimeError("unable to satisfy dependency %s %s from %s; try running without --no-download" % (name, svr, map(str, aver)))
-
- cver = sver[-1]
- return "%s/%s-%s" % (tdir, name, cver)
-
-
-def crate_info_from_toml(cdir):
- try:
- with open(os.path.join(cdir, 'Cargo.toml'), 'rb') as ctoml:
- #import pdb; pdb.set_trace()
- cfg = toml.load(ctoml)
- build = []
- p = cfg.get('package',cfg.get('project', {}))
- name = p.get('name', None)
- #if name == 'num_cpus':
- # import pdb; pdb.set_trace()
- ver = p.get('version', None)
- if (name is None) or (ver is None):
- import pdb; pdb.set_trace()
- raise RuntimeError('invalid .toml file format')
-
- # look for a "links" item
- lnks = p.get('links', [])
- if type(lnks) is not list:
- lnks = [lnks]
-
- # look for a "build" item
- bf = p.get('build', None)
-
- # if we have a 'links', there must be a 'build'
- if len(lnks) > 0 and bf is None:
- import pdb; pdb.set_trace()
- raise RuntimeError('cargo requires a "build" item if "links" is specified')
-
- # there can be target specific build script overrides
- boverrides = {}
- for lnk in lnks:
- boverrides.update(cfg.get('target', {}).get(TARGET, {}).get(lnk, {}))
-
- bmain = False
- if bf is not None:
- build.append({'type':'build_script', \
- 'path':[ bf ], \
- 'name':name.replace('-','_'), \
- 'links': lnks, \
- 'overrides': boverrides})
-
- # look for libs array
- libs = cfg.get('lib', [])
- if type(libs) is not list:
- libs = [libs]
- for l in libs:
- l['type'] = 'lib'
- l['links'] = lnks
- if l.get('path', None) is None:
- l['path'] = [ 'lib.rs' ]
- build.append(l)
- bmain = True
-
- # look for bins array
- bins = cfg.get('bin', [])
- if type(bins) is not list:
- bins = [bins]
- for b in bins:
- if b.get('path', None) is None:
- b['path'] = [ os.path.join('bin', '%s.rs' % b['name']), os.path.join('bin', 'main.rs'), '%s.rs' % b['name'], 'main.rs' ]
- build.append({'type': 'bin', \
- 'name':b['name'], \
- 'path':b['path'], \
- 'links': lnks})
- bmain = True
-
- # if no explicit directions on what to build, then add a default
- if bmain == False:
- build.append({'type':'lib', 'path':'lib.rs', 'name':name.replace('-','_')})
-
- for b in build:
- # make sure the path is a list of possible paths
- if type(b['path']) is not list:
- b['path'] = [ b['path'] ]
- bin_paths = []
- for p in b['path']:
- bin_paths.append(os.path.join(cdir, p))
- bin_paths.append(os.path.join(cdir, 'src', p))
-
- found_path = None
- for p in bin_paths:
- if os.path.isfile(p):
- found_path = p
- break
-
- if found_path == None:
- import pdb; pdb.set_trace()
- raise RuntimeError('could not find %s to build in %s', (build, cdir))
- else:
- b['path'] = found_path
-
- d = cfg.get('build-dependencies', {})
- d.update(cfg.get('dependencies', {}))
- d.update(cfg.get('target', {}).get(TARGET, {}).get('dependencies', {}))
- deps = []
- for k,v in d.iteritems():
- if type(v) is not dict:
- deps.append({'name':k, 'req': v})
- elif 'path' in v:
- if v.get('version', None) is None:
- deps.append({'name':k, 'path':os.path.join(cdir, v['path']), 'local':True, 'req':0})
- else:
- opts = v.get('optional',False)
- ftrs = v.get('features',[])
- deps.append({'name':k, 'path': v['path'], 'req':v['version'], 'features':ftrs, 'optional':opts})
- else:
- opts = v.get('optional',False)
- ftrs = v.get('features',[])
- deps.append({'name':k, 'req':v['version'], 'features':ftrs, 'optional':opts})
-
- return (name, ver, deps, build)
-
- except Exception, e:
- dbg('failed to load toml file for: %s (%s)' % (cdir, str(e)))
- import pdb; pdb.set_trace()
-
- return (None, None, [], 'lib.rs')
-
-
-def crate_info_from_index(idir, name, svr):
- if len(name) == 1:
- ipath = os.path.join(idir, '1', name)
- elif len(name) == 2:
- ipath = os.path.join(idir, '2', name)
- elif len(name) == 3:
- ipath = os.path.join(idir, '3', name[0:1], name)
- else:
- ipath = os.path.join(idir, name[0:2], name[2:4], name)
-
- dbg('opening crate info: %s' % ipath)
- dep_infos = []
- with open(ipath, 'rb') as fin:
- lines = fin.readlines()
- for l in lines:
- dep_infos.append(json.loads(l))
-
- passed = {}
- for info in dep_infos:
- if 'vers' not in info:
- continue
- sv = Semver(info['vers'])
- if svr.compare(sv):
- passed[sv] = info
-
- keys = sorted(passed.iterkeys())
- best_match = keys.pop()
- dbg('best match is %s-%s' % (name, best_match))
- best_info = passed[best_match]
- name = best_info.get('name', None)
- ver = best_info.get('vers', None)
- deps = best_info.get('deps', [])
- ftrs = best_info.get('features', [])
- cksum = best_info.get('cksum', None)
-
- # only include deps without a 'target' or ones with matching 'target'
- deps = [x for x in deps if x.get('target', TARGET) == TARGET]
-
- return (name, ver, deps, ftrs, cksum)
-
-
-def find_crate_by_name_and_semver(name, svr):
- for c in CRATES.itervalues():
- if c.name() == name and svr.compare(c.version()):
- return c
- for c in UNRESOLVED:
- if c.name() == name and svr.compare(c.version()):
- return c
- return None
-
-
-def args_parser():
- parser = argparse.ArgumentParser(description='Cargo Bootstrap Tool')
- parser.add_argument('--cargo-root', type=str, default=os.getcwd(),
- help="specify the cargo repo root path")
- parser.add_argument('--target-dir', type=str, default=tempfile.mkdtemp(),
- help="specify the path for storing built dependency libs")
- parser.add_argument('--crate-index', type=str, default=None,
- help="path to where the crate index should be cloned")
- parser.add_argument('--target', type=str, default=None,
- help="target triple for machine we're bootstrapping for")
- parser.add_argument('--host', type=str, default=None,
- help="host triple for machine we're bootstrapping on")
- parser.add_argument('--no-clone', action='store_true',
- help="skip cloning crates index, --crate-index must point to an existing clone of the crates index")
- parser.add_argument('--no-git', action='store_true',
- help="don't assume that the crates index and cargo root are git repos; implies --no-clone")
- parser.add_argument('--no-clean', action='store_true',
- help="don't delete the target dir and crate index")
- parser.add_argument('--download', action='store_true',
- help="only download the crates needed to build cargo")
- parser.add_argument('--no-download', action='store_true',
- help="don't download any crates (fail if any do not exist)")
- parser.add_argument('--graph', action='store_true',
- help="output a dot graph of the dependencies")
- parser.add_argument('--urls-file', type=str, default=None,
- help="file to write crate URLs to")
- parser.add_argument('--blacklist', type=str, default="",
- help="space-separated list of crates to skip")
- parser.add_argument('--include-optional', type=str, default="",
- help="space-separated list of optional crates to include")
- parser.add_argument('--patchdir', type=str,
- help="directory with patches to apply after downloading crates. organized by crate/NNNN-description.patch")
- parser.add_argument('--crate-cache', type=str,
- help="download and save crates to crate cache (directory)")
- return parser
-
-
-def open_or_clone_repo(rdir, rurl, no_clone):
- try:
- repo = git.open_repo(rdir)
- return repo
- except:
- repo = None
-
- if repo is None and no_clone is False:
- dbg('Cloning %s to %s' % (rurl, rdir))
- return git.clone(rurl, rdir)
-
- if repo is None and no_clone is True:
- repo = rdir
-
- return repo
-
-
-def patch_crates(targetdir, patchdir):
- """
- Apply patches in patchdir to downloaded crates
- patchdir organization:
-
- <patchdir>/
- <crate>/
- <patch>.patch
- """
- for patch in glob(os.path.join(patchdir, '*', '*.patch')):
- crateid = os.path.basename(os.path.dirname(patch))
- m = re.match(r'^([A-Za-z0-9_-]+?)(?:-([\d.]+))?$', crateid)
- if m:
- cratename = m.group(1)
- else:
- cratename = crateid
- if cratename != crateid:
- dirs = glob(os.path.join(targetdir, crateid))
- else:
- dirs = glob(os.path.join(targetdir, '%s-*' % (cratename)))
- for cratedir in dirs:
- # check if patch has been applied
- patchpath = os.path.abspath(patch)
- p = subprocess.Popen(['patch', '--dry-run', '-s', '-f', '-F', '10', '-p1', '-i', patchpath], cwd=cratedir)
- rc = p.wait()
- if rc == 0:
- dbg("patching %s with patch %s" % (os.path.basename(cratedir), os.path.basename(patch)))
- p = subprocess.Popen(['patch', '-s', '-F', '10', '-p1', '-i', patchpath], cwd=cratedir)
- rc = p.wait()
- if rc != 0:
- dbg("%s: failed to apply %s (rc=%s)" % (os.path.basename(cratedir), os.path.basename(patch), rc))
- else:
- dbg("%s: %s does not apply (rc=%s)" % (os.path.basename(cratedir), os.path.basename(patch), rc))
-
-
-if __name__ == "__main__":
- try:
- # parse args
- parser = args_parser()
- args = parser.parse_args()
-
- # clone the cargo index
- if args.crate_index is None:
- args.crate_index = os.path.normpath(os.path.join(args.target_dir, 'index'))
- dbg('cargo: %s, target: %s, index: %s' % \
- (args.cargo_root, args.target_dir, args.crate_index))
-
- TARGET = args.target
- HOST = args.host
- URLS_FILE = args.urls_file
- BLACKLIST = args.blacklist.split()
- INCLUDE_OPTIONAL = args.include_optional.split()
- if args.crate_cache and os.path.isdir(args.crate_cache):
- CRATE_CACHE = os.path.abspath(args.crate_cache)
-
- if not args.no_git:
- index = open_or_clone_repo(args.crate_index, CRATES_INDEX, args.no_clone)
- cargo = open_or_clone_repo(args.cargo_root, CARGO_REPO, args.no_clone)
-
- if index is None:
- raise RuntimeError('You must have a local clone of the crates index, ' \
- 'omit --no-clone to allow this script to clone it for ' \
- 'you, or pass --no-git to bypass this check.')
- if cargo is None:
- raise RuntimeError('You must have a local clone of the cargo repo ' \
- 'so that this script can read the cargo toml file.')
-
- if TARGET is None:
- raise RuntimeError('You must specify the target triple of this machine')
- if HOST is None:
- HOST = TARGET
-
- except Exception, e:
- frame = inspect.trace()[-1]
- print >> sys.stderr, "\nException:\n from %s, line %d:\n %s\n" % (frame[1], frame[2], e)
- parser.print_help()
- if not args.no_clean:
- print "cleaning up %s" % (args.target_dir)
- shutil.rmtree(args.target_dir)
- sys.exit(1)
-
- try:
-
- # load cargo deps
- name, ver, deps, build = crate_info_from_toml(args.cargo_root)
- cargo_crate = Crate(name, ver, deps, args.cargo_root, build)
- UNRESOLVED.append(cargo_crate)
-
- if args.graph:
- GRAPH = open(os.path.join(args.target_dir, 'deps.dot'), 'wb')
- print >> GRAPH, "digraph %s {" % name
-
- # resolve and download all of the dependencies
- print ''
- print '===================================='
- print '===== DOWNLOADING DEPENDENCIES ====='
- print '===================================='
- while len(UNRESOLVED) > 0:
- crate = UNRESOLVED.pop(0)
- crate.resolve(args.target_dir, args.crate_index, args.no_download, GRAPH)
-
- if args.graph:
- print >> GRAPH, "}"
- GRAPH.close()
-
- if args.patchdir:
- print ''
- print '========================'
- print '===== PATCH CRATES ====='
- print '========================'
- patch_crates(args.target_dir, args.patchdir)
-
- if args.download:
- print "done downloading..."
- sys.exit(0)
-
- # build cargo
- print ''
- print '=========================='
- print '===== BUILDING CARGO ====='
- print '=========================='
- cargo_crate.build('bootstrap.py', args.target_dir)
-
- # cleanup
- if not args.no_clean:
- print "cleaning up %s..." % (args.target_dir)
- shutil.rmtree(args.target_dir)
- print "done"
-
- except Exception, e:
- frame = inspect.trace()[-1]
- print >> sys.stderr, "\nException:\n from %s, line %d:\n %s\n" % (frame[1], frame[2], e)
- if not args.no_clean:
- print "cleaning up %s..." % (args.target_dir)
- shutil.rmtree(args.target_dir)
- sys.exit(1)
-
-