aboutsummaryrefslogtreecommitdiff
path: root/development/tools
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-10-31 19:06:35 +0100
committerMx Kookie <kookie@spacekookie.de>2020-12-21 05:10:28 +0100
commit90a45a314f99c7b08f25a02efcee2a847685562b (patch)
treea3c15278adbda7d0a79dcc170340b501da01d847 /development/tools
parentf56286e4c490ea69163988742a90d4376a4db08e (diff)
Add 'development/tools/cargo-workspace2/' from commit 'c3e1e0679bae7aa6bd668125cb997812a590f875'
git-subtree-dir: development/tools/cargo-workspace2 git-subtree-mainline: ddeaea72333d8bb6911ac45fcfe40ee1caa00b04 git-subtree-split: c3e1e0679bae7aa6bd668125cb997812a590f875
Diffstat (limited to '')
-rw-r--r--development/tools/cargo-workspace2/.envrc1
-rw-r--r--development/tools/cargo-workspace2/.gitignore2
-rw-r--r--development/tools/cargo-workspace2/.gitlab-ci.yml8
-rw-r--r--development/tools/cargo-workspace2/.travis.yml17
-rw-r--r--development/tools/cargo-workspace2/Cargo.lock192
-rw-r--r--development/tools/cargo-workspace2/Cargo.toml14
-rw-r--r--development/tools/cargo-workspace2/LICENSE674
-rw-r--r--development/tools/cargo-workspace2/README.md90
-rw-r--r--development/tools/cargo-workspace2/build.rs26
-rw-r--r--development/tools/cargo-workspace2/docs/ws2ql.md19
-rw-r--r--development/tools/cargo-workspace2/shell.nix9
-rw-r--r--development/tools/cargo-workspace2/src/bin/cargo-ws2.rs42
-rw-r--r--development/tools/cargo-workspace2/src/cargo/deps.rs75
-rw-r--r--development/tools/cargo-workspace2/src/cargo/error.rs42
-rw-r--r--development/tools/cargo-workspace2/src/cargo/gen.rs34
-rw-r--r--development/tools/cargo-workspace2/src/cargo/mod.rs25
-rw-r--r--development/tools/cargo-workspace2/src/cargo/parser.rs69
-rw-r--r--development/tools/cargo-workspace2/src/cli.rs91
-rw-r--r--development/tools/cargo-workspace2/src/lib.rs64
-rw-r--r--development/tools/cargo-workspace2/src/models/_crate.rs112
-rw-r--r--development/tools/cargo-workspace2/src/models/cargo.rs132
-rw-r--r--development/tools/cargo-workspace2/src/models/graph.rs78
-rw-r--r--development/tools/cargo-workspace2/src/models/mod.rs50
-rw-r--r--development/tools/cargo-workspace2/src/models/publish.rs20
-rw-r--r--development/tools/cargo-workspace2/src/ops/error.rs28
-rw-r--r--development/tools/cargo-workspace2/src/ops/executor.rs33
-rw-r--r--development/tools/cargo-workspace2/src/ops/mod.rs106
-rw-r--r--development/tools/cargo-workspace2/src/ops/parser.rs112
-rw-r--r--development/tools/cargo-workspace2/src/ops/publish/exec.rs117
-rw-r--r--development/tools/cargo-workspace2/src/ops/publish/mod.rs132
-rw-r--r--development/tools/cargo-workspace2/src/query/executor.rs83
-rw-r--r--development/tools/cargo-workspace2/src/query/mod.rs280
-rw-r--r--development/tools/cargo-workspace2/tests/resources/Cargo.toml11
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-client/Cargo.toml13
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-core/Cargo.toml25
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-crypto/Cargo.toml16
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-files/Cargo.toml13
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-http/Cargo.toml23
-rw-r--r--development/tools/cargo-workspace2/tests/resources/lockchain-server/Cargo.toml12
-rw-r--r--development/tools/cargo-workspace2/tests/tests.rs89
40 files changed, 2979 insertions, 0 deletions
diff --git a/development/tools/cargo-workspace2/.envrc b/development/tools/cargo-workspace2/.envrc
new file mode 100644
index 000000000000..051d09d292a8
--- /dev/null
+++ b/development/tools/cargo-workspace2/.envrc
@@ -0,0 +1 @@
+eval "$(lorri direnv)"
diff --git a/development/tools/cargo-workspace2/.gitignore b/development/tools/cargo-workspace2/.gitignore
new file mode 100644
index 000000000000..53eaa21960d1
--- /dev/null
+++ b/development/tools/cargo-workspace2/.gitignore
@@ -0,0 +1,2 @@
+/target
+**/*.rs.bk
diff --git a/development/tools/cargo-workspace2/.gitlab-ci.yml b/development/tools/cargo-workspace2/.gitlab-ci.yml
new file mode 100644
index 000000000000..c202dacb2f29
--- /dev/null
+++ b/development/tools/cargo-workspace2/.gitlab-ci.yml
@@ -0,0 +1,8 @@
+stages:
+ - test
+
+test:
+ image: rust:buster
+ script:
+ - cargo test
+
diff --git a/development/tools/cargo-workspace2/.travis.yml b/development/tools/cargo-workspace2/.travis.yml
new file mode 100644
index 000000000000..4c9b688766d0
--- /dev/null
+++ b/development/tools/cargo-workspace2/.travis.yml
@@ -0,0 +1,17 @@
+language: rust
+
+cache: cargo
+
+rust:
+- stable
+- beta
+- nightly
+
+matrix:
+ allow_failures:
+ - rust: nightly
+ fast_finish: true
+
+script:
+- cargo build --verbose --all
+- cargo test --verbose --all \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/Cargo.lock b/development/tools/cargo-workspace2/Cargo.lock
new file mode 100644
index 000000000000..3b59ec93405b
--- /dev/null
+++ b/development/tools/cargo-workspace2/Cargo.lock
@@ -0,0 +1,192 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "ascii"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "cargo-workspace2"
+version = "0.2.1"
+dependencies = [
+ "semver",
+ "textwrap",
+ "toml_edit",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "combine"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680"
+dependencies = [
+ "ascii",
+ "byteorder",
+ "either",
+ "memchr",
+ "unreachable",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "libc"
+version = "0.2.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
+
+[[package]]
+name = "memchr"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+
+[[package]]
+name = "num-integer"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "textwrap"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f53b1aca7d5fe2e17498a38cac0e1f5a33234d5b980fb36b9402bb93b98ae4"
+dependencies = [
+ "chrono",
+ "combine",
+ "linked-hash-map",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+dependencies = [
+ "void",
+]
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/development/tools/cargo-workspace2/Cargo.toml b/development/tools/cargo-workspace2/Cargo.toml
new file mode 100644
index 000000000000..8c8abb0144e2
--- /dev/null
+++ b/development/tools/cargo-workspace2/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "cargo-workspace2"
+version = "0.2.1"
+description = "A tool to query and manage complex cargo workspaces"
+authors = ["Mx Kookie <kookie@spacekookie.de>"]
+documentation = "https://docs.rs/cargo-workspace2"
+repository = "https://git.open-communication.net/spacekookie/cargo-workspace2"
+license = "GPL-3.0-or-later"
+edition = "2018"
+
+[dependencies]
+toml_edit = "0.1.3"
+semver = "0.9.0"
+textwrap = "0.12" \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/LICENSE b/development/tools/cargo-workspace2/LICENSE
new file mode 100644
index 000000000000..94a9ed024d38
--- /dev/null
+++ b/development/tools/cargo-workspace2/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/development/tools/cargo-workspace2/README.md b/development/tools/cargo-workspace2/README.md
new file mode 100644
index 000000000000..c5ae6e7463bf
--- /dev/null
+++ b/development/tools/cargo-workspace2/README.md
@@ -0,0 +1,90 @@
+# cargo-workspace2 🔹 [![pipeline status](https://git.open-communication.net/spacekookie/cargo-workspace2/badges/develop/pipeline.svg)](https://git.open-communication.net/spacekookie/cargo-workspace2/-/commits/develop)
+
+A better tool to manage complex cargo workspaces, by dynamically
+building a dependency graph of your project to query, and run commands
+on. This project is both a CLI and a library. A repl is on the
+roadmap.
+
+## Usage
+
+When using `cargo-ws2` you first provide it some query to find crates
+in your workspace, and then a command with it's specific parameters to
+execute for each crate selected in the query.
+
+`cargo ws2 <QUERY LANG> <COMMAND> [COMMAND OPTIONS]`
+
+
+Currently supported commands.
+
+* `print` - echo the selected crate set
+* `publish` - publish a set of crates
+
+Additionally, there are special "bang commands", that take precedence
+over other argument line input, and don't have to be put in a
+particular position.
+
+* `!help [COMMAND]` - show a help screen for the program, or a specific command
+* `!version` - print the program, rustc version, etc
+* `!debug` - enable debug printing, and parse the rest of the line as normal
+
+`cargo-ws2` provides a query system based on the `ws2ql` query
+expression language. Following are some examples.
+
+* List crates between `[ ]`: `[ foo bar baz ]`
+* Or query the dependency graph with a `{}` block: `{ foo < }` (all
+ crates that depend on `foo`)
+* Include crates by path tree: `[ ./foo/* ]` (not implemented yet!)
+* Even search via regex: `[/crate-suffix\$/]` (not implemented yet!)
+
+See the full description of `ws2ql` in the [docs](./docs/ws2ql.md)!
+
+
+### Publishing crates
+
+This tool was largely written to make publishing crates in a workspace
+easier. Let's look at an example.
+
+```toml
+[package]
+name = "foo"
+version = "0.1.0"
+# ...
+
+[dependencies]
+bar = { version = "0.5.0", path = "../bar" }
+```
+
+```toml
+[package]
+name = "bar"
+version = "0.5.0"
+```
+
+This is a common setup: pointing cargo at the `path` of `bar` means
+that changes to the sources on disk become available, before having to
+publish to [crates.io](https://crates.io). But having a version
+dependency is required when trying to publish, thus we add this too.
+
+Unfortunately now, when we update `bar` to version `0.6.0` our
+workspace will stop building, because `foo` depends on a version of
+`bar` that's different from the one we're pointing it to.
+
+What `cargo-ws2` does when you run `cargo ws2 [bar] publish minor` is
+bump bar to `0.6.0`, and also update the dependency line in `foo` to
+be `{ version = "0.6.0", path = "../bar" }`, without touching the
+version of `foo`.
+
+If you want all dependent crates to be bumped to the same version,
+prefix your publish level (`major`, `minor`, `patch`, or a semver
+string) with `=`.
+
+`cargo ws2 [bar] publish =minor` will bump both foo, and bar to
+`0.6.0` (picking the highest version of the set if their starting
+versions are not yet the same.
+
+
+## License
+
+`cargo-workspace2` is free software, licensed under the GNU General
+Public License 3.0 (or later). See the LICENSE file for a full copy
+of the license.
diff --git a/development/tools/cargo-workspace2/build.rs b/development/tools/cargo-workspace2/build.rs
new file mode 100644
index 000000000000..8e150d082cfe
--- /dev/null
+++ b/development/tools/cargo-workspace2/build.rs
@@ -0,0 +1,26 @@
+use std::{env, fs::File, io::Write, path::PathBuf, process::Command};
+
+fn main() {
+ let rustc_path = env::var("CARGO_WS2_RUSTC").unwrap_or_else(|_| "rustc".into());
+ let version = Command::new(rustc_path)
+ .arg("-V")
+ .output()
+ .expect("failed to get rustc version. Is rustc in $PATH?")
+ .stdout;
+
+ let out_path = PathBuf::new();
+ let mut f = File::create(
+ out_path
+ .join(env::var("OUT_DIR").unwrap())
+ .join("rustc.version"),
+ )
+ .unwrap();
+ f.write_all(
+ String::from_utf8(version)
+ .expect("Invalid rustc command return")
+ .replace("\"", "")
+ .trim()
+ .as_bytes(),
+ )
+ .unwrap();
+}
diff --git a/development/tools/cargo-workspace2/docs/ws2ql.md b/development/tools/cargo-workspace2/docs/ws2ql.md
new file mode 100644
index 000000000000..a255a650bb50
--- /dev/null
+++ b/development/tools/cargo-workspace2/docs/ws2ql.md
@@ -0,0 +1,19 @@
+# ws2 query language
+
+The `cargo-ws2` query language (`ws2ql`) allows users to specify a
+set of inputs, and an operation to execute on it.
+
+## Basic rules
+
+* Inside `[]` are sets (meaning items de-dup), space separated
+* IF `[]` contains a `/` anywhere _but_ the beginning AND end,
+ query becomes a path glob
+* IF `[]` contains `/` at start AND end, query becomes a regex
+* An operation is parsed in the order of the fields in it's struct
+ (so publish order is `type mod devel`, etc)
+* Inside `{}` you can create dependency maps
+ * `{ foo < }` represents all crates that depend on `foo`
+ * `{ foo < bar &< }` represents all crates that depend on `foo` AND `bar`
+ * `{ foo < bar |< }` represents all crates that depend on `foo` OR `bar`
+ * `{ foo < bar &!< }` represents all crates that depend on `foo` AND NOT `bar`
+
diff --git a/development/tools/cargo-workspace2/shell.nix b/development/tools/cargo-workspace2/shell.nix
new file mode 100644
index 000000000000..e055bd79d189
--- /dev/null
+++ b/development/tools/cargo-workspace2/shell.nix
@@ -0,0 +1,9 @@
+with import <nixpkgs> {
+ config.android_sdk.accept_license = true;
+ config.allowUnfree = true;
+};
+
+stdenv.mkDerivation {
+ name = "cargo-workspace2";
+ buildInputs = with pkgs; [ clangStdenv ];
+}
diff --git a/development/tools/cargo-workspace2/src/bin/cargo-ws2.rs b/development/tools/cargo-workspace2/src/bin/cargo-ws2.rs
new file mode 100644
index 000000000000..8de5bc60f6a4
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/bin/cargo-ws2.rs
@@ -0,0 +1,42 @@
+// extern crate cargo_ws_release;
+// #[macro_use]
+// extern crate clap;
+// extern crate toml;
+// extern crate toml_edit;
+
+// use cargo_ws_release::data_models::level::*;
+// use cargo_ws_release::do_batch_release;
+// use clap::{App, Arg};
+// use std::fs::File;
+// use std::process;
+
+use cargo_workspace2 as ws2;
+
+use std::env::{args, current_dir};
+use ws2::cli::{self, CmdSet};
+use ws2::models::{CargoWorkspace, CrateId, Workspace};
+use ws2::{ops::Op, query::Query};
+
+fn main() {
+ let CmdSet { debug, line } = cli::parse_env_args();
+ let (q, rest) = Query::parse(line.into_iter());
+
+ let path = current_dir().unwrap();
+ let ws = Workspace::process(match CargoWorkspace::open(path) {
+ Ok(c) => c,
+ Err(e) => {
+ eprintln!("An error occured: {}", e);
+ std::process::exit(1);
+ }
+ });
+
+ let set = ws.query(q);
+ let op = Op::parse(rest);
+
+ mutate(ws, op, set);
+}
+
+/// Consume a workspace to mutate it
+fn mutate(mut ws: Workspace, op: Op, set: Vec<CrateId>) {
+ ws.execute(op, set);
+}
diff --git a/development/tools/cargo-workspace2/src/cargo/deps.rs b/development/tools/cargo-workspace2/src/cargo/deps.rs
new file mode 100644
index 000000000000..d40170ccd841
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cargo/deps.rs
@@ -0,0 +1,75 @@
+use super::v_to_s;
+use toml_edit::InlineTable;
+
+/// An intra-workspace dependency
+///
+/// In a Cargo.toml file these are expressed as paths, and sometimes
+/// also as versions.
+///
+/// ```toml
+/// [dependencies]
+/// my-other-crate = { version = "0.1.0", path = "../other-crate" }
+/// ```
+#[derive(Debug, Clone)]
+pub struct Dependency {
+ pub name: String,
+ pub alias: Option<String>,
+ pub version: Option<String>,
+ pub path: Option<String>,
+}
+
+impl Dependency {
+ pub(crate) fn parse(n: String, t: &InlineTable) -> Option<Self> {
+ let v = t.get("version").map(|s| v_to_s(s));
+ let p = t.get("path").map(|s| v_to_s(s));
+
+ // If a `package` key is present, set it as the name, and set
+ // the `n` as the alias. When we look for keys later, the
+ // alias has precedence over the actual name, but this way
+ // `name` is always the actual crate name which is important
+ // for dependency resolution.
+ let (alias, name) = match t
+ .get("package")
+ .map(|s| v_to_s(s).replace("\"", "").trim().to_string())
+ {
+ Some(alias) => (Some(n), alias),
+ None => (None, n),
+ };
+
+ match (v, p) {
+ (version @ Some(_), path @ Some(_)) => Some(Self {
+ name,
+ alias,
+ version,
+ path,
+ }),
+ (version @ Some(_), None) => Some(Self {
+ name,
+ alias,
+ version,
+ path: None,
+ }),
+ (None, path @ Some(_)) => Some(Self {
+ name,
+ alias,
+ version: None,
+ path,
+ }),
+ (None, None) => None,
+ }
+ }
+
+ /// Check if the dependency has a provided version
+ pub fn has_version(&self) -> bool {
+ self.version.is_some()
+ }
+
+ /// Check if the dependency has a provided path
+ pub fn has_path(&self) -> bool {
+ self.path.is_some()
+ }
+
+ pub fn alias(&self) -> Option<String> {
+ self.alias.clone()
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/cargo/error.rs b/development/tools/cargo-workspace2/src/cargo/error.rs
new file mode 100644
index 000000000000..cf4b7e3d1671
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cargo/error.rs
@@ -0,0 +1,42 @@
+use std::{fmt, io};
+use toml_edit::TomlError;
+
+/// Errors occured while interacting with Cargo.toml files
+#[derive(Debug)]
+pub enum CargoError {
+ /// Failed to read or write a file
+ Io,
+ /// Error parsing Cargo.toml file
+ Parsing,
+ /// Provided Cargo.toml was no workspace
+ NoWorkspace,
+ /// Provided Cargo.toml had no dependencies
+ NoDependencies,
+}
+
+impl fmt::Display for CargoError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Io => "General I/O error",
+ Self::Parsing => "Parsing error",
+ Self::NoWorkspace => "Selected crate root is not a workspace!",
+ Self::NoDependencies => "No dependencies found!",
+ }
+ )
+ }
+}
+
+impl From<io::Error> for CargoError {
+ fn from(_: io::Error) -> Self {
+ Self::Io
+ }
+}
+
+impl From<TomlError> for CargoError {
+ fn from(_: TomlError) -> Self {
+ Self::Parsing
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/cargo/gen.rs b/development/tools/cargo-workspace2/src/cargo/gen.rs
new file mode 100644
index 000000000000..4ba891e15835
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cargo/gen.rs
@@ -0,0 +1,34 @@
+//! Generate toml data
+
+use super::{CargoError, Result};
+use std::{fs::File, io::Write, path::PathBuf};
+use toml_edit::{value, Document, Item, Value};
+
+/// Sync a document back into it's Cargo.toml
+pub(crate) fn sync(doc: &mut Document, path: PathBuf) -> Result<()> {
+ if !path.exists() {
+ return Err(CargoError::Io);
+ }
+
+ let mut f = File::create(path)?;
+ f.write_all(doc.to_string().as_bytes())?;
+ Ok(())
+}
+
+/// Takes a mutable document, dependency alias or name, and version
+pub(crate) fn update_dependency(doc: &mut Document, dep: &String, ver: &String) {
+ match doc.as_table_mut().entry("dependencies") {
+ Item::Table(ref mut t) => match t.entry(dep.as_str()) {
+ Item::Value(Value::InlineTable(ref mut t)) => {
+ if let Some(v) = t.get_mut("version") {
+ *v = Value::from(ver.clone());
+ }
+ return;
+ }
+ _ => {}
+ },
+ _ => {}
+ }
+
+ // eprintln!("Invalid dependency format!");
+}
diff --git a/development/tools/cargo-workspace2/src/cargo/mod.rs b/development/tools/cargo-workspace2/src/cargo/mod.rs
new file mode 100644
index 000000000000..deb54563d6ba
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cargo/mod.rs
@@ -0,0 +1,25 @@
+//! A set of models directly related to `Cargo.toml` files
+
+mod deps;
+pub use deps::Dependency;
+
+mod error;
+pub use error::CargoError;
+
+mod parser;
+pub(crate) use parser::{get_members, parse_dependencies, parse_root_toml, parse_toml};
+
+mod gen;
+pub(crate) use gen::{sync, update_dependency};
+
+pub(crate) type Result<T> = std::result::Result<T, CargoError>;
+
+use toml_edit::Value;
+
+/// Turn a toml Value into a String, panic if it fails
+pub(self) fn v_to_s(v: &Value) -> String {
+ match v {
+ Value::String(s) => s.to_string(),
+ _ => unreachable!(),
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/cargo/parser.rs b/development/tools/cargo-workspace2/src/cargo/parser.rs
new file mode 100644
index 000000000000..c3e67e2785fb
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cargo/parser.rs
@@ -0,0 +1,69 @@
+use crate::cargo::{CargoError, Dependency, Result};
+use std::{fs::File, io::Read, path::PathBuf};
+use toml_edit::{Document, Item, Value};
+
+/// Parse the root entry of a cargo workspace into a document
+pub(crate) fn parse_root_toml(p: PathBuf) -> Result<Document> {
+ let mut buf = String::new();
+ File::open(p)?.read_to_string(&mut buf)?;
+ Ok(buf.parse::<Document>()?)
+}
+
+/// For a root-config, get the paths of member crates
+pub(crate) fn get_members(doc: &Document) -> Result<Vec<String>> {
+ match &doc["workspace"]["members"] {
+ Item::Value(Value::Array(arr)) => Ok(arr
+ .iter()
+ .filter_map(|val| match val {
+ Value::String(name) => Some(format!("{}", name)),
+ val => {
+ eprintln!("Ignoring value `{:?}` in List of strings", val);
+ None
+ }
+ })
+ .map(|s| s.replace("\"", "").trim().into())
+ .map(|s: String| {
+ if s.trim().starts_with("#") {
+ s.split("\n").map(ToString::to_string).collect()
+ } else {
+ vec![s]
+ }
+ })
+ // Holy shit this crate is useless! Why would it not just
+ // strip the commented lines, instead of doing some
+ // bullshit like this...
+ .fold(vec![], |mut vec, ovec| {
+ ovec.into_iter().for_each(|i| {
+ if !i.trim().starts_with("#") && i != "" {
+ vec.push(i.trim().to_string());
+ }
+ });
+ vec
+ })),
+ _ => Err(CargoError::NoWorkspace),
+ }
+}
+
+/// Parse a member crate Cargo.toml file
+pub(crate) fn parse_toml(p: PathBuf) -> Result<Document> {
+ let mut buf = String::new();
+ File::open(p)?.read_to_string(&mut buf)?;
+ Ok(buf.parse::<Document>()?)
+}
+
+/// Parse a member crate set of dependencies
+///
+/// When a crate is not a table, it can't be a workspace member
+/// dependency, so is skipped.
+pub(crate) fn parse_dependencies(doc: &Document) -> Vec<Dependency> {
+ match &doc["dependencies"] {
+ Item::Table(ref t) => t
+ .iter()
+ .filter_map(|(n, v)| match v {
+ Item::Value(Value::InlineTable(ref t)) => Dependency::parse(n.to_string(), t),
+ _ => None,
+ })
+ .collect(),
+ _ => vec![],
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/cli.rs b/development/tools/cargo-workspace2/src/cli.rs
new file mode 100644
index 000000000000..afea9e224176
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/cli.rs
@@ -0,0 +1,91 @@
+//! Helpers and utilities to parse the CLI input
+
+use std::env;
+
+pub struct CmdSet {
+ pub debug: bool,
+ pub line: Vec<String>,
+}
+
+fn get_nth(idx: usize) -> Option<String> {
+ env::args().nth(idx).as_ref().map(|s| s.to_owned()).clone()
+}
+
+/// Call this instead of env::args() - it handles !commands too
+pub fn parse_env_args() -> CmdSet {
+ let mut line: Vec<_> = env::args().collect();
+ let mut debug = false;
+
+ if line.len() == 1 {
+ render_help(2);
+ }
+
+ find_bang_commands().into_iter().for_each(|(idx, bang)| {
+ let maybe_next = line.iter().nth(idx + 1).as_ref().map(|s| s.to_owned());
+
+ match bang.trim() {
+ "!help" => match maybe_next {
+ None => render_help(0),
+ Some(cmd) => crate::ops::render_help(cmd.to_string()),
+ },
+ "!version" => render_version(),
+ "!debug" => {
+ debug = true;
+ line.remove(idx);
+ }
+ bang => {
+ if debug {
+ eprintln!("Unrecognised bang command: {}", bang);
+ }
+
+ line.remove(idx);
+ }
+ }
+ });
+
+ CmdSet { line, debug }
+}
+
+/// Get env::args() and look for any string with a `!` in front of it.
+/// If it's not in the set of known bang commands, ignore it.
+fn find_bang_commands() -> Vec<(usize, String)> {
+ env::args()
+ .enumerate()
+ .filter_map(|(idx, s)| {
+ if s.starts_with("!") {
+ Some((idx, s))
+ } else {
+ None
+ }
+ })
+ .collect()
+}
+
+pub(crate) fn render_help(code: i32) -> ! {
+ eprintln!("cargo-ws v{}", env!("CARGO_PKG_VERSION"));
+ eprintln!("An expression language and command executor for cargo workspaces.");
+ eprintln!("Usage: cargo ws2 <QUERY LANG> <COMMAND> [COMMAND ARGS] [!debug]");
+ eprintln!(" cargo ws2 [!version | !debug]");
+ eprintln!(" cargo ws2 !help [COMMAND]");
+ eprintln!("");
+
+ crate::ops::list_commands();
+ eprintln!("");
+
+ eprintln!("Query language examples:\n");
+ eprintln!(" - [ foo bar ]: select crates foo and bar");
+ eprintln!(" - {{ foo < }}: select crates that depend on foo");
+ eprintln!(" - {{ foo < bar &< }}: select crates that depend on foo AND bar");
+ eprintln!(" - {{ foo < bar |< }}: select crates that depend on foo OR bar");
+ eprintln!("\nIf you have any questions, or find bugs, please e-mail me: kookie@spacekookie.de");
+ std::process::exit(code)
+}
+
+fn render_version() -> ! {
+ eprintln!("cargo-ws v{}", env!("CARGO_PKG_VERSION"));
+ eprintln!(
+ "Build with: {}",
+ include_str!(concat!(env!("OUT_DIR"), "/rustc.version"))
+ );
+ std::process::exit(0)
+}
diff --git a/development/tools/cargo-workspace2/src/lib.rs b/development/tools/cargo-workspace2/src/lib.rs
new file mode 100644
index 000000000000..689c3601cd55
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/lib.rs
@@ -0,0 +1,64 @@
+//! cargo-workspace2 is a library to help manage cargo workspaces
+//!
+//! Out of the box the `cargo` workspace experience leaves a lot to be
+//! desired. Managing a repo with many crates in it can get out of
+//! hand quickly. Moreover, other tools that try to solve these
+//! issues often pick _one_ particular usecase of cargo workspaces,
+//! and enforce very strict rules on how to use them.
+//!
+//! This library aims to solve some of the issues of dealing with
+//! workspaces in a way that doesn't enforce a usage mode for the
+//! user.
+//!
+//! This package also publishes a binary (cargo ws2), which is
+//! recommended for most users. In case the binary handles a use-case
+//! you have in a way that you don't like, this library aims to
+//! provide a fallback so that you don't have to re-implement
+//! everything from scratch.
+//!
+//! ## Using this library
+//!
+//! Parsing happens in stages. First you need to use the
+//! [`cargo`](./cargo/index.html) module to parse the actual
+//! `Cargo.toml` files. After that you can use the cargo models in
+//! [`models`](models/index.html) to further process dependencies, and
+//! create a [`DepGraph`](models/struct.DepGraph.html) to resolve queries and make changes.
+
+pub mod cargo;
+pub mod models;
+pub mod ops;
+pub mod query;
+
+#[doc(hidden)]
+pub mod cli;
+
+// extern crate toml;
+// extern crate toml_edit;
+
+// pub use data_models::graph;
+// use data_models::level::Level;
+// use graph::DepGraph;
+// use std::fs::File;
+// pub use utilities::cargo_utils;
+// pub use utilities::utils;
+
+// pub mod data_models;
+// pub mod utilities;
+
+// pub fn do_batch_release(f: File, lvl: &Level) -> DepGraph {
+// let members = cargo_utils::get_members(f);
+// let configs = cargo_utils::batch_load_configs(&members);
+
+// let v = configs
+// .iter()
+// .map(|c| cargo_utils::parse_config(c, &members))
+// .fold(DepGraph::new(), |mut graph, (name, deps)| {
+// graph.add_node(name.clone());
+
+// deps.iter()
+// .fold(graph, |graph, dep| graph.add_dependency(&name, dep))
+// });
+
+// println!("{:#?}", v);
+// v
+// }
diff --git a/development/tools/cargo-workspace2/src/models/_crate.rs b/development/tools/cargo-workspace2/src/models/_crate.rs
new file mode 100644
index 000000000000..68d2baad2bad
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/models/_crate.rs
@@ -0,0 +1,112 @@
+use crate::models::{CargoCrate, CrateId, DepGraph};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::{
+ cmp::{self, Eq, Ord, PartialEq, PartialOrd},
+ collections::BTreeSet,
+ path::PathBuf,
+};
+
+static ID_CTR: AtomicUsize = AtomicUsize::new(0);
+
+/// A crate in a cargo workspace
+///
+/// Has a name, path (stored as the offset of the root), and set of
+/// dependencies inside the workspace. To get the dependents of this
+/// crate, query the dependency graph with the set of other crate IDs.
+#[derive(Clone, Debug)]
+pub struct Crate {
+ /// Numeric Id of this crate
+ pub id: CrateId,
+ /// Package name, not the folder name
+ pub name: String,
+ /// Path offset of the workspace root
+ pub cc: CargoCrate,
+ /// List of dependencies this crate has inside this workspace
+ pub dependencies: BTreeSet<CrateId>,
+}
+
+impl PartialEq for Crate {
+ fn eq(&self, other: &Self) -> bool {
+ self.id == other.id
+ }
+}
+
+impl Eq for Crate {}
+
+impl Ord for Crate {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.id.cmp(&other.id)
+ }
+}
+
+impl PartialOrd for Crate {
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+/// Increment the monotonicly increasing Id
+fn incr_id() -> usize {
+ ID_CTR.fetch_add(1, Ordering::Relaxed)
+}
+
+impl Crate {
+ pub fn new(cc: CargoCrate) -> Self {
+ Self {
+ id: incr_id(),
+ name: cc.name(),
+ cc,
+ dependencies: BTreeSet::default(),
+ }
+ }
+
+ /// Call this function once all crates have been loaded into scope
+ pub fn process(&mut self, g: &DepGraph) {
+ let deps: Vec<_> = self
+ .cc
+ .dependencies
+ .iter()
+ .filter_map(|d| g.find_crate(&d.name))
+ .collect();
+
+ deps.into_iter().for_each(|cid| self.add_dependency(cid));
+ }
+
+ /// Get the crate name
+ pub fn name(&self) -> &String {
+ &self.name
+ }
+
+ /// Get the crate path
+ pub fn path(&self) -> &PathBuf {
+ &self.cc.path
+ }
+
+ /// Get the current version
+ pub fn version(&self) -> String {
+ self.cc.version()
+ }
+
+ /// Add a dependency of this crate
+ pub fn add_dependency(&mut self, id: CrateId) {
+ self.dependencies.insert(id);
+ }
+
+ /// Check if this crate has a particular dependency
+ pub fn has_dependency(&self, id: CrateId) -> bool {
+ self.dependencies.contains(&id)
+ }
+
+ pub fn change_dependency(&mut self, dep: &String, new_ver: &String) {
+ self.cc.change_dep(dep, new_ver);
+ }
+
+ /// Publish a new version of this crate
+ pub fn publish(&mut self, new_version: String) {
+ self.cc.set_version(new_version);
+ }
+
+ pub fn sync(&mut self) {
+ self.cc.sync();
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/models/cargo.rs b/development/tools/cargo-workspace2/src/models/cargo.rs
new file mode 100644
index 000000000000..b020e82e418d
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/models/cargo.rs
@@ -0,0 +1,132 @@
+use crate::cargo::{self, Dependency, Result};
+use std::{fmt, path::PathBuf};
+use toml_edit::{value, Document, Item, Value};
+
+/// Initial representation of a crate, before being parsed
+#[derive(Clone)]
+pub struct CargoCrate {
+ pub doc: Document,
+ pub path: PathBuf,
+ pub dependencies: Vec<Dependency>,
+}
+
+impl fmt::Debug for CargoCrate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.path.as_path().display())
+ }
+}
+
+impl CargoCrate {
+ /// Get the crate name from the inner document
+ pub fn name(&self) -> String {
+ match &self.doc["package"]["name"] {
+ Item::Value(Value::String(ref name)) => {
+ name.to_string().replace("\"", "").as_str().trim().into()
+ }
+ _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)),
+ }
+ }
+
+ /// Get the current version
+ pub fn version(&self) -> String {
+ match &self.doc["package"]["version"] {
+ Item::Value(Value::String(ref name)) => {
+ name.to_string().replace("\"", "").as_str().trim().into()
+ }
+ _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)),
+ }
+ }
+
+ /// Find a cargo dependency by name
+ pub fn dep_by_name(&self, name: &String) -> &Dependency {
+ self.dependencies
+ .iter()
+ .find(|c| &c.name == name)
+ .as_ref()
+ .unwrap()
+ }
+
+ pub fn change_dep(&mut self, dep: &String, ver: &String) {
+ let dep = self
+ .dep_by_name(dep)
+ .alias()
+ .unwrap_or(dep.to_string())
+ .clone();
+ cargo::update_dependency(&mut self.doc, &dep, ver);
+ }
+
+ pub fn all_deps_mut(&mut self) -> Vec<&mut Dependency> {
+ self.dependencies.iter_mut().collect()
+ }
+
+ /// Check if this crate depends on a specific version of another
+ pub fn has_version(&self, name: &String) -> bool {
+ self.dep_by_name(name).has_version()
+ }
+
+ /// Check if this crate depends on a specific path of another
+ pub fn has_path(&self, name: &String) -> bool {
+ self.dep_by_name(name).has_version()
+ }
+
+ /// Set a new version for this crate
+ pub fn set_version(&mut self, version: String) {
+ self.doc["package"]["version"] = value(version);
+ }
+
+ /// Sync any changes made to the document to disk
+ pub fn sync(&mut self) {
+ cargo::sync(&mut self.doc, self.path.join("Cargo.toml")).unwrap();
+ }
+}
+
+/// Initial representation of the workspate, before getting parsed
+pub struct CargoWorkspace {
+ pub root: PathBuf,
+ pub crates: Vec<CargoCrate>,
+}
+
+impl CargoWorkspace {
+ /// Open a workspace and parse dependency graph
+ ///
+ /// Point this to the root of the workspace, do the root
+ /// `Cargo.toml` file.
+ pub fn open(p: impl Into<PathBuf>) -> Result<Self> {
+ let path = p.into();
+
+ let root_cfg = cargo::parse_root_toml(path.join("Cargo.toml"))?;
+ let members = cargo::get_members(&root_cfg)?;
+
+ let m_cfg: Vec<_> = members
+ .into_iter()
+ .filter_map(
+ |name| match cargo::parse_toml(path.join(&name).join("Cargo.toml")) {
+ Ok(doc) => Some((
+ PathBuf::new().join(name),
+ cargo::parse_dependencies(&doc),
+ doc,
+ )),
+ Err(e) => {
+ eprintln!(
+ "Error occured while parsing member `{}`/`Cargo.toml`: {:?}",
+ name, e
+ );
+ None
+ }
+ },
+ )
+ .collect();
+
+ Ok(Self {
+ root: path,
+ crates: m_cfg
+ .into_iter()
+ .map(|(path, dependencies, doc)| CargoCrate {
+ path,
+ dependencies,
+ doc,
+ })
+ .collect(),
+ })
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/models/graph.rs b/development/tools/cargo-workspace2/src/models/graph.rs
new file mode 100644
index 000000000000..867c463fb1e3
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/models/graph.rs
@@ -0,0 +1,78 @@
+use crate::models::{CargoCrate, Crate, CrateId};
+use std::collections::{BTreeMap, BTreeSet};
+use std::path::PathBuf;
+
+/// Dependency graph in a workspace
+pub struct DepGraph {
+ /// Mapping of crates in the workspace
+ members: BTreeMap<CrateId, Crate>,
+ /// Map of crates and the members that depend on them
+ dependents: BTreeMap<CrateId, BTreeSet<CrateId>>,
+}
+
+impl DepGraph {
+ /// Create a new, empty dependency graph
+ pub fn new() -> Self {
+ Self {
+ members: Default::default(),
+ dependents: Default::default(),
+ }
+ }
+
+ pub fn add_crate(&mut self, cc: CargoCrate) {
+ let cc = Crate::new(cc);
+ self.members.insert(cc.id, cc);
+ }
+
+ /// Cache the dependents graph for all crates
+ pub fn finalise(&mut self) {
+ let mut members = self.members.clone();
+ members.iter_mut().for_each(|(_, c)| c.process(&self));
+
+ members.iter().for_each(|(id, _crate)| {
+ _crate.dependencies.iter().for_each(|dep_id| {
+ self.dependents.entry(*dep_id).or_default().insert(*id);
+ });
+ });
+ let _ = std::mem::replace(&mut self.members, members);
+ }
+
+ /// Get a crate by ID
+ pub fn get_crate(&self, id: CrateId) -> &Crate {
+ self.members.get(&id).as_ref().unwrap()
+ }
+
+ /// Get mutable access to a crate by ID
+ pub fn mut_crate(&mut self, id: CrateId) -> &mut Crate {
+ self.members.get_mut(&id).unwrap()
+ }
+
+ /// Find a crate via it's name
+ pub fn find_crate(&self, name: &String) -> Option<CrateId> {
+ self.members
+ .iter()
+ .find(|(_, c)| c.name() == name)
+ .map(|(id, _)| *id)
+ }
+
+ /// Find a crate by it's path-offset in the workspace
+ pub fn find_crate_by_path(&self, name: &String) -> Option<CrateId> {
+ self.members
+ .iter()
+ .find(|(_, c)| c.path() == &PathBuf::new().join(name))
+ .map(|(id, _)| *id)
+ }
+
+ /// Get a crate's dependents
+ pub fn get_dependents(&self, id: CrateId) -> Vec<CrateId> {
+ self.dependents
+ .get(&id)
+ .as_ref()
+ .map(|set| set.iter().cloned().collect())
+ .unwrap_or(vec![])
+ }
+
+ pub fn get_all(&self) -> Vec<&Crate> {
+ self.members.iter().map(|(_, c)| c).collect()
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/models/mod.rs b/development/tools/cargo-workspace2/src/models/mod.rs
new file mode 100644
index 000000000000..759b2703f9f0
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/models/mod.rs
@@ -0,0 +1,50 @@
+//! Collection of cargo workspace data models.
+//!
+//! To start parsing types, construct a `CargoWorkspace`, which you
+//! can then modify with commands found in [`ops`](../ops/index.html).
+
+mod cargo;
+pub use cargo::{CargoCrate, CargoWorkspace};
+
+mod _crate;
+pub use _crate::Crate;
+
+mod publish;
+pub use publish::{MutationSet, PubMutation};
+
+mod graph;
+pub use graph::DepGraph;
+
+pub type CrateId = usize;
+
+use crate::{ops::Op, query::Query};
+use std::path::PathBuf;
+
+/// A fully parsed workspace
+pub struct Workspace {
+ pub root: PathBuf,
+ dgraph: DepGraph,
+}
+
+impl Workspace {
+ /// Create a parsed workspace by passing in the stage1 parse data
+ pub fn process(cws: CargoWorkspace) -> Self {
+ let CargoWorkspace { root, crates } = cws;
+
+ let mut dgraph = DepGraph::new();
+ crates.into_iter().for_each(|cc| dgraph.add_crate(cc));
+ dgraph.finalise();
+
+ Self { root, dgraph }
+ }
+
+ /// Execute a query on this workspace to find crate IDs
+ pub fn query(&self, q: Query) -> Vec<CrateId> {
+ q.execute(&self.dgraph)
+ }
+
+ /// Execute an operation on a set of crates this in workspace
+ pub fn execute(&mut self, op: Op, target: Vec<CrateId>) {
+ op.execute(target, self.root.clone(), &mut self.dgraph)
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/models/publish.rs b/development/tools/cargo-workspace2/src/models/publish.rs
new file mode 100644
index 000000000000..11984017d49f
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/models/publish.rs
@@ -0,0 +1,20 @@
+use crate::models::{Crate, CrateId};
+
+/// A publishing mutation executed on the graph
+pub struct PubMutation {
+ _crate: CrateId,
+ new_version: String,
+}
+
+impl PubMutation {
+ /// Createa new motation from a crate a version string
+ pub fn new(c: &Crate, new_version: String) -> Self {
+ Self {
+ _crate: c.id,
+ new_version,
+ }
+ }
+}
+
+/// A collection of mutations performed in a batch
+pub struct MutationSet {}
diff --git a/development/tools/cargo-workspace2/src/ops/error.rs b/development/tools/cargo-workspace2/src/ops/error.rs
new file mode 100644
index 000000000000..3dde73d954d8
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/error.rs
@@ -0,0 +1,28 @@
+use std::fmt::{self, Display, Formatter};
+
+/// Special result type that wraps an OpError
+pub type Result<T> = std::result::Result<T, OpError>;
+
+/// An error that occured while running an operation
+#[derive(Debug)]
+pub enum OpError {
+ NoSuchCrate(String),
+ CircularDependency(String, String),
+}
+
+impl Display for OpError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::NoSuchCrate(n) => format!("No crate `{}` was not found in the workspace", n),
+ Self::CircularDependency(a, b) => format!(
+ "Crates `{}` and `{}` share a hard circular dependency.\
+ Operation not possible!",
+ a, b
+ ),
+ }
+ )
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/ops/executor.rs b/development/tools/cargo-workspace2/src/ops/executor.rs
new file mode 100644
index 000000000000..90a4f68303b1
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/executor.rs
@@ -0,0 +1,33 @@
+//! An op executor
+
+use super::{versions, Print, Publish, PublishMod as Mod, PublishType};
+use crate::models::{CrateId, DepGraph};
+use semver::Version;
+use std::path::PathBuf;
+
+#[inline]
+fn abs_path<'a>(root: &PathBuf, c_path: PathBuf, abs: bool) -> String {
+ if abs {
+ root.join(c_path).display().to_string()
+ } else {
+ c_path.display().to_string()
+ }
+}
+
+/// A simple print executor
+pub(super) fn print(p: Print, set: Vec<CrateId>, root: PathBuf, g: &mut DepGraph) {
+ set.into_iter()
+ .map(|id| g.get_crate(id))
+ .map(|_crate| {
+ let c_path = _crate.path().clone();
+
+ match p {
+ Print::Name => format!("{}", _crate.name()),
+ Print::Path { abs } => format!("{}", abs_path(&root, c_path, abs)),
+ Print::Both { abs } => {
+ format!("{}: {}", _crate.name(), abs_path(&root, c_path, abs))
+ }
+ }
+ })
+ .for_each(|s| println!("{}", s));
+}
diff --git a/development/tools/cargo-workspace2/src/ops/mod.rs b/development/tools/cargo-workspace2/src/ops/mod.rs
new file mode 100644
index 000000000000..026aaa0a140a
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/mod.rs
@@ -0,0 +1,106 @@
+//! Atomic operations on a cargo workspace
+//!
+//! This module contains operations that can be executed on a
+//! workspace. They take some set of inputs, modelled as fields, and
+//! produce a shared output which is represented by `Result`
+
+mod publish;
+pub(self) use publish::{versions, Publish, PublishMod, PublishType};
+
+mod error;
+mod executor;
+mod parser;
+
+use crate::models::{CrateId, DepGraph};
+pub use error::{OpError, Result};
+use std::path::PathBuf;
+
+trait RenderHelp {
+ fn render_help(c: i32) -> !;
+}
+
+/// Render the help-page for a particular command
+pub(crate) fn render_help(cmd: String) -> ! {
+ match cmd.as_str() {
+ "print" => Print::render_help(0),
+ "publish" => Publish::render_help(0),
+ c => {
+ eprintln!("Unknown command `{}`", c);
+ std::process::exit(2);
+ }
+ }
+}
+
+pub(crate) fn list_commands() {
+ eprintln!("Available commands:\n");
+ eprintln!(" - print: echo the selected crate set");
+ eprintln!(" - publish: release new crates on crates.io");
+}
+
+/// Differentiating operation enum
+pub enum Op {
+ /// Publish a new version, according to some rules
+ Publish(Publish),
+ /// Print the query selection
+ Print(Print),
+}
+
+impl Op {
+ /// Parse an arg line into an operation to execute
+ pub fn parse(line: Vec<String>) -> Self {
+ match parser::run(line) {
+ Some(op) => op,
+ None => std::process::exit(2),
+ }
+ }
+
+ pub(crate) fn execute(self, set: Vec<CrateId>, root: PathBuf, g: &mut DepGraph) {
+ match self {
+ Self::Publish(p) => publish::run(p, set, g),
+ Self::Print(p) => executor::print(p, set, root, g),
+ }
+ }
+}
+
+/// Ask the user to be sure
+pub(self) fn verify_user() {
+ eprintln!("------");
+ use std::io::{stdin, stdout, Write};
+ let mut s = String::new();
+ print!("Execute operations? [N|y]: ");
+ let _ = stdout().flush();
+ stdin().read_line(&mut s).expect("Failed to read term!");
+
+ match s.trim() {
+ "Y" | "y" => {}
+ _ => std::process::exit(0),
+ }
+}
+
+/// Selection of which type to print
+pub enum Print {
+ /// Default: just the package name
+ Name,
+ /// The path inside the repo
+ Path { abs: bool },
+ /// Both the name and path
+ Both { abs: bool },
+}
+
+impl Default for Print {
+ fn default() -> Self {
+ Self::Name
+ }
+}
+
+impl RenderHelp for Print {
+ fn render_help(code: i32) -> ! {
+ eprintln!("Print the selected set of crates");
+ eprintln!("Usage: cargo ws2 <...> print [OPTIONS]\n");
+ eprintln!("Available options:\n");
+ eprintln!(" - path: print the path of the crate, instead of the name");
+ eprintln!(" - both: print the both the path and the name");
+ eprintln!(" - abs: (If `path` or `both`) Use an absolute path, instead of relative");
+ std::process::exit(code)
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/ops/parser.rs b/development/tools/cargo-workspace2/src/ops/parser.rs
new file mode 100644
index 000000000000..6b7d39b71851
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/parser.rs
@@ -0,0 +1,112 @@
+use super::{Op, Print, Publish, PublishMod as Mod, PublishType::*};
+use semver::Version;
+
+/// Parse a argument line into an operation
+pub(super) fn run(mut line: Vec<String>) -> Option<Op> {
+ let op = line.remove(0);
+
+ match op.as_str() {
+ "print" => parse_print(line),
+ "publish" => parse_publish(line),
+ op => {
+ eprintln!("[ERROR]: Unrecognised operation `{}`", op);
+ None
+ }
+ }
+}
+
+fn parse_print(line: Vec<String>) -> Option<Op> {
+ let abs = line
+ .get(1)
+ .map(|abs| match abs.as_str() {
+ "abs" | "absolute" => true,
+ _ => false,
+ })
+ .unwrap_or(false);
+
+ match line.get(0).map(|s| s.as_str()) {
+ Some("path") => Some(Op::Print(Print::Path { abs })),
+ Some("both") => Some(Op::Print(Print::Both { abs })),
+ Some("name") | None => Some(Op::Print(Print::Name)),
+ Some(val) => {
+ eprintln!("[ERROR]: Unrecognised param: `{}`", val);
+ None
+ }
+ }
+}
+
+fn publish_error() -> Option<Op> {
+ eprintln!(
+ "[ERROR]: Missing or invalid operands!
+Usage: cargo ws2 <QUERY LANG> publish <level> [modifier] [devel]\n
+ Valid <level>: major, minor, patch, or '=<VERSION>' to set an override version.\n
+ Valid [modifier]: alpha, beta, rc\n
+ [devel]: after publishing, set crate version to <VERSION>-devel"
+ );
+ None
+}
+
+fn parse_publish(mut line: Vec<String>) -> Option<Op> {
+ line.reverse();
+ let tt = match line.pop() {
+ Some(tt) => tt,
+ None => {
+ return publish_error();
+ }
+ };
+
+ let _mod = line.pop();
+ let tt = match (tt.as_str(), _mod.as_ref().map(|s| s.as_str())) {
+ ("major", Some("alpha")) => Major(Mod::Alpha),
+ ("major", Some("beta")) => Major(Mod::Beta),
+ ("major", Some("rc")) => Major(Mod::Rc),
+ ("minor", Some("alpha")) => Minor(Mod::Alpha),
+ ("minor", Some("beta")) => Minor(Mod::Beta),
+ ("minor", Some("rc")) => Minor(Mod::Rc),
+ ("patch", Some("alpha")) => Patch(Mod::Alpha),
+ ("patch", Some("beta")) => Patch(Mod::Beta),
+ ("patch", Some("rc")) => Patch(Mod::Rc),
+ ("major", Some(_)) | ("major", None) => Major(Mod::None),
+ ("minor", Some(_)) | ("minor", None) => Minor(Mod::None),
+ ("patch", Some(_)) | ("patch", None) => Patch(Mod::None),
+ (v, _) if v.trim().starts_with("=") => {
+ let vers = v.replace("=", "").to_string();
+ if vers.as_str() == "" {
+ return publish_error();
+ } else {
+ Fixed(vers)
+ }
+ }
+ (_, _) => {
+ return publish_error();
+ }
+ };
+
+ let devel = match tt {
+ // Means _mod was not a mod
+ Major(Mod::None) | Minor(Mod::None) | Patch(Mod::None) => {
+ if let Some(devel) = _mod {
+ if devel == "devel" {
+ true
+ } else {
+ return publish_error();
+ }
+ } else {
+ false
+ }
+ }
+ _ => {
+ if let Some(devel) = line.pop() {
+ if devel == "devel" {
+ true
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+ };
+
+ Some(Op::Publish(Publish { tt, devel }))
+}
diff --git a/development/tools/cargo-workspace2/src/ops/publish/exec.rs b/development/tools/cargo-workspace2/src/ops/publish/exec.rs
new file mode 100644
index 000000000000..3f2d36863bcf
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/publish/exec.rs
@@ -0,0 +1,117 @@
+//! Publishing executor
+
+use super::{Publish, PublishMod as Mod, PublishType};
+use crate::{
+ models::{CrateId, DepGraph},
+ ops::verify_user,
+};
+use semver::Version;
+use textwrap;
+
+pub(in crate::ops) fn run(p: Publish, set: Vec<CrateId>, g: &mut DepGraph) {
+ let Publish { ref tt, devel } = p;
+
+ let vec = set.into_iter().fold(vec![], |mut vec, id| {
+ let c = g.mut_crate(id);
+ let c_name = c.name().clone();
+ let curr_ver = c.version();
+ let new_ver = bump(&curr_ver, tt);
+ c.publish(new_ver.clone());
+ eprintln!("Bumping `{}`: `{}` ==> `{}`", c.name(), &curr_ver, new_ver);
+ drop(c);
+
+ g.get_dependents(id).into_iter().for_each(|id| {
+ let dep = g.mut_crate(id);
+ dep.change_dependency(&c_name, &new_ver);
+ eprintln!("Changing dependency `{}`: {} = {{ version = `{}`, ... }} ==> {{ version = `{}`, ... }}",
+ dep.name(),
+ c_name,
+ &curr_ver,
+ new_ver);
+ vec.push(id);
+ });
+
+ vec.push(id);
+ vec
+ });
+
+ // If we make it past this point the user is sure
+ verify_user();
+
+ vec.into_iter().for_each(|(id)| {
+ let c = g.mut_crate(id);
+ c.sync();
+ });
+
+ eprintln!("{}", textwrap::fill("Publish complete. Check that everything is in order, \
+ then run `cargo publish` to upload to crates.io!", 80));
+}
+
+/// Bump a version number according to some rules
+fn bump(v: &String, tt: &PublishType) -> String {
+ let mut ver = Version::parse(&v).unwrap();
+ let ver = match tt {
+ PublishType::Fixed(ref ver) => Version::parse(ver).unwrap(),
+ PublishType::Major(_) => {
+ ver.increment_major();
+ ver
+ }
+ PublishType::Minor(_) => {
+ ver.increment_minor();
+ ver
+ }
+ PublishType::Patch(_) => {
+ ver.increment_patch();
+ ver
+ }
+ };
+
+ // If a mod applies, handle it...
+ if let Some(_mod) = tt._mod() {
+ if ver.is_prerelease() {
+ let v = ver.clone().to_string();
+ let num = v.split(".").last().unwrap();
+ let num: usize = num.parse().unwrap();
+ ver.clone()
+ .to_string()
+ .replace(&format!("{}", num), &format!("{}", num + 1))
+ .to_string()
+ } else {
+ format!(
+ "{}-{}",
+ ver.to_string(),
+ match _mod {
+ Mod::Alpha => "alpha.0",
+ Mod::Beta => "beta.0",
+ Mod::Rc => "rc.0",
+ _ => unreachable!(),
+ }
+ )
+ }
+ } else {
+ ver.to_string()
+ }
+}
+
+macro_rules! assert_bump {
+ ($input:expr, $level_mod:expr, $expect:expr) => {
+ assert_eq!(bump(&$input.to_owned(), $level_mod), $expect.to_string());
+ };
+}
+
+#[cfg(test)]
+use super::{PublishMod::*, PublishType::*};
+
+#[test]
+fn bump_major() {
+ assert_bump!("1.0.0", &Major(None), "2.0.0");
+ assert_bump!("1.5.4", &Major(None), "2.0.0");
+ assert_bump!("1.3.0", &Major(Alpha), "2.0.0-alpha.0");
+}
+
+#[test]
+fn bump_minor() {
+ assert_bump!("1.1.0", &Minor(None), "1.2.0");
+ assert_bump!("1.5.4", &Minor(Beta), "1.6.0-beta.0");
+ assert_bump!("1.5.4-beta.0", &Minor(Beta), "1.6.0-beta.0");
+}
diff --git a/development/tools/cargo-workspace2/src/ops/publish/mod.rs b/development/tools/cargo-workspace2/src/ops/publish/mod.rs
new file mode 100644
index 000000000000..6022496af273
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/ops/publish/mod.rs
@@ -0,0 +1,132 @@
+//! Publishing operation handling
+
+mod exec;
+pub(super) use exec::run;
+
+use super::RenderHelp;
+
+/// Publish a single crate to crates.io
+///
+/// This command does the following things
+///
+/// 0. Determine if the git tree has modifications, `cargo publish`
+/// will refuse to work otherwise.
+/// 1. Find the crate in question
+/// 2. Bump the version number according to the user input
+/// 3. Find all dependent crates in the workspace with a version bound
+/// 4. Update the version bound to the new version
+pub struct Publish {
+ /// The version type to publish
+ pub tt: PublishType,
+ /// Whether to set a new devel version after publish
+ pub devel: bool,
+}
+
+impl RenderHelp for Publish {
+ fn render_help(code: i32) -> ! {
+ eprintln!("Publish the selected set of crates");
+ eprintln!("Usage: cargo ws2 <...> publish [=]<level> [OPTIONS]");
+ eprintln!("");
+ eprintln!("When prepending `=` to the level, bump crates in sync\n");
+ eprintln!("Available levels:\n");
+ eprintln!(" - major: Bump major version (1.0.0 -> 2.0.0)");
+ eprintln!(" - minor: Bump minor version (0.5.0 -> 0.6.0)");
+ eprintln!(" - patch: Bump patch version (0.5.0 -> 0.5.1)");
+ eprintln!("");
+ eprintln!("Available options:\n");
+ eprintln!(" - alpha: Create a new alpha (append `-alpha.X`)");
+ eprintln!(" - beta: Create a new beta (append `-beta.X`)");
+ eprintln!(" - rc: Create a new rc (append `-rc.X`)");
+ eprintln!(" - devel: Tag next version as -devel");
+ std::process::exit(code)
+ }
+}
+
+/// The level to which to update
+///
+/// New versions are based on the previous version, and are always
+/// computed on the fly.
+///
+/// It's recommended you use the [`versions`](./versions/index.html)
+/// builder functions.
+pub enum PublishType {
+ /// A fixed version to set the set to
+ Fixed(String),
+ /// A major bump (e.g. 1.0 -> 2.0)
+ Major(PublishMod),
+ /// A minor bump (e.g. 0.1 -> 0.2)
+ Minor(PublishMod),
+ /// A patch bump (e.g. 1.5.0 -> 1.5.1)
+ Patch(PublishMod),
+}
+
+impl PublishType {
+ pub(crate) fn _mod(&self) -> Option<&PublishMod> {
+ match self {
+ Self::Major(ref m) => Some(m),
+ Self::Minor(ref m) => Some(m),
+ Self::Patch(ref m) => Some(m),
+ Self::Fixed(_) => None,
+ }
+ .and_then(|_mod| match _mod {
+ PublishMod::None => None,
+ other => Some(other),
+ })
+ }
+}
+
+/// Version string modifier
+///
+/// It's recommended you use the [`versions`](./versions/index.html)
+/// builder functions.
+pub enum PublishMod {
+ /// No version modification
+ None,
+ /// Append `-alpha$X` where `$X` is a continuously increasing number
+ Alpha,
+ /// Append `-beta$X` where `$X` is a continuously increasing number
+ Beta,
+ /// Append `-rc$X` where `$X` is a continuously increasing number
+ Rc,
+}
+
+/// Version bump management
+pub mod versions {
+ use super::{PublishMod, PublishType};
+
+ /// Create a major publish
+ pub fn major(_mod: PublishMod) -> PublishType {
+ PublishType::Major(_mod)
+ }
+
+ /// Create a minor publish
+ pub fn minor(_mod: PublishMod) -> PublishType {
+ PublishType::Minor(_mod)
+ }
+
+ /// Create a patch publish
+ pub fn patch(_mod: PublishMod) -> PublishType {
+ PublishType::Patch(_mod)
+ }
+
+ /// Create a none modifier
+ pub fn mod_none() -> PublishMod {
+ PublishMod::None
+ }
+
+ /// Create an alpha modifier
+ pub fn mod_alpha() -> PublishMod {
+ PublishMod::Alpha
+ }
+
+ /// Create a beta modifier
+ pub fn mod_beta() -> PublishMod {
+ PublishMod::Beta
+ }
+
+ /// Create an rc modifier
+ pub fn mod_rc() -> PublishMod {
+ PublishMod::Rc
+ }
+}
+
diff --git a/development/tools/cargo-workspace2/src/query/executor.rs b/development/tools/cargo-workspace2/src/query/executor.rs
new file mode 100644
index 000000000000..da67324de4c0
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/query/executor.rs
@@ -0,0 +1,83 @@
+use super::{Constraint, DepConstraint};
+use crate::models::{CrateId, DepGraph};
+use std::collections::BTreeSet;
+
+/// Execute a simple set query
+pub(crate) fn set(crates: &Vec<String>, g: &DepGraph) -> Vec<CrateId> {
+ crates
+ .iter()
+ .filter_map(|name| match g.find_crate(name) {
+ None => {
+ eprintln!("[ERROR]: Unable to find crate: `{}`", name);
+ None
+ }
+ some => some,
+ })
+ .collect()
+}
+
+/// Execute a search query on the dependency graph
+pub(crate) fn deps(mut deps: Vec<DepConstraint>, g: &DepGraph) -> Vec<CrateId> {
+ // Parse the anchor point (first crate)
+ let DepConstraint {
+ ref _crate,
+ ref constraint,
+ } = deps.remove(0);
+ let init_id = get_crate_error(_crate, g);
+
+ // Get it's dependents
+ let mut dependents: BTreeSet<_> = match constraint {
+ Constraint::Initial(true) => g.get_dependents(init_id).into_iter().collect(),
+ Constraint::Initial(false) => g
+ .get_all()
+ .iter()
+ .filter(|c| c.has_dependency(init_id))
+ .map(|c| c.id)
+ .collect(),
+ _ => {
+ eprintln!("Invalid initial constraint! Only `<` and `!<` are allowed!");
+ std::process::exit(2);
+ }
+ };
+
+ // Then loop over all other constraints and subtract crates from
+ // the dependents set until all constraints are met.
+ deps.reverse();
+ while let Some(dc) = deps.pop() {
+ let DepConstraint {
+ ref _crate,
+ ref constraint,
+ } = dc;
+
+ let id = get_crate_error(_crate, g);
+ let ldeps = g.get_dependents(id);
+ dependents = apply_constraint(dependents, ldeps, constraint);
+ }
+
+ dependents.into_iter().collect()
+}
+
+fn get_crate_error(_crate: &String, g: &DepGraph) -> CrateId {
+ match g.find_crate(&_crate.trim().to_string()) {
+ Some(id) => id,
+ None => {
+ eprintln!("[ERROR]: Crate `{}` not found in workspace!", _crate);
+ std::process::exit(2);
+ }
+ }
+}
+
+fn apply_constraint(
+ init: BTreeSet<CrateId>,
+ cmp: Vec<CrateId>,
+ cnd: &Constraint,
+) -> BTreeSet<CrateId> {
+ let cmp: BTreeSet<CrateId> = cmp.into_iter().collect();
+ let init = init.into_iter();
+ match cnd {
+ Constraint::And(true) => init.filter(|id| cmp.contains(id)).collect(),
+ Constraint::And(false) => init.filter(|id| !cmp.contains(id)).collect(),
+ Constraint::Or => init.chain(cmp.into_iter()).collect(),
+ _ => todo!(),
+ }
+}
diff --git a/development/tools/cargo-workspace2/src/query/mod.rs b/development/tools/cargo-workspace2/src/query/mod.rs
new file mode 100644
index 000000000000..a888f6571663
--- /dev/null
+++ b/development/tools/cargo-workspace2/src/query/mod.rs
@@ -0,0 +1,280 @@
+//! Parse the query language for the CLI and other tools
+//!
+//! The `cargo-ws2` query language (`ws2ql`) allows users to specify a
+//! set of inputs, and an operation to execute on it.
+//!
+//! ## Basic rules
+//!
+//! * Inside `[]` are sets (meaning items de-dup) that don't require
+//! reparations
+//! * IF `[]` contains a `/` anywhere _but_ the beginning AND end,
+//! query becomes a path glob
+//! * IF `[]` contains `/` at start and end, query becomes a regex
+//! * An operation is parsed in the order of the fields in it's struct
+//! (so publish order is `type mod devel`, etc)
+//! * Inside `{}` you can create dependency maps with arrows.
+//! * `{ foo < }` represents all crates that depend on `foo`
+//! * `{ foo < bar < }` represents all crates that depend on `foo` AND `bar`
+//! * `{ foo < bar |< }` represents all crates that depend on `foo` but NOT on `bar`
+//!
+//! ... etc.
+
+use crate::models::{CrateId, DepGraph};
+
+mod executor;
+
+/// A query on the dependency graph
+#[derive(Debug)]
+pub enum Query {
+ /// Simple set of names `[ a b c ]`
+ Set(Vec<String>),
+ /// Simple path glob query `./foo/*`
+ Path(String),
+ /// Regular expression done on paths in tree `/foo\$/`
+ Regex(String),
+ /// A dependency graph query (see documentation for rules)
+ DepGraph(Vec<DepConstraint>),
+}
+
+impl Query {
+ /// Parse an argument iterator (provided by `std::env::args`)
+ pub fn parse<'a>(line: impl Iterator<Item = String>) -> (Self, Vec<String>) {
+ let parser = QueryParser::new(line.skip(1).collect());
+ match parser.run() {
+ Some((q, rest)) => (q, rest),
+ None => {
+ eprintln!("Failed to parse query!");
+ std::process::exit(2);
+ }
+ }
+ }
+
+ /// Execute a query with a dependency graph
+ pub fn execute(self, g: &DepGraph) -> Vec<CrateId> {
+ match self {
+ Self::Set(ref crates) => executor::set(crates, g),
+ Self::DepGraph(deps) => executor::deps(deps, g),
+ _ => todo!(),
+ }
+ }
+}
+
+/// Express a dependency constraint
+#[derive(Debug)]
+pub struct DepConstraint {
+ pub _crate: String,
+ pub constraint: Constraint,
+}
+
+/// All constraints can be negated
+#[derive(Debug)]
+pub enum Constraint {
+ Initial(bool),
+ And(bool),
+ Or,
+}
+
+struct QueryParser {
+ line: Vec<String>,
+}
+
+impl QueryParser {
+ fn new(line: Vec<String>) -> Self {
+ Self { line }
+ }
+
+ /// Run the parser until it yields an error or finished query
+ fn run(mut self) -> Option<(Query, Vec<String>)> {
+ let line: Vec<String> =
+ std::mem::replace(&mut self.line, vec![])
+ .into_iter()
+ .fold(vec![], |mut vec, line| {
+ line.split(" ").for_each(|seg| {
+ vec.push(seg.into());
+ });
+ vec
+ });
+
+ // This is here to get around infinately sized enums
+ #[derive(Debug)]
+ enum Brace {
+ Block,
+ Curly,
+ }
+
+ // Track the state of the query braces
+ #[derive(Debug)]
+ enum BraceState {
+ Missing,
+ BlockOpen,
+ CurlyOpen,
+ Done(Brace),
+ }
+ use {Brace::*, BraceState::*};
+ let mut bs = Missing;
+ let mut buf = vec![];
+ let mut cbuf = String::new(); // Store a single crate name as a buffer
+ let mut skip = 1;
+
+ // Parse the crate set
+ for elem in &line {
+ match (&bs, elem.as_str()) {
+ // The segment starts with a [
+ (Missing, e) if e.starts_with("[") => {
+ bs = BlockOpen;
+ // Handle the case where we need to grab the crate from this segment
+ if let Some(_crate) = e.strip_prefix("[") {
+ if _crate != "" {
+ buf.push(_crate.to_string());
+ }
+ }
+ }
+ // The segment starts with a {
+ (Missing, e) if e.starts_with("{") => {
+ bs = CurlyOpen;
+
+ if let Some(_crate) = e.strip_prefix("{") {
+ if _crate != "" {
+ cbuf = _crate.into();
+ }
+ }
+ }
+ (BlockOpen, e) if e.ends_with("]") => {
+ if let Some(_crate) = e.strip_suffix("]") {
+ if _crate != "" {
+ buf.push(_crate.to_string());
+ }
+ }
+
+ bs = Done(Block);
+ break;
+ }
+ (BlockOpen, _crate) => buf.push(_crate.to_string()),
+ (CurlyOpen, e) if e.ends_with("}") && cbuf == "" => {
+ bs = Done(Curly);
+ break;
+ }
+ (CurlyOpen, e) if e.ends_with("}") && cbuf != "" => {
+ eprintln!("[ERROR]: Out of place `}}`, expected operand!");
+ std::process::exit(2);
+ }
+ (CurlyOpen, op) if cbuf != "" => {
+ buf.push(format!("{} $ {}", cbuf, op));
+ cbuf = "".into();
+ }
+ (CurlyOpen, _crate) => {
+ cbuf = _crate.into();
+ }
+ (_, _) => {}
+ }
+ skip += 1;
+ }
+
+ let rest = line.into_iter().skip(skip).collect();
+ match bs {
+ Done(Block) => Some((Query::Set(buf), rest)),
+ Done(Curly) => {
+ let mut init = true;
+
+ let c: Vec<_> = buf
+ .into_iter()
+ .map(|val| {
+ let mut s: Vec<_> = val.split("$").collect();
+ let _crate = s.remove(0).trim().to_string();
+ let c = s.remove(0).trim().to_string();
+
+ DepConstraint {
+ _crate,
+ constraint: match c.as_str() {
+ "<" if init => {
+ init = false;
+ Constraint::Initial(true)
+ }
+ "!<" if init => {
+ init = false;
+ Constraint::Initial(false)
+ }
+ "&<" => Constraint::And(true),
+ "!&<" => Constraint::And(false),
+ "|<" => Constraint::Or,
+ c => {
+ eprintln!("[ERROR]: Invalid constraint: `{}`", c);
+ std::process::exit(2);
+ }
+ },
+ }
+ })
+ .collect();
+
+ if c.len() < 1 {
+ eprintln!("[ERROR]: Provided an empty graph set: {{ }}. At least one dependency required!");
+ std::process::exit(2);
+ }
+
+ Some((Query::DepGraph(c), rest))
+ }
+ _ if rest.len() < 1 => crate::cli::render_help(2),
+ _line => {
+ eprintln!("[ERROR]: You reached some unimplemented code in cargo-ws2! \
+ This might be a bug, or it might be a missing feature. Contact me with your query, \
+ and we can see which one it is :)");
+ std::process::exit(2);
+ }
+ }
+ }
+}
+
+#[test]
+fn block_parser_spaced() {
+ let _ = QueryParser::new(
+ vec!["", "[", "foo", "bar", "baz", "]", "publish", "minor"]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .run();
+}
+
+#[test]
+fn block_parser_offset_front() {
+ let _ = QueryParser::new(
+ vec!["my-program", "[foo", "bar", "baz", "]", "publish", "minor"]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .run();
+}
+
+#[test]
+fn block_parser_offset_back() {
+ let _ = QueryParser::new(
+ vec!["my-program", "[", "foo", "bar", "baz]", "publish", "minor"]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .run();
+}
+
+#[test]
+fn block_parser_offset_both() {
+ let _ = QueryParser::new(
+ vec!["my-program", "[foo", "bar", "baz]", "publish", "minor"]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .run();
+}
+
+#[test]
+fn curly_parser_simple() {
+ let _ = QueryParser::new(
+ vec!["my-program", "{ foo < bar &< }", "print"]
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .run();
+}
diff --git a/development/tools/cargo-workspace2/tests/resources/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/Cargo.toml
new file mode 100644
index 000000000000..87cdac7cab85
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/Cargo.toml
@@ -0,0 +1,11 @@
+[workspace]
+members = [
+ "lockchain-core",
+ "lockchain-files",
+ "lockchain-crypto",
+
+ "lockchain-server",
+ "lockchain-client",
+
+ "lockchain-http",
+] \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-client/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-client/Cargo.toml
new file mode 100644
index 000000000000..097319a81d20
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-client/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "lockchain-client"
+description = "Client side component of the lockchain stack. Decrypts records and provides a Rust API to use them."
+version = "0.0.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+documentation = "https://docs.rs/lockchain-client"
+homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-client"
+readme = "README.md"
+license = "MIT/X11 OR Apache-2.0"
+
+[dependencies]
+lockchain-core = { version = "0.9.0" } #, path = "../lockchain-core" }
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-core/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-core/Cargo.toml
new file mode 100644
index 000000000000..4d6694c68a79
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-core/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "lockchain-core"
+description = "Provides common abstractions for the lockchain crate ecoystem"
+documentation = "https://docs.rs/lockchain-core"
+homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-core"
+readme = "README.md"
+license = "MIT/X11 OR Apache-2.0"
+version = "0.9.1-alpha.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+[dependencies]
+chrono = { version = "0.4", features = ["serde"] }
+serde_derive = "1.0"
+serde_json = "1.0"
+serde = "1.0"
+
+nix = "0.11"
+pam-auth = "0.5"
+
+base64 = "0.8"
+bcrypt = "0.2"
+rand = "0.4"
+blake2 = "0.7"
+
+keybob = "0.3"
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-crypto/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-crypto/Cargo.toml
new file mode 100644
index 000000000000..10a69aeb39d0
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-crypto/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "lockchain-crypto"
+description = "Small shim layer crate for cryptographic operations on lockchain data records"
+documentation = "https://docs.rs/lockchain-crypto"
+homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-crypto"
+readme = "README.md"
+license = "MIT/X11 OR Apache-2.0"
+version = "0.8.1-alpha.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+[dependencies]
+lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" }
+serde_derive = "1.0"
+serde = "1.0"
+
+miscreant = "0.4.0-beta1" \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-files/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-files/Cargo.toml
new file mode 100644
index 000000000000..e63455a40c6b
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-files/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "lockchain-files"
+description = "Filesystem storage backend for lockchain vaults"
+version = "0.9.1-alpha.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+documentation = "https://docs.rs/lockchain-files"
+homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-files"
+readme = "README.md"
+license = "MIT/X11 OR Apache-2.0"
+
+[dependencies]
+lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" }
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-http/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-http/Cargo.toml
new file mode 100644
index 000000000000..91500336aafe
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-http/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "lockchain-http"
+description = "Generic HTTP interface crate for the lockchain ecosystem. Can serve both encrypted and cleartext records"
+version = "0.4.1-alpha.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+documentation = "https://docs.rs/lockchain-http"
+homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-http"
+readme = "README.md"
+license = "MIT/X11 OR Apache-2.0"
+
+[dependencies]
+lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" }
+env_logger = "0.5"
+
+# Serialisation stack
+serde = "1.0"
+serde_derive = "1.0"
+
+# Web Stack
+futures = "0.1"
+actix = "0.5"
+actix-web = "0.6" \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/tests/resources/lockchain-server/Cargo.toml b/development/tools/cargo-workspace2/tests/resources/lockchain-server/Cargo.toml
new file mode 100644
index 000000000000..1fa3a329bb78
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/resources/lockchain-server/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "lockchain-server"
+version = "0.1.0"
+authors = ["Katharina Fey <kookie@spacekookie.de>"]
+
+[dependencies]
+lockchain-core = { path = "../lockchain-core" }
+lockchain-files = { path = "../lockchain-files" }
+lockchain-http = { path = "../lockchain-http" }
+
+clap = "2.0"
+insult = "2.0.3" \ No newline at end of file
diff --git a/development/tools/cargo-workspace2/tests/tests.rs b/development/tools/cargo-workspace2/tests/tests.rs
new file mode 100644
index 000000000000..b417c4806220
--- /dev/null
+++ b/development/tools/cargo-workspace2/tests/tests.rs
@@ -0,0 +1,89 @@
+// extern crate cargo_ws_release;
+
+// use cargo_ws_release::data_models::level::Level;
+// use cargo_ws_release::do_batch_release;
+// use cargo_ws_release::utilities::cargo_utils::bump_version;
+// use std::env::*;
+// use std::fs::File;
+// use std::path::Path;
+
+// #[test]
+// fn do_batch_release_returns_dependency_graph() {
+// let project_dir = current_dir().unwrap().to_str().unwrap().to_owned();
+// let test_resource_dir = format!("{}/tests/resources", project_dir);
+// assert!(set_current_dir(Path::new(&test_resource_dir)).is_ok());
+// let test_cargo_file_path = format!("{}/Cargo.toml", test_resource_dir);
+// let cargo_file = File::open(test_cargo_file_path).expect("Failed to open test Cargo.toml");
+
+// let dep_graph = do_batch_release(cargo_file, &Level::Major);
+
+// assert!(dep_graph.nodes.contains_key("lockchain-server"));
+// assert!(
+// dep_graph
+// .nodes
+// .get("lockchain-http")
+// .unwrap()
+// .deps
+// .first()
+// .unwrap()
+// .name
+// == "lockchain-core"
+// )
+// }
+
+// #[test]
+// fn parse_level_from_str() {
+// let levels = vec![
+// ("major", Some(Level::Major)),
+// ("minor", Some(Level::Minor)),
+// ("patch", Some(Level::Patch)),
+// ("rc", Some(Level::RC)),
+// ("beta", Some(Level::Beta)),
+// ("alpha", Some(Level::Alpha)),
+// ("invalid_level", None),
+// ];
+
+// levels
+// .iter()
+// .for_each(|(level_str, level)| assert_eq!(Level::from_str(level_str), *level));
+// }
+
+// #[test]
+// fn bump_version_with_level() {
+// let versions = vec![
+// ("0.9.9", Level::Major, "1.0.0"),
+// ("0.9.9", Level::Minor, "0.10.0"),
+// ("0.9.9", Level::Patch, "0.9.10"),
+// ("1.9.9", Level::Alpha, "1.9.10-alpha.1"),
+// ("1.9.9", Level::Beta, "1.9.10-beta.1"),
+// ("1.9.9", Level::RC, "1.9.10-rc.1"),
+// ("1.9.9-dev", Level::Alpha, "1.9.9-alpha.1"),
+// ("1.9.9-test", Level::Beta, "1.9.9-beta.1"),
+// ("1.9.9-stg", Level::RC, "1.9.9-rc.1"),
+// ("1.9.9-dev.1", Level::Alpha, "1.9.9-alpha.1"),
+// ("1.9.9-test.4", Level::Beta, "1.9.9-beta.1"),
+// ("1.9.9-stg.2", Level::RC, "1.9.9-rc.1"),
+// ("2.3.9-alpha.8", Level::Alpha, "2.3.9-alpha.9"),
+// ("2.3.9-beta.7", Level::Beta, "2.3.9-beta.8"),
+// ("2.3.9-rc.6", Level::RC, "2.3.9-rc.7"),
+// ("2.3.9-alpha.8.5.6.7.4", Level::Alpha, "2.3.9-alpha.9"),
+// ];
+
+// versions
+// .iter()
+// .for_each(|(given_version, given_level, expected_version)| {
+// assert_eq!(
+// bump_version(given_version, given_level).unwrap(),
+// expected_version.to_string()
+// )
+// });
+// }
+
+// #[test]
+// fn bump_version_invalid_version_error() {
+// let invalid_versions = vec!["a.b.c", "1.x.0", "1.", ".0.1", "1.1", "", " 1. 2. 3"];
+
+// invalid_versions
+// .iter()
+// .for_each(|v| assert!(bump_version(v, &Level::Major).is_err()));
+// }