diff --git a/development/tools/cargo-workspace2/.envrc b/development/tools/cargo-workspace2/.envrc new file mode 100644 index 00000000000..051d09d292a --- /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 00000000000..53eaa21960d --- /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 00000000000..c202dacb2f2 --- /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 00000000000..4c9b688766d --- /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 00000000000..3b59ec93405 --- /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 00000000000..8c8abb0144e --- /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 "] +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 00000000000..94a9ed024d3 --- /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. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/development/tools/cargo-workspace2/README.md b/development/tools/cargo-workspace2/README.md new file mode 100644 index 00000000000..c5ae6e7463b --- /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 [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 00000000000..8e150d082cf --- /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 00000000000..a255a650bb5 --- /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 00000000000..e055bd79d18 --- /dev/null +++ b/development/tools/cargo-workspace2/shell.nix @@ -0,0 +1,9 @@ +with import { + 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 00000000000..8de5bc60f6a --- /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) { + 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 00000000000..d40170ccd84 --- /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, + pub version: Option, + pub path: Option, +} + +impl Dependency { + pub(crate) fn parse(n: String, t: &InlineTable) -> Option { + 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 { + 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 00000000000..cf4b7e3d167 --- /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 for CargoError { + fn from(_: io::Error) -> Self { + Self::Io + } +} + +impl From 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 00000000000..4ba891e1583 --- /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 00000000000..deb54563d6b --- /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 = std::result::Result; + +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 00000000000..c3e67e2785f --- /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 { + let mut buf = String::new(); + File::open(p)?.read_to_string(&mut buf)?; + Ok(buf.parse::()?) +} + +/// For a root-config, get the paths of member crates +pub(crate) fn get_members(doc: &Document) -> Result> { + 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 { + let mut buf = String::new(); + File::open(p)?.read_to_string(&mut buf)?; + Ok(buf.parse::()?) +} + +/// 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 { + 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 00000000000..afea9e22417 --- /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, +} + +fn get_nth(idx: usize) -> Option { + 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 [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 00000000000..689c3601cd5 --- /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 00000000000..68d2baad2ba --- /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, +} + +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 { + 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 00000000000..b020e82e418 --- /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, +} + +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, +} + +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) -> Result { + 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 00000000000..867c463fb1e --- /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, + /// Map of crates and the members that depend on them + dependents: BTreeMap>, +} + +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 { + 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 { + 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 { + 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 00000000000..759b2703f9f --- /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 { + q.execute(&self.dgraph) + } + + /// Execute an operation on a set of crates this in workspace + pub fn execute(&mut self, op: Op, target: Vec) { + 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 00000000000..11984017d49 --- /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 00000000000..3dde73d954d --- /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 = std::result::Result; + +/// 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 00000000000..90a4f68303b --- /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, 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 00000000000..026aaa0a140 --- /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) -> Self { + match parser::run(line) { + Some(op) => op, + None => std::process::exit(2), + } + } + + pub(crate) fn execute(self, set: Vec, 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 00000000000..6b7d39b7185 --- /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) -> Option { + 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) -> Option { + 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 { + eprintln!( + "[ERROR]: Missing or invalid operands! +Usage: cargo ws2 publish [modifier] [devel]\n + Valid : major, minor, patch, or '=' to set an override version.\n + Valid [modifier]: alpha, beta, rc\n + [devel]: after publishing, set crate version to -devel" + ); + None +} + +fn parse_publish(mut line: Vec) -> Option { + 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 00000000000..3f2d36863bc --- /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, 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 00000000000..6022496af27 --- /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 [=] [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 00000000000..da67324de4c --- /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, g: &DepGraph) -> Vec { + 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, g: &DepGraph) -> Vec { + // 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, + cmp: Vec, + cnd: &Constraint, +) -> BTreeSet { + let cmp: BTreeSet = 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 00000000000..a888f657166 --- /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), + /// 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), +} + +impl Query { + /// Parse an argument iterator (provided by `std::env::args`) + pub fn parse<'a>(line: impl Iterator) -> (Self, Vec) { + 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 { + 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, +} + +impl QueryParser { + fn new(line: Vec) -> Self { + Self { line } + } + + /// Run the parser until it yields an error or finished query + fn run(mut self) -> Option<(Query, Vec)> { + let line: Vec = + 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 00000000000..87cdac7cab8 --- /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 00000000000..097319a81d2 --- /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 "] + +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 00000000000..4d6694c68a7 --- /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 "] + +[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 00000000000..10a69aeb39d --- /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 "] + +[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 00000000000..e63455a40c6 --- /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 "] + +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 00000000000..91500336aaf --- /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 "] + +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 00000000000..1fa3a329bb7 --- /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 "] + +[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 00000000000..b417c480622 --- /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())); +// }