Last active
April 22, 2016 00:08
-
-
Save zofrex/47a60042be1305931f920ded70014851 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
set -eux | |
rm -r ../cargo-out/* | |
./bootstrap.py --crate-index=/home/zofrex/crates --target-dir=/home/zofrex/cargo-out --no-clone --no-clean --target x86_64-unknown-openbsd --download || true | |
sed -i"" -e 's/\(\[\[test\]\]\) /\1\ | |
/' ../cargo-out/url-0.5.9/Cargo.toml | |
sed -i -e 's/^\(miniz-sys.*\), optional = true/\1/' ../cargo-out/flate2-0.2.13/Cargo.toml | |
./bootstrap.py --crate-index=/home/zofrex/crates --target-dir=/home/zofrex/cargo-out --no-clone --no-clean --target x86_64-unknown-openbsd --download | |
sed -i -E -e 's/([^\/].*fs.*(remove_dir_all|create_dir_all).*)/\/\/\1/' ../cargo-out/libgit2-sys-0.4.2/build.rs | |
sed -i -E -e 's/([^\/].*fs.*(remove_dir_all|create_dir_all).*)/\/\/\1/' ../cargo-out/libgit2-sys-0.3.12/build.rs | |
sed -i"" -e 's/^rand.*version\([ ]*=[ ]*"[0-9.]*"\).*/rand\1/' ../cargo-out/uuid-0.2.0/Cargo.toml | |
./bootstrap.py --crate-index=/home/zofrex/crates --target-dir=/home/zofrex/cargo-out --no-clone --no-clean --target x86_64-unknown-openbsd || true | |
"cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" -L /usr/local/lib "-L" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib" "/home/zofrex/cargo-out/cargo-0_11_0.0.o" "-o" "/home/zofrex/cargo-out/cargo-0_11_0" "-Wl,--gc-sections" "-pie" "-nodefaultlibs" "-L" "/home/zofrex/cargo-out" "-L" "/home/zofrex/cargo-out/lib" "-L" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib" "-Wl,-Bstatic" "-Wl,-Bdynamic" "/home/zofrex/cargo-out/libcargo-0_11_0.rlib" "/home/zofrex/cargo-out/libcrossbeam-0_2_9.rlib" "/home/zofrex/cargo-out/libsemver-0_2_3.rlib" "/home/zofrex/cargo-out/libtoml-0_1_28.rlib" "/home/zofrex/cargo-out/libflate2-0_2_13.rlib" "/home/zofrex/cargo-out/libnom-1_2_2.rlib" "/home/zofrex/cargo-out/libnum_cpus-0_2_11.rlib" "/home/zofrex/cargo-out/libtime-0_1_35.rlib" "/home/zofrex/cargo-out/libdocopt-0_6_80.rlib" "/home/zofrex/cargo-out/libminiz_sys-0_1_7.rlib" "/home/zofrex/cargo-out/libtar-0_4_5.rlib" "/home/zofrex/cargo-out/libfiletime-0_1_10.rlib" "/home/zofrex/cargo-out/libenv_logger-0_3_3.rlib" "/home/zofrex/cargo-out/libcrates_io-0_2_0.rlib" "/home/zofrex/cargo-out/liburl-0_2_38.rlib" "/home/zofrex/cargo-out/libuuid-0_1_18.rlib" "/home/zofrex/cargo-out/libfs2-0_2_4.rlib" "/home/zofrex/cargo-out/libregex-0_1_66.rlib" "/home/zofrex/cargo-out/libthread_local-0_2_3.rlib" "/home/zofrex/cargo-out/libutf8_ranges-0_1_3.rlib" "/home/zofrex/cargo-out/libaho_corasick-0_5_1.rlib" "/home/zofrex/cargo-out/libmemchr-0_1_11.rlib" "/home/zofrex/cargo-out/libthread_id-2_0_0.rlib" "/home/zofrex/cargo-out/libregex_syntax-0_3_1.rlib" "/home/zofrex/cargo-out/libglob-0_2_11.rlib" "/home/zofrex/cargo-out/libterm-0_4_4.rlib" "/home/zofrex/cargo-out/libunreachable-0_1_1.rlib" "/home/zofrex/cargo-out/libvoid-1_0_1.rlib" "/home/zofrex/cargo-out/libgit2_curl-0_4_0.rlib" "/home/zofrex/cargo-out/libgit2-0_4_2.rlib" "/home/zofrex/cargo-out/libbitflags-0_1_1.rlib" "/home/zofrex/cargo-out/libcurl-0_2_18.rlib" "/home/zofrex/cargo-out/liburl-0_5_9.rlib" "/home/zofrex/cargo-out/libuuid-0_2_0.rlib" "/home/zofrex/cargo-out/liblog-0_3_6.rlib" "/home/zofrex/cargo-out/librand-0_3_14.rlib" "/home/zofrex/cargo-out/librustc_serialize-0_3_19.rlib" "/home/zofrex/cargo-out/libcurl_sys-0_1_34.rlib" "/home/zofrex/cargo-out/libunicode_normalization-0_1_2.rlib" "/home/zofrex/cargo-out/libopenssl_sys-0_7_10.rlib" "/home/zofrex/cargo-out/liblibgit2_sys-0_4_2.rlib" "/home/zofrex/cargo-out/liblibz_sys-1_0_1.rlib" "/home/zofrex/cargo-out/liblibc-0_2_10.rlib" "/home/zofrex/cargo-out/libunicode_bidi-0_2_3.rlib" "/home/zofrex/cargo-out/libmatches-0_1_2.rlib" "/home/zofrex/cargo-out/libstrsim-0_3_0.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/libstd-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/libcollections-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/librustc_unicode-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/librand-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/liballoc-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/liballoc_system-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/liblibc-db5a760f.rlib" "/usr/local/lib/rustlib/x86_64-unknown-openbsd/lib/libcore-db5a760f.rlib" "-l" "curl" "-l" "curl" "-l" "ssl" "-l" "crypto" "-l" "ssl" "-l" "crypto" "-l" "z" "-l" "z" "-l" "util" "-l" "pthread" "-l" "gcc" "-l" "c" "-l" "m" "-l" "compiler-rt" | |
../cargo-out/cargo-0_11_0 build --verbose |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
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 | |
--test-semver triggers the execution of the Semver and SemverRange class tests. | |
``` | |
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, \ | |
cStringIO, \ | |
hashlib, \ | |
httplib, \ | |
inspect, \ | |
json, \ | |
os, \ | |
re, \ | |
shutil, \ | |
subprocess, \ | |
sys, \ | |
tarfile, \ | |
tempfile, \ | |
urlparse | |
import pytoml as toml | |
import dulwich.porcelain as git | |
TARGET = None | |
HOST = None | |
GRAPH = 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('^(?P<op>(?:\<=|\>=|=|\<|\>|\^|\~))?\s*' | |
'(?P<major>(?:\*|0|[1-9][0-9]*))' | |
'(\.(?P<minor>(?:\*|0|[1-9][0-9]*)))?' | |
'(\.(?P<patch>(?:\*|0|[1-9][0-9]*)))?' | |
'(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' | |
'(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$') | |
SEMVER = re.compile('^\s*(?P<major>(?:0|[1-9][0-9]*))' | |
'(\.(?P<minor>(?:0|[1-9][0-9]*)))?' | |
'(\.(?P<patch>(?:0|[1-9][0-9]*)))?' | |
'(\-(?P<prerelease>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?' | |
'(\+(?P<build>[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$') | |
BSCRIPT = re.compile('^cargo:(?P<key>([^\s=]+))(=(?P<value>.+))?$') | |
BNAME = re.compile('^(lib)?(?P<name>([^_]+))(_.*)?$') | |
BUILT = {} | |
CRATES = {} | |
UNRESOLVED = [] | |
PFX = [] | |
def idnt(f): | |
def do_indent(*cargs): | |
ret = f(*cargs) | |
return ret | |
return do_indent | |
def dbgCtx(f): | |
def do_dbg(self, *cargs): | |
global PFX | |
PFX.append(self.name()) | |
ret = f(self, *cargs) | |
PFX.pop() | |
return ret | |
return do_dbg | |
def dbg(s): | |
global PFX | |
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 | |
elif lmin < rmin: | |
return True | |
elif lpat < rpat: | |
return True | |
elif lpre is not None and rpre is None: | |
return True | |
elif 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(dict): | |
def __init__(self, sv): | |
match = SV_RANGE.match(str(sv)) | |
if match is None: | |
raise ValueError('%s is not a valid semver range string' % sv) | |
self._input = sv | |
self.update(match.groupdict()) | |
self.prerelease = PreRelease(self['prerelease']) | |
# fix up the op | |
op = self['op'] | |
if op is None: | |
if self['major'] == '*' or self['minor'] == '*' or self['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 | |
def parts_raw(self): | |
return (self['major'],self['minor'],self['patch'],self['prerelease'],self['build']) | |
def __str__(self): | |
major, minor, patch, prerelease, build = self.parts_raw() | |
if self['op'] == '*': | |
if self['major'] == '*': | |
return '*' | |
elif self['minor'] == '*': | |
return major + '*' | |
else: | |
return major + '.' + minor + '.*' | |
else: | |
s = self['op'] | |
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 lower(self): | |
op = self['op'] | |
major,minor,patch,_,_ = self.parts_raw() | |
if op in ('<=', '<', '=', '>', '>='): | |
return None | |
if op == '*': | |
# wildcards specify a range | |
if self['major'] == '*': | |
return Semver('0.0.0') | |
elif self['minor'] == '*': | |
return Semver(major + '.0.0') | |
elif self['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') | |
def upper(self): | |
op = self['op'] | |
major,minor,patch,_,_ = self.parts_raw() | |
if op in ('<=', '<', '=', '>', '>='): | |
return None | |
if op == '*': | |
# wildcards specify a range | |
if self['major'] == '*': | |
return None | |
elif self['minor'] == '*': | |
return Semver(str(int(major) + 1) + '.0.0') | |
elif self['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') | |
def compare(self, sv): | |
if type(sv) is not Semver: | |
sv = Semver(sv) | |
op = self['op'] | |
major,minor,patch,_,_ = self.parts_raw() | |
if op == '*': | |
if self['major'] == '*': | |
return sv >= Semver('0.0.0') | |
return (sv >= self.lower()) and (sv < self.upper()) | |
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(): | |
print '\ntesting parsing:' | |
print '"1" is: "%s"' % Semver("1") | |
print '"1.1" is: "%s"' % Semver("1.1") | |
print '"1.1.1" is: "%s"' % Semver("1.1.1") | |
print '"1.1.1-alpha" is: "%s"' % Semver("1.1.1-alpha") | |
print '"1.1.1-alpha.1" is: "%s"' % Semver("1.1.1-alpha.1") | |
print '"1.1.1-alpha+beta" is: "%s"' % Semver("1.1.1-alpha+beta") | |
print '"1.1.1-alpha.1+beta" is: "%s"' % Semver("1.1.1-alpha.1+beta") | |
print '"1.1.1-alpha.1+beta.1" is: "%s"' % Semver("1.1.1-alpha.1+beta.1") | |
print '\ntesting equality:' | |
print '"1" == "1.0.0" is: %s' % (Semver("1") == Semver("1.0.0")) | |
print '"1.1" == "1.1.0" is: %s' % (Semver("1.1") == Semver("1.1.0")) | |
print '"1.1.1" == "1.1.1" is: %s' % (Semver("1.1.1") == Semver("1.1.1")) | |
print '"1.1.1-alpha" == "1.1.1-alpha" is: %s' % (Semver("1.1.1-alpha") == Semver("1.1.1-alpha")) | |
print '"1.1.1-alpha.1" == "1.1.1-alpha.1" is: %s' % (Semver("1.1.1-alpha.1") == Semver("1.1.1-alpha.1")) | |
print '"1.1.1-alpha+beta" == "1.1.1-alpha+beta" is: %s' % (Semver("1.1.1-alpha+beta") == Semver("1.1.1-alpha+beta")) | |
print '"1.1.1-alpha.1+beta" == "1.1.1-alpha.1+beta" is: %s' % (Semver("1.1.1-alpha.1+beta") == Semver("1.1.1-alpha.1+beta")) | |
print '"1.1.1-alpha.1+beta.1" == "1.1.1-alpha.1+beta.1" is: %s' % (Semver("1.1.1-alpha.1+beta.1") == Semver("1.1.1-alpha.1+beta.1")) | |
print '\ntesting less than:' | |
print '"1" < "2.0.0" is: %s' % (Semver("1") < Semver("2.0.0")) | |
print '"1.1" < "1.2.0" is: %s' % (Semver("1.1") < Semver("1.2.0")) | |
print '"1.1.1" < "1.1.2" is: %s' % (Semver("1.1.1") < Semver("1.1.2")) | |
print '"1.1.1-alpha" < "1.1.1" is: %s' % (Semver("1.1.1-alpha") < Semver("1.1.1")) | |
print '"1.1.1-alpha" < "1.1.1-beta" is: %s' % (Semver("1.1.1-alpha") < Semver("1.1.1-beta")) | |
print '"1.1.1-1" < "1.1.1-alpha" is: %s' % (Semver("1.1.1-alpha") < Semver("1.1.1-beta")) | |
print '"1.1.1-alpha" < "1.1.1-alpha.1" is: %s' % (Semver("1.1.1-alpha") < Semver("1.1.1-alpha.1")) | |
print '"1.1.1-alpha.1" < "1.1.1-alpha.2" is: %s' % (Semver("1.1.1-alpha.1") < Semver("1.1.1-alpha.2")) | |
print '"1.1.1-alpha+beta" < "1.1.1+beta" is: %s' % (Semver("1.1.1-alpha+beta") < Semver("1.1.1+beta")) | |
print '"1.1.1-alpha+beta" < "1.1.1-beta+beta" is: %s' % (Semver("1.1.1-alpha+beta") < Semver("1.1.1-beta+beta")) | |
print '"1.1.1-1+beta" < "1.1.1-alpha+beta" is: %s' % (Semver("1.1.1-alpha+beta") < Semver("1.1.1-beta+beta")) | |
print '"1.1.1-alpha+beta" < "1.1.1-alpha.1+beta" is: %s' % (Semver("1.1.1-alpha+beta") < Semver("1.1.1-alpha.1+beta")) | |
print '"1.1.1-alpha.1+beta" < "1.1.1-alpha.2+beta" is: %s' % (Semver("1.1.1-alpha.1+beta") < Semver("1.1.1-alpha.2+beta")) | |
print '\ntesting semver range parsing:' | |
print '"0" lower: %s, upper: %s' % (SemverRange('0').lower(), SemverRange('0').upper()) | |
print '"0.0" lower: %s, upper: %s' % (SemverRange('0.0').lower(), SemverRange('0.0').upper()) | |
print '"0.0.0" lower: %s, upper: %s' % (SemverRange('0.0.0').lower(), SemverRange('0.0.0').upper()) | |
print '"0.0.1" lower: %s, upper: %s' % (SemverRange('0.0.1').lower(), SemverRange('0.0.1').upper()) | |
print '"0.1.1" lower: %s, upper: %s' % (SemverRange('0.1.1').lower(), SemverRange('0.1.1').upper()) | |
print '"1.1.1" lower: %s, upper: %s' % (SemverRange('1.1.1').lower(), SemverRange('1.1.1').upper()) | |
print '"^0" lower: %s, upper: %s' % (SemverRange('^0').lower(), SemverRange('^0').upper()) | |
print '"^0.0" lower: %s, upper: %s' % (SemverRange('^0.0').lower(), SemverRange('^0.0').upper()) | |
print '"^0.0.0" lower: %s, upper: %s' % (SemverRange('^0.0.0').lower(), SemverRange('^0.0.0').upper()) | |
print '"^0.0.1" lower: %s, upper: %s' % (SemverRange('^0.0.1').lower(), SemverRange('^0.0.1').upper()) | |
print '"^0.1.1" lower: %s, upper: %s' % (SemverRange('^0.1.1').lower(), SemverRange('^0.1.1').upper()) | |
print '"^1.1.1" lower: %s, upper: %s' % (SemverRange('^1.1.1').lower(), SemverRange('^1.1.1').upper()) | |
print '"~0" lower: %s, upper: %s' % (SemverRange('~0').lower(), SemverRange('~0').upper()) | |
print '"~0.0" lower: %s, upper: %s' % (SemverRange('~0.0').lower(), SemverRange('~0.0').upper()) | |
print '"~0.0.0" lower: %s, upper: %s' % (SemverRange('~0.0.0').lower(), SemverRange('~0.0.0').upper()) | |
print '"~0.0.1" lower: %s, upper: %s' % (SemverRange('~0.0.1').lower(), SemverRange('~0.0.1').upper()) | |
print '"~0.1.1" lower: %s, upper: %s' % (SemverRange('~0.1.1').lower(), SemverRange('~0.1.1').upper()) | |
print '"~1.1.1" lower: %s, upper: %s' % (SemverRange('~1.1.1').lower(), SemverRange('~1.1.1').upper()) | |
print '"*" lower: %s, upper: %s' % (SemverRange('*').lower(), SemverRange('*').upper()) | |
print '"0.*" lower: %s, upper: %s' % (SemverRange('0.*').lower(), SemverRange('0.*').upper()) | |
print '"0.0.*" lower: %s, upper: %s' % (SemverRange('0.0.*').lower(), SemverRange('0.0.*').upper()) | |
class Runner(object): | |
def __init__(self, c, e, cwd=None): | |
self._cmd = c | |
if type(self._cmd) is not 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 = filter(lambda x: x.get('type', None) == 'build_script', build) | |
# then add the lib/bin builds | |
self._build += filter(lambda x: x.get('type', None) != 'build_script', build) | |
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 self._deps.has_key(str(crate)): | |
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, graph=None): | |
global CRATES | |
global UNRESOLVED | |
if self._resolved: | |
return | |
if CRATES.has_key(str(self)): | |
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: | |
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 dcrate is None: | |
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 | |
try: | |
if dcrate is None: | |
dcrate = Crate(name, ver, deps, cdir, build) | |
if CRATES.has_key(str(dcrate)): | |
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 type(tftrs) is dict: | |
tftrs = tftrs.keys() | |
else: | |
tftrs = filter(lambda x: len(x) > 0, tftrs) | |
# add 'default' if default_features is true | |
if d.get('default_features', True): | |
tftrs.append('default') | |
features = [] | |
if type(ftrs) is 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 ftrs.has_key(f): | |
if not f in features: | |
print 'appending feature %s' % f | |
features.append(f) | |
for k in ftrs[f]: | |
# guard against infinite recursion | |
if not k in features: | |
features.append(k) | |
print 'appending feature %s' % k | |
add_features(k) | |
for k in tftrs: | |
add_features(k) | |
else: | |
features += filter(lambda x: (len(x) > 0) and (x in tftrs), ftrs) | |
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=[]): | |
global BUILT | |
global CRATES | |
global TARGET | |
global HOST | |
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 BUILT.has_key(str(self)): | |
return ({'name':self.name(), 'lib':output}, self._env, self._extra_flags) | |
externs = [] | |
extra_flags = [] | |
for dep,info in self._deps.iteritems(): | |
if CRATES.has_key(dep): | |
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 = ['/usr/local/bin/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))) | |
if str(self) == 'libgit2-sys-0.4.2': | |
print 'Aborting because who knows what the fuck is gonna happen otherwise' | |
#sys.exit(0) | |
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) | |
@idnt | |
def dl_crate(url, depth=0): | |
if depth > 10: | |
raise RuntimeError('too many redirects') | |
loc = urlparse.urlparse(url) | |
if loc.scheme == 'https': | |
conn = httplib.HTTPSConnection(loc.netloc) | |
elif loc.scheme == 'http': | |
conn = httplib.HTTPConnection(loc.netloc) | |
else: | |
raise RuntimeError('unsupported url scheme: %s' % loc.scheme) | |
conn.request("GET", loc.path) | |
res = conn.getresponse() | |
dbg('%sconnected to %s...%s' % ((' ' * depth), url, res.status)) | |
headers = dict(res.getheaders()) | |
if headers.has_key('location') and headers['location'] != url: | |
return dl_crate(headers['location'], depth + 1) | |
return res.read() | |
@idnt | |
def dl_and_check_crate(tdir, name, ver, cksum): | |
global CRATES | |
cname = '%s-%s' % (name, ver) | |
cdir = os.path.join(tdir, cname) | |
if CRATES.has_key(cname): | |
dbg('skipping %s...already downloaded' % cname) | |
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) | |
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)) | |
fbuf = cStringIO.StringIO(buf) | |
with tarfile.open(fileobj=fbuf) as tf: | |
dbg('unpacking result to %s...' % cdir) | |
tf.extractall(path=tdir) | |
return cdir | |
@idnt | |
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 v.has_key('path'): | |
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))) | |
sys.exit(1) | |
return (None, None, [], 'lib.rs') | |
@idnt | |
def crate_info_from_index(idir, name, svr): | |
global TARGET | |
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 not info.has_key('vers'): | |
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 = filter(lambda x: x.get('target', TARGET) == TARGET, deps) | |
return (name, ver, deps, ftrs, cksum) | |
def find_crate_by_name_and_semver(name, svr): | |
global CRATES | |
global UNRESOLVED | |
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('--test-semver', action='store_true', | |
help="run semver parsing tests") | |
parser.add_argument('--no-clone', action='store_true', | |
help="skip cloning crates index, --target-dir 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('--graph', action='store_true', | |
help="output a dot graph of the dependencies") | |
return parser | |
@idnt | |
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) | |
return repo | |
if __name__ == "__main__": | |
try: | |
# parse args | |
parser = args_parser() | |
args = parser.parse_args() | |
if args.test_semver: | |
test_semver() | |
sys.exit(0) | |
# 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 | |
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: | |
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, GRAPH) | |
if args.graph: | |
print >> GRAPH, "}" | |
GRAPH.close() | |
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..." | |
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: | |
shutil.rmtree(args.target_dir) | |
sys.exit(1) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment